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.
6 #include <ether_driver.h>
7 #include <net/if_media.h>
11 #include "BeOSCompatibility.h"
12 #include "ECMDevice.h"
15 ECMDevice::ECMDevice(usb_device device
)
21 fControlInterfaceIndex(0),
22 fDataInterfaceIndex(0),
31 fNotifyBufferLength(0),
32 fLinkStateChangeSem(-1),
33 fHasConnection(false),
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");
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");
55 fNotifyReadSem
= create_sem(0, DRIVER_NAME
"_notify_read");
56 if (fNotifyReadSem
< B_OK
) {
57 TRACE_ALWAYS("failed to create read notify sem\n");
61 fNotifyWriteSem
= create_sem(0, DRIVER_NAME
"_notify_write");
62 if (fNotifyWriteSem
< B_OK
) {
63 TRACE_ALWAYS("failed to create write notify sem\n");
67 if (_SetupDevice() != B_OK
) {
68 TRACE_ALWAYS("failed to setup device\n");
72 if (_ReadMACAddress(fDevice
, fMACAddress
) != B_OK
) {
73 TRACE_ALWAYS("failed to read mac address\n");
81 ECMDevice::~ECMDevice()
83 if (fNotifyReadSem
>= B_OK
)
84 delete_sem(fNotifyReadSem
);
85 if (fNotifyWriteSem
>= B_OK
)
86 delete_sem(fNotifyWriteSem
);
89 gUSBModule
->cancel_queued_transfers(fNotifyEndpoint
);
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]);
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");
124 if (!(interface
->endpoint
[0].descr
->endpoint_address
& USB_ENDPOINT_ADDR_DIR_IN
))
125 fWriteEndpoint
= interface
->endpoint
[0].handle
;
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
;
132 fWriteEndpoint
= interface
->endpoint
[1].handle
;
134 if (fReadEndpoint
== 0 || fWriteEndpoint
== 0) {
135 TRACE_ALWAYS("no read and write endpoints found\n");
139 // the device should now be ready
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]);
177 ECMDevice::Read(uint8
*buffer
, size_t *numBytes
)
181 return B_DEVICE_NOT_FOUND
;
184 status_t result
= gUSBModule
->queue_bulk(fReadEndpoint
, buffer
, *numBytes
,
185 _ReadCallback
, this);
186 if (result
!= B_OK
) {
191 result
= acquire_sem_etc(fNotifyReadSem
, 1, B_CAN_INTERRUPT
, 0);
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");
208 *numBytes
= fActualLengthRead
;
214 ECMDevice::Write(const uint8
*buffer
, size_t *numBytes
)
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
) {
228 result
= acquire_sem_etc(fNotifyWriteSem
, 1, B_CAN_INTERRUPT
, 0);
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");
245 *numBytes
= fActualLengthWrite
;
251 ECMDevice::Control(uint32 op
, void *buffer
, size_t length
)
258 memcpy(buffer
, &fMACAddress
, sizeof(fMACAddress
));
261 case ETHER_GETFRAMESIZE
:
262 *(uint32
*)buffer
= fMaxSegmentSize
;
265 #if HAIKU_TARGET_PLATFORM_HAIKU
266 case ETHER_SET_LINK_STATE_SEM
:
267 fLinkStateChangeSem
= *(sem_id
*)buffer
;
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
;
282 TRACE_ALWAYS("unsupported ioctl %" B_PRIu32
"\n", op
);
285 return B_DEV_INVALID_IOCTL
;
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)
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
);
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");
325 if (deviceDescriptor
->vendor_id
!= fVendorID
326 && deviceDescriptor
->product_id
!= fProductID
) {
327 // this certainly isn't the same device
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
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
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
345 status_t result
= _SetupDevice();
346 if (result
!= B_OK
) {
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
;
359 if (result
== B_OK
&& noNotifications
&& fLinkStateChangeSem
>= B_OK
)
360 release_sem_etc(fLinkStateChangeSem
, 1, B_DO_NOT_RESCHEDULE
);
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");
378 uint8 controlIndex
= 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
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
)
414 if (!foundUnionDescriptor
) {
415 TRACE_ALWAYS("did not find a union descriptor\n");
419 if (!foundEthernetDescriptor
) {
420 TRACE_ALWAYS("did not find an ethernet descriptor\n");
424 if (controlIndex
>= config
->interface_count
) {
425 TRACE_ALWAYS("control interface index invalid\n");
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");
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");
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");
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");
469 fDataInterfaceIndex
= dataIndex
;
475 ECMDevice::_ReadMACAddress(usb_device device
, uint8
*buffer
)
477 if (fMACAddressIndex
== 0)
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
);
488 if (actualLength
!= macStringLength
) {
489 TRACE_ALWAYS("did not retrieve full mac address\n");
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]);
508 ECMDevice::_ReadCallback(void *cookie
, int32 status
, void *data
,
511 ECMDevice
*device
= (ECMDevice
*)cookie
;
512 device
->fActualLengthRead
= actualLength
;
513 device
->fStatusRead
= status
;
514 release_sem_etc(device
->fNotifyReadSem
, 1, B_DO_NOT_RESCHEDULE
);
519 ECMDevice::_WriteCallback(void *cookie
, int32 status
, void *data
,
522 ECMDevice
*device
= (ECMDevice
*)cookie
;
523 device
->fActualLengthWrite
= actualLength
;
524 device
->fStatusWrite
= status
;
525 release_sem_etc(device
->fNotifyWriteSem
, 1, B_DO_NOT_RESCHEDULE
);
530 ECMDevice::_NotifyCallback(void *cookie
, int32 status
, void *data
,
533 ECMDevice
*device
= (ECMDevice
*)cookie
;
534 atomic_add(&device
->fInsideNotify
, 1);
535 if (status
== B_CANCELED
|| device
->fRemoved
) {
536 atomic_add(&device
->fInsideNotify
, -1);
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;
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");
561 cdc_connection_speed
*speed
;
562 speed
= (cdc_connection_speed
*)¬ification
->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;
573 TRACE_ALWAYS("unsupported notification 0x%02x\n",
574 notification
->notification_code
);
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);