Hardware Timers

From Vita Development Wiki
Revision as of 22:57, 11 August 2023 by CreepNT (talk | contribs) (Add top timer)
Jump to navigation Jump to search

The PSVita system embeds 8 Word timers (SceWT0 to SceWT7) and 6 Longrange timers (SceLT0 to SceLT5). The timers consist of a counter that gets incremented at a configurable time interval (multiple of a base frequency?), and can generate an interrupt when a certain value is reached.

Most timers are managed by SceSystimer.

Available timers

The Address column indicates the physical address at which the interface for a timer is located.

Each timer interface takes 0x1000 bytes.

List of timers
Name Address Interrupt ID Usage
? 0xE20B0000 ? Never used by the operating system
SceLT0 / SceSystimerLongrangeTimer0 0xE20B1000 0x88 Available for SceSystimer
SceLT1 / SceSystimerLongrangeTimer1 0xE20B2000 0x89 Available for SceSystimer
SceLT2 / SceSystimerLongrangeTimer2 0xE20B3000 0x8A Available for SceSystimer
SceLT3 / SceSystimerLongrangeTimer3 0xE20B4000 0x8B Available for SceSystimer
SceLT4 / SceSystimerLongrangeTimer4 0xE20B5000 0x8C Available for SceSystimer
SceLT5 0xE20B6000 0x8D Used by SceKernelThreadMgr as CPU timer
SceWT0 / SceSystimerWordTimer0 0xE20B7000 0x80 Available for SceSystimer
SceWT1 / SceSystimerWordTimer1 0xE20B8000 0x81 Available for SceSystimer
SceWT2 / SceSystimerWordTimer2 0xE20B9000 0x82 Available for SceSystimer
SceWT3 / SceSystimerWordTimer3 0xE20BA000 0x83 Available for SceSystimer
SceWT4 / SceSystimerWordTimer4 0xE20BB000 0x84 Available for SceSystimer
SceWT5 / SceSystimerWordTimer5 0xE20BC000 0x85 Available for SceSystimer
SceWT6 / SceSystimerWordTimer6 0xE20BD000 0x86 Available for SceSystimer
SceWT7 / SceTimerForUsleep 0xE20BE000 0x87 Used by Tzs SceKernelIntrMgr for usleep

Misc Reg

Miscellaneous features related to all timers can be accessed from an interface at physical address 0xE20BF000.

Timer Misc Reg interface
Offset Usage
0x0 Bus Error Address
0x4 Bus Error Attributes
0x8 Secure Bus Error Address
0xC Secure Bus Error Attributes

Configuration register

The configuration register is identical for all timers. The period of a Systimer tick is calculated using the following formulas:

  • t = (prescale_factor + 1) / src_clk in seconds (multiply by 10^9 for ns)
  • f = 1 / t = src_clk / (prescale_factor+1) in Hz (divide by 10^6 for MHz)

src_clk is the frequency of the timer's input clock - see below for more information.

Configuration register bit mappings
Mask Name Effect / Notes
0x00000001 enable 0: timer stopped - 1: timer counting
0x0000000E ?
0x00000070 ? ?Mode select?
0x00000080 ?
0x00000700 clk2_ctrl Changes clock if clk_select == 2
0x00000800 RAZ/WI Read As Zero / Write Ignore (always 0)
0x0000F000 ?
0x000F0000 ?
0x00F00000 clk_select Controls which clock is used as input to prescaler (see below)
0xFF000000 prescale_factor Input clock division factor (the higher this is, the slower the timer will tick)
Available input clocks
clk_select Input clock
0 ScePower SysClock (190/222MHz)
1 ~37MHz
2 Varies based on clk2_ctrl
clk2_ctrl
0 27MHz
2-3 ~24.57MHz
4 ~37.38MHz
5 ~74MHz
1,6,7 No clock (timer doesn't tick)
3 48MHz
4 60Hz
5 ~35.6kHz
8 60Hz
9 ~45kHz
6-7,A-F No clock (timer doesn't tick)
Known configurations
Timer Value Explaination
SceWT7 (SceTimerForUsleep) 0xDD00000D SysClock source / (prescale_factor = 221) -> 0.85/1MHz frequency
SceLT5 0x2F34500D 48MHz source / (prescale_factor = 47) -> 1MHz frequency

Word Timers

Timers with 32-bit counters.

typedef volatile _SceWordTimer {
    SceUInt32 unk0; // Value at which an interrupt is triggered?
    SceUInt32 current; // Current value of the timer's counter
    SceUInt32 cfg; // Configuration register
    SceUInt32 unkC; // Another counter?
    SceUInt32 unk10; // Unused?
    SceUInt32 unk14; // Interrupt status? Write 0x3 to clear pending IRQ?
} SceWordTimer;

Long Timers

Timers with 64-bit counters.

SceLT5 timer is configured to increment every microsecond (1MHz frequency).

typedef volatile _SceLongTimer {
    SceKernelSysClock current;  // Current value of the timer counter
    SceKernelSysClock unk8;  // Value at which an interrupt is triggered?
    SceKernelSysClock unk10; // Another counter?
    SceUInt32 unk18; // Interrupt status? Write 0x3 to clear pending IRQ?
    SceUInt32 cfg; // Configuration register
} SceLongTimer;

NOTE: On ARMv7 processors that do not support the Large Physical Address Extension, such as the PS Vita's CPU, 64-bit accesses are not guaranteed to be atomic.

This can lead to issues when reading the timer if the low word of a counter is about to overflow.

To ensure the readings from a timer are accurate, use code similar to the following code:

//Return value should hopefully be optimized into registers
SceKernelSysClock readCounter(volatile SceKernelSysClock* pTimer) {
   SceKernelSysClock ctr;
   SceUInt32 ctrHi;
   do {
       ctr.u.hi = pTimer->u.hi; //Read high word first
       ctr.u.lo = pTimer->u.lo; //Read low word next
       ctrHi = pTimer->u.hi;  //Read high word again to make sure no overflow happened
    } while (ctr.hi != ctrHi); //Try again if high word changed while reading low word

    return ctr;
}

Top Timer

Located at physical address 0xE20B0000. Called like this because it's the first one in Timer memory region.

The Top timer is a 64-bit timer with the following restrictions:

  • Cannot change the input clock (always SysClock)
  • Doesn't support configuration other than prescale factor
    • All fields other than prescale_factor and enable are RAZ/WI in this timer's configuration register
  • Cannot generate interrupts?
typedef volatile _SceTopTimer {
    SceKernelSysClock current; // Current value of the timer counter
    unsigned RAZ_WI;           //Read As Zero / Write Ignore
    SceUInt32 cfg;             //Configuration register
} SceHiddenTimer;