From f6686f1e86927f038086023362251ebe78ce5ad6 Mon Sep 17 00:00:00 2001 From: Bad Diode Date: Wed, 2 Jun 2021 17:26:08 +0200 Subject: Init repo with basic BG framebuffer renderer --- src/bios_calls.s | 337 ++++++++++++++++++++++++++++ src/common.h | 667 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/filesystem.c | 409 ++++++++++++++++++++++++++++++++++ src/filesystem.h | 36 +++ src/interrupts.c | 91 ++++++++ src/interrupts.s | 89 ++++++++ src/main.c | 81 +++++++ src/renderer.c | 86 +++++++ src/shorthand.h | 47 ++++ 9 files changed, 1843 insertions(+) create mode 100644 src/bios_calls.s create mode 100644 src/common.h create mode 100644 src/filesystem.c create mode 100644 src/filesystem.h create mode 100644 src/interrupts.c create mode 100644 src/interrupts.s create mode 100644 src/main.c create mode 100644 src/renderer.c create mode 100644 src/shorthand.h (limited to 'src') diff --git a/src/bios_calls.s b/src/bios_calls.s new file mode 100644 index 0000000..740fa02 --- /dev/null +++ b/src/bios_calls.s @@ -0,0 +1,337 @@ +@ +@ Arithmetic functions. +@ + +@ Division. + .text + .code 16 + .align 2 + .global bios_div + .thumb_func +bios_div: + swi 0x06 + bx lr + +@ Square root. + .text + .code 16 + .align 2 + .global bios_sqrt + .thumb_func +bios_sqrt: + swi 0x08 + bx lr + +@ Arc-tangent. + .text + .code 16 + .align 2 + .global bios_atan + .thumb_func +bios_atan: + swi 0x09 + bx lr + +@ Arc-tangent2. + .text + .code 16 + .align 2 + .global bios_atan2 + .thumb_func +bios_atan2: + swi 0x0a + bx lr + +@ +@ Rotation/Scaling. +@ + +@ BG Affine Set. + .text + .code 16 + .align 2 + .global bios_bg_affine_set + .thumb_func +bios_bg_affine_set: + swi 0x0e + bx lr + +@ OBJ Affine Set. + .text + .code 16 + .align 2 + .global bios_obj_affine_set + .thumb_func +bios_obj_affine_set: + swi 0x0f + bx lr + +@ +@ (De)compression. +@ + +@ Bit unpacking. + .text + .code 16 + .align 2 + .global bios_bit_unpack + .thumb_func +bios_bit_unpack: + swi 0x10 + bx lr + +@ Huffman decompression. + .text + .code 16 + .align 2 + .global bios_huff_expand + .thumb_func +bios_huff_expand: + swi 0x13 + bx lr + +@ LZ77 decompression (Fast, WRAM, 8 bit writes). + .text + .code 16 + .align 2 + .global bios_lz77_expand_wram + .thumb_func +bios_lz77_expand_wram: + swi 0x11 + bx lr + +@ LZ77 decompression (Slow, VRAM, 16 bit writes). + .text + .code 16 + .align 2 + .global bios_lz77_expand_vram + .thumb_func +bios_lz77_expand_vram: + swi 0x12 + bx lr + +@ Run length encoding decompression (Fast, WRAM, 8 bit writes). + .text + .code 16 + .align 2 + .global bios_rle_expand_wram + .thumb_func +bios_rle_expand_wram: + swi 0x14 + bx lr + +@ Run length encoding decompression (Slow, VRAM, 16 bit writes). + .text + .code 16 + .align 2 + .global bios_rle_expand_vram + .thumb_func +bios_rle_expand_vram: + swi 0x15 + bx lr + +@ +@ Memory copy. +@ + +@ Memcopy/memfill in 32 byte units (Fast). + .text + .code 16 + .align 2 + .global bios_memcopy_32 + .thumb_func +bios_memcopy_32: + swi 0x0C + bx lr + +@ Memcopy/memfill in 4 / 2 byte units (Slow). + .text + .code 16 + .align 2 + .global bios_memcopy_8 + .thumb_func +bios_memcopy_8: + swi 0x0B + bx lr + +@ +@ Sound functions. +@ + +@ MIDI key to frequency. + .text + .code 16 + .align 2 + .global bios_midi2freq + .thumb_func +bios_midi2freq: + swi 0x1f + bx lr + +@ Sound bias. + .text + .code 16 + .align 2 + .global bios_sound_bias + .thumb_func +bios_sound_bias: + swi 0x19 + bx lr + +@ Sound channels clear. + .text + .code 16 + .align 2 + .global bios_sound_clear + .thumb_func +bios_sound_clear: + swi 0x1e + bx lr + +@ Sound driver initialization. + .text + .code 16 + .align 2 + .global bios_sound_init + .thumb_func +bios_sound_init: + swi 0x1a + bx lr + +@ Sound main function. To be called each 1/60 of a second after the sound VSync. + .text + .code 16 + .align 2 + .global bios_sound_main + .thumb_func +bios_sound_main: + swi 0x1c + bx lr + +@ Sound driver operation mode. + .text + .code 16 + .align 2 + .global bios_sound_mode + .thumb_func +bios_sound_mode: + swi 0x1b + bx lr + +@ Sound VSync. Called just after the VBlank interrupt each 1/60 of a second. + .text + .code 16 + .align 2 + .global bios_sound_vsync + .thumb_func +bios_sound_vsync: + swi 0x1d + bx lr + +@ Sound VSync off In case of issues manually call this to stop the sound. + .text + .code 16 + .align 2 + .global bios_sound_vsync_off + .thumb_func +bios_sound_vsync_off: + swi 0x28 + bx lr + +@ +@ Halt/Reset functions. +@ + +@ Halt until interrupt request. + .text + .code 16 + .align 2 + .global bios_halt + .thumb_func +bios_halt: + swi 0x02 + bx lr + +@ Halt until one of the specified interrupts occur. + .text + .code 16 + .align 2 + .global bios_interrupt_wait + .thumb_func +bios_interrupt_wait: + swi 0x04 + bx lr + +@ Halt until the VBlank interrupt occurs. + .text + .code 16 + .align 2 + .global bios_vblank_wait + .thumb_func +bios_vblank_wait: + swi 0x05 + bx lr + +@ Stop. Switches GBA to low power mode. + .text + .code 16 + .align 2 + .global bios_stop + .thumb_func +bios_stop: + swi 0x03 + bx lr + +@ Soft reset. + .text + .code 16 + .align 2 + .global bios_soft_reset + .thumb_func +bios_soft_reset: + swi 0x00 + bx lr + +@ Register RAM reset. + .text + .code 16 + .align 2 + .global bios_regram_reset + .thumb_func +bios_regram_reset: + swi 0x01 + bx lr + +@ Hard reset. + .text + .code 16 + .align 2 + .global bios_hard_reset + .thumb_func +bios_hard_reset: + swi 0x26 + bx lr + +@ +@ Misc functions. +@ + +@ BIOS checksum. + .text + .code 16 + .align 2 + .global bios_bios_checksum + .thumb_func +bios_bios_checksum: + swi 0x0d + bx lr + +@ MultiBoot. + .text + .code 16 + .align 2 + .global bios_multiboot + .thumb_func +bios_multiboot: + swi 0x25 + bx lr diff --git a/src/common.h b/src/common.h new file mode 100644 index 0000000..fbe01a4 --- /dev/null +++ b/src/common.h @@ -0,0 +1,667 @@ +#ifndef COMMON_H +#define COMMON_H + +#include "shorthand.h" + +#define CPU_FREQUENCY (2 << 23) + +// +// Memory sections. +// + +// Defines for the different memory sections in the GBA. +#define MEM_SROM 0x00000000 +#define MEM_EW 0x02000000 +#define MEM_IW 0x03000000 +#define MEM_IO 0x04000000 +#define MEM_PAL 0x05000000 +#define MEM_VRAM 0x06000000 +#define MEM_OAM 0x07000000 +#define MEM_PAK 0x08000000 +#define MEM_CART 0x0E000000 + +// +// Display modes. +// + +// Display registers. +#define DISP_CTRL *((vu32*)(MEM_IO + 0x0000)) +#define DISP_STATUS *((vu16*)(MEM_IO + 0x0004)) +#define DISP_VCOUNT *((vu16*)(MEM_IO + 0x0006)) + +// The first three bits in the DISP_CTRL are used to control the video mode. +#define DISP_MODE_0 0x0000 +#define DISP_MODE_1 0x0001 +#define DISP_MODE_2 0x0002 +#define DISP_MODE_3 0x0003 +#define DISP_MODE_4 0x0004 +#define DISP_MODE_5 0x0005 +#define DISP_GB (1 << 3) +#define DISP_PAGE (1 << 4) +#define DISP_OAM_HBLANK (1 << 5) +#define DISP_OBJ_1D (1 << 6) +#define DISP_BLANK (1 << 7) +#define DISP_BG_0 (1 << 8) +#define DISP_BG_1 (1 << 9) +#define DISP_BG_2 (1 << 10) +#define DISP_BG_3 (1 << 11) +#define DISP_OBJ (1 << 12) +#define DISP_ENABLE_SPRITES DISP_OBJ | DISP_OBJ_1D + +// These bits are used to control the DISP_STATUS register. +#define DISP_VBLANK_STATUS (1 << 0x0) +#define DISP_HBLANK_STATUS (1 << 0x1) +#define DISP_VCOUNT_STATUS (1 << 0x2) +#define DISP_VBLANK_IRQ (1 << 0x3) +#define DISP_HBLANK_IRQ (1 << 0x4) +#define DISP_VCOUNT_IRQ (1 << 0x5) +#define DISP_VCOUNT_TRIGGER(N) ((N) << 0x8) + +// Registers to control of BG layers. +#define BG_CTRL(N) *((vu16*)(0x04000008 + 0x0002 * (N))) + +// Bits to control the background. +#define BG_PRIORITY(N) ((N) & 0x3) +#define BG_CHARBLOCK(N) ((N) << 2) +#define BG_MOSAIC (1 << 6) +#define BG_HIGH_COLOR (1 << 7) +#define BG_SCREENBLOCK(N) ((N) << 8) +#define BG_AFFINE (1 << 0xD) +#define BG_SIZE(N) ((N) << 0xE) + +// BG registers for horizontal displacement. +#define BG_H_SCROLL_0 *((vu16*)(0x04000010 + 0x0004 * 0)) +#define BG_H_SCROLL_1 *((vu16*)(0x04000010 + 0x0004 * 1)) +#define BG_H_SCROLL_2 *((vu16*)(0x04000010 + 0x0004 * 2)) +#define BG_H_SCROLL_3 *((vu16*)(0x04000010 + 0x0004 * 3)) + +// BG registers for vertical displacement. +#define BG_V_SCROLL_0 *((vu16*)(0x04000012 + 0x0004 * 0)) +#define BG_V_SCROLL_1 *((vu16*)(0x04000012 + 0x0004 * 1)) +#define BG_V_SCROLL_2 *((vu16*)(0x04000012 + 0x0004 * 2)) +#define BG_V_SCROLL_3 *((vu16*)(0x04000012 + 0x0004 * 3)) + +// Screen settings. +#define SCREEN_WIDTH 240 +#define SCREEN_HEIGHT 160 + +// +// Colors. +// + +// The GBA in mode 3 expects rbg15 colors in the VRAM, where each component +// (RGB) have a 0--31 range. For example, pure red would be rgb15(31, 0, 0). +typedef u16 Color; + +// A palette is composed of 16 colors, with color at index 0 being transparent +// for sprites. +typedef Color Palette[16]; + +// Inline function to calculate the 15 bit color value. +#define RGB15(R,G,B) (u16)(((B) << 10) | ((G) << 5) | (R)); + +// Some nice default colors. +#define COLOR_RED RGB15(31, 0,12) +#define COLOR_BLUE RGB15(2, 15,30) +#define COLOR_CYAN RGB15(0, 30,30) +#define COLOR_GREY RGB15(12,12,12) +#define COLOR_BLACK RGB15(0, 0, 0) +#define COLOR_WHITE RGB15(28,28,28) + +// +// Tile memory access. +// + +// NOTE: Only defining 4bpp tiles for now. +// TODO: typedef u32 Tile[8]; +typedef struct Tile { + u32 row[8]; +} Tile; + +// Screenblocks and charblocks (tile blocks). +typedef Tile TileBlock[512]; +#define TILE_MEM ((TileBlock*) MEM_VRAM) +typedef u16 ScreenBlock[1024]; +#define SCREENBLOCK_MEM ((ScreenBlock*)MEM_VRAM) + +// Screenblock entry bits. +#define SCREENBLOCK_ENTRY_H_FLIP (1 << 0xA) +#define SCREENBLOCK_ENTRY_V_FLIP (1 << 0xB) +#define SCREENBLOCK_ENTRY_PAL(N) ((N) << 0xC) + +inline size_t +se_index(size_t tile_x, size_t tile_y, size_t map_width) { + size_t sbb = ((tile_x >> 5) + (tile_y >> 5) * (map_width >> 5)); + return sbb * 1024 + ((tile_x & 31) + (tile_y & 31) * 32); +} + +// We can treat the screen as a HxW matrix. With the following macro we can +// write a pixel to the screen at the (x, y) position using: +// +// FRAMEBUFFER[y][x] = color; +// +typedef Color Scanline[SCREEN_WIDTH]; +#define FRAMEBUFFER ((Scanline*) MEM_VRAM) +#define SCREEN_BUFFER ((u16*) MEM_VRAM) +#define PAL_BUFFER_BG ((u16*) MEM_PAL) +#define PAL_BUFFER_SPRITES ((u16*)(MEM_PAL + 0x200)) +#define PAL_BANK_BG ((Palette*) MEM_PAL) +#define PAL_BANK_SPRITES ((Palette*)(MEM_PAL + 0x200)) + +// +// Sprites. +// + +// Using macros instead of aligned structs for setting up OBJ attributes and +// affine parameters. +// TODO: Benchmark if this would be slower or the same that TONC's +// implementation. +// TODO: Cleanup OBJ/OAM memory copying and access. +#define OBJ_ATTR_0(N) *((vu16*)(MEM_OAM + 0 + 8 * (N))) +#define OBJ_ATTR_1(N) *((vu16*)(MEM_OAM + 2 + 8 * (N))) +#define OBJ_ATTR_2(N) *((vu16*)(MEM_OAM + 4 + 8 * (N))) +#define OBJ_AFFINE_PA(N) *((vs16*)(MEM_OAM + 6 + 8 * 0 + 8 * 4 * (N))) +#define OBJ_AFFINE_PB(N) *((vs16*)(MEM_OAM + 6 + 8 * 1 + 8 * 4 * (N))) +#define OBJ_AFFINE_PC(N) *((vs16*)(MEM_OAM + 6 + 8 * 2 + 8 * 4 * (N))) +#define OBJ_AFFINE_PD(N) *((vs16*)(MEM_OAM + 6 + 8 * 3 + 8 * 4 * (N))) + +// OBJ_ATTR_0 parameters +#define OBJ_Y_COORD(N) ((N) & 0xFF) +#define OBJ_NORMAL (0x00 << 0x8) +#define OBJ_AFFINE (0x01 << 0x8) +#define OBJ_HIDDEN (0x02 << 0x8) +#define OBJ_AFFINE_2X (0x03 << 0x8) +#define OBJ_ALPHA_BLEND (0x01 << 0xA) +#define OBJ_WINDOW (0x02 << 0xA) +#define OBJ_SHAPE_SQUARE (0x00 << 0xE) +#define OBJ_SHAPE_WIDE (0x01 << 0xE) +#define OBJ_SHAPE_TALL (0x02 << 0xE) + +// OBJ_ATTR_1 parameters +#define OBJ_X_COORD(N) ((N) & 0x1FF) +#define OBJ_AFFINE_IDX(N) ((N) << 0x9) +#define OBJ_H_FLIP (0x01 << 0xC) +#define OBJ_V_FLIP (0x01 << 0xD) +#define OBJ_SIZE_SMALL (0x00 << 0xE) +#define OBJ_SIZE_MID (0x01 << 0xE) +#define OBJ_SIZE_BIG (0x02 << 0xE) +#define OBJ_SIZE_HUGE (0x03 << 0xE) + +// OBJ_ATTR_2 parameters +#define OBJ_TILE_INDEX(N) ((N) & 0x3FF) +#define OBJ_PRIORITY(N) ((N) << 0xA) +#define OBJ_PAL_BANK(N) ((N) << 0xC) + +// +// Mode 4 page flipping +// + +static inline +void +flip_page(vu16 *backbuffer) { + backbuffer = (u16*)((u32)backbuffer ^ 0x0A000); + DISP_CTRL ^= DISP_PAGE; +} + +#define SCREEN_PAGE_1 ((vu16*) MEM_VRAM) +#define SCREEN_PAGE_2 ((vu16*) (MEM_VRAM + 0xa000)) + +// +// Profiling. +// + +#define TIMER_DATA_0 *((vu16*) (0x04000100 + 0x04 * 0)) +#define TIMER_DATA_1 *((vu16*) (0x04000100 + 0x04 * 1)) +#define TIMER_DATA_2 *((vu16*) (0x04000100 + 0x04 * 2)) +#define TIMER_DATA_3 *((vu16*) (0x04000100 + 0x04 * 3)) +#define TIMER_CTRL_0 *((vu16*) (0x04000102 + 0x04 * 0)) +#define TIMER_CTRL_1 *((vu16*) (0x04000102 + 0x04 * 1)) +#define TIMER_CTRL_2 *((vu16*) (0x04000102 + 0x04 * 2)) +#define TIMER_CTRL_3 *((vu16*) (0x04000102 + 0x04 * 3)) + +// Timer control bits. +#define TIMER_CTRL_FREQ_0 0 +#define TIMER_CTRL_FREQ_1 1 +#define TIMER_CTRL_FREQ_2 2 +#define TIMER_CTRL_FREQ_3 3 +#define TIMER_CTRL_CASCADE (1 << 2) +#define TIMER_CTRL_IRQ (1 << 6) +#define TIMER_CTRL_ENABLE (1 << 7) +#define TIMER_CTRL_DISABLE (0 << 7) + +// We use timers 2 and 3 to count the number of cycles since the profile_start +// functions is called. Don't use if the code we are trying to profile make use +// of these timers. +static inline +void +profile_start(void) { + TIMER_DATA_2 = 0; + TIMER_DATA_3 = 0; + TIMER_CTRL_2 = 0; + TIMER_CTRL_3 = 0; + TIMER_CTRL_3 = TIMER_CTRL_ENABLE | TIMER_CTRL_CASCADE; + TIMER_CTRL_2 = TIMER_CTRL_ENABLE; +} + +static inline +u32 +profile_stop(void) { + TIMER_CTRL_2 = 0; + return (TIMER_DATA_3 << 16) | TIMER_DATA_2; +} + +static inline +u32 +profile_measure(void) { + return (TIMER_DATA_3 << 16) | TIMER_DATA_2; +} + +// +// Input handling. +// + +// Memory address for key input and control register +#define KEY_INPUTS *((vu16*) 0x04000130) +#define KEY_CTRL *((vu16*) 0x04000132) + +// Key control register bits. +#define KEY_IRQ_KEY(N) (N) +#define KEY_IRQ (1 << 0xE) +#define KEY_IRQ_IF_SET (1 << 0xF) + +// Alias for key pressing bits. +#define KEY_A (1 << 0) +#define KEY_B (1 << 1) +#define KEY_SELECT (1 << 2) +#define KEY_START (1 << 3) +#define KEY_RIGHT (1 << 4) +#define KEY_LEFT (1 << 5) +#define KEY_UP (1 << 6) +#define KEY_DOWN (1 << 7) +#define KEY_R (1 << 8) +#define KEY_L (1 << 9) + +#define KEY_MASK 0x03FF + +// Saving the previous and current key states as globals for now. +static u16 key_curr = 0; +static u16 key_prev = 0; + +static inline +void +poll_keys(void) { + key_prev = key_curr; + key_curr = ~KEY_INPUTS & KEY_MASK; +} + +// Returns true if the given key has been pressed at time of calling and was not +// pressed since the previous call. For example, if a key is being held, this +// function will return `true` only on the frame where the key initially +// activated. +static inline +u32 +key_tap(u32 key) { + return (key_curr & key) & ~(key_prev & key); +} + +// Check if a given key is currently pressed. +static inline +u32 +key_pressed(u32 key) { + return (key_curr & key); +} + +// Check if a given key was just released. +static inline +u32 +key_released(u32 key) { + return ~(key_curr & key) & (key_prev & key); +} + +// Check if the given key is pressed and has been since at least one frame. +static inline +u32 +key_hold(u32 key) { + return key_curr & key_prev & key; +} + +// Check if the given key/button is currently pressed. +#define KEY_PRESSED(key) (~(KEY_INPUTS) & key) + +// Back/unpack bits. +static inline +u32 +unpack_1bb(u8 hex) { + const u32 conversion_u32[16] = { + 0x00000000, 0x00000001, 0x00000010, 0x00000011, + 0x00000100, 0x00000101, 0x00000110, 0x00000111, + 0x00001000, 0x00001001, 0x00001010, 0x00001011, + 0x00001100, 0x00001101, 0x00001110, 0x00001111, + }; + u8 low = hex & 0xF; + u8 high = (hex >> 4) & 0xF; + return (conversion_u32[high] << 16) | conversion_u32[low]; +} + +// Unpack N tiles packed at 1bpp. +static inline +void +unpack_tiles(u32 *src, u32 *dst, size_t n_tiles) { + u32 *target_src = src + n_tiles * 2; + while (src != target_src) { + *dst++ = unpack_1bb((*src >> 24) & 0xFF); + *dst++ = unpack_1bb((*src >> 16) & 0xFF); + *dst++ = unpack_1bb((*src >> 8) & 0xFF); + *dst++ = unpack_1bb(*src & 0xFF); + src++; + } +} + +// +// Direct Memory Access (DMA) +// + + +// Source, destination, and control registers. +#define DMA_SRC(N) *((vu32*) 0x040000B0 + (N) * 12) +#define DMA_DST(N) *((vu32*) 0x040000B4 + (N) * 12) +#define DMA_CTRL(N) *((vu32*) 0x040000B8 + (N) * 12) + +// DMA control bits. +#define DMA_DST_INC (0 << 0x15) +#define DMA_DST_DEC (1 << 0x15) +#define DMA_DST_FIXED (2 << 0x15) +#define DMA_DST_RELOAD (3 << 0x15) +#define DMA_SRC_INC (0 << 0x17) +#define DMA_SRC_DEC (1 << 0x17) +#define DMA_SRC_FIXED (2 << 0x17) +#define DMA_REPEAT (1 << 0x19) +#define DMA_CHUNK_16 (0 << 0x1A) +#define DMA_CHUNK_32 (1 << 0x1A) +#define DMA_NOW (0 << 0x1C) +#define DMA_VBLANK (1 << 0x1C) +#define DMA_HBLANK (2 << 0x1C) +#define DMA_REFRESH (3 << 0x1C) +#define DMA_IRQ (1 << 0x1E) +#define DMA_ENABLE (1 << 0x1F) + +// Custom struct for cleaner DMA transfer functions. +typedef struct DmaStr { + const void *src; + void *dst; + u32 ctrl; +} DmaStr; + +#define DMA_TRANSFER ((volatile DmaStr*) 0x040000B0) + +// Transfer `count` number of chunks from src to dst using a DMA channel. Note +// that chunks are not bytes, but instead configured based on bits set by +// DMA_CTRL. +inline +void +dma_transfer_copy(void *dst, const void *src, u32 count, int channel, u32 options) { + DMA_TRANSFER[channel].ctrl = 0; + DMA_TRANSFER[channel].src = src; + DMA_TRANSFER[channel].dst = dst; + DMA_TRANSFER[channel].ctrl = count | options; +} + +inline +void +dma_transfer_fill(void *dst, volatile u32 src, u32 count, int channel, u32 options) { + DMA_TRANSFER[channel].ctrl = 0; + DMA_TRANSFER[channel].src = (const void *)&src; + DMA_TRANSFER[channel].dst = dst; + DMA_TRANSFER[channel].ctrl = count | options | DMA_SRC_FIXED; +} + +// Copy N number of bytes using a DMA channel. +inline +void +dma_copy(void *dst, const void *src, u32 size, int channel) { + dma_transfer_copy(dst, src, size / 4, channel, DMA_CHUNK_32 | DMA_ENABLE); +} + +// Fill the dst location with the word set at src. +inline +void +dma_fill(void *dst, vu32 src, u32 size, int channel) { + dma_transfer_fill(dst, src, size / 4, channel, DMA_CHUNK_32 | DMA_ENABLE); +} + +// +// Interrupts. +// + +#define IRQ_ENABLE *((vu16*) 0x04000200) +#define IRQ_ACK *((vu16*) 0x04000202) +#define IRQ_CTRL *((vu16*) 0x04000208) +#define IRQ_ACK_BIOS *((vu16*) 0x03007FF8) + +typedef enum { + IRQ_VBLANK, + IRQ_HBLANK, + IRQ_VCOUNT, + IRQ_TIMER_0, + IRQ_TIMER_1, + IRQ_TIMER_2, + IRQ_TIMER_3, + IRQ_SERIAL, + IRQ_DMA_0, + IRQ_DMA_1, + IRQ_DMA_2, + IRQ_DMA_3, + IRQ_KEYPAD, + IRQ_GAMEPAK, +} IrqIndex; + +typedef void (*IrsFunc)(void); + +// Stub function pointer needed for when we want to enable interrupts that don't +// require a custom function, such as for the BIOS VSync. +void irs_stub(void); + +// Set and enable a given function in the interrupt table. If func is NULL, the +// interrupt will be disabled instead. +void irs_set(IrqIndex idx, IrsFunc func); + +// Initialize the function pointer for the main IRS routine written in ARM +// assembly and enable interrupts. +void irq_init(void); + +// +// BIOS function declarations. +// + +// These functions declarations can be used to call the BIOS functions from the +// asm code. +int bios_vblank_wait(); +int bios_div(int num, int denom); + +// +// Sound. +// + +// Sound registers. +#define SOUND_SQUARE1_SWEEP *((vu16*)(MEM_IO + 0x60)) +#define SOUND_SQUARE1_CTRL *((vu16*)(MEM_IO + 0x62)) +#define SOUND_SQUARE1_FREQ *((vu16*)(MEM_IO + 0x64)) +#define SOUND_SQUARE2_CTRL *((vu16*)(MEM_IO + 0x68)) +#define SOUND_SQUARE2_FREQ *((vu16*)(MEM_IO + 0x6C)) +#define SOUND_WAVE_MODE *((vu16*)(MEM_IO + 0x70)) +#define SOUND_WAVE_CTRL *((vu16*)(MEM_IO + 0x72)) +#define SOUND_WAVE_FREQ *((vu16*)(MEM_IO + 0x74)) +#define SOUND_NOISE_CTRL *((vu16*)(MEM_IO + 0x78)) +#define SOUND_NOISE_FREQ *((vu16*)(MEM_IO + 0x7C)) +#define SOUND_DMG_MASTER *((vu16*)(MEM_IO + 0x80)) +#define SOUND_DSOUND_MASTER *((vu16*)(MEM_IO + 0x82)) +#define SOUND_STATUS *((vu16*)(MEM_IO + 0x84)) +#define SOUND_BIAS *((vu16*)(MEM_IO + 0x88)) + +// Sound DMG master bits. +#define SOUND_VOLUME_LEFT(N) (N) +#define SOUND_VOLUME_RIGHT(N) ((N) << 4) +#define SOUND_ENABLE_SQUARE1_LEFT (1 << 0x8) +#define SOUND_ENABLE_SQUARE2_LEFT (1 << 0x9) +#define SOUND_ENABLE_WAVE_LEFT (1 << 0xA) +#define SOUND_ENABLE_NOISE_LEFT (1 << 0xB) +#define SOUND_ENABLE_SQUARE1_RIGHT (1 << 0xC) +#define SOUND_ENABLE_SQUARE2_RIGHT (1 << 0xD) +#define SOUND_ENABLE_WAVE_RIGHT (1 << 0xE) +#define SOUND_ENABLE_NOISE_RIGHT (1 << 0xF) + +typedef enum { + SOUND_DSOUND = (0x0 << 0), + SOUND_SQUARE1 = (0x1 << 0), + SOUND_SQUARE2 = (0x1 << 1), + SOUND_WAVE = (0x1 << 2), + SOUND_NOISE = (0x1 << 3), +} SoundChannel; + +inline u16 +sound_volume(SoundChannel channels, u8 volume) { + volume = volume & 0x7; + channels = channels & 0xF; + return volume | (volume << 0x4) | (channels << 0x8) | (channels << 0xC); +} + +// Sound Direct Sound master bits. +#define SOUND_DMG25 0x0 +#define SOUND_DMG50 0x1 +#define SOUND_DMG100 0x2 +#define SOUND_DSOUND_RATIO_A (1 << 0x2) +#define SOUND_DSOUND_RATIO_B (1 << 0x3) +#define SOUND_DSOUND_LEFT_A (1 << 0x8) +#define SOUND_DSOUND_RIGHT_A (1 << 0x9) +#define SOUND_DSOUND_TIMER_A (1 << 0xA) +#define SOUND_DSOUND_RESET_A (1 << 0xB) +#define SOUND_DSOUND_LEFT_B (1 << 0xC) +#define SOUND_DSOUND_RIGHT_B (1 << 0xD) +#define SOUND_DSOUND_TIMER_B (1 << 0xE) +#define SOUND_DSOUND_RESET_B (1 << 0xF) + +// Direct sound FIFO queues. +#define SOUND_FIFO_A ((u16*)(MEM_IO + 0xA0)) +#define SOUND_FIFO_B ((u16*)(MEM_IO + 0xA4)) + +// Sound status bits. +#define SOUND_ENABLE (1 << 0x7) + +// DMG square control bits. +#define SOUND_SQUARE_LEN(N) (N) +#define SOUND_SQUARE_DUTY(N) ((N) << 0x6) +#define SOUND_SQUARE_ENV_TIME(N) ((N) << 0x8) +#define SOUND_SQUARE_ENV_DIR(N) ((N) << 0xB) +#define SOUND_SQUARE_ENV_VOL(N) ((N) << 0xC) + +// DMG square 1 sweep control bits. +#define SOUND_SWEEP_NUMBER(N) (N) +#define SOUND_SWEEP_DIR(N) ((N) << 0x3) +#define SOUND_SWEEP_TIME(N) ((N) << 0x4) + +// DMG frequency bits (Square/Wave). +#define SOUND_FREQ_TIMED (1 << 0xE) +#define SOUND_FREQ_RESET (1 << 0xF) + +// DMG wave ram. +#define SOUND_WAVE_RAM_0 *((vu32*)(MEM_IO + 0x90)) +#define SOUND_WAVE_RAM_1 *((vu32*)(MEM_IO + 0x94)) +#define SOUND_WAVE_RAM_2 *((vu32*)(MEM_IO + 0x98)) +#define SOUND_WAVE_RAM_3 *((vu32*)(MEM_IO + 0x9C)) + +// DMG wave control bits. +#define SOUND_WAVE_LENGTH(N) (N) +#define SOUND_WAVE_MUTE 0x0 +#define SOUND_WAVE_VOL_100 (0x1 << 0xD) +#define SOUND_WAVE_VOL_75 (0x4 << 0xD) +#define SOUND_WAVE_VOL_50 (0x2 << 0xD) +#define SOUND_WAVE_VOL_25 (0x3 << 0xD) + +// DMG wave mode bits. +#define SOUND_WAVE_BANK_MODE(N) ((N) << 0x5) +#define SOUND_WAVE_BANK_SELECT(N) ((N) << 0x6) +#define SOUND_WAVE_ENABLE (1 << 0x7) + +typedef u8 WaveBank[32]; + +#define SOUND_WAVE_RAM ((WaveBank*)(MEM_IO + 0x90)) + +typedef enum { + NOTE_C_2 , NOTE_C_SHARP_2 , NOTE_D_2 , NOTE_D_SHARP_2 , + NOTE_E_2 , NOTE_F_2 , NOTE_F_SHARP_2 , NOTE_G_2 , + NOTE_G_SHARP_2 , NOTE_A_2 , NOTE_A_SHARP_2 , NOTE_B_2 , + NOTE_C_3 , NOTE_C_SHARP_3 , NOTE_D_3 , NOTE_D_SHARP_3 , + NOTE_E_3 , NOTE_F_3 , NOTE_F_SHARP_3 , NOTE_G_3 , + NOTE_G_SHARP_3 , NOTE_A_3 , NOTE_A_SHARP_3 , NOTE_B_3 , + NOTE_C_4 , NOTE_C_SHARP_4 , NOTE_D_4 , NOTE_D_SHARP_4 , + NOTE_E_4 , NOTE_F_4 , NOTE_F_SHARP_4 , NOTE_G_4 , + NOTE_G_SHARP_4 , NOTE_A_4 , NOTE_A_SHARP_4 , NOTE_B_4 , + NOTE_C_5 , NOTE_C_SHARP_5 , NOTE_D_5 , NOTE_D_SHARP_5 , + NOTE_E_5 , NOTE_F_5 , NOTE_F_SHARP_5 , NOTE_G_5 , + NOTE_G_SHARP_5 , NOTE_A_5 , NOTE_A_SHARP_5 , NOTE_B_5 , + NOTE_C_6 , NOTE_C_SHARP_6 , NOTE_D_6 , NOTE_D_SHARP_6 , + NOTE_E_6 , NOTE_F_6 , NOTE_F_SHARP_6 , NOTE_G_6 , + NOTE_G_SHARP_6 , NOTE_A_6 , NOTE_A_SHARP_6 , NOTE_B_6 , + NOTE_C_7 , NOTE_C_SHARP_7 , NOTE_D_7 , NOTE_D_SHARP_7 , + NOTE_E_7 , NOTE_F_7 , NOTE_F_SHARP_7 , NOTE_G_7 , + NOTE_G_SHARP_7 , NOTE_A_7 , NOTE_A_SHARP_7 , NOTE_B_7 , + NOTE_C_8 +} Note; + +static const u32 sound_rates[] = { + 44 , 156 , 262 , 363 , 457 , 547 , 631 , 710 , 785 , 856 , 923 , 986 , + 1046, 1102, 1155, 1205, 1252, 1297, 1339, 1379, 1416, 1452, 1485, 1517, + 1547, 1575, 1601, 1626, 1650, 1672, 1693, 1713, 1732, 1750, 1766, 1782, + 1797, 1811, 1824, 1837, 1849, 1860, 1870, 1880, 1890, 1899, 1907, 1915, + 1922, 1929, 1936, 1942, 1948, 1954, 1959, 1964, 1969, 1973, 1977, 1981, + 1985, 1988, 1992, 1995, 1998, 2001, 2003, 2006, 2008, 2010, 2012, 2014, + 2016, +}; + +// +// System control. +// + +// Used to configure gamepak access timings. +#define SYSTEM_WAIT *((vu16*)(MEM_IO + 0x0204)) + +// This register defaults to 0, but manufacture cartridges use the values +// provided below. +#define SYSTEM_WAIT_DEFAULT 0 +#define SYSTEM_WAIT_CARTRIDGE 0x4317 + +// +// Misc. +// + +// Custom VSync option. This will waste a lot of battery power, since the +// machine is not clocked down. Prefer using `bios_vblank_wait()` if interrupts +// are enabled. +static inline void +wait_vsync(void) { + while(DISP_VCOUNT >= 160); + while(DISP_VCOUNT < 160); +} + +// General utility macros. +#define MIN(A, B) ((A) <= (B) ? (A) : (B)) +#define MAX(A, B) ((A) >= (B) ? (A) : (B)) +#define CLAMP(X, MIN, MAX) ((X) <= (MIN) ? (MIN) : (X) > (MAX) ? (MAX): (X)) +#define LEN(ARR) (sizeof(ARR) / sizeof((ARR)[0])) + +// Fixed-point arithmetic for (i.P) numbers. +#define FP_MUL(A,B,P) (((A) * (B)) >> (P)) +#define FP_DIV(A,B,P) (((A) << (P)) / (B)) +#define FP_LERP(Y0,Y1,X,P) ((Y0) + FP_MUL((X), ((Y1) - (Y0)), P)) + +// +// Memory section macros for devkitARM. +// + +#define IWRAM_DATA __attribute__((section(".iwram"))) +#define IWRAM_CODE __attribute__((section(".iwram"), long_call, target("arm"))) +#define EWRAM_DATA __attribute__((section(".ewram"))) +#define EWRAM_CODE __attribute__((section(".ewram"), long_call)) +#define EWRAM_BSS __attribute__((section(".sbss"))) + +#endif // COMMON_H diff --git a/src/filesystem.c b/src/filesystem.c new file mode 100644 index 0000000..00c0605 --- /dev/null +++ b/src/filesystem.c @@ -0,0 +1,409 @@ +/* +Copyright (c) 2021 Bad Diode + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE. +*/ + +#include + +#include "filesystem.h" + +// This file implements a filesystem with a minimum block size of 256 bytes. The +// maximum number of files depends on the block size. The default 1KB block size +// will give us 32-64 files depending on the size of MEM_CART. In case we want +// to use a block size of 512 bytes, we will have up to 128 file available. +// Blocks of 256 bytes will give us the maximum of 255 files available, since +// a block index of 0xFF will be considered as a null block. + +// A fileblock of 1KB give us a maximum of 64 files. +#define FILE_BLOCK_SIZE KB(1) +#define FILE_MAX_FILES 64 +#define FILE_N_BLOCKS 62 + +// With this file name size sizeof(FileIndex) will be 32 bytes. 32 * 64 files +// give us 2KB spent on file index that we can't use for data (so maximum of 62 +// files without accounting for the block index). +#define FILE_NAME_SIZE 30 +#define FILE_INDEX_NUM 62 + +// Since we are reserving the first 2K bytes for the filesystem, we have 60 +// blocks available for writing data. If you were to change the previous +// parameters, you *must* recalculate the initial block start location. +#define FILE_DATA_START KB(2) + +// We must write to the SRAM using the 8bit bus. +#define SRAM ((vu8*)(MEM_CART)) + +// Special filesystem constants. +enum { FS_INIT_PATTERN = 0xBA, FS_NULL = 0xFF }; + +typedef struct FileBlock { + // Size used in the current block (in bytes). Should be smaller than: + // FILE_BLOCK_SIZE - sizeof(FileBlock) + u16 size; + // The index for the next block. Set to FS_NULL if there is none. + u8 next_block; + u8 prev_block; +} FileBlock; + +typedef struct FileIndex { + // File name. + char name[FILE_NAME_SIZE + 1]; + // Index to the first block of this file. If set to FS_NULL this file + // has not yet been written to. + u8 first_block; +} FileIndex; + +// The filesystem header. +typedef struct FileSystem { + // The first byte of the SRAM can become corrupted in some situations, like + // changing cartridges for example. + u8 blank; + // If the filesystem exists, this will be set to FS_INIT_PATTERN. + u8 initialized; + // Number of blocks in use. + u8 busy_blocks; + // Number of files currently existing in the filesystem. + u8 num_files; + // This stores a bitmap pattern to keep track of the blocks in use by the + // filesystem. The first byte maps the first 8 blocks and so on. + u8 used_blocks[FILE_MAX_FILES / 8]; + // The list of possible file indexes. + FileIndex files[FILE_INDEX_NUM]; +} FileSystem; + +#define FILE_BLOCK_CAPACITY (FILE_BLOCK_SIZE - sizeof(FileBlock)) + +EWRAM_BSS +static FileSystem filesystem; + +void +_fs_read(u8 *dst, u16 pos, u16 n_bytes) { + for (size_t i = 0; i < n_bytes; ++i) { + dst[i] = SRAM[pos + i]; + } +} + +void +_fs_write(u8 *src, u16 pos, u16 n_bytes) { + for (size_t i = 0; i < n_bytes; ++i) { + SRAM[pos + i] = src[i]; + } +} + +void +fs_init(void) { + // Load filesystem if existing. + _fs_read(&filesystem, 0, sizeof(FileSystem)); + if (filesystem.initialized != FS_INIT_PATTERN) { + // Clear SRAM. + for (size_t i = 0; i < KB(64) / 8; ++i) { + SRAM[i] = 0x00; + } + + // Initialize block headers. + FileBlock block = { + .size = 0, + .next_block = FS_NULL, + .prev_block = FS_NULL, + }; + for (size_t i = 0; i < FILE_INDEX_NUM; ++i) { + u16 block_pos = FILE_DATA_START + FILE_BLOCK_SIZE * i; + _fs_write(&block, block_pos, sizeof(FileBlock)); + } + + // Initialize filesystem. + dma_fill(&filesystem, 0, sizeof(FileSystem), 3); + filesystem.initialized = FS_INIT_PATTERN; + for (size_t i = 0; i < FILE_INDEX_NUM; ++i) { + filesystem.files[i].first_block = FS_NULL; + } + + // Write the FS to disk. + _fs_write(&filesystem, 0, sizeof(FileSystem)); + } +} + +void +_fs_update_filesystem_header(void) { + _fs_write(&filesystem, 0, offsetof(FileSystem, files)); +} + +void +_fs_update_file_index(u16 index) { + _fs_write(&filesystem.files[index], + offsetof(FileSystem, files) + index * sizeof(FileIndex), + sizeof(FileIndex)); +} + +File +fs_open_file(char *name, OpenMode mode) { + // Try to find an existing file. + for (size_t i = 0; i < filesystem.num_files; ++i) { + // TODO: Replace strcmp with vectorized fixed size char comparison. + if (strcmp(name, filesystem.files[i].name) == 0) { + return (File){i, 0, mode}; + } + } + + // If read only. + if ((mode & (FS_OPEN_WRITE | FS_OPEN_APPEND)) == 0) { + return (File){FS_NULL, 0, mode}; + } + + // Create a new file if there is space. + if (filesystem.num_files < FILE_INDEX_NUM) { + u16 index = filesystem.num_files++; + u16 k = 0; + while(*name) { + filesystem.files[index].name[k++] = *name++; + } + + // Update file index and filesystem on SRAM. + _fs_update_file_index(index); + _fs_update_filesystem_header(); + + return (File){index, 0, mode}; + } + return (File){FS_NULL, 0, mode}; +} + +u16 +fs_file_size(File *file) { + u16 size = 0; + FileBlock block; + u16 blk_id = filesystem.files[file->index].first_block; + while (blk_id != FS_NULL) { + u16 block_pos = FILE_DATA_START + FILE_BLOCK_SIZE * blk_id; + _fs_read(&block, block_pos, sizeof(FileBlock)); + size += block.size; + blk_id = block.next_block; + } + return size; +} + +u8 +_fs_init_new_block(void) { + // Find free block. + u8 block_index = 0; + for (size_t j = 0; j < LEN(filesystem.used_blocks); ++j) { + for (size_t i = 0; i < 8; ++i, block_index++) { + u8 blk = (filesystem.used_blocks[j] >> i) & 0x1; + if (blk == 0) { + // Initialize the block. + filesystem.busy_blocks++; + filesystem.used_blocks[j] |= (1 << i); + _fs_update_filesystem_header(); + return block_index; + } + } + } + return FS_NULL; +} + +// Recursively free blocks starting at blk_id. To improve performance, the +// filesystem header is updated in memory but not written to disk. It is +// responsability of the caller to perform the filesystem update. +void +_fs_free_blocks(u8 blk_id) { + if (blk_id == FS_NULL) { + return; + } + + // Read block. + FileBlock block; + u16 block_pos = FILE_DATA_START + FILE_BLOCK_SIZE * blk_id; + _fs_read(&block, block_pos, sizeof(FileBlock)); + + // Update block. + u8 next_block = block.next_block; + block = (FileBlock){ + .size = 0, + .next_block = FS_NULL, + .prev_block = FS_NULL, + }; + _fs_write(&block, block_pos, sizeof(FileBlock)); + + // Update dirty and busy blocks. + filesystem.busy_blocks--; + filesystem.used_blocks[blk_id / 8] &= ~(1 << (blk_id % 8)); + + _fs_free_blocks(next_block); +} + +void +_fs_write_to_block(u8 *src, u16 n_bytes, u16 blk_offset, + u8 blk_id, u8 prev_blk, bool append) { + // Read initial block. + FileBlock block; + u16 block_pos = FILE_DATA_START + FILE_BLOCK_SIZE * blk_id; + _fs_read(&block, block_pos, sizeof(FileBlock)); + u16 block_capacity = FILE_BLOCK_CAPACITY - blk_offset; + + // Write capacity. + u16 block_bytes = MIN(block_capacity, n_bytes); + _fs_write(src, block_pos + sizeof(FileBlock) + blk_offset, block_bytes); + + if (n_bytes > block_capacity) { + if (block.next_block == FS_NULL) { + // Find new available block and initialize it. + block.next_block = _fs_init_new_block(); + } + _fs_write_to_block(src + block_capacity, n_bytes - block_capacity, 0, + block.next_block, blk_id, append); + } else if (block.next_block != FS_NULL){ + // Recursively free unused blocks. + _fs_free_blocks(block.next_block); + _fs_update_filesystem_header(); + block.next_block = FS_NULL; + } + + // Update block header. + if (prev_blk != FS_NULL) { + block.prev_block = prev_blk; + } + block.size = block_bytes; + _fs_write(&block, block_pos, sizeof(FileBlock)); +} + +int +fs_seek(File *file, int offset, SeekMode mode) { + u16 file_size = fs_file_size(file); + u16 new_offset = 0; + switch (mode) { + case FS_SEEK_SET: { + new_offset = offset; + } break; + case FS_SEEK_CUR: { + new_offset = MAX((int)file->offset + offset, 0); + } break; + case FS_SEEK_END: { + new_offset = MAX((int)file_size - 1 + offset, 0); + } break; + } + if (new_offset != 0 && new_offset >= file_size) { + return -1; + } + file->offset = new_offset; + return 0; +} + +u16 +fs_write(u8 *src, u16 n_bytes, File *file) { + if ((file->mode & (FS_OPEN_WRITE | FS_OPEN_APPEND)) == 0) { + return 0; + } + + FileIndex *file_idx = &filesystem.files[file->index]; + + u8 blk_id = FS_NULL; + u8 blk_prev = FS_NULL; + u16 offset = file->offset; + if (file_idx->first_block == FS_NULL) { + // Check how many blocks will this write requires and if we have enough + // available. + u16 blocks_required = n_bytes / FILE_BLOCK_CAPACITY; + u16 blocks_available = FILE_N_BLOCKS - filesystem.busy_blocks; + if (blocks_required > blocks_available) { + return 0; + } + + // Find the first available block. + blk_id = _fs_init_new_block(); + file_idx->first_block = blk_id; + + // Update file index on SRAM. + _fs_update_file_index(file->index); + } else { + // Check how many blocks will this write requires and if we have + // enough available. + u16 file_size = fs_file_size(file); + u16 blocks_in_file = file_size / FILE_BLOCK_SIZE; + u16 blocks_available = FILE_N_BLOCKS - filesystem.busy_blocks + blocks_in_file; + u16 blocks_required = (n_bytes + offset) / FILE_BLOCK_CAPACITY; + if (blocks_required > blocks_available) { + return 0; + } + + blk_id = file_idx->first_block; + + // If there is an offset find the block index and relative offset. + if (offset >= FILE_BLOCK_CAPACITY) { + u16 n_blocks_offset = offset / FILE_BLOCK_CAPACITY; + for (size_t i = 0; i < n_blocks_offset; ++i) { + FileBlock block; + u16 block_pos = FILE_DATA_START + FILE_BLOCK_SIZE * blk_id; + _fs_read(&block, block_pos, sizeof(FileBlock)); + blk_id = block.next_block; + blk_prev = block.prev_block; + if (blk_id == FS_NULL) { + return 0; + } + } + offset = offset % FILE_BLOCK_CAPACITY; + } + } + + // Write to block. + _fs_write_to_block(src, n_bytes, offset, blk_id, blk_prev, + file->mode == FS_OPEN_APPEND); + + return n_bytes; +} + +void +_fs_read_from_block(u8 *dst, u16 n_bytes, u16 blk_offset, u8 blk_id) { + // Read initial block. + FileBlock block; + u16 block_pos = FILE_DATA_START + FILE_BLOCK_SIZE * blk_id; + _fs_read(&block, block_pos, sizeof(FileBlock)); + + u16 read_bytes = MIN(block.size - blk_offset, n_bytes); + _fs_read(dst, block_pos + blk_offset + sizeof(FileBlock), read_bytes); + + u16 remaining_bytes = n_bytes - read_bytes; + if (block.next_block != FS_NULL && remaining_bytes > 0) { + _fs_read_from_block(dst + read_bytes, remaining_bytes, 0, block.next_block); + } +} + +u16 +fs_read(u8 *dst, u16 n_bytes, File *file) { + if ((file->mode & FS_OPEN_READ) == 0) { + return 0; + } + + // If there is an offset find the block index and relative offset. + u8 blk_id = filesystem.files[file->index].first_block; + u16 offset = file->offset; + + // Read as much as we can from the file after the offset. + u16 file_size = fs_file_size(file); + if (offset + n_bytes >= file_size) { + n_bytes = file_size - offset; + } + + if (offset >= FILE_BLOCK_CAPACITY) { + u16 n_blocks_offset = offset / FILE_BLOCK_CAPACITY; + for (size_t i = 0; i < n_blocks_offset; ++i) { + FileBlock block; + u16 block_pos = FILE_DATA_START + FILE_BLOCK_SIZE * blk_id; + _fs_read(&block, block_pos, sizeof(FileBlock)); + blk_id = block.next_block; + if (blk_id == FS_NULL) { + return 0; + } + } + offset = offset % FILE_BLOCK_CAPACITY; + } + + // Copy n_bytes to destination. + _fs_read_from_block(dst, n_bytes, offset, blk_id); + + return n_bytes; +} diff --git a/src/filesystem.h b/src/filesystem.h new file mode 100644 index 0000000..d50e5e7 --- /dev/null +++ b/src/filesystem.h @@ -0,0 +1,36 @@ +#ifndef FILESYSTEM_H +#define FILESYSTEM_H + +#include "common.h" + +typedef enum { + FS_OPEN_READ = (1 << 0), + FS_OPEN_WRITE = (1 << 1), + FS_OPEN_APPEND = (1 << 2), +} OpenMode; + +typedef struct File { + // File index offset. + u8 index; + // The offset within the file. Must always be valid, and so the File struct + // shouldn't be manaully modified unless we are sure we know what we are + // doing. + u16 offset; + // The mode of this file (read/write/append). + OpenMode mode; +} File; + +typedef enum { + FS_SEEK_SET, + FS_SEEK_CUR, + FS_SEEK_END, +} SeekMode; + +void fs_init(void); +File fs_open_file(char *name, OpenMode mode); +u16 fs_file_size(File *file); +int fs_seek(File *file, int offset, SeekMode mode); +u16 fs_write(u8 *src, u16 n_bytes, File *file); +u16 fs_read(u8 *dst, u16 n_bytes, File *file); + +#endif // FILESYSTEM_H diff --git a/src/interrupts.c b/src/interrupts.c new file mode 100644 index 0000000..8e560dc --- /dev/null +++ b/src/interrupts.c @@ -0,0 +1,91 @@ +#include "common.h" + +IrsFunc irs_table[] = { + [IRQ_VBLANK ] = NULL, + [IRQ_HBLANK ] = NULL, + [IRQ_VCOUNT ] = NULL, + [IRQ_TIMER_0] = NULL, + [IRQ_TIMER_1] = NULL, + [IRQ_TIMER_2] = NULL, + [IRQ_TIMER_3] = NULL, + [IRQ_SERIAL ] = NULL, + [IRQ_DMA_0 ] = NULL, + [IRQ_DMA_1 ] = NULL, + [IRQ_DMA_2 ] = NULL, + [IRQ_DMA_3 ] = NULL, + [IRQ_KEYPAD ] = NULL, + [IRQ_GAMEPAK] = NULL, +}; + +// External irs_main function, has to be written in ARM assembly. +void irs_main(void); +#define IRS_MAIN *(IrsFunc*)(0x03007FFC) + +void +irq_enable(IrqIndex idx) { + switch (idx) { + case IRQ_VBLANK: { DISP_STATUS |= DISP_VBLANK_IRQ; } break; + case IRQ_HBLANK: { DISP_STATUS |= DISP_HBLANK_IRQ; } break; + case IRQ_VCOUNT: { DISP_STATUS |= DISP_VCOUNT_IRQ; } break; + case IRQ_TIMER_0: { TIMER_CTRL_0 |= TIMER_CTRL_IRQ; } break; + case IRQ_TIMER_1: { TIMER_CTRL_1 |= TIMER_CTRL_IRQ; } break; + case IRQ_TIMER_2: { TIMER_CTRL_2 |= TIMER_CTRL_IRQ; } break; + case IRQ_TIMER_3: { TIMER_CTRL_3 |= TIMER_CTRL_IRQ; } break; + case IRQ_SERIAL: { /* TODO: Set REG_SERIAL? */ } break; + case IRQ_DMA_0: { DMA_CTRL(0) |= DMA_IRQ; } break; + case IRQ_DMA_1: { DMA_CTRL(1) |= DMA_IRQ; } break; + case IRQ_DMA_2: { DMA_CTRL(2) |= DMA_IRQ; } break; + case IRQ_DMA_3: { DMA_CTRL(3) |= DMA_IRQ; } break; + case IRQ_KEYPAD: { KEY_CTRL |= KEY_IRQ; } break; + case IRQ_GAMEPAK: { /* Nothing to do here...*/ } break; + } + IRQ_ENABLE |= (1 << idx); +} + +void +irq_disable(IrqIndex idx) { + switch (idx) { + case IRQ_VBLANK: { DISP_STATUS &= ~DISP_VBLANK_IRQ; } break; + case IRQ_HBLANK: { DISP_STATUS &= ~DISP_HBLANK_IRQ; } break; + case IRQ_VCOUNT: { DISP_STATUS &= ~DISP_VCOUNT_IRQ; } break; + case IRQ_TIMER_0: { TIMER_CTRL_0 &= ~TIMER_CTRL_IRQ; } break; + case IRQ_TIMER_1: { TIMER_CTRL_1 &= ~TIMER_CTRL_IRQ; } break; + case IRQ_TIMER_2: { TIMER_CTRL_2 &= ~TIMER_CTRL_IRQ; } break; + case IRQ_TIMER_3: { TIMER_CTRL_3 &= ~TIMER_CTRL_IRQ; } break; + case IRQ_SERIAL: { /* TODO: Set REG_SERIAL? */ } break; + case IRQ_DMA_0: { DMA_CTRL(0) &= ~DMA_IRQ; } break; + case IRQ_DMA_1: { DMA_CTRL(1) &= ~DMA_IRQ; } break; + case IRQ_DMA_2: { DMA_CTRL(2) &= ~DMA_IRQ; } break; + case IRQ_DMA_3: { DMA_CTRL(3) &= ~DMA_IRQ; } break; + case IRQ_KEYPAD: { KEY_CTRL &= ~KEY_IRQ; } break; + case IRQ_GAMEPAK: { /* Nothing to do here...*/ } break; + } + IRQ_ENABLE &= ~(1 << idx); +} + +void +irs_set(IrqIndex idx, IrsFunc func) { + // Store IRQ_CTRL status and disable interrupts for now. + u16 irq_ctrl = IRQ_CTRL; + IRQ_CTRL = 0; + + // Update the IRS table and enable/disable the given IRQ. + irs_table[idx] = func; + if (func == NULL) { + irq_disable(idx); + } else { + irq_enable(idx); + } + + // Restore previous irq_ctrl. + IRQ_CTRL = irq_ctrl; +} + +void +irq_init(void) { + IRS_MAIN = irs_main; + IRQ_CTRL = 1; +} + +void +irs_stub(void) {} diff --git a/src/interrupts.s b/src/interrupts.s new file mode 100644 index 0000000..67b9fe9 --- /dev/null +++ b/src/interrupts.s @@ -0,0 +1,89 @@ + .file "interrupts.s" + .extern irs_table; + .section .iwram, "ax", %progbits + .arm + .align + .global irs_main + +irs_main: + @ Get the contents of IRQ_ENABLE, IRQ_ACK, and IRQ_CTRL + ldr ip, mem_irq_base_reg @ ip = (IRQ_ENABLE << 16) | IRQ_ACK + ldr r0, [ip] @ r0 = irq_enable + and r1, r0, r0, lsr #16 @ r1 = irq_enable & irq_ack + + @ Disable IRQ_CTRL for now. + mov r3, #0 @ r3 = 0 + strh r3, [ip, #8] @ *(ip + 0x8) = r3 + + @ r0 = irs_table address pointer + @ r2 = tmp + @ r3 = 0 (for r3; r3 < 14; r3++) + ldr r0, = irs_table +irs_main_fp_search: + @ Check that the current index is an active IRQ. + mov r2, #1 + and r2, r1, r2, lsl r3 + cmp r2, #0 + beq irs_main_fp_search_not_enabled + + @ Extract the function pointer for this IRS if available. + ldr r2, [r0] + cmp r2, #0 + bne irs_main_handle_irs + +irs_main_fp_search_not_enabled: + add r0, r0, #4 + add r3, #1 + cmp r3, #14 + bne irs_main_fp_search + b irs_main_exit + +irs_main_handle_irs: + @ r2: Contains IRQ function pointer. + @ r3: Stores the IRQ index. + + @ Acknowledge that we are handling this interrupt writing to IRQ_ACK and + @ IRQ_ACK_BIOS. + mov r0, #1 + lsl r0, r0, r3 + strh r0, [ip, #2] + ldr r1, mem_irq_ack_bios @ r1 = IRQ_ACK_BIOS + str r0, [r1] + + @ Store the SPSR in one of the free registers and save it along with the + @ return pointer. + mrs r3, spsr + stmfd sp!, {r3, lr} + + @ Set CPU to system mode + mrs r3, cpsr + bic r3, r3, #0xDF + orr r3, r3, #0x1F + msr cpsr, r3 + + @ Call isr function pointer + stmfd sp!, {lr} + mov lr, pc + bx r2 + ldmfd sp!, {lr} + + @ Set CPU to irq mode + mrs r3, cpsr + bic r3, r3, #0xDF + orr r3, r3, #0x92 + msr cpsr, r3 + + @ Restore the SPSR and the registers. + ldmfd sp!, {r3, lr} + msr spsr, r3 + +irs_main_exit: + @ Restore IRQ_CTRL. + mov r3, #1 @ r3 = 0 + strh r3, [ip, #8] @ *(ip + 0x8) = r3 + bx lr + +mem_irq_base_reg: + .word 0x04000200 @ IRQ_ENABLE +mem_irq_ack_bios: + .word 0x03007FF8 @ IRQ_ACK_BIOS diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..e228034 --- /dev/null +++ b/src/main.c @@ -0,0 +1,81 @@ +/* +Copyright (c) 2021 Bad Diode + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE. +*/ + +#include "common.h" +// #include "text.h" + +#include "interrupts.c" +#include "filesystem.c" +#include "renderer.c" + +// +// Config parameters. +// + +#ifdef PROF_ENABLE +#if PROF_ENABLE == 0 +#define PROF(F,VAR) (profile_start(),(F),(VAR) = profile_stop()) +#elif PROF_ENABLE == 1 +#define PROF(F,VAR) (profile_start(),(F),(VAR) = MAX(profile_stop(), (VAR))) +#endif +#ifndef PROF_SHOW_X +#define PROF_SHOW_X 0 +#endif +#ifndef PROF_SHOW_Y +#define PROF_SHOW_Y 0 +#endif +#define PROF_SHOW() \ + do { \ + txt_position((PROF_SHOW_X), (PROF_SHOW_Y));\ + txt_printf("EVAL: %lu ", eval_cycles);\ + txt_position((PROF_SHOW_X), (PROF_SHOW_Y));\ + txt_printf("FLIP: %lu ", flip_cycles);\ + } while (0) +#define PROF_INIT() \ + u32 eval_cycles = 0;\ + u32 flip_cycles = 0; +#else +#define PROF(F,VAR) (F) +#define PROF_SHOW() +#define PROF_INIT() +#endif + +int main(void) { + // Adjust system wait times. + SYSTEM_WAIT = SYSTEM_WAIT_CARTRIDGE; + + // Initialize filesystem. + fs_init(); + + // Initialize renderer. + renderer_init(); + + // Register interrupts. + irq_init(); + irs_set(IRQ_VBLANK, irs_stub); + + draw_pixel(0, 0, 1); + draw_pixel(0, 1, 2); + draw_pixel(0, 2, 3); + draw_pixel(0, 3, 4); + + // Main loop. + PROF_INIT(); + size_t frame_counter = 0; + while(true) { + bios_vblank_wait(); + PROF_SHOW(); + PROF(flip_buffer(), flip_cycles); + frame_counter++; + } + + return 0; +} diff --git a/src/renderer.c b/src/renderer.c new file mode 100644 index 0000000..9fe55b2 --- /dev/null +++ b/src/renderer.c @@ -0,0 +1,86 @@ +// TODO: For now we pack front/backbuffers together but this make it so that we +// can only use 2 backgrounds. Instead we can move the backbuffer to the end of +// the VRAM. This will give us 3 backgrounds but eats into the available memory +// for sprites but should be fine for non sprite intensive applications. +#define FRONTBUFFER ((u32*)(MEM_VRAM)) +#define BACKBUFFER ((u32*)(MEM_VRAM + KB(96) - KB(20))) + +// Adjust both of these if the location of the map changes. Each screnblock +// requires 2K. +#define FRONTBUFFER_TILEMAP ((u16*)(MEM_VRAM + KB(20))) +#define FRONTBUFFER_SCREENBLOCK 10 + +static u32 dirty_tiles[21] = {0}; + +// TODO: Allow disable bound checking at compile time. +#define BOUNDCHECK_SCREEN() if (x >= SCREEN_WIDTH || y >= SCREEN_HEIGHT) return; + +IWRAM_CODE +void +draw_pixel(u16 x, u16 y, u8 color) { + BOUNDCHECK_SCREEN(); + + // Find row position for the given x/y coordinates. + size_t tile_x = x / 8; + size_t tile_y = y / 8; + size_t start_col = x % 8; + size_t start_row = y % 8; + size_t pos = start_row + (tile_x + tile_y * 32) * 8; + + // Update backbuffer. + size_t shift = start_col * sizeof(u32); + BACKBUFFER[pos] = (BACKBUFFER[pos] & ~(0xF << shift)) | color << shift; + + // Mark tile as dirty. + dirty_tiles[tile_y] |= 1 << tile_x; +} + +IWRAM_CODE +void +flip_buffer(void) { + // Copy dirty tiles from the backbuffer to the frontbuffer. + Tile *dst = FRONTBUFFER; + Tile *src = BACKBUFFER; + for (size_t j = 0; j < 20; ++j) { + if (dirty_tiles[j] == 0) { + continue; + } + for (size_t i = 0, k = 1; i < 30; ++i, k <<= 1) { + if (dirty_tiles[j] & k) { + dst[i + j * 32] = src[i + j * 32]; + } + } + dirty_tiles[j] = 0; + } +} + +void +renderer_init(void) { + // Initialize display mode and bg palette. + DISP_CTRL = DISP_MODE_0 | DISP_BG_0 | DISP_OBJ; + + // Initialize backgrounds. + BG_CTRL(0) = BG_CHARBLOCK(0) | BG_SCREENBLOCK(FRONTBUFFER_SCREENBLOCK); + + // TODO: Initialize other backgrounds if needed. + + // Use DMA to clear front and back buffers. + dma_fill(FRONTBUFFER, 0, KB(20), 3); + dma_fill(BACKBUFFER, 0, KB(20), 3); + + // Initialize default palette. + PAL_BUFFER_BG[0] = COLOR_BLACK; + PAL_BUFFER_BG[1] = COLOR_WHITE; + PAL_BUFFER_BG[2] = COLOR_RED; + PAL_BUFFER_BG[3] = COLOR_BLUE; + PAL_BUFFER_BG[4] = COLOR_CYAN; + PAL_BUFFER_BG[5] = COLOR_GREY; + + // Initialize background memory map. + for (size_t i = 0; i < 32 * 20; ++i) { + FRONTBUFFER_TILEMAP[i] = i; + } + +// // Load font data into VRAM. +// unpack_tiles(&bd_font, FONT_DATA, 256); +} diff --git a/src/shorthand.h b/src/shorthand.h new file mode 100644 index 0000000..08cc7fe --- /dev/null +++ b/src/shorthand.h @@ -0,0 +1,47 @@ +/* +Copyright (c) 2021 Bad Diode + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE. +*/ + +#ifndef UTILS_SHORTHAND_H +#define UTILS_SHORTHAND_H + +#include +#include +#include +#include + +// +// This simple header just typedefs the basic C define types to a shorter name, +// loads the quality of life bool macro for _Bool and defines shorthand macros +// for byte sizes. + +typedef uint8_t u8; +typedef uint16_t u16; +typedef uint32_t u32; +typedef uint64_t u64; +typedef int8_t s8; +typedef int16_t s16; +typedef int32_t s32; +typedef int64_t s64; +typedef volatile u8 vu8; +typedef volatile u16 vu16; +typedef volatile u32 vu32; +typedef volatile u64 vu64; +typedef volatile s8 vs8; +typedef volatile s16 vs16; +typedef volatile s32 vs32; +typedef volatile s64 vs64; + +#define KB(N) ((u64)(N) * 1024) +#define MB(N) ((u64)KB(N) * 1024) +#define GB(N) ((u64)MB(N) * 1024) +#define TB(N) ((u64)GB(N) * 1024) + +#endif // UTILS_SHORTHAND_H -- cgit v1.2.1