1
/****************************************************************************
2
**
3
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4
** All rights reserved.
5
** Contact: Nokia Corporation (qt-info@nokia.com)
6
**
7
** This file is part of the tools applications of the Qt Toolkit.
8
**
9
** $QT_BEGIN_LICENSE:LGPL$
10
** GNU Lesser General Public License Usage
11
** This file may be used under the terms of the GNU Lesser General Public
12
** License version 2.1 as published by the Free Software Foundation and
13
** appearing in the file LICENSE.LGPL included in the packaging of this
14
** file. Please review the following information to ensure the GNU Lesser
15
** General Public License version 2.1 requirements will be met:
16
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17
**
18
** In addition, as a special exception, Nokia gives you certain additional
19
** rights. These rights are described in the Nokia Qt LGPL Exception
20
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21
**
22
** GNU General Public License Usage
23
** Alternatively, this file may be used under the terms of the GNU General
24
** Public License version 3.0 as published by the Free Software Foundation
25
** and appearing in the file LICENSE.GPL included in the packaging of this
26
** file. Please review the following information to ensure the GNU General
27
** Public License version 3.0 requirements will be met:
28
** http://www.gnu.org/copyleft/gpl.html.
29
**
30
** Other Usage
31
** Alternatively, this file may be used in accordance with the terms and
32
** conditions contained in a signed written agreement between you and Nokia.
33
**
34
**
35
**
36
**
37
**
38
** $QT_END_LICENSE$
39
**
40
****************************************************************************/
41
42
#include "serenum.h"
43
#include <QByteArray>
44
#include <QString>
45
#include <QDebug>
46
#include <QFileInfo>
47
#include <QDir>
48
49
#include <usb.h>
50
51
class InterfaceInfo
52
{
53
public:
54
    InterfaceInfo(const QString &mf, const QString &pr, int mfid, int prid);
55
    QString manufacturer;
56
    QString product;
57
    int manufacturerid;
58
    int productid;
59
};
60
61
InterfaceInfo::InterfaceInfo(const QString &mf, const QString &pr, int mfid, int prid) :
62
    manufacturer(mf),
63
    product(pr),
64
    manufacturerid(mfid),
65
    productid(prid)
66
{
67
    if(mf.isEmpty())
68
        manufacturer = QString("[%1]").arg(mfid, 4, 16, QChar('0'));
69
    if(pr.isEmpty())
70
        product = QString("[%1]").arg(prid, 4, 16, QChar('0'));
71
}
72
73
QList<SerialPortId> enumerateSerialPorts(int loglevel)
74
{
75
    QList<QString> eligibleInterfaces;
76
    QList<InterfaceInfo> eligibleInterfacesInfo;
77
    QList<SerialPortId> list;
78
79
    usb_init();
80
    usb_find_busses();
81
    usb_find_devices();
82
83
    for (struct usb_bus *bus = usb_get_busses(); bus; bus = bus->next) {
84
        for (struct usb_device *device = bus->devices; device; device = device->next) {
85
            for (int n = 0; n < device->descriptor.bNumConfigurations && device->config; ++n) {
86
                struct usb_config_descriptor &usbConfig =device->config[n];
87
                QList<int> usableInterfaces;
88
                for (int m = 0; m < usbConfig.bNumInterfaces; ++m) {
89
                    for (int o = 0; o < usbConfig.interface[m].num_altsetting; ++o) {
90
                        struct usb_interface_descriptor &descriptor = usbConfig.interface[m].altsetting[o];
91
                        if (descriptor.bInterfaceClass != 2 // "Communication"
92
                                || descriptor.bInterfaceSubClass != 2 // Abstract (modem)
93
                                || descriptor.bInterfaceProtocol != 255) // Vendor Specific
94
                            continue;
95
96
                        unsigned char *buf = descriptor.extra;
97
                        unsigned int size = descriptor.extralen;
98
                        while (size >= 2 * sizeof(u_int8_t)) {
99
                            // for Communication devices there is a slave interface for the actual
100
                            // data transmission.
101
                            // the extra info stores that as a index for the interface
102
                            if (buf[0] >= 5 && buf[1] == 36 && buf[2] == 6) { // CDC Union
103
                                for (int i = 4; i < buf[0]; i++)
104
                                    usableInterfaces.append((int) buf[i]);
105
                            }
106
                            size -= buf[0];
107
                            buf += buf[0];
108
                        }
109
                    }
110
                }
111
                
112
                if (usableInterfaces.isEmpty())
113
                    continue;
114
                
115
                QString manufacturerString;
116
                QString productString;
117
                
118
                usb_dev_handle *devh = usb_open(device);
119
                if (devh) {
120
                    QByteArray buf;
121
                    buf.resize(256);
122
                    int err = usb_get_string_simple(devh, device->descriptor.iManufacturer, buf.data(), buf.size());
123
                    if (err < 0) {
124
                        if (loglevel > 1)
125
                            qDebug() << "      can't read manufacturer name, error:" << err;
126
                    } else {
127
                        manufacturerString = QString::fromAscii(buf);
128
                        if (loglevel > 1)
129
                            qDebug() << "      manufacturer:" << manufacturerString;
130
                    }
131
132
                    buf.resize(256);
133
                    err = usb_get_string_simple(devh, device->descriptor.iProduct, buf.data(), buf.size());
134
                    if (err < 0) {
135
                        if (loglevel > 1)
136
                            qDebug() << "      can't read product name, error:" << err;
137
                    } else {
138
                        productString = QString::fromAscii(buf);
139
                        if (loglevel > 1)
140
                            qDebug() << "      product:" << productString;
141
                    }
142
                    usb_close(devh);
143
                } else if (loglevel > 0) {
144
                    qDebug() << "      can't open usb device";
145
                }
146
147
                // second loop to find the actual data interface.
148
                foreach (int i, usableInterfaces) {
149
                    for (int m = 0; m < usbConfig.bNumInterfaces; ++m) {
150
                        for (int o = 0; o < usbConfig.interface[m].num_altsetting; ++o) {
151
                            struct usb_interface_descriptor &descriptor = usbConfig.interface[m].altsetting[o];
152
                            if (descriptor.bInterfaceNumber != i)
153
                                continue;
154
                            if (descriptor.bInterfaceClass == 10) { // "CDC Data"
155
                                if (loglevel > 1) {
156
                                    qDebug() << "      found the data port"
157
                                             << "bus:" << bus->dirname
158
                                             << "device" << device->filename
159
                                             << "interface" << descriptor.bInterfaceNumber;
160
                                }
161
#ifdef Q_OS_MAC
162
                                eligibleInterfaces << QString("^cu\\.usbmodem.*%1$")
163
                                                      .arg(QString("%1").arg(descriptor.bInterfaceNumber, 1, 16).toUpper()); 
164
#else
165
                                // ### manufacturer and product strings are only readable as root :(
166
                                if (!manufacturerString.isEmpty() && !productString.isEmpty()) {
167
                                    eligibleInterfaces << QString("usb-%1_%2-if%3")
168
                                                          .arg(manufacturerString.replace(QChar(' '), QChar('_')))
169
                                                          .arg(productString.replace(QChar(' '), QChar('_')))
170
                                                          .arg(i, 2, 16, QChar('0'));
171
                                } else {
172
                                    eligibleInterfaces << QString("if%1").arg(i, 2, 16, QChar('0')); // fix!
173
                                }
174
#endif
175
                                eligibleInterfacesInfo << InterfaceInfo(manufacturerString, productString, device->descriptor.idVendor, device->descriptor.idProduct);
176
                            }
177
                        }
178
                    }
179
                }
180
            }
181
        }
182
    }
183
    
184
    if (loglevel > 1)
185
        qDebug() << "      searching for interfaces:" << eligibleInterfaces;
186
187
#ifdef Q_OS_MAC
188
    QDir dir("/dev/");
189
    bool allowAny = false;
190
#else
191
    QDir dir("/dev/serial/by-id/");
192
    bool allowAny = eligibleInterfaces.isEmpty();
193
#endif
194
    foreach (const QFileInfo &info, dir.entryInfoList(QDir::System)) {
195
        if (!info.isDir()) {
196
            bool usable = allowAny;
197
            QString friendlyName = info.fileName();
198
            foreach (const QString &iface, eligibleInterfaces) {
199
                if (info.fileName().contains(QRegExp(iface))) {
200
                    if (loglevel > 1)
201
                        qDebug() << "      found device file:" << info.fileName() << endl;
202
#ifdef Q_OS_MAC
203
                    friendlyName = eligibleInterfacesInfo[eligibleInterfaces.indexOf(iface)].product;
204
#endif
205
                    usable = true;
206
                    break;
207
                }
208
            }
209
            if (!usable)
210
                continue;
211
212
            SerialPortId id;
213
            id.friendlyName = friendlyName;
214
            id.portName = info.canonicalFilePath();
215
            list << id;
216
        }
217
    }
218
219
    if (list.isEmpty() && !eligibleInterfacesInfo.isEmpty() && loglevel > 0) {
220
        qDebug() << "Possible USB devices found, but without serial drivers:";
221
        foreach(const InterfaceInfo &iface, eligibleInterfacesInfo) {
222
            qDebug() << "    Manufacturer:"
223
                     << iface.manufacturer
224
                     << "Product:"
225
                     << iface.product
226
#ifdef Q_OS_LINUX
227
                     << endl
228
                     << "    Load generic driver using:"
229
                     << QString("sudo modprobe usbserial vendor=0x%1 product=0x%2")
230
                        .arg(iface.manufacturerid, 4, 16, QChar('0'))
231
                        .arg(iface.productid, 4, 16, QChar('0'));
232
#else
233
                     ;
234
#endif
235
        }
236
    }
237
    return list;
238
}