Software APIs
spi_passthrough_test.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 <assert.h>
6 #include <stdbool.h>
7 #include <string.h>
8 
9 #include "dt/dt_api.h" // Generated
10 #include "dt/dt_pinmux.h" // Generated
11 #include "dt/dt_rv_plic.h" // Generated
12 #include "dt/dt_spi_device.h" // Generated
13 #include "dt/dt_spi_host.h" // Generated
22 #include "sw/device/lib/runtime/irq.h"
24 #include "sw/device/lib/testing/pinmux_testutils.h"
25 #include "sw/device/lib/testing/spi_device_testutils.h"
26 #include "sw/device/lib/testing/spi_flash_testutils.h"
27 #include "sw/device/lib/testing/spi_host_testutils.h"
28 #include "sw/device/lib/testing/test_framework/check.h"
30 
31 OTTF_DEFINE_TEST_CONFIG();
32 
33 // Bit map of command slots to be filtered. This is supplied by the DV
34 // environment.
35 const volatile uint32_t kFilteredCommands;
36 
37 // Whether to upload write commands and have software relay them.
38 const volatile uint8_t kUploadWriteCommands;
39 
40 static const uint32_t kPlicTarget = 0;
41 static dif_pinmux_t pinmux;
42 static dt_pinmux_t kPinmuxDt = (dt_pinmux_t)0;
43 static_assert(kDtPinmuxCount >= 1, "This test requires Pinmux");
44 static dif_rv_plic_t rv_plic;
45 static dt_rv_plic_t kRvPlicDt = (dt_rv_plic_t)0;
46 static_assert(kDtRvPlicCount >= 1, "This test requires a RV PLIC");
47 static dif_spi_device_handle_t spi_device;
48 static dt_spi_device_t kSpiDeviceDt = (dt_spi_device_t)0;
49 static_assert(kDtSpiDeviceCount >= 1,
50  "This test requires one SPI Device instance");
51 static dif_spi_host_t spi_hosts[kDtSpiHostCount];
52 static dt_spi_host_t kSpiHost0Dt = (dt_spi_host_t)0;
53 static_assert(kDtSpiHostCount >= 1,
54  "This test requiest at least one SPI Host instance");
55 
56 // Enable pull-ups for spi_host data pins to avoid floating inputs.
57 static const pinmux_pad_attributes_t pinmux_pad_config[] = {
58 #if defined(OPENTITAN_IS_EARLGREY)
59  {
60  .pad = kDtPadIob1,
61  .flags = kDifPinmuxPadAttrPullResistorEnable |
62  kDifPinmuxPadAttrPullResistorUp,
63  },
64  {
65  .pad = kDtPadIob3,
66  .flags = kDifPinmuxPadAttrPullResistorEnable |
67  kDifPinmuxPadAttrPullResistorUp,
68  },
69 #elif defined(OPENTITAN_IS_DARJEELING)
70 // Darjeeling does not need these pads pulled-up
71 #else
72 #error "spi_passthrough_test does not support this top"
73 #endif
74  {
75  .pad = kDtPadSpiHost0Sd0,
76  .flags = kDifPinmuxPadAttrPullResistorEnable |
77  kDifPinmuxPadAttrPullResistorUp,
78  },
79  {
80  .pad = kDtPadSpiHost0Sd1,
81  .flags = kDifPinmuxPadAttrPullResistorEnable |
82  kDifPinmuxPadAttrPullResistorUp,
83  },
84  {
85  .pad = kDtPadSpiHost0Sd2,
86  .flags = kDifPinmuxPadAttrPullResistorEnable |
87  kDifPinmuxPadAttrPullResistorUp,
88  },
89  {
90  .pad = kDtPadSpiHost0Sd3,
91  .flags = kDifPinmuxPadAttrPullResistorEnable |
92  kDifPinmuxPadAttrPullResistorUp,
93  },
94 };
95 
96 #if defined(OPENTITAN_IS_EARLGREY)
97 /**
98  * A convenience struct to associate a mux selection that connects a pad and
99  * peripheral. This can be used for an input mux or an output mux.
100  */
101 typedef struct pinmux_select {
102  dt_pad_t pad;
103  dt_spi_host_t peripheral_dt;
104  dt_spi_host_periph_io_t peripheral_sig;
106 
107 static const pinmux_select_t pinmux_out_config[] = {
108  {
109  .pad = kDtPadIob0,
110  .peripheral_dt = kDtSpiHost1,
111  .peripheral_sig = kDtSpiHostPeriphIoCsb,
112  },
113  {
114  .pad = kDtPadIob2,
115  .peripheral_dt = kDtSpiHost1,
116  .peripheral_sig = kDtSpiHostPeriphIoSck,
117  },
118  {
119  .pad = kDtPadIob1,
120  .peripheral_dt = kDtSpiHost1,
121  .peripheral_sig = kDtSpiHostPeriphIoSd0,
122  },
123  {
124  .pad = kDtPadIob3,
125  .peripheral_dt = kDtSpiHost1,
126  .peripheral_sig = kDtSpiHostPeriphIoSd1,
127  },
128  // These peripheral I/Os are not assigned for tests.
129  // {
130  // .pad = ???,
131  // .peripheral_dt = kDtSpiHost1,
132  // .peripheral_sig = kDtSpiHostPeriphIoSd2,
133  // },
134  // {
135  // .pad = ???,
136  // .peripheral_dt = kDtSpiHost1,
137  // .peripheral_sig = kDtSpiHostPeriphIoSd3,
138  // },
139 };
140 
141 static const pinmux_select_t pinmux_in_config[] = {
142  {
143  .pad = kDtPadIob1,
144  .peripheral_dt = kDtSpiHost1,
145  .peripheral_sig = kDtSpiHostPeriphIoSd0,
146  },
147  {
148  .pad = kDtPadIob3,
149  .peripheral_dt = kDtSpiHost1,
150  .peripheral_sig = kDtSpiHostPeriphIoSd1,
151  },
152  // These peripheral I/Os are not assigned for tests.
153  // {
154  // .pad = ???,
155  // .peripheral_dt = kDtSpiHost1,
156  // .peripheral_sig = kDtSpiHostPeriphIoSd2,
157  // },
158  // {
159  // .pad = ???,
160  // .peripheral_dt = kDtSpiHost1,
161  // .peripheral_sig = kDtSpiHostPeriphIoSd3,
162  // },
163 };
164 #elif defined(OPENTITAN_IS_DARJEELING)
165 // Darjeeling only has 1 SPI host, so SpiHost1 does not exist
166 #else
167 #error "spi_passthrough_test does not support this top"
168 #endif
169 
170 /**
171  * Initialize the provided SPI host. For the most part, the values provided are
172  * filler, as spi_host0 will be in passthrough mode and spi_host1 will remain
173  * unused throughout the test.
174  */
175 void init_spi_host(dif_spi_host_t *spi_host,
176  uint32_t peripheral_clock_freq_hz) {
177  dif_spi_host_config_t config = {
178  .spi_clock = peripheral_clock_freq_hz / 2,
179  .peripheral_clock_freq_hz = peripheral_clock_freq_hz,
180  .chip_select =
181  {
182  .idle = 2,
183  .trail = 2,
184  .lead = 2,
185  },
186  };
187  CHECK_DIF_OK(dif_spi_host_configure(spi_host, config));
188  CHECK_DIF_OK(dif_spi_host_output_set_enabled(spi_host, /*enabled=*/true));
189 }
190 
191 /**
192  * Handle an incoming Write Status command.
193  *
194  * Modifies the internal status register and relays the command out to the
195  * downstream SPI flash.
196  *
197  * @param status The aggregated value of all three flash status registers prior
198  * to this command's execution.
199  * @param offset The bit offset for the byte to be modified by the payload.
200  * @param opcode The opcode corresponding to the incoming command.
201  */
202 void handle_write_status(uint32_t status, uint8_t offset, uint8_t opcode) {
203  uint8_t payload;
204  uint16_t occupancy;
205  uint32_t start_offset;
207  &spi_device, &occupancy, &start_offset));
208  CHECK(occupancy == 1);
210  &spi_device, start_offset, occupancy, &payload));
211 
212  status &= (0xffu << offset);
213  status |= ((uint32_t)(payload) << offset);
214  CHECK_DIF_OK(dif_spi_device_set_flash_status_registers(&spi_device, status));
215 
216  CHECK_STATUS_OK(spi_flash_testutils_issue_write_enable(&spi_hosts[0]));
217 
218  dif_spi_host_segment_t transaction[] = {
220  .opcode = {.opcode = opcode, .width = kDifSpiHostWidthStandard}},
221  {
222  .type = kDifSpiHostSegmentTypeTx,
223  .tx =
224  {
225  .width = kDifSpiHostWidthStandard,
226  .buf = &payload,
227  .length = 1,
228  },
229  },
230  };
231  CHECK_DIF_OK(dif_spi_host_transaction(&spi_hosts[0], /*csid=*/0, transaction,
232  ARRAYSIZE(transaction)));
233  CHECK_STATUS_OK(spi_flash_testutils_wait_until_not_busy(&spi_hosts[0]));
234  CHECK_DIF_OK(dif_spi_device_clear_flash_busy_bit(&spi_device));
235 }
236 
237 /**
238  * Handle an incoming Chip Erase command.
239  *
240  * Relays the command out to the downstream SPI flash.
241  */
242 void handle_chip_erase(void) {
243  CHECK_STATUS_OK(spi_flash_testutils_erase_chip(&spi_hosts[0]));
244  CHECK_DIF_OK(dif_spi_device_clear_flash_busy_bit(&spi_device));
245 }
246 
247 /**
248  * Handle an incoming Sector Erase command.
249  *
250  * Relays the command out to the downstream SPI flash.
251  */
252 void handle_sector_erase(void) {
253  uint8_t occupancy;
254  CHECK_DIF_OK(
255  dif_spi_device_get_flash_address_fifo_occupancy(&spi_device, &occupancy));
256  CHECK(occupancy == 1);
257 
258  uint32_t address;
259  CHECK_DIF_OK(dif_spi_device_pop_flash_address_fifo(&spi_device, &address));
260 
261  dif_toggle_t addr4b_enabled;
262  CHECK_DIF_OK(
263  dif_spi_device_get_4b_address_mode(&spi_device, &addr4b_enabled));
264 
265  bool addr_is_4b = dif_toggle_to_bool(addr4b_enabled);
266  CHECK_STATUS_OK(
267  spi_flash_testutils_erase_sector(&spi_hosts[0], address, addr_is_4b));
268  CHECK_DIF_OK(dif_spi_device_clear_flash_busy_bit(&spi_device));
269 }
270 
271 /**
272  * Handle an incoming Page Program command.
273  *
274  * Relays the command out to the downstream SPI flash.
275  */
276 void handle_page_program(void) {
277  uint8_t address_occupancy;
279  &spi_device, &address_occupancy));
280  CHECK(address_occupancy == 1);
281 
282  uint32_t address;
283  CHECK_DIF_OK(dif_spi_device_pop_flash_address_fifo(&spi_device, &address));
284 
285  uint8_t payload[256];
286  uint16_t payload_occupancy;
287  uint32_t start_offset;
289  &spi_device, &payload_occupancy, &start_offset));
290  CHECK(start_offset == 0);
291  CHECK(payload_occupancy <= sizeof(payload));
293  &spi_device, start_offset, payload_occupancy, payload));
294 
295  dif_toggle_t addr4b_enabled;
296  CHECK_DIF_OK(
297  dif_spi_device_get_4b_address_mode(&spi_device, &addr4b_enabled));
298 
299  bool addr_is_4b = dif_toggle_to_bool(addr4b_enabled);
300  CHECK_STATUS_OK(spi_flash_testutils_program_page(
301  &spi_hosts[0], payload, payload_occupancy, address, addr_is_4b));
302  CHECK_DIF_OK(dif_spi_device_clear_flash_busy_bit(&spi_device));
303 }
304 
305 // This function assumes only one command will ever be uploaded to the FIFO at a
306 // time. The IP does not currently make any such guarantee.
307 void spi_device_process_upload_fifo(void) {
308  dif_irq_type_t irq_type;
309  CHECK_DIF_OK(dif_spi_device_irq_get_type(
310  &spi_device.dev, kDifSpiDeviceIrqUploadCmdfifoNotEmpty, &irq_type));
311  if (irq_type == kDifIrqTypeEvent) {
312  CHECK_DIF_OK(dif_spi_device_irq_acknowledge(
313  &spi_device.dev, kDifSpiDeviceIrqUploadCmdfifoNotEmpty));
314  }
315 
316  // Uploaded commands should all set the busy bit, and WREN should have been
317  // submitted before the uploaded command.
318  uint32_t status;
319  CHECK_DIF_OK(dif_spi_device_get_flash_status_registers(&spi_device, &status));
320  CHECK(status & kSpiFlashStatusBitWip);
321  CHECK(status & kSpiFlashStatusBitWel);
322 
323  uint8_t command;
324  CHECK_DIF_OK(dif_spi_device_pop_flash_command_fifo(&spi_device, &command));
325  // Check command against the ones we expect.
326  // Call command-specific handlers, probably, which validate the commands. Then
327  // execute.
328  if (command == kSpiDeviceFlashOpWriteStatus1) {
329  handle_write_status(status, /*offset=*/0, command);
330  } else if (command == kSpiDeviceFlashOpWriteStatus2) {
331  handle_write_status(status, /*offset=*/8, command);
332  } else if (command == kSpiDeviceFlashOpWriteStatus3) {
333  handle_write_status(status, /*offset=*/16, command);
334  } else if (command == kSpiDeviceFlashOpChipErase) {
335  handle_chip_erase();
336  } else if (command == kSpiDeviceFlashOpSectorErase) {
337  handle_sector_erase();
338  } else if (command == kSpiDeviceFlashOpPageProgram) {
339  handle_page_program();
340  } else {
341  CHECK(false, "Received unexpected command 0x%x", command);
342  }
343 
344  CHECK_DIF_OK(dif_spi_device_irq_set_enabled(
345  &spi_device.dev, kDifSpiDeviceIrqUploadCmdfifoNotEmpty,
347 }
348 
349 /**
350  * Check that the command FIFO is not empty, and mask the interrupt for deferred
351  * processing.
352  *
353  * Runs in interrupt context.
354  */
355 void spi_device_isr(void) {
356  bool cmdfifo_not_empty;
357  CHECK_DIF_OK(dif_spi_device_irq_is_pending(
358  &spi_device.dev, kDifSpiDeviceIrqUploadCmdfifoNotEmpty,
359  &cmdfifo_not_empty));
360  CHECK(cmdfifo_not_empty);
361  CHECK_DIF_OK(dif_spi_device_irq_set_enabled(
362  &spi_device.dev, kDifSpiDeviceIrqUploadCmdfifoNotEmpty,
364 }
365 
366 /**
367  * Handle SPI device interrupts.
368  *
369  * Runs in interrupt context.
370  */
371 void ottf_external_isr(uint32_t *exc_info) {
372  dif_rv_plic_irq_id_t plic_irq_id;
373  CHECK_DIF_OK(dif_rv_plic_irq_claim(&rv_plic, kPlicTarget, &plic_irq_id));
374 
375  dt_instance_id_t peripheral_id = dt_plic_id_to_instance_id(plic_irq_id);
376  if (peripheral_id == dt_spi_device_instance_id(kSpiDeviceDt)) {
377  dt_spi_device_irq_t irq =
378  dt_spi_device_irq_from_plic_id(kSpiDeviceDt, plic_irq_id);
379  CHECK(irq == kDtSpiDeviceIrqUploadCmdfifoNotEmpty);
380  spi_device_isr();
381  }
382 
383  // Complete the IRQ at PLIC.
384  CHECK_DIF_OK(dif_rv_plic_irq_complete(&rv_plic, kPlicTarget, plic_irq_id));
385 }
386 
387 bool test_main(void) {
388  // Initialize the pinmux.
389  CHECK_DIF_OK(dif_pinmux_init_from_dt(kPinmuxDt, &pinmux));
390  pinmux_testutils_init(&pinmux);
391  pinmux_testutils_configure_pads(&pinmux, pinmux_pad_config,
392  ARRAYSIZE(pinmux_pad_config));
393 #if defined(OPENTITAN_IS_EARLGREY)
394  for (int i = 0; i < ARRAYSIZE(pinmux_in_config); ++i) {
395  pinmux_select_t setting = pinmux_in_config[i];
396  dt_periph_io_t peripheral =
397  dt_spi_host_periph_io(setting.peripheral_dt, setting.peripheral_sig);
398  CHECK_DIF_OK(dif_pinmux_mio_select_input(&pinmux, peripheral, setting.pad));
399  }
400  for (int i = 0; i < ARRAYSIZE(pinmux_out_config); ++i) {
401  pinmux_select_t setting = pinmux_out_config[i];
402  dt_periph_io_t peripheral =
403  dt_spi_host_periph_io(setting.peripheral_dt, setting.peripheral_sig);
404  CHECK_DIF_OK(
405  dif_pinmux_mio_select_output(&pinmux, setting.pad, peripheral));
406  }
407 #elif defined(OPENTITAN_IS_DARJEELING)
408 // Darjeeling only has 1 SPI Host, and so does not need these pinmux settings
409 #else
410 #error "spi_passthrough_test does not support this top"
411 #endif
412 
413  // Configure fast slew rate, strong drive strength, and weak pull-ups for SPI
414  // Host 0 pads.
415  CHECK_STATUS_OK(spi_host_testutils_configure_host0_pad_attrs(&pinmux));
416 
417  // Configure fast slew rate and strong drive strength for SPI device pads.
418  CHECK_STATUS_OK(spi_device_testutils_configure_pad_attrs(&pinmux));
419 
420  // Initialize the PLIC.
421  CHECK_DIF_OK(dif_rv_plic_init_from_dt(kRvPlicDt, &rv_plic));
422 
423  // Initialize the spi_host devices.
424  for (size_t i = 0; i < kDtSpiHostCount; i++) {
425  dt_spi_host_t spi_host_dt = (dt_spi_host_t)i;
426  uint32_t clock_freq =
427  dt_clock_frequency(dt_spi_host_clock(spi_host_dt, kDtSpiHostClockClk));
428  CHECK_DIF_OK(dif_spi_host_init_from_dt(spi_host_dt, &spi_hosts[i]));
429  init_spi_host(&spi_hosts[i], clock_freq);
430  }
431 
432  // Initialize spi_device.
433  CHECK_DIF_OK(dif_spi_device_init_from_dt(kSpiDeviceDt, &spi_device.dev));
434  bool upload_write_commands = (kUploadWriteCommands != 0);
435  CHECK_STATUS_OK(spi_device_testutils_configure_passthrough(
436  &spi_device, kFilteredCommands, upload_write_commands));
437 
438  // Enable all spi_device and spi_host interrupts, and check that they do not
439  // trigger unless command upload is enabled.
440  for (int i = 0; i < kDtSpiDeviceIrqCount; ++i) {
441  dt_spi_device_irq_t spi_irq = (dt_spi_device_irq_t)i;
442  CHECK_DIF_OK(dif_spi_device_irq_set_enabled(&spi_device.dev, spi_irq,
445  dt_spi_device_irq_to_plic_id(kSpiDeviceDt, spi_irq);
446  CHECK_DIF_OK(dif_rv_plic_irq_set_enabled(&rv_plic, irq, kPlicTarget,
448  CHECK_DIF_OK(dif_rv_plic_irq_set_priority(&rv_plic, irq, 0x1));
449  }
450  for (int i = 0; i < kDtSpiHostIrqCount; ++i) {
451  dt_spi_host_irq_t spi_irq = (dt_spi_host_irq_t)i;
452  CHECK_DIF_OK(dif_spi_host_irq_set_enabled(&spi_hosts[0], spi_irq,
454  dif_rv_plic_irq_id_t irq = dt_spi_host_irq_to_plic_id(kSpiHost0Dt, spi_irq);
455  CHECK_DIF_OK(dif_rv_plic_irq_set_enabled(&rv_plic, irq, kPlicTarget,
457  CHECK_DIF_OK(dif_rv_plic_irq_set_priority(&rv_plic, irq, 0x1));
458  }
459  irq_external_ctrl(/*en=*/true);
460 
461  // Send the DV environment a specific message for synchronization. The
462  // sequencer can pick this up and know it can begin sending SPI flash
463  // transactions.
464  LOG_INFO("Test setup complete.");
465 
466  while (true) {
467  bool cmdfifo_not_empty_irq_pending;
468  irq_global_ctrl(/*en=*/false);
469  CHECK_DIF_OK(dif_spi_device_irq_is_pending(
470  &spi_device.dev, kDifSpiDeviceIrqUploadCmdfifoNotEmpty,
471  &cmdfifo_not_empty_irq_pending));
472  if (!cmdfifo_not_empty_irq_pending) {
474  }
475  irq_global_ctrl(/*en=*/true);
476  spi_device_process_upload_fifo();
477  }
478  return true;
479 }