You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
piklab/src/common/port/usb_port.cpp

412 lines
14 KiB

/***************************************************************************
* Copyright (C) 2005 Lorenz Mösenlechner & Matthias Kranz *
* <icd2linux@hcilab.org> *
* Copyright (C) 2003-2005 Orion Sky Lawlor <olawlor@acm.org> *
* Copyright (C) 2005-2007 Nicolas Hadacek <hadacek@kde.org> *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
***************************************************************************/
#include "usb_port.h"
#ifdef HAVE_USB
# include <usb.h>
#endif
#include <tqdatetime.h>
#include "common/common/version_data.h"
#include "common/common/number.h"
//-----------------------------------------------------------------------------
class Port::USB::Private
{
public:
#ifdef HAVE_USB
Private() : _interface(0) {}
const usb_interface_descriptor *_interface;
#endif
};
bool Port::USB::_initialized = false;
void Port::USB::initialize()
{
if (_initialized) return;
_initialized = true;
#ifdef HAVE_USB
usb_init();
VersionData vd = VersionData::fromString(LIBUSB_VERSION);
TQString s = TQString("libusb %1").arg(vd.pretty());
if ( vd<VersionData(0, 1, 8) ) tqWarning("%s: may be too old (you need at least version 0.1.8)", s.latin1());
#endif
}
bool Port::USB::isAvailable()
{
#ifdef HAVE_USB
return true;
#else
return false;
#endif
}
usb_bus *Port::USB::getBusses()
{
initialize();
#ifdef HAVE_USB
// refresh libusb structures
usb_find_busses();
usb_find_devices();
return usb_get_busses();
#else
return 0;
#endif
}
bool Port::USB::findBulk(const struct usb_device *dev)
{
initialize();
#ifdef HAVE_USB
int configuration = -1, interface = -1, altsetting = -1, bulk_endpoint = -1;
// walk through the possible configs, etc.
tqDebug("This device has %d possible configuration(s).", dev->descriptor.bNumConfigurations);
for (int c=0; c<dev->descriptor.bNumConfigurations; c++) {
tqDebug("Looking at configuration %d...This configuration has %d interfaces.", c, dev->config[c].bNumInterfaces);
for(int i=0; i<dev->config[c].bNumInterfaces; i++) {
tqDebug(" Looking at interface %d...This interface has %d altsettings.", i, dev->config[c].interface[i].num_altsetting);
for (int a=0; a < dev->config[c].interface[i].num_altsetting; a++) {
tqDebug(" Looking at altsetting %d...This altsetting has %d endpoints.", a, dev->config[c].interface[i].altsetting[a].bNumEndpoints);
for (int e=0; e < dev->config[c].interface[i].altsetting[a].bNumEndpoints; e++) {
TQString s;
s.sprintf(" Endpoint %d: Address %02xh, attributes %02xh ", e, dev->config[c].interface[i].altsetting[a].endpoint[e].bEndpointAddress, dev->config[c].interface[i].altsetting[a].endpoint[e].bmAttributes);
uchar attribs = (dev->config[c].interface[i].altsetting[a].endpoint[e].bmAttributes & 3);
switch (attribs) {
case 0: s += "(Control) "; break;
case 1: s += "(Isochronous) "; break;
case 2: s += "(Bulk) ";
/* Found the correct configuration, interface etc... it has bulk endpoints! */
configuration=c;
interface=i;
altsetting=a;
break;
case 3: s += "(Interrupt) "; break;
default: s += "ERROR! Got an illegal value in endpoint bmAttributes";
}
if ( attribs!=0 ) {
uchar dir = (dev->config[c].interface[i].altsetting[a].endpoint[e].bEndpointAddress & 0x80);
switch (dir) {
case 0x00: s += "(Out)";
// Do nothing in this case
break;
case 0x80: s += "(In)";
if ((dev->config[c].interface[i].altsetting[a].endpoint[e].bmAttributes & 0x03) == 2) /* Make sure it's a *bulk* endpoint */ {
// Found the correct endpoint to use for bulk transfer; use its ADDRESS
bulk_endpoint=dev->config[c].interface[i].altsetting[a].endpoint[e].bEndpointAddress;
}
break;
default: s += "ERROR! Got an illegal value in endpoint bEndpointAddress";
}
}
tqDebug("%s", s.latin1());
}
}
}
}
if (bulk_endpoint<0) {
tqDebug("No valid interface found!");
return false;
}
#endif
return true;
}
TQStringList Port::USB::probedDeviceList()
{
initialize();
TQStringList list;
#ifdef HAVE_USB
usb_init(); // needed ?
for (usb_bus *bus=getBusses(); bus; bus=bus->next) {
for (struct usb_device *dev=bus->devices; dev; dev=dev->next) {
if ( dev->descriptor.idVendor==0 ) continue; // controller
list.append(TQString("Vendor Id: %1 - Product Id: %2")
.arg(toLabel(NumberBase::Hex, dev->descriptor.idVendor, 4)).arg(toLabel(NumberBase::Hex, dev->descriptor.idProduct, 4)));
}
}
#endif
return list;
}
struct usb_device *Port::USB::findDevice(uint vendorId, uint productId)
{
initialize();
#ifdef HAVE_USB
for (usb_bus *bus=getBusses(); bus; bus=bus->next) {
for (struct usb_device *dev=bus->devices; dev; dev=dev->next) {
if ( dev->descriptor.idVendor==vendorId && dev->descriptor.idProduct==productId )
return dev;
}
}
#else
Q_UNUSED(vendorId); Q_UNUSED(productId);
tqDebug("USB support disabled");
#endif
return 0;
}
//-----------------------------------------------------------------------------
const char * const Port::USB::ENDPOINT_MODE_NAMES[Nb_EndpointModes] = {
"bulk", "interrupt", "control", "isochronous"
};
Port::USB::USB(Log::Base &base, uint vendorId, uint productId, uint config, uint interface)
: Base(base), _vendorId(vendorId), _productId(productId),
_config(config), _interface(interface), _handle(0), _device(0)
{
Q_ASSERT( config>=1 );
_private = new Private;
initialize();
}
Port::USB::~USB()
{
close();
delete _private;
}
void Port::USB::setSystemError(const TQString &message)
{
#ifdef HAVE_USB
log(Log::LineType::Error, message + TQString(" (err=%1).").arg(usb_strerror()));
#else
log(Log::LineType::Error, message);
#endif
}
void Port::USB::tryToDetachDriver()
{
// try to detach an already existing driver... (linux only)
#if defined(LIBUSB_HAS_GET_DRIVER_NP) && LIBUSB_HAS_GET_DRIVER_NP
log(Log::DebugLevel::Extra, "find if there is already an installed driver");
char dname[256] = "";
if ( usb_get_driver_np(_handle, _interface, dname, 255)<0 ) return;
log(Log::DebugLevel::Normal, TQString(" a driver \"%1\" is already installed...").arg(dname));
# if defined(LIBUSB_HAS_DETACH_KERNEL_DRIVER_NP) && LIBUSB_HAS_DETACH_KERNEL_DRIVER_NP
usb_detach_kernel_driver_np(_handle, _interface);
log(Log::DebugLevel::Normal, " try to detach it...");
if ( usb_get_driver_np(_handle, _interface, dname, 255)<0 ) return;
log(Log::DebugLevel::Normal, " failed to detach it");
# endif
#endif
}
bool Port::USB::internalOpen()
{
#ifdef HAVE_USB
_device = findDevice(_vendorId, _productId);
if ( _device==0 ) {
log(Log::LineType::Error, i18n("Could not find USB device (vendor=%1 product=%2).")
.arg(toLabel(NumberBase::Hex, _vendorId, 4)).arg(toLabel(NumberBase::Hex, _productId, 4)));
return false;
}
log(Log::DebugLevel::Extra, TQString("found USB device as \"%1\" on bus \"%2\"").arg(_device->filename).arg(_device->bus->dirname));
_handle = usb_open(_device);
if ( _handle==0 ) {
setSystemError(i18n("Error opening USB device."));
return false;
}
// windows: usb_reset takes about 7-10 seconds to re-enumerate the device...
// BSD : not implemented in libusb...
# if !defined(Q_OS_BSD4) && !defined(Q_OS_WIN)
if ( usb_reset(_handle)<0 ) {
setSystemError(i18n("Error resetting USB device."));
return false;
}
# endif
usb_close(_handle);
_handle = usb_open(_device);
if ( _handle==0 ) {
setSystemError(i18n("Error opening USB device."));
return false;
}
tryToDetachDriver();
uint i = 0;
for (; i<_device->descriptor.bNumConfigurations; i++)
if ( _config==_device->config[i].bConfigurationValue ) break;
if ( i==_device->descriptor.bNumConfigurations ) {
uint old = _config;
i = 0;
_config = _device->config[i].bConfigurationValue;
log(Log::LineType::Warning, i18n("Configuration %1 not present: using %2").arg(old).arg(_config));
}
const usb_config_descriptor &configd = _device->config[i];
if ( usb_set_configuration(_handle, _config)<0 ) {
setSystemError(i18n("Error setting USB configuration %1.").arg(_config));
return false;
}
for (i=0; i<configd.bNumInterfaces; i++)
if ( _interface==configd.interface[i].altsetting[0].bInterfaceNumber ) break;
if ( i==configd.bNumInterfaces ) {
uint old = _interface;
i = 0;
_interface = configd.interface[i].altsetting[0].bInterfaceNumber;
log(Log::LineType::Warning, i18n("Interface %1 not present: using %2").arg(old).arg(_interface));
}
_private->_interface = &(configd.interface[i].altsetting[0]);
if ( usb_claim_interface(_handle, _interface)<0 ) {
setSystemError(i18n("Could not claim USB interface %1").arg(_interface));
return false;
}
log(Log::DebugLevel::Max, TQString("alternate setting is %1").arg(_private->_interface->bAlternateSetting));
log(Log::DebugLevel::Max, TQString("USB bcdDevice: %1").arg(toHexLabel(_device->descriptor.bcdDevice, 4)));
return true;
#else
log(Log::LineType::Error, i18n("USB support disabled"));
return false;
#endif
}
void Port::USB::internalClose()
{
if ( _handle==0 ) return;
#ifdef HAVE_USB
usb_release_interface(_handle, _interface);
usb_close(_handle);
_private->_interface = 0;
#endif
_device = 0;
_handle = 0;
}
bool Port::USB::sendControlMessage(const ControlMessageData &data)
{
if ( hasError() ) return false;
#ifdef HAVE_USB
TQString s = data.bytes;
uint length = strlen(data.bytes) / 2;
TQByteArray ba(length);
for (uint i=0; i<length; i++)
ba[i] = fromString(NumberBase::Hex, s.mid(2*i, 2), 0);
int res = usb_control_msg(_handle, data.type, data.request, data.value, 0, ba.data(), length, 1000); // 1s
if ( res<0 ) {
setSystemError(i18n("Error sending control message to USB port."));
return false;
}
#endif
return true;
}
uint timeout(uint size)
{
return qMax(size*5, uint(1000)); // 5ms per byte or 1s
}
Port::USB::EndpointMode Port::USB::endpointMode(uint ep) const
{
#ifdef HAVE_USB
uint index = ep & USB_ENDPOINT_ADDRESS_MASK;
Q_ASSERT(_private->_interface);
const usb_endpoint_descriptor *ued = _private->_interface->endpoint + index;
Q_ASSERT(ued);
switch (ued->bmAttributes & USB_ENDPOINT_TYPE_MASK) {
case USB_ENDPOINT_TYPE_BULK: return Bulk;
case USB_ENDPOINT_TYPE_INTERRUPT: return Interrupt;
case USB_ENDPOINT_TYPE_ISOCHRONOUS: return Isochronous;
case USB_ENDPOINT_TYPE_CONTROL: return Control;
default: break;
}
#endif
Q_ASSERT(false);
return Nb_EndpointModes;
}
Port::IODir Port::USB::endpointDir(uint ep) const
{
#ifdef HAVE_USB
switch (ep & USB_ENDPOINT_DIR_MASK) {
case USB_ENDPOINT_IN: return In;
case USB_ENDPOINT_OUT: return Out;
default: break;
}
#endif
Q_ASSERT(false);
return NoIO;
}
bool Port::USB::write(uint ep, const char *data, uint size)
{
if ( hasError() ) return false;
#ifdef HAVE_USB
IODir dir = endpointDir(ep);
EndpointMode mode = endpointMode(ep);
log(Log::DebugLevel::LowLevel, TQString("write to endpoint %1 (%2 - %3) %4 chars: \"%5\"")
.arg(toHexLabel(ep, 2)).arg(ENDPOINT_MODE_NAMES[mode]).arg(IO_DIR_NAMES[dir]).arg(size).arg(toPrintable(data, size, PrintEscapeAll)));
Q_ASSERT( dir==Out );
TQTime time;
time.start();
int todo = size;
for (;;) {
int res = 0;
//tqDebug("write ep=%i todo=%i/%i", ep, todo, size);
if ( mode==Interrupt ) res = usb_interrupt_write(_handle, ep, (char *)data + size - todo, todo, timeout(todo));
else res = usb_bulk_write(_handle, ep, (char *)data + size - todo, todo, timeout(todo));
//tqDebug("res: %i", res);
if ( res==todo ) break;
if ( uint(time.elapsed())>3000 ) { // 3 s
if ( res<0 ) setSystemError(i18n("Error sending data (ep=%1 res=%2)").arg(toHexLabel(ep, 2)).arg(res));
else log(Log::LineType::Error, i18n("Timeout: only some data sent (%1/%2 bytes).").arg(size-todo).arg(size));
return false;
}
if ( res==0 ) log(Log::DebugLevel::Normal, i18n("Nothing sent: retrying..."));
if ( res>0 ) todo -= res;
msleep(100);
}
#else
Q_UNUSED(ep); Q_UNUSED(data); Q_UNUSED(size);
#endif
return true;
}
bool Port::USB::read(uint ep, char *data, uint size, bool *poll)
{
if ( hasError() ) return false;
#ifdef HAVE_USB
IODir dir = endpointDir(ep);
EndpointMode mode = endpointMode(ep);
log(Log::DebugLevel::LowLevel, TQString("read from endpoint %1 (%2 - %3) %4 chars")
.arg(toHexLabel(ep, 2)).arg(ENDPOINT_MODE_NAMES[mode]).arg(IO_DIR_NAMES[dir]).arg(size));
Q_ASSERT( dir==In );
TQTime time;
time.start();
int todo = size;
for (;;) {
int res = 0;
//tqDebug("read ep=%i size=%i", ep, todo);
if ( mode==Interrupt ) res = usb_interrupt_read(_handle, ep, data + size - todo, todo, timeout(todo));
else res = usb_bulk_read(_handle, ep, data + size - todo, todo, timeout(todo));
//tqDebug("res: %i", res);
if ( res==todo ) break;
if ( uint(time.elapsed())>3000 ) { // 3 s: seems to help icd2 in some case (?)
if ( res<0 ) setSystemError(i18n("Error receiving data (ep=%1 res=%2)").arg(toHexLabel(ep, 2)).arg(res));
else log(Log::LineType::Error, i18n("Timeout: only some data received (%1/%2 bytes).").arg(size-todo).arg(size));
return false;
}
if ( res==0 ) {
if (poll) {
*poll = false;
return true;
} else log(Log::DebugLevel::Normal, i18n("Nothing received: retrying..."));
}
if ( res>0 ) todo -= res;
msleep(100);
}
if (poll) *poll = true;
#else
Q_UNUSED(ep); Q_UNUSED(data); Q_UNUSED(size);
#endif
return true;
}