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.
This commit is contained in:
Mark Liversedge
2011-10-26 02:29:10 +01:00
parent 85e9b8861e
commit 9b079f6c9b
9 changed files with 176 additions and 66 deletions

View File

@@ -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;

View File

@@ -58,13 +58,18 @@
#include <windows.h>
#include <winbase.h>
#include "USBXpress.h" // for Garmin USB1 sticks
#include "LibUsb.h" // for Garmin USB2 sticks
#else
#include <termios.h> // unix!!
#include <unistd.h> // unix!!
#include <sys/ioctl.h>
#endif
#if defined GC_HAVE_LIBUSB && (defined WIN32 || __linux__)
#include "LibUsb.h" // for Garmin USB2 sticks
#endif
#include <QDebug>
// 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

View File

@@ -275,7 +275,6 @@ void ANTChannel::broadcastEvent(unsigned char *ant_message)
checkCinqo();
} else {
//
// We got some telemetry on this channel
//

View File

@@ -16,17 +16,16 @@
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifdef WIN32
#if defined GC_HAVE_LIBUSB && (defined WIN32 || __linux__)
#include <QString>
#include <QDebug>
#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: "<<rc<< usb_strerror();
return rc;
}
readBufSize = rc;
int bytesToGo = bytes - bufRemain;
@@ -159,14 +184,26 @@ int LibUsb::read(char *buf, int bytes)
int LibUsb::write(char *buf, int bytes)
{
if (!isDllLoaded)
return -1;
#ifdef WIN32
if (!isDllLoaded) return -1;
#endif
// check it isn't closed
if (!device) return -1;
#ifdef WIN32
int rc = usb_interrupt_write(device, writeEndpoint, buf, bytes, 1000);
#else
//int rc=0;
//for (int i=0;rc >= 0 && i<bytes;i ++) // interrupt writes in pairs
//usb_interrupt_write(device, writeEndpoint, buf+i, 1, 1000);
int rc = usb_bulk_write(device, writeEndpoint, buf, bytes, 125);
#endif
if (rc < 0)
{
qDebug()<<"usb_interrupt_write Error writing: "<< usb_strerror();
// don't report timeouts - lots of noise
if (rc != -110) qDebug()<<"usb_interrupt_write Error writing: "<< usb_strerror();
}
return rc;
@@ -184,7 +221,7 @@ struct usb_dev_handle* LibUsb::OpenAntStick()
{
if (dev->descriptor.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:"<<QString("/dev/bus/usb/%1/%2").arg(bus->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

View File

@@ -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 <usb.h> // for the constants etc
#ifdef WIN32
#include <QLibrary> // 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

View File

@@ -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

View File

@@ -0,0 +1,2 @@
ATTRS{idVendor}=="0fcf", ATTRS{idProduct}=="1008", MODE="0666"

16
src/linux/README Normal file
View File

@@ -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.

View File

@@ -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