| /* |
| * SPDX-License-Identifier: BSD-2-Clause |
| * |
| * Copyright (c) 2023 Rivos Systems Inc. |
| * |
| */ |
| |
| #include <sbi/riscv_asm.h> |
| #include <sbi/riscv_barrier.h> |
| #include <sbi/riscv_encoding.h> |
| #include <sbi/riscv_locks.h> |
| #include <sbi/sbi_domain.h> |
| #include <sbi/sbi_ecall.h> |
| #include <sbi/sbi_ecall_interface.h> |
| #include <sbi/sbi_error.h> |
| #include <sbi/sbi_fifo.h> |
| #include <sbi/sbi_hart.h> |
| #include <sbi/sbi_heap.h> |
| #include <sbi/sbi_hsm.h> |
| #include <sbi/sbi_ipi.h> |
| #include <sbi/sbi_list.h> |
| #include <sbi/sbi_platform.h> |
| #include <sbi/sbi_pmu.h> |
| #include <sbi/sbi_sse.h> |
| #include <sbi/sbi_scratch.h> |
| #include <sbi/sbi_string.h> |
| #include <sbi/sbi_trap.h> |
| |
| #include <sbi/sbi_console.h> |
| |
| #define sse_get_hart_state_ptr(__scratch) \ |
| sbi_scratch_read_type((__scratch), void *, shs_ptr_off) |
| |
| #define sse_thishart_state_ptr() \ |
| sse_get_hart_state_ptr(sbi_scratch_thishart_ptr()) |
| |
| #define sse_set_hart_state_ptr(__scratch, __sse_state) \ |
| sbi_scratch_write_type((__scratch), void *, shs_ptr_off, (__sse_state)) |
| |
| #define EVENT_IS_GLOBAL(__event_id) ((__event_id) & SBI_SSE_EVENT_GLOBAL_BIT) |
| |
| static const uint32_t supported_events[] = { |
| SBI_SSE_EVENT_LOCAL_RAS, |
| SBI_SSE_EVENT_GLOBAL_RAS, |
| SBI_SSE_EVENT_LOCAL_PMU, |
| SBI_SSE_EVENT_LOCAL_SOFTWARE, |
| SBI_SSE_EVENT_GLOBAL_SOFTWARE, |
| }; |
| |
| #define EVENT_COUNT array_size(supported_events) |
| |
| #define sse_event_invoke_cb(_event, _cb, ...) \ |
| { \ |
| if (_event->cb_ops && _event->cb_ops->_cb) \ |
| _event->cb_ops->_cb(_event->event_id, ##__VA_ARGS__); \ |
| } |
| |
| struct sse_entry_state { |
| /** entry pc */ |
| unsigned long pc; |
| /** a6 register state */ |
| unsigned long arg; |
| }; |
| |
| struct sse_interrupted_state { |
| /** sepc register state */ |
| unsigned long sepc; |
| /** flags register state */ |
| unsigned long flags; |
| /** a6 register state */ |
| unsigned long a6; |
| /** a7 register state */ |
| unsigned long a7; |
| }; |
| |
| struct sse_ipi_inject_data { |
| uint32_t event_id; |
| }; |
| |
| struct sbi_sse_event_attrs { |
| unsigned long status; |
| unsigned long prio; |
| unsigned long config; |
| unsigned long hartid; |
| struct sse_entry_state entry; |
| struct sse_interrupted_state interrupted; |
| }; |
| |
| /* Make sure all attributes are packed for direct memcpy in ATTR_READ */ |
| #define assert_field_offset(field, attr_offset) \ |
| _Static_assert( \ |
| ((offsetof(struct sbi_sse_event_attrs, field)) / \ |
| sizeof(unsigned long)) == attr_offset, \ |
| "field " #field \ |
| " from struct sbi_sse_event_attrs invalid offset, expected " #attr_offset) |
| |
| assert_field_offset(status, SBI_SSE_ATTR_STATUS); |
| assert_field_offset(prio, SBI_SSE_ATTR_PRIO); |
| assert_field_offset(config, SBI_SSE_ATTR_CONFIG); |
| assert_field_offset(hartid, SBI_SSE_ATTR_PREFERRED_HART); |
| assert_field_offset(entry.pc, SBI_SSE_ATTR_ENTRY_PC); |
| assert_field_offset(entry.arg, SBI_SSE_ATTR_ENTRY_ARG); |
| assert_field_offset(interrupted.sepc, SBI_SSE_ATTR_INTERRUPTED_SEPC); |
| assert_field_offset(interrupted.flags, SBI_SSE_ATTR_INTERRUPTED_FLAGS); |
| assert_field_offset(interrupted.a6, SBI_SSE_ATTR_INTERRUPTED_A6); |
| assert_field_offset(interrupted.a7, SBI_SSE_ATTR_INTERRUPTED_A7); |
| |
| struct sbi_sse_event { |
| struct sbi_sse_event_attrs attrs; |
| uint32_t event_id; |
| const struct sbi_sse_cb_ops *cb_ops; |
| struct sbi_dlist node; |
| }; |
| |
| /** Per-hart state */ |
| struct sse_hart_state { |
| /* Priority sorted list of enabled events (global and local in >= |
| * ENABLED state). This list is protected by the enabled_event_lock. |
| * |
| * Global events can also be inserted in this list. Since these events |
| * can be accessed by all harts, we actually need to lock independently |
| * (see sse_global_event). |
| * |
| * Local events do not actually need to be locked since, we do |
| * not have preemption and there are solely accessed by the current |
| * hart. So when inserting a local event in this list, we just need to |
| * lock the list at insertion/removal time. |
| * |
| * When an event is in a state >= ENABLED, then it is inserted in the |
| * this enabled_event_list and thus can only be removed from this |
| * list upon disable ecall or on complete with ONESHOT flag. |
| */ |
| struct sbi_dlist enabled_event_list; |
| |
| /** |
| * Lock that protects enabled_event_list |
| */ |
| spinlock_t enabled_event_lock; |
| |
| /** |
| * List of local events allocated at boot time. |
| */ |
| struct sbi_sse_event *local_events; |
| }; |
| |
| /** |
| * Global events are accessible by all harts |
| */ |
| struct sse_global_event { |
| /** |
| * global event struct |
| */ |
| struct sbi_sse_event event; |
| |
| /** |
| * Global event lock protecting access from multiple harts from ecall to |
| * the event. |
| */ |
| spinlock_t lock; |
| }; |
| |
| static unsigned int local_event_count; |
| static unsigned int global_event_count; |
| static struct sse_global_event *global_events; |
| |
| static unsigned long sse_inject_fifo_off; |
| static unsigned long sse_inject_fifo_mem_off; |
| /* Offset of pointer to SSE HART state in scratch space */ |
| static unsigned long shs_ptr_off; |
| |
| static u32 sse_ipi_inject_event = SBI_IPI_EVENT_MAX; |
| |
| static int sse_ipi_inject_send(unsigned long hartid, uint32_t event_id); |
| |
| static unsigned long sse_event_state(struct sbi_sse_event *e) |
| { |
| return e->attrs.status & SBI_SSE_ATTR_STATUS_STATE_MASK; |
| } |
| |
| static unsigned long sse_event_pending(struct sbi_sse_event *e) |
| { |
| return !!(e->attrs.status & BIT(SBI_SSE_ATTR_STATUS_PENDING_OFFSET)); |
| } |
| |
| static bool sse_event_is_global(struct sbi_sse_event *e) |
| { |
| return EVENT_IS_GLOBAL(e->event_id); |
| } |
| |
| static bool sse_event_is_local(struct sbi_sse_event *e) |
| { |
| return !sse_event_is_global(e); |
| } |
| |
| /** |
| * If event is global, must be called under global event lock |
| */ |
| static struct sse_hart_state *sse_get_hart_state(struct sbi_sse_event *e) |
| { |
| struct sbi_scratch *s = sbi_hartid_to_scratch(e->attrs.hartid); |
| |
| return sse_get_hart_state_ptr(s); |
| } |
| |
| static struct sse_global_event *sse_get_global_event(struct sbi_sse_event *e) |
| { |
| return container_of(e, struct sse_global_event, event); |
| } |
| |
| /** |
| * If event is global, must be called under enabled event lock |
| */ |
| static void sse_enabled_event_lock(struct sbi_sse_event *e) |
| { |
| struct sse_hart_state *shs; |
| |
| shs = sse_get_hart_state(e); |
| spin_lock(&shs->enabled_event_lock); |
| } |
| |
| /** |
| * If event is global, must be called under enabled event lock |
| */ |
| static void sse_enabled_event_unlock(struct sbi_sse_event *e) |
| { |
| struct sse_hart_state *shs; |
| |
| shs = sse_get_hart_state(e); |
| spin_unlock(&shs->enabled_event_lock); |
| } |
| |
| static void sse_event_set_state(struct sbi_sse_event *e, |
| unsigned long new_state) |
| { |
| e->attrs.status &= ~SBI_SSE_ATTR_STATUS_STATE_MASK; |
| e->attrs.status |= new_state; |
| } |
| |
| static struct sbi_sse_event *sse_event_get(uint32_t event_id) |
| { |
| unsigned int i; |
| struct sbi_sse_event *e; |
| struct sse_hart_state *shs; |
| |
| if (EVENT_IS_GLOBAL(event_id)) { |
| for (i = 0; i < global_event_count; i++) { |
| e = &global_events[i].event; |
| if (e->event_id == event_id) { |
| spin_lock(&global_events[i].lock); |
| return e; |
| } |
| } |
| } else { |
| shs = sse_thishart_state_ptr(); |
| for (i = 0; i < local_event_count; i++) { |
| e = &shs->local_events[i]; |
| if (e->event_id == event_id) |
| return e; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| static void sse_event_put(struct sbi_sse_event *e) |
| { |
| struct sse_global_event *ge; |
| |
| if (sse_event_is_local(e)) |
| return; |
| |
| ge = sse_get_global_event(e); |
| spin_unlock(&ge->lock); |
| } |
| |
| static void sse_event_remove_from_list(struct sbi_sse_event *e) |
| { |
| sbi_list_del(&e->node); |
| } |
| |
| /** |
| * Must be called under owner hart lock |
| */ |
| static void sse_event_add_to_list(struct sbi_sse_event *e) |
| { |
| struct sse_hart_state *state = sse_get_hart_state(e); |
| struct sbi_sse_event *tmp; |
| |
| sbi_list_for_each_entry(tmp, &state->enabled_event_list, node) { |
| if (e->attrs.prio < tmp->attrs.prio) |
| break; |
| if (e->attrs.prio == tmp->attrs.prio && |
| e->event_id < tmp->event_id) |
| break; |
| } |
| sbi_list_add_tail(&e->node, &tmp->node); |
| } |
| |
| /** |
| * Must be called under owner hart lock |
| */ |
| static int sse_event_disable(struct sbi_sse_event *e) |
| { |
| if (sse_event_state(e) != SBI_SSE_STATE_ENABLED) |
| return SBI_EINVALID_STATE; |
| |
| sse_event_invoke_cb(e, disable_cb); |
| |
| sse_event_remove_from_list(e); |
| sse_event_set_state(e, SBI_SSE_STATE_REGISTERED); |
| |
| return SBI_OK; |
| } |
| |
| static int sse_event_set_hart_id_check(struct sbi_sse_event *e, |
| unsigned long new_hartid) |
| { |
| int hstate; |
| unsigned int hartid = (uint32_t)new_hartid; |
| struct sbi_domain *hd = sbi_domain_thishart_ptr(); |
| |
| if (!sse_event_is_global(e)) |
| return SBI_EBAD_RANGE; |
| |
| if (!sbi_domain_is_assigned_hart(hd, new_hartid)) |
| return SBI_EINVAL; |
| |
| hstate = sbi_hsm_hart_get_state(hd, hartid); |
| if (hstate != SBI_HSM_STATE_STARTED) |
| return SBI_EINVAL; |
| |
| return SBI_OK; |
| } |
| |
| static int sse_event_set_attr_check(struct sbi_sse_event *e, uint32_t attr_id, |
| unsigned long val) |
| { |
| switch (attr_id) { |
| case SBI_SSE_ATTR_CONFIG: |
| if (sse_event_state(e) >= SBI_SSE_STATE_ENABLED) |
| return SBI_EINVALID_STATE; |
| |
| if (val & ~SBI_SSE_ATTR_CONFIG_ONESHOT) |
| return SBI_EINVAL; |
| |
| return SBI_OK; |
| case SBI_SSE_ATTR_PRIO: |
| if (sse_event_state(e) >= SBI_SSE_STATE_ENABLED) |
| return SBI_EINVALID_STATE; |
| |
| #if __riscv_xlen > 32 |
| if (val != (uint32_t)val) |
| return SBI_EINVAL; |
| #endif |
| return SBI_OK; |
| case SBI_SSE_ATTR_PREFERRED_HART: |
| if (sse_event_state(e) >= SBI_SSE_STATE_ENABLED) |
| return SBI_EINVALID_STATE; |
| |
| return sse_event_set_hart_id_check(e, val); |
| case SBI_SSE_ATTR_INTERRUPTED_FLAGS: |
| if (val & ~(SBI_SSE_ATTR_INTERRUPTED_FLAGS_STATUS_SPP | |
| SBI_SSE_ATTR_INTERRUPTED_FLAGS_STATUS_SPIE | |
| SBI_SSE_ATTR_INTERRUPTED_FLAGS_HSTATUS_SPV | |
| SBI_SSE_ATTR_INTERRUPTED_FLAGS_HSTATUS_SPVP)) |
| return SBI_EINVAL; |
| __attribute__((__fallthrough__)); |
| case SBI_SSE_ATTR_INTERRUPTED_SEPC: |
| case SBI_SSE_ATTR_INTERRUPTED_A6: |
| case SBI_SSE_ATTR_INTERRUPTED_A7: |
| if (sse_event_state(e) != SBI_SSE_STATE_RUNNING) |
| return SBI_EINVALID_STATE; |
| |
| if (current_hartid() != e->attrs.hartid) |
| return SBI_EINVAL; |
| |
| return SBI_OK; |
| default: |
| return SBI_EBAD_RANGE; |
| } |
| } |
| |
| static void sse_event_set_attr(struct sbi_sse_event *e, uint32_t attr_id, |
| unsigned long val) |
| { |
| switch (attr_id) { |
| case SBI_SSE_ATTR_CONFIG: |
| e->attrs.config = val; |
| break; |
| case SBI_SSE_ATTR_PRIO: |
| e->attrs.prio = (uint32_t)val; |
| break; |
| case SBI_SSE_ATTR_PREFERRED_HART: |
| e->attrs.hartid = val; |
| sse_event_invoke_cb(e, set_hartid_cb, val); |
| break; |
| |
| case SBI_SSE_ATTR_INTERRUPTED_SEPC: |
| e->attrs.interrupted.sepc = val; |
| break; |
| case SBI_SSE_ATTR_INTERRUPTED_FLAGS: |
| e->attrs.interrupted.flags = val; |
| break; |
| case SBI_SSE_ATTR_INTERRUPTED_A6: |
| e->attrs.interrupted.a6 = val; |
| break; |
| case SBI_SSE_ATTR_INTERRUPTED_A7: |
| e->attrs.interrupted.a7 = val; |
| break; |
| } |
| } |
| |
| static int sse_event_register(struct sbi_sse_event *e, |
| unsigned long handler_entry_pc, |
| unsigned long handler_entry_arg) |
| { |
| if (sse_event_state(e) != SBI_SSE_STATE_UNUSED) |
| return SBI_EINVALID_STATE; |
| |
| e->attrs.entry.pc = handler_entry_pc; |
| e->attrs.entry.arg = handler_entry_arg; |
| |
| sse_event_set_state(e, SBI_SSE_STATE_REGISTERED); |
| |
| sse_event_invoke_cb(e, register_cb); |
| |
| return 0; |
| } |
| |
| static int sse_event_unregister(struct sbi_sse_event *e) |
| { |
| if (sse_event_state(e) != SBI_SSE_STATE_REGISTERED) |
| return SBI_EINVALID_STATE; |
| |
| sse_event_invoke_cb(e, unregister_cb); |
| |
| sse_event_set_state(e, SBI_SSE_STATE_UNUSED); |
| |
| return 0; |
| } |
| |
| static unsigned long sse_interrupted_flags(unsigned long mstatus) |
| { |
| unsigned long hstatus, flags = 0; |
| |
| if (mstatus & (MSTATUS_SPIE)) |
| flags |= SBI_SSE_ATTR_INTERRUPTED_FLAGS_STATUS_SPIE; |
| if (mstatus & (MSTATUS_SPP)) |
| flags |= SBI_SSE_ATTR_INTERRUPTED_FLAGS_STATUS_SPP; |
| |
| if (misa_extension('H')) { |
| hstatus = csr_read(CSR_HSTATUS); |
| if (hstatus & HSTATUS_SPV) |
| flags |= SBI_SSE_ATTR_INTERRUPTED_FLAGS_HSTATUS_SPV; |
| if (hstatus & HSTATUS_SPVP) |
| flags |= SBI_SSE_ATTR_INTERRUPTED_FLAGS_HSTATUS_SPVP; |
| } |
| |
| return flags; |
| } |
| |
| static void sse_event_inject(struct sbi_sse_event *e, |
| struct sbi_trap_regs *regs) |
| { |
| struct sse_interrupted_state *i_ctx = &e->attrs.interrupted; |
| |
| sse_event_set_state(e, SBI_SSE_STATE_RUNNING); |
| |
| e->attrs.status = ~BIT(SBI_SSE_ATTR_STATUS_PENDING_OFFSET); |
| |
| i_ctx->a6 = regs->a6; |
| i_ctx->a7 = regs->a7; |
| i_ctx->flags = sse_interrupted_flags(regs->mstatus); |
| i_ctx->sepc = csr_read(CSR_SEPC); |
| |
| regs->mstatus &= ~(MSTATUS_SPP | SSTATUS_SPIE); |
| if (regs->mstatus & MSTATUS_MPP) |
| regs->mstatus |= MSTATUS_SPP; |
| if (regs->mstatus & MSTATUS_SIE) |
| regs->mstatus |= MSTATUS_SPIE; |
| |
| if (misa_extension('H')) { |
| unsigned long hstatus = csr_read(CSR_HSTATUS); |
| |
| #if __riscv_xlen == 64 |
| if (regs->mstatus & MSTATUS_MPV) |
| #elif __riscv_xlen == 32 |
| if (regs->mstatusH & MSTATUSH_MPV) |
| #else |
| #error "Unexpected __riscv_xlen" |
| #endif |
| hstatus |= HSTATUS_SPV; |
| |
| hstatus &= ~HSTATUS_SPVP; |
| if (hstatus & HSTATUS_SPV && regs->mstatus & SSTATUS_SPP) |
| hstatus |= HSTATUS_SPVP; |
| |
| csr_write(CSR_HSTATUS, hstatus); |
| } |
| csr_write(CSR_SEPC, regs->mepc); |
| |
| /* Setup entry context */ |
| regs->a6 = e->attrs.entry.arg; |
| regs->a7 = current_hartid(); |
| regs->mepc = e->attrs.entry.pc; |
| |
| /* Return to S-mode with virtualization disabled */ |
| regs->mstatus &= ~(MSTATUS_MPP | MSTATUS_SIE); |
| regs->mstatus |= (PRV_S << MSTATUS_MPP_SHIFT); |
| |
| #if __riscv_xlen == 64 |
| regs->mstatus &= ~MSTATUS_MPV; |
| #elif __riscv_xlen == 32 |
| regs->mstatusH &= ~MSTATUSH_MPV; |
| #else |
| #error "Unexpected __riscv_xlen" |
| #endif |
| |
| } |
| |
| static void sse_event_resume(struct sbi_sse_event *e, |
| struct sbi_trap_regs *regs) |
| { |
| struct sse_interrupted_state *i_ctx = &e->attrs.interrupted; |
| |
| regs->mepc = csr_read(CSR_SEPC); |
| |
| regs->mstatus &= ~MSTATUS_MPP; |
| if (regs->mstatus & MSTATUS_SPP) |
| regs->mstatus |= (PRV_S << MSTATUS_MPP_SHIFT); |
| |
| if (misa_extension('H')) { |
| unsigned long hstatus = csr_read(CSR_HSTATUS); |
| #if __riscv_xlen == 64 |
| regs->mstatus &= ~MSTATUS_MPV; |
| if (hstatus & HSTATUS_SPV) |
| regs->mstatus |= MSTATUS_MPV; |
| #elif __riscv_xlen == 32 |
| regs->mstatusH &= ~MSTATUSH_MPV; |
| if (hstatus & HSTATUS_SPV) |
| regs->mstatusH |= MSTATUSH_MPV; |
| #else |
| #error "Unexpected __riscv_xlen" |
| #endif |
| hstatus &= ~(HSTATUS_SPV | HSTATUS_SPVP); |
| if (i_ctx->flags & SBI_SSE_ATTR_INTERRUPTED_FLAGS_HSTATUS_SPV) |
| hstatus |= HSTATUS_SPV; |
| |
| if (i_ctx->flags & SBI_SSE_ATTR_INTERRUPTED_FLAGS_HSTATUS_SPVP) |
| hstatus |= HSTATUS_SPVP; |
| |
| csr_write(CSR_HSTATUS, hstatus); |
| } |
| |
| regs->mstatus &= ~MSTATUS_SIE; |
| if (regs->mstatus & MSTATUS_SPIE) |
| regs->mstatus |= MSTATUS_SIE; |
| |
| regs->mstatus &= ~MSTATUS_SPIE; |
| if (i_ctx->flags & SBI_SSE_ATTR_INTERRUPTED_FLAGS_STATUS_SPIE) |
| regs->mstatus |= MSTATUS_SPIE; |
| |
| regs->mstatus &= ~MSTATUS_SPP; |
| if (i_ctx->flags & SBI_SSE_ATTR_INTERRUPTED_FLAGS_STATUS_SPP) |
| regs->mstatus |= MSTATUS_SPP; |
| |
| regs->a7 = i_ctx->a7; |
| regs->a6 = i_ctx->a6; |
| csr_write(CSR_SEPC, i_ctx->sepc); |
| } |
| |
| static bool sse_event_is_ready(struct sbi_sse_event *e) |
| { |
| if (!sse_event_pending(e) || |
| sse_event_state(e) != SBI_SSE_STATE_ENABLED || |
| e->attrs.hartid != current_hartid()) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| static bool sse_event_check_inject(struct sbi_sse_event *e, |
| struct sbi_trap_regs *regs) |
| { |
| /* |
| * List of event is ordered by priority, stop at first running |
| * event since all other events after this one are of lower |
| * priority. This means an event of higher priority is already |
| * running. |
| */ |
| if (sse_event_state(e) == SBI_SSE_STATE_RUNNING) { |
| return true; |
| } |
| |
| if (sse_event_is_ready(e)) { |
| sse_event_inject(e, regs); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /* Return true if an event has been injected, false otherwise */ |
| void sbi_sse_process_pending_events(struct sbi_trap_regs *regs) |
| { |
| bool ret; |
| struct sbi_sse_event *e; |
| struct sse_hart_state *state = sse_thishart_state_ptr(); |
| |
| spin_lock(&state->enabled_event_lock); |
| |
| sbi_list_for_each_entry(e, &state->enabled_event_list, node) { |
| ret = sse_event_check_inject(e, regs); |
| if (ret) |
| goto out; |
| } |
| |
| out: |
| spin_unlock(&state->enabled_event_lock); |
| } |
| |
| static int sse_event_set_pending(struct sbi_sse_event *e) |
| { |
| if (sse_event_state(e) != SBI_SSE_STATE_RUNNING && |
| sse_event_state(e) != SBI_SSE_STATE_ENABLED) |
| return SBI_EINVALID_STATE; |
| |
| e->attrs.status |= BIT(SBI_SSE_ATTR_STATUS_PENDING_OFFSET); |
| |
| return SBI_OK; |
| } |
| |
| static void sse_ipi_inject_process(struct sbi_scratch *scratch) |
| { |
| struct sbi_sse_event *e; |
| struct sse_ipi_inject_data evt; |
| struct sbi_fifo *sse_inject_fifo_r = |
| sbi_scratch_offset_ptr(scratch, sse_inject_fifo_off); |
| |
| /* Mark all queued events as pending */ |
| while (!sbi_fifo_dequeue(sse_inject_fifo_r, &evt)) { |
| e = sse_event_get(evt.event_id); |
| if (!e) |
| continue; |
| |
| sse_event_set_pending(e); |
| sse_event_put(e); |
| } |
| } |
| |
| static struct sbi_ipi_event_ops sse_ipi_inject_ops = { |
| .name = "IPI_SSE_INJECT", |
| .process = sse_ipi_inject_process, |
| }; |
| |
| static int sse_ipi_inject_send(unsigned long hartid, uint32_t event_id) |
| { |
| int ret; |
| struct sbi_scratch *remote_scratch = NULL; |
| struct sse_ipi_inject_data evt = {event_id}; |
| struct sbi_fifo *sse_inject_fifo_r; |
| |
| remote_scratch = sbi_hartid_to_scratch(hartid); |
| if (!remote_scratch) |
| return SBI_EINVAL; |
| sse_inject_fifo_r = |
| sbi_scratch_offset_ptr(remote_scratch, sse_inject_fifo_off); |
| |
| ret = sbi_fifo_enqueue(sse_inject_fifo_r, &evt); |
| if (ret) |
| return SBI_EFAIL; |
| |
| ret = sbi_ipi_send_many(1, hartid, sse_ipi_inject_event, NULL); |
| if (ret) |
| return SBI_EFAIL; |
| |
| return SBI_OK; |
| } |
| |
| static int sse_inject_event(uint32_t event_id, unsigned long hartid, |
| struct sbi_ecall_return *out) |
| { |
| int ret; |
| struct sbi_sse_event *e; |
| |
| e = sse_event_get(event_id); |
| if (!e) |
| return SBI_EINVAL; |
| |
| |
| /* In case of global event, provided hart_id is ignored */ |
| if (sse_event_is_global(e)) |
| hartid = e->attrs.hartid; |
| |
| /* Event is for another hart, send it through IPI */ |
| if (hartid != current_hartid()) { |
| sse_event_put(e); |
| return sse_ipi_inject_send(hartid, event_id); |
| } |
| |
| ret = sse_event_set_pending(e); |
| sse_event_put(e); |
| if (ret) |
| return ret; |
| |
| return SBI_OK; |
| } |
| |
| /** |
| * Must be called under owner hart lock |
| */ |
| static int sse_event_enable(struct sbi_sse_event *e) |
| { |
| if (sse_event_state(e) != SBI_SSE_STATE_REGISTERED) |
| return SBI_EINVALID_STATE; |
| |
| sse_event_set_state(e, SBI_SSE_STATE_ENABLED); |
| sse_event_add_to_list(e); |
| |
| sse_event_invoke_cb(e, enable_cb); |
| |
| if (sse_event_is_global(e) && sse_event_pending(e)) |
| sbi_ipi_send_many(1, e->attrs.hartid, sse_ipi_inject_event, |
| NULL); |
| |
| return SBI_OK; |
| } |
| |
| static int sse_event_complete(struct sbi_sse_event *e, |
| struct sbi_trap_regs *regs, |
| struct sbi_ecall_return *out) |
| { |
| if (sse_event_state(e) != SBI_SSE_STATE_RUNNING) |
| return SBI_EINVALID_STATE; |
| |
| if (e->attrs.hartid != current_hartid()) |
| return SBI_EINVAL; |
| |
| sse_event_set_state(e, SBI_SSE_STATE_ENABLED); |
| if (e->attrs.config & SBI_SSE_ATTR_CONFIG_ONESHOT) |
| sse_event_disable(e); |
| |
| sse_event_invoke_cb(e, complete_cb); |
| |
| sse_event_resume(e, regs); |
| out->skip_regs_update = true; |
| |
| return SBI_OK; |
| } |
| |
| int sbi_sse_complete(struct sbi_trap_regs *regs, struct sbi_ecall_return *out) |
| { |
| int ret = SBI_OK; |
| struct sbi_sse_event *tmp; |
| struct sse_hart_state *state = sse_thishart_state_ptr(); |
| |
| spin_lock(&state->enabled_event_lock); |
| sbi_list_for_each_entry(tmp, &state->enabled_event_list, node) { |
| /* |
| * List of event is ordered by priority, first one running is |
| * the one that needs to be completed |
| */ |
| if (sse_event_state(tmp) == SBI_SSE_STATE_RUNNING) { |
| ret = sse_event_complete(tmp, regs, out); |
| break; |
| } |
| } |
| spin_unlock(&state->enabled_event_lock); |
| |
| return ret; |
| } |
| |
| int sbi_sse_enable(uint32_t event_id) |
| { |
| int ret; |
| struct sbi_sse_event *e; |
| |
| e = sse_event_get(event_id); |
| if (!e) |
| return SBI_EINVAL; |
| |
| sse_enabled_event_lock(e); |
| ret = sse_event_enable(e); |
| sse_enabled_event_unlock(e); |
| sse_event_put(e); |
| |
| return ret; |
| } |
| |
| int sbi_sse_disable(uint32_t event_id) |
| { |
| int ret; |
| struct sbi_sse_event *e; |
| |
| e = sse_event_get(event_id); |
| if (!e) |
| return SBI_EINVAL; |
| |
| sse_enabled_event_lock(e); |
| ret = sse_event_disable(e); |
| sse_enabled_event_unlock(e); |
| |
| sse_event_put(e); |
| |
| return ret; |
| } |
| |
| int sbi_sse_inject_from_ecall(uint32_t event_id, unsigned long hartid, |
| struct sbi_ecall_return *out) |
| { |
| if (!sbi_domain_is_assigned_hart(sbi_domain_thishart_ptr(), hartid)) |
| return SBI_EINVAL; |
| |
| return sse_inject_event(event_id, hartid, out); |
| } |
| |
| int sbi_sse_inject_event(uint32_t event_id) |
| { |
| /* We don't really care about return value here */ |
| struct sbi_ecall_return out; |
| |
| return sse_inject_event(event_id, current_hartid(), &out); |
| } |
| |
| int sbi_sse_set_cb_ops(uint32_t event_id, const struct sbi_sse_cb_ops *cb_ops) |
| { |
| struct sbi_sse_event *e; |
| |
| if (cb_ops->set_hartid_cb && !EVENT_IS_GLOBAL(event_id)) |
| return SBI_EINVAL; |
| |
| e = sse_event_get(event_id); |
| if (!e) |
| return SBI_EINVAL; |
| |
| e->cb_ops = cb_ops; |
| sse_event_put(e); |
| |
| return SBI_OK; |
| } |
| |
| int sbi_sse_attr_check(uint32_t base_attr_id, uint32_t attr_count, |
| unsigned long phys_lo, unsigned long phys_hi, |
| unsigned long access) |
| { |
| const unsigned align = __riscv_xlen >> 3; |
| uint64_t end_id = (uint64_t)base_attr_id + (attr_count - 1); |
| |
| if (attr_count == 0) |
| return SBI_ERR_INVALID_PARAM; |
| |
| if (end_id >= SBI_SSE_ATTR_MAX) |
| return SBI_EBAD_RANGE; |
| |
| if (phys_lo & (align - 1)) |
| return SBI_EINVALID_ADDR; |
| |
| /* |
| * On RV32, the M-mode can only access the first 4GB of |
| * the physical address space because M-mode does not have |
| * MMU to access full 34-bit physical address space. |
| * |
| * Based on above, we simply fail if the upper 32bits of |
| * the physical address (i.e. a2 register) is non-zero on |
| * RV32. |
| */ |
| if (phys_hi) |
| return SBI_EINVALID_ADDR; |
| |
| if (!sbi_domain_check_addr_range(sbi_domain_thishart_ptr(), phys_lo, |
| sizeof(unsigned long) * attr_count, |
| PRV_S, access)) |
| return SBI_EINVALID_ADDR; |
| |
| return SBI_OK; |
| } |
| |
| static void copy_attrs(unsigned long *out, const unsigned long *in, |
| unsigned int long_count) |
| { |
| int i = 0; |
| |
| /* |
| * sbi_memcpy() does byte-per-byte copy, using this yields long-per-long |
| * copy |
| */ |
| for (i = 0; i < long_count; i++) |
| out[i] = in[i]; |
| } |
| |
| int sbi_sse_read_attrs(uint32_t event_id, uint32_t base_attr_id, |
| uint32_t attr_count, unsigned long output_phys_lo, |
| unsigned long output_phys_hi) |
| { |
| int ret; |
| unsigned long *e_attrs; |
| struct sbi_sse_event *e; |
| unsigned long *attrs; |
| |
| ret = sbi_sse_attr_check(base_attr_id, attr_count, output_phys_lo, |
| output_phys_hi, SBI_DOMAIN_WRITE); |
| if (ret) |
| return ret; |
| |
| e = sse_event_get(event_id); |
| if (!e) |
| return SBI_EINVAL; |
| |
| sbi_hart_map_saddr(output_phys_lo, sizeof(unsigned long) * attr_count); |
| |
| /* |
| * Copy all attributes at once since struct sse_event_attrs is matching |
| * the SBI_SSE_ATTR_* attributes. READ_ATTR is used in SSE handling path |
| * to retrieve the value of registers when interrupted. Rather than |
| * doing multiple SBI calls a single one is done allowing to retrieve |
| * them all at once. |
| */ |
| e_attrs = (unsigned long *)&e->attrs; |
| attrs = (unsigned long *)output_phys_lo; |
| copy_attrs(attrs, &e_attrs[base_attr_id], attr_count); |
| |
| sbi_hart_unmap_saddr(); |
| |
| sse_event_put(e); |
| |
| return SBI_OK; |
| } |
| |
| static int sse_write_attrs(struct sbi_sse_event *e, uint32_t base_attr_id, |
| uint32_t attr_count, unsigned long input_phys) |
| { |
| int ret = 0; |
| unsigned long attr = 0, val; |
| uint32_t id, end_id = base_attr_id + attr_count; |
| unsigned long *attrs = (unsigned long *)input_phys; |
| |
| sbi_hart_map_saddr(input_phys, sizeof(unsigned long) * attr_count); |
| |
| for (id = base_attr_id; id < end_id; id++) { |
| val = attrs[attr++]; |
| ret = sse_event_set_attr_check(e, id, val); |
| if (ret) |
| goto out; |
| } |
| |
| attr = 0; |
| for (id = base_attr_id; id < end_id; id++) { |
| val = attrs[attr++]; |
| sse_event_set_attr(e, id, val); |
| } |
| |
| out: |
| sbi_hart_unmap_saddr(); |
| |
| return ret; |
| } |
| |
| int sbi_sse_write_attrs(uint32_t event_id, uint32_t base_attr_id, |
| uint32_t attr_count, unsigned long input_phys_lo, |
| unsigned long input_phys_hi) |
| { |
| int ret = 0; |
| struct sbi_sse_event *e; |
| |
| ret = sbi_sse_attr_check(base_attr_id, attr_count, input_phys_lo, |
| input_phys_hi, SBI_DOMAIN_READ); |
| if (ret) |
| return ret; |
| |
| e = sse_event_get(event_id); |
| if (!e) |
| return SBI_EINVAL; |
| |
| ret = sse_write_attrs(e, base_attr_id, attr_count, input_phys_lo); |
| sse_event_put(e); |
| |
| return ret; |
| } |
| |
| int sbi_sse_register(uint32_t event_id, unsigned long handler_entry_pc, |
| unsigned long handler_entry_arg) |
| { |
| int ret; |
| struct sbi_sse_event *e; |
| |
| if (handler_entry_pc & 0x1) |
| return SBI_EINVAL; |
| |
| if (!sbi_domain_check_addr_range(sbi_domain_thishart_ptr(), |
| handler_entry_pc, |
| sizeof(unsigned long), PRV_S, |
| SBI_DOMAIN_EXECUTE)) |
| return SBI_EINVALID_ADDR; |
| |
| e = sse_event_get(event_id); |
| if (!e) |
| return SBI_EINVAL; |
| |
| ret = sse_event_register(e, handler_entry_pc, handler_entry_arg); |
| sse_event_put(e); |
| |
| return ret; |
| } |
| |
| int sbi_sse_unregister(uint32_t event_id) |
| { |
| int ret; |
| struct sbi_sse_event *e; |
| |
| e = sse_event_get(event_id); |
| if (!e) |
| return SBI_EINVAL; |
| |
| ret = sse_event_unregister(e); |
| sse_event_put(e); |
| |
| return ret; |
| } |
| |
| static void sse_event_init(struct sbi_sse_event *e, uint32_t event_id) |
| { |
| e->event_id = event_id; |
| e->attrs.hartid = current_hartid(); |
| /* Declare all events as injectable */ |
| e->attrs.status |= BIT(SBI_SSE_ATTR_STATUS_INJECT_OFFSET); |
| } |
| |
| static void sse_event_count_init() |
| { |
| unsigned int i; |
| |
| for (i = 0; i < EVENT_COUNT; i++) { |
| if (EVENT_IS_GLOBAL(supported_events[i])) |
| global_event_count++; |
| else |
| local_event_count++; |
| } |
| } |
| |
| static int sse_global_init() |
| { |
| struct sbi_sse_event *e; |
| unsigned int i, ev = 0; |
| |
| global_events = sbi_zalloc(sizeof(*global_events) * global_event_count); |
| if (!global_events) |
| return SBI_ENOMEM; |
| |
| for (i = 0; i < EVENT_COUNT; i++) { |
| if (!EVENT_IS_GLOBAL(supported_events[i])) |
| continue; |
| |
| e = &global_events[ev].event; |
| sse_event_init(e, supported_events[i]); |
| SPIN_LOCK_INIT(global_events[ev].lock); |
| |
| ev++; |
| } |
| |
| return 0; |
| } |
| |
| static void sse_local_init(struct sse_hart_state *shs) |
| { |
| unsigned int i, ev = 0; |
| |
| SBI_INIT_LIST_HEAD(&shs->enabled_event_list); |
| SPIN_LOCK_INIT(shs->enabled_event_lock); |
| |
| for (i = 0; i < EVENT_COUNT; i++) { |
| if (EVENT_IS_GLOBAL(supported_events[i])) |
| continue; |
| |
| sse_event_init(&shs->local_events[ev++], supported_events[i]); |
| } |
| } |
| |
| int sbi_sse_init(struct sbi_scratch *scratch, bool cold_boot) |
| { |
| int ret; |
| void *sse_inject_mem; |
| struct sse_hart_state *shs; |
| struct sbi_fifo *sse_inject_q; |
| |
| if (cold_boot) { |
| sse_event_count_init(); |
| |
| ret = sse_global_init(); |
| if (ret) |
| return ret; |
| |
| shs_ptr_off = sbi_scratch_alloc_offset(sizeof(void *)); |
| if (!shs_ptr_off) |
| return SBI_ENOMEM; |
| |
| sse_inject_fifo_off = |
| sbi_scratch_alloc_offset(sizeof(*sse_inject_q)); |
| if (!sse_inject_fifo_off) { |
| sbi_scratch_free_offset(shs_ptr_off); |
| return SBI_ENOMEM; |
| } |
| |
| sse_inject_fifo_mem_off = sbi_scratch_alloc_offset( |
| EVENT_COUNT * sizeof(struct sse_ipi_inject_data)); |
| if (!sse_inject_fifo_mem_off) { |
| sbi_scratch_free_offset(sse_inject_fifo_off); |
| sbi_scratch_free_offset(shs_ptr_off); |
| return SBI_ENOMEM; |
| } |
| |
| ret = sbi_ipi_event_create(&sse_ipi_inject_ops); |
| if (ret < 0) { |
| sbi_scratch_free_offset(shs_ptr_off); |
| return ret; |
| } |
| sse_ipi_inject_event = ret; |
| } |
| |
| shs = sse_get_hart_state_ptr(scratch); |
| if (!shs) { |
| /* Allocate per hart state and local events at once */ |
| shs = sbi_zalloc(sizeof(*shs) + sizeof(struct sbi_sse_event) * |
| local_event_count); |
| if (!shs) |
| return SBI_ENOMEM; |
| |
| shs->local_events = (struct sbi_sse_event *)(shs + 1); |
| |
| sse_set_hart_state_ptr(scratch, shs); |
| } |
| |
| sse_local_init(shs); |
| |
| sse_inject_q = sbi_scratch_offset_ptr(scratch, sse_inject_fifo_off); |
| sse_inject_mem = |
| sbi_scratch_offset_ptr(scratch, sse_inject_fifo_mem_off); |
| |
| sbi_fifo_init(sse_inject_q, sse_inject_mem, EVENT_COUNT, |
| sizeof(struct sse_ipi_inject_data)); |
| |
| return 0; |
| } |
| |
| void sbi_sse_exit(struct sbi_scratch *scratch) |
| { |
| int i; |
| struct sbi_sse_event *e; |
| |
| for (i = 0; i < EVENT_COUNT; i++) { |
| e = sse_event_get(supported_events[i]); |
| if (!e) |
| continue; |
| |
| if (e->attrs.hartid != current_hartid()) |
| goto skip; |
| |
| if (sse_event_state(e) > SBI_SSE_STATE_REGISTERED) { |
| sbi_printf("Event %d in invalid state at exit", i); |
| sse_event_set_state(e, SBI_SSE_STATE_UNUSED); |
| } |
| |
| skip: |
| sse_event_put(e); |
| } |
| } |