Contents

RISC-V 101

/* common instructions */
mv a0, sp // a0 = sp
lw a0, (a1) // a0 = *a1
sw a0, (a1) // *a1 = a0
addi a0, a1, 111 // a0 = a1 + 111
bnez a0, <label> // branch if a0 is not equal to zero
jal ra, <label> // label 호출 후 return address를 ra에 저장
ret // ra 위치로 이동

/* privileged instructions */
csrr rd, csr // csr 레지스터 읽어오는 명령
csrw csr, rs
csrrw rd, csr, rs // 읽으면서 쓰는 명령
sret // trap handler에서 돌아오는 명령어
sfence.vma // TLB 비우는 명령어
  • Modes
    • U-mode : user 모드
    • S-mode : kernel 모드
    • M-mode : BIOS가 돌아가는 모드
  • CSR (control and status register) : CPU settings이 저장된 레지스터
    __asm__ __volatile__("assembly" : output_operands : input_operands : clobbered_registers);
    __asm__ __volatile__("csrw sscratch, %0" : : "r"(123));
    
  • operands는 constraints 형태로 저장되는데 “r”(input)이랑 “=r”(output)이 있음
  • clobbered_registers는 컴파일러한테 이 명령에 대한 어떤 정보를 알려주는 거임

    Boot

  • SBI (supervisor binary interface) : BIOS 같은 건데 kernel을 위한 API 같은 느낌임.
    • OpenSBI는 QEMU에서 사용되는데 마지막에 0x80200000으로 점프함
  • Linker script : linker가 참조하는 파일인데 여러 메모리 영역들을 어떤 주소에 할당할지 정해줄 수 있음
ENTRY(boot)

SECTIONS {
	. = 0x80200000;
	.text : {
		KEEP(*(.text.boot));
		*(.text .text.*);
	}
	
	.rodata : ALIGN(4) {
		*(.rodata .rodata.*);
	}
	
	.data : ALIGN(4) {
		*(.data .data.*);
	}
	
	.bss : ALIGN(4) {
		__bss = .;
		*(.bss .bss.* .sbss .sbss.*);
		__bss_end = .;
	}
	
	. = ALIGN(4);
	. += 1024 * 128;
	__stack_top = .;
}
  • kernel source
extern char *__bss, *__bss_end, *__stack_top;

void memset(unsigned char *begin, char c, unsigned int n)
{
	for(unsigned int i=0;i<n;i++) {
		*(begin + i) = c;
	}
}

void main(void)
{
	memset((unsigned *)__bss, 0, (__bss_end - __bss));
	while(1);
}

__attribute__((section(".text.boot")))
__attribute__((naked))
void boot(void)
{
	__asm__ __volatile__(
		"mv sp, %0\n"	
		"j main\n"
		:
		: "r"(__stack_top)
	);
}

printf 구현

struct sbiret {
	long error;
	long value;
};

struct sbiret sbi_call(long arg0, long arg1, long arg2, long arg3, long arg4, long arg5, long fid, long eid)
{
	register long a0 __asm__("a0") = arg0;
	register long a1 __asm__("a1") = arg1;
	register long a2 __asm__("a2") = arg2;
	register long a3 __asm__("a3") = arg3;
	register long a4 __asm__("a4") = arg4;
	register long a5 __asm__("a5") = arg5;
	register long a6 __asm__("a6") = fid;
	register long a7 __asm__("a7") = eid;
	
	__asm__ __volatile__("ecall"
						: "=r"(a0), "=r"(a1)
						: "r"(a0), "r"(a1), "r"(a2), "r"(a3), "r"(a4), "r"(a5), "r"(a6), "r"(a7)
						: "memory");
	return (struct retsbi) {.error = a0, .value = a1};
}

void putchar(char ch)
{
	sbi_call(ch, 0, 0, 0, 0, 0, 0, 1);
}
  • SBI가 제공하는 기능을 호출하기 위해서 ecall을 사용하면 됨
    • ecall 실행 시 M-mode로 전환되고 OpenSBI의 processing handler가 호출됨
    • a0-a5는 인자로 사용됨
    • a7은 EID(Extension ID)를 나타냄
    • a6은 EID에 대한 FID(Function ID)를 나타냄
    • OpenSBI의 모든 기능의 callee는 a0(error)랑 a1(return value)을 제외한 다른 레지스터는 내용을 바꾸지 않음

panic 구현

#define PANIC(fmt, ...) \
	do { \
		printf("PANIC: %s: %d: " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__); \
		while (1) {} \
	} while(0)

Exception 처리 구현

#define WRITE_CSR(register, value) \
	do { \
		uint32_t tmp = value; \
		__asm__ __volatile__("csrw " #register ", %0" :: "r"(tmp)); \
	} while(0)

WRITE_CSR(stvec, (uint32_t) kernel_entry);
  • stvec을 통해 exception이 발생했을 때 호출할 함수를 등록할 수 있다
  • stvec의 하위 2bit는 mode에 대한 정보가 저장되기 때문에 4바이트 단위로 align을 해줘야 됨
__attribute__((naked))
__attribute__((aligned(4)))
void kernel_entry(void) {
    __asm__ __volatile__(
        "csrw sscratch, sp\n"
        "addi sp, sp, -4 * 31\n"
        "sw ra,  4 * 0(sp)\n"
        "sw gp,  4 * 1(sp)\n"
        "sw tp,  4 * 2(sp)\n"
        "sw t0,  4 * 3(sp)\n"
        "sw t1,  4 * 4(sp)\n"
        "sw t2,  4 * 5(sp)\n"
        "sw t3,  4 * 6(sp)\n"
        "sw t4,  4 * 7(sp)\n"
        "sw t5,  4 * 8(sp)\n"
        "sw t6,  4 * 9(sp)\n"
        "sw a0,  4 * 10(sp)\n"
        "sw a1,  4 * 11(sp)\n"
        "sw a2,  4 * 12(sp)\n"
        "sw a3,  4 * 13(sp)\n"
        "sw a4,  4 * 14(sp)\n"
        "sw a5,  4 * 15(sp)\n"
        "sw a6,  4 * 16(sp)\n"
        "sw a7,  4 * 17(sp)\n"
        "sw s0,  4 * 18(sp)\n"
        "sw s1,  4 * 19(sp)\n"
        "sw s2,  4 * 20(sp)\n"
        "sw s3,  4 * 21(sp)\n"
        "sw s4,  4 * 22(sp)\n"
        "sw s5,  4 * 23(sp)\n"
        "sw s6,  4 * 24(sp)\n"
        "sw s7,  4 * 25(sp)\n"
        "sw s8,  4 * 26(sp)\n"
        "sw s9,  4 * 27(sp)\n"
        "sw s10, 4 * 28(sp)\n"
        "sw s11, 4 * 29(sp)\n"

        "csrr a0, sscratch\n"
        "sw a0, 4 * 30(sp)\n"

        "mv a0, sp\n"
        "call handle_trap\n"

        "lw ra,  4 * 0(sp)\n"
        "lw gp,  4 * 1(sp)\n"
        "lw tp,  4 * 2(sp)\n"
        "lw t0,  4 * 3(sp)\n"
        "lw t1,  4 * 4(sp)\n"
        "lw t2,  4 * 5(sp)\n"
        "lw t3,  4 * 6(sp)\n"
        "lw t4,  4 * 7(sp)\n"
        "lw t5,  4 * 8(sp)\n"
        "lw t6,  4 * 9(sp)\n"
        "lw a0,  4 * 10(sp)\n"
        "lw a1,  4 * 11(sp)\n"
        "lw a2,  4 * 12(sp)\n"
        "lw a3,  4 * 13(sp)\n"
        "lw a4,  4 * 14(sp)\n"
        "lw a5,  4 * 15(sp)\n"
        "lw a6,  4 * 16(sp)\n"
        "lw a7,  4 * 17(sp)\n"
        "lw s0,  4 * 18(sp)\n"
        "lw s1,  4 * 19(sp)\n"
        "lw s2,  4 * 20(sp)\n"
        "lw s3,  4 * 21(sp)\n"
        "lw s4,  4 * 22(sp)\n"
        "lw s5,  4 * 23(sp)\n"
        "lw s6,  4 * 24(sp)\n"
        "lw s7,  4 * 25(sp)\n"
        "lw s8,  4 * 26(sp)\n"
        "lw s9,  4 * 27(sp)\n"
        "lw s10, 4 * 28(sp)\n"
        "lw s11, 4 * 29(sp)\n"
        "lw sp,  4 * 30(sp)\n"
        "sret\n"
    );
}
  • kernel entry에서는 registers를 stack에 저장해놓고 handle_trap을 호출
#define READ_CSR(register) \
	({ \
		uint32_t tmp; \
		__asm__ __volatile__( \
			"csrr %0, " #register : "=r"(tmp)); \
		); \
		tmp; \
	})

struct trap_frame {
    uint32_t ra;
    uint32_t gp;
    uint32_t tp;
    uint32_t t0;
    uint32_t t1;
    uint32_t t2;
    uint32_t t3;
    uint32_t t4;
    uint32_t t5;
    uint32_t t6;
    uint32_t a0;
    uint32_t a1;
    uint32_t a2;
    uint32_t a3;
    uint32_t a4;
    uint32_t a5;
    uint32_t a6;
    uint32_t a7;
    uint32_t s0;
    uint32_t s1;
    uint32_t s2;
    uint32_t s3;
    uint32_t s4;
    uint32_t s5;
    uint32_t s6;
    uint32_t s7;
    uint32_t s8;
    uint32_t s9;
    uint32_t s10;
    uint32_t s11;
    uint32_t sp;
} __attribute__((packed));

void handle_trap(struct trap_frame *f) {
    uint32_t scause = READ_CSR(scause);
    uint32_t stval = READ_CSR(stval);
    uint32_t sepc = READ_CSR(sepc);

    PANIC("unexpected trap scause=%x, stval=%x, sepc=%x\n", scause, stval, sepc);
}
  • exception 정보에 대한 CSR
    • scause : exception의 종류
    • stval : exception 종류에 따른 추가 정보
    • sepc : exception이 발생한 pc 위치

Memory allocation

{
	. = ALIGN(4096);
	__free_ram = .;
	. += 4 * 1024 * 1024
	__free_ram_end = .;
}
  • linker script에 dynamic allocation을 위한 영역을 정의
#define PAGE_SIZE 4096

extern char __free_ram[], __free_ram_end[];

paddr_t allocate_pages(size_t n)
{
	static paddr_t next_paddr = __free_ram;
	paddr_t r = next_paddr;
	next_paddr += n * PAGE_SIZE;
	
	if (next_paddr > __free_ram_end)
		PANIC("out of memory!");
	
	return r;
}
  • 단순한 linear allocation을 사용한 코드

Process


head1