diff --git a/cmake/sources.cmake b/cmake/sources.cmake index 31fb2d5..fe99893 100644 --- a/cmake/sources.cmake +++ b/cmake/sources.cmake @@ -6,7 +6,6 @@ set( JOYBUS_SOURCES ${LIBJOYBUS_ROOT_DIR}/src/bus.c ${LIBJOYBUS_ROOT_DIR}/src/checksum.c - ${LIBJOYBUS_ROOT_DIR}/src/n64_pak_fs.c ${LIBJOYBUS_ROOT_DIR}/src/host/common.c ${LIBJOYBUS_ROOT_DIR}/src/host/gcn.c ${LIBJOYBUS_ROOT_DIR}/src/host/n64.c diff --git a/examples/esp32/n64-target-cpak/main/main.c b/examples/esp32/n64-target-cpak/main/main.c index 28bbd05..47438fd 100644 --- a/examples/esp32/n64-target-cpak/main/main.c +++ b/examples/esp32/n64-target-cpak/main/main.c @@ -1,4 +1,3 @@ -#include #include #include @@ -24,12 +23,9 @@ void app_main(void) // Initialize a N64 controller target as a standard controller. joybus_target_n64_controller_init(&n64_controller); - // Format the pak, since RAM starts empty on every boot. - joybus_n64_pak_fs_format(pak_memory, 1, esp_random()); - // Initialize a single bank controller pak over that memory and plug it into the controller. - joybus_target_n64_pak_controller_init(&controller_pak, 1); - joybus_target_n64_pak_controller_set_memory(&controller_pak, pak_memory); + joybus_target_n64_pak_controller_init(&controller_pak); + joybus_target_n64_pak_controller_set_memory(&controller_pak, pak_memory, 1); joybus_target_n64_controller_attach_pak(&n64_controller, JOYBUS_TARGET_N64_PAK(&controller_pak)); // Attach the target to the bus. diff --git a/examples/pico-sdk/n64-target-cpak/CMakeLists.txt b/examples/pico-sdk/n64-target-cpak/CMakeLists.txt index 3a5ce6f..425d444 100644 --- a/examples/pico-sdk/n64-target-cpak/CMakeLists.txt +++ b/examples/pico-sdk/n64-target-cpak/CMakeLists.txt @@ -17,7 +17,7 @@ add_subdirectory(${LIBJOYBUS_ROOT} ${CMAKE_BINARY_DIR}/_deps/libjoybus-build) add_executable(n64-target-cpak main.c) # Link with the standard library and libjoybus -target_link_libraries(n64-target-cpak pico_stdlib pico_rand joybus) +target_link_libraries(n64-target-cpak pico_stdlib joybus) # Generate additional binary outputs pico_add_extra_outputs(n64-target-cpak) diff --git a/examples/pico-sdk/n64-target-cpak/README.md b/examples/pico-sdk/n64-target-cpak/README.md index 711fd5b..8e40cdd 100644 --- a/examples/pico-sdk/n64-target-cpak/README.md +++ b/examples/pico-sdk/n64-target-cpak/README.md @@ -2,7 +2,7 @@ Act as a N64 controller with a Controller Pak on Pi Pico device using the Pico SDK. The controller has no inputs wired up, see the `n64-target` example for those. -The Controller Pak is a single bank held in RAM. It is formatted at boot, so a console sees an empty pak, and its contents are lost on reset. +The Controller Pak is a single bank held in RAM, so its contents are lost on reset. ## Building diff --git a/examples/pico-sdk/n64-target-cpak/main.c b/examples/pico-sdk/n64-target-cpak/main.c index aabaf1f..155621d 100644 --- a/examples/pico-sdk/n64-target-cpak/main.c +++ b/examples/pico-sdk/n64-target-cpak/main.c @@ -1,4 +1,3 @@ -#include "pico/rand.h" #include "pico/stdlib.h" #include @@ -24,12 +23,9 @@ int main() // Initialize a N64 controller target as a standard controller joybus_target_n64_controller_init(&n64_controller); - // Format the pak, since RAM starts empty on every boot - joybus_n64_pak_fs_format(pak_memory, 1, get_rand_32()); - // Initialize a single bank controller pak over that memory and plug it into the controller - joybus_target_n64_pak_controller_init(&controller_pak, 1); - joybus_target_n64_pak_controller_set_memory(&controller_pak, pak_memory); + joybus_target_n64_pak_controller_init(&controller_pak); + joybus_target_n64_pak_controller_set_memory(&controller_pak, pak_memory, 1); joybus_target_n64_controller_attach_pak(&n64_controller, JOYBUS_TARGET_N64_PAK(&controller_pak)); // Attach the target to the bus diff --git a/include/joybus/common/n64_pak_fs.h b/include/joybus/common/n64_pak_fs.h deleted file mode 100644 index 65516c8..0000000 --- a/include/joybus/common/n64_pak_fs.h +++ /dev/null @@ -1,116 +0,0 @@ -/** - * @addtogroup joybus - * - * N64 Controller Pak filesystem helpers. - * - * A Controller Pak keeps its system area in the first bank: four copies of an - * ID block in page 0, an inode table and a mirror of it per bank, then two - * pages of note table. These routines write that area for a given bank count - * and check whether one is present, so an emulated pak can present itself as - * formatted. They work on a bank held in memory, or a block or page at a time - * for a pak whose banks are not, and do not touch the bus. - * - * @{ - */ - -#pragma once - -#include -#include - -#include - -/// Size of one page in bytes, the unit the filesystem allocates in -#define JOYBUS_N64_PAK_FS_PAGE_SIZE 256 - -/// The most banks whose system area fits in the first bank -#define JOYBUS_N64_PAK_FS_MAX_BANKS 62 - -/// Page of the first bank holding the four ID copies -#define JOYBUS_N64_PAK_FS_ID_PAGE 0 - -/// Page of the first bank holding a bank's inode table -#define JOYBUS_N64_PAK_FS_INODE_PAGE(bank) (1 + (bank)) - -/// Page of the first bank holding the mirror of a bank's inode table, for a given bank count -#define JOYBUS_N64_PAK_FS_INODE_MIRROR_PAGE(banks, bank) (1 + (banks) + (bank)) - -/// First page of the note table in the first bank, for a given bank count -#define JOYBUS_N64_PAK_FS_NOTE_TABLE_PAGE(banks) (1 + 2 * (banks)) - -/// Number of pages the note table takes -#define JOYBUS_N64_PAK_FS_NOTE_TABLE_PAGES 2 - -/// Number of system pages in the first bank for a given bank count -#define JOYBUS_N64_PAK_FS_SYSTEM_PAGES(banks) (JOYBUS_N64_PAK_FS_NOTE_TABLE_PAGE(banks) + JOYBUS_N64_PAK_FS_NOTE_TABLE_PAGES) - -/// First page of a bank the filesystem allocates, for a given bank count. Page 0 of every later bank is reserved -#define JOYBUS_N64_PAK_FS_FIRST_DATA_PAGE(banks, bank) ((bank) == 0 ? JOYBUS_N64_PAK_FS_SYSTEM_PAGES(banks) : 1) - -/** - * Check whether a block address in the first bank holds a copy of the ID. - * - * @param addr block-aligned address within the first bank - * @return true if the block is one of the four ID copies, false otherwise - */ -bool joybus_n64_pak_fs_is_id_block(uint16_t addr); - -/** - * Read the bank count an ID block names. - * - * @param block an ID block, ::JOYBUS_N64_PAK_BLOCK_SIZE bytes - * @return the bank count in the block - */ -uint8_t joybus_n64_pak_fs_id_banks(const uint8_t block[JOYBUS_N64_PAK_BLOCK_SIZE]); - -/** - * Check whether an ID block is intact and names the given bank count. - * - * A pak holds a filesystem when any one of its four ID copies passes. - * - * @param block an ID block, ::JOYBUS_N64_PAK_BLOCK_SIZE bytes - * @param banks how many banks the pak presents - * @return true if both checksums match, the block marks a Controller Pak, and it names that many banks - */ -bool joybus_n64_pak_fs_id_valid(const uint8_t block[JOYBUS_N64_PAK_BLOCK_SIZE], uint8_t banks); - -/** - * Check whether a bank holds a filesystem for the given bank count. - * - * Only the ID copies in page 0 are read. - * - * @param bank0 the first bank of the pak, at least ::JOYBUS_N64_PAK_FS_PAGE_SIZE bytes - * @param banks how many banks the pak presents - * @return true if an intact ID names that many banks, false otherwise - */ -bool joybus_n64_pak_fs_valid(const uint8_t *bank0, uint8_t banks); - -/** - * Fill one page of a fresh system area for the given bank count. - * - * Every page from 0 to JOYBUS_N64_PAK_FS_SYSTEM_PAGES(banks) - 1, each written - * at its index in the first bank, together format the pak. - * - * @param page the page to fill, ::JOYBUS_N64_PAK_FS_PAGE_SIZE bytes - * @param index the page's index in the first bank, below JOYBUS_N64_PAK_FS_SYSTEM_PAGES(banks) - * @param banks how many banks the pak presents, 1 to ::JOYBUS_N64_PAK_FS_MAX_BANKS - * @param random value stored in the ID to tell this pak apart from others - * @return 0 on success, -JOYBUS_ERR_INVALID_ARG for a bank count or index out of range - */ -int joybus_n64_pak_fs_format_page(uint8_t page[JOYBUS_N64_PAK_FS_PAGE_SIZE], uint8_t index, uint8_t banks, - uint32_t random); - -/** - * Write a fresh filesystem for the given bank count into a bank. - * - * Only the system area is written. The data pages keep their contents but are - * all marked free, so a console sees an empty pak. - * - * @param bank0 the first bank of the pak, ::JOYBUS_N64_PAK_BANK_SIZE bytes - * @param banks how many banks the pak presents, 1 to ::JOYBUS_N64_PAK_FS_MAX_BANKS - * @param random value stored in the ID to tell this pak apart from others - * @return positive number of bytes written from the start of the bank, a negative joybus_error on failure - */ -int joybus_n64_pak_fs_format(uint8_t *bank0, uint8_t banks, uint32_t random); - -/** @} */ diff --git a/include/joybus/joybus.h b/include/joybus/joybus.h index bc686fb..490d26f 100644 --- a/include/joybus/joybus.h +++ b/include/joybus/joybus.h @@ -8,7 +8,6 @@ #include #include #include -#include #include #include #include diff --git a/include/joybus/target/n64_pak_controller.h b/include/joybus/target/n64_pak_controller.h index ae0c36e..d0a6e19 100644 --- a/include/joybus/target/n64_pak_controller.h +++ b/include/joybus/target/n64_pak_controller.h @@ -10,13 +10,10 @@ * An original pak is one 32 KB bank, which a console formats and mounts on * its own. Larger third party paks hold several banks and present one at a * time, switched by a write to the probe area at 0x8000. A banked pak from - * this target must be formatted with joybus_n64_pak_fs_format() before use. + * this target must already hold a filesystem for that many banks before use. * A select past the last bank is ignored, which keeps an accessory probe from * moving the bank but also stops a console counting the banks of a blank pak. * - * A write to an ID block that names another bank count is refused with a - * transfer error, so the pak cannot be reformatted to a different shape. - * * @{ */ @@ -109,7 +106,7 @@ struct joybus_target_n64_pak_controller { /// Callback for block written events joybus_target_n64_pak_controller_written_cb on_written; - /// Banks the pak presents, 1 for an original pak + /// Banks the storage holds, 1 for an original pak uint8_t banks; /// Bank the console has selected, always 0 on a one bank pak @@ -123,24 +120,25 @@ struct joybus_target_n64_pak_controller { * joybus_target_n64_pak_controller_set_storage() or * joybus_target_n64_pak_controller_set_memory(). * - * @param pak the controller pak to initialize - * @param banks how many banks it presents, 1 to ::JOYBUS_N64_PAK_FS_MAX_BANKS, not checked here + * @param pak the controller pak to initialize */ -void joybus_target_n64_pak_controller_init(struct joybus_target_n64_pak_controller *pak, uint8_t banks); +void joybus_target_n64_pak_controller_init(struct joybus_target_n64_pak_controller *pak); /** * Set the storage behind the pak. * * For a pak that cannot be held in memory, such as one of many banks kept in * flash. The callbacks may answer busy while a bank is fetched, which the - * console retries. + * console retries. Setting storage again swaps the pak, which then starts on + * bank 0. * * @param pak the controller pak + * @param banks how many banks the storage holds, 1 to 62, not checked here * @param read callback that reads a block of a bank * @param write callback that writes a block of a bank * @param user_data pointer handed back to the callbacks through `pak->user_data` */ -void joybus_target_n64_pak_controller_set_storage(struct joybus_target_n64_pak_controller *pak, +void joybus_target_n64_pak_controller_set_storage(struct joybus_target_n64_pak_controller *pak, uint8_t banks, joybus_target_n64_pak_controller_read_cb read, joybus_target_n64_pak_controller_write_cb write, void *user_data); @@ -149,13 +147,15 @@ void joybus_target_n64_pak_controller_set_storage(struct joybus_target_n64_pak_c * * The buffer is read and written in place, so persisting it is up to the * caller, which the written callback helps with. A fresh buffer needs a - * filesystem before a console will mount it, see joybus_n64_pak_fs_format(). - * A console can format a one bank pak itself, a banked pak it cannot. + * filesystem before a console will mount it. A console can format a one bank + * pak itself, a banked pak it cannot. * * @param pak the controller pak * @param memory the banks, back to back + * @param banks how many banks the memory holds, 1 to 62, not checked here */ -void joybus_target_n64_pak_controller_set_memory(struct joybus_target_n64_pak_controller *pak, uint8_t *memory); +void joybus_target_n64_pak_controller_set_memory(struct joybus_target_n64_pak_controller *pak, uint8_t *memory, + uint8_t banks); /** * Set the bank select callback for the controller pak. diff --git a/libjoybus.slcc b/libjoybus.slcc index 63165f3..9fb3afe 100644 --- a/libjoybus.slcc +++ b/libjoybus.slcc @@ -20,7 +20,6 @@ include: source: - path: src/bus.c - path: src/checksum.c - - path: src/n64_pak_fs.c - path: src/backend/gecko_sdk/joybus.c - path: src/host/common.c - path: src/host/gcn.c diff --git a/src/n64_pak_fs.c b/src/n64_pak_fs.c deleted file mode 100644 index 932750b..0000000 --- a/src/n64_pak_fs.c +++ /dev/null @@ -1,186 +0,0 @@ -#include -#include - -#include -#include -#include - -// Offsets of the four ID copies in page 0 -#define ID_COPIES 4 -static JOYBUS_RAM_DATA const uint16_t id_offsets[ID_COPIES] = {0x20, 0x60, 0x80, 0xC0}; - -// ID block layout, 32 bytes with the checksums covering the first 28 -#define ID_SIZE 32 -#define ID_SUMMED_BYTES 28 -#define ID_REPAIRED 0x00 -#define ID_RANDOM 0x04 -#define ID_DEVICE 0x18 -#define ID_BANKS 0x1A -#define ID_VERSION 0x1B -#define ID_CHECKSUM 0x1C -#define ID_INV_CHECKSUM 0x1E - -// Device ID with the low bit set, which marks a Controller Pak -#define ID_DEVICE_PAK 0x0001 - -// Inode entries are two bytes -#define INODE_ENTRIES (JOYBUS_N64_PAK_FS_PAGE_SIZE / 2) - -// Inode entry of a free page -#define INODE_FREE 0x0003 - -// Pak fields are big endian -static uint16_t read16(const uint8_t *p) -{ - return (uint16_t)(p[0] << 8 | p[1]); -} - -static void write16(uint8_t *p, uint16_t value) -{ - p[0] = value >> 8; - p[1] = value & 0xFF; -} - -static void write32(uint8_t *p, uint32_t value) -{ - write16(p, value >> 16); - write16(p + 2, value & 0xFFFF); -} - -// Compute the two ID checksums a console verifies -static void id_checksums(const uint8_t *id, uint16_t *sum, uint16_t *inverted) -{ - *sum = 0; - *inverted = 0; - - // Sum the 16-bit halves of the ID, and their complements - for (size_t i = 0; i < ID_SUMMED_BYTES; i += 2) { - uint16_t half = read16(&id[i]); - *sum += half; - *inverted += (uint16_t)~half; - } -} - -// Build the ID block as a console writes one -static void build_id(uint8_t id[ID_SIZE], uint8_t banks, uint32_t random) -{ - memset(id, 0, ID_SIZE); - write32(&id[ID_REPAIRED], 0xFFFFFFFF); - - // A console compares the whole ID on every access, so the random word is what tells paks apart - write32(&id[ID_RANDOM], random); - write16(&id[ID_DEVICE], ID_DEVICE_PAK); - id[ID_BANKS] = banks; - id[ID_VERSION] = 0; - - // Checksum the ID - uint16_t sum, inverted; - id_checksums(id, &sum, &inverted); - write16(&id[ID_CHECKSUM], sum); - write16(&id[ID_INV_CHECKSUM], inverted); -} - -// Build an inode page with every data page free -static void inode_page(uint8_t *page, uint8_t bank, uint8_t banks) -{ - // Bank 0's data starts after the system area, later banks' after a reserved first page - size_t first = JOYBUS_N64_PAK_FS_FIRST_DATA_PAGE(banks, bank); - - // Mark every data page free - memset(page, 0, JOYBUS_N64_PAK_FS_PAGE_SIZE); - for (size_t entry = first; entry < INODE_ENTRIES; entry++) - write16(&page[entry * 2], INODE_FREE); - - // Byte 1 holds the byte sum of the entries from the first data page on - uint8_t sum = 0; - for (size_t i = first * 2; i < JOYBUS_N64_PAK_FS_PAGE_SIZE; i++) - sum += page[i]; - page[1] = sum; -} - -bool joybus_n64_pak_fs_id_valid(const uint8_t block[JOYBUS_N64_PAK_BLOCK_SIZE], uint8_t banks) -{ - // Both checksums must match - uint16_t sum, inverted; - id_checksums(block, &sum, &inverted); - if (read16(&block[ID_CHECKSUM]) != sum || read16(&block[ID_INV_CHECKSUM]) != inverted) - return false; - - // The low bit of the device ID marks a Controller Pak - if ((read16(&block[ID_DEVICE]) & 1) == 0) - return false; - - // The ID must name the bank count the pak presents - return block[ID_BANKS] == banks; -} - -bool joybus_n64_pak_fs_valid(const uint8_t *bank0, uint8_t banks) -{ - // Any one intact copy is enough, a console repairs the rest from it - for (int i = 0; i < ID_COPIES; i++) { - if (joybus_n64_pak_fs_id_valid(&bank0[id_offsets[i]], banks)) - return true; - } - - return false; -} - -JOYBUS_RAM_FUNC -bool joybus_n64_pak_fs_is_id_block(uint16_t addr) -{ - for (int i = 0; i < ID_COPIES; i++) { - if (addr == id_offsets[i]) - return true; - } - - return false; -} - -JOYBUS_RAM_FUNC -uint8_t joybus_n64_pak_fs_id_banks(const uint8_t block[JOYBUS_N64_PAK_BLOCK_SIZE]) -{ - return block[ID_BANKS]; -} - -int joybus_n64_pak_fs_format_page(uint8_t page[JOYBUS_N64_PAK_FS_PAGE_SIZE], uint8_t index, uint8_t banks, - uint32_t random) -{ - if (banks == 0 || banks > JOYBUS_N64_PAK_FS_MAX_BANKS || index >= JOYBUS_N64_PAK_FS_SYSTEM_PAGES(banks)) - return -JOYBUS_ERR_INVALID_ARG; - - // One inode page per bank, then a mirror of each - if (index >= JOYBUS_N64_PAK_FS_INODE_PAGE(0) && index < JOYBUS_N64_PAK_FS_INODE_PAGE(banks)) { - inode_page(page, index - JOYBUS_N64_PAK_FS_INODE_PAGE(0), banks); - return 0; - } - - if (index >= JOYBUS_N64_PAK_FS_INODE_MIRROR_PAGE(banks, 0) && - index < JOYBUS_N64_PAK_FS_INODE_MIRROR_PAGE(banks, banks)) { - inode_page(page, index - JOYBUS_N64_PAK_FS_INODE_MIRROR_PAGE(banks, 0), banks); - return 0; - } - - // The ID page and the note table are empty, apart from the ID copies - memset(page, 0, JOYBUS_N64_PAK_FS_PAGE_SIZE); - - if (index == JOYBUS_N64_PAK_FS_ID_PAGE) { - uint8_t id[ID_SIZE]; - build_id(id, banks, random); - - for (int i = 0; i < ID_COPIES; i++) - memcpy(&page[id_offsets[i]], id, ID_SIZE); - } - - return 0; -} - -int joybus_n64_pak_fs_format(uint8_t *bank0, uint8_t banks, uint32_t random) -{ - if (banks == 0 || banks > JOYBUS_N64_PAK_FS_MAX_BANKS) - return -JOYBUS_ERR_INVALID_ARG; - - for (uint8_t index = 0; index < JOYBUS_N64_PAK_FS_SYSTEM_PAGES(banks); index++) - joybus_n64_pak_fs_format_page(&bank0[index * JOYBUS_N64_PAK_FS_PAGE_SIZE], index, banks, random); - - return JOYBUS_N64_PAK_FS_SYSTEM_PAGES(banks) * JOYBUS_N64_PAK_FS_PAGE_SIZE; -} diff --git a/src/target/n64_pak_controller.c b/src/target/n64_pak_controller.c index 86db712..3fe4eb6 100644 --- a/src/target/n64_pak_controller.c +++ b/src/target/n64_pak_controller.c @@ -4,7 +4,6 @@ #include #include #include -#include #include #include @@ -17,6 +16,10 @@ static int pak_controller_read_block(struct joybus_target_n64_pak *pak, uint16_t { struct joybus_target_n64_pak_controller *pak_controller = JOYBUS_TARGET_N64_PAK_CONTROLLER(pak); + // No storage yet reads as a pak that is not ready + if (pak_controller->read == NULL) + return -JOYBUS_ERR_BUSY; + if (pak_controller->banks == 1) { // The probe area aliases onto the bank, which accessory detection relies on addr &= PAK_CONTROLLER_BANK_MASK; @@ -26,10 +29,6 @@ static int pak_controller_read_block(struct joybus_target_n64_pak *pak, uint16_t return 0; } - // No storage yet reads as a pak that is not ready - if (pak_controller->read == NULL) - return -JOYBUS_ERR_BUSY; - return pak_controller->read(pak_controller, pak_controller->selected, addr, buf); } @@ -39,6 +38,10 @@ static int pak_controller_write_block(struct joybus_target_n64_pak *pak, uint16_ { struct joybus_target_n64_pak_controller *pak_controller = JOYBUS_TARGET_N64_PAK_CONTROLLER(pak); + // No storage yet reads as a pak that is not ready + if (pak_controller->write == NULL) + return -JOYBUS_ERR_BUSY; + if (pak_controller->banks == 1) { // The probe area aliases onto the bank addr &= PAK_CONTROLLER_BANK_MASK; @@ -59,15 +62,6 @@ static int pak_controller_write_block(struct joybus_target_n64_pak *pak, uint16_ return 0; } - // Refuse an ID write naming another bank count, so the pak cannot be reformatted to a different shape - if (pak_controller->selected == 0 && joybus_n64_pak_fs_is_id_block(addr) && - joybus_n64_pak_fs_id_banks(buf) != pak_controller->banks) - return -JOYBUS_ERR_INVALID_ARG; - - // No storage yet reads as a pak that is not ready - if (pak_controller->write == NULL) - return -JOYBUS_ERR_BUSY; - int result = pak_controller->write(pak_controller, pak_controller->selected, addr, buf); if (result != 0) return result; @@ -103,32 +97,33 @@ static JOYBUS_RAM_DATA const struct joybus_target_n64_pak_api pak_controller_api .write_block = pak_controller_write_block, }; -void joybus_target_n64_pak_controller_init(struct joybus_target_n64_pak_controller *pak_controller, uint8_t banks) +void joybus_target_n64_pak_controller_init(struct joybus_target_n64_pak_controller *pak_controller) { // Start from a clean state memset(pak_controller, 0, sizeof(*pak_controller)); - // Set the shape of the pak - pak_controller->banks = banks; - // Set the base pak API implementation struct joybus_target_n64_pak *pak = JOYBUS_TARGET_N64_PAK(pak_controller); pak->api = &pak_controller_api; } void joybus_target_n64_pak_controller_set_storage(struct joybus_target_n64_pak_controller *pak_controller, - joybus_target_n64_pak_controller_read_cb read, + uint8_t banks, joybus_target_n64_pak_controller_read_cb read, joybus_target_n64_pak_controller_write_cb write, void *user_data) { pak_controller->read = read; pak_controller->write = write; pak_controller->user_data = user_data; + + // Take the shape of the new storage, starting from its first bank + pak_controller->banks = banks; + pak_controller->selected = 0; } void joybus_target_n64_pak_controller_set_memory(struct joybus_target_n64_pak_controller *pak_controller, - uint8_t *memory) + uint8_t *memory, uint8_t banks) { - joybus_target_n64_pak_controller_set_storage(pak_controller, memory_read_block, memory_write_block, memory); + joybus_target_n64_pak_controller_set_storage(pak_controller, banks, memory_read_block, memory_write_block, memory); } void joybus_target_n64_pak_controller_set_select_cb(struct joybus_target_n64_pak_controller *pak_controller, diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 8a9c8af..2093ad0 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -23,9 +23,6 @@ endfunction() # Checksum tests add_libjoybus_test(test_checksum test_checksum.c) -# N64 Controller Pak filesystem tests -add_libjoybus_test(test_n64_pak_fs test_n64_pak_fs.c) - # Bus target attachment and command dispatch tests add_libjoybus_test(test_bus test_bus.c) diff --git a/test/target/test_n64_pak_controller.c b/test/target/test_n64_pak_controller.c index fb0e525..39e126a 100644 --- a/test/target/test_n64_pak_controller.c +++ b/test/target/test_n64_pak_controller.c @@ -5,7 +5,6 @@ #include #include #include -#include #include #include #include @@ -94,19 +93,11 @@ static int pak_write_fill(uint16_t addr, uint8_t fill) return pak_controller.base.api->write_block(&pak_controller.base, addr, buf); } -// Write a block whose ID bank count byte is `banks` directly through the pak API -static int pak_write_id(uint16_t addr, uint8_t banks) -{ - uint8_t buf[JOYBUS_N64_PAK_BLOCK_SIZE] = {0}; - buf[0x1A] = banks; - return pak_controller.base.api->write_block(&pak_controller.base, addr, buf); -} - // Set the pak up with the given bank count over the spy storage, hosted in a fresh controller static void make_pak(uint8_t banks) { - joybus_target_n64_pak_controller_init(&pak_controller, banks); - joybus_target_n64_pak_controller_set_storage(&pak_controller, spy_read_block, spy_write_block, &storage); + joybus_target_n64_pak_controller_init(&pak_controller); + joybus_target_n64_pak_controller_set_storage(&pak_controller, banks, spy_read_block, spy_write_block, &storage); joybus_target_n64_pak_controller_set_select_cb(&pak_controller, on_select); joybus_target_n64_pak_controller_set_written_cb(&pak_controller, on_written); @@ -251,48 +242,6 @@ static void test_banked_select_without_callback() TEST_ASSERT_EQUAL(2, pak_controller.selected); } -// --------------------------------------------------------------------------- -// ID guard -// --------------------------------------------------------------------------- - -// Test that an ID write naming another bank count is refused -static void test_id_write_with_other_bank_count_dropped() -{ - static const uint16_t id_addrs[] = {0x20, 0x60, 0x80, 0xC0}; - - for (int i = 0; i < 4; i++) { - TEST_ASSERT_EQUAL(-JOYBUS_ERR_INVALID_ARG, pak_write_id(id_addrs[i], 1)); - TEST_ASSERT_EQUAL(0, storage.writes); - TEST_ASSERT_EQUAL(0, written.count); - } -} - -// Test that an ID write naming the pak's own bank count goes through -static void test_id_write_with_own_bank_count_kept() -{ - TEST_ASSERT_EQUAL(0, pak_write_id(0x20, 16)); - TEST_ASSERT_EQUAL(1, storage.writes); - TEST_ASSERT_EQUAL_HEX16(0x0020, storage.addr); -} - -// Test that the guard only watches bank 0, the same addresses elsewhere are data -static void test_id_guard_only_in_bank_zero() -{ - TEST_ASSERT_EQUAL(0, pak_write_fill(0x8000, 1)); - TEST_ASSERT_EQUAL(0, pak_write_id(0x20, 1)); - TEST_ASSERT_EQUAL(1, storage.writes); - TEST_ASSERT_EQUAL(1, storage.bank); -} - -// Test that a one bank pak's guard covers the aliased ID addresses too -static void test_id_guard_on_one_bank_alias() -{ - make_pak(1); - - TEST_ASSERT_EQUAL(-JOYBUS_ERR_INVALID_ARG, pak_write_id(0x8020, 16)); - TEST_ASSERT_EQUAL(0, storage.writes); -} - // --------------------------------------------------------------------------- // Storage // --------------------------------------------------------------------------- @@ -301,10 +250,30 @@ static void test_id_guard_on_one_bank_alias() static void test_no_storage_is_busy() { uint8_t buf[JOYBUS_N64_PAK_BLOCK_SIZE]; - joybus_target_n64_pak_controller_init(&pak_controller, 16); + joybus_target_n64_pak_controller_init(&pak_controller); TEST_ASSERT_EQUAL(-JOYBUS_ERR_BUSY, pak_read(0x0100, buf)); TEST_ASSERT_EQUAL(-JOYBUS_ERR_BUSY, pak_write_fill(0x0100, 0)); + + // The probe area answers busy too, since the pak has no shape yet + TEST_ASSERT_EQUAL(-JOYBUS_ERR_BUSY, pak_read(0x8000, buf)); + TEST_ASSERT_EQUAL(-JOYBUS_ERR_BUSY, pak_write_fill(0x8000, 0)); +} + +// Test that setting storage again takes its bank count and starts on bank 0 +static void test_storage_swap_resets_shape() +{ + TEST_ASSERT_EQUAL(0, pak_write_fill(0x8000, 8)); + TEST_ASSERT_EQUAL(8, pak_controller.selected); + + joybus_target_n64_pak_controller_set_storage(&pak_controller, 4, spy_read_block, spy_write_block, &storage); + TEST_ASSERT_EQUAL(0, pak_controller.selected); + + // Selects are now bounded by the new count + TEST_ASSERT_EQUAL(0, pak_write_fill(0x8000, 8)); + TEST_ASSERT_EQUAL(0, pak_controller.selected); + TEST_ASSERT_EQUAL(0, pak_write_fill(0x8000, 3)); + TEST_ASSERT_EQUAL(3, pak_controller.selected); } // Test that a busy backend is reported as busy, with no written event @@ -338,8 +307,8 @@ static void test_memory_storage() memset(memory, 0, sizeof(memory)); memset(&memory[JOYBUS_N64_PAK_BANK_SIZE + 0x0100], 0x77, JOYBUS_N64_PAK_BLOCK_SIZE); - joybus_target_n64_pak_controller_init(&pak_controller, 2); - joybus_target_n64_pak_controller_set_memory(&pak_controller, memory); + joybus_target_n64_pak_controller_init(&pak_controller); + joybus_target_n64_pak_controller_set_memory(&pak_controller, memory, 2); joybus_target_n64_pak_controller_set_written_cb(&pak_controller, on_written); // Bank 1 reads back what was put there @@ -399,12 +368,8 @@ int main(int argc, char **argv) RUN_TEST(test_banked_motor_region_write_ignored); RUN_TEST(test_banked_select_without_callback); - RUN_TEST(test_id_write_with_other_bank_count_dropped); - RUN_TEST(test_id_write_with_own_bank_count_kept); - RUN_TEST(test_id_guard_only_in_bank_zero); - RUN_TEST(test_id_guard_on_one_bank_alias); - RUN_TEST(test_no_storage_is_busy); + RUN_TEST(test_storage_swap_resets_shape); RUN_TEST(test_storage_busy_propagates); RUN_TEST(test_written_callback_after_store); RUN_TEST(test_memory_storage); diff --git a/test/test_n64_pak_fs.c b/test/test_n64_pak_fs.c deleted file mode 100644 index c55b4d1..0000000 --- a/test/test_n64_pak_fs.c +++ /dev/null @@ -1,259 +0,0 @@ -#include - -#include -#include - -#include "unity.h" - -// A bank to format, and a copy of an ID block a console formatted for sixteen -// banks, as read back from a real pak -static uint8_t bank0[JOYBUS_N64_PAK_BANK_SIZE]; -static const uint8_t console_id[32] = {0x00, 0x27, 0x00, 0x00, 0x00, 0x03, 0x23, 0xc6, 0x04, 0x3c, 0x98, - 0x69, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x01, 0x10, 0x00, 0xd0, 0x96, 0x2f, 0x5c}; - -void setUp(void) -{ - memset(bank0, 0xFF, sizeof(bank0)); -} - -void tearDown(void) -{ -} - -// The ID checksums as the console computes them, independently of the library -static void console_id_checksums(const uint8_t *id, uint16_t *sum, uint16_t *inverted) -{ - *sum = 0; - *inverted = 0; - for (int i = 0; i < 28; i += 2) { - uint16_t half = (uint16_t)(id[i] << 8 | id[i + 1]); - *sum += half; - *inverted += (uint16_t)~half; - } -} - -// Test that erased flash is not a filesystem -static void test_valid_rejects_erased() -{ - TEST_ASSERT_FALSE(joybus_n64_pak_fs_valid(bank0, 1)); - TEST_ASSERT_FALSE(joybus_n64_pak_fs_valid(bank0, 16)); -} - -// Test that a console-written ID is accepted for its bank count only -static void test_valid_accepts_console_id() -{ - memcpy(&bank0[0x20], console_id, sizeof(console_id)); - - TEST_ASSERT_TRUE(joybus_n64_pak_fs_valid(bank0, 16)); - TEST_ASSERT_FALSE(joybus_n64_pak_fs_valid(bank0, 1)); -} - -// Test that any one intact copy is enough -static void test_valid_accepts_any_copy() -{ - memcpy(&bank0[0xC0], console_id, sizeof(console_id)); - - TEST_ASSERT_TRUE(joybus_n64_pak_fs_valid(bank0, 16)); -} - -// Test that a corrupt checksum is rejected -static void test_valid_rejects_bad_checksum() -{ - memcpy(&bank0[0x20], console_id, sizeof(console_id)); - bank0[0x20 + 0x1C] ^= 0x01; - - TEST_ASSERT_FALSE(joybus_n64_pak_fs_valid(bank0, 16)); -} - -// Test that a format covers the system area and nothing more -static void test_format_extent() -{ - TEST_ASSERT_EQUAL((3 + 2 * 16) * 256, joybus_n64_pak_fs_format(bank0, 16, 0)); - TEST_ASSERT_EQUAL_HEX8(0xFF, bank0[(3 + 2 * 16) * 256]); - TEST_ASSERT_EQUAL_HEX8(0xFF, bank0[JOYBUS_N64_PAK_BANK_SIZE - 1]); -} - -// Test that a bank count outside the supported range is rejected without writing -static void test_format_rejects_bad_bank_count() -{ - TEST_ASSERT_EQUAL(-JOYBUS_ERR_INVALID_ARG, joybus_n64_pak_fs_format(bank0, 0, 0)); - TEST_ASSERT_EQUAL(-JOYBUS_ERR_INVALID_ARG, joybus_n64_pak_fs_format(bank0, JOYBUS_N64_PAK_FS_MAX_BANKS + 1, 0)); - TEST_ASSERT_EQUAL_HEX8(0xFF, bank0[0]); - TEST_ASSERT_FALSE(joybus_n64_pak_fs_valid(bank0, 0)); -} - -// Test that the largest bank count leaves one data page in the first bank -static void test_format_max_banks() -{ - TEST_ASSERT_EQUAL(127 * 256, joybus_n64_pak_fs_format(bank0, JOYBUS_N64_PAK_FS_MAX_BANKS, 0)); - TEST_ASSERT_EQUAL_HEX8(0xFF, bank0[127 * 256]); - - // Only page 127 is free, so the inode sum is a single free marker - const uint8_t *table = &bank0[1 * 256]; - TEST_ASSERT_EQUAL_HEX8(0x03, table[1]); - TEST_ASSERT_EQUAL_HEX16(0x0000, (uint16_t)(table[126 * 2] << 8 | table[126 * 2 + 1])); - TEST_ASSERT_EQUAL_HEX16(0x0003, (uint16_t)(table[127 * 2] << 8 | table[127 * 2 + 1])); - TEST_ASSERT_TRUE(joybus_n64_pak_fs_valid(bank0, JOYBUS_N64_PAK_FS_MAX_BANKS)); -} - -// Test that every ID copy is written and passes the console's checks -static void test_format_ids() -{ - joybus_n64_pak_fs_format(bank0, 16, 0x12345678); - - static const uint16_t offsets[] = {0x20, 0x60, 0x80, 0xC0}; - for (int i = 0; i < 4; i++) { - const uint8_t *id = &bank0[offsets[i]]; - uint16_t sum, inverted; - console_id_checksums(id, &sum, &inverted); - - TEST_ASSERT_EQUAL_HEX16(sum, (uint16_t)(id[0x1C] << 8 | id[0x1D])); - TEST_ASSERT_EQUAL_HEX16(inverted, (uint16_t)(id[0x1E] << 8 | id[0x1F])); - TEST_ASSERT_EACH_EQUAL_HEX8(0xFF, &id[0x00], 4); - TEST_ASSERT_EQUAL_HEX32(0x12345678, (uint32_t)id[0x04] << 24 | id[0x05] << 16 | id[0x06] << 8 | id[0x07]); - TEST_ASSERT_EACH_EQUAL_HEX8(0x00, &id[0x08], 16); - TEST_ASSERT_EQUAL_HEX16(0x0001, (uint16_t)(id[0x18] << 8 | id[0x19])); - TEST_ASSERT_EQUAL_HEX8(16, id[0x1A]); - TEST_ASSERT_EQUAL_HEX8(0x00, id[0x1B]); - } - - TEST_ASSERT_TRUE(joybus_n64_pak_fs_valid(bank0, 16)); -} - -// Test the inode tables of a sixteen bank pak, whose checksums a console -// mounting one has been seen to accept -static void test_format_inodes_sixteen_banks() -{ - joybus_n64_pak_fs_format(bank0, 16, 0); - - // Bank 0's data starts at page 35, and 93 free pages sum to 0x17 - const uint8_t *table = &bank0[1 * 256]; - TEST_ASSERT_EQUAL_HEX8(0x17, table[1]); - TEST_ASSERT_EQUAL_HEX16(0x0000, (uint16_t)(table[34 * 2] << 8 | table[34 * 2 + 1])); - TEST_ASSERT_EQUAL_HEX16(0x0003, (uint16_t)(table[35 * 2] << 8 | table[35 * 2 + 1])); - TEST_ASSERT_EQUAL_HEX16(0x0003, (uint16_t)(table[127 * 2] << 8 | table[127 * 2 + 1])); - - // A later bank's data starts at page 1, and 127 free pages sum to 0x7D - const uint8_t *later = &bank0[2 * 256]; - TEST_ASSERT_EQUAL_HEX8(0x7D, later[1]); - TEST_ASSERT_EQUAL_HEX16(0x0003, (uint16_t)(later[1 * 2] << 8 | later[1 * 2 + 1])); - - // The mirrors follow the tables, and the note table after them is empty - for (int bank = 0; bank < 16; bank++) - TEST_ASSERT_EQUAL_MEMORY(&bank0[(1 + bank) * 256], &bank0[(17 + bank) * 256], 256); - for (int i = 33 * 256; i < 35 * 256; i++) - TEST_ASSERT_EQUAL_HEX8(0x00, bank0[i]); -} - -// Test the inode table of a one bank pak, the original's layout -static void test_format_inodes_one_bank() -{ - TEST_ASSERT_EQUAL(5 * 256, joybus_n64_pak_fs_format(bank0, 1, 0)); - - // Data starts at page 5, and 123 free pages sum to 0x71 - const uint8_t *table = &bank0[1 * 256]; - TEST_ASSERT_EQUAL_HEX8(0x71, table[1]); - TEST_ASSERT_EQUAL_HEX16(0x0000, (uint16_t)(table[4 * 2] << 8 | table[4 * 2 + 1])); - TEST_ASSERT_EQUAL_HEX16(0x0003, (uint16_t)(table[5 * 2] << 8 | table[5 * 2 + 1])); - TEST_ASSERT_EQUAL_MEMORY(&bank0[1 * 256], &bank0[2 * 256], 256); - TEST_ASSERT_TRUE(joybus_n64_pak_fs_valid(bank0, 1)); -} - -// Test that a single ID block is checked for its checksums and bank count -static void test_id_valid() -{ - uint8_t block[32]; - memcpy(block, console_id, sizeof(block)); - - TEST_ASSERT_TRUE(joybus_n64_pak_fs_id_valid(block, 16)); - TEST_ASSERT_FALSE(joybus_n64_pak_fs_id_valid(block, 1)); - - block[0x1E] ^= 0x01; - TEST_ASSERT_FALSE(joybus_n64_pak_fs_id_valid(block, 16)); -} - -// Test that a page index or bank count outside the system area is rejected without writing -static void test_format_page_rejects_out_of_range() -{ - uint8_t page[256]; - memset(page, 0xFF, sizeof(page)); - - TEST_ASSERT_EQUAL(-JOYBUS_ERR_INVALID_ARG, joybus_n64_pak_fs_format_page(page, 3 + 2 * 16, 16, 0)); - TEST_ASSERT_EQUAL(-JOYBUS_ERR_INVALID_ARG, joybus_n64_pak_fs_format_page(page, 0, 0, 0)); - TEST_ASSERT_EQUAL(-JOYBUS_ERR_INVALID_ARG, - joybus_n64_pak_fs_format_page(page, 0, JOYBUS_N64_PAK_FS_MAX_BANKS + 1, 0)); - TEST_ASSERT_EACH_EQUAL_HEX8(0xFF, page, sizeof(page)); - - TEST_ASSERT_EQUAL(0, joybus_n64_pak_fs_format_page(page, 2 + 2 * 16, 16, 0)); -} - -// Test that page 0 built alone holds ID blocks that pass one at a time, as a -// pak with no bank in memory checks them -static void test_format_page_ids_valid_by_block() -{ - uint8_t page[256]; - TEST_ASSERT_EQUAL(0, joybus_n64_pak_fs_format_page(page, 0, 16, 0x12345678)); - - int copies = 0; - for (uint16_t addr = 0; addr < sizeof(page); addr += JOYBUS_N64_PAK_BLOCK_SIZE) { - if (!joybus_n64_pak_fs_is_id_block(addr)) - continue; - - TEST_ASSERT_TRUE(joybus_n64_pak_fs_id_valid(&page[addr], 16)); - copies++; - } - - TEST_ASSERT_EQUAL(4, copies); -} - -// Test that the layout macros name the pages the format writes, for sixteen banks and for one -static void test_layout_pages() -{ - TEST_ASSERT_EQUAL(0, JOYBUS_N64_PAK_FS_ID_PAGE); - TEST_ASSERT_EQUAL(1, JOYBUS_N64_PAK_FS_INODE_PAGE(0)); - TEST_ASSERT_EQUAL(16, JOYBUS_N64_PAK_FS_INODE_PAGE(15)); - TEST_ASSERT_EQUAL(17, JOYBUS_N64_PAK_FS_INODE_MIRROR_PAGE(16, 0)); - TEST_ASSERT_EQUAL(32, JOYBUS_N64_PAK_FS_INODE_MIRROR_PAGE(16, 15)); - TEST_ASSERT_EQUAL(33, JOYBUS_N64_PAK_FS_NOTE_TABLE_PAGE(16)); - TEST_ASSERT_EQUAL(35, JOYBUS_N64_PAK_FS_SYSTEM_PAGES(16)); - TEST_ASSERT_EQUAL(35, JOYBUS_N64_PAK_FS_FIRST_DATA_PAGE(16, 0)); - TEST_ASSERT_EQUAL(1, JOYBUS_N64_PAK_FS_FIRST_DATA_PAGE(16, 5)); - - TEST_ASSERT_EQUAL(2, JOYBUS_N64_PAK_FS_INODE_MIRROR_PAGE(1, 0)); - TEST_ASSERT_EQUAL(3, JOYBUS_N64_PAK_FS_NOTE_TABLE_PAGE(1)); - TEST_ASSERT_EQUAL(5, JOYBUS_N64_PAK_FS_SYSTEM_PAGES(1)); - TEST_ASSERT_EQUAL(5, JOYBUS_N64_PAK_FS_FIRST_DATA_PAGE(1, 0)); - - // Every mirror page matches its inode page once formatted - joybus_n64_pak_fs_format(bank0, 16, 0); - for (int bank = 0; bank < 16; bank++) { - TEST_ASSERT_EQUAL_MEMORY(&bank0[JOYBUS_N64_PAK_FS_INODE_PAGE(bank) * 256], - &bank0[JOYBUS_N64_PAK_FS_INODE_MIRROR_PAGE(16, bank) * 256], 256); - } -} - -int main(int argc, char **argv) -{ - UNITY_BEGIN(); - - RUN_TEST(test_layout_pages); - - RUN_TEST(test_id_valid); - RUN_TEST(test_format_page_rejects_out_of_range); - RUN_TEST(test_format_page_ids_valid_by_block); - - RUN_TEST(test_valid_rejects_erased); - RUN_TEST(test_valid_accepts_console_id); - RUN_TEST(test_valid_accepts_any_copy); - RUN_TEST(test_valid_rejects_bad_checksum); - - RUN_TEST(test_format_extent); - RUN_TEST(test_format_rejects_bad_bank_count); - RUN_TEST(test_format_max_banks); - RUN_TEST(test_format_ids); - RUN_TEST(test_format_inodes_sixteen_banks); - RUN_TEST(test_format_inodes_one_bank); - - return UNITY_END(); -}