Software APIs
mbx_smoketest.c
1 // Copyright lowRISC contributors (OpenTitan project).
2 // Licensed under the Apache License, Version 2.0, see LICENSE for details.
3 // SPDX-License-Identifier: Apache-2.0
4 
5 #include <stddef.h>
6 #include <stdint.h>
7 
14 #include "sw/device/lib/runtime/irq.h"
16 #include "sw/device/lib/testing/rv_core_ibex_testutils.h"
17 #include "sw/device/lib/testing/test_framework/check.h"
19 
20 #include "hw/top_darjeeling/sw/autogen/top_darjeeling.h"
21 #include "mbx_regs.h" // Generated
22 
23 OTTF_DEFINE_TEST_CONFIG();
24 
25 enum {
26  kHart = 0,
27  kIrqVoid = UINT32_MAX,
28 };
29 
30 static dif_rv_core_ibex_t rv_core_ibex;
31 static dif_sram_ctrl_t sram_ctrl_mbox;
32 static dif_rv_plic_t rv_plic;
33 
34 static dif_mbx_t mbx[kDtMbxCount];
35 static dif_mbx_transaction_t txn[kDtMbxCount];
36 
37 // Define some test-state that allow multiple mailboxes transactions to
38 // handled at the same time. We need to hold some global state to ensure
39 // that the different routines can be reentrant.
40 typedef enum mbx_txn_state {
41  kStateIdle = 0,
42  kStateWaitingForRequest = 1,
43  kStateReceivedRequest = 2,
44  kStateGeneratingResponse = 3,
45  kStateWaitingForResponseDrained = 4,
46  kStateCleaningUp = 5,
47 } mbx_txn_state_t;
48 typedef struct mbx_test_handler_state {
49  dif_mbx_t *mbx;
50  dif_mbx_irq_state_snapshot_t irq_state;
52  mbx_txn_state_t txn_state;
53  dif_mbx_irq_t mbx_irq_serviced;
54  dif_rv_plic_irq_id_t plic_irq_serviced;
56 static volatile mbx_test_handler_state_t handler_state[kDtMbxCount];
57 static volatile bool is_finished = false;
58 
59 // CONSTANTS
60 static const dif_mbx_irq_t mbx_irq_ids[] = {
61  kDtMbxIrqMbxReady, kDtMbxIrqMbxAbort, kDtMbxIrqMbxError};
62 
63 enum {
64  kSoftwareBarrierTimeoutUsec = 100,
65 };
66 /* This value is updated by the testbench to synchronize. */
67 static volatile const uint8_t kSoftwareBarrier = 0;
68 static volatile const uint8_t kNumTxns = 0;
69 
70 //////////////////////
71 // HELPER FUNCTIONS //
72 //////////////////////
73 
74 static void increment_array_uint32(uint32_t *arr, uint32_t size) {
75  for (size_t i = 0; i < size; ++i) {
76  arr[i]++;
77  }
78 }
79 
80 ////////////////////////
81 // CONFIGURE MEMORIES //
82 ////////////////////////
83 
84 enum {
85  kSramCtrlMbxSize = TOP_DARJEELING_SRAM_CTRL_MBOX_RAM_SIZE_BYTES,
86  kMbxSizeDWORDS = 8, // The size we are allocating to each mbx for this test
87  // (imbx + ombx == kMbxSizeDWORDS * 2)
88 };
89 
90 static_assert(
91  kDtMbxCount * (kMbxSizeDWORDS * 2) <= kSramCtrlMbxSize / sizeof(uint32_t),
92  "As specified, the mailbox memories cannot fit in the backing SRAM!");
93 
94 // Backing storage for objects used by the mailbox handler(s)
95 // (dif_mbx_transaction_t)
96 static uint32_t data_dwords[kDtMbxCount][kMbxSizeDWORDS];
97 
98 /**
99  * Setup the mailbox CSRs
100  *
101  * - Setup imbx/ombx base+limit addresses to match SoC memory
102  * - Other misc init tasks (addr_range_valid, etc)
103  */
104 void configure_mbx_peripherals(void) {
105  uint32_t mbx_size_bytes = kMbxSizeDWORDS * sizeof(uint32_t);
106 
107  for (dt_mbx_t mbx_idx = 0; mbx_idx < kDtMbxCount; mbx_idx++) {
108  uint32_t sram_start =
109  dt_sram_ctrl_reg_block(kDtSramCtrlMbox, kDtSramCtrlRegBlockRam);
110  uint32_t mbx_region_base = sram_start + (mbx_size_bytes * 2 * mbx_idx);
111  // Set the memory ranges
112  dif_mbx_range_config_t config = {
113  .imbx_base_addr = mbx_region_base,
114  .imbx_limit_addr = // limit_addr is _inclusive_, hence (sizeW - 1)
115  mbx_region_base + mbx_size_bytes - sizeof(uint32_t),
116  .ombx_base_addr = mbx_region_base + mbx_size_bytes,
117  .ombx_limit_addr =
118  mbx_region_base + (mbx_size_bytes * 2) - sizeof(uint32_t),
119  };
120  // This DIF also writes the bit indicating the range configuration is valid.
121  CHECK_DIF_OK(dif_mbx_range_set(&mbx[mbx_idx], config));
122 
123  // Readback the range configuration registers, check they have been set as
124  // expected.
125  dif_mbx_range_config_t config_readback;
126  CHECK_DIF_OK(dif_mbx_range_get(&mbx[mbx_idx], &config_readback));
127  CHECK(memcmp(&config, &config_readback, sizeof(dif_mbx_range_config_t)) ==
128  0);
129 
130  // Clear the control register.
131  mmio_region_write32(mbx[mbx_idx].base_addr, MBX_CONTROL_REG_OFFSET, 0);
132  }
133 }
134 
135 /**
136  * Initialize the global state that synchronizes the main_thread/ISR
137  */
138 static void init_global_state(void) {
139  for (dt_mbx_t mbx_idx = 0; mbx_idx < kDtMbxCount; mbx_idx++) {
140  // Initialize storage for mbx transaction objects
141  txn[mbx_idx].data_dwords = data_dwords[mbx_idx];
142  // Create an initial snapshot of the pending interrupts
143  dif_mbx_irq_state_snapshot_t snapshot;
144  CHECK_DIF_OK(dif_mbx_irq_get_state(&mbx[mbx_idx], &snapshot));
145  CHECK(snapshot == 0,
146  "No interrupts should be pending yet! (mbx[%0d].snapshot = 0x%0x)",
147  mbx_idx, snapshot);
148  // Setup the state objects
149  handler_state[mbx_idx] =
150  (struct mbx_test_handler_state){.mbx = &mbx[mbx_idx],
151  .irq_state = snapshot,
152  .txn = &txn[mbx_idx],
153  .txn_state = kStateIdle,
154  .mbx_irq_serviced = kIrqVoid,
155  .plic_irq_serviced = kIrqVoid};
156  }
157 }
158 
159 //////////////////////////
160 // CONFIGURE INTERRUPTS //
161 //////////////////////////
162 
163 /**
164  * Initialize the peripherals used in this test.
165  */
166 static void init_peripherals(void) {
167  CHECK_DIF_OK(dif_rv_core_ibex_init_from_dt(kDtRvCoreIbex, &rv_core_ibex));
168  CHECK_DIF_OK(dif_rv_plic_init_from_dt(kDtRvPlic, &rv_plic));
169  CHECK_DIF_OK(dif_sram_ctrl_init_from_dt(kDtSramCtrlMbox, &sram_ctrl_mbox));
170 
171  for (dt_mbx_t mbx_idx = 0; mbx_idx < kDtMbxCount; mbx_idx++) {
172  CHECK_DIF_OK(dif_mbx_init_from_dt(mbx_idx, &mbx[mbx_idx]));
173  }
174 
175  // ADDITIONAL INITIALIZATION
176  CHECK_DIF_OK(dif_sram_ctrl_scramble(
177  &sram_ctrl_mbox)); // Scramble to initialize the memory with valid ECC
178 }
179 
180 /**
181  * Enable the interrupts required by this test.
182  */
183 static void init_interrupts(void) {
184  irq_global_ctrl(false);
185  irq_external_ctrl(false);
186 
187  // Set Ibex IRQ priority threshold level to lowest (0)
188  // - All IRQs with prio > 0 will not be masked
189  CHECK_DIF_OK(
191 
192  // Enable each IRQ for each mailbox at the PLIC and the mailbox itself.
193  for (dt_mbx_t mbx_idx = 0; mbx_idx < kDtMbxCount; mbx_idx++) {
194  for (int i = 0; i < ARRAYSIZE(mbx_irq_ids); i++) {
195  dt_plic_irq_id_t plic_id = dt_mbx_irq_to_plic_id(mbx_idx, mbx_irq_ids[i]);
196 
197  CHECK_DIF_OK(dif_rv_plic_irq_set_enabled(&rv_plic, plic_id, kHart,
199  CHECK_DIF_OK(dif_rv_plic_irq_set_priority(&rv_plic, plic_id,
201  CHECK_DIF_OK(dif_mbx_irq_set_enabled(&mbx[mbx_idx], mbx_irq_ids[i],
203  }
204  }
205 
206  irq_external_ctrl(true);
207  irq_global_ctrl(true);
208 }
209 
210 /**
211  * External ISR handler for this test.
212  * (Our overridden ottf_external_isr() calls this function only.)
213  *
214  * - Claim the interrupt
215  * - Check this irq_id is valid for this test
216  * - Setup state in the global mbx_test_handler_state_t object
217  * - This allows the main thread (e.g. responder_mbx_transaction()) to
218  * continue
219  */
220 static status_t external_isr(void) {
221  volatile mbx_test_handler_state_t *mbxths;
222  dif_rv_plic_irq_id_t plic_irq_id;
223 
224  // (1) First, find which interrupt fired at PLIC by claiming it.
225  TRY(dif_rv_plic_irq_claim(&rv_plic, kHart, &plic_irq_id));
226 
227  // Check the plic_irq is actually from a mailbox peripheral
228  // This test currently cannot handle any other interrupts, as the logic/ISRs
229  // are not sufficiently robust.
230  dt_instance_id_t inst_id = dt_plic_id_to_instance_id(plic_irq_id);
231  dt_device_type_t device_type = dt_device_type(inst_id);
232  CHECK(device_type == kDtDeviceTypeMbx,
233  "got an irq from a plic_peripheral that is not a mailbox!");
234 
235  dt_mbx_t mbx = dt_mbx_from_instance_id(inst_id);
236  dif_mbx_irq_t mbx_irq_id = dt_mbx_irq_from_plic_id(mbx, plic_irq_id);
237 
238  mbxths = &handler_state[mbx];
239  mbxths->mbx_irq_serviced = mbx_irq_id;
240  mbxths->plic_irq_serviced = plic_irq_id;
241 
242  // (2) Handle the peripheral
243  switch (mbx_irq_id) {
244  case kDtMbxIrqMbxReady: {
245  // First, mask the interrupt
246  // - The interrupt will not be de-asserted by the peripheral until the
247  // requester
248  // drains the response from the ombx. Until then, it cannot be cleared.
249  // - The main thread will subsequently poll for the de-assertion of the
250  // status.busy to determine when this occurs.
251  CHECK_DIF_OK(dif_mbx_irq_set_enabled(
252  mbxths->mbx, mbxths->mbx_irq_serviced, kDifToggleDisabled));
253  CHECK_DIF_OK(dif_rv_plic_irq_set_enabled(
254  &rv_plic, mbxths->plic_irq_serviced, kHart, kDifToggleDisabled));
255 
256  // Declare the maximum number of DWORDs that we are prepared to accept.
257  mbxths->txn->nr_dwords = kMbxSizeDWORDS;
258  // Read message from imbx memory region
259  CHECK_DIF_OK(dif_mbx_process_request(mbxths->mbx, mbxths->txn));
260  mbxths->txn_state = kStateReceivedRequest;
261 
262  break;
263  }
264  case kDtMbxIrqMbxAbort: {
265  CHECK(false, "Saw kDtMbxIrqMbxAbort, should not occur in this test!");
266  break;
267  }
268  case kDtMbxIrqMbxError: {
269  CHECK(false, "Saw kDtMbxIrqMbxError, should not occur in this test!");
270  break;
271  }
272  default: {
273  CHECK(false, "Invalid mbx_irq_id: %d", mbx_irq_id);
274  break;
275  }
276  }
277 
278  // (3) Clear the IRQ at the peripheral and at the PLIC.
279  // - This section is lifted from the end of the isr_testutils autgenerated
280  // handler.
281  // - Only the plic_irq_complete() routine matters, since we cannot-yet clear
282  // the INTR_STATE reg at the mbx as the event input is still asserted.
283 
284  // Acknowledge the IRQ at the peripheral if IRQ is of the event type.
285  dif_irq_type_t type;
286  CHECK_DIF_OK(
287  dif_mbx_irq_get_type(mbxths->mbx, mbxths->mbx_irq_serviced, &type));
288  if (type == kDifIrqTypeEvent) {
289  CHECK_DIF_OK(
290  dif_mbx_irq_acknowledge(mbxths->mbx, mbxths->mbx_irq_serviced));
291  }
292  // Complete the IRQ at the PLIC.
293  CHECK_DIF_OK(
294  dif_rv_plic_irq_complete(&rv_plic, kHart, mbxths->plic_irq_serviced));
295 
296  // Set the boolean which allows ATOMIC_WAIT_FOR_INTERRUPT() to return.
297  is_finished = true;
298 
299  return OK_STATUS();
300 }
301 
302 static volatile status_t isr_result;
303 /* This overrides the weak-symbol for ottf_external_isr() */
304 void ottf_external_isr(void) {
305  status_t tmp = external_isr();
306  if (status_ok(isr_result)) {
307  isr_result = tmp;
308  }
309 }
310 
311 //////////
312 // TEST //
313 //////////
314 
315 /**
316  * Perform a basic 'responder' role of the mbx transaction.
317  * This test simply responds with the same message as the request, but with
318  * all DWORDS incremented by 1.
319  *
320  * Request
321  * - SoC-Side writes data into mbx and sets Go.
322  * - Wait for interrupt on the core-side
323  * - Read message from imbx memory region
324  * Response
325  * - Write message back into ombx memory region (and set the object-size)
326  * - Poll/Wait for interrupt on soc-side
327  * - Read each word from the soc.RDATA register (then write to ack-it)
328  */
329 void responder_mbx_transaction(volatile mbx_test_handler_state_t *mbxths) {
330  mbxths->txn_state = kStateGeneratingResponse;
331 
332  // Send the response to the requester
333  // - Create new data for the outbound message
334  increment_array_uint32(mbxths->txn->data_dwords, mbxths->txn->nr_dwords);
335  CHECK_DIF_OK(dif_mbx_generate_response(mbxths->mbx, *mbxths->txn));
336 
337  mbxths->txn_state = kStateWaitingForResponseDrained;
338 
339  { // Poll the mbx until it reports not-busy.
340  bool is_busy = true;
341  while (is_busy) {
342  CHECK_DIF_OK(dif_mbx_is_busy(mbxths->mbx, &is_busy));
343  }
344  }
345  // This indicates the requester has consumed our message from the ombx.
346  // - Only at this point is it now possible to clear the 'ready' interrupt.
347 
348  mbxths->txn_state = kStateCleaningUp;
349 
350  CHECK_DIF_OK(dif_mbx_irq_acknowledge(mbxths->mbx, kDtMbxIrqMbxReady));
351  // Un-mask the interrupt.
352  CHECK_DIF_OK(dif_rv_plic_irq_set_enabled(&rv_plic, mbxths->plic_irq_serviced,
353  kHart, kDifToggleEnabled));
354  CHECK_DIF_OK(dif_mbx_irq_set_enabled(mbxths->mbx, mbxths->mbx_irq_serviced,
356 
357  mbxths->mbx_irq_serviced = kIrqVoid;
358  mbxths->plic_irq_serviced = kIrqVoid;
359  mbxths->txn_state = kStateIdle;
360 }
361 
362 bool test_main(void) {
363  init_peripherals();
364  configure_mbx_peripherals();
365  init_interrupts();
366  init_global_state();
367 
368  // Used to syncronise with testbench.
369  LOG_INFO("init_complete");
370 
371  // Wait for the testbench to set the number of transactions
372  IBEX_SPIN_FOR(kSoftwareBarrier == 1, kSoftwareBarrierTimeoutUsec);
373  size_t numTxns = kNumTxns;
374  LOG_INFO("sw will await %0d transactions before ending the test.", numTxns);
375 
376  // Used to syncronise with testbench.
377  LOG_INFO("received_tb_cfg");
378 
379  // Respond to transaction requests from the tb.
380  for (size_t i = 0; i < numTxns; ++i) {
381  dt_mbx_t mbx_id;
382  bool got_mbx_id = false;
383 
384  // Loop WFI->ISR->WFI->etc. until 'is_finished' is set true
385  // Use this to only advance iff our ISR sets it
386  ATOMIC_WAIT_FOR_INTERRUPT(is_finished);
387 
388  // Find which mbx received the request
389  for (dt_mbx_t mbx = 0; mbx < kDtMbxCount; mbx++) {
390  if (handler_state[mbx].txn_state == kStateReceivedRequest) {
391  // This test should only have one mailbox transaction (req+rsp) in
392  // flight at a time. The test is designed with this limitation in
393  // mind, and the sw is not robust to handling multiple in-flight
394  // transactions.
395  CHECK(!got_mbx_id,
396  "After ISR set 'is_finished', multiple mbx's had received "
397  "requests");
398  got_mbx_id = true;
399  mbx_id = mbx;
400  }
401  }
402  // Should not be possible to return from the WFI loop and then fail this
403  // check.
404  CHECK(got_mbx_id, "Something went wrong. Aborting test.");
405 
406  // Clear the interrupt indicator in preparation for the next request;
407  // we must do this before sending the response because the sender expects
408  // the response before sending another request.
409  is_finished = false;
410 
411  // Complete the txn
412  LOG_INFO("Test sw responding to pending request in mbx[%0d]", mbx_id);
413  responder_mbx_transaction(&handler_state[mbx_id]);
414  }
415 
416  LOG_INFO("End of test.");
417 
418  return true;
419 }