From 3e0bb6c9d1f788b51d26ed55acd32291df721129 Mon Sep 17 00:00:00 2001 From: Bad Diode Date: Wed, 28 Apr 2021 19:30:20 +0200 Subject: Add support for IRQ handling --- Makefile | 6 +-- src/common.h | 133 +++++++++++++++++++++++++++++++++++++++++++++++++++++- src/gba-buttons.c | 7 ++- src/irs.s | 88 ++++++++++++++++++++++++++++++++++++ src/main.c | 38 ++++++++-------- 5 files changed, 248 insertions(+), 24 deletions(-) create mode 100644 src/irs.s diff --git a/Makefile b/Makefile index 7202bb9..741cbfd 100644 --- a/Makefile +++ b/Makefile @@ -30,13 +30,13 @@ CC := arm-none-eabi-gcc OBJCOPY := arm-none-eabi-objcopy ARCH := -mthumb -mthumb-interwork SPECS := -specs=gba.specs -CFLAGS := -g -Wall -Wextra -pedantic -fno-strict-aliasing -Wno-incompatible-pointer-types +CFLAGS := -Wall -Wextra -pedantic -fno-strict-aliasing -Wno-incompatible-pointer-types CFLAGS += -mcpu=arm7tdmi -mtune=arm7tdmi $(ARCH) CFLAGS += -I$(LIBGBA_SRC) LDFLAGS := $(ARCH) $(SPECS) LDLIBS := $(LIBGBA) -RELEASE_CFLAGS := -DNDEBUG -O2 -DEBUG_CFLAGS := -DDEBUG -O2 +RELEASE_CFLAGS := -DNDEBUG -O2 -g +DEBUG_CFLAGS := -DDEBUG -O2 -g .PHONY: clean run diff --git a/src/common.h b/src/common.h index e3405be..dac48ad 100644 --- a/src/common.h +++ b/src/common.h @@ -46,6 +46,15 @@ #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))) @@ -245,8 +254,14 @@ u32 profile_stop(void) { // Input handling. // -// Memory address for key input register +// 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) @@ -385,4 +400,120 @@ dma_fill(void *dst, const void *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); + +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, +}; + +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; +} + +#define IRS_MAIN *(IrsFunc*)(0x03007FFC) + +// External irs_main function, has to be written in ARM assembly. +void irs_main(void); + +void +irq_init() { + IRS_MAIN = irs_main; + IRQ_CTRL = 1; +} + #endif // GBAEXP_COMMON_H diff --git a/src/gba-buttons.c b/src/gba-buttons.c index dd06351..d49383e 100644 --- a/src/gba-buttons.c +++ b/src/gba-buttons.c @@ -136,7 +136,12 @@ u32 gba_btn_fx_startselect[] = { 0x01800000, 0x00000000, 0x00000000, 0x00000004, }; -typedef enum {BTN_STATE_IDLE, BTN_STATE_PRESSED, BTN_STATE_RELEASED, BTN_STATE_HOLD} BtnState; +typedef enum { + BTN_STATE_IDLE = 0, + BTN_STATE_PRESSED, + BTN_STATE_RELEASED, + BTN_STATE_HOLD, +} BtnState; typedef struct AnimationEntry { int x_offset; diff --git a/src/irs.s b/src/irs.s new file mode 100644 index 0000000..924441e --- /dev/null +++ b/src/irs.s @@ -0,0 +1,88 @@ + .file "irs.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] + @ TODO: IRQ_ACK_BIOS + + @ 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 + .word 0x04000202 @ IRQ_ACK + .word 0x04000208 @ IRQ_CTRL diff --git a/src/main.c b/src/main.c index decd40d..6073a6a 100644 --- a/src/main.c +++ b/src/main.c @@ -17,11 +17,17 @@ // BIOS calls // +int bios_vblank_wait(); int bios_div(int num, int denom); -int normal_div(int num, int denom) { - return num / denom; -}; +int hblank_counter = 0; + +void +irs_hblank_func() { + u16 clr = (DISP_VCOUNT / 8); + PAL_BUFFER_BG[0] = rgb15(clr, 0, 31 - clr); + hblank_counter++; +} int main(void) { // Configure the display in mode 0 to show OBJs, where tile memory is @@ -36,28 +42,22 @@ int main(void) { // Initialize text engine. txt_init(0, COLOR_RED, 0); - size_t n_iter = 1000; - profile_start(); - int test = 0; - for (size_t i = 0; i < n_iter; ++i) { - txt_printf("Bios div: %d\n", bios_div(5 * i, 5)); - } - u32 bios_div_perf = profile_stop(); - - profile_start(); - for (size_t i = 0; i < n_iter; ++i) { - txt_printf("Code div: %d\n", normal_div(5 * i, 5)); - } - u32 code_perf = profile_stop(); - - txt_printf("C code div perf: %d\n", code_perf); - txt_printf("BIOS div perf: %d\n", bios_div_perf); + // Register interrupts. + irq_init(); + irs_set(IRQ_HBLANK, irs_hblank_func); int frame_counter = 0; while(true) { + // bios_vblank_wait(); wait_vsync(); poll_keys(); + txt_position(0, 1); + txt_clear_line(); + txt_printf(" HBlank counter: %d\n", hblank_counter); + txt_clear_line(); + txt_printf(" Frame counter: %d", frame_counter); + frame_counter++; // update_button_sprites(); }; -- cgit v1.2.1