ps2sdk  1.1
A collection of Open Source libraries used for developing applications on Sony's PlayStation 2® (PS2).
Kernel

Modules

 Cache
 
 EE Loadfile: ELF and IRX loader client library.
 
 Interrupts
 
 Semaphores
 
 Threads
 
 Timers
 

Detailed Description

The core library of the EE.

Overview

Based on the EE kernel emulation in the Play! PS2 Emulator and my own tests.

Semaphores

Synchronization on the EE is provided by semaphores. Semaphores have a wait queue for threads when the resource is used.

Attributes

sema.count
Filled by ReferSemaStatus(). The current count.
sema.max_count
The number of times the semaphore can be used.
sema.init_count
The initial count value when creating the semaphore.
sema.wait_threads
The number of threads waiting on the semaphore.
sema.option
A pointer to user data. E.g. A string constant describing its use.

Mutexes

A mutex is a semaphore that allows only one running thread into a critical section of code.

sema.max_count
Set the max count to 1.
sema.init_count
To create a mutex locked, set the init_count to 0.
sema.init_count
To create a mutex unlocked, set the init_count to 1.

Using Semaphores

The functions with a lowercase 'i' should be used when interrupts are disabled like when in an interrupt handler.

int CreateSema(ee_sema_t *sema);
s32 CreateSema(ee_sema_t *sema)

Returns the id of the semaphore.

int WaitSema(int id);
s32 WaitSema(s32 sema_id)

A thread calls WaitSema() decreasing the count using the resource. When another thread calls WaitSema(), if the count is 0, that thread is put into the semaphore's wait queue.

int SignalSema(int id);
int iSignalSema(int id);
s32 SignalSema(s32 sema_id)
s32 iSignalSema(s32 sema_id)

A thread calls SignalSema() increasing the count returning the resource. The next thread waiting in the semaphore's queue is run.

int PollSema(int id);
int iPollSema(int id);
s32 iPollSema(s32 sema_id)
s32 PollSema(s32 sema_id)

A thread calls PollSema() decreasing the count using the resource. If the count is already zero, it does not put the thread calling PollSema() into a wait state, but returns an error.

Using PollSema() prior to WaitSema() on a semaphore acting as a mutex can create a race condition.

E.g.
A thread calls SignalSema() on a semaphore, then a thread using PollSema() uses the resource. WaitSema() then puts the next thread into wait status. No other threads are in the critical section of code to call SignalSema() to free the resource.
int DeleteSema(int id);
int iDeleteSema(int id);
s32 DeleteSema(s32 sema_id)
s32 iDeleteSema(s32 sema_id)

After DeleteSema() is called, threads in the semaphore's wait queue are returned to running status, the semaphore is deleted, and calls referring to the semaphore return an error.

int ReferSemaStatus(int id, ee_sema_t *sema);
int iReferSemaStatus(int id, ee_sema_t *sema);
s32 ReferSemaStatus(s32 sema_id, ee_sema_t *sema)
s32 iReferSemaStatus(s32 sema_id, ee_sema_t *sema)

ReferSemaStatus() can be used to check the current count, the max_count, and number of threads waiting on the semaphore.

Threads

Multithreading on the EE kernel is cooperative. The kernel provides a pool of 256 threads to be used. A few are reserved for fixing bugs and various libraries on the EE may run their own.

Cooperative Multithreading

Threads with a higher priority have to cede control to other threads. Threads with a longer amount of execution time will take time away from other threads since there isn't a regular preemptive schedule.

Attributes

thread.status
Status of the thread.
Status Effect
THS_RUN Running
THS_READY Ready to run
THS_WAIT Waiting
THS_SUSPEND Suspended
THS_DORMANT Newly created or terminated

Threads in THS_WAIT state can be suspended, too.

thread.func
Function for the thread to run.
thread.stack
Memory aligned to 16 byte boundary to use as stack.
thread.stack_size
Since the stack is 16-byte aligned, size is a multiple of 16.
thread.gp_reg
Pointer to global offset (the symbol _gp is defined by the linkscript).
thread.initial_priority
Initial priority when using CreateThread(). 0 - 127 (lower number is higher priority, but 0 is reserved by the kernel).
thread.current_priority
Filled by ReferThreadStatus(). Contains the thread's current priority.
thread.option
Pointer to user data. Documented not to work.
thread.waitType
Filled by ReferThreadStatus(). Contains why the thread is waiting.
Type Reason
THS_WT_NONE Not waiting
THS_WT_WAKE Waiting for wakeup
THS_WT_SEMA Waiting for semaphore

If a thread state is THS_WAIT and the wait type is THS_WT_WAKE, then the thread has been put to sleep using SleepThread().

thread.waitId
Filled by ReferThreadStatus(). Contains the semaphore's ID if waiting on a semaphore.
thread.wakeupCount
Filled by ReferThreadStatus(). Contains the number of times other threads have tried to wake up the thread when it wasn't sleeping.

Using Threads

The functions with a lowercase 'i' should be used when interrupts are disabled like when in an interrupt handler.

s32 CreateThread(ee_thread_t *thread)

Create the thread and put it into THS_DORMANT state. Returns thread's id on success.

int DeleteThread(int id);
s32 DeleteThread(s32 thread_id)

Deletes THS_DORMANT thread from another thread.

int StartThread(int id);
s32 StartThread(s32 thread_id, void *args)

Starts a THS_DORMANT thread.

void ExitThread(void);
void ExitThread(void)

Changes the calling thread to THS_DORMANT.

void ExitDeleteThread(void);
void ExitDeleteThread(void)

Changes the calling thread to THS_DORMANT then returns it to internal pool.

int SleepThread(void);
s32 SleepThread(void)

If the wakeupCount is 0, puts the current thread into THS_WAIT status.

int GetThreadId(void);
s32 GetThreadId(void)

Returns the id of the current thread.

int TerminateThread(int id);
int iTerminateThread(int id);
s32 iTerminateThread(s32 thread_id)
s32 TerminateThread(s32 thread_id)

Changes a thread to THS_DORMANT state from another thread.

int ChangeThreadPriority(int id, int priority);
int iChangeThreadPriority(int id, int priority);
s32 iChangeThreadPriority(s32 thread_id, s32 priority)
s32 ChangeThreadPriority(s32 thread_id, s32 priority)

Changes a thread's priority and schedules it at the bottom of that priority's queue

int RotateThreadReadyQueue(int priority);
int iRotateThreadReadyQueue(int priority);
s32 iRotateThreadReadyQueue(s32 priority)
s32 RotateThreadReadyQueue(s32 priority)

Finds first ready or running thread of a given priority and schedules it at the bottom of the queue.

int ReleaseWaitThread(int id);
int iReleaseWaitThread(int id);
s32 iReleaseWaitThread(s32 thread_id)
s32 ReleaseWaitThread(s32 thread_id)

Release a thread from THS_WAIT state.

int WakeupThread(int id);
int iWakeupThread(int id);
s32 iWakeupThread(s32 thread_id)
s32 WakeupThread(s32 thread_id)

Removes THS_WAIT status from a thread. If the thread isn't in wait status, the wakeupCount is incremented.

int CancelWakeupThread(int id);
int iCancelWakeupThread(int id);
s32 iCancelWakeupThread(s32 thread_id)
s32 CancelWakeupThread(s32 thread_id)

Set thread's wakeupCount to 0.

int SuspendThread(int id);
int iSuspendThread(int id);
s32 iSuspendThread(s32 thread_id)
s32 SuspendThread(s32 thread_id)

Suspends a thread, adding THS_SUSPEND to its status.

int ResumeThread(int id);
int iResumeThread(int id);
s32 iResumeThread(s32 thread_id)
s32 ResumeThread(s32 thread_id)

Removes THS_SUSPEND state from a thread.

s32 ReferThreadStatus(s32 thread_id, ee_thread_status_t *info)
s32 iReferThreadStatus(s32 thread_id, ee_thread_status_t *info)

Retrieve the status of a thread.

Example

This example shows how to implement a preemptive schedule for a group of threads.

#include <tamtypes.h>
#include <stdio.h>
#include <kernel.h>
// I've provided an alternative wakeup and sleep implementation using
// semaphores.
//#define USE_SEMA
// The global offset pointer
extern void *_gp;
#define SECONDS_TO_RUN 10
// A kilobyte of stack per thread
#define THREAD_STACK_SIZE 1024
volatile int counter = 0;
volatile int executing = 0;
// Scheduler attributes
#define SCHEDULER_PRIORITY 30
// Alarm intervals
// NTSC Frames per second
#define SCHEDULER_SECOND 30
// HBlanks 1/29.96 second NTSC
#define SCHEDULER_TIME 525
// HBlanks 1/30 second NTSC
//#define SCHEDULER_TIME 524
// HBlanks 1/25 second 576i
//#define SCHEDULER_TIME 625
//#define SCHEDULER_SECOND 25
#ifdef USE_SEMA
volatile int scheduler_sema = 0;
#endif
unsigned char scheduler_stack[THREAD_STACK_SIZE] ALIGNED(16);
int scheduler_id;
// Threads
unsigned char thread_a_stack[THREAD_STACK_SIZE] ALIGNED(16);
int thread_a_id;
volatile unsigned int thread_a_cycles = 0;
unsigned char thread_b_stack[THREAD_STACK_SIZE] ALIGNED(16);
int thread_b_id;
volatile unsigned int thread_b_cycles = 0;
void thread_a(void *arg);
void thread_b(void *arg);
// This scheduler thread could just be replaced with
// iRotateThreadReadyQueue() in the interrupt function.
// Using a thread allows for more flexibility and doesn't affect other threads
// since interrupts are enabled.
void scheduler(void* a)
{
while(1)
{
#ifdef USE_SEMA
WaitSema(scheduler_sema);
#else
#endif
RotateThreadReadyQueue(SCHEDULER_PRIORITY);
if (counter > SCHEDULER_SECOND * SECONDS_TO_RUN)
executing = 0;
#ifdef USE_SEMA
PollSema(scheduler_sema);
#endif
}
}
void interrupt(s32 id, u16 time, void *arg)
{
counter++;
#ifdef USE_SEMA
iSignalSema(scheduler_sema);
#else
iWakeupThread(scheduler_id);
#endif
iSetAlarm(SCHEDULER_TIME, interrupt, NULL);
// Calling this re-enables interrupts properly
}
int main( int argc, char **argv)
{
volatile unsigned int main_cycles = 0;
int interrupt_id = -1;
ee_thread_t thread;
#ifdef USE_SEMA
ee_sema_t sema;
// Create a mutex for the scheduler and indicate that the resource
// is already being used by setting init_count to 0.
sema.max_count = 1;
sema.init_count = 0;
sema.option = 0;
if ((scheduler_sema = CreateSema(&sema)) < 0)
return -1;
#endif
// Setup scheduler thread which will rotate the running threads
// Give it a high priority
thread.func = scheduler;
thread.stack = scheduler_stack;
thread.stack_size = THREAD_STACK_SIZE;
thread.gp_reg = &_gp;
thread.initial_priority = 1;
scheduler_id = CreateThread(&thread);
if (scheduler_id < 0)
return -1;
// Start the scheduler thread, it will go straight to wait status
StartThread(scheduler_id, NULL);
// Change the priority of the main thread so we know it's higher than
// the threads we're about to start.
ChangeThreadPriority(GetThreadId(), SCHEDULER_PRIORITY-1);
// Create 2 threads for the scheduler to rotate
thread.func = thread_a;
thread.stack = thread_a_stack;
thread.stack_size = THREAD_STACK_SIZE;
thread.gp_reg = &_gp;
thread.initial_priority = SCHEDULER_PRIORITY;
if ((thread_a_id = CreateThread(&thread)) < 0)
{
TerminateThread(scheduler_id);
DeleteThread(scheduler_id);
return -1;
}
thread.func = thread_b;
thread.stack = thread_b_stack;
thread.stack_size = THREAD_STACK_SIZE;
thread.gp_reg = &_gp;
thread.initial_priority = SCHEDULER_PRIORITY;
if ((thread_b_id = CreateThread(&thread)) < 0)
{
TerminateThread(thread_a_id);
DeleteThread(thread_a_id);
TerminateThread(scheduler_id);
DeleteThread(scheduler_id);
return -1;
}
// Use an interrupt to rotate a queue of threads to give each thread equal
// slices of cpu time.
// Use a hardware timer interrupt instead of an alarm for more finetuned
// control and less overhead
interrupt_id = SetAlarm(SCHEDULER_TIME, interrupt, NULL);
// Indicate that we're about to execute parallel threads
executing = 1;
// Start threads and change the main thread so it's on the same
// schedule.
printf("Starting threads\n");
StartThread(thread_a_id, NULL);
StartThread(thread_b_id, NULL);
ChangeThreadPriority(GetThreadId(),SCHEDULER_PRIORITY);
while(executing)
{
main_cycles++;
}
// Threads are done so change the main thread back to a higher priority
ChangeThreadPriority(GetThreadId(),SCHEDULER_PRIORITY-1);
TerminateThread(thread_a_id);
TerminateThread(thread_b_id);
DeleteThread(thread_a_id);
DeleteThread(thread_b_id);
printf("Threads done: Executed %d %d %d cycles in %d seconds\n",
thread_a_cycles, thread_b_cycles, main_cycles,
counter/SCHEDULER_SECOND);
// Releasing alarms does not work correctly on an unpatched kernel.
ReleaseAlarm(interrupt_id);
TerminateThread(scheduler_id);
DeleteThread(scheduler_id);
#ifdef USE_SEMA
// Delete the semaphore after stopping the scheduler thread.
DeleteSema(scheduler_sema);
#endif
return 0;
}
// Thread functions
void thread_a(void *arg)
{
while(1)
{
thread_a_cycles++;
}
}
void thread_b(void *arg)
{
volatile int i;
while(1)
{
thread_b_cycles++;
// Simulate a work load that gets heavier as time passes
// Comment this out to see how thread_a isn't affected by
// how long thread_b takes to run
#if 1
for (i = 0; i > thread_b_cycles; i++)
#endif
{
;
}
}
}
int main()
Definition: callstacktest.c:45
#define ExitHandler()
Definition: kernel.h:28
s32 iSetAlarm(u16 time, void(*callback)(s32 alarm_id, u16 time, void *common), void *common)
#define ALIGNED(x)
Definition: kernel.h:50
void * _gp
s32 SetAlarm(u16 time, void(*callback)(s32 alarm_id, u16 time, void *common), void *common)
s32 ReleaseAlarm(s32 alarm_id)
Definition: alarm.c:110
u32 time
Definition: libmouse.c:37
int init_count
Definition: kernel.h:218
int max_count
Definition: kernel.h:217
u32 option
Definition: kernel.h:221
void * gp_reg
Definition: kernel.h:230
int stack_size
Definition: kernel.h:229
void * func
Definition: kernel.h:227
void * stack
Definition: kernel.h:228
int initial_priority
Definition: kernel.h:231
#define NULL
Definition: tamtypes.h:91
signed int s32
Definition: tamtypes.h:58
unsigned short u16
Definition: tamtypes.h:24