인터럽트 사용하기

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

1.

stdint.h 만드는 내용이 없어서

그냥 <stdint.h>로 가져와서 썻다.

 

84p

책따라 하면 printf_buf를 만드는 부분이 없다.

 

https://github.com/navilera/Navilos/blob/master/lib/stdio.c

위 링크를 보니 맨 위에 길이 정의하고, static 변수 sprintf_buf로 선언한게 있길래 참고해서 수정

 

5장 빌드 중

NULL 타입 없다길래

<stddef.h> 추가.

 

 

 

8장 136p

task.c 가 아니라 task.h

 

10장 컨텍스트 스위칭에서

컨텍스트 스위칭 관련 함수들을 어느 경로에 어떤 이름으로 만들어야 하는지가 없다.

깃헙 보니 lib/아키텍처/swtich.c/h인건 확인

 

printf 함수는 다양한 포멧으로 출력을 하는 만큼 복잡해서 여러 단계로 만듬

우선 debug_printf 부터

 

 

lib/stdio.h에 debug_printf 함수 추가

...는 가변인자

#ifndef LIB_STDIO_H_
#define LIB_STDIO_H_

uint32_t putstr(const char* s);
uint32_t debug_printf(const char* format, ...);

#endif

 

 

lib/stdio.c에 debug_printf 함수 구현

가변인자 처리를 위한 va_list, va_start, va_end가 추가되고

형식 지정자 처리는 vsprintf에서 수행

#include <stdint.h>
#include "HalUart.h"
#include "stdio.h"

uint32_t putstr(const char* s)
{
	uint32_t c =0;
	while(*s)
	{
		Hal_uart_put_char(*s++);
		c++;
	}
	return c;
}

uint32_t debug_printf(const char* format, ...)
{
	va_list args;
	va_start(args, format);
	vsprintf(printf_buf, format, args);
	va_end(args);
	
	return putstr(printf_buf);
}

include/stdarg.h 에 

가변인자 처리를 위한 컴파일러 빌트인 함수들을 재정의

va_ ~ 는 stdio lib가 아니라 컴파일러 빌트인 함수

#ifndef INCLUDE_STDARG_H_
#define INCLUDE_STDARG_H_

typedef __builtin_va_list va_list;

#define va_start(v, l)	__builtin_va_start(v,l)
#define va_end(v)		__builtin_va_end(v)
#define va_arg(v,l)		__builtin_va_arg(v,l)

#endif

 

 

이제 vsprintf를 구현하면 먼저 lib/stdio.h

#ifndef LIB_STDIO_H_
#define LIB_STDIO_H_

uint32_t putstr(const char* s);
uint32_t debug_printf(const char* format, ...);
uint32_t vsprintf(char* buf, const char* format, va_list arg);

#endif

 

 

lib/stdio.c에 vsprintf 구현

 

%c, &s, %u, %x  기능 제공

 

uint32_t vsprintf(char* buf, const char* format, va_list arg)
{
	uint32_t	 c =0;

	char		ch;
	char*		str;
	uint32_t	uint;
	uint32_t	hex;

	for(uint32_t i=0; format[i] ; i++)
	{
		if(format[i] == '%')
		{
			i++;
			switch(format[i])
			{
				case 'c':
					ch = (chat)va_arg(arg,int32_t);
					buf[c++] = ch;
					break;
				case 's':
					str = (char*)va_arg(arg, char*);
					if (str == NULL)
					{
						str = "(NULL)";
					}
					while(*str)
					{
						buf[c++] = (*str++);
					}
					break;
				case 'u':
					uint = (uint32_t)va_arg(arg, uint32_t);
					c += utoa(&buf[c], uint, utoa_dec);
					break;
				case 'x':
					hex = (uint32_t)va_arg(arg, uint32_t);
					c += utoa(&buf[c], hex, utoa_hex);
					break;
			}
		}
		else
		{
			buf[c++] = format[i];
		}
	}

	if (c >= PRINTF_BUF_LEN)
	{
		buf[0] = '\0';
		return 0;
	}

	buf[c] = '\0';
	return c;
}

 

%u, %x에서 utoa 함수를 쓰고 있는데

말그대로 부호없는 정수를 문자열로 바꾸는 내용

 

lib/stdio.h 에 선언부터

#ifndef LIB_STDIO_H_
#define LIB_STDIO_H_

#include "stdarg.h"

typedef enum utoa_t
{
	utoa_dec = 10,
	utoa_hex = 16
}utoa_t;

uint32_t putstr(const char* s);
uint32_t debug_printf(const char* format, ...);
uint32_t vsprintf(char* buf, const char* format, va_list arg);
uint32_t utoa(char* buf, uint32_t val, utoa_t base);

#endif

 

 

중간에 t >= 10인 경우는 base가 16일때 11 ~ 15 인 경우이므로

ABCDEF로 만드는 거 때문에 

'0'을 어짜피 뺏다 다시 더하므로 무시해서 보면

'A'를 기준으로 A는 10일 떄이므로 -10했을떄

t=11이라면 t = 1이 될것이고

'A' + 1 = 'B'로 출력하라는 식인거같다.

말이 좀 이상한데 16진수 부분 출력하기 위해 이렇게 표현한거같다. 

 

uint32_t utoa(char* buf, uint32_t val, utoa_t base)
{
	uint32_t	c	= 0;
	int32_t		idx	= 0;
	char		tmp[11];

	do
	{
		uint32_t t =val % (uint32_t)base;
		if(t >= 10)
		{
			t += 'A' - '0' - 10;
		}
		tmp[idx] = (t + '0');
		val /= base;
		idx++;
	}while(val);

	//reverse
	idx--;
	while (idx > =0)
	{
		buf[c++] = tmp[idx];
		idx--;
	}
	return c;
}

 

테스트 코드 작성하고

#include <stdint.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();

	i = 100;
	while(i--)
	{
		uint8_t ch = Hal_uart_get_char();
		Hal_uart_put_char(ch);
	}
}

static void Hw_init(void)
{
	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);
}

 

 

 

 

 

하드웨어 문제로 나머지, 나눗셈 연산 때문에 makefile 변경 

LD를 gcc로 변경

LDFLAGS 표준 시작 파일, 표준 라이브러리, 디폴트 파일 등 사용 안함 추가 -lgcc는 잘모르겠다.

링킹 부분도 LDFLAGS 추가

 

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)	\
		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 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 $@ $<

 

 

빌드 진행하다보니

printf_buf가 없는데 책에 빠진부분을 깃헙 참고해서

lib/stdio.c 맨 위에 추가하고

 

NULL 타입 얘기도 나오길래 그냥 lib/stdio.c에 <stddef.h> 도 추가

 

수정한 lib/stdio.c 코드

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

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

#define PRINTF_BUF_LEN 1024

static char printf_buf[PRINTF_BUF_LEN];

uint32_t putstr(const char* s)
{
	uint32_t c =0;
	while(*s)
	{
		Hal_uart_put_char(*s++);
		c++;
	}
	return c;
}

uint32_t debug_printf(const char* format, ...)
{
	va_list args;
	va_start(args, format);
	vsprintf(printf_buf, format, args);
	va_end(args);
	
	return putstr(printf_buf);
}

uint32_t vsprintf(char* buf, const char* format, va_list arg)
{
	uint32_t	 c =0;

	char		ch;
	char*		str;
	uint32_t	uint;
	uint32_t	hex;

	for(uint32_t i=0; format[i] ; i++)
	{
		if(format[i] == '%')
		{
			i++;
			switch(format[i])
			{
				case 'c':
					ch = (char)va_arg(arg,int32_t);
					buf[c++] = ch;
					break;
				case 's':
					str = (char*)va_arg(arg, char*);
					if (str == NULL)
					{
						str = "(NULL)";
					}
					while(*str)
					{
						buf[c++] = (*str++);
					}
					break;
				case 'u':
					uint = (uint32_t)va_arg(arg, uint32_t);
					c += utoa(&buf[c], uint, utoa_dec);
					break;
				case 'x':
					hex = (uint32_t)va_arg(arg, uint32_t);
					c += utoa(&buf[c], hex, utoa_hex);
					break;
			}
		}
		else
		{
			buf[c++] = format[i];
		}
	}

	if (c >= PRINTF_BUF_LEN)
	{
		buf[0] = '\0';
		return 0;
	}

	buf[c] = '\0';
	return c;
}

uint32_t utoa(char* buf, uint32_t val, utoa_t base)
{
	uint32_t	c	= 0;
	int32_t		idx	= 0;
	char		tmp[11];

	do
	{
		uint32_t t =val % (uint32_t)base;
		if(t >= 10)
		{
			t += 'A' - '0' - 10;
		}
		tmp[idx] = (t + '0');
		val /= base;
		idx++;
	}while(val);

	//reverse
	idx--;
	while (idx >= 0)
	{
		buf[c++] = tmp[idx];
		idx--;
	}
	return c;
}

 

빌드 하고 돌린 결과

테스트 코드가 잘 나왔다 !

 

 

 

 

 

소스트리 현황

 

 

이제 uart를 통해 터미널에서 N 100개 찍히는걸 확인했으니

이제 pritntf 함수를 구현해 hello world 출력을 구현한다.

 

printf는 stdio.h에 가져와서 쓰고 일단 putstr부터 만들면

 

lib/stdio.h

#ifndef LIB_STDIO_H_
#define LIB_STDIO_H_

uint32_t putstr(const char* s);

#endif

 

lib/stdio.c

#include <stdint.h>
#include "HalUart.h"
#include "stdio.h"

uint32_t putstr(const char* s)
{
	uint32_t c =0;
	while(*s)
	{
		Hal_uart_put_char(*s++);
		c++;
	}
	return c;
}

 

 

make file에다가도 추가된 lib 폴더와 stdio.c/.h 파일추가해준뒤

ARCH=armv7-a
MCPU=cortex-a8

TARGET=rvpb

CC=arm-none-eabi-gcc
AS=arm-none-eabi-as
LD=arm-none-eabi-ld
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)	\
		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 lib

CFLAGS = -c -g -std=c11

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) -Map=$(MAP_FILE)
	$(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 $@ $<

 

 

 

boot/Main.c도 수정

#include <stdint.h>

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

static void Hw_init(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");
}

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

 

빌드하면 메인 함수에 작성한대로

Hello world가 잘 출력되었다.

 

 

 

이번에는 getchar를 구현할 차례인대

송신 버퍼가 비워진지 확인하고, 데이터 레지스터에 값을 넣는 식으로 송신했듯이

이번에는 수신 버퍼가 채워진지 체크하고,

체워지면 데이터레지스터에서 값을 받아오면 되는 식이다.

 

 

hal/rvpb/Uart.c와

hal/HalUart.h에 get char 관련 함수를 추가해주자.

 

수신 버퍼가 채워지면, 에러있는경우 클리어해주고 아니면 데이터를 받아와 반환해주는 내용이다.

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;
}

hal/HalUart.h

#ifndef HAL_HALUART_H_
#define HAL_HALUART_H_

void Hal_uart_init(void);
void Hal_uart_put_char(uint8_t ch);
uint8_t Hal_uart_get_char(void);

#endif /* HAL_HAALUART_H_ */

 

main.c도 입력받은 뒤 에코하도록 수정해서 보면

#include <stdint.h>

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

static void Hw_init(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");

	i = 100;
	while(i--)
	{
		uint8_t ch = Hal_uart_get_char();
		Hal_uart_put_char(ch);
	}
}

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

 

 

 

빌드하고 돌린뒤 키보드 입력을 주면 딜레이가 좀 있긴하지만 동작하긴한다.

 

 

 

 

드디여 하드웨어 장치를 다루게 될 차례인데

가장 먼저 UART를 소개하고 있다.

 

이유는 UART로 콘솔 입출력을 하기 위함이며

지금 QEMU에서 사용하는 징차인 RealViewPB에는 PL011이라는 UART 하드웨어를 사용하고 있다고 한다.

 

가장 먼저 할 일은 레지스터를 코드로 구현한다는데

레지스터 헤더를 구조체로 추상화해서 쓴걸 깃헙에서 다운받아서 쓰라고 한다.

 

hal/rvpb/Uart.h 인데

리얼뷰PB에 종속적인 코드라

여러 플랫폼을 고려해서 디렉토리를 이런 구조로 구현한다고 한다.

 

https://github.com/navilera/Navilos/blob/master/hal/rvpb/Uart.h

 

GitHub - navilera/Navilos: RTOS for various embedded platform

RTOS for various embedded platform. Contribute to navilera/Navilos development by creating an account on GitHub.

github.com

 

 

헤더를 만들고

UART 하드웨어를 제어할 변수 코드

hal/rvpb/Regs.c

구조체 접근 규칙으로 접근하면 되니 베이스 주소만 가져오면되나보다.

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

volatile PL011_t* Uart = (PL011_t*)UART_BASE_ADDRESS0;

 

 

지금 계속 HAL 폴더에서 작업하고 있는데 

Hardware Abstraction Layer의 약어로

각 하드웨어 플랫폼마다 구현 방식은 다르더라도

공통적인 API를 통해 사용할수 있도록 추상화한 계층이라고 한다.

 

지금까지는 rvpb에 종속적인 코드를 구현했지만

이번에는 여러 하드웨어에서 사용할수 있는 공용 HAL 인터페이스를 구현하면

 

hal/HalUart.h

라즈베리파이든, realviewpb이든 각 플랫폼 별로 구현해두면 다양한 UART 하드웨어를

공용 인터페이스로 쓴다는 내용인거 같고

uart를 쓰려면 초기화해야되느 init과 가장 간단하게 쓰기 위해 putchar

타깃 플랫폼이 rvpb니 hal/rvpb/Uart.c에서 이 두 공용 인터페이스를 구현하는데

#ifndef HAL_HALUART_H_
#define HAL_HALUART_H_

void Hal_uart_init(void);
void Hal_uart_put_char(uint8_t ch);

#endif /* HAL_HAALUART_H_ */

 

hal/rvpb/Uart.c

rvpb의 Uart 구현코드는 이런데

PL011_t 타입의 Uart 포인터 변수의

uartcr이란 구조체에 접근해서 안에 있는 UARTEN = 0한 상태에서

TXE=1, RXE=1 로 송수신 인에이블해주고

설정을 마치면 다시 UARTEN=1으로 인에이블 해준다는 소린거 같다.

 

 

아래의 putchar 로직은

uart 출력 버퍼 (uartfr.bits.txff)가 0이되면 = 출력버퍼가 비워지면

uartdr.all = (ch & 0xff), dr 데이터 레지스터로 내보낸다는 의미라고한다.

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

extern volatile PL011_t* Uart;

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;
}

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

 

 

일단 init 부분만 좀 더보면

rvpb의 Uart.h에서 PL011_t는 다음의 형태를 가지고 있다.

typedef struct PL011_t
{
    UARTDR_t    uartdr;         //0x000
    UARTRSR_t   uartrsr;        //0x004
    uint32_t    reserved0[4];   //0x008-0x014
    UARTFR_t    uartfr;         //0x018
    uint32_t    reserved1;      //0x01C
    UARTILPR_t  uartilpr;       //0x020
    UARTIBRD_t  uartibrd;       //0x024
    UARTFBRD_t  uartfbrd;       //0x028
    UARTLCR_H_t uartlcr_h;      //0x02C
    UARTCR_t    uartcr;         //0x030
    UARTIFLS_t  uartifls;       //0x034
    UARTIMSC_t  uartimsc;       //0x038
    UARTRIS_t   uartris;        //0x03C
    UARTMIS_t   uartmis;        //0x040
    UARTICR_t   uarticr;        //0x044
    UARTDMACR_t uartdmacr;      //0x048
} PL011_t;

 

PL011_t의 각 공용체 의미는 데이터 시트를 제대로 봐야하니 넘어가고

아까 init에서 쓴 uarticr만 보면

이런식으로 되어있다. 데이터시트 없이 무슨뜻인지 알겠는 부분도 있지만 안봐서 제대로는 모르겠다.

typedef union UARTCR_t
{
    uint32_t all;
    struct {
        uint32_t UARTEN:1;      // 0
        uint32_t SIREN:1;       // 1
        uint32_t SIRLP:1;       // 2
        uint32_t Reserved1:4;   // 6:3
        uint32_t LBE:1;         // 7
        uint32_t TXE:1;         // 8
        uint32_t RXE:1;         // 9
        uint32_t DTR:1;         // 10
        uint32_t RTS:1;         // 11
        uint32_t Out1:1;        // 12
        uint32_t Out2:1;        // 13
        uint32_t RTSEn:1;       // 14
        uint32_t CTSEn:1;       // 15
        uint32_t reserved2:16;
    } bits;
} UARTCR_t;

 

 

이제 UART 코드를 구현했으니 

boot/Main.c에서 uart를 쓰도록 구현하면

 

이런 식이고

굳이 Hw_init을 추가한건

지금은 uart 초기화만 하고 있지만 나중에 다른 하드웨어 초기화 코드도 같이 포함시켜서

이 함수 하나로 동작시키기 위한다고 한다.

 

코드 내용은 간단하게 N을 100번 출력

#include <stdint.h>

#include "HalUart.h"

static void Hw_init(void);

void main(void)
{
	Hw_init();

	uint32_t i = 100;
	while(i--)
	{
		Hal_uart_put_char('N');
	}
}

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

 

 

이제 hal api랑 rvpb에 종속적인 코드를 추가했으니 이를 반영하도록

Makefile을 수정하는데

 

Makefile 복습해야하는 부분들좀 다시보긴 해야할거같다.

인글루드 디렉토리가 여러개 늘었고

CFLAGS가 따로 빠진거

run 시 -nographic 옵션을 주는데 이러면 시리얼 포트가 콘솔과 연결되서 바로 볼수 있다고 한다.

 

 

ARCH=armv7-a
MCPU=cortex-a8

TARGET=rvpb

CC=arm-none-eabi-gcc
AS=arm-none-eabi-as
LD=arm-none-eabi-ld
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)

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

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

CFLAGS = -c -g -std=c11

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) -Map=$(MAP_FILE)
	$(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 $@ $<

 

 

그래서 런을 돌리면 바로 아까 메인함수에 구현한대로

uart를 초기화하고 N이 100번출력하는 결과가 나온다.

 

 

소스 트리 구조

 

 

이제 리셋 익셉션 핸들러 구현하면

 

#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 dummy_handler
		fiq_handler_addr:		.word dummy_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

	dummy_handler:
		B .
.end

 

 

어셈블리를 제대로 공부하지 않아서

여기서 사용되는 명령어들을 좀 찾아봤다.

 

 

 

 

ARM 어셈블리 일부 문법

- 상수는 #이 들어감 ( LDR에는 대신 = 사용)

니모닉 설명 예시 비고
LDR 레지스터 로드 명령 LDR sp, =USRSYS_STACK_TOP  
ORR 논리 OR ORR r1, r1, #ARM_MODE_BIT_SYS  
MSR 레지스터에서 PSR로 이동 MSR cpsr, r1  
MRS PSR에서 레지스터로 이동 MRS r0, cpsr  
BIC 해당되는 비트 클리어 BIC r1, r0, #0x1F R1 = R0 AND ~ 0x10
B 분기    

 

ref 

https://0xsaika.tistory.com/5

 

ARM assembly 문법정리

니모닉 간단한 설명 페이지 아키텍처[1] ADC , ADD carry 포함 더하기, 더하기 ADD, SUB, RSB, ADC, SBC 및 RSC 모두 ADR 프로그램 또는 레지스터 기준 주소 로드(짧은 범위) ADR 모두 ADRL  의사 명령어 프로그..

0xsaika.tistory.com

https://kyuhyuk.kr/article/raspberry-pi/2019/05/15/ARM-Assembly

 

ARM Assembly 기초

ARM CPU의 기본 구성 R0 ~ R14 총 15개의 범용 레지스터를 가지고 있습니다. 범용 레지스터 R13 는 특수 레지스터 SP 로 사용됩니다. SP 는 C언어 사용시 스택의 주소를 저장하는 레지스터입니다. 범용

kyuhyuk.kr

http://trace32.com/wiki/index.php/AND,_ORR,_EOR,_BIC_and_ORN

 

AND, ORR, EOR, BIC and ORN - TRACE32

이번에는 비트연산 명령어인 AND, ORR, EOR, BIC, ORN 명령에 대해 알아보겠습니다. AND : Logical AND 두 개의 비트가 모두 "1"이면 "1", 하나라도 "0"이면 "0"이 나오는 AND연산을 수행합니다. 아래의 "ANDS R6,R6,

trace32.com

 

 

 

 

 

동작 모드 전환하는 부분을 보면

1. MSR r0, cpsr

r0에다가 cpsr 저장

 

2. BIC r1, r0, #0x1F

r1 = r0 AND ~0x1F

 

3. ORR r1, r1 #ARM_MODE_BIT_UND

r1과 #ARM_MODE_BIT_UND 그러니까 0x1B와 or 연산

 

4. MSR cpsr, r1

r1의 값을 cpsr에 대입

 

5. LDR sp, =UND_STACK_TOP

스택 포인터 값을 UND_STACK_TOP (=UND_STACK_START + UND_STACK_SIZE - 4= 0x007F 0000)으로 변경

=> 대강 cpsr의 모드 비트를 UND 모드로 바꾸고 스택 포인터도 UND 스택 탑으로 바꾸는 내용

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

 

 

정리하면 위 어셈블리 코드의 리셋 핸들러 부분은

모든 모드를 돌아가면서 스택 탑 주소를 sp에 넣어보는 동작인듯 하다.

 

 

ARCH=armv7-a
MCPU=cortex-a8

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

LINKER_SCRIPT=./navilos.ld

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

INC_DIRS=include

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)

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

gdb:
	gdb-multiarch

$(navilos):$(ASM_OBJS) $(LINKER_SCRIPT)
	$(LD) -n -T $(LINKER_SCRIPT) -o $(navilos) $(ASM_OBJS)
	$(OC) -O binary $(navilos) $(navilos_bin)

build/%.o: boot/%.S
	mkdir -p $(shell dirname $@)
	$(CC) -march=$(ARCH) -mcpu=$(MCPU) -I $(INC_DIRS) -c -g -o $@ $<
djc4223@ubuntu:~/github/navilos$

헤더 파일 추가하는 내용이랑

as 대신 gcc,  $(CC)

그리고 실행 파일이 아닌 오브젝트 파일이 나와야 링커에 전달할 수 있으니 -c

 

그런데 AS을 쓸때랑 달리 mcpu랑 march랑 충돌한다고 워닝이 발생해서

 

 

 

march 부분을 빼서 빌드시켰더니 이상이 없다.

 

디버그 할수 있도록 돌리고

 

 

디버거 연결하고

 

navilos.axf 심볼 로드하고

list를 치면 어셈블리 코드가 보인다.

 

 

가장 먼저 

vector_start

reset_handler_addr

통해서

MRS r0, cpsr 부터 쭉쭉 가고

36번줄 명령어 실행 후 

info register를 치면

 

지금 SVC_STACK_TOP이 sp에 들어가 있고

지금 그 sp 값이 0x3ffffc인데 이게 맞는건지 잘 모르겠다.

 

 

다시 매모리맵을 정리하면 이렇게 된다.

가장 먼저 SVC_STACK_TOP을 찾았는데

SVC 스택은 0x0030 0000 ~ 0x003F FFFF의 범위를 갖는다.

스택은 위에서부터 내려오니 스택포인터는 0x003F FFFF일것이나

다른 스택과 패딩 4바이트 정도 줘서 -4 한 위치인 0x003F FFFC가 SVC_STACK_TOP이 된다.

 

 

그다음 로드한 IRQ_STACK_TOP도

0x004f ffff에서 -4한 0x004f fffc 위 그림에다가는 잘못 적었다.

 

FIQ 모드, 마지막에 오는 USRSYS 모드도

정상적으로 잡힌다.

 

 

 

 

 

 

드디여 메인함수에 진입할 차례

boot/Main.c를 작성하는데

책에서는 stdint.h를 include 디렉토리에다가 넣어두고 ""로 가져오는걸로 적혀있긴한데

실제 코드 내용은 없어서 대신 <>를 썻더니 아무이상없이 가져와지고

 

내용은 1024 * 1024 * 100한 주소에다가 

sizeof(long)한 값을 넣는 내용이다.

 

32비트 ARM 머신이므로 int가 2바이트 long은 4바이트니 4가 들어가고,

1024 * 1024 * 100은 100mb 지만 계산결과인 104,857,600를

16진수로 바꾸면 0x640 0000이 된다.

 

0x0640 0000 번지에 4를 입력하는 코드가 되고

 

#include <stdint.h>

void main(void)
{
	uint32_t* dummyAddr = (uint32_t*)(1024 * 1024 * 100);
	*dummyAddr = sizeof(long);
}

 

boot/Entry.S는

대부분 그대로이지만 리셋 핸들러 마지막에

BL main이 추가되어 있어서

 

main 라벨로 넘어가게 된다. 아래 링크를 보니 

BL이 B 처럼 분기 하되 분기 끝나고 돌아오는 내용까지 포함된거라고 한다

그 뒤에는 dummy handler로 무한루프

https://blog.daum.net/tlos6733/132

 

[Branch] 01 - ARM Branch 명령어 분석

작성자 : 박진범 메일 : jinb.park7@gmail.com :) Goal - b, bl, blx instruction 의 opcode 를 확인한다. 바이너리 파일에서 b, bl, blx instruction 을 해석할 수 있게 한다. - bl, blx 의 차이점을 안다. - bl..

blog.daum.net

 

 

 

#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 dummy_handler
		fiq_handler_addr:		.word dummy_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

 

 

Main.c의 메인 함수로 점프하게 되는데

컴파일러는 C언어 함수명을 자동으로 전역 심벌로 만들어서

Entry.S의 BL main을 통해 메인 함수로 넘어간다고 한다.

 

 

C 언어 파일도 컴파일, 링킹하는 내용도 추가하고

ARCH=armv7-a
MCPU=cortex-a8

CC=arm-none-eabi-gcc
AS=arm-none-eabi-as
LD=arm-none-eabi-ld
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))

C_SRCS=$(wildcard boot/*.c)
C_OBJS=$(patsubst boot/%.c, build/%.o, $(C_SRCS))

INC_DIRS=include

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)

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) -Map=$(MAP_FILE)
	$(OC) -O binary $(navilos) $(navilos_bin)

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

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

 

 

 

디버거로 확인해보니

메인함수에 작성한대로 0x0640 0000번지에 4라는 값이 들어가 있는걸 확인할 수 있다.

메인함수 진입 끝

 

 

 

지난 글에서 ARM 코어는 전원 인가시 가장 먼저 0x00에 위치한 리셋 익셉션 핸들러를 호출한다고 했던거 같은데

리셋 익셉션 핸들러는 메모리 맵을 우선 설정해줘야 한다고 한다.

동작 모드별 메모리맵 설정이 끝난 후 C 언어 main으로 진입

 

https://ggamji.tistory.com/113

 

navilos - 3. 메모리맵

방학 하는 동안 노느라 너무 오랜만이라 잘 생각나지않는데 이번에는 부팅 과정을 다룬다. 부팅 하드웨어 초기화 과정 정도로 이해하고 있었는데 리셋 익셉션 핸들러 처리 후 C언어로 넘어가기

ggamji.tistory.com

 

 

위 글에서 ARM의 7개의 동작 모드와 메모리 맵을 그렸는데 

리셋 익셉션 핸들러에서 각 동작 모드를 돌아가며 스택을 초기화 시키도록 코드를 구현하자

 

#define	INST_ADDR_START		0
#define	USRSYS_STACK_START	0x00100000
#define	SVC_STACK_START		0x00300000
#define	IRQ_STACK_START		0x00400000
#define	FIQ_STACK_START		0x00500000
#define	ABT_STACK_START		0x00600000
#define	UND_STACK_START		0x00700000
#define	TASK_STACK_START	0x00800000
#define	GLOBAL_ADDR_START	0x04800000
#define	DALLOC_ADDR_START	0x04900000

#define	INST_MEM_SIZE		(USRSYS_STACK_START - INST_ADDR_START)
#define	USRSYS_STACK_SIZE	(SVC_STACK_START - USRSYS_STACK_START)
#define SVC_STACK_SIZE		(IRQ_STACK_START - SVC_STACK_START)
#define	IRQ_STACK_SIZE		(FIQ_STACK_START - IRQ_STACK_START)
#define	FIQ_STACK_SIZE		(ABT_STACK_START - FIQ_STACK_START)
#define	ABT_STACK_SIZE		(UND_STACK_START - ABT_STACK_START)
#define UND_STACK_SIZE		(TASK_STACK_START - UND_STACK_START)
#define	TASK_STACK_SIZE		(GLOBAL_ADDR_START - TASK_START_START)
#define DALLOC_MEM_SIZE		(55 * 1024 * 1024)

#define	USRSYS_STACK_TOP	(USRSYS_STACK_START + USRSYS_STACK_SIZE - 4)
#define	SVC_STACK_TOP		(SVC_STACK_START + SVC_STACK_SIZE - 4)
#define	IRQ_STACK_TOP		(IRQ_STACK_START + IRQ_STACK_SIZE - 4)
#define FIQ_STACK_TOP		(FIQ_STACK_START + FIQ_STACK_SIZE - 4)
#define ABT_STACK_TOP		(ABT_STACK_START + ABT_STACK_SIZE - 4)
#define UND_STACK_TOP		(UND_STACK_START + UND_STACK_SIZE - 4)

include/MemoryMap.h 헤더 파일 구현하고

 

#define ARM_MODE_BIT_USR 0X10
#define	ARM_MODE_BIT_FIQ 0X11
#define	ARM_MODE_BIT_IRQ 0X12
#define	ARM_MODE_BIT_SVC 0X13
#define ARM_MODE_BIT_ABT 0X17
#define ARM_MODE_BIT_UND 0X1B
#define	ARM_MODE_BIT_SYS 0X1F
#define ARM_MODE_BIT_MON 0X16

include/ARMv7AR.h 도 구현

 

 

각 동작 모드별로 비트가 다른데

프로그램 상태 레지스터 PSR에서 이런 상태를 보관하는데

현재 상태를 저장하는 레지스터로 CPSR(Current PSR)

상태를 저장하는 레지스터로 SPSR(Saved PSR)이 존재한다.

 

아래 그림은 cortex-a8의 PSR로

여기서 M[4:0]이 모드비트인데

위 ARMv7AR.h 헤더에 구현한데로 저 자리에 다음의 값이 들어갈때 동작모드가 변경되며,

익셉션이 발생 시 하드웨어가 알아서 바꾼다.

10000 : USR

10001 :FIQ

....

11011 : UND

11111 : SYS

 

 

ARM 동작 모드

- 익셉션과 관련된 동작모드와 관련되지 않은 동작모드가 있음

- User 모드 USR : ARM 상태나 Thumb 상태로 동작하며 일반적으로 이 모드로 동작

- Fast Interrupt 모드 FIQ : FIQ 익셉션 발생시 전환. 이 모드는 ARM 상태서만 동작. 뱅크드 레지스터가짐

- 인터럽트 모드 IRQ : IRQ 익셉션 발생시 전환. ARM 상태와 Thumb 상태 둘다 동작 

- Supervisor 모드 SVC : 운영체제가 시스템 콜 호출하기 위한 보호모드. 호출시 SVC 모드로 변경후 수행. 

- Abort 모드 ABT : 어보트가 발생시 동작

- System 모드 SYS : 사용자 프로세스가 커널 모드가 필요할때 사용. 소프트웨어로 전환 가능

- Undefined 모드 und : 정의되지 않은 명령어 발생시 진입

* 뱅크드 레지스터 : 빠른 처리를 위한 별도 레지스터

 

 

 

 

ARM 레지스터

- 각 동작마다 고유의 뱅크드 레지스터와 공유되는 레지스터가 존재

- 공유되는 레지스터 = 범용 레지스터(R0 ~ R12) + R15(PC) + CPSR

- 모드 별 뱅크드 레지스터는 아래 그림 참조

- R13 : 스택 포인터

- R14 : 링크 레지스터로 서브 루틴 호출시 리턴어드레스 보관

- R15 : 프로그램 카운터

https://ggamji.tistory.com/109

 

navilos - 2. 펌웨어 만들고, 빌드 자동화

ARM 아키텍처의 동작 시작 - 전원 인가시 리셋 벡터 메모리 주소 0x00000000의 명령 실행 시작점 만들기 - boot 폴더에 Entry.S 파일 만들고 다음과 같이 작성 - .text는 .end까지 텍스트 섹션을 의미 - .code

ggamji.tistory.com

 

예전 글에 어셈블 코드 작성하는 내용을 올리긴 했지만 한참 된 내용이라 잘 생각나지는 않았다.

방금 간단하게 어셈블리어에 대해서 공부했지만 여전히 이해되지 않는 부분이있어서 다시 살펴보면

 

 

 

.text
    .code 32

    .global vector_start
    .global vector_end
    
    vector_start:
    	MOV R0, R1
    vector_end:
    	.space 1024, 0
.end

 

.code 32 : 명령어크기는 32비트

.global : C언어의 extern 역활. 외부에서 읽을수 있게

MOV R0, R1 : R1의 값을 R0에

.space 1024, 0 : 현위치에서 1024바이트를 0으로 채우기

 

 

라고 정리를 했었는데

여전히 '.'이 무엇인지  잘 몰라 찾아보니

 

다음 글을 참고했다.

https://stackoverflow.com/questions/60403872/what-is-word-data-and-text-in-assembly

 

What is .word, .data and .text in assembly?

I am trying to learn to get a better understanding with assembler Could someone explain what .data, .word and .text means does in the following code?? I don't get what this is for and what it doe...

stackoverflow.com

 

.text는 텍스트 세그먼트

.data는 변수 선언부 = 데이터 세그먼트

.word 의 경우 16비트 메모리는 2바이트, 32비트 메모리는 4바이트 할당을 

.end는 종료 부분을 나타낸다고 한다.

 

추가로 아래의 C언어 코드를

int array[] = {0x12121212, 0x23232323, 0x34343434, 0x4, 0x5};

어셈블리로 표현하면 아래와 같다고 한다.

array:

  .word  0x12121212, 0x23232323, 0x34343434, 0x4, 0x5

 

 

 

 

다음 내용은 익셉션 벡터 테이블을 구현하는데

.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_hander_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 dummy_handler
		fiq_handler_addr:		.word dummy_handler
	vector_end:

	reset_handler:
		LDR		R0, =0x10000000
		LDR		R1, [R0]
	
	dummy_handler:
		B.
.end

여전히 이해 안되는 부분이 몇가지가 있다.

 

.text에서 시작해서 .end로 끝나는데 .data가 보이지 않는다.

LDR pc, reset_handler_addr 인데 pc에다가 저 주소를 넣는건 알겠지만 저 주소를 입력한 적이없다.

(이건 아직 구현단계가 아니니 넘어가도 될거같고)

 

 

다시 앞에서 보니 

 

global _start

_start: 에서 시작한다 했지만

 

 

이전에 구현한 링커 내용을 보면

ENTRY(vector_start)
SECTIONS
{
	. = 0x0;

	.text :
	{
		*(vector_start)
		*(.text .rodata)
	}
	.data :
	{
		*(.data)
	}
	.bss :
	{
		*(.bss)
	}
}

 

 

ENTRY(vector_start) : 시작점을 vector_start 심벌을 지정해서 _start가 없어도 됬었던거 같다.

'.' 은 위치 카운터인가 역활을 한다 했는데,

. =0x0;  : 첫 섹션(.text)의 위치가 0x0에서 시작한다를 알려주기 위한 용도

 

.text, .data, .bss 순서로 섹션을 메모리에 배치하는데

 .text 안에서는 *(vector_start), *(.text  .rodata) 순으로 배치된다. 정도로 이해가 된다.

 

 

 

 

리셋 벡터

ARM 코어에 전원 인가시 리셋 벡터 0x0000 0000의 명령이 실행하는데 

아래의 코드와 위 링커에 따르면 vector_start는 0x0로 해당 위치에 배치되서

MOV R0, R1이 인가시 바로 실행되고, 그다음에 .space 1024, 0 명령이 수행된다는 소리로 보인다.

.text
    .code 32

    .global vector_start
    .global vector_end
    
    vector_start:
    	MOV R0, R1
    vector_end:
    	.space 1024, 0
.end

 

 

 

 

 

다시 익셉션 벡터 테이블로 돌아와서 보면

.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_hander_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 dummy_handler
		fiq_handler_addr:		.word dummy_handler
	vector_end:

	reset_handler:
		LDR		R0, =0x10000000
		LDR		R1, [R0]
	
	dummy_handler:
		B.
.end

 

vector_start에서 각 핸들러 주소를 담는, 점프하는 내용들이 쭉 있는데

reset_handler_addr:    .word reset_handler는 아래 섹션 처럼 구현한다는 말인거 같다.

dummy_handler 는 B.이 전부인데

 

아래의 링크에 따르면 

b는 무조건 분기이며

dummy_handler:

   B . 

는 결국 dummy_handler로 무조건 점프, 즉 무한루프를 하라는 의미라고한다.

 

https://stackoverflow.com/questions/48084634/what-does-b-mean-in-this-assembly-code

 

What does `b .` mean in this ASSEMBLY code?

So I'm looking into the source code for Redox OS (An operating system made with Rust) just to see if I can learn something. I'm reading the assembly file start.s in the bootloader folder. In the

stackoverflow.com

 

 

 

따로 정리하지 않았는데

RealViewPB의 0x1000 0000의 주소는 ID 레지스터의 역활로 하드웨어에 대한 설명을 하고 있고

여기에는 0x1780500이 들어있어서 위 익셉션 벡터 테이블만 구현한 어셈블리 코드를 빌드해서 실행한걸

디버거로 보면

 

info register (i r) 명령어 결과 r1에 0x1780500이 코드 대로 들어가 있다.

 

 

방학 하는 동안 노느라 너무 오랜만이라 잘 생각나지않는데

이번에는 부팅 과정을 다룬다.

 

부팅

하드웨어 초기화 과정 정도로 이해하고 있었는데

리셋 익셉션 핸들러 처리 후 C언어로 넘어가기 직전의 상태 혹은 펌웨어가 대기하는 상태 정도라고한다.

 

 

QEMU 메모리

디폴트로 128MB 할당

실행파일은 메모리를  text, data, bss 영역으로 나누어 사용

 

메모리 배치

- 고속 저용량 메모리 : text, data(일부 빠른처리 필요한거)

- 저속 대용량 메모리 : data, bss

<-> qemu에선 그냥 구분없이 배치, 실무에선 매우 중요

 

text 영역

- 코드에 관한 부분, 작아도 되지만 1MB 할당.

- 익셉션 벡터 테이블도 text 영역에 포함시킴 -> 0x0000 0000에서 시작

- 크기가 1MB 이므로 0x000F FFFF

2^10 = 1kb

2^20 = 1MB = 2^4 * 2^4 * 2^4 * 2^4 *2^4

 

data 영역과 bss영역 배치 시 고려 사항

- 아래의 사항을 고려하여 순서와 크기를 고려해야함.

- 데이터 형태 : 동작 모드별 스택, 태스크 스택, 전역 변수, 동적 메모리 영역

- 데이터 속성 : 성능 중요한지, 큰 공간이 필요한지, 공유인지

<-> QEMU에선 크게 중요하지 않으니 순서대로 각 동작모드별로 1MB씩 할당

 

동작 모드 

- USR, SYS : 0x0010 0000 ~ 0x002F FFFF(2MB)

- SVC : 0x0030 0000 ~ 0x003F FFFF

- IRQ : 0x0040 0000 ~ 0x004F FFFF

- FIQ : 0x0050 0000 ~ 0x005F FFFF

- ABT : 0x0060 0000 ~ 0x006F FFFF

- UND : 0x0070 0000 ~ 0x007F FFFF

 

ARM 동작모드

- USR : normal program execution mode

- SYS : run privileged operating system tasks

- IRQ (interrupt request) : when a low priority(normal) interrupt is raised

- FIQ : when a high priority(fast) interrupt is raised

- ABT(abort) : Used to handle memory access violations

- UND(undefined instruction) : Used to handled undefined instruction

http://recipes.egloos.com/4985227

 

ARM 동작 Modes - 나는 어느 mode를 써야 하나

 

recipes.egloos.com

 

 

메모리 맵

- 테스크 공간 : 태스크 별 1MB 할당, 최대 64개 -> 64MB

- 전역변수 공간 : 1MB

- 나머지 : 동적 할당

 

 

계속 진행하려고 해도

링커스크립트 부분을 잘 몰라 진행하기가 어렵다

다른거 부터 잠깐하고 돌아와야겠다.

ARM 아키텍처의 동작 시작

- 전원 인가시 리셋 벡터 메모리 주소 0x00000000의 명령 실행

 

 

시작점 만들기

- boot 폴더에 Entry.S 파일 만들고 다음과 같이 작성

- .text는 .end까지 텍스트 섹션을 의미

- .code는 명령어 크기가 32비트를 의미

- .global은 C언어의 extern 역활 => vector_start, vector_end 주소를 외부에서 읽기 가능

- MOV R0, R1 : R1을 R0에 담고

- .space 1024, 0 : 해당 위치부터 1024바이트를 0으로 채우기

 

* hexdump 데이터를 16진수로 표현

컴파일 및 핵스 덤프보기

- arm-none-eabi-as : 어셈블리어 소스 파일 컴파일

- armv7 아키텍처 coretex-a8 코어 cpu(RealViewPB)로 Entry.S의 목적파일을 만듬

- 목적파일에는 심벌 정보를 포함하여 objcopy로 바이너리만 추출 -> hexdump로 바이너리 확인

- 0001 e1a0는 MOV R0, R1

 

 

실행파일 ELF 만들기

- 실행 파일을 만들려면 목적 파일과 링커 필요, 링커에 정보를 전달하는 링커 스크립트 필요

*  운영체제는 링커스크립트제공하나 펌웨어 개발시에는 펌웨어 동작 HW에 맞게 펌웨어 섹션 배치 필요

 

링커 스크립트

- ENTRY 지시어 : 심벌 지정, 1번 줄에선 vector_start 심벌 지정

- SECTION 지시어 : 섹션 배치 설정 정보

- .=0x0 : 첫섹션이 메모리 0x00000000에 위치

- .text : 텍스트 섹션 베치 순서 정보, vector_start 심벌이 나온 후 .text 섹션 배치

- 그 뒤 data 섹션과 bss 섹션 메모리에 배치

 

실행파일 만들기

- arm-none-eabi-ld에 -n 옵션(섹션 자동정렬 no), -T(링커 스트립트 지정), -nostdlib(표준라이브러리 링킹x)

  -> 링킹 결과 navilos.axf 생선

- arm-none-eabi-objdump -D로 디스어셈블 한 결과

 ->  vector_start가 0x00000000에서 시작하며, mov r0, r1 명령어(기계어로 e1a0 0001)가 잘 들어가 있음

 

 

 

QEMU에서 실행하기

-M으로 realview-pb-a8 기계 지정

-kernel로 실행할 elf 지정

-S는 실행하자마자 정지

-gdb tcp::1234,ipv4는 gdb와 연결하도록 소켓 포트 지정

* gdb로 동작하는지 메모리확인하기 위함

 

 

gdb 사용하기

arm-none-eabi-gdb가 안보여서 그냥 gdb-multiarch를 사용했다.

첫 바이트에 0xe1a00001이 나오고 그 뒤에는 0x00000000이 쭉 나오니 잘 올라갔다.

 

 

빌드 자동화 Makefile 작성폴더의 

- 11번 줄 : boot폴더의 *.S 파일들 모두 ASM_SRCS에 담기

- 12번줄 : boot 폴더의 *.S 파일들을 .o로 바꾸고 디렉터리도 build로 변경 + 기존의 ASM_SRCS들

- 33 ~ 35번줄 : navilos.axf, navilos.bin 생성

- 37 ~ 39번줄 : *.S를 *.o로 컴파일

 

빌드 및 실행하기

- 빌드한 결과 build 폴더에 목적 파일과 실행파일이 위치

- make debug로 qemu 실행하며 디버깅 준비

 

+ Recent posts