드디여 하드웨어 장치를 다루게 될 차례인데
가장 먼저 UART를 소개하고 있다.
이유는 UART로 콘솔 입출력을 하기 위함이며
지금 QEMU에서 사용하는 징차인 RealViewPB에는 PL011이라는 UART 하드웨어를 사용하고 있다고 한다.
가장 먼저 할 일은 레지스터를 코드로 구현한다는데
레지스터 헤더를 구조체로 추상화해서 쓴걸 깃헙에서 다운받아서 쓰라고 한다.
hal/rvpb/Uart.h 인데
리얼뷰PB에 종속적인 코드라
여러 플랫폼을 고려해서 디렉토리를 이런 구조로 구현한다고 한다.
https://github.com/navilera/Navilos/blob/master/hal/rvpb/Uart.h
헤더를 만들고
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번출력하는 결과가 나온다.
소스 트리 구조
'컴퓨터과학 > os' 카테고리의 다른 글
navilos - 9. UART 3 printf 구현하기 (0) | 2022.08.20 |
---|---|
navilos - 8. UART 2 putstr,getchar (0) | 2022.08.20 |
navilos - 6. 익셉션 핸들러 구현2, 메인함수 진입 (0) | 2022.08.18 |
navilos - 5. 익셉션 핸들러 구현 1 (0) | 2022.08.17 |
navilos - 4. 어셈블리어 공부하며 본 초기코드 (0) | 2022.08.17 |