root/src/kits/device/USBDevice.cpp
/*
 * Copyright 2007-2008, Haiku Inc. All rights reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *              Michael Lotz <mmlr@mlotz.ch>
 */

#include <ByteOrder.h>
#include <USBKit.h>
#include <usb_raw.h>
#include <UTF8.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <new>


BUSBDevice::BUSBDevice(const char *path)
        :       fPath(NULL),
                fRawFD(-1),
                fConfigurations(NULL),
                fActiveConfiguration(0),
                fManufacturerString(NULL),
                fProductString(NULL),
                fSerialNumberString(NULL)
{
        memset(&fDescriptor, 0, sizeof(fDescriptor));

        if (path)
                SetTo(path);
}


BUSBDevice::~BUSBDevice()
{
        Unset();
}


status_t
BUSBDevice::InitCheck()
{
        return (fRawFD >= 0 ? B_OK : B_ERROR);
}


status_t
BUSBDevice::SetTo(const char *path)
{
        if (!path)
                return B_BAD_VALUE;

        fPath = strdup(path);
        fRawFD = open(path, O_RDWR | O_CLOEXEC);
        if (fRawFD < 0) {
                Unset();
                return B_ERROR;
        }

        usb_raw_command command;
        if (ioctl(fRawFD, B_USB_RAW_COMMAND_GET_VERSION, &command, sizeof(command))
                || command.version.status != B_USB_RAW_PROTOCOL_VERSION) {
                Unset();
                return B_ERROR;
        }

        command.device.descriptor = &fDescriptor;
        if (ioctl(fRawFD, B_USB_RAW_COMMAND_GET_DEVICE_DESCRIPTOR, &command,
                sizeof(command)) || command.device.status != B_USB_RAW_STATUS_SUCCESS) {
                Unset();
                return B_ERROR;
        }

        fConfigurations = new(std::nothrow) BUSBConfiguration *[
                fDescriptor.num_configurations];
        if (fConfigurations == NULL)
                return B_NO_MEMORY;

        for (uint32 i = 0; i < fDescriptor.num_configurations; i++) {
                fConfigurations[i] = new(std::nothrow) BUSBConfiguration(this, i,
                        fRawFD);
        }

        return B_OK;
}


void
BUSBDevice::Unset()
{
        if (fRawFD >= 0)
                close(fRawFD);
        fRawFD = -1;

        free(fPath);
        fPath = NULL;

        delete[] fManufacturerString;
        delete[] fProductString;
        delete[] fSerialNumberString;
        fManufacturerString = fProductString = fSerialNumberString = NULL;

        if (fConfigurations != NULL) {
                for (int32 i = 0; i < fDescriptor.num_configurations; i++)
                        delete fConfigurations[i];

                delete[] fConfigurations;
                fConfigurations = NULL;
        }

        memset(&fDescriptor, 0, sizeof(fDescriptor));
}


const char *
BUSBDevice::Location() const
{
        if (!fPath || strlen(fPath) < 12)
                return NULL;

        return &fPath[12];
}


bool
BUSBDevice::IsHub() const
{
        return fDescriptor.device_class == 0x09;
}


uint16
BUSBDevice::USBVersion() const
{
        return fDescriptor.usb_version;
}


uint8
BUSBDevice::Class() const
{
        return fDescriptor.device_class;
}


uint8
BUSBDevice::Subclass() const
{
        return fDescriptor.device_subclass;
}


uint8
BUSBDevice::Protocol() const
{
        return fDescriptor.device_protocol;
}


uint8
BUSBDevice::MaxEndpoint0PacketSize() const
{
        return fDescriptor.max_packet_size_0;
}


uint16
BUSBDevice::VendorID() const
{
        return fDescriptor.vendor_id;
}


uint16
BUSBDevice::ProductID() const
{
        return fDescriptor.product_id;
}


uint16
BUSBDevice::Version() const
{
        return fDescriptor.device_version;
}


const char *
BUSBDevice::ManufacturerString() const
{
        if (fDescriptor.manufacturer == 0)
                return "";

        if (fManufacturerString)
                return fManufacturerString;

        fManufacturerString = DecodeStringDescriptor(fDescriptor.manufacturer);
        if (fManufacturerString == NULL)
                return "";

        return fManufacturerString;
}


const char *
BUSBDevice::ProductString() const
{
        if (fDescriptor.product == 0)
                return "";

        if (fProductString)
                return fProductString;

        fProductString = DecodeStringDescriptor(fDescriptor.product);
        if (fProductString == NULL)
                return "";

        return fProductString;
}


const char *
BUSBDevice::SerialNumberString() const
{
        if (fDescriptor.serial_number == 0)
                return "";

        if (fSerialNumberString)
                return fSerialNumberString;

        fSerialNumberString = DecodeStringDescriptor(fDescriptor.serial_number);
        if (fSerialNumberString == NULL)
                return "";

        return fSerialNumberString;
}


const usb_device_descriptor *
BUSBDevice::Descriptor() const
{
        return &fDescriptor;
}


size_t
BUSBDevice::GetStringDescriptor(uint32 index,
        usb_string_descriptor *descriptor, size_t length) const
{
        if (!descriptor)
                return B_BAD_VALUE;

        usb_raw_command command;
        command.string.descriptor = descriptor;
        command.string.string_index = index;
        command.string.length = length;

        if (ioctl(fRawFD, B_USB_RAW_COMMAND_GET_STRING_DESCRIPTOR, &command,
                sizeof(command)) || command.string.status != B_USB_RAW_STATUS_SUCCESS)
                return 0;

        return command.string.length;
}


char *
BUSBDevice::DecodeStringDescriptor(uint32 index) const
{
        char buffer[300];
        usb_string_descriptor *stringDescriptor;
        stringDescriptor = (usb_string_descriptor *)&buffer;

        int32 stringLength = GetStringDescriptor(index, stringDescriptor,
                sizeof(buffer) - sizeof(usb_string_descriptor)) - 1;

        if (stringLength < 3)
                return NULL;

        int32 resultLength = 0;

        // USB is always little-endian, UCS-2 is big-endian.
        uint16* ustr = (uint16*)stringDescriptor->string;
        for (int32 i = 0; i < (stringLength / 2); i++) {
                // Increase size of result as needed by source character.
                const uint16 character = B_LENDIAN_TO_HOST_INT16(ustr[i]);
                resultLength++;
                if (character >= 0x80)
                        resultLength++;
                if (character >= 0x800)
                        resultLength++;

                ustr[i] = B_SWAP_INT16(ustr[i]);
        }

        char *result = new(std::nothrow) char[resultLength + 1];
        if (result == NULL)
                return NULL;

        status_t status = convert_to_utf8(B_UNICODE_CONVERSION,
                (const char*)stringDescriptor->string, &stringLength,
                result, &resultLength, NULL);
        if (status != B_OK) {
                delete[] result;
                return NULL;
        }
        result[resultLength] = 0;
        return result;
}


size_t
BUSBDevice::GetDescriptor(uint8 type, uint8 index, uint16 languageID,
        void *data, size_t length) const
{
        if (length > 0 && data == NULL)
                return B_BAD_VALUE;

        usb_raw_command command;
        command.descriptor.type = type;
        command.descriptor.index = index;
        command.descriptor.language_id = languageID;
        command.descriptor.data = data;
        command.descriptor.length = length;

        if (ioctl(fRawFD, B_USB_RAW_COMMAND_GET_DESCRIPTOR, &command,
                sizeof(command)) || command.descriptor.status != B_USB_RAW_STATUS_SUCCESS)
                return 0;

        return command.descriptor.length;
}


uint32
BUSBDevice::CountConfigurations() const
{
        return fDescriptor.num_configurations;
}


const BUSBConfiguration *
BUSBDevice::ConfigurationAt(uint32 index) const
{
        if (index >= fDescriptor.num_configurations || fConfigurations == NULL)
                return NULL;

        return fConfigurations[index];
}


const BUSBConfiguration *
BUSBDevice::ActiveConfiguration() const
{
        if (fConfigurations == NULL)
                return NULL;

        return fConfigurations[fActiveConfiguration];
}


status_t
BUSBDevice::SetConfiguration(const BUSBConfiguration *configuration)
{
        if (!configuration || configuration->Index() >= fDescriptor.num_configurations)
                return B_BAD_VALUE;

        usb_raw_command command;
        command.config.config_index = configuration->Index();

        if (ioctl(fRawFD, B_USB_RAW_COMMAND_SET_CONFIGURATION, &command,
                sizeof(command)) || command.config.status != B_USB_RAW_STATUS_SUCCESS)
                return B_ERROR;

        fActiveConfiguration = configuration->Index();
        return B_OK;
}


ssize_t
BUSBDevice::ControlTransfer(uint8 requestType, uint8 request, uint16 value,
        uint16 index, uint16 length, void *data) const
{
        if (length > 0 && data == NULL)
                return B_BAD_VALUE;

        usb_raw_command command;
        command.control.request_type = requestType;
        command.control.request = request;
        command.control.value = value;
        command.control.index = index;
        command.control.length = length;
        command.control.data = data;

        if (ioctl(fRawFD, B_USB_RAW_COMMAND_CONTROL_TRANSFER, &command,
                sizeof(command)) || command.control.status != B_USB_RAW_STATUS_SUCCESS)
                return B_ERROR;

        return command.control.length;
}


// definition of reserved virtual functions
void BUSBDevice::_ReservedUSBDevice1() {};
void BUSBDevice::_ReservedUSBDevice2() {};
void BUSBDevice::_ReservedUSBDevice3() {};
void BUSBDevice::_ReservedUSBDevice4() {};
void BUSBDevice::_ReservedUSBDevice5() {};