1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
|
These are some development notes I've put together that would be of great
aid to those willing to contribute to the PSn00bSDK project. Many of these
came from my own experience dealing with the PS1 at low-level when I ran
into some unexplained quirks while some are from disassembly observations
and clarification of existing documents. More entries will be added when
I run into more previously undocumented quirks in the future.
- Lameguy64
* When calling C functions (ie. BIOS functions) from assembly code you'll
need to allocate N words on the stack first when calling a function that has
N arguments (addiu $sp, -(4*N) where N = number of arguments of the function
being called) even if the arguments are on registers a0 to a3 and the C
functions don't always use the space allotted in stack. When calling a
function with a variable number of arguments (printf) always allocate
16 bytes of stack.
* Hooking a custom handler using BIOS function HookEntryInt (B(19h), known
as SetCustomExitFromException in nocash docs) is only triggered when there's
an IRQ that is not yet acknowledged by previous IRQ handlers built into the
kernel. This is also the best point to acknowledge any IRQs without breaking
compatibility with built-in BIOS IRQ handlers and is what the official SDK
uses to handle IRQs. To make sure this handler is triggered on every interrupt
you must call ChangeClearPad(0) and ChangeClearRCnt(3, 0) (which are functions
(B(5Bh)) and C(0Ah) respectively) otherwise the pad and root counter handlers
in the kernel will acknowledge the interrupt before your handler, preventing
you from handling them yourself.
* It is not advisable to handle interrupts using event handlers like in
PSXSDK as it breaks BIOS features that depend on interrupts. Clearing the
VBlank IRQ in a event handler for example prevents the BIOS controller
functions from working as it depends on the VBlank IRQ to determine when to
query controllers. Acknowledge interrupts using a custom handler set by BIOS
function HookEntryInt (B(19h), known as SetCustomExitFromException in nocash
docs).
* When running in high resolution mode you must additionally wait for bit 31
in GPUSTAT (1F801814h) to alternate on every frame (frame 0: wait until 0,
frame 1: wait until 1, frame 2: wait until 0) before waiting for VSync
otherwise the GPU will only draw the first field if you don't have drawing
to displayed area enabled. Performing this check in a low resolution/non
interlaced mode is harmless.
* There's a hardware bug in the GPU FillVRAM command GP0(02h) where if you
set the height to 512 pixels the primitive is processed with a height of 0
as the hardware does not appear to interpret the last bit of the height
value. This is most apparent when putting a DRAWENV with the height of 512
pixels (for PAL for example) and background clearing is enabled, hence why
DRAWENV.isbg is not effective in the official SDK.
* In the official SDK, DMA IRQs appear to be enabled only when a callback
function is set (ie. DrawSyncCallback() enables IRQ for DMA channel 2). DMA
IRQs are only triggered on transfer completion.
Additional notes by spicyjpeg:
* The SDK provides no support yet for replacing the BIOS exception handler with
a custom one, however it can be done (if you are ok with losing all BIOS
controller, memory card and file functionality). In order not to break anything
your exception handler must do the following:
* prevent GTE opcodes from being executed twice due to a hardware glitch (the
nocash docs explain how to do this);
* define _irq_func_table[12] as an *extern* array of function pointers, call
the appropriate entry when an IRQ occurs and clear the respective flag in
register 1F801070h;
* handle syscalls 01h-02h, i.e. EnterCriticalSection and ExitCriticalSection
properly. You should also handle syscall FF00h (invalid API usage), as well
as breaks 1800h and 1C00h (division errors injected by GCC), by locking up
and maybe showing a BSOD or similar;
* overwrite the default BIOS API vectors with a passthrough that checks no
controller- or interrupt-related function is being called. This is necessary
(although ugly) as libpsn00b often calls such functions internally.
* For some reason mipsel-unknown-elf-nm (symbol map generator) insists on
outputting 64-bit addresses (with the top 32 bits set, e.g. FFFFFFFF80010000)
even when feeding it a regular 32-bit MIPS executable, while the standard x86
nm tool that ships with most GCC packages prints the proper 32-bit address.
Unclear whether this is a bug, intended behavior or the result of some ancient
ELF ABI flag crap. DL_ParseSymbolMap() will ignore the top 32 bits, so this
should only bother you if you're implementing your own symbol map parser.
* I haven't worked on psxspu but, for those willing to write some code, this is
the formula to calculate SPU pitch values for playing musical notes ("^" is the
power operator, not xor):
frequency = (ref / 32) * (2 ^ ((note - 9) / 12))
spu_pitch = frequency / 44100 * 4096
ref = frequency the sample should be played at to play a middle A (MIDI note 69)
note = MIDI note number (usually 0-127, 60 is middle C)
* If you are overriding any of the memory allocation functions, DO NOT ENABLE
LINK-TIME OPTIMIZATION. GCC has a long-standing bug with LTO and weak functions
written in assembly, also LTO hasn't been tested at all yet.
|