15fd54aceSGreg Kroah-Hartman // SPDX-License-Identifier: GPL-2.0+ 21da177e4SLinus Torvalds /* 31da177e4SLinus Torvalds * devices.c 41da177e4SLinus Torvalds * (C) Copyright 1999 Randy Dunlap. 544526f91SCarlos Sánchez Acosta * (C) Copyright 1999,2000 Thomas Sailer <sailer@ife.ee.ethz.ch>. 644526f91SCarlos Sánchez Acosta * (proc file per device) 71da177e4SLinus Torvalds * (C) Copyright 1999 Deti Fliegl (new USB architecture) 81da177e4SLinus Torvalds * 91da177e4SLinus Torvalds ************************************************************* 101da177e4SLinus Torvalds * 111da177e4SLinus Torvalds * <mountpoint>/devices contains USB topology, device, config, class, 121da177e4SLinus Torvalds * interface, & endpoint data. 131da177e4SLinus Torvalds * 1421470e32SMauro Carvalho Chehab * I considered using /dev/bus/usb/device# for each device 151da177e4SLinus Torvalds * as it is attached or detached, but I didn't like this for some 161da177e4SLinus Torvalds * reason -- maybe it's just too deep of a directory structure. 171da177e4SLinus Torvalds * I also don't like looking in multiple places to gather and view 181da177e4SLinus Torvalds * the data. Having only one file for ./devices also prevents race 191da177e4SLinus Torvalds * conditions that could arise if a program was reading device info 201da177e4SLinus Torvalds * for devices that are being removed (unplugged). (That is, the 211da177e4SLinus Torvalds * program may find a directory for devnum_12 then try to open it, 221da177e4SLinus Torvalds * but it was just unplugged, so the directory is now deleted. 231da177e4SLinus Torvalds * But programs would just have to be prepared for situations like 241da177e4SLinus Torvalds * this in any plug-and-play environment.) 251da177e4SLinus Torvalds * 261da177e4SLinus Torvalds * 1999-12-16: Thomas Sailer <sailer@ife.ee.ethz.ch> 271da177e4SLinus Torvalds * Converted the whole proc stuff to real 281da177e4SLinus Torvalds * read methods. Now not the whole device list needs to fit 291da177e4SLinus Torvalds * into one page, only the device list for one bus. 3021470e32SMauro Carvalho Chehab * Added a poll method to /sys/kernel/debug/usb/devices, to wake 311da177e4SLinus Torvalds * up an eventual usbd 321da177e4SLinus Torvalds * 2000-01-04: Thomas Sailer <sailer@ife.ee.ethz.ch> 331da177e4SLinus Torvalds * Turned into its own filesystem 341da177e4SLinus Torvalds * 2000-07-05: Ashley Montanaro <ashley@compsoc.man.ac.uk> 351da177e4SLinus Torvalds * Converted file reading routine to dump to buffer once 361da177e4SLinus Torvalds * per device, not per bus 371da177e4SLinus Torvalds */ 381da177e4SLinus Torvalds 391da177e4SLinus Torvalds #include <linux/fs.h> 401da177e4SLinus Torvalds #include <linux/mm.h> 415a0e3ad6STejun Heo #include <linux/gfp.h> 421da177e4SLinus Torvalds #include <linux/usb.h> 431da177e4SLinus Torvalds #include <linux/usbdevice_fs.h> 4427729aadSEric Lescouet #include <linux/usb/hcd.h> 454186ecf8SArjan van de Ven #include <linux/mutex.h> 4644526f91SCarlos Sánchez Acosta #include <linux/uaccess.h> 471da177e4SLinus Torvalds 486d5e8254SGreg KH #include "usb.h" 491da177e4SLinus Torvalds 501da177e4SLinus Torvalds /* Define ALLOW_SERIAL_NUMBER if you want to see the serial number of devices */ 511da177e4SLinus Torvalds #define ALLOW_SERIAL_NUMBER 521da177e4SLinus Torvalds 53bce1a702SDmitry Torokhov static const char format_topo[] = 54834e2312SAlan Stern /* T: Bus=dd Lev=dd Prnt=dd Port=dd Cnt=dd Dev#=ddd Spd=dddd MxCh=dd */ 55834e2312SAlan Stern "\nT: Bus=%2.2d Lev=%2.2d Prnt=%2.2d Port=%2.2d Cnt=%2.2d Dev#=%3d Spd=%-4s MxCh=%2d\n"; 561da177e4SLinus Torvalds 57bce1a702SDmitry Torokhov static const char format_string_manufacturer[] = 581da177e4SLinus Torvalds /* S: Manufacturer=xxxx */ 591da177e4SLinus Torvalds "S: Manufacturer=%.100s\n"; 601da177e4SLinus Torvalds 61bce1a702SDmitry Torokhov static const char format_string_product[] = 621da177e4SLinus Torvalds /* S: Product=xxxx */ 631da177e4SLinus Torvalds "S: Product=%.100s\n"; 641da177e4SLinus Torvalds 651da177e4SLinus Torvalds #ifdef ALLOW_SERIAL_NUMBER 66bce1a702SDmitry Torokhov static const char format_string_serialnumber[] = 671da177e4SLinus Torvalds /* S: SerialNumber=xxxx */ 681da177e4SLinus Torvalds "S: SerialNumber=%.100s\n"; 691da177e4SLinus Torvalds #endif 701da177e4SLinus Torvalds 71bce1a702SDmitry Torokhov static const char format_bandwidth[] = 721da177e4SLinus Torvalds /* B: Alloc=ddd/ddd us (xx%), #Int=ddd, #Iso=ddd */ 731da177e4SLinus Torvalds "B: Alloc=%3d/%3d us (%2d%%), #Int=%3d, #Iso=%3d\n"; 741da177e4SLinus Torvalds 75bce1a702SDmitry Torokhov static const char format_device1[] = 761da177e4SLinus Torvalds /* D: Ver=xx.xx Cls=xx(sssss) Sub=xx Prot=xx MxPS=dd #Cfgs=dd */ 771da177e4SLinus Torvalds "D: Ver=%2x.%02x Cls=%02x(%-5s) Sub=%02x Prot=%02x MxPS=%2d #Cfgs=%3d\n"; 781da177e4SLinus Torvalds 79bce1a702SDmitry Torokhov static const char format_device2[] = 801da177e4SLinus Torvalds /* P: Vendor=xxxx ProdID=xxxx Rev=xx.xx */ 811da177e4SLinus Torvalds "P: Vendor=%04x ProdID=%04x Rev=%2x.%02x\n"; 821da177e4SLinus Torvalds 83bce1a702SDmitry Torokhov static const char format_config[] = 841da177e4SLinus Torvalds /* C: #Ifs=dd Cfg#=dd Atr=xx MPwr=dddmA */ 851da177e4SLinus Torvalds "C:%c #Ifs=%2d Cfg#=%2d Atr=%02x MxPwr=%3dmA\n"; 861da177e4SLinus Torvalds 87bce1a702SDmitry Torokhov static const char format_iad[] = 88165fe97eSCraig W. Nadler /* A: FirstIf#=dd IfCount=dd Cls=xx(sssss) Sub=xx Prot=xx */ 89165fe97eSCraig W. Nadler "A: FirstIf#=%2d IfCount=%2d Cls=%02x(%-5s) Sub=%02x Prot=%02x\n"; 90165fe97eSCraig W. Nadler 91bce1a702SDmitry Torokhov static const char format_iface[] = 921da177e4SLinus Torvalds /* I: If#=dd Alt=dd #EPs=dd Cls=xx(sssss) Sub=xx Prot=xx Driver=xxxx*/ 932360e4aaSDavid Brownell "I:%c If#=%2d Alt=%2d #EPs=%2d Cls=%02x(%-5s) Sub=%02x Prot=%02x Driver=%s\n"; 941da177e4SLinus Torvalds 95bce1a702SDmitry Torokhov static const char format_endpt[] = 961da177e4SLinus Torvalds /* E: Ad=xx(s) Atr=xx(ssss) MxPS=dddd Ivl=D?s */ 971da177e4SLinus Torvalds "E: Ad=%02x(%c) Atr=%02x(%-4s) MxPS=%4d Ivl=%d%cs\n"; 981da177e4SLinus Torvalds 991da177e4SLinus Torvalds struct class_info { 1001da177e4SLinus Torvalds int class; 1011da177e4SLinus Torvalds char *class_name; 1021da177e4SLinus Torvalds }; 1031da177e4SLinus Torvalds 10444526f91SCarlos Sánchez Acosta static const struct class_info clas_info[] = { 10544526f91SCarlos Sánchez Acosta /* max. 5 chars. per name string */ 1061da177e4SLinus Torvalds {USB_CLASS_PER_INTERFACE, ">ifc"}, 1071da177e4SLinus Torvalds {USB_CLASS_AUDIO, "audio"}, 1081da177e4SLinus Torvalds {USB_CLASS_COMM, "comm."}, 1091da177e4SLinus Torvalds {USB_CLASS_HID, "HID"}, 1101da177e4SLinus Torvalds {USB_CLASS_PHYSICAL, "PID"}, 111bf7fbb02SFrans Pop {USB_CLASS_STILL_IMAGE, "still"}, 1121da177e4SLinus Torvalds {USB_CLASS_PRINTER, "print"}, 1131da177e4SLinus Torvalds {USB_CLASS_MASS_STORAGE, "stor."}, 114bf7fbb02SFrans Pop {USB_CLASS_HUB, "hub"}, 1151da177e4SLinus Torvalds {USB_CLASS_CDC_DATA, "data"}, 1161da177e4SLinus Torvalds {USB_CLASS_CSCID, "scard"}, 1171da177e4SLinus Torvalds {USB_CLASS_CONTENT_SEC, "c-sec"}, 118165fe97eSCraig W. Nadler {USB_CLASS_VIDEO, "video"}, 11903cc8353SRob Gill {USB_CLASS_PERSONAL_HEALTHCARE, "perhc"}, 12003cc8353SRob Gill {USB_CLASS_AUDIO_VIDEO, "av"}, 12103cc8353SRob Gill {USB_CLASS_BILLBOARD, "blbrd"}, 12203cc8353SRob Gill {USB_CLASS_USB_TYPE_C_BRIDGE, "bridg"}, 123bf7fbb02SFrans Pop {USB_CLASS_WIRELESS_CONTROLLER, "wlcon"}, 124bf7fbb02SFrans Pop {USB_CLASS_MISC, "misc"}, 125bf7fbb02SFrans Pop {USB_CLASS_APP_SPEC, "app."}, 126bf7fbb02SFrans Pop {USB_CLASS_VENDOR_SPEC, "vend."}, 1271da177e4SLinus Torvalds {-1, "unk."} /* leave as last */ 1281da177e4SLinus Torvalds }; 1291da177e4SLinus Torvalds 1301da177e4SLinus Torvalds /*****************************************************************/ 1311da177e4SLinus Torvalds 1321da177e4SLinus Torvalds static const char *class_decode(const int class) 1331da177e4SLinus Torvalds { 1341da177e4SLinus Torvalds int ix; 1351da177e4SLinus Torvalds 1361da177e4SLinus Torvalds for (ix = 0; clas_info[ix].class != -1; ix++) 1371da177e4SLinus Torvalds if (clas_info[ix].class == class) 1381da177e4SLinus Torvalds break; 1391a68f71dSOliver Neukum return clas_info[ix].class_name; 1401da177e4SLinus Torvalds } 1411da177e4SLinus Torvalds 142376c0d3fSGreg Kroah-Hartman static char *usb_dump_endpoint_descriptor(int speed, char *start, char *end, 143376c0d3fSGreg Kroah-Hartman const struct usb_endpoint_descriptor *desc) 1441da177e4SLinus Torvalds { 1451da177e4SLinus Torvalds char dir, unit, *type; 14687ad46c9SLuiz Fernando N. Capitulino unsigned interval, bandwidth = 1; 1471da177e4SLinus Torvalds 1481da177e4SLinus Torvalds if (start > end) 1491da177e4SLinus Torvalds return start; 15087ad46c9SLuiz Fernando N. Capitulino 15187ad46c9SLuiz Fernando N. Capitulino dir = usb_endpoint_dir_in(desc) ? 'I' : 'O'; 15287ad46c9SLuiz Fernando N. Capitulino 15391f97521SFelipe Balbi if (speed == USB_SPEED_HIGH) 15491f97521SFelipe Balbi bandwidth = usb_endpoint_maxp_mult(desc); 1551da177e4SLinus Torvalds 1561da177e4SLinus Torvalds /* this isn't checking for illegal values */ 1572e0fe709SJulia Lawall switch (usb_endpoint_type(desc)) { 1581da177e4SLinus Torvalds case USB_ENDPOINT_XFER_CONTROL: 1591da177e4SLinus Torvalds type = "Ctrl"; 1601da177e4SLinus Torvalds dir = 'B'; /* ctrl is bidirectional */ 1611da177e4SLinus Torvalds break; 1621da177e4SLinus Torvalds case USB_ENDPOINT_XFER_ISOC: 1631da177e4SLinus Torvalds type = "Isoc"; 1641da177e4SLinus Torvalds break; 1651da177e4SLinus Torvalds case USB_ENDPOINT_XFER_BULK: 1661da177e4SLinus Torvalds type = "Bulk"; 1671da177e4SLinus Torvalds break; 1681da177e4SLinus Torvalds case USB_ENDPOINT_XFER_INT: 1691da177e4SLinus Torvalds type = "Int."; 1701da177e4SLinus Torvalds break; 1711da177e4SLinus Torvalds default: /* "can't happen" */ 1721da177e4SLinus Torvalds return start; 1731da177e4SLinus Torvalds } 174*fb95c7cfSChunfeng Yun 175*fb95c7cfSChunfeng Yun interval = usb_decode_interval(desc, speed); 176*fb95c7cfSChunfeng Yun if (interval % 1000) { 1771da177e4SLinus Torvalds unit = 'u'; 178*fb95c7cfSChunfeng Yun } else { 1791da177e4SLinus Torvalds unit = 'm'; 1801da177e4SLinus Torvalds interval /= 1000; 1811da177e4SLinus Torvalds } 1821da177e4SLinus Torvalds 1831da177e4SLinus Torvalds start += sprintf(start, format_endpt, desc->bEndpointAddress, dir, 1841da177e4SLinus Torvalds desc->bmAttributes, type, 185c091b6b3SFelipe Balbi usb_endpoint_maxp(desc) * 186376c0d3fSGreg Kroah-Hartman bandwidth, 1871da177e4SLinus Torvalds interval, unit); 1881da177e4SLinus Torvalds return start; 1891da177e4SLinus Torvalds } 1901da177e4SLinus Torvalds 1911da177e4SLinus Torvalds static char *usb_dump_interface_descriptor(char *start, char *end, 1921da177e4SLinus Torvalds const struct usb_interface_cache *intfc, 1931da177e4SLinus Torvalds const struct usb_interface *iface, 1941da177e4SLinus Torvalds int setno) 1951da177e4SLinus Torvalds { 196376c0d3fSGreg Kroah-Hartman const struct usb_interface_descriptor *desc; 1978d790d74SDmitry Torokhov const char *driver_name = ""; 1982360e4aaSDavid Brownell int active = 0; 1991da177e4SLinus Torvalds 2001da177e4SLinus Torvalds if (start > end) 2011da177e4SLinus Torvalds return start; 202376c0d3fSGreg Kroah-Hartman desc = &intfc->altsetting[setno].desc; 2032360e4aaSDavid Brownell if (iface) { 2041da177e4SLinus Torvalds driver_name = (iface->dev.driver 2051da177e4SLinus Torvalds ? iface->dev.driver->name 2061da177e4SLinus Torvalds : "(none)"); 2072360e4aaSDavid Brownell active = (desc == &iface->cur_altsetting->desc); 2082360e4aaSDavid Brownell } 2091da177e4SLinus Torvalds start += sprintf(start, format_iface, 2102360e4aaSDavid Brownell active ? '*' : ' ', /* mark active altsetting */ 2111da177e4SLinus Torvalds desc->bInterfaceNumber, 2121da177e4SLinus Torvalds desc->bAlternateSetting, 2131da177e4SLinus Torvalds desc->bNumEndpoints, 2141da177e4SLinus Torvalds desc->bInterfaceClass, 2151da177e4SLinus Torvalds class_decode(desc->bInterfaceClass), 2161da177e4SLinus Torvalds desc->bInterfaceSubClass, 2171da177e4SLinus Torvalds desc->bInterfaceProtocol, 2181da177e4SLinus Torvalds driver_name); 2191da177e4SLinus Torvalds return start; 2201da177e4SLinus Torvalds } 2211da177e4SLinus Torvalds 222376c0d3fSGreg Kroah-Hartman static char *usb_dump_interface(int speed, char *start, char *end, 2231da177e4SLinus Torvalds const struct usb_interface_cache *intfc, 224376c0d3fSGreg Kroah-Hartman const struct usb_interface *iface, int setno) 225376c0d3fSGreg Kroah-Hartman { 2261da177e4SLinus Torvalds const struct usb_host_interface *desc = &intfc->altsetting[setno]; 2271da177e4SLinus Torvalds int i; 2281da177e4SLinus Torvalds 2291da177e4SLinus Torvalds start = usb_dump_interface_descriptor(start, end, intfc, iface, setno); 2301da177e4SLinus Torvalds for (i = 0; i < desc->desc.bNumEndpoints; i++) { 2311da177e4SLinus Torvalds if (start > end) 2321da177e4SLinus Torvalds return start; 2331da177e4SLinus Torvalds start = usb_dump_endpoint_descriptor(speed, 2341da177e4SLinus Torvalds start, end, &desc->endpoint[i].desc); 2351da177e4SLinus Torvalds } 2361da177e4SLinus Torvalds return start; 2371da177e4SLinus Torvalds } 2381da177e4SLinus Torvalds 239165fe97eSCraig W. Nadler static char *usb_dump_iad_descriptor(char *start, char *end, 240165fe97eSCraig W. Nadler const struct usb_interface_assoc_descriptor *iad) 241165fe97eSCraig W. Nadler { 242165fe97eSCraig W. Nadler if (start > end) 243165fe97eSCraig W. Nadler return start; 244165fe97eSCraig W. Nadler start += sprintf(start, format_iad, 245165fe97eSCraig W. Nadler iad->bFirstInterface, 246165fe97eSCraig W. Nadler iad->bInterfaceCount, 247165fe97eSCraig W. Nadler iad->bFunctionClass, 248165fe97eSCraig W. Nadler class_decode(iad->bFunctionClass), 249165fe97eSCraig W. Nadler iad->bFunctionSubClass, 250165fe97eSCraig W. Nadler iad->bFunctionProtocol); 251165fe97eSCraig W. Nadler return start; 252165fe97eSCraig W. Nadler } 253165fe97eSCraig W. Nadler 2541da177e4SLinus Torvalds /* TBD: 2551da177e4SLinus Torvalds * 0. TBDs 2561da177e4SLinus Torvalds * 1. marking active interface altsettings (code lists all, but should mark 2571da177e4SLinus Torvalds * which ones are active, if any) 2581da177e4SLinus Torvalds */ 259376c0d3fSGreg Kroah-Hartman static char *usb_dump_config_descriptor(char *start, char *end, 260376c0d3fSGreg Kroah-Hartman const struct usb_config_descriptor *desc, 2618d8479dbSSebastian Andrzej Siewior int active, int speed) 2621da177e4SLinus Torvalds { 2638d8479dbSSebastian Andrzej Siewior int mul; 2648d8479dbSSebastian Andrzej Siewior 2651da177e4SLinus Torvalds if (start > end) 2661da177e4SLinus Torvalds return start; 2678a1b2725SMathias Nyman if (speed >= USB_SPEED_SUPER) 2688d8479dbSSebastian Andrzej Siewior mul = 8; 2698d8479dbSSebastian Andrzej Siewior else 2708d8479dbSSebastian Andrzej Siewior mul = 2; 2711da177e4SLinus Torvalds start += sprintf(start, format_config, 272376c0d3fSGreg Kroah-Hartman /* mark active/actual/current cfg. */ 273376c0d3fSGreg Kroah-Hartman active ? '*' : ' ', 2741da177e4SLinus Torvalds desc->bNumInterfaces, 2751da177e4SLinus Torvalds desc->bConfigurationValue, 2761da177e4SLinus Torvalds desc->bmAttributes, 2778d8479dbSSebastian Andrzej Siewior desc->bMaxPower * mul); 2781da177e4SLinus Torvalds return start; 2791da177e4SLinus Torvalds } 2801da177e4SLinus Torvalds 281376c0d3fSGreg Kroah-Hartman static char *usb_dump_config(int speed, char *start, char *end, 282376c0d3fSGreg Kroah-Hartman const struct usb_host_config *config, int active) 2831da177e4SLinus Torvalds { 2841da177e4SLinus Torvalds int i, j; 2851da177e4SLinus Torvalds struct usb_interface_cache *intfc; 2861da177e4SLinus Torvalds struct usb_interface *interface; 2871da177e4SLinus Torvalds 2881da177e4SLinus Torvalds if (start > end) 2891da177e4SLinus Torvalds return start; 290376c0d3fSGreg Kroah-Hartman if (!config) 291376c0d3fSGreg Kroah-Hartman /* getting these some in 2.3.7; none in 2.3.6 */ 2921da177e4SLinus Torvalds return start + sprintf(start, "(null Cfg. desc.)\n"); 2938d8479dbSSebastian Andrzej Siewior start = usb_dump_config_descriptor(start, end, &config->desc, active, 2948d8479dbSSebastian Andrzej Siewior speed); 295165fe97eSCraig W. Nadler for (i = 0; i < USB_MAXIADS; i++) { 296165fe97eSCraig W. Nadler if (config->intf_assoc[i] == NULL) 297165fe97eSCraig W. Nadler break; 298165fe97eSCraig W. Nadler start = usb_dump_iad_descriptor(start, end, 299165fe97eSCraig W. Nadler config->intf_assoc[i]); 300165fe97eSCraig W. Nadler } 3011da177e4SLinus Torvalds for (i = 0; i < config->desc.bNumInterfaces; i++) { 3021da177e4SLinus Torvalds intfc = config->intf_cache[i]; 3031da177e4SLinus Torvalds interface = config->interface[i]; 3041da177e4SLinus Torvalds for (j = 0; j < intfc->num_altsetting; j++) { 3051da177e4SLinus Torvalds if (start > end) 3061da177e4SLinus Torvalds return start; 3071da177e4SLinus Torvalds start = usb_dump_interface(speed, 3081da177e4SLinus Torvalds start, end, intfc, interface, j); 3091da177e4SLinus Torvalds } 3101da177e4SLinus Torvalds } 3111da177e4SLinus Torvalds return start; 3121da177e4SLinus Torvalds } 3131da177e4SLinus Torvalds 3141da177e4SLinus Torvalds /* 3151da177e4SLinus Torvalds * Dump the different USB descriptors. 3161da177e4SLinus Torvalds */ 317376c0d3fSGreg Kroah-Hartman static char *usb_dump_device_descriptor(char *start, char *end, 318376c0d3fSGreg Kroah-Hartman const struct usb_device_descriptor *desc) 3191da177e4SLinus Torvalds { 3201da177e4SLinus Torvalds u16 bcdUSB = le16_to_cpu(desc->bcdUSB); 3211da177e4SLinus Torvalds u16 bcdDevice = le16_to_cpu(desc->bcdDevice); 3221da177e4SLinus Torvalds 3231da177e4SLinus Torvalds if (start > end) 3241da177e4SLinus Torvalds return start; 3251da177e4SLinus Torvalds start += sprintf(start, format_device1, 3261da177e4SLinus Torvalds bcdUSB >> 8, bcdUSB & 0xff, 3271da177e4SLinus Torvalds desc->bDeviceClass, 3281da177e4SLinus Torvalds class_decode(desc->bDeviceClass), 3291da177e4SLinus Torvalds desc->bDeviceSubClass, 3301da177e4SLinus Torvalds desc->bDeviceProtocol, 3311da177e4SLinus Torvalds desc->bMaxPacketSize0, 3321da177e4SLinus Torvalds desc->bNumConfigurations); 3331da177e4SLinus Torvalds if (start > end) 3341da177e4SLinus Torvalds return start; 3351da177e4SLinus Torvalds start += sprintf(start, format_device2, 3361da177e4SLinus Torvalds le16_to_cpu(desc->idVendor), 3371da177e4SLinus Torvalds le16_to_cpu(desc->idProduct), 3381da177e4SLinus Torvalds bcdDevice >> 8, bcdDevice & 0xff); 3391da177e4SLinus Torvalds return start; 3401da177e4SLinus Torvalds } 3411da177e4SLinus Torvalds 3421da177e4SLinus Torvalds /* 3431da177e4SLinus Torvalds * Dump the different strings that this device holds. 3441da177e4SLinus Torvalds */ 345376c0d3fSGreg Kroah-Hartman static char *usb_dump_device_strings(char *start, char *end, 346376c0d3fSGreg Kroah-Hartman struct usb_device *dev) 3471da177e4SLinus Torvalds { 3481da177e4SLinus Torvalds if (start > end) 3491da177e4SLinus Torvalds return start; 3501da177e4SLinus Torvalds if (dev->manufacturer) 351376c0d3fSGreg Kroah-Hartman start += sprintf(start, format_string_manufacturer, 352376c0d3fSGreg Kroah-Hartman dev->manufacturer); 3531da177e4SLinus Torvalds if (start > end) 3541da177e4SLinus Torvalds goto out; 3551da177e4SLinus Torvalds if (dev->product) 3561da177e4SLinus Torvalds start += sprintf(start, format_string_product, dev->product); 3571da177e4SLinus Torvalds if (start > end) 3581da177e4SLinus Torvalds goto out; 3591da177e4SLinus Torvalds #ifdef ALLOW_SERIAL_NUMBER 3601da177e4SLinus Torvalds if (dev->serial) 361376c0d3fSGreg Kroah-Hartman start += sprintf(start, format_string_serialnumber, 362376c0d3fSGreg Kroah-Hartman dev->serial); 3631da177e4SLinus Torvalds #endif 3641da177e4SLinus Torvalds out: 3651da177e4SLinus Torvalds return start; 3661da177e4SLinus Torvalds } 3671da177e4SLinus Torvalds 3681da177e4SLinus Torvalds static char *usb_dump_desc(char *start, char *end, struct usb_device *dev) 3691da177e4SLinus Torvalds { 3701da177e4SLinus Torvalds int i; 3711da177e4SLinus Torvalds 3721da177e4SLinus Torvalds if (start > end) 3731da177e4SLinus Torvalds return start; 3741da177e4SLinus Torvalds 3751da177e4SLinus Torvalds start = usb_dump_device_descriptor(start, end, &dev->descriptor); 3761da177e4SLinus Torvalds 3771da177e4SLinus Torvalds if (start > end) 3781da177e4SLinus Torvalds return start; 3791da177e4SLinus Torvalds 3801da177e4SLinus Torvalds start = usb_dump_device_strings(start, end, dev); 3811da177e4SLinus Torvalds 3821da177e4SLinus Torvalds for (i = 0; i < dev->descriptor.bNumConfigurations; i++) { 3831da177e4SLinus Torvalds if (start > end) 3841da177e4SLinus Torvalds return start; 3851da177e4SLinus Torvalds start = usb_dump_config(dev->speed, 3861da177e4SLinus Torvalds start, end, dev->config + i, 3871da177e4SLinus Torvalds /* active ? */ 3881da177e4SLinus Torvalds (dev->config + i) == dev->actconfig); 3891da177e4SLinus Torvalds } 3901da177e4SLinus Torvalds return start; 3911da177e4SLinus Torvalds } 3921da177e4SLinus Torvalds 3931da177e4SLinus Torvalds 3941da177e4SLinus Torvalds #ifdef PROC_EXTRA /* TBD: may want to add this code later */ 3951da177e4SLinus Torvalds 396376c0d3fSGreg Kroah-Hartman static char *usb_dump_hub_descriptor(char *start, char *end, 397376c0d3fSGreg Kroah-Hartman const struct usb_hub_descriptor *desc) 3981da177e4SLinus Torvalds { 3991da177e4SLinus Torvalds int leng = USB_DT_HUB_NONVAR_SIZE; 4001da177e4SLinus Torvalds unsigned char *ptr = (unsigned char *)desc; 4011da177e4SLinus Torvalds 4021da177e4SLinus Torvalds if (start > end) 4031da177e4SLinus Torvalds return start; 4041da177e4SLinus Torvalds start += sprintf(start, "Interface:"); 4051da177e4SLinus Torvalds while (leng && start <= end) { 4061da177e4SLinus Torvalds start += sprintf(start, " %02x", *ptr); 4071da177e4SLinus Torvalds ptr++; leng--; 4081da177e4SLinus Torvalds } 4091da177e4SLinus Torvalds *start++ = '\n'; 4101da177e4SLinus Torvalds return start; 4111da177e4SLinus Torvalds } 4121da177e4SLinus Torvalds 413376c0d3fSGreg Kroah-Hartman static char *usb_dump_string(char *start, char *end, 414376c0d3fSGreg Kroah-Hartman const struct usb_device *dev, char *id, int index) 4151da177e4SLinus Torvalds { 4161da177e4SLinus Torvalds if (start > end) 4171da177e4SLinus Torvalds return start; 4181da177e4SLinus Torvalds start += sprintf(start, "Interface:"); 419376c0d3fSGreg Kroah-Hartman if (index <= dev->maxstring && dev->stringindex && 420376c0d3fSGreg Kroah-Hartman dev->stringindex[index]) 421376c0d3fSGreg Kroah-Hartman start += sprintf(start, "%s: %.100s ", id, 422376c0d3fSGreg Kroah-Hartman dev->stringindex[index]); 4231da177e4SLinus Torvalds return start; 4241da177e4SLinus Torvalds } 4251da177e4SLinus Torvalds 4261da177e4SLinus Torvalds #endif /* PROC_EXTRA */ 4271da177e4SLinus Torvalds 4281da177e4SLinus Torvalds /*****************************************************************/ 4291da177e4SLinus Torvalds 4301da177e4SLinus Torvalds /* This is a recursive function. Parameters: 4311da177e4SLinus Torvalds * buffer - the user-space buffer to write data into 4321da177e4SLinus Torvalds * nbytes - the maximum number of bytes to write 4331da177e4SLinus Torvalds * skip_bytes - the number of bytes to skip before writing anything 4341da177e4SLinus Torvalds * file_offset - the offset into the devices file on completion 4351da177e4SLinus Torvalds * The caller must own the device lock. 4361da177e4SLinus Torvalds */ 437376c0d3fSGreg Kroah-Hartman static ssize_t usb_device_dump(char __user **buffer, size_t *nbytes, 438376c0d3fSGreg Kroah-Hartman loff_t *skip_bytes, loff_t *file_offset, 439376c0d3fSGreg Kroah-Hartman struct usb_device *usbdev, struct usb_bus *bus, 440376c0d3fSGreg Kroah-Hartman int level, int index, int count) 4411da177e4SLinus Torvalds { 4421da177e4SLinus Torvalds int chix; 4431da177e4SLinus Torvalds int ret, cnt = 0; 4441da177e4SLinus Torvalds int parent_devnum = 0; 4451da177e4SLinus Torvalds char *pages_start, *data_end, *speed; 4461da177e4SLinus Torvalds unsigned int length; 4471da177e4SLinus Torvalds ssize_t total_written = 0; 448ff823c79SLan Tianyu struct usb_device *childdev = NULL; 4491da177e4SLinus Torvalds 4501da177e4SLinus Torvalds /* don't bother with anything else if we're not writing any data */ 4511da177e4SLinus Torvalds if (*nbytes <= 0) 4521da177e4SLinus Torvalds return 0; 4531da177e4SLinus Torvalds 4541da177e4SLinus Torvalds if (level > MAX_TOPO_LEVEL) 4551da177e4SLinus Torvalds return 0; 456376c0d3fSGreg Kroah-Hartman /* allocate 2^1 pages = 8K (on i386); 457376c0d3fSGreg Kroah-Hartman * should be more than enough for one device */ 458acbe2febSOliver Neukum pages_start = (char *)__get_free_pages(GFP_NOIO, 1); 459376c0d3fSGreg Kroah-Hartman if (!pages_start) 4601da177e4SLinus Torvalds return -ENOMEM; 4611da177e4SLinus Torvalds 4621da177e4SLinus Torvalds if (usbdev->parent && usbdev->parent->devnum != -1) 4631da177e4SLinus Torvalds parent_devnum = usbdev->parent->devnum; 4641da177e4SLinus Torvalds /* 4651da177e4SLinus Torvalds * So the root hub's parent is 0 and any device that is 4661da177e4SLinus Torvalds * plugged into the root hub has a parent of 0. 4671da177e4SLinus Torvalds */ 4681da177e4SLinus Torvalds switch (usbdev->speed) { 4691da177e4SLinus Torvalds case USB_SPEED_LOW: 4701da177e4SLinus Torvalds speed = "1.5"; break; 4711da177e4SLinus Torvalds case USB_SPEED_UNKNOWN: /* usb 1.1 root hub code */ 4721da177e4SLinus Torvalds case USB_SPEED_FULL: 4731da177e4SLinus Torvalds speed = "12"; break; 474834e2312SAlan Stern case USB_SPEED_WIRELESS: /* Wireless has no real fixed speed */ 4751da177e4SLinus Torvalds case USB_SPEED_HIGH: 4761da177e4SLinus Torvalds speed = "480"; break; 477834e2312SAlan Stern case USB_SPEED_SUPER: 478834e2312SAlan Stern speed = "5000"; break; 4798a1b2725SMathias Nyman case USB_SPEED_SUPER_PLUS: 4808a1b2725SMathias Nyman speed = "10000"; break; 4811da177e4SLinus Torvalds default: 4821da177e4SLinus Torvalds speed = "??"; 4831da177e4SLinus Torvalds } 4841da177e4SLinus Torvalds data_end = pages_start + sprintf(pages_start, format_topo, 4851da177e4SLinus Torvalds bus->busnum, level, parent_devnum, 4861da177e4SLinus Torvalds index, count, usbdev->devnum, 4871da177e4SLinus Torvalds speed, usbdev->maxchild); 4881da177e4SLinus Torvalds /* 4891da177e4SLinus Torvalds * level = topology-tier level; 4901da177e4SLinus Torvalds * parent_devnum = parent device number; 4911da177e4SLinus Torvalds * index = parent's connector number; 4921da177e4SLinus Torvalds * count = device count at this level 4931da177e4SLinus Torvalds */ 4941da177e4SLinus Torvalds /* If this is the root hub, display the bandwidth information */ 4951da177e4SLinus Torvalds if (level == 0) { 4961da177e4SLinus Torvalds int max; 4971da177e4SLinus Torvalds 4982868a2b1SDmitry Torokhov /* super/high speed reserves 80%, full/low reserves 90% */ 4992868a2b1SDmitry Torokhov if (usbdev->speed == USB_SPEED_HIGH || 5008a1b2725SMathias Nyman usbdev->speed >= USB_SPEED_SUPER) 5011da177e4SLinus Torvalds max = 800; 5021da177e4SLinus Torvalds else 5031da177e4SLinus Torvalds max = FRAME_TIME_MAX_USECS_ALLOC; 5041da177e4SLinus Torvalds 5051da177e4SLinus Torvalds /* report "average" periodic allocation over a microsecond. 5061da177e4SLinus Torvalds * the schedules are actually bursty, HCDs need to deal with 5071da177e4SLinus Torvalds * that and just compute/report this average. 5081da177e4SLinus Torvalds */ 5091da177e4SLinus Torvalds data_end += sprintf(data_end, format_bandwidth, 5101da177e4SLinus Torvalds bus->bandwidth_allocated, max, 5111da177e4SLinus Torvalds (100 * bus->bandwidth_allocated + max / 2) 5121da177e4SLinus Torvalds / max, 5131da177e4SLinus Torvalds bus->bandwidth_int_reqs, 5141da177e4SLinus Torvalds bus->bandwidth_isoc_reqs); 5151da177e4SLinus Torvalds 5161da177e4SLinus Torvalds } 517376c0d3fSGreg Kroah-Hartman data_end = usb_dump_desc(data_end, pages_start + (2 * PAGE_SIZE) - 256, 518376c0d3fSGreg Kroah-Hartman usbdev); 5191da177e4SLinus Torvalds 5201da177e4SLinus Torvalds if (data_end > (pages_start + (2 * PAGE_SIZE) - 256)) 5211da177e4SLinus Torvalds data_end += sprintf(data_end, "(truncated)\n"); 5221da177e4SLinus Torvalds 5231da177e4SLinus Torvalds length = data_end - pages_start; 5241da177e4SLinus Torvalds /* if we can start copying some data to the user */ 5251da177e4SLinus Torvalds if (length > *skip_bytes) { 5261da177e4SLinus Torvalds length -= *skip_bytes; 5271da177e4SLinus Torvalds if (length > *nbytes) 5281da177e4SLinus Torvalds length = *nbytes; 5291da177e4SLinus Torvalds if (copy_to_user(*buffer, pages_start + *skip_bytes, length)) { 5301da177e4SLinus Torvalds free_pages((unsigned long)pages_start, 1); 5311da177e4SLinus Torvalds return -EFAULT; 5321da177e4SLinus Torvalds } 5331da177e4SLinus Torvalds *nbytes -= length; 5341da177e4SLinus Torvalds *file_offset += length; 5351da177e4SLinus Torvalds total_written += length; 5361da177e4SLinus Torvalds *buffer += length; 5371da177e4SLinus Torvalds *skip_bytes = 0; 5381da177e4SLinus Torvalds } else 5391da177e4SLinus Torvalds *skip_bytes -= length; 5401da177e4SLinus Torvalds 5411da177e4SLinus Torvalds free_pages((unsigned long)pages_start, 1); 5421da177e4SLinus Torvalds 5431da177e4SLinus Torvalds /* Now look at all of this device's children. */ 544ff823c79SLan Tianyu usb_hub_for_each_child(usbdev, chix, childdev) { 5459ad3d6ccSAlan Stern usb_lock_device(childdev); 546376c0d3fSGreg Kroah-Hartman ret = usb_device_dump(buffer, nbytes, skip_bytes, 547376c0d3fSGreg Kroah-Hartman file_offset, childdev, bus, 548ff823c79SLan Tianyu level + 1, chix - 1, ++cnt); 5499ad3d6ccSAlan Stern usb_unlock_device(childdev); 5501da177e4SLinus Torvalds if (ret == -EFAULT) 5511da177e4SLinus Torvalds return total_written; 5521da177e4SLinus Torvalds total_written += ret; 5531da177e4SLinus Torvalds } 5541da177e4SLinus Torvalds return total_written; 5551da177e4SLinus Torvalds } 5561da177e4SLinus Torvalds 557376c0d3fSGreg Kroah-Hartman static ssize_t usb_device_read(struct file *file, char __user *buf, 558376c0d3fSGreg Kroah-Hartman size_t nbytes, loff_t *ppos) 5591da177e4SLinus Torvalds { 5601da177e4SLinus Torvalds struct usb_bus *bus; 5611da177e4SLinus Torvalds ssize_t ret, total_written = 0; 5621da177e4SLinus Torvalds loff_t skip_bytes = *ppos; 5635363de75SHeiner Kallweit int id; 5641da177e4SLinus Torvalds 5651da177e4SLinus Torvalds if (*ppos < 0) 5661da177e4SLinus Torvalds return -EINVAL; 5671da177e4SLinus Torvalds if (nbytes <= 0) 5681da177e4SLinus Torvalds return 0; 5691da177e4SLinus Torvalds 570a4b5d606SHeiner Kallweit mutex_lock(&usb_bus_idr_lock); 5711da177e4SLinus Torvalds /* print devices for all busses */ 5725363de75SHeiner Kallweit idr_for_each_entry(&usb_bus_idr, bus, id) { 5731da177e4SLinus Torvalds /* recurse through all children of the root hub */ 5740a231403SAlan Stern if (!bus_to_hcd(bus)->rh_registered) 5751da177e4SLinus Torvalds continue; 5761da177e4SLinus Torvalds usb_lock_device(bus->root_hub); 577376c0d3fSGreg Kroah-Hartman ret = usb_device_dump(&buf, &nbytes, &skip_bytes, ppos, 578376c0d3fSGreg Kroah-Hartman bus->root_hub, bus, 0, 0, 0); 5791da177e4SLinus Torvalds usb_unlock_device(bus->root_hub); 5801da177e4SLinus Torvalds if (ret < 0) { 581a4b5d606SHeiner Kallweit mutex_unlock(&usb_bus_idr_lock); 5821da177e4SLinus Torvalds return ret; 5831da177e4SLinus Torvalds } 5841da177e4SLinus Torvalds total_written += ret; 5851da177e4SLinus Torvalds } 586a4b5d606SHeiner Kallweit mutex_unlock(&usb_bus_idr_lock); 5871da177e4SLinus Torvalds return total_written; 5881da177e4SLinus Torvalds } 5891da177e4SLinus Torvalds 590066202ddSLuiz Fernando N. Capitulino const struct file_operations usbfs_devices_fops = { 591b25472f9SAl Viro .llseek = no_seek_end_llseek, 5921da177e4SLinus Torvalds .read = usb_device_read, 5931da177e4SLinus Torvalds }; 594