mirror of
https://github.com/riscv-software-src/opensbi.git
synced 2026-05-23 14:21:32 +01:00
lib: sbi_timer: Add support for timer events
Currently, the sbi_timer only supports timer events configured via SBI calls. Introduce struct sbi_timer_event and related functions to allow configuring timer events from any part of OpenSBI. Signed-off-by: Anup Patel <anup.patel@oss.qualcomm.com> Reviewed-by: Nicholas Piggin <npiggin@gmail.com> Link: https://lore.kernel.org/r/20260425104048.2335262-4-anup.patel@oss.qualcomm.com Signed-off-by: Anup Patel <anup@brainfault.org>
This commit is contained in:
+62
-3
@@ -10,7 +10,60 @@
|
|||||||
#ifndef __SBI_TIMER_H__
|
#ifndef __SBI_TIMER_H__
|
||||||
#define __SBI_TIMER_H__
|
#define __SBI_TIMER_H__
|
||||||
|
|
||||||
#include <sbi/sbi_types.h>
|
#include <sbi/sbi_list.h>
|
||||||
|
|
||||||
|
/** Timer event re-start details */
|
||||||
|
struct sbi_timer_event_restart {
|
||||||
|
/** Flag indicating whether event re-start is required */
|
||||||
|
bool required;
|
||||||
|
/** Next time stamp for event if re-start is required */
|
||||||
|
u64 next_event;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Timer event abstraction */
|
||||||
|
struct sbi_timer_event {
|
||||||
|
/** List head for per-HART event list (Internal) */
|
||||||
|
struct sbi_dlist head;
|
||||||
|
|
||||||
|
/** Hart on which the event is started / running (Internal) */
|
||||||
|
int hart_index;
|
||||||
|
|
||||||
|
/** Time stamp when the event expires (Internal) */
|
||||||
|
u64 time_stamp;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event callback to be called upon expiry.
|
||||||
|
*
|
||||||
|
* If the callback wants to re-start the event then
|
||||||
|
* it must update the event re-start details.
|
||||||
|
*
|
||||||
|
* NOTE: This will be called with the per-HART timer
|
||||||
|
* event list lock held.
|
||||||
|
*/
|
||||||
|
void (*callback)(struct sbi_timer_event *ev,
|
||||||
|
struct sbi_timer_event_restart *restart);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event cleanup to be called upon sbi_timer_exit()
|
||||||
|
*
|
||||||
|
* NOTE: This will be called with per-HART timer
|
||||||
|
* event list lock held.
|
||||||
|
*/
|
||||||
|
void (*cleanup)(struct sbi_timer_event *ev);
|
||||||
|
|
||||||
|
/** Event specific private data */
|
||||||
|
void *priv;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define SBI_INIT_TIMER_EVENT(__ptr, __callback, __cleanup, __priv) \
|
||||||
|
do { \
|
||||||
|
SBI_INIT_LIST_HEAD(&(__ptr)->head); \
|
||||||
|
(__ptr)->hart_index = -1; \
|
||||||
|
(__ptr)->time_stamp = 0; \
|
||||||
|
(__ptr)->callback = (__callback); \
|
||||||
|
(__ptr)->cleanup = (__cleanup); \
|
||||||
|
(__ptr)->priv = (__priv); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
/** Timer hardware device */
|
/** Timer hardware device */
|
||||||
struct sbi_timer_device {
|
struct sbi_timer_device {
|
||||||
@@ -86,8 +139,14 @@ void sbi_timer_set_delta(ulong delta);
|
|||||||
void sbi_timer_set_delta_upper(ulong delta_upper);
|
void sbi_timer_set_delta_upper(ulong delta_upper);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/** Start timer event for current HART */
|
/** Start timer event on current HART */
|
||||||
void sbi_timer_event_start(u64 next_event);
|
void sbi_timer_event_start(struct sbi_timer_event *ev, u64 next_event);
|
||||||
|
|
||||||
|
/** Stop timer event on current HART */
|
||||||
|
void sbi_timer_event_stop(struct sbi_timer_event *ev);
|
||||||
|
|
||||||
|
/** Start supervisor timer event on current HART */
|
||||||
|
void sbi_timer_smode_event_start(u64 next_event);
|
||||||
|
|
||||||
/** Process timer event for current HART */
|
/** Process timer event for current HART */
|
||||||
void sbi_timer_process(void);
|
void sbi_timer_process(void);
|
||||||
|
|||||||
@@ -54,9 +54,9 @@ static int sbi_ecall_legacy_handler(unsigned long extid, unsigned long funcid,
|
|||||||
switch (extid) {
|
switch (extid) {
|
||||||
case SBI_EXT_0_1_SET_TIMER:
|
case SBI_EXT_0_1_SET_TIMER:
|
||||||
#if __riscv_xlen == 32
|
#if __riscv_xlen == 32
|
||||||
sbi_timer_event_start((((u64)regs->a1 << 32) | (u64)regs->a0));
|
sbi_timer_smode_event_start((((u64)regs->a1 << 32) | (u64)regs->a0));
|
||||||
#else
|
#else
|
||||||
sbi_timer_event_start((u64)regs->a0);
|
sbi_timer_smode_event_start((u64)regs->a0);
|
||||||
#endif
|
#endif
|
||||||
break;
|
break;
|
||||||
case SBI_EXT_0_1_CONSOLE_PUTCHAR:
|
case SBI_EXT_0_1_CONSOLE_PUTCHAR:
|
||||||
|
|||||||
@@ -22,9 +22,9 @@ static int sbi_ecall_time_handler(unsigned long extid, unsigned long funcid,
|
|||||||
|
|
||||||
if (funcid == SBI_EXT_TIME_SET_TIMER) {
|
if (funcid == SBI_EXT_TIME_SET_TIMER) {
|
||||||
#if __riscv_xlen == 32
|
#if __riscv_xlen == 32
|
||||||
sbi_timer_event_start((((u64)regs->a1 << 32) | (u64)regs->a0));
|
sbi_timer_smode_event_start((((u64)regs->a1 << 32) | (u64)regs->a0));
|
||||||
#else
|
#else
|
||||||
sbi_timer_event_start((u64)regs->a0);
|
sbi_timer_smode_event_start((u64)regs->a0);
|
||||||
#endif
|
#endif
|
||||||
} else
|
} else
|
||||||
ret = SBI_ENOTSUPP;
|
ret = SBI_ENOTSUPP;
|
||||||
|
|||||||
+182
-16
@@ -10,9 +10,11 @@
|
|||||||
#include <sbi/riscv_asm.h>
|
#include <sbi/riscv_asm.h>
|
||||||
#include <sbi/riscv_barrier.h>
|
#include <sbi/riscv_barrier.h>
|
||||||
#include <sbi/riscv_encoding.h>
|
#include <sbi/riscv_encoding.h>
|
||||||
|
#include <sbi/riscv_locks.h>
|
||||||
#include <sbi/sbi_console.h>
|
#include <sbi/sbi_console.h>
|
||||||
#include <sbi/sbi_error.h>
|
#include <sbi/sbi_error.h>
|
||||||
#include <sbi/sbi_hart.h>
|
#include <sbi/sbi_hart.h>
|
||||||
|
#include <sbi/sbi_hartmask.h>
|
||||||
#include <sbi/sbi_platform.h>
|
#include <sbi/sbi_platform.h>
|
||||||
#include <sbi/sbi_pmu.h>
|
#include <sbi/sbi_pmu.h>
|
||||||
#include <sbi/sbi_scratch.h>
|
#include <sbi/sbi_scratch.h>
|
||||||
@@ -20,6 +22,9 @@
|
|||||||
|
|
||||||
struct timer_state {
|
struct timer_state {
|
||||||
u64 time_delta;
|
u64 time_delta;
|
||||||
|
spinlock_t event_list_lock;
|
||||||
|
struct sbi_dlist event_list;
|
||||||
|
struct sbi_timer_event smode_ev;
|
||||||
};
|
};
|
||||||
|
|
||||||
static unsigned long timer_state_off;
|
static unsigned long timer_state_off;
|
||||||
@@ -136,8 +141,130 @@ void sbi_timer_set_delta_upper(ulong delta_upper)
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void sbi_timer_event_start(u64 next_event)
|
static void __sbi_timer_update_device(struct timer_state *tstate)
|
||||||
{
|
{
|
||||||
|
struct sbi_timer_event *ev;
|
||||||
|
|
||||||
|
if (!timer_dev)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (sbi_list_empty(&tstate->event_list)) {
|
||||||
|
if (timer_dev->timer_event_stop)
|
||||||
|
timer_dev->timer_event_stop();
|
||||||
|
csr_clear(CSR_MIE, MIP_MTIP);
|
||||||
|
} else {
|
||||||
|
ev = sbi_list_first_entry(&tstate->event_list, struct sbi_timer_event, head);
|
||||||
|
if (timer_dev->timer_event_start)
|
||||||
|
timer_dev->timer_event_start(ev->time_stamp);
|
||||||
|
csr_set(CSR_MIE, MIP_MTIP);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void __sbi_timer_event_stop(struct sbi_timer_event *ev)
|
||||||
|
{
|
||||||
|
if (ev->hart_index > -1) {
|
||||||
|
sbi_list_del(&ev->head);
|
||||||
|
ev->hart_index = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void __sbi_timer_event_start(struct timer_state *tstate,
|
||||||
|
struct sbi_timer_event *ev, u64 next_event)
|
||||||
|
{
|
||||||
|
struct sbi_timer_event *tev, *next_ev = NULL;
|
||||||
|
|
||||||
|
/* Find where to insert the event in per-HART event list */
|
||||||
|
sbi_list_for_each_entry(tev, &tstate->event_list, head) {
|
||||||
|
if (next_event < tev->time_stamp) {
|
||||||
|
next_ev = tev;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Insert the event in per-HART event list */
|
||||||
|
ev->hart_index = current_hartindex();
|
||||||
|
ev->time_stamp = next_event;
|
||||||
|
if (next_ev)
|
||||||
|
sbi_list_add(&ev->head, &next_ev->head);
|
||||||
|
else
|
||||||
|
sbi_list_add_tail(&ev->head, &tstate->event_list);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sbi_timer_event_start(struct sbi_timer_event *ev, u64 next_event)
|
||||||
|
{
|
||||||
|
struct timer_state *tstate;
|
||||||
|
|
||||||
|
if (!ev)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* Ensure that event is not on the per-HART event list */
|
||||||
|
if (ev->hart_index > -1) {
|
||||||
|
tstate = sbi_scratch_offset_ptr(sbi_hartindex_to_scratch(ev->hart_index),
|
||||||
|
timer_state_off);
|
||||||
|
spin_lock(&tstate->event_list_lock);
|
||||||
|
__sbi_timer_event_stop(ev);
|
||||||
|
spin_unlock(&tstate->event_list_lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
tstate = sbi_scratch_thishart_offset_ptr(timer_state_off);
|
||||||
|
spin_lock(&tstate->event_list_lock);
|
||||||
|
|
||||||
|
__sbi_timer_event_start(tstate, ev, next_event);
|
||||||
|
__sbi_timer_update_device(tstate);
|
||||||
|
|
||||||
|
spin_unlock(&tstate->event_list_lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sbi_timer_event_stop(struct sbi_timer_event *ev)
|
||||||
|
{
|
||||||
|
struct timer_state *tstate;
|
||||||
|
int ev_hart_index;
|
||||||
|
|
||||||
|
if (!ev)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* Ensure that event is not on the per-HART event list */
|
||||||
|
ev_hart_index = ev->hart_index;
|
||||||
|
if (ev->hart_index > -1) {
|
||||||
|
tstate = sbi_scratch_offset_ptr(sbi_hartindex_to_scratch(ev->hart_index),
|
||||||
|
timer_state_off);
|
||||||
|
spin_lock(&tstate->event_list_lock);
|
||||||
|
__sbi_timer_event_stop(ev);
|
||||||
|
spin_unlock(&tstate->event_list_lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Re-program timer device on the current HART */
|
||||||
|
if (ev_hart_index == current_hartindex()) {
|
||||||
|
tstate = sbi_scratch_thishart_offset_ptr(timer_state_off);
|
||||||
|
spin_lock(&tstate->event_list_lock);
|
||||||
|
__sbi_timer_update_device(tstate);
|
||||||
|
spin_unlock(&tstate->event_list_lock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void sbi_timer_smode_event_callback(struct sbi_timer_event *ev,
|
||||||
|
struct sbi_timer_event_restart *restart)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* If sstc extension is available, supervisor can receive the timer
|
||||||
|
* directly without M-mode come in between. This function should
|
||||||
|
* only invoked if M-mode programs the timer for its own purpose.
|
||||||
|
*/
|
||||||
|
if (!sbi_hart_has_extension(sbi_scratch_thishart_ptr(), SBI_HART_EXT_SSTC))
|
||||||
|
csr_set(CSR_MIP, MIP_STIP);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void sbi_timer_smode_event_cleanup(struct sbi_timer_event *ev)
|
||||||
|
{
|
||||||
|
if (!sbi_hart_has_extension(sbi_scratch_thishart_ptr(), SBI_HART_EXT_SSTC))
|
||||||
|
csr_clear(CSR_MIP, MIP_STIP);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sbi_timer_smode_event_start(u64 next_event)
|
||||||
|
{
|
||||||
|
struct timer_state *tstate = sbi_scratch_offset_ptr(sbi_scratch_thishart_ptr(),
|
||||||
|
timer_state_off);
|
||||||
|
|
||||||
sbi_pmu_ctr_incr_fw(SBI_PMU_FW_SET_TIMER);
|
sbi_pmu_ctr_incr_fw(SBI_PMU_FW_SET_TIMER);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -146,23 +273,47 @@ void sbi_timer_event_start(u64 next_event)
|
|||||||
*/
|
*/
|
||||||
if (sbi_hart_has_extension(sbi_scratch_thishart_ptr(), SBI_HART_EXT_SSTC)) {
|
if (sbi_hart_has_extension(sbi_scratch_thishart_ptr(), SBI_HART_EXT_SSTC)) {
|
||||||
csr_write64(CSR_STIMECMP, next_event);
|
csr_write64(CSR_STIMECMP, next_event);
|
||||||
} else if (timer_dev && timer_dev->timer_event_start) {
|
} else {
|
||||||
timer_dev->timer_event_start(next_event);
|
|
||||||
csr_clear(CSR_MIP, MIP_STIP);
|
csr_clear(CSR_MIP, MIP_STIP);
|
||||||
|
sbi_timer_event_start(&tstate->smode_ev, next_event);
|
||||||
}
|
}
|
||||||
csr_set(CSR_MIE, MIP_MTIP);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void sbi_timer_process(void)
|
void sbi_timer_process(void)
|
||||||
{
|
{
|
||||||
csr_clear(CSR_MIE, MIP_MTIP);
|
struct timer_state *tstate = sbi_scratch_thishart_offset_ptr(timer_state_off);
|
||||||
/*
|
struct sbi_timer_event_restart restart;
|
||||||
* If sstc extension is available, supervisor can receive the timer
|
SBI_LIST_HEAD(restart_list);
|
||||||
* directly without M-mode come in between. This function should
|
struct sbi_timer_event *ev;
|
||||||
* only invoked if M-mode programs the timer for its own purpose.
|
|
||||||
*/
|
spin_lock(&tstate->event_list_lock);
|
||||||
if (!sbi_hart_has_extension(sbi_scratch_thishart_ptr(), SBI_HART_EXT_SSTC))
|
|
||||||
csr_set(CSR_MIP, MIP_STIP);
|
while (!sbi_list_empty(&tstate->event_list)) {
|
||||||
|
ev = sbi_list_first_entry(&tstate->event_list, struct sbi_timer_event, head);
|
||||||
|
if (ev->time_stamp > sbi_timer_value())
|
||||||
|
break;
|
||||||
|
|
||||||
|
__sbi_timer_event_stop(ev);
|
||||||
|
if (ev->callback) {
|
||||||
|
restart.required = false;
|
||||||
|
restart.next_event = 0;
|
||||||
|
ev->callback(ev, &restart);
|
||||||
|
if (restart.required) {
|
||||||
|
ev->time_stamp = restart.next_event;
|
||||||
|
sbi_list_add_tail(&ev->head, &restart_list);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (!sbi_list_empty(&restart_list)) {
|
||||||
|
ev = sbi_list_first_entry(&tstate->event_list, struct sbi_timer_event, head);
|
||||||
|
sbi_list_del(&ev->head);
|
||||||
|
__sbi_timer_event_start(tstate, ev, ev->time_stamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
__sbi_timer_update_device(tstate);
|
||||||
|
|
||||||
|
spin_unlock(&tstate->event_list_lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
const struct sbi_timer_device *sbi_timer_get_device(void)
|
const struct sbi_timer_device *sbi_timer_get_device(void)
|
||||||
@@ -204,6 +355,11 @@ int sbi_timer_init(struct sbi_scratch *scratch, bool cold_boot)
|
|||||||
|
|
||||||
tstate = sbi_scratch_offset_ptr(scratch, timer_state_off);
|
tstate = sbi_scratch_offset_ptr(scratch, timer_state_off);
|
||||||
tstate->time_delta = 0;
|
tstate->time_delta = 0;
|
||||||
|
SPIN_LOCK_INIT(tstate->event_list_lock);
|
||||||
|
SBI_INIT_LIST_HEAD(&tstate->event_list);
|
||||||
|
SBI_INIT_TIMER_EVENT(&tstate->smode_ev,
|
||||||
|
sbi_timer_smode_event_callback,
|
||||||
|
sbi_timer_smode_event_cleanup, NULL);
|
||||||
|
|
||||||
if (timer_dev && timer_dev->warm_init) {
|
if (timer_dev && timer_dev->warm_init) {
|
||||||
ret = timer_dev->warm_init();
|
ret = timer_dev->warm_init();
|
||||||
@@ -216,9 +372,19 @@ int sbi_timer_init(struct sbi_scratch *scratch, bool cold_boot)
|
|||||||
|
|
||||||
void sbi_timer_exit(struct sbi_scratch *scratch)
|
void sbi_timer_exit(struct sbi_scratch *scratch)
|
||||||
{
|
{
|
||||||
if (timer_dev && timer_dev->timer_event_stop)
|
struct timer_state *tstate = sbi_scratch_thishart_offset_ptr(timer_state_off);
|
||||||
timer_dev->timer_event_stop();
|
struct sbi_timer_event *ev;
|
||||||
|
|
||||||
csr_clear(CSR_MIP, MIP_STIP);
|
spin_lock(&tstate->event_list_lock);
|
||||||
csr_clear(CSR_MIE, MIP_MTIP);
|
|
||||||
|
while (!sbi_list_empty(&tstate->event_list)) {
|
||||||
|
ev = sbi_list_first_entry(&tstate->event_list, struct sbi_timer_event, head);
|
||||||
|
__sbi_timer_event_stop(ev);
|
||||||
|
if (ev->cleanup)
|
||||||
|
ev->cleanup(ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
__sbi_timer_update_device(tstate);
|
||||||
|
|
||||||
|
spin_unlock(&tstate->event_list_lock);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user