From 9b079f6c9b8972dda023075e4b5d6c51ce4c9529 Mon Sep 17 00:00:00 2001 From: Mark Liversedge Date: Wed, 26 Oct 2011 02:29:10 +0100 Subject: [PATCH] Support Garmin USB2 sticks on Linux Fixup support for USB2 on Linux via libusb. The code is pretty ghetto with a maze of pre-processor conditionals. But it works. Of special note: * You MUST build with a very specific version of libusb, the stock libs in Ubuntu do not work. Might pull the code into the main repo, since its pretty darned tiny. * On Linux access controls for the USB devices is controlled by udev, and requires a rule adding to ensure the devie can be read/written by users other than root. A udev rule file has been added to a 'linux' subdirectory in src. Some of the timeouts and usb setup/reset code has been adjusted to ensure the device is always in a consistent state. This appears to be more important on Linux than Windows for some reason. --- src/ANT.cpp | 36 ++++++++- src/ANT.h | 16 +++- src/ANTChannel.cpp | 1 - src/LibUsb.cpp | 141 ++++++++++++++++++++++------------ src/LibUsb.h | 12 ++- src/gcconfig.pri.in | 2 + src/linux/51-garmin-usb.rules | 2 + src/linux/README | 16 ++++ src/src.pro | 16 +++- 9 files changed, 176 insertions(+), 66 deletions(-) create mode 100644 src/linux/51-garmin-usb.rules create mode 100644 src/linux/README diff --git a/src/ANT.cpp b/src/ANT.cpp index 5ae14dcf2..84ba03f06 100644 --- a/src/ANT.cpp +++ b/src/ANT.cpp @@ -104,9 +104,9 @@ ANT::ANT(QObject *parent, DeviceConfiguration *devConf) : QThread(parent) connect(antChannel[i], SIGNAL(searchComplete(int)), this, SLOT(searchComplete(int))); } - // on windows we use libusb to read from USB2 + // on windows and linux we use libusb to read from USB2 // sticks, if it is not available we use stubs -#ifdef WIN32 +#if defined GC_HAVE_LIBUSB && (defined WIN32 || __linux__) usbMode = USBNone; usb2 = new LibUsb(); #endif @@ -114,7 +114,7 @@ ANT::ANT(QObject *parent, DeviceConfiguration *devConf) : QThread(parent) ANT::~ANT() { -#ifdef WIN32 +#if defined GC_HAVE_LIBUSB && (defined WIN32 || __linux__) delete usb2; #endif } @@ -183,7 +183,7 @@ void ANT::run() // read more bytes from the device uint8_t byte; if (rawRead(&byte, 1) > 0) receiveByte((unsigned char)byte); - else msleep(1); + else msleep(5); //---------------------------------------------------------------------- // LISTEN TO CONTROLLER FOR COMMANDS @@ -646,6 +646,13 @@ int ANT::closePort() break; } #else + +#if defined __linux__ && defined GC_HAVE_LIBUSB + if (usbMode == USB2) { + usb2->close(); + return 0; + } +#endif tcflush(devicePort, TCIOFLUSH); // clear out the garbage return close(devicePort); #endif @@ -677,6 +684,15 @@ int ANT::openPort() int ldisc=N_TTY; // LINUX #endif +#if defined __linux__&& defined GC_HAVE_LIBUSB + int rc; + if ((rc=usb2->open()) != -1) { + usbMode = USB2; + return rc; + } else { + usbMode = USB1; + } +#endif if ((devicePort=open(deviceFilename.toAscii(),O_RDWR | O_NOCTTY | O_NONBLOCK)) == -1) return errno; @@ -736,6 +752,13 @@ int ANT::rawWrite(uint8_t *bytes, int size) // unix!! return rc; #else + +#if defined __linux__ && defined GC_HAVE_LIBUSB + if (usbMode == USB2) { + return usb2->write((char *)bytes, size); + } +#endif + int ibytes; ioctl(devicePort, FIONREAD, &ibytes); @@ -771,6 +794,11 @@ int ANT::rawRead(uint8_t bytes[], int size) #else +#if defined __linux__ && defined GC_HAVE_LIBUSB + if (usbMode == USB2) { + return usb2->read((char *)bytes, size); + } +#endif int timeout=0, i=0; uint8_t byte; diff --git a/src/ANT.h b/src/ANT.h index 03e550e50..290f7bd71 100644 --- a/src/ANT.h +++ b/src/ANT.h @@ -58,13 +58,18 @@ #include #include #include "USBXpress.h" // for Garmin USB1 sticks -#include "LibUsb.h" // for Garmin USB2 sticks #else #include // unix!! #include // unix!! #include #endif +#if defined GC_HAVE_LIBUSB && (defined WIN32 || __linux__) +#include "LibUsb.h" // for Garmin USB2 sticks +#endif + +#include + // timeouts for read/write of serial port in ms #define ANT_READTIMEOUT 1000 #define ANT_WRITETIMEOUT 2000 @@ -324,14 +329,17 @@ private: #ifdef WIN32 HANDLE devicePort; // file descriptor for reading from com3 DCB deviceSettings; // serial port settings baud rate et al - LibUsb *usb2; // used for USB2 support - enum UsbMode { USBNone, USB1, USB2 }; - enum UsbMode usbMode; #else int devicePort; // unix!! struct termios deviceSettings; // unix!! #endif +#if defined GC_HAVE_LIBUSB && (defined WIN32 || __linux__) + LibUsb *usb2; // used for USB2 support + enum UsbMode { USBNone, USB1, USB2 }; + enum UsbMode usbMode; +#endif + // telemetry and state QStringList antIDs; #if 0 diff --git a/src/ANTChannel.cpp b/src/ANTChannel.cpp index dccba69b1..6a235cb73 100755 --- a/src/ANTChannel.cpp +++ b/src/ANTChannel.cpp @@ -275,7 +275,6 @@ void ANTChannel::broadcastEvent(unsigned char *ant_message) checkCinqo(); } else { - // // We got some telemetry on this channel // diff --git a/src/LibUsb.cpp b/src/LibUsb.cpp index a2df7782e..76963c1c7 100644 --- a/src/LibUsb.cpp +++ b/src/LibUsb.cpp @@ -16,17 +16,16 @@ * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#ifdef WIN32 +#if defined GC_HAVE_LIBUSB && (defined WIN32 || __linux__) #include #include #include "LibUsb.h" -#ifdef GC_HAVE_LIBUSB // only include if windows and have libusb installed - - LibUsb::LibUsb() { +// dynamix load of libusb on Windows, it is statically linked in Linux +#ifdef WIN32 QLibrary libusb0("libusb0"); /************************************************************************* @@ -54,14 +53,16 @@ LibUsb::LibUsb() return; } /************************************************************************/ +#endif intf = NULL; readBufIndex = 0; readBufSize = 0; - usb_set_debug(255); + usb_set_debug(0); +#ifdef WIN32 // Initialize the library. usb_init(); @@ -70,26 +71,41 @@ LibUsb::LibUsb() // Find all connected devices. usb_find_devices(); +#endif } int LibUsb::open() { - if (!isDllLoaded) - return -1; +#ifdef WIN32 + if (!isDllLoaded) return -1; +#else + // Initialize the library. + usb_init(); - // Search busses & devices for USB2 ANT+ stick - device = OpenAntStick(); + // Find all busses. + usb_find_busses(); - if (device == NULL) - return -1; + // Find all connected devices. + usb_find_devices(); +#endif - int rc = usb_clear_halt(device, writeEndpoint); - if (rc < 0) - qDebug()<<"usb_clear_halt writeEndpoint Error: "<< usb_strerror(); + readBufSize = 0; + readBufIndex = 0; + + // Search USB busses for USB2 ANT+ stick host controllers + device = OpenAntStick(); + + if (device == NULL) return -1; + + // reset the device, god only knows what mess we left it in... + int rc = usb_reset(device); + if (rc < 0) qDebug()<<"usb_reset Error: "<< usb_strerror(); + + rc = usb_clear_halt(device, writeEndpoint); + if (rc < 0) qDebug()<<"usb_clear_halt writeEndpoint Error: "<< usb_strerror(); rc = usb_clear_halt(device, readEndpoint); - if (rc < 0) - qDebug()<<"usb_clear_halt readEndpoint Error: "<< usb_strerror(); + if (rc < 0) qDebug()<<"usb_clear_halt readEndpoint Error: "<< usb_strerror(); return rc; } @@ -98,25 +114,32 @@ void LibUsb::close() { if (device) { - usb_release_interface(device, 0); - usb_close(device); - } + // stop any further write attempts whilst we close down + usb_dev_handle *p = device; + device = NULL; - device = NULL; + usb_release_interface(p, interface); + usb_close(p); + } } int LibUsb::read(char *buf, int bytes) { - if (!isDllLoaded) - return -1; + // check it isn't closed + if (!device) return -1; + +#ifdef WIN32 + if (!isDllLoaded) return -1; +#endif // The USB2 stick really doesn't like you reading 1 byte when more are available - // so we need a buffered reader + // so we need a buffered reader BUT ON WINDOWS ONLY appears to be a driver issue + // and not related to the underlying hardware int bufRemain = readBufSize - readBufIndex; // Can we entirely satisfy the request from the buffer? - if (bufRemain > bytes) + if (bufRemain >= bytes) { // Yes, so do it memcpy(buf, readBuf+readBufIndex, bytes); @@ -125,18 +148,20 @@ int LibUsb::read(char *buf, int bytes) } // No, so partially satisfy by emptying the buffer, then refill the buffer for the rest - memcpy(buf, readBuf+readBufIndex, bufRemain); + if (bufRemain) { + memcpy(buf, readBuf+readBufIndex, bufRemain); + } + readBufSize = 0; readBufIndex = 0; - int rc = usb_bulk_read(device, readEndpoint, readBuf, 64, 1000); - + int rc = usb_bulk_read(device, readEndpoint, readBuf, 64, 125); if (rc < 0) { - qDebug()<<"usb_bulk_read Error reading: "<< usb_strerror(); + // don't report timeouts - lots of noise so commented out + //qDebug()<<"usb_bulk_read Error reading: "<= 0 && idescriptor.idVendor == GARMIN_USB2_VID && dev->descriptor.idProduct == GARMIN_USB2_PID) { - qDebug() << "Found a Garmin USB2 ANT+ stick"; + //qDebug() << "Found a Garmin USB2 ANT+ stick"; if ((udev = usb_open(dev))) { @@ -193,14 +230,18 @@ struct usb_dev_handle* LibUsb::OpenAntStick() if ((intf = usb_find_interface(&dev->config[0])) != NULL) { int rc = usb_set_configuration(udev, 1); - if (rc < 0) + if (rc < 0) { qDebug()<<"usb_set_configuration Error: "<< usb_strerror(); - rc = usb_claim_interface(udev, 0); - if (rc < 0) - qDebug()<<"usb_claim_interface Error: "<< usb_strerror(); - rc = usb_set_altinterface(udev, 0); - if (rc < 0) - qDebug()<<"usb_set_altinterface Error: "<< usb_strerror(); +#ifdef __linux__ + qDebug()<<"check permissions on:"<dirname).arg(dev->filename); +#endif + } + rc = usb_claim_interface(udev, interface); + if (rc < 0) qDebug()<<"usb_claim_interface Error: "<< usb_strerror(); + + rc = usb_set_altinterface(udev, alternate); + if (rc < 0) qDebug()<<"usb_set_altinterface Error: "<< usb_strerror(); + return udev; } } @@ -219,20 +260,21 @@ struct usb_interface_descriptor* LibUsb::usb_find_interface(struct usb_config_de readEndpoint = -1; writeEndpoint = -1; + interface = -1; + alternate = -1; - if (!config_descriptor) - return NULL; + if (!config_descriptor) return NULL; - if (!config_descriptor->bNumInterfaces) - return NULL; + if (!config_descriptor->bNumInterfaces) return NULL; - if (!config_descriptor->interface[0].num_altsetting) - return NULL; + if (!config_descriptor->interface[0].num_altsetting) return NULL; intf = &config_descriptor->interface[0].altsetting[0]; - if (intf->bNumEndpoints != 2) - return NULL; + if (intf->bNumEndpoints != 2) return NULL; + + interface = intf->bInterfaceNumber; + alternate = intf->bAlternateSetting; for (int i = 0 ; i < 2; i++) { @@ -271,5 +313,4 @@ int LibUsb::write(char *, int) return -1; } -#endif // Have LIBUSB -#endif // WIN32 +#endif // Have LIBUSB and WIN32 or LINUX diff --git a/src/LibUsb.h b/src/LibUsb.h index 9c9c43704..a037c0d0d 100644 --- a/src/LibUsb.h +++ b/src/LibUsb.h @@ -19,10 +19,11 @@ #ifndef gc_LibUsb_h #define gc_LibUsb_h -#if defined WIN32 +#if defined GC_HAVE_LIBUSB && (defined WIN32 || defined __linux__) -#ifdef GC_HAVE_LIBUSB #include // for the constants etc + +#ifdef WIN32 #include // for dynamically loading libusb0.dll #endif @@ -38,8 +39,8 @@ public: int read(char *buf, int bytes); int write(char *buf, int bytes); private: -#ifdef GC_HAVE_LIBUSB +#ifdef WIN32 // we only do dynamic loading on Windows /************************************************************************* * Functions to load from libusb0.dll */ @@ -70,12 +71,16 @@ private: IntUsb_dev_handleIntProto usb_claim_interface; IntUsb_dev_handleIntProto usb_set_altinterface; /************************************************************************/ +#endif struct usb_dev_handle* OpenAntStick(); struct usb_interface_descriptor* usb_find_interface(struct usb_config_descriptor* config_descriptor); struct usb_dev_handle* device; struct usb_interface_descriptor* intf; + int readEndpoint, writeEndpoint; + int interface; + int alternate; char readBuf[64]; int readBufIndex; @@ -84,5 +89,4 @@ private: bool isDllLoaded; #endif }; -#endif // WIN32 #endif // gc_LibUsb_h diff --git a/src/gcconfig.pri.in b/src/gcconfig.pri.in index 2f1ae92ea..95b003ea1 100644 --- a/src/gcconfig.pri.in +++ b/src/gcconfig.pri.in @@ -90,6 +90,8 @@ D2XX_INCLUDE = /usr/local/include/D2XX # ** Garmin USB2 sticks whose driver uses libusb-win32. # You must make sure you install v0.1.12.2 of libusb-win from: # http://sourceforge.net/projects/libusb-win32/files/libusb-win32-releases/0.1.12.2/ +# or on Linux you MUST use 1.12.2 from: +# http://prdownloads.sourceforge.net/libusb/libusb-0.1.12.tar.gz #LIBUSB_INSTALL=/c/libusb-win32-device-bin-0.1.12.2 # if you want video playback on training mode then diff --git a/src/linux/51-garmin-usb.rules b/src/linux/51-garmin-usb.rules new file mode 100644 index 000000000..d9ea183ee --- /dev/null +++ b/src/linux/51-garmin-usb.rules @@ -0,0 +1,2 @@ +ATTRS{idVendor}=="0fcf", ATTRS{idProduct}=="1008", MODE="0666" + diff --git a/src/linux/README b/src/linux/README new file mode 100644 index 000000000..ea0bd03d2 --- /dev/null +++ b/src/linux/README @@ -0,0 +1,16 @@ +LINUX ACCESS PERMISSIONS FOR GARMIN USB2 STICK + +When using Garmin USB2 sticks on Linux the udev rules that +control access to the USB devices tend to mount the Garmin +stick with permissions that restrict access to normal users. + +To change the way permissions are granted you need to configure +udev with a rules file. + +On UBUNTU and other Debian based distros this is achieved by +writing a rules file and placing it in /etc/udev/rules.d. + +We have createed that file (51-garmin-usb.rules) here and +you will need to copy it into /dev/udev/rules.d for the +Garmin stick to be accessable from GoldenCheetah when run +as a normal (not root) user. diff --git a/src/src.pro b/src/src.pro index 4dbe584ac..7a1f8dee3 100644 --- a/src/src.pro +++ b/src/src.pro @@ -83,10 +83,20 @@ qwt3d { DEFINES += GC_HAVE_USBXPRESS } -# are we supporting USB2 devices on Windows? +# are we supporting USB2 devices !isEmpty( LIBUSB_INSTALL ) { INCLUDEPATH += $${LIBUSB_INSTALL}/include DEFINES += GC_HAVE_LIBUSB + SOURCES += LibUsb.cpp + HEADERS += LibUsb.h + + macx { + // not supported at present + } else { + unix { + LIBS += $${LIBUSB_INSTALL}/lib/libusb.a + } + } } # are we supporting video playback? @@ -123,8 +133,8 @@ win32 { RC_FILE = windowsico.rc # Windows only USB support - SOURCES += USBXpress.cpp LibUsb.cpp - HEADERS += USBXpress.h LibUsb.h + SOURCES += USBXpress.cpp + HEADERS += USBXpress.h } # local qxt widgets - rather than add another dependency on libqxt