드디여 마지막 장인 동기화까지 왔다.

 

비선점형 멀티태스킹을 수행하는 나빌로스에서 커널 API Kernel_yield를 구현해서 

각 태스크가 다음 태스크에게 컨텍스트를 넘겨주었는데 싱글 코어는 괜찬지만 멀티 코어에서는 문제가 된다.

 

 

 

운영체제에서의 동기화와 아토믹 오퍼레이션

동기화 : 한 작업을 아토믹 오퍼레이션(atomic operation ?)으로 만드는 작업

아토믹 오퍼레이션 : 해당 작업이 끝날때까지 컨텍스트 스위칭이 발생하지 않음

-> 멀티코어 환경에선 아토믹 오퍼레이션이 수행될때 컨텍스트 스위칭 발생 안함.

= 다른작업이 못끼어든다

= 다른 코어도 방해하지 못함

=> 원자가 더이상 쪼개질수없는 단위처럼, 더이상 쪼갤수 없는 동작이란 의미로 이에 해당되는 작업이 크리티컬 섹션임

 

동기화

- 작업, 태스크가 크리티컬 섹션(다른 작업, 코어가 끼어들지 못함)인 경우, 아토믹 오퍼레이션으로 만듬

- 구현 알고리즘 : 세마포어, 뮤텍스, 스핀락

 

 

세마포어

아래는 세마포어 알고리즘의 슈도코드, 60년대 초에 나온 오래된 알고리즘

Test() : 크리티컬 섹션에 진입 가능한지 시험함 = 세마포어를 잠글수 있는지 확인

Release() : 크리티컬 섹션을 나갈 때 호출. 세마포어를 풀어줌

 

=> 크리티컬 섹션에 들어가면 잠금. 

=> 잠긴 동안 컨텍스트 스위칭이나 다른 코어 작업 불가

=> 크리티컬 섹션에서 나오면 잠금 해제

Test(S)
{
	while S <= 0; //대기
    S--;
}

Release(S)
{
	S++;
}

 

세마포어 구현하기

kernel/synch.h

#ifndef KERNEL_SYNCH_H_
#define KERNEL_SYNCH_H_

#include <stdint.h>

void	Kernel_sem_init(int32_t max);
bool	Kernel_sem_test(void);
void	Kernel_sem_release(void);

#endif

 

kernel/synch.c

sem test는 못잠글때 ( S<=0) false 반환 

sem release는 세마포어 변수 증가

 

sem init은 세마포어 초기화 함수 max 파라미터로 세마포어 최대값 받음. 8 못넘김

max가 1이면 크리티컬 섹션에 컨텍스트 한개만 진입 가능(바이너리 세마포어)

2면 2개, 3은 3까지 최대가 8이므로 8개 컨텍스트 진입 가능

0보다 작으면 8개 진입 가능

 

#define DEF_SEM_MAX 8

#include "synch.h"

static int32_t sSemMax;
static int32_t sSem;

void Kernel_sem_init(int32_t max)
{
	sSemMax = (max <= 0) ? DEF_SEM_MAX : max;
	sSemMax = (max >= DEF_SEM_MAX) ? DEF_SEM_MAX : max;

	sSem = sSemMax;
}

bool Kernel_sem_test(void)
{
	if (sSem <= 0)
	{
		return false;
	}
	
	sSem--;

	return true;
}

void Kernel_sem_release(void)
{
	sSem++;

	if(sSem >= sSemMax)
	{
		sSem = sSemMax;
	}
}

 

세마포어용 커널 API 추가

kernel.h

#ifndef KERNEL_KERNEL_H_
#define KERNEL_KERNEL_H

#include "task.h"
#include "event.h"

void Kernel_start(void);
void Kernel_yield(void);

void Kernel_send_events(uint32_t event_list);
KernelEventFlag_t Kernel_wait_events(uint32_t waiting_list);

void Kernel_lock_sem(void);
void Kernel_unlock_sem(void);

#endif

kernel.c

Kernel lock sem은 무한루프로 대기하는걸 Kernel_yield로 다음 태스크에게 전환하는걸로 구현

->  크리티컬 섹션 잠금을 가진 태스크의 컨텍스트까지가서 넘어가서야 세마포어 잠금을 풀수있도록 하기위함

 

#include <stdint.h>
#include <stdbool.h>

#include "memio.h"

#include "Kernel.h"
#include "event.h"
#include "msg.h"

void Kernel_start(void)
{
	Kernel_task_start();
}

void Kernel_yield(void)
{
	Kernel_task_scheduler();
}

void Kernel_send_events(uint32_t event_list)
{
	for (uint32_t i = 0; i < 32 ;i++)
	{
		if ((event_list >> i) & 1)
		{
			KernelEventFlag_t sending_event = KernelEventFlag_Empty;
			sending_event = (KernelEventFlag_t)SET_BIT(sending_event, i);
			Kernel_event_flag_set(sending_event);
		}
	}
}

KernelEventFlag_t Kernel_wait_events(uint32_t waiting_list)
{
	for (uint32_t i = 0; i < 32; i++)
	{
		if ((waiting_list >> i) & 1)
		{
			KernelEventFlag_t waiting_event = KernelEventFlag_Empty;
			waiting_event = (KernelEventFlag_t)SET_BIT(waiting_event, i);

			if (Kernel_event_flag_check(waiting_event))
			{
				return waiting_event;
			}
		}
	}

	return KernelEventFlag_Empty;
}

bool Kernel_send_msg(KernelMsgQ_t Qname, void* data, uint32_t count)
{
	uint8_t* d = (uint8_t*)data;

	for (uint32_t i = 0; i < count ; i++)
	{
		if (false == Kernel_msgQ_enqueue(Qname, *d))
		{
			for (uint32_t j = 0; j < i; j++)
			{
				uint8_t rollback;
				Kernel_msgQ_dequeue(Qname, &rollback);
			}
			return false;
		}
		d++;
	}
	return true;
}

uint32_t Kernel_recv_msg(KernelMsgQ_t Qname, void* out_data, uint32_t count)
{
	uint8_t* d = (uint8_t*)out_data;

	for (uint32_t i = 0; i < count ; i++)
	{
		if (false == Kernel_msgQ_dequeue(Qname, d))
		{
			return i;
		}
		d++;
	}
	return count;
}

void Kernel_lock_sem(void)
{
	while(false == Kernel_sem_test())
	{
		Kernel_yield();
	}
}

void Kernel_unlock_sem(void)
{
	Kernel_sem_release();
}

 

 

리얼뷰 피비는 싱글코어 에뮬레이터이며, 비선점형 스케줄러라 직접 Kernel_yield 호출 필요 동작가능한 코드 구현하면

 

Uart.c x키 눌럿을떄 CmdOut 이벤트 발생하도록 수정시키고

#include <stdint.h>
#include "Uart.h"
#include "HalUart.h"
#include "HalInterrupt.h"

#include "Kernel.h"
#include "msg.h"

extern volatile PL011_t* Uart;

static void interrupt_handler(void);

void Hal_uart_init(void)
{
	//enable UART
	Uart->uartcr.bits.UARTEN=0;
	Uart->uartcr.bits.TXE=1;
	Uart->uartcr.bits.RXE=1;
	Uart->uartcr.bits.UARTEN=1;

	Uart->uartimsc.bits.RXIM=1;

	Hal_interrupt_enable(UART_INTERRUPT0);
	Hal_interrupt_register_handler(interrupt_handler, UART_INTERRUPT0);
}

void Hal_uart_put_char(uint8_t ch)
{
	while(Uart->uartfr.bits.TXFF);
	Uart->uartdr.all = (ch & 0xFF);
}

uint8_t Hal_uart_get_char(void)
{
	uint32_t data;

	while(Uart->uartfr.bits.RXFE);

	data = Uart->uartdr.all;

	//check for and error flag
	if (data & 0xFFFFFF00)
	{
		//clear the error
		Uart->uartrsr.all = 0xFF;
		return 0;
	}

	return (uint8_t)(data & 0xff);
}

static void interrupt_handler(void)
{
	uint8_t ch = Hal_uart_get_char();

	if (ch != 'x')
	{
		Hal_uart_put_char(ch);
		Kernel_send_msg(KernelMsgQ_Task0, &ch, 1);
		Kernel_send_events(KernelEventFlag_UartIn);
	}
	else
	{
		Kernel_send_events(KernelEventFlag_CmdOut);
	}
}

 

Test_critical_section()

크리티컬 섹션을 만들기위해 여러 코어가 같이 쓰는 공유 자원 필요

첫 인자  uint32_t p는 공유 자원 값을 바꾸기위한 값

다음은 호출한 태스크 번호

어느태스크가 어떤 값을 보냈는지 출력

공유 변수 값을 바꾼후 다음 테스크로 스캐줄링

 

#include <stdint.h>
#include <stdbool.h>

#include "HalInterrupt.h"
#include "HalUart.h"
#include "HalTimer.h"

#include "task.h"
#include "Kernel.h"
#include "msg.h"
#include "synch.h"

#include "stdio.h"
#include "stdlib.h"

static void Hw_init(void);
static void Kernel_init(void);

static void	Printf_test(void);
static void Timer_test(void);

static void Test_critical_section(uint32_t p, uint32_t taskId);

void User_task0(void);
void User_task1(void);
void User_task2(void);

static uint32_t shared_value;

void main(void)
{
	Hw_init();

	uint32_t i = 100;
	while(i--)
	{
		Hal_uart_put_char('N');
	}
	Hal_uart_put_char('\n');
	putstr("Hello World!\n");

	Printf_test();
	Timer_test();

	Kernel_init();
	
	while(true);
}

static void Hw_init(void)
{
	Hal_interrupt_init();
	Hal_uart_init();
	Hal_timer_init();
}

static void Printf_test(void)
{
	char* str = "printf pointer test";
	char* nullptr = 0;
	uint32_t i = 5;
	uint32_t* sysctrl0 = (uint32_t*)0x10001000;

	debug_printf("%s\b", "Hello printf");
	debug_printf("output string pointer: %s\n", str);
	debug_printf("%s is null pointer, %u number\n", nullptr, 10);
	debug_printf("%u=5\n", i);
	debug_printf("dec=%u hex=%x\n", 0xff, 0xff);
	debug_printf("print zero %u\n", 0);
	debug_printf("SYSCTRL0 %x\n", *sysctrl0);

}

static void Timer_test(void)
{
	for(uint32_t i = 0; i < 2 ; i++)
	{
		debug_printf("current count : %u\n", Hal_timer_get_1ms_counter());
		delay(1000);
	}
}

static void Kernel_init(void)
{
	uint32_t taskId;

	Kernel_task_init();
	Kernel_event_flag_init();

	taskId = Kernel_task_create(User_task0);
	if (NOT_ENOUGH_TASK_NUM == taskId)
	{
		putstr("Task0 creation fail\n");
	}

	taskId = Kernel_task_create(User_task1);
	if (NOT_ENOUGH_TASK_NUM == taskId)
	{
		putstr("Task1 creation fail\n");
	}

	taskId = Kernel_task_create(User_task2);
	if (NOT_ENOUGH_TASK_NUM == taskId)
	{
		putstr("Task2 creation fail\n");
	}

	Kernel_start();
}

void User_task0(void)
{
	uint32_t	local = 0;
	debug_printf("User Task #0 SP = 0x%x\n", &local);

	uint8_t		cmdBuf[16];
	uint32_t	cmdBufIdx = 0;
	uint8_t		uartch = 0;

	while(true)
	{
		KernelEventFlag_t handle_event = Kernel_wait_events(KernelEventFlag_UartIn | KernelEventFlag_CmdOut);
		switch(handle_event)
		{
			case KernelEventFlag_UartIn:
				Kernel_recv_msg(KernelMsgQ_Task0, &uartch, 1);
				if (uartch == '\r')
				{
					cmdBuf[cmdBufIdx] = '\0';

					Kernel_send_msg(KernelMsgQ_Task1, &cmdBufIdx, 1);
					Kernel_send_msg(KernelMsgQ_Task1, cmdBuf, cmdBufIdx);
					Kernel_send_events(KernelEventFlag_CmdIn);
					/*
					while(true)
					{
						Kernel_send_events(KernelEventFlag_CmdIn);
						if (false == Kernel_send_msg(KernelMsgQ_Task1, &cmdBufIdx, 1))
						{
							Kernel_yield();
						}
						else if (false == Kernel_send_msg(KernelMsgQ_Task1, cmdBuf, cmdBufIdx))
						{
							uint8_t rollback;
							Kernel_recv_msg(KernelMsgQ_Task1, &rollback, 1);
							Kernel_yield();
						}
					}
					*/
					cmdBufIdx = 0;
				}
				else
				{
					cmdBuf[cmdBufIdx] = uartch;
					cmdBufIdx++;
					cmdBufIdx %= 16;
				}
				break;
			case KernelEventFlag_CmdOut:
				Test_critical_section(5, 0);
				break;
		}
		Kernel_yield();
	}
}

void User_task1(void)
{
	uint32_t local = 0;

	debug_printf("User Task #1 SP = 0x%x\n", &local);

	uint8_t cmdlen = 0;
	uint8_t cmd[16] = {0};

	while(true)
	{
		KernelEventFlag_t handle_event = Kernel_wait_events(KernelEventFlag_CmdIn);
		switch(handle_event)
		{
			case KernelEventFlag_CmdIn:
				memclr(cmd, 16);
				Kernel_recv_msg(KernelMsgQ_Task1, &cmdlen, 1);
				Kernel_recv_msg(KernelMsgQ_Task1, cmd, cmdlen);
				debug_printf("\nRecv Cmd : %s\n", cmd);
				break;
		}
		Kernel_yield();
	}
}

void User_task2(void)
{
	uint32_t local = 0;

	debug_printf("User Task #2 SP = 0x%x\n", &local);

	while(true)
	{
		Kernel_yield();
	}
}

static void Test_critical_section(uint32_t p, uint32_t taskId)
{
	debug_printf("User Task #%u Send=%u\n", taskId, p);
	shared_value = p;
	Kernel_yield();
	delay(1000);
	debug_printf("User Task #%u Shared Value = %u\n", taskId, shared_value);
}

 

테스트 크리티컬 섹션 함수은

유아트 컨트롤러에서 x 입력시 cmdout 이벤트를 발생시켜

태스크 1이 받아 호출된다.

여기서 인자를 5, 0(태스크 번호)를 주어

공유된 변수 값을 덮어쓰고 5가 출력되어 정상적으로 동작했다.

 

 

 

하지만 동시에

태스크 2에서 공유 변수값을 3으로 바꾸도록 하면

void User_task2(void)
{
	uint32_t local = 0;

	debug_printf("User Task #2 SP = 0x%x\n", &local);

	while(true)
	{
		Test_critical_section(3, 2);
		Kernel_yield();
	}
}

 

 

태스크2의 차례때 테스트 크리티칼 섹션 함수가 계속 수행되어

3을 보내고 3을 받는 식이 되지만

중간에 태스크 0이 끼어들어

정작 태스크0이 보낸건 5지만, 커널 양보 호출후 공유된 변수 값을 출력할때는 3이 나오고만다.

이런식으로 같은 자원을 여러 태스크끼리 공유할때 문제가 발생

 

 

거의 다왔는데 메시지 부터 꼬인걸 도저히 해결이 안된다.

일단 세마포어와 뮤텍스가 뭐하는건지 봣으니 여기까지 그냥 마무리

 

 

 

이벤트의 한계

- 지난 글에서 UART 인터럽트 핸들러로 입력 이벤트를 보냈지만 어떤 문자인지는 보낼수가 없었음

 -> 데이터를 메시지로 보내면 된다.

-  비유 : 이벤트(뻴) - 메시지(사서함)

 

 

구현 목표

- 인터럽트 핸들러에서 이벤트 외에 데이터를 메시지로 보내고 이벤트 핸들러에서 메시지를 처리하자

 

 

메시지 설계하기

- 보통 메시지를 큐를 이용해서 구현 -> 메시지 큐

- 큐 구현 : 배열이나 동적 할당으로 링크드 리스트를 만들어 사용하나 임베디드 시스템에선 동적할당안쓰고 배열사용

- 배열로 큐 구현하려면? 데이터 들어갈 인덱스 변수와 꺼낼 데이터 인덱스 변수 필요(front, rear)

 

 

메시지 큐 만들기

 

kernel/msg.h

- 원형 큐 자료구조 KernelCirQ_t 에서 front는 꺼낼 데이터 인덱스, rear는 데이터 넣을 위치 인덱스, 배열 크기는 512

- 태스크가 3개여서 메시지 큐를 3개 사용. 각 메시지 큐를 지시하도록 KernelMsgQ_t 이넘 선언

 

#ifndef KERNEL_MSG_H_
#define KERNEL_MSG_H_

#define MSG_Q_SIZE_BYTE		512

typedef enum KernelMsgQ_t
{
	KernelMsgQ_Task0,
	KernelMsgQ_Task1,
	KErnelMsgQ_Task2,

	KernelMsgQ_Num
} KernelMsgQ_t;

typedef struct KernelCirQ_t
{
	uint32_t front;
	uint32_t rear;
	uint8_t Queue[MSG_Q_SIZE_BYTE];
} KernelCirQ_t;

void Kernel_msgQ_init(void);
bool Kernel_msgQ_is_empty(KernelMsqQ_t Qname);
bool Kernel_msgQ_is_full(KernelMsgQ_t Qname);
bool Kernel_msgQ_enqueue(KernelMsgQ_t Qname, uint8_t data);
bool Kernel_msgQ_dequeue(KernelMsgQ_t Qname, uint8_t* out_data);

#endif

 

kernel/msg.c

여기선 그렇게 어려운 내용은 없었다.

메시지 큐 3개 초기화 하는 함수나

비었는지, 꽉찻는지 확인하는 함수

유효성 검사후 데이터 입력하거나 빼는 함수

 

#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>

#include "msg.h"

KernelCirQ_t sMsgQ[KernelMsgQ_Num];

void Kernel_msgQ_init(void)
{
	for (uint32_t i = 0; i < KernelMsgQ_Num ; i++)
	{
		sMsgQ[i].front = 0;
		sMsgQ[i].rear = 0;
		memclr(sMsgQ[i].Queue, MSG_Q_SIZE_BYTE);
	}
}

bool Kernel_msgQ_is_empty(KernelMsgQ_t Qname)
{
	if (Qname >= KernelMsgQ_Num)
	{
		return false;
	}

	if (sMsgQ[Qname].front == sMsgq[Qname].rear)
	{
		return true;
	}

	return false;
}

bool Kernel_msgQ_is_full(KernelMsgQ_t Qname)
{
	if (Qname >= KernelMsgQ_Num)
	{
		return false;
	}

	if (((sMsgQ[Qname].rear + 1) % MSG_Q_SIZE_BYTE) == sMsgQ[Qname].front)
	{
		return true;
	}

	return false;
}

bool Kernel_msgQ_enqueue(KernelMsgQ_t Qname, uint8_t data)
{
	if (Qname >= KernelMsgQ_Num)
	{
		return false;
	}

	if (Kernel_msgQ_is_full(Qname))
	{
		return false;
	}

	sMsgQ[Qname].rear++;
	sMsgQ[Qname].rear %= MSG_Q_SIZE_BYTE;

	uint32_t idx = sMsgQ[Qname].rear;
	sMsgQ[Qname].Queue[idx] = data;

	return true;
}

bool Kernel_msgQ_dequeue(KernelMsgQ_t Qname, uint8_t* out_data)
{
	if (Qname >= KernelMsgQ_Num)
	{
		return false;
	}

	if (Kernel_msgQ_is_empty(Qname))
	{
		return false;
	}

	sMsgQ[Qname].front++;
	sMsgQ[Qname].front %= MSG_Q_SIZE_BYTE;

	uint32_t idx = sMsgQ[Qname].front;
	*out_data = sMsgQ[Qname].Queue[idx];

	return true;
}

 

메시지 송수신 API 구현하기

Kernel_send_msg

메시지큐와 데이터 포인터, 길이를 인자로 전달받아

길이만큼 루프돌리며 1바이트씩 enqueue 수행 

메시지큐가 꽉차 false 리턴받으면 deque시켜 비워준다.

 

 

kernel_recv_msg

메시지큐 이름과 저장할 위치 포인터, 길이 전달받아

메시지 디큐 함수로 1바이트씩 꺼내서 저장한다.

메시지큐가 비어있어 false를 반환받거나 정상적으로 수행되면 받은 길이 수를 반환한다.

 

#include <stdint.h>
#include <stdbool.h>

#include "memio.h"

#include "Kernel.h"
#include "event.h"

void Kernel_start(void)
{
	Kernel_task_start();
}

void Kernel_yield(void)
{
	Kernel_task_scheduler();
}

void Kernel_send_events(uint32_t event_list)
{
	for (uint32_t i = 0; i < 32 ;i++)
	{
		if ((event_list >> i) & 1)
		{
			KernelEventFlag_t sending_event = KernelEventFlag_Empty;
			sending_event = (KernelEventFlag_t)SET_BIT(sending_event, i);
			Kernel_event_flag_set(sending_event);
		}
	}
}

KernelEventFlag_t Kernel_wait_events(uint32_t waiting_list)
{
	for (uint32_t i = 0; i < 32; i++)
	{
		if ((waiting_list >> i) & 1)
		{
			KernelEventFlag_t waiting_event = KernelEventFlag_Empty;
			waiting_event = (KernelEventFlag_t)SET_BIT(waiting_event, i);

			if (Kernel_event_flag_check(waiting_event))
			{
				return waiting_event;
			}
		}
	}

	return KernelEventFlag_Empty;
}

bool Kernel_send_msg(Kernel_MsgQ_t Qname, void* data, uint32_t count)
{
	uint8_t* d = (uint8_t*)data;

	for (uint32_t i = 0; i < count ; i++)
	{
		if (false == Kernel_msgQ_enqueue(Qname, *d))
		{
			for (uint32_t j = 0; j < i; j++)
			{
				uint8_t rollback;
				Kernel_msgQ_dequeue(Qname, &rollback);
			}
			return false;
		}
		d++;
	}
	return true;
}

uint32_t Kernel_recv_msg(KernelMsgQ_t Qname, void* out_data, uint32_t count)
{
	uint8_t* d = (uint8_t*)out_data;

	for (uint32_t i = 0; i < count ; i++)
	{
		if (false == Kernel_msgQ_dequeue(Qname, d))
		{
			return i;
		}
		d++;
	}
	return count;
}

 

지금까지 한게

 

메시지 큐 설계, 기능 구현

메시지 송수신 커널 API 

 

이제 할건

 

유아트 입력 인터럽트 발생시 이벤트 송신과 함께 메시지 보내도록 구현하는데

잠깐 이벤트랑 보면

이벤트는 uint32_t sEventFlag를 이용해서 송수신을 했다면

메시지는 메시지 큐를 구현해서 송수신하는게 차이인거같다.

 

 

 

아무튼 유아트 입력 인터럽트시 메시지도 같이 보내도록 수정하면

* 주의 : 지난 이벤트 장에서 유아트 인이랑 CMDIN 이벤트를 같이 보내도록 했는데 그러면 유아트 입력떄마다 task1에서 리시빙 처리한다. 유아트 인 이벤트만 보내도록 뺴야한다.

#include <stdint.h>
#include "Uart.h"
#include "HalUart.h"
#include "HalInterrupt.h"

#include "Kernel.h"
#include "msg.h"

extern volatile PL011_t* Uart;

static void interrupt_handler(void);

void Hal_uart_init(void)
{
	//enable UART
	Uart->uartcr.bits.UARTEN=0;
	Uart->uartcr.bits.TXE=1;
	Uart->uartcr.bits.RXE=1;
	Uart->uartcr.bits.UARTEN=1;

	Uart->uartimsc.bits.RXIM=1;

	Hal_interrupt_enable(UART_INTERRUPT0);
	Hal_interrupt_register_handler(interrupt_handler, UART_INTERRUPT0);
}

void Hal_uart_put_char(uint8_t ch)
{
	while(Uart->uartfr.bits.TXFF);
	Uart->uartdr.all = (ch & 0xFF);
}

uint8_t Hal_uart_get_char(void)
{
	uint32_t data;

	while(Uart->uartfr.bits.RXFE);

	data = Uart->uartdr.all;

	//check for and error flag
	if (data & 0xFFFFFF00)
	{
		//clear the error
		Uart->uartrsr.all = 0xFF;
		return 0;
	}

	return (uint8_t)(data & 0xff);
}

static void interrupt_handler(void)
{
	uint8_t ch = Hal_uart_get_char();
	Hal_uart_put_char(ch);

	Kernel_send_events(KernelEventFlag_UartIn);
	Kernel_send_msg(KernelMsqQ_Task0, &ch, 1);
    if (ch == 'x')
    {
    	Kernel_send_events(KernelEventFlag_CmdOut);
    }
}

 

 

 

 

 

빌드하는 중간에

오타나 수정하다보면

memclr를 정의하지 않아서 링킹이 안된다.

 

lib/stdlib.c , .h에 그냥 구현해주자.

#ifndef LIB_STDLIB_H_
#define LIB_STDLIB_h_

void delay(uint32_t ms);
void memclr(void* dst, uint32_t count);

#endif

 

#include <stdint.h>
#include <stdbool.h>
#include "HalTimer.h"

void delay(uint32_t ms)
{
	uint32_t goal = Hal_timer_get_1ms_counter() + ms;

	while(goal != Hal_timer_get_1ms_counter());
}

void memclr(void* dst, uint32_t count)
{
	uint8_t *d = (uint8_t)dst;

	while(count--)
	{
		*d++ = 0;
	}
}

 

다 하고

 

 태스크0이  CmdIn 이벤트와함께 메시지를 보내는데

책에서 안전하게 보내는법이라 나와있는데로 구현하면 좀 이상하게 동작한다.

어쩔때는 잘동작하고 지금은 이상한데 뭐가 문젠지는 잘모르겠지만

 

원래대로 돌려서 쓰니 이젠 또 잘된다.

 

 

#include <stdint.h>
#include <stdbool.h>

#include "HalInterrupt.h"
#include "HalUart.h"
#include "HalTimer.h"

#include "task.h"
#include "Kernel.h"
#include "msg.h"

#include "stdio.h"
#include "stdlib.h"

static void Hw_init(void);
static void Kernel_init(void);

static void	Printf_test(void);
static void Timer_test(void);

void User_task0(void);
void User_task1(void);
void User_task2(void);

void main(void)
{
	Hw_init();

	uint32_t i = 100;
	while(i--)
	{
		Hal_uart_put_char('N');
	}
	Hal_uart_put_char('\n');
	putstr("Hello World!\n");

	Printf_test();
	Timer_test();

	Kernel_init();
	
	while(true);
}

static void Hw_init(void)
{
	Hal_interrupt_init();
	Hal_uart_init();
	Hal_timer_init();
}

static void Printf_test(void)
{
	char* str = "printf pointer test";
	char* nullptr = 0;
	uint32_t i = 5;
	uint32_t* sysctrl0 = (uint32_t*)0x10001000;

	debug_printf("%s\b", "Hello printf");
	debug_printf("output string pointer: %s\n", str);
	debug_printf("%s is null pointer, %u number\n", nullptr, 10);
	debug_printf("%u=5\n", i);
	debug_printf("dec=%u hex=%x\n", 0xff, 0xff);
	debug_printf("print zero %u\n", 0);
	debug_printf("SYSCTRL0 %x\n", *sysctrl0);

}

static void Timer_test(void)
{
	for(uint32_t i = 0; i < 2 ; i++)
	{
		debug_printf("current count : %u\n", Hal_timer_get_1ms_counter());
		delay(1000);
	}
}

static void Kernel_init(void)
{
	uint32_t taskId;

	Kernel_task_init();
	Kernel_event_flag_init();

	taskId = Kernel_task_create(User_task0);
	if (NOT_ENOUGH_TASK_NUM == taskId)
	{
		putstr("Task0 creation fail\n");
	}

	taskId = Kernel_task_create(User_task1);
	if (NOT_ENOUGH_TASK_NUM == taskId)
	{
		putstr("Task1 creation fail\n");
	}

	taskId = Kernel_task_create(User_task2);
	if (NOT_ENOUGH_TASK_NUM == taskId)
	{
		putstr("Task2 creation fail\n");
	}

	Kernel_start();
}

void User_task0(void)
{
	uint32_t	local = 0;
	debug_printf("User Task #0 SP = 0x%x\n", &local);

	uint8_t		cmdBuf[16];
	uint32_t	cmdBufIdx = 0;
	uint8_t		uartch = 0;

	while(true)
	{
		KernelEventFlag_t handle_event = Kernel_wait_events(KernelEventFlag_UartIn | KernelEventFlag_CmdOut);
		switch(handle_event)
		{
			case KernelEventFlag_UartIn:
				Kernel_recv_msg(KernelMsgQ_Task0, &uartch, 1);
				if (uartch == '\r')
				{
					cmdBuf[cmdBufIdx] = '\0';

					Kernel_send_msg(KernelMsgQ_Task1, &cmdBufIdx, 1);
					Kernel_send_msg(KernelMsgQ_Task1, cmdBuf, cmdBufIdx);
					Kernel_send_events(KernelEventFlag_CmdIn);
					/*
					while(true)
					{
						Kernel_send_events(KernelEventFlag_CmdIn);
						if (false == Kernel_send_msg(KernelMsgQ_Task1, &cmdBufIdx, 1))
						{
							Kernel_yield();
						}
						else if (false == Kernel_send_msg(KernelMsgQ_Task1, cmdBuf, cmdBufIdx))
						{
							uint8_t rollback;
							Kernel_recv_msg(KernelMsgQ_Task1, &rollback, 1);
							Kernel_yield();
						}
					}
					*/
					cmdBufIdx = 0;
				}
				else
				{
					cmdBuf[cmdBufIdx] = uartch;
					cmdBufIdx++;
					cmdBufIdx %= 16;
				}
				break;
			case KernelEventFlag_CmdOut:
				debug_printf("\nCmdOut Event by Task0\n");
				break;
		}
		Kernel_yield();
	}
}

void User_task1(void)
{
	uint32_t local = 0;

	debug_printf("User Task #1 SP = 0x%x\n", &local);

	uint8_t cmdlen = 0;
	uint8_t cmd[16] = {0};

	while(true)
	{
		KernelEventFlag_t handle_event = Kernel_wait_events(KernelEventFlag_CmdIn);
		switch(handle_event)
		{
			case KernelEventFlag_CmdIn:
				memclr(cmd, 16);
				Kernel_recv_msg(KernelMsgQ_Task1, &cmdlen, 1);
				Kernel_recv_msg(KernelMsgQ_Task1, cmd, cmdlen);
				debug_printf("\nRecv Cmd : %s\n", cmd);
				break;
		}
		Kernel_yield();
	}
}

void User_task2(void)
{
	uint32_t local = 0;

	debug_printf("User Task #2 SP = 0x%x\n", &local);

	while(true)
	{
		Kernel_yield();
	}
}

 

 

 

처음에는 원래 코드도 안돌아갔는데

어째저째 고치다보니 지금 잘 동작하니 

여기서 너무 시간 지체해서 넘어가야겠다.

 

 

맨 처음에 event.h를 만들때

예약된 자리가 여러개 있었는데,

원래 책에서는 처음 event.h 만들면서 UartIN 플래그 이름만 붙여있었지만

나는 이 헤더파일을 깃헙 완성본에서 복붙하다보니까 이미 사용자 정의 이벤트 이름들이 다 붙어있다.

두번째 이벤트가 CmdIn인데 이걸로 이벤트 처리를 하도록 메인함수를 수정하자.

 

 

boot/Main.c

태스크 0은 유아트 입력 이벤트 처리 시에 커널 API로 커맨드 입력 이벤트를 보내는걸 추가하고

태스크 1은 커맨드 입력 이벤트를 읽어들이면 태스크 1이 이벤트 핸들링했다고 출력해주는 내용이다.

#include <stdint.h>
#include <stdbool.h>

#include "HalInterrupt.h"
#include "HalUart.h"
#include "HalTimer.h"

#include "task.h"
#include "Kernel.h"

#include "stdio.h"
#include "stdlib.h"

static void Hw_init(void);
static void Kernel_init(void);

static void	Printf_test(void);
static void Timer_test(void);

void User_task0(void);
void User_task1(void);
void User_task2(void);

void main(void)
{
	Hw_init();

	uint32_t i = 100;
	while(i--)
	{
		Hal_uart_put_char('N');
	}
	Hal_uart_put_char('\n');
	putstr("Hello World!\n");

	Printf_test();
	Timer_test();

	Kernel_init();
	
	while(true);
}

static void Hw_init(void)
{
	Hal_interrupt_init();
	Hal_uart_init();
	Hal_timer_init();
}

static void Printf_test(void)
{
	char* str = "printf pointer test";
	char* nullptr = 0;
	uint32_t i = 5;
	uint32_t* sysctrl0 = (uint32_t*)0x10001000;

	debug_printf("%s\b", "Hello printf");
	debug_printf("output string pointer: %s\n", str);
	debug_printf("%s is null pointer, %u number\n", nullptr, 10);
	debug_printf("%u=5\n", i);
	debug_printf("dec=%u hex=%x\n", 0xff, 0xff);
	debug_printf("print zero %u\n", 0);
	debug_printf("SYSCTRL0 %x\n", *sysctrl0);

}

static void Timer_test(void)
{
	for(uint32_t i = 0; i < 5 ; i++)
	{
		debug_printf("current count : %u\n", Hal_timer_get_1ms_counter());
		delay(1000);
	}
}

static void Kernel_init(void)
{
	uint32_t taskId;

	Kernel_task_init();
	Kernel_event_flag_init();

	taskId = Kernel_task_create(User_task0);
	if (NOT_ENOUGH_TASK_NUM == taskId)
	{
		putstr("Task0 creation fail\n");
	}

	taskId = Kernel_task_create(User_task1);
	if (NOT_ENOUGH_TASK_NUM == taskId)
	{
		putstr("Task1 creation fail\n");
	}

	taskId = Kernel_task_create(User_task2);
	if (NOT_ENOUGH_TASK_NUM == taskId)
	{
		putstr("Task2 creation fail\n");
	}

	Kernel_start();
}

void User_task0(void)
{
	uint32_t local = 0;
	
	debug_printf("User Task #0 SP = 0x%x\n", &local);

	while(true)
	{
		KernelEventFlag_t handle_event = Kernel_wait_events(KernelEventFlag_UartIn);
		switch(handle_event)
		{
			case KernelEventFlag_UartIn:
				debug_printf("\nEvent handled\n");
				Kernel_send_events(KernelEventFlag_CmdIn);
				break;
		}
		Kernel_yield();
		delay(100);
	}
}

void User_task1(void)
{
	uint32_t local = 0;

	debug_printf("User Task #1 SP = 0x%x\n", &local);

	while(true)
	{
		KernelEventFlag_t handle_event = Kernel_wait_events(KernelEventFlag_CmdIn);
		switch(handle_event)
		{
			case KernelEventFlag_CmdIn:
				debug_printf("\nEvent handled by Task1\n");
				break;
		}
		Kernel_yield();
		delay(100);
	}
}

void User_task2(void)
{
	uint32_t local = 0;

	while(true)
	{
		debug_printf("User Task #2 SP = 0x%x\n", &local);
		Kernel_yield();
		delay(100);
	}
}

 

 

 

태스크들이 동작중에 유아트 입력 인터럽트가 들어오면

인터럽트 핸들러가 처리 후. 이벤트를 보낸다.

 

태스크 0번 동작 때 유아트 입력 이벤트를 확인하고 이벤트 핸들링 한 후에 커맨드 입력 이벤트를 보낸다.

 

태스크 1번 동작 차례가되면 커맨드 입력 이벤트가 보내졌지 확인해 처리하면서

아래의 결과가 나온다.

 

 

여러 이벤트 플래그 동시 보내고 처리

 

이전 글에서 kernel/Kernel.c에 Kernel_send 웨이팅 이벤트 함수는

uint32_t 이벤트리스트를 인자로 전달하는데,

그냥 이벤트 플래그가 아니라 uint32_t인 이유는 or연산으로 여러 이벤트를 같이 보내도록 하기 위해서였다고 했다.

 

UART 입력 인터럽트 발생 시 유아트 입력과 커맨드 입력 이벤트

+ 입력된 값이 'x'인 경우 CmdOut 이벤트를 보내도록 인터럽트 핸들러를 수정하면

 

hal/rvpb/Uart.c

#include <stdint.h>
#include "Uart.h"
#include "HalUart.h"
#include "HalInterrupt.h"

#include "Kernel.h"

extern volatile PL011_t* Uart;

static void interrupt_handler(void);

void Hal_uart_init(void)
{
	//enable UART
	Uart->uartcr.bits.UARTEN=0;
	Uart->uartcr.bits.TXE=1;
	Uart->uartcr.bits.RXE=1;
	Uart->uartcr.bits.UARTEN=1;

	Uart->uartimsc.bits.RXIM=1;

	Hal_interrupt_enable(UART_INTERRUPT0);
	Hal_interrupt_register_handler(interrupt_handler, UART_INTERRUPT0);
}

void Hal_uart_put_char(uint8_t ch)
{
	while(Uart->uartfr.bits.TXFF);
	Uart->uartdr.all = (ch & 0xFF);
}

uint8_t Hal_uart_get_char(void)
{
	uint32_t data;

	while(Uart->uartfr.bits.RXFE);

	data = Uart->uartdr.all;

	//check for and error flag
	if (data & 0xFFFFFF00)
	{
		//clear the error
		Uart->uartrsr.all = 0xFF;
		return 0;
	}

	return (uint8_t)(data & 0xff);
}

static void interrupt_handler(void)
{
	uint8_t ch = Hal_uart_get_char();
	Hal_uart_put_char(ch);

	Kernel_send_events(KernelEventFlag_UartIn|KernelEventFlag_CmdIn);

	if (ch == 'x')
	{
		Kernel_send_events(KernelEventFlag_CmdOut);
	}
}

 

 

 

태스크 0은 유아트 입력, cmd out 이벤트 처리

태스크 1은 그대로 커맨드 입력 이벤츠 처리 하도록 된 메인함수 코드

 

boot/Main.c

#include <stdint.h>
#include <stdbool.h>

#include "HalInterrupt.h"
#include "HalUart.h"
#include "HalTimer.h"

#include "task.h"
#include "Kernel.h"

#include "stdio.h"
#include "stdlib.h"

static void Hw_init(void);
static void Kernel_init(void);

static void	Printf_test(void);
static void Timer_test(void);

void User_task0(void);
void User_task1(void);
void User_task2(void);

void main(void)
{
	Hw_init();

	uint32_t i = 100;
	while(i--)
	{
		Hal_uart_put_char('N');
	}
	Hal_uart_put_char('\n');
	putstr("Hello World!\n");

	Printf_test();
	Timer_test();

	Kernel_init();
	
	while(true);
}

static void Hw_init(void)
{
	Hal_interrupt_init();
	Hal_uart_init();
	Hal_timer_init();
}

static void Printf_test(void)
{
	char* str = "printf pointer test";
	char* nullptr = 0;
	uint32_t i = 5;
	uint32_t* sysctrl0 = (uint32_t*)0x10001000;

	debug_printf("%s\b", "Hello printf");
	debug_printf("output string pointer: %s\n", str);
	debug_printf("%s is null pointer, %u number\n", nullptr, 10);
	debug_printf("%u=5\n", i);
	debug_printf("dec=%u hex=%x\n", 0xff, 0xff);
	debug_printf("print zero %u\n", 0);
	debug_printf("SYSCTRL0 %x\n", *sysctrl0);

}

static void Timer_test(void)
{
	for(uint32_t i = 0; i < 5 ; i++)
	{
		debug_printf("current count : %u\n", Hal_timer_get_1ms_counter());
		delay(1000);
	}
}

static void Kernel_init(void)
{
	uint32_t taskId;

	Kernel_task_init();
	Kernel_event_flag_init();

	taskId = Kernel_task_create(User_task0);
	if (NOT_ENOUGH_TASK_NUM == taskId)
	{
		putstr("Task0 creation fail\n");
	}

	taskId = Kernel_task_create(User_task1);
	if (NOT_ENOUGH_TASK_NUM == taskId)
	{
		putstr("Task1 creation fail\n");
	}

	taskId = Kernel_task_create(User_task2);
	if (NOT_ENOUGH_TASK_NUM == taskId)
	{
		putstr("Task2 creation fail\n");
	}

	Kernel_start();
}

void User_task0(void)
{
	uint32_t local = 0;
	
	debug_printf("User Task #0 SP = 0x%x\n", &local);

	while(true)
	{
		KernelEventFlag_t handle_event = Kernel_wait_events(KernelEventFlag_UartIn | KernelEventFlag_CmdOut);
		switch(handle_event)
		{
			case KernelEventFlag_UartIn:
				debug_printf("\nEvent handled by Task#0\n");
				Kernel_send_events(KernelEventFlag_CmdIn);
				break;
			case KernelEventFlag_CmdOut:
				debug_printf("\nCmdOut Event by Task0\n");
				break;
		}
		Kernel_yield();
		delay(100);
	}
}

void User_task1(void)
{
	uint32_t local = 0;

	debug_printf("User Task #1 SP = 0x%x\n", &local);

	while(true)
	{
		KernelEventFlag_t handle_event = Kernel_wait_events(KernelEventFlag_CmdIn);
		switch(handle_event)
		{
			case KernelEventFlag_CmdIn:
				debug_printf("\nEvent handled by Task1\n");
				break;
		}
		Kernel_yield();
		delay(100);
	}
}

void User_task2(void)
{
	uint32_t local = 0;

	while(true)
	{
		debug_printf("User Task #2 SP = 0x%x\n", &local);
		Kernel_yield();
		delay(100);
	}
}

 

 

키를 입력하면 기존 대로 동작하다가

x를 입력시에는

기존 대로

event handled task 0

event handled task 1 

출력 후

 

sEventFlag에 남아있던 CmdOut 플래그를 태스크 0번때 읽어들여

cmdout event by task0이 출력된다.

 

 

 

이벤트

- 인터럽트와 태스크간 연결 매체

- 버튼 클릭 -> 전기 신호 흐름 -> 컨트롤러가 인식 -> 소프트웨어 인터럽트 처리(IRQ, FIQ 발생) -> IRQ FIQ 핸들러가 인터럽트 확인 -> 해당 인터럽트 핸들러가 처리

- GUI의 경우 : 마우스 클릭(인터럽트 발생) -> 닷넷 or x윈도우가 이벤트로 만들어 핸들러 함수에 전달

 * RTOS의 경우 커널이 인터럽트를 이벤트로 바꿔 전달 이벤트 핸들러가 처리

 

 

이벤트 플래그

- 임의로 정한 갑하는 값

- 아래 이벤트 플래그를 16진수로 표현 시 0x8000 0002 -> 31번, 2번 이벤트 발생

 

이벤트 처리 구현

kernel/event.h

 

이벤트 플래그 선언. 유아트 입력, 커맨드 입력, 출력, 잠금해제 이벤트 플래그 존재하고

나머진 미정

#ifndef KERNEL_EVENT_H_
#define KERNEL_EVENT_H_

#include <stdbool.h>

typedef enum KernelEventFlag_t
{
    KernelEventFlag_UartIn      = 0x00000001,
    KernelEventFlag_CmdIn       = 0x00000002,
    KernelEventFlag_CmdOut      = 0x00000004,
    KernelEventFlag_Unlock      = 0x00000008,
    KernelEventFlag_Reserved04  = 0x00000010,
    KernelEventFlag_Reserved05  = 0x00000020,
    KernelEventFlag_Reserved06  = 0x00000040,
    KernelEventFlag_Reserved07  = 0x00000080,
    KernelEventFlag_Reserved08  = 0x00000100,
    KernelEventFlag_Reserved09  = 0x00000200,
    KernelEventFlag_Reserved10  = 0x00000400,
    KernelEventFlag_Reserved11  = 0x00000800,
    KernelEventFlag_Reserved12  = 0x00001000,
    KernelEventFlag_Reserved13  = 0x00002000,
    KernelEventFlag_Reserved14  = 0x00004000,
    KernelEventFlag_Reserved15  = 0x00008000,
    KernelEventFlag_Reserved16  = 0x00010000,
    KernelEventFlag_Reserved17  = 0x00020000,
    KernelEventFlag_Reserved18  = 0x00040000,
    KernelEventFlag_Reserved19  = 0x00080000,
    KernelEventFlag_Reserved20  = 0x00100000,
    KernelEventFlag_Reserved21  = 0x00200000,
    KernelEventFlag_Reserved22  = 0x00400000,
    KernelEventFlag_Reserved23  = 0x00800000,
    KernelEventFlag_Reserved24  = 0x01000000,
    KernelEventFlag_Reserved25  = 0x02000000,
    KernelEventFlag_Reserved26  = 0x04000000,
    KernelEventFlag_Reserved27  = 0x08000000,
    KernelEventFlag_Reserved28  = 0x10000000,
    KernelEventFlag_Reserved29  = 0x20000000,
    KernelEventFlag_Reserved30  = 0x40000000,
	KernelEventFlag_Reserved31  = 0x80000000,

    KernelEventFlag_Empty       = 0x00000000,
} KernelEventFlag_t;

void Kernel_event_flag_init(void);
void Kernel_event_flag_set(KernelEventFlag_t event);
void Kernel_event_flag_clear(KernelEventFlag_t event);
bool Kernel_event_flag_check(KernelEventFlag_t event);

#endif /* KERNEL_EVENT_H_ */

 

 

kernel/event.c

 

sEventflag  이벤트 플래그 32개 기록해서 보관 + 태스크에 전달하기 위한 변수

flag_clear 특정 플래그 반전 함수

flag_check 해당 이벤트가 1인지 체크. 1이면 호출한 함수에게 대기중임을 알려주고 플래그 클리어

#include <stdint.h>
#include <stdbool.h>

#include "stdio.h"
#include "event.h"

static uint32_t sEventFlag;

void Kernel_event_flag_init(void)
{
	sEventFlag = 0;
}

void Kernel_event_flag_set(KernelEventFlag_t event)
{
	sEventFlag |= (uint32_t)event;
}

void Kernel_event_flag_clear(KernelEventFlag_t event)
{
	sEventFlag &= ~((uint32_t) event);
}

bool Kernel_event_flag_check(KernelEventFlag_t event)
{
	if (sEventFlag & (uint32_t) event)
	{
		Kernel_event_flag_clear(event);
		return true;
	}
	return false;
}

 

 

커널 API로 이벤트 처리하기위해 kernel/Kernel.c, .h에 함수 추가

#ifndef KERNEL_KERNEL_H_
#define KERNEL_KERNEL_H

#include "task.h"
#include "event.h"

void Kernel_start(void);
void Kernel_yield(void);

void Kernel_send_events(uint32_t event_list);
KernelEventFlag_t Kernel_wait_events(uint32_t waiting_list);

#endif

 

 

kernel/Kernel.c

 

Kernel_send_events

이벤트를 전달하기 위한 함수로 uint32_t로 이벤트들의 목록을 받아

  * 1100 0000 0000 0000, 0010 0000 0000 0000과 같이 이벤트가 하나 혹은 여러개가 발생한 경우를 매개변수로 받는듯

비트 연산자로 해당 플래그가 올라왔는지 확인

플래그가 올라온 이벤트가 있으면 uint32_t 이벤트 목록을 KernelEventFlag_t인 sending_event에다 담아서(SET_BIT) 

Kernel_event_flag_set으로 sEventFlag에 등록

* sEventFlag에 이벤트리스트를 등록함으로서 sending을 하나 어떻게 받아서 처리하는지는 don't care

 

 

Kernel_wait_events 

위와 비슷하게 대기할 이벤트 목록을 받아서

올라온 이벤트 플래그가 있으면 내리고, 내린 대기 이벤트 플래그 반환

waiting list에 올라온 플래그가 없으면 엠프티 반환

 

uint32_t 로 전달하는건 event1 | event2 .. 와같여 어러 이벤트를 or 연산해서 한번에 보내기 위함.

#include <stdint.h>
#include <stdbool.h>

#include "memio.h"

#include "Kernel.h"
#include "event.h"

void Kernel_start(void)
{
	Kernel_task_start();
}

void Kernel_yield(void)
{
	Kernel_task_scheduler();
}

void Kernel_send_events(uint32_t event_list)
{
	for (uint32_t i = 0; i < 32 ;i++)
	{
		if ((event_list >> i) & 1)
		{
			KernelEventFlag_t sending_event = KernelEventFlag_Empty;
			sending_event = (KernelEventFlag_t)SET_BIT(sending_event, i);
			Kernel_event_flag_set(sending_event);
		}
	}
}

KernelEventFlag_t Kernel_wait_events(uint32_t waiting_list)
{
	for (uint32_t i = 0; i < 32; i++)
	{
		if ((waiting_list >> i) & 1)
		{
			KernelEventFlag_t waiting_event = KernelEventFlag_Empty;
			waiting_event = (KernelEventFlag_t)SET_BIT(waiting_event, i);

			if (Kernel_event_flag_check(waiting_event))
			{
				return waiting_event;
			}
		}
	}

	return KernelEventFlag_Empty;
}

 

 

 

인터럽트로 이벤트 발생시키기

QEMU에는 인터럽트가 별로 없으며, 타이머는 자주 발생해 Uart 만 사용

 

 

Uart 입력 인터럽트 발생 시 유아트 입력 이벤트 발생 시키기

interrupt_handler에서 Kernel_send_events에다가 enum인 UartIn을 전달하여 인터럽트 처리 후 커널에 이벤트 전달

#include <stdint.h>
#include "Uart.h"
#include "HalUart.h"
#include "HalInterrupt.h"

#include "Kernel.h"

extern volatile PL011_t* Uart;

static void interrupt_handler(void);

void Hal_uart_init(void)
{
	//enable UART
	Uart->uartcr.bits.UARTEN=0;
	Uart->uartcr.bits.TXE=1;
	Uart->uartcr.bits.RXE=1;
	Uart->uartcr.bits.UARTEN=1;

	Uart->uartimsc.bits.RXIM=1;

	Hal_interrupt_enable(UART_INTERRUPT0);
	Hal_interrupt_register_handler(interrupt_handler, UART_INTERRUPT0);
}

void Hal_uart_put_char(uint8_t ch)
{
	while(Uart->uartfr.bits.TXFF);
	Uart->uartdr.all = (ch & 0xFF);
}

uint8_t Hal_uart_get_char(void)
{
	uint32_t data;

	while(Uart->uartfr.bits.RXFE);

	data = Uart->uartdr.all;

	//check for and error flag
	if (data & 0xFFFFFF00)
	{
		//clear the error
		Uart->uartrsr.all = 0xFF;
		return 0;
	}

	return (uint8_t)(data & 0xff);
}

static void interrupt_handler(void)
{
	uint8_t ch = Hal_uart_get_char();
	Hal_uart_put_char(ch);

	Kernel_send_events(KernelEventFlag_UartIn);
}

 

 

 

boot/Main.c에

이벤트 플래그 초기화와

User Task0에 유아트 입력 이벤트 처리 코드 구현

 

Kernel_wait_event(KernelEventFlag_UartIn) 으로

태스크 0 동작때마다 유아트 입력이 들어왔는지 확인. 확인 후

sEventFlag에서 유아트인 플래그는 off되고 handle_event에 일단 유아트 이벤트 플래그 등록

스위칭을 통해 처리할 이벤트 핸들 이벤트가 유아트인 이면 이벤트 처리됨 문자열 출력

 

#include <stdint.h>
#include <stdbool.h>

#include "HalInterrupt.h"
#include "HalUart.h"
#include "HalTimer.h"

#include "task.h"
#include "Kernel.h"

#include "stdio.h"
#include "stdlib.h"

static void Hw_init(void);
static void Kernel_init(void);

static void	Printf_test(void);
static void Timer_test(void);

void User_task0(void);
void User_task1(void);
void User_task2(void);

void main(void)
{
	Hw_init();

	uint32_t i = 100;
	while(i--)
	{
		Hal_uart_put_char('N');
	}
	Hal_uart_put_char('\n');
	putstr("Hello World!\n");

	Printf_test();
	Timer_test();

	Kernel_init();
	
	while(true);
}

static void Hw_init(void)
{
	Hal_interrupt_init();
	Hal_uart_init();
	Hal_timer_init();
}

static void Printf_test(void)
{
	char* str = "printf pointer test";
	char* nullptr = 0;
	uint32_t i = 5;
	uint32_t* sysctrl0 = (uint32_t*)0x10001000;

	debug_printf("%s\b", "Hello printf");
	debug_printf("output string pointer: %s\n", str);
	debug_printf("%s is null pointer, %u number\n", nullptr, 10);
	debug_printf("%u=5\n", i);
	debug_printf("dec=%u hex=%x\n", 0xff, 0xff);
	debug_printf("print zero %u\n", 0);
	debug_printf("SYSCTRL0 %x\n", *sysctrl0);

}

static void Timer_test(void)
{
	for(uint32_t i = 0; i < 5 ; i++)
	{
		debug_printf("current count : %u\n", Hal_timer_get_1ms_counter());
		delay(1000);
	}
}

static void Kernel_init(void)
{
	uint32_t taskId;

	Kernel_task_init();
	Kernel_event_flag_init();

	taskId = Kernel_task_create(User_task0);
	if (NOT_ENOUGH_TASK_NUM == taskId)
	{
		putstr("Task0 creation fail\n");
	}

	taskId = Kernel_task_create(User_task1);
	if (NOT_ENOUGH_TASK_NUM == taskId)
	{
		putstr("Task1 creation fail\n");
	}

	taskId = Kernel_task_create(User_task2);
	if (NOT_ENOUGH_TASK_NUM == taskId)
	{
		putstr("Task2 creation fail\n");
	}

	Kernel_start();
}

void User_task0(void)
{
	uint32_t local = 0;
	
	debug_printf("User Task #0 SP = 0x%x\n", &local);

	while(true)
	{
		KernelEventFlag_t handle_event = Kernel_wait_events(KernelEventFlag_UartIn);
		switch(handle_event)
		{
			case KernelEventFlag_UartIn:
				debug_printf("\nEvent handled\n");
				break;
		}
		Kernel_yield();
	}
}

void User_task1(void)
{
	uint32_t local = 0;

	while(true)
	{
		debug_printf("User Task #1 SP = 0x%x\n", &local);
		Kernel_yield();
	}
}

void User_task2(void)
{
	uint32_t local = 0;

	while(true)
	{
		debug_printf("User Task #2 SP = 0x%x\n", &local);
		Kernel_yield();
	}
}

 

딜레이를 안줬더니 너무 빨리 지나가서

위 코드의 각 태스크에 delay(100) 추가해서 돌리니

잘 나온다. User task #0은 루프 밖이라 한번 출력하고 마니 안나오는게 당연하고

 

 

 

 

스케줄링 = 스케줄러 + 컨텍스트 스위칭

라운드 로빈 스케줄러와 컨텍스트 스위칭 구현했으니 스케줄링 시점 결정이 필요

 

시분할 시스템과 선점형 비선점형 멀티테스킹

- 시분할 시스템 : 정기적인 타이머 인터럽트로 태스크를 일정 시간만 동작하고 다음으로 바꾸도록 스케줄링하는 운영체제

- 선점형 멀티 태스킹 : 태스크가 스케줄링 요청안해도 커널이 강제로 스케줄링

 -> 시분할 시스템은 선점형 멀티태스킹에 가까움.

- 비선점형 멀티태스킹 : 태스크가 스케줄링 요청안하면 커널이 스케줄링 안함

- 선점형/ 비선점형 결정 : 임베디드 시스템 요구 사항에 달림.

* 나빌로스는 비선점형 스케줄링 = 태스크가 커널에 스케줄링 요청 필요

  = >현재 태스크가 다음 태스크에게 자원을 양보(yield) 하도록 구현하자!

 

 

yield(양보) 구현하기

- 태스크보다는 커널 영역 -> kernel/Kernel.c, kernel/Kernel.h 구현

Kernel_yield 호출 즉시 kernel_task_scheduler(task.c) 함수로 스케줄러에서 다음에 동작할 태스크 선정

-> 호출한 태스크 컨텍스트 백업

-> 스케줄러가 선정한 태스크 스택 포인터 복구

-> 태스크 스택 포인터를 이용해 컨택스트 복구

-> Kernel_yield()가 마치면서 다음 태스크 동작

kernel/task.c

 

kernel/Kernel.h

#ifndef KERNEL_KERNEL_H_
#define KERNEL_KERNEL_H

#include "task.h"

void Kernel_yield(void);

#endif

kernel/Kernel.c 

#include <stdint.h>
#include <stdbool.h>

#include "Kernel.h"

void Kernel_yield(void)
{
	Kernel_task_scheduler();
}

 

 

이전 코드는 커널 시작 시 동작 태스크가 없어서 동작하지 않음.

 

컨텍스트 스위칭 과정 정리

- 호출 과정 : Restore_context<-Save_context <- kernel_task_context_switching <- kernel_task_scheduler <- kernel_yield

- Saved_context 함수에서 1. 현재 태스크 컨택스트를 스택에 백업  2. TCB에 SP 백업

- Restore_context 함수에서 1. 다음 TCB에서 SP 가져옴 2. SP에서 컨택스트 읽음

<-> 커널 시작시에는 동작 태스크가 없어 저장도 못하고, 복원할거도 없다!

=> 해결책 : 첫 스케줄링 시 컨텍스트 백업(Saved_context) 없이, 0번 TCB을 복구하자(=TCB 정적 전역변수 초기값 사용)

 

 

 

kernel/task.c

추가 사항

1. static uint32_t sCurrent_tcb_index;

2. kernel_task_int() - sCurrent_tcb_index = 0;

3. Kernel_Task_start() 함수 

 

Kernel_task_start 커널 시작 시 한번만 호출

- 0번 tcb 주소 가져와 - restore_context

-> 기존 태스크 컨택스트는 사라지고 다음 태스크 컨택스트를 가져옴.

#include <stdint.h>
#include <stdbool.h>

#include "ARMv7AR.h"
#include "task.h"

//task
static KernelTcb_t	sTask_list[MAX_TASK_NUM];
static uint32_t		sAllocated_tcb_index;
static uint32_t		sCurrent_tcb_index;

//scheduling
static uint32_t		sCurrent_tcb_index;
static KernelTcb_t*	Scheduler_round_robin_algorithm(void);

//context switching
static KernelTcb_t*	sCurrent_tcb;
static KernelTcb_t*	sNext_tcb;

void Kernel_task_init(void)
{
	sAllocated_tcb_index = 0;
	sCurrent_tcb_index = 0;

	for(uint32_t i = 0; i < MAX_TASK_NUM ; i++)
	{
		sTask_list[i].stack_base = (uint8_t*)(TASK_STACK_START + (i * USR_TASK_STACK_SIZE));
		sTask_list[i].sp = (uint32_t)sTask_list[i].stack_base + USR_TASK_STACK_SIZE - 4;

		sTask_list[i].sp -= sizeof(KernelTaskContext_t);
		KernelTaskContext_t* ctx = (KernelTaskContext_t*)sTask_list[i].sp;
		ctx->pc = 0;
		ctx->spsr = ARM_MODE_BIT_SYS;
	}
}

uint32_t Kernel_task_create(KernelTaskFunc_t startFunc)
{
	KernelTcb_t* new_tcb = &sTask_list[sAllocated_tcb_index++];

	if(sAllocated_tcb_index > MAX_TASK_NUM)
	{
		return NOT_ENOUGH_TASK_NUM;
	}

	KernelTaskContext_t* ctx = (KernelTaskContext_t*)new_tcb->sp;
	ctx -> pc = (uint32_t)startFunc;

	return (sAllocated_tcb_index - 1);
}

static KernelTcb_t*	Scheduler_round_robin_algorithm(void)
{
	sCurrent_tcb_index++;
	sCurrent_tcb_index %= sAllocated_tcb_index;

	return &sTask_list[sCurrent_tcb_index];
}

void Kernel_task_scheduler(void)
{
	sCurrent_tcb = &sTask_list[sCurrent_tcb_index];
	sNext_tcb = Scheduler_round_robin_algorithm();

	Kernel_task_context_switching();
}

__attribute__ ((naked)) void Kernel_task_context_switching(void)
{
	__asm__ ("B Save_context");
	__asm__ ("B Restore_context");
}

static __attribute__ ((naked)) void Saved_context(void)
{
	//save current task context into the current task stack
	__asm__ ("PUSH {lr}");
	__asm__ ("PUSH {r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12}");
	__asm__ ("MRS r0, cpsr");
	__asm__ ("PUSH {r0}");
	//save current task stack pointer into the current TCB
	__asm__ ("LDR r0, =sCurrent_tcb");
	__asm__ ("LDR r0, [r0]");
	__asm__ ("STMIA r0!, {sp}");
}

static __attribute__ ((naked)) void Restore_context(void)
{
	//restore next task stack pointer from the next TCB
	__asm__ ("LDR	r0, =sNext_tcb");
    __asm__ ("LDR	r0, [r0]");
    __asm__ ("LDMIA	r0!, {sp}");
    // restore next task context from the next task stack
    __asm__ ("POP {r0}");
    __asm__ ("MSR cpsr, r0");
    __asm__ ("POP {r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12}");
    __asm__ ("POP {pc}");
}

void Kernel_task_start(void)
{
	sNext_tcb = &sTask_list[sCurrent_tcb_index];
	Restore_context();
}

 

 

메인 함수에서 커널 관련 초기화 함수를 모아서 한번에 쓸수 있도록

task.c/.h의 Kernel_task_start() 함수를

Kernel.c/.hdp 커널 API인  Kernel_start() 구현해서 호출

 

kernel/Kernel.h

#ifndef KERNEL_KERNEL_H_
#define KERNEL_KERNEL_H

#include "task.h"

void Kernel_start(void);
void Kernel_yield(void);

#endif

kernel/Kernel.c

#include <stdint.h>
#include <stdbool.h>

#include "Kernel.h"

void Kernel_start(void)
{
	Kernel_task_start();
}

void Kernel_yield(void)
{
	Kernel_task_scheduler();
}

 

Main 함수 수정 : 첫 태스크 컨택스트를 복원해서 실행 추가

 

Kernel_init() 함수 맨 끝에 Kernel_start() 추가

Kernel_init() 정리

1. Kernel_task_int()으로 64개의 태스크 스택 배이스, SP 등록, 컨텍스트 영역 추가

2. Kernel_task_create를 세번 호출

- Kernel_task_create 동작

    1. 호출할 함수 매개변수로 받음

    2. 현재 tcb가져옴 + sAllocated_tcb_index를 1 추가(다음 tcb로 넘어가도록)

    3. tcb의 스택포인터를 가져온다 = 현재 스택 포인태 위에는 컨텍스트가 존재

    4. 컨택스트의 pc에 인자로 전달받은 함수 저장 = 이 함수 실행

    5. 현재 tcb 인덱스 반환

3. Kernel_start() 호출

    1. sCurrent_tcb_index는 0번이므로 맨 처음 만든 tcb 주소 가져옴

    2. 가져온 tcb의 컨택스트를 로드

    3. tcb로 로드한 컨택스트에 pc에 등록된 함수(=태스크 생성시 등록한 함수) 호출

 

주의 : 아직 각 태스크 등록 함수는 Kernel_yield 호출 하지 않았다! = 다음 태스크에게 양보안한다

 

 

boot/Main.c

#include <stdint.h>
#include <stdbool.h>

#include "HalInterrupt.h"
#include "HalUart.h"
#include "HalTimer.h"

#include "task.h"
#include "Kernel.h"

#include "stdio.h"
#include "stdlib.h"

static void Hw_init(void);
static void Kernel_init(void);

static void	Printf_test(void);
static void Timer_test(void);

void User_task0(void);
void User_task1(void);
void User_task2(void);

void main(void)
{
	Hw_init();

	uint32_t i = 100;
	while(i--)
	{
		Hal_uart_put_char('N');
	}
	Hal_uart_put_char('\n');
	putstr("Hello World!\n");

	Printf_test();
	Timer_test();
}

static void Hw_init(void)
{
	Hal_interrupt_init();
	Hal_uart_init();
	Hal_timer_init();
}

static void Printf_test(void)
{
	char* str = "printf pointer test";
	char* nullptr = 0;
	uint32_t i = 5;
	uint32_t* sysctrl0 = (uint32_t*)0x10001000;

	debug_printf("%s\b", "Hello printf");
	debug_printf("output string pointer: %s\n", str);
	debug_printf("%s is null pointer, %u number\n", nullptr, 10);
	debug_printf("%u=5\n", i);
	debug_printf("dec=%u hex=%x\n", 0xff, 0xff);
	debug_printf("print zero %u\n", 0);
	debug_printf("SYSCTRL0 %x\n", *sysctrl0);

}

static void Timer_test(void)
{
	while(true)
	{
		debug_printf("current count : %u\n", Hal_timer_get_1ms_counter());
		delay(1000);
	}
}

static void Kernel_init(void)
{
	uint32_t taskId;

	Kernel_task_init();

	taskId = Kernel_task_create(User_task0);
	if (NOT_ENOUGH_TASK_NUM == taskId)
	{
		putstr("Task0 creation fail\n");
	}

	taskId = Kernel_task_create(User_task1);
	if (NOT_ENOUGH_TASK_NUM == taskId)
	{
		putStr("Task1 creation fail\n");
	}

	taskId = Kernel_task_create(User_task2);
	if (NOT_ENOUGH_TASK_NUM == taskId)
	{
		putStr("Task2 creation fail\n");
	}

	Kernel_start();
}

void User_task0(void)
{
	debug_printf("User Task#0\n");

	while(true);
}

void User_task1(void)
{
	debug_printf("User Task#1\n");

	while(true);
}

void User_task2(void)
{
	debug_printf("User Task#2\n");

	while(true);
}

 

 

스택의 스택 포인터를 가져와서 볼수 있도록

지역변수 선언

해당 지역변수의 주소= 스택포인터를 확인할수 있도록 debug_printf 와

다음 태스크에게 넘겨줄수 있도록 kernel_yield() 추가.

 

#include <stdint.h>
#include <stdbool.h>

#include "HalInterrupt.h"
#include "HalUart.h"
#include "HalTimer.h"

#include "task.h"
#include "Kernel.h"

#include "stdio.h"
#include "stdlib.h"

static void Hw_init(void);
static void Kernel_init(void);

static void	Printf_test(void);
static void Timer_test(void);

void User_task0(void);
void User_task1(void);
void User_task2(void);

void main(void)
{
	Hw_init();

	uint32_t i = 100;
	while(i--)
	{
		Hal_uart_put_char('N');
	}
	Hal_uart_put_char('\n');
	putstr("Hello World!\n");

	Printf_test();
	Timer_test();

	Kernel_init();
	
	while(true);
}

static void Hw_init(void)
{
	Hal_interrupt_init();
	Hal_uart_init();
	Hal_timer_init();
}

static void Printf_test(void)
{
	char* str = "printf pointer test";
	char* nullptr = 0;
	uint32_t i = 5;
	uint32_t* sysctrl0 = (uint32_t*)0x10001000;

	debug_printf("%s\b", "Hello printf");
	debug_printf("output string pointer: %s\n", str);
	debug_printf("%s is null pointer, %u number\n", nullptr, 10);
	debug_printf("%u=5\n", i);
	debug_printf("dec=%u hex=%x\n", 0xff, 0xff);
	debug_printf("print zero %u\n", 0);
	debug_printf("SYSCTRL0 %x\n", *sysctrl0);

}

static void Timer_test(void)
{
	for(uint32_t i = 0; i < 5 ; i++)
	{
		debug_printf("current count : %u\n", Hal_timer_get_1ms_counter());
		delay(1000);
	}
}

static void Kernel_init(void)
{
	uint32_t taskId;

	Kernel_task_init();

	taskId = Kernel_task_create(User_task0);
	if (NOT_ENOUGH_TASK_NUM == taskId)
	{
		putstr("Task0 creation fail\n");
	}

	taskId = Kernel_task_create(User_task1);
	if (NOT_ENOUGH_TASK_NUM == taskId)
	{
		putstr("Task1 creation fail\n");
	}

	taskId = Kernel_task_create(User_task2);
	if (NOT_ENOUGH_TASK_NUM == taskId)
	{
		putstr("Task2 creation fail\n");
	}

	Kernel_start();
}

void User_task0(void)
{
	uint32_t local = 0;

	while(true)
	{
		debug_printf("User Task #0 SP = 0x%x\n", &local);
		Kernel_yield();
	}
}

void User_task1(void)
{
	uint32_t local = 0;

	while(true)
	{
		debug_printf("User Task #1 SP = 0x%x\n", &local);
		Kernel_yield();
	}
}

void User_task2(void)
{
	uint32_t local = 0;

	while(true)
	{
		debug_printf("User Task #2 SP = 0x%x\n", &local);
		Kernel_yield();
	}
}

 

kernel 폴더와 관련 파일들이 추가됬으니 makefile도 수정하자

 

ARCH=armv7-a
MCPU=cortex-a8

TARGET=rvpb

CC=arm-none-eabi-gcc
AS=arm-none-eabi-as
LD=arm-none-eabi-gcc
OC=arm-none-eabi-objcopy

LINKER_SCRIPT=./navilos.ld
MAP_FILE=build/navilos.map

ASM_SRCS=$(wildcard boot/*.S)
ASM_OBJS=$(patsubst boot/%.S, build/%.os, $(ASM_SRCS))

VPATH = boot			\
		hal/$(TARGET)	\
		kernel			\
		lib

C_SRCS	=	$(notdir $(wildcard boot/*.c))
C_SRCS	+=	$(notdir $(wildcard hal/$(TARGET)/*.c))
C_SRCS	+=	$(notdir $(wildcard lib/*.c))
C_OBJS	=	$(patsubst %.c, build/%.o, $(C_SRCS))

INC_DIRS	=	-I include			\
				-I hal				\
				-I hal/$(TARGET)	\
				-I kernel			\
				-I lib

CFLAGS	= -c -g -std=c11

LDFLAGS	= -nostartfiles -nostdlib -nodefaultlibs -static -lgcc

navilos=build/navilos.axf
navilos_bin=build/navilos.bin

.PHONY: all clean run debug gdb

all:$(navilos)

clean:
	@rm -fr build

run: $(navilos)
	qemu-system-arm -M realview-pb-a8 -kernel $(navilos) -nographic

debug: $(navilos)
	qemu-system-arm -M realview-pb-a8 -kernel $(navilos) -S -gdb tcp::1234,ipv4

gdb:
	gdb-multiarch

$(navilos):$(ASM_OBJS) $(C_OBJS) $(LINKER_SCRIPT)
	$(LD) -n -T $(LINKER_SCRIPT) -o $(navilos) $(ASM_OBJS) $(C_OBJS) -Wl,-Map=$(MAP_FILE) $(LDFLAGS)
	$(OC) -O binary $(navilos) $(navilos_bin)

build/%.os: %.S
	mkdir -p $(shell dirname $@)
	$(CC) -mcpu=$(MCPU) $(INC_DIRS) $(CFLAGS) -o $@ $<

build/%.o: %.c
	mkdir -p $(shell dirname $@)
	$(CC) -mcpu=$(MCPU) $(INC_DIRS) $(CFLAGS) -o $@ $<

 

이전에 문법 에러한것도 다 고치고,

책에 빠진 부분이 있어서 로직이 제대로 돌아가지 않은 부분이있었다.

깃헙 메인함수 참고해서 수정하고 빌드 후 돌리면

 

테스트 마친 후 테스크 0, 1, 2, 3이 서로 돌아가면서

커널 생성 때 등록한 함수 User_task0, 1, 2를 호출하면서

각자의 스택포인터를 출력하는걸 확인할수 있다.

 

 

컨텍스트 스위칭

다른 태스크, 프로그램으로 전환하기 위해 기존 정보 컨택스트를 다른대다 백업하고 

실행할 태스크의 컨택스트를 로드하는 동작

 

과정

1. 현재 태스크 컨텍스트를 스택에 백업

2. 실행할 태스크 컨트롤 블록을 스캐줄러로 전달받음

3. 전달받은 태스크 컨트롤 블록에서 스택 포인터를 읽고

3. 읽은 스택포인터로부터 컨텍스트를 읽어 ARM 코어에 복구

4. 실행할 태스크 직전 실행 위치로 이동

 

kernel/task.c

위 과정을 하도록 Kernel_task_scheduler 함수를 구현했는데

정작 중요한 Kernel_task_context_switching() 내용이 없어 구현해야한다.

 

#include <stdint.h>
#include <stdbool.h>

#include "ARMv7AR"
#include "task.h"

//task
static KernelTcb_t	sTask_list[MAX_TASK_NUM];
static uint32_t		sAllocated_tcb_index;

//scheduling
static uint32_t		sCurrent_tcb_index;
static KernelTcb_t*	Scheduler_round_robin_algorithm(void);

//context switching
static KernelTcb_t*	sCurrent_tcb;
static KernelTcb_t*	sNext_tcb;

void Kernel_task_init(void)
{
	sAllocated_tcb_index = 0;

	for(uint32_t i = 0; i < MAX_TASK_NUM ; i++)
	{
		sTask_list[i].stack_base = (uint8_t*)(TASK_STACK_START + (i * USR_TASK_STACK_SIZE));
		sTask_list[i].sp = (uint32_t)sTask_list[i].stack_base + USR_TASK_STACK_SIZE - 4;

		sTask_list[i].sp -= sizeof(KernelTaskContext_t);
		KernelTaskContext_t* ctx = (KernelTaskContext_t*)sTask_list[i].sp;
		ctx->pc = 0;
		ctx->spsr = ARM_MOD_BIT_SYS;
	}
}

uint32_t Kernel_task_create(KernelTaskFunc_t startFunc)
{
	KernelTcb_t* new_tcb = &sTask_list[sAllocated_tcb_index++];

	if(sAllocated_tcb_index > MAX_TASK_NUM)
	{
		return NOT_ENOUGH_TASK_NUM;
	}

	KernelTaskContext_t* ctx = (KernelTaskContext_t*)new_tcb->sp;
	ctx -> pc = (uint32_t)startFunc;

	return (sAllocated_tcb_index - 1);
}

static KernelTcb_t*	Scheduler_round_robin_algorithm(void)
{
	sCurrent_tcb_index++;
	sCurrent_tcb_index %= sAllocated_tcb_index;

	return &sTask_list[sCurrent_tcb_index];
}

void Kernel_task_scheduler(void)
{
	sCurrent_tcb = &sTask_list[sCurrent_tcb_index];
	sNext_tcb = Scheduler_round_robin_algorithm();

	Kernel_task_context_switching();
}

 

 

그런데 컨텍스트 스위칭 함수 구현 부분은

책에 설명이 제대로 되있지 않아 깃헙 참고해서 구현하면...

 

lib/(아키텍처)/switch.h/.c 이름으로 있긴한데

현재 아키텍처 고려해서 하는건 아니기도하고

뒤에 트리를보면 별도 switch 파일을 만드는게 없어서 그냥 task.c에다가 그대로 구현하야겠다.

 

위 코드의 kernel_task_scheduler 아래에 

kernel_task_context_switching 함수를 구현하면서

__attribute__ ((naked))로 어트리뷰트를 네이크드 설정 시 컴파일러가

자동으로 스택 백업, 복구, 리턴 관련 어셈블리어를 생성하지 않는다고 한다.

=> 역 어샘블시

<Save_context>

<Restore_context>

만 나온다.

 

ARM의 인스트럭션 B를 쓴건 LR(링크 레지스터 : 함수 호출전 리턴 주소 보관)을 바꾸지 않기 위함인데

naked로 스택 확보해서 뭐 하지 않았기 때문이라 한다.

void Kernel_task_scheduler(void)
{
	sCurrent_tcb = &sTask_list[sCurrent_tcb_index];
	sNext_tcb = Scheduler_round_robin_algorithm();

	Kernel_task_context_switching();
}

__attribute__ ((naked)) void Kernel_task_context_switching(void)
{
	__asm__ ("B Save_context");
	__asm__ ("B Restore_context");
}

 

컨텍스트  스위칭 과정 보기

 

컨텍스트 스위칭 과정을 정리해서 보면 

task1 이 동작하다가 전환 시 

task1 context를 저장하고

변경된 sp를 task1 tcb에 저장.

그 다음 task2 tcb의 sp를 읽고

해당 sp로부터 task2 context를 복구

task2 작업 스택 원래 sp로 돌아와 수행

 

 

 

 

태스크 초기화 및 생성

 

커널 tcb를 sTask_list[MAX_TASK_NUM] 그러니까 64개 배열 형태의 전역 변수로 만들었으니 

global variable 영역에 있을 것이고 ..

태스크 스택은 1MB 씩 task stack 영역에 있으니까 맞겟지..?

 

태스크 컨텍스트 저장 하기

 

이제 컨텍스트 백업을 위한 코드와 복구를 위한 코드를 구현해야하는데.

 

태스크 컨텍스트의 메모리 주소는 spsr, 레지스터, pc가 작은데서 큰 순이지만

스택은 주소가 큰곳에서 작은곳으로 가서 

가장 마지막에 담아야할 pc를 스택의 가장 먼저

중간인 r0_r12은 pc 다음

가장 마지막에 읽어야할 spsr를 어샘블리어 상에서 가장 먼저 저장시켜야한다.

 

 

아까 쓴 kernel_task_context_switching에 이어서

Saved_context를 구현하면

 

먼저 LR을 스택에 푸시해서 태스크 컨택스트 pc 변수 저장(복귀때 리턴 주소)

그 다음 r0, r1 ... r12를 스택에 저장

프로그램 상태 레지스터 psr을 바로 직접 메모리에 저장불가해 r0에 전달했다가 푸시

 

그 뒤 tcb의 포인터 변수를 로드 LDR r0, =sCurrent_tcb

LDR r0, [r0] 로 r0에 저장된 tcb 포인터의 주소에서 실제 값 tcb 포인터를 가져온 뒤

tcb 포인터 주소를 sp에 저장

 

이 과정을 c언어로 쓰면 => sCurrent_tcp->sp = ARM_코어_SP_레지스터값

 

__attribute__ ((naked)) void Kernel_task_context_switching(void)
{
	__asm__ ("B Save_context");
	__asm__ ("B Restore_context");
}

static __attribute__ ((naked)) void Saved_context(void)
{
	//save current task context into the current task stack
	__asm__ ("PUSH {lr}");
	__asm__ ("PUSH {r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12}");
	__asm__ ("MRS r0, cpsr");
	__asm__ ("PUSH {r0}");
	//save current task stack pointer into the current TCB
	__asm__ ("LDR r0, =sCurrent_tcb");
	__asm__ ("LDR r0, [r0]");
	__asm__ ("STMIA r0!, {sp}");
}

 

-------------------------

컨텍스트 복구하기

 

이 글을 일요일에 쓰고, 화목은 조별 과제 준비하느라 어떻게 했는지 잘 생각안나는데

이번에는 컨텍스트 복구 과정을 그림을 따라보면

1. 다음 TCB SP읽어서 현재 ARM SP에 쓰기

2. 현재 ARM SP로부터 태스크 컨택스트 읽기 - cpsr -> r0_r12 -> pc 순

3. 태스크 2의 작업스택으로 돌아와 동작 수행한다

naked를 오랜만에 보는대

스택 복구, 리턴 같은 코드를 생성하지 않도록 컴파일러에 전달하는 명령어였던거같다.

attribute는 컴파일러 확장기능을 쓰도록 하는 명령어이고

 

static __attribute__ ((naked)) void Restore_context(void)
{
	//restore next task stack pointer from the next TCB
	__asm__ ("LDR	r0, =sNext_tcb");
    __asm__ ("LDR	r0, [r0]");
    __asm__ ("LDMIA	r0!, {sp}");
    // restore next task context from the next task stack
    __asm__ ("POP {r0}");
    __asm__ ("MSR cpsr, r0");
    __asm__ ("POP {r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12}");
    __asm__ ("POP {pc}");
}

 

스캐줄러

현재 태스크 다음에 어떤 태스크를 실행할지 결정하는 프로그램

대표적인 동작 알고리즘으로 라운드로빈 (인덱스를 올려가며 최대가 되면 0이되어 반복)

우선순위

 

 

 

라운드로빈 스캐줄러 구현

kernel/task.c

tcb 인덱스를 위한 sCurrent_tcb_index와

라운드 로빈 스캐줄러 함수 선언 + 구현

sCurrent_tcb_index %= sAllocated_tcb_index는 최대 tcb 인덱스를 넘기지 않기 위해 사용

#include <stdint.h>
#include <stdbool.h>

#include "ARMv7AR"
#include "task.h"

static KernelTcb_t	sTask_list[MAX_TASK_NUM];
static uint32_t		sAllocated_tcb_index;

static uint32_t		sCurrent_tcb_index;
static KernelTcb_t*	Scheduler_round_robin_algorithm(void);

void Kernel_task_init(void)
{
	sAllocated_tcb_index = 0;

	for(uint32_t i = 0; i < MAX_TASK_NUM ; i++)
	{
		sTask_list[i].stack_base = (uint8_t*)(TASK_STACK_START + (i * USR_TASK_STACK_SIZE));
		sTask_list[i].sp = (uint32_t)sTask_list[i].stack_base + USR_TASK_STACK_SIZE - 4;

		sTask_list[i].sp -= sizeof(KernelTaskContext_t);
		KernelTaskContext_t* ctx = (KernelTaskContext_t*)sTask_list[i].sp;
		ctx->pc = 0;
		ctx->spsr = ARM_MOD_BIT_SYS;
	}
}

uint32_t Kernel_task_create(KernelTaskFunc_t startFunc)
{
	KernelTcb_t* new_tcb = &sTask_list[sAllocated_tcb_index++];

	if(sAllocated_tcb_index > MAX_TASK_NUM)
	{
		return NOT_ENOUGH_TASK_NUM;
	}

	KernelTaskContext_t* ctx = (KernelTaskContext_t*)new_tcb->sp;
	ctx -> pc = (uint32_t)startFunc;

	return (sAllocated_tcb_index - 1);
}

static KernelTcb_t*	Scheduler_round_robin_algorithm(void)
{
	sCurrent_tcb_index++;
	sCurrent_tcb_index %= sAllocated_tcb_index;

	return &sTask_list[sCurrent_tcb_index];
}

 

 

 

 

지금까지 한게.

1. 익셉션 벡터 테이블 만들어서 동작모드별, 태스크 등 메모리 할당해주고 메인함수로 진입하게 구현

2. UART 하드웨어 초기화, 입출력, printf

3. 인터럽트 하드웨어 초기화, HW와 인터럽트 컨트롤러 연결, 익셉션 벡터와 인터럽트 헨들러 연결

4. 타이머 초기화, 딜레이 함수 구현

 

으로 RTOS 구현에 필요한 함수들을 구현했고 이번에는 태스크를 다룬다.

 

TCB task control block

개별 태스크를 추상화한 자료 구조

태스크 = 프로그램

태스크 간 전환시 문제 발생이 없어야 함

 

TCB 구성 요소?

컨텍스트 : 프로그램의 현재 상태 정보

태스크 이름

태스크 번호, 우선순위

 

 

 

 

kernel/task.h

이전에 TASK STACK 사이즈를 64MB로 만들었고,

USR_TACK_STACK_SIZE가 0x100000 = 1024 x 1024 = 1MB이니

최대 스택 개수는 64개가 된다.

 

커널 태스크 컨택스트는  컨텍스트를 추상화해서 상태레지스터 spsr, 범용레지스터 r0-r12, pc 영역을

커널tcb는 (범용레지스터에 있는) 스택 포인터 sp와 (개별 테스크의) 스택 베이스로 스택관련 정보를 저장

 

#ifndef KERNEL_TASK_H_
#define KERNEL_TASK_H_

#include "MemoryMap.h"

#define NOT_ENOUGH_TASK_NUM		0xFFFFFFFF
#define USR_TASK_STACK_SIZE		0x100000
#define MAX_TASK_NUM			(TASK_STACK_SIZE / USR_TASK_STACK_SIZE)

typedef struct KernelTaskContext_t
{
	uint32_t spsr;
	uint32_t r0_r12[13];
	uint32_t pc;
}	KernelTaskContext_t;

typedef struct KernelTcb_t
{
	uint32_t	sp;
	uint8_t*	stack_base;
} KernelTcb_t;

typedef void (*KernelTaskFunc_t)(void);

void Kernel_task_init(void);
uint32_t Kernel_task_create(KernelTaskFunc_t startFunc);

#endif

 

 

 

kernel/task.c

앞서 추상화한 테스크컨텍스와 tcb 구조체를 가지고

커널 테스크 초기화, 생성 구현

 

sAllocated tcp index를 기준으로 이거보다 아래는 할당된거 크면 할당되지 않은거

 

태스크 컨트롤 블록을 64개 배열로 선언하고

각 태스크 컨트롤 블록별 스택 베이스와

스택 포인터를 우선 1. 위에서 패딩을 위해 -4한 지점로 초기화 2. 태스크 컨택스트 크기만큼 아래로 내려간 지점을 SP로 설정

KernelTaskContext_t ctx = (KernelTaskContext_t*)sTask_list[i].sp;로 sp기준으로 윗 부분에 태스크 컨택스트 저장

 

 

#include <stdint.h>
#include <stdbool.h>

#include "ARMv7AR"
#include "task.h"

static KernelTcb_t	sTask_list[MAX_TASK_NUM];
static uint32_t		sAllocated_tcb_index;

void Kernel_task_init(void)
{
	sAllocated_tcb_index = 0;

	for(uint32_t i = 0; i < MAX_TASK_NUM ; i++)
	{
		sTask_list[i].stack_base = (uint8_t*)(TASK_STACK_START + (i * USR_TASK_STACK_SIZE));
		sTask_list[i].sp = (uint32_t)sTask_list[i].stack_base + USR_TASK_STACK_SIZE - 4;

		sTask_list[i].sp -= sizeof(KernelTaskContext_t);
		KernelTaskContext_t* ctx = (KernelTaskContext_t*)sTask_list[i].sp;
		ctx->pc = 0;
		ctx->spsr = ARM_MOD_BIT_SYS;
	}
}

uint32_t Kernel_task_create(KernelTaskFunc_t startFunc)
{
	return NOT_ENOUGH_TASK_NUM;
}

 

위 코드에서 태스크 생성 부분을 제대로 구현하면

 

태스크 생성시 안쓰는 tcb 블록 가져오고 인덱스는 ++

그다음 태스크 리스트 최대 크기를 넘었는지 체크

그러고나서 현재 태스크 컨트롤 블록/컨텍스트의 pc에 태스크 함수 등록하고 이 tcb 블록 인덱스 반환

 

 

#include <stdint.h>
#include <stdbool.h>

#include "ARMv7AR"
#include "task.h"

static KernelTcb_t	sTask_list[MAX_TASK_NUM];
static uint32_t		sAllocated_tcb_index;

void Kernel_task_init(void)
{
	sAllocated_tcb_index = 0;

	for(uint32_t i = 0; i < MAX_TASK_NUM ; i++)
	{
		sTask_list[i].stack_base = (uint8_t*)(TASK_STACK_START + (i * USR_TASK_STACK_SIZE));
		sTask_list[i].sp = (uint32_t)sTask_list[i].stack_base + USR_TASK_STACK_SIZE - 4;

		sTask_list[i].sp -= sizeof(KernelTaskContext_t);
		KernelTaskContext_t* ctx = (KernelTaskContext_t*)sTask_list[i].sp;
		ctx->pc = 0;
		ctx->spsr = ARM_MOD_BIT_SYS;
	}
}

uint32_t Kernel_task_create(KernelTaskFunc_t startFunc)
{
	KernelTcb_t* new_tcb = &sTask_list[sAllocated_tcb_index++];

	if(sAllocated_tcb_index > MAX_TASK_NUM)
	{
		return NOT_ENOUGH_TASK_NUM;
	}

	KernelTaskContext_t* ctx = (KernelTaskContext_t*)new_tcb->sp;
	ctx -> pc = (uint32_t)startFunc;

	return (sAllocated_tcb_index - 1);
}

 

 

태스크 쓰기 위해선 스케줄러와 컨텍스트 스위칭을 구현해야해서 제대로는 못쓰고

대신 더미 태스크 함수를 만들면

boot/Main.c

 

 

#include <stdint.h>
#include <stdbool.h>

#include "HalInterrupt.h"
#include "HalUart.h"
#include "HalTimer.h"

#include "task.h"

#include "stdio.h"
#include "stdlib.h"

static void Hw_init(void);
static void Kernel_init(void);

static void	Printf_test(void);
static void Timer_test(void);

void User_task0(void);
void User_task1(void);
void User_task2(void);

void main(void)
{
	Hw_init();

	uint32_t i = 100;
	while(i--)
	{
		Hal_uart_put_char('N');
	}
	Hal_uart_put_char('\n');
	putstr("Hello World!\n");

	Printf_test();
	Timer_test();
}

static void Hw_init(void)
{
	Hal_interrupt_init();
	Hal_uart_init();
	Hal_timer_init();
}

static void Printf_test(void)
{
	char* str = "printf pointer test";
	char* nullptr = 0;
	uint32_t i = 5;
	uint32_t* sysctrl0 = (uint32_t*)0x10001000;

	debug_printf("%s\b", "Hello printf");
	debug_printf("output string pointer: %s\n", str);
	debug_printf("%s is null pointer, %u number\n", nullptr, 10);
	debug_printf("%u=5\n", i);
	debug_printf("dec=%u hex=%x\n", 0xff, 0xff);
	debug_printf("print zero %u\n", 0);
	debug_printf("SYSCTRL0 %x\n", *sysctrl0);

}

static void Timer_test(void)
{
	while(true)
	{
		debug_printf("current count : %u\n", Hal_timer_get_1ms_counter());
		delay(1000);
	}
}

static void Kernel_init(void)
{
	uint32_t taskId;

	Kernel_task_init();

	taskId = Kernel_task_create(User_task0);
	if (NOT_ENOUGH_TASK_NUM == taskId)
	{
		putstr("Task0 creation fail\n");
	}

	taskId = Kernel_task_create(User_task1);
	if (NOT_ENOUGH_TASK_NUM == taskId)
	{
		putStr("Task1 creation fail\n");
	}

	taskId = Kernel_task_create(User_task2);
	if (NOT_ENOUGH_TASK_NUM == taskId)
	{
		putStr("Task2 creation fail\n");
	}
}

void User_task0(void)
{
	debug_printf("User Task#0\n");

	while(true);
}

void User_task1(void)
{
	debug_printf("User Task#1\n");

	while(true);
}

void User_task2(void)
{
	debug_printf("User Task#2\n");

	while(true);
}

 

 

 

RealViewPB에는 SP804 타이머 내장하며, 카운터가 내려가는 식

 

 

먼저 SP804 하드웨어 레지스터 구조체로 추상화

 

hal/rvpb/Timer.h

 

Timer_t의

timerxload는 카운터 목표값

timerxvalue는 감소 레지스터

타이머 on 시 timerxvalue에 timerxload 값을 복사 후 timerxvalue가 감소 0이 되면 인터럽트 발생

timermode는 timerxload사용할지말지 결정

timerpre는 클럭마다 얼마나 줄일지 결정

timerxcontrol은 타이머 hw 속성 설정

 

timerxintclr인터럽트 처리 완료를 hw에 알리는 레지스터

 

프리러닝모드 timerxvalue 최댓값에서 0까지 내려가면 인터럽트 발생하는 모드

피리오딕모드 timerxload에 지정한값부터 0까지 내려가면 인터럽트 발생

 

 

#ifndef HAL_RVPB_TIMER_H_
#define HAL_RVPB_TIMER_H_

typedef union TimerXControl_t
{
    uint32_t all;
    struct {
        uint32_t OneShot:1;     //0
        uint32_t TimerSize:1;   //1
        uint32_t TimerPre:2;    //3:2
        uint32_t Reserved0:1;   //4
        uint32_t IntEnable:1;   //5
        uint32_t TimerMode:1;   //6
        uint32_t TimerEn:1;     //7
        uint32_t Reserved1:24;  //31:8
    } bits;
} TimerXControl_t;

typedef union TimerXRIS_t
{
    uint32_t all;
    struct {
        uint32_t TimerXRIS:1;   //0
        uint32_t Reserved:31;   //31:1
    } bits;
} TimerXRIS_t;

typedef union TimerXMIS_t
{
    uint32_t all;
    struct {
        uint32_t TimerXMIS:1;   //0
        uint32_t Reserved:31;   //31:1
    } bits;
} TimerXMIS_t;

typedef struct Timer_t
{
    uint32_t        timerxload;     // 0x00
    uint32_t        timerxvalue;    // 0x04
    TimerXControl_t timerxcontrol;  // 0x08
    uint32_t        timerxintclr;   // 0x0C
    TimerXRIS_t     timerxris;      // 0x10
    TimerXMIS_t     timerxmis;      // 0x14
    uint32_t        timerxbgload;   // 0x18
} Timer_t;

#define TIMER_CPU_BASE  0x10011000
#define TIMER_INTERRUPT 36

#define TIMER_FREERUNNING   0
#define TIMER_PERIOIC       1

#define TIMER_16BIT_COUNTER 0
#define TIMER_32BIT_COUNTER 1

#define TIMER_1MZ_INTERVAL       (1024 * 1024)

#endif /* HAL_RVPB_TIMER_H_ */

 

책에 빠져있는데

Regs.c에 타이머 인스턴스 선언해야한다.

#include <stdint.h>
#include "Uart.h"
#include "Interrupt.h"
#include "Timer.h"

volatile PL011_t*	Uart	= (PL011_t*)UART_BASE_ADDRESS0;
volatile GicCput_t*	GicCpu	= (GicCput_t*)GIC_CPU_BASE;
volatile GicDist_t*	GicDist	= (GicDist_t*)GIC_DIST_BASE;
volatile Timer_t*	Timer	= (Timer_t*)TIMER_CPU_BASE;

 

 

 

 

공용 인터페이스  API 간단하게 정의하고

hal/HalTimer.h

#ifndef HAL_HALTIMER_H_
#define HaL_HALTIMER_H_

void	Hal_timer_init(void);

#endif

 

hal/rvpb/Timer.c

타이머 hal 구현하면

 

timeren=0 타이머 끄고

프리러닝 모드 설정 timermode=0, oneshot=0

16비트 카운터 모드 설정 timersize==0

분주율 1 timerpre=0

인터럽트 켜기 intenable = 1

로드 레지스터 키기

카운터레지스터는 0xffffffff

#include <stdint.h>
#include "Timer.h"
#include "HalTimer.h"
#include "HalInterrupt.h"

extern volatile Timer_t* Timer;

static void interrupt_handler(void);

static uint32_t internal_1ms_counter;

void Hal_timer_init(void)
{
	//interface reset
	Timer->timerxcontrol.bits.TimerEn=0;
	Timer->timerxcontrol.bits.TimerMode=0;
	Timer->timerxcontrol.bits.OneShot=0;
	Timer->timerxcontrol.bits.TimerSize=0;
	Timer->timerxcontrol.bits.TimerPre=0;
	Timer->timerxcontrol.bits.IntEnable=1;
	Timer->timerxload=0;
	Timer->timerxvalue=0xFFFFFFFF;

	//set periodic mode
	Timer->timerxcontrol.bits.TimerMode=TIMER_PERIOIC;
	Timer->timerxcontrol.bits.TimerSize=TIMER_32BIT_COUNTER;
	Timer->timerxcontrol.bits.OneShot=0;
	Timer->timerxcontrol.bits.TimerPre=0;
	Timer->timerxcontrol.bits.IntEnable=1;

	uint32_t interval_1ms = TIMER_1MZ_INTERVAL / 1000;

	Timer->timerxload = interval_1ms;
	Timer->timerxcontrol.bits.TimerEn=1;

	internal_1ms_counter=0;

	//Register Timer interrupt handler
	Hal_interrupt_enable(TIMER_INTERRUPT);
	Hal_interrupt_register_handler(interrupt_handler, TIMER_INTERRUPT);
}

static void interrupt_handler(void)
{
	internal_1ms_counter++;

	Timer->timerxintclr=1;
}

 

realviewpb는 클럭 소스로 1mhz나 32.768khz 사용

SYSCTRL0 레지스터로 타이머클럭소스 확인 가능. 주소는 0x10001000

클럭 확인하도록 main.c 수정하면..

 

#include <stdint.h>
#include <stdbool.h>

#include "HalInterrupt.h"
#include "HalUart.h"
#include "stdio.h"

static void Hw_init(void);
static void	Printf_test(void);

void main(void)
{
	Hw_init();

	uint32_t i = 100;
	while(i--)
	{
		Hal_uart_put_char('N');
	}
	Hal_uart_put_char('\n');
	putstr("Hello World!\n");

	Printf_test();

	while(true);
}

static void Hw_init(void)
{
	Hal_interrupt_init();
	Hal_uart_init();
}

static void Printf_test(void)
{
	char* str = "printf pointer test";
	char* nullptr = 0;
	uint32_t i = 5;
	uint32_t* sysctrl0 = (uint32_t*)0x10001000;

	debug_printf("%s\b", "Hello printf");
	debug_printf("output string pointer: %s\n", str);
	debug_printf("%s is null pointer, %u number\n", nullptr, 10);
	debug_printf("%u=5\n", i);
	debug_printf("dec=%u hex=%x\n", 0xff, 0xff);
	debug_printf("print zero %u\n", 0);
	debug_printf("SYSCTRL0 %x\n", *sysctrl0);
}

 

시간지연함수 delay를 쓰기위해 타이머 카운터를 이용

 

stdlib.h에 delay 함수 작성

 

lib/stdlib.h

#ifndef LIB_STDLIB_H_
#define LIB_STDLIB_h_

void delay(uint32_t ms);

#endif

 

hal/HalTimer.h

타이머 카운터 변수값을 반환하는

hal_timer_get_1ms_counter 선언

#ifndef HAL_HALTIMER_H_
#define HaL_HALTIMER_H_

void		Hal_timer_init(void);
uint32_t	Hal_timer_get_1ms_counter(void);

#endif

 

 

hal/rvpb/Timer.c에 위 함수 구현

#include <stdint.h>
#include "Timer.h"
#include "HalTimer.h"
#include "HalInterrupt.h"

extern volatile Timer_t* Timer;

static void interrupt_handler(void);

static uint32_t internal_1ms_counter;

void Hal_timer_init(void)
{
	//interface reset
	Timer->timerxcontrol.bits.TimerEn=0;
	Timer->timerxcontrol.bits.TimerMode=0;
	Timer->timerxcontrol.bits.OneShot=0;
	Timer->timerxcontrol.bits.TimerSize=0;
	Timer->timerxcontrol.bits.TimerPre=0;
	Timer->timerxcontrol.bits.IntEnable=1;
	Timer->timerxload=0;
	Timer->timerxvalue=0xFFFFFFFF;

	//set periodic mode
	Timer->timerxcontrol.bits.TimerMode=TIMER_PERIOIC;
	Timer->timerxcontrol.bits.TimerSize=TIMER_32BIT_COUNTER;
	Timer->timerxcontrol.bits.OneShow=0;
	Timer->timerxcontrol.bits.TimerPre=0;
	Timer->timerxcontrol.bits.IntEnable=1;

	uint32_t interval_1ms = TIMER_1MZ_INTERVAL / 1000;

	Timer->timerxload = interval_1ms;
	Timer->timerxcontrol.bits.TimerEn=1;

	internal_1ms_counter=0;

	//Register Timer interrupt handler
	Hal_interrupt_enable(TIMER_INTERRUPT);
	Hal_interrupt_register_handler(interrupt_handler, TIMER_INTERRUPT);
}

static void interrupt_handler(void)
{
	internal_1ms_counter++;

	Timer->timerxintclr=1;
}

uint32_t Hal_timer_get_1ms_counter(void)
{
	return internal_1ms_counter;
}

 

lib/stdlib.c 에서

delay 함수 구현

#include <stdint.h>
#include <stdbool.h>
#include "HalTimer.h"

void delay(uint32_t ms)
{
	uint32_t goal = Hal_timer_get_1ms_counter() + ms;

	while(goal != Hal_timer_get_1ms_counter());
}

 

 

boot/Main.c에 테스트 코드 작성하는데

책에는 좀 빠진 부분이 있다.

타이머 초기화나 타이머 테스트를 메인함수에 넣는 내용

 

#include <stdint.h>
#include <stdbool.h>

#include "HalInterrupt.h"
#include "HalUart.h"
#include "HalTimer.h"

#include "stdio.h"
#include "stdlib.h"

static void Hw_init(void);
static void	Printf_test(void);
static void Timer_test(void);

void main(void)
{
	Hw_init();

	uint32_t i = 100;
	while(i--)
	{
		Hal_uart_put_char('N');
	}
	Hal_uart_put_char('\n');
	putstr("Hello World!\n");

	Printf_test();
	Timer_test();
}

static void Hw_init(void)
{
	Hal_interrupt_init();
	Hal_uart_init();
	Hal_timer_init();
}

static void Printf_test(void)
{
	char* str = "printf pointer test";
	char* nullptr = 0;
	uint32_t i = 5;
	uint32_t* sysctrl0 = (uint32_t*)0x10001000;

	debug_printf("%s\b", "Hello printf");
	debug_printf("output string pointer: %s\n", str);
	debug_printf("%s is null pointer, %u number\n", nullptr, 10);
	debug_printf("%u=5\n", i);
	debug_printf("dec=%u hex=%x\n", 0xff, 0xff);
	debug_printf("print zero %u\n", 0);
	debug_printf("SYSCTRL0 %x\n", *sysctrl0);

}

static void Timer_test(void)
{
	while(true)
	{
		debug_printf("current count : %u\n", Hal_timer_get_1ms_counter());
		delay(1000);
	}
}

 

 

위 코드로 빌드하면 잘 동작한다.

 

 

 

현재 소스 트리

 

인터럽트 사용하기

1. 인터럽트 컨트롤러 초기화 코드 구현

2. 인터럽트 발생 하드웨어와 인터럽트 컨트롤러 연결

3. 익셉션 핸들러 구현하여 적절한 인터럽트 핸들러 호출

 

UART 인터럽트

- UART 하드웨어에서 인터럽트 발생 -> 컨트롤러로 전달 -> 인터럽트 컨트롤러는 ARM 코어로 전달.

 

 

 

 

인터럽트 컨트롤러

RealViewPB에서는 GIC general interrupt contller 하드웨어가 존재

레지스터 구조체

 

hal/rvpb/Interrupt.h

#ifndef HAL_RVPB_INTERRUPT_H_
#define HAL_RVPB_INTERRUPT_H_

typedef union CpuControl_t
{
    uint32_t all;
    struct {
        uint32_t Enable:1;          // 0
        uint32_t reserved:31;
    } bits;
} CpuControl_t;

typedef union PriorityMask_t
{
    uint32_t all;
    struct {
        uint32_t Reserved:4;        // 3:0
        uint32_t Prioritymask:4;    // 7:4
        uint32_t reserved:24;
    } bits;
} PriorityMask_t;

typedef union BinaryPoint_t
{
    uint32_t all;
    struct {
        uint32_t Binarypoint:3;     // 2:0
        uint32_t reserved:29;
    } bits;
} BinaryPoint_t;

typedef union InterruptAck_t
{
    uint32_t all;
    struct {
        uint32_t InterruptID:10;    // 9:0
        uint32_t CPUsourceID:3;     // 12:10
        uint32_t reserved:19;
    } bits;
} InterruptAck_t;

typedef union EndOfInterrupt_t
{
    uint32_t all;
    struct {
        uint32_t InterruptID:10;    // 9:0
        uint32_t CPUsourceID:3;     // 12:10
        uint32_t reserved:19;
    } bits;
} EndOfInterrupt_t;

typedef union RunningInterrupt_t
{
    uint32_t all;
    struct {
        uint32_t Reserved:4;        // 3:0
        uint32_t Priority:4;        // 7:4
        uint32_t reserved:24;
    } bits;
} RunningInterrupt_t;

typedef union HighestPendInter_t
{
    uint32_t all;
    struct {
        uint32_t InterruptID:10;    // 9:0
        uint32_t CPUsourceID:3;     // 12:10
        uint32_t reserved:19;
    } bits;
} HighestPendInter_t;

typedef union DistributorCtrl_t
{
    uint32_t all;
    struct {
        uint32_t Enable:1;          // 0
        uint32_t reserved:31;
    } bits;
} DistributorCtrl_t;

typedef union ControllerType_t
{
    uint32_t all;
    struct {
        uint32_t IDlinesnumber:5;   // 4:0
        uint32_t CPUnumber:3;       // 7:5
        uint32_t reserved:24;
    } bits;
} ControllerType_t;



typedef struct GicCput_t
{
    CpuControl_t       cpucontrol;        //0x000
    PriorityMask_t     prioritymask;      //0x004
    BinaryPoint_t      binarypoint;       //0x008
    InterruptAck_t     interruptack;      //0x00C
    EndOfInterrupt_t   endofinterrupt;    //0x010
    RunningInterrupt_t runninginterrupt;  //0x014
    HighestPendInter_t highestpendinter;  //0x018
} GicCput_t;

typedef struct GicDist_t
{
    DistributorCtrl_t   distributorctrl;    //0x000
    ControllerType_t    controllertype;     //0x004
    uint32_t            reserved0[62];      //0x008-0x0FC
    uint32_t            reserved1;          //0x100
    uint32_t            setenable1;         //0x104
    uint32_t            setenable2;         //0x108
    uint32_t            reserved2[29];      //0x10C-0x17C
    uint32_t            reserved3;          //0x180
    uint32_t            clearenable1;       //0x184
    uint32_t            clearenable2;       //0x188
} GicDist_t;

#define GIC_CPU_BASE  0x1E000000  //CPU interface
#define GIC_DIST_BASE 0x1E001000  //distributor

#define GIC_PRIORITY_MASK_NONE  0xF

#define GIC_IRQ_START           32
#define GIC_IRQ_END             95

#endif /* HAL_RVPB_INTERRUPT_H_ */

 

 

GIC에서는 

CPU interpace resister와 distribution resister 제공

두 레지스터 구조체의 베이스 주소가 있으니

 

hal/rvpb/Regs.c에 인스턴스 선언

#include <stdint.h>
#include "Uart.h"
#include "Interrupt.h"

volatile PL011_t*	Uart	= (PL011_t*)UART_BASE_ADDRESS0;
volatile GicCput_t*	GicCpu	= (GicCput_t*)GIC_CPU_BASE;
volatile GicDist_t*	GicDist	= (GicDist_t*)GIC_DIST_BASE;

 

 

공용 인터럽트 API로

hal/HalInterrupt.h 작성

초기화, 활성화, 비활성화, 핸들러등록, 핸들러 호출 정의

#ifndef HAL_HALINTERRUPT_H_
#define HAL_HALINTERRUPT_H_

#define INTERRUPT_HANDLER_NUM	255

typedef void (*InterHdlr_fptr)(void);

void Hal_interrupt_init(void);
void Hal_interrupt_enable(uint32_t interrupt_num);
void Hal_interrupt_disable(uint32_t interrupt_num);
void Hal_interrupt_register_handler(InterHdlr_fptr handler, uint32_t interrupt_num);
void Hal_interrupt_run_handler(void);

#endif

 

인터럽트 함수 구현

Regs.c에 선언한 GicCpu, GicDist 인스턴스 가져와서

초기화, 활성, 비활성화, 등록, 핸들러 동작 하도록

비트를 이래저래 하는대

 

인클루드 하는 헤더중에

memio.h와 armcpu.h는 바로 뒤에 나온다.

 

enable 부분에서 enable1, 2가 있는데

GIC는 인터럽트 64개를 관리해서 setenable1, setenable2 레지스터에서 32개씩 관리

IRQ는 32번부터 시작 -> ID32 ~ ID95

UART는 IRQ ID 44 -> setenable1레지스터의 12번 비트에 연결

 

hal/rvpb/Interrupt.c

#include <stdint.h>
#include <stddef.h>

#include "memio.h"
#include "armcpu.h"
#include "Interrupt.h"
#include "HalInterrupt.h"

extern volatile GicCput_t*	GicCpu;
extern volatile GicDist_t*	GicDist;

static InterHdlr_fptr sHandlers[INTERRUPT_HANDLER_NUM];

void Hal_interrupt_init(void)
{
	GicCpu->cpucontrol.bits.Enable = 1;
	GicCpu->prioritymask.bits.Prioritymask = GIC_PRIORITY_MASK_NONE;
	GicDist->distributorctrl.bits.Enable = 1;

	for(uint32_t i = 0; i < INTERRUPT_HANDLER_NUM ; i++)
	{
		sHandlers[i] = NULL;
	}

	enable_irq();
}

void Hal_interrupt_enable(uint32_t interrupt_num)
{
	if ((interrupt_num < GIC_IRQ_START) || (GIC_IRQ_END < interrupt_num))
	{
		return;
	}

	uint32_t bit_num = interrupt_num - GIC_IRQ_START;

	if(bit_num < GIC_IRQ_START)
	{
		SET_BIT(GicDist->setenable1, bit_num);
	}
	else
	{
		bit_num -= GIC_IRQ_START;
		SET_BIT(GicDist->setenable2, bit_num);
	}
}

void Hal_interrupt_disable(uint32_t interrupt_num)
{
	if ((interrupt_num < GIC_IRQ_START) || (GIC_IRQ_END < interrupt_num))
	{
		return;
	}

	uint32_t bit_num = interrupt_num - GIC_IRQ_START;

	if(bit_num < GIC_IRQ_START)
	{
		CLR_BIT(GicDist->setenable1, bit_num);
	}
	else
	{
		bit_num -= GIC_IRQ_START;
		CLR_BIT(GicDist->setenable2, bit_num);
	}
}

void Hal_interrupt_register_handler(InterHdlr_fptr handler, uint32_t interrupt_num)
{
	sHandlers[interrupt_num] = handler;
}

void Hal_interrupt_run_handler(void)
{
	uint32_t interrupt_num = GicCpu->interruptack.bits.InterruptID;

	if(sHandlers[interrupt_num] != NULL)
	{
		sHandlers[interrupt_num]();
	}
	
	GicCpu->endofinterrupt.bits.InterruptID = interrupt_num;
}

 

SET_BIT, CLR_BIT 매크로 구현을 위해

include/memio.h 구현

#define SET_BIT(p, n)	((p) |= (1 << (n)))
#define CLR_BIT(p, n)	((p) &= ~(1 << (n)))

 

armcpu.h, armcpu.c 를 작성하여 IRQ, FIQ 인에이블 디스에이블 구현

 

lib/armcpu.h

#ifndef LIB_ARMCPU_H_
#define LIB_ARMCPU_H_

void enable_irq(void);
void enable_fiq(void);
void disable_irq(void);
void disable_fiq(void);

#endif

 

cspr을 제어하기 위해서

인라인 어셈블리어 사용(장점 : 컴파일러가 레지스터 백업, 복구, 리턴처리를 자동으로 만듬)

<-> 완전 어셈블러어로 작성시 직접 다 구현해야함.

 

lib/armcpu.c

#include "armcpu.h"

void enable_irq(void)
{
	__asm__ ("PUSH {r0, r1}");
	__asm__ ("MRS r0, cpsr");
	__asm__ ("BIC r1, r0, #0x80");
	__asm__ ("MSR cpsr, r1");
	__asm__ ("POP {r0, r1}");
}

void enable_fiq(void)
{
	__asm__ ("PUSH {r0, r1}");
	__asm__ ("MRS r0, cpsr");
	__asm__ ("BIC r1, r0, #0x40");
	__asm__ ("MSR cpsr, r1");
	__asm__ ("POP {r0, r1}");
}

void disable_irq(void)
{
	__asm__ ("PUSH {r0, r1}");
	__asm__ ("MRS r0, cpsr");
	__asm__ ("ORR r1, r0, #0x80");
	__asm__ ("MSR cpsr, r1");
	__asm__ ("POP {r0, r1}");
}

void disable_fiq(void)
{
	__asm__ ("PUSH {r0, r1}");
	__asm__ ("MRS r0, cpsr");
	__asm__ ("ORR r1, r0, #0x40");
	__asm__ ("MSR cpsr, r1");
	__asm__ ("POP {r0, r1}");
}

 

 

 

 

지금까지 GIC 설정했으니

 

인터럽트 사용하기

1. 인터럽트 컨트롤러 초기화 코드 구현  V

2. 인터럽트 발생 하드웨어와 인터럽트 컨트롤러 연결    <-

3. 익셉션 핸들러 구현하여 적절한 인터럽트 핸들러 호출

 

 

 

UART와 인터럽트 컨트롤러를 연결할 차례

 

uart 초기화 함수에서 

uart 인터럽트를 인에이블하고, 인터럽트 헨들러를 등록하는데 뜬금없이

UART_INTERRUPT0 이게 보인다.

어디있나 찾아보니 hal/rvpb/Uart.h에 이미 정의되있더라.

 

hal/rvpb/Uart.c 코드

#include <stdint.h>
#include "Uart.h"
#include "HalUart.h"
#include "HalInterrupt.h"

extern volatile PL011_t* Uart;

static void interrupt_handler();

void Hal_uart_init(void)
{
	//enable UART
	Uart->uartcr.bits.UARTEN=0;
	Uart->uartcr.bits.TXE=1;
	Uart->uartcr.bits.RXE=1;
	Uart->uartcr.bits.UARTEN=1;

	Hal_interrupt_enable(UART_INTERRUPT0);
	Hal_interrupt_register_handler(interrupt_handler, UART_INTERRUPT0);
}

void Hal_uart_put_char(uint8_t ch)
{
	while(Uart->uartfr.bits.TXFF);
	Uart->uartdr.all = (ch & 0xFF);
}

uint8_t Hal_uart_get_char(void)
{
	uint8_t data;

	while(Uart->uartfr.bits.RXFE);

	//check for and error flag
	if (Uart->uartdr.all & 0xFFFFFF00)
	{
		//clear the error
		Uart->uartrsr.all = 0xFF;
		return 0;
	}

	data = Uart->uartdr.bits.DATA;
	return data;
}

static void interrupt_handler()
{
	uint8_t ch = Hal_uart_get_char();
	Hal_uart_put_char(ch);
}

 

UART 하드웨어와 인터럽트 컨트롤러를 연결하면

main 함수도 수정

 

100회 풀링 방식 입력 대신 인터럽트 처리하도록 무한루프 돌리고

하드웨어 초기화 부분에 인터럽트 초기화 추가

 

#include <stdint.h>

#include "HalInterrupt.h"
#include "HalUart.h"
#include "stdio.h"

static void Hw_init(void);
static void	Printf_test(void);

void main(void)
{
	Hw_init();

	uint32_t i = 100;
	while(i--)
	{
		Hal_uart_put_char('N');
	}
	Hal_uart_put_char('\n');
	putstr("Hello World!\n");

	Printf_test();

	while(1);
}

static void Hw_init(void)
{
	Hal_interrupt_init();
	Hal_uart_init();
}

static void Printf_test(void)
{
	char* str = "printf pointer test";
	char* nullptr = 0;
	uint32_t i = 5;

	debug_printf("%s\b", "Hello printf");
	debug_printf("output string pointer: %s\n", str);
	debug_printf("%s is null pointer, %u number\n", nullptr, 10);
	debug_printf("%u=5\n", i);
	debug_printf("dec=%u hex=%x\n", 0xff, 0xff);
	debug_printf("print zero %u\n", 0);
}

 

ARM 코어가 인터럽트 받음(여기선 IRQ만 사용)

-> IRQ 익셉션 발생

-> IRQ 동작 모드로 전환

-> 익셉션 벡터 테이블의 IRQ 익셉션 벡터로 점프해서 실행

 

<-> 하지만 아직 IRQ 익셉션 벡터와 인터럽트 핸들러가 연결안됨

=> 익셉션 벡터 테이블 IRQ 익셉션 벡터와 인터럽트 컨트롤러 인터럽트 핸들러 연결할 차례

 

 

boot/Handler.c

익셉션 핸들러

__attribute__는 GCC의 컴파일러 확장기능 사용을 의미하는 지시어

__attribute__ ((interrupt ("IRQ")))는 IRQ 핸들러에 진입, 나오는 코드를 자동으로 만들어줌

* hal interrupt run handler는 현재 인터럽트 번호를 가져와 해당 인터럽트 핸들러 호출

#include <stdbool.h>
#include <stdint.h>
#include "HalInterrupt.h"

__attribute__ ((interrupt ("IRQ"))) void Irq_Handler(void)
{
	Hal_interrupt_run_handler();
}

__attribute__ ((interrupt ("FIQ"))) void Fiq_Handler(void)
{
	while(true);
}

 

 

boot/Entry.S  익셉션 벡터 테이블

irq_handler_addr과 fiq_handler_add을

Irq_Handler와 Fiq_Handler로 연결

 

reset_handler의 main을 통해 메인함수에 진입해서 동작하다가

arm 코어가 인터럽트 받으면 하드웨어적으로 irq 익셉션 발생해서 동작모드 바꾸고

irq handler로 넘어가 인터럽트 수행 후 돌아온다는 건가 싶다.

 

#include "ARMv7AR.h"
#include "MemoryMap.h"

.text
	.code 32

	.global vector_start
	.global vector_end

	vector_start:
		LDR		PC, reset_handler_addr
		LDR		PC, undef_handler_addr
		LDR		PC, svc_handler_addr
		LDR		PC, pftch_abt_handler_addr
		LDR		PC, data_abt_handler_addr
		B	.
		LDR		PC, irq_handler_addr
		LDR		PC, fiq_handler_addr

		reset_handler_addr:		.word reset_handler
		undef_handler_addr:		.word dummy_handler
		svc_handler_addr:		.word dummy_handler
		pftch_abt_handler_addr:	.word dummy_handler
		data_abt_handler_addr:	.word dummy_handler
		irq_handler_addr:		.word Irq_Handler
		fiq_handler_addr:		.word Fiq_Handler
	vector_end:

	reset_handler:
		MRS r0, cpsr
		BIC r1, r0, #0x1F
		ORR r1, r1, #ARM_MODE_BIT_SVC
		MSR cpsr, r1
		LDR sp, =SVC_STACK_TOP

		MRS r0, cpsr
		BIC r1, r0, #0x1F
		ORR r1, r1, #ARM_MODE_BIT_IRQ
		MSR cpsr, r1
		LDR sp, =IRQ_STACK_TOP

		MRS r0, cpsr
		BIC r1, r0, #0x1F
		ORR r1, r1, #ARM_MODE_BIT_FIQ
		MSR cpsr, r1
		LDR sp, =FIQ_STACK_TOP

		MRS r0, cpsr
		BIC r1, r0, #0x1F
		ORR r1, r1, #ARM_MODE_BIT_ABT
		MSR cpsr, r1
		LDR sp, =ABT_STACK_TOP

		MRS r0, cpsr
		BIC r1, r0, #0x1F
		ORR r1, r1, #ARM_MODE_BIT_UND
		MSR cpsr, r1
		LDR sp, =UND_STACK_TOP

		MRS r0, cpsr
		BIC r1, r0, #0x1F
		ORR r1, r1, #ARM_MODE_BIT_SYS
		MSR cpsr, r1
		LDR sp, =USRSYS_STACK_TOP

		BL main

	dummy_handler:
		B .
.end

 

 

다 잘 작성한거 같은데

키보드 입력 안되길래 보니 

Uart.c에서 입력 인터럽트 인에이블 시키는걸 빠트렸다.

Uart->uartimsc.bits.RXIM

그리고 계속 키보드 입력을 줘도 값이 밀렸엇는데 다시보니 최적화된 걸 안써서 그렇더라

 

수정한 hal/rvpb/Uart.c

#include <stdint.h>
#include "Uart.h"
#include "HalUart.h"
#include "HalInterrupt.h"

extern volatile PL011_t* Uart;

static void interrupt_handler(void);

void Hal_uart_init(void)
{
	//enable UART
	Uart->uartcr.bits.UARTEN=0;
	Uart->uartcr.bits.TXE=1;
	Uart->uartcr.bits.RXE=1;
	Uart->uartcr.bits.UARTEN=1;

	Uart->uartimsc.bits.RXIM=1;

	Hal_interrupt_enable(UART_INTERRUPT0);
	Hal_interrupt_register_handler(interrupt_handler, UART_INTERRUPT0);
}

void Hal_uart_put_char(uint8_t ch)
{
	while(Uart->uartfr.bits.TXFF);
	Uart->uartdr.all = (ch & 0xFF);
}

uint8_t Hal_uart_get_char(void)
{
	uint32_t data;

	while(Uart->uartfr.bits.RXFE);

	data = Uart->uartdr.all;

	//check for and error flag
	if (data & 0xFFFFFF00)
	{
		//clear the error
		Uart->uartrsr.all = 0xFF;
		return 0;
	}

	return (uint8_t)(data & 0xff);
}

static void interrupt_handler(void)
{
	uint8_t ch = Hal_uart_get_char();
	Hal_uart_put_char(ch);
}

 

 

 

 

 

 

이젠 uart 인터럽트도 잘 동작한다.

 

 

현재 소스트리

 

'컴퓨터과학 > os' 카테고리의 다른 글

navilos - 12. 태스크  (0) 2022.08.22
navilos - 11. 타이머로 delay 구현  (0) 2022.08.21
나빌로스 하다가 찾은 문제  (0) 2022.08.20
navilos - 9. UART 3 printf 구현하기  (0) 2022.08.20
navilos - 8. UART 2 putstr,getchar  (0) 2022.08.20

+ Recent posts