github 를 뒤지다가 rom 코드 부터 커널을 step by step 으로 올리는 친절한 예제가 있어, 따라해보며 블로그에 포스팅을 해보려 한다.
원본 게시글
docs : https://quard-star-tutorial.readthedocs.io/zh-cn/latest/
github : https://github.com/QQxiaoming/quard_star_tutorial
간략히 보니, QEMU 빌드부터 진행하는데, 타겟 SoC 의 구성을 셋팅하는 것 부터 시작해서 아키텍쳐 적으로 이해하는데 많은 도움이 될 것 같다.
문서가 중국어로 되어있기 때문에, ai 의 도움을 받아 한국어로 번역을 하면서 첨언할 부분을 추가할 예정이다.
의존성 패키지 설치
sudo apt install ninja-build pkg-config libglib2.0-dev libpixman-1-dev libgtk-3-dev libcap-ng-dev libattr1-dev libsdl2-dev device-tree-compiler bison flex gperf intltool mtd-utils libslirp-dev
QEMU 환경 구축
본 예제에서는 8.0.0 버전을 사용한다. 레포지토리에 빌드에 필요한 소스코드가 포함되어있으므로 별도로 진행할 필요가 없다. 친절하게 build.sh 스크립트에 해당 내용이 명시되어있으므로 그대로 따라한다.
./build.sh qemu
스크립트 내용을 살펴보면 알겠지만, 실행되는 세부 내용은 아래와 같다.
- --prefix=$SHELL_FOLDER/output/qemu:
- QEMU가 설치될 디렉토리를 지정한다.
- --target-list=riscv64-softmmu:
- QEMU가 지원할 타겟 아키텍처를 지정한다. 여기서는 riscv64 아키텍처를 대상으로 하며, softmmu는 소프트웨어 MMU(메모리 관리 유닛)을 사용함을 의미한다. 이는 RISC-V 64비트 하드웨어를 시뮬레이션하는 데 사용된다.
- --enable-gtk:
- GTK+ 그래픽 프론트엔드를 활성화한다. 이를 통해 QEMU의 그래픽 인터페이스를 사용할 수 있다.
- --enable-virtfs:
- VirtFS를 활성화한다. VirtFS는 호스트와 게스트 간의 파일 시스템 공유를 가능하게 하는 기능이다.
- --disable-gio:
- GIO(GNOME Input/Output)를 비활성화한다. GIO는 GLib의 입출력, 파일 시스템, 네트워킹 등의 기능을 제공하는 모듈이다.
- --enable-plugins:
- QEMU 플러그인을 활성화한다. 이를 통해 QEMU에 다양한 기능을 추가할 수 있다.
- --audio-drv-list=pa,alsa,sdl,oss:
- 사용할 오디오 드라이버를 지정한다. 여기서는 pa(PulseAudio), alsa(Advanced Linux Sound Architecture), sdl(Simple DirectMedia Layer), oss(Open Sound System) 드라이버를 사용하도록 설정한다.
설치가 완료되었는지 제대로 확인한다.
$ output/qemu/bin/qemu-system-riscv64 -version
QEMU emulator version 8.0.0
Copyright (c) 2003-2022 Fabrice Bellard and the QEMU Project developers
QEMU 에 가상의 보드 추가하기
프로젝트에 진행할 가상의 Quard-star board에 관한 소스코드는 이미 레포지토리에 포함되어있다.
따라하는 입장에서 별도로 진행할 내용은 없고, 그냥 기작성된 코드를 이해해보자.
/qemu-8.0.0/hw/riscv/quard_star.c
보드 등록 및 정의
static const TypeInfo quard_star_machine_typeinfo = {
.name = MACHINE_TYPE_NAME("quard-star"),
.parent = TYPE_MACHINE,
.class_init = quard_star_machine_class_init,
.instance_init = quard_star_machine_instance_init,
.instance_size = sizeof(RISCVVirtState),
};
static void quard_star_machine_init_register_types(void)
{
type_register_static(&quard_star_machine_typeinfo);
}
type_init(quard_star_machine_init_register_types)
- quard_star_machine_typeinfo: QEMU 시스템에 보드를 등록하기 위한 구조체이다. 보드의 이름, 부모 타입, 클래스 초기화 함수, 인스턴스 초기화 함수, 인스턴스 크기를 정의한다.
- quard_star_machine_init_register_types: quard_star_machine_typeinfo 구조체를 QEMU 타입 시스템에 등록하는 함수이다.
- type_init: QEMU 초기화 시 quard_star_machine_init_register_types 함수를 호출하여 보드를 등록한다.
보드 리소스 초기화 함수 및 코어 수 등록
static void quard_star_machine_class_init(ObjectClass *oc, void *data)
{
MachineClass *mc = MACHINE_CLASS(oc);
mc->desc = "RISC-V Quard Star board";
mc->init = quard_star_machine_init;
mc->max_cpus = QUARD_STAR_MANAGEMENT_CPU_COUNT +
QUARD_STAR_COMPUTE_CPU_COUNT;
...
}
- mc->init: 보드 초기화 함수로 quard_star_machine_init을 설정한다.
- mc->max_cpus: 보드가 지원하는 최대 SMP(대칭 멀티프로세싱) 코어 수를 설정한다.
qemu 소스 디렉토리의 include/hw/riscv/quard-star.h 를 참고하면
management cpu 는 1개, compute cpu 는 7개로, 총 옥타코어이다.
가상 CPU 구성
static void quard_star_cpu_create(MachineState *machine)
{
QuardStarState *s = RISCV_VIRT_MACHINE(machine);
object_initialize_child(OBJECT(machine), "c-cluster",
&s->c_cluster, TYPE_CPU_CLUSTER);
qdev_prop_set_uint32(DEVICE(&s->c_cluster), "cluster-id", 0);
object_initialize_child(OBJECT(&s->c_cluster), "c-cpus",
&s->c_cpus, TYPE_RISCV_HART_ARRAY);
object_property_set_str(OBJECT(&s->c_cpus), "cpu-type",
machine->cpu_type, &error_abort);
object_property_set_int(OBJECT(&s->c_cpus), "hartid-base",
0, &error_abort);
object_property_set_int(OBJECT(&s->c_cpus), "num-harts",
QUARD_STAR_COMPUTE_CPU_COUNT, &error_abort);
object_property_set_int(OBJECT(&s->c_cpus), "resetvec",
quard_star_memmap[QUARD_STAR_MROM].base,
&error_abort);
sysbus_realize(SYS_BUS_DEVICE(&s->c_cpus), &error_fatal);
qdev_realize(DEVICE(&s->c_cluster), NULL, &error_abort);
// similar as above. Set r-cluster
...
...
}
보드 내부에 cpu 가 클러스터로 구성되어있다. (c-cluster, r-cluster)
c-cluster 는 compute 전용, r-cluster 는 management 전용 cpu 로 보인다.
virt machine 을 보면, num-harts 관련 함수의 인자로 hart_count 변수를 입력 받는데, 해당 예제의 경우 상수값이 들어간다.
따라서 이후의 -smp 옵션도 따로 필요없을것으로 보인다. (디폴트가 7+1 코어 구성)
메모리 관련 리소스 정의
여기서는 CPU 내부에 마스크롬(Mask ROM), SRAM, DDR 메모리를 정의한다.
- 마스크롬(Mask ROM): CPU가 부팅될 때 내부의 고정된 코드를 실행하는 데 사용된다.
- SRAM: 초기 부팅 코드의 데이터 저장 공간으로 사용된다.
- DDR: 일반적으로 실제 보드에서는 컨트롤러 초기화 후에 사용할 수 있지만, QEMU 시뮬레이션에서는 바로 사용할 수 있는 메모리로 설정된다. 그러나 현실성을 추구하기 위해 초기 부팅 시에는 이 메모리 공간을 사용하지 않는다.
static const MemMapEntry quard_star_memmap[] = {
[QUARD_STAR_MROM] = { 0x00000000, 0x20000 },
[QUARD_STAR_SRAM] = { 0x00020000, 0xe0000 },
...
[QUARD_STAR_DRAM] = { 0x80000000, 0x0 },
};
static void quard_star_memory_create(MachineState *machine)
{
MemoryRegion *system_memory = get_system_memory();
QuardStarState *s = RISCV_VIRT_MACHINE(machine);
MemoryRegion *main_mem = g_new(MemoryRegion, 1);
MemoryRegion *sram_mem = g_new(MemoryRegion, 1);
MemoryRegion *mask_rom = g_new(MemoryRegion, 1);
memory_region_init_ram(main_mem, NULL, "riscv_quard_star_board.dram",
machine->ram_size, &error_fatal);
memory_region_add_subregion(system_memory,
quard_star_memmap[QUARD_STAR_DRAM].base, main_mem);
memory_region_init_ram(sram_mem, NULL, "riscv_quard_star_board.sram",
quard_star_memmap[QUARD_STAR_SRAM].size, &error_fatal);
memory_region_add_subregion(system_memory,
quard_star_memmap[QUARD_STAR_SRAM].base, sram_mem);
memory_region_init_rom(mask_rom, NULL, "riscv_quard_star_board.mrom",
quard_star_memmap[QUARD_STAR_MROM].size, &error_fatal);
memory_region_add_subregion(system_memory,
quard_star_memmap[QUARD_STAR_MROM].base, mask_rom);
quard_star_setup_rom_reset_vec(machine, &s->r_cpus,
quard_star_memmap[QUARD_STAR_FLASH].base,
quard_star_memmap[QUARD_STAR_MROM].base,
quard_star_memmap[QUARD_STAR_MROM].size,
0x0, 0x0);
}
memory_region_init_ram 으로 특정 타입의 메모리 영역을 설정하고
memory_region_add_subregion 을 통해 시스템 메모리에 추가한다.
quard_star_setup_rom_reset_vec 함수 호출을 통해 rom code 와 관련된 설정을 한다.
아래에서 살펴보자
Mask ROM 코드를 MROM 영역에 로드
quard_star_setup_rom_reset_vec 을 통해 아래를 인자로 전달받는다
- start_addr = FLASH 의 base address
- rom_base = rom 의 base address
- rom_size = rom 저장공간의 size
static void quard_star_setup_rom_reset_vec(MachineState *machine,
RISCVHartArrayState *harts, hwaddr start_addr,
hwaddr rom_base, hwaddr rom_size,
uint64_t kernel_entry, uint32_t fdt_load_addr)
{
QuardStarState *s = RISCV_VIRT_MACHINE(machine);
uint32_t start_addr_hi32 = 0x00000000;
if (!riscv_is_32bit(harts)) {
start_addr_hi32 = start_addr >> 32;
}
/* reset vector */
uint32_t reset_vec[10] = {
0x00000297, /* 1: auipc t0, %pcrel_hi(fw_dyn) */
0x02828613, /* addi a2, t0, %pcrel_lo(1b) */
0xf1402573, /* csrr a0, mhartid */
0,
0,
0x00028067, /* jr t0 */
start_addr, /* start: .dword */
start_addr_hi32,
fdt_load_addr, /* fdt_laddr: .dword */
0x00000000,
/* fw_dyn: */
};
if (riscv_is_32bit(harts)) {
reset_vec[3] = 0x0202a583; /* lw a1, 32(t0) */
reset_vec[4] = 0x0182a283; /* lw t0, 24(t0) */
} else {
reset_vec[3] = 0x0202b583; /* ld a1, 32(t0) */
reset_vec[4] = 0x0182b283; /* ld t0, 24(t0) */
}
/* copy in the reset vector in little_endian byte order */
for (int i = 0; i < ARRAY_SIZE(reset_vec); i++) {
reset_vec[i] = cpu_to_le32(reset_vec[i]);
}
...
rom_add_blob_fixed_as("mrom.reset", reset_vec, sizeof(reset_vec),
rom_base, &address_space_memory);
}
롬코드를 해석해보면, 아래와 같다.\.
- 0x0000_0000 _00 을 t0 에 load
- %pcrel_hi(fw_dyn) : 컴파일러 지시어로, 'fw_dyn 의 주소(0x28) - 현 pc값(0x00)' 의 상위 20비트
- 즉 t0 에 0x0 을 load
- a2 에 0x00 을 저장 (이유는 모르겠음..)
- a0 에 hartid 를 load
- a1 에 t0 (0x0) + 32byte offset 에 위치한 값을 load => fdt_load_addr
- t0 에 t0 (0x0) + 24byte offset 에 위치한 값을 load => 매개변수로 전달받은 flash 메모리의 시작 위치
- t0 (flash 메모리의 시작 주소) 으로 점프 (risc-v ABI 규격에 따르면 a0 은 매개변수0, a1 은 매개변수 1 로 사용된다.)
'Project > Embedded' 카테고리의 다른 글
[QEMU] 임베디드 리눅스 시스템 구축 프로젝트 - OpenSBI (0) | 2024.06.12 |
---|---|
[QEMU] 임베디드 리눅스 시스템 구축 프로젝트 - OpenSBI overview (0) | 2024.06.11 |
[QEMU] 임베디드 리눅스 시스템 구축 프로젝트 - Flash 테스트 (0) | 2024.06.10 |
[QEMU] 임베디드 리눅스 시스템 구축 프로젝트 - 가상 보드 등록 2 (0) | 2024.06.09 |
NUCELO 보드 네이밍 정보 (NUCELO-F401RE) (0) | 2024.01.02 |
댓글