Hardware Timers: Difference between revisions
(→Configuration register: Refactor into tables) |
(Add changes based off 0.920 phymem table) |
||
(13 intermediate revisions by 2 users not shown) | |||
Line 1: | Line 1: | ||
The PSVita system embeds 8 Word timers (<code>SceWT0</code> to <code>SceWT7</code>) and 6 Longrange timers (<code>SceLT0</code> to <code>SceLT5</code>). The timers | The PSVita system embeds 8 Word timers (<code>SceWT0</code> to <code>SceWT7</code>) and 6 Longrange timers (<code>SceLT0</code> to <code>SceLT5</code>). 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]]. | Most timers are managed by [[SceSystimer]]. | ||
Line 13: | Line 13: | ||
|- | |- | ||
! Name !! Address !! Interrupt ID !! Usage | ! Name !! Address !! Interrupt ID !! Usage | ||
|- | |||
| <code>GT</code> || 0xE20B0000 || ? || Never used by the operating system | |||
|- | |- | ||
| SceLT0 / SceSystimerLongrangeTimer0 || 0xE20B1000 || 0x88 || Available for [[SceSystimer]] | | SceLT0 / SceSystimerLongrangeTimer0 || 0xE20B1000 || 0x88 || Available for [[SceSystimer]] | ||
Line 24: | Line 26: | ||
| SceLT4 / SceSystimerLongrangeTimer4 || 0xE20B5000 || 0x8C || Available for [[SceSystimer]] | | SceLT4 / SceSystimerLongrangeTimer4 || 0xE20B5000 || 0x8C || Available for [[SceSystimer]] | ||
|- | |- | ||
| SceLT5 || 0xE20B6000 || 0x8D || Used by [[SceKernelThreadMgr]] as CPU timer | | SceLT5 / SceThreadmgrTimer || 0xE20B6000 || 0x8D || Used by [[SceKernelThreadMgr]] as CPU timer | ||
|- | |- | ||
| SceWT0 / SceSystimerWordTimer0 || 0xE20B7000 || 0x80 || Available for [[SceSystimer]] | | SceWT0 / SceSystimerWordTimer0 || 0xE20B7000 || 0x80 || Available for [[SceSystimer]] | ||
Line 40: | Line 42: | ||
| SceWT6 / SceSystimerWordTimer6 || 0xE20BD000 || 0x86 || Available for [[SceSystimer]] | | SceWT6 / SceSystimerWordTimer6 || 0xE20BD000 || 0x86 || Available for [[SceSystimer]] | ||
|- | |- | ||
| SceWT7 / SceTimerForUsleep || 0xE20BE000 || 0x87 || Used by [[SceKernelIntrMgr#SceIntrmgrForTZS|Tzs SceKernelIntrMgr]] for [[SceKernelIntrMgr#sceKernelUsleepForTZS|usleep]] - ? | | SceWT7 / SceTimerForUsleep || 0xE20BE000 || 0x87 || Used by [[SceKernelIntrMgr#SceIntrmgrForTZS|Tzs SceKernelIntrMgr]] for [[SceKernelIntrMgr#sceKernelUsleepForTZS|usleep]] | ||
|} | |||
== Bus Error Registers == | |||
Called <code>TMBERR</code> in 0.920. This interface found at physical address <code>0xE20BF000</code> is used to manage Timer Bus Errors. | |||
{| class="wikitable" | |||
|+ Timer Bus Error 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: | |||
* <code>t = (prescale_factor + 1) / src_clk</code> in seconds (multiply by 10^9 for ns) | |||
* <code>f = 1 / t = src_clk / (prescale_factor+1)</code> in Hz (divide by 10^6 for MHz) | |||
<code>src_clk</code> is the frequency of the timer's input clock - see below for more information. | |||
{| class="wikitable" | |||
|+ 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 <code>clk_select == 2</code> | |||
|- | |||
| 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) | |||
|} | |||
{| class="wikitable" | |||
|+ Available input clocks | |||
|- | |||
! <code>clk_select</code> || Input clock | |||
|- | |||
| 0 || [[ScePower#scePowerSetSysClockFrequencyForDriver|ScePower SysClock]] (190/222MHz) | |||
|- | |||
| 1 || ~37MHz | |||
|- | |||
| 2 | |||
| Varies based on '''<code>clk2_ctrl</code>''' | |||
{| class="wikitable" | |||
! <code>clk2_ctrl</code> | |||
! | |||
|- | |||
| 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) | |||
|} | |||
{| class="wikitable" | |||
|+ 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 | |||
|} | |} | ||
Line 49: | Line 158: | ||
typedef volatile _SceWordTimer { | typedef volatile _SceWordTimer { | ||
SceUInt32 unk0; // Value at which an interrupt is triggered? | SceUInt32 unk0; // Value at which an interrupt is triggered? | ||
SceUInt32 | SceUInt32 current; // Current value of the timer's counter | ||
SceUInt32 | SceUInt32 cfg; // Configuration register | ||
SceUInt32 unkC; // Another counter? | SceUInt32 unkC; // Another counter? | ||
SceUInt32 unk10; // Unused? | SceUInt32 unk10; // Unused? | ||
SceUInt32 unk14; // Write 0x3 to clear | SceUInt32 unk14; // Interrupt status? Write 0x3 to clear pending IRQ? | ||
} SceWordTimer; | } SceWordTimer; | ||
</source> | </source> | ||
Line 65: | Line 169: | ||
Timers with 64-bit counters. | Timers with 64-bit counters. | ||
SceLT5 timer is configured to increment every microsecond (1MHz frequency). | |||
<source lang="C"> | <source lang="C"> | ||
typedef volatile _SceLongTimer { | typedef volatile _SceLongTimer { | ||
SceKernelSysClock | SceKernelSysClock current; // Current value of the timer counter | ||
SceKernelSysClock unk8; // Value at which an interrupt is triggered? | SceKernelSysClock unk8; // Value at which an interrupt is triggered? | ||
SceKernelSysClock unk10; // Another counter? | SceKernelSysClock unk10; // Another counter? | ||
SceUInt32 unk18; // Write 0x3 to clear | SceUInt32 unk18; // Interrupt status? Write 0x3 to clear pending IRQ? | ||
SceUInt32 | SceUInt32 cfg; // Configuration register | ||
} SceLongTimer; | } SceLongTimer; | ||
</source> | </source> | ||
NOTE: | NOTE: On ARMv7 processors that do not support the Large Physical Address Extension, such as the PS Vita's CPU, [https://developer.arm.com/documentation/ddi0406/cb/Application-Level-Architecture/Application-Level-Memory-Model/Memory-types-and-attributes-and-the-memory-order-model/Atomicity-in-the-ARM-architecture?lang=en 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. | 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: | To ensure the readings from a timer are accurate, use code similar to the following code: | ||
<source lang="C"> | <source lang="C"> | ||
//Return value should hopefully be optimized into registers | //Return value should hopefully be optimized into registers | ||
SceKernelSysClock readCounter(volatile SceKernelSysClock* | SceKernelSysClock readCounter(volatile SceKernelSysClock* pTimer) { | ||
SceKernelSysClock ctr; | SceKernelSysClock ctr; | ||
SceUInt32 ctrHi; | SceUInt32 ctrHi; | ||
do { | do { | ||
ctr.hi = | ctr.u.hi = pTimer->u.hi; //Read high word first | ||
ctr.lo = | ctr.u.lo = pTimer->u.lo; //Read low word next | ||
ctrHi = | ctrHi = pTimer->u.hi; //Read high word again to make sure no overflow happened | ||
} while ( | } while (ctr.hi != ctrHi); //Try again if high word changed while reading low word | ||
return ctr; | return ctr; | ||
Line 94: | Line 201: | ||
</source> | </source> | ||
== | == Global Timer == | ||
Located at physical address <code>0xE20B0000</code>. | |||
This name is derived from the abbreviation used to name it in 0.920 <code>sysmem.skprx</code> physical address table: <code>GT</code>. | |||
The Global Timer is a 64-bit timer with the following restrictions: | |||
* Fixed input clock selection (always SysClock) | |||
* Only prescale factor can be changed in the configuration register | |||
** '''All fields other than <code>prescale_factor</code> and <code>enable</code> are RAZ/WI''' | |||
* Cannot generate interrupts? | |||
<source lang="c"> | |||
typedef volatile _SceTopTimer { | |||
SceKernelSysClock current; // Current value of the timer counter | |||
unsigned RAZ_WI; //Read As Zero / Write Ignore | |||
SceUInt32 cfg; //Configuration register | |||
} SceHiddenTimer; | |||
</source> | |||
Latest revision as of 19:48, 22 February 2024
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.
Name | Address | Interrupt ID | Usage |
---|---|---|---|
GT |
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 / SceThreadmgrTimer | 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 |
Bus Error Registers
Called TMBERR
in 0.920. This interface found at physical address 0xE20BF000
is used to manage Timer Bus Errors.
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.
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) |
clk_select |
Input clock | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | ScePower SysClock (190/222MHz) | ||||||||||||
1 | ~37MHz | ||||||||||||
2 | Varies based on clk2_ctrl
| ||||||||||||
3 | 48MHz | ||||||||||||
4 | 60Hz | ||||||||||||
5 | ~35.6kHz | ||||||||||||
8 | 60Hz | ||||||||||||
9 | ~45kHz | ||||||||||||
6-7,A-F | No clock (timer doesn't tick) |
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; }
Global Timer
Located at physical address 0xE20B0000
.
This name is derived from the abbreviation used to name it in 0.920 sysmem.skprx
physical address table: GT
.
The Global Timer is a 64-bit timer with the following restrictions:
- Fixed input clock selection (always SysClock)
- Only prescale factor can be changed in the configuration register
- All fields other than
prescale_factor
andenable
are RAZ/WI
- All fields other than
- 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;