diff options
Diffstat (limited to 'src/common/port')
-rw-r--r-- | src/common/port/Makefile.am | 7 | ||||
-rw-r--r-- | src/common/port/parallel.cpp | 247 | ||||
-rw-r--r-- | src/common/port/parallel.h | 70 | ||||
-rw-r--r-- | src/common/port/port.cpp | 98 | ||||
-rw-r--r-- | src/common/port/port.h | 56 | ||||
-rw-r--r-- | src/common/port/port.pro | 10 | ||||
-rw-r--r-- | src/common/port/port_base.cpp | 127 | ||||
-rw-r--r-- | src/common/port/port_base.h | 59 | ||||
-rw-r--r-- | src/common/port/serial.cpp | 523 | ||||
-rw-r--r-- | src/common/port/serial.h | 113 | ||||
-rw-r--r-- | src/common/port/usb_port.cpp | 411 | ||||
-rw-r--r-- | src/common/port/usb_port.h | 70 |
12 files changed, 1791 insertions, 0 deletions
diff --git a/src/common/port/Makefile.am b/src/common/port/Makefile.am new file mode 100644 index 0000000..d1d2ce8 --- /dev/null +++ b/src/common/port/Makefile.am @@ -0,0 +1,7 @@ +INCLUDES = -I$(top_srcdir)/src $(all_includes) +METASOURCES = AUTO + +noinst_LTLIBRARIES = libport.la +libport_la_SOURCES = parallel.cpp port.cpp serial.cpp usb_port.cpp \ + port_base.cpp +libport_la_LDFLAGS = $(all_libraries) diff --git a/src/common/port/parallel.cpp b/src/common/port/parallel.cpp new file mode 100644 index 0000000..22b6a4c --- /dev/null +++ b/src/common/port/parallel.cpp @@ -0,0 +1,247 @@ +/*************************************************************************** + * Copyright (C) 2005-2007 Nicolas Hadacek <hadacek@kde.org> * + * Copyright (C) 2003-2004 Alain Gibaud <alain.gibaud@free.fr> * + * Copyright (C) 2002-2003 Stephen Landamore <stephen@landamore.com> * + * * + * 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 "parallel.h" + +#include "common/global/global.h" +#if defined(HAVE_PPDEV) +# include <linux/ppdev.h> +# include <linux/parport.h> +# include <fcntl.h> +# include <sys/ioctl.h> +# include <unistd.h> // needed on some system +# include <errno.h> +#elif defined(HAVE_PPBUS) +# include <sys/param.h> +# include <machine/cpufunc.h> +# include <dev/ppbus/ppi.h> +# include <dev/ppbus/ppbconf.h> +# include <fcntl.h> +# include <unistd.h> +# include <errno.h> +#endif +#include "common/common/misc.h" + +//----------------------------------------------------------------------------- +QStringList *Port::Parallel::_list = 0; + +Port::IODirs Port::Parallel::probe(const QString &device) +{ +#if defined(HAVE_PPDEV) + int fd = ::open(device.latin1(), O_RDWR); + if ( fd<0) return NoIO; + if ( ioctl(fd, PPCLAIM)<0 ) { + ::close(fd) ; + return In; + } + ioctl(fd, PPRELEASE); + ::close(fd); + return (In | Out); +#elif defined(HAVE_PPBUS) + int fd = ::open(device.latin1(), O_RDWR); + if ( fd<0 ) return NoIO; + ::close(fd); + return In | Out; // #### can we detect read-only ? +#else + return NoIO; +#endif +} + +QStringList Port::Parallel::deviceList() +{ + QStringList list; +#if defined(HAVE_PPDEV) + // standard parport in user space + for(int i = 0; i<8; ++i) list.append(QString("/dev/parport%1").arg(i)); + // new devfs parport flavour + for(int i = 0; i<8; ++i) list.append(QString("/dev/parports/%1").arg(i)); +#elif defined(HAVE_PPBUS) + // FreeBSD + for(int i = 0; i<8; ++i) list.append(QString("/dev/ppi%1").arg(i)); +#endif + return list; +} + +const QStringList &Port::Parallel::probedDeviceList() +{ + if ( _list==0 ) { + QStringList all = deviceList(); + _list = new QStringList; + for (uint i=0; i<uint(all.count()); i++) + if( probe(all[i]) & (In | Out) ) _list->append(all[i]); + } + return *_list; +} + +bool Port::Parallel::isAvailable() +{ +#if defined(HAVE_PPDEV) || defined(HAVE_PPBUS) + return true; +#else + return false; +#endif +} + +//----------------------------------------------------------------------------- +const Port::Parallel::PPinData Port::Parallel::PIN_DATA[Nb_Pins] = { + { Control, 0x01, Out, "/DS" }, // !strobe + { Data, 0x01, Out, "D0" }, // data 0 + { Data, 0x02, Out, "D1" }, // data 1 + { Data, 0x04, Out, "D2" }, // data 2 + { Data, 0x08, Out, "D3" }, // data 3 + { Data, 0x10, Out, "D4" }, // data 4 + { Data, 0x20, Out, "D5" }, // data 5 + { Data, 0x40, Out, "D6" }, // data 6 + { Data, 0x80, Out, "D7" }, // data 7 + { Status, 0x40, In, "/ACK" }, // !ack + { Status, 0x80, In, "BUSY" }, // busy + { Status, 0x20, In, "PAPER" }, // pout + { Status, 0x10, In, "SELin" }, // select + { Control, 0x02, Out, "LF" }, // !feed + { Status, 0x08, In, "/ERROR" }, // !error + { Control, 0x04, Out, "PRIME" }, // !init + { Control, 0x08, Out, "SELout" }, // !si + { Nb_RequestTypes, 0x00, NoIO, "GND" }, // GND + { Nb_RequestTypes, 0x00, NoIO, "GND" }, // GND + { Nb_RequestTypes, 0x00, NoIO, "GND" }, // GND + { Nb_RequestTypes, 0x00, NoIO, "GND" }, // GND + { Nb_RequestTypes, 0x00, NoIO, "GND" }, // GND + { Nb_RequestTypes, 0x00, NoIO, "GND" }, // GND + { Nb_RequestTypes, 0x00, NoIO, "GND" }, // GND + { Nb_RequestTypes, 0x00, NoIO, "GND" } // GND +}; + +QValueVector<Port::PinData> Port::Parallel::pinData(IODir dir) const +{ + QValueVector<PinData> v; + for (uint i=0; i<Nb_Pins; i++) { + if ( PIN_DATA[i].dir!=dir ) continue; + PinData pd = { i, PIN_DATA[i].label }; + v.append(pd); + } + return v; +} + +bool Port::Parallel::isGroundPin(uint pin) const +{ + return ( PIN_DATA[pin].label==QString("GND") ); +} + +Port::IODir Port::Parallel::ioDir(uint pin) const +{ + return PIN_DATA[pin].dir; +} + +const Port::Parallel::RequestData Port::Parallel::REQUEST_DATA[Nb_RequestTypes] = { +#if defined(HAVE_PPDEV) + { PPRCONTROL, PPWCONTROL }, + { PPRSTATUS, 0 }, + { PPRDATA, PPWDATA } +#elif defined(HAVE_PPBUS) + { PPIGCTRL, PPISCTRL }, + { PPIGSTATUS, 0 }, + { PPIGDATA, PPISDATA } +#else + { 0, 0 }, + { 0, 0 }, + { 0, 0 } +#endif +}; + +Port::Parallel::Parallel(const QString &device, Log::Base &base) + : Base(base), _fd(-1), _device(device) +{ + for (uint i=0; i<Nb_RequestTypes; i++) _images[i] = 0; +} + +bool Port::Parallel::internalOpen() +{ +#if defined(HAVE_PPDEV) + _fd = ::open(_device.latin1(), O_RDWR); + if ( _fd<0 ) { + setSystemError(i18n("Could not open device \"%1\"").arg(_device)); + return false; + } + if ( ioctl(_fd, PPCLAIM)<0 ) { + setSystemError(i18n("Could not claim device \"%1\": check it is read/write enabled").arg(_device)); + return false; + } +#elif defined(HAVE_PPBUS) + _fd = ::open(_device.latin1(), O_RDWR); + if ( _fd<0 ) { + setSystemError(i18n("Could not open device \"%1\"").arg(_device)); + return false; + } +#endif + return true; +} + +void Port::Parallel::internalClose() +{ + if ( _fd<0 ) return; +#if defined(HAVE_PPDEV) + ioctl(_fd, PPRELEASE); + ::close(_fd); +#elif defined(HAVE_PPBUS) + ::close(_fd); +#endif + _fd = -1; +} + +bool Port::Parallel::setPinOn(uint pin, bool on, LogicType type) +{ + if ( _fd<0 ) return false; +#if defined(HAVE_PPDEV) || defined(HAVE_PPBUS) + Q_ASSERT( pin<Nb_Pins ); + Q_ASSERT( PIN_DATA[pin].dir==Out ); + RequestType rtype = PIN_DATA[pin].rType; + Q_ASSERT( rtype!=Nb_RequestTypes ); + uchar mask = PIN_DATA[pin].mask; + uchar c = (XOR(type==NegativeLogic, on) ? _images[rtype] | mask : _images[rtype] & ~mask); + int request = REQUEST_DATA[rtype].write; + Q_ASSERT( request!=0 ); + if ( ioctl(_fd, request, &c)<0 ) { + setSystemError(i18n("Error setting pin %1 to %2").arg(PIN_DATA[pin].label).arg(on)); + return false; + } + _images[rtype] = c; +#endif + return true; +} + +bool Port::Parallel::readPin(uint pin, LogicType type, bool &value) +{ + if ( _fd<0 ) return false; +#if defined(HAVE_PPDEV) || defined(HAVE_PPBUS) + Q_ASSERT( pin<Nb_Pins ); + Q_ASSERT( PIN_DATA[pin].dir==In ); + RequestType rtype = PIN_DATA[pin].rType; + Q_ASSERT( rtype!=Nb_RequestTypes ); + int request = REQUEST_DATA[rtype].read; + Q_ASSERT( request!=0 ); + uchar c = 0; + if ( ioctl(_fd, request, &c)<0 ) { + setSystemError(i18n("Error reading bit on pin %1").arg(PIN_DATA[pin].label)); + return false; + } + _images[rtype] = c; + value = (type==NegativeLogic ? ~c : c ) & PIN_DATA[pin].mask; +#endif + return true; +} + +void Port::Parallel::setSystemError(const QString &message) +{ +#if defined(HAVE_PPDEV) || defined(HAVE_PPBUS) + log(Log::LineType::Error, message + QString(" (errno=%1).").arg(strerror(errno))); +#else + Q_UNUSED(message); +#endif +} diff --git a/src/common/port/parallel.h b/src/common/port/parallel.h new file mode 100644 index 0000000..1473db0 --- /dev/null +++ b/src/common/port/parallel.h @@ -0,0 +1,70 @@ +/*************************************************************************** + * Copyright (C) 2005 Nicolas Hadacek <hadacek@kde.org> * + * Copyright (C) 2003-2004 Alain Gibaud <alain.gibaud@free.fr> * + * Copyright (C) 2002-2003 Stephen Landamore <stephen@landamore.com> * + * * + * 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. * + ***************************************************************************/ +#ifndef PARALLEL_H +#define PARALLEL_H + +#include "port_base.h" +#if defined(Q_OS_WIN) +# undef ERROR +#endif + +namespace Port +{ + +class Parallel : public Base +{ +public: + Parallel(const QString &device, Log::Base &base); + virtual ~Parallel() { close(); } + virtual Description description() const { return Description(PortType::Parallel, _device); } + + static IODirs probe(const QString &device); + static const QStringList &probedDeviceList(); + static bool isAvailable(); + + enum Pin { DS = 0, D0, D1, D2, D3, D4, D5, D6, D7, ACK, BUSY, PAPER, SELin, + LF, ERROR, PRIME, SELout, P18, P19, P20, P21, P22, P23, P24, P25, + Nb_Pins }; + enum RequestType { Control = 0, Status, Data, Nb_RequestTypes }; + struct PPinData { + RequestType rType; + uchar mask; + IODir dir; + const char *label; + }; + static const PPinData PIN_DATA[Nb_Pins]; + virtual bool setPinOn(uint pin, bool on, LogicType type); + virtual bool readPin(uint pin, LogicType type, bool &value); + virtual QValueVector<PinData> pinData(IODir dir) const; + virtual bool isGroundPin(uint pin) const; + virtual uint groundPin() const { return P25; } + virtual IODir ioDir(uint pin) const; + +private: + int _fd; + QString _device; + struct RequestData { + int read, write; + }; + static const RequestData REQUEST_DATA[Nb_RequestTypes]; + uchar _images[Nb_RequestTypes]; + + static QStringList *_list; + static QStringList deviceList(); + + virtual bool internalOpen(); + virtual void internalClose(); + virtual void setSystemError(const QString &message); +}; + +} // namespace + +#endif diff --git a/src/common/port/port.cpp b/src/common/port/port.cpp new file mode 100644 index 0000000..c56c120 --- /dev/null +++ b/src/common/port/port.cpp @@ -0,0 +1,98 @@ +/*************************************************************************** + * Copyright (C) 2005-2006 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 "port.h" + +#if defined(Q_WS_WIN) +# include <sys/timeb.h> +#else +# include <sys/time.h> +#endif + +#include "common/global/global.h" +#include "serial.h" +#include "parallel.h" +#include "usb_port.h" + +//----------------------------------------------------------------------------- +void getTime(int &sec, int &usec) +{ +#if defined (Q_OS_WIN) + struct _timeb tb; + _ftime (&tb); + sec = tb.time; + usec = tb.millitm * 1000 + 500; +#else + struct timeval tv; + gettimeofday(&tv, 0); + usec = tv.tv_usec; + sec = tv.tv_sec; +#endif +} + +void Port::msleep(uint ms) +{ + usleep(ms*1000); +} + +// from Brian C Lane's code +// works better than usleep +void Port::usleep(uint us) +{ + if ( us==0 ) return; + int tsec, tusec; + getTime(tsec, tusec); + int usec = (tusec + us) % 1000000; + int sec = tsec + (tusec + us) / 1000000; + for (;;) { + getTime(tsec, tusec); + if ( tsec>sec ) return; + if ( tsec==sec && tusec>usec ) return; + } +} + +//----------------------------------------------------------------------------- +const PortType::Data PortType::DATA[Nb_Types] = { + { I18N_NOOP("Serial Port"), "serial", true }, + { I18N_NOOP("Parallel Port"), "parallel", true }, + { I18N_NOOP("USB Port"), "usb", false } +}; + +const char * const Port::IO_DIR_NAMES[3] = { "no_io", "in", "out" }; + +QStringList Port::probedDeviceList(PortType type) +{ + if ( !isAvailable(type) ) return QStringList(); + switch (type.type()) { + case PortType::Serial: return Serial::probedDeviceList(); + case PortType::Parallel: return Parallel::probedDeviceList(); + case PortType::USB: return USB::probedDeviceList(); + case PortType::Nb_Types: break; + } + return QStringList(); +} + +bool Port::isAvailable(PortType type) +{ + switch (type.type()) { + case PortType::Serial: return Serial::isAvailable(); + case PortType::Parallel: return Parallel::isAvailable(); + case PortType::USB: return USB::isAvailable(); + case PortType::Nb_Types: break; + } + return false; +} + +PortType Port::findType(const QString &portDevice) +{ + FOR_EACH(PortType, type) { + if ( !type.data().withDevice ) continue; + if ( probedDeviceList(type).contains(portDevice) ) return type; + } + return PortType::Nb_Types; +} diff --git a/src/common/port/port.h b/src/common/port/port.h new file mode 100644 index 0000000..f385deb --- /dev/null +++ b/src/common/port/port.h @@ -0,0 +1,56 @@ +/*************************************************************************** + * Copyright (C) 2005-2006 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. * + ***************************************************************************/ +#ifndef PORT_H +#define PORT_H + +#include <qstringlist.h> + +#include "common/global/global.h" +#include "common/common/key_enum.h" + +//----------------------------------------------------------------------------- +struct PortTypeData { + const char *label, *key; + bool withDevice; +}; + +BEGIN_DECLARE_ENUM(PortType) + Serial = 0, Parallel, USB +END_DECLARE_ENUM(PortType, PortTypeData) + +namespace Port +{ +//----------------------------------------------------------------------------- +extern void msleep(uint ms); // in milliseconds +extern void usleep(uint us); // in microseconds + +//----------------------------------------------------------------------------- + class Description { + public: + Description() : type(PortType::Nb_Types) {} + Description(PortType ptype, const QString &pdevice) : type(ptype), device(pdevice) {} + PortType type; + QString device; + }; + + enum IODir { NoIO = 0, In = 1, Out = 2 }; + extern const char * const IO_DIR_NAMES[3]; + Q_DECLARE_FLAGS(IODirs, IODir) + Q_DECLARE_OPERATORS_FOR_FLAGS(IODirs) + extern QStringList probedDeviceList(PortType type); + extern PortType findType(const QString &device); + extern bool isAvailable(PortType type); + struct PinData { + uint pin; + const char *label; + }; + enum LogicType { PositiveLogic, NegativeLogic }; +} // namespace + +#endif diff --git a/src/common/port/port.pro b/src/common/port/port.pro new file mode 100644 index 0000000..f09414b --- /dev/null +++ b/src/common/port/port.pro @@ -0,0 +1,10 @@ +STOPDIR = ../../.. +include($${STOPDIR}/lib.pro) + +TARGET = port +HEADERS += port.h port_base.h parallel.h serial.h usb_port.h +SOURCES += port.cpp port_base.cpp parallel.cpp serial.cpp usb_port.cpp +contains(DEFINES, HAVE_USB) { + win32:INCLUDEPATH += $${LIBUSB_PATH}\include +} + diff --git a/src/common/port/port_base.cpp b/src/common/port/port_base.cpp new file mode 100644 index 0000000..7528eeb --- /dev/null +++ b/src/common/port/port_base.cpp @@ -0,0 +1,127 @@ +/*************************************************************************** + * Copyright (C) 2005-2006 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 "port_base.h" + +#include "common/global/global.h" +#include "common/common/misc.h" +#include "common/common/number.h" + +bool Port::Base::open() +{ + close(); + resetError(); + _closing = false; + return internalOpen(); +} + +void Port::Base::close() +{ + _closing = true; + internalClose(); +} + +bool Port::Base::send(const char *data, uint size, uint timeout) +{ + log(Log::DebugLevel::LowLevel, QString("Sending: \"%1\"").arg(toPrintable(data, size, PrintAlphaNum))); + return internalSend(data, size, timeout); +} + +bool Port::Base::receive(uint size, QString &s, uint timeout) +{ + QMemArray<uchar> a; + if ( !receive(size, a, timeout) ) return false; + s.fill(0, size); + for (uint i=0; i<size; i++) s[i] = a[i]; + return true; +} + +bool Port::Base::receive(uint size, QMemArray<uchar> &a, uint timeout) +{ + a.resize(size); + bool ok = internalReceive(size, (char *)a.data(), timeout); + if (ok) log(Log::DebugLevel::LowLevel, QString("Received: \"%1\"").arg(toPrintable(a, PrintAlphaNum))); + return ok; +} + +bool Port::Base::receiveChar(char &c, uint timeout) +{ + if ( !internalReceive(1, &c, timeout) ) return false; + log(Log::DebugLevel::LowLevel, QString("Received: \"%1\"").arg(toPrintable(c, PrintAlphaNum))); + return true; +} + +bool Port::Base::setPinOn(uint, bool, LogicType) +{ + qFatal("setPinOn not implemented"); + return false; +} +bool Port::Base::readPin(uint, LogicType, bool &) +{ + qFatal("readPin not implemented"); + return 0; +} +QValueVector<Port::PinData> Port::Base::pinData(IODir) const +{ + qFatal("pinData not implemented"); + return QValueVector<PinData>(); +} +bool Port::Base::isGroundPin(uint) const +{ + qFatal("isGroundPin not implemented"); + return false; +} +uint Port::Base::groundPin() const +{ + qFatal("groundPin not implemented"); + return 0; +} +Port::IODir Port::Base::ioDir(uint) const +{ + qFatal("ioType not implemented"); + return NoIO; +} + +void Port::Base::log(Log::LineType lineType, const QString &message) +{ + if ( lineType==Log::LineType::Error && _closing ) return; + Log::Base::log(lineType, description().type.label() + ": " + message); + if ( lineType==Log::LineType::Error ) close(); +} + +void Port::Base::log(Log::DebugLevel level, const QString &message) +{ + Log::Base::log(level, description().type.label() + ": " + message); +} + +void Port::Base::logData(const QString &) +{ +/* + QString vs; + for (uint i=0; i<s.length(); i++) { + char c = s[i]; + switch (c) { + case '\r': vs += "\\r"; break; + case '\n': vs += "\\n"; break; + case '<': vs += "<"; break; + case '>': vs += ">"; break; + default: { + if ( c>=32 && c<=126 ) vs += c; + else { + QString tmp; + tmp.sprintf("\\x%02x", c); + vs += tmp; + } + break; + } + } + } + qDebug("%s", vs.latin1()); +*/ + //log(Log::Debug, vs); +} diff --git a/src/common/port/port_base.h b/src/common/port/port_base.h new file mode 100644 index 0000000..3ae4787 --- /dev/null +++ b/src/common/port/port_base.h @@ -0,0 +1,59 @@ +/*************************************************************************** + * Copyright (C) 2005-2006 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. * + ***************************************************************************/ +#ifndef PORT_BASE_H +#define PORT_BASE_H + +#include "port.h" +#include "common/global/log.h" + +namespace Port +{ + +class Base : public Log::Base +{ +public: + Base(Log::Base &base) : Log::Base(&base) {} + virtual PortType type() const { return description().type; } + virtual Description description() const = 0; + bool open(); + void close(); + + virtual void log(Log::LineType kind, const QString &message); + virtual void log(Log::DebugLevel level, const QString &message); + void logData(const QString &s); + + enum { DEFAULT_TIMEOUT = 3000 }; // 3s + bool sendChar(char c, uint timeout = DEFAULT_TIMEOUT) { return send(&c, 1, timeout); } + bool send(const char *data, uint size, uint timeout = DEFAULT_TIMEOUT); + bool send(const QMemArray<uchar> &a, uint timeout = DEFAULT_TIMEOUT) { return send((const char *)a.data(), a.count(), timeout); } + bool receiveChar(char &c, uint timeout = DEFAULT_TIMEOUT); + bool receive(uint size, QString &s, uint timeout = DEFAULT_TIMEOUT); + bool receive(uint size, QMemArray<uchar> &a, uint timeout = DEFAULT_TIMEOUT); + + virtual bool setPinOn(uint pin, bool on, LogicType type); + virtual bool readPin(uint pin, LogicType type, bool &value); + virtual QValueVector<PinData> pinData(IODir dir) const; + virtual bool isGroundPin(uint pin) const; + virtual uint groundPin() const; + virtual IODir ioDir(uint pin) const; + +protected: + virtual bool internalOpen() = 0; + virtual void internalClose() = 0; + virtual bool internalSend(const char *, uint, uint) { qFatal("Not implemented"); return false; } + virtual bool internalReceive(uint, char *, uint) { qFatal("Not implemented"); return false; } + virtual void setSystemError(const QString &message) = 0; + +private: + bool _closing; +}; + +} // namespace + +#endif diff --git a/src/common/port/serial.cpp b/src/common/port/serial.cpp new file mode 100644 index 0000000..b370d22 --- /dev/null +++ b/src/common/port/serial.cpp @@ -0,0 +1,523 @@ +/*************************************************************************** + * Copyright (C) 2005-2006 Nicolas Hadacek <hadacek@kde.org> * + * Copyright (C) 2003-2004 Alain Gibaud <alain.gibaud@free.fr> * + * Copyright (C) 2002-2003 Stephen Landamore <stephen@landamore.com> * + * * + * 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 "serial.h" + +#ifdef Q_OS_UNIX +# include <stdio.h> +# include <fcntl.h> +# include <sys/time.h> +# include <sys/types.h> +# include <sys/stat.h> +# include <sys/ioctl.h> +# include <errno.h> +# include <unistd.h> // needed on some system +#endif +#include <qdatetime.h> + +//----------------------------------------------------------------------------- +QStringList *Port::Serial::_list = 0; +#if defined(Q_OS_UNIX) +const Port::Serial::Handle Port::Serial::INVALID_HANDLE = -1; +#elif defined(Q_OS_WIN) +const Port::Serial::Handle Port::Serial::INVALID_HANDLE = INVALID_HANDLE_VALUE; +#endif + +Port::Serial::Handle Port::Serial::openHandle(const QString &device, IODirs dirs) +{ +#if defined(Q_OS_UNIX) + // open non blocking: avoid missing DCD (comment from xwisp2) + int mode = O_NOCTTY | O_NONBLOCK; + if ( dirs & In ) { + if ( dirs & Out ) mode |= O_RDWR; + else mode |= O_RDONLY; + } else mode |= O_WRONLY; + return ::open(device.latin1(), mode); +#elif defined(Q_OS_WIN) + int mode = 0; + if ( dirs & In ) mode |= GENERIC_READ; + if ( dirs & Out ) mode |= GENERIC_WRITE; + return CreateFileA(device.latin1(), mode, 0, NULL, OPEN_EXISTING, FILE_FLAG_NO_BUFFERING, NULL); +#endif +} + +void Port::Serial::closeHandle(Handle handle) +{ +#if defined(Q_OS_UNIX) + ::close(handle); +#elif defined(Q_OS_WIN) + CloseHandle(handle); +#endif +} + +Port::IODirs Port::Serial::probe(const QString &device) +{ + Handle handle = openHandle(device, In); + if ( handle==INVALID_HANDLE ) return NoIO; + closeHandle(handle); + handle = openHandle(device, In | Out); + if ( handle==INVALID_HANDLE ) return In; + closeHandle(handle); + return (In | Out); +} + +QStringList Port::Serial::deviceList() +{ + QStringList list; +#if defined(Q_OS_UNIX) + // standard serport in user space + for (uint i=0; i<8; i++) list.append(QString("/dev/ttyS%1").arg(i)); + // new devfs serport flavour + for (uint i=0; i<8; i++) list.append(QString("/dev/tts/%1").arg(i)); + // standard USB serport in user space + for (uint i=0; i<8; i++) list.append(QString("/dev/ttyUSB%1").arg(i)); + // new devfs USB serport flavour + for (uint i=0; i<8; i++) list.append(QString("/dev/usb/tts/%1").arg(i)); + // FreeBSD + for (uint i=0; i<8; i++) list.append(QString("/dev/ttyd%1").arg(i)); + // Slackware 11 devfs USB Serial port support. + for (uint i=0; i<8; i++) list.append(QString("/dev/tts/USB%1").arg(i)); + // MacOSX devfs USB Serial port support. + list.append("/dev/tty.usbserial"); +#elif defined(Q_OS_WIN) + for (uint i=1; i<10; i++) list.append(QString("COM%1").arg(i)); +#endif + return list; +} + +const QStringList &Port::Serial::probedDeviceList() +{ + if ( _list==0 ) { + QStringList all = deviceList(); + _list = new QStringList; + for (uint i=0; i<uint(all.count()); i++) + if( probe(all[i]) & (In | Out) ) _list->append(all[i]); + } + return *_list; +} + +//----------------------------------------------------------------------------- +const uint Port::Serial::SPEED_VALUES[Nb_Speeds] = { + 0, 50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800, 9600, 19200, 38400, + 57600, 115200 +}; +const Port::Serial::SpeedData Port::Serial::SPEED_DATA[Nb_Speeds] = { +#if defined(Q_OS_UNIX) + { true, B0 }, { true, B50 }, { true, B75 }, { true, B110 }, + { true, B134 }, { true, B150 }, { true, B200 }, { true, B300 }, + { true, B600 }, { true, B1200 }, { true, B1800 }, { true, B2400 }, + { true, B4800 }, { true, B9600 }, { true, B19200 }, { true, B38400 }, + { true, B57600 }, { true, B115200 } +#elif defined(Q_OS_WIN) + { false, 0 }, { false, 0 }, { false, 0 }, { true, CBR_110 }, + { false, 0 }, { false, 0 }, { false, 0 }, { true, CBR_300 }, + { true, CBR_600 }, { true, CBR_1200 }, { false, 0 }, { true, CBR_2400 }, + { true, CBR_4800 }, { true, CBR_9600 }, { true, CBR_19200 }, { true, CBR_38400 }, + { true, CBR_57600 }, { true, CBR_115200 } +#endif +}; + +const Port::Serial::SPinData Port::Serial::PIN_DATA[Nb_Pins] = { + { In, "DCD" }, { In, "RX" }, { Out, "TX" }, { Out, "DTR" }, + { NoIO, "GND" }, { In, "DSR" }, { Out, "RTS" }, { In, "CTS" }, + { Out, "RI" } +}; + +QValueVector<Port::PinData> Port::Serial::pinData(IODir dir) const +{ + QValueVector<PinData> v; + for (uint i=0; i<Nb_Pins; i++) { + if ( PIN_DATA[i].dir!=dir ) continue; + PinData pd = { i, PIN_DATA[i].label }; + v.append(pd); + } + return v; +} +bool Port::Serial::isGroundPin(uint pin) const +{ + return ( PIN_DATA[pin].label==QString("GND") ); +} + +Port::IODir Port::Serial::ioDir(uint pin) const +{ + return PIN_DATA[pin].dir; +} + +Port::Serial::Serial(const QString &device, Properties properties, Log::Base &base) + : Base(base), _device(device), _properties(properties), _fd(INVALID_HANDLE) +{} + +bool Port::Serial::getParameters(Parameters ¶meters) +{ + if ( _fd==INVALID_HANDLE ) return false; +#if defined(Q_OS_UNIX) + if ( tcgetattr(_fd, ¶meters)<0 ) { +#elif defined(Q_OS_WIN) + if ( !GetCommState(_fd, ¶meters.dcb) || !GetCommTimeouts(_fd, ¶meters.comtmo) ) { +#endif + setSystemError(i18n("Could not get file descriptor parameters")); + return false; + } + return true; +} + +bool Port::Serial::setParameters(const Parameters ¶meters) +{ + if ( _fd==INVALID_HANDLE ) return false; +#if defined(Q_OS_UNIX) + if ( tcsetattr(_fd, TCSANOW, ¶meters)<0 ) { +#elif defined(Q_OS_WIN) + Parameters tmp = parameters; + if ( !SetCommState(_fd, &tmp.dcb) || !SetCommTimeouts(_fd, &tmp.comtmo) ) { +#endif + setSystemError(i18n("Could not set file descriptor parameters")); + return false; + } + return true; +} + +bool Port::Serial::internalOpen() +{ + _fd = openHandle(_device, In | Out); + if ( _fd==INVALID_HANDLE ) { + setSystemError(i18n("Could not open device \"%1\" read-write").arg(_device)); + return false; + } + if ( !getParameters(_oldParameters) ) return false; // save configuration +#if defined(Q_OS_UNIX) + if ( _properties & Blocking ) { + int flags = fcntl(_fd, F_GETFL, 0); + if ( fcntl(_fd, F_SETFL, flags & ~O_NONBLOCK)<0 ) { + setSystemError(i18n("Could not modify file descriptor flags")); + return false; + } + } +#endif + return flush(DEFAULT_TIMEOUT); +} + +void Port::Serial::internalClose() +{ + if ( _fd==INVALID_HANDLE ) return; + if ( _properties & NeedFlush ) flush(0); + if ( _properties & NeedBreak ) doBreak(1); + setParameters(_oldParameters); + closeHandle(_fd); + _fd = INVALID_HANDLE; +} + +bool Port::Serial::internalSend(const char *data, uint size, uint timeout) +{ + if ( _fd==INVALID_HANDLE ) return false; + QTime time; + time.start(); + for (uint todo=size; todo!=0; ) { +#if defined(Q_OS_UNIX) + int res = write(_fd, data+size-todo, todo); + if ( res<0 && errno!=EAGAIN ) { +#elif defined(Q_OS_WIN) + DWORD res = 0; + if ( WriteFile(_fd, data+size-todo, todo, &res, NULL)==0 ) { +#endif + setSystemError(i18n("Error sending data")); + return false; + } + if ( res>0 ) todo -= res; + else { + if ( uint(time.elapsed())>timeout ) { + log(Log::LineType::Error, i18n("Timeout sending data (%1/%2 bytes sent).").arg(size-todo).arg(size)); + return false; + } + msleep(1); + } + } + if ( (_properties & NeedDrain) && !drain(timeout) ) return false; + return true; +} + +bool Port::Serial::internalReceive(uint size, char *data, uint timeout) +{ + if ( _fd==INVALID_HANDLE ) return false; + QTime time; + time.start(); + for(uint todo=size; todo!=0; ) { +#if defined(Q_OS_UNIX) + // this help reduce CPU usage. It also prevents blocking if the serial cable is disconnected + fd_set rfd; + FD_ZERO(&rfd); + FD_SET(_fd, &rfd); + struct timeval tv; + tv.tv_sec = timeout / 1000; + tv.tv_usec = (timeout%1000)*1000; + int res = select(_fd+1, &rfd, 0, 0, &tv); + if ( res<0 ) { + setSystemError(i18n("Error receiving data")); + return false; + } + if ( res==0 ) { + log(Log::LineType::Error, i18n("Timeout waiting for data.")); + return false; + } + res = read(_fd, data+size-todo, todo); + if ( res<0 && errno!=EAGAIN ) { +#elif defined(Q_OS_WIN) + DWORD res = 0; + if ( ReadFile(_fd, data+size-todo, todo, &res, NULL)==0 ) { +#endif + setSystemError(i18n("Error receiving data")); + return false; + } + if ( res>0 ) todo -= res; + else { + if ( uint(time.elapsed())>timeout ) { + log(Log::LineType::Error, i18n("Timeout receiving data (%1/%2 bytes received).").arg(size-todo).arg(size)); + return false; + } + msleep(1); + } + } + return true; +} + +bool Port::Serial::drain(uint timeout) +{ + if ( _fd==INVALID_HANDLE ) return false; +#if defined(Q_OS_UNIX) + // tcdrain will block if the serial cable is disconnected + // so we first check for data in output buffer... + QTime time; + time.start(); + for (;;) { + int nb; + if ( ioctl(_fd, TIOCOUTQ, &nb)==-1 ) { + setSystemError(i18n("Error checking for data in output buffer")); + return false; + } + if ( nb==0 ) break; + if ( uint(time.elapsed())>timeout ) { + _fd = INVALID_HANDLE; // otherwise close blocks... + log(Log::LineType::Error, i18n("Timeout sending data (%1 bytes left).").arg(nb)); + return false; + } + } + if ( tcdrain(_fd)<0 ) { + setSystemError(i18n("Error while draining")); + return false; + } +#endif + return true; +} + +bool Port::Serial::flush(uint timeout) +{ + if ( _fd==INVALID_HANDLE ) return false; + if ( (_properties & NeedDrain) && !drain(timeout) ) return false; +#if defined(Q_OS_UNIX) + if ( tcflush(_fd, TCIFLUSH)<0 ) { +#elif defined(Q_OS_WIN) + if ( FlushFileBuffers(_fd)==0 || PurgeComm(_fd, PURGE_TXABORT)==0 + || PurgeComm(_fd, PURGE_RXABORT)==0 || PurgeComm(_fd, PURGE_TXCLEAR)==0 + || PurgeComm(_fd, PURGE_RXCLEAR)==0 ) { +#endif + setSystemError(i18n("Could not flush device")); + return false; + } + return true; +} + +bool Port::Serial::internalSetPinOn(Pin pin, bool on) +{ +#if defined(Q_OS_UNIX) + int bit = 0; + switch (pin) { + case TX: return ( ioctl(_fd, on ? TIOCSBRK : TIOCCBRK, 0)>=0 ); + case DTR: bit = TIOCM_DTR; break; + case RTS: bit = TIOCM_RTS; break; + case RI: bit = TIOCM_RI; break; + default: Q_ASSERT(false); return false; + } + return ( ioctl(_fd, on ? TIOCMBIS : TIOCMBIC, &bit)>=0 ); +#elif defined(Q_OS_WIN) + DWORD func = 0; + switch (pin) { + case TX: func = (on ? SETBREAK : CLRBREAK); break; + case DTR: func = (on ? SETDTR : CLRDTR); break; + case RTS: func = (on ? SETRTS : CLRRTS); break; + case RI: // #### not possible with Win32 API ?? + default: Q_ASSERT(false); return false; + } + return ( EscapeCommFunction(_fd, func)!=0 ); +#endif +} + +bool Port::Serial::setPinOn(uint pin, bool on, LogicType type) +{ + if ( _fd==INVALID_HANDLE ) return false; + if ( type==NegativeLogic ) on = !on; + Q_ASSERT( pin<Nb_Pins ); + Q_ASSERT( PIN_DATA[pin].dir==Out ); + if ( !internalSetPinOn(Pin(pin), on) ) { + setSystemError(i18n("Error setting bit %1 of serial port to %2").arg(PIN_DATA[pin].label).arg(on)); + return false; + } + return true; +} + +bool Port::Serial::internalReadPin(Pin pin, LogicType type, bool &value) +{ +#if defined(Q_OS_UNIX) + int bits; + if ( ioctl(_fd, TIOCMGET, &bits)<0 ) return false; + int mask = 0; + switch (pin) { + case DCD: mask = TIOCM_CD; break; + case RX : mask = TIOCM_SR; break; + case DSR: mask = TIOCM_DSR; break; + case CTS: mask = TIOCM_CTS; break; + default: Q_ASSERT(false); return false; + } + value = ((type==NegativeLogic ? ~bits : bits) & mask); + return true; +#elif defined(Q_OS_WIN) + DWORD status; + if ( GetCommModemStatus(_fd, &status)==0 ) return false; + switch (pin) { + case DCD: value = (status & MS_RLSD_ON); break; + case DSR: value = (status & MS_DSR_ON); break; + case CTS: value = (status & MS_CTS_ON); break; + case RX: // not possible with Win32 API ?? + default: Q_ASSERT(false); return false; + } + if ( type==NegativeLogic) value = !value; + return true; +#endif +} + + bool Port::Serial::readPin(uint pin, LogicType type, bool &value) +{ + if ( _fd==INVALID_HANDLE ) return false; + Q_ASSERT( pin<Nb_Pins ); + Q_ASSERT( PIN_DATA[pin].dir==In ); + if ( !internalReadPin(Pin(pin), type, value) ) { + setSystemError(i18n("Error reading serial pin %1").arg(PIN_DATA[pin].label)); + return false; + } + return true; +} + +bool Port::Serial::setMode(InputFlags inputFlags, ControlFlags controlFlags, Speed speed, uint readTimeout) +{ + Q_ASSERT (SPEED_DATA[speed].supported ); + if ( !flush(0) ) return false; + Parameters parameters; + if ( !getParameters(parameters) ) return false; +#if defined(Q_OS_UNIX) + cfsetispeed(¶meters, SPEED_DATA[speed].flag); + cfsetospeed(¶meters, SPEED_DATA[speed].flag); + parameters.c_cflag &= ~(CSIZE|PARENB|PARODD|HUPCL|CSTOPB); + int cflags = 0; + if ( controlFlags & ByteSize8 ) cflags |= CS8; + if ( controlFlags & HardwareFlowControl ) cflags |= CRTSCTS; + if ( controlFlags & EnableReceiver ) cflags |= CREAD; + if ( controlFlags & IgnoreControlLines ) cflags |= CLOCAL; + parameters.c_cflag |= cflags; + parameters.c_iflag &= ~(ISTRIP|INPCK|IGNCR|INLCR|ICRNL|IXOFF|IXON); + int iflags = 0; + if ( inputFlags & IgnoreBreak ) iflags |= IGNBRK; + if ( inputFlags & IgnoreParity ) iflags |= IGNPAR; + parameters.c_iflag |= iflags; + parameters.c_oflag &= ~OPOST; + parameters.c_lflag &= ~(ICANON|ECHO|ECHONL|ISIG|IEXTEN|TOSTOP); + parameters.c_cc[VMIN] = 0; // wait for 1 char or timeout + parameters.c_cc[VTIME] = readTimeout/100; // wait in deciseconds +#elif defined(Q_OS_WIN) + if ( controlFlags & EnableReceiver ) ; // #### not sure what to do + if ( controlFlags & IgnoreControlLines ) ; // #### not sure what to do + setHardwareFlowControl(parameters, (controlFlags & HardwareFlowControl)); + if ( inputFlags & IgnoreParity ) parameters.dcb.Parity = NOPARITY; + parameters.dcb.StopBits = ONESTOPBIT; + if ( controlFlags & ByteSize8 ) parameters.dcb.ByteSize = 8; + parameters.dcb.BaudRate = SPEED_DATA[speed].flag; + parameters.comtmo.ReadIntervalTimeout = MAXDWORD; + parameters.comtmo.ReadTotalTimeoutMultiplier = MAXDWORD; + parameters.comtmo.ReadTotalTimeoutConstant = readTimeout; + parameters.comtmo.WriteTotalTimeoutMultiplier = 1; + parameters.comtmo.WriteTotalTimeoutConstant = readTimeout; +#endif + return setParameters(parameters); + // need flush ?? +} + +bool Port::Serial::doBreak(uint duration) +{ + if ( !flush(0) ) return false; +#if defined(Q_OS_UNIX) + if ( ioctl(_fd, TIOCSBRK)<0 ) { +#elif defined(Q_OS_WIN) + if ( SetCommBreak(_fd)==0 ) { +#endif + setSystemError(i18n("Error setting up break")); + return false; + } + msleep(duration); +#if defined(Q_OS_UNIX) + if ( ioctl(_fd, TIOCCBRK) ) { +#elif defined(Q_OS_WIN) + if ( ClearCommBreak(_fd)==0 ) { +#endif + setSystemError(i18n("Error clearing up break")); + return false; + } + msleep(1); + return flush(0); +} + +void Port::Serial::setHardwareFlowControl(Parameters ¶meters, bool on) +{ +#if defined(Q_OS_UNIX) + if (on) parameters.c_cflag |= CRTSCTS; + else parameters.c_cflag &= ~CRTSCTS; + parameters.c_cc[VMIN] = 0; // #### needed ? + parameters.c_cc[VTIME] = 0; // #### needed ? +#elif defined(Q_OS_WIN) + // #### not sure about that + parameters.dcb.fParity = on; + parameters.dcb.fOutxCtsFlow = on; + parameters.dcb.fOutxDsrFlow = on; + parameters.dcb.fDtrControl = (on ? DTR_CONTROL_ENABLE : DTR_CONTROL_DISABLE); + parameters.dcb.fDsrSensitivity = on; + parameters.dcb.fTXContinueOnXoff = on; + parameters.dcb.fOutX = on; + parameters.dcb.fInX = on; + parameters.dcb.fNull = on; + parameters.dcb.fRtsControl = (on ? RTS_CONTROL_ENABLE : RTS_CONTROL_DISABLE); + parameters.dcb.fAbortOnError = on; +#endif +} + +bool Port::Serial::setHardwareFlowControl(bool on) +{ + Parameters parameters; + if ( !getParameters(parameters) ) return false; + setHardwareFlowControl(parameters, on); + return setParameters(parameters); +} + +void Port::Serial::setSystemError(const QString &message) +{ +#if defined(Q_OS_UNIX) + log(Log::LineType::Error, message + QString(" (errno=%1)").arg(strerror(errno))); +#elif defined(Q_OS_WIN) + LPVOID lpMsgBuf; + FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&lpMsgBuf, 0, NULL); + log(Log::LineType::Error, message + QString(" (last error=%1 %2)").arg(GetLastError()).arg((const char *)(LPCTSTR)lpMsgBuf)); + LocalFree(lpMsgBuf); +#endif +} diff --git a/src/common/port/serial.h b/src/common/port/serial.h new file mode 100644 index 0000000..b2911e6 --- /dev/null +++ b/src/common/port/serial.h @@ -0,0 +1,113 @@ +/*************************************************************************** + * Copyright (C) 2005 Nicolas Hadacek <hadacek@kde.org> * + * Copyright (C) 2003-2004 Alain Gibaud <alain.gibaud@free.fr> * + * Copyright (C) 2002-2003 Stephen Landamore <stephen@landamore.com> * + * * + * 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. * + ***************************************************************************/ +#ifndef SERIAL_H +#define SERIAL_H + +#include "common/global/global.h" +#ifdef Q_OS_UNIX +# include <termios.h> +#elif defined(Q_OS_WIN) +# include <io.h> +#endif +#include "port_base.h" + +namespace Port +{ + +class Serial : public Base +{ +public: + enum Speed { S0 = 0, S50, S75, S110, S134, S150, S200, S300, S600, S1200, + S1800, S2400, S4800, S9600, S19200, S38400, S57600, S115200, + Nb_Speeds }; + static const uint SPEED_VALUES[Nb_Speeds]; + struct SpeedData { + bool supported; + uint flag; + }; + static const SpeedData SPEED_DATA[Nb_Speeds]; + + enum Property { NoProperty = 0, NeedDrain = 1, NeedFlush = 2, NeedBreak = 4, + Blocking = 8 }; + Q_DECLARE_FLAGS(Properties, Property) + + Serial(const QString &device, Properties properties, Log::Base &base); + virtual ~Serial() { close(); } + virtual Description description() const { return Description(PortType::Serial, _device); } + + static const QStringList &probedDeviceList(); + static IODirs probe(const QString &device); + static bool isAvailable() { return true; } + + enum InputFlag { NoInputFlag = 0, IgnoreBreak = 1, IgnoreParity = 2 }; + Q_DECLARE_FLAGS(InputFlags, InputFlag) + enum ControlFlag { NoControlFlag = 0, ByteSize8 = 1, HardwareFlowControl = 2, + EnableReceiver = 4, IgnoreControlLines = 8 }; + Q_DECLARE_FLAGS(ControlFlags, ControlFlag) + bool setMode(InputFlags inputFlags, ControlFlags controlFlags, Speed speed, uint readTimeout); // in ms + bool drain(uint timeout); + bool flush(uint timeout); + bool doBreak(uint duration); // in ms + bool setHardwareFlowControl(bool on); + + enum Pin { DCD = 0, RX, TX, DTR, SG, DSR, RTS, CTS, RI, Nb_Pins }; + struct SPinData { + IODir dir; + const char *label; + }; + static const SPinData PIN_DATA[Nb_Pins]; + virtual bool setPinOn(uint pin, bool on, LogicType type); + virtual bool readPin(uint pin, LogicType type, bool &value); + virtual QValueVector<PinData> pinData(IODir dir) const; + virtual bool isGroundPin(uint pin) const; + virtual uint groundPin() const { return SG; } + virtual IODir ioDir(uint pin) const; + +private: + QString _device; + Properties _properties; +#if defined(Q_OS_UNIX) + typedef int Handle; + typedef termios Parameters; +#elif defined(Q_OS_WIN) + typedef HANDLE Handle; + struct Parameters { + DCB dcb; + COMMTIMEOUTS comtmo; + }; +#endif + Handle _fd; + Parameters _oldParameters; + + bool setParameters(const Parameters ¶meters); + bool getParameters(Parameters ¶meters); + virtual bool internalOpen(); + virtual void internalClose(); + virtual bool internalSend(const char *data, uint size, uint timeout); + virtual bool internalReceive(uint size, char *data, uint timeout); + virtual void setSystemError(const QString &message); + bool internalSetPinOn(Pin pin, bool on); + bool internalReadPin(Pin pin, LogicType type, bool &value); + + static const Handle INVALID_HANDLE; + static Handle openHandle(const QString &device, IODirs dirs); + static void closeHandle(Handle handle); + static QStringList *_list; + static QStringList deviceList(); + static void setHardwareFlowControl(Parameters ¶meters, bool on); +}; +Q_DECLARE_OPERATORS_FOR_FLAGS(Serial::Properties) +Q_DECLARE_OPERATORS_FOR_FLAGS(Serial::InputFlags) +Q_DECLARE_OPERATORS_FOR_FLAGS(Serial::ControlFlags) + +} // namespace + +#endif diff --git a/src/common/port/usb_port.cpp b/src/common/port/usb_port.cpp new file mode 100644 index 0000000..392b483 --- /dev/null +++ b/src/common/port/usb_port.cpp @@ -0,0 +1,411 @@ +/*************************************************************************** + * 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 <qdatetime.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); + QString s = QString("libusb %1").arg(vd.pretty()); + if ( vd<VersionData(0, 1, 8) ) qWarning("%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. + qDebug("This device has %d possible configuration(s).", dev->descriptor.bNumConfigurations); + for (int c=0; c<dev->descriptor.bNumConfigurations; c++) { + qDebug("Looking at configuration %d...This configuration has %d interfaces.", c, dev->config[c].bNumInterfaces); + for(int i=0; i<dev->config[c].bNumInterfaces; i++) { + qDebug(" 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++) { + qDebug(" 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++) { + QString 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"; + } + } + qDebug("%s", s.latin1()); + } + } + } + } + if (bulk_endpoint<0) { + qDebug("No valid interface found!"); + return false; + } +#endif + return true; +} + +QStringList Port::USB::probedDeviceList() +{ + initialize(); + QStringList 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(QString("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); + qDebug("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 QString &message) +{ +#ifdef HAVE_USB + log(Log::LineType::Error, message + QString(" (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, QString(" 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, QString("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, QString("alternate setting is %1").arg(_private->_interface->bAlternateSetting)); + log(Log::DebugLevel::Max, QString("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 + QString s = data.bytes; + uint length = strlen(data.bytes) / 2; + QByteArray 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, QString("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 ); + QTime time; + time.start(); + int todo = size; + for (;;) { + int res = 0; + //qDebug("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)); + //qDebug("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, QString("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 ); + QTime time; + time.start(); + int todo = size; + for (;;) { + int res = 0; + //qDebug("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)); + //qDebug("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; +} diff --git a/src/common/port/usb_port.h b/src/common/port/usb_port.h new file mode 100644 index 0000000..73961cc --- /dev/null +++ b/src/common/port/usb_port.h @@ -0,0 +1,70 @@ +/*************************************************************************** + * Copyright (C) 2005 Lorenz Mösenlechner & Matthias Kranz * + * <icd2linux@hcilab.org> * + * Copyright (C) 2005 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. * + ***************************************************************************/ +#ifndef USB_PORT_H +#define USB_PORT_H + +#include "port_base.h" +#if defined(Q_OS_WIN) +#undef interface +#endif +struct usb_dev_handle; +struct usb_device; +struct usb_bus; + +namespace Port +{ + +class USB : public Base +{ +public: + USB(Log::Base &base, uint vendorId, uint productId, uint config, uint interface); + virtual ~USB(); + virtual Description description() const { return Description(PortType::USB, QString::null); } + + static struct usb_device *findDevice(uint vendorId, uint productId); + static bool isAvailable(); + static QStringList probedDeviceList(); + + struct ControlMessageData { + int type, request, value; + const char *bytes; + }; + bool sendControlMessage(const ControlMessageData &data); + enum EndpointMode { Bulk = 0, Interrupt, Control, Isochronous, Nb_EndpointModes }; + static const char * const ENDPOINT_MODE_NAMES[Nb_EndpointModes]; + EndpointMode endpointMode(uint ep) const; + IODir endpointDir(uint ep) const; + +protected: + bool write(uint endPoint, const char *data, uint size); + bool read(uint endPoint, char *data, uint size, bool *poll = 0); + +private: + class Private; + Private *_private; + uint _vendorId, _productId, _config, _interface; + usb_dev_handle *_handle; + usb_device *_device; + + virtual bool internalOpen(); + virtual void internalClose(); + virtual void setSystemError(const QString &message); + void tryToDetachDriver(); + + static bool _initialized; + static void initialize(); + static usb_bus *getBusses(); + static bool findBulk(const struct usb_device *device); +}; + +} // namespace + +#endif |