PA2 Part 2 - Emulated Hardware Device
Warning
If someone is reading this blog, please be aware that the writer DID NOT consider the experience of the other readers.
After all, the most important thing is about writing things down for better memorization.
IO Interaction Between AM and NEMU
- Generally speaking, NEMU would register some special memory address as MMIO abstract registers, such as
CONFIG_AUDIO_CTL_MMIO
(0xa0000200) when initializing corresponding devices. - When the guest program(application) is trying to read/write data from these special memory address, NEMU would intercept those read/write operations and run the correlated callback functions which are set up with the special register, to simulate read/write op to hardware registers.
Timer
- The memory address of RTC is 0xa0000048, where NEMU registers 8 bytes of memory space.
- Accessing this memory area would invoke function
rtc_io_handler
which effectively returns the uptime in us as a 64-bit int. - As AM could conduct a read operation of at most 32 bits(
inl
), we have to ‘read’ twice for the complete 64-bit data. - The first 4 bytes starting from 0xa0000048 is the lower part of the 8-byte uptime.
Keyboard
There seems to be quite a lot of things going on in
nemu/src/device/keyboard.c
, but it’s a quite simple task as long as one understand the interaction logic between AM and NEMU, as there’re only 4 bytes(AKA 1 32-bit int) representing a keycode being registered atCONFIG_I8042_DATA_MMIO
(0xa0000060).There’s an interesting design
#define KEYDOWN_MASK 0x8000
in AM. It is basically an indicator of whether the key is pressed or released. Here’s how it works:- First of all, a convention in NEMU states that, if the keycode’s 15th bit is non-zero, that means the key is pressed down, and vice versa.
- Then, we can use this mask to judge whether the keycode represents a pressed key or a released one by conducting bitwise operation AND(&) between the keycode and the
KEYDOWN_MASK
, if the result is1
, that means a pressed key, and vice versa.
Fun_Fact
This 15th bit convention is primarily a Windows-specific implementation detail.
Other systems have other conventions, like using XKeyEvent
structure on X11(Linux/Unix).
VGA
This is a quite troublesome part. Here’s the deal:
- The guest program(application) could write frame data to frame buffer register(
CONFIG_FB_ADDR
0xa1000000) with the api provided by AM(io_write
). - AM would then write the pixels sent by the client to
FB_ADDR
(same asCONFIG_FB_ADDR
). There’re no callback function for frame buffer in NEMU, so it is direct write tovmem
. - NEMU constantly updates vga, whenever the value stored in the abstract sync register turns into
1
, NEMU will use the data in frame buffer to update the screen.
- The guest program(application) could write frame data to frame buffer register(
The code in
am-kernels/kernels/typing-game
is really fun, especially this part:1
2
3
4
5
6
7
8
9
10for (int ch = 0; ch < 26; ch++) {
char *c = &font[CHAR_H * ch];
for (int i = 0, y = 0; y < CHAR_H; y++)
for (int x = 0; x < CHAR_W; x++, i++) {
int t = (c[y] >> (CHAR_W - x - 1)) & 1;
texture[WHITE][ch][i] = t ? COL_WHITE : COL_PURPLE;
texture[GREEN][ch][i] = t ? COL_GREEN : COL_PURPLE;
texture[RED][ch][i] = t ? COL_RED : COL_PURPLE;
}
}This is where each english letter is ‘pre-painted’. By checking each bit(pixel) of the letter with bitwise operation, the program effectively find out the alpha value of each pixel. Then, it writes color data into the corresponding place in
texture
,COL_PURPLE
for zero-alpha pixel.
Audio
This is the most difficult part of the emulated device done in PA2. Mostly because it requires a ring buffer queue which is written and read by differenct programs(AM & NEMU). Also, Learning SDL is also a troublesome part, cuz I really don’t consider its official wiki very friendly.
Most of my time was spent on tuning the ring buffer, which is kinda like a producer-consumer model I learnt in OS lessons before. It was the first time that I implement some theoretical OS stuff into actual program, it really took me a while to fix all these bugs created by a fool filled with ignorance, which is me…
Anyway, the core idea is like this:
- The guest program(application) could write audio data to sound buffer register(
CONFIG_SB_ADDR
). - AM would then write the audio data to the queue(
sbuf
, the ring buffer) when there’re enough space. - NEMU would read audio data from the queue(
sbuf
) and copy them to SDL2’s stream. BTW, this process is actually done in SDL_Audio’s callback function.
- The guest program(application) could write audio data to sound buffer register(
Key difficulties when maintaining
sbuf
:When AM writes audio data:
- Check whether there’re enough space(
count + len <= sbuf_size
). If not, halt until there are. - Check whether the write-pointer has reached the boundary of the queue. If so, reset the pointer to the start, and write the rest of the data.
- Check whether there’re enough space(
When NEMU reads audio data:
- Check whether there’re valid data in
sbuf
(count >= 0
). If not, return. - Check whether the read-pointer has reached the boundary of the queue. If so, reset the pointer to the start, and read the rest of the required data.
- Check whether there’re valid data in
In real practice, the speed of AM writing audio data is significantly faster than NEMU reading, as the frequency of calling SDL2’s callback function is pretty low.
End of PA2
- It’s been quite a journey since I started learning PA. I consider myself utterly fortunate to have found a lesson that aligns so perfectly with what psychologists called the Zone of Proximal Development. PA has been exactly that for me – a space where I can challenge myself while steadily improving.
- When I first began learning PA, I couldn’t even write a basic linked list in C. Now, I found myself maintaining a ring buffer queue that is accessed by differenct programs. How exciting to see how far I’ve come!
- I am not a student at NJU, not even a CS major student to be precise. I am also not a 985/211 university student. However, my passion for programming, curiosity about the knowledge behind the computing machine, and the desire for meaningful challenges have guided me to this point. This lesson, PA, has shown me the way of learning. I know there’s still a long way to go, but the courage I’ve gained will surely motivate me to continue the rest of my journey.
- Looking back, now I feel my college life has not been wasted.
I want to express my gratitude to:
- Dr. HuiYan Wang (http://www.why.ink:8080) for her excellent open classes for PA on Bilibili.
- Dr. ZiHao Yu (https://sashimi-yzh.github.io) for his brilliant emulator NEMU and the detailed manual for PA.
- The members of the ysyx QQ group, who provided invaluable support.
- And, of course, the vast resources available on the Internet – wiki, documentation, forums – and the assistance from LLMs like ChatGPT and Claude.
I will be resting for a while, and then head for the pre-learning of ysyx, which is basicaly knowledge and expirement of Digital Circuit.
Cheers!
- Title: PA2 Part 2 - Emulated Hardware Device
- Author: Last
- Created at : 2024-11-22 18:10:25
- Link: https://blog.imlast.top/2024/11/22/nju-pa-2-part2/
- License: This work is licensed under CC BY-NC-SA 4.0.