via http://wololo.net/2012/06/07/syscall-internals/

I assume you understand what is a syscall and how syscalls are called from user space in PSP. If not, please read Freddy’s post.We are going to take a closer look at what happens in kernel space, where syscalls are intercepted and served. This is a pretty technical post so get some coffee.

When a syscall is issued (called) from user space, the syscall exception handler which is installed in interruptmgr will take control. The hardware will put a pointer in coprocessor register r12, which leads to a fixed size (0×4010) table in memory. Let’s call it syscall table. The first 4 words of the table are the header, and the rest are slots for function pointers.
The struct of the syscall table header is like this:
struct syscall_table {
u32 unk_0;
u32 seed;
u32 table_size; // always 0x4000
u32 total_size; // always 0x4010
u32 func[0]; // 0x1000 slots of function pointers
};
The most important field is the seed, which is generated randomly when the system boots up. Meanwhile, register r21 of the coprocessor has a value by which you can index into the function pointer slot. The Kernel then reads the value of that slot, and jalr (jumps) to it, which is the real implementation of the syscall.
We, as a user space process, have no privilege to write or read the syscall table directly, because the table is within kernel memory space.
The syscall table is initialized in loadcore, when system/game boots up. The seed is generated at that moment, and never changes until next bootup. All the slots in the function pointers table will be initialized to an address in loadcore, which does nothing but returns an error number (0×80020001).
When a module is loaded, it has to register its exported function in loadcore. The registration in a word is to find a proper slot(s) to store the function pointers, then assign a syscall number to it.
The Kernel first decides how many slots reserved for the module. The algo is as below:
reserved_slots_cnt = (random() & 3) + 1 + exported_syscall_cnt;
Then kernel will decide which slot index to start, with this algo:
start_index = random() % exported_syscall_cnt;
BTW, the random() function is reading the Count register.
Then from the start_index, the kernel copies the function addr into the table in a
fixed sequence (the order follows the sequence of exports).
At this point, the exported function has already been assigned a syscall number, which is calculated as:
syscall_num = seed / 4 + slot_index;
Note that at this moment, all function pointers are masked by 0×80000000, i.e., any attempt to call this syscall will be trapped by a Bus error. This is how Sony protects un-imported syscalls.
Note also the reserved slots count for certain module is larger than the total amount of exported functions. Therefore, the spare slots are filled with a fixed address, which triggers break exception. It brings more trouble for syscall estimation.
As a sum up, the algo for filling the syscall table is as below:
for (i = start_index; i < reserved_slots_cnt + start_index; i++) {
int index = i;
if (index > reserved_slots_cnt)
index = index % reserved_slots_cnt; // wrap at the end