Software APIs
usb_testutils.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 "sw/device/lib/testing/usb_testutils.h"
6 
8 #include "sw/device/lib/testing/test_framework/check.h"
9 
11 
12 #define MODULE_ID MAKE_MODULE_ID('u', 's', 't')
13 
14 #define USBDEV_BASE_ADDR TOP_EARLGREY_USBDEV_BASE_ADDR
15 
16 static dif_usbdev_t usbdev;
17 static dif_usbdev_buffer_pool_t buffer_pool;
18 
19 // Internal function to create the packet that will form the next part of a
20 // larger buffer transfer
21 static bool usb_testutils_part_prepare(usb_testutils_ctx_t *ctx,
22  usb_testutils_transfer_t *transfer,
23  dif_usbdev_buffer_t *next_part,
24  bool *last) {
25  CHECK(ctx && transfer && last);
26 
27  // Allocate and fill a packet buffer
28  dif_result_t result =
29  dif_usbdev_buffer_request(ctx->dev, ctx->buffer_pool, next_part);
30  if (result != kDifOk) {
31  return false;
32  }
33 
34  // Determine the maximum bytes/packet
35  unsigned max_packet = USBDEV_MAX_PACKET_SIZE;
36  if (transfer->flags & kUsbTestutilsXfrMaxPacketSupplied) {
37  max_packet = (unsigned)(transfer->flags & kUsbTestutilsXfrMaxPacketMask);
38  }
39 
40  // How much are we sending this time?
41  unsigned part_len = transfer->length - transfer->offset;
42  if (part_len > max_packet) {
43  part_len = max_packet;
44  }
45  size_t bytes_written = 0U;
46  if (part_len) {
47  CHECK_DIF_OK(dif_usbdev_buffer_write(ctx->dev, next_part,
48  &transfer->buffer[transfer->offset],
49  part_len, &bytes_written));
50  }
51  // Is this the last packet?
52  uint32_t next_offset = transfer->offset + bytes_written;
53  *last = true;
54  if (bytes_written == max_packet) {
55  if (next_offset < transfer->length ||
56  (transfer->flags & kUsbTestutilsXfrEmployZLP)) {
57  *last = false;
58  }
59  } else {
60  CHECK(bytes_written < max_packet);
61  }
62 
63  transfer->offset = next_offset;
64  return true;
65 }
66 
67 // Internal function to perform the next part of a larger buffer transfer
68 static bool usb_testutils_transfer_next_part(
69  usb_testutils_ctx_t *ctx, uint8_t ep, usb_testutils_transfer_t *transfer) {
70  // Do we need to prepare a packet?
71  if (!transfer->next_valid &&
72  !usb_testutils_part_prepare(ctx, transfer, &transfer->next_part,
73  &transfer->last)) {
74  return false;
75  }
76 
77  // Send the existing prepared packet
78  CHECK_DIF_OK(dif_usbdev_send(ctx->dev, ep, &transfer->next_part));
79  transfer->next_valid = false;
80 
81  // If we're double-buffering, request and fill another buffer immediately;
82  // we'll then be able to supply it much more promptly later...
83  if ((transfer->flags & kUsbTestutilsXfrDoubleBuffered) && !transfer->last) {
84  transfer->next_valid = usb_testutils_part_prepare(
85  ctx, transfer, &transfer->next_part, &transfer->last);
86  }
87 
88  return true;
89 }
90 
91 status_t usb_testutils_poll(usb_testutils_ctx_t *ctx) {
92  uint32_t istate;
93 
94  // Collect a set of interrupts
95  TRY(dif_usbdev_irq_get_state(ctx->dev, &istate));
96 
97  if (!istate) {
98  return OK_STATUS();
99  }
100 
101  // Process IN completions first so we get the fact that send completed
102  // before processing a response to that transmission
103  // This is also important for device IN performance
104  if (istate & (1u << kDifUsbdevIrqPktSent)) {
105  uint16_t sentep;
106  TRY(dif_usbdev_get_tx_sent(ctx->dev, &sentep));
107  TRC_C('a' + sentep);
108  unsigned ep = 0u;
109  while (sentep && ep < USBDEV_NUM_ENDPOINTS) {
110  if (sentep & (1u << ep)) {
111  // Free up the buffer and optionally callback
112  TRY(dif_usbdev_clear_tx_status(ctx->dev, ctx->buffer_pool,
113  (uint8_t)ep));
114 
115  // If we have a larger transfer in progress, continue with that
116  usb_testutils_transfer_t *transfer = &ctx->in[ep].transfer;
117  usb_testutils_xfr_result_t res = kUsbTestutilsXfrResultOk;
118  bool done = true;
119  if (transfer->buffer) {
120  if (transfer->next_valid || !transfer->last) {
121  if (usb_testutils_transfer_next_part(ctx, (uint8_t)ep, transfer)) {
122  done = false;
123  } else {
124  res = kUsbTestutilsXfrResultFailed;
125  }
126  }
127  if (done) {
128  // Larger buffer transfer now completed; forget the buffer
129  transfer->buffer = NULL;
130  }
131  }
132  // Notify that we've sent the single packet, or larger buffer transfer
133  // is now complete
134  if (done && ctx->in[ep].tx_done_callback) {
135  TRY(ctx->in[ep].tx_done_callback(ctx->in[ep].ep_ctx, res));
136  }
137  sentep &= ~(1u << ep);
138  }
139  ep++;
140  }
141  }
142 
143  // Keep buffers available for packet reception
144  TRY(dif_usbdev_fill_available_fifos(ctx->dev, ctx->buffer_pool));
145 
146  if (istate & (1u << kDifUsbdevIrqPktReceived)) {
147  // TODO: we run the risk of starving the IN side here if the rx_callback(s)
148  // are time-consuming
149  while (true) {
150  bool is_empty;
151  TRY(dif_usbdev_status_get_rx_fifo_empty(ctx->dev, &is_empty));
152  if (is_empty) {
153  break;
154  }
155 
156  dif_usbdev_rx_packet_info_t packet_info;
157  dif_usbdev_buffer_t buffer;
158  TRY(dif_usbdev_recv(ctx->dev, &packet_info, &buffer));
159 
160  unsigned ep = packet_info.endpoint;
161  if (ctx->out[ep].rx_callback) {
162  TRY(ctx->out[ep].rx_callback(ctx->out[ep].ep_ctx, packet_info, buffer));
163  } else {
164  // Note: this could happen following endpoint removal
165  TRC_S("USB: unexpected RX ");
166  TRC_I(ep, 8);
167  TRY(dif_usbdev_buffer_return(ctx->dev, ctx->buffer_pool, &buffer));
168  }
169  }
170  }
171  if (istate & (1u << kDifUsbdevIrqLinkReset)) {
172  TRC_S("USB: Bus reset");
173  // Link reset
174  for (int ep = 0; ep < USBDEV_NUM_ENDPOINTS; ep++) {
175  // Notify the IN endpoint first because transmission is more significantly
176  // impacted, and then the OUT endpoint before advancing to the next
177  // endpoint number in case the order is important to the client(s)
178  if (ctx->in[ep].reset) {
179  TRY(ctx->in[ep].reset(ctx->in[ep].ep_ctx));
180  }
181  if (ctx->out[ep].reset) {
182  TRY(ctx->out[ep].reset(ctx->out[ep].ep_ctx));
183  }
184  }
185  }
186 
187  // Clear the interrupts that we've received and handled
188  TRY(dif_usbdev_irq_acknowledge_state(ctx->dev, istate));
189 
190  // Record bus frame
191  if ((istate & (1u << kDifUsbdevIrqFrame))) {
192  // The first bus frame is 1
193  TRY(dif_usbdev_status_get_frame(ctx->dev, &ctx->frame));
194  ctx->got_frame = true;
195  }
196 
197  // Report all link events to the registered callback handler, if any
198  if (ctx->link_callback) {
199  const dif_usbdev_irq_state_snapshot_t kIrqsLink =
200  (1u << kDifUsbdevIrqPowered) | (1u << kDifUsbdevIrqDisconnected) |
201  (1u << kDifUsbdevIrqHostLost) | (1u << kDifUsbdevIrqLinkReset) |
202  (1u << kDifUsbdevIrqLinkSuspend) | (1u << kDifUsbdevIrqLinkResume) |
203  (1u << kDifUsbdevIrqFrame);
204  if (istate & kIrqsLink) {
205  // Retrieve and report the current link state, since this should help
206  // resolve any confusion caused by delayed reporting of earlier link
207  // events (eg. disconnect/powered, suspend/resume)
208  dif_usbdev_link_state_t link_state;
209  TRY(dif_usbdev_status_get_link_state(ctx->dev, &link_state));
210 
211  // Indicate only the link-related interrupts
212  TRY(ctx->link_callback(ctx->ctx_link, istate & kIrqsLink, link_state));
213  }
214  }
215 
216  // Note: LinkInErr will be raised in response to a packet being NAKed by the
217  // host which is not expected behavior on a physical USB but this is something
218  // that the DPI model does to exercise packet resending when running
219  // usbdev_stream_test
220  //
221  // We can expect AVFIFO empty and RXFIFO full interrupts when using a real
222  // host and also 'LinkOut' errors because these can be triggered by a lack of
223  // space in the RXFIFO
224 
225  if (istate &
226  ~((1u << kDifUsbdevIrqLinkReset) | (1u << kDifUsbdevIrqPktReceived) |
227  (1u << kDifUsbdevIrqPktSent) | (1u << kDifUsbdevIrqFrame) |
228  (1u << kDifUsbdevIrqAvSetupEmpty) | (1u << kDifUsbdevIrqAvOutEmpty) |
229  (1u << kDifUsbdevIrqRxFull) | (1u << kDifUsbdevIrqLinkOutErr) |
230  (1u << kDifUsbdevIrqLinkInErr))) {
231  // Report anything that really should not be happening during testing,
232  // at least for now
233  //
234  // TODO - introduce deliberate generation of each of these errors, and
235  // modify usb_testutils_ to return the information without faulting
236  // it?
237  if (istate &
238  ((1u << kDifUsbdevIrqRxFull) | (1u << kDifUsbdevIrqAvOverflow) |
239  (1u << kDifUsbdevIrqLinkInErr) | (1u << kDifUsbdevIrqRxCrcErr) |
240  (1u << kDifUsbdevIrqRxPidErr) | (1u << kDifUsbdevIrqRxBitstuffErr) |
241  (1u << kDifUsbdevIrqLinkOutErr))) {
242  LOG_INFO("USB: Unexpected interrupts: 0x%08x", istate);
243  } else {
244  // Other events are optionally reported
245  TRC_C('I');
246  TRC_I(istate, 12);
247  TRC_C(' ');
248  }
249  }
250 
251  // TODO - clean this up
252  // Frame ticks every 1ms, use to flush data every 16ms
253  // (faster in DPI but this seems to work ok)
254  //
255  // Ensure that we do not flush until we have received a frame
256  if (ctx->got_frame && (ctx->frame & 0xf) == 1) {
257  if (ctx->flushed == 0) {
258  for (unsigned ep = 0; ep < USBDEV_NUM_ENDPOINTS; ep++) {
259  if (ctx->in[ep].flush) {
260  TRY(ctx->in[ep].flush(ctx->in[ep].ep_ctx));
261  }
262  }
263  ctx->flushed = 1;
264  }
265  } else {
266  ctx->flushed = 0;
267  }
268  // TODO Errors? What Errors?
269  return OK_STATUS();
270 }
271 
272 status_t usb_testutils_link_callback_register(usb_testutils_ctx_t *ctx,
273  usb_testutils_link_handler_t link,
274  void *ctx_link) {
275  CHECK(ctx != NULL);
276 
277  // Retain the new link callback handler and its context pointer; either of
278  // these may be NULL, respectively to deregister a handler or because no
279  // context is required.
280  ctx->link_callback = link;
281  ctx->ctx_link = ctx_link;
282 
283  return OK_STATUS();
284 }
285 
286 status_t usb_testutils_transfer_send(usb_testutils_ctx_t *ctx, uint8_t ep,
287  const uint8_t *data, uint32_t length,
288  usb_testutils_xfr_flags_t flags) {
289  CHECK(ep < USBDEV_NUM_ENDPOINTS);
290 
291  usb_testutils_transfer_t *transfer = &ctx->in[ep].transfer;
292  if (transfer->buffer) {
293  // If there is an in-progress transfer, then we cannot accept another
294  return OK_STATUS(false);
295  }
296 
297  // Describe this transfer
298  transfer->buffer = data;
299  transfer->offset = 0U;
300  transfer->length = length;
301  transfer->flags = flags;
302  transfer->next_valid = false;
303 
304  if (!usb_testutils_transfer_next_part(ctx, ep, transfer)) {
305  // Forget about the attempted transfer
306  transfer->buffer = NULL;
307  return OK_STATUS(false);
308  }
309 
310  // Buffer transfer is underway...
311  return OK_STATUS(true);
312 }
313 
314 status_t usb_testutils_in_endpoint_setup(
315  usb_testutils_ctx_t *ctx, uint8_t ep, usb_testutils_transfer_type_t ep_type,
316  void *ep_ctx, usb_testutils_tx_done_handler_t tx_done,
317  usb_testutils_tx_flush_handler_t flush,
318  usb_testutils_reset_handler_t reset) {
319  // Store callback handler information before we enable the endpoint
320  ctx->in[ep].ep_type = ep_type;
321  ctx->in[ep].ep_ctx = ep_ctx;
322  ctx->in[ep].tx_done_callback = tx_done;
323  ctx->in[ep].flush = flush;
324  ctx->in[ep].reset = reset;
325 
326  dif_usbdev_endpoint_id_t endpoint = {
327  .number = ep,
328  .direction = USBDEV_ENDPOINT_DIR_IN,
329  };
330 
331  TRY(dif_usbdev_endpoint_stall_enable(ctx->dev, endpoint, kDifToggleDisabled));
332 
333  // Specify whether this is an Isochronous endpoint (no acknowledgement/retry)
335  if (ep_type == kUsbTransferTypeIsochronous) {
336  iso = kDifToggleEnabled;
337  }
338  CHECK_DIF_OK(dif_usbdev_endpoint_iso_enable(ctx->dev, endpoint, iso));
339 
340  // Enable IN traffic from device to host
341  TRY(dif_usbdev_endpoint_enable(ctx->dev, endpoint, kDifToggleEnabled));
342  return OK_STATUS();
343 }
344 
345 status_t usb_testutils_out_endpoint_setup(
346  usb_testutils_ctx_t *ctx, uint8_t ep, usb_testutils_transfer_type_t ep_type,
347  usb_testutils_out_transfer_mode_t out_mode, void *ep_ctx,
348  usb_testutils_rx_handler_t rx, usb_testutils_reset_handler_t reset) {
349  // Store callback handler information before we enable the endpoint
350  ctx->out[ep].ep_type = ep_type;
351  ctx->out[ep].ep_ctx = ep_ctx;
352  ctx->out[ep].rx_callback = rx;
353  ctx->out[ep].reset = reset;
354 
355  dif_usbdev_endpoint_id_t endpoint = {
356  .number = ep,
357  .direction = USBDEV_ENDPOINT_DIR_OUT,
358  };
359 
360  TRY(dif_usbdev_endpoint_stall_enable(ctx->dev, endpoint, kDifToggleDisabled));
361 
362  // Specify whether this is an Isochronous endpoint (no acknowledgement/retry)
364  if (ep_type == kUsbTransferTypeIsochronous) {
365  iso = kDifToggleEnabled;
366  }
367  CHECK_DIF_OK(dif_usbdev_endpoint_iso_enable(ctx->dev, endpoint, iso));
368 
369  // Enable/disable the endpoint and reception of OUT packets?
371  if (out_mode == kUsbdevOutDisabled) {
372  enabled = kDifToggleDisabled;
373  }
374  // Enable/disable generation of NAK responses once OUT packet has been
375  // received?
377  if (out_mode == kUsbdevOutMessage) {
378  nak = kDifToggleEnabled;
379  }
380 
381  TRY(dif_usbdev_endpoint_out_enable(ctx->dev, ep, enabled));
382  TRY(dif_usbdev_endpoint_set_nak_out_enable(ctx->dev, ep, nak));
383  // Now we may enable the OUT endpoint
384  TRY(dif_usbdev_endpoint_enable(ctx->dev, endpoint, enabled));
385  return OK_STATUS();
386 }
387 
388 status_t usb_testutils_endpoint_setup(
389  usb_testutils_ctx_t *ctx, uint8_t ep, usb_testutils_transfer_type_t in_type,
390  usb_testutils_transfer_type_t out_type,
391  usb_testutils_out_transfer_mode_t out_mode, void *ep_ctx,
392  usb_testutils_tx_done_handler_t tx_done, usb_testutils_rx_handler_t rx,
393  usb_testutils_tx_flush_handler_t flush,
394  usb_testutils_reset_handler_t reset) {
395  TRY(usb_testutils_in_endpoint_setup(ctx, ep, in_type, ep_ctx, tx_done, flush,
396  reset));
397 
398  // Note: register the link reset handler only on the IN endpoint so that it
399  // does not get invoked twice
400  return usb_testutils_out_endpoint_setup(ctx, ep, out_type, out_mode, ep_ctx,
401  rx, NULL);
402 }
403 
404 status_t usb_testutils_in_endpoint_remove(usb_testutils_ctx_t *ctx,
405  uint8_t ep) {
406  // Disable IN traffic
407  dif_usbdev_endpoint_id_t endpoint = {
408  .number = ep,
409  .direction = USBDEV_ENDPOINT_DIR_IN,
410  };
411  TRY(dif_usbdev_endpoint_enable(ctx->dev, endpoint, kDifToggleDisabled));
412 
413  // Remove callback handlers
414  ctx->in[ep].tx_done_callback = NULL;
415  ctx->in[ep].flush = NULL;
416  ctx->in[ep].reset = NULL;
417 
418  return OK_STATUS();
419 }
420 
421 status_t usb_testutils_out_endpoint_remove(usb_testutils_ctx_t *ctx,
422  uint8_t ep) {
423  // Disable OUT traffic
424  dif_usbdev_endpoint_id_t endpoint = {
425  .number = ep,
426  .direction = USBDEV_ENDPOINT_DIR_OUT,
427  };
428  TRY(dif_usbdev_endpoint_enable(ctx->dev, endpoint, kDifToggleDisabled));
429 
430  // Return the rest of the OUT endpoint configuration to its default state
431  TRY(dif_usbdev_endpoint_set_nak_out_enable(ctx->dev, endpoint.number,
433  TRY(dif_usbdev_endpoint_out_enable(ctx->dev, endpoint.number,
435 
436  // Remove callback handlers
437  ctx->out[ep].rx_callback = NULL;
438  ctx->out[ep].reset = NULL;
439 
440  return OK_STATUS();
441 }
442 
443 status_t usb_testutils_endpoint_remove(usb_testutils_ctx_t *ctx, uint8_t ep) {
444  TRY(usb_testutils_in_endpoint_remove(ctx, ep));
445  return usb_testutils_out_endpoint_remove(ctx, ep);
446 }
447 
448 status_t usb_testutils_init(usb_testutils_ctx_t *ctx, bool pinflip,
449  bool en_diff_rcvr, bool tx_use_d_se0) {
450  TRY_CHECK(ctx != NULL);
451  ctx->dev = &usbdev;
452  ctx->buffer_pool = &buffer_pool;
453 
454  // Ensure that we do not invoke the endpoint 'flush' functions before
455  // detection of the first bus frame
456  ctx->got_frame = false;
457  ctx->frame = 0u;
458 
459  // No callback handler for link events
460  ctx->link_callback = NULL;
461 
462  TRY(dif_usbdev_init(mmio_region_from_addr(USBDEV_BASE_ADDR), ctx->dev));
463 
464  dif_usbdev_config_t config = {
466  .use_tx_d_se0 = dif_bool_to_toggle(tx_use_d_se0),
467  .single_bit_eop = kDifToggleDisabled,
468  .pin_flip = dif_bool_to_toggle(pinflip),
469  .clock_sync_signals = kDifToggleEnabled,
470  };
471  TRY(dif_usbdev_configure(ctx->dev, ctx->buffer_pool, config));
472 
473  // Set up context
474  static_assert(USBDEV_NUM_ENDPOINTS <= UINT8_MAX,
475  "USBDEV_NUM_ENDPOINTS must fit into uint8_t");
476  for (uint8_t i = 0; i < USBDEV_NUM_ENDPOINTS; i++) {
477  TRY(usb_testutils_endpoint_setup(
478  ctx, i, kUsbTransferTypeControl, kUsbTransferTypeControl,
479  kUsbdevOutDisabled, NULL, NULL, NULL, NULL, NULL));
480  }
481 
482  // All about polling...
483  TRY(dif_usbdev_irq_disable_all(ctx->dev, NULL));
484 
485  // Clear any outstanding interrupts such as Powered/Disconnected interrupts
486  // from when the usbdev came out of reset
487  TRY(dif_usbdev_irq_acknowledge_all(ctx->dev));
488 
489  // Provide buffers for any packet reception
490  TRY(dif_usbdev_fill_available_fifos(ctx->dev, ctx->buffer_pool));
491 
492  // Preemptively enable SETUP reception on endpoint zero for the
493  // Default Control Pipe; all other settings for that endpoint will be applied
494  // once the callback handlers are registered by a call to _endpoint_setup()
496 
497  return OK_STATUS();
498 }
499 
500 status_t usb_testutils_fin(usb_testutils_ctx_t *ctx) {
501  // Remove the endpoints in reverse order so that Endpoint Zero goes down last
502  static_assert(USBDEV_NUM_ENDPOINTS <= UINT8_MAX,
503  "USBDEV_NUM_ENDPOINTS must fit into uint8_t");
504  static_assert(USBDEV_NUM_ENDPOINTS > 0,
505  "USBDEV_NUM_ENDPOINTS - 1 must not overflow");
506  uint8_t ep = USBDEV_NUM_ENDPOINTS;
507  do {
508  ep--;
509  TRY(usb_testutils_endpoint_remove(ctx, ep));
510  } while (ep > 0U);
511 
512  // Disconnect from the bus
514  return OK_STATUS();
515 }
516 
517 // `extern` declarations to give the inline functions in the
518 // corresponding header a link location.
519 
520 extern int usb_testutils_halted(usb_testutils_ctx_t *ctx,
521  dif_usbdev_endpoint_id_t endpoint);