From 515e0115166e20824bcb7d868af0d0e934511bca Mon Sep 17 00:00:00 2001 From: ChinYikMing Date: Sat, 1 Feb 2025 06:20:26 +0800 Subject: [PATCH 1/2] Trap guestOS to run SDL-oriented applications To trap the guest OS SDL applications, virtual memory translation should be handled when accessing the bidirectional event queues in syscall_sdl.c. Additionally, when using the guest OS, the SDL application might be launched and terminated multiple times. To enhance the user experience, it is heuristic to properly handle three main types of exits: 1, SDL application built-in exit: The source code of the SDL application should be modified to call the syscall_exit(syscall number: 93) somewhere when executing cleanup routine before exit(). 2. Ctrl-c (SIGINT) exit: Detect the ctrl-c keycode. 3. SDL window Quit event: The source code of the SDL application should be modified to call the syscall_exit(syscall number: 93) when receiving SDL window Quit event. The main reason for handling these three types of exits is that SDL2_Mixer may malfunction if not properly initialized and destroyed, as it runs on the host side not on the guest side. Additionally, when terminating an SDL application in the guest OS, the previous SDL window should be closed. Moreover, the default audio backend of SDL2_Mixer(downloadable from brew or Linux distro pkg managers), FluidSynth, occasionally generates the warning: "fluidsynth: warning: Ringbuffer full, try increasing synth.polyphony!" This issue causes the audio to hang, leading to an unstable playback experience. Thus, dropping FluidSynth in favor of the Timidity backend, which provides a stable audio playback experience. Known issue: Calling SDL_DestroyWindow() on macOS does not close the previous SDL window. Close: #510 --- src/devices/uart.c | 15 +++- src/emulate.c | 17 +++- src/syscall.c | 13 ++- src/syscall_sdl.c | 203 ++++++++++++++++++++++++++++++++++++++++----- src/system.c | 6 +- 5 files changed, 226 insertions(+), 28 deletions(-) diff --git a/src/devices/uart.c b/src/devices/uart.c index f4cfa892..69bed7d1 100644 --- a/src/devices/uart.c +++ b/src/devices/uart.c @@ -12,7 +12,6 @@ #include #include "uart.h" - /* Emulate 8250 (plain, without loopback mode support) */ #define U8250_INTR_THRE 1 @@ -66,10 +65,22 @@ static uint8_t u8250_handle_in(u8250_state_t *uart) if (value == 1) { /* start of heading (Ctrl-a) */ if (getchar() == 120) { /* keyboard x */ printf("\n"); /* end emulator with newline */ - exit(0); + exit(EXIT_SUCCESS); } } +#if RV32_HAS(SDL) && RV32_HAS(SYSTEM) && !RV32_HAS(ELF_LOADER) + /* + * The guestOS may repeatedly open and close the SDL window, + * and the user could close the application by pressing the ctrl-c key. + * Need to trap the ctrl-c key and ensure the SDL window and + * SDL mixer are destroyed properly. + */ + extern void sdl_video_audio_cleanup(); + if (value == 3) /* ctrl-c */ + sdl_video_audio_cleanup(); +#endif + return value; } diff --git a/src/emulate.c b/src/emulate.c index 2c430b69..76df73e0 100644 --- a/src/emulate.c +++ b/src/emulate.c @@ -1299,7 +1299,22 @@ void ecall_handler(riscv_t *rv) syscall_handler(rv); #elif RV32_HAS(SYSTEM) if (rv->priv_mode == RV_PRIV_U_MODE) { - SET_CAUSE_AND_TVAL_THEN_TRAP(rv, ECALL_U, 0); + switch (rv_get_reg( + rv, + rv_reg_a7)) { /* trap guestOS's SDL-oriented application syscall */ + case 0xBEEF: + case 0xC0DE: + case 0xFEED: + case 0xBABE: + case 0xD00D: + case 93: + syscall_handler(rv); + rv->PC += 4; + break; + default: + SET_CAUSE_AND_TVAL_THEN_TRAP(rv, ECALL_U, 0); + break; + } } else if (rv->priv_mode == RV_PRIV_S_MODE) { /* trap to SBI syscall handler */ rv->PC += 4; diff --git a/src/syscall.c b/src/syscall.c index 86f9d2ca..1834550d 100644 --- a/src/syscall.c +++ b/src/syscall.c @@ -132,9 +132,20 @@ static void syscall_write(riscv_t *rv) rv_set_reg(rv, rv_reg_a0, -1); } - static void syscall_exit(riscv_t *rv) { +#if RV32_HAS(SDL) && RV32_HAS(SYSTEM) && !RV32_HAS(ELF_LOADER) + /* + * The guestOS may repeatedly open and close the SDL window, + * and the user could close the application using the application’s + * built-in exit function. Need to trap the built-in exit and + * ensure the SDL window and SDL mixer are destroyed properly. + */ + extern void sdl_video_audio_cleanup(); + sdl_video_audio_cleanup(); + return; +#endif + /* simply halt cpu and save exit code. * the application decides the usage of exit code */ diff --git a/src/syscall_sdl.c b/src/syscall_sdl.c index fa4f9c70..9b1ae055 100644 --- a/src/syscall_sdl.c +++ b/src/syscall_sdl.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -37,6 +38,42 @@ /* Max size of sound is around 18000 bytes */ #define SFX_SAMPLE_SIZE 32768 +#define R 1 +#define W 0 + +#if RV32_HAS(SYSTEM) && !RV32_HAS(ELF_LOADER) +#define get_offset_by_addr(addr) (addr) & (MASK(RV_PG_SHIFT)) +static uint32_t offset; +static uint32_t curr_page_data_size; +static uint32_t remain_size; +static uint32_t curr_offset; +static uint32_t curr_data_size; + +#define GET_DATA_FROM_RANDOM_PAGE(source_vaddr, dest) \ + do { \ + while (remain_size > 0) { \ + uint32_t screen_addr = \ + rv->io.mem_translate(rv, source_vaddr + curr_offset, R); \ + offset = get_offset_by_addr(screen_addr); \ + curr_page_data_size = RV_PG_SIZE - offset; \ + curr_data_size = remain_size <= curr_page_data_size \ + ? remain_size \ + : curr_page_data_size; \ + memory_read(attr->mem, dest + curr_offset, screen_addr, \ + sizeof(uint8_t) * curr_data_size); \ + remain_size -= curr_data_size; \ + curr_offset += curr_data_size; \ + } \ + } while (0) + +#define GET_VIDEO_DATA_FROM_RANDOM_PAGE(source_vaddr, dest) \ + GET_DATA_FROM_RANDOM_PAGE(source_vaddr, dest) +#define GET_SFX_DATA_FROM_RANDOM_PAGE(source_vaddr, dest) \ + GET_DATA_FROM_RANDOM_PAGE(source_vaddr, dest) +#define GET_MUSIC_DATA_FROM_RANDOM_PAGE(source_vaddr, dest) \ + GET_DATA_FROM_RANDOM_PAGE(source_vaddr, dest) +#endif + /* sound-related request type */ enum { INIT_AUDIO, @@ -66,6 +103,11 @@ static uint8_t *sfx_samples; static uint32_t nr_sfx_samples; static int chan; +/* Used to properly destroy audio, compatible to process VM emulation */ +static bool audio_init = false; +static bool sfx_thread_init = false; +static bool music_thread_init = false; + typedef struct { void *data; int size; @@ -181,9 +223,9 @@ static void event_push(riscv_t *rv, event_t event) event_queue.end &= queues_capacity - 1; uint32_t count; - memory_read(attr->mem, (void *) &count, event_count, sizeof(uint32_t)); + count = rv->io.mem_read_w(rv, event_count); count += 1; - memory_write(attr->mem, event_count, (void *) &count, sizeof(uint32_t)); + rv->io.mem_write_w(rv, event_count, count); } static inline uint32_t round_pow2(uint32_t x) @@ -213,7 +255,7 @@ static bool check_sdl(riscv_t *rv, int width, int height) if (!window) { /* check if video has been initialized. */ if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) != 0) { fprintf(stderr, "Failed to call SDL_Init()\n"); - exit(1); + exit(EXIT_FAILURE); } window = SDL_CreateWindow("rv32emu", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, width, height, @@ -221,7 +263,7 @@ static bool check_sdl(riscv_t *rv, int width, int height) if (!window) { fprintf(stderr, "Window could not be created! SDL_Error: %s\n", SDL_GetError()); - exit(1); + exit(EXIT_FAILURE); } renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_PRESENTVSYNC); @@ -307,12 +349,26 @@ void syscall_draw_frame(riscv_t *rv) if (!check_sdl(rv, width, height)) return; - /* read directly into video memory */ + uint32_t total_size = width * height * 4; +#if RV32_HAS(SYSTEM) && !RV32_HAS(ELF_LOADER) + static uint8_t tmp_buf[256 * RV_PG_SIZE]; + uint8_t *tmp_buf_ptr = &tmp_buf[0]; + uint32_t screen_vaddr = screen; + remain_size = total_size; + curr_offset = 0; + memset(tmp_buf_ptr, 0, sizeof(tmp_buf)); + + GET_VIDEO_DATA_FROM_RANDOM_PAGE(screen_vaddr, tmp_buf_ptr); +#endif int pitch = 0; void *pixels_ptr; if (SDL_LockTexture(texture, NULL, &pixels_ptr, &pitch)) - exit(-1); - memory_read(attr->mem, pixels_ptr, screen, width * height * 4); + exit(EXIT_FAILURE); +#if RV32_HAS(SYSTEM) && !RV32_HAS(ELF_LOADER) + memcpy(pixels_ptr, tmp_buf_ptr, total_size); +#else + memory_read(attr->mem, pixels_ptr, screen, total_size); +#endif SDL_UnlockTexture(texture); int actual_width, actual_height; @@ -324,13 +380,34 @@ void syscall_draw_frame(riscv_t *rv) void syscall_setup_queue(riscv_t *rv) { +#if RV32_HAS(SYSTEM) && !RV32_HAS(ELF_LOADER) + /* + * The guestOS might exit and execute the SDL-based program again + * thus clearing the queue is required to avoid using the + * access the old events. + */ + event_queue.base = event_queue.end = 0; + submission_queue.base = submission_queue.start = 0; +#endif + /* setup_queue(base, capacity, event_count) */ uint32_t base = rv_get_reg(rv, rv_reg_a0); queues_capacity = rv_get_reg(rv, rv_reg_a1); event_count = rv_get_reg(rv, rv_reg_a2); +#if RV32_HAS(SYSTEM) && !RV32_HAS(ELF_LOADER) + uint32_t submission_queue_addr = + rv->io.mem_translate(rv, base + sizeof(event_t) * queues_capacity, R); + + uint32_t event_queue_addr = rv->io.mem_translate(rv, base, R); + + /* now the bases are the gPA and host can access directly */ + event_queue.base = event_queue_addr; + submission_queue.base = submission_queue_addr; +#else event_queue.base = base; submission_queue.base = base + sizeof(event_t) * queues_capacity; +#endif queues_capacity = round_pow2(queues_capacity); } @@ -360,8 +437,14 @@ void syscall_submit_queue(riscv_t *rv) if (unlikely(!title)) return; +#if RV32_HAS(SYSTEM) && !RV32_HAS(ELF_LOADER) + uint32_t addr = rv->io.mem_translate(rv, submission.title.title, R); + memory_read(PRIV(rv)->mem, (uint8_t *) title, addr, + submission.title.size); +#else memory_read(PRIV(rv)->mem, (uint8_t *) title, submission.title.title, submission.title.size); +#endif title[submission.title.size] = 0; SDL_SetWindowTitle(window, title); @@ -718,6 +801,22 @@ static void play_sfx(riscv_t *rv) int volume = rv_get_reg(rv, rv_reg_a2); sfxinfo_t sfxinfo; + uint8_t sfx_data[SFX_SAMPLE_SIZE]; +#if RV32_HAS(SYSTEM) && !RV32_HAS(ELF_LOADER) + uint32_t addr = rv->io.mem_translate(rv, sfxinfo_addr, R); + memory_read(attr->mem, (uint8_t *) &sfxinfo, addr, sizeof(sfxinfo_t)); + + uint32_t sfx_data_offset = + *((uint32_t *) ((uint8_t *) attr->mem->mem_base + addr)); + uint32_t sfx_data_size = + *(uint32_t *) ((uint8_t *) attr->mem->mem_base + addr + 4); + uint32_t sfx_data_vaddr = sfx_data_offset; + uint8_t *sfx_data_ptr = &sfx_data[0]; + remain_size = sfx_data_size; + curr_offset = 0; + + GET_SFX_DATA_FROM_RANDOM_PAGE(sfx_data_vaddr, sfx_data_ptr); +#else memory_read(attr->mem, (uint8_t *) &sfxinfo, sfxinfo_addr, sizeof(sfxinfo_t)); @@ -727,9 +826,9 @@ static void play_sfx(riscv_t *rv) */ uint32_t sfx_data_offset = *((uint32_t *) &sfxinfo); uint32_t sfx_data_size = *(uint32_t *) ((uint32_t *) &sfxinfo + 1); - uint8_t sfx_data[SFX_SAMPLE_SIZE]; memory_read(attr->mem, sfx_data, sfx_data_offset, sizeof(uint8_t) * sfx_data_size); +#endif sound_t sfx = { .data = sfx_data, @@ -737,6 +836,7 @@ static void play_sfx(riscv_t *rv) .volume = volume, }; pthread_create(&sfx_thread, NULL, sfx_handler, &sfx); + sfx_thread_init = true; /* FIXME: In web browser runtime, web workers in thread pool do not reap * after sfx_handler return, thus we have to join them. sfx_handler does not * contain infinite loop,so do not worry to be stalled by it */ @@ -754,6 +854,26 @@ static void play_music(riscv_t *rv) int looping = rv_get_reg(rv, rv_reg_a3); musicinfo_t musicinfo; + uint8_t music_data[MUSIC_MAX_SIZE]; +#if RV32_HAS(SYSTEM) && !RV32_HAS(ELF_LOADER) + uint32_t addr = rv->io.mem_translate(rv, musicinfo_addr, R); + memory_read(attr->mem, (uint8_t *) &musicinfo, addr, sizeof(musicinfo_t)); + + /* The data and size in the application must be positioned in the first two + * fields of the structure. This ensures emulator compatibility with + * various applications when accessing different musicinfo_t instances. + */ + uint32_t music_data_offset = + *((uint32_t *) ((uint8_t *) attr->mem->mem_base + addr)); + uint32_t music_data_size = + *(uint32_t *) ((uint8_t *) attr->mem->mem_base + addr + 4); + uint32_t music_data_vaddr = music_data_offset; + uint8_t *music_data_ptr = &music_data[0]; + remain_size = music_data_size; + curr_offset = 0; + + GET_MUSIC_DATA_FROM_RANDOM_PAGE(music_data_vaddr, music_data_ptr); +#else memory_read(attr->mem, (uint8_t *) &musicinfo, musicinfo_addr, sizeof(musicinfo_t)); @@ -763,8 +883,8 @@ static void play_music(riscv_t *rv) */ uint32_t music_data_offset = *((uint32_t *) &musicinfo); uint32_t music_data_size = *(uint32_t *) ((uint32_t *) &musicinfo + 1); - uint8_t music_data[MUSIC_MAX_SIZE]; memory_read(attr->mem, music_data, music_data_offset, music_data_size); +#endif sound_t music = { .data = music_data, @@ -773,6 +893,7 @@ static void play_music(riscv_t *rv) .volume = volume, }; pthread_create(&music_thread, NULL, music_handler, &music); + music_thread_init = true; /* FIXME: In web browser runtime, web workers in thread pool do not reap * after music_handler return, thus we have to join them. music_handler does * not contain infinite loop,so do not worry to be stalled by it */ @@ -781,7 +902,7 @@ static void play_music(riscv_t *rv) #endif } -static void stop_music(riscv_t *rv UNUSED) +static void stop_music() { if (Mix_PlayingMusic()) Mix_HaltMusic(); @@ -800,7 +921,7 @@ static void init_audio(void) if (!(SDL_WasInit(-1) & SDL_INIT_AUDIO)) { if (SDL_Init(SDL_INIT_AUDIO) != 0) { fprintf(stderr, "Failed to call SDL_Init()\n"); - exit(1); + exit(EXIT_FAILURE); } } @@ -808,29 +929,69 @@ static void init_audio(void) sfx_samples = malloc(SFX_SAMPLE_SIZE); if (unlikely(!sfx_samples)) { fprintf(stderr, "Failed to allocate memory for buffer\n"); - exit(1); + exit(EXIT_FAILURE); } /* Initialize SDL2 Mixer */ if (Mix_Init(MIX_INIT_MID) != MIX_INIT_MID) { fprintf(stderr, "Mix_Init failed: %s\n", Mix_GetError()); - exit(1); + exit(EXIT_FAILURE); } if (Mix_OpenAudio(SAMPLE_RATE, AUDIO_U8, CHANNEL_USED, CHUNK_SIZE) == -1) { fprintf(stderr, "Mix_OpenAudio failed: %s\n", Mix_GetError()); Mix_Quit(); - exit(1); + exit(EXIT_FAILURE); } + audio_init = true; } -static void shutdown_audio(riscv_t *rv) +static void shutdown_audio() { - stop_music(rv); - Mix_HaltChannel(-1); + /* + * sfx_thread and music_thread might contain invalid identifiers. + * Additionally, there is no built-in method to verify the validity + * of a pthread_t type. Therefore, the sfx_thread_init and music_thread_init + * flags are used for validation, ensuring that pthread_join always operates + * on a valid pthread_t identifier. + */ + + if (music_thread_init) { + stop_music(); + pthread_join(music_thread, NULL); + Mix_FreeMusic(mid); + free(music_midi_data); + music_midi_data = NULL; + } + + if (sfx_thread_init) { + pthread_join(sfx_thread, NULL); + Mix_HaltChannel(-1); + Mix_FreeChunk(sfx_chunk); + free(sfx_samples); + sfx_samples = NULL; + } + Mix_CloseAudio(); Mix_Quit(); - free(music_midi_data); - free(sfx_samples); + + audio_init = sfx_thread_init = music_thread_init = false; +} + +void sdl_video_audio_cleanup() +{ + if (window) { + SDL_DestroyWindow(window); + window = NULL; + } + /* + * The sfx_or_music_thread_init flag might not be set if a quick ctrl-c + * occurs while the audio configuration is being initialized. Therefore, + * need to destroy the audio settings by checking audio_init flag. + */ + bool sfx_or_music_thread_init = sfx_thread_init | music_thread_init; + if (sfx_or_music_thread_init || (!sfx_or_music_thread_init && audio_init)) + shutdown_audio(); + SDL_Quit(); } void syscall_setup_audio(riscv_t *rv) @@ -843,7 +1004,7 @@ void syscall_setup_audio(riscv_t *rv) init_audio(); break; case SHUTDOWN_AUDIO: - shutdown_audio(rv); + shutdown_audio(); break; default: fprintf(stderr, "unknown sound request\n"); @@ -867,7 +1028,7 @@ void syscall_control_audio(riscv_t *rv) set_music_volume(rv); break; case STOP_MUSIC: - stop_music(rv); + stop_music(); break; default: fprintf(stderr, "unknown sound control request\n"); diff --git a/src/system.c b/src/system.c index 4a5aac97..d454a25e 100644 --- a/src/system.c +++ b/src/system.c @@ -118,8 +118,6 @@ enum SUPPORTED_MMIO { } while (0) #endif -uint32_t ppn; -uint32_t offset; static bool ppn_is_valid(riscv_t *rv, uint32_t ppn) { vm_attr_t *attr = PRIV(rv); @@ -139,7 +137,7 @@ static bool ppn_is_valid(riscv_t *rv, uint32_t ppn) * @level: the level of which the PTE is located * @return: NULL if a not found or fault else the corresponding PTE */ -uint32_t *mmu_walk(riscv_t *rv, const uint32_t vaddr, uint32_t *level) +pte_t *mmu_walk(riscv_t *rv, const uint32_t vaddr, uint32_t *level) { vm_attr_t *attr = PRIV(rv); uint32_t ppn = rv->csr_satp & MASK(22); @@ -259,6 +257,8 @@ MMU_FAULT_CHECK_IMPL(ifetch, pagefault_insn) MMU_FAULT_CHECK_IMPL(read, pagefault_load) MMU_FAULT_CHECK_IMPL(write, pagefault_store) +uint32_t ppn; +uint32_t offset; #define get_ppn_and_offset() \ do { \ assert(pte); \ From f20b95901808f68158cf65638a8836890c33f0b4 Mon Sep 17 00:00:00 2001 From: ChinYikMing Date: Sat, 1 Feb 2025 18:19:10 +0800 Subject: [PATCH 2/2] Update README to reflect running SDL-oriented applications in guestOS --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 1fae4584..1e72285f 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,12 @@ $ make ENABLE_SYSTEM=1 $ build/rv32emu -k -i ``` +Build with a larger INITRD_SIZE (e.g., 64 MiB) to run SDL-oriented application because the default 8 MiB is insufficient for SDL-oriented application artifacts: +```shell +$ make system ENABLE_SYSTEM=1 ENABLE_SDL=1 INITRD_SIZE=64 +``` +Once login the guestOS, run `doom-riscv` or `quake` or `smolnes`. To terminate SDL-oriented applications, use the built-in exit utility, ctrl-c or the SDL window close button(X). + #### Build Linux image An automated build script is provided to compile the RISC-V cross-compiler, Busybox, and Linux kernel from source. Please note that it only supports the Linux host environment. It can be found at tools/build-linux-image.sh. ```