/* Driver for USB Ethernet Control Model devices Copyright (C) 2008 Michael Lotz Distributed under the terms of the MIT license. */ #include #include #include #include #include "ECMDevice.h" #include "Driver.h" ECMDevice::ECMDevice(usb_device device) : fStatus(B_ERROR), fOpen(false), fRemoved(false), fInsideNotify(0), fDevice(device), fControlInterfaceIndex(0), fDataInterfaceIndex(0), fMACAddressIndex(0), fMaxSegmentSize(0), fNotifyEndpoint(0), fReadEndpoint(0), fWriteEndpoint(0), fNotifyReadSem(-1), fNotifyWriteSem(-1), fNotifyBuffer(NULL), fNotifyBufferLength(0), fLinkStateChangeSem(-1), fHasConnection(false), fDownstreamSpeed(0), fUpstreamSpeed(0) { const usb_device_descriptor *deviceDescriptor = gUSBModule->get_device_descriptor(device); if (deviceDescriptor == NULL) { TRACE_ALWAYS("failed to get device descriptor\n"); return; } fVendorID = deviceDescriptor->vendor_id; fProductID = deviceDescriptor->product_id; fNotifyBufferLength = 64; fNotifyBuffer = (uint8 *)malloc(fNotifyBufferLength); if (fNotifyBuffer == NULL) { TRACE_ALWAYS("out of memory for notify buffer allocation\n"); return; } fNotifyReadSem = create_sem(0, DRIVER_NAME"_notify_read"); if (fNotifyReadSem < B_OK) { TRACE_ALWAYS("failed to create read notify sem\n"); return; } fNotifyWriteSem = create_sem(0, DRIVER_NAME"_notify_write"); if (fNotifyWriteSem < B_OK) { TRACE_ALWAYS("failed to create write notify sem\n"); return; } if (_SetupDevice() != B_OK) { TRACE_ALWAYS("failed to setup device\n"); return; } if (_ReadMACAddress(fDevice, fMACAddress) != B_OK) { TRACE_ALWAYS("failed to read mac address\n"); return; } fStatus = B_OK; } ECMDevice::~ECMDevice() { if (fNotifyReadSem >= B_OK) delete_sem(fNotifyReadSem); if (fNotifyWriteSem >= B_OK) delete_sem(fNotifyWriteSem); if (!fRemoved) gUSBModule->cancel_queued_transfers(fNotifyEndpoint); free(fNotifyBuffer); } status_t ECMDevice::Open() { if (fOpen) return B_BUSY; if (fRemoved) return B_ERROR; // reset the device by switching the data interface to the disabled first // interface and then enable it by setting the second actual data interface const usb_configuration_info *config = gUSBModule->get_configuration(fDevice); gUSBModule->set_alt_interface(fDevice, &config->interface[fDataInterfaceIndex].alt[0]); // update to the changed config config = gUSBModule->get_configuration(fDevice); gUSBModule->set_alt_interface(fDevice, &config->interface[fDataInterfaceIndex].alt[1]); gUSBModule->set_alt_interface(fDevice, &config->interface[fControlInterfaceIndex].alt[0]); // update again config = gUSBModule->get_configuration(fDevice); usb_interface_info *interface = config->interface[fDataInterfaceIndex].active; if (interface->endpoint_count < 2) { TRACE_ALWAYS("setting the data alternate interface failed\n"); return B_ERROR; } if (!(interface->endpoint[0].descr->endpoint_address & USB_ENDPOINT_ADDR_DIR_IN)) fWriteEndpoint = interface->endpoint[0].handle; else fReadEndpoint = interface->endpoint[0].handle; if (interface->endpoint[1].descr->endpoint_address & USB_ENDPOINT_ADDR_DIR_IN) fReadEndpoint = interface->endpoint[1].handle; else fWriteEndpoint = interface->endpoint[1].handle; if (fReadEndpoint == 0 || fWriteEndpoint == 0) { TRACE_ALWAYS("no read and write endpoints found\n"); return B_ERROR; } if (gUSBModule->queue_interrupt(fNotifyEndpoint, fNotifyBuffer, fNotifyBufferLength, _NotifyCallback, this) != B_OK) { // we cannot use notifications - hardcode to active connection fHasConnection = true; fDownstreamSpeed = 1000 * 1000 * 10; // 10Mbps fUpstreamSpeed = 1000 * 1000 * 10; // 10Mbps } // the device should now be ready fOpen = true; return B_OK; } status_t ECMDevice::Close() { if (fRemoved) { fOpen = false; return B_OK; } gUSBModule->cancel_queued_transfers(fNotifyEndpoint); gUSBModule->cancel_queued_transfers(fReadEndpoint); gUSBModule->cancel_queued_transfers(fWriteEndpoint); // put the device into non-connected mode again by switching the data // interface to the disabled alternate const usb_configuration_info *config = gUSBModule->get_configuration(fDevice); gUSBModule->set_alt_interface(fDevice, &config->interface[fDataInterfaceIndex].alt[0]); fOpen = false; return B_OK; } status_t ECMDevice::Free() { return B_OK; } status_t ECMDevice::Read(uint8 *buffer, size_t *numBytes) { if (fRemoved) { *numBytes = 0; return B_DEVICE_NOT_FOUND; } status_t result = gUSBModule->queue_bulk(fReadEndpoint, buffer, *numBytes, _ReadCallback, this); if (result != B_OK) { *numBytes = 0; return result; } result = acquire_sem_etc(fNotifyReadSem, 1, B_CAN_INTERRUPT, 0); if (result < B_OK) { *numBytes = 0; return result; } if (fStatusRead != B_OK && fStatusRead != B_CANCELED && !fRemoved) { TRACE_ALWAYS("device status error 0x%08" B_PRIx32 "\n", fStatusRead); result = gUSBModule->clear_feature(fReadEndpoint, USB_FEATURE_ENDPOINT_HALT); if (result != B_OK) { TRACE_ALWAYS("failed to clear halt state on read\n"); *numBytes = 0; return result; } } *numBytes = fActualLengthRead; return B_OK; } status_t ECMDevice::Write(const uint8 *buffer, size_t *numBytes) { if (fRemoved) { *numBytes = 0; return B_DEVICE_NOT_FOUND; } status_t result = gUSBModule->queue_bulk(fWriteEndpoint, (uint8 *)buffer, *numBytes, _WriteCallback, this); if (result != B_OK) { *numBytes = 0; return result; } result = acquire_sem_etc(fNotifyWriteSem, 1, B_CAN_INTERRUPT, 0); if (result < B_OK) { *numBytes = 0; return result; } if (fStatusWrite != B_OK && fStatusWrite != B_CANCELED && !fRemoved) { TRACE_ALWAYS("device status error 0x%08" B_PRIx32 "\n", fStatusWrite); result = gUSBModule->clear_feature(fWriteEndpoint, USB_FEATURE_ENDPOINT_HALT); if (result != B_OK) { TRACE_ALWAYS("failed to clear halt state on write\n"); *numBytes = 0; return result; } } *numBytes = fActualLengthWrite; return B_OK; } status_t ECMDevice::Control(uint32 op, void *buffer, size_t length) { switch (op) { case ETHER_INIT: return B_OK; case ETHER_GETADDR: memcpy(buffer, &fMACAddress, sizeof(fMACAddress)); return B_OK; case ETHER_GETFRAMESIZE: *(uint32 *)buffer = fMaxSegmentSize; return B_OK; case ETHER_SET_LINK_STATE_SEM: fLinkStateChangeSem = *(sem_id *)buffer; return B_OK; case ETHER_GET_LINK_STATE: { ether_link_state *state = (ether_link_state *)buffer; state->media = IFM_ETHER | IFM_FULL_DUPLEX | (fHasConnection ? IFM_ACTIVE : 0); state->quality = 1000; state->speed = fDownstreamSpeed; return B_OK; } default: TRACE_ALWAYS("unsupported ioctl %" B_PRIu32 "\n", op); } return B_DEV_INVALID_IOCTL; } void ECMDevice::Removed() { fRemoved = true; fHasConnection = false; fDownstreamSpeed = fUpstreamSpeed = 0; // the notify hook is different from the read and write hooks as it does // itself schedule traffic (while the other hooks only release a semaphore // to notify another thread which in turn safly checks for the removed // case) - so we must ensure that we are not inside the notify hook anymore // before returning, as we would otherwise violate the promise not to use // any of the pipes after returning from the removed hook while (atomic_add(&fInsideNotify, 0) != 0) snooze(100); gUSBModule->cancel_queued_transfers(fNotifyEndpoint); gUSBModule->cancel_queued_transfers(fReadEndpoint); gUSBModule->cancel_queued_transfers(fWriteEndpoint); if (fLinkStateChangeSem >= B_OK) release_sem_etc(fLinkStateChangeSem, 1, B_DO_NOT_RESCHEDULE); } status_t ECMDevice::CompareAndReattach(usb_device device) { const usb_device_descriptor *deviceDescriptor = gUSBModule->get_device_descriptor(device); if (deviceDescriptor == NULL) { TRACE_ALWAYS("failed to get device descriptor\n"); return B_ERROR; } if (deviceDescriptor->vendor_id != fVendorID && deviceDescriptor->product_id != fProductID) { // this certainly isn't the same device return B_BAD_VALUE; } // this might be the same device that was replugged - read the MAC address // (which should be at the same index) to make sure uint8 macBuffer[6]; if (_ReadMACAddress(device, macBuffer) != B_OK || memcmp(macBuffer, fMACAddress, sizeof(macBuffer)) != 0) { // reading the MAC address failed or they are not the same return B_BAD_VALUE; } // this is the same device that was replugged - clear the removed state, // re-setup the endpoints and transfers and open the device if it was // previously opened fDevice = device; fRemoved = false; status_t result = _SetupDevice(); if (result != B_OK) { fRemoved = true; return result; } // in case notifications do not work we will have a hardcoded connection // need to register that and notify the network stack ourselfs if this is // the case as the open will not result in a corresponding notification bool noNotifications = fHasConnection; if (fOpen) { fOpen = false; result = Open(); if (result == B_OK && noNotifications && fLinkStateChangeSem >= B_OK) release_sem_etc(fLinkStateChangeSem, 1, B_DO_NOT_RESCHEDULE); } return B_OK; } status_t ECMDevice::_SetupDevice() { const usb_device_descriptor *deviceDescriptor = gUSBModule->get_device_descriptor(fDevice); if (deviceDescriptor == NULL) { TRACE_ALWAYS("failed to get device descriptor\n"); return B_ERROR; } uint8 controlIndex = 0; uint8 dataIndex = 0; bool foundUnionDescriptor = false; bool foundEthernetDescriptor = false; bool found = false; const usb_configuration_info *config = NULL; for (int i = 0; i < deviceDescriptor->num_configurations && !found; i++) { config = gUSBModule->get_nth_configuration(fDevice, i); if (config == NULL) continue; for (size_t j = 0; j < config->interface_count && !found; j++) { const usb_interface_info *interface = config->interface[j].active; usb_interface_descriptor *descriptor = interface->descr; if (descriptor->interface_class != USB_INTERFACE_CLASS_CDC || descriptor->interface_subclass != USB_INTERFACE_SUBCLASS_ECM || interface->generic_count == 0) { continue; } // try to find and interpret the union and ethernet functional // descriptors foundUnionDescriptor = foundEthernetDescriptor = false; for (size_t k = 0; k < interface->generic_count; k++) { usb_generic_descriptor *generic = &interface->generic[k]->generic; if (generic->length >= 5 && generic->data[0] == FUNCTIONAL_SUBTYPE_UNION) { controlIndex = generic->data[1]; dataIndex = generic->data[2]; foundUnionDescriptor = true; } else if (generic->length >= sizeof(ethernet_functional_descriptor) && generic->data[0] == FUNCTIONAL_SUBTYPE_ETHERNET) { ethernet_functional_descriptor *ethernet = (ethernet_functional_descriptor *)generic->data; fMACAddressIndex = ethernet->mac_address_index; fMaxSegmentSize = ethernet->max_segment_size; foundEthernetDescriptor = true; } if (foundUnionDescriptor && foundEthernetDescriptor) { found = true; break; } } } } if (!foundUnionDescriptor) { TRACE_ALWAYS("did not find a union descriptor\n"); return B_ERROR; } if (!foundEthernetDescriptor) { TRACE_ALWAYS("did not find an ethernet descriptor\n"); return B_ERROR; } // set the current configuration gUSBModule->set_configuration(fDevice, config); if (controlIndex >= config->interface_count) { TRACE_ALWAYS("control interface index invalid\n"); return B_ERROR; } // check that the indicated control interface fits our needs usb_interface_info *interface = config->interface[controlIndex].active; usb_interface_descriptor *descriptor = interface->descr; if ((descriptor->interface_class != USB_INTERFACE_CLASS_CDC || descriptor->interface_subclass != USB_INTERFACE_SUBCLASS_ECM) || interface->endpoint_count == 0) { TRACE_ALWAYS("control interface invalid\n"); return B_ERROR; } fControlInterfaceIndex = controlIndex; fNotifyEndpoint = interface->endpoint[0].handle; fNotifyBufferLength = interface->endpoint[0].descr->max_packet_size; if (dataIndex >= config->interface_count) { TRACE_ALWAYS("data interface index invalid\n"); return B_ERROR; } // check that the indicated data interface fits our needs if (config->interface[dataIndex].alt_count < 2) { TRACE_ALWAYS("data interface does not provide two alternate interfaces\n"); return B_ERROR; } // alternate 0 is the disabled, endpoint-less default interface interface = &config->interface[dataIndex].alt[1]; descriptor = interface->descr; if (descriptor->interface_class != USB_INTERFACE_CLASS_CDC_DATA || interface->endpoint_count < 2) { TRACE_ALWAYS("data interface invalid\n"); return B_ERROR; } fDataInterfaceIndex = dataIndex; return B_OK; } status_t ECMDevice::_ReadMACAddress(usb_device device, uint8 *buffer) { if (fMACAddressIndex == 0) return B_BAD_VALUE; size_t actualLength = 0; size_t macStringLength = 26; uint8 macString[macStringLength]; status_t result = gUSBModule->get_descriptor(device, USB_DESCRIPTOR_STRING, fMACAddressIndex, 0, macString, macStringLength, &actualLength); if (result != B_OK) return result; if (actualLength != macStringLength) { TRACE_ALWAYS("did not retrieve full mac address\n"); return B_ERROR; } char macPart[3]; macPart[2] = 0; for (int32 i = 0; i < 6; i++) { macPart[0] = macString[2 + i * 4 + 0]; macPart[1] = macString[2 + i * 4 + 2]; buffer[i] = strtol(macPart, NULL, 16); } TRACE_ALWAYS("read mac address: %02x:%02x:%02x:%02x:%02x:%02x\n", buffer[0], buffer[1], buffer[2], buffer[3], buffer[4], buffer[5]); return B_OK; } void ECMDevice::_ReadCallback(void *cookie, int32 status, void *data, size_t actualLength) { ECMDevice *device = (ECMDevice *)cookie; device->fActualLengthRead = actualLength; device->fStatusRead = status; release_sem_etc(device->fNotifyReadSem, 1, B_DO_NOT_RESCHEDULE); } void ECMDevice::_WriteCallback(void *cookie, int32 status, void *data, size_t actualLength) { ECMDevice *device = (ECMDevice *)cookie; device->fActualLengthWrite = actualLength; device->fStatusWrite = status; release_sem_etc(device->fNotifyWriteSem, 1, B_DO_NOT_RESCHEDULE); } void ECMDevice::_NotifyCallback(void *cookie, int32 status, void *data, size_t actualLength) { ECMDevice *device = (ECMDevice *)cookie; atomic_add(&device->fInsideNotify, 1); if (status == B_CANCELED || device->fRemoved) { atomic_add(&device->fInsideNotify, -1); return; } if (status == B_OK && actualLength >= sizeof(cdc_notification)) { bool linkStateChange = false; cdc_notification *notification = (cdc_notification *)device->fNotifyBuffer; switch (notification->notification_code) { case CDC_NOTIFY_NETWORK_CONNECTION: TRACE("connection state change to %d\n", notification->value); device->fHasConnection = notification->value > 0; linkStateChange = true; break; case CDC_NOTIFY_CONNECTION_SPEED_CHANGE: { if (notification->data_length < sizeof(cdc_connection_speed) || actualLength < sizeof(cdc_notification) + sizeof(cdc_connection_speed)) { TRACE_ALWAYS("not enough data in connection speed change\n"); break; } cdc_connection_speed *speed; speed = (cdc_connection_speed *)¬ification->data[0]; device->fUpstreamSpeed = speed->upstream_speed; device->fDownstreamSpeed = speed->downstream_speed; device->fHasConnection = true; TRACE("connection speed change to %" B_PRId32 "/%" B_PRId32 "\n", speed->downstream_speed, speed->upstream_speed); linkStateChange = true; break; } default: TRACE_ALWAYS("unsupported notification 0x%02x\n", notification->notification_code); break; } if (linkStateChange && device->fLinkStateChangeSem >= B_OK) release_sem_etc(device->fLinkStateChangeSem, 1, B_DO_NOT_RESCHEDULE); } if (status != B_OK) { TRACE_ALWAYS("device status error 0x%08" B_PRIx32 "\n", status); if (gUSBModule->clear_feature(device->fNotifyEndpoint, USB_FEATURE_ENDPOINT_HALT) != B_OK) TRACE_ALWAYS("failed to clear halt state in notify hook\n"); } // schedule next notification buffer gUSBModule->queue_interrupt(device->fNotifyEndpoint, device->fNotifyBuffer, device->fNotifyBufferLength, _NotifyCallback, device); atomic_add(&device->fInsideNotify, -1); }