1 /*
2 Driver for USB Ethernet Control Model devices
3 Copyright (C) 2008 Michael Lotz <mmlr@mlotz.ch>
4 Distributed under the terms of the MIT license.
5 */
6 #include <ether_driver.h>
7 #include <net/if_media.h>
8 #include <string.h>
9 #include <stdlib.h>
10
11 #include "ECMDevice.h"
12 #include "Driver.h"
13
ECMDevice(usb_device device)14 ECMDevice::ECMDevice(usb_device device)
15 : fStatus(B_ERROR),
16 fOpen(false),
17 fRemoved(false),
18 fInsideNotify(0),
19 fDevice(device),
20 fControlInterfaceIndex(0),
21 fDataInterfaceIndex(0),
22 fMACAddressIndex(0),
23 fMaxSegmentSize(0),
24 fNotifyEndpoint(0),
25 fReadEndpoint(0),
26 fWriteEndpoint(0),
27 fNotifyReadSem(-1),
28 fNotifyWriteSem(-1),
29 fNotifyBuffer(NULL),
30 fNotifyBufferLength(0),
31 fLinkStateChangeSem(-1),
32 fHasConnection(false),
33 fDownstreamSpeed(0),
34 fUpstreamSpeed(0)
35 {
36 const usb_device_descriptor *deviceDescriptor
37 = gUSBModule->get_device_descriptor(device);
38
39 if (deviceDescriptor == NULL) {
40 TRACE_ALWAYS("failed to get device descriptor\n");
41 return;
42 }
43
44 fVendorID = deviceDescriptor->vendor_id;
45 fProductID = deviceDescriptor->product_id;
46
47 fNotifyBufferLength = 64;
48 fNotifyBuffer = (uint8 *)malloc(fNotifyBufferLength);
49 if (fNotifyBuffer == NULL) {
50 TRACE_ALWAYS("out of memory for notify buffer allocation\n");
51 return;
52 }
53
54 fNotifyReadSem = create_sem(0, DRIVER_NAME"_notify_read");
55 if (fNotifyReadSem < B_OK) {
56 TRACE_ALWAYS("failed to create read notify sem\n");
57 return;
58 }
59
60 fNotifyWriteSem = create_sem(0, DRIVER_NAME"_notify_write");
61 if (fNotifyWriteSem < B_OK) {
62 TRACE_ALWAYS("failed to create write notify sem\n");
63 return;
64 }
65
66 if (_SetupDevice() != B_OK) {
67 TRACE_ALWAYS("failed to setup device\n");
68 return;
69 }
70
71 if (_ReadMACAddress(fDevice, fMACAddress) != B_OK) {
72 TRACE_ALWAYS("failed to read mac address\n");
73 return;
74 }
75
76 fStatus = B_OK;
77 }
78
79
~ECMDevice()80 ECMDevice::~ECMDevice()
81 {
82 if (fNotifyReadSem >= B_OK)
83 delete_sem(fNotifyReadSem);
84 if (fNotifyWriteSem >= B_OK)
85 delete_sem(fNotifyWriteSem);
86
87 if (!fRemoved)
88 gUSBModule->cancel_queued_transfers(fNotifyEndpoint);
89
90 free(fNotifyBuffer);
91 }
92
93
94 status_t
Open()95 ECMDevice::Open()
96 {
97 if (fOpen)
98 return B_BUSY;
99 if (fRemoved)
100 return B_ERROR;
101
102 // reset the device by switching the data interface to the disabled first
103 // interface and then enable it by setting the second actual data interface
104 const usb_configuration_info *config
105 = gUSBModule->get_configuration(fDevice);
106
107 gUSBModule->set_alt_interface(fDevice,
108 &config->interface[fDataInterfaceIndex].alt[0]);
109
110 // update to the changed config
111 config = gUSBModule->get_configuration(fDevice);
112 gUSBModule->set_alt_interface(fDevice,
113 &config->interface[fDataInterfaceIndex].alt[1]);
114 gUSBModule->set_alt_interface(fDevice,
115 &config->interface[fControlInterfaceIndex].alt[0]);
116
117 // update again
118 config = gUSBModule->get_configuration(fDevice);
119 usb_interface_info *interface = config->interface[fDataInterfaceIndex].active;
120 if (interface->endpoint_count < 2) {
121 TRACE_ALWAYS("setting the data alternate interface failed\n");
122 return B_ERROR;
123 }
124
125 if (!(interface->endpoint[0].descr->endpoint_address & USB_ENDPOINT_ADDR_DIR_IN))
126 fWriteEndpoint = interface->endpoint[0].handle;
127 else
128 fReadEndpoint = interface->endpoint[0].handle;
129
130 if (interface->endpoint[1].descr->endpoint_address & USB_ENDPOINT_ADDR_DIR_IN)
131 fReadEndpoint = interface->endpoint[1].handle;
132 else
133 fWriteEndpoint = interface->endpoint[1].handle;
134
135 if (fReadEndpoint == 0 || fWriteEndpoint == 0) {
136 TRACE_ALWAYS("no read and write endpoints found\n");
137 return B_ERROR;
138 }
139
140 if (gUSBModule->queue_interrupt(fNotifyEndpoint, fNotifyBuffer,
141 fNotifyBufferLength, _NotifyCallback, this) != B_OK) {
142 // we cannot use notifications - hardcode to active connection
143 fHasConnection = true;
144 fDownstreamSpeed = 1000 * 1000 * 10; // 10Mbps
145 fUpstreamSpeed = 1000 * 1000 * 10; // 10Mbps
146 }
147
148 // the device should now be ready
149 fOpen = true;
150 return B_OK;
151 }
152
153
154 status_t
Close()155 ECMDevice::Close()
156 {
157 if (fRemoved) {
158 fOpen = false;
159 return B_OK;
160 }
161
162 gUSBModule->cancel_queued_transfers(fNotifyEndpoint);
163 gUSBModule->cancel_queued_transfers(fReadEndpoint);
164 gUSBModule->cancel_queued_transfers(fWriteEndpoint);
165
166 // put the device into non-connected mode again by switching the data
167 // interface to the disabled alternate
168 const usb_configuration_info *config
169 = gUSBModule->get_configuration(fDevice);
170
171 gUSBModule->set_alt_interface(fDevice,
172 &config->interface[fDataInterfaceIndex].alt[0]);
173
174 fOpen = false;
175 return B_OK;
176 }
177
178
179 status_t
Free()180 ECMDevice::Free()
181 {
182 return B_OK;
183 }
184
185
186 status_t
Read(uint8 * buffer,size_t * numBytes)187 ECMDevice::Read(uint8 *buffer, size_t *numBytes)
188 {
189 if (fRemoved) {
190 *numBytes = 0;
191 return B_DEVICE_NOT_FOUND;
192 }
193
194 status_t result = gUSBModule->queue_bulk(fReadEndpoint, buffer, *numBytes,
195 _ReadCallback, this);
196 if (result != B_OK) {
197 *numBytes = 0;
198 return result;
199 }
200
201 result = acquire_sem_etc(fNotifyReadSem, 1, B_CAN_INTERRUPT, 0);
202 if (result < B_OK) {
203 *numBytes = 0;
204 return result;
205 }
206
207 if (fStatusRead != B_OK && fStatusRead != B_CANCELED && !fRemoved) {
208 TRACE_ALWAYS("device status error 0x%08" B_PRIx32 "\n", fStatusRead);
209 result = gUSBModule->clear_feature(fReadEndpoint,
210 USB_FEATURE_ENDPOINT_HALT);
211 if (result != B_OK) {
212 TRACE_ALWAYS("failed to clear halt state on read\n");
213 *numBytes = 0;
214 return result;
215 }
216 }
217
218 *numBytes = fActualLengthRead;
219 return B_OK;
220 }
221
222
223 status_t
Write(const uint8 * buffer,size_t * numBytes)224 ECMDevice::Write(const uint8 *buffer, size_t *numBytes)
225 {
226 if (fRemoved) {
227 *numBytes = 0;
228 return B_DEVICE_NOT_FOUND;
229 }
230
231 status_t result = gUSBModule->queue_bulk(fWriteEndpoint, (uint8 *)buffer,
232 *numBytes, _WriteCallback, this);
233 if (result != B_OK) {
234 *numBytes = 0;
235 return result;
236 }
237
238 result = acquire_sem_etc(fNotifyWriteSem, 1, B_CAN_INTERRUPT, 0);
239 if (result < B_OK) {
240 *numBytes = 0;
241 return result;
242 }
243
244 if (fStatusWrite != B_OK && fStatusWrite != B_CANCELED && !fRemoved) {
245 TRACE_ALWAYS("device status error 0x%08" B_PRIx32 "\n", fStatusWrite);
246 result = gUSBModule->clear_feature(fWriteEndpoint,
247 USB_FEATURE_ENDPOINT_HALT);
248 if (result != B_OK) {
249 TRACE_ALWAYS("failed to clear halt state on write\n");
250 *numBytes = 0;
251 return result;
252 }
253 }
254
255 *numBytes = fActualLengthWrite;
256 return B_OK;
257 }
258
259
260 status_t
Control(uint32 op,void * buffer,size_t length)261 ECMDevice::Control(uint32 op, void *buffer, size_t length)
262 {
263 switch (op) {
264 case ETHER_INIT:
265 return B_OK;
266
267 case ETHER_GETADDR:
268 memcpy(buffer, &fMACAddress, sizeof(fMACAddress));
269 return B_OK;
270
271 case ETHER_GETFRAMESIZE:
272 *(uint32 *)buffer = fMaxSegmentSize;
273 return B_OK;
274
275 case ETHER_SET_LINK_STATE_SEM:
276 fLinkStateChangeSem = *(sem_id *)buffer;
277 return B_OK;
278
279 case ETHER_GET_LINK_STATE:
280 {
281 ether_link_state *state = (ether_link_state *)buffer;
282 state->media = IFM_ETHER | IFM_FULL_DUPLEX
283 | (fHasConnection ? IFM_ACTIVE : 0);
284 state->quality = 1000;
285 state->speed = fDownstreamSpeed;
286 return B_OK;
287 }
288
289 default:
290 TRACE_ALWAYS("unsupported ioctl %" B_PRIu32 "\n", op);
291 }
292
293 return B_DEV_INVALID_IOCTL;
294 }
295
296
297 void
Removed()298 ECMDevice::Removed()
299 {
300 fRemoved = true;
301 fHasConnection = false;
302 fDownstreamSpeed = fUpstreamSpeed = 0;
303
304 // the notify hook is different from the read and write hooks as it does
305 // itself schedule traffic (while the other hooks only release a semaphore
306 // to notify another thread which in turn safly checks for the removed
307 // case) - so we must ensure that we are not inside the notify hook anymore
308 // before returning, as we would otherwise violate the promise not to use
309 // any of the pipes after returning from the removed hook
310 while (atomic_add(&fInsideNotify, 0) != 0)
311 snooze(100);
312
313 gUSBModule->cancel_queued_transfers(fNotifyEndpoint);
314 gUSBModule->cancel_queued_transfers(fReadEndpoint);
315 gUSBModule->cancel_queued_transfers(fWriteEndpoint);
316
317 if (fLinkStateChangeSem >= B_OK)
318 release_sem_etc(fLinkStateChangeSem, 1, B_DO_NOT_RESCHEDULE);
319 }
320
321
322 status_t
CompareAndReattach(usb_device device)323 ECMDevice::CompareAndReattach(usb_device device)
324 {
325 const usb_device_descriptor *deviceDescriptor
326 = gUSBModule->get_device_descriptor(device);
327
328 if (deviceDescriptor == NULL) {
329 TRACE_ALWAYS("failed to get device descriptor\n");
330 return B_ERROR;
331 }
332
333 if (deviceDescriptor->vendor_id != fVendorID
334 && deviceDescriptor->product_id != fProductID) {
335 // this certainly isn't the same device
336 return B_BAD_VALUE;
337 }
338
339 // this might be the same device that was replugged - read the MAC address
340 // (which should be at the same index) to make sure
341 uint8 macBuffer[6];
342 if (_ReadMACAddress(device, macBuffer) != B_OK
343 || memcmp(macBuffer, fMACAddress, sizeof(macBuffer)) != 0) {
344 // reading the MAC address failed or they are not the same
345 return B_BAD_VALUE;
346 }
347
348 // this is the same device that was replugged - clear the removed state,
349 // re-setup the endpoints and transfers and open the device if it was
350 // previously opened
351 fDevice = device;
352 fRemoved = false;
353 status_t result = _SetupDevice();
354 if (result != B_OK) {
355 fRemoved = true;
356 return result;
357 }
358
359 // in case notifications do not work we will have a hardcoded connection
360 // need to register that and notify the network stack ourselfs if this is
361 // the case as the open will not result in a corresponding notification
362 bool noNotifications = fHasConnection;
363
364 if (fOpen) {
365 fOpen = false;
366 result = Open();
367 if (result == B_OK && noNotifications && fLinkStateChangeSem >= B_OK)
368 release_sem_etc(fLinkStateChangeSem, 1, B_DO_NOT_RESCHEDULE);
369 }
370
371 return B_OK;
372 }
373
374
375 status_t
_SetupDevice()376 ECMDevice::_SetupDevice()
377 {
378 const usb_device_descriptor *deviceDescriptor
379 = gUSBModule->get_device_descriptor(fDevice);
380
381 if (deviceDescriptor == NULL) {
382 TRACE_ALWAYS("failed to get device descriptor\n");
383 return B_ERROR;
384 }
385
386 uint8 controlIndex = 0;
387 uint8 dataIndex = 0;
388 bool foundUnionDescriptor = false;
389 bool foundEthernetDescriptor = false;
390 bool found = false;
391 const usb_configuration_info *config = NULL;
392 for (int i = 0; i < deviceDescriptor->num_configurations && !found; i++) {
393 config = gUSBModule->get_nth_configuration(fDevice, i);
394 if (config == NULL)
395 continue;
396
397 for (size_t j = 0; j < config->interface_count && !found; j++) {
398 const usb_interface_info *interface = config->interface[j].active;
399 usb_interface_descriptor *descriptor = interface->descr;
400 if (descriptor->interface_class != USB_INTERFACE_CLASS_CDC
401 || descriptor->interface_subclass != USB_INTERFACE_SUBCLASS_ECM
402 || interface->generic_count == 0) {
403 continue;
404 }
405
406 // try to find and interpret the union and ethernet functional
407 // descriptors
408 foundUnionDescriptor = foundEthernetDescriptor = false;
409 for (size_t k = 0; k < interface->generic_count; k++) {
410 usb_generic_descriptor *generic = &interface->generic[k]->generic;
411 if (generic->length >= 5
412 && generic->data[0] == FUNCTIONAL_SUBTYPE_UNION) {
413 controlIndex = generic->data[1];
414 dataIndex = generic->data[2];
415 foundUnionDescriptor = true;
416 } else if (generic->length >= sizeof(ethernet_functional_descriptor)
417 && generic->data[0] == FUNCTIONAL_SUBTYPE_ETHERNET) {
418 ethernet_functional_descriptor *ethernet
419 = (ethernet_functional_descriptor *)generic->data;
420 fMACAddressIndex = ethernet->mac_address_index;
421 fMaxSegmentSize = ethernet->max_segment_size;
422 foundEthernetDescriptor = true;
423 }
424
425 if (foundUnionDescriptor && foundEthernetDescriptor) {
426 found = true;
427 break;
428 }
429 }
430 }
431 }
432
433 if (!foundUnionDescriptor) {
434 TRACE_ALWAYS("did not find a union descriptor\n");
435 return B_ERROR;
436 }
437
438 if (!foundEthernetDescriptor) {
439 TRACE_ALWAYS("did not find an ethernet descriptor\n");
440 return B_ERROR;
441 }
442
443 // set the current configuration
444 gUSBModule->set_configuration(fDevice, config);
445 if (controlIndex >= config->interface_count) {
446 TRACE_ALWAYS("control interface index invalid\n");
447 return B_ERROR;
448 }
449
450 // check that the indicated control interface fits our needs
451 usb_interface_info *interface = config->interface[controlIndex].active;
452 usb_interface_descriptor *descriptor = interface->descr;
453 if ((descriptor->interface_class != USB_INTERFACE_CLASS_CDC
454 || descriptor->interface_subclass != USB_INTERFACE_SUBCLASS_ECM)
455 || interface->endpoint_count == 0) {
456 TRACE_ALWAYS("control interface invalid\n");
457 return B_ERROR;
458 }
459
460 fControlInterfaceIndex = controlIndex;
461 fNotifyEndpoint = interface->endpoint[0].handle;
462 fNotifyBufferLength = interface->endpoint[0].descr->max_packet_size;
463
464 if (dataIndex >= config->interface_count) {
465 TRACE_ALWAYS("data interface index invalid\n");
466 return B_ERROR;
467 }
468
469 // check that the indicated data interface fits our needs
470 if (config->interface[dataIndex].alt_count < 2) {
471 TRACE_ALWAYS("data interface does not provide two alternate interfaces\n");
472 return B_ERROR;
473 }
474
475 // alternate 0 is the disabled, endpoint-less default interface
476 interface = &config->interface[dataIndex].alt[1];
477 descriptor = interface->descr;
478 if (descriptor->interface_class != USB_INTERFACE_CLASS_CDC_DATA
479 || interface->endpoint_count < 2) {
480 TRACE_ALWAYS("data interface invalid\n");
481 return B_ERROR;
482 }
483
484 fDataInterfaceIndex = dataIndex;
485 return B_OK;
486 }
487
488
489 status_t
_ReadMACAddress(usb_device device,uint8 * buffer)490 ECMDevice::_ReadMACAddress(usb_device device, uint8 *buffer)
491 {
492 if (fMACAddressIndex == 0)
493 return B_BAD_VALUE;
494
495 size_t actualLength = 0;
496 size_t macStringLength = 26;
497 uint8 macString[macStringLength];
498 status_t result = gUSBModule->get_descriptor(device, USB_DESCRIPTOR_STRING,
499 fMACAddressIndex, 0, macString, macStringLength, &actualLength);
500 if (result != B_OK)
501 return result;
502
503 if (actualLength != macStringLength) {
504 TRACE_ALWAYS("did not retrieve full mac address\n");
505 return B_ERROR;
506 }
507
508 char macPart[3];
509 macPart[2] = 0;
510 for (int32 i = 0; i < 6; i++) {
511 macPart[0] = macString[2 + i * 4 + 0];
512 macPart[1] = macString[2 + i * 4 + 2];
513 buffer[i] = strtol(macPart, NULL, 16);
514 }
515
516 TRACE_ALWAYS("read mac address: %02x:%02x:%02x:%02x:%02x:%02x\n",
517 buffer[0], buffer[1], buffer[2], buffer[3], buffer[4], buffer[5]);
518 return B_OK;
519 }
520
521
522 void
_ReadCallback(void * cookie,int32 status,void * data,size_t actualLength)523 ECMDevice::_ReadCallback(void *cookie, int32 status, void *data,
524 size_t actualLength)
525 {
526 ECMDevice *device = (ECMDevice *)cookie;
527 device->fActualLengthRead = actualLength;
528 device->fStatusRead = status;
529 release_sem_etc(device->fNotifyReadSem, 1, B_DO_NOT_RESCHEDULE);
530 }
531
532
533 void
_WriteCallback(void * cookie,int32 status,void * data,size_t actualLength)534 ECMDevice::_WriteCallback(void *cookie, int32 status, void *data,
535 size_t actualLength)
536 {
537 ECMDevice *device = (ECMDevice *)cookie;
538 device->fActualLengthWrite = actualLength;
539 device->fStatusWrite = status;
540 release_sem_etc(device->fNotifyWriteSem, 1, B_DO_NOT_RESCHEDULE);
541 }
542
543
544 void
_NotifyCallback(void * cookie,int32 status,void * data,size_t actualLength)545 ECMDevice::_NotifyCallback(void *cookie, int32 status, void *data,
546 size_t actualLength)
547 {
548 ECMDevice *device = (ECMDevice *)cookie;
549 atomic_add(&device->fInsideNotify, 1);
550 if (status == B_CANCELED || device->fRemoved) {
551 atomic_add(&device->fInsideNotify, -1);
552 return;
553 }
554
555 if (status == B_OK && actualLength >= sizeof(cdc_notification)) {
556 bool linkStateChange = false;
557 cdc_notification *notification
558 = (cdc_notification *)device->fNotifyBuffer;
559
560 switch (notification->notification_code) {
561 case CDC_NOTIFY_NETWORK_CONNECTION:
562 TRACE("connection state change to %d\n", notification->value);
563 device->fHasConnection = notification->value > 0;
564 linkStateChange = true;
565 break;
566
567 case CDC_NOTIFY_CONNECTION_SPEED_CHANGE:
568 {
569 if (notification->data_length < sizeof(cdc_connection_speed)
570 || actualLength < sizeof(cdc_notification)
571 + sizeof(cdc_connection_speed)) {
572 TRACE_ALWAYS("not enough data in connection speed change\n");
573 break;
574 }
575
576 cdc_connection_speed *speed;
577 speed = (cdc_connection_speed *)¬ification->data[0];
578 device->fUpstreamSpeed = speed->upstream_speed;
579 device->fDownstreamSpeed = speed->downstream_speed;
580 device->fHasConnection = true;
581 TRACE("connection speed change to %" B_PRId32 "/%" B_PRId32 "\n",
582 speed->downstream_speed, speed->upstream_speed);
583 linkStateChange = true;
584 break;
585 }
586
587 default:
588 TRACE_ALWAYS("unsupported notification 0x%02x\n",
589 notification->notification_code);
590 break;
591 }
592
593 if (linkStateChange && device->fLinkStateChangeSem >= B_OK)
594 release_sem_etc(device->fLinkStateChangeSem, 1, B_DO_NOT_RESCHEDULE);
595 }
596
597 if (status != B_OK) {
598 TRACE_ALWAYS("device status error 0x%08" B_PRIx32 "\n", status);
599 if (gUSBModule->clear_feature(device->fNotifyEndpoint,
600 USB_FEATURE_ENDPOINT_HALT) != B_OK)
601 TRACE_ALWAYS("failed to clear halt state in notify hook\n");
602 }
603
604 // schedule next notification buffer
605 gUSBModule->queue_interrupt(device->fNotifyEndpoint, device->fNotifyBuffer,
606 device->fNotifyBufferLength, _NotifyCallback, device);
607 atomic_add(&device->fInsideNotify, -1);
608 }
609