Software APIs
usbdev_iso.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 #include "usbdev_iso.h"
5 
6 #include <cassert>
7 #include <cstdio>
8 
9 #include "usbdev_utils.h"
10 
11 // Stub callback function supplied to libusb.
12 void LIBUSB_CALL USBDevIso::CbStubIN(struct libusb_transfer *xfr) {
13  USBDevIso *self = reinterpret_cast<USBDevIso *>(xfr->user_data);
14  self->CallbackIN(xfr);
15 }
16 
17 void LIBUSB_CALL USBDevIso::CbStubOUT(struct libusb_transfer *xfr) {
18  USBDevIso *self = reinterpret_cast<USBDevIso *>(xfr->user_data);
19  self->CallbackOUT(xfr);
20 }
21 
22 bool USBDevIso::Open(unsigned interface) {
23  int rc = dev_->ClaimInterface(interface);
24  if (rc < 0) {
25  return dev_->ErrorUSB("ERROR: Claiming interface", rc);
26  }
27 
28  // Retain the interface number.
29  interface_ = interface;
30 
31  // Remember the (assumed) endpoints which we're using.
32  epOut_ = interface + 1U;
33  epIn_ = 0x80U | epOut_;
34 
35  // No transfers in progress.
36  xfrIn_ = nullptr;
37  xfrOut_ = nullptr;
38 
39  // Expected sequence number of first packet.
40  tst_seq_ = 0U;
41 
42  // Maximum size of a packet in bytes.
43  maxPacketSize_ = USBDevice::kDevIsoMaxPacketSize;
44 
45  return true;
46 }
47 
49  SetClosing(true);
50 
51  int rc = dev_->ReleaseInterface(interface_);
52  if (rc < 0) {
53  std::cerr << "" << std::endl;
54  }
55 }
56 
58  SetClosing(true);
59 
60  while (inActive_ || outActive_) {
61  dev_->Service();
62  }
63 
64  int rc = dev_->ReleaseInterface(interface_);
65  if (rc < 0) {
66  std::cerr << "" << std::endl;
67  }
68 }
69 
71  SetClosing(false);
72 
73  int rc = dev_->ClaimInterface(interface_);
74  if (rc < 0) {
75  return dev_->ErrorUSB("ERROR: Claiming interface", rc);
76  }
77  return true;
78 }
79 
80 // Return an indication of whether this stream has completed its transfer.
81 bool USBDevIso::Completed() const {
82  // Note: an Isochronous stream presently cannot know whether it has
83  // transferred sufficient data for the device-side software to have completed.
84  //
85  // TODO: perhaps we just time out after a period of inactivity. The device-
86  // side software ultimately decides success or failure of the test, at which
87  // point the test harness will be terminated.
88  return false;
89 }
90 
91 // Return a summary report of the stream settings of status.
92 std::string USBDevIso::Report(bool status, bool verbose) const { return ""; }
93 
94 void USBDevIso::DumpIsoTransfer(struct libusb_transfer *xfr) const {
95  for (int idx = 0U; idx < xfr->num_iso_packets; idx++) {
96  struct libusb_iso_packet_descriptor *pack = &xfrIn_->iso_packet_desc[idx];
97  std::cout << "Requested " << pack->length << " actual "
98  << pack->actual_length << std::endl;
99  // Buffer dumping works only because we have just a single Iso packet per
100  // transfer.
101  buffer_dump(stdout, (uint8_t *)xfr->buffer, pack->actual_length);
102  }
103 }
104 
105 // Retrieving of IN traffic from device.
106 bool USBDevIso::ServiceIN() {
107  // Ensure that we have enough space available for a full packet; the device
108  // software decides upon the length of each packet.
109  uint8_t *space;
110  bool ok = ProvisionSpace(&space, maxPacketSize_);
111  if (ok) {
112  if (!xfrIn_) {
113  xfrIn_ = dev_->AllocTransfer(kNumIsoPackets);
114  if (!xfrIn_) {
115  return false;
116  }
117  }
118 
119  dev_->FillIsoTransfer(xfrIn_, epIn_, space, maxPacketSize_, kNumIsoPackets,
120  CbStubIN, this, kIsoTimeout);
121  dev_->SetIsoPacketLengths(xfrIn_, maxPacketSize_);
122 
123  int rc = dev_->SubmitTransfer(xfrIn_);
124  if (rc < 0) {
125  return dev_->ErrorUSB("ERROR: Submitting IN transfer", rc);
126  }
127  inActive_ = true;
128  } else {
129  inActive_ = false;
130  }
131  return true;
132 }
133 
134 // Sending of OUT traffic to device.
135 bool USBDevIso::ServiceOUT() {
136  // Do we have one or more packets ready for sending?
137  if (pktLen_.empty()) {
138  // Nothing to propagate at this time.
139  outActive_ = false;
140  } else {
141  uint32_t len = pktLen_.front();
142  pktLen_.pop();
143  // We should have propagated only valid packets to the OUT side ready for
144  // transmission.
145  assert(len >= sizeof(usbdev_stream_sig_t));
146 
147  uint8_t *data;
148  size_t num_bytes = DataAvailable(&data);
149  assert(num_bytes >= len);
150 
151  // Supply details of the single OUT packet.
152  if (!xfrOut_) {
153  xfrOut_ = dev_->AllocTransfer(kNumIsoPackets);
154  if (!xfrOut_) {
155  // Stream is not operational.
156  return false;
157  }
158  }
159  dev_->FillIsoTransfer(xfrOut_, epOut_, data, len, kNumIsoPackets, CbStubOUT,
160  this, kIsoTimeout);
161  dev_->SetIsoPacketLengths(xfrOut_, len);
162 
163  int rc = dev_->SubmitTransfer(xfrOut_);
164  if (rc < 0) {
165  return dev_->ErrorUSB("ERROR: Submitting OUT transfer", rc);
166  }
167  outActive_ = true;
168  }
169  // Stream remains operational, even if it presently has no work on the OUT
170  // side.
171  return true;
172 }
173 
175  if (failed_) {
176  return false;
177  }
178  // (Re)start Isochronous IN traffic if not already in progress.
179  if (!inActive_ && !ServiceIN()) {
180  return false;
181  }
182  // (Re)start Isochronous OUT traffic if not already in progress and there is
183  // data available to be transmitted.
184  if (!outActive_ && !ServiceOUT()) {
185  return false;
186  }
187  return true;
188 }
189 
190 // Callback function supplied to libusb for IN transfers.
191 void USBDevIso::CallbackIN(struct libusb_transfer *xfr) {
192  if (xfr->status != LIBUSB_TRANSFER_COMPLETED) {
193  std::cerr << PrefixID() << " Invalid/unexpected IN transfer status "
194  << xfr->status << std::endl;
195  failed_ = true;
196  return;
197  }
198 
199  if (verbose_) {
200  std::cout << PrefixID() << "CallbackIN xfr " << xfr << " num_iso_packets "
201  << xfr->num_iso_packets << std::endl;
202  DumpIsoTransfer(xfr);
203  }
204 
205  for (int idx = 0U; idx < xfr->num_iso_packets; idx++) {
206  struct libusb_iso_packet_descriptor *pack = &xfr->iso_packet_desc[idx];
207  if (pack->status != LIBUSB_TRANSFER_COMPLETED) {
208  std::cerr << "ERROR: pack " << idx << " status " << pack->status
209  << std::endl;
210  inActive_ = false;
211  return;
212  }
213 
214  if (pack->actual_length) {
215  // Reset signature detection, because a new signature is included at the
216  // start of each Isochronous packet.
217  SigReset();
218 
219  // Check that this packet is recognized as commencing with a valid
220  // signature, process the data within the packet, and then retain its
221  // details.
222  usbdev_stream_sig_t sig;
223  uint32_t dropped = SigDetect(&sig, xfr->buffer, pack->actual_length);
224  if (SigReceived() && dropped < pack->actual_length &&
225  sizeof(usbdev_stream_sig_t) <= pack->actual_length - dropped) {
226  // Pick up information from this packet signature.
227  SigProcess(sig);
228 
229  // Valid packet received; payload includes the signature which we
230  // retain and propagate to the caller to permit synchronization.
231  uint32_t payload = pack->actual_length - dropped;
232  pktLen_.push(payload);
233 
234  // Since packets may have been dropped we must use the supplied values
235  // of the device-side LFSR
236  uint16_t seq = (uint16_t)((sig.seq_hi << 8) | sig.seq_lo);
237  if (seq == tst_seq_) {
238  if (sig.init_lfsr != tst_lfsr_) {
239  std::cerr << "ERROR: Unexpected device-side LFSR value (expected 0x"
240  << std::hex << tst_lfsr_ << " received 0x"
241  << sig.init_lfsr << ")" << std::dec << std::endl;
242  inActive_ = false;
243  return;
244  }
245  } else if (seq < tst_seq_) {
246  std::cerr << "ERROR: Iso stream packets out of order (expected seq 0x"
247  << std::hex << tst_seq_ << " received 0x" << seq << ")"
248  << std::dec << std::endl;
249  inActive_ = false;
250  return;
251  } else {
252  // One or more packets has disappeared; use the supplied LFSR to
253  // resynchronize.
254  tst_lfsr_ = sig.init_lfsr;
255  }
256 
257  // Remember the sequence number that we expect to see next.
258  tst_seq_ = seq + 1U;
259 
260  // Supply the host-side LFSR value so that the device may check the
261  // content of received OUT packets.
262  const size_t sig_size = sizeof(usbdev_stream_sig_t);
263  uint8_t *dp = &xfr->buffer[dropped];
264  dp[offsetof(usbdev_stream_sig_t, init_lfsr)] = dpi_lfsr_;
265  ProcessData(dp + sig_size, payload - sig_size);
266 
267  CommitData(payload);
268  } else {
269  std::cerr << PrefixID() << " received invalid Iso packet of "
270  << pack->actual_length << " bytes" << std::endl;
271  }
272  }
273  }
274 
275  if (CanSchedule()) {
276  // Attempt to set up another IN transfer.
277  failed_ = !ServiceIN();
278  } else {
279  inActive_ = false;
280  }
281 }
282 
283 // Callback function supplied to libusb for OUT transfers.
284 void USBDevIso::CallbackOUT(struct libusb_transfer *xfr) {
285  if (xfr->status != LIBUSB_TRANSFER_COMPLETED) {
286  std::cerr << PrefixID() << " Invalid/unexpected OUT transfer status "
287  << xfr->status << std::endl;
288  failed_ = true;
289  return;
290  }
291 
292  if (verbose_) {
293  const void *buf = reinterpret_cast<void *>(xfr->buffer);
294  std::cout << PrefixID() << "CallbackOUT xfr " << xfr << " buffer " << buf
295  << " num_iso_packets " << xfr->num_iso_packets << std::endl;
296  DumpIsoTransfer(xfr);
297  }
298 
299  for (int idx = 0U; idx < xfr->num_iso_packets; idx++) {
300  struct libusb_iso_packet_descriptor *pack = &xfr->iso_packet_desc[idx];
301  if (pack->status != LIBUSB_TRANSFER_COMPLETED) {
302  std::cout << "ERROR: pack " << idx << " status " << pack->status
303  << std::endl;
304  outActive_ = false;
305  exit(0);
306  return;
307  }
308 
309  if (pack->actual_length) {
310  ConsumeData(pack->actual_length);
311  }
312  }
313 
314  if (CanSchedule()) {
315  // Attempt to set up another OUT transfer.
316  failed_ = !ServiceOUT();
317  } else {
318  outActive_ = false;
319  }
320 }