root/src/add-ons/kernel/bus_managers/usb/Device.cpp
/*
 * Copyright 2003-2014, Haiku Inc. All rights reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *              Michael Lotz <mmlr@mlotz.ch>
 *              Niels S. Reedijk
 */


#include "usb_private.h"

#include <StackOrHeapArray.h>


Device::Device(Object* parent, int8 hubAddress, uint8 hubPort,
        usb_device_descriptor& desc, int8 deviceAddress, usb_speed speed,
        bool isRootHub, void* controllerCookie)
        :
        Object(parent),
        fDeviceDescriptor(desc),
        fInitOK(false),
        fAvailable(true),
        fIsRootHub(isRootHub),
        fConfigurations(NULL),
        fCurrentConfiguration(NULL),
        fSpeed(speed),
        fDeviceAddress(deviceAddress),
        fHubAddress(hubAddress),
        fHubPort(hubPort),
        fControllerCookie(controllerCookie),
        fNode(NULL)
{
        TRACE("creating device\n");

        fDefaultPipe = new(std::nothrow) ControlPipe(this);
        if (fDefaultPipe == NULL) {
                TRACE_ERROR("could not allocate default pipe\n");
                return;
        }

        fDefaultPipe->InitCommon(fDeviceAddress, 0, fSpeed, Pipe::Default,
                fDeviceDescriptor.max_packet_size_0, 0, fHubAddress, fHubPort);

        // Get the device descriptor
        // We already have a part of it, but we want it all
        size_t actualLength;
        status_t status = GetDescriptor(USB_DESCRIPTOR_DEVICE, 0, 0,
                (void*)&fDeviceDescriptor, sizeof(fDeviceDescriptor), &actualLength);

        if (status < B_OK || actualLength != sizeof(fDeviceDescriptor)) {
                TRACE_ERROR("error while getting the device descriptor\n");
                return;
        }

        TRACE("full device descriptor for device %d:\n", fDeviceAddress);
        TRACE("\tlength:..............%d\n", fDeviceDescriptor.length);
        TRACE("\tdescriptor_type:.....0x%04x\n", fDeviceDescriptor.descriptor_type);
        TRACE("\tusb_version:.........0x%04x\n", fDeviceDescriptor.usb_version);
        TRACE("\tdevice_class:........0x%02x\n", fDeviceDescriptor.device_class);
        TRACE("\tdevice_subclass:.....0x%02x\n", fDeviceDescriptor.device_subclass);
        TRACE("\tdevice_protocol:.....0x%02x\n", fDeviceDescriptor.device_protocol);
        TRACE("\tmax_packet_size_0:...%d\n", fDeviceDescriptor.max_packet_size_0);
        TRACE("\tvendor_id:...........0x%04x\n", fDeviceDescriptor.vendor_id);
        TRACE("\tproduct_id:..........0x%04x\n", fDeviceDescriptor.product_id);
        TRACE("\tdevice_version:......0x%04x\n", fDeviceDescriptor.device_version);
        TRACE("\tmanufacturer:........0x%02x\n", fDeviceDescriptor.manufacturer);
        TRACE("\tproduct:.............0x%02x\n", fDeviceDescriptor.product);
        TRACE("\tserial_number:.......0x%02x\n", fDeviceDescriptor.serial_number);
        TRACE("\tnum_configurations:..%d\n", fDeviceDescriptor.num_configurations);

        // Get the configurations
        fConfigurations = (usb_configuration_info*)malloc(
                fDeviceDescriptor.num_configurations * sizeof(usb_configuration_info));
        if (fConfigurations == NULL) {
                TRACE_ERROR("out of memory during config creations!\n");
                return;
        }

        memset(fConfigurations, 0, fDeviceDescriptor.num_configurations
                * sizeof(usb_configuration_info));
        for (int32 i = 0; i < fDeviceDescriptor.num_configurations; i++) {
                usb_configuration_descriptor configDescriptor;
                status = GetDescriptor(USB_DESCRIPTOR_CONFIGURATION, i, 0,
                        (void*)&configDescriptor, sizeof(usb_configuration_descriptor),
                        &actualLength);

                if (status < B_OK
                        || actualLength != sizeof(usb_configuration_descriptor)) {
                        TRACE_ERROR("error fetching configuration %" B_PRId32 "\n", i);
                        return;
                }

                TRACE("configuration %" B_PRId32 "\n", i);
                TRACE("\tlength:..............%d\n", configDescriptor.length);
                TRACE("\tdescriptor_type:.....0x%02x\n",
                        configDescriptor.descriptor_type);
                TRACE("\ttotal_length:........%d\n", configDescriptor.total_length);
                TRACE("\tnumber_interfaces:...%d\n",
                        configDescriptor.number_interfaces);
                TRACE("\tconfiguration_value:.0x%02x\n",
                        configDescriptor.configuration_value);
                TRACE("\tconfiguration:.......0x%02x\n",
                        configDescriptor.configuration);
                TRACE("\tattributes:..........0x%02x\n", configDescriptor.attributes);
                TRACE("\tmax_power:...........%d\n", configDescriptor.max_power);

                uint8* configData = (uint8*)malloc(configDescriptor.total_length);
                if (configData == NULL) {
                        TRACE_ERROR("out of memory when reading config\n");
                        return;
                }

                status = GetDescriptor(USB_DESCRIPTOR_CONFIGURATION, i, 0,
                        (void*)configData, configDescriptor.total_length, &actualLength);

                if (status < B_OK || actualLength != configDescriptor.total_length) {
                        TRACE_ERROR("error fetching full configuration"
                                " descriptor %" B_PRId32 " got %" B_PRIuSIZE " expected %"
                                B_PRIu16 "\n", i, actualLength, configDescriptor.total_length);
                        free(configData);
                        return;
                }

                usb_configuration_descriptor* configuration
                        = (usb_configuration_descriptor*)configData;
                fConfigurations[i].descr = configuration;
                fConfigurations[i].interface_count = configuration->number_interfaces;
                fConfigurations[i].interface = (usb_interface_list*)malloc(
                        configuration->number_interfaces * sizeof(usb_interface_list));
                if (fConfigurations[i].interface == NULL) {
                        TRACE_ERROR("out of memory when creating interfaces\n");
                        return;
                }

                memset(fConfigurations[i].interface, 0,
                        configuration->number_interfaces * sizeof(usb_interface_list));

                usb_interface_info* currentInterface = NULL;
                uint32 descriptorStart = sizeof(usb_configuration_descriptor);
                while (descriptorStart < actualLength) {
                        switch (configData[descriptorStart + 1]) {
                                case USB_DESCRIPTOR_INTERFACE:
                                {
                                        TRACE("got interface descriptor\n");
                                        usb_interface_descriptor* interfaceDescriptor
                                                = (usb_interface_descriptor*)&configData[
                                                        descriptorStart];
                                        TRACE("\tlength:.............%d\n",
                                                interfaceDescriptor->length);
                                        TRACE("\tdescriptor_type:....0x%02x\n",
                                                interfaceDescriptor->descriptor_type);
                                        TRACE("\tinterface_number:...%d\n",
                                                interfaceDescriptor->interface_number);
                                        TRACE("\talternate_setting:..%d\n",
                                                interfaceDescriptor->alternate_setting);
                                        TRACE("\tnum_endpoints:......%d\n",
                                                interfaceDescriptor->num_endpoints);
                                        TRACE("\tinterface_class:....0x%02x\n",
                                                interfaceDescriptor->interface_class);
                                        TRACE("\tinterface_subclass:.0x%02x\n",
                                                interfaceDescriptor->interface_subclass);
                                        TRACE("\tinterface_protocol:.0x%02x\n",
                                                interfaceDescriptor->interface_protocol);
                                        TRACE("\tinterface:..........%d\n",
                                                interfaceDescriptor->interface);

                                        if (interfaceDescriptor->interface_number
                                                        >= fConfigurations[i].interface_count) {
                                                interfaceDescriptor->interface_number
                                                        = fConfigurations[i].interface_count - 1;
                                                TRACE_ERROR("Corrected invalid interface_number!\n");
                                        }

                                        usb_interface_list* interfaceList = &fConfigurations[i]
                                                .interface[interfaceDescriptor->interface_number];

                                        // Allocate this alternate
                                        interfaceList->alt_count++;
                                        usb_interface_info* newAlternates
                                                = (usb_interface_info*)realloc(interfaceList->alt,
                                                interfaceList->alt_count * sizeof(usb_interface_info));
                                        if (newAlternates == NULL) {
                                                TRACE_ERROR("out of memory allocating"
                                                        " alternate interface\n");
                                                interfaceList->alt_count--;
                                                return;
                                        }

                                        interfaceList->alt = newAlternates;

                                        // Set active interface always to the first one
                                        interfaceList->active = interfaceList->alt;

                                        // Setup this alternate
                                        usb_interface_info* interfaceInfo
                                                = &interfaceList->alt[interfaceList->alt_count - 1];
                                        interfaceInfo->descr = interfaceDescriptor;
                                        interfaceInfo->endpoint_count = 0;
                                        interfaceInfo->endpoint = NULL;
                                        interfaceInfo->generic_count = 0;
                                        interfaceInfo->generic = NULL;

                                        Interface* interface = new(std::nothrow) Interface(this,
                                                interfaceDescriptor->interface_number);
                                        if (interface == NULL) {
                                                TRACE_ERROR("failed to allocate"
                                                        " interface object\n");
                                                return;
                                        }

                                        interfaceInfo->handle = interface->USBID();
                                        currentInterface = interfaceInfo;
                                        break;
                                }

                                case USB_DESCRIPTOR_ENDPOINT:
                                {
                                        TRACE("got endpoint descriptor\n");
                                        usb_endpoint_descriptor* endpointDescriptor
                                                = (usb_endpoint_descriptor*)&configData[descriptorStart];
                                        TRACE("\tlength:.............%d\n",
                                                endpointDescriptor->length);
                                        TRACE("\tdescriptor_type:....0x%02x\n",
                                                endpointDescriptor->descriptor_type);
                                        TRACE("\tendpoint_address:...0x%02x\n",
                                                endpointDescriptor->endpoint_address);
                                        TRACE("\tattributes:.........0x%02x\n",
                                                endpointDescriptor->attributes);
                                        TRACE("\tmax_packet_size:....%d\n",
                                                endpointDescriptor->max_packet_size);
                                        TRACE("\tinterval:...........%d\n",
                                                endpointDescriptor->interval);

                                        if (currentInterface == NULL)
                                                break;

                                        // Allocate this endpoint
                                        currentInterface->endpoint_count++;
                                        usb_endpoint_info* newEndpoints = (usb_endpoint_info*)
                                                realloc(currentInterface->endpoint,
                                                        currentInterface->endpoint_count
                                                                * sizeof(usb_endpoint_info));
                                        if (newEndpoints == NULL) {
                                                TRACE_ERROR("out of memory allocating new endpoint\n");
                                                currentInterface->endpoint_count--;
                                                return;
                                        }

                                        currentInterface->endpoint = newEndpoints;

                                        // Setup this endpoint
                                        usb_endpoint_info* endpointInfo = &currentInterface
                                                ->endpoint[currentInterface->endpoint_count - 1];
                                        endpointInfo->descr = endpointDescriptor;
                                        endpointInfo->handle = 0;
                                        break;
                                }

                                case USB_DESCRIPTOR_ENDPOINT_SS_COMPANION: {
                                        if (currentInterface != NULL) {
                                                usb_endpoint_descriptor* desc
                                                        = currentInterface->endpoint[
                                                                currentInterface->endpoint_count - 1].descr;
                                                if ((uint8*)desc != (&configData[descriptorStart
                                                                - desc->length])) {
                                                        TRACE_ERROR("found endpoint companion descriptor "
                                                                "not immediately following endpoint "
                                                                "descriptor, ignoring!\n");
                                                        break;
                                                }
                                                // TODO: It'd be nicer if we could store the endpoint
                                                // companion descriptor along with the endpoint
                                                // descriptor, but as the interface struct is public
                                                // API, that would be an ABI break.
                                        }

                                        // fall through
                                }

                                default:
                                        TRACE("got generic descriptor\n");
                                        usb_generic_descriptor* genericDescriptor
                                                = (usb_generic_descriptor*)&configData[descriptorStart];
                                        TRACE("\tlength:.............%d\n",
                                                genericDescriptor->length);
                                        TRACE("\tdescriptor_type:....0x%02x\n",
                                                genericDescriptor->descriptor_type);

                                        if (currentInterface == NULL)
                                                break;

                                        // Allocate this descriptor
                                        currentInterface->generic_count++;
                                        usb_descriptor** newGenerics = (usb_descriptor**)realloc(
                                                currentInterface->generic,
                                                currentInterface->generic_count
                                                        * sizeof(usb_descriptor*));
                                        if (newGenerics == NULL) {
                                                TRACE_ERROR("out of memory allocating"
                                                        " generic descriptor\n");
                                                currentInterface->generic_count--;
                                                return;
                                        }

                                        currentInterface->generic = newGenerics;

                                        // Add this descriptor
                                        currentInterface->generic[
                                                currentInterface->generic_count - 1]
                                                        = (usb_descriptor*)genericDescriptor;
                                        break;
                        }

                        descriptorStart += configData[descriptorStart];
                }
        }

        // Set default configuration
        TRACE("setting default configuration\n");
        if (SetConfigurationAt(0) != B_OK) {
                TRACE_ERROR("failed to set default configuration\n");
                return;
        }

        fInitOK = true;
}


Device::~Device()
{
        if (fNode != NULL) {
                status_t error = gDeviceManager->unregister_node(fNode);
                if (error != B_OK && error != B_BUSY)
                        TRACE_ERROR("failed to unregister device node\n");
                fNode = NULL;
        }

        // Destroy open endpoints. Do not send a device request to unconfigure
        // though, since we may be deleted because the device was unplugged already.
        Unconfigure(false);

        // Destroy all Interfaces in the Configurations hierarchy.
        for (int32 i = 0; fConfigurations != NULL
                        && i < fDeviceDescriptor.num_configurations; i++) {
                usb_configuration_info* configuration = &fConfigurations[i];
                if (configuration == NULL || configuration->interface == NULL)
                        continue;

                for (size_t j = 0; j < configuration->interface_count; j++) {
                        usb_interface_list* interfaceList = &configuration->interface[j];
                        if (interfaceList->alt == NULL)
                                continue;

                        for (size_t k = 0; k < interfaceList->alt_count; k++) {
                                usb_interface_info* interface = &interfaceList->alt[k];
                                Interface* interfaceObject =
                                        (Interface*)GetStack()->GetObject(interface->handle);
                                interface->handle = 0;
                                if (interfaceObject != NULL)
                                        interfaceObject->ReleaseReference();
                                delete interfaceObject;
                        }
                }
        }

        if (fDefaultPipe != NULL) {
                fDefaultPipe->PutUSBID(false);

                // Also put our ID to prevent further transfers on the default pipe
                // from being queued. (We have to do this after putting the pipe's ID,
                // since we are its parent.)
                PutUSBID(false);

                fDefaultPipe->CancelQueuedTransfers(true);
                fDefaultPipe->WaitForIdle();
        }

        // Ensure we are gone from the stack before deleting public structures.
        PutUSBID(true);
        delete fDefaultPipe;

        if (fConfigurations == NULL) {
                // we didn't get far in device setup, so everything below is unneeded
                return;
        }

        // Free the Configurations hierarchy.
        for (int32 i = 0; i < fDeviceDescriptor.num_configurations; i++) {
                usb_configuration_info* configuration = &fConfigurations[i];
                if (configuration == NULL)
                        continue;

                free(configuration->descr);
                if (configuration->interface == NULL)
                        continue;

                for (size_t j = 0; j < configuration->interface_count; j++) {
                        usb_interface_list* interfaceList = &configuration->interface[j];
                        if (interfaceList->alt == NULL)
                                continue;

                        for (size_t k = 0; k < interfaceList->alt_count; k++) {
                                usb_interface_info* interface = &interfaceList->alt[k];
                                free(interface->endpoint);
                                free(interface->generic);
                        }

                        free(interfaceList->alt);
                }

                free(configuration->interface);
        }

        free(fConfigurations);
}


status_t
Device::InitCheck()
{
        if (fInitOK)
                return B_OK;

        return B_ERROR;
}


status_t
Device::Changed(change_item** changeList, bool added)
{
        fAvailable = added;
        change_item* changeItem = new(std::nothrow) change_item;
        if (changeItem == NULL)
                return B_NO_MEMORY;

        changeItem->added = added;
        changeItem->device = this;
        changeItem->link = *changeList;
        *changeList = changeItem;
        return B_OK;
}


status_t
Device::GetDescriptor(uint8 descriptorType, uint8 index, uint16 languageID,
        void* data, size_t dataLength, size_t* actualLength)
{
        if (!fAvailable)
                return B_ERROR;

        return fDefaultPipe->SendRequest(
                USB_REQTYPE_DEVICE_IN | USB_REQTYPE_STANDARD,
                USB_REQUEST_GET_DESCRIPTOR, (descriptorType << 8) | index,
                languageID, dataLength, data, dataLength, actualLength);
}


const usb_configuration_info*
Device::Configuration() const
{
        return fCurrentConfiguration;
}


const usb_configuration_info*
Device::ConfigurationAt(uint8 index) const
{
        if (index >= fDeviceDescriptor.num_configurations)
                return NULL;

        return &fConfigurations[index];
}


status_t
Device::SetConfiguration(const usb_configuration_info* configuration)
{
        if (!configuration)
                return Unconfigure(true);

        for (uint8 i = 0; i < fDeviceDescriptor.num_configurations; i++) {
                if (configuration->descr->configuration_value
                                == fConfigurations[i].descr->configuration_value)
                        return SetConfigurationAt(i);
        }

        return B_BAD_VALUE;
}


status_t
Device::SetConfigurationAt(uint8 index)
{
        if (!fAvailable)
                return B_ERROR;
        if (index >= fDeviceDescriptor.num_configurations)
                return B_BAD_VALUE;
        if (&fConfigurations[index] == fCurrentConfiguration)
                return B_OK;

        // Destroy our open endpoints
        Unconfigure(false);

        // Tell the device to set the configuration
        status_t result = fDefaultPipe->SendRequest(
                USB_REQTYPE_DEVICE_OUT | USB_REQTYPE_STANDARD,
                USB_REQUEST_SET_CONFIGURATION,
                fConfigurations[index].descr->configuration_value, 0, 0, NULL, 0, NULL);
        if (result < B_OK)
                return result;

        // Set current configuration
        fCurrentConfiguration = &fConfigurations[index];

        // Initialize all the endpoints that are now active
        InitEndpoints(-1);

        // Wait some for the configuration being finished
        if (!fIsRootHub)
                snooze(USB_DELAY_SET_CONFIGURATION);
        return B_OK;
}


void
Device::InitEndpoints(int32 interfaceIndex)
{
        for (size_t j = 0; j < fCurrentConfiguration->interface_count; j++) {
                if (interfaceIndex >= 0 && j != (size_t)interfaceIndex)
                        continue;

                usb_interface_info* interfaceInfo
                        = fCurrentConfiguration->interface[j].active;
                if (interfaceInfo == NULL)
                        continue;

                for (size_t i = 0; i < interfaceInfo->endpoint_count; i++) {
                        usb_endpoint_info* endpoint = &interfaceInfo->endpoint[i];
                        Pipe* pipe = NULL;

                        usb_endpoint_ss_companion_descriptor* comp_descr = NULL;
                        if (fSpeed >= USB_SPEED_SUPERSPEED) {
                                // We should have a companion descriptor for this device.
                                // Let's find it: it'll be the "i"th one.
                                size_t k = 0;
                                for (size_t j = 0; j < interfaceInfo->generic_count; j++) {
                                        usb_descriptor* desc = interfaceInfo->generic[j];
                                        if (desc->endpoint.descriptor_type
                                                        != USB_DESCRIPTOR_ENDPOINT_SS_COMPANION) {
                                                continue;
                                        }
                                        if (k == i) {
                                                comp_descr =
                                                        (usb_endpoint_ss_companion_descriptor*)desc;
                                                break;
                                        }
                                        k++;
                                }
                                if (comp_descr == NULL) {
                                        TRACE_ERROR("SuperSpeed device without an endpoint companion "
                                                "descriptor!");
                                }
                        }

                        Pipe::pipeDirection direction = Pipe::Out;
                        if ((endpoint->descr->endpoint_address & 0x80) != 0)
                                direction = Pipe::In;

                        switch (endpoint->descr->attributes & 0x03) {
                                case USB_ENDPOINT_ATTR_CONTROL:         // Control Endpoint
                                        pipe = new(std::nothrow) ControlPipe(this);
                                        direction = Pipe::Default;
                                        break;

                                case USB_ENDPOINT_ATTR_ISOCHRONOUS:     // Isochronous Endpoint
                                        pipe = new(std::nothrow) IsochronousPipe(this);
                                        break;

                                case USB_ENDPOINT_ATTR_BULK:            // Bulk Endpoint
                                        pipe = new(std::nothrow) BulkPipe(this);
                                        break;

                                case USB_ENDPOINT_ATTR_INTERRUPT:       // Interrupt Endpoint
                                        pipe = new(std::nothrow) InterruptPipe(this);
                                        break;
                        }

                        if (pipe == NULL) {
                                TRACE_ERROR("failed to allocate pipe\n");
                                endpoint->handle = 0;
                                continue;
                        }

                        pipe->InitCommon(fDeviceAddress,
                                endpoint->descr->endpoint_address & 0x0f,
                                fSpeed, direction, endpoint->descr->max_packet_size,
                                endpoint->descr->interval, fHubAddress, fHubPort);
                        if (comp_descr != NULL) {
                                pipe->InitSuperSpeed(comp_descr->max_burst,
                                        comp_descr->bytes_per_interval);
                        }
                        endpoint->handle = pipe->USBID();
                }
        }
}


status_t
Device::Unconfigure(bool atDeviceLevel)
{
        // If we only want to destroy our open pipes before setting
        // another configuration unconfigure will be called with
        // atDevice = false. otherwise we explicitly want to unconfigure
        // the device and have to send it the corresponding request.
        if (atDeviceLevel && fAvailable) {
                status_t result = fDefaultPipe->SendRequest(
                        USB_REQTYPE_DEVICE_OUT | USB_REQTYPE_STANDARD,
                        USB_REQUEST_SET_CONFIGURATION, 0, 0, 0, NULL, 0, NULL);
                if (result < B_OK)
                        return result;

                snooze(USB_DELAY_SET_CONFIGURATION);
        }

        if (!fCurrentConfiguration)
                return B_OK;

        ClearEndpoints(-1);
        fCurrentConfiguration = NULL;
        return B_OK;
}


void
Device::ClearEndpoints(int32 interfaceIndex)
{
        if (fCurrentConfiguration == NULL
                || fCurrentConfiguration->interface == NULL)
                return;

        for (size_t j = 0; j < fCurrentConfiguration->interface_count; j++) {
                if (interfaceIndex >= 0 && j != (size_t)interfaceIndex)
                        continue;

                usb_interface_info* interfaceInfo
                        = fCurrentConfiguration->interface[j].active;
                if (interfaceInfo == NULL || interfaceInfo->endpoint == NULL)
                        continue;

                for (size_t i = 0; i < interfaceInfo->endpoint_count; i++) {
                        usb_endpoint_info* endpoint = &interfaceInfo->endpoint[i];
                        Pipe* pipe = (Pipe*)GetStack()->GetObject(endpoint->handle);
                        if (pipe != NULL)
                                pipe->ReleaseReference();
                        delete pipe;
                        endpoint->handle = 0;
                }
        }
}


status_t
Device::SetAltInterface(const usb_interface_info* interface)
{
        uint8 interfaceNumber = interface->descr->interface_number;
        // Tell the device to set the alternate settings
        status_t result = fDefaultPipe->SendRequest(
                USB_REQTYPE_INTERFACE_OUT | USB_REQTYPE_STANDARD,
                USB_REQUEST_SET_INTERFACE,
                interface->descr->alternate_setting, interfaceNumber, 0, NULL, 0, NULL);
        if (result < B_OK)
                return result;

        // Clear the no longer active endpoints
        ClearEndpoints(interfaceNumber);

        // Update the active pointer of the interface list
        usb_interface_list* interfaceList
                = &fCurrentConfiguration->interface[interfaceNumber];
        interfaceList->active
                = &interfaceList->alt[interface->descr->alternate_setting];

        // Initialize the new endpoints
        InitEndpoints(interfaceNumber);
        return result;
}


const usb_device_descriptor*
Device::DeviceDescriptor() const
{
        return &fDeviceDescriptor;
}


status_t
Device::ReportDevice(usb_support_descriptor* supportDescriptors,
        uint32 supportDescriptorCount, const usb_notify_hooks* hooks,
        usb_driver_cookie** cookies, bool added, bool recursive)
{
        TRACE("reporting device\n");
        bool supported = false;
        if (supportDescriptorCount == 0 || supportDescriptors == NULL)
                supported = true;

        for (uint32 i = 0; !supported && i < supportDescriptorCount; i++) {
                if ((supportDescriptors[i].vendor != 0
                                && fDeviceDescriptor.vendor_id != supportDescriptors[i].vendor)
                        || (supportDescriptors[i].product != 0
                                && fDeviceDescriptor.product_id
                                        != supportDescriptors[i].product))
                        continue;

                if ((supportDescriptors[i].dev_class == 0
                                || fDeviceDescriptor.device_class
                                        == supportDescriptors[i].dev_class)
                        && (supportDescriptors[i].dev_subclass == 0
                                || fDeviceDescriptor.device_subclass
                                        == supportDescriptors[i].dev_subclass)
                        && (supportDescriptors[i].dev_protocol == 0
                                || fDeviceDescriptor.device_protocol
                                        == supportDescriptors[i].dev_protocol)) {
                        supported = true;
                }

                // we have to check all interfaces for matching class/subclass/protocol
                for (uint32 j = 0;
                                !supported && j < fDeviceDescriptor.num_configurations; j++) {
                        for (uint32 k = 0;
                                        !supported && k < fConfigurations[j].interface_count; k++) {
                                for (uint32 l = 0; !supported
                                        && l < fConfigurations[j].interface[k].alt_count; l++) {
                                        usb_interface_descriptor* descriptor
                                                = fConfigurations[j].interface[k].alt[l].descr;
                                        if ((supportDescriptors[i].dev_class == 0
                                                        || descriptor->interface_class
                                                                == supportDescriptors[i].dev_class)
                                                && (supportDescriptors[i].dev_subclass == 0
                                                        || descriptor->interface_subclass
                                                                == supportDescriptors[i].dev_subclass)
                                                && (supportDescriptors[i].dev_protocol == 0
                                                        || descriptor->interface_protocol
                                                                == supportDescriptors[i].dev_protocol)) {
                                                supported = true;
                                        }
                                }
                        }
                }
        }

        if (!supported)
                return B_UNSUPPORTED;

        if ((added && hooks->device_added == NULL)
                || (!added && hooks->device_removed == NULL)) {
                // hooks are not installed, but report success to indicate that
                // the driver supports the device
                return B_OK;
        }

        usb_id id = USBID();
        if (added) {
                usb_driver_cookie* cookie = new(std::nothrow) usb_driver_cookie;
                if (hooks->device_added(id, &cookie->cookie) >= B_OK) {
                        cookie->device = id;
                        cookie->link = *cookies;
                        *cookies = cookie;
                } else
                        delete cookie;
        } else {
                usb_driver_cookie** pointer = cookies;
                usb_driver_cookie* cookie = *cookies;
                while (cookie != NULL) {
                        if (cookie->device == id)
                                break;
                        pointer = &cookie->link;
                        cookie = cookie->link;
                }

                if (cookie == NULL) {
                        // the device is supported, but there is no cookie. this most
                        // probably means that the device_added hook above failed.
                        return B_OK;
                }

                hooks->device_removed(cookie->cookie);
                *pointer = cookie->link;
                delete cookie;
        }

        return B_OK;
}


status_t
Device::BuildDeviceName(char* string, uint32* index, size_t bufferSize,
        Device* device)
{
        if (!Parent() || (Parent()->Type() & USB_OBJECT_HUB) == 0)
                return B_ERROR;

        ((Hub*)Parent())->BuildDeviceName(string, index, bufferSize, this);
        return B_OK;
}


status_t
Device::SetFeature(uint16 selector)
{
        if (!fAvailable)
                return B_ERROR;

        TRACE("set feature %u\n", selector);
        return fDefaultPipe->SendRequest(
                USB_REQTYPE_STANDARD | USB_REQTYPE_DEVICE_OUT,
                USB_REQUEST_SET_FEATURE, selector, 0, 0, NULL, 0, NULL);
}


status_t
Device::ClearFeature(uint16 selector)
{
        if (!fAvailable)
                return B_ERROR;

        TRACE("clear feature %u\n", selector);
        return fDefaultPipe->SendRequest(
                USB_REQTYPE_STANDARD | USB_REQTYPE_DEVICE_OUT,
                USB_REQUEST_CLEAR_FEATURE, selector, 0, 0, NULL, 0, NULL);
}


status_t
Device::GetStatus(uint16* status)
{
        if (!fAvailable)
                return B_ERROR;

        TRACE("get status\n");
        return fDefaultPipe->SendRequest(
                USB_REQTYPE_STANDARD | USB_REQTYPE_DEVICE_IN,
                USB_REQUEST_GET_STATUS, 0, 0, 2, (void*)status, 2, NULL);
}


device_node*
Device::RegisterNode(device_node *parent)
{
        usb_id id = USBID();
        if (parent == NULL)
                parent = ((Device*)Parent())->Node();

        // determine how many attributes we will need
        uint32 deviceAttrTotal = 1;
        for (uint32 j = 0; j < fDeviceDescriptor.num_configurations; j++) {
                for (uint32 k = 0; k < fConfigurations[j].interface_count; k++) {
                        for (uint32 l = 0; l < fConfigurations[j].interface[k].alt_count; l++) {
                                deviceAttrTotal += 3;
                        }
                }
        }

        BStackOrHeapArray<device_attr, 16> attrs(deviceAttrTotal + 4 + 5);
        attrs[0] = { B_DEVICE_BUS, B_STRING_TYPE, { .string = "usb" } };

        // location
        attrs[1] = { USB_DEVICE_ID_ITEM, B_UINT32_TYPE, { .ui32 = id } };
        attrs[2] = { B_DEVICE_FLAGS, B_UINT32_TYPE, { .ui32 = B_FIND_MULTIPLE_CHILDREN } };
        attrs[3] = { B_DEVICE_PRETTY_NAME, B_STRING_TYPE, { .string = "USB device" } };

        uint32 attrCount = 4;

        if (fDeviceDescriptor.vendor_id != 0) {
                attrs[attrCount].name = B_DEVICE_VENDOR_ID;
                attrs[attrCount].type = B_UINT16_TYPE;
                attrs[attrCount].value.ui16 = fDeviceDescriptor.vendor_id;
                attrCount++;

                attrs[attrCount].name = B_DEVICE_ID;
                attrs[attrCount].type = B_UINT16_TYPE;
                attrs[attrCount].value.ui16 = fDeviceDescriptor.product_id;
                attrCount++;
        }

        uint32 attrClassesIndex = attrCount;

        if (fDeviceDescriptor.device_class != 0) {
                attrs[attrCount].name = USB_DEVICE_CLASS;
                attrs[attrCount].type = B_UINT8_TYPE;
                attrs[attrCount].value.ui8 = fDeviceDescriptor.device_class;
                attrCount++;

                attrs[attrCount].name = USB_DEVICE_SUBCLASS;
                attrs[attrCount].type = B_UINT8_TYPE;
                attrs[attrCount].value.ui8 = fDeviceDescriptor.device_subclass;
                attrCount++;

                attrs[attrCount].name = USB_DEVICE_PROTOCOL;
                attrs[attrCount].type = B_UINT8_TYPE;
                attrs[attrCount].value.ui8 = fDeviceDescriptor.device_protocol;
                attrCount++;
        }

        for (uint32 j = 0; j < fDeviceDescriptor.num_configurations; j++) {
                for (uint32 k = 0; k < fConfigurations[j].interface_count; k++) {
                        for (uint32 l = 0; l < fConfigurations[j].interface[k].alt_count; l++) {
                                usb_interface_descriptor* descriptor
                                        = fConfigurations[j].interface[k].alt[l].descr;
                                bool found = false;
                                for (uint32 i = attrClassesIndex; i < attrCount;) {
                                        if (attrs[i++].value.ui8 != descriptor->interface_class)
                                                continue;
                                        if (attrs[i++].value.ui8 != descriptor->interface_subclass)
                                                continue;
                                        if (attrs[i++].value.ui8 != descriptor->interface_protocol)
                                                continue;
                                        found = true;
                                        break;
                                }
                                if (found)
                                        continue;

                                attrs[attrCount].name = USB_DEVICE_CLASS;
                                attrs[attrCount].type = B_UINT8_TYPE;
                                attrs[attrCount].value.ui8 = descriptor->interface_class;
                                attrCount++;

                                attrs[attrCount].name = USB_DEVICE_SUBCLASS;
                                attrs[attrCount].type = B_UINT8_TYPE;
                                attrs[attrCount].value.ui8 = descriptor->interface_subclass;
                                attrCount++;

                                attrs[attrCount].name = USB_DEVICE_PROTOCOL;
                                attrs[attrCount].type = B_UINT8_TYPE;
                                attrs[attrCount].value.ui8 = descriptor->interface_protocol;
                                attrCount++;
                        }
                }
        }

        attrs[attrCount].name = NULL;
        attrs[attrCount].type = 0;
        attrs[attrCount].value.string = NULL;
        attrCount++;

        device_node* node = NULL;
        if (gDeviceManager->register_node(parent, USB_DEVICE_MODULE_NAME, attrs,
                        NULL, &node) != B_OK) {
                TRACE_ERROR("failed to register device node\n");
        } else
                fNode = node;
        return node;
}