usb_ecm: Use the current configuration instead of a fixed one.
[haiku.git] / src / add-ons / kernel / drivers / network / usb_ecm / ECMDevice.cpp
blob715092777df4c642168bbff90e45c0b76490aeaf
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>
11 #include "BeOSCompatibility.h"
12 #include "ECMDevice.h"
13 #include "Driver.h"
15 ECMDevice::ECMDevice(usb_device device)
16 : fStatus(B_ERROR),
17 fOpen(false),
18 fRemoved(false),
19 fInsideNotify(0),
20 fDevice(device),
21 fControlInterfaceIndex(0),
22 fDataInterfaceIndex(0),
23 fMACAddressIndex(0),
24 fMaxSegmentSize(0),
25 fNotifyEndpoint(0),
26 fReadEndpoint(0),
27 fWriteEndpoint(0),
28 fNotifyReadSem(-1),
29 fNotifyWriteSem(-1),
30 fNotifyBuffer(NULL),
31 fNotifyBufferLength(0),
32 fLinkStateChangeSem(-1),
33 fHasConnection(false),
34 fDownstreamSpeed(0),
35 fUpstreamSpeed(0)
37 const usb_device_descriptor *deviceDescriptor
38 = gUSBModule->get_device_descriptor(device);
40 if (deviceDescriptor == NULL) {
41 TRACE_ALWAYS("failed to get device descriptor\n");
42 return;
45 fVendorID = deviceDescriptor->vendor_id;
46 fProductID = deviceDescriptor->product_id;
48 fNotifyBufferLength = 64;
49 fNotifyBuffer = (uint8 *)malloc(fNotifyBufferLength);
50 if (fNotifyBuffer == NULL) {
51 TRACE_ALWAYS("out of memory for notify buffer allocation\n");
52 return;
55 fNotifyReadSem = create_sem(0, DRIVER_NAME"_notify_read");
56 if (fNotifyReadSem < B_OK) {
57 TRACE_ALWAYS("failed to create read notify sem\n");
58 return;
61 fNotifyWriteSem = create_sem(0, DRIVER_NAME"_notify_write");
62 if (fNotifyWriteSem < B_OK) {
63 TRACE_ALWAYS("failed to create write notify sem\n");
64 return;
67 if (_SetupDevice() != B_OK) {
68 TRACE_ALWAYS("failed to setup device\n");
69 return;
72 if (_ReadMACAddress(fDevice, fMACAddress) != B_OK) {
73 TRACE_ALWAYS("failed to read mac address\n");
74 return;
77 fStatus = B_OK;
81 ECMDevice::~ECMDevice()
83 if (fNotifyReadSem >= B_OK)
84 delete_sem(fNotifyReadSem);
85 if (fNotifyWriteSem >= B_OK)
86 delete_sem(fNotifyWriteSem);
88 if (!fRemoved)
89 gUSBModule->cancel_queued_transfers(fNotifyEndpoint);
91 free(fNotifyBuffer);
95 status_t
96 ECMDevice::Open()
98 if (fOpen)
99 return B_BUSY;
100 if (fRemoved)
101 return B_ERROR;
103 // reset the device by switching the data interface to the disabled first
104 // interface and then enable it by setting the second actual data interface
105 const usb_configuration_info *config
106 = gUSBModule->get_configuration(fDevice);
108 gUSBModule->set_alt_interface(fDevice,
109 &config->interface[fDataInterfaceIndex].alt[0]);
111 // update to the changed config
112 config = gUSBModule->get_configuration(fDevice);
113 gUSBModule->set_alt_interface(fDevice,
114 &config->interface[fDataInterfaceIndex].alt[1]);
116 // update again
117 config = gUSBModule->get_configuration(fDevice);
118 usb_interface_info *interface = config->interface[fDataInterfaceIndex].active;
119 if (interface->endpoint_count < 2) {
120 TRACE_ALWAYS("setting the data alternate interface failed\n");
121 return B_ERROR;
124 if (!(interface->endpoint[0].descr->endpoint_address & USB_ENDPOINT_ADDR_DIR_IN))
125 fWriteEndpoint = interface->endpoint[0].handle;
126 else
127 fReadEndpoint = interface->endpoint[0].handle;
129 if (interface->endpoint[1].descr->endpoint_address & USB_ENDPOINT_ADDR_DIR_IN)
130 fReadEndpoint = interface->endpoint[1].handle;
131 else
132 fWriteEndpoint = interface->endpoint[1].handle;
134 if (fReadEndpoint == 0 || fWriteEndpoint == 0) {
135 TRACE_ALWAYS("no read and write endpoints found\n");
136 return B_ERROR;
139 // the device should now be ready
140 fOpen = true;
141 return B_OK;
145 status_t
146 ECMDevice::Close()
148 if (fRemoved) {
149 fOpen = false;
150 return B_OK;
153 gUSBModule->cancel_queued_transfers(fReadEndpoint);
154 gUSBModule->cancel_queued_transfers(fWriteEndpoint);
156 // put the device into non-connected mode again by switching the data
157 // interface to the disabled alternate
158 const usb_configuration_info *config
159 = gUSBModule->get_configuration(fDevice);
161 gUSBModule->set_alt_interface(fDevice,
162 &config->interface[fDataInterfaceIndex].alt[0]);
164 fOpen = false;
165 return B_OK;
169 status_t
170 ECMDevice::Free()
172 return B_OK;
176 status_t
177 ECMDevice::Read(uint8 *buffer, size_t *numBytes)
179 if (fRemoved) {
180 *numBytes = 0;
181 return B_DEVICE_NOT_FOUND;
184 status_t result = gUSBModule->queue_bulk(fReadEndpoint, buffer, *numBytes,
185 _ReadCallback, this);
186 if (result != B_OK) {
187 *numBytes = 0;
188 return result;
191 result = acquire_sem_etc(fNotifyReadSem, 1, B_CAN_INTERRUPT, 0);
192 if (result < B_OK) {
193 *numBytes = 0;
194 return result;
197 if (fStatusRead != B_OK && fStatusRead != B_CANCELED && !fRemoved) {
198 TRACE_ALWAYS("device status error 0x%08" B_PRIx32 "\n", fStatusRead);
199 result = gUSBModule->clear_feature(fReadEndpoint,
200 USB_FEATURE_ENDPOINT_HALT);
201 if (result != B_OK) {
202 TRACE_ALWAYS("failed to clear halt state on read\n");
203 *numBytes = 0;
204 return result;
208 *numBytes = fActualLengthRead;
209 return B_OK;
213 status_t
214 ECMDevice::Write(const uint8 *buffer, size_t *numBytes)
216 if (fRemoved) {
217 *numBytes = 0;
218 return B_DEVICE_NOT_FOUND;
221 status_t result = gUSBModule->queue_bulk(fWriteEndpoint, (uint8 *)buffer,
222 *numBytes, _WriteCallback, this);
223 if (result != B_OK) {
224 *numBytes = 0;
225 return result;
228 result = acquire_sem_etc(fNotifyWriteSem, 1, B_CAN_INTERRUPT, 0);
229 if (result < B_OK) {
230 *numBytes = 0;
231 return result;
234 if (fStatusWrite != B_OK && fStatusWrite != B_CANCELED && !fRemoved) {
235 TRACE_ALWAYS("device status error 0x%08" B_PRIx32 "\n", fStatusWrite);
236 result = gUSBModule->clear_feature(fWriteEndpoint,
237 USB_FEATURE_ENDPOINT_HALT);
238 if (result != B_OK) {
239 TRACE_ALWAYS("failed to clear halt state on write\n");
240 *numBytes = 0;
241 return result;
245 *numBytes = fActualLengthWrite;
246 return B_OK;
250 status_t
251 ECMDevice::Control(uint32 op, void *buffer, size_t length)
253 switch (op) {
254 case ETHER_INIT:
255 return B_OK;
257 case ETHER_GETADDR:
258 memcpy(buffer, &fMACAddress, sizeof(fMACAddress));
259 return B_OK;
261 case ETHER_GETFRAMESIZE:
262 *(uint32 *)buffer = fMaxSegmentSize;
263 return B_OK;
265 #if HAIKU_TARGET_PLATFORM_HAIKU
266 case ETHER_SET_LINK_STATE_SEM:
267 fLinkStateChangeSem = *(sem_id *)buffer;
268 return B_OK;
270 case ETHER_GET_LINK_STATE:
272 ether_link_state *state = (ether_link_state *)buffer;
273 state->media = IFM_ETHER | IFM_FULL_DUPLEX
274 | (fHasConnection ? IFM_ACTIVE : 0);
275 state->quality = 1000;
276 state->speed = fDownstreamSpeed;
277 return B_OK;
279 #endif
281 default:
282 TRACE_ALWAYS("unsupported ioctl %" B_PRIu32 "\n", op);
285 return B_DEV_INVALID_IOCTL;
289 void
290 ECMDevice::Removed()
292 fRemoved = true;
293 fHasConnection = false;
294 fDownstreamSpeed = fUpstreamSpeed = 0;
296 // the notify hook is different from the read and write hooks as it does
297 // itself schedule traffic (while the other hooks only release a semaphore
298 // to notify another thread which in turn safly checks for the removed
299 // case) - so we must ensure that we are not inside the notify hook anymore
300 // before returning, as we would otherwise violate the promise not to use
301 // any of the pipes after returning from the removed hook
302 while (atomic_add(&fInsideNotify, 0) != 0)
303 snooze(100);
305 gUSBModule->cancel_queued_transfers(fNotifyEndpoint);
306 gUSBModule->cancel_queued_transfers(fReadEndpoint);
307 gUSBModule->cancel_queued_transfers(fWriteEndpoint);
309 if (fLinkStateChangeSem >= B_OK)
310 release_sem_etc(fLinkStateChangeSem, 1, B_DO_NOT_RESCHEDULE);
314 status_t
315 ECMDevice::CompareAndReattach(usb_device device)
317 const usb_device_descriptor *deviceDescriptor
318 = gUSBModule->get_device_descriptor(device);
320 if (deviceDescriptor == NULL) {
321 TRACE_ALWAYS("failed to get device descriptor\n");
322 return B_ERROR;
325 if (deviceDescriptor->vendor_id != fVendorID
326 && deviceDescriptor->product_id != fProductID) {
327 // this certainly isn't the same device
328 return B_BAD_VALUE;
331 // this might be the same device that was replugged - read the MAC address
332 // (which should be at the same index) to make sure
333 uint8 macBuffer[6];
334 if (_ReadMACAddress(device, macBuffer) != B_OK
335 || memcmp(macBuffer, fMACAddress, sizeof(macBuffer)) != 0) {
336 // reading the MAC address failed or they are not the same
337 return B_BAD_VALUE;
340 // this is the same device that was replugged - clear the removed state,
341 // re-setup the endpoints and transfers and open the device if it was
342 // previously opened
343 fDevice = device;
344 fRemoved = false;
345 status_t result = _SetupDevice();
346 if (result != B_OK) {
347 fRemoved = true;
348 return result;
351 // in case notifications do not work we will have a hardcoded connection
352 // need to register that and notify the network stack ourselfs if this is
353 // the case as the open will not result in a corresponding notification
354 bool noNotifications = fHasConnection;
356 if (fOpen) {
357 fOpen = false;
358 result = Open();
359 if (result == B_OK && noNotifications && fLinkStateChangeSem >= B_OK)
360 release_sem_etc(fLinkStateChangeSem, 1, B_DO_NOT_RESCHEDULE);
363 return B_OK;
367 status_t
368 ECMDevice::_SetupDevice()
370 const usb_configuration_info *config
371 = gUSBModule->get_configuration(fDevice);
373 if (config == NULL) {
374 TRACE_ALWAYS("failed to get device configuration\n");
375 return B_ERROR;
378 uint8 controlIndex = 0;
379 uint8 dataIndex = 0;
380 bool foundUnionDescriptor = false;
381 bool foundEthernetDescriptor = false;
382 for (size_t i = 0; i < config->interface_count
383 && (!foundUnionDescriptor || !foundEthernetDescriptor); i++) {
384 usb_interface_info *interface = config->interface[i].active;
385 usb_interface_descriptor *descriptor = interface->descr;
386 if (descriptor->interface_class == USB_INTERFACE_CLASS_CDC
387 && descriptor->interface_subclass == USB_INTERFACE_SUBCLASS_ECM
388 && interface->generic_count > 0) {
389 // try to find and interpret the union and ethernet functional
390 // descriptors
391 foundUnionDescriptor = foundEthernetDescriptor = false;
392 for (size_t j = 0; j < interface->generic_count; j++) {
393 usb_generic_descriptor *generic = &interface->generic[j]->generic;
394 if (generic->length >= 5
395 && generic->data[0] == FUNCTIONAL_SUBTYPE_UNION) {
396 controlIndex = generic->data[1];
397 dataIndex = generic->data[2];
398 foundUnionDescriptor = true;
399 } else if (generic->length >= sizeof(ethernet_functional_descriptor)
400 && generic->data[0] == FUNCTIONAL_SUBTYPE_ETHERNET) {
401 ethernet_functional_descriptor *ethernet
402 = (ethernet_functional_descriptor *)generic->data;
403 fMACAddressIndex = ethernet->mac_address_index;
404 fMaxSegmentSize = ethernet->max_segment_size;
405 foundEthernetDescriptor = true;
408 if (foundUnionDescriptor && foundEthernetDescriptor)
409 break;
414 if (!foundUnionDescriptor) {
415 TRACE_ALWAYS("did not find a union descriptor\n");
416 return B_ERROR;
419 if (!foundEthernetDescriptor) {
420 TRACE_ALWAYS("did not find an ethernet descriptor\n");
421 return B_ERROR;
424 if (controlIndex >= config->interface_count) {
425 TRACE_ALWAYS("control interface index invalid\n");
426 return B_ERROR;
429 // check that the indicated control interface fits our needs
430 usb_interface_info *interface = config->interface[controlIndex].active;
431 usb_interface_descriptor *descriptor = interface->descr;
432 if ((descriptor->interface_class != USB_INTERFACE_CLASS_CDC
433 || descriptor->interface_subclass != USB_INTERFACE_SUBCLASS_ECM)
434 || interface->endpoint_count == 0) {
435 TRACE_ALWAYS("control interface invalid\n");
436 return B_ERROR;
439 fControlInterfaceIndex = controlIndex;
440 fNotifyEndpoint = interface->endpoint[0].handle;
441 if (gUSBModule->queue_interrupt(fNotifyEndpoint, fNotifyBuffer,
442 fNotifyBufferLength, _NotifyCallback, this) != B_OK) {
443 // we cannot use notifications - hardcode to active connection
444 fHasConnection = true;
445 fDownstreamSpeed = 1000 * 1000 * 10; // 10Mbps
446 fUpstreamSpeed = 1000 * 1000 * 10; // 10Mbps
449 if (dataIndex >= config->interface_count) {
450 TRACE_ALWAYS("data interface index invalid\n");
451 return B_ERROR;
454 // check that the indicated data interface fits our needs
455 if (config->interface[dataIndex].alt_count < 2) {
456 TRACE_ALWAYS("data interface does not provide two alternate interfaces\n");
457 return B_ERROR;
460 // alternate 0 is the disabled, endpoint-less default interface
461 interface = &config->interface[dataIndex].alt[1];
462 descriptor = interface->descr;
463 if (descriptor->interface_class != USB_INTERFACE_CLASS_CDC_DATA
464 || interface->endpoint_count < 2) {
465 TRACE_ALWAYS("data interface invalid\n");
466 return B_ERROR;
469 fDataInterfaceIndex = dataIndex;
470 return B_OK;
474 status_t
475 ECMDevice::_ReadMACAddress(usb_device device, uint8 *buffer)
477 if (fMACAddressIndex == 0)
478 return B_BAD_VALUE;
480 size_t actualLength = 0;
481 size_t macStringLength = 26;
482 uint8 macString[macStringLength];
483 status_t result = gUSBModule->get_descriptor(device, USB_DESCRIPTOR_STRING,
484 fMACAddressIndex, 0, macString, macStringLength, &actualLength);
485 if (result != B_OK)
486 return result;
488 if (actualLength != macStringLength) {
489 TRACE_ALWAYS("did not retrieve full mac address\n");
490 return B_ERROR;
493 char macPart[3];
494 macPart[2] = 0;
495 for (int32 i = 0; i < 6; i++) {
496 macPart[0] = macString[2 + i * 4 + 0];
497 macPart[1] = macString[2 + i * 4 + 2];
498 buffer[i] = strtol(macPart, NULL, 16);
501 TRACE_ALWAYS("read mac address: %02x:%02x:%02x:%02x:%02x:%02x\n",
502 buffer[0], buffer[1], buffer[2], buffer[3], buffer[4], buffer[5]);
503 return B_OK;
507 void
508 ECMDevice::_ReadCallback(void *cookie, int32 status, void *data,
509 size_t actualLength)
511 ECMDevice *device = (ECMDevice *)cookie;
512 device->fActualLengthRead = actualLength;
513 device->fStatusRead = status;
514 release_sem_etc(device->fNotifyReadSem, 1, B_DO_NOT_RESCHEDULE);
518 void
519 ECMDevice::_WriteCallback(void *cookie, int32 status, void *data,
520 size_t actualLength)
522 ECMDevice *device = (ECMDevice *)cookie;
523 device->fActualLengthWrite = actualLength;
524 device->fStatusWrite = status;
525 release_sem_etc(device->fNotifyWriteSem, 1, B_DO_NOT_RESCHEDULE);
529 void
530 ECMDevice::_NotifyCallback(void *cookie, int32 status, void *data,
531 size_t actualLength)
533 ECMDevice *device = (ECMDevice *)cookie;
534 atomic_add(&device->fInsideNotify, 1);
535 if (status == B_CANCELED || device->fRemoved) {
536 atomic_add(&device->fInsideNotify, -1);
537 return;
540 if (status == B_OK && actualLength >= sizeof(cdc_notification)) {
541 bool linkStateChange = false;
542 cdc_notification *notification
543 = (cdc_notification *)device->fNotifyBuffer;
545 switch (notification->notification_code) {
546 case CDC_NOTIFY_NETWORK_CONNECTION:
547 TRACE("connection state change to %d\n", notification->value);
548 device->fHasConnection = notification->value > 0;
549 linkStateChange = true;
550 break;
552 case CDC_NOTIFY_CONNECTION_SPEED_CHANGE:
554 if (notification->data_length < sizeof(cdc_connection_speed)
555 || actualLength < sizeof(cdc_notification)
556 + sizeof(cdc_connection_speed)) {
557 TRACE_ALWAYS("not enough data in connection speed change\n");
558 break;
561 cdc_connection_speed *speed;
562 speed = (cdc_connection_speed *)&notification->data[0];
563 device->fUpstreamSpeed = speed->upstream_speed;
564 device->fDownstreamSpeed = speed->downstream_speed;
565 device->fHasConnection = true;
566 TRACE("connection speed change to %ld/%ld\n",
567 speed->downstream_speed, speed->upstream_speed);
568 linkStateChange = true;
569 break;
572 default:
573 TRACE_ALWAYS("unsupported notification 0x%02x\n",
574 notification->notification_code);
575 break;
578 if (linkStateChange && device->fLinkStateChangeSem >= B_OK)
579 release_sem_etc(device->fLinkStateChangeSem, 1, B_DO_NOT_RESCHEDULE);
582 if (status != B_OK) {
583 TRACE_ALWAYS("device status error 0x%08" B_PRIx32 "\n", status);
584 if (gUSBModule->clear_feature(device->fNotifyEndpoint,
585 USB_FEATURE_ENDPOINT_HALT) != B_OK)
586 TRACE_ALWAYS("failed to clear halt state in notify hook\n");
589 // schedule next notification buffer
590 gUSBModule->queue_interrupt(device->fNotifyEndpoint, device->fNotifyBuffer,
591 device->fNotifyBufferLength, _NotifyCallback, device);
592 atomic_add(&device->fInsideNotify, -1);