PS2 PlayStation 2 EE Alarm Functionality

Discussion in 'PS2 Homebrew' started by sp193, Jun 28, 2017.

  1. 796
    1,437
    247
    sp193

    sp193 Developer

    Joined:
    Oct 13, 2014
    Messages:
    796
    Likes Received:
    1,437
    Trophy Points:
    247
    Location:
    Singapore
    Home Page:
    I didn't know that SCEI replaced the SetAlarm and ReleaseAlarm functions. At first, I thought it was simply to support the restriction about the EIE status bit getting eternally cleared to 0.

    But disassembling the original implementation reveals (at least at this point) that it probably wasn't totally functional in the first place. It appears that ReleaseAlarm is incapable of correctly releasing the alarm entry. It will mark the specified alarm as being unallocated, but the alarm queue is being updated incorrectly (first alarm will go missing, which may/may not be the correct entry).

    The alarm system has got management functions (SetAlarm and ReleaseAlarm), 64 alarms that can be freely allocated in any order and an alarm queue. The queue lists the ID of the alarms in the order that they will expire.

    The EE kernel reserves Timer #3 for the alarm functions, which is always set to count towards the target counter of the alarm that is next in queue. At every assertion of INTC interrupt 12, the alarm queue is processed; the next alarm is dequeued and freed, before its callback is called via a callback dispatcher that is part of rom0:EENULL.

    rom0:EENULL loads at 0x00081fc0. It contains both the idle thread and a small stub that helps the kernel with dispatching callbacks within user mode (but with interrupts disabled). After the user-mode callback is executed, syscall #5 is invoked and the execution of the INTC interrupt 12 handler continues in kernel mode.

    Core alarm functions:
    Code:
    static u64 AlarmStatus;
    static s32 AlarmCount;
    
    struct alarm{
       void *callback;   //0x78
       void *common;   //0x7c
       void *gp;   //0x80
       u32 init_count;   //0x84
       u32 target;   //0x88
    };
    
    static struct alarm alarms[64];
    static s8 alarmTargets[64];
    
    s32 SetAlarm(u16 time, void (*callback)(s32 dispatch_id, u16 time, void *common), void *common)
    {
       void *gp;
       u32 target, init_count;
       int pos, i, j, k, alarmID;
       struct alarm *alarm;
    
       alarmID = -1;
       for(i = 0; i < 64; i++) {
           if((AlarmStatus >> i) & 1) == 0) {
               AlarmStatus |= (1 << i);
               alarmID = i;
               break;
           }
       }
    
       if(alarmID < 0)
           return -1;
    
       //0x00002340
       alarm = alarms[alarmID];
       asm volatile("move %0, $gp\n" :"=r"(gp):);
       alarm->gp = gp;
       alarm->callback = callback;
       init_count = *T3_COUNT;
       alarm->init_count = init_count;
       target = 0xFFFF < (*T3_COUNT + time) ? *T3_COUNT + time : (*T3_COUNT + time) & 0xFFFF;
       alarm->target = target;
    
       if(init_count < target)
       {   //0x000023b8 - target is in the future
           for(pos = 0; pos < AlarmCount; pos++)
           {   //Try to find the spot in the queue to insert this new request in.
               if(target < alarms[alarmTargets[pos]].target)
               {   //Move back all requests.
                   if(AlarmCount >= pos)
                   {
                       for(j = AlarmCount; j >= pos ; j--)
                           alarmTargets[j+1] = alarmTargets[j];
                   }
    
                   break;
               }
           }
       }
       else
       {   //0x00002450
           for(pos = 0; pos < AlarmCount; pos++)
           {   //Try to find the spot in the queue to insert this new request in (target < now).
               if(alarms[alarmTargets[pos]].target < init_count)
               {
                   for(; pos < AlarmCount; pos++)
                   {   //Continue scanning (target < alarm[pos].target)
                       if(target < alarms[alarmTargets[pos]].target)
                       {   //Move back all requests.
                           for(j = AlarmCount; j >= pos; j--)
                               alarmTargets[j+1] = alarmTargets[j];
    
                           break;
                       }
                   }
    
                   break;
               }
           }
       }
       //0x00002530
       alarmTarget[pos] = alarmID;
       AlarmCount++;
    
       if(pos == 0)
       {   //If there was nothing in queue, start the timer.
           *T3_COMP = target;
           *T3_MODE = Tn_MODE(3,0,0,0,0,1,1,0,0,0);
       }
    
       return alarmID;
    }
    
    s32 ReleaseAlarm(s32 id)
    {
       int i;
    
       if((AlarmStatus >> id) & 1 == 0)
           return -1;
    
       //0x00002594
       --AlarmCount;
       AlarmStatus &= ~(1 << id)
       if(AlarmCount == 0)
           *T3_MODE = Tn_MODE(3,0,0,0,0,1,0,0,0,0);
    
       for(i = 1; i < AlarmCount; i++)   //BUGBUG: Wait, what? This will delete the wrong entry.
           alarmTargets[i] = alarmTargets[i+1];
    
       //Calculate time remaining, considering overflow.
       return((*T3_COUNT < alarms[id].init_count) ? (0x10000 + *T3_COUNT) - alarms[id].init_count : *T3_COUNT - alarms[id].init_count);
    }
    
    typedef (*UserModeCallbackDispatcher_t)();
    static UserModeCallbackDispatcher_t UserModeCallbackDispatcher = NULL;
    
    //0x80001578
    static void setUserModeCallbackDispatcher(UserModeCallbackDispatcher_t dispatcher)
    {
       UserModeCallbackDispatcher = dispatcher;
    }
    
    /*   At kernel entry point:
    
       ...
       _SifDmaInit();
       setUserModeCallbackDispatcher((void*)0x81fe0);
       SetVSyncFlag(0, 0);
       ...   */
    static void InvokeUserModeCallback(UserModeCallbackDispatcher_t *dispatcher, void *callback, u64 arg1, u64 arg2, u64 arg3);
    
    //INTC 12 (TIM3) handler
    void Intc12AlarmHandler(void)
    {
       u64 gp;
       u16 now;
       int i, alarmID;
    
       if(AlarmCount < 2)
       {   //No next alarm. Disable CMPE.
           *T3_MODE = Tn_MODE(3,0,0,0,0,1,0,0,1,0);
       }
       else
       {   //Update comparator
           *T3_MODE = Tn_MODE(3,0,0,0,0,1,1,0,1,0);
           *T3_COMP = alarms[alarmTargets[1]].target;
       }
    
       do{
           AlarmCount--;
           alarmID = alarmTargets[0];
           for(i = 0; i < AlarmCount; i++)
               alarmTargets[i] = alarmTargets[i+1];
    
           AlarmStatus &= ~(1 << alarmID);
           asm volatile(   "move %0, $gp\n"
                           "move $gp, %1\n" :"=r"(gp):"r"(alarm[alarmID].gp));
           now = (u16)alarm[alarmID].target;
           InvokeUserModeCallback(UserModeCallbackDispatcher, alarm[alarmID].callback, alarmID, now, alarm[alarmID].common);
           asm volatile(   "move $gp, %0\n" ::"r"(gp));
    
           if(AlarmCount <= 0)
               break;
       }while(alarm[alarmTargets[0]].target == now);
    }
    
    static u128 dispatch_ra;   //0x80016fc0
    static u128 dispatch_sp;   //0x80016fc0
    
    static void InvokeUserModeCallback(UserModeCallbackDispatcher_t *dispatcher, void *callback, u64 arg1, u64 arg2, u64 arg3)
    {   //Also set status EXL=1 and KSU=01b (User Mode)
       asm volatile(   "lui $k0, %hi(%0)\n"
                       "sw $ra, %lo(%0)($k0)\n"
                       "lui $k0, %hi(%1)\n"
                       "sw $sp, %lo(%1)($k0)\n"
                       "mtc0 %2, EPC\n"
                       "sync.p\n"
                       "daddu $v1, %3\n"
                       "daddu $a0, %4\n"
                       "daddu $a1, %5\n"
                       "daddu $a2, %6\n"
                       "mfc0 $k0, status\n"
                       "ori $k0, $k0, 0x12\n"
                       "mtc0 $k0, status\n"
                       "sync.p\n"
                       "eret\n": "=m"(dispatch_ra),"=m"(dispatch_sp):"r"(dispatcher),"r"(callback),"r"(arg1),"r"(arg2),"r"(arg3));
    }
    
    //Syscall #5 handler - called by EENULL
    void ResumeIntrDispatch(void)
    {   //Clear IE and EXL, and set KSU=00b (Kernel Mode)
       asm volatile(   "mfc0 $at, status\n"
                       "addiu $k0, $zero, 0xFFE4\n"
                       "and $at, $at, $k0\n"
                       "mtc0 $at, status\n"
                       "sync.p\n"
                       "lui $k0, %hi(%0)\n"
                       "lw $ra, %lo(%0)($k0)\n"
                       "lui $k0, %hi(%1)\n"
                       "jr $ra\n"
                       "lw $sp, %lo(%1)($k0)\n"::"m"(dispatch_ra),"m"(dispatch_sp));
    }
    
    
    EENULL (Loaded at 0x00081fc0):
    Code:
    .set noreorder
    
    .globl IdleThread
    .ent IdleThread
    IdleThread:
       nop
       nop
       nop
       nop
       nop
       nop
       b IdleThread
       nop
    .end IdleThread
    .p2align 4
    
    #Will be executed in user mode, but with interrupts disabled.
    #Set stack address to 0x00081fc0
    .globl UModeCallbackDispatcher
    .ent UModeCallbackDispatcher
    UModeCallbackDispatcher:
       lui $sp, 0x0008
       jalr $v1
       addiu $sp, $sp, 0x1fc0
       addiu $v1, $zero, -5
       syscall
    .end UModeCallbackDispatcher
    .p2align 4
    
    The idle thread is first thread in the EE, which is always run when the EE is... idle. The callback dispatcher exists at 0x00081fe0. The region from 0x00081000-0x00081fc0 is the stack for the user-mode callback.

    I guess this means that the update to the alarms functions should be copied and added to the homebrew PS2SDK as well.

    The SCEI patch replaces all the functions and assigns new syscall numbers for the new SetAlarm and ReleaseAlarm implementations, allowing the old implementation to exist. While the INTC interrupt 12 handler's registration is also replaced, the original interrupt handler is re-registered upon a soft reset.

    For reasons, the assembly instructions within the kernel I picked looked confusing (registers were getting copied and reused many times), so it is possible that I made mistakes. The code likely won't compile, but I did try to make the logic as accurate as possible. The sample was the kernel from ROM v1.01J.
     
    kozarovv, STLcardsWS and Berion like this.
  2. 796
    1,437
    247
    sp193

    sp193 Developer

    Joined:
    Oct 13, 2014
    Messages:
    796
    Likes Received:
    1,437
    Trophy Points:
    247
    Location:
    Singapore
    Home Page:
    I have tested the old ReleaseAlarm implementation, and it was really unable to release the correct alarm.

    There were also various minor differences in behaviour between the old version and the patch. For example, it is no longer possible to release an alarm that has triggered.

    Main implementation: https://github.com/ps2dev/ps2sdk/blob/master/ee/kernel/src/srcfile/src/alarm.c
    Dispatching functions: https://github.com/ps2dev/ps2sdk/blob/master/ee/kernel/src/srcfile/src/dispatch.s
    Entry point: https://github.com/ps2dev/ps2sdk/blob/master/ee/kernel/src/srcfile/src/srcfile.c
     
    kozarovv, STLcardsWS and Berion like this.

Share This Page