blob: 3e092fb2e937348788d1f0f9a8021cb7298f2c75 [file] [log] [blame] [edit]
/*
* Haiku Backend for libusb
* Copyright © 2014 Akshay Jaggi <akshay1994.leo@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <new>
#include <vector>
#include "haiku_usb.h"
int _errno_to_libusb(int status)
{
return status;
}
USBTransfer::USBTransfer(struct usbi_transfer* itransfer, USBDevice* device)
{
fUsbiTransfer=itransfer;
fLibusbTransfer=USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer);
fUSBDevice=device;
fCancelled=false;
}
USBTransfer::~USBTransfer()
{
}
struct usbi_transfer*
USBTransfer::UsbiTransfer()
{
return fUsbiTransfer;
}
void
USBTransfer::SetCancelled()
{
fCancelled=true;
}
bool
USBTransfer::IsCancelled()
{
return fCancelled;
}
void
USBTransfer::Do(int fRawFD)
{
switch(fLibusbTransfer->type)
{
case LIBUSB_TRANSFER_TYPE_CONTROL:
{
struct libusb_control_setup* setup=(struct libusb_control_setup*)fLibusbTransfer->buffer;
usb_raw_command command;
command.control.request_type=setup->bmRequestType;
command.control.request=setup->bRequest;
command.control.value=setup->wValue;
command.control.index=setup->wIndex;
command.control.length=setup->wLength;
command.control.data=fLibusbTransfer->buffer + LIBUSB_CONTROL_SETUP_SIZE;
if(fCancelled)
{
break;
}
if(ioctl(fRawFD,B_USB_RAW_COMMAND_CONTROL_TRANSFER,&command,
sizeof(command)) || command.control.status!=B_USB_RAW_STATUS_SUCCESS) {
fUsbiTransfer->transferred=-1;
usbi_err(TRANSFER_CTX(fLibusbTransfer),"failed control transfer");
break;
}
fUsbiTransfer->transferred=command.control.length;
}
break;
case LIBUSB_TRANSFER_TYPE_BULK:
case LIBUSB_TRANSFER_TYPE_INTERRUPT:
{
usb_raw_command command;
command.transfer.interface=fUSBDevice->EndpointToInterface(fLibusbTransfer->endpoint);
command.transfer.endpoint=fUSBDevice->EndpointToIndex(fLibusbTransfer->endpoint);
command.transfer.data=fLibusbTransfer->buffer;
command.transfer.length=fLibusbTransfer->length;
if(fCancelled)
{
break;
}
if(fLibusbTransfer->type==LIBUSB_TRANSFER_TYPE_BULK)
{
if(ioctl(fRawFD,B_USB_RAW_COMMAND_BULK_TRANSFER,&command,
sizeof(command)) || command.transfer.status!=B_USB_RAW_STATUS_SUCCESS) {
fUsbiTransfer->transferred=-1;
usbi_err(TRANSFER_CTX(fLibusbTransfer),"failed bulk transfer");
break;
}
}
else
{
if(ioctl(fRawFD,B_USB_RAW_COMMAND_INTERRUPT_TRANSFER,&command,
sizeof(command)) || command.transfer.status!=B_USB_RAW_STATUS_SUCCESS) {
fUsbiTransfer->transferred=-1;
usbi_err(TRANSFER_CTX(fLibusbTransfer),"failed interrupt transfer");
break;
}
}
fUsbiTransfer->transferred=command.transfer.length;
}
break;
// IsochronousTransfers not tested
case LIBUSB_TRANSFER_TYPE_ISOCHRONOUS:
{
usb_raw_command command;
command.isochronous.interface=fUSBDevice->EndpointToInterface(fLibusbTransfer->endpoint);
command.isochronous.endpoint=fUSBDevice->EndpointToIndex(fLibusbTransfer->endpoint);
command.isochronous.data=fLibusbTransfer->buffer;
command.isochronous.length=fLibusbTransfer->length;
command.isochronous.packet_count=fLibusbTransfer->num_iso_packets;
int i=0;
usb_iso_packet_descriptor *packetDescriptors = new usb_iso_packet_descriptor[fLibusbTransfer->num_iso_packets];
for (i=0; i<fLibusbTransfer->num_iso_packets; i++)
{
if((int16)(fLibusbTransfer->iso_packet_desc[i]).length!=(fLibusbTransfer->iso_packet_desc[i]).length)
{
fUsbiTransfer->transferred=-1;
usbi_err(TRANSFER_CTX(fLibusbTransfer),"failed isochronous transfer");
break;
}
packetDescriptors[i].request_length=(int16)(fLibusbTransfer->iso_packet_desc[i]).length;
}
if(i<fLibusbTransfer->num_iso_packets)
{
break; // TODO Handle this error
}
command.isochronous.packet_descriptors=packetDescriptors;
if(fCancelled)
{
break;
}
if(ioctl(fRawFD,B_USB_RAW_COMMAND_ISOCHRONOUS_TRANSFER,&command,
sizeof(command)) || command.isochronous.status!=B_USB_RAW_STATUS_SUCCESS) {
fUsbiTransfer->transferred=-1;
usbi_err(TRANSFER_CTX(fLibusbTransfer),"failed isochronous transfer");
break;
}
for (i=0; i<fLibusbTransfer->num_iso_packets; i++)
{
(fLibusbTransfer->iso_packet_desc[i]).actual_length=packetDescriptors[i].actual_length;
switch(packetDescriptors[i].status)
{
case B_OK: (fLibusbTransfer->iso_packet_desc[i]).status=LIBUSB_TRANSFER_COMPLETED;
break;
default: (fLibusbTransfer->iso_packet_desc[i]).status=LIBUSB_TRANSFER_ERROR;
break;
}
}
delete[] packetDescriptors;
// Do we put the length of transfer here, for isochronous transfers?
fUsbiTransfer->transferred=command.transfer.length;
}
break;
default:
usbi_err(TRANSFER_CTX(fLibusbTransfer),"Unknown type of transfer");
}
}
bool
USBDeviceHandle::InitCheck()
{
return fInitCheck;
}
status_t
USBDeviceHandle::TransfersThread(void* self)
{
USBDeviceHandle* handle = (USBDeviceHandle*)self;
handle->TransfersWorker();
return B_OK;
}
void
USBDeviceHandle::TransfersWorker()
{
while(true)
{
status_t status = acquire_sem(fTransfersSem);
if(status== B_BAD_SEM_ID)
break;
if(status == B_INTERRUPTED)
continue;
fTransfersLock.Lock();
USBTransfer* fPendingTransfer= (USBTransfer*) fTransfers.RemoveItem((int32)0);
fTransfersLock.Unlock();
fPendingTransfer->Do(fRawFD);
usbi_signal_transfer_completion(fPendingTransfer->UsbiTransfer());
}
}
status_t
USBDeviceHandle::SubmitTransfer(struct usbi_transfer* itransfer)
{
USBTransfer* transfer = new USBTransfer(itransfer,fUSBDevice);
*((USBTransfer**)usbi_transfer_get_os_priv(itransfer))=transfer;
BAutolock locker(fTransfersLock);
fTransfers.AddItem(transfer);
release_sem(fTransfersSem);
return LIBUSB_SUCCESS;
}
status_t
USBDeviceHandle::CancelTransfer(USBTransfer* transfer)
{
transfer->SetCancelled();
fTransfersLock.Lock();
bool removed = fTransfers.RemoveItem(transfer);
fTransfersLock.Unlock();
if(removed)
{
usbi_signal_transfer_completion(transfer->UsbiTransfer());
}
return LIBUSB_SUCCESS;
}
USBDeviceHandle::USBDeviceHandle(USBDevice* dev)
:
fTransfersThread(-1),
fUSBDevice(dev),
fClaimedInterfaces(0),
fInitCheck(false)
{
fRawFD=open(dev->Location(), O_RDWR | O_CLOEXEC);
if(fRawFD < 0)
{
usbi_err(NULL,"failed to open device");
return;
}
fTransfersSem = create_sem(0, "Transfers Queue Sem");
fTransfersThread = spawn_thread(TransfersThread,"Transfer Worker",B_NORMAL_PRIORITY, this);
resume_thread(fTransfersThread);
fInitCheck = true;
}
USBDeviceHandle::~USBDeviceHandle()
{
if(fRawFD>0)
close(fRawFD);
for(int i=0; i<32; i++)
{
if(fClaimedInterfaces&(1<<i))
ReleaseInterface(i);
}
delete_sem(fTransfersSem);
if(fTransfersThread>0)
wait_for_thread(fTransfersThread, NULL);
}
int
USBDeviceHandle::ClaimInterface(int inumber)
{
int status=fUSBDevice->ClaimInterface(inumber);
if(status==LIBUSB_SUCCESS)
{
fClaimedInterfaces|=(1<<inumber);
}
return status;
}
int
USBDeviceHandle::ReleaseInterface(int inumber)
{
fUSBDevice->ReleaseInterface(inumber);
fClaimedInterfaces&=(!(1<<inumber));
return LIBUSB_SUCCESS;
}
int
USBDeviceHandle::SetConfiguration(int config)
{
int config_index=fUSBDevice->CheckInterfacesFree(config);
if(config_index==LIBUSB_ERROR_BUSY || config_index==LIBUSB_ERROR_NOT_FOUND)
return config_index;
usb_raw_command command;
command.config.config_index=config_index;
if(ioctl(fRawFD,B_USB_RAW_COMMAND_SET_CONFIGURATION,&command,
sizeof(command)) || command.config.status != B_USB_RAW_STATUS_SUCCESS) {
return _errno_to_libusb(command.config.status);
}
fUSBDevice->SetActiveConfiguration(config_index);
return LIBUSB_SUCCESS;
}
int
USBDeviceHandle::SetAltSetting(int inumber, int alt)
{
usb_raw_command command;
command.alternate.config_index=fUSBDevice->ActiveConfigurationIndex();
command.alternate.interface_index=inumber;
if(ioctl(fRawFD,B_USB_RAW_COMMAND_GET_ACTIVE_ALT_INTERFACE_INDEX,&command,
sizeof(command)) || command.alternate.status!=B_USB_RAW_STATUS_SUCCESS) {
usbi_err(NULL,"Error retrieving active alternate interface");
return _errno_to_libusb(command.alternate.status);
}
if(command.alternate.alternate_info == alt)
{
usbi_dbg("Setting alternate interface successful");
return LIBUSB_SUCCESS;
}
command.alternate.alternate_info = alt;
if(ioctl(fRawFD,B_USB_RAW_COMMAND_SET_ALT_INTERFACE,&command, //IF IOCTL FAILS DEVICE DISONNECTED PROBABLY
sizeof(command)) || command.alternate.status!=B_USB_RAW_STATUS_SUCCESS) {
usbi_err(NULL,"Error setting alternate interface");
return _errno_to_libusb(command.alternate.status);
}
usbi_dbg("Setting alternate interface successful");
return LIBUSB_SUCCESS;
}
USBDevice::USBDevice(const char * path)
:
fPath(NULL),
fActiveConfiguration(0), //0?
fConfigurationDescriptors(NULL),
fClaimedInterfaces(0),
fEndpointToIndex(NULL),
fEndpointToInterface(NULL),
fInitCheck(false)
{
fPath=strdup(path);
Initialise();
}
USBDevice::~USBDevice()
{
free(fPath);
if (fConfigurationDescriptors)
{
for(int i=0;i<fDeviceDescriptor.num_configurations;i++)
{
if (fConfigurationDescriptors[i])
delete fConfigurationDescriptors[i];
}
delete[] fConfigurationDescriptors;
}
if (fEndpointToIndex)
delete[] fEndpointToIndex;
if (fEndpointToInterface)
delete[] fEndpointToInterface;
}
bool
USBDevice::InitCheck()
{
return fInitCheck;
}
const char*
USBDevice::Location() const
{
return fPath;
}
uint8
USBDevice::CountConfigurations() const
{
return fDeviceDescriptor.num_configurations;
}
const usb_device_descriptor*
USBDevice::Descriptor() const
{
return &fDeviceDescriptor;
}
const usb_configuration_descriptor*
USBDevice::ConfigurationDescriptor(uint32 index) const
{
if(index>CountConfigurations())
return NULL;
return (usb_configuration_descriptor*) fConfigurationDescriptors[index];
}
const usb_configuration_descriptor*
USBDevice::ActiveConfiguration() const
{
return (usb_configuration_descriptor*) fConfigurationDescriptors[fActiveConfiguration];
}
int
USBDevice::ActiveConfigurationIndex() const
{
return fActiveConfiguration;
}
int USBDevice::ClaimInterface(int interface)
{
if(interface>ActiveConfiguration()->number_interfaces)
{
return LIBUSB_ERROR_NOT_FOUND;
}
if((fClaimedInterfaces & (1<<interface)) !=0 )
return LIBUSB_ERROR_BUSY;
fClaimedInterfaces|=(1<<interface);
return LIBUSB_SUCCESS;
}
int USBDevice::ReleaseInterface(int interface)
{
fClaimedInterfaces&=(!(1<<interface));
return LIBUSB_SUCCESS;
}
int
USBDevice::CheckInterfacesFree(int config)
{
if(fConfigToIndex.count(config)==0)
return LIBUSB_ERROR_NOT_FOUND;
if(fClaimedInterfaces==0)
return fConfigToIndex[(uint8)config];
return LIBUSB_ERROR_BUSY;
}
int
USBDevice::SetActiveConfiguration(int config_index)
{
fActiveConfiguration=config_index;
return LIBUSB_SUCCESS;
}
uint8
USBDevice::EndpointToIndex(uint8 address) const
{
return fEndpointToIndex[fActiveConfiguration][address];
}
uint8
USBDevice::EndpointToInterface(uint8 address) const
{
return fEndpointToInterface[fActiveConfiguration][address];
}
int
USBDevice::Initialise() //Do we need more error checking, etc? How to report?
{
int fRawFD=open(fPath, O_RDWR | O_CLOEXEC);
if(fRawFD < 0)
return B_ERROR;
usb_raw_command command;
command.device.descriptor = &fDeviceDescriptor;
if(ioctl(fRawFD, B_USB_RAW_COMMAND_GET_DEVICE_DESCRIPTOR, &command,
sizeof(command)) || command.device.status != B_USB_RAW_STATUS_SUCCESS) {
close(fRawFD);
return B_ERROR;
}
size_t size;
fConfigurationDescriptors = new(std::nothrow) unsigned char*[fDeviceDescriptor.num_configurations];
fEndpointToIndex = new(std::nothrow) map<uint8,uint8> [fDeviceDescriptor.num_configurations];
fEndpointToInterface = new(std::nothrow) map<uint8,uint8> [fDeviceDescriptor.num_configurations];
for( int i=0; i<fDeviceDescriptor.num_configurations; i++)
{
size=0;
usb_configuration_descriptor tmp_config;
command.config.descriptor = &tmp_config;
command.config.config_index = i;
if(ioctl(fRawFD, B_USB_RAW_COMMAND_GET_CONFIGURATION_DESCRIPTOR, &command,
sizeof(command)) || command.config.status != B_USB_RAW_STATUS_SUCCESS) {
usbi_err(NULL,"failed retrieving configuration descriptor");
close(fRawFD);
return B_ERROR;
}
fConfigToIndex[tmp_config.configuration_value]=i;
fConfigurationDescriptors[i]=new(std::nothrow) unsigned char[tmp_config.total_length];
command.control.request_type=128;
command.control.request=6;
command.control.value=(2<<8)|i;
command.control.index=0;
command.control.length=tmp_config.total_length;
command.control.data=fConfigurationDescriptors[i];
if(ioctl(fRawFD,B_USB_RAW_COMMAND_CONTROL_TRANSFER,&command,
sizeof(command)) || command.control.status!=B_USB_RAW_STATUS_SUCCESS) {
usbi_err(NULL,"failed retrieving full configuration descriptor");
close(fRawFD);
return B_ERROR;
}
for( int j=0;j<tmp_config.number_interfaces;j++)
{
command.alternate.config_index=i;
command.alternate.interface_index=j;
if(ioctl(fRawFD,B_USB_RAW_COMMAND_GET_ALT_INTERFACE_COUNT, &command,
sizeof(command)) || command.config.status != B_USB_RAW_STATUS_SUCCESS) {
usbi_err(NULL,"failed retrieving number of alternate interfaces");
close(fRawFD);
return B_ERROR;
}
int num_alternate=command.alternate.alternate_info;
for( int k=0;k<num_alternate;k++)
{
usb_interface_descriptor tmp_interface;
command.interface_etc.config_index=i;
command.interface_etc.interface_index=j;
command.interface_etc.alternate_index=k;
command.interface_etc.descriptor=&tmp_interface;
if(ioctl(fRawFD,B_USB_RAW_COMMAND_GET_INTERFACE_DESCRIPTOR_ETC, &command,
sizeof(command)) || command.config.status != B_USB_RAW_STATUS_SUCCESS) {
usbi_err(NULL,"failed retrieving interface descriptor");
close(fRawFD);
return B_ERROR;
}
for( int l=0;l<tmp_interface.num_endpoints;l++)
{
usb_endpoint_descriptor tmp_endpoint;
command.endpoint_etc.config_index=i;
command.endpoint_etc.interface_index=j;
command.endpoint_etc.alternate_index=k;
command.endpoint_etc.endpoint_index=l;
command.endpoint_etc.descriptor=&tmp_endpoint;
if(ioctl(fRawFD,B_USB_RAW_COMMAND_GET_ENDPOINT_DESCRIPTOR_ETC, &command,
sizeof(command)) || command.config.status != B_USB_RAW_STATUS_SUCCESS) {
usbi_err(NULL,"failed retrieving endpoint descriptor");
close(fRawFD);
return B_ERROR;
}
fEndpointToIndex[i][tmp_endpoint.endpoint_address]=l;
fEndpointToInterface[i][tmp_endpoint.endpoint_address]=j;
}
}
}
}
close(fRawFD);
fInitCheck = true;
return B_OK;
}