diff options
author | Bad Diode <bd@badd10de.dev> | 2021-06-02 17:26:08 +0200 |
---|---|---|
committer | Bad Diode <bd@badd10de.dev> | 2021-06-02 17:26:08 +0200 |
commit | f6686f1e86927f038086023362251ebe78ce5ad6 (patch) | |
tree | d196fc1c32c55442a2ac75d4ce046b1c0e0d6d48 | |
download | stepper-f6686f1e86927f038086023362251ebe78ce5ad6.tar.gz stepper-f6686f1e86927f038086023362251ebe78ce5ad6.zip |
Init repo with basic BG framebuffer renderer
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | Makefile | 79 | ||||
-rw-r--r-- | src/bios_calls.s | 337 | ||||
-rw-r--r-- | src/common.h | 667 | ||||
-rw-r--r-- | src/filesystem.c | 409 | ||||
-rw-r--r-- | src/filesystem.h | 36 | ||||
-rw-r--r-- | src/interrupts.c | 91 | ||||
-rw-r--r-- | src/interrupts.s | 89 | ||||
-rw-r--r-- | src/main.c | 81 | ||||
-rw-r--r-- | src/renderer.c | 86 | ||||
-rw-r--r-- | src/shorthand.h | 47 |
11 files changed, 1923 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..378eac2 --- /dev/null +++ b/.gitignore | |||
@@ -0,0 +1 @@ | |||
build | |||
diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..1dd6375 --- /dev/null +++ b/Makefile | |||
@@ -0,0 +1,79 @@ | |||
1 | .POSIX: | ||
2 | .SUFFIXES: | ||
3 | |||
4 | # Path to the development kit (devkitARM) and the GBA library. | ||
5 | DEVKITPRO := /opt/devkitpro | ||
6 | DEVKITBIN := $(DEVKITPRO)/devkitARM/bin | ||
7 | DEVKITTOOLS := $(DEVKITPRO)/tools/bin | ||
8 | LIBGBA_DIR := $(DEVKITPRO)/libgba | ||
9 | LIBGBA_SRC := $(DEVKITPRO)/libgba/include/ | ||
10 | LIBGBA := $(LIBGBA_DIR)/lib/libgba.a | ||
11 | LIBGBA += $(LIBGBA_DIR)/lib/libfat.a | ||
12 | LIBGBA += $(LIBGBA_DIR)/lib/libmm.a | ||
13 | |||
14 | # Source code location and files to watch for changes. | ||
15 | SRC_DIR := src | ||
16 | BUILD_DIR := build | ||
17 | SRC_MAIN := $(SRC_DIR)/main.c | ||
18 | ASM_FILES := $(wildcard $(SRC_DIR)/*.s) | ||
19 | WATCH_SRC := $(shell find $(SRC_DIR) -name *.c -or -name *.s -or -name *.h) | ||
20 | INC_DIRS := $(shell find $(SRC_DIR) -type d) | ||
21 | INC_DIRS += $(BUILD_DIR) | ||
22 | INC_FLAGS := $(addprefix -I,$(INC_DIRS)) | ||
23 | INC_FLAGS += -I$(LIBGBA_SRC) | ||
24 | |||
25 | # Output library names and executables. | ||
26 | TARGET := sequencer | ||
27 | ELF := $(BUILD_DIR)/$(TARGET).elf | ||
28 | BIN := $(BUILD_DIR)/$(TARGET).gba | ||
29 | |||
30 | # Compiler and linker configuration. | ||
31 | CC := $(DEVKITBIN)/arm-none-eabi-gcc | ||
32 | OBJCOPY := $(DEVKITBIN)/arm-none-eabi-objcopy | ||
33 | ARCH := -mthumb -mthumb-interwork | ||
34 | SPECS := -specs=gba.specs | ||
35 | CONFIG := | ||
36 | CFLAGS := -Wall -Wextra -pedantic -Wno-incompatible-pointer-types | ||
37 | CFLAGS += -fno-strict-aliasing | ||
38 | CFLAGS += -mcpu=arm7tdmi -mtune=arm7tdmi $(ARCH) | ||
39 | CFLAGS += $(INC_FLAGS) | ||
40 | CFLAGS += $(CONFIG) | ||
41 | LDFLAGS := $(ARCH) $(SPECS) | ||
42 | LDLIBS := $(LIBGBA) | ||
43 | RELEASE_CFLAGS := -DNDEBUG -O3 | ||
44 | DEBUG_CFLAGS := -DDEBUG -O2 -g | ||
45 | |||
46 | .PHONY: clean run | ||
47 | |||
48 | # Setup debug/release builds. | ||
49 | # make clean && make <target> DEBUG=0 | ||
50 | # make clean && make <target> DEBUG=1 | ||
51 | DEBUG ?= 0 | ||
52 | ifeq ($(DEBUG), 1) | ||
53 | CFLAGS += $(DEBUG_CFLAGS) | ||
54 | else | ||
55 | CFLAGS += $(RELEASE_CFLAGS) | ||
56 | endif | ||
57 | |||
58 | main: $(BUILD_DIR) $(BIN) | ||
59 | |||
60 | # Strip and fix header to create final .gba file. | ||
61 | $(BIN): $(ELF) | ||
62 | $(OBJCOPY) -v -O binary $(ELF) $(BIN) | ||
63 | $(DEVKITTOOLS)/gbafix $(BIN) | ||
64 | |||
65 | # Link files. | ||
66 | $(ELF): $(SRC_MAIN) $(WATCH_SRC) | ||
67 | $(CC) $(CFLAGS) $(LDFLAGS) -o $(ELF) $(SRC_MAIN) $(ASM_FILES) $(LDLIBS) | ||
68 | |||
69 | # Create build directory if needed. | ||
70 | $(BUILD_DIR): | ||
71 | mkdir -p $(BUILD_DIR) | ||
72 | |||
73 | # Test the output .gba in an emulator. | ||
74 | run: main | ||
75 | mgba-qt $(BIN) | ||
76 | |||
77 | # Remove build directory. | ||
78 | clean: | ||
79 | rm -rf $(BUILD_DIR) | ||
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 @@ | |||
1 | @ | ||
2 | @ Arithmetic functions. | ||
3 | @ | ||
4 | |||
5 | @ Division. | ||
6 | .text | ||
7 | .code 16 | ||
8 | .align 2 | ||
9 | .global bios_div | ||
10 | .thumb_func | ||
11 | bios_div: | ||
12 | swi 0x06 | ||
13 | bx lr | ||
14 | |||
15 | @ Square root. | ||
16 | .text | ||
17 | .code 16 | ||
18 | .align 2 | ||
19 | .global bios_sqrt | ||
20 | .thumb_func | ||
21 | bios_sqrt: | ||
22 | swi 0x08 | ||
23 | bx lr | ||
24 | |||
25 | @ Arc-tangent. | ||
26 | .text | ||
27 | .code 16 | ||
28 | .align 2 | ||
29 | .global bios_atan | ||
30 | .thumb_func | ||
31 | bios_atan: | ||
32 | swi 0x09 | ||
33 | bx lr | ||
34 | |||
35 | @ Arc-tangent2. | ||
36 | .text | ||
37 | .code 16 | ||
38 | .align 2 | ||
39 | .global bios_atan2 | ||
40 | .thumb_func | ||
41 | bios_atan2: | ||
42 | swi 0x0a | ||
43 | bx lr | ||
44 | |||
45 | @ | ||
46 | @ Rotation/Scaling. | ||
47 | @ | ||
48 | |||
49 | @ BG Affine Set. | ||
50 | .text | ||
51 | .code 16 | ||
52 | .align 2 | ||
53 | .global bios_bg_affine_set | ||
54 | .thumb_func | ||
55 | bios_bg_affine_set: | ||
56 | swi 0x0e | ||
57 | bx lr | ||
58 | |||
59 | @ OBJ Affine Set. | ||
60 | .text | ||
61 | .code 16 | ||
62 | .align 2 | ||
63 | .global bios_obj_affine_set | ||
64 | .thumb_func | ||
65 | bios_obj_affine_set: | ||
66 | swi 0x0f | ||
67 | bx lr | ||
68 | |||
69 | @ | ||
70 | @ (De)compression. | ||
71 | @ | ||
72 | |||
73 | @ Bit unpacking. | ||
74 | .text | ||
75 | .code 16 | ||
76 | .align 2 | ||
77 | .global bios_bit_unpack | ||
78 | .thumb_func | ||
79 | bios_bit_unpack: | ||
80 | swi 0x10 | ||
81 | bx lr | ||
82 | |||
83 | @ Huffman decompression. | ||
84 | .text | ||
85 | .code 16 | ||
86 | .align 2 | ||
87 | .global bios_huff_expand | ||
88 | .thumb_func | ||
89 | bios_huff_expand: | ||
90 | swi 0x13 | ||
91 | bx lr | ||
92 | |||
93 | @ LZ77 decompression (Fast, WRAM, 8 bit writes). | ||
94 | .text | ||
95 | .code 16 | ||
96 | .align 2 | ||
97 | .global bios_lz77_expand_wram | ||
98 | .thumb_func | ||
99 | bios_lz77_expand_wram: | ||
100 | swi 0x11 | ||
101 | bx lr | ||
102 | |||
103 | @ LZ77 decompression (Slow, VRAM, 16 bit writes). | ||
104 | .text | ||
105 | .code 16 | ||
106 | .align 2 | ||
107 | .global bios_lz77_expand_vram | ||
108 | .thumb_func | ||
109 | bios_lz77_expand_vram: | ||
110 | swi 0x12 | ||
111 | bx lr | ||
112 | |||
113 | @ Run length encoding decompression (Fast, WRAM, 8 bit writes). | ||
114 | .text | ||
115 | .code 16 | ||
116 | .align 2 | ||
117 | .global bios_rle_expand_wram | ||
118 | .thumb_func | ||
119 | bios_rle_expand_wram: | ||
120 | swi 0x14 | ||
121 | bx lr | ||
122 | |||
123 | @ Run length encoding decompression (Slow, VRAM, 16 bit writes). | ||
124 | .text | ||
125 | .code 16 | ||
126 | .align 2 | ||
127 | .global bios_rle_expand_vram | ||
128 | .thumb_func | ||
129 | bios_rle_expand_vram: | ||
130 | swi 0x15 | ||
131 | bx lr | ||
132 | |||
133 | @ | ||
134 | @ Memory copy. | ||
135 | @ | ||
136 | |||
137 | @ Memcopy/memfill in 32 byte units (Fast). | ||
138 | .text | ||
139 | .code 16 | ||
140 | .align 2 | ||
141 | .global bios_memcopy_32 | ||
142 | .thumb_func | ||
143 | bios_memcopy_32: | ||
144 | swi 0x0C | ||
145 | bx lr | ||
146 | |||
147 | @ Memcopy/memfill in 4 / 2 byte units (Slow). | ||
148 | .text | ||
149 | .code 16 | ||
150 | .align 2 | ||
151 | .global bios_memcopy_8 | ||
152 | .thumb_func | ||
153 | bios_memcopy_8: | ||
154 | swi 0x0B | ||
155 | bx lr | ||
156 | |||
157 | @ | ||
158 | @ Sound functions. | ||
159 | @ | ||
160 | |||
161 | @ MIDI key to frequency. | ||
162 | .text | ||
163 | .code 16 | ||
164 | .align 2 | ||
165 | .global bios_midi2freq | ||
166 | .thumb_func | ||
167 | bios_midi2freq: | ||
168 | swi 0x1f | ||
169 | bx lr | ||
170 | |||
171 | @ Sound bias. | ||
172 | .text | ||
173 | .code 16 | ||
174 | .align 2 | ||
175 | .global bios_sound_bias | ||
176 | .thumb_func | ||
177 | bios_sound_bias: | ||
178 | swi 0x19 | ||
179 | bx lr | ||
180 | |||
181 | @ Sound channels clear. | ||
182 | .text | ||
183 | .code 16 | ||
184 | .align 2 | ||
185 | .global bios_sound_clear | ||
186 | .thumb_func | ||
187 | bios_sound_clear: | ||
188 | swi 0x1e | ||
189 | bx lr | ||
190 | |||
191 | @ Sound driver initialization. | ||
192 | .text | ||
193 | .code 16 | ||
194 | .align 2 | ||
195 | .global bios_sound_init | ||
196 | .thumb_func | ||
197 | bios_sound_init: | ||
198 | swi 0x1a | ||
199 | bx lr | ||
200 | |||
201 | @ Sound main function. To be called each 1/60 of a second after the sound VSync. | ||
202 | .text | ||
203 | .code 16 | ||
204 | .align 2 | ||
205 | .global bios_sound_main | ||
206 | .thumb_func | ||
207 | bios_sound_main: | ||
208 | swi 0x1c | ||
209 | bx lr | ||
210 | |||
211 | @ Sound driver operation mode. | ||
212 | .text | ||
213 | .code 16 | ||
214 | .align 2 | ||
215 | .global bios_sound_mode | ||
216 | .thumb_func | ||
217 | bios_sound_mode: | ||
218 | swi 0x1b | ||
219 | bx lr | ||
220 | |||
221 | @ Sound VSync. Called just after the VBlank interrupt each 1/60 of a second. | ||
222 | .text | ||
223 | .code 16 | ||
224 | .align 2 | ||
225 | .global bios_sound_vsync | ||
226 | .thumb_func | ||
227 | bios_sound_vsync: | ||
228 | swi 0x1d | ||
229 | bx lr | ||
230 | |||
231 | @ Sound VSync off In case of issues manually call this to stop the sound. | ||
232 | .text | ||
233 | .code 16 | ||
234 | .align 2 | ||
235 | .global bios_sound_vsync_off | ||
236 | .thumb_func | ||
237 | bios_sound_vsync_off: | ||
238 | swi 0x28 | ||
239 | bx lr | ||
240 | |||
241 | @ | ||
242 | @ Halt/Reset functions. | ||
243 | @ | ||
244 | |||
245 | @ Halt until interrupt request. | ||
246 | .text | ||
247 | .code 16 | ||
248 | .align 2 | ||
249 | .global bios_halt | ||
250 | .thumb_func | ||
251 | bios_halt: | ||
252 | swi 0x02 | ||
253 | bx lr | ||
254 | |||
255 | @ Halt until one of the specified interrupts occur. | ||
256 | .text | ||
257 | .code 16 | ||
258 | .align 2 | ||
259 | .global bios_interrupt_wait | ||
260 | .thumb_func | ||
261 | bios_interrupt_wait: | ||
262 | swi 0x04 | ||
263 | bx lr | ||
264 | |||
265 | @ Halt until the VBlank interrupt occurs. | ||
266 | .text | ||
267 | .code 16 | ||
268 | .align 2 | ||
269 | .global bios_vblank_wait | ||
270 | .thumb_func | ||
271 | bios_vblank_wait: | ||
272 | swi 0x05 | ||
273 | bx lr | ||
274 | |||
275 | @ Stop. Switches GBA to low power mode. | ||
276 | .text | ||
277 | .code 16 | ||
278 | .align 2 | ||
279 | .global bios_stop | ||
280 | .thumb_func | ||
281 | bios_stop: | ||
282 | swi 0x03 | ||
283 | bx lr | ||
284 | |||
285 | @ Soft reset. | ||
286 | .text | ||
287 | .code 16 | ||
288 | .align 2 | ||
289 | .global bios_soft_reset | ||
290 | .thumb_func | ||
291 | bios_soft_reset: | ||
292 | swi 0x00 | ||
293 | bx lr | ||
294 | |||
295 | @ Register RAM reset. | ||
296 | .text | ||
297 | .code 16 | ||
298 | .align 2 | ||
299 | .global bios_regram_reset | ||
300 | .thumb_func | ||
301 | bios_regram_reset: | ||
302 | swi 0x01 | ||
303 | bx lr | ||
304 | |||
305 | @ Hard reset. | ||
306 | .text | ||
307 | .code 16 | ||
308 | .align 2 | ||
309 | .global bios_hard_reset | ||
310 | .thumb_func | ||
311 | bios_hard_reset: | ||
312 | swi 0x26 | ||
313 | bx lr | ||
314 | |||
315 | @ | ||
316 | @ Misc functions. | ||
317 | @ | ||
318 | |||
319 | @ BIOS checksum. | ||
320 | .text | ||
321 | .code 16 | ||
322 | .align 2 | ||
323 | .global bios_bios_checksum | ||
324 | .thumb_func | ||
325 | bios_bios_checksum: | ||
326 | swi 0x0d | ||
327 | bx lr | ||
328 | |||
329 | @ MultiBoot. | ||
330 | .text | ||
331 | .code 16 | ||
332 | .align 2 | ||
333 | .global bios_multiboot | ||
334 | .thumb_func | ||
335 | bios_multiboot: | ||
336 | swi 0x25 | ||
337 | 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 @@ | |||
1 | #ifndef COMMON_H | ||
2 | #define COMMON_H | ||
3 | |||
4 | #include "shorthand.h" | ||
5 | |||
6 | #define CPU_FREQUENCY (2 << 23) | ||
7 | |||
8 | // | ||
9 | // Memory sections. | ||
10 | // | ||
11 | |||
12 | // Defines for the different memory sections in the GBA. | ||
13 | #define MEM_SROM 0x00000000 | ||
14 | #define MEM_EW 0x02000000 | ||
15 | #define MEM_IW 0x03000000 | ||
16 | #define MEM_IO 0x04000000 | ||
17 | #define MEM_PAL 0x05000000 | ||
18 | #define MEM_VRAM 0x06000000 | ||
19 | #define MEM_OAM 0x07000000 | ||
20 | #define MEM_PAK 0x08000000 | ||
21 | #define MEM_CART 0x0E000000 | ||
22 | |||
23 | // | ||
24 | // Display modes. | ||
25 | // | ||
26 | |||
27 | // Display registers. | ||
28 | #define DISP_CTRL *((vu32*)(MEM_IO + 0x0000)) | ||
29 | #define DISP_STATUS *((vu16*)(MEM_IO + 0x0004)) | ||
30 | #define DISP_VCOUNT *((vu16*)(MEM_IO + 0x0006)) | ||
31 | |||
32 | // The first three bits in the DISP_CTRL are used to control the video mode. | ||
33 | #define DISP_MODE_0 0x0000 | ||
34 | #define DISP_MODE_1 0x0001 | ||
35 | #define DISP_MODE_2 0x0002 | ||
36 | #define DISP_MODE_3 0x0003 | ||
37 | #define DISP_MODE_4 0x0004 | ||
38 | #define DISP_MODE_5 0x0005 | ||
39 | #define DISP_GB (1 << 3) | ||
40 | #define DISP_PAGE (1 << 4) | ||
41 | #define DISP_OAM_HBLANK (1 << 5) | ||
42 | #define DISP_OBJ_1D (1 << 6) | ||
43 | #define DISP_BLANK (1 << 7) | ||
44 | #define DISP_BG_0 (1 << 8) | ||
45 | #define DISP_BG_1 (1 << 9) | ||
46 | #define DISP_BG_2 (1 << 10) | ||
47 | #define DISP_BG_3 (1 << 11) | ||
48 | #define DISP_OBJ (1 << 12) | ||
49 | #define DISP_ENABLE_SPRITES DISP_OBJ | DISP_OBJ_1D | ||
50 | |||
51 | // These bits are used to control the DISP_STATUS register. | ||
52 | #define DISP_VBLANK_STATUS (1 << 0x0) | ||
53 | #define DISP_HBLANK_STATUS (1 << 0x1) | ||
54 | #define DISP_VCOUNT_STATUS (1 << 0x2) | ||
55 | #define DISP_VBLANK_IRQ (1 << 0x3) | ||
56 | #define DISP_HBLANK_IRQ (1 << 0x4) | ||
57 | #define DISP_VCOUNT_IRQ (1 << 0x5) | ||
58 | #define DISP_VCOUNT_TRIGGER(N) ((N) << 0x8) | ||
59 | |||
60 | // Registers to control of BG layers. | ||
61 | #define BG_CTRL(N) *((vu16*)(0x04000008 + 0x0002 * (N))) | ||
62 | |||
63 | // Bits to control the background. | ||
64 | #define BG_PRIORITY(N) ((N) & 0x3) | ||
65 | #define BG_CHARBLOCK(N) ((N) << 2) | ||
66 | #define BG_MOSAIC (1 << 6) | ||
67 | #define BG_HIGH_COLOR (1 << 7) | ||
68 | #define BG_SCREENBLOCK(N) ((N) << 8) | ||
69 | #define BG_AFFINE (1 << 0xD) | ||
70 | #define BG_SIZE(N) ((N) << 0xE) | ||
71 | |||
72 | // BG registers for horizontal displacement. | ||
73 | #define BG_H_SCROLL_0 *((vu16*)(0x04000010 + 0x0004 * 0)) | ||
74 | #define BG_H_SCROLL_1 *((vu16*)(0x04000010 + 0x0004 * 1)) | ||
75 | #define BG_H_SCROLL_2 *((vu16*)(0x04000010 + 0x0004 * 2)) | ||
76 | #define BG_H_SCROLL_3 *((vu16*)(0x04000010 + 0x0004 * 3)) | ||
77 | |||
78 | // BG registers for vertical displacement. | ||
79 | #define BG_V_SCROLL_0 *((vu16*)(0x04000012 + 0x0004 * 0)) | ||
80 | #define BG_V_SCROLL_1 *((vu16*)(0x04000012 + 0x0004 * 1)) | ||
81 | #define BG_V_SCROLL_2 *((vu16*)(0x04000012 + 0x0004 * 2)) | ||
82 | #define BG_V_SCROLL_3 *((vu16*)(0x04000012 + 0x0004 * 3)) | ||
83 | |||
84 | // Screen settings. | ||
85 | #define SCREEN_WIDTH 240 | ||
86 | #define SCREEN_HEIGHT 160 | ||
87 | |||
88 | // | ||
89 | // Colors. | ||
90 | // | ||
91 | |||
92 | // The GBA in mode 3 expects rbg15 colors in the VRAM, where each component | ||
93 | // (RGB) have a 0--31 range. For example, pure red would be rgb15(31, 0, 0). | ||
94 | typedef u16 Color; | ||
95 | |||
96 | // A palette is composed of 16 colors, with color at index 0 being transparent | ||
97 | // for sprites. | ||
98 | typedef Color Palette[16]; | ||
99 | |||
100 | // Inline function to calculate the 15 bit color value. | ||
101 | #define RGB15(R,G,B) (u16)(((B) << 10) | ((G) << 5) | (R)); | ||
102 | |||
103 | // Some nice default colors. | ||
104 | #define COLOR_RED RGB15(31, 0,12) | ||
105 | #define COLOR_BLUE RGB15(2, 15,30) | ||
106 | #define COLOR_CYAN RGB15(0, 30,30) | ||
107 | #define COLOR_GREY RGB15(12,12,12) | ||
108 | #define COLOR_BLACK RGB15(0, 0, 0) | ||
109 | #define COLOR_WHITE RGB15(28,28,28) | ||
110 | |||
111 | // | ||
112 | // Tile memory access. | ||
113 | // | ||
114 | |||
115 | // NOTE: Only defining 4bpp tiles for now. | ||
116 | // TODO: typedef u32 Tile[8]; | ||
117 | typedef struct Tile { | ||
118 | u32 row[8]; | ||
119 | } Tile; | ||
120 | |||
121 | // Screenblocks and charblocks (tile blocks). | ||
122 | typedef Tile TileBlock[512]; | ||
123 | #define TILE_MEM ((TileBlock*) MEM_VRAM) | ||
124 | typedef u16 ScreenBlock[1024]; | ||
125 | #define SCREENBLOCK_MEM ((ScreenBlock*)MEM_VRAM) | ||
126 | |||
127 | // Screenblock entry bits. | ||
128 | #define SCREENBLOCK_ENTRY_H_FLIP (1 << 0xA) | ||
129 | #define SCREENBLOCK_ENTRY_V_FLIP (1 << 0xB) | ||
130 | #define SCREENBLOCK_ENTRY_PAL(N) ((N) << 0xC) | ||
131 | |||
132 | inline size_t | ||
133 | se_index(size_t tile_x, size_t tile_y, size_t map_width) { | ||
134 | size_t sbb = ((tile_x >> 5) + (tile_y >> 5) * (map_width >> 5)); | ||
135 | return sbb * 1024 + ((tile_x & 31) + (tile_y & 31) * 32); | ||
136 | } | ||
137 | |||
138 | // We can treat the screen as a HxW matrix. With the following macro we can | ||
139 | // write a pixel to the screen at the (x, y) position using: | ||
140 | // | ||
141 | // FRAMEBUFFER[y][x] = color; | ||
142 | // | ||
143 | typedef Color Scanline[SCREEN_WIDTH]; | ||
144 | #define FRAMEBUFFER ((Scanline*) MEM_VRAM) | ||
145 | #define SCREEN_BUFFER ((u16*) MEM_VRAM) | ||
146 | #define PAL_BUFFER_BG ((u16*) MEM_PAL) | ||
147 | #define PAL_BUFFER_SPRITES ((u16*)(MEM_PAL + 0x200)) | ||
148 | #define PAL_BANK_BG ((Palette*) MEM_PAL) | ||
149 | #define PAL_BANK_SPRITES ((Palette*)(MEM_PAL + 0x200)) | ||
150 | |||
151 | // | ||
152 | // Sprites. | ||
153 | // | ||
154 | |||
155 | // Using macros instead of aligned structs for setting up OBJ attributes and | ||
156 | // affine parameters. | ||
157 | // TODO: Benchmark if this would be slower or the same that TONC's | ||
158 | // implementation. | ||
159 | // TODO: Cleanup OBJ/OAM memory copying and access. | ||
160 | #define OBJ_ATTR_0(N) *((vu16*)(MEM_OAM + 0 + 8 * (N))) | ||
161 | #define OBJ_ATTR_1(N) *((vu16*)(MEM_OAM + 2 + 8 * (N))) | ||
162 | #define OBJ_ATTR_2(N) *((vu16*)(MEM_OAM + 4 + 8 * (N))) | ||
163 | #define OBJ_AFFINE_PA(N) *((vs16*)(MEM_OAM + 6 + 8 * 0 + 8 * 4 * (N))) | ||
164 | #define OBJ_AFFINE_PB(N) *((vs16*)(MEM_OAM + 6 + 8 * 1 + 8 * 4 * (N))) | ||
165 | #define OBJ_AFFINE_PC(N) *((vs16*)(MEM_OAM + 6 + 8 * 2 + 8 * 4 * (N))) | ||
166 | #define OBJ_AFFINE_PD(N) *((vs16*)(MEM_OAM + 6 + 8 * 3 + 8 * 4 * (N))) | ||
167 | |||
168 | // OBJ_ATTR_0 parameters | ||
169 | #define OBJ_Y_COORD(N) ((N) & 0xFF) | ||
170 | #define OBJ_NORMAL (0x00 << 0x8) | ||
171 | #define OBJ_AFFINE (0x01 << 0x8) | ||
172 | #define OBJ_HIDDEN (0x02 << 0x8) | ||
173 | #define OBJ_AFFINE_2X (0x03 << 0x8) | ||
174 | #define OBJ_ALPHA_BLEND (0x01 << 0xA) | ||
175 | #define OBJ_WINDOW (0x02 << 0xA) | ||
176 | #define OBJ_SHAPE_SQUARE (0x00 << 0xE) | ||
177 | #define OBJ_SHAPE_WIDE (0x01 << 0xE) | ||
178 | #define OBJ_SHAPE_TALL (0x02 << 0xE) | ||
179 | |||
180 | // OBJ_ATTR_1 parameters | ||
181 | #define OBJ_X_COORD(N) ((N) & 0x1FF) | ||
182 | #define OBJ_AFFINE_IDX(N) ((N) << 0x9) | ||
183 | #define OBJ_H_FLIP (0x01 << 0xC) | ||
184 | #define OBJ_V_FLIP (0x01 << 0xD) | ||
185 | #define OBJ_SIZE_SMALL (0x00 << 0xE) | ||
186 | #define OBJ_SIZE_MID (0x01 << 0xE) | ||
187 | #define OBJ_SIZE_BIG (0x02 << 0xE) | ||
188 | #define OBJ_SIZE_HUGE (0x03 << 0xE) | ||
189 | |||
190 | // OBJ_ATTR_2 parameters | ||
191 | #define OBJ_TILE_INDEX(N) ((N) & 0x3FF) | ||
192 | #define OBJ_PRIORITY(N) ((N) << 0xA) | ||
193 | #define OBJ_PAL_BANK(N) ((N) << 0xC) | ||
194 | |||
195 | // | ||
196 | // Mode 4 page flipping | ||
197 | // | ||
198 | |||
199 | static inline | ||
200 | void | ||
201 | flip_page(vu16 *backbuffer) { | ||
202 | backbuffer = (u16*)((u32)backbuffer ^ 0x0A000); | ||
203 | DISP_CTRL ^= DISP_PAGE; | ||
204 | } | ||
205 | |||
206 | #define SCREEN_PAGE_1 ((vu16*) MEM_VRAM) | ||
207 | #define SCREEN_PAGE_2 ((vu16*) (MEM_VRAM + 0xa000)) | ||
208 | |||
209 | // | ||
210 | // Profiling. | ||
211 | // | ||
212 | |||
213 | #define TIMER_DATA_0 *((vu16*) (0x04000100 + 0x04 * 0)) | ||
214 | #define TIMER_DATA_1 *((vu16*) (0x04000100 + 0x04 * 1)) | ||
215 | #define TIMER_DATA_2 *((vu16*) (0x04000100 + 0x04 * 2)) | ||
216 | #define TIMER_DATA_3 *((vu16*) (0x04000100 + 0x04 * 3)) | ||
217 | #define TIMER_CTRL_0 *((vu16*) (0x04000102 + 0x04 * 0)) | ||
218 | #define TIMER_CTRL_1 *((vu16*) (0x04000102 + 0x04 * 1)) | ||
219 | #define TIMER_CTRL_2 *((vu16*) (0x04000102 + 0x04 * 2)) | ||
220 | #define TIMER_CTRL_3 *((vu16*) (0x04000102 + 0x04 * 3)) | ||
221 | |||
222 | // Timer control bits. | ||
223 | #define TIMER_CTRL_FREQ_0 0 | ||
224 | #define TIMER_CTRL_FREQ_1 1 | ||
225 | #define TIMER_CTRL_FREQ_2 2 | ||
226 | #define TIMER_CTRL_FREQ_3 3 | ||
227 | #define TIMER_CTRL_CASCADE (1 << 2) | ||
228 | #define TIMER_CTRL_IRQ (1 << 6) | ||
229 | #define TIMER_CTRL_ENABLE (1 << 7) | ||
230 | #define TIMER_CTRL_DISABLE (0 << 7) | ||
231 | |||
232 | // We use timers 2 and 3 to count the number of cycles since the profile_start | ||
233 | // functions is called. Don't use if the code we are trying to profile make use | ||
234 | // of these timers. | ||
235 | static inline | ||
236 | void | ||
237 | profile_start(void) { | ||
238 | TIMER_DATA_2 = 0; | ||
239 | TIMER_DATA_3 = 0; | ||
240 | TIMER_CTRL_2 = 0; | ||
241 | TIMER_CTRL_3 = 0; | ||
242 | TIMER_CTRL_3 = TIMER_CTRL_ENABLE | TIMER_CTRL_CASCADE; | ||
243 | TIMER_CTRL_2 = TIMER_CTRL_ENABLE; | ||
244 | } | ||
245 | |||
246 | static inline | ||
247 | u32 | ||
248 | profile_stop(void) { | ||
249 | TIMER_CTRL_2 = 0; | ||
250 | return (TIMER_DATA_3 << 16) | TIMER_DATA_2; | ||
251 | } | ||
252 | |||
253 | static inline | ||
254 | u32 | ||
255 | profile_measure(void) { | ||
256 | return (TIMER_DATA_3 << 16) | TIMER_DATA_2; | ||
257 | } | ||
258 | |||
259 | // | ||
260 | // Input handling. | ||
261 | // | ||
262 | |||
263 | // Memory address for key input and control register | ||
264 | #define KEY_INPUTS *((vu16*) 0x04000130) | ||
265 | #define KEY_CTRL *((vu16*) 0x04000132) | ||
266 | |||
267 | // Key control register bits. | ||
268 | #define KEY_IRQ_KEY(N) (N) | ||
269 | #define KEY_IRQ (1 << 0xE) | ||
270 | #define KEY_IRQ_IF_SET (1 << 0xF) | ||
271 | |||
272 | // Alias for key pressing bits. | ||
273 | #define KEY_A (1 << 0) | ||
274 | #define KEY_B (1 << 1) | ||
275 | #define KEY_SELECT (1 << 2) | ||
276 | #define KEY_START (1 << 3) | ||
277 | #define KEY_RIGHT (1 << 4) | ||
278 | #define KEY_LEFT (1 << 5) | ||
279 | #define KEY_UP (1 << 6) | ||
280 | #define KEY_DOWN (1 << 7) | ||
281 | #define KEY_R (1 << 8) | ||
282 | #define KEY_L (1 << 9) | ||
283 | |||
284 | #define KEY_MASK 0x03FF | ||
285 | |||
286 | // Saving the previous and current key states as globals for now. | ||
287 | static u16 key_curr = 0; | ||
288 | static u16 key_prev = 0; | ||
289 | |||
290 | static inline | ||
291 | void | ||
292 | poll_keys(void) { | ||
293 | key_prev = key_curr; | ||
294 | key_curr = ~KEY_INPUTS & KEY_MASK; | ||
295 | } | ||
296 | |||
297 | // Returns true if the given key has been pressed at time of calling and was not | ||
298 | // pressed since the previous call. For example, if a key is being held, this | ||
299 | // function will return `true` only on the frame where the key initially | ||
300 | // activated. | ||
301 | static inline | ||
302 | u32 | ||
303 | key_tap(u32 key) { | ||
304 | return (key_curr & key) & ~(key_prev & key); | ||
305 | } | ||
306 | |||
307 | // Check if a given key is currently pressed. | ||
308 | static inline | ||
309 | u32 | ||
310 | key_pressed(u32 key) { | ||
311 | return (key_curr & key); | ||
312 | } | ||
313 | |||
314 | // Check if a given key was just released. | ||
315 | static inline | ||
316 | u32 | ||
317 | key_released(u32 key) { | ||
318 | return ~(key_curr & key) & (key_prev & key); | ||
319 | } | ||
320 | |||
321 | // Check if the given key is pressed and has been since at least one frame. | ||
322 | static inline | ||
323 | u32 | ||
324 | key_hold(u32 key) { | ||
325 | return key_curr & key_prev & key; | ||
326 | } | ||
327 | |||
328 | // Check if the given key/button is currently pressed. | ||
329 | #define KEY_PRESSED(key) (~(KEY_INPUTS) & key) | ||
330 | |||
331 | // Back/unpack bits. | ||
332 | static inline | ||
333 | u32 | ||
334 | unpack_1bb(u8 hex) { | ||
335 | const u32 conversion_u32[16] = { | ||
336 | 0x00000000, 0x00000001, 0x00000010, 0x00000011, | ||
337 | 0x00000100, 0x00000101, 0x00000110, 0x00000111, | ||
338 | 0x00001000, 0x00001001, 0x00001010, 0x00001011, | ||
339 | 0x00001100, 0x00001101, 0x00001110, 0x00001111, | ||
340 | }; | ||
341 | u8 low = hex & 0xF; | ||
342 | u8 high = (hex >> 4) & 0xF; | ||
343 | return (conversion_u32[high] << 16) | conversion_u32[low]; | ||
344 | } | ||
345 | |||
346 | // Unpack N tiles packed at 1bpp. | ||
347 | static inline | ||
348 | void | ||
349 | unpack_tiles(u32 *src, u32 *dst, size_t n_tiles) { | ||
350 | u32 *target_src = src + n_tiles * 2; | ||
351 | while (src != target_src) { | ||
352 | *dst++ = unpack_1bb((*src >> 24) & 0xFF); | ||
353 | *dst++ = unpack_1bb((*src >> 16) & 0xFF); | ||
354 | *dst++ = unpack_1bb((*src >> 8) & 0xFF); | ||
355 | *dst++ = unpack_1bb(*src & 0xFF); | ||
356 | src++; | ||
357 | } | ||
358 | } | ||
359 | |||
360 | // | ||
361 | // Direct Memory Access (DMA) | ||
362 | // | ||
363 | |||
364 | |||
365 | // Source, destination, and control registers. | ||
366 | #define DMA_SRC(N) *((vu32*) 0x040000B0 + (N) * 12) | ||
367 | #define DMA_DST(N) *((vu32*) 0x040000B4 + (N) * 12) | ||
368 | #define DMA_CTRL(N) *((vu32*) 0x040000B8 + (N) * 12) | ||
369 | |||
370 | // DMA control bits. | ||
371 | #define DMA_DST_INC (0 << 0x15) | ||
372 | #define DMA_DST_DEC (1 << 0x15) | ||
373 | #define DMA_DST_FIXED (2 << 0x15) | ||
374 | #define DMA_DST_RELOAD (3 << 0x15) | ||
375 | #define DMA_SRC_INC (0 << 0x17) | ||
376 | #define DMA_SRC_DEC (1 << 0x17) | ||
377 | #define DMA_SRC_FIXED (2 << 0x17) | ||
378 | #define DMA_REPEAT (1 << 0x19) | ||
379 | #define DMA_CHUNK_16 (0 << 0x1A) | ||
380 | #define DMA_CHUNK_32 (1 << 0x1A) | ||
381 | #define DMA_NOW (0 << 0x1C) | ||
382 | #define DMA_VBLANK (1 << 0x1C) | ||
383 | #define DMA_HBLANK (2 << 0x1C) | ||
384 | #define DMA_REFRESH (3 << 0x1C) | ||
385 | #define DMA_IRQ (1 << 0x1E) | ||
386 | #define DMA_ENABLE (1 << 0x1F) | ||
387 | |||
388 | // Custom struct for cleaner DMA transfer functions. | ||
389 | typedef struct DmaStr { | ||
390 | const void *src; | ||
391 | void *dst; | ||
392 | u32 ctrl; | ||
393 | } DmaStr; | ||
394 | |||
395 | #define DMA_TRANSFER ((volatile DmaStr*) 0x040000B0) | ||
396 | |||
397 | // Transfer `count` number of chunks from src to dst using a DMA channel. Note | ||
398 | // that chunks are not bytes, but instead configured based on bits set by | ||
399 | // DMA_CTRL. | ||
400 | inline | ||
401 | void | ||
402 | dma_transfer_copy(void *dst, const void *src, u32 count, int channel, u32 options) { | ||
403 | DMA_TRANSFER[channel].ctrl = 0; | ||
404 | DMA_TRANSFER[channel].src = src; | ||
405 | DMA_TRANSFER[channel].dst = dst; | ||
406 | DMA_TRANSFER[channel].ctrl = count | options; | ||
407 | } | ||
408 | |||
409 | inline | ||
410 | void | ||
411 | dma_transfer_fill(void *dst, volatile u32 src, u32 count, int channel, u32 options) { | ||
412 | DMA_TRANSFER[channel].ctrl = 0; | ||
413 | DMA_TRANSFER[channel].src = (const void *)&src; | ||
414 | DMA_TRANSFER[channel].dst = dst; | ||
415 | DMA_TRANSFER[channel].ctrl = count | options | DMA_SRC_FIXED; | ||
416 | } | ||
417 | |||
418 | // Copy N number of bytes using a DMA channel. | ||
419 | inline | ||
420 | void | ||
421 | dma_copy(void *dst, const void *src, u32 size, int channel) { | ||
422 | dma_transfer_copy(dst, src, size / 4, channel, DMA_CHUNK_32 | DMA_ENABLE); | ||
423 | } | ||
424 | |||
425 | // Fill the dst location with the word set at src. | ||
426 | inline | ||
427 | void | ||
428 | dma_fill(void *dst, vu32 src, u32 size, int channel) { | ||
429 | dma_transfer_fill(dst, src, size / 4, channel, DMA_CHUNK_32 | DMA_ENABLE); | ||
430 | } | ||
431 | |||
432 | // | ||
433 | // Interrupts. | ||
434 | // | ||
435 | |||
436 | #define IRQ_ENABLE *((vu16*) 0x04000200) | ||
437 | #define IRQ_ACK *((vu16*) 0x04000202) | ||
438 | #define IRQ_CTRL *((vu16*) 0x04000208) | ||
439 | #define IRQ_ACK_BIOS *((vu16*) 0x03007FF8) | ||
440 | |||
441 | typedef enum { | ||
442 | IRQ_VBLANK, | ||
443 | IRQ_HBLANK, | ||
444 | IRQ_VCOUNT, | ||
445 | IRQ_TIMER_0, | ||
446 | IRQ_TIMER_1, | ||
447 | IRQ_TIMER_2, | ||
448 | IRQ_TIMER_3, | ||
449 | IRQ_SERIAL, | ||
450 | IRQ_DMA_0, | ||
451 | IRQ_DMA_1, | ||
452 | IRQ_DMA_2, | ||
453 | IRQ_DMA_3, | ||
454 | IRQ_KEYPAD, | ||
455 | IRQ_GAMEPAK, | ||
456 | } IrqIndex; | ||
457 | |||
458 | typedef void (*IrsFunc)(void); | ||
459 | |||
460 | // Stub function pointer needed for when we want to enable interrupts that don't | ||
461 | // require a custom function, such as for the BIOS VSync. | ||
462 | void irs_stub(void); | ||
463 | |||
464 | // Set and enable a given function in the interrupt table. If func is NULL, the | ||
465 | // interrupt will be disabled instead. | ||
466 | void irs_set(IrqIndex idx, IrsFunc func); | ||
467 | |||
468 | // Initialize the function pointer for the main IRS routine written in ARM | ||
469 | // assembly and enable interrupts. | ||
470 | void irq_init(void); | ||
471 | |||
472 | // | ||
473 | // BIOS function declarations. | ||
474 | // | ||
475 | |||
476 | // These functions declarations can be used to call the BIOS functions from the | ||
477 | // asm code. | ||
478 | int bios_vblank_wait(); | ||
479 | int bios_div(int num, int denom); | ||
480 | |||
481 | // | ||
482 | // Sound. | ||
483 | // | ||
484 | |||
485 | // Sound registers. | ||
486 | #define SOUND_SQUARE1_SWEEP *((vu16*)(MEM_IO + 0x60)) | ||
487 | #define SOUND_SQUARE1_CTRL *((vu16*)(MEM_IO + 0x62)) | ||
488 | #define SOUND_SQUARE1_FREQ *((vu16*)(MEM_IO + 0x64)) | ||
489 | #define SOUND_SQUARE2_CTRL *((vu16*)(MEM_IO + 0x68)) | ||
490 | #define SOUND_SQUARE2_FREQ *((vu16*)(MEM_IO + 0x6C)) | ||
491 | #define SOUND_WAVE_MODE *((vu16*)(MEM_IO + 0x70)) | ||
492 | #define SOUND_WAVE_CTRL *((vu16*)(MEM_IO + 0x72)) | ||
493 | #define SOUND_WAVE_FREQ *((vu16*)(MEM_IO + 0x74)) | ||
494 | #define SOUND_NOISE_CTRL *((vu16*)(MEM_IO + 0x78)) | ||
495 | #define SOUND_NOISE_FREQ *((vu16*)(MEM_IO + 0x7C)) | ||
496 | #define SOUND_DMG_MASTER *((vu16*)(MEM_IO + 0x80)) | ||
497 | #define SOUND_DSOUND_MASTER *((vu16*)(MEM_IO + 0x82)) | ||
498 | #define SOUND_STATUS *((vu16*)(MEM_IO + 0x84)) | ||
499 | #define SOUND_BIAS *((vu16*)(MEM_IO + 0x88)) | ||
500 | |||
501 | // Sound DMG master bits. | ||
502 | #define SOUND_VOLUME_LEFT(N) (N) | ||
503 | #define SOUND_VOLUME_RIGHT(N) ((N) << 4) | ||
504 | #define SOUND_ENABLE_SQUARE1_LEFT (1 << 0x8) | ||
505 | #define SOUND_ENABLE_SQUARE2_LEFT (1 << 0x9) | ||
506 | #define SOUND_ENABLE_WAVE_LEFT (1 << 0xA) | ||
507 | #define SOUND_ENABLE_NOISE_LEFT (1 << 0xB) | ||
508 | #define SOUND_ENABLE_SQUARE1_RIGHT (1 << 0xC) | ||
509 | #define SOUND_ENABLE_SQUARE2_RIGHT (1 << 0xD) | ||
510 | #define SOUND_ENABLE_WAVE_RIGHT (1 << 0xE) | ||
511 | #define SOUND_ENABLE_NOISE_RIGHT (1 << 0xF) | ||
512 | |||
513 | typedef enum { | ||
514 | SOUND_DSOUND = (0x0 << 0), | ||
515 | SOUND_SQUARE1 = (0x1 << 0), | ||
516 | SOUND_SQUARE2 = (0x1 << 1), | ||
517 | SOUND_WAVE = (0x1 << 2), | ||
518 | SOUND_NOISE = (0x1 << 3), | ||
519 | } SoundChannel; | ||
520 | |||
521 | inline u16 | ||
522 | sound_volume(SoundChannel channels, u8 volume) { | ||
523 | volume = volume & 0x7; | ||
524 | channels = channels & 0xF; | ||
525 | return volume | (volume << 0x4) | (channels << 0x8) | (channels << 0xC); | ||
526 | } | ||
527 | |||
528 | // Sound Direct Sound master bits. | ||
529 | #define SOUND_DMG25 0x0 | ||
530 | #define SOUND_DMG50 0x1 | ||
531 | #define SOUND_DMG100 0x2 | ||
532 | #define SOUND_DSOUND_RATIO_A (1 << 0x2) | ||
533 | #define SOUND_DSOUND_RATIO_B (1 << 0x3) | ||
534 | #define SOUND_DSOUND_LEFT_A (1 << 0x8) | ||
535 | #define SOUND_DSOUND_RIGHT_A (1 << 0x9) | ||
536 | #define SOUND_DSOUND_TIMER_A (1 << 0xA) | ||
537 | #define SOUND_DSOUND_RESET_A (1 << 0xB) | ||
538 | #define SOUND_DSOUND_LEFT_B (1 << 0xC) | ||
539 | #define SOUND_DSOUND_RIGHT_B (1 << 0xD) | ||
540 | #define SOUND_DSOUND_TIMER_B (1 << 0xE) | ||
541 | #define SOUND_DSOUND_RESET_B (1 << 0xF) | ||
542 | |||
543 | // Direct sound FIFO queues. | ||
544 | #define SOUND_FIFO_A ((u16*)(MEM_IO + 0xA0)) | ||
545 | #define SOUND_FIFO_B ((u16*)(MEM_IO + 0xA4)) | ||
546 | |||
547 | // Sound status bits. | ||
548 | #define SOUND_ENABLE (1 << 0x7) | ||
549 | |||
550 | // DMG square control bits. | ||
551 | #define SOUND_SQUARE_LEN(N) (N) | ||
552 | #define SOUND_SQUARE_DUTY(N) ((N) << 0x6) | ||
553 | #define SOUND_SQUARE_ENV_TIME(N) ((N) << 0x8) | ||
554 | #define SOUND_SQUARE_ENV_DIR(N) ((N) << 0xB) | ||
555 | #define SOUND_SQUARE_ENV_VOL(N) ((N) << 0xC) | ||
556 | |||
557 | // DMG square 1 sweep control bits. | ||
558 | #define SOUND_SWEEP_NUMBER(N) (N) | ||
559 | #define SOUND_SWEEP_DIR(N) ((N) << 0x3) | ||
560 | #define SOUND_SWEEP_TIME(N) ((N) << 0x4) | ||
561 | |||
562 | // DMG frequency bits (Square/Wave). | ||
563 | #define SOUND_FREQ_TIMED (1 << 0xE) | ||
564 | #define SOUND_FREQ_RESET (1 << 0xF) | ||
565 | |||
566 | // DMG wave ram. | ||
567 | #define SOUND_WAVE_RAM_0 *((vu32*)(MEM_IO + 0x90)) | ||
568 | #define SOUND_WAVE_RAM_1 *((vu32*)(MEM_IO + 0x94)) | ||
569 | #define SOUND_WAVE_RAM_2 *((vu32*)(MEM_IO + 0x98)) | ||
570 | #define SOUND_WAVE_RAM_3 *((vu32*)(MEM_IO + 0x9C)) | ||
571 | |||
572 | // DMG wave control bits. | ||
573 | #define SOUND_WAVE_LENGTH(N) (N) | ||
574 | #define SOUND_WAVE_MUTE 0x0 | ||
575 | #define SOUND_WAVE_VOL_100 (0x1 << 0xD) | ||
576 | #define SOUND_WAVE_VOL_75 (0x4 << 0xD) | ||
577 | #define SOUND_WAVE_VOL_50 (0x2 << 0xD) | ||
578 | #define SOUND_WAVE_VOL_25 (0x3 << 0xD) | ||
579 | |||
580 | // DMG wave mode bits. | ||
581 | #define SOUND_WAVE_BANK_MODE(N) ((N) << 0x5) | ||
582 | #define SOUND_WAVE_BANK_SELECT(N) ((N) << 0x6) | ||
583 | #define SOUND_WAVE_ENABLE (1 << 0x7) | ||
584 | |||
585 | typedef u8 WaveBank[32]; | ||
586 | |||
587 | #define SOUND_WAVE_RAM ((WaveBank*)(MEM_IO + 0x90)) | ||
588 | |||
589 | typedef enum { | ||
590 | NOTE_C_2 , NOTE_C_SHARP_2 , NOTE_D_2 , NOTE_D_SHARP_2 , | ||
591 | NOTE_E_2 , NOTE_F_2 , NOTE_F_SHARP_2 , NOTE_G_2 , | ||
592 | NOTE_G_SHARP_2 , NOTE_A_2 , NOTE_A_SHARP_2 , NOTE_B_2 , | ||
593 | NOTE_C_3 , NOTE_C_SHARP_3 , NOTE_D_3 , NOTE_D_SHARP_3 , | ||
594 | NOTE_E_3 , NOTE_F_3 , NOTE_F_SHARP_3 , NOTE_G_3 , | ||
595 | NOTE_G_SHARP_3 , NOTE_A_3 , NOTE_A_SHARP_3 , NOTE_B_3 , | ||
596 | NOTE_C_4 , NOTE_C_SHARP_4 , NOTE_D_4 , NOTE_D_SHARP_4 , | ||
597 | NOTE_E_4 , NOTE_F_4 , NOTE_F_SHARP_4 , NOTE_G_4 , | ||
598 | NOTE_G_SHARP_4 , NOTE_A_4 , NOTE_A_SHARP_4 , NOTE_B_4 , | ||
599 | NOTE_C_5 , NOTE_C_SHARP_5 , NOTE_D_5 , NOTE_D_SHARP_5 , | ||
600 | NOTE_E_5 , NOTE_F_5 , NOTE_F_SHARP_5 , NOTE_G_5 , | ||
601 | NOTE_G_SHARP_5 , NOTE_A_5 , NOTE_A_SHARP_5 , NOTE_B_5 , | ||
602 | NOTE_C_6 , NOTE_C_SHARP_6 , NOTE_D_6 , NOTE_D_SHARP_6 , | ||
603 | NOTE_E_6 , NOTE_F_6 , NOTE_F_SHARP_6 , NOTE_G_6 , | ||
604 | NOTE_G_SHARP_6 , NOTE_A_6 , NOTE_A_SHARP_6 , NOTE_B_6 , | ||
605 | NOTE_C_7 , NOTE_C_SHARP_7 , NOTE_D_7 , NOTE_D_SHARP_7 , | ||
606 | NOTE_E_7 , NOTE_F_7 , NOTE_F_SHARP_7 , NOTE_G_7 , | ||
607 | NOTE_G_SHARP_7 , NOTE_A_7 , NOTE_A_SHARP_7 , NOTE_B_7 , | ||
608 | NOTE_C_8 | ||
609 | } Note; | ||
610 | |||
611 | static const u32 sound_rates[] = { | ||
612 | 44 , 156 , 262 , 363 , 457 , 547 , 631 , 710 , 785 , 856 , 923 , 986 , | ||
613 | 1046, 1102, 1155, 1205, 1252, 1297, 1339, 1379, 1416, 1452, 1485, 1517, | ||
614 | 1547, 1575, 1601, 1626, 1650, 1672, 1693, 1713, 1732, 1750, 1766, 1782, | ||
615 | 1797, 1811, 1824, 1837, 1849, 1860, 1870, 1880, 1890, 1899, 1907, 1915, | ||
616 | 1922, 1929, 1936, 1942, 1948, 1954, 1959, 1964, 1969, 1973, 1977, 1981, | ||
617 | 1985, 1988, 1992, 1995, 1998, 2001, 2003, 2006, 2008, 2010, 2012, 2014, | ||
618 | 2016, | ||
619 | }; | ||
620 | |||
621 | // | ||
622 | // System control. | ||
623 | // | ||
624 | |||
625 | // Used to configure gamepak access timings. | ||
626 | #define SYSTEM_WAIT *((vu16*)(MEM_IO + 0x0204)) | ||
627 | |||
628 | // This register defaults to 0, but manufacture cartridges use the values | ||
629 | // provided below. | ||
630 | #define SYSTEM_WAIT_DEFAULT 0 | ||
631 | #define SYSTEM_WAIT_CARTRIDGE 0x4317 | ||
632 | |||
633 | // | ||
634 | // Misc. | ||
635 | // | ||
636 | |||
637 | // Custom VSync option. This will waste a lot of battery power, since the | ||
638 | // machine is not clocked down. Prefer using `bios_vblank_wait()` if interrupts | ||
639 | // are enabled. | ||
640 | static inline void | ||
641 | wait_vsync(void) { | ||
642 | while(DISP_VCOUNT >= 160); | ||
643 | while(DISP_VCOUNT < 160); | ||
644 | } | ||
645 | |||
646 | // General utility macros. | ||
647 | #define MIN(A, B) ((A) <= (B) ? (A) : (B)) | ||
648 | #define MAX(A, B) ((A) >= (B) ? (A) : (B)) | ||
649 | #define CLAMP(X, MIN, MAX) ((X) <= (MIN) ? (MIN) : (X) > (MAX) ? (MAX): (X)) | ||
650 | #define LEN(ARR) (sizeof(ARR) / sizeof((ARR)[0])) | ||
651 | |||
652 | // Fixed-point arithmetic for (i.P) numbers. | ||
653 | #define FP_MUL(A,B,P) (((A) * (B)) >> (P)) | ||
654 | #define FP_DIV(A,B,P) (((A) << (P)) / (B)) | ||
655 | #define FP_LERP(Y0,Y1,X,P) ((Y0) + FP_MUL((X), ((Y1) - (Y0)), P)) | ||
656 | |||
657 | // | ||
658 | // Memory section macros for devkitARM. | ||
659 | // | ||
660 | |||
661 | #define IWRAM_DATA __attribute__((section(".iwram"))) | ||
662 | #define IWRAM_CODE __attribute__((section(".iwram"), long_call, target("arm"))) | ||
663 | #define EWRAM_DATA __attribute__((section(".ewram"))) | ||
664 | #define EWRAM_CODE __attribute__((section(".ewram"), long_call)) | ||
665 | #define EWRAM_BSS __attribute__((section(".sbss"))) | ||
666 | |||
667 | #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 @@ | |||
1 | /* | ||
2 | Copyright (c) 2021 Bad Diode | ||
3 | |||
4 | Permission to use, copy, modify, and distribute this software for any | ||
5 | purpose with or without fee is hereby granted, provided that the above | ||
6 | copyright notice and this permission notice appear in all copies. | ||
7 | |||
8 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | ||
9 | WITH REGARD TO THIS SOFTWARE. | ||
10 | */ | ||
11 | |||
12 | #include <string.h> | ||
13 | |||
14 | #include "filesystem.h" | ||
15 | |||
16 | // This file implements a filesystem with a minimum block size of 256 bytes. The | ||
17 | // maximum number of files depends on the block size. The default 1KB block size | ||
18 | // will give us 32-64 files depending on the size of MEM_CART. In case we want | ||
19 | // to use a block size of 512 bytes, we will have up to 128 file available. | ||
20 | // Blocks of 256 bytes will give us the maximum of 255 files available, since | ||
21 | // a block index of 0xFF will be considered as a null block. | ||
22 | |||
23 | // A fileblock of 1KB give us a maximum of 64 files. | ||
24 | #define FILE_BLOCK_SIZE KB(1) | ||
25 | #define FILE_MAX_FILES 64 | ||
26 | #define FILE_N_BLOCKS 62 | ||
27 | |||
28 | // With this file name size sizeof(FileIndex) will be 32 bytes. 32 * 64 files | ||
29 | // give us 2KB spent on file index that we can't use for data (so maximum of 62 | ||
30 | // files without accounting for the block index). | ||
31 | #define FILE_NAME_SIZE 30 | ||
32 | #define FILE_INDEX_NUM 62 | ||
33 | |||
34 | // Since we are reserving the first 2K bytes for the filesystem, we have 60 | ||
35 | // blocks available for writing data. If you were to change the previous | ||
36 | // parameters, you *must* recalculate the initial block start location. | ||
37 | #define FILE_DATA_START KB(2) | ||
38 | |||
39 | // We must write to the SRAM using the 8bit bus. | ||
40 | #define SRAM ((vu8*)(MEM_CART)) | ||
41 | |||
42 | // Special filesystem constants. | ||
43 | enum { FS_INIT_PATTERN = 0xBA, FS_NULL = 0xFF }; | ||
44 | |||
45 | typedef struct FileBlock { | ||
46 | // Size used in the current block (in bytes). Should be smaller than: | ||
47 | // FILE_BLOCK_SIZE - sizeof(FileBlock) | ||
48 | u16 size; | ||
49 | // The index for the next block. Set to FS_NULL if there is none. | ||
50 | u8 next_block; | ||
51 | u8 prev_block; | ||
52 | } FileBlock; | ||
53 | |||
54 | typedef struct FileIndex { | ||
55 | // File name. | ||
56 | char name[FILE_NAME_SIZE + 1]; | ||
57 | // Index to the first block of this file. If set to FS_NULL this file | ||
58 | // has not yet been written to. | ||
59 | u8 first_block; | ||
60 | } FileIndex; | ||
61 | |||
62 | // The filesystem header. | ||
63 | typedef struct FileSystem { | ||
64 | // The first byte of the SRAM can become corrupted in some situations, like | ||
65 | // changing cartridges for example. | ||
66 | u8 blank; | ||
67 | // If the filesystem exists, this will be set to FS_INIT_PATTERN. | ||
68 | u8 initialized; | ||
69 | // Number of blocks in use. | ||
70 | u8 busy_blocks; | ||
71 | // Number of files currently existing in the filesystem. | ||
72 | u8 num_files; | ||
73 | // This stores a bitmap pattern to keep track of the blocks in use by the | ||
74 | // filesystem. The first byte maps the first 8 blocks and so on. | ||
75 | u8 used_blocks[FILE_MAX_FILES / 8]; | ||
76 | // The list of possible file indexes. | ||
77 | FileIndex files[FILE_INDEX_NUM]; | ||
78 | } FileSystem; | ||
79 | |||
80 | #define FILE_BLOCK_CAPACITY (FILE_BLOCK_SIZE - sizeof(FileBlock)) | ||
81 | |||
82 | EWRAM_BSS | ||
83 | static FileSystem filesystem; | ||
84 | |||
85 | void | ||
86 | _fs_read(u8 *dst, u16 pos, u16 n_bytes) { | ||
87 | for (size_t i = 0; i < n_bytes; ++i) { | ||
88 | dst[i] = SRAM[pos + i]; | ||
89 | } | ||
90 | } | ||
91 | |||
92 | void | ||
93 | _fs_write(u8 *src, u16 pos, u16 n_bytes) { | ||
94 | for (size_t i = 0; i < n_bytes; ++i) { | ||
95 | SRAM[pos + i] = src[i]; | ||
96 | } | ||
97 | } | ||
98 | |||
99 | void | ||
100 | fs_init(void) { | ||
101 | // Load filesystem if existing. | ||
102 | _fs_read(&filesystem, 0, sizeof(FileSystem)); | ||
103 | if (filesystem.initialized != FS_INIT_PATTERN) { | ||
104 | // Clear SRAM. | ||
105 | for (size_t i = 0; i < KB(64) / 8; ++i) { | ||
106 | SRAM[i] = 0x00; | ||
107 | } | ||
108 | |||
109 | // Initialize block headers. | ||
110 | FileBlock block = { | ||
111 | .size = 0, | ||
112 | .next_block = FS_NULL, | ||
113 | .prev_block = FS_NULL, | ||
114 | }; | ||
115 | for (size_t i = 0; i < FILE_INDEX_NUM; ++i) { | ||
116 | u16 block_pos = FILE_DATA_START + FILE_BLOCK_SIZE * i; | ||
117 | _fs_write(&block, block_pos, sizeof(FileBlock)); | ||
118 | } | ||
119 | |||
120 | // Initialize filesystem. | ||
121 | dma_fill(&filesystem, 0, sizeof(FileSystem), 3); | ||
122 | filesystem.initialized = FS_INIT_PATTERN; | ||
123 | for (size_t i = 0; i < FILE_INDEX_NUM; ++i) { | ||
124 | filesystem.files[i].first_block = FS_NULL; | ||
125 | } | ||
126 | |||
127 | // Write the FS to disk. | ||
128 | _fs_write(&filesystem, 0, sizeof(FileSystem)); | ||
129 | } | ||
130 | } | ||
131 | |||
132 | void | ||
133 | _fs_update_filesystem_header(void) { | ||
134 | _fs_write(&filesystem, 0, offsetof(FileSystem, files)); | ||
135 | } | ||
136 | |||
137 | void | ||
138 | _fs_update_file_index(u16 index) { | ||
139 | _fs_write(&filesystem.files[index], | ||
140 | offsetof(FileSystem, files) + index * sizeof(FileIndex), | ||
141 | sizeof(FileIndex)); | ||
142 | } | ||
143 | |||
144 | File | ||
145 | fs_open_file(char *name, OpenMode mode) { | ||
146 | // Try to find an existing file. | ||
147 | for (size_t i = 0; i < filesystem.num_files; ++i) { | ||
148 | // TODO: Replace strcmp with vectorized fixed size char comparison. | ||
149 | if (strcmp(name, filesystem.files[i].name) == 0) { | ||
150 | return (File){i, 0, mode}; | ||
151 | } | ||
152 | } | ||
153 | |||
154 | // If read only. | ||
155 | if ((mode & (FS_OPEN_WRITE | FS_OPEN_APPEND)) == 0) { | ||
156 | return (File){FS_NULL, 0, mode}; | ||
157 | } | ||
158 | |||
159 | // Create a new file if there is space. | ||
160 | if (filesystem.num_files < FILE_INDEX_NUM) { | ||
161 | u16 index = filesystem.num_files++; | ||
162 | u16 k = 0; | ||
163 | while(*name) { | ||
164 | filesystem.files[index].name[k++] = *name++; | ||
165 | } | ||
166 | |||
167 | // Update file index and filesystem on SRAM. | ||
168 | _fs_update_file_index(index); | ||
169 | _fs_update_filesystem_header(); | ||
170 | |||
171 | return (File){index, 0, mode}; | ||
172 | } | ||
173 | return (File){FS_NULL, 0, mode}; | ||
174 | } | ||
175 | |||
176 | u16 | ||
177 | fs_file_size(File *file) { | ||
178 | u16 size = 0; | ||
179 | FileBlock block; | ||
180 | u16 blk_id = filesystem.files[file->index].first_block; | ||
181 | while (blk_id != FS_NULL) { | ||
182 | u16 block_pos = FILE_DATA_START + FILE_BLOCK_SIZE * blk_id; | ||
183 | _fs_read(&block, block_pos, sizeof(FileBlock)); | ||
184 | size += block.size; | ||
185 | blk_id = block.next_block; | ||
186 | } | ||
187 | return size; | ||
188 | } | ||
189 | |||
190 | u8 | ||
191 | _fs_init_new_block(void) { | ||
192 | // Find free block. | ||
193 | u8 block_index = 0; | ||
194 | for (size_t j = 0; j < LEN(filesystem.used_blocks); ++j) { | ||
195 | for (size_t i = 0; i < 8; ++i, block_index++) { | ||
196 | u8 blk = (filesystem.used_blocks[j] >> i) & 0x1; | ||
197 | if (blk == 0) { | ||
198 | // Initialize the block. | ||
199 | filesystem.busy_blocks++; | ||
200 | filesystem.used_blocks[j] |= (1 << i); | ||
201 | _fs_update_filesystem_header(); | ||
202 | return block_index; | ||
203 | } | ||
204 | } | ||
205 | } | ||
206 | return FS_NULL; | ||
207 | } | ||
208 | |||
209 | // Recursively free blocks starting at blk_id. To improve performance, the | ||
210 | // filesystem header is updated in memory but not written to disk. It is | ||
211 | // responsability of the caller to perform the filesystem update. | ||
212 | void | ||
213 | _fs_free_blocks(u8 blk_id) { | ||
214 | if (blk_id == FS_NULL) { | ||
215 | return; | ||
216 | } | ||
217 | |||
218 | // Read block. | ||
219 | FileBlock block; | ||
220 | u16 block_pos = FILE_DATA_START + FILE_BLOCK_SIZE * blk_id; | ||
221 | _fs_read(&block, block_pos, sizeof(FileBlock)); | ||
222 | |||
223 | // Update block. | ||
224 | u8 next_block = block.next_block; | ||
225 | block = (FileBlock){ | ||
226 | .size = 0, | ||
227 | .next_block = FS_NULL, | ||
228 | .prev_block = FS_NULL, | ||
229 | }; | ||
230 | _fs_write(&block, block_pos, sizeof(FileBlock)); | ||
231 | |||
232 | // Update dirty and busy blocks. | ||
233 | filesystem.busy_blocks--; | ||
234 | filesystem.used_blocks[blk_id / 8] &= ~(1 << (blk_id % 8)); | ||
235 | |||
236 | _fs_free_blocks(next_block); | ||
237 | } | ||
238 | |||
239 | void | ||
240 | _fs_write_to_block(u8 *src, u16 n_bytes, u16 blk_offset, | ||
241 | u8 blk_id, u8 prev_blk, bool append) { | ||
242 | // Read initial block. | ||
243 | FileBlock block; | ||
244 | u16 block_pos = FILE_DATA_START + FILE_BLOCK_SIZE * blk_id; | ||
245 | _fs_read(&block, block_pos, sizeof(FileBlock)); | ||
246 | u16 block_capacity = FILE_BLOCK_CAPACITY - blk_offset; | ||
247 | |||
248 | // Write capacity. | ||
249 | u16 block_bytes = MIN(block_capacity, n_bytes); | ||
250 | _fs_write(src, block_pos + sizeof(FileBlock) + blk_offset, block_bytes); | ||
251 | |||
252 | if (n_bytes > block_capacity) { | ||
253 | if (block.next_block == FS_NULL) { | ||
254 | // Find new available block and initialize it. | ||
255 | block.next_block = _fs_init_new_block(); | ||
256 | } | ||
257 | _fs_write_to_block(src + block_capacity, n_bytes - block_capacity, 0, | ||
258 | block.next_block, blk_id, append); | ||
259 | } else if (block.next_block != FS_NULL){ | ||
260 | // Recursively free unused blocks. | ||
261 | _fs_free_blocks(block.next_block); | ||
262 | _fs_update_filesystem_header(); | ||
263 | block.next_block = FS_NULL; | ||
264 | } | ||
265 | |||
266 | // Update block header. | ||
267 | if (prev_blk != FS_NULL) { | ||
268 | block.prev_block = prev_blk; | ||
269 | } | ||
270 | block.size = block_bytes; | ||
271 | _fs_write(&block, block_pos, sizeof(FileBlock)); | ||
272 | } | ||
273 | |||
274 | int | ||
275 | fs_seek(File *file, int offset, SeekMode mode) { | ||
276 | u16 file_size = fs_file_size(file); | ||
277 | u16 new_offset = 0; | ||
278 | switch (mode) { | ||
279 | case FS_SEEK_SET: { | ||
280 | new_offset = offset; | ||
281 | } break; | ||
282 | case FS_SEEK_CUR: { | ||
283 | new_offset = MAX((int)file->offset + offset, 0); | ||
284 | } break; | ||
285 | case FS_SEEK_END: { | ||
286 | new_offset = MAX((int)file_size - 1 + offset, 0); | ||
287 | } break; | ||
288 | } | ||
289 | if (new_offset != 0 && new_offset >= file_size) { | ||
290 | return -1; | ||
291 | } | ||
292 | file->offset = new_offset; | ||
293 | return 0; | ||
294 | } | ||
295 | |||
296 | u16 | ||
297 | fs_write(u8 *src, u16 n_bytes, File *file) { | ||
298 | if ((file->mode & (FS_OPEN_WRITE | FS_OPEN_APPEND)) == 0) { | ||
299 | return 0; | ||
300 | } | ||
301 | |||
302 | FileIndex *file_idx = &filesystem.files[file->index]; | ||
303 | |||
304 | u8 blk_id = FS_NULL; | ||
305 | u8 blk_prev = FS_NULL; | ||
306 | u16 offset = file->offset; | ||
307 | if (file_idx->first_block == FS_NULL) { | ||
308 | // Check how many blocks will this write requires and if we have enough | ||
309 | // available. | ||
310 | u16 blocks_required = n_bytes / FILE_BLOCK_CAPACITY; | ||
311 | u16 blocks_available = FILE_N_BLOCKS - filesystem.busy_blocks; | ||
312 | if (blocks_required > blocks_available) { | ||
313 | return 0; | ||
314 | } | ||
315 | |||
316 | // Find the first available block. | ||
317 | blk_id = _fs_init_new_block(); | ||
318 | file_idx->first_block = blk_id; | ||
319 | |||
320 | // Update file index on SRAM. | ||
321 | _fs_update_file_index(file->index); | ||
322 | } else { | ||
323 | // Check how many blocks will this write requires and if we have | ||
324 | // enough available. | ||
325 | u16 file_size = fs_file_size(file); | ||
326 | u16 blocks_in_file = file_size / FILE_BLOCK_SIZE; | ||
327 | u16 blocks_available = FILE_N_BLOCKS - filesystem.busy_blocks + blocks_in_file; | ||
328 | u16 blocks_required = (n_bytes + offset) / FILE_BLOCK_CAPACITY; | ||
329 | if (blocks_required > blocks_available) { | ||
330 | return 0; | ||
331 | } | ||
332 | |||
333 | blk_id = file_idx->first_block; | ||
334 | |||
335 | // If there is an offset find the block index and relative offset. | ||
336 | if (offset >= FILE_BLOCK_CAPACITY) { | ||
337 | u16 n_blocks_offset = offset / FILE_BLOCK_CAPACITY; | ||
338 | for (size_t i = 0; i < n_blocks_offset; ++i) { | ||
339 | FileBlock block; | ||
340 | u16 block_pos = FILE_DATA_START + FILE_BLOCK_SIZE * blk_id; | ||
341 | _fs_read(&block, block_pos, sizeof(FileBlock)); | ||
342 | blk_id = block.next_block; | ||
343 | blk_prev = block.prev_block; | ||
344 | if (blk_id == FS_NULL) { | ||
345 | return 0; | ||
346 | } | ||
347 | } | ||
348 | offset = offset % FILE_BLOCK_CAPACITY; | ||
349 | } | ||
350 | } | ||
351 | |||
352 | // Write to block. | ||
353 | _fs_write_to_block(src, n_bytes, offset, blk_id, blk_prev, | ||
354 | file->mode == FS_OPEN_APPEND); | ||
355 | |||
356 | return n_bytes; | ||
357 | } | ||
358 | |||
359 | void | ||
360 | _fs_read_from_block(u8 *dst, u16 n_bytes, u16 blk_offset, u8 blk_id) { | ||
361 | // Read initial block. | ||
362 | FileBlock block; | ||
363 | u16 block_pos = FILE_DATA_START + FILE_BLOCK_SIZE * blk_id; | ||
364 | _fs_read(&block, block_pos, sizeof(FileBlock)); | ||
365 | |||
366 | u16 read_bytes = MIN(block.size - blk_offset, n_bytes); | ||
367 | _fs_read(dst, block_pos + blk_offset + sizeof(FileBlock), read_bytes); | ||
368 | |||
369 | u16 remaining_bytes = n_bytes - read_bytes; | ||
370 | if (block.next_block != FS_NULL && remaining_bytes > 0) { | ||
371 | _fs_read_from_block(dst + read_bytes, remaining_bytes, 0, block.next_block); | ||
372 | } | ||
373 | } | ||
374 | |||
375 | u16 | ||
376 | fs_read(u8 *dst, u16 n_bytes, File *file) { | ||
377 | if ((file->mode & FS_OPEN_READ) == 0) { | ||
378 | return 0; | ||
379 | } | ||
380 | |||
381 | // If there is an offset find the block index and relative offset. | ||
382 | u8 blk_id = filesystem.files[file->index].first_block; | ||
383 | u16 offset = file->offset; | ||
384 | |||
385 | // Read as much as we can from the file after the offset. | ||
386 | u16 file_size = fs_file_size(file); | ||
387 | if (offset + n_bytes >= file_size) { | ||
388 | n_bytes = file_size - offset; | ||
389 | } | ||
390 | |||
391 | if (offset >= FILE_BLOCK_CAPACITY) { | ||
392 | u16 n_blocks_offset = offset / FILE_BLOCK_CAPACITY; | ||
393 | for (size_t i = 0; i < n_blocks_offset; ++i) { | ||
394 | FileBlock block; | ||
395 | u16 block_pos = FILE_DATA_START + FILE_BLOCK_SIZE * blk_id; | ||
396 | _fs_read(&block, block_pos, sizeof(FileBlock)); | ||
397 | blk_id = block.next_block; | ||
398 | if (blk_id == FS_NULL) { | ||
399 | return 0; | ||
400 | } | ||
401 | } | ||
402 | offset = offset % FILE_BLOCK_CAPACITY; | ||
403 | } | ||
404 | |||
405 | // Copy n_bytes to destination. | ||
406 | _fs_read_from_block(dst, n_bytes, offset, blk_id); | ||
407 | |||
408 | return n_bytes; | ||
409 | } | ||
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 @@ | |||
1 | #ifndef FILESYSTEM_H | ||
2 | #define FILESYSTEM_H | ||
3 | |||
4 | #include "common.h" | ||
5 | |||
6 | typedef enum { | ||
7 | FS_OPEN_READ = (1 << 0), | ||
8 | FS_OPEN_WRITE = (1 << 1), | ||
9 | FS_OPEN_APPEND = (1 << 2), | ||
10 | } OpenMode; | ||
11 | |||
12 | typedef struct File { | ||
13 | // File index offset. | ||
14 | u8 index; | ||
15 | // The offset within the file. Must always be valid, and so the File struct | ||
16 | // shouldn't be manaully modified unless we are sure we know what we are | ||
17 | // doing. | ||
18 | u16 offset; | ||
19 | // The mode of this file (read/write/append). | ||
20 | OpenMode mode; | ||
21 | } File; | ||
22 | |||
23 | typedef enum { | ||
24 | FS_SEEK_SET, | ||
25 | FS_SEEK_CUR, | ||
26 | FS_SEEK_END, | ||
27 | } SeekMode; | ||
28 | |||
29 | void fs_init(void); | ||
30 | File fs_open_file(char *name, OpenMode mode); | ||
31 | u16 fs_file_size(File *file); | ||
32 | int fs_seek(File *file, int offset, SeekMode mode); | ||
33 | u16 fs_write(u8 *src, u16 n_bytes, File *file); | ||
34 | u16 fs_read(u8 *dst, u16 n_bytes, File *file); | ||
35 | |||
36 | #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 @@ | |||
1 | #include "common.h" | ||
2 | |||
3 | IrsFunc irs_table[] = { | ||
4 | [IRQ_VBLANK ] = NULL, | ||
5 | [IRQ_HBLANK ] = NULL, | ||
6 | [IRQ_VCOUNT ] = NULL, | ||
7 | [IRQ_TIMER_0] = NULL, | ||
8 | [IRQ_TIMER_1] = NULL, | ||
9 | [IRQ_TIMER_2] = NULL, | ||
10 | [IRQ_TIMER_3] = NULL, | ||
11 | [IRQ_SERIAL ] = NULL, | ||
12 | [IRQ_DMA_0 ] = NULL, | ||
13 | [IRQ_DMA_1 ] = NULL, | ||
14 | [IRQ_DMA_2 ] = NULL, | ||
15 | [IRQ_DMA_3 ] = NULL, | ||
16 | [IRQ_KEYPAD ] = NULL, | ||
17 | [IRQ_GAMEPAK] = NULL, | ||
18 | }; | ||
19 | |||
20 | // External irs_main function, has to be written in ARM assembly. | ||
21 | void irs_main(void); | ||
22 | #define IRS_MAIN *(IrsFunc*)(0x03007FFC) | ||
23 | |||
24 | void | ||
25 | irq_enable(IrqIndex idx) { | ||
26 | switch (idx) { | ||
27 | case IRQ_VBLANK: { DISP_STATUS |= DISP_VBLANK_IRQ; } break; | ||
28 | case IRQ_HBLANK: { DISP_STATUS |= DISP_HBLANK_IRQ; } break; | ||
29 | case IRQ_VCOUNT: { DISP_STATUS |= DISP_VCOUNT_IRQ; } break; | ||
30 | case IRQ_TIMER_0: { TIMER_CTRL_0 |= TIMER_CTRL_IRQ; } break; | ||
31 | case IRQ_TIMER_1: { TIMER_CTRL_1 |= TIMER_CTRL_IRQ; } break; | ||
32 | case IRQ_TIMER_2: { TIMER_CTRL_2 |= TIMER_CTRL_IRQ; } break; | ||
33 | case IRQ_TIMER_3: { TIMER_CTRL_3 |= TIMER_CTRL_IRQ; } break; | ||
34 | case IRQ_SERIAL: { /* TODO: Set REG_SERIAL? */ } break; | ||
35 | case IRQ_DMA_0: { DMA_CTRL(0) |= DMA_IRQ; } break; | ||
36 | case IRQ_DMA_1: { DMA_CTRL(1) |= DMA_IRQ; } break; | ||
37 | case IRQ_DMA_2: { DMA_CTRL(2) |= DMA_IRQ; } break; | ||
38 | case IRQ_DMA_3: { DMA_CTRL(3) |= DMA_IRQ; } break; | ||
39 | case IRQ_KEYPAD: { KEY_CTRL |= KEY_IRQ; } break; | ||
40 | case IRQ_GAMEPAK: { /* Nothing to do here...*/ } break; | ||
41 | } | ||
42 | IRQ_ENABLE |= (1 << idx); | ||
43 | } | ||
44 | |||
45 | void | ||
46 | irq_disable(IrqIndex idx) { | ||
47 | switch (idx) { | ||
48 | case IRQ_VBLANK: { DISP_STATUS &= ~DISP_VBLANK_IRQ; } break; | ||
49 | case IRQ_HBLANK: { DISP_STATUS &= ~DISP_HBLANK_IRQ; } break; | ||
50 | case IRQ_VCOUNT: { DISP_STATUS &= ~DISP_VCOUNT_IRQ; } break; | ||
51 | case IRQ_TIMER_0: { TIMER_CTRL_0 &= ~TIMER_CTRL_IRQ; } break; | ||
52 | case IRQ_TIMER_1: { TIMER_CTRL_1 &= ~TIMER_CTRL_IRQ; } break; | ||
53 | case IRQ_TIMER_2: { TIMER_CTRL_2 &= ~TIMER_CTRL_IRQ; } break; | ||
54 | case IRQ_TIMER_3: { TIMER_CTRL_3 &= ~TIMER_CTRL_IRQ; } break; | ||
55 | case IRQ_SERIAL: { /* TODO: Set REG_SERIAL? */ } break; | ||
56 | case IRQ_DMA_0: { DMA_CTRL(0) &= ~DMA_IRQ; } break; | ||
57 | case IRQ_DMA_1: { DMA_CTRL(1) &= ~DMA_IRQ; } break; | ||
58 | case IRQ_DMA_2: { DMA_CTRL(2) &= ~DMA_IRQ; } break; | ||
59 | case IRQ_DMA_3: { DMA_CTRL(3) &= ~DMA_IRQ; } break; | ||
60 | case IRQ_KEYPAD: { KEY_CTRL &= ~KEY_IRQ; } break; | ||
61 | case IRQ_GAMEPAK: { /* Nothing to do here...*/ } break; | ||
62 | } | ||
63 | IRQ_ENABLE &= ~(1 << idx); | ||
64 | } | ||
65 | |||
66 | void | ||
67 | irs_set(IrqIndex idx, IrsFunc func) { | ||
68 | // Store IRQ_CTRL status and disable interrupts for now. | ||
69 | u16 irq_ctrl = IRQ_CTRL; | ||
70 | IRQ_CTRL = 0; | ||
71 | |||
72 | // Update the IRS table and enable/disable the given IRQ. | ||
73 | irs_table[idx] = func; | ||
74 | if (func == NULL) { | ||
75 | irq_disable(idx); | ||
76 | } else { | ||
77 | irq_enable(idx); | ||
78 | } | ||
79 | |||
80 | // Restore previous irq_ctrl. | ||
81 | IRQ_CTRL = irq_ctrl; | ||
82 | } | ||
83 | |||
84 | void | ||
85 | irq_init(void) { | ||
86 | IRS_MAIN = irs_main; | ||
87 | IRQ_CTRL = 1; | ||
88 | } | ||
89 | |||
90 | void | ||
91 | 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 @@ | |||
1 | .file "interrupts.s" | ||
2 | .extern irs_table; | ||
3 | .section .iwram, "ax", %progbits | ||
4 | .arm | ||
5 | .align | ||
6 | .global irs_main | ||
7 | |||
8 | irs_main: | ||
9 | @ Get the contents of IRQ_ENABLE, IRQ_ACK, and IRQ_CTRL | ||
10 | ldr ip, mem_irq_base_reg @ ip = (IRQ_ENABLE << 16) | IRQ_ACK | ||
11 | ldr r0, [ip] @ r0 = irq_enable | ||
12 | and r1, r0, r0, lsr #16 @ r1 = irq_enable & irq_ack | ||
13 | |||
14 | @ Disable IRQ_CTRL for now. | ||
15 | mov r3, #0 @ r3 = 0 | ||
16 | strh r3, [ip, #8] @ *(ip + 0x8) = r3 | ||
17 | |||
18 | @ r0 = irs_table address pointer | ||
19 | @ r2 = tmp | ||
20 | @ r3 = 0 (for r3; r3 < 14; r3++) | ||
21 | ldr r0, = irs_table | ||
22 | irs_main_fp_search: | ||
23 | @ Check that the current index is an active IRQ. | ||
24 | mov r2, #1 | ||
25 | and r2, r1, r2, lsl r3 | ||
26 | cmp r2, #0 | ||
27 | beq irs_main_fp_search_not_enabled | ||
28 | |||
29 | @ Extract the function pointer for this IRS if available. | ||
30 | ldr r2, [r0] | ||
31 | cmp r2, #0 | ||
32 | bne irs_main_handle_irs | ||
33 | |||
34 | irs_main_fp_search_not_enabled: | ||
35 | add r0, r0, #4 | ||
36 | add r3, #1 | ||
37 | cmp r3, #14 | ||
38 | bne irs_main_fp_search | ||
39 | b irs_main_exit | ||
40 | |||
41 | irs_main_handle_irs: | ||
42 | @ r2: Contains IRQ function pointer. | ||
43 | @ r3: Stores the IRQ index. | ||
44 | |||
45 | @ Acknowledge that we are handling this interrupt writing to IRQ_ACK and | ||
46 | @ IRQ_ACK_BIOS. | ||
47 | mov r0, #1 | ||
48 | lsl r0, r0, r3 | ||
49 | strh r0, [ip, #2] | ||
50 | ldr r1, mem_irq_ack_bios @ r1 = IRQ_ACK_BIOS | ||
51 | str r0, [r1] | ||
52 | |||
53 | @ Store the SPSR in one of the free registers and save it along with the | ||
54 | @ return pointer. | ||
55 | mrs r3, spsr | ||
56 | stmfd sp!, {r3, lr} | ||
57 | |||
58 | @ Set CPU to system mode | ||
59 | mrs r3, cpsr | ||
60 | bic r3, r3, #0xDF | ||
61 | orr r3, r3, #0x1F | ||
62 | msr cpsr, r3 | ||
63 | |||
64 | @ Call isr function pointer | ||
65 | stmfd sp!, {lr} | ||
66 | mov lr, pc | ||
67 | bx r2 | ||
68 | ldmfd sp!, {lr} | ||
69 | |||
70 | @ Set CPU to irq mode | ||
71 | mrs r3, cpsr | ||
72 | bic r3, r3, #0xDF | ||
73 | orr r3, r3, #0x92 | ||
74 | msr cpsr, r3 | ||
75 | |||
76 | @ Restore the SPSR and the registers. | ||
77 | ldmfd sp!, {r3, lr} | ||
78 | msr spsr, r3 | ||
79 | |||
80 | irs_main_exit: | ||
81 | @ Restore IRQ_CTRL. | ||
82 | mov r3, #1 @ r3 = 0 | ||
83 | strh r3, [ip, #8] @ *(ip + 0x8) = r3 | ||
84 | bx lr | ||
85 | |||
86 | mem_irq_base_reg: | ||
87 | .word 0x04000200 @ IRQ_ENABLE | ||
88 | mem_irq_ack_bios: | ||
89 | .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 @@ | |||
1 | /* | ||
2 | Copyright (c) 2021 Bad Diode | ||
3 | |||
4 | Permission to use, copy, modify, and distribute this software for any | ||
5 | purpose with or without fee is hereby granted, provided that the above | ||
6 | copyright notice and this permission notice appear in all copies. | ||
7 | |||
8 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | ||
9 | WITH REGARD TO THIS SOFTWARE. | ||
10 | */ | ||
11 | |||
12 | #include "common.h" | ||
13 | // #include "text.h" | ||
14 | |||
15 | #include "interrupts.c" | ||
16 | #include "filesystem.c" | ||
17 | #include "renderer.c" | ||
18 | |||
19 | // | ||
20 | // Config parameters. | ||
21 | // | ||
22 | |||
23 | #ifdef PROF_ENABLE | ||
24 | #if PROF_ENABLE == 0 | ||
25 | #define PROF(F,VAR) (profile_start(),(F),(VAR) = profile_stop()) | ||
26 | #elif PROF_ENABLE == 1 | ||
27 | #define PROF(F,VAR) (profile_start(),(F),(VAR) = MAX(profile_stop(), (VAR))) | ||
28 | #endif | ||
29 | #ifndef PROF_SHOW_X | ||
30 | #define PROF_SHOW_X 0 | ||
31 | #endif | ||
32 | #ifndef PROF_SHOW_Y | ||
33 | #define PROF_SHOW_Y 0 | ||
34 | #endif | ||
35 | #define PROF_SHOW() \ | ||
36 | do { \ | ||
37 | txt_position((PROF_SHOW_X), (PROF_SHOW_Y));\ | ||
38 | txt_printf("EVAL: %lu ", eval_cycles);\ | ||
39 | txt_position((PROF_SHOW_X), (PROF_SHOW_Y));\ | ||
40 | txt_printf("FLIP: %lu ", flip_cycles);\ | ||
41 | } while (0) | ||
42 | #define PROF_INIT() \ | ||
43 | u32 eval_cycles = 0;\ | ||
44 | u32 flip_cycles = 0; | ||
45 | #else | ||
46 | #define PROF(F,VAR) (F) | ||
47 | #define PROF_SHOW() | ||
48 | #define PROF_INIT() | ||
49 | #endif | ||
50 | |||
51 | int main(void) { | ||
52 | // Adjust system wait times. | ||
53 | SYSTEM_WAIT = SYSTEM_WAIT_CARTRIDGE; | ||
54 | |||
55 | // Initialize filesystem. | ||
56 | fs_init(); | ||
57 | |||
58 | // Initialize renderer. | ||
59 | renderer_init(); | ||
60 | |||
61 | // Register interrupts. | ||
62 | irq_init(); | ||
63 | irs_set(IRQ_VBLANK, irs_stub); | ||
64 | |||
65 | draw_pixel(0, 0, 1); | ||
66 | draw_pixel(0, 1, 2); | ||
67 | draw_pixel(0, 2, 3); | ||
68 | draw_pixel(0, 3, 4); | ||
69 | |||
70 | // Main loop. | ||
71 | PROF_INIT(); | ||
72 | size_t frame_counter = 0; | ||
73 | while(true) { | ||
74 | bios_vblank_wait(); | ||
75 | PROF_SHOW(); | ||
76 | PROF(flip_buffer(), flip_cycles); | ||
77 | frame_counter++; | ||
78 | } | ||
79 | |||
80 | return 0; | ||
81 | } | ||
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 @@ | |||
1 | // TODO: For now we pack front/backbuffers together but this make it so that we | ||
2 | // can only use 2 backgrounds. Instead we can move the backbuffer to the end of | ||
3 | // the VRAM. This will give us 3 backgrounds but eats into the available memory | ||
4 | // for sprites but should be fine for non sprite intensive applications. | ||
5 | #define FRONTBUFFER ((u32*)(MEM_VRAM)) | ||
6 | #define BACKBUFFER ((u32*)(MEM_VRAM + KB(96) - KB(20))) | ||
7 | |||
8 | // Adjust both of these if the location of the map changes. Each screnblock | ||
9 | // requires 2K. | ||
10 | #define FRONTBUFFER_TILEMAP ((u16*)(MEM_VRAM + KB(20))) | ||
11 | #define FRONTBUFFER_SCREENBLOCK 10 | ||
12 | |||
13 | static u32 dirty_tiles[21] = {0}; | ||
14 | |||
15 | // TODO: Allow disable bound checking at compile time. | ||
16 | #define BOUNDCHECK_SCREEN() if (x >= SCREEN_WIDTH || y >= SCREEN_HEIGHT) return; | ||
17 | |||
18 | IWRAM_CODE | ||
19 | void | ||
20 | draw_pixel(u16 x, u16 y, u8 color) { | ||
21 | BOUNDCHECK_SCREEN(); | ||
22 | |||
23 | // Find row position for the given x/y coordinates. | ||
24 | size_t tile_x = x / 8; | ||
25 | size_t tile_y = y / 8; | ||
26 | size_t start_col = x % 8; | ||
27 | size_t start_row = y % 8; | ||
28 | size_t pos = start_row + (tile_x + tile_y * 32) * 8; | ||
29 | |||
30 | // Update backbuffer. | ||
31 | size_t shift = start_col * sizeof(u32); | ||
32 | BACKBUFFER[pos] = (BACKBUFFER[pos] & ~(0xF << shift)) | color << shift; | ||
33 | |||
34 | // Mark tile as dirty. | ||
35 | dirty_tiles[tile_y] |= 1 << tile_x; | ||
36 | } | ||
37 | |||
38 | IWRAM_CODE | ||
39 | void | ||
40 | flip_buffer(void) { | ||
41 | // Copy dirty tiles from the backbuffer to the frontbuffer. | ||
42 | Tile *dst = FRONTBUFFER; | ||
43 | Tile *src = BACKBUFFER; | ||
44 | for (size_t j = 0; j < 20; ++j) { | ||
45 | if (dirty_tiles[j] == 0) { | ||
46 | continue; | ||
47 | } | ||
48 | for (size_t i = 0, k = 1; i < 30; ++i, k <<= 1) { | ||
49 | if (dirty_tiles[j] & k) { | ||
50 | dst[i + j * 32] = src[i + j * 32]; | ||
51 | } | ||
52 | } | ||
53 | dirty_tiles[j] = 0; | ||
54 | } | ||
55 | } | ||
56 | |||
57 | void | ||
58 | renderer_init(void) { | ||
59 | // Initialize display mode and bg palette. | ||
60 | DISP_CTRL = DISP_MODE_0 | DISP_BG_0 | DISP_OBJ; | ||
61 | |||
62 | // Initialize backgrounds. | ||
63 | BG_CTRL(0) = BG_CHARBLOCK(0) | BG_SCREENBLOCK(FRONTBUFFER_SCREENBLOCK); | ||
64 | |||
65 | // TODO: Initialize other backgrounds if needed. | ||
66 | |||
67 | // Use DMA to clear front and back buffers. | ||
68 | dma_fill(FRONTBUFFER, 0, KB(20), 3); | ||
69 | dma_fill(BACKBUFFER, 0, KB(20), 3); | ||
70 | |||
71 | // Initialize default palette. | ||
72 | PAL_BUFFER_BG[0] = COLOR_BLACK; | ||
73 | PAL_BUFFER_BG[1] = COLOR_WHITE; | ||
74 | PAL_BUFFER_BG[2] = COLOR_RED; | ||
75 | PAL_BUFFER_BG[3] = COLOR_BLUE; | ||
76 | PAL_BUFFER_BG[4] = COLOR_CYAN; | ||
77 | PAL_BUFFER_BG[5] = COLOR_GREY; | ||
78 | |||
79 | // Initialize background memory map. | ||
80 | for (size_t i = 0; i < 32 * 20; ++i) { | ||
81 | FRONTBUFFER_TILEMAP[i] = i; | ||
82 | } | ||
83 | |||
84 | // // Load font data into VRAM. | ||
85 | // unpack_tiles(&bd_font, FONT_DATA, 256); | ||
86 | } | ||
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 @@ | |||
1 | /* | ||
2 | Copyright (c) 2021 Bad Diode | ||
3 | |||
4 | Permission to use, copy, modify, and distribute this software for any | ||
5 | purpose with or without fee is hereby granted, provided that the above | ||
6 | copyright notice and this permission notice appear in all copies. | ||
7 | |||
8 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | ||
9 | WITH REGARD TO THIS SOFTWARE. | ||
10 | */ | ||
11 | |||
12 | #ifndef UTILS_SHORTHAND_H | ||
13 | #define UTILS_SHORTHAND_H | ||
14 | |||
15 | #include <assert.h> | ||
16 | #include <stdbool.h> | ||
17 | #include <stddef.h> | ||
18 | #include <stdint.h> | ||
19 | |||
20 | // | ||
21 | // This simple header just typedefs the basic C define types to a shorter name, | ||
22 | // loads the quality of life bool macro for _Bool and defines shorthand macros | ||
23 | // for byte sizes. | ||
24 | |||
25 | typedef uint8_t u8; | ||
26 | typedef uint16_t u16; | ||
27 | typedef uint32_t u32; | ||
28 | typedef uint64_t u64; | ||
29 | typedef int8_t s8; | ||
30 | typedef int16_t s16; | ||
31 | typedef int32_t s32; | ||
32 | typedef int64_t s64; | ||
33 | typedef volatile u8 vu8; | ||
34 | typedef volatile u16 vu16; | ||
35 | typedef volatile u32 vu32; | ||
36 | typedef volatile u64 vu64; | ||
37 | typedef volatile s8 vs8; | ||
38 | typedef volatile s16 vs16; | ||
39 | typedef volatile s32 vs32; | ||
40 | typedef volatile s64 vs64; | ||
41 | |||
42 | #define KB(N) ((u64)(N) * 1024) | ||
43 | #define MB(N) ((u64)KB(N) * 1024) | ||
44 | #define GB(N) ((u64)MB(N) * 1024) | ||
45 | #define TB(N) ((u64)GB(N) * 1024) | ||
46 | |||
47 | #endif // UTILS_SHORTHAND_H | ||