diff options
| -rw-r--r-- | include/sbi/sbi_dbtr.h | 10 | ||||
| -rw-r--r-- | lib/sbi/sbi_dbtr.c | 166 | ||||
| -rw-r--r-- | lib/sbi/sbi_ecall.c | 2 | ||||
| -rw-r--r-- | lib/sbi/tests/sbi_ecall_test.c | 59 |
4 files changed, 215 insertions, 22 deletions
diff --git a/include/sbi/sbi_dbtr.h b/include/sbi/sbi_dbtr.h index 5e0bf84e..90871f0d 100644 --- a/include/sbi/sbi_dbtr.h +++ b/include/sbi/sbi_dbtr.h @@ -77,6 +77,16 @@ struct sbi_dbtr_hart_triggers_state { u32 probed; }; +/** Platform specific debug trigger operations */ +struct sbi_dbtr_device { + char name[32]; + bool (*trigger_supported)(unsigned long idx, unsigned long tdata1, + unsigned long tdata2, unsigned long tdata3); +}; + +const struct sbi_dbtr_device *sbi_dbtr_get_device(void); +void sbi_dbtr_set_device(const struct sbi_dbtr_device *dev); + #define TDATA1_GET_TYPE(_t1) \ EXTRACT_FIELD(_t1, RV_DBTR_BIT_MASK(TDATA1, TYPE)) diff --git a/lib/sbi/sbi_dbtr.c b/lib/sbi/sbi_dbtr.c index 01047969..4fd7fb5e 100644 --- a/lib/sbi/sbi_dbtr.c +++ b/lib/sbi/sbi_dbtr.c @@ -24,6 +24,22 @@ /** Offset of pointer to HART's debug triggers info in scratch space */ static unsigned long hart_state_ptr_offset; +/** Device specific debug trigger operations */ +static const struct sbi_dbtr_device *dbtr_dev = NULL; + +const struct sbi_dbtr_device *sbi_dbtr_get_device(void) +{ + return dbtr_dev; +} + +void sbi_dbtr_set_device(const struct sbi_dbtr_device *dev) +{ + if (!dev || dbtr_dev) + return; + + dbtr_dev = dev; +} + #define dbtr_get_hart_state_ptr(__scratch) \ sbi_scratch_read_type((__scratch), void *, hart_state_ptr_offset) @@ -105,10 +121,75 @@ static void sbi_trigger_init(struct sbi_dbtr_trigger *trig, trig->index = idx; } -static inline struct sbi_dbtr_trigger *sbi_alloc_trigger(void) +static bool dbtr_trigger_hw_supported(unsigned long idx, unsigned long tdata1, + unsigned long tdata2, + unsigned long tdata3) +{ + if (dbtr_dev && dbtr_dev->trigger_supported) + return dbtr_dev->trigger_supported(idx, tdata1, tdata2, + tdata3); + + return true; +} + +static bool dbtr_trigger_any_hw_supported( + struct sbi_dbtr_hart_triggers_state *hs, + unsigned long tdata1, unsigned long tdata2, + unsigned long tdata3) +{ + unsigned long type = TDATA1_GET_TYPE(tdata1); + struct sbi_dbtr_trigger *trig; + int i; + + for (i = 0; i < hs->total_trigs; i++) { + trig = INDEX_TO_TRIGGER(i); + if (__test_bit(type, &trig->type_mask) && + dbtr_trigger_hw_supported(trig->index, tdata1, tdata2, + tdata3)) + return true; + } + + return false; +} + +/* + * Find the first free hardware trigger slot supporting the configuration. + * Slots set in claimed_mask are treated as taken, which allows the caller + * to track slot availability. A 32-bit mask covers RV_MAX_TRIGGERS (32); + * for a larger number of triggers, this function needs to be updated. + */ +static int dbtr_find_free_slot(struct sbi_dbtr_hart_triggers_state *hs, + u32 claimed_mask, + unsigned long tdata1, unsigned long tdata2, + unsigned long tdata3) +{ + unsigned long type = TDATA1_GET_TYPE(tdata1); + struct sbi_dbtr_trigger *trig; + int i; + + for (i = 0; i < hs->total_trigs; i++) { + trig = INDEX_TO_TRIGGER(i); + if (trig->state & RV_DBTR_BIT_MASK(TS, MAPPED)) + continue; + if (claimed_mask & BIT(i)) + continue; + if (!__test_bit(type, &trig->type_mask)) + continue; + if (!dbtr_trigger_hw_supported(trig->index, tdata1, + tdata2, tdata3)) + continue; + return i; + } + + return SBI_ENOENT; +} + +static inline struct sbi_dbtr_trigger *sbi_alloc_trigger(unsigned long tdata1, + unsigned long tdata2, + unsigned long tdata3) { int i; - struct sbi_dbtr_trigger *f_trig = NULL; + struct sbi_dbtr_trigger *f_trig; struct sbi_dbtr_hart_triggers_state *hart_state; hart_state = dbtr_thishart_state_ptr(); @@ -118,17 +199,12 @@ static inline struct sbi_dbtr_trigger *sbi_alloc_trigger(void) 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) + i = dbtr_find_free_slot(hart_state, 0, tdata1, tdata2, tdata3); + if (i < 0) return NULL; + f_trig = INDEX_TO_TRIGGER(i); + hart_state->available_trigs--; __set_bit(RV_DBTR_BIT(TS, MAPPED), &f_trig->state); return f_trig; @@ -547,7 +623,8 @@ int sbi_dbtr_num_trig(unsigned long data, unsigned long *out) for (i = 0; i < hs->total_trigs; i++) { trig = INDEX_TO_TRIGGER(i); - if (__test_bit(type, &trig->type_mask)) + if (__test_bit(type, &trig->type_mask) && + dbtr_trigger_hw_supported(trig->index, data, 0, 0)) total++; } @@ -573,7 +650,7 @@ int sbi_dbtr_read_trig(unsigned long smode, if (trig_idx_base >= hs->total_trigs || trig_idx_base + trig_count >= hs->total_trigs) - return SBI_ERR_INVALID_PARAM; + return SBI_ERR_BAD_RANGE; if (sbi_dbtr_shmem_disabled(hs)) return SBI_ERR_NO_SHMEM; @@ -608,6 +685,8 @@ int sbi_dbtr_install_trig(unsigned long smode, struct sbi_dbtr_data_msg *recv; struct sbi_dbtr_id_msg *xmit; unsigned long ctrl; + u32 claimed = 0; + int slot; struct sbi_dbtr_trigger *trig; struct sbi_dbtr_hart_triggers_state *hs = NULL; bool tdata2_impl, tdata3_impl; @@ -626,8 +705,10 @@ int sbi_dbtr_install_trig(unsigned long smode, /* * SBI v3.0 sec 19.4 requires SBI_ERR_NOT_SUPPORTED when a trigger * programs a non-zero value into an unimplemented optional CSR. Only - * the "whole CSR unimplemented" case is caught; WARL bits tied off - * inside an otherwise-implemented CSR are not. + * the "whole CSR unimplemented" case is caught here; WARL bits tied + * off inside an otherwise-implemented CSR are delegated to the + * device-specific trigger_supported() callback via + * dbtr_trigger_any_hw_supported(). */ tdata2_impl = tdata_implemented(CSR_TDATA2); tdata3_impl = tdata_implemented(CSR_TDATA3); @@ -648,7 +729,7 @@ int sbi_dbtr_install_trig(unsigned long smode, *out = _idx; sbi_hart_protection_unmap_range((unsigned long)shmem_base, trig_count * sizeof(*entry)); - return SBI_ERR_FAILED; + return SBI_ERR_INVALID_PARAM; } if ((recv->tdata2 && !tdata2_impl) || @@ -658,6 +739,16 @@ int sbi_dbtr_install_trig(unsigned long smode, trig_count * sizeof(*entry)); return SBI_ERR_NOT_SUPPORTED; } + + if (!dbtr_trigger_any_hw_supported(hs, + lle_to_cpu(recv->tdata1), + lle_to_cpu(recv->tdata2), + lle_to_cpu(recv->tdata3))) { + *out = _idx; + sbi_hart_protection_unmap_range((unsigned long)shmem_base, + trig_count * sizeof(*entry)); + return SBI_ERR_NOT_SUPPORTED; + } } if (hs->available_trigs < trig_count) { @@ -667,17 +758,40 @@ int sbi_dbtr_install_trig(unsigned long smode, return SBI_ERR_FAILED; } - /* Install triggers */ + /* + * Dry-run the allocation of the whole batch so that no trigger + * is installed if any of the requested configurations cannot be + * matched to a free hardware trigger slot. + */ 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(); + recv = (struct sbi_dbtr_data_msg *)(&entry->data); + slot = dbtr_find_free_slot(hs, claimed, + lle_to_cpu(recv->tdata1), + lle_to_cpu(recv->tdata2), + lle_to_cpu(recv->tdata3)); + if (slot < 0) { + *out = _idx; + sbi_hart_protection_unmap_range((unsigned long)shmem_base, + trig_count * sizeof(*entry)); + return SBI_ERR_FAILED; + } + claimed |= BIT(slot); + } + /* Install triggers */ + for_each_trig_entry(shmem_base, trig_count, typeof(*entry), entry) { recv = (struct sbi_dbtr_data_msg *)(&entry->data); xmit = (struct sbi_dbtr_id_msg *)(&entry->id); + /* + * The dry-run above matched every requested configuration + * to a free hardware trigger slot, so allocation must + * succeed. + */ + trig = sbi_alloc_trigger(lle_to_cpu(recv->tdata1), + lle_to_cpu(recv->tdata2), + lle_to_cpu(recv->tdata3)); + dbtr_trigger_setup(trig, recv); dbtr_trigger_enable(trig); xmit->idx = cpu_to_lle(trig->index); @@ -789,6 +903,14 @@ int sbi_dbtr_update_trig(unsigned long smode, return SBI_ERR_NOT_SUPPORTED; } + if (!dbtr_trigger_hw_supported(trig->index, + lle_to_cpu(entry->data.tdata1), + lle_to_cpu(entry->data.tdata2), + lle_to_cpu(entry->data.tdata3))) { + sbi_hart_protection_unmap_range((unsigned long)entry, sizeof(*entry)); + return SBI_ERR_NOT_SUPPORTED; + } + dbtr_trigger_setup(trig, &entry->data); sbi_hart_protection_unmap_range((unsigned long)entry, sizeof(*entry)); dbtr_trigger_enable(trig); diff --git a/lib/sbi/sbi_ecall.c b/lib/sbi/sbi_ecall.c index 745fa313..65c5a55a 100644 --- a/lib/sbi/sbi_ecall.c +++ b/lib/sbi/sbi_ecall.c @@ -66,6 +66,8 @@ void sbi_ecall_get_extensions_str(char *exts_str, int exts_str_size, bool experi sbi_list_for_each_entry(t, &ecall_exts_list, head) { if (experimental != t->experimental) continue; + if (offset + sbi_strlen(t->name) + 1 > exts_str_size) + break; sbi_snprintf(exts_str + offset, exts_str_size - offset, "%s,", t->name); offset = offset + sbi_strlen(t->name) + 1; diff --git a/lib/sbi/tests/sbi_ecall_test.c b/lib/sbi/tests/sbi_ecall_test.c index 5b6ce37c..f5c553e9 100644 --- a/lib/sbi/tests/sbi_ecall_test.c +++ b/lib/sbi/tests/sbi_ecall_test.c @@ -40,10 +40,69 @@ static void test_sbi_ecall_register_find_extension(struct sbiunit_test_case *tes SBIUNIT_EXPECT_EQ(test, sbi_ecall_find_extension(SBI_EXT_EXPERIMENTAL_START), NULL); } +static void test_sbi_ecall_get_extensions_str_bounds(struct sbiunit_test_case *test) +{ + struct sbi_ecall_extension e1 = { + .extid_start = SBI_EXT_EXPERIMENTAL_START, + .extid_end = SBI_EXT_EXPERIMENTAL_START, + .name = "Alpha", + .handle = dummy_handler, + .experimental = false, + }; + struct sbi_ecall_extension e2 = { + .extid_start = SBI_EXT_EXPERIMENTAL_START + 1, + .extid_end = SBI_EXT_EXPERIMENTAL_START + 1, + .name = "Bravo", + .handle = dummy_handler, + .experimental = false, + }; + struct sbi_ecall_extension e3 = { + .extid_start = SBI_EXT_EXPERIMENTAL_START + 2, + .extid_end = SBI_EXT_EXPERIMENTAL_START + 2, + .name = "Charli", + .handle = dummy_handler, + .experimental = false, + }; + char storage[16 + 16]; + char *buf = storage; + char big[128]; + int i; + int found_alpha = 0; + + SBIUNIT_EXPECT_EQ(test, sbi_ecall_register_extension(&e1), 0); + SBIUNIT_EXPECT_EQ(test, sbi_ecall_register_extension(&e2), 0); + SBIUNIT_EXPECT_EQ(test, sbi_ecall_register_extension(&e3), 0); + + for (i = 16; i < 32; i++) + storage[i] = (char)0xA5; + + /* Undersized buffer must not write past the caller-provided size. */ + sbi_ecall_get_extensions_str(buf, 16, false); + SBIUNIT_EXPECT_EQ(test, buf[15], '\0'); + for (i = 16; i < 32; i++) + SBIUNIT_EXPECT_EQ(test, (unsigned char)storage[i], 0xA5); + + /* Negative control: room for the full list, including registered names. */ + sbi_ecall_get_extensions_str(big, sizeof(big), false); + SBIUNIT_EXPECT_NE(test, sbi_strlen(big), 0); + for (i = 0; big[i] != '\0'; i++) { + if (sbi_strncmp(&big[i], "Alpha", 5) == 0) { + found_alpha = 1; + break; + } + } + SBIUNIT_EXPECT_EQ(test, found_alpha, 1); + + sbi_ecall_unregister_extension(&e1); + sbi_ecall_unregister_extension(&e2); + sbi_ecall_unregister_extension(&e3); +} + static struct sbiunit_test_case ecall_tests[] = { SBIUNIT_TEST_CASE(test_sbi_ecall_version), SBIUNIT_TEST_CASE(test_sbi_ecall_impid), SBIUNIT_TEST_CASE(test_sbi_ecall_register_find_extension), + SBIUNIT_TEST_CASE(test_sbi_ecall_get_extensions_str_bounds), SBIUNIT_END_CASE, }; |
