Newer
Older
/*
* BRLTTY - A background process providing access to the console screen (when in
* text mode) for a blind person using a refreshable braille display.
*
* Copyright (C) 1995-2018 by The BRLTTY Developers.
*
* BRLTTY comes with ABSOLUTELY NO WARRANTY.
*
* This is free software, placed under the terms of the
* GNU Lesser General Public License, as published by the Free Software
* Foundation; either version 2.1 of the License, or (at your option) any
* later version. Please see the file LICENSE-LGPL for details.
*
* This software is maintained by Dave Mielke <dave@mielke.cc>.
*/
#include "prologue.h"
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/stat.h>
#ifdef HAVE_REGEX_H
#include <regex.h>
#endif /* HAVE_REGEX_H */
#include "io_usb.h"
#include "usb_internal.h"
uint8_t recipient,
uint8_t type,
uint8_t request,
uint16_t value,
uint16_t index,
int timeout
) {
return usbControlTransfer(device, UsbControlDirection_Input, recipient, type,
request, value, index, buffer, length, timeout);
}
uint8_t recipient,
uint8_t type,
uint8_t request,
uint16_t value,
uint16_t index,
int timeout
) {
return usbControlTransfer(device, UsbControlDirection_Output, recipient, type,
request, value, index, (void *)buffer, length, timeout);
}
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
usbGetDescriptor (
UsbDevice *device,
unsigned char type,
unsigned char number,
unsigned int index,
UsbDescriptor *descriptor,
int timeout
) {
return usbControlRead(device, UsbControlRecipient_Device, UsbControlType_Standard,
UsbStandardRequest_GetDescriptor, (type << 8) | number, index,
descriptor->bytes, sizeof(descriptor->bytes), timeout);
}
int
usbGetDeviceDescriptor (
UsbDevice *device,
UsbDeviceDescriptor *descriptor
) {
UsbDescriptor desc;
int size = usbGetDescriptor(device, UsbDescriptorType_Device, 0, 0, &desc, 1000);
if (size != -1) {
*descriptor = desc.device;
}
return size;
}
int
usbGetLanguage (
UsbDevice *device,
uint16_t *language,
int timeout
) {
UsbDescriptor descriptor;
ssize_t size = usbGetDescriptor(device, UsbDescriptorType_String,
*language = getLittleEndian16(descriptor.string.wData[0]);
logMessage(LOG_CATEGORY(USB_IO), "USB language: %02X", *language);
} else {
logMessage(LOG_ERR, "USB language code string too short: %"PRIssize, size);
errno = EIO;
} else {
logMessage(LOG_ERR, "USB language code string read error");
return 0;
}
char *
usbDecodeString (const UsbStringDescriptor *descriptor) {
size_t count = (descriptor->bLength - 2) / sizeof(descriptor->wData[0]);
char buffer[(count * UTF8_LEN_MAX) + 1];
const uint16_t *source = descriptor->wData;
const uint16_t *end = source + count;
char *target = buffer;
while (source < end) {
size_t length = convertWcharToUtf8(getLittleEndian16(*source++), target);
target += length;
}
*target = 0;
{
char *string = strdup(buffer);
if (!string) logMallocError();
return string;
}
}
char *
usbGetString (
UsbDevice *device,
unsigned char number,
int timeout
) {
UsbDescriptor descriptor;
if (!device->language) {
if (!usbGetLanguage(device, &device->language, timeout)) {
if (usbGetDescriptor(device, UsbDescriptorType_String,
number, device->language,
&descriptor, timeout) == -1) {
logMessage(LOG_ERR, "USB string read error: %u", number);
return usbDecodeString(&descriptor.string);
}
char *
usbGetManufacturer (UsbDevice *device, int timeout) {
return usbGetString(device, device->descriptor.iManufacturer, timeout);
}
char *
usbGetProduct (UsbDevice *device, int timeout) {
return usbGetString(device, device->descriptor.iProduct, timeout);
}
char *
usbGetSerialNumber (UsbDevice *device, int timeout) {
return usbGetString(device, device->descriptor.iSerialNumber, timeout);
}
static size_t
usbFormatLogSetupPacket (char *buffer, size_t size, const void *data) {
const UsbSetupPacket *setup = data;
size_t length;
STR_BEGIN(buffer, size);
STR_PRINTF("setup packet: Typ:%02X Req:%02X Val:%04X Idx:%04X Len:%04X",
setup->bRequestType, setup->bRequest,
getLittleEndian16(setup->wValue),
getLittleEndian16(setup->wIndex),
getLittleEndian16(setup->wLength));
length = STR_LENGTH;
STR_END;
return length;
}
void
usbLogSetupPacket (const UsbSetupPacket *setup) {
logData(LOG_CATEGORY(USB_IO), usbFormatLogSetupPacket, setup);
}
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
void
usbMakeSetupPacket (
UsbSetupPacket *setup,
uint8_t direction,
uint8_t recipient,
uint8_t type,
uint8_t request,
uint16_t value,
uint16_t index,
uint16_t length
) {
setup->bRequestType = direction | recipient | type;
setup->bRequest = request;
putLittleEndian16(&setup->wValue, value);
putLittleEndian16(&setup->wIndex, index);
putLittleEndian16(&setup->wLength, length);
usbLogSetupPacket(setup);
}
void
usbLogEndpointData (
UsbEndpoint *endpoint, const char *label,
const void *data, size_t size
) {
logBytes(LOG_CATEGORY(USB_IO), "endpoint %02X %s", data, size,
endpoint->descriptor->bEndpointAddress, label);
}
void
usbLogString (
UsbDevice *device,
unsigned char number,
) {
if (number) {
char *string = usbGetString(device, number, 1000);
if (string) {
logMessage(LOG_INFO, "USB: %s: %s", label, string);
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
free(string);
}
}
}
int
usbStringEquals (const char *reference, const char *value) {
return strcmp(reference, value) == 0;
}
int
usbStringMatches (const char *reference, const char *value) {
int ok = 0;
#ifdef HAVE_REGEX_H
regex_t expression;
if (regcomp(&expression, value, REG_EXTENDED|REG_NOSUB) == 0) {
if (regexec(&expression, reference, 0, NULL, 0) == 0) {
ok = 1;
}
regfree(&expression);
}
#endif /* HAVE_REGEX_H */
return ok;
}
int
usbVerifyString (
UsbDevice *device,
UsbStringVerifier verify,
unsigned char index,
const char *value
) {
int ok = 0;
if (!(value && *value)) return 1;
if (index) {
char *reference = usbGetString(device, index, 1000);
if (reference) {
if (verify(reference, value)) ok = 1;
free(reference);
}
}
return ok;
}
int
usbVerifyManufacturerName (UsbDevice *device, const char *eRegExp) {
return usbVerifyString(device, usbStringMatches,
device->descriptor.iManufacturer, eRegExp);
}
int
usbVerifyProductDescription (UsbDevice *device, const char *eRegExp) {
return usbVerifyString(device, usbStringMatches,
device->descriptor.iProduct, eRegExp);
}
int
usbVerifySerialNumber (UsbDevice *device, const char *string) {
return usbVerifyString(device, usbStringEquals,
device->descriptor.iSerialNumber, string);
}
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
int
usbParseVendorIdentifier (uint16_t *identifier, const char *string) {
if (string && *string) {
unsigned int value;
if (isUnsignedInteger(&value, string)) {
if ((value > 0) && (value <= UINT16_MAX)) {
*identifier = value;
return 1;
}
}
logMessage(LOG_WARNING, "invalid USB vendor identifier: %s", string);
return 0;
}
*identifier = 0;
return 1;
}
int
usbVerifyVendorIdentifier (const UsbDeviceDescriptor *descriptor, uint16_t identifier) {
if (!identifier) return 1;
return identifier == getLittleEndian16(descriptor->idVendor);
}
int
usbParseProductIdentifier (uint16_t *identifier, const char *string) {
if (string && *string) {
unsigned int value;
if (isUnsignedInteger(&value, string)) {
if ((value > 0) && (value <= UINT16_MAX)) {
*identifier = value;
return 1;
}
}
logMessage(LOG_WARNING, "invalid USB product identifier: %s", string);
return 0;
}
*identifier = 0;
return 1;
}
int
usbVerifyProductIdentifier (const UsbDeviceDescriptor *descriptor, uint16_t identifier) {
if (!identifier) return 1;
return identifier == getLittleEndian16(descriptor->idProduct);
}
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
static int
usbVerifyStrings (
UsbDevice *device,
const char *const *strings,
unsigned char number
) {
if (!strings) return 1;
if (!number) return 0;
char *string = usbGetString(device, number, 1000);
int matched = 0;
if (string) {
while (*strings) {
if (strcmp(*strings, string) == 0) {
matched = 1;
break;
}
strings += 1;
}
free(string);
}
return matched;
}
const UsbDeviceDescriptor *
usbDeviceDescriptor (UsbDevice *device) {
return &device->descriptor;
}
int
usbGetConfiguration (
UsbDevice *device,
unsigned char *configuration
) {
ssize_t size = usbControlRead(device, UsbControlRecipient_Device, UsbControlType_Standard,
UsbStandardRequest_GetConfiguration, 0, 0,
configuration, sizeof(*configuration), 1000);
logMessage(LOG_WARNING, "USB standard request not supported: get configuration");
}
static void
usbDeallocateConfigurationDescriptor (UsbDevice *device) {
if (device->configuration) {
free(device->configuration);
device->configuration = NULL;
}
}
const UsbConfigurationDescriptor *
usbConfigurationDescriptor (
UsbDevice *device
) {
if (!device->configuration) {
if (device->descriptor.bNumConfigurations < 2) {
current = 1;
} else if (!usbGetConfiguration(device, ¤t)) {
current = 0;
}
if (current) {
UsbDescriptor descriptor;
unsigned char number;
for (number=0; number<device->descriptor.bNumConfigurations; number++) {
int size = usbGetDescriptor(device, UsbDescriptorType_Configuration,
number, 0, &descriptor, 1000);
if (size == -1) {
logMessage(LOG_WARNING, "USB configuration descriptor not readable: %d", number);
} else if (descriptor.configuration.bConfigurationValue == current) {
break;
}
}
if (number < device->descriptor.bNumConfigurations) {
int length = getLittleEndian16(descriptor.configuration.wTotalLength);
UsbDescriptor *descriptors;
if ((descriptors = malloc(length))) {
if (length > sizeof(descriptor)) {
size = usbControlRead(device, UsbControlRecipient_Device, UsbControlType_Standard,
UsbStandardRequest_GetDescriptor,
(UsbDescriptorType_Configuration << 8) | number,
0, descriptors, length, 1000);
} else {
memcpy(descriptors, &descriptor, (size = length));
}
if (size != -1) {
device->configuration = &descriptors->configuration;
} else {
free(descriptors);
}
} else {
logSystemError("USB configuration descriptor allocate");
logMessage(LOG_ERR, "USB configuration descriptor not found: %d", current);
}
}
}
return device->configuration;
}
int
usbConfigureDevice (
UsbDevice *device,
unsigned char configuration
) {
usbCloseInterface(device);
if (usbSetConfiguration(device, configuration)) {
usbDeallocateConfigurationDescriptor(device);
return 1;
}
{
const UsbConfigurationDescriptor *descriptor = usbConfigurationDescriptor(device);
if (descriptor)
if (descriptor->bConfigurationValue == configuration)
return 1;
}
return 0;
}
int
usbNextDescriptor (
UsbDevice *device,
const UsbDescriptor **descriptor
) {
if (*descriptor) {
const UsbDescriptor *next = (UsbDescriptor *)&(*descriptor)->bytes[(*descriptor)->header.bLength];
const UsbDescriptor *first = (UsbDescriptor *)device->configuration;
unsigned int length = getLittleEndian16(first->configuration.wTotalLength);
if ((&next->bytes[0] - &first->bytes[0]) >= length) return 0;
if ((&next->bytes[next->header.bLength] - &first->bytes[0]) > length) return 0;
*descriptor = next;
} else if (usbConfigurationDescriptor(device)) {
*descriptor = (UsbDescriptor *)device->configuration;
} else {
return 0;
}
return 1;
}
const UsbInterfaceDescriptor *
usbInterfaceDescriptor (
UsbDevice *device,
unsigned char interface,
unsigned char alternative
) {
const UsbDescriptor *descriptor = NULL;
while (usbNextDescriptor(device, &descriptor)) {
if (descriptor->interface.bDescriptorType == UsbDescriptorType_Interface) {
if (descriptor->interface.bInterfaceNumber == interface) {
if (descriptor->interface.bAlternateSetting == alternative) {
logMessage(LOG_WARNING, "USB: interface descriptor not found: %d.%d", interface, alternative);
unsigned int
usbAlternativeCount (
UsbDevice *device,
unsigned char interface
) {
unsigned int count = 0;
const UsbDescriptor *descriptor = NULL;
while (usbNextDescriptor(device, &descriptor)) {
if (descriptor->interface.bDescriptorType == UsbDescriptorType_Interface) {
if (descriptor->interface.bInterfaceNumber == interface) {
const UsbEndpointDescriptor *
usbEndpointDescriptor (
UsbDevice *device,
unsigned char endpointAddress
) {
const UsbDescriptor *descriptor = NULL;
while (usbNextDescriptor(device, &descriptor)) {
if (descriptor->endpoint.bDescriptorType == UsbDescriptorType_Endpoint) {
if (descriptor->endpoint.bEndpointAddress == endpointAddress) {
logMessage(LOG_WARNING, "USB: endpoint descriptor not found: %02X", endpointAddress);
static void
usbCancelInputMonitor (UsbEndpoint *endpoint) {
if (endpoint->direction.input.pipe.monitor) {
asyncCancelRequest(endpoint->direction.input.pipe.monitor);
endpoint->direction.input.pipe.monitor = NULL;
}
}
static inline int
usbHaveInputPipe (UsbEndpoint *endpoint) {
return endpoint->direction.input.pipe.output != INVALID_FILE_DESCRIPTOR;
}
static inline int
usbHaveInputError (UsbEndpoint *endpoint) {
return endpoint->direction.input.pipe.input == INVALID_FILE_DESCRIPTOR;
}
void
usbSetEndpointInputError (UsbEndpoint *endpoint, int error) {
if (!usbHaveInputError(endpoint)) {
endpoint->direction.input.pipe.error = error;
closeFile(&endpoint->direction.input.pipe.input);
}
}
static int
usbSetInputError (void *item, void *data) {
UsbEndpoint *endpoint = item;
const int *error = data;
if (usbHaveInputPipe(endpoint)) {
usbSetEndpointInputError(endpoint, *error);
}
return 0;
}
void
usbSetDeviceInputError (UsbDevice *device, int error) {
processQueue(device->endpoints, usbSetInputError, &error);
}
int
usbEnqueueInput (UsbEndpoint *endpoint, const void *buffer, size_t length) {
if (usbHaveInputError(endpoint)) {
errno = EIO;
return 0;
}
return writeFile(endpoint->direction.input.pipe.input, buffer, length) != -1;
}
void
usbDestroyInputPipe (UsbEndpoint *endpoint) {
usbCancelInputMonitor(endpoint);
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
closeFile(&endpoint->direction.input.pipe.input);
closeFile(&endpoint->direction.input.pipe.output);
}
int
usbMakeInputPipe (UsbEndpoint *endpoint) {
if (usbHaveInputPipe(endpoint)) return 1;
if (createAnonymousPipe(&endpoint->direction.input.pipe.input,
&endpoint->direction.input.pipe.output)) {
if (setBlockingIo(endpoint->direction.input.pipe.output, 0)) {
return 1;
}
}
usbDestroyInputPipe(endpoint);
return 0;
}
int
usbMonitorInputPipe (
UsbDevice *device, unsigned char endpointNumber,
AsyncMonitorCallback *callback, void *data
) {
UsbEndpoint *endpoint = usbGetInputEndpoint(device, endpointNumber);
if (endpoint) {
if (usbHaveInputPipe(endpoint)) {
usbCancelInputMonitor(endpoint);
if (!callback) return 1;
if (asyncMonitorFileInput(&endpoint->direction.input.pipe.monitor,
endpoint->direction.input.pipe.output,
callback, data)) {
return 1;
}
}
}
return 0;
}
static void
usbDeallocateEndpoint (void *item, void *data) {
UsbEndpoint *endpoint = item;
switch (USB_ENDPOINT_DIRECTION(endpoint->descriptor)) {
case UsbEndpointDirection_Input:
if (endpoint->direction.input.pending.alarm) {
asyncCancelRequest(endpoint->direction.input.pending.alarm);
endpoint->direction.input.pending.alarm = NULL;
}
if (endpoint->direction.input.pending.requests) {
deallocateQueue(endpoint->direction.input.pending.requests);
endpoint->direction.input.pending.requests = NULL;
if (endpoint->direction.input.completed.request) {
free(endpoint->direction.input.completed.request);
endpoint->direction.input.completed.request = NULL;
usbDeallocateEndpointExtension(endpoint->extension);
switch (USB_ENDPOINT_DIRECTION(endpoint->descriptor)) {
case UsbEndpointDirection_Input:
usbDestroyInputPipe(endpoint);
break;
default:
break;
}
usbTestEndpoint (const void *item, void *data) {
const UsbEndpoint *endpoint = item;
const unsigned char *endpointAddress = data;
return endpoint->descriptor->bEndpointAddress == *endpointAddress;
}
UsbEndpoint *
usbGetEndpoint (UsbDevice *device, unsigned char endpointAddress) {
UsbEndpoint *endpoint;
const UsbEndpointDescriptor *descriptor;
if ((endpoint = findItem(device->endpoints, usbTestEndpoint, &endpointAddress))) return endpoint;
if ((descriptor = usbEndpointDescriptor(device, endpointAddress))) {
{
const char *direction;
const char *transfer;
switch (USB_ENDPOINT_DIRECTION(descriptor)) {
case UsbEndpointDirection_Input: direction = "in"; break;
case UsbEndpointDirection_Output: direction = "out"; break;
}
switch (USB_ENDPOINT_TRANSFER(descriptor)) {
case UsbEndpointTransfer_Control: transfer = "ctl"; break;
case UsbEndpointTransfer_Isochronous: transfer = "iso"; break;
case UsbEndpointTransfer_Bulk: transfer = "blk"; break;
case UsbEndpointTransfer_Interrupt: transfer = "int"; break;
}
logMessage(LOG_CATEGORY(USB_IO), "ept=%02X dir=%s xfr=%s pkt=%d ivl=%dms",
descriptor->bEndpointAddress, direction, transfer,
getLittleEndian16(descriptor->wMaxPacketSize),
}
if ((endpoint = malloc(sizeof(*endpoint)))) {
memset(endpoint, 0, sizeof(*endpoint));
endpoint->device = device;
endpoint->descriptor = descriptor;
endpoint->extension = NULL;
endpoint->prepare = NULL;
switch (USB_ENDPOINT_DIRECTION(endpoint->descriptor)) {
case UsbEndpointDirection_Input:
endpoint->direction.input.pending.requests = NULL;
endpoint->direction.input.pending.alarm = NULL;
endpoint->direction.input.pending.delay = 0;
endpoint->direction.input.completed.request = NULL;
endpoint->direction.input.completed.buffer = NULL;
endpoint->direction.input.completed.length = 0;
endpoint->direction.input.pipe.input = INVALID_FILE_DESCRIPTOR;
endpoint->direction.input.pipe.output = INVALID_FILE_DESCRIPTOR;
endpoint->direction.input.pipe.monitor = NULL;
endpoint->direction.input.pipe.error = 0;
break;
}
if (usbAllocateEndpointExtension(endpoint)) {
if (enqueueItem(device->endpoints, endpoint)) {
if (device->disableEndpointReset) {
logMessage(LOG_CATEGORY(USB_IO), "endpoint reset disabled");
} else {
usbClearHalt(device, endpoint->descriptor->bEndpointAddress);
}
if (!endpoint->prepare || endpoint->prepare(endpoint)) return endpoint;
deleteItem(device->endpoints, endpoint);
}
usbDeallocateEndpointExtension(endpoint->extension);
}
free(endpoint);
}
}
return NULL;
}
UsbEndpoint *
usbGetInputEndpoint (UsbDevice *device, unsigned char endpointNumber) {
return usbGetEndpoint(device, endpointNumber|UsbEndpointDirection_Input);
}
UsbEndpoint *
usbGetOutputEndpoint (UsbDevice *device, unsigned char endpointNumber) {
return usbGetEndpoint(device, endpointNumber|UsbEndpointDirection_Output);
}
static int
usbFinishEndpoint (void *item, void *data) {
UsbEndpoint *endpoint = item;
switch (USB_ENDPOINT_DIRECTION(endpoint->descriptor)) {
case UsbEndpointDirection_Input:
if (endpoint->direction.input.pending.requests) {
deleteElements(endpoint->direction.input.pending.requests);
}
break;
default:
break;
}
return 0;
}
static void
usbRemoveEndpoints (UsbDevice *device, int final) {
if (device->endpoints) {
processQueue(device->endpoints, usbFinishEndpoint, NULL);
deleteElements(device->endpoints);
if (final) {
deallocateQueue(device->endpoints);
device->endpoints = NULL;
}
}
}
static void
usbDeallocateInputFilter (void *item, void *data) {
UsbInputFilterEntry *entry = item;
free(entry);
}
int
usbAddInputFilter (UsbDevice *device, UsbInputFilter *filter) {
if (enqueueItem(device->inputFilters, entry)) return 1;
return 0;
}
static int
usbApplyInputFilter (void *item, void *data) {
UsbInputFilterEntry *entry = item;
return !entry->filter(data);
}
int
usbApplyInputFilters (UsbEndpoint *endpoint, void *buffer, size_t size, ssize_t *length) {
Queue *filters = endpoint->device->inputFilters;
if (getQueueSize(filters) == 0) {
usbLogEndpointData(endpoint, "input", buffer, *length);
} else {
usbLogEndpointData(endpoint, "unfiltered input", buffer, *length);
UsbInputFilterData data = {
.buffer = buffer,
.size = size,
.length = *length
};
if (processQueue(filters, usbApplyInputFilter, &data)) {
errno = EIO;
return 0;
}
*length = data.length;
usbLogEndpointData(endpoint, "filtered input", buffer, *length);
return 1;
}
void
usbCloseInterface (
UsbDevice *device
) {
if (device->interface) {
usbReleaseInterface(device, device->interface->bInterfaceNumber);
device->interface = NULL;
}
}
int
usbOpenInterface (
UsbDevice *device,
unsigned char interface,
unsigned char alternative
) {
const UsbInterfaceDescriptor *descriptor = usbInterfaceDescriptor(device, interface, alternative);
if (!descriptor) return 0;
if (descriptor == device->interface) return 1;
if (device->interface)
if (device->interface->bInterfaceNumber != interface)
usbCloseInterface(device);
if (!device->interface)
if (!usbClaimInterface(device, interface))
return 0;
if (usbAlternativeCount(device, interface) == 1) goto done;
{
unsigned char response[1];
ssize_t size = usbControlRead(device, UsbControlRecipient_Interface, UsbControlType_Standard,
UsbStandardRequest_GetInterface, 0, interface,
response, sizeof(response), 1000);
if (size != -1) {
if (response[0] == alternative) goto done;
} else {
logMessage(LOG_WARNING, "USB standard request not supported: get interface");
if (usbSetAlternative(device, interface, alternative)) goto done;
if (!device->interface) usbReleaseInterface(device, interface);
return 0;
done:
device->interface = descriptor;
return 1;
}
void
usbCloseDevice (UsbDevice *device) {
if (device->serial.operations) {
const UsbSerialOperations *uso = device->serial.operations;
if (uso->disableAdapter) uso->disableAdapter(device);
}
if (device->inputFilters) {
deallocateQueue(device->inputFilters);
device->inputFilters = NULL;
}
if (device->serial.data) {
device->serial.operations->destroyData(device->serial.data);
device->serial.data = NULL;
}
usbDeallocateDeviceExtension(device->extension);