Software APIs
stream_test.cc
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 // USB streaming data test
6 //
7 // Linux host-side application that receives a stream of LFSR-generated data
8 // from the USB device, checks the received bytestream and then XORs it with a
9 // host-side LFSR-generated byte stream to transmit back to the device.
10 //
11 // By default the streaming test expects a number of USB serial connections to
12 // the target device, one port per endpoint:
13 //
14 // /dev/ttyUSB0 - supplies and receives LFSR-generated byte stream for one/
15 // the only endpoint
16 // /dev/ttyUSB1 - a secondary stream
17 // /dev/ttyUSB..
18 //
19 // Note that the mapping from device endpoints to USB port number is not
20 // guaranteed, and when multiple streams are used, it is _not_ necessarily the
21 // case that ascending streams/endpoints in usbdev_stream_test are mapped to
22 // a contiguous range of ascending ttyUSBi port names.
23 //
24 // Either or both of the initial input port and the initial output port may be
25 // overridden using command line parameters.
26 //
27 // Usage:
28 // stream [-v<bool>][-c<bool>][-r<bool>][-s<bool>]
29 // [[-d<bus>:<address>] | [--device <bus>:<address>]]
30 // [<input port>[ <output port>]]
31 //
32 // --device programmatically specify a particular USB device by bus number
33 // and device address (see 'lsusb' output).
34 //
35 // -c check any retrieved data against expectations
36 // -d specify a particular USB device by bus number and device address
37 // -r retrieve data from device
38 // -s send data to device
39 // -t use serial ports (ttyUSBx) in preference to libusb Bulk Transfer
40 // streams for usbdev_stream_test
41 // -v verbose reporting
42 // -z perform suspend-resume signaling throughout the test
43 //
44 // <bool> values may be 0,1,n or y, and they default to 1.
45 //
46 // Build without libusb dependency:
47 // eg. g++ -Wall -Werror -o stream_test *.cc
48 #include "stream_test.h"
49 
50 #include <cassert>
51 #include <cctype>
52 #include <cerrno>
53 #include <cinttypes>
54 #include <cstdbool>
55 #include <cstddef>
56 #include <cstdint>
57 #include <cstdio>
58 #include <cstdlib>
59 #include <cstring>
60 #include <ctime>
61 #include <iostream>
62 #include <sys/time.h>
63 
64 #include "usb_device.h"
65 #if STREAMTEST_LIBUSB
66 #include "usbdev_int.h"
67 #include "usbdev_iso.h"
68 #endif
69 #include "usbdev_serial.h"
70 #include "usbdev_utils.h"
71 
72 // Test properties
73 //
74 // 16MiB takes about 40s presently with no appreciable CPU activity on the CW310
75 // (ie. undefined transmitted data, and no checking of received data) but ca.
76 // 152s with LFSR generation and checking across all of the 11 streams possible.
77 //
78 // Note: in normal use such as regression tests, the stream signatures will
79 // override the specified transfer amount.
80 constexpr uint32_t kTransferBytes = (0x10U << 20);
81 
82 // Has any data yet been received from the device?
83 bool received = false;
84 
85 // Time of first data reception.
86 uint64_t start_time = 0U;
87 
88 // Configuration settings for the test.
89 TestConfig cfg(false, // Not verbose
90  true, // Retrieve data from the device
91  true, // Check the retrieved data
92  true); // Send modified data to the device
93 
94 static USBDevice dev;
95 
96 // State information for each of the streams.
97 static USBDevStream *streams[STREAMS_MAX];
98 
99 // Parse a command line option and return boolean value.
100 static bool GetBool(const char *p);
101 
102 // Construct a modified port name for the next stream.
103 static void PortNext(char *next, size_t n, const char *curr);
104 
105 // Parse a command line option and return boolean value.
106 bool GetBool(const char *p) {
107  return (*p == '1') || (tolower(*p) == 'y') || (*p == '\r') || (*p == '\n') ||
108  (*p == '\0');
109 }
110 
111 // Parse a command line option, retrieving a byte and indicating
112 // success/failure.
113 bool GetByte(const char **pp, uint8_t &byte) {
114  const char *p = *pp;
115  if (isdigit(*p)) {
116  uint32_t n = 0u;
117  do {
118  n = (n * 10) + *p++ - '0';
119  } while (n < 0x100u && isdigit(*p));
120  if (n < 0x100u) {
121  byte = (uint8_t)n;
122  *pp = p;
123  return true;
124  }
125  }
126  return false;
127 }
128 
129 // Parse a command line option specifying the bus number and device address.
130 bool GetDevice(const char *p, uint8_t &busNumber, uint8_t &devAddress) {
131  return GetByte(&p, busNumber) && (*p++ == ':') && GetByte(&p, devAddress) &&
132  *p == '\0';
133 }
134 
135 // Construct a modified port name for the next stream.
136 void PortNext(char *next, size_t n, const char *curr) {
137  // We're expecting a port name of the form '/dev/ttyUSB<n>'
138  if (curr != next) {
139  strncpy(next, curr, n);
140  }
141  while (*next != '\0') {
142  if (isdigit(*next)) {
143  int port = atoi(next);
144  snprintf(next, n, "%u", (unsigned)port + 1U);
145  break;
146  }
147  next++;
148  n--;
149  }
150 }
151 
152 // Report command line syntax.
153 void ReportSyntax(void) {
154  fputs(
155  "Usage:\n"
156  " stream [-n<streams>][-v<bool>][-c<bool>][-r<bool>][-s<bool>][-t][-z]\n"
157  " [[-d<bus>:<address>] | [--device <bus>:<address>]]\n"
158  " [<input port>[ <output port>]]"
159  "\n\n"
160  " --device programmatically specify a particular USB device by bus\n"
161  " number and device address (see 'lsusb' output).\n"
162  "\n\n"
163  " -c check any retrieved data against expectations\n"
164  " -d specify a particular USB device by bus number"
165  " and device address\n"
166  " -r retrieve data from device\n"
167  " -s send data to device\n"
168  " -t use serial ports (ttyUSBx) in preference to libusb Bulk\n"
169  " Transfer streams for usbdev_stream_test\n"
170  " -v verbose reporting\n"
171  " -z perform suspend-resume signaling throughout the test"
172  "\n\n"
173  " <bool> values may be 0,1,n or y, and they default to 1\n",
174  stderr);
175 }
176 
177 static int RunTest(USBDevice *dev, const char *in_port, const char *out_port) {
178  // We need to modify the port names for each non-initial stream.
179  char out_name[FILENAME_MAX];
180  char in_name[FILENAME_MAX];
181 
182  // Collect the test number and the test arguments so that we may ascertain
183  // the transfer type of each of the streams.
184  uint8_t testNum = dev->TestNumber();
185  uint8_t testArg[4];
186  for (unsigned arg = 0U; arg < 4U; arg++) {
187  testArg[arg] = dev->TestArg(arg);
188  }
189 
190  // Determine the number of streams from the test descriptor; the device-side
191  // software supplies the stream count.
192  unsigned nstreams = 2U;
193  switch (testNum) {
194  case USBDevice::kUsbTestNumberStreams:
195  case USBDevice::kUsbTestNumberIso:
196  case USBDevice::kUsbTestNumberMixed:
197  // The lower nibble of the first test argument specifies the stream count
198  // in these test descriptions.
199  nstreams = testArg[0] & 0xfU;
200  break;
201  // Other tests default to 2 Bulk streams.
202  default:
203  nstreams = 2U;
204  break;
205  }
206 
207  // Decide upon the number of bytes to be transferred for the entire test.
208  uint32_t transfer_bytes = kTransferBytes;
209  transfer_bytes = (transfer_bytes + nstreams - 1) / nstreams;
210  if (cfg.verbose) {
211  std::cout << " - " << nstreams << " stream(s), 0x" << std::hex
212  << transfer_bytes << std::dec << " bytes each" << std::endl;
213  }
214 
215  // Initialize all streams.
216  for (unsigned idx = 0U; idx < nstreams; idx++) {
217  USBDevStream::StreamType streamType;
218 
219  switch (testNum) {
220  case USBDevice::kUsbTestNumberStreams:
221  // For the basic streaming test where all active endpoints are using
222  // Bulk Transfer types, we may either use the ttyUSBn serial port
223  // interface or we may use libusb.
224  //
225  // In the former case we cannot support suspend-resume testing because
226  // data will get buffered somewhere within the software layers and
227  // lost when the file descriptors are closed and opened.
228  if (cfg.serial && !cfg.suspending) {
229  streamType = USBDevStream::StreamType_Serial;
230  } else {
231  streamType = USBDevStream::StreamType_Bulk;
232  }
233  break;
234  case USBDevice::kUsbTestNumberIso:
235  streamType = USBDevStream::StreamType_Isochronous;
236  break;
237  case USBDevice::kUsbTestNumberMixed: {
238  uint32_t mixedTypes =
239  (testArg[3] << 16) | (testArg[2] << 8) | testArg[1];
240  // Two bits per stream specify the stream/transfer type in terms of the
241  // USB standard endpoint types.
242  switch ((mixedTypes >> (idx * 2)) & 3U) {
243  case 0U:
244  streamType = USBDevStream::StreamType_Control;
245  break;
246  case 1U:
247  streamType = USBDevStream::StreamType_Isochronous;
248  break;
249  case 2U:
250  streamType = USBDevStream::StreamType_Bulk;
251  break;
252  default:
253  streamType = USBDevStream::StreamType_Interrupt;
254  break;
255  }
256  } break;
257  // Other tests default to 2 Bulk streams.
258  default:
259  streamType = USBDevStream::StreamType_Bulk;
260  break;
261  }
262 
263  std::cout << "S" << idx << ": " << USBDevStream::StreamTypeName(streamType)
264  << std::endl;
265 
266  bool opened(false);
267 #if STREAMTEST_LIBUSB
268  bool bulk(true);
269 #endif
270  switch (streamType) {
271  case USBDevStream::StreamType_Serial: {
272  USBDevSerial *s;
273  s = new USBDevSerial(idx, transfer_bytes, cfg.retrieve, cfg.check,
274  cfg.send, cfg.verbose);
275  if (s) {
276  opened = s->Open(in_port, out_port);
277  if (opened) {
278  streams[idx] = s;
279 
280  // Modify the port name for the next stream.
281  PortNext(out_name, sizeof(out_name), out_port);
282  PortNext(in_name, sizeof(in_name), in_port);
283  out_port = out_name;
284  in_port = in_name;
285  }
286  }
287  } break;
288 
289 #if STREAMTEST_LIBUSB
290  case USBDevStream::StreamType_Isochronous: {
291  USBDevIso *iso;
292  iso = new USBDevIso(dev, idx, transfer_bytes, cfg.retrieve, cfg.check,
293  cfg.send, cfg.verbose);
294  if (iso) {
295  opened = iso->Open(idx);
296  if (opened) {
297  streams[idx] = iso;
298  }
299  }
300  } break;
301 
302  case USBDevStream::StreamType_Interrupt:
303  bulk = false;
304  // no break; Bulk Transfers are handled identically to Interrupt
305  // Transfers.
306  case USBDevStream::StreamType_Bulk: {
307  USBDevInt *interrupt;
308  interrupt = new USBDevInt(dev, bulk, idx, transfer_bytes, cfg.retrieve,
309  cfg.check, cfg.send, cfg.verbose);
310  if (interrupt) {
311  opened = interrupt->Open(idx);
312  if (opened) {
313  streams[idx] = interrupt;
314  }
315  }
316  } break;
317 #endif
318  default:
319  assert(!"Unrecognised/invalid stream type");
320  break;
321  }
322 
323  if (!opened) {
324  std::cerr << "Failed to open stream" << std::endl;
325  if (idx > 0U) {
326  do {
327  idx--;
328  delete streams[idx];
329  } while (idx > 0U);
330  }
331  return 1;
332  }
333  }
334 
335  std::cout << "Streaming..." << std::endl;
336 
337  // Times are in microseconds.
338  constexpr uint32_t kRunInterval = 5 * 1000000; // Running before suspending.
339  constexpr uint32_t kSuspendingInterval = 5 * 1000; // Suspending.
340  constexpr uint32_t kSuspendedInterval = 5 * 1000000; // Device is suspended.
341  // Resume Signaling shall occur for at least 20ms but we have no control.
342  // over its duration, so there's little point trying to communicate sooner.
343  constexpr uint32_t kResumeInterval = 30 * 1000; // Resuming before traffic.
344  uint64_t start_time = time_us();
345  uint32_t prev_bytes = 0;
346  bool done = false;
347  do {
348  uint32_t total_bytes = 0U;
349  uint32_t total_recv = 0U;
350  uint32_t total_sent = 0U;
351  bool failed = false;
352 
353  done = false;
354  switch (dev->CurrentState()) {
355  case USBDevice::StateStreaming:
356  done = true;
357  for (unsigned idx = 0U; idx < nstreams; idx++) {
358  // Service this stream.
359  if (!streams[idx]->Service()) {
360  failed = true;
361  break;
362  }
363 
364  // Update the running totals.
365  total_bytes += streams[idx]->TransferBytes();
366  total_recv += streams[idx]->BytesRecvd();
367  total_sent += streams[idx]->BytesSent();
368 
369  // Has the stream completed all its work yet?
370  if (!streams[idx]->Completed()) {
371  done = false;
372  }
373  }
374 
375  // Initiate transition to Suspended.
376  if (cfg.suspending && elapsed_time(start_time) >= kRunInterval) {
377  std::cout << "Waiting to suspend" << std::endl;
378  // Notify all of the streams that no more traffic shall be initiated.
379  for (unsigned idx = 0U; idx < nstreams; idx++) {
380  streams[idx]->Pause();
381  }
382  if (true) {
383  // Initiate autosuspend.
384  dev->Suspend();
385  // Start of Suspending interval.
386  start_time = time_us();
387  } else {
388  // TODO: There remains an issue in which the host-side software
389  // apparently fails to complete one or more transfers, which
390  // prevents us from properly resuming the transfers after the device
391  // has cycled through suspend-resume; this code persists in order
392  // to demonstrate and further investigate that.
393  //
394  // At the time the issue appeared to be in the behavior of the
395  // driver stack/libusb.
396 
397  std::cout << "Attempting to resume" << std::endl;
398  // Notify all of the streams that no more traffic shall be
399  // initiated.
400  for (unsigned idx = 0U; idx < nstreams; idx++) {
401  streams[idx]->Resume();
402  }
403  std::cout << "Resuming streaming..." << std::endl;
404  // Start of Running interval.
405  start_time = time_us();
406  }
407  }
408  break;
409 
410  // Put the device into Suspended for a while.
411  case USBDevice::StateSuspending:
412  if (elapsed_time(start_time) >= kSuspendingInterval) {
413  dev->SetState(USBDevice::StateSuspended);
414  // Start of Suspended interval.
415  start_time = time_us();
416  std::cout << "Suspended" << std::endl;
417  }
418  break;
419 
420  case USBDevice::StateSuspended:
421  if (elapsed_time(start_time) >= kSuspendedInterval) {
422  dev->Resume();
423  // Start of Resuming interval.
424  start_time = time_us();
425  }
426  break;
427 
428  case USBDevice::StateResuming:
429  if (elapsed_time(start_time) >= kResumeInterval) {
430  for (unsigned idx = 0U; idx < nstreams; idx++) {
431  streams[idx]->Resume();
432  }
433 
434  dev->SetState(USBDevice::StateStreaming);
435  // Start of Running interval.
436  start_time = time_us();
437  }
438  break;
439  }
440 
441  // Service the USBDevice to keep USB transfers flowing.
442  if (!failed) {
443  failed = !dev->Service();
444  }
445 
446  // Tidy up if something went wrong.
447  if (failed) {
448  for (unsigned idx = 0U; idx < nstreams; idx++) {
449  (void)streams[idx]->Stop();
450  }
451  return 3;
452  }
453 
454  // Down counting of the number of bytes remaining to be transferred.
455  if (std::abs((int32_t)total_sent - (int32_t)prev_bytes) >= 0x1000 || done) {
456  // Note: if there are Isochronous streams present then the bytes left
457  // count may hit zero some time before the test completes on the device
458  // side because packet delivery is not guaranteed.
459  uint32_t bytes_left =
460  (total_sent < total_bytes) ? (total_bytes - total_sent) : 0U;
461  std::cout << "Bytes received: 0x" << std::hex << total_recv
462  << " -- Left to send: 0x" << bytes_left << " "
463  << std::dec << std::endl;
464  prev_bytes = total_sent;
465  }
466  } while (!done);
467 
468  uint64_t elapsed_time = time_us() - start_time;
469 
470  // Report time elapsed from the start of data transfer.
471  for (unsigned idx = 0U; idx < nstreams; idx++) {
472  streams[idx]->Stop();
473  }
474 
475  // TODO: introduce a crude estimate of the performance being achieved,
476  // for profiling the performance of IN and OUT traffic; totals and individual
477  // endpoints?
478  double elapsed_secs = elapsed_time / 1e6;
479  printf("Test completed in %.2lf seconds (%" PRIu64 "us)\n", elapsed_secs,
480  elapsed_time);
481 
482  return 0;
483 }
484 
485 int main(int argc, char *argv[]) {
486  const uint16_t kVendorID = 0x18d1u;
487  const uint16_t kProductID = 0x503au;
488  const char *out_port = nullptr;
489  const char *in_port = nullptr;
490  uint8_t devAddress = 0u;
491  uint8_t busNumber = 0u;
492 
493  cfg.override_flags = false;
494 
495  // Collect options and alternative port names.
496  for (int i = 1; i < argc; i++) {
497  if (argv[i][0] == '-') {
498  switch (tolower(argv[i][1])) {
499  case 'c':
500  cfg.check = GetBool(&argv[i][2]);
501  cfg.override_flags = true;
502  break;
503  case 'd':
504  if (!GetDevice(&argv[i][2], busNumber, devAddress)) {
505  std::cerr << "ERROR: Unrecognised option '" << argv[i] << "'"
506  << std::endl;
507  ReportSyntax();
508  return 7;
509  }
510  break;
511  case 'r':
512  cfg.retrieve = GetBool(&argv[i][2]);
513  cfg.override_flags = true;
514  break;
515  case 's':
516  cfg.send = GetBool(&argv[i][2]);
517  cfg.override_flags = true;
518  break;
519  case 't':
520  cfg.serial = GetBool(&argv[i][2]);
521  break;
522  case 'v':
523  cfg.verbose = GetBool(&argv[i][2]);
524  break;
525  case 'z':
526  cfg.suspending = GetBool(&argv[i][2]);
527  break;
528  case '-':
529  // The bus/address may be specified programmatically as '--device'
530  // with confidence that this parameter/syntax will not change.
531  if (!strcmp(&argv[i][2], "device") && i < argc - 1) {
532  // The next argument should be 'bus:address'
533  if (GetDevice(argv[++i], busNumber, devAddress)) {
534  break;
535  }
536  }
537  // no break
538  default:
539  std::cerr << "ERROR: Unrecognised option '" << argv[i] << "'"
540  << std::endl;
541  ReportSyntax();
542  return 6;
543  }
544  } else if (!out_port) {
545  out_port = argv[i];
546  } else if (!in_port) {
547  in_port = argv[i];
548  } else {
549  std::cerr << "ERROR: Parameter '" << argv[i] << "' unrecognised"
550  << std::endl;
551  ReportSyntax();
552  return 7;
553  }
554  }
555 
556  // Furnish test with default port names.
557  if (!out_port) {
558  out_port = "/dev/ttyUSB0";
559  }
560  if (!in_port) {
561  in_port = "/dev/ttyUSB0";
562  }
563 
564  std::cout << "USB Streaming Test" << std::endl
565  << " (host-side implementation of usbdev streaming tests)"
566  << std::endl;
567 
568  // Locate the USB device using Vendor and Product IDs, and optionally a
569  // specific device address and bus number to handle the presence of multiple
570  // similar devices.
571  USBDevice dev(cfg.verbose);
572  if (!dev.Init(kVendorID, kProductID, devAddress, busNumber)) {
573  return 2;
574  }
575 
576  if (!dev.Open()) {
577  dev.Fin();
578  return 3;
579  }
580 
581  // Read a vendor-specific test descriptor from the device-side software in
582  // order to ascertain the test configuration and required behavior.
583  if (!dev.ReadTestDesc()) {
584  dev.Fin();
585  return 3;
586  }
587 
588  int rc = RunTest(&dev, in_port, out_port);
589 
590  dev.Fin();
591 
592  return rc;
593 }