| /* |
| * SPDX-License-Identifier: BSD-2-Clause |
| * |
| * Copyright (c) 2023 Ventana Micro Systems, Inc. |
| * |
| * Author(s): |
| * Himanshu Chauhan <hchauhan@ventanamicro.com> |
| */ |
| |
| #include <sbi/sbi_ecall_interface.h> |
| #include <sbi/sbi_csr_detect.h> |
| #include <sbi/sbi_platform.h> |
| #include <sbi/sbi_byteorder.h> |
| #include <sbi/sbi_console.h> |
| #include <sbi/sbi_domain.h> |
| #include <sbi/sbi_trap.h> |
| #include <sbi/sbi_dbtr.h> |
| #include <sbi/sbi_heap.h> |
| #include <sbi/riscv_encoding.h> |
| #include <sbi/riscv_asm.h> |
| |
| |
| /** Offset of pointer to HART's debug triggers info in scratch space */ |
| static unsigned long hart_state_ptr_offset; |
| |
| #define dbtr_get_hart_state_ptr(__scratch) \ |
| sbi_scratch_read_type((__scratch), void *, hart_state_ptr_offset) |
| |
| #define dbtr_thishart_state_ptr() \ |
| dbtr_get_hart_state_ptr(sbi_scratch_thishart_ptr()) |
| |
| #define dbtr_set_hart_state_ptr(__scratch, __hart_state) \ |
| sbi_scratch_write_type((__scratch), void *, hart_state_ptr_offset, \ |
| (__hart_state)) |
| |
| #define INDEX_TO_TRIGGER(_index) \ |
| ({ \ |
| struct sbi_dbtr_trigger *__trg = NULL; \ |
| struct sbi_dbtr_hart_triggers_state *__hs = NULL; \ |
| __hs = dbtr_get_hart_state_ptr(sbi_scratch_thishart_ptr()); \ |
| __trg = &__hs->triggers[_index]; \ |
| (__trg); \ |
| }) |
| |
| #define for_each_trig_entry(_base, _max, _etype, _entry) \ |
| for (int _idx = 0; _entry = ((_etype *)_base + _idx), \ |
| _idx < _max; \ |
| _idx++, _entry = ((_etype *)_base + _idx)) |
| |
| #if __riscv_xlen == 64 |
| #define DBTR_SHMEM_MAKE_PHYS(_p_hi, _p_lo) (_p_lo) |
| #elif __riscv_xlen == 32 |
| #define DBTR_SHMEM_MAKE_PHYS(_p_hi, _p_lo) (((u64)(_p_hi) << 32) | (_p_lo)) |
| #else |
| #error "Undefined XLEN" |
| #endif |
| |
| static inline int sbi_dbtr_shmem_disabled(void) |
| { |
| struct sbi_dbtr_hart_triggers_state *hs = NULL; |
| |
| hs = dbtr_get_hart_state_ptr(sbi_scratch_thishart_ptr()); |
| |
| if (!hs) |
| return 1; |
| |
| return (hs->shmem.phys_lo == SBI_DBTR_SHMEM_INVALID_ADDR && |
| hs->shmem.phys_hi == SBI_DBTR_SHMEM_INVALID_ADDR |
| ? 1 : 0); |
| } |
| |
| static inline void sbi_dbtr_disable_shmem(void) |
| { |
| struct sbi_dbtr_hart_triggers_state *hs = NULL; |
| |
| hs = dbtr_get_hart_state_ptr(sbi_scratch_thishart_ptr()); |
| |
| if (!hs) |
| return; |
| |
| hs->shmem.phys_lo = SBI_DBTR_SHMEM_INVALID_ADDR; |
| hs->shmem.phys_hi = SBI_DBTR_SHMEM_INVALID_ADDR; |
| } |
| |
| static inline void *hart_shmem_base(void) |
| { |
| unsigned long phys_hi, phys_lo; |
| struct sbi_dbtr_hart_triggers_state *hs = NULL; |
| |
| hs = dbtr_get_hart_state_ptr(sbi_scratch_thishart_ptr()); |
| if (!hs) |
| return NULL; |
| |
| phys_hi = hs->shmem.phys_hi; |
| phys_lo = hs->shmem.phys_lo; |
| |
| if (phys_hi == SBI_DBTR_SHMEM_INVALID_ADDR && phys_hi == phys_lo) |
| return NULL; |
| |
| return ((void *)(unsigned long)DBTR_SHMEM_MAKE_PHYS(phys_hi, phys_lo)); |
| } |
| |
| static void sbi_trigger_init(struct sbi_dbtr_trigger *trig, |
| unsigned long type_mask, unsigned long idx) |
| { |
| trig->type_mask = type_mask; |
| trig->state = 0; |
| trig->tdata1 = 0; |
| trig->tdata2 = 0; |
| trig->tdata3 = 0; |
| trig->index = idx; |
| } |
| |
| static inline struct sbi_dbtr_trigger *sbi_alloc_trigger(void) |
| { |
| int i; |
| struct sbi_dbtr_trigger *f_trig = NULL; |
| struct sbi_dbtr_hart_triggers_state *hart_state; |
| |
| hart_state = dbtr_thishart_state_ptr(); |
| if (!hart_state) |
| return NULL; |
| |
| if (hart_state->available_trigs <= 0) |
| return NULL; |
| |
| for (i = 0; i < hart_state->total_trigs; i++) { |
| f_trig = INDEX_TO_TRIGGER(i); |
| if (f_trig->state & RV_DBTR_BIT_MASK(TS, MAPPED)) |
| continue; |
| hart_state->available_trigs--; |
| break; |
| } |
| |
| if (i == hart_state->total_trigs) |
| return NULL; |
| |
| __set_bit(RV_DBTR_BIT(TS, MAPPED), &f_trig->state); |
| |
| return f_trig; |
| } |
| |
| static inline void sbi_free_trigger(struct sbi_dbtr_trigger *trig) |
| { |
| struct sbi_dbtr_hart_triggers_state *hart_state; |
| |
| if (trig == NULL) |
| return; |
| |
| hart_state = dbtr_thishart_state_ptr(); |
| if (!hart_state) |
| return; |
| |
| trig->state = 0; |
| trig->tdata1 = 0; |
| trig->tdata2 = 0; |
| trig->tdata3 = 0; |
| |
| hart_state->available_trigs++; |
| } |
| |
| int sbi_dbtr_init(struct sbi_scratch *scratch, bool coldboot) |
| { |
| struct sbi_trap_info trap = {0}; |
| unsigned long tdata1; |
| unsigned long val; |
| int i; |
| struct sbi_dbtr_hart_triggers_state *hart_state = NULL; |
| |
| if (!sbi_hart_has_extension(scratch, SBI_HART_EXT_SDTRIG)) |
| return SBI_SUCCESS; |
| |
| if (coldboot) { |
| hart_state_ptr_offset = sbi_scratch_alloc_type_offset(void *); |
| if (!hart_state_ptr_offset) |
| return SBI_ENOMEM; |
| } |
| |
| hart_state = dbtr_get_hart_state_ptr(scratch); |
| if (!hart_state) { |
| hart_state = sbi_zalloc(sizeof(*hart_state)); |
| if (!hart_state) |
| return SBI_ENOMEM; |
| hart_state->hartid = current_hartid(); |
| dbtr_set_hart_state_ptr(scratch, hart_state); |
| } |
| |
| /* disable the shared memory */ |
| sbi_dbtr_disable_shmem(); |
| |
| /* Skip probing triggers if already probed */ |
| if (hart_state->probed) |
| goto _probed; |
| |
| for (i = 0; i < RV_MAX_TRIGGERS; i++) { |
| csr_write_allowed(CSR_TSELECT, (ulong)&trap, i); |
| if (trap.cause) |
| break; |
| |
| val = csr_read_allowed(CSR_TSELECT, (ulong)&trap); |
| if (trap.cause) |
| break; |
| |
| /* |
| * Read back tselect and check that it contains the |
| * written value |
| */ |
| if (val != i) |
| break; |
| |
| val = csr_read_allowed(CSR_TINFO, (ulong)&trap); |
| if (trap.cause) { |
| /* |
| * If reading tinfo caused an exception, the |
| * debugger must read tdata1 to discover the |
| * type. |
| */ |
| tdata1 = csr_read_allowed(CSR_TDATA1, |
| (ulong)&trap); |
| if (trap.cause) |
| break; |
| |
| if (TDATA1_GET_TYPE(tdata1) == 0) |
| break; |
| |
| sbi_trigger_init(INDEX_TO_TRIGGER(i), |
| BIT(TDATA1_GET_TYPE(tdata1)), |
| i); |
| hart_state->total_trigs++; |
| } else { |
| if (val == 1) |
| break; |
| |
| sbi_trigger_init(INDEX_TO_TRIGGER(i), val, i); |
| hart_state->total_trigs++; |
| } |
| } |
| |
| hart_state->probed = 1; |
| |
| _probed: |
| hart_state->available_trigs = hart_state->total_trigs; |
| |
| return SBI_SUCCESS; |
| } |
| |
| int sbi_dbtr_get_total_triggers(void) |
| { |
| struct sbi_dbtr_hart_triggers_state *hs; |
| struct sbi_scratch *scratch = sbi_scratch_thishart_ptr(); |
| |
| /* |
| * This function may be used during ecall registration. |
| * By that time the debug trigger module might not be |
| * initialized. If the extension is not supported, report |
| * number of triggers as 0. |
| */ |
| if (!sbi_hart_has_extension(scratch, SBI_HART_EXT_SDTRIG)) |
| return 0; |
| |
| hs = dbtr_thishart_state_ptr(); |
| if (!hs) |
| return 0; |
| |
| return hs->total_trigs; |
| } |
| |
| int sbi_dbtr_setup_shmem(const struct sbi_domain *dom, unsigned long smode, |
| unsigned long shmem_phys_lo, |
| unsigned long shmem_phys_hi) |
| { |
| u32 hartid = current_hartid(); |
| struct sbi_dbtr_hart_triggers_state *hart_state; |
| |
| if (dom && !sbi_domain_is_assigned_hart(dom, hartid)) { |
| sbi_dprintf("%s: calling hart not assigned to this domain\n", |
| __func__); |
| return SBI_ERR_DENIED; |
| } |
| |
| /* call is to disable shared memory */ |
| if (shmem_phys_lo == SBI_DBTR_SHMEM_INVALID_ADDR |
| && shmem_phys_hi == SBI_DBTR_SHMEM_INVALID_ADDR) { |
| sbi_dbtr_disable_shmem(); |
| return SBI_SUCCESS; |
| } |
| |
| /* the shared memory must be disabled on this hart */ |
| if (!sbi_dbtr_shmem_disabled()) |
| return SBI_ERR_ALREADY_AVAILABLE; |
| |
| /* lower physical address must be XLEN/8 bytes aligned */ |
| if (shmem_phys_lo & SBI_DBTR_SHMEM_ALIGN_MASK) |
| return SBI_ERR_INVALID_PARAM; |
| |
| if (dom && !sbi_domain_check_addr(dom, |
| DBTR_SHMEM_MAKE_PHYS(shmem_phys_hi, shmem_phys_lo), smode, |
| SBI_DOMAIN_READ | SBI_DOMAIN_WRITE)) |
| return SBI_ERR_INVALID_ADDRESS; |
| |
| hart_state = dbtr_thishart_state_ptr(); |
| if (!hart_state) |
| return SBI_ERR_FAILED; |
| |
| hart_state->shmem.phys_lo = shmem_phys_lo; |
| hart_state->shmem.phys_hi = shmem_phys_hi; |
| |
| return SBI_SUCCESS; |
| } |
| |
| static void dbtr_trigger_setup(struct sbi_dbtr_trigger *trig, |
| struct sbi_dbtr_data_msg *recv) |
| { |
| unsigned long tdata1; |
| |
| if (!trig) |
| return; |
| |
| trig->tdata1 = lle_to_cpu(recv->tdata1); |
| trig->tdata2 = lle_to_cpu(recv->tdata2); |
| trig->tdata3 = lle_to_cpu(recv->tdata3); |
| |
| tdata1 = lle_to_cpu(recv->tdata1); |
| |
| trig->state = 0; |
| |
| __set_bit(RV_DBTR_BIT(TS, MAPPED), &trig->state); |
| |
| SET_TRIG_HW_INDEX(trig->state, trig->index); |
| |
| switch (TDATA1_GET_TYPE(tdata1)) { |
| case RISCV_DBTR_TRIG_MCONTROL: |
| if (__test_bit(RV_DBTR_BIT(MC, U), &tdata1)) |
| __set_bit(RV_DBTR_BIT(TS, U), &trig->state); |
| |
| if (__test_bit(RV_DBTR_BIT(MC, S), &tdata1)) |
| __set_bit(RV_DBTR_BIT(TS, S), &trig->state); |
| break; |
| case RISCV_DBTR_TRIG_MCONTROL6: |
| if (__test_bit(RV_DBTR_BIT(MC6, U), &tdata1)) |
| __set_bit(RV_DBTR_BIT(TS, U), &trig->state); |
| |
| if (__test_bit(RV_DBTR_BIT(MC6, S), &tdata1)) |
| __set_bit(RV_DBTR_BIT(TS, S), &trig->state); |
| |
| if (__test_bit(RV_DBTR_BIT(MC6, VU), &tdata1)) |
| __set_bit(RV_DBTR_BIT(TS, VU), &trig->state); |
| |
| if (__test_bit(RV_DBTR_BIT(MC6, VS), &tdata1)) |
| __set_bit(RV_DBTR_BIT(TS, VS), &trig->state); |
| break; |
| default: |
| sbi_dprintf("%s: Unknown type (tdata1: 0x%lx Type: %ld)\n", |
| __func__, tdata1, TDATA1_GET_TYPE(tdata1)); |
| break; |
| } |
| } |
| |
| static inline void update_bit(unsigned long new, int nr, volatile unsigned long *addr) |
| { |
| if (new) |
| __set_bit(nr, addr); |
| else |
| __clear_bit(nr, addr); |
| } |
| |
| static void dbtr_trigger_enable(struct sbi_dbtr_trigger *trig) |
| { |
| unsigned long state; |
| unsigned long tdata1; |
| |
| if (!trig && !(trig->state & RV_DBTR_BIT_MASK(TS, MAPPED))) |
| return; |
| |
| state = trig->state; |
| tdata1 = trig->tdata1; |
| |
| switch (TDATA1_GET_TYPE(tdata1)) { |
| case RISCV_DBTR_TRIG_MCONTROL: |
| update_bit(state & RV_DBTR_BIT_MASK(TS, U), |
| RV_DBTR_BIT(MC, U), &trig->tdata1); |
| update_bit(state & RV_DBTR_BIT_MASK(TS, S), |
| RV_DBTR_BIT(MC, S), &trig->tdata1); |
| break; |
| case RISCV_DBTR_TRIG_MCONTROL6: |
| update_bit(state & RV_DBTR_BIT_MASK(TS, VU), |
| RV_DBTR_BIT(MC6, VU), &trig->tdata1); |
| update_bit(state & RV_DBTR_BIT_MASK(TS, VS), |
| RV_DBTR_BIT(MC6, VS), &trig->tdata1); |
| update_bit(state & RV_DBTR_BIT_MASK(TS, U), |
| RV_DBTR_BIT(MC6, U), &trig->tdata1); |
| update_bit(state & RV_DBTR_BIT_MASK(TS, S), |
| RV_DBTR_BIT(MC6, S), &trig->tdata1); |
| break; |
| default: |
| break; |
| } |
| |
| /* |
| * RISC-V Debug Support v1.0.0 section 5.5: |
| * Debugger cannot simply set a trigger by writing tdata1, then tdata2, |
| * etc. The current value of tdata2 might not be legal with the new |
| * value of tdata1. To help with this situation, it is guaranteed that |
| * writing 0 to tdata1 disables the trigger, and leaves it in a state |
| * where tdata2 and tdata3 can be written with any value that makes |
| * sense for any trigger type supported by this trigger. |
| */ |
| csr_write(CSR_TSELECT, trig->index); |
| csr_write(CSR_TDATA1, 0x0); |
| csr_write(CSR_TDATA2, trig->tdata2); |
| csr_write(CSR_TDATA1, trig->tdata1); |
| } |
| |
| static void dbtr_trigger_disable(struct sbi_dbtr_trigger *trig) |
| { |
| unsigned long tdata1; |
| |
| if (!trig && !(trig->state & RV_DBTR_BIT_MASK(TS, MAPPED))) |
| return; |
| |
| tdata1 = trig->tdata1; |
| |
| switch (TDATA1_GET_TYPE(tdata1)) { |
| case RISCV_DBTR_TRIG_MCONTROL: |
| __clear_bit(RV_DBTR_BIT(MC, U), &trig->tdata1); |
| __clear_bit(RV_DBTR_BIT(MC, S), &trig->tdata1); |
| break; |
| case RISCV_DBTR_TRIG_MCONTROL6: |
| __clear_bit(RV_DBTR_BIT(MC6, VU), &trig->tdata1); |
| __clear_bit(RV_DBTR_BIT(MC6, VS), &trig->tdata1); |
| __clear_bit(RV_DBTR_BIT(MC6, U), &trig->tdata1); |
| __clear_bit(RV_DBTR_BIT(MC6, S), &trig->tdata1); |
| break; |
| default: |
| break; |
| } |
| |
| csr_write(CSR_TSELECT, trig->index); |
| csr_write(CSR_TDATA1, trig->tdata1); |
| } |
| |
| static void dbtr_trigger_clear(struct sbi_dbtr_trigger *trig) |
| { |
| if (!trig && !(trig->state & RV_DBTR_BIT_MASK(TS, MAPPED))) |
| return; |
| |
| csr_write(CSR_TSELECT, trig->index); |
| csr_write(CSR_TDATA1, 0x0); |
| csr_write(CSR_TDATA2, 0x0); |
| } |
| |
| static int dbtr_trigger_supported(unsigned long type) |
| { |
| switch (type) { |
| case RISCV_DBTR_TRIG_MCONTROL: |
| case RISCV_DBTR_TRIG_MCONTROL6: |
| return 1; |
| default: |
| break; |
| } |
| |
| return 0; |
| } |
| |
| static int dbtr_trigger_valid(unsigned long type, unsigned long tdata) |
| { |
| switch (type) { |
| case RISCV_DBTR_TRIG_MCONTROL: |
| if (!(tdata & RV_DBTR_BIT_MASK(MC, DMODE)) && |
| !(tdata & RV_DBTR_BIT_MASK(MC, M))) |
| return 1; |
| break; |
| case RISCV_DBTR_TRIG_MCONTROL6: |
| if (!(tdata & RV_DBTR_BIT_MASK(MC6, DMODE)) && |
| !(tdata & RV_DBTR_BIT_MASK(MC6, M))) |
| return 1; |
| break; |
| default: |
| break; |
| } |
| |
| return 0; |
| } |
| |
| int sbi_dbtr_num_trig(unsigned long data, unsigned long *out) |
| { |
| unsigned long type = TDATA1_GET_TYPE(data); |
| u32 hartid = current_hartid(); |
| unsigned long total = 0; |
| struct sbi_dbtr_trigger *trig; |
| int i; |
| struct sbi_dbtr_hart_triggers_state *hs; |
| |
| hs = dbtr_thishart_state_ptr(); |
| if (!hs) |
| return SBI_ERR_FAILED; |
| |
| if (data == 0) { |
| *out = hs->total_trigs; |
| return SBI_SUCCESS; |
| } |
| |
| for (i = 0; i < hs->total_trigs; i++) { |
| trig = INDEX_TO_TRIGGER(i); |
| |
| if (__test_bit(type, &trig->type_mask)) |
| total++; |
| } |
| |
| sbi_dprintf("%s: hart%d: total triggers of type %lu: %lu\n", |
| __func__, hartid, type, total); |
| |
| *out = total; |
| return SBI_SUCCESS; |
| } |
| |
| int sbi_dbtr_read_trig(unsigned long smode, |
| unsigned long trig_idx_base, unsigned long trig_count) |
| { |
| struct sbi_dbtr_data_msg *xmit; |
| struct sbi_dbtr_trigger *trig; |
| struct sbi_dbtr_shmem_entry *entry; |
| void *shmem_base = NULL; |
| struct sbi_dbtr_hart_triggers_state *hs = NULL; |
| |
| hs = dbtr_thishart_state_ptr(); |
| if (!hs) |
| return SBI_ERR_FAILED; |
| |
| if (trig_idx_base >= hs->total_trigs || |
| trig_idx_base + trig_count >= hs->total_trigs) |
| return SBI_ERR_INVALID_PARAM; |
| |
| if (sbi_dbtr_shmem_disabled()) |
| return SBI_ERR_NO_SHMEM; |
| |
| shmem_base = hart_shmem_base(); |
| |
| for_each_trig_entry(shmem_base, trig_count, typeof(*entry), entry) { |
| sbi_hart_map_saddr((unsigned long)entry, sizeof(*entry)); |
| xmit = &entry->data; |
| trig = INDEX_TO_TRIGGER((_idx + trig_idx_base)); |
| xmit->tstate = cpu_to_lle(trig->state); |
| xmit->tdata1 = cpu_to_lle(trig->tdata1); |
| xmit->tdata2 = cpu_to_lle(trig->tdata2); |
| xmit->tdata3 = cpu_to_lle(trig->tdata3); |
| sbi_hart_unmap_saddr(); |
| } |
| |
| return SBI_SUCCESS; |
| } |
| |
| int sbi_dbtr_install_trig(unsigned long smode, |
| unsigned long trig_count, unsigned long *out) |
| { |
| void *shmem_base = NULL; |
| struct sbi_dbtr_shmem_entry *entry; |
| struct sbi_dbtr_data_msg *recv; |
| struct sbi_dbtr_id_msg *xmit; |
| unsigned long ctrl; |
| struct sbi_dbtr_trigger *trig; |
| struct sbi_dbtr_hart_triggers_state *hs = NULL; |
| |
| if (sbi_dbtr_shmem_disabled()) |
| return SBI_ERR_NO_SHMEM; |
| |
| shmem_base = hart_shmem_base(); |
| hs = dbtr_thishart_state_ptr(); |
| |
| /* Check requested triggers configuration */ |
| for_each_trig_entry(shmem_base, trig_count, typeof(*entry), entry) { |
| sbi_hart_map_saddr((unsigned long)entry, sizeof(*entry)); |
| recv = (struct sbi_dbtr_data_msg *)(&entry->data); |
| ctrl = recv->tdata1; |
| |
| if (!dbtr_trigger_supported(TDATA1_GET_TYPE(ctrl))) { |
| *out = _idx; |
| sbi_hart_unmap_saddr(); |
| return SBI_ERR_FAILED; |
| } |
| |
| if (!dbtr_trigger_valid(TDATA1_GET_TYPE(ctrl), ctrl)) { |
| *out = _idx; |
| sbi_hart_unmap_saddr(); |
| return SBI_ERR_FAILED; |
| } |
| sbi_hart_unmap_saddr(); |
| } |
| |
| if (hs->available_trigs < trig_count) { |
| *out = hs->available_trigs; |
| return SBI_ERR_FAILED; |
| } |
| |
| /* Install triggers */ |
| for_each_trig_entry(shmem_base, trig_count, typeof(*entry), entry) { |
| /* |
| * Since we have already checked if enough triggers are |
| * available, trigger allocation must succeed. |
| */ |
| trig = sbi_alloc_trigger(); |
| |
| sbi_hart_map_saddr((unsigned long)entry, sizeof(*entry)); |
| |
| recv = (struct sbi_dbtr_data_msg *)(&entry->data); |
| xmit = (struct sbi_dbtr_id_msg *)(&entry->id); |
| |
| dbtr_trigger_setup(trig, recv); |
| dbtr_trigger_enable(trig); |
| xmit->idx = cpu_to_lle(trig->index); |
| sbi_hart_unmap_saddr(); |
| } |
| |
| return SBI_SUCCESS; |
| } |
| |
| int sbi_dbtr_uninstall_trig(unsigned long trig_idx_base, |
| unsigned long trig_idx_mask) |
| { |
| unsigned long trig_mask = trig_idx_mask << trig_idx_base; |
| unsigned long idx = trig_idx_base; |
| struct sbi_dbtr_trigger *trig; |
| struct sbi_dbtr_hart_triggers_state *hs; |
| |
| hs = dbtr_thishart_state_ptr(); |
| if (!hs) |
| return SBI_ERR_FAILED; |
| |
| for_each_set_bit_from(idx, &trig_mask, hs->total_trigs) { |
| trig = INDEX_TO_TRIGGER(idx); |
| if (!(trig->state & RV_DBTR_BIT_MASK(TS, MAPPED))) |
| return SBI_ERR_INVALID_PARAM; |
| |
| dbtr_trigger_clear(trig); |
| |
| sbi_free_trigger(trig); |
| } |
| |
| return SBI_SUCCESS; |
| } |
| |
| int sbi_dbtr_enable_trig(unsigned long trig_idx_base, |
| unsigned long trig_idx_mask) |
| { |
| unsigned long trig_mask = trig_idx_mask << trig_idx_base; |
| unsigned long idx = trig_idx_base; |
| struct sbi_dbtr_trigger *trig; |
| struct sbi_dbtr_hart_triggers_state *hs; |
| |
| hs = dbtr_thishart_state_ptr(); |
| if (!hs) |
| return SBI_ERR_FAILED; |
| |
| for_each_set_bit_from(idx, &trig_mask, hs->total_trigs) { |
| trig = INDEX_TO_TRIGGER(idx); |
| sbi_dprintf("%s: enable trigger %lu\n", __func__, idx); |
| dbtr_trigger_enable(trig); |
| } |
| |
| return SBI_SUCCESS; |
| } |
| |
| int sbi_dbtr_update_trig(unsigned long smode, |
| unsigned long trig_idx_base, |
| unsigned long trig_idx_mask) |
| { |
| unsigned long trig_mask = trig_idx_mask << trig_idx_base; |
| unsigned long idx = trig_idx_base; |
| struct sbi_dbtr_data_msg *recv; |
| unsigned long uidx = 0; |
| struct sbi_dbtr_trigger *trig; |
| struct sbi_dbtr_shmem_entry *entry; |
| void *shmem_base = NULL; |
| struct sbi_dbtr_hart_triggers_state *hs = NULL; |
| |
| if (sbi_dbtr_shmem_disabled()) |
| return SBI_ERR_NO_SHMEM; |
| |
| shmem_base = hart_shmem_base(); |
| hs = dbtr_thishart_state_ptr(); |
| |
| for_each_set_bit_from(idx, &trig_mask, hs->total_trigs) { |
| trig = INDEX_TO_TRIGGER(idx); |
| |
| if (!(trig->state & RV_DBTR_BIT_MASK(TS, MAPPED))) |
| return SBI_ERR_INVALID_PARAM; |
| |
| entry = (shmem_base + uidx * sizeof(*entry)); |
| recv = &entry->data; |
| |
| trig->tdata2 = lle_to_cpu(recv->tdata2); |
| dbtr_trigger_enable(trig); |
| uidx++; |
| } |
| |
| return SBI_SUCCESS; |
| } |
| |
| int sbi_dbtr_disable_trig(unsigned long trig_idx_base, |
| unsigned long trig_idx_mask) |
| { |
| unsigned long trig_mask = trig_idx_mask << trig_idx_base; |
| unsigned long idx = trig_idx_base; |
| struct sbi_dbtr_trigger *trig; |
| struct sbi_dbtr_hart_triggers_state *hs; |
| |
| hs = dbtr_thishart_state_ptr(); |
| if (!hs) |
| return SBI_ERR_FAILED; |
| |
| for_each_set_bit_from(idx, &trig_mask, hs->total_trigs) { |
| trig = INDEX_TO_TRIGGER(idx); |
| dbtr_trigger_disable(trig); |
| } |
| |
| return SBI_SUCCESS; |
| } |