summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/sbi/sbi_dbtr.h10
-rw-r--r--lib/sbi/sbi_dbtr.c166
-rw-r--r--lib/sbi/sbi_ecall.c2
-rw-r--r--lib/sbi/tests/sbi_ecall_test.c59
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,
};