From 84cd7b2934146b7ad2917545f8d4c9fbbe97fea3 Mon Sep 17 00:00:00 2001 From: David Guillen Fandos Date: Wed, 14 Jul 2021 01:17:55 +0200 Subject: [PATCH] Rewrite ROM buffer allocation This improves the existing on-demand ROM paging and also breaks down ROM buffers into 1MB blocks for platforms with memory fragmentation issues. Fixed some potential RTC reg issue in said platforms too. Fixed page pinning on interpreter (would crash due to LRU evictions). --- cpu.c | 2 + gba_memory.c | 433 ++++++++++++++++++++++++-------------------------- gba_memory.h | 17 +- gpsp_config.h | 3 + libretro.c | 8 +- 5 files changed, 235 insertions(+), 228 deletions(-) diff --git a/cpu.c b/cpu.c index c50f643..6f322c0 100644 --- a/cpu.c +++ b/cpu.c @@ -690,6 +690,7 @@ void print_register_usage(void) { \ pc_region = new_pc_region; \ pc_address_block = memory_map_read[new_pc_region]; \ + touch_gamepak_page(pc_region); \ \ if(!pc_address_block) \ pc_address_block = load_gamepak_page(pc_region & 0x3FF); \ @@ -1635,6 +1636,7 @@ void execute_arm(u32 cycles) if(!pc_address_block) pc_address_block = load_gamepak_page(pc_region & 0x3FF); + touch_gamepak_page(pc_region); cycles_remaining = cycles; while(1) diff --git a/gba_memory.c b/gba_memory.c index 1c45349..14db907 100644 --- a/gba_memory.c +++ b/gba_memory.c @@ -319,23 +319,31 @@ u32 bios_read_protect; // Up to 128kb, store SRAM, flash ROM, or EEPROM here. u8 gamepak_backup[1024 * 128]; -// Keeps us knowing how much we have left. -u8 *gamepak_rom; -u32 gamepak_size; - dma_transfer_type dma[4]; -typedef struct -{ - u32 page_timestamp; - u32 physical_index; -} gamepak_swap_entry_type; +// ROM memory is allocated in blocks of 1MB to better map the native block +// mapping system. We will try to allocate 32 of them to allow loading +// ROMs up to 32MB, but we might fail on memory constrained systems. -u32 gamepak_ram_buffer_size; -u32 gamepak_ram_pages; +u8 *gamepak_buffers[32]; /* Pointers to malloc'ed blocks */ +u32 gamepak_buffer_count; /* Value between 1 and 32 */ +u32 gamepak_size; /* Size of the ROM in bytes */ -// Enough to map the gamepak RAM space. -gamepak_swap_entry_type *gamepak_memory_map; +// LRU queue with the loaded blocks and what they map to +struct { + u16 next_lru; /* Index in the struct to the next LRU entry */ + s16 phy_rom; /* ROM page number (-1 means not mapped) */ +} gamepak_blk_queue[1024]; + +u16 gamepak_lru_head; +u16 gamepak_lru_tail; + +// Stick page bit: prevents page eviction for a frame. This is used to prevent +// unmapping code pages while being used (ie. in the interpreter). +u32 gamepak_sticky_bit[1024/32]; + +#define gamepak_sb_test(idx) \ + (gamepak_sticky_bit[((unsigned)(idx)) >> 5] & (1 << (((unsigned)(idx)) & 31))) // This is global so that it can be kept open for large ROMs to swap // pages from, so there's no slowdown with opening and closing the file @@ -1676,16 +1684,16 @@ static u32 encode_bcd(u8 value) return h * 16 + l; } +// RTC writes need to reflect in the bytes [0xC4..0xC9] of the gamepak #define write_rtc_register(index, _value) \ update_address = 0x80000C4 + (index * 2); \ rtc_registers[index] = _value; \ rtc_page_index = update_address >> 15; \ map = memory_map_read[rtc_page_index]; \ \ - if(!map) \ - map = load_gamepak_page(rtc_page_index & 0x3FF); \ - \ - address16(map, update_address & 0x7FFF) = eswap16(_value) \ + if(map) { \ + address16(map, update_address & 0x7FFF) = eswap16(_value); \ + } \ void function_cc write_rtc(u32 address, u32 value) { @@ -2191,27 +2199,34 @@ typedef struct u32 translation_gate_target_3; } ini_t; +typedef struct +{ + char gamepak_title[13]; + char gamepak_code[5]; + char gamepak_maker[3]; +} gamepak_info_t; + #include "gba_over.h" -static s32 load_game_config_over(char *gamepak_title, char *gamepak_code, char *gamepak_maker) +static s32 load_game_config_over(gamepak_info_t *gpinfo) { unsigned i = 0; for (i = 0; i < sizeof(gbaover)/sizeof(gbaover[0]); i++) { - if (strcmp(gbaover[i].gamepak_code, gamepak_code)) + if (strcmp(gbaover[i].gamepak_code, gpinfo->gamepak_code)) continue; - if (strcmp(gbaover[i].gamepak_title, gamepak_title)) + if (strcmp(gbaover[i].gamepak_title, gpinfo->gamepak_title)) continue; printf("gamepak title: %s\n", gbaover[i].gamepak_title); printf("gamepak code : %s\n", gbaover[i].gamepak_code); printf("gamepak maker: %s\n", gbaover[i].gamepak_maker); - printf("INPUT gamepak title: %s\n", gamepak_title); - printf("INPUT gamepak code : %s\n", gamepak_code); - printf("INPUT gamepak maker: %s\n", gamepak_maker); + printf("INPUT gamepak title: %s\n", gpinfo->gamepak_title); + printf("INPUT gamepak code : %s\n", gpinfo->gamepak_code); + printf("INPUT gamepak maker: %s\n", gpinfo->gamepak_maker); if (gbaover[i].idle_loop_target_pc != 0) idle_loop_target_pc = gbaover[i].idle_loop_target_pc; @@ -2248,7 +2263,7 @@ static s32 load_game_config_over(char *gamepak_title, char *gamepak_code, char * return -1; } -static s32 load_game_config(char *gamepak_title, char *gamepak_code, char *gamepak_maker) +static s32 load_game_config(gamepak_info_t *gpinfo) { char current_line[256]; char current_variable[256]; @@ -2270,21 +2285,21 @@ static s32 load_game_config(char *gamepak_title, char *gamepak_code, char *gamep != -1) { if(strcmp(current_variable, "game_name") || - strcmp(current_value, gamepak_title)) + strcmp(current_value, gpinfo->gamepak_title)) continue; if(!fgets(current_line, 256, config_file) || (parse_config_line(current_line, current_variable, current_value) == -1) || strcmp(current_variable, "game_code") || - strcmp(current_value, gamepak_code)) + strcmp(current_value, gpinfo->gamepak_code)) continue; if(!fgets(current_line, 256, config_file) || (parse_config_line(current_line, current_variable, current_value) == -1) || strcmp(current_variable, "vender_code") || - strcmp(current_value, gamepak_maker)) + strcmp(current_value, gpinfo->gamepak_maker)) continue; while(fgets(current_line, 256, config_file)) @@ -2333,108 +2348,6 @@ static s32 load_game_config(char *gamepak_title, char *gamepak_code, char *gamep return -1; } -static s32 load_gamepak_raw(const char *name) -{ - FILE *fd = fopen(name, "rb"); - - if(fd) - { - u32 file_size = file_length(fd); - - // First, close the last one if it was open, we won't - // be needing it anymore. - if(gamepak_file_large) - fclose(gamepak_file_large); - - // If it's a big file size keep it, don't close it, we'll - // probably want to load from it more later. - if(file_size <= gamepak_ram_buffer_size) - { - fread(gamepak_rom, 1, file_size, fd); - fclose(fd); - - gamepak_file_large = NULL; - } - else - { - // Read in just enough for the header - fread(gamepak_rom, 1, 0x100, fd); - gamepak_file_large = fd; - } - - return file_size; - } - - return -1; -} - -char gamepak_title[13]; -char gamepak_code[5]; -char gamepak_maker[3]; -char gamepak_filename[512]; - -u32 load_gamepak(const struct retro_game_info* info, const char *name) -{ - char *p; - - s32 file_size = load_gamepak_raw(name); - - if(file_size == -1) - return -1; - - gamepak_size = (file_size + 0x7FFF) & ~0x7FFF; - - strncpy(gamepak_filename, name, sizeof(gamepak_filename)); - gamepak_filename[sizeof(gamepak_filename) - 1] = 0; - - p = strrchr(gamepak_filename, PATH_SEPARATOR_CHAR); - if (p) - p++; - else - p = gamepak_filename; - - snprintf(backup_filename, sizeof(backup_filename), "%s%c%s", save_path, PATH_SEPARATOR_CHAR, p); - p = strrchr(backup_filename, '.'); - if (p) - strcpy(p, ".sav"); - - if (!use_libretro_save_method) - load_backup(backup_filename); - - memcpy(gamepak_title, gamepak_rom + 0xA0, 12); - memcpy(gamepak_code, gamepak_rom + 0xAC, 4); - memcpy(gamepak_maker, gamepak_rom + 0xB0, 2); - gamepak_title[12] = 0; - gamepak_code[4] = 0; - gamepak_maker[2] = 0; - - idle_loop_target_pc = 0xFFFFFFFF; - iwram_stack_optimize = 1; - translation_gate_targets = 0; - flash_device_id = FLASH_DEVICE_MACRONIX_64KB; - flash_size = FLASH_SIZE_64KB; - - if ((load_game_config_over(gamepak_title, gamepak_code, gamepak_maker)) == -1) - load_game_config(gamepak_title, gamepak_code, gamepak_maker); - - return 0; -} - -s32 load_bios(char *name) -{ - FILE *fd = fopen(name, "rb"); - - if(!fd) - return -1; - - fread(bios_rom, 1, 0x4000, fd); - - // This is a hack to get Zelda working, because emulating - // the proper memory read behavior here is much too expensive. - fclose(fd); - return 0; -} - // DMA memory regions can be one of the following: // IWRAM - 32kb offset from the contiguous iwram region. // EWRAM - also contiguous but with self modifying code check mirror. @@ -3051,6 +2964,17 @@ cpu_alert_type dma_transfer(dma_transfer_type *dma) // Be sure to do this after loading ROMs. +#define map_rom_entry(type, idx, ptr, mirror_blocks) { \ + unsigned mcount; \ + for(mcount = 0; mcount < 1024; mcount += (mirror_blocks)) { \ + memory_map_##type[(0x8000000 / (32 * 1024)) + (idx) + mcount] = (ptr); \ + memory_map_##type[(0xA000000 / (32 * 1024)) + (idx) + mcount] = (ptr); \ + } \ + for(mcount = 0; mcount < 512; mcount += (mirror_blocks)) { \ + memory_map_##type[(0xC000000 / (32 * 1024)) + (idx) + mcount] = (ptr); \ + } \ +} + #define map_region(type, start, end, mirror_blocks, region) \ for(map_offset = (start) / 0x8000; map_offset < \ ((end) / 0x8000); map_offset++) \ @@ -3059,10 +2983,12 @@ cpu_alert_type dma_transfer(dma_transfer_type *dma) ((u8 *)region) + ((map_offset % mirror_blocks) * 0x8000); \ } \ -#define map_null(type, start, end) \ +#define map_null(type, start, end) { \ + u32 map_offset; \ for(map_offset = start / 0x8000; map_offset < (end / 0x8000); \ map_offset++) \ memory_map_##type[map_offset] = NULL; \ +} #define map_vram(type) \ for(map_offset = 0x6000000 / 0x8000; map_offset < (0x7000000 / 0x8000); \ @@ -3075,115 +3001,85 @@ cpu_alert_type dma_transfer(dma_transfer_type *dma) } \ -// Picks a page to evict -u32 page_time = 0; - static u32 evict_gamepak_page(void) { - // Find the one with the smallest frame timestamp - u32 page_index = 0; - u32 physical_index; - u32 smallest = gamepak_memory_map[0].page_timestamp; - u32 i; + u32 ret; + s16 phyrom; + do { + // Return the index to the last used entry + u32 newhead = gamepak_blk_queue[gamepak_lru_head].next_lru; + phyrom = gamepak_blk_queue[gamepak_lru_head].phy_rom; + ret = gamepak_lru_head; - for(i = 1; i < gamepak_ram_pages; i++) - { - if(gamepak_memory_map[i].page_timestamp <= smallest) - { - smallest = gamepak_memory_map[i].page_timestamp; - page_index = i; - } + // Second elem becomes head now + gamepak_lru_head = newhead; + + // The evicted element goes at the end of the queue + gamepak_blk_queue[gamepak_lru_tail].next_lru = ret; + gamepak_lru_tail = ret; + // If this page is marked as sticky, we keep going through the list + } while (phyrom >= 0 && gamepak_sb_test(phyrom)); + + // We unmap the ROM page if it was mapped, ensure we do not access it + // without triggering a "page fault" + if (phyrom >= 0) { + map_rom_entry(read, phyrom, NULL, gamepak_size >> 15); } - physical_index = gamepak_memory_map[page_index].physical_index; - - memory_map_read[(0x8000000 / (32 * 1024)) + physical_index] = NULL; - memory_map_read[(0xA000000 / (32 * 1024)) + physical_index] = NULL; - memory_map_read[(0xC000000 / (32 * 1024)) + physical_index] = NULL; - - return page_index; + return ret; } u8 *load_gamepak_page(u32 physical_index) { if(physical_index >= (gamepak_size >> 15)) - return gamepak_rom; + return &gamepak_buffers[0][0]; - u32 page_index = evict_gamepak_page(); - u32 page_offset = page_index * (32 * 1024); - u8 *swap_location = gamepak_rom + page_offset; + u32 entry = evict_gamepak_page(); + u32 block_idx = entry / 32; + u32 block_off = entry % 32; + u8 *swap_location = &gamepak_buffers[block_idx][32 * 1024 * block_off]; - gamepak_memory_map[page_index].page_timestamp = page_time; - gamepak_memory_map[page_index].physical_index = physical_index; - page_time++; + // Fill in the entry + gamepak_blk_queue[entry].phy_rom = physical_index; fseek(gamepak_file_large, physical_index * (32 * 1024), SEEK_SET); fread(swap_location, 1, (32 * 1024), gamepak_file_large); - memory_map_read[(0x8000000 / (32 * 1024)) + physical_index] = swap_location; - memory_map_read[(0xA000000 / (32 * 1024)) + physical_index] = swap_location; - memory_map_read[(0xC000000 / (32 * 1024)) + physical_index] = swap_location; + + // Map it to the read handlers now + map_rom_entry(read, physical_index, swap_location, gamepak_size >> 15); // If RTC is active page the RTC register bytes so they can be read - if((rtc_state != RTC_DISABLED) && (physical_index == 0)) - memcpy(swap_location + 0xC4, rtc_registers, sizeof(rtc_registers)); + if ((rtc_state != RTC_DISABLED) && (physical_index == 0)) { + address16(swap_location, 0xC4) = eswap16(rtc_registers[0]); + address16(swap_location, 0xC6) = eswap16(rtc_registers[1]); + address16(swap_location, 0xC8) = eswap16(rtc_registers[2]); + } return swap_location; } -static void init_memory_gamepak(void) -{ - u32 map_offset = 0; - - if(gamepak_size > gamepak_ram_buffer_size) - { - // Large ROMs get special treatment because they - // can't fit into the 16MB ROM buffer. - u32 i; - for(i = 0; i < gamepak_ram_pages; i++) - { - gamepak_memory_map[i].page_timestamp = 0; - gamepak_memory_map[i].physical_index = 0; - } - - map_null(read, 0x8000000, 0xD000000); - } - else - { - /* Map the ROM using mirroring, not many games use it */ - unsigned numblocks = gamepak_size >> 15; - map_region(read, 0x8000000, 0xA000000, numblocks, gamepak_rom); - map_region(read, 0xA000000, 0xC000000, numblocks, gamepak_rom); - map_region(read, 0xC000000, 0xD000000, numblocks, gamepak_rom); - /* Do not map D-E regions since they are also used for FLASH */ - } -} - void init_gamepak_buffer(void) { - // Try to initialize 32MB (this is mainly for non-PSP platforms) - gamepak_rom = NULL; - - gamepak_ram_buffer_size = 32 * 1024 * 1024; - gamepak_rom = (u8*)malloc(gamepak_ram_buffer_size); - - if(!gamepak_rom) + unsigned i; + // Try to allocate up to 32 blocks of 1MB each + gamepak_buffer_count = 0; + while (gamepak_buffer_count < ROM_BUFFER_SIZE) { - // Try 16MB, for PSP, then lower in 2MB increments - gamepak_ram_buffer_size = 16 * 1024 * 1024; - gamepak_rom = (u8*)malloc(gamepak_ram_buffer_size); - - while(!gamepak_rom) - { - gamepak_ram_buffer_size -= (2 * 1024 * 1024); - gamepak_rom = (u8*)malloc(gamepak_ram_buffer_size); - } + void *ptr = malloc(1024*1024); + if (!ptr) + break; + gamepak_buffers[gamepak_buffer_count++] = (u8*)ptr; } - // Here's assuming we'll have enough memory left over for this, - // and that the above succeeded (if not we're in trouble all around) - gamepak_ram_pages = gamepak_ram_buffer_size / (32 * 1024); - gamepak_memory_map = (gamepak_swap_entry_type*)malloc( - sizeof(gamepak_swap_entry_type) * gamepak_ram_pages); + // Initialize the memory map structure + for (i = 0; i < 1024; i++) + { + gamepak_blk_queue[i].next_lru = (u16)(i + 1); + gamepak_blk_queue[i].phy_rom = -1; + } + + gamepak_lru_head = 0; + gamepak_lru_tail = 32 * gamepak_buffer_count - 1; } void init_memory(void) @@ -3200,7 +3096,6 @@ void init_memory(void) map_null(read, 0x6000000, 0x7000000); map_vram(read); map_null(read, 0x7000000, 0x8000000); - init_memory_gamepak(); map_null(read, 0xE000000, 0x10000000); memset(io_registers, 0, sizeof(io_registers)); @@ -3245,16 +3140,9 @@ void memory_term(void) gamepak_file_large = NULL; } - if (gamepak_memory_map) + while (gamepak_buffer_count) { - free(gamepak_memory_map); - gamepak_memory_map = NULL; - } - - if (gamepak_rom) - { - free(gamepak_rom); - gamepak_rom = NULL; + free(gamepak_buffers[--gamepak_buffer_count]); } } @@ -3352,3 +3240,102 @@ void memory_##type##_savestate(void) \ memory_savestate_builder(read) memory_savestate_builder(write) + +static s32 load_gamepak_raw(const char *name) +{ + unsigned i, j; + gamepak_file_large = fopen(name, "rb"); + if(gamepak_file_large) + { + // Round size to 32KB pages + gamepak_size = file_length(gamepak_file_large); + gamepak_size = (gamepak_size + 0x7FFF) & ~0x7FFF; + + // Load stuff in 1MB chunks + u32 buf_blocks = (gamepak_size + 1024*1024-1) / (1024*1024); + u32 rom_blocks = gamepak_size >> 15; + u32 ldblks = buf_blocks < gamepak_buffer_count ? + buf_blocks : gamepak_buffer_count; + + // Unmap the ROM space since we will re-map it now + map_null(read, 0x8000000, 0xD000000); + + // Proceed to read the whole ROM or as much as possible. + for (i = 0; i < ldblks; i++) + { + // Load 1MB chunk and map it + fread(gamepak_buffers[i], 1, 1024*1024, gamepak_file_large); + for (j = 0; j < 32 && i*32 + j < rom_blocks; j++) + { + u32 phyn = i*32 + j; + u8* blkptr = &gamepak_buffers[i][32 * 1024 * j]; + u32 entry = evict_gamepak_page(); + gamepak_blk_queue[entry].phy_rom = phyn; + // Map it to the read handlers now + map_rom_entry(read, phyn, blkptr, rom_blocks); + } + } + + return 0; + } + + return -1; +} + +u32 load_gamepak(const struct retro_game_info* info, const char *name) +{ + char *p; + char gamepak_filename[512]; + gamepak_info_t gpinfo; + + if (load_gamepak_raw(name)) + return -1; + + strncpy(gamepak_filename, name, sizeof(gamepak_filename)); + gamepak_filename[sizeof(gamepak_filename) - 1] = 0; + + p = strrchr(gamepak_filename, PATH_SEPARATOR_CHAR); + if (p) + p++; + else + p = gamepak_filename; + + snprintf(backup_filename, sizeof(backup_filename), "%s%c%s", save_path, PATH_SEPARATOR_CHAR, p); + p = strrchr(backup_filename, '.'); + if (p) + strcpy(p, ".sav"); + + if (!use_libretro_save_method) + load_backup(backup_filename); + + // Buffer 0 always has the first 1MB chunk of the ROM + memset(&gpinfo, 0, sizeof(gpinfo)); + memcpy(gpinfo.gamepak_title, &gamepak_buffers[0][0xA0], 12); + memcpy(gpinfo.gamepak_code, &gamepak_buffers[0][0xAC], 4); + memcpy(gpinfo.gamepak_maker, &gamepak_buffers[0][0xB0], 2); + + idle_loop_target_pc = 0xFFFFFFFF; + iwram_stack_optimize = 1; + translation_gate_targets = 0; + flash_device_id = FLASH_DEVICE_MACRONIX_64KB; + flash_size = FLASH_SIZE_64KB; + + if ((load_game_config_over(&gpinfo)) < 0) + load_game_config(&gpinfo); + + return 0; +} + +s32 load_bios(char *name) +{ + FILE *fd = fopen(name, "rb"); + + if(!fd) + return -1; + + fread(bios_rom, 1, 0x4000, fd); + fclose(fd); + return 0; +} + + diff --git a/gba_memory.h b/gba_memory.h index 54caee9..2060789 100644 --- a/gba_memory.h +++ b/gba_memory.h @@ -205,8 +205,6 @@ void init_gamepak_buffer(void); void memory_term(void); u8 *load_gamepak_page(u32 physical_index); -extern u8 *gamepak_rom; -extern u32 gamepak_ram_buffer_size; extern u32 oam_update; extern u32 gbc_sound_update; extern u32 gbc_sound_wave_update; @@ -295,6 +293,21 @@ static inline void state_mem_write(const void* src, size_t size) state_mem_write_ptr += size; } +// Page sticky bit routines +extern u32 gamepak_sticky_bit[1024/32]; +static inline void touch_gamepak_page(u32 physical_index) +{ + u32 idx = (physical_index >> 5) & 31; + u32 bof = physical_index & 31; + + gamepak_sticky_bit[idx] |= (1 << bof); +} + +static inline void clear_gamepak_stickybits(void) +{ + memset(gamepak_sticky_bit, 0, sizeof(gamepak_sticky_bit)); +} + /* this is an upper limit, ToDo : calculate the exact state size */ #define GBA_STATE_MEM_SIZE (512*1024) diff --git a/gpsp_config.h b/gpsp_config.h index ef09180..d0248d2 100644 --- a/gpsp_config.h +++ b/gpsp_config.h @@ -2,6 +2,9 @@ #ifndef GPSP_CONFIG_H #define GPSP_CONFIG_H +/* ROM buffer size in megabytes (this is a maximum value!) */ +#define ROM_BUFFER_SIZE 32 + /* Cache sizes and their config knobs */ #if defined(PSP) #define ROM_TRANSLATION_CACHE_SIZE (1024 * 1024 * 2) diff --git a/libretro.c b/libretro.c index 5dfe0df..f31235b 100644 --- a/libretro.c +++ b/libretro.c @@ -510,8 +510,7 @@ void retro_init(void) #endif #endif - if (!gamepak_rom) - init_gamepak_buffer(); + init_gamepak_buffer(); init_sound(1); if(!gba_screen_pixels) @@ -975,7 +974,6 @@ bool retro_load_game(const struct retro_game_info* info) } memset(gamepak_backup, -1, sizeof(gamepak_backup)); - gamepak_filename[0] = 0; if (load_gamepak(info, info->path) != 0) { error_msg("Could not load the game file."); @@ -1162,7 +1160,11 @@ void retro_run(void) execute_arm_translate(execute_cycles); else #endif + { + /* Sticky bits only used in interpreter */ + clear_gamepak_stickybits(); execute_arm(execute_cycles); + } render_audio(); video_run();