Skip to content

Commit

Permalink
soc/power9/timer.c: implement udelay() without interrupts
Browse files Browse the repository at this point in the history
Thanks to 'wait' instruction and exception definition on POWER it is
possible to use Decrementer Exception without actually implementing
Decrementer Interrupt handler. This greatly simplifies previous
implementation and makes it available to use in all stages.

Signed-off-by: Krystian Hebel <krystian.hebel@3mdeb.com>
Change-Id: I74cc95a503d4f3642609eff3b781f0b1c3168a3d
  • Loading branch information
krystian-hebel committed Nov 9, 2021
1 parent 5d80d66 commit d2ec14c
Showing 1 changed file with 17 additions and 106 deletions.
123 changes: 17 additions & 106 deletions src/soc/ibm/power9/timer.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
#include <delay.h>
#include <timer.h>
#include <timestamp.h>
#include <string.h> // memcpy
#include <cpu/power/spr.h>

/* Time base frequency is 512 MHz so 512 ticks per usec */
Expand All @@ -21,59 +20,8 @@ int timestamp_tick_freq_mhz(void)
return TB_TICKS_PER_USEC;
}

/*
* If udelay() were to be used in bootblock, a padding for interrupt vector must
* be added to memlayout.ld or bootblock itself.
*
* In ramstage, take care to not overwrite payload with handlers or vice versa.
*/
#if ENV_ROMSTAGE

volatile uint64_t hdec_done;

/*
* Interrupt handler - no stack and only one register (normally used by OS) can
* be repurposed. This is enough, for now...
*
* "When a Hypervisor Decrementer interrupt occurs, the existing Hypervisor
* Decrementer exception will cease to exist within a reasonable period of time,
* but not later than the completion of the next context synchronizing
* instruction or event."
*
* 'hrfid' is context synchronizing. Note that the above does not apply to
* non-Hypervisor Decrementer - the interrupt does not result in exception
* ceasing to exist:
*
* "When the contents of DEC[0] change from 1 to 0, the existing Decrementer
* exception, if any, will cease to exist within a reasonable period of
* time, but not later than the completion of the next context synchronizing
* instruction or event."
*/
static uint32_t hdec_handler_template[] = {
0xf9ad0000, /* std r13,0(r13) */
0x4c000224 /* hrfid */
};

void init_timer(void)
{
uint64_t tmp;

/*
* Initialize L3 cache for interrupt vectors. This could be done in
* bootblock, but then care must be taken to keep the loop properly aligned
* with regard to cache lines and different tagging schemes between levels
* of cache (real vs. effective address, different numbers of bits for index
* etc). Leaving this for romstage is safer and easier as there is no need
* for invalidation of currently running code. In this case, just two
* instructions in a loop are sufficient:
* 1. Data Cache Block set to Zero for 0..0x1000
* 2. Instruction Cache Block Invalidate for 0..0x1000 - single 'isync'
* before enabling interrupts by writing to MSR is enough (not defined
* by ISA but in POWER9 Processor User's Manual, 4.6.2.2)
*/
for (tmp = 0; tmp < 0x1000; tmp += 128)
asm volatile("dcbz 0, %0; icbi 0, %0;" :: "r"(tmp) : "memory");

/*
* Set both decrementers to the highest possible value. POWER9 implements
* 56 bits, they decrement with 512MHz frequency. Decrementer exception
Expand All @@ -85,82 +33,45 @@ void init_timer(void)
* Without it the counter overflows and generates an interrupt after ~4.2 s.
*/

tmp = read_spr(SPR_LPCR); /* LPCR */
/*
* 46 - LD - Large Decrementer (does not apply to HDEC)
* 59 - HEIC - Hypervisor External Interrupt Control
* 63 - HDICE - Hypervisor Decrementer Interrupt Conditionally Enable
*/
write_spr(SPR_LPCR, tmp | SPR_LPCR_LD | SPR_LPCR_HEIC | SPR_LPCR_HDICE);

write_spr(SPR_LPCR, read_spr(SPR_LPCR) | SPR_LPCR_LD);
write_spr(SPR_DEC, SPR_DEC_LONGEST_TIME);
write_spr(SPR_HDEC, SPR_DEC_LONGEST_TIME);

/* r13 is reserved for thread ID, we don't have threads so borrow it */
asm volatile("mr 13, %0" :: "r"(&hdec_done));

memcpy((void *)0x980, hdec_handler_template, sizeof(hdec_handler_template));

/*
* Other interrupts are enabled when MSR[48] = 1, make them halt.
*
* `b .` = 0x48000000
* `rfid` = 0x4c000024
* `hrfid` = 0x4c000224
*
* TODO: these interrupts shouldn't happen, we don't know how to handle
* them, maybe add a (destructive) handler that calls die()?
*/
*(uint32_t *)0x500 = 0x48000000; // External interrupt
*(uint32_t *)0xF00 = 0x48000000; // Performance monitor
*(uint32_t *)0xA00 = 0x48000000; // Privileged Doorbell
*(uint32_t *)0xE60 = 0x48000000; // Hypervisor Maintenance
*(uint32_t *)0xE80 = 0x48000000; // Hypervisor Doorbell

*(uint32_t *)0x900 = 0x48000000; // Decrementer

asm volatile("sync; isync" ::: "memory");

tmp = read_msr();
write_msr(tmp | 0x8000); /* EE - External Interrupt Enable */
}

void udelay(unsigned int usec)
{
uint64_t start = read_spr(SPR_TB);
uint64_t end = start + usec * TB_TICKS_PER_USEC;

hdec_done = 0;

/*
* HDEC interrupt is generated "within a reasonable period of time", but
* this may not be precise enough. Set an interrupt for 1us less than
* "When the contents of the DEC0 change from 0 to 1, a Decrementer
* exception will come into existence within a reasonable period of time",
* but this may not be precise enough. Set an interrupt for 1us less than
* requested and busy-loop the rest.
*
* In tests on Talos 2 this gives between 0 and 1/32 us more than requested,
* while interrupt only solution gave between 6/32 and 11/32 us more.
*/
if (usec > 1) {
write_spr(SPR_HDEC, (usec - 1) * TB_TICKS_PER_USEC);
write_spr(SPR_DEC, (usec - 1) * TB_TICKS_PER_USEC);
asm volatile("or 31,31,31"); // Lower priority

do {
asm volatile("wait");
} while(!hdec_done);

} while(read_spr(SPR_DEC) < SPR_DEC_LONGEST_TIME);

/*
* "When the contents of DEC0 change from 1 to 0, the existing
* Decrementer exception, if any, will cease to exist within a
* reasonable period of time, but not later than the completion of
* the next context synchronizing instruction or event" - last part
* of sentence doesn't matter, in worst case 'wait' in next udelay()
* will be executed more than once but this is still cheaper than
* synchronizing context explicitly.
*/
write_spr(SPR_DEC, SPR_DEC_LONGEST_TIME);
asm volatile("or 2,2,2"); // Back to normal priority
}

while (end > read_spr(SPR_TB));
}
#endif

#if ENV_RAMSTAGE

void udelay(unsigned int usec)
{
/* Active, but a delay */
(void)wait_us(usec, false);
}

#endif

0 comments on commit d2ec14c

Please sign in to comment.