diff --git a/build/jam/images/definitions/minimum b/build/jam/images/definitions/minimum index ea851c6995..099e5eb249 100644 --- a/build/jam/images/definitions/minimum +++ b/build/jam/images/definitions/minimum @@ -209,7 +209,7 @@ SYSTEM_ADD_ONS_DRIVERS_NET = [ FFilterByBuildFeatures etherpci pegasus - usb_asix usb_davicom usb_ecm + usb_asix usb_davicom usb_ecm usb_rndis wb840 ] ; diff --git a/src/add-ons/kernel/drivers/network/ether/Jamfile b/src/add-ons/kernel/drivers/network/ether/Jamfile index 3883b66f09..7912f0d946 100644 --- a/src/add-ons/kernel/drivers/network/ether/Jamfile +++ b/src/add-ons/kernel/drivers/network/ether/Jamfile @@ -7,6 +7,7 @@ HaikuSubInclude virtio ; HaikuSubInclude usb_asix ; HaikuSubInclude usb_davicom ; HaikuSubInclude usb_ecm ; +HaikuSubInclude usb_rndis ; HaikuSubInclude wb840 ; # FreeBSD 12 drivers diff --git a/src/add-ons/kernel/drivers/network/ether/usb_rndis/Driver.cpp b/src/add-ons/kernel/drivers/network/ether/usb_rndis/Driver.cpp new file mode 100644 index 0000000000..da7bbf1d29 --- /dev/null +++ b/src/add-ons/kernel/drivers/network/ether/usb_rndis/Driver.cpp @@ -0,0 +1,287 @@ +/* + Driver for USB RNDIS Network devices + Copyright (C) 2022 Adrien Destugues + Distributed under the terms of the MIT license. +*/ +#include +#include +#include +#include + +#include "Driver.h" +#include "RNDISDevice.h" + +int32 api_version = B_CUR_DRIVER_API_VERSION; +static const char *sDeviceBaseName = "net/usb_rndis/"; +RNDISDevice *gRNDISDevices[MAX_DEVICES]; +char *gDeviceNames[MAX_DEVICES + 1]; +usb_module_info *gUSBModule = NULL; +mutex gDriverLock; + + +status_t +usb_rndis_device_added(usb_device device, void **cookie) +{ + *cookie = NULL; + + // check if this is a replug of an existing device first + mutex_lock(&gDriverLock); + for (int32 i = 0; i < MAX_DEVICES; i++) { + if (gRNDISDevices[i] == NULL) + continue; + + if (gRNDISDevices[i]->CompareAndReattach(device) != B_OK) + continue; + + TRACE_ALWAYS("rndis device %" B_PRId32 " replugged\n", i); + *cookie = gRNDISDevices[i]; + mutex_unlock(&gDriverLock); + return B_OK; + } + + // no such device yet, create a new one + RNDISDevice *rndisDevice = new RNDISDevice(device); + status_t status = rndisDevice->InitCheck(); + if (status < B_OK) { + delete rndisDevice; + mutex_unlock(&gDriverLock); + return status; + } + + for (int32 i = 0; i < MAX_DEVICES; i++) { + if (gRNDISDevices[i] != NULL) + continue; + + gRNDISDevices[i] = rndisDevice; + *cookie = rndisDevice; + + TRACE_ALWAYS("rndis device %" B_PRId32 " added\n", i); + mutex_unlock(&gDriverLock); + return B_OK; + } + + // no space for the device + delete rndisDevice; + mutex_unlock(&gDriverLock); + return B_ERROR; +} + + +status_t +usb_rndis_device_removed(void *cookie) +{ + mutex_lock(&gDriverLock); + + RNDISDevice *device = (RNDISDevice *)cookie; + for (int32 i = 0; i < MAX_DEVICES; i++) { + if (gRNDISDevices[i] == device) { + if (device->IsOpen()) { + // the device will be deleted upon being freed + device->Removed(); + } else { + gRNDISDevices[i] = NULL; + delete device; + } + break; + } + } + + mutex_unlock(&gDriverLock); + return B_OK; +} + + +//#pragma mark - + + +status_t +init_hardware() +{ + TRACE("init_hardware()\n"); + return B_OK; +} + + +status_t +init_driver() +{ + TRACE("init_driver()\n"); + status_t status = get_module(B_USB_MODULE_NAME, + (module_info **)&gUSBModule); + if (status < B_OK) + return status; + + for (int32 i = 0; i < MAX_DEVICES; i++) + gRNDISDevices[i] = NULL; + + gDeviceNames[0] = NULL; + mutex_init(&gDriverLock, DRIVER_NAME"_devices"); + + static usb_notify_hooks notifyHooks = { + &usb_rndis_device_added, + &usb_rndis_device_removed + }; + + static usb_support_descriptor supportDescriptor = { + 0xE0, /* CDC - Wireless device */ + 0x01, 0x03, /* RNDIS subclass/protocol */ + 0, 0 /* no specific vendor or device */ + }; + + gUSBModule->register_driver(DRIVER_NAME, &supportDescriptor, 1, NULL); + gUSBModule->install_notify(DRIVER_NAME, ¬ifyHooks); + return B_OK; +} + + +void +uninit_driver() +{ + TRACE("uninit_driver()\n"); + gUSBModule->uninstall_notify(DRIVER_NAME); + mutex_lock(&gDriverLock); + + for (int32 i = 0; i < MAX_DEVICES; i++) { + if (gRNDISDevices[i] != NULL) { + delete gRNDISDevices[i]; + gRNDISDevices[i] = NULL; + } + } + + for (int32 i = 0; gDeviceNames[i]; i++) { + free(gDeviceNames[i]); + gDeviceNames[i] = NULL; + } + + mutex_destroy(&gDriverLock); + put_module(B_USB_MODULE_NAME); +} + + +static status_t +usb_rndis_open(const char *name, uint32 flags, void **cookie) +{ + TRACE("open(%s, %" B_PRIu32 ", %p)\n", name, flags, cookie); + mutex_lock(&gDriverLock); + + *cookie = NULL; + status_t status = ENODEV; + int32 index = strtol(name + strlen(sDeviceBaseName), NULL, 10); + if (index >= 0 && index < MAX_DEVICES && gRNDISDevices[index] != NULL) { + status = gRNDISDevices[index]->Open(); + if (status == B_OK) + *cookie = gRNDISDevices[index]; + } + + mutex_unlock(&gDriverLock); + return status; +} + + +static status_t +usb_rndis_read(void *cookie, off_t position, void *buffer, size_t *numBytes) +{ + TRACE("read(%p, %" B_PRIdOFF ", %p, %lu)\n", cookie, position, buffer, *numBytes); + RNDISDevice *device = (RNDISDevice *)cookie; + return device->Read((uint8 *)buffer, numBytes); +} + + +static status_t +usb_rndis_write(void *cookie, off_t position, const void *buffer, + size_t *numBytes) +{ + TRACE("write(%p, %" B_PRIdOFF ", %p, %lu)\n", cookie, position, buffer, *numBytes); + RNDISDevice *device = (RNDISDevice *)cookie; + return device->Write((const uint8 *)buffer, numBytes); +} + + +static status_t +usb_rndis_control(void *cookie, uint32 op, void *buffer, size_t length) +{ + TRACE("control(%p, %" B_PRIu32 ", %p, %lu)\n", cookie, op, buffer, length); + RNDISDevice *device = (RNDISDevice *)cookie; + return device->Control(op, buffer, length); +} + + +static status_t +usb_rndis_close(void *cookie) +{ + TRACE("close(%p)\n", cookie); + RNDISDevice *device = (RNDISDevice *)cookie; + return device->Close(); +} + + +static status_t +usb_rndis_free(void *cookie) +{ + TRACE("free(%p)\n", cookie); + RNDISDevice *device = (RNDISDevice *)cookie; + mutex_lock(&gDriverLock); + status_t status = device->Free(); + for (int32 i = 0; i < MAX_DEVICES; i++) { + if (gRNDISDevices[i] == device) { + // the device is removed already but as it was open the + // removed hook has not deleted the object + gRNDISDevices[i] = NULL; + delete device; + break; + } + } + + mutex_unlock(&gDriverLock); + return status; +} + + +const char ** +publish_devices() +{ + TRACE("publish_devices()\n"); + for (int32 i = 0; gDeviceNames[i]; i++) { + free(gDeviceNames[i]); + gDeviceNames[i] = NULL; + } + + int32 deviceCount = 0; + mutex_lock(&gDriverLock); + for (int32 i = 0; i < MAX_DEVICES; i++) { + if (gRNDISDevices[i] == NULL) + continue; + + gDeviceNames[deviceCount] = (char *)malloc(strlen(sDeviceBaseName) + 4); + if (gDeviceNames[deviceCount] != NULL) { + sprintf(gDeviceNames[deviceCount], "%s%" B_PRId32, sDeviceBaseName, + i); + TRACE("publishing %s\n", gDeviceNames[deviceCount]); + deviceCount++; + } else + TRACE_ALWAYS("publish_devices - no memory to allocate device name\n"); + } + + gDeviceNames[deviceCount] = NULL; + mutex_unlock(&gDriverLock); + return (const char **)&gDeviceNames[0]; +} + + +device_hooks * +find_device(const char *name) +{ + TRACE("find_device(%s)\n", name); + static device_hooks deviceHooks = { + usb_rndis_open, + usb_rndis_close, + usb_rndis_free, + usb_rndis_control, + usb_rndis_read, + usb_rndis_write, + NULL, /* select */ + NULL /* deselect */ + }; + + return &deviceHooks; +} diff --git a/src/add-ons/kernel/drivers/network/ether/usb_rndis/Driver.h b/src/add-ons/kernel/drivers/network/ether/usb_rndis/Driver.h new file mode 100644 index 0000000000..bc68cfc82c --- /dev/null +++ b/src/add-ons/kernel/drivers/network/ether/usb_rndis/Driver.h @@ -0,0 +1,40 @@ +/* + Driver for USB RNDIS network devices + Copyright (C) 2022 Adrien Destugues + Distributed under the terms of the MIT license. +*/ +#ifndef _USB_RNDIS_DRIVER_H_ +#define _USB_RNDIS_DRIVER_H_ + +#include +#include +#include +#include +#include + +#include + +#define DRIVER_NAME "usb_rndis" +#define MAX_DEVICES 8 + +extern usb_module_info *gUSBModule; + +extern "C" { +status_t usb_rndis_device_added(usb_device device, void **cookie); +status_t usb_rndis_device_removed(void *cookie); + +status_t init_hardware(); +void uninit_driver(); + +const char **publish_devices(); +device_hooks *find_device(const char *name); +} + +#if TRACE_RNDIS +#define TRACE(x...) dprintf(DRIVER_NAME ": " x) +#else +#define TRACE(x...) +#endif +#define TRACE_ALWAYS(x...) dprintf(DRIVER_NAME ": " x) + +#endif //_USB_RNDIS_DRIVER_H_ diff --git a/src/add-ons/kernel/drivers/network/ether/usb_rndis/Jamfile b/src/add-ons/kernel/drivers/network/ether/usb_rndis/Jamfile new file mode 100644 index 0000000000..43fd191d4f --- /dev/null +++ b/src/add-ons/kernel/drivers/network/ether/usb_rndis/Jamfile @@ -0,0 +1,9 @@ +SubDir HAIKU_TOP src add-ons kernel drivers network ether usb_rndis ; + +UsePrivateKernelHeaders ; +UsePrivateHeaders net ; + +KernelAddon usb_rndis : + Driver.cpp + RNDISDevice.cpp + ; diff --git a/src/add-ons/kernel/drivers/network/ether/usb_rndis/RNDISDevice.cpp b/src/add-ons/kernel/drivers/network/ether/usb_rndis/RNDISDevice.cpp new file mode 100644 index 0000000000..23e07edabe --- /dev/null +++ b/src/add-ons/kernel/drivers/network/ether/usb_rndis/RNDISDevice.cpp @@ -0,0 +1,848 @@ +/* + Driver for USB RNDIS Network devices + Copyright (C) 2022 Adrien Destugues + Distributed under the terms of the MIT license. +*/ +#include +#include +#include +#include + +#include "RNDISDevice.h" +#include "Driver.h" + + +const uint32 OID_GEN_MAXIMUM_FRAME_SIZE = 0x00010106; +const uint32 OID_GEN_LINK_SPEED = 0x00010107; +const uint32 OID_GEN_CURRENT_PACKET_FILTER = 0x0001010E; +const uint32 OID_GEN_MEDIA_CONNECT_STATUS = 0x00010114; +const uint32 OID_802_3_PERMANENT_ADDRESS = 0x01010101; + + +enum RndisCommands { + REMOTE_NDIS_PACKET_MSG = 1, + REMOTE_NDIS_INITIALIZE_MSG = 2, + REMOTE_NDIS_INITIALIZE_CMPLT = 0x80000002, + REMOTE_NDIS_HALT_MSG = 3, + REMOTE_NDIS_QUERY_MSG = 4, + REMOTE_NDIS_QUERY_CMPLT = 0x80000004, + REMOTE_NDIS_SET_MSG = 5, + REMOTE_NDIS_SET_CMPLT = 0x80000005, + REMOTE_NDIS_RESET_MSG = 6, + REMOTE_NDIS_RESET_CMPLT = 0x80000006, + REMOTE_NDIS_INDICATE_STATUS_MSG = 7, + REMOTE_NDIS_KEEPALIVE_MSG = 8, + REMOTE_NDIS_KEEPALIVE_CMPLT = 0x80000008 +}; + + +enum Status +{ + RNDIS_STATUS_SUCCESS = 0, + RNDIS_STATUS_FAILURE = 0xC0000001, + RNDIS_STATUS_INVALID_DATA = 0xC0010015, + RNDIS_STATUS_NOT_SUPPORTED = 0xC00000BB, + RNDIS_STATUS_MEDIA_CONNECT = 0x4001000B, + RNDIS_STATUS_MEDIA_DISCONNECT = 0x4001000C, +}; + + +enum MediaConnectStatus +{ + MEDIA_STATE_UNKNOWN, + MEDIA_STATE_CONNECTED, + MEDIA_STATE_DISCONNECTED +}; + + +const uint32 NDIS_PACKET_TYPE_ALL_MULTICAST = 0x00000004; +const uint32 NDIS_PACKET_TYPE_BROADCAST = 0x00000008; + + + +RNDISDevice::RNDISDevice(usb_device device) + : fStatus(B_ERROR), + fOpen(false), + fRemoved(false), + fInsideNotify(0), + fDevice(device), + fDataInterfaceIndex(0), + fMaxSegmentSize(0), + fNotifyEndpoint(0), + fReadEndpoint(0), + fWriteEndpoint(0), + fNotifyReadSem(-1), + fNotifyWriteSem(-1), + fNotifyControlSem(-1), + fLinkStateChangeSem(-1), + fMediaConnectState(MEDIA_STATE_UNKNOWN), + fDownstreamSpeed(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; + + 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; + } + + fNotifyControlSem = create_sem(0, DRIVER_NAME"_notify_control"); + if (fNotifyControlSem < B_OK) { + TRACE_ALWAYS("failed to create control notify sem\n"); + return; + } + + if (_SetupDevice() != B_OK) { + TRACE_ALWAYS("failed to setup device\n"); + return; + } + + fStatus = B_OK; +} + + +RNDISDevice::~RNDISDevice() +{ + if (fNotifyReadSem >= B_OK) + delete_sem(fNotifyReadSem); + if (fNotifyWriteSem >= B_OK) + delete_sem(fNotifyWriteSem); + + if (!fRemoved) + gUSBModule->cancel_queued_transfers(fNotifyEndpoint); +} + + +status_t +RNDISDevice::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); + + 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, + sizeof(fNotifyBuffer), _NotifyCallback, this) != B_OK) { + TRACE_ALWAYS("failed to setup notification interrupt\n"); + return B_ERROR; + } + + status_t status = _RNDISInitialize(); + if (status != B_OK) { + TRACE_ALWAYS("failed to read mac address\n"); + return status; + } + + status = _ReadMACAddress(fDevice, fMACAddress); + if (status != B_OK) { + TRACE_ALWAYS("failed to read mac address\n"); + return status; + } + + // TODO these are non-fatal but make sure we have sane defaults for them + status = _ReadMaxSegmentSize(fDevice); + if (status != B_OK) { + TRACE_ALWAYS("failed to read fragment siez\n"); + } + + status = _ReadMediaState(fDevice); + if (status != B_OK) { + fMediaConnectState = MEDIA_STATE_CONNECTED; + TRACE_ALWAYS("failed to read media state\n"); + } + + status = _ReadLinkSpeed(fDevice); + if (status != B_OK) { + fDownstreamSpeed = 1000 * 100; // 10Mbps + TRACE_ALWAYS("failed to read link speed\n"); + } + + // Tell the device to connect + status = _EnableBroadcast(fDevice); + TRACE("Initialization result: %s\n", strerror(status)); + + // the device should now be ready + if (status == B_OK) + fOpen = true; + return status; +} + + +status_t +RNDISDevice::Close() +{ + if (fRemoved) { + fOpen = false; + return B_OK; + } + + // TODO tell the device to disconnect? + + gUSBModule->cancel_queued_transfers(fNotifyEndpoint); + gUSBModule->cancel_queued_transfers(fReadEndpoint); + gUSBModule->cancel_queued_transfers(fWriteEndpoint); + + fOpen = false; + return B_OK; +} + + +status_t +RNDISDevice::Free() +{ + return B_OK; +} + + +status_t +RNDISDevice::Read(uint8 *buffer, size_t *numBytes) +{ + if (fRemoved) { + *numBytes = 0; + return B_DEVICE_NOT_FOUND; + } + + iovec vec[2]; + uint32 header[11] = { 0 }; + + vec[0].iov_base = &header; + vec[0].iov_len = sizeof(header); + + vec[1].iov_base = buffer; + vec[1].iov_len = *numBytes; + + status_t result = gUSBModule->queue_bulk_v(fReadEndpoint, vec, 2, + _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; + } + } + + // TODO buffering is needed if we receive multiple packets, OOB data, etc since each Read + // call can only return one packet? (which isn't great and could be improved on network stack + // side to reduce syscalls if we had a readmmsg device call or something similar...) + + if (header[0] != REMOTE_NDIS_PACKET_MSG) { + TRACE_ALWAYS("Received unexpected packet type %08" B_PRIx32 " on data link\n", header[0]); + *numBytes = 0; + return B_BAD_VALUE; + } + + if (header[1] != fActualLengthRead) { + TRACE_ALWAYS("Received frame length %08" B_PRIx32 " but USB transfer length is %08" + B_PRIx32 "\n", header[1], fActualLengthRead); + } + + if (header[4] != 0 || header[5] != 0 || header[6] != 0) { + TRACE_ALWAYS("Received frame has out of bound data: off %08" B_PRIx32 " len %08" B_PRIx32 + " count %08" B_PRIx32 "\n", header[4], header[5], header[6]); + } + + if (header[7] != 0 || header[8] != 0) { + TRACE_ALWAYS("Received frame has per-packet info: off %08" B_PRIx32 " len %08" B_PRIx32 + "\n", header[7], header[8]); + } + + if (header[9] != 0) { + TRACE_ALWAYS("Received frame has non-0 reserved fied %08x\n", header[9]); + } + + *numBytes = header[3]; + + TRACE("Received data packet len %08x data [off %08x len %08x]\n", + header[1], header[2], header[3]); + + return B_OK; +} + + +status_t +RNDISDevice::Write(const uint8 *buffer, size_t *numBytes) +{ + if (fRemoved) { + *numBytes = 0; + return B_DEVICE_NOT_FOUND; + } + + iovec vec[2]; + + uint32 header[11] = { 0 }; + header[0] = REMOTE_NDIS_PACKET_MSG; + header[1] = *numBytes + sizeof(header); + header[2] = 0x24; + header[3] = *numBytes; + + vec[0].iov_base = &header; + vec[0].iov_len = sizeof(header); + + vec[1].iov_base = (void*)buffer; + vec[1].iov_len = *numBytes; + + status_t result = gUSBModule->queue_bulk_v(fWriteEndpoint, vec, 2, _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 +RNDISDevice::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; + // FIXME get media duplex state from OID_GEN_LINK_STATE if supported + state->media = IFM_ETHER | IFM_FULL_DUPLEX; + if (fMediaConnectState != MEDIA_STATE_DISCONNECTED) + state->media |= IFM_ACTIVE; + state->quality = 1000; + state->speed = fDownstreamSpeed * 100; + return B_OK; + } + + default: + TRACE_ALWAYS("unsupported ioctl %" B_PRIu32 "\n", op); + } + + return B_DEV_INVALID_IOCTL; +} + + +void +RNDISDevice::Removed() +{ + fRemoved = true; + fMediaConnectState = MEDIA_STATE_DISCONNECTED; + fDownstreamSpeed = 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 +RNDISDevice::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 = (fMediaConnectState == MEDIA_STATE_CONNECTED); + + 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 +RNDISDevice::_SendCommand(const void* data, size_t length) +{ + size_t actualLength; + return gUSBModule->send_request(fDevice, USB_REQTYPE_INTERFACE_OUT | USB_REQTYPE_CLASS, + USB_CDC_SEND_ENCAPSULATED_COMMAND, 0, 0, length, (void*)data, &actualLength); +} + + +status_t +RNDISDevice::_ReadResponse(void* data, size_t length) +{ + size_t actualLength; + return gUSBModule->send_request(fDevice, USB_REQTYPE_INTERFACE_IN | USB_REQTYPE_CLASS, + USB_CDC_GET_ENCAPSULATED_RESPONSE, 0, 0, length, data, &actualLength); +} + + +status_t +RNDISDevice::_RNDISInitialize() +{ + uint32 request[] = { + REMOTE_NDIS_INITIALIZE_MSG, + 6 * sizeof(uint32), // MessageLength + 0x00000000, // RequestID + 0x00000001, 0x00000000, // Version (major, minor) + 0x00004000 // MaxTransferSize + }; + + status_t result = _SendCommand(request, sizeof(request)); + TRACE("Send init command results in %s\n", strerror(result)); + + acquire_sem(fNotifyControlSem); + + TRACE("Received notification after init command\n"); + + uint32 response[0x34 / 4]; + + result = _ReadResponse(response, sizeof(response)); + TRACE("Read init command results in %s\n", strerror(result)); + if (result != B_OK) + return result; + + TRACE("Type %" B_PRIx32 "\n", response[0]); + TRACE("Length %" B_PRIx32 "\n", response[1]); + TRACE("Req ID %" B_PRIx32 "\n", response[2]); + TRACE("Status %" B_PRIx32 "\n", response[3]); + TRACE("Vers Maj %" B_PRIx32 "\n", response[4]); + TRACE("Vers Min %" B_PRIx32 "\n", response[5]); + TRACE("DevFlags %" B_PRIx32 "\n", response[6]); + TRACE("Medium %" B_PRIx32 "\n", response[7]); + TRACE("Max Pkts %" B_PRIx32 "\n", response[8]); + TRACE("Max Bytes %" B_PRIx32 "\n", response[9]); + TRACE("Alignment %" B_PRIx32 "\n", response[10]); + TRACE("Reserved "); + for (int i = 11; i < 0x34 / 4; i++) + TRACE("%" B_PRIx32 " ", response[i]); + TRACE("\n"); + + // TODO configure stuff until we get a SET_CPLT message meaning everything is configured + // TODO set up a notification sytem to be notified if these change? Do we have OIDs for them? + // OID_GEN_HARDWARE_STATUS, OID_GEN_MEDIA_IN_USE + + return B_OK; +} + + +status_t +RNDISDevice::_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 foundCMDescriptor = 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_COMMUNICATION_WIRELESS_DEVICE_CLASS + || descriptor->interface_subclass != 0x01 + || descriptor->interface_protocol != 0x03 + || interface->generic_count == 0) { + continue; + } + + // try to find and interpret the union and call management functional + // descriptors (they allow us to locate the data interface) + foundUnionDescriptor = foundCMDescriptor = false; + for (size_t k = 0; k < interface->generic_count; k++) { + usb_generic_descriptor *generic = &interface->generic[k]->generic; + if (generic->length >= sizeof(usb_cdc_union_functional_descriptor) + && generic->data[0] == USB_CDC_UNION_FUNCTIONAL_DESCRIPTOR) { + controlIndex = generic->data[1]; + foundUnionDescriptor = true; + } else if (generic->length >= sizeof(usb_cdc_cm_functional_descriptor) + && generic->data[0] == USB_CDC_CM_FUNCTIONAL_DESCRIPTOR) { + usb_cdc_cm_functional_descriptor *cm + = (usb_cdc_cm_functional_descriptor *)generic; + dataIndex = cm->data_interface; + foundCMDescriptor = true; + } + + if (foundUnionDescriptor && foundCMDescriptor) { + found = true; + break; + } + } + } + } + + if (!foundUnionDescriptor) { + TRACE_ALWAYS("did not find a union descriptor\n"); + return B_ERROR; + } + + if (!foundCMDescriptor) { + 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 != 0xE0 + || descriptor->interface_subclass != 0x01 + || descriptor->interface_protocol != 0x03) + || interface->endpoint_count == 0) { + TRACE_ALWAYS("control interface invalid\n"); + return B_ERROR; + } + + fNotifyEndpoint = interface->endpoint[0].handle; + if (interface->endpoint[0].descr->max_packet_size > sizeof(fNotifyBuffer)) { + TRACE_ALWAYS("Notify buffer is too small, need at least %d bytes\n", + interface->endpoint[0].descr->max_packet_size); + return B_ERROR; + } + + if (dataIndex >= config->interface_count) { + TRACE_ALWAYS("data interface index %d out of range %" B_PRIuSIZE "\n", dataIndex, + config->interface_count); + return B_ERROR; + } + + interface = &config->interface[dataIndex].alt[0]; + descriptor = interface->descr; + if (descriptor->interface_class != USB_CDC_DATA_INTERFACE_CLASS + || interface->endpoint_count < 2) { + TRACE_ALWAYS("data interface %d invalid (class %x, %" B_PRIuSIZE " endpoints)\n", dataIndex, + descriptor->interface_class, interface->endpoint_count); + return B_ERROR; + } + + fDataInterfaceIndex = dataIndex; + return B_OK; +} + + +status_t +RNDISDevice::_GetOID(uint32 oid, void* buffer, size_t length) +{ + uint32 request[] = { + REMOTE_NDIS_QUERY_MSG, + 7 * sizeof(uint32), // Length of the request + 0x00000001, // Request ID (FIXME generate this dynamically if we need multiple requests in + // flight, so we can match up the replies with the different requests) + oid, + 0, 0, 0 + }; + + status_t result = _SendCommand(request, sizeof(request)); + if (result != B_OK) + return result; + + acquire_sem(fNotifyControlSem); + + uint8 response[length + 24]; + result = _ReadResponse(response, length + 24); + memcpy(buffer, &response[24], length); + return result; +} + + +status_t +RNDISDevice::_ReadMACAddress(usb_device device, uint8 *buffer) +{ + status_t result = _GetOID(OID_802_3_PERMANENT_ADDRESS, buffer, 6); + if (result != B_OK) + return result; + + TRACE_ALWAYS("mac address: %02x:%02x:%02x:%02x:%02x:%02x\n", + buffer[0], buffer[1], buffer[2], buffer[3], buffer[4], buffer[5]); + return B_OK; +} + + +status_t +RNDISDevice::_ReadMaxSegmentSize(usb_device device) +{ + status_t result = _GetOID(OID_GEN_MAXIMUM_FRAME_SIZE, &fMaxSegmentSize, + sizeof(fMaxSegmentSize)); + if (result != B_OK) + return result; + + TRACE_ALWAYS("max frame size: %" B_PRId32 "\n", fMaxSegmentSize); + return B_OK; +} + + +status_t +RNDISDevice::_ReadMediaState(usb_device device) +{ + status_t result = _GetOID(OID_GEN_MEDIA_CONNECT_STATUS, &fMediaConnectState, + sizeof(fMediaConnectState)); + if (result != B_OK) + return result; + + TRACE_ALWAYS("media connect state: %" B_PRId32 "\n", fMediaConnectState); + return B_OK; +} + + +status_t +RNDISDevice::_ReadLinkSpeed(usb_device device) +{ + status_t result = _GetOID(OID_GEN_LINK_SPEED, &fDownstreamSpeed, + sizeof(fDownstreamSpeed)); + if (result != B_OK) + return result; + + TRACE_ALWAYS("link speed: %" B_PRId32 " * 100bps\n", fDownstreamSpeed); + return B_OK; +} + + +status_t +RNDISDevice::_EnableBroadcast(usb_device device) +{ + uint32 request[] = { + REMOTE_NDIS_SET_MSG, + 8 * sizeof(uint32), // Length of the request + 0x00000001, // Request ID (FIXME generate this dynamically if we need multiple requests in + // flight, so we can match up the replies with the different requests) + OID_GEN_CURRENT_PACKET_FILTER, + 0x14, // buffer length + 0x14, // buffer offset + 0, // reserved + NDIS_PACKET_TYPE_ALL_MULTICAST | NDIS_PACKET_TYPE_BROADCAST + }; + + status_t result = _SendCommand(request, sizeof(request)); + if (result != B_OK) { + TRACE_ALWAYS("Failed to start traffic (set oid: %s)\n", strerror(result)); + return result; + } + + acquire_sem(fNotifyControlSem); + + uint32 response[4]; + result = _ReadResponse(response, 4 * sizeof(uint32)); + if (result != B_OK) { + TRACE_ALWAYS("Failed to start traffic (response: %s)\n", strerror(result)); + return result; + } + + // TODO check other fields in the response (message type, length, request id) match our request + + switch (response[3]) + { + case RNDIS_STATUS_SUCCESS: + return B_OK; + case RNDIS_STATUS_FAILURE: + return B_ERROR; + case RNDIS_STATUS_INVALID_DATA: + return B_BAD_DATA; + case RNDIS_STATUS_NOT_SUPPORTED: + return B_NOT_SUPPORTED; + case RNDIS_STATUS_MEDIA_CONNECT: + return EISCONN; + case RNDIS_STATUS_MEDIA_DISCONNECT: + return ENOTCONN; + default: + TRACE_ALWAYS("Unexpected error code %" B_PRIx32 "\n", response[3]); + return B_IO_ERROR; + } +} + + +void +RNDISDevice::_ReadCallback(void *cookie, int32 status, void *data, + size_t actualLength) +{ + RNDISDevice *device = (RNDISDevice *)cookie; + device->fActualLengthRead = actualLength; + device->fStatusRead = status; + release_sem_etc(device->fNotifyReadSem, 1, B_DO_NOT_RESCHEDULE); +} + + +void +RNDISDevice::_WriteCallback(void *cookie, int32 status, void *data, + size_t actualLength) +{ + RNDISDevice *device = (RNDISDevice *)cookie; + device->fActualLengthWrite = actualLength; + device->fStatusWrite = status; + release_sem_etc(device->fNotifyWriteSem, 1, B_DO_NOT_RESCHEDULE); +} + + +void +RNDISDevice::_NotifyCallback(void *cookie, int32 status, void *_data, + size_t actualLength) +{ + RNDISDevice *device = (RNDISDevice *)cookie; + atomic_add(&device->fInsideNotify, 1); + if (status == B_CANCELED || device->fRemoved) { + atomic_add(&device->fInsideNotify, -1); + return; + } + + 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"); + } else if (actualLength != 8) { + TRACE_ALWAYS("Received notification with unexpected number of bytes %" B_PRIuSIZE "\n", + actualLength); + } else { +#ifdef TRACE_RNDIS + uint32* data = (uint32*)_data; + uint32 notification = data[0]; + uint32 reserved = data[1]; + TRACE("Received notification %" PRIx32 " %" PRIx32 "\n", notification, reserved); +#endif + release_sem_etc(device->fNotifyControlSem, 1, B_DO_NOT_RESCHEDULE); + } + + // schedule next notification buffer + gUSBModule->queue_interrupt(device->fNotifyEndpoint, device->fNotifyBuffer, + sizeof(device->fNotifyBuffer), _NotifyCallback, device); + atomic_add(&device->fInsideNotify, -1); +} diff --git a/src/add-ons/kernel/drivers/network/ether/usb_rndis/RNDISDevice.h b/src/add-ons/kernel/drivers/network/ether/usb_rndis/RNDISDevice.h new file mode 100644 index 0000000000..fa36282f70 --- /dev/null +++ b/src/add-ons/kernel/drivers/network/ether/usb_rndis/RNDISDevice.h @@ -0,0 +1,90 @@ +/* + Driver for USB RNDIS network devices + Copyright (C) 2022 Adrien Destugues + Distributed under the terms of the MIT license. +*/ +#ifndef _USB_RNDIS_DEVICE_H_ +#define _USB_RNDIS_DEVICE_H_ + +#include "Driver.h" + +class RNDISDevice { +public: + RNDISDevice(usb_device device); + ~RNDISDevice(); + + status_t InitCheck() { return fStatus; }; + + status_t Open(); + bool IsOpen() { return fOpen; }; + + status_t Close(); + status_t Free(); + + status_t Read(uint8 *buffer, size_t *numBytes); + status_t Write(const uint8 *buffer, size_t *numBytes); + status_t Control(uint32 op, void *buffer, size_t length); + + void Removed(); + bool IsRemoved() { return fRemoved; }; + + status_t CompareAndReattach(usb_device device); + +private: +static void _ReadCallback(void *cookie, int32 status, + void *data, size_t actualLength); +static void _WriteCallback(void *cookie, int32 status, + void *data, size_t actualLength); +static void _NotifyCallback(void *cookie, int32 status, + void *data, size_t actualLength); + + status_t _SendCommand(const void*, size_t); + status_t _ReadResponse(void*, size_t); + + status_t _RNDISInitialize(); + status_t _GetOID(uint32 oid, void* buffer, size_t length); + + status_t _SetupDevice(); + status_t _ReadMACAddress(usb_device device, uint8 *buffer); + status_t _ReadMaxSegmentSize(usb_device device); + status_t _ReadMediaState(usb_device device); + status_t _ReadLinkSpeed(usb_device device); + status_t _EnableBroadcast(usb_device device); + + // state tracking + status_t fStatus; + bool fOpen; + bool fRemoved; + int32 fInsideNotify; + usb_device fDevice; + uint16 fVendorID; + uint16 fProductID; + + // interface and device infos + uint8 fDataInterfaceIndex; + uint32 fMaxSegmentSize; + + // pipes for notifications and data io + usb_pipe fNotifyEndpoint; + usb_pipe fReadEndpoint; + usb_pipe fWriteEndpoint; + + // data stores for async usb transfers + uint32 fActualLengthRead; + uint32 fActualLengthWrite; + int32 fStatusRead; + int32 fStatusWrite; + sem_id fNotifyReadSem; + sem_id fNotifyWriteSem; + sem_id fNotifyControlSem; + + uint8 fNotifyBuffer[8]; + + // connection data + sem_id fLinkStateChangeSem; + uint8 fMACAddress[6]; + uint32 fMediaConnectState; + uint32 fDownstreamSpeed; +}; + +#endif //_USB_RNDIS_DEVICE_H_