/ src / core / usb.cc
usb.cc
  1  /*
  2   * usb.cc
  3   *
  4   */
  5  
  6  #ifndef _GNU_SOURCE
  7  #define _GNU_SOURCE
  8  #endif
  9  
 10  #include "version.h"
 11  #include "config.h"
 12  #include "usb.h"
 13  #include "osutils.h"
 14  #include "heuristics.h"
 15  #include "options.h"
 16  #include <stdio.h>
 17  #include <stdlib.h>
 18  #include <map>
 19  #include <sys/types.h>
 20  #include <sys/stat.h>
 21  #include <sys/ioctl.h>
 22  #include <errno.h>
 23  #include <fcntl.h>
 24  #include <string.h>
 25  #include <stdlib.h>
 26  #include <unistd.h>
 27  #include <dirent.h>
 28  #include <cstring>
 29  
 30  #define PROCBUSUSBDEVICES "/proc/bus/usb/devices"
 31  #define SYSKERNELDEBUGUSBDEVICES "/sys/kernel/debug/usb/devices"
 32  #define USBID_PATH DATADIR"/usb.ids:/usr/share/lshw/usb.ids:/usr/local/share/usb.ids:/usr/share/usb.ids:/etc/usb.ids:/usr/share/hwdata/usb.ids:/usr/share/misc/usb.ids"
 33  
 34  #define USB_CLASS_PER_INTERFACE         0         /* for DeviceClass */
 35  #define USB_CLASS_AUDIO                 1
 36  #define USB_CLASS_COMM                  2
 37  #define USB_CLASS_HID                   3
 38  #define USB_CLASS_IMAGING               6
 39  #define USB_CLASS_PRINTER               7
 40  #define USB_CLASS_MASS_STORAGE          8
 41  #define USB_CLASS_HUB                   9
 42  #define USB_CLASS_DATA                  0xa
 43  #define USB_CLASS_SMARTCARD             0xb
 44  #define USB_CLASS_VIDEO                 0xe
 45  #define USB_CLASS_WIRELESS              0xe0
 46  #define USB_CLASS_VENDOR_SPEC           0xff
 47  
 48  #define USB_SC_AUDIOCONTROL   1
 49  #define USB_SC_AUDIOSTREAMING   2
 50  #define USB_SC_AUDIOMIDISTREAMING 3
 51  #define USB_SC_COMMMODEM    2
 52  #define USB_SC_COMMETHERNET   6
 53  #define USB_SC_COMMOBEX     0xb
 54  #define USB_SC_HIDNONE      0
 55  #define USB_SC_HIDBOOT      1
 56  #define USB_PROT_HIDKBD     1
 57  #define USB_PROT_HIDMOUSE   2
 58  #define USB_SC_PRINTER      1
 59  #define USB_PROT_PRINTERUNIDIR    1
 60  #define USB_PROT_PRINTERBIDIR   2
 61  #define USB_PROT_PRINTER1284    3
 62  #define USB_SC_STORAGERBC   1
 63  #define USB_SC_STORAGEATAPI   2
 64  #define USB_SC_STORAGEFLOPPY    4
 65  #define USB_SC_STORAGESCSI    6
 66  #define USB_SC_WIRELESSRADIO    1
 67  #define USB_PROT_BLUETOOTH    1
 68  
 69  static map<u_int16_t,string> usbvendors;
 70  static map<u_int32_t,string> usbproducts;
 71  
 72  #define PRODID(x, y) ((x << 16) + y)
 73  
 74  __ID("@(#) $Id$");
 75  
 76  static string usbhost(unsigned bus)
 77  {
 78    char buffer[10];
 79  
 80    snprintf(buffer, sizeof(buffer), "usb%u", bus);
 81  
 82    return string(buffer);
 83  }
 84  
 85  
 86  static string usbhandle(unsigned bus, unsigned level, unsigned dev)
 87  {
 88    char buffer[10];
 89  
 90    snprintf(buffer, sizeof(buffer), "USB:%u:%u", bus, dev);
 91  
 92    return string(buffer);
 93  }
 94  
 95  
 96  static string usbbusinfo(unsigned bus, unsigned level, unsigned port)
 97  {
 98    char buffer[10];
 99  
100    if(level>0)
101      snprintf(buffer, sizeof(buffer), "usb@%u:%u", bus, port);
102    else
103      snprintf(buffer, sizeof(buffer), "usb@%u", bus);
104  
105    return string(buffer);
106  }
107  
108  
109  static string usbspeed(float speed)
110  {
111    char buffer[15];
112  
113    snprintf(buffer, sizeof(buffer), "%.fMbit/s", speed);
114  
115    return string(buffer);
116  }
117  
118  
119  static bool addUSBChild(hwNode & n, hwNode & device, unsigned bus, unsigned lev, unsigned prnt)
120  {
121    hwNode * parent = NULL;
122  
123    device.addHint("bus.icon", string("usb"));
124  
125    if(prnt>0) parent = n.findChildByHandle(usbhandle(bus, lev-1, prnt));
126    if(parent)
127    {
128      if(parent->getBusInfo().find(":")==string::npos)
129        device.setBusInfo(parent->getBusInfo()+":"+device.getPhysId());
130      else
131        device.setBusInfo(parent->getBusInfo()+"."+device.getPhysId());
132      parent->addChild(device);
133      return true;
134    }
135    else
136    {
137  // USB host
138      {
139        string businfo = guessBusInfo(device.getSerial());
140        parent = n.findChildByBusInfo(businfo);
141        if(!parent)                                 // still no luck
142        {
143          unsigned long long ioport = strtoll(device.getSerial().c_str(), NULL, 16);
144          parent = n.findChildByResource(hw::resource::ioport(ioport, ioport));
145        }
146        device.setSerial("");                       // serial# has no meaning for USB hosts
147      }
148      if(parent)
149      {
150        parent->addChild(device);
151        return true;
152      }
153      else
154        n.addChild(device);
155      return false;
156    }
157  }
158  
159  
160  static bool setUSBClass(hwNode & device, unsigned cls, unsigned sub, unsigned prot)
161  {
162    if(device.getClass()!=hw::generic) return false;
163    switch(cls)
164    {
165      case USB_CLASS_AUDIO:
166        device.setClass(hw::multimedia);
167        device.setDescription(_("Audio device"));
168        switch(sub)
169        {
170          case USB_SC_AUDIOCONTROL:
171            device.addCapability("audio-control", _("Control device"));
172            break;
173          case USB_SC_AUDIOMIDISTREAMING:
174            device.addCapability("midi", _("MIDI"));
175          case USB_SC_AUDIOSTREAMING:
176            device.addCapability("audio-streaming", _("Audio streaming"));
177            break;
178        }
179        break;
180      case USB_CLASS_COMM:
181        device.setClass(hw::communication);
182        device.setDescription(_("Communication device"));
183        if(sub == USB_SC_COMMMODEM)
184        {
185          device.setDescription(_("Modem"));
186          if((prot>=1) && (prot<=6)) device.addCapability("atcommands", _("AT (Hayes) compatible"));
187        }
188        if(sub==USB_SC_COMMETHERNET) device.addCapability("ethernet", _("Ethernet networking"));
189        if(sub==USB_SC_COMMOBEX) device.addCapability("obex", _("OBEX networking"));
190        break;
191      case USB_CLASS_HID:
192        device.setClass(hw::input);
193        device.setDescription(_("Human interface device"));
194        if((sub==USB_SC_HIDNONE)||(sub==USB_SC_HIDBOOT))
195        {
196          switch(prot)
197          {
198            case USB_PROT_HIDKBD:
199              device.setDescription(_("Keyboard"));
200              break;
201            case USB_PROT_HIDMOUSE:
202              device.setDescription(_("Mouse"));
203              break;
204          }
205        }
206        break;
207      case USB_CLASS_PRINTER:
208        device.setClass(hw::printer);
209        device.setDescription(_("Printer"));
210        device.addHint("icon", string("printer"));
211        if(sub==USB_SC_PRINTER)
212        {
213          switch(prot)
214          {
215            case USB_PROT_PRINTERUNIDIR:
216              device.addCapability("unidirectional", _("Unidirectional"));
217              break;
218            case USB_PROT_PRINTERBIDIR:
219              device.addCapability("bidirectional", _("Bidirectional"));
220              break;
221            case USB_PROT_PRINTER1284:
222              device.addCapability("ieee1284.4", _("IEEE 1284.4 compatible bidirectional"));
223              break;
224          }
225        }
226        break;
227      case USB_CLASS_MASS_STORAGE:
228        device.setClass(hw::storage);
229        device.setDescription(_("Mass storage device"));
230        switch(sub)
231        {
232          case USB_SC_STORAGERBC:
233            device.addCapability("flash", _("RBC (typically Flash) mass storage"));
234            break;
235          case USB_SC_STORAGEATAPI:
236            device.addCapability("atapi", _("SFF-8020i, MMC-2 (ATAPI)"));
237            break;
238          case USB_SC_STORAGEFLOPPY:
239            device.addCapability("floppy", _("Floppy (UFI)"));
240            break;
241          case USB_SC_STORAGESCSI:
242            device.addCapability("scsi", _("SCSI"));
243            break;
244        }
245        break;
246      case USB_CLASS_HUB:
247        device.setClass(hw::bus);
248        device.setDescription(_("USB hub"));
249        break;
250      case USB_CLASS_DATA:
251        device.setClass(hw::generic);
252        break;
253      case USB_CLASS_SMARTCARD:
254        device.setClass(hw::generic);
255        device.setDescription(_("Smart card reader"));
256        break;
257      case USB_CLASS_VIDEO:
258        device.setClass(hw::multimedia);
259        device.setDescription(_("Video"));
260        break;
261      case USB_CLASS_WIRELESS:
262        device.setClass(hw::communication);
263        device.setDescription(_("Wireless interface"));
264        if((sub==USB_SC_WIRELESSRADIO) && (prot==USB_PROT_BLUETOOTH))
265        {
266          device.setDescription(_("Bluetooth wireless interface"));
267          device.addCapability("bluetooth", _("Bluetooth wireless radio"));
268          device.addHint("icon", string("bluetooth"));
269        }
270        break;
271      default:
272        device.setDescription(_("Generic USB device"));
273        return false;
274    }
275  
276    return true;
277  }
278  
279  
280  static bool describeUSB(hwNode & device, unsigned vendor, unsigned prodid)
281  {
282    if(usbvendors.find(vendor)==usbvendors.end()) return false;
283  
284    device.setVendor(usbvendors[vendor]+(enabled("output:numeric")?" ["+tohex(vendor)+"]":""));
285    device.addHint("usb.idVendor", vendor);
286    device.addHint("usb.idProduct", prodid);
287  
288    if(usbproducts.find(PRODID(vendor, prodid))!=usbproducts.end())
289      device.setProduct(usbproducts[PRODID(vendor, prodid)]+(enabled("output:numeric")?" ["+tohex(vendor)+":"+tohex(prodid)+"]":""));
290  
291    return true;
292  }
293  
294  
295  static bool load_usbids(const string & name)
296  {
297    FILE * usbids = NULL;
298    u_int16_t vendorid = 0;
299  
300    usbids = fopen(name.c_str(), "r");
301    if(!usbids) return false;
302  
303    while(!feof(usbids))
304    {
305      char * buffer = NULL;
306      size_t linelen;
307      unsigned t = 0;
308      char *description = NULL;
309  
310      if(getline(&buffer, &linelen, usbids)>0)
311      {
312        if(buffer[linelen-1]<' ')
313          buffer[linelen-1] = '\0';                 // chop \n
314        string line = string(buffer);
315        free(buffer);
316        buffer = NULL;
317  
318        description = NULL;
319        t = 0;
320        if(line.length()>0)
321        {
322          if(line[0] == '\t')                       // product id entry
323          {
324            line.erase(0, 1);
325            if(matches(line, "^[[:xdigit:]][[:xdigit:]][[:xdigit:]][[:xdigit:]]"))
326              t = strtol(line.c_str(), &description, 16);
327            if(description && (description != line.c_str()))
328            {
329              usbproducts[PRODID(vendorid,t)] = hw::strip(description);
330            }
331          }
332          else                                      // vendor id entry
333          {
334            if(matches(line, "^[[:xdigit:]][[:xdigit:]][[:xdigit:]][[:xdigit:]]"))
335              t = strtol(line.c_str(), &description, 16);
336            if(description && (description != line.c_str()))
337            {
338              vendorid = t;
339              usbvendors[t] = hw::strip(description);
340            }
341          }
342        }
343      }
344      if(buffer != NULL) free(buffer);
345    }
346  
347    fclose(usbids);
348  
349    return true;
350  }
351  
352  
353  bool scan_usb(hwNode & n)
354  {
355    hwNode device("device");
356    FILE * usbdevices = NULL;
357    bool defined = false;
358    unsigned int bus,lev,prnt,port,cnt,devnum,mxch;
359    float speed;
360    char ver[10+1];
361    unsigned int cls, sub, prot, mxps, numcfgs;
362    unsigned int vendor, prodid;
363    char rev[10+1];
364    unsigned numifs, cfgnum, atr;
365    char mxpwr[10+1];
366    unsigned ifnum, alt, numeps;
367    char driver[80+1];
368  
369    if (!exists(SYSKERNELDEBUGUSBDEVICES) && !exists(PROCBUSUSBDEVICES))
370      return false;
371  
372    vector < string > filenames;
373    splitlines(USBID_PATH, filenames, ':');
374    for (int i = filenames.size() - 1; i >= 0; i--)
375    {
376      load_usbids(filenames[i]);
377    }
378    filenames.clear();
379  
380    if (exists(SYSKERNELDEBUGUSBDEVICES))
381      usbdevices = fopen(SYSKERNELDEBUGUSBDEVICES, "r");
382  
383    if(!usbdevices && exists(PROCBUSUSBDEVICES))
384      usbdevices = fopen(PROCBUSUSBDEVICES, "r");
385  
386    if(!usbdevices)
387      return false;
388  
389    while(!feof(usbdevices))
390    {
391      char * buffer = NULL;
392      size_t linelen;
393      char strname[80+1];
394      char strval[80+1];
395  
396      if(getline(&buffer, &linelen, usbdevices)>0)
397      {
398        string line = hw::strip(string(buffer));
399        free(buffer);
400        buffer = NULL;
401  
402        if(line.length()<=0)
403        {
404          if(defined)
405            addUSBChild(n, device, bus, lev, prnt);
406          device = hwNode("device");
407          defined = false;
408        }
409        else
410        {
411          if((line.length()>1) && (line[1] == ':'))
412            switch(line[0])
413            {
414              case 'T':
415                bus = lev = prnt = port = cnt = devnum = mxch = 0;
416                speed = 0.0;
417                strcpy(ver, "");
418                strcpy(rev, "");
419                cls = sub = prot = mxps = numcfgs = 0;
420                vendor = prodid = 0;
421                if(sscanf(line.c_str(), "T: Bus=%u Lev=%u Prnt=%u Port=%u Cnt=%u Dev#=%u Spd=%f MxCh=%u", &bus, &lev, &prnt, &port, &cnt, &devnum, &speed, &mxch)>0)
422                {
423                defined = true;
424                if(lev==0)
425                {
426                  device = hwNode("usbhost", hw::bus);
427                  device.claim();
428                  device.setLogicalName(usbhost(bus));
429                }
430                else
431                  device = hwNode("usb");
432                device.setHandle(usbhandle(bus, lev, devnum));
433                device.setBusInfo(usbbusinfo(bus, lev, port));
434                device.setPhysId(port+1);
435                device.setConfig("speed", usbspeed(speed));
436                if(mxch>0)
437                {
438                  snprintf(strval, sizeof(strval), "%u", mxch);
439                  device.setConfig("slots", strval);
440                }
441            }
442            break;
443            case 'D':
444              strcpy(ver, "");
445              cls = sub = prot = mxps = numcfgs = 0;
446              if(sscanf(line.c_str(), "D: Ver=%s Cls=%x(%*5c) Sub=%x Prot=%x MxPS=%u #Cfgs=%u", ver, &cls, &sub, &prot, &mxps, &numcfgs)>0)
447              {
448                setUSBClass(device, cls, sub, prot);
449                device.addCapability(string("usb-")+string(ver));
450                device.describeCapability("usb-1.00", "USB 1.0");
451                device.describeCapability("usb-1.10", "USB 1.1");
452                device.describeCapability("usb-2.00", "USB 2.0");
453                device.addHint("usb.bDeviceClass", cls);
454                device.addHint("usb.bDeviceSubClass", sub);
455                device.addHint("usb.bDeviceProtocol", prot);
456              }
457              break;
458            case 'P':
459              vendor = prodid = 0;
460              strcpy(rev, "");
461              if(sscanf(line.c_str(), "P: Vendor=%x ProdID=%x Rev=%10s", &vendor, &prodid, rev)>0)
462              {
463                describeUSB(device, vendor, prodid);
464                device.setVersion(hw::strip(rev));
465              }
466              break;
467            case 'S':
468              memset(strname, 0, sizeof(strname));
469              memset(strval, 0, sizeof(strval));
470              if(sscanf(line.c_str(), "S: %80[^=]=%80[ -z]", strname, strval)>0)
471              {
472                if(strcasecmp(strname, "Manufacturer")==0)
473                  device.setVendor(hw::strip(strval)+(enabled("output:numeric")?" ["+tohex(vendor)+"]":""));
474                if(strcasecmp(strname, "Product")==0)
475                  device.setProduct(hw::strip(strval)+(enabled("output:numeric")?" ["+tohex(vendor)+":"+tohex(prodid)+"]":""));
476                if(strcasecmp(strname, "SerialNumber")==0)
477                  device.setSerial(hw::strip(strval));
478              }
479              break;
480            case 'C':
481              numifs = cfgnum = atr = 0;
482              strcpy(mxpwr, "");
483              if(sscanf(line.c_str(), "C:* #Ifs=%u Cfg#=%u Atr=%x MxPwr=%s", &numifs, &cfgnum, &atr, mxpwr)>0)
484              {
485                if(strcmp("0mA", mxpwr)!=0)
486                  device.setConfig("maxpower", mxpwr);
487              }
488              break;
489            case 'I':
490              ifnum = alt = numeps = cls = sub = prot = 0;
491              memset(driver, 0, sizeof(driver));
492              if(((sscanf(line.c_str(), "I:* If#=%u Alt=%u #EPs=%u Cls=%x(%*5c) Sub=%x Prot=%x Driver=%80[ -z]", &ifnum, &alt, &numeps, &cls, &sub, &prot, driver)>0) && (cfgnum>0)) || ((sscanf(line.c_str(), "I: If#=%u Alt=%u #EPs=%u Cls=%x(%*5c) Sub=%x Prot=%x Driver=%80[ -z]", &ifnum, &alt, &numeps, &cls, &sub, &prot, driver)>0) && (cfgnum>0)))
493              {
494                setUSBClass(device, cls, sub, prot);
495                if((strlen(driver)!=0) && (strcasecmp("(none)", driver)!=0))
496                {
497                  device.setConfig("driver", hw::strip(driver));
498                  device.claim();
499                }
500              }
501              break;
502          }
503        }
504      }
505      if(buffer != NULL) free(buffer);
506    }
507    if(defined)
508      addUSBChild(n, device, bus, lev, prnt);
509  
510    if(usbdevices) fclose(usbdevices);
511  
512    return true;
513  }