162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * devices.c 462306a36Sopenharmony_ci * (C) Copyright 1999 Randy Dunlap. 562306a36Sopenharmony_ci * (C) Copyright 1999,2000 Thomas Sailer <sailer@ife.ee.ethz.ch>. 662306a36Sopenharmony_ci * (proc file per device) 762306a36Sopenharmony_ci * (C) Copyright 1999 Deti Fliegl (new USB architecture) 862306a36Sopenharmony_ci * 962306a36Sopenharmony_ci ************************************************************* 1062306a36Sopenharmony_ci * 1162306a36Sopenharmony_ci * <mountpoint>/devices contains USB topology, device, config, class, 1262306a36Sopenharmony_ci * interface, & endpoint data. 1362306a36Sopenharmony_ci * 1462306a36Sopenharmony_ci * I considered using /dev/bus/usb/device# for each device 1562306a36Sopenharmony_ci * as it is attached or detached, but I didn't like this for some 1662306a36Sopenharmony_ci * reason -- maybe it's just too deep of a directory structure. 1762306a36Sopenharmony_ci * I also don't like looking in multiple places to gather and view 1862306a36Sopenharmony_ci * the data. Having only one file for ./devices also prevents race 1962306a36Sopenharmony_ci * conditions that could arise if a program was reading device info 2062306a36Sopenharmony_ci * for devices that are being removed (unplugged). (That is, the 2162306a36Sopenharmony_ci * program may find a directory for devnum_12 then try to open it, 2262306a36Sopenharmony_ci * but it was just unplugged, so the directory is now deleted. 2362306a36Sopenharmony_ci * But programs would just have to be prepared for situations like 2462306a36Sopenharmony_ci * this in any plug-and-play environment.) 2562306a36Sopenharmony_ci * 2662306a36Sopenharmony_ci * 1999-12-16: Thomas Sailer <sailer@ife.ee.ethz.ch> 2762306a36Sopenharmony_ci * Converted the whole proc stuff to real 2862306a36Sopenharmony_ci * read methods. Now not the whole device list needs to fit 2962306a36Sopenharmony_ci * into one page, only the device list for one bus. 3062306a36Sopenharmony_ci * Added a poll method to /sys/kernel/debug/usb/devices, to wake 3162306a36Sopenharmony_ci * up an eventual usbd 3262306a36Sopenharmony_ci * 2000-01-04: Thomas Sailer <sailer@ife.ee.ethz.ch> 3362306a36Sopenharmony_ci * Turned into its own filesystem 3462306a36Sopenharmony_ci * 2000-07-05: Ashley Montanaro <ashley@compsoc.man.ac.uk> 3562306a36Sopenharmony_ci * Converted file reading routine to dump to buffer once 3662306a36Sopenharmony_ci * per device, not per bus 3762306a36Sopenharmony_ci */ 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_ci#include <linux/fs.h> 4062306a36Sopenharmony_ci#include <linux/mm.h> 4162306a36Sopenharmony_ci#include <linux/gfp.h> 4262306a36Sopenharmony_ci#include <linux/usb.h> 4362306a36Sopenharmony_ci#include <linux/usbdevice_fs.h> 4462306a36Sopenharmony_ci#include <linux/usb/hcd.h> 4562306a36Sopenharmony_ci#include <linux/mutex.h> 4662306a36Sopenharmony_ci#include <linux/uaccess.h> 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_ci#include "usb.h" 4962306a36Sopenharmony_ci 5062306a36Sopenharmony_ci/* Define ALLOW_SERIAL_NUMBER if you want to see the serial number of devices */ 5162306a36Sopenharmony_ci#define ALLOW_SERIAL_NUMBER 5262306a36Sopenharmony_ci 5362306a36Sopenharmony_cistatic const char format_topo[] = 5462306a36Sopenharmony_ci/* T: Bus=dd Lev=dd Prnt=dd Port=dd Cnt=dd Dev#=ddd Spd=dddd MxCh=dd */ 5562306a36Sopenharmony_ci"\nT: Bus=%2.2d Lev=%2.2d Prnt=%2.2d Port=%2.2d Cnt=%2.2d Dev#=%3d Spd=%-4s MxCh=%2d\n"; 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_cistatic const char format_string_manufacturer[] = 5862306a36Sopenharmony_ci/* S: Manufacturer=xxxx */ 5962306a36Sopenharmony_ci "S: Manufacturer=%.100s\n"; 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_cistatic const char format_string_product[] = 6262306a36Sopenharmony_ci/* S: Product=xxxx */ 6362306a36Sopenharmony_ci "S: Product=%.100s\n"; 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_ci#ifdef ALLOW_SERIAL_NUMBER 6662306a36Sopenharmony_cistatic const char format_string_serialnumber[] = 6762306a36Sopenharmony_ci/* S: SerialNumber=xxxx */ 6862306a36Sopenharmony_ci "S: SerialNumber=%.100s\n"; 6962306a36Sopenharmony_ci#endif 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_cistatic const char format_bandwidth[] = 7262306a36Sopenharmony_ci/* B: Alloc=ddd/ddd us (xx%), #Int=ddd, #Iso=ddd */ 7362306a36Sopenharmony_ci "B: Alloc=%3d/%3d us (%2d%%), #Int=%3d, #Iso=%3d\n"; 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_cistatic const char format_device1[] = 7662306a36Sopenharmony_ci/* D: Ver=xx.xx Cls=xx(sssss) Sub=xx Prot=xx MxPS=dd #Cfgs=dd */ 7762306a36Sopenharmony_ci "D: Ver=%2x.%02x Cls=%02x(%-5s) Sub=%02x Prot=%02x MxPS=%2d #Cfgs=%3d\n"; 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_cistatic const char format_device2[] = 8062306a36Sopenharmony_ci/* P: Vendor=xxxx ProdID=xxxx Rev=xx.xx */ 8162306a36Sopenharmony_ci "P: Vendor=%04x ProdID=%04x Rev=%2x.%02x\n"; 8262306a36Sopenharmony_ci 8362306a36Sopenharmony_cistatic const char format_config[] = 8462306a36Sopenharmony_ci/* C: #Ifs=dd Cfg#=dd Atr=xx MPwr=dddmA */ 8562306a36Sopenharmony_ci "C:%c #Ifs=%2d Cfg#=%2d Atr=%02x MxPwr=%3dmA\n"; 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_cistatic const char format_iad[] = 8862306a36Sopenharmony_ci/* A: FirstIf#=dd IfCount=dd Cls=xx(sssss) Sub=xx Prot=xx */ 8962306a36Sopenharmony_ci "A: FirstIf#=%2d IfCount=%2d Cls=%02x(%-5s) Sub=%02x Prot=%02x\n"; 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_cistatic const char format_iface[] = 9262306a36Sopenharmony_ci/* I: If#=dd Alt=dd #EPs=dd Cls=xx(sssss) Sub=xx Prot=xx Driver=xxxx*/ 9362306a36Sopenharmony_ci "I:%c If#=%2d Alt=%2d #EPs=%2d Cls=%02x(%-5s) Sub=%02x Prot=%02x Driver=%s\n"; 9462306a36Sopenharmony_ci 9562306a36Sopenharmony_cistatic const char format_endpt[] = 9662306a36Sopenharmony_ci/* E: Ad=xx(s) Atr=xx(ssss) MxPS=dddd Ivl=D?s */ 9762306a36Sopenharmony_ci "E: Ad=%02x(%c) Atr=%02x(%-4s) MxPS=%4d Ivl=%d%cs\n"; 9862306a36Sopenharmony_ci 9962306a36Sopenharmony_cistruct class_info { 10062306a36Sopenharmony_ci int class; 10162306a36Sopenharmony_ci char *class_name; 10262306a36Sopenharmony_ci}; 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_cistatic const struct class_info clas_info[] = { 10562306a36Sopenharmony_ci /* max. 5 chars. per name string */ 10662306a36Sopenharmony_ci {USB_CLASS_PER_INTERFACE, ">ifc"}, 10762306a36Sopenharmony_ci {USB_CLASS_AUDIO, "audio"}, 10862306a36Sopenharmony_ci {USB_CLASS_COMM, "comm."}, 10962306a36Sopenharmony_ci {USB_CLASS_HID, "HID"}, 11062306a36Sopenharmony_ci {USB_CLASS_PHYSICAL, "PID"}, 11162306a36Sopenharmony_ci {USB_CLASS_STILL_IMAGE, "still"}, 11262306a36Sopenharmony_ci {USB_CLASS_PRINTER, "print"}, 11362306a36Sopenharmony_ci {USB_CLASS_MASS_STORAGE, "stor."}, 11462306a36Sopenharmony_ci {USB_CLASS_HUB, "hub"}, 11562306a36Sopenharmony_ci {USB_CLASS_CDC_DATA, "data"}, 11662306a36Sopenharmony_ci {USB_CLASS_CSCID, "scard"}, 11762306a36Sopenharmony_ci {USB_CLASS_CONTENT_SEC, "c-sec"}, 11862306a36Sopenharmony_ci {USB_CLASS_VIDEO, "video"}, 11962306a36Sopenharmony_ci {USB_CLASS_PERSONAL_HEALTHCARE, "perhc"}, 12062306a36Sopenharmony_ci {USB_CLASS_AUDIO_VIDEO, "av"}, 12162306a36Sopenharmony_ci {USB_CLASS_BILLBOARD, "blbrd"}, 12262306a36Sopenharmony_ci {USB_CLASS_USB_TYPE_C_BRIDGE, "bridg"}, 12362306a36Sopenharmony_ci {USB_CLASS_WIRELESS_CONTROLLER, "wlcon"}, 12462306a36Sopenharmony_ci {USB_CLASS_MISC, "misc"}, 12562306a36Sopenharmony_ci {USB_CLASS_APP_SPEC, "app."}, 12662306a36Sopenharmony_ci {USB_CLASS_VENDOR_SPEC, "vend."}, 12762306a36Sopenharmony_ci {-1, "unk."} /* leave as last */ 12862306a36Sopenharmony_ci}; 12962306a36Sopenharmony_ci 13062306a36Sopenharmony_ci/*****************************************************************/ 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_cistatic const char *class_decode(const int class) 13362306a36Sopenharmony_ci{ 13462306a36Sopenharmony_ci int ix; 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_ci for (ix = 0; clas_info[ix].class != -1; ix++) 13762306a36Sopenharmony_ci if (clas_info[ix].class == class) 13862306a36Sopenharmony_ci break; 13962306a36Sopenharmony_ci return clas_info[ix].class_name; 14062306a36Sopenharmony_ci} 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_cistatic char *usb_dump_endpoint_descriptor(int speed, char *start, char *end, 14362306a36Sopenharmony_ci const struct usb_endpoint_descriptor *desc) 14462306a36Sopenharmony_ci{ 14562306a36Sopenharmony_ci char dir, unit, *type; 14662306a36Sopenharmony_ci unsigned interval, bandwidth = 1; 14762306a36Sopenharmony_ci 14862306a36Sopenharmony_ci if (start > end) 14962306a36Sopenharmony_ci return start; 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_ci dir = usb_endpoint_dir_in(desc) ? 'I' : 'O'; 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_ci if (speed == USB_SPEED_HIGH) 15462306a36Sopenharmony_ci bandwidth = usb_endpoint_maxp_mult(desc); 15562306a36Sopenharmony_ci 15662306a36Sopenharmony_ci /* this isn't checking for illegal values */ 15762306a36Sopenharmony_ci switch (usb_endpoint_type(desc)) { 15862306a36Sopenharmony_ci case USB_ENDPOINT_XFER_CONTROL: 15962306a36Sopenharmony_ci type = "Ctrl"; 16062306a36Sopenharmony_ci dir = 'B'; /* ctrl is bidirectional */ 16162306a36Sopenharmony_ci break; 16262306a36Sopenharmony_ci case USB_ENDPOINT_XFER_ISOC: 16362306a36Sopenharmony_ci type = "Isoc"; 16462306a36Sopenharmony_ci break; 16562306a36Sopenharmony_ci case USB_ENDPOINT_XFER_BULK: 16662306a36Sopenharmony_ci type = "Bulk"; 16762306a36Sopenharmony_ci break; 16862306a36Sopenharmony_ci case USB_ENDPOINT_XFER_INT: 16962306a36Sopenharmony_ci type = "Int."; 17062306a36Sopenharmony_ci break; 17162306a36Sopenharmony_ci default: /* "can't happen" */ 17262306a36Sopenharmony_ci return start; 17362306a36Sopenharmony_ci } 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_ci interval = usb_decode_interval(desc, speed); 17662306a36Sopenharmony_ci if (interval % 1000) { 17762306a36Sopenharmony_ci unit = 'u'; 17862306a36Sopenharmony_ci } else { 17962306a36Sopenharmony_ci unit = 'm'; 18062306a36Sopenharmony_ci interval /= 1000; 18162306a36Sopenharmony_ci } 18262306a36Sopenharmony_ci 18362306a36Sopenharmony_ci start += sprintf(start, format_endpt, desc->bEndpointAddress, dir, 18462306a36Sopenharmony_ci desc->bmAttributes, type, 18562306a36Sopenharmony_ci usb_endpoint_maxp(desc) * 18662306a36Sopenharmony_ci bandwidth, 18762306a36Sopenharmony_ci interval, unit); 18862306a36Sopenharmony_ci return start; 18962306a36Sopenharmony_ci} 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_cistatic char *usb_dump_interface_descriptor(char *start, char *end, 19262306a36Sopenharmony_ci const struct usb_interface_cache *intfc, 19362306a36Sopenharmony_ci const struct usb_interface *iface, 19462306a36Sopenharmony_ci int setno) 19562306a36Sopenharmony_ci{ 19662306a36Sopenharmony_ci const struct usb_interface_descriptor *desc; 19762306a36Sopenharmony_ci const char *driver_name = ""; 19862306a36Sopenharmony_ci int active = 0; 19962306a36Sopenharmony_ci 20062306a36Sopenharmony_ci if (start > end) 20162306a36Sopenharmony_ci return start; 20262306a36Sopenharmony_ci desc = &intfc->altsetting[setno].desc; 20362306a36Sopenharmony_ci if (iface) { 20462306a36Sopenharmony_ci driver_name = (iface->dev.driver 20562306a36Sopenharmony_ci ? iface->dev.driver->name 20662306a36Sopenharmony_ci : "(none)"); 20762306a36Sopenharmony_ci active = (desc == &iface->cur_altsetting->desc); 20862306a36Sopenharmony_ci } 20962306a36Sopenharmony_ci start += sprintf(start, format_iface, 21062306a36Sopenharmony_ci active ? '*' : ' ', /* mark active altsetting */ 21162306a36Sopenharmony_ci desc->bInterfaceNumber, 21262306a36Sopenharmony_ci desc->bAlternateSetting, 21362306a36Sopenharmony_ci desc->bNumEndpoints, 21462306a36Sopenharmony_ci desc->bInterfaceClass, 21562306a36Sopenharmony_ci class_decode(desc->bInterfaceClass), 21662306a36Sopenharmony_ci desc->bInterfaceSubClass, 21762306a36Sopenharmony_ci desc->bInterfaceProtocol, 21862306a36Sopenharmony_ci driver_name); 21962306a36Sopenharmony_ci return start; 22062306a36Sopenharmony_ci} 22162306a36Sopenharmony_ci 22262306a36Sopenharmony_cistatic char *usb_dump_interface(int speed, char *start, char *end, 22362306a36Sopenharmony_ci const struct usb_interface_cache *intfc, 22462306a36Sopenharmony_ci const struct usb_interface *iface, int setno) 22562306a36Sopenharmony_ci{ 22662306a36Sopenharmony_ci const struct usb_host_interface *desc = &intfc->altsetting[setno]; 22762306a36Sopenharmony_ci int i; 22862306a36Sopenharmony_ci 22962306a36Sopenharmony_ci start = usb_dump_interface_descriptor(start, end, intfc, iface, setno); 23062306a36Sopenharmony_ci for (i = 0; i < desc->desc.bNumEndpoints; i++) { 23162306a36Sopenharmony_ci start = usb_dump_endpoint_descriptor(speed, 23262306a36Sopenharmony_ci start, end, &desc->endpoint[i].desc); 23362306a36Sopenharmony_ci } 23462306a36Sopenharmony_ci return start; 23562306a36Sopenharmony_ci} 23662306a36Sopenharmony_ci 23762306a36Sopenharmony_cistatic char *usb_dump_iad_descriptor(char *start, char *end, 23862306a36Sopenharmony_ci const struct usb_interface_assoc_descriptor *iad) 23962306a36Sopenharmony_ci{ 24062306a36Sopenharmony_ci if (start > end) 24162306a36Sopenharmony_ci return start; 24262306a36Sopenharmony_ci start += sprintf(start, format_iad, 24362306a36Sopenharmony_ci iad->bFirstInterface, 24462306a36Sopenharmony_ci iad->bInterfaceCount, 24562306a36Sopenharmony_ci iad->bFunctionClass, 24662306a36Sopenharmony_ci class_decode(iad->bFunctionClass), 24762306a36Sopenharmony_ci iad->bFunctionSubClass, 24862306a36Sopenharmony_ci iad->bFunctionProtocol); 24962306a36Sopenharmony_ci return start; 25062306a36Sopenharmony_ci} 25162306a36Sopenharmony_ci 25262306a36Sopenharmony_ci/* TBD: 25362306a36Sopenharmony_ci * 0. TBDs 25462306a36Sopenharmony_ci * 1. marking active interface altsettings (code lists all, but should mark 25562306a36Sopenharmony_ci * which ones are active, if any) 25662306a36Sopenharmony_ci */ 25762306a36Sopenharmony_cistatic char *usb_dump_config_descriptor(char *start, char *end, 25862306a36Sopenharmony_ci const struct usb_config_descriptor *desc, 25962306a36Sopenharmony_ci int active, int speed) 26062306a36Sopenharmony_ci{ 26162306a36Sopenharmony_ci int mul; 26262306a36Sopenharmony_ci 26362306a36Sopenharmony_ci if (start > end) 26462306a36Sopenharmony_ci return start; 26562306a36Sopenharmony_ci if (speed >= USB_SPEED_SUPER) 26662306a36Sopenharmony_ci mul = 8; 26762306a36Sopenharmony_ci else 26862306a36Sopenharmony_ci mul = 2; 26962306a36Sopenharmony_ci start += sprintf(start, format_config, 27062306a36Sopenharmony_ci /* mark active/actual/current cfg. */ 27162306a36Sopenharmony_ci active ? '*' : ' ', 27262306a36Sopenharmony_ci desc->bNumInterfaces, 27362306a36Sopenharmony_ci desc->bConfigurationValue, 27462306a36Sopenharmony_ci desc->bmAttributes, 27562306a36Sopenharmony_ci desc->bMaxPower * mul); 27662306a36Sopenharmony_ci return start; 27762306a36Sopenharmony_ci} 27862306a36Sopenharmony_ci 27962306a36Sopenharmony_cistatic char *usb_dump_config(int speed, char *start, char *end, 28062306a36Sopenharmony_ci const struct usb_host_config *config, int active) 28162306a36Sopenharmony_ci{ 28262306a36Sopenharmony_ci int i, j; 28362306a36Sopenharmony_ci struct usb_interface_cache *intfc; 28462306a36Sopenharmony_ci struct usb_interface *interface; 28562306a36Sopenharmony_ci 28662306a36Sopenharmony_ci if (start > end) 28762306a36Sopenharmony_ci return start; 28862306a36Sopenharmony_ci if (!config) 28962306a36Sopenharmony_ci /* getting these some in 2.3.7; none in 2.3.6 */ 29062306a36Sopenharmony_ci return start + sprintf(start, "(null Cfg. desc.)\n"); 29162306a36Sopenharmony_ci start = usb_dump_config_descriptor(start, end, &config->desc, active, 29262306a36Sopenharmony_ci speed); 29362306a36Sopenharmony_ci for (i = 0; i < USB_MAXIADS; i++) { 29462306a36Sopenharmony_ci if (config->intf_assoc[i] == NULL) 29562306a36Sopenharmony_ci break; 29662306a36Sopenharmony_ci start = usb_dump_iad_descriptor(start, end, 29762306a36Sopenharmony_ci config->intf_assoc[i]); 29862306a36Sopenharmony_ci } 29962306a36Sopenharmony_ci for (i = 0; i < config->desc.bNumInterfaces; i++) { 30062306a36Sopenharmony_ci intfc = config->intf_cache[i]; 30162306a36Sopenharmony_ci interface = config->interface[i]; 30262306a36Sopenharmony_ci for (j = 0; j < intfc->num_altsetting; j++) { 30362306a36Sopenharmony_ci start = usb_dump_interface(speed, 30462306a36Sopenharmony_ci start, end, intfc, interface, j); 30562306a36Sopenharmony_ci } 30662306a36Sopenharmony_ci } 30762306a36Sopenharmony_ci return start; 30862306a36Sopenharmony_ci} 30962306a36Sopenharmony_ci 31062306a36Sopenharmony_ci/* 31162306a36Sopenharmony_ci * Dump the different USB descriptors. 31262306a36Sopenharmony_ci */ 31362306a36Sopenharmony_cistatic char *usb_dump_device_descriptor(char *start, char *end, 31462306a36Sopenharmony_ci const struct usb_device_descriptor *desc) 31562306a36Sopenharmony_ci{ 31662306a36Sopenharmony_ci u16 bcdUSB = le16_to_cpu(desc->bcdUSB); 31762306a36Sopenharmony_ci u16 bcdDevice = le16_to_cpu(desc->bcdDevice); 31862306a36Sopenharmony_ci 31962306a36Sopenharmony_ci if (start > end) 32062306a36Sopenharmony_ci return start; 32162306a36Sopenharmony_ci start += sprintf(start, format_device1, 32262306a36Sopenharmony_ci bcdUSB >> 8, bcdUSB & 0xff, 32362306a36Sopenharmony_ci desc->bDeviceClass, 32462306a36Sopenharmony_ci class_decode(desc->bDeviceClass), 32562306a36Sopenharmony_ci desc->bDeviceSubClass, 32662306a36Sopenharmony_ci desc->bDeviceProtocol, 32762306a36Sopenharmony_ci desc->bMaxPacketSize0, 32862306a36Sopenharmony_ci desc->bNumConfigurations); 32962306a36Sopenharmony_ci if (start > end) 33062306a36Sopenharmony_ci return start; 33162306a36Sopenharmony_ci start += sprintf(start, format_device2, 33262306a36Sopenharmony_ci le16_to_cpu(desc->idVendor), 33362306a36Sopenharmony_ci le16_to_cpu(desc->idProduct), 33462306a36Sopenharmony_ci bcdDevice >> 8, bcdDevice & 0xff); 33562306a36Sopenharmony_ci return start; 33662306a36Sopenharmony_ci} 33762306a36Sopenharmony_ci 33862306a36Sopenharmony_ci/* 33962306a36Sopenharmony_ci * Dump the different strings that this device holds. 34062306a36Sopenharmony_ci */ 34162306a36Sopenharmony_cistatic char *usb_dump_device_strings(char *start, char *end, 34262306a36Sopenharmony_ci struct usb_device *dev) 34362306a36Sopenharmony_ci{ 34462306a36Sopenharmony_ci if (start > end) 34562306a36Sopenharmony_ci return start; 34662306a36Sopenharmony_ci if (dev->manufacturer) 34762306a36Sopenharmony_ci start += sprintf(start, format_string_manufacturer, 34862306a36Sopenharmony_ci dev->manufacturer); 34962306a36Sopenharmony_ci if (start > end) 35062306a36Sopenharmony_ci goto out; 35162306a36Sopenharmony_ci if (dev->product) 35262306a36Sopenharmony_ci start += sprintf(start, format_string_product, dev->product); 35362306a36Sopenharmony_ci if (start > end) 35462306a36Sopenharmony_ci goto out; 35562306a36Sopenharmony_ci#ifdef ALLOW_SERIAL_NUMBER 35662306a36Sopenharmony_ci if (dev->serial) 35762306a36Sopenharmony_ci start += sprintf(start, format_string_serialnumber, 35862306a36Sopenharmony_ci dev->serial); 35962306a36Sopenharmony_ci#endif 36062306a36Sopenharmony_ci out: 36162306a36Sopenharmony_ci return start; 36262306a36Sopenharmony_ci} 36362306a36Sopenharmony_ci 36462306a36Sopenharmony_cistatic char *usb_dump_desc(char *start, char *end, struct usb_device *dev) 36562306a36Sopenharmony_ci{ 36662306a36Sopenharmony_ci int i; 36762306a36Sopenharmony_ci 36862306a36Sopenharmony_ci start = usb_dump_device_descriptor(start, end, &dev->descriptor); 36962306a36Sopenharmony_ci 37062306a36Sopenharmony_ci start = usb_dump_device_strings(start, end, dev); 37162306a36Sopenharmony_ci 37262306a36Sopenharmony_ci for (i = 0; i < dev->descriptor.bNumConfigurations; i++) { 37362306a36Sopenharmony_ci start = usb_dump_config(dev->speed, 37462306a36Sopenharmony_ci start, end, dev->config + i, 37562306a36Sopenharmony_ci /* active ? */ 37662306a36Sopenharmony_ci (dev->config + i) == dev->actconfig); 37762306a36Sopenharmony_ci } 37862306a36Sopenharmony_ci return start; 37962306a36Sopenharmony_ci} 38062306a36Sopenharmony_ci 38162306a36Sopenharmony_ci/*****************************************************************/ 38262306a36Sopenharmony_ci 38362306a36Sopenharmony_ci/* This is a recursive function. Parameters: 38462306a36Sopenharmony_ci * buffer - the user-space buffer to write data into 38562306a36Sopenharmony_ci * nbytes - the maximum number of bytes to write 38662306a36Sopenharmony_ci * skip_bytes - the number of bytes to skip before writing anything 38762306a36Sopenharmony_ci * file_offset - the offset into the devices file on completion 38862306a36Sopenharmony_ci * The caller must own the device lock. 38962306a36Sopenharmony_ci */ 39062306a36Sopenharmony_cistatic ssize_t usb_device_dump(char __user **buffer, size_t *nbytes, 39162306a36Sopenharmony_ci loff_t *skip_bytes, loff_t *file_offset, 39262306a36Sopenharmony_ci struct usb_device *usbdev, struct usb_bus *bus, 39362306a36Sopenharmony_ci int level, int index, int count) 39462306a36Sopenharmony_ci{ 39562306a36Sopenharmony_ci int chix; 39662306a36Sopenharmony_ci int ret, cnt = 0; 39762306a36Sopenharmony_ci int parent_devnum = 0; 39862306a36Sopenharmony_ci char *pages_start, *data_end, *speed; 39962306a36Sopenharmony_ci unsigned int length; 40062306a36Sopenharmony_ci ssize_t total_written = 0; 40162306a36Sopenharmony_ci struct usb_device *childdev = NULL; 40262306a36Sopenharmony_ci 40362306a36Sopenharmony_ci /* don't bother with anything else if we're not writing any data */ 40462306a36Sopenharmony_ci if (*nbytes <= 0) 40562306a36Sopenharmony_ci return 0; 40662306a36Sopenharmony_ci 40762306a36Sopenharmony_ci if (level > MAX_TOPO_LEVEL) 40862306a36Sopenharmony_ci return 0; 40962306a36Sopenharmony_ci /* allocate 2^1 pages = 8K (on i386); 41062306a36Sopenharmony_ci * should be more than enough for one device */ 41162306a36Sopenharmony_ci pages_start = (char *)__get_free_pages(GFP_NOIO, 1); 41262306a36Sopenharmony_ci if (!pages_start) 41362306a36Sopenharmony_ci return -ENOMEM; 41462306a36Sopenharmony_ci 41562306a36Sopenharmony_ci if (usbdev->parent && usbdev->parent->devnum != -1) 41662306a36Sopenharmony_ci parent_devnum = usbdev->parent->devnum; 41762306a36Sopenharmony_ci /* 41862306a36Sopenharmony_ci * So the root hub's parent is 0 and any device that is 41962306a36Sopenharmony_ci * plugged into the root hub has a parent of 0. 42062306a36Sopenharmony_ci */ 42162306a36Sopenharmony_ci switch (usbdev->speed) { 42262306a36Sopenharmony_ci case USB_SPEED_LOW: 42362306a36Sopenharmony_ci speed = "1.5"; break; 42462306a36Sopenharmony_ci case USB_SPEED_UNKNOWN: /* usb 1.1 root hub code */ 42562306a36Sopenharmony_ci case USB_SPEED_FULL: 42662306a36Sopenharmony_ci speed = "12"; break; 42762306a36Sopenharmony_ci case USB_SPEED_HIGH: 42862306a36Sopenharmony_ci speed = "480"; break; 42962306a36Sopenharmony_ci case USB_SPEED_SUPER: 43062306a36Sopenharmony_ci speed = "5000"; break; 43162306a36Sopenharmony_ci case USB_SPEED_SUPER_PLUS: 43262306a36Sopenharmony_ci speed = "10000"; break; 43362306a36Sopenharmony_ci default: 43462306a36Sopenharmony_ci speed = "??"; 43562306a36Sopenharmony_ci } 43662306a36Sopenharmony_ci data_end = pages_start + sprintf(pages_start, format_topo, 43762306a36Sopenharmony_ci bus->busnum, level, parent_devnum, 43862306a36Sopenharmony_ci index, count, usbdev->devnum, 43962306a36Sopenharmony_ci speed, usbdev->maxchild); 44062306a36Sopenharmony_ci /* 44162306a36Sopenharmony_ci * level = topology-tier level; 44262306a36Sopenharmony_ci * parent_devnum = parent device number; 44362306a36Sopenharmony_ci * index = parent's connector number; 44462306a36Sopenharmony_ci * count = device count at this level 44562306a36Sopenharmony_ci */ 44662306a36Sopenharmony_ci /* If this is the root hub, display the bandwidth information */ 44762306a36Sopenharmony_ci if (level == 0) { 44862306a36Sopenharmony_ci int max; 44962306a36Sopenharmony_ci 45062306a36Sopenharmony_ci /* super/high speed reserves 80%, full/low reserves 90% */ 45162306a36Sopenharmony_ci if (usbdev->speed == USB_SPEED_HIGH || 45262306a36Sopenharmony_ci usbdev->speed >= USB_SPEED_SUPER) 45362306a36Sopenharmony_ci max = 800; 45462306a36Sopenharmony_ci else 45562306a36Sopenharmony_ci max = FRAME_TIME_MAX_USECS_ALLOC; 45662306a36Sopenharmony_ci 45762306a36Sopenharmony_ci /* report "average" periodic allocation over a microsecond. 45862306a36Sopenharmony_ci * the schedules are actually bursty, HCDs need to deal with 45962306a36Sopenharmony_ci * that and just compute/report this average. 46062306a36Sopenharmony_ci */ 46162306a36Sopenharmony_ci data_end += sprintf(data_end, format_bandwidth, 46262306a36Sopenharmony_ci bus->bandwidth_allocated, max, 46362306a36Sopenharmony_ci (100 * bus->bandwidth_allocated + max / 2) 46462306a36Sopenharmony_ci / max, 46562306a36Sopenharmony_ci bus->bandwidth_int_reqs, 46662306a36Sopenharmony_ci bus->bandwidth_isoc_reqs); 46762306a36Sopenharmony_ci 46862306a36Sopenharmony_ci } 46962306a36Sopenharmony_ci data_end = usb_dump_desc(data_end, pages_start + (2 * PAGE_SIZE) - 256, 47062306a36Sopenharmony_ci usbdev); 47162306a36Sopenharmony_ci 47262306a36Sopenharmony_ci if (data_end > (pages_start + (2 * PAGE_SIZE) - 256)) 47362306a36Sopenharmony_ci data_end += sprintf(data_end, "(truncated)\n"); 47462306a36Sopenharmony_ci 47562306a36Sopenharmony_ci length = data_end - pages_start; 47662306a36Sopenharmony_ci /* if we can start copying some data to the user */ 47762306a36Sopenharmony_ci if (length > *skip_bytes) { 47862306a36Sopenharmony_ci length -= *skip_bytes; 47962306a36Sopenharmony_ci if (length > *nbytes) 48062306a36Sopenharmony_ci length = *nbytes; 48162306a36Sopenharmony_ci if (copy_to_user(*buffer, pages_start + *skip_bytes, length)) { 48262306a36Sopenharmony_ci free_pages((unsigned long)pages_start, 1); 48362306a36Sopenharmony_ci return -EFAULT; 48462306a36Sopenharmony_ci } 48562306a36Sopenharmony_ci *nbytes -= length; 48662306a36Sopenharmony_ci *file_offset += length; 48762306a36Sopenharmony_ci total_written += length; 48862306a36Sopenharmony_ci *buffer += length; 48962306a36Sopenharmony_ci *skip_bytes = 0; 49062306a36Sopenharmony_ci } else 49162306a36Sopenharmony_ci *skip_bytes -= length; 49262306a36Sopenharmony_ci 49362306a36Sopenharmony_ci free_pages((unsigned long)pages_start, 1); 49462306a36Sopenharmony_ci 49562306a36Sopenharmony_ci /* Now look at all of this device's children. */ 49662306a36Sopenharmony_ci usb_hub_for_each_child(usbdev, chix, childdev) { 49762306a36Sopenharmony_ci usb_lock_device(childdev); 49862306a36Sopenharmony_ci ret = usb_device_dump(buffer, nbytes, skip_bytes, 49962306a36Sopenharmony_ci file_offset, childdev, bus, 50062306a36Sopenharmony_ci level + 1, chix - 1, ++cnt); 50162306a36Sopenharmony_ci usb_unlock_device(childdev); 50262306a36Sopenharmony_ci if (ret == -EFAULT) 50362306a36Sopenharmony_ci return total_written; 50462306a36Sopenharmony_ci total_written += ret; 50562306a36Sopenharmony_ci } 50662306a36Sopenharmony_ci return total_written; 50762306a36Sopenharmony_ci} 50862306a36Sopenharmony_ci 50962306a36Sopenharmony_cistatic ssize_t usb_device_read(struct file *file, char __user *buf, 51062306a36Sopenharmony_ci size_t nbytes, loff_t *ppos) 51162306a36Sopenharmony_ci{ 51262306a36Sopenharmony_ci struct usb_bus *bus; 51362306a36Sopenharmony_ci ssize_t ret, total_written = 0; 51462306a36Sopenharmony_ci loff_t skip_bytes = *ppos; 51562306a36Sopenharmony_ci int id; 51662306a36Sopenharmony_ci 51762306a36Sopenharmony_ci if (*ppos < 0) 51862306a36Sopenharmony_ci return -EINVAL; 51962306a36Sopenharmony_ci if (nbytes <= 0) 52062306a36Sopenharmony_ci return 0; 52162306a36Sopenharmony_ci 52262306a36Sopenharmony_ci mutex_lock(&usb_bus_idr_lock); 52362306a36Sopenharmony_ci /* print devices for all busses */ 52462306a36Sopenharmony_ci idr_for_each_entry(&usb_bus_idr, bus, id) { 52562306a36Sopenharmony_ci /* recurse through all children of the root hub */ 52662306a36Sopenharmony_ci if (!bus_to_hcd(bus)->rh_registered) 52762306a36Sopenharmony_ci continue; 52862306a36Sopenharmony_ci usb_lock_device(bus->root_hub); 52962306a36Sopenharmony_ci ret = usb_device_dump(&buf, &nbytes, &skip_bytes, ppos, 53062306a36Sopenharmony_ci bus->root_hub, bus, 0, 0, 0); 53162306a36Sopenharmony_ci usb_unlock_device(bus->root_hub); 53262306a36Sopenharmony_ci if (ret < 0) { 53362306a36Sopenharmony_ci mutex_unlock(&usb_bus_idr_lock); 53462306a36Sopenharmony_ci return ret; 53562306a36Sopenharmony_ci } 53662306a36Sopenharmony_ci total_written += ret; 53762306a36Sopenharmony_ci } 53862306a36Sopenharmony_ci mutex_unlock(&usb_bus_idr_lock); 53962306a36Sopenharmony_ci return total_written; 54062306a36Sopenharmony_ci} 54162306a36Sopenharmony_ci 54262306a36Sopenharmony_ciconst struct file_operations usbfs_devices_fops = { 54362306a36Sopenharmony_ci .llseek = no_seek_end_llseek, 54462306a36Sopenharmony_ci .read = usb_device_read, 54562306a36Sopenharmony_ci}; 546