吳中玄
I will develop an OS Kernel based on RISC-V and use QEMU as the simulation tool. QEMU can emulate a virtual RISC-V platform, including the CPU, memory, and peripheral devices, enabling me to proceed with the development
I will accomplish the following functions.
1.Bootstrap
2.Print Hello World!
3.Mem_Management
4.Context_Switch
5.Trap
The system is configured to have 8 harts by default, but in this project, I am only using a single hart. Therefore, I check whether the current hart is the only active one (hart ID 0) and treat hart 0 as the primary core. All other harts are set to idle. The wfi instruction will halt the current hart and put it into an idle state.
Hart 0 serves as the primary core responsible for executing tasks.
Since the RISC-V simulation using QEMU does not have a display, I need to use UART to connect the QEMU-simulated RISC-V machine with my host machine, allowing the simulation results to be displayed on my host.
When initializing the UART, the interrupts are first disabled (by setting IER to 0) to prevent interruptions during the initialization process.
The UART baud rate is configured using the baud rate divisor, and accessing the DLL and DLM registers requires setting the 7th bit of the LCR register (DLAB) to 1.
The UART clock frequency is 1.8432 MHz, and my target baud rate is 38400 bps, so the divisor must be set to 3 to achieve this.
Thus,DLL and DLM are set to 3.
Check the 5th bit of the LSR register to confirm whether the transmit buffer is idle. When the buffer is idle, write the character to the THR, and the UART begins transmission
linker script
Define the output machine architecture as RISC-V to ensure the executable file is generated for the RISC-V platform.
Define the available memory regions in the system, specifying their attributes, starting addresses, and sizes.
Define the setup for sections (.text, .rodata, .data, .bss).
Finally, calculate the remaining memory size to determine how to configure the heap.
Save the context of the current control flow, then jump to trap_handler to handle the interrupt or exception.
Finally, restore the context and execute mret to return to the state before the trap.
The if-else statement determines whether this trap is an interrupt or an exception. If it is 1, it is an interrupt; if it is 0, it is an exception.
For interrupts, the switch-case statement is used to identify the reason for the interrupt.
For exceptions, the program currently skips the exception and executes the next instruction (directly exiting the exception).
The plic_claim function is used to determine which external device has triggered the current interrupt.
The plic_complete function is used to notify that the interrupt processing is complete.
First, determine the reason for the trap. In this example, it is an external interrupt, so it enters case 11 and proceeds to external_interrupt_handler(). Then, plic_claim is used to identify which external device triggered the interrupt. In this example, it simulates inputting a character, so the source is UART (with an IRQ of 10 in the PLIC). Next, uart_isr is called to handle the interrupt. Finally, plic_complete is used to notify that the interrupt processing is complete, and the program returns to the next instruction of the original program to continue execution.