Timer Interrupt using RISC-V
This week, I worked on the implementation of Timer Interrupt using the RISC-V Angel. The RISC-V Angel had a timer interrupt code for itself for the usage of Linux ELF file. However, it does not state obviously how do they use it and why are they writing their code in such way. Thus, I had to decode the source code from RISC-V Angel and from the source code of RISC-V Linux.
Understand about Interrupt
Although I had learnt interrupt in my University course, I found that it is still insufficient to be used to apply for the implementation of Timer in RISC-V. I had done more research on internet to understand how the timer interrupt works, rather than how timer is being configured to be used. I would recommend this link, as it explains a lot about the details of interrupt. I would like to highlight the most important part of interrupt would be the saving of context and restoring of context. As interrupt event happen, it should have a current snapshot of the CPU registers and program counter, and restore them back after the end operation of interrupt.
Implementation of Interrupt
Initially I had no idea how RISC-V Angel had configure for the Interrupt, as there is nothing stated in the source code. After searching more, I found they were in the RISC-V Linux source code, and it is written in assembly as well. It is really irritating when I tried to extract these assembly code, as these code includes many libraries that would most probably include the whole source code directory. In the end, I decided to write my own code for that. These interrupt flow can be described as following :
enabling the interrupt register in main > define an interrupt service routine > setup a counter value > wait for interrupt counts > Interrupt occur > save current register snapshot > do the interrupt service routine (ISR) > restore register snapshot > back to main
Dealing with interrupt, I had to understand more about the RISC-V Control and Status Register (CSR). The RISC-V Angel does not implement different levels of ISA, all implementations are done in Supervisor Level ISA. The interrupt would be triggered when the
CSR_COMPARE (counter value) is equivalent with the
CSR_COUNT (count), where the CSR_COUNT would increment by 1 for each CPU cycle. Although I only did a snapshot for 3 registers during the testing, I had tested this mechanism on the simulator, and fortunately the mechanism worked fine. Besides, I found that the implementation of sret in the RISC-V Angel ISA is incorrectly, as compared from the ELF file generated from RISC-V GNU compiler.
The Calling of Assembly function in C and Vice Versa
As I decide to write my code for the timer interrupt, I decided to write them in the C code instead of implementing it in the Simulator itself. In order to do this, I would need to call assembly in C, as well as calling the C function from assembly.This took most of my time in implementing the timer interrupt, as I keep getting the error in undefined reference, which finding the functions that had been defined, in both calling cases. After struggling days to solve this undefined reference error, I found that the usage of
extern "C" function_to_Assembly/C can solve the problem. The extern “C” tells the compiler do not mangle the name of the function, so that these functions could be called in both assembly and C. Then, the C function can be called in the assembly by just
jal function_in_C . Note : The usage of JAL instruction will save the program counter in RA register
On the other hand, the assembly function need to be defined by
.global assembly_func and
.type assembly_func, @function in the assembly file, and just call normally in C by using assembly_func(), or any other way that could call the function. I found that this is really a good experience in learning interrupt as previously I was just exposed to using the interrupt, by configuring the registers and the ISR. There are so many things that had been hidden from programmers, and only being reveal when doing more detail learning on it.