본문 바로가기
Project/Embedded

[QEMU] 임베디드 리눅스 시스템 구축 프로젝트 - 가상 보드 등록 2

by FastBench 2024. 6. 9.

이전글

https://microelectronics.tistory.com/68

 

원본 게시글

docs : https://quard-star-tutorial.readthedocs.io/zh-cn/latest/

github : https://github.com/QQxiaoming/quard_star_tutorial

 

마찬가지로, 이미 기작성된 코드이므로 간단하게 읽으면서 흐름을 파악하는 것을 목표로 한다.

 

https://quard-star-tutorial.readthedocs.io/zh-cn/latest/index.html

시리얼포트 추가

실제 보드의 초기 디버깅 중, 시리얼 출력은 매우 유용하다. 거의 모든 SOC는 시리얼 포트를 포함하고 있기 때문에 우리도 정의에 시리얼 포트를 추가해야 한다.

우선 세 개의 시리얼 포트 기본 주소를 추가한다. 세 개를 추가하는 이유는 나중에 여러 권한 영역 내에서 다른 시스템을 실행할 수 있기 때문에 다른 권한의 시리얼 출력이 필요할 수 있기 때문이다.

 

아래는 quard-star.c 의 MemoryMapEntry 구조체이다.

static const MemMapEntry quard_star_memmap[] = {
	....
    [QUARD_STAR_UART0]       = { 0x10000000,    0x1000 },
    [QUARD_STAR_UART1]       = { 0x10001000,    0x1000 },
    [QUARD_STAR_UART2]       = { 0x10002000,    0x1000 },
	...
}

 

초기화 함수에서 serial_mm_init 을 호출하여 시리얼 인스터스 생성이 가능하다.

해당 시리얼 에뮬레이션은 ns16550a 의 정의를 따른다.

static void quard_star_serial_create(MachineState *machine)
{    
    MemoryRegion *system_memory = get_system_memory();
    QuardStarState *s = RISCV_VIRT_MACHINE(machine);

    serial_mm_init(system_memory, quard_star_memmap[QUARD_STAR_UART0].base,
        0, qdev_get_gpio_in(DEVICE(s->plic), QUARD_STAR_UART0_IRQ), 399193,
        serial_hd(0), DEVICE_LITTLE_ENDIAN);
    serial_mm_init(system_memory, quard_star_memmap[QUARD_STAR_UART1].base,
        0, qdev_get_gpio_in(DEVICE(s->plic), QUARD_STAR_UART1_IRQ), 399193,
        serial_hd(1), DEVICE_LITTLE_ENDIAN);
    serial_mm_init(system_memory, quard_star_memmap[QUARD_STAR_UART2].base,
        0, qdev_get_gpio_in(DEVICE(s->plic), QUARD_STAR_UART2_IRQ), 399193,
        serial_hd(2), DEVICE_LITTLE_ENDIAN);
}

위의 qdev_get_gpio_in 함수는 시리얼 인터럽트 신호를 구성하는 데 사용되므로, 시리얼 포트를 올바르게 사용하려면 인터럽트 컨트롤러 관련 코드를 추가해야 한다.

 

인터럽트 컨트롤러

시리얼 포트가 인터럽트를 사용하, 많은 외부 장치도 인터럽트 컨트롤러를 필요로 한다.

인터럽트 컨트롤러의 기본 주소를 추가한다. RISC-V 표준 인터럽트 컨트롤러는 두 부분으로 나뉜다:

코어 로컬 인터럽트 (CLINT)와 플랫폼 레벨 인터럽트 컨트롤러 (PLIC).

CLINT는 SMP 아키텍처에서 각 코어마다 고유한 인터럽트를 가지며, PLIC는 모든 코어가 공유하는 외부 인터럽트 컨트롤러이다.

static const MemMapEntry quard_star_memmap[] = {
    ...
    [QUARD_STAR_CLINT]       = { 0x02000000,   0x10000 },
    [QUARD_STAR_PLIC]        = { 0x0c000000, 0x4000000 },
	...
}
static void quard_star_interrupt_controller_create(MachineState *machine)
{
    QuardStarState *s = RISCV_VIRT_MACHINE(machine);
    char *plic_hart_config;

    riscv_aclint_swi_create(
        quard_star_memmap[QUARD_STAR_CLINT].base,
        0, machine->smp.cpus, false);
    riscv_aclint_mtimer_create(quard_star_memmap[QUARD_STAR_CLINT].base + RISCV_ACLINT_SWI_SIZE,
        RISCV_ACLINT_DEFAULT_MTIMER_SIZE, 0, machine->smp.cpus,
        RISCV_ACLINT_DEFAULT_MTIMECMP, RISCV_ACLINT_DEFAULT_MTIME,
        RISCV_ACLINT_DEFAULT_TIMEBASE_FREQ, true);

    plic_hart_config = riscv_plic_hart_config_string(machine->smp.cpus);
    s->plic = sifive_plic_create(
        quard_star_memmap[QUARD_STAR_PLIC].base,
        plic_hart_config,  machine->smp.cpus, 0,
        QUARD_STAR_PLIC_NUM_SOURCES,
        QUARD_STAR_PLIC_NUM_PRIORITIES,
        QUARD_STAR_PLIC_PRIORITY_BASE,
        QUARD_STAR_PLIC_PENDING_BASE,
        QUARD_STAR_PLIC_ENABLE_BASE,
        QUARD_STAR_PLIC_ENABLE_STRIDE,
        QUARD_STAR_PLIC_CONTEXT_BASE,
        QUARD_STAR_PLIC_CONTEXT_STRIDE,
        quard_star_memmap[QUARD_STAR_PLIC].size);
    g_free(plic_hart_config);
}

 

지금까지 인터럽트 컨트롤러와 시리얼 포트를 추가했으므로, 간단한 시리얼 기능 테스트를 진행할 수 있다.

하지만 현재는 펌웨어 프로그램을 저장하고 DDR에 로드할 수 있는 장치가 없으므로, pflash 에뮬레이션 장치를 펌웨어를 저장하는 스토리지로 추가한다.

 

pflash 추가 (QSPI NOR Flash)

pflash는 병렬 플래시 메모리로, 현재는 실제 장치에서 거의 사용되지 않는다.

시중에서 흔히 볼 수 있는 것은 QSPI NOR 플래시로, 저레벨 펌웨어의 저장 장치로 많이 사용된다.

NOR 플래시는 일반적으로 XIP(Execute In Place)를 지원하므로 기본적으로 pflash와 동일하다.

 

따라서 편리함을 위해 QEMU에서 제공하는 pflash를 초기 펌웨어의 저장 장치로 사용한다.

pflash 시작주소는 2000_0000 이고 크기는 32M이다. 이는 펌웨어를 저장하기에 충분한 크기이다

static const MemMapEntry quard_star_memmap[] = {
	....
    [QUARD_STAR_FLASH]       = { 0x20000000, 0x2000000 },
	...
};

pflash 장치를 생성하고 이를 시스템 버스의 해당 주소에 매핑한 다음,

이를 QEMU의 -drive if=pflash,bus=0,unit=0,………… 매개변수와 연결한다.

이렇게 하면 시뮬레이션 시작 시 이 플래시에 펌웨어 파일을 로드할 수 있다.

 

아래 코드의 플로우를 간단하게 주석에 추가했다.

static void quard_star_flash_create(MachineState *machine)
{
	// 시스템 메모리 및 장치 상태 초기화
    MemoryRegion *system_memory = get_system_memory();
    QuardStarState *s = RISCV_VIRT_MACHINE(machine);
    uint64_t flash_sector_size = 256 * KiB;
    DeviceState *dev = qdev_new(TYPE_PFLASH_CFI01);

	// 플래시 장치 속성 설정
    qdev_prop_set_uint64(dev, "sector-length", flash_sector_size);
    qdev_prop_set_uint8(dev, "width", 4);
    qdev_prop_set_uint8(dev, "device-width", 2);
    qdev_prop_set_bit(dev, "big-endian", false);
    qdev_prop_set_uint16(dev, "id0", 0x89);
    qdev_prop_set_uint16(dev, "id1", 0x18);
    qdev_prop_set_uint16(dev, "id2", 0x00);
    qdev_prop_set_uint16(dev, "id3", 0x00);
    qdev_prop_set_string(dev, "name", "quard-star.flash0");

    // 장치를 QuardStarState 객체에 추가
    object_property_add_child(OBJECT(s), "quard-star.flash0", OBJECT(dev));
    object_property_add_alias(OBJECT(s), "pflash0",
                              OBJECT(dev), "drive");
                
    // 플래시 장치 초기화 및 설정
    s->flash = PFLASH_CFI01(dev);
    pflash_cfi01_legacy_drive(s->flash, drive_get(IF_PFLASH, 0, 0));

	// 플래시 메모리 크기 및 정렬 확인
    assert(QEMU_IS_ALIGNED(quard_star_memmap[QUARD_STAR_FLASH].size, 
                                flash_sector_size));
    assert(quard_star_memmap[QUARD_STAR_FLASH].size/flash_sector_size <= UINT32_MAX);

    // 플래시 장치의 블록 수 설정
    qdev_prop_set_uint32(dev, "num-blocks", 
                    quard_star_memmap[QUARD_STAR_FLASH].size / flash_sector_size);
    sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);

	// 시스템 버스에 플래시 장치 추가
    memory_region_add_subregion(system_memory, 
                            quard_star_memmap[QUARD_STAR_FLASH].base,
                            sysbus_mmio_get_region(SYS_BUS_DEVICE(dev), 0));
}
 

댓글