diff --git a/include/mega65/fileio.h b/include/mega65/fileio.h index 1052f64..e00e8f7 100644 --- a/include/mega65/fileio.h +++ b/include/mega65/fileio.h @@ -82,7 +82,7 @@ chdir(char* filename); __attribute__((leaf)) #endif uint8_t -chdirroot(void); +chdirroot(uint8_t partition); /** * @brief Struct for holding version information of the hypervisor diff --git a/include/mega65/shres.h b/include/mega65/shres.h new file mode 100644 index 0000000..97186b5 --- /dev/null +++ b/include/mega65/shres.h @@ -0,0 +1,136 @@ +/** + * @file shres.h + * @brief MEGA65 SYSPART Shared Resources Access API + * + * This API provides access to shared resources stored in the MEGA65 system + * partition (SYSPART), including files such as fonts, icons, or other binary + * assets. + * + * Resources are accessed via a special trap instruction and a sector-based + * directory format. This API allows enumeration, opening, reading, and seeking + * within those shared resources. + */ + +#ifndef SHRES_H +#define SHRES_H + +/// Triggers the low-level SYSPART shared resource trap. +extern void shres_trap(void); + +/// Registers used for communicating with the shared resource trap. +extern unsigned char shres_regs[5]; + +/// Flag indicating that the resource is a font. +#define SHRES_FLAG_FONT 1 + +/// Indicates the font is 16x16 pixels (requires SHRES_FLAG_FONT). +#define SHRES_FLAG_16x16 2 + +/// Indicates the font supports Unicode (requires SHRES_FLAG_FONT). +#define SHRES_FLAG_UNICODE 4 + +/// Maximum length of a resource name (not including null terminator). +#define MAX_RES_NAME_LEN 240 + +/** + * @struct shared_resource + * @brief Represents a file or other data resource in the SYSPART directory. + * + * This structure includes metadata stored on disk (such as name, flags, and + * sector info), as well as an in-memory field to track read position for + * streaming access. + */ +struct shared_resource_dirent { + /// First sector of the resource. + unsigned long first_sector; + + /// Length of the resource in sectors. + unsigned long length_in_sectors; + + /// Length of the resource in bytes. + unsigned long length; + + /// Bitmask of resource flags (e.g., SHRES_FLAG_FONT, SHRES_FLAG_16x16). + unsigned long flags; + + /// Length of the resource name (as stored in the directory). + unsigned char name_len; + + /// Null-terminated name of the resource (up to MAX_RES_NAME_LEN). + char name[MAX_RES_NAME_LEN + 1]; +}; + +struct shared_resource { + /// First sector of the resource. + unsigned long first_sector; + + /// Length of the resource in sectors. + unsigned long length_in_sectors; + + /// Length of the resource in bytes. + unsigned long length; + + /// Current read position in bytes (used internally; not stored on disk). + unsigned long position; +}; + + +/// Type alias for a directory handle (really a sector index). +#define shared_resource_dir unsigned int + +/** + * @brief Opens a named shared resource and verifies required flags. + * + * @param resource_name The null-terminated name of the resource to open. + * @param required_flags A bitmask of flags that must be set on the resource. + * @param file_handle Pointer to a shared_resource structure to populate. + * @return 0 on success, 1 if not found or error. + */ +char shopen(char* resource_name, unsigned long required_flags, + struct shared_resource* file_handle); + +/** + * @brief Reads data from an open shared resource file. + * + * @param ptr Pointer to the output buffer. + * @param count Number of bytes to read. + * @param f Pointer to the shared_resource handle. + * @return Number of bytes actually read. + */ +unsigned int shread( + unsigned char* ptr, unsigned int count, struct shared_resource* f); + +/** + * @brief Seeks to a new position in an open shared resource. + * + * @param f Pointer to the shared_resource handle. + * @param offset Byte offset from the origin. + * @param whence One of SEEK_SET, SEEK_CUR, or SEEK_END. + * @return 0 on success, 1 if the seek was out of bounds or invalid. + */ +char shseek(struct shared_resource* f, long offset, unsigned char whence); + +/** + * @brief Opens the shared resource directory for iteration. + * + * @return A directory handle (starting sector index), or 0xFFFF on failure. + */ +shared_resource_dir shdopen(void); + +/** + * @brief Reads the next matching directory entry. + * + * @param required_flags A bitmask of required flags the resource must match. + * @param directory_handle Pointer to the directory handle returned by + * shdopen(). + * @param dirent Pointer to a shared_resource structure to populate. + * @return + * - 0 on success (entry read and matches), + * - 1 if the trap failed, + * - 2 if end of directory reached (no more entries match). + */ +char shdread(unsigned long required_flags, + shared_resource_dir* directory_handle, + struct shared_resource_dirent* dirent); + +#endif // SHRES_H diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4683bc7..e4c26b9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,6 +1,7 @@ set(assembler llvm/fileio.s llvm/dirent.s + llvm/shres_asm.s llvm/memory_asm.s) set(objects @@ -14,6 +15,7 @@ set(objects mouse.c random.c sdcard.c + shres.c targets.c tests.c time.c) @@ -30,6 +32,7 @@ set(headers ${PROJECT_SOURCE_DIR}/include/mega65/mouse.h ${PROJECT_SOURCE_DIR}/include/mega65/random.h ${PROJECT_SOURCE_DIR}/include/mega65/sdcard.h + ${PROJECT_SOURCE_DIR}/include/mega65/shres.h ${PROJECT_SOURCE_DIR}/include/mega65/targets.h ${PROJECT_SOURCE_DIR}/include/mega65/tests.h ${PROJECT_SOURCE_DIR}/include/mega65/time.h) diff --git a/src/cc65/fileio.s b/src/cc65/fileio.s index 26eec38..921aa77 100644 --- a/src/cc65/fileio.s +++ b/src/cc65/fileio.s @@ -146,10 +146,15 @@ _close: rts _chdirroot: + ;; HYPPO requires partition number in X. + tax ;; Change to root directory of volume lda #$3C sta $d640 clv + ;; HYPPO trap_dos_cdroot() doesn't set return value: It is deemed to always succeed + ;; if called on a valid disk. + ldx #$00 ldx #$00 rts @@ -177,9 +182,6 @@ chdir_file_exists: lda #$0C sta $d640 clv - lda #$18 - sta $D640 - clv ldx #$00 rts diff --git a/src/cc65/shres_asm.s b/src/cc65/shres_asm.s new file mode 100644 index 0000000..7a03296 --- /dev/null +++ b/src/cc65/shres_asm.s @@ -0,0 +1,28 @@ + + .setcpu "65C02" + .export _shres_trap,_shres_regs + +.SEGMENT "CODE" + + .p4510 + + ;; closedir takes file descriptor as argument (appears in A) +_shres_trap: + NEG ; Prefix instructions to make LDA -> LDQ + NEG + LDA _shres_regs + STA $D645 + NOP + NEG ; Prefix instructions to make STA -> STQ + NEG + STA _shres_regs + PHP + PLA + STA _shres_regs+4 + LDX #$00 + TAX + RTS + +_shres_regs: + .dword 0 ; regs + .byte 0 ; processor flags diff --git a/src/hal.c b/src/hal.c index e88e9bd..065c2cc 100644 --- a/src/hal.c +++ b/src/hal.c @@ -7,11 +7,23 @@ void usleep(uint32_t micros) // Each VIC-II raster line is ~64 microseconds // this is not totally accurate, but is a reasonable approach while (micros > 64) { - uint8_t b = PEEK(0xD012); +#ifdef LLVM_503_WORKAROUND + asm volatile( + "ldx $D012\n" + "1:\n" + "cpx $D012\n" + "beq 1b\n" + : + : + : "x" // X is clobbered + ); +#else + uint8_t b = PEEK(0xD012); while (PEEK(0xD012) == b) { - continue; + continue; } - micros -= 64; +#endif + micros -= 64; } return; } diff --git a/src/llvm/fileio.s b/src/llvm/fileio.s index f7ceb8f..f1cacf3 100644 --- a/src/llvm/fileio.s +++ b/src/llvm/fileio.s @@ -133,6 +133,7 @@ close: .global chdirroot .section .text.fileio_chdirroot,"ax",@progbits chdirroot: + ldx #0 hyppo HYPPO_CDROOTDIR rts @@ -144,16 +145,18 @@ chdir: hyppo HYPPO_FINDFILE bcs chdir_ok lda #FILE_ERROR - rts + rts chdir_ok: hyppo HYPPO_CHDIR - hyppo HYPPO_OPENFILE; outputs to A + rts .global gethyppoversion .section .text.fileio_gethyppoversion,"ax",@progbits gethyppoversion: - hyppo HYPPO_GETVERSION ; outputs to Q = A, X, Y, Z - stq (__rc2) ; store Q to __rc2 pointer - ldz #0 ; Z must be cleared before returning - rts + phy + hyppo HYPPO_GETVERSION ; outputs to Q = A, X, Y, Z + stq (__rc2) ; store Q to __rc2 pointer + ldz #0 ; Z must be cleared before returning + ply + rts diff --git a/src/llvm/shres_asm.s b/src/llvm/shres_asm.s new file mode 100644 index 0000000..417ecdf --- /dev/null +++ b/src/llvm/shres_asm.s @@ -0,0 +1,49 @@ +.global _shres_trap +.global _shres_regs + +.section .text + +; _shres_trap() +; Sends a 32-bit argument in _shres_regs[0..3] to $D645, +; then stores 32-bit return result back into _shres_regs[0..3] +; and processor flags into _shres_regs[4]. + +_shres_trap: + +phy +phz + + lda _shres_regs+0 + ldx _shres_regs+1 + ldy _shres_regs+2 + ldz _shres_regs+3 + + sta $D645 ; Store to the MEGA65 SYSPART trap address + nop ; Delay/stabilize (preserved from original) + + + ; Store result back into _shres_regs + sta _shres_regs+0 + stx _shres_regs+1 + sty _shres_regs+2 + stz _shres_regs+3 + + php + pla + sta _shres_regs+4 ; Save status register into 5th byte + ldx #0 + txa + + plz + ply + + rts + +; Register block: 4 bytes for argument/result, 1 byte for flags + + .section .bss +_shres_regs: + .space 5 + + + .section .text diff --git a/src/shres.c b/src/shres.c new file mode 100644 index 0000000..50424e0 --- /dev/null +++ b/src/shres.c @@ -0,0 +1,243 @@ +/** + * @file shres.c + * @brief Implementation of MEGA65 SYSPART Shared Resources Access API + * + * Provides functions to access shared resources stored in the MEGA65 system + * partition (SYSPART), including file opening, reading, seeking, and directory + * traversal. Resources are accessed via a custom trap interface to the SYSPART + * filesystem. + */ + +#include +#include + +#include "mega65/shres.h" +#include "mega65/memory.h" + +void _shres_trap(void); +extern unsigned char _shres_regs[5]; + +/// Magic string identifying the SYSPART shared resource area. +static const unsigned char magic_string[22] + = { 0x4D, 0x45, 0x47, 0x41, '6', '5', // "MEGA65" + 0x53, 0x48, 0x41, 0x52, 0x45, 0x44, // "SHARED" + 0x52, 0x45, 0x53, 0x4F, 0x55, 0x52, 0x43, 0x45, 0x53, // "RESOURCES" + 0x00 }; + +/** + * @brief Waits until the SD card is no longer busy. + */ +void sdcard_busy_wait(void) +{ + while (PEEK(0xD680) & 0x03) { + continue; + } +} + +/** + * @brief Executes the shared resource trap for a given sector number. + * + * @param arg The sector number to read (as a 32-bit address). + * @return 0 on success, 1 if the SD card is busy or the trap fails. + */ +char do_shres_trap(unsigned long arg) +{ + if (PEEK(0xD680) & 0x03) { + return 1; + } + + _shres_regs[0] = (arg >> 0) & 0xff; + _shres_regs[1] = (arg >> 8) & 0xff; + _shres_regs[2] = (arg >> 16) & 0xff; + _shres_regs[3] = (arg >> 24) & 0xff; + _shres_trap(); + + // Success is indicated by bit 0 = 1 in shres_regs[4] + return (_shres_regs[4] & 0x01) ^ 0x01; +} + +/** + * @brief Opens a shared resource file by name and flags. + * + * @param resource_name The null-terminated name of the resource to open. + * @param required_flags Bitmask of required flags the resource must have. + * @param file_handle Pointer to the shared_resource struct to populate. + * @return 0 on success, 1 if the resource was not found or an error occurred. + */ + +struct shared_resource_dirent sr_dirent; + +char shopen(char* resource_name, unsigned long required_flags, + struct shared_resource* file_handle) +{ + unsigned int d = shdopen(); + + if (d == 0xffff) { + return 1; + } + + while (!shdread(required_flags, &d, &sr_dirent)) { + if (!strcmp(resource_name, sr_dirent.name)) { + lcopy((unsigned long)&sr_dirent, + (unsigned long)file_handle, + sizeof(struct shared_resource)); + file_handle->position=0; + return 0; + } + } + + return 2; +} + +/** + * @brief Reads bytes from an open shared resource. + * + * @param ptr Pointer to the output buffer to fill. + * @param count Number of bytes to read. + * @param f Pointer to an open shared_resource structure. + * @return Number of bytes actually read. + */ +unsigned int shread( + unsigned char* ptr, unsigned int count, struct shared_resource* f) +{ + unsigned int read_bytes = 0; + + if (!f || f->position >= f->length) { + return 0; + } + + if (count > (f->length - f->position)) { + count = (f->length - f->position); + } + + while (count > 0) { + unsigned int bytes = 512 - (f->position & 511); + if (bytes > count) { + bytes = count; + } + + // Load the appropriate sector + if (do_shres_trap(f->first_sector + (f->position >> 9))) { + break; + } + // And wait for the SD card to actually read it. + sdcard_busy_wait(); + + // Select SD card buffer, not FDC buffer + POKE(0xD689L,PEEK(0xD689L)|0x80); + + // Copy bytes from the sector buffer to the output buffer + lcopy(0xffd6e00L + (f->position & 511), (unsigned long)ptr, bytes); + + ptr += bytes; + f->position += bytes; + count -= bytes; + read_bytes += bytes; + } + + return read_bytes; +} + +/** + * @brief Seeks to a specific byte offset in an open shared resource. + * + * @param f Pointer to an open shared_resource structure. + * @param offset Byte offset to seek. + * @param whence One of SEEK_SET, SEEK_CUR, or SEEK_END. + * @return 0 on success, 1 if seek was out of bounds or f is NULL. + */ +char shseek(struct shared_resource* f, long offset, unsigned char whence) +{ + if (!f) { + return 1; + } + + switch (whence) { + case SEEK_CUR: + f->position += offset; + break; + case SEEK_END: + f->position = f->length + offset; + break; + case SEEK_SET: + default: + f->position = offset; + break; + } + + if ((long)f->position < 0) { + f->position = 0; + return 2; + } + if (f->position > f->length) { + f->position = f->length; + return 3; + } + + return 0; +} + +/** + * @brief Opens the shared resource directory for reading entries. + * + * @return A directory handle (starting sector index) on success, or 0xffff on + * failure. + */ +shared_resource_dir shdopen(void) +{ + unsigned char i; + + if (do_shres_trap(0)) { + return 0xffff; + } + + sdcard_busy_wait(); + + + // Verify magic string in sector 0 + for (i = 0; magic_string[i]; i++) { + if (lpeek(0xffd6e00L + i) != magic_string[i]) { + return 0xffff; + } + } + + // Directory starts at sector 1 + return 1; +} + +/** + * @brief Reads the next resource entry from the shared resource directory + * that matches the required flags. + * + * @param required_flags Bitmask of required flags the resource must have. + * @param directory_handle Pointer to the current directory sector + * (auto-increments). + * @param dirent Pointer to a shared_resource struct to populate. + * @return + * - 0 on success (matching entry read), + * - 1 if trap failed, + * - 2 if end of directory reached (no more entries). + */ +char shdread(unsigned long required_flags, + shared_resource_dir* directory_handle, struct shared_resource_dirent* dirent) +{ + do { + if (do_shres_trap(*directory_handle)) { + return 1; + } + + sdcard_busy_wait(); + + // Check for end of directory (name_len == 0) + if (!lpeek(0xffd6e10L)) { + return 2; + } + + // Copy entry from buffer into dirent + lcopy(0xffd6e00L, (unsigned long)dirent, 256); + + (*directory_handle)++; + } while ((dirent->flags & required_flags) != required_flags); + + return 0; +}