본문 바로가기
Project/Embedded

[QEMU] 임베디드 리눅스 시스템 구축 프로젝트 - Flash 테스트

by FastBench 2024. 6. 10.

이전글

https://microelectronics.tistory.com/68

https://microelectronics.tistory.com/69

 

원본 게시글

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

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

 

Cross Compiler 설치

본문에는 bootlin 에서 다운받아 설치하지만, 본인은 riscv-toolchain github 를 통해 설치했다.

https://github.com/riscv-collab/riscv-gnu-toolchain.git

 

GitHub - riscv-collab/riscv-gnu-toolchain: GNU toolchain for RISC-V, including GCC

GNU toolchain for RISC-V, including GCC. Contribute to riscv-collab/riscv-gnu-toolchain development by creating an account on GitHub.

github.com

설치 후, PATH 잡아주는 것을 잊지 말자.

 

테스트 펌웨어 작성

최신 레포지토리 내의 lowlevelboot 디렉토리는 이미 해당 내용이 skip 되어 있으므로, 새로운 test 폴더를 만들어 테스트 한다. 

테스트 펌웨어는 UART 장치를 통해 Hello quard star board 를 출력하는 펌웨어이다. Flash 장치에 올라간다.

startup.s

.section .text             // 데이터 섹션 이름을 .text로 정의
.globl _start              // 전역 심볼 _start 정의
.type _start,@function     // _start를 함수로 정의

_start:                    // 함수 시작점
    csrr    a0, mhartid    // mhartid 레지스터는 코어의 hart ID를 정의함, 이를 a0 레지스터에 로드
    li      t0, 0x0        // li는 즉시 값을 로드하는 명령어, t0에 0을 로드
    beq     a0, t0, _core0 // a0와 t0를 비교, 같으면 _core0으로 점프, 그렇지 않으면 아래로 진행
    					   // 즉 hart0 만 코드를 실행하고, 나머지는 무한 루프
_loop:                     // _loop 레이블 정의
    j       _loop          // _loop로 점프 (무한 루프)
_core0:                    // _core0 레이블 정의, hart 0만이 여기서 실행을 계속함
    li      t0, 0x100      // t0에 0x100을 로드
    slli    t0, t0, 20     // t0를 20비트 왼쪽으로 시프트, t0 = 0x10000000
    					   // 0x10000000 은 UART 디바이스의 주소임
    li      t1, 'H'        // t1에 'H'의 ASCII 코드 값 로드
    sb      t1, 0(t0)      // t1의 값을 t0가 가리키는 주소에 저장, UART0의 데이터 레지스터에 쓰기
                           // 이 시점에서 UART를 통해 "H" 출력
    li      t1, 'e'        // 이후 같은 방식으로 문자를 하나씩 출력
    sb      t1, 0(t0)
    li      t1, 'l'
    sb      t1, 0(t0)
    li      t1, 'l'
    sb      t1, 0(t0)
    li      t1, 'o'
    sb      t1, 0(t0)
    li      t1, ' '
    sb      t1, 0(t0)
    li      t1, 'Q'
    sb      t1, 0(t0)
    li      t1, 'u'
    sb      t1, 0(t0)
    li      t1, 'a'
    sb      t1, 0(t0)
    li      t1, 'r'
    sb      t1, 0(t0)
    li      t1, 'd'
    sb      t1, 0(t0)
    li      t1, ' '
    sb      t1, 0(t0)
    li      t1, 'S'
    sb      t1, 0(t0)
    li      t1, 't'
    sb      t1, 0(t0)
    li      t1, 'a'
    sb      t1, 0(t0)
    li      t1, 'r'
    sb      t1, 0(t0)
    li      t1, ' '
    sb      t1, 0(t0)
    li      t1, 'b'
    sb      t1, 0(t0)
    li      t1, 'o'
    sb      t1, 0(t0)
    li      t1, 'a'
    sb      t1, 0(t0)
    li      t1, 'r'
    sb      t1, 0(t0)
    li      t1, 'd'
    sb      t1, 0(t0)
    li      t1, '!'
    sb      t1, 0(t0)
    li      t1, '\n'
    sb      t1, 0(t0)      // 여기까지 "Hello Quard Star board!" 출력
    j       _loop          // 완료 후 루프로 돌아가기

.end                       // 어셈블리 파일의 끝

 

boot.lds

OUTPUT_ARCH("riscv")  /* 출력 실행 파일 플랫폼 */

ENTRY(_start)         /* 프로그램 진입 함수 */

MEMORY                /* 메모리 영역 정의 */
{ 
    /* flash라는 이름의 메모리 영역 속성 및 시작 주소와 크기 정의 */
    flash (rxai!w) : ORIGIN = 0x20000000, LENGTH = 512k 
}

SECTIONS              /* 섹션 영역 정의 */
{
  .text :             /* .text 섹션 영역 */
  {
    KEEP(*(.text))    /* 모든 .text 섹션을 이 영역에 링크, keep은 최적화 방지를 의미, 즉 이 섹션을 항상 유지 */
  } >flash            /* 섹션 영역의 주소(LMA와 VMA 동일)는 flash 메모리 영역에 위치 */
}
  • r: 읽기(Read) 가능
  • x: 실행(Execute) 가능
  • a: 할당(Allocatable) 가능
  • i: 초기화된 데이터(Initialized) 포함
  • !w: 쓰기(Write) 불가능 (느낌표 !는 부정을 의미하며, 쓰기 불가능을 나타냄)

이 플래그들을 조합하여 해당 메모리 영역이 어떻게 사용될지를 정의한다. flash 메모리 영역은 읽기와 실행이 가능하고, 할당 가능하며, 초기화된 데이터를 포함하지만, 쓰기는 불가능하도록 설정한다.

 

빌드

riscv64-unknown-elf-gcc -c startup.s -o startup.o
riscv64-unknown-elf-gcc -nostartfiles -T ./boot.lds -Wl,-Map=./test_fw.map -Wl,--gc-sections startup.o -o test_fw.elf
riscv64-unknown-elf-objcopy -O binary -S test_fw.elf test_fw.bin
riscv64-unknown-elf-objdump --source --demangle --disassemble --reloc --wide test_fw.elf > test_fw.dump

컴파일시 -Wl,<option>,-Wl 으로 옵션을 그룹화 하는 것은 링커에게 따로 개별적으로 옵션을 전달하기 위함이다.

  • --gc-sections 옵션 : 사용하지 않는 섹션을 제거하여 최종 바이너리 파일의 크기를 줄이는 용도이다.
  • --source 옵션 : 소스코드를 포함한 disassembly 출력을 생성한다.
  • --demangle 옵션 : 심볼이름을 demangle 하여 가독성 있게 출력한다.
  • --reloc 옵션 : 재배치 정보를 포함하여 disassembly 출력을 생성한다.
  • --wide 옵션 : 출력을 넓게 표시하여 긴 라인이 잘리지 않게 한다.

flash 펌웨어 생성

우리의 pflash는 32M이므로, flash 펌웨어 크기도 32M이다.

dd명령어를 통해 32M 파일을 생성하고, 빈 위치는 0으로 채운다. 이렇게 하면 fw.bin 펌웨어가 생성된다.

실제로 flash 메모리에 write 를 할때도 바이너리 파일을 dd 명령어를 사용하여 블록 인터페이스에 맞게 변환한다.

 

 

$ dd of=fw.bin bs=1k count=32k if=/dev/zero
>
32768+0 records in
32768+0 records out
33554432 bytes (34 MB, 32 MiB) copied, 0.0997465 s, 336 MB/s

$ dd of=fw.bin conv=notrunc seek=0 if=test_fw.bin
>
0+1 records in
0+1 records out
210 bytes copied, 0.000113868 s, 1.8 MB/s
  • conv=notrunc 옵션은 dd 명령어가 파일을 복사할 때 기존 파일의 내용을 덮어쓰지 않고 유지하도록 한다. 즉, 새로 복사되는 내용만 갱신되고, 나머지 파일은 그대로 유지된다.
  • seek=0 옵션은 출력 파일(fw.bin)의 시작 위치에서부터 데이터를 쓰기 시작하도록 한다. 여기서 seek는 쓰기 시작할 블록의 오프셋을 지정한다. 0은 파일의 처음부터 쓰기 시작하는 것을 의미한다.

QEMU 실행

../output/qemu/bin/qemu-system-riscv64 -M quard-star -m 1G \
-drive if=pflash,format=raw,file=./fw.bin -nographic
>
Hello Quard Star board!

정상적으로 출력되는 것이 확인된다.

 

qemu 를 종료하려면 ctrl+a 를 입력한 다음 바로  x 를  누르거나, ctrl+a 이후 c를 눌러 QEMU 자체 콘솔이 나오면 q를 입력하여 빠져 나간다.

댓글