Driver for USB Ethernet Control Model devices
Copyright (C) 2008 Michael Lotz <mmlr@mlotz.ch>
Distributed under the terms of the MIT license.
*/
#include <ether_driver.h>
#include <net/if_media.h>
#include <string.h>
#include <stdlib.h>
#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;
const usb_configuration_info *config
= gUSBModule->get_configuration(fDevice);
gUSBModule->set_alt_interface(fDevice,
&config->interface[fDataInterfaceIndex].alt[0]);
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]);
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) {
fHasConnection = true;
fDownstreamSpeed = 1000 * 1000 * 10;
fUpstreamSpeed = 1000 * 1000 * 10;
}
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);
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;
#if HAIKU_TARGET_PLATFORM_HAIKU
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;
}
#endif
default:
TRACE_ALWAYS("unsupported ioctl %" B_PRIu32 "\n", op);
}
return B_DEV_INVALID_IOCTL;
}
void
ECMDevice::Removed()
{
fRemoved = true;
fHasConnection = false;
fDownstreamSpeed = fUpstreamSpeed = 0;
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) {
return B_BAD_VALUE;
}
uint8 macBuffer[6];
if (_ReadMACAddress(device, macBuffer) != B_OK
|| memcmp(macBuffer, fMACAddress, sizeof(macBuffer)) != 0) {
return B_BAD_VALUE;
}
fDevice = device;
fRemoved = false;
status_t result = _SetupDevice();
if (result != B_OK) {
fRemoved = true;
return result;
}
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;
}
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;
}
gUSBModule->set_configuration(fDevice, config);
if (controlIndex >= config->interface_count) {
TRACE_ALWAYS("control interface index invalid\n");
return B_ERROR;
}
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;
}
if (config->interface[dataIndex].alt_count < 2) {
TRACE_ALWAYS("data interface does not provide two alternate interfaces\n");
return B_ERROR;
}
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 %ld/%ld\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");
}
gUSBModule->queue_interrupt(device->fNotifyEndpoint, device->fNotifyBuffer,
device->fNotifyBufferLength, _NotifyCallback, device);
atomic_add(&device->fInsideNotify, -1);
}