From b7fa8a246cba80d74c0993dd0275b3c1589ef8b6 Mon Sep 17 00:00:00 2001 From: Anup Patel Date: Sat, 25 Apr 2026 16:10:47 +0530 Subject: [PATCH] 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 Reviewed-by: Nicholas Piggin Link: https://lore.kernel.org/r/20260425104048.2335262-4-anup.patel@oss.qualcomm.com Signed-off-by: Anup Patel --- include/sbi/sbi_timer.h | 65 +++++++++++- lib/sbi/sbi_ecall_legacy.c | 4 +- lib/sbi/sbi_ecall_time.c | 4 +- lib/sbi/sbi_timer.c | 198 ++++++++++++++++++++++++++++++++++--- 4 files changed, 248 insertions(+), 23 deletions(-) diff --git a/include/sbi/sbi_timer.h b/include/sbi/sbi_timer.h index 2e1a7879..914a5f12 100644 --- a/include/sbi/sbi_timer.h +++ b/include/sbi/sbi_timer.h @@ -10,7 +10,60 @@ #ifndef __SBI_TIMER_H__ #define __SBI_TIMER_H__ -#include +#include + +/** 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 */ struct sbi_timer_device { @@ -86,8 +139,14 @@ void sbi_timer_set_delta(ulong delta); void sbi_timer_set_delta_upper(ulong delta_upper); #endif -/** Start timer event for current HART */ -void sbi_timer_event_start(u64 next_event); +/** Start timer event on current HART */ +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 */ void sbi_timer_process(void); diff --git a/lib/sbi/sbi_ecall_legacy.c b/lib/sbi/sbi_ecall_legacy.c index 50a7660d..4501c4ad 100644 --- a/lib/sbi/sbi_ecall_legacy.c +++ b/lib/sbi/sbi_ecall_legacy.c @@ -54,9 +54,9 @@ static int sbi_ecall_legacy_handler(unsigned long extid, unsigned long funcid, switch (extid) { case SBI_EXT_0_1_SET_TIMER: #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 - sbi_timer_event_start((u64)regs->a0); + sbi_timer_smode_event_start((u64)regs->a0); #endif break; case SBI_EXT_0_1_CONSOLE_PUTCHAR: diff --git a/lib/sbi/sbi_ecall_time.c b/lib/sbi/sbi_ecall_time.c index 6ea6f054..a0aa580d 100644 --- a/lib/sbi/sbi_ecall_time.c +++ b/lib/sbi/sbi_ecall_time.c @@ -22,9 +22,9 @@ static int sbi_ecall_time_handler(unsigned long extid, unsigned long funcid, if (funcid == SBI_EXT_TIME_SET_TIMER) { #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 - sbi_timer_event_start((u64)regs->a0); + sbi_timer_smode_event_start((u64)regs->a0); #endif } else ret = SBI_ENOTSUPP; diff --git a/lib/sbi/sbi_timer.c b/lib/sbi/sbi_timer.c index 3ad96b1d..70b8b5d2 100644 --- a/lib/sbi/sbi_timer.c +++ b/lib/sbi/sbi_timer.c @@ -10,9 +10,11 @@ #include #include #include +#include #include #include #include +#include #include #include #include @@ -20,6 +22,9 @@ struct timer_state { 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; @@ -136,8 +141,130 @@ void sbi_timer_set_delta_upper(ulong delta_upper) } #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); /** @@ -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)) { csr_write64(CSR_STIMECMP, next_event); - } else if (timer_dev && timer_dev->timer_event_start) { - timer_dev->timer_event_start(next_event); + } else { 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) { - csr_clear(CSR_MIE, MIP_MTIP); - /* - * 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); + struct timer_state *tstate = sbi_scratch_thishart_offset_ptr(timer_state_off); + struct sbi_timer_event_restart restart; + SBI_LIST_HEAD(restart_list); + struct sbi_timer_event *ev; + + spin_lock(&tstate->event_list_lock); + + 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) @@ -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->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) { 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) { - if (timer_dev && timer_dev->timer_event_stop) - timer_dev->timer_event_stop(); + struct timer_state *tstate = sbi_scratch_thishart_offset_ptr(timer_state_off); + struct sbi_timer_event *ev; - csr_clear(CSR_MIP, MIP_STIP); - csr_clear(CSR_MIE, MIP_MTIP); + spin_lock(&tstate->event_list_lock); + + 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); }