| 1 | // SPDX-License-Identifier: GPL-2.0+ | 
|---|
| 2 | /* | 
|---|
| 3 | * devices.c | 
|---|
| 4 | * (C) Copyright 1999 Randy Dunlap. | 
|---|
| 5 | * (C) Copyright 1999,2000 Thomas Sailer <sailer@ife.ee.ethz.ch>. | 
|---|
| 6 | *     (proc file per device) | 
|---|
| 7 | * (C) Copyright 1999 Deti Fliegl (new USB architecture) | 
|---|
| 8 | * | 
|---|
| 9 | ************************************************************* | 
|---|
| 10 | * | 
|---|
| 11 | * <mountpoint>/devices contains USB topology, device, config, class, | 
|---|
| 12 | * interface, & endpoint data. | 
|---|
| 13 | * | 
|---|
| 14 | * I considered using /dev/bus/usb/device# for each device | 
|---|
| 15 | * as it is attached or detached, but I didn't like this for some | 
|---|
| 16 | * reason -- maybe it's just too deep of a directory structure. | 
|---|
| 17 | * I also don't like looking in multiple places to gather and view | 
|---|
| 18 | * the data.  Having only one file for ./devices also prevents race | 
|---|
| 19 | * conditions that could arise if a program was reading device info | 
|---|
| 20 | * for devices that are being removed (unplugged).  (That is, the | 
|---|
| 21 | * program may find a directory for devnum_12 then try to open it, | 
|---|
| 22 | * but it was just unplugged, so the directory is now deleted. | 
|---|
| 23 | * But programs would just have to be prepared for situations like | 
|---|
| 24 | * this in any plug-and-play environment.) | 
|---|
| 25 | * | 
|---|
| 26 | * 1999-12-16: Thomas Sailer <sailer@ife.ee.ethz.ch> | 
|---|
| 27 | *   Converted the whole proc stuff to real | 
|---|
| 28 | *   read methods. Now not the whole device list needs to fit | 
|---|
| 29 | *   into one page, only the device list for one bus. | 
|---|
| 30 | *   Added a poll method to /sys/kernel/debug/usb/devices, to wake | 
|---|
| 31 | *   up an eventual usbd | 
|---|
| 32 | * 2000-01-04: Thomas Sailer <sailer@ife.ee.ethz.ch> | 
|---|
| 33 | *   Turned into its own filesystem | 
|---|
| 34 | * 2000-07-05: Ashley Montanaro <ashley@compsoc.man.ac.uk> | 
|---|
| 35 | *   Converted file reading routine to dump to buffer once | 
|---|
| 36 | *   per device, not per bus | 
|---|
| 37 | */ | 
|---|
| 38 |  | 
|---|
| 39 | #include <linux/fs.h> | 
|---|
| 40 | #include <linux/mm.h> | 
|---|
| 41 | #include <linux/gfp.h> | 
|---|
| 42 | #include <linux/usb.h> | 
|---|
| 43 | #include <linux/usbdevice_fs.h> | 
|---|
| 44 | #include <linux/usb/hcd.h> | 
|---|
| 45 | #include <linux/mutex.h> | 
|---|
| 46 | #include <linux/uaccess.h> | 
|---|
| 47 |  | 
|---|
| 48 | #include "usb.h" | 
|---|
| 49 |  | 
|---|
| 50 | /* Define ALLOW_SERIAL_NUMBER if you want to see the serial number of devices */ | 
|---|
| 51 | #define ALLOW_SERIAL_NUMBER | 
|---|
| 52 |  | 
|---|
| 53 | static const char format_topo[] = | 
|---|
| 54 | /* T:  Bus=dd Lev=dd Prnt=dd Port=dd Cnt=dd Dev#=ddd Spd=dddd MxCh=dd */ | 
|---|
| 55 | "\nT:  Bus=%2.2d Lev=%2.2d Prnt=%2.2d Port=%2.2d Cnt=%2.2d Dev#=%3d Spd=%-4s MxCh=%2d\n"; | 
|---|
| 56 |  | 
|---|
| 57 | static const char format_string_manufacturer[] = | 
|---|
| 58 | /* S:  Manufacturer=xxxx */ | 
|---|
| 59 | "S:  Manufacturer=%.100s\n"; | 
|---|
| 60 |  | 
|---|
| 61 | static const char format_string_product[] = | 
|---|
| 62 | /* S:  Product=xxxx */ | 
|---|
| 63 | "S:  Product=%.100s\n"; | 
|---|
| 64 |  | 
|---|
| 65 | #ifdef ALLOW_SERIAL_NUMBER | 
|---|
| 66 | static const char format_string_serialnumber[] = | 
|---|
| 67 | /* S:  SerialNumber=xxxx */ | 
|---|
| 68 | "S:  SerialNumber=%.100s\n"; | 
|---|
| 69 | #endif | 
|---|
| 70 |  | 
|---|
| 71 | static const char format_bandwidth[] = | 
|---|
| 72 | /* B:  Alloc=ddd/ddd us (xx%), #Int=ddd, #Iso=ddd */ | 
|---|
| 73 | "B:  Alloc=%3d/%3d us (%2d%%), #Int=%3d, #Iso=%3d\n"; | 
|---|
| 74 |  | 
|---|
| 75 | static const char format_device1[] = | 
|---|
| 76 | /* D:  Ver=xx.xx Cls=xx(sssss) Sub=xx Prot=xx MxPS=dd #Cfgs=dd */ | 
|---|
| 77 | "D:  Ver=%2x.%02x Cls=%02x(%-5s) Sub=%02x Prot=%02x MxPS=%2d #Cfgs=%3d\n"; | 
|---|
| 78 |  | 
|---|
| 79 | static const char format_device2[] = | 
|---|
| 80 | /* P:  Vendor=xxxx ProdID=xxxx Rev=xx.xx */ | 
|---|
| 81 | "P:  Vendor=%04x ProdID=%04x Rev=%2x.%02x\n"; | 
|---|
| 82 |  | 
|---|
| 83 | static const char format_config[] = | 
|---|
| 84 | /* C:  #Ifs=dd Cfg#=dd Atr=xx MPwr=dddmA */ | 
|---|
| 85 | "C:%c #Ifs=%2d Cfg#=%2d Atr=%02x MxPwr=%3dmA\n"; | 
|---|
| 86 |  | 
|---|
| 87 | static const char format_iad[] = | 
|---|
| 88 | /* A:  FirstIf#=dd IfCount=dd Cls=xx(sssss) Sub=xx Prot=xx */ | 
|---|
| 89 | "A:  FirstIf#=%2d IfCount=%2d Cls=%02x(%-5s) Sub=%02x Prot=%02x\n"; | 
|---|
| 90 |  | 
|---|
| 91 | static const char format_iface[] = | 
|---|
| 92 | /* I:  If#=dd Alt=dd #EPs=dd Cls=xx(sssss) Sub=xx Prot=xx Driver=xxxx*/ | 
|---|
| 93 | "I:%c If#=%2d Alt=%2d #EPs=%2d Cls=%02x(%-5s) Sub=%02x Prot=%02x Driver=%s\n"; | 
|---|
| 94 |  | 
|---|
| 95 | static const char format_endpt[] = | 
|---|
| 96 | /* E:  Ad=xx(s) Atr=xx(ssss) MxPS=dddd Ivl=D?s */ | 
|---|
| 97 | "E:  Ad=%02x(%c) Atr=%02x(%-4s) MxPS=%4d Ivl=%d%cs\n"; | 
|---|
| 98 |  | 
|---|
| 99 | struct class_info { | 
|---|
| 100 | int class; | 
|---|
| 101 | char *class_name; | 
|---|
| 102 | }; | 
|---|
| 103 |  | 
|---|
| 104 | static const struct class_info clas_info[] = { | 
|---|
| 105 | /* max. 5 chars. per name string */ | 
|---|
| 106 | {USB_CLASS_PER_INTERFACE, ">ifc"}, | 
|---|
| 107 | {USB_CLASS_AUDIO,		.class_name: "audio"}, | 
|---|
| 108 | {USB_CLASS_COMM,		.class_name: "comm."}, | 
|---|
| 109 | {USB_CLASS_HID,			.class_name: "HID"}, | 
|---|
| 110 | {USB_CLASS_PHYSICAL,		.class_name: "PID"}, | 
|---|
| 111 | {USB_CLASS_STILL_IMAGE,		.class_name: "still"}, | 
|---|
| 112 | {USB_CLASS_PRINTER,		.class_name: "print"}, | 
|---|
| 113 | {USB_CLASS_MASS_STORAGE,	.class_name: "stor."}, | 
|---|
| 114 | {USB_CLASS_HUB,			.class_name: "hub"}, | 
|---|
| 115 | {USB_CLASS_CDC_DATA,		.class_name: "data"}, | 
|---|
| 116 | {USB_CLASS_CSCID,		.class_name: "scard"}, | 
|---|
| 117 | {USB_CLASS_CONTENT_SEC,		.class_name: "c-sec"}, | 
|---|
| 118 | {USB_CLASS_VIDEO,		.class_name: "video"}, | 
|---|
| 119 | {USB_CLASS_PERSONAL_HEALTHCARE,	.class_name: "perhc"}, | 
|---|
| 120 | {USB_CLASS_AUDIO_VIDEO,		.class_name: "av"}, | 
|---|
| 121 | {USB_CLASS_BILLBOARD,		.class_name: "blbrd"}, | 
|---|
| 122 | {USB_CLASS_USB_TYPE_C_BRIDGE,	.class_name: "bridg"}, | 
|---|
| 123 | {USB_CLASS_WIRELESS_CONTROLLER,	.class_name: "wlcon"}, | 
|---|
| 124 | {USB_CLASS_MISC,		.class_name: "misc"}, | 
|---|
| 125 | {USB_CLASS_APP_SPEC,		.class_name: "app."}, | 
|---|
| 126 | {USB_CLASS_VENDOR_SPEC,		.class_name: "vend."}, | 
|---|
| 127 | {.class: -1,				.class_name: "unk."}		/* leave as last */ | 
|---|
| 128 | }; | 
|---|
| 129 |  | 
|---|
| 130 | /*****************************************************************/ | 
|---|
| 131 |  | 
|---|
| 132 | static const char *class_decode(const int class) | 
|---|
| 133 | { | 
|---|
| 134 | int ix; | 
|---|
| 135 |  | 
|---|
| 136 | for (ix = 0; clas_info[ix].class != -1; ix++) | 
|---|
| 137 | if (clas_info[ix].class == class) | 
|---|
| 138 | break; | 
|---|
| 139 | return clas_info[ix].class_name; | 
|---|
| 140 | } | 
|---|
| 141 |  | 
|---|
| 142 | static char *usb_dump_endpoint_descriptor(int speed, char *start, char *end, | 
|---|
| 143 | const struct usb_endpoint_descriptor *desc) | 
|---|
| 144 | { | 
|---|
| 145 | char dir, unit, *type; | 
|---|
| 146 | unsigned interval, bandwidth = 1; | 
|---|
| 147 |  | 
|---|
| 148 | if (start > end) | 
|---|
| 149 | return start; | 
|---|
| 150 |  | 
|---|
| 151 | dir = usb_endpoint_dir_in(epd: desc) ? 'I' : 'O'; | 
|---|
| 152 |  | 
|---|
| 153 | if (speed == USB_SPEED_HIGH) | 
|---|
| 154 | bandwidth = usb_endpoint_maxp_mult(epd: desc); | 
|---|
| 155 |  | 
|---|
| 156 | /* this isn't checking for illegal values */ | 
|---|
| 157 | switch (usb_endpoint_type(epd: desc)) { | 
|---|
| 158 | case USB_ENDPOINT_XFER_CONTROL: | 
|---|
| 159 | type = "Ctrl"; | 
|---|
| 160 | dir = 'B';			/* ctrl is bidirectional */ | 
|---|
| 161 | break; | 
|---|
| 162 | case USB_ENDPOINT_XFER_ISOC: | 
|---|
| 163 | type = "Isoc"; | 
|---|
| 164 | break; | 
|---|
| 165 | case USB_ENDPOINT_XFER_BULK: | 
|---|
| 166 | type = "Bulk"; | 
|---|
| 167 | break; | 
|---|
| 168 | case USB_ENDPOINT_XFER_INT: | 
|---|
| 169 | type = "Int."; | 
|---|
| 170 | break; | 
|---|
| 171 | default:	/* "can't happen" */ | 
|---|
| 172 | return start; | 
|---|
| 173 | } | 
|---|
| 174 |  | 
|---|
| 175 | interval = usb_decode_interval(epd: desc, speed); | 
|---|
| 176 | if (interval % 1000) { | 
|---|
| 177 | unit = 'u'; | 
|---|
| 178 | } else { | 
|---|
| 179 | unit = 'm'; | 
|---|
| 180 | interval /= 1000; | 
|---|
| 181 | } | 
|---|
| 182 |  | 
|---|
| 183 | start += sprintf(buf: start, fmt: format_endpt, desc->bEndpointAddress, dir, | 
|---|
| 184 | desc->bmAttributes, type, | 
|---|
| 185 | usb_endpoint_maxp(epd: desc) * | 
|---|
| 186 | bandwidth, | 
|---|
| 187 | interval, unit); | 
|---|
| 188 | return start; | 
|---|
| 189 | } | 
|---|
| 190 |  | 
|---|
| 191 | static char *usb_dump_interface_descriptor(char *start, char *end, | 
|---|
| 192 | const struct usb_interface_cache *intfc, | 
|---|
| 193 | const struct usb_interface *iface, | 
|---|
| 194 | int setno) | 
|---|
| 195 | { | 
|---|
| 196 | const struct usb_interface_descriptor *desc; | 
|---|
| 197 | const char *driver_name = ""; | 
|---|
| 198 | int active = 0; | 
|---|
| 199 |  | 
|---|
| 200 | if (start > end) | 
|---|
| 201 | return start; | 
|---|
| 202 | desc = &intfc->altsetting[setno].desc; | 
|---|
| 203 | if (iface) { | 
|---|
| 204 | driver_name = (iface->dev.driver | 
|---|
| 205 | ? iface->dev.driver->name | 
|---|
| 206 | : "(none)"); | 
|---|
| 207 | active = (desc == &iface->cur_altsetting->desc); | 
|---|
| 208 | } | 
|---|
| 209 | start += sprintf(buf: start, fmt: format_iface, | 
|---|
| 210 | active ? '*' : ' ',	/* mark active altsetting */ | 
|---|
| 211 | desc->bInterfaceNumber, | 
|---|
| 212 | desc->bAlternateSetting, | 
|---|
| 213 | desc->bNumEndpoints, | 
|---|
| 214 | desc->bInterfaceClass, | 
|---|
| 215 | class_decode(class: desc->bInterfaceClass), | 
|---|
| 216 | desc->bInterfaceSubClass, | 
|---|
| 217 | desc->bInterfaceProtocol, | 
|---|
| 218 | driver_name); | 
|---|
| 219 | return start; | 
|---|
| 220 | } | 
|---|
| 221 |  | 
|---|
| 222 | static char *usb_dump_interface(int speed, char *start, char *end, | 
|---|
| 223 | const struct usb_interface_cache *intfc, | 
|---|
| 224 | const struct usb_interface *iface, int setno) | 
|---|
| 225 | { | 
|---|
| 226 | const struct usb_host_interface *desc = &intfc->altsetting[setno]; | 
|---|
| 227 | int i; | 
|---|
| 228 |  | 
|---|
| 229 | start = usb_dump_interface_descriptor(start, end, intfc, iface, setno); | 
|---|
| 230 | for (i = 0; i < desc->desc.bNumEndpoints; i++) { | 
|---|
| 231 | start = usb_dump_endpoint_descriptor(speed, | 
|---|
| 232 | start, end, desc: &desc->endpoint[i].desc); | 
|---|
| 233 | } | 
|---|
| 234 | return start; | 
|---|
| 235 | } | 
|---|
| 236 |  | 
|---|
| 237 | static char *usb_dump_iad_descriptor(char *start, char *end, | 
|---|
| 238 | const struct usb_interface_assoc_descriptor *iad) | 
|---|
| 239 | { | 
|---|
| 240 | if (start > end) | 
|---|
| 241 | return start; | 
|---|
| 242 | start += sprintf(buf: start, fmt: format_iad, | 
|---|
| 243 | iad->bFirstInterface, | 
|---|
| 244 | iad->bInterfaceCount, | 
|---|
| 245 | iad->bFunctionClass, | 
|---|
| 246 | class_decode(class: iad->bFunctionClass), | 
|---|
| 247 | iad->bFunctionSubClass, | 
|---|
| 248 | iad->bFunctionProtocol); | 
|---|
| 249 | return start; | 
|---|
| 250 | } | 
|---|
| 251 |  | 
|---|
| 252 | /* TBD: | 
|---|
| 253 | * 0. TBDs | 
|---|
| 254 | * 1. marking active interface altsettings (code lists all, but should mark | 
|---|
| 255 | *    which ones are active, if any) | 
|---|
| 256 | */ | 
|---|
| 257 | static char *usb_dump_config_descriptor(char *start, char *end, | 
|---|
| 258 | const struct usb_config_descriptor *desc, | 
|---|
| 259 | int active, int speed) | 
|---|
| 260 | { | 
|---|
| 261 | int mul; | 
|---|
| 262 |  | 
|---|
| 263 | if (start > end) | 
|---|
| 264 | return start; | 
|---|
| 265 | if (speed >= USB_SPEED_SUPER) | 
|---|
| 266 | mul = 8; | 
|---|
| 267 | else | 
|---|
| 268 | mul = 2; | 
|---|
| 269 | start += sprintf(buf: start, fmt: format_config, | 
|---|
| 270 | /* mark active/actual/current cfg. */ | 
|---|
| 271 | active ? '*' : ' ', | 
|---|
| 272 | desc->bNumInterfaces, | 
|---|
| 273 | desc->bConfigurationValue, | 
|---|
| 274 | desc->bmAttributes, | 
|---|
| 275 | desc->bMaxPower * mul); | 
|---|
| 276 | return start; | 
|---|
| 277 | } | 
|---|
| 278 |  | 
|---|
| 279 | static char *usb_dump_config(int speed, char *start, char *end, | 
|---|
| 280 | const struct usb_host_config *config, int active) | 
|---|
| 281 | { | 
|---|
| 282 | int i, j; | 
|---|
| 283 | struct usb_interface_cache *intfc; | 
|---|
| 284 | struct usb_interface *interface; | 
|---|
| 285 |  | 
|---|
| 286 | if (start > end) | 
|---|
| 287 | return start; | 
|---|
| 288 | if (!config) | 
|---|
| 289 | /* getting these some in 2.3.7; none in 2.3.6 */ | 
|---|
| 290 | return start + sprintf(buf: start, fmt: "(null Cfg. desc.)\n"); | 
|---|
| 291 | start = usb_dump_config_descriptor(start, end, desc: &config->desc, active, | 
|---|
| 292 | speed); | 
|---|
| 293 | for (i = 0; i < USB_MAXIADS; i++) { | 
|---|
| 294 | if (config->intf_assoc[i] == NULL) | 
|---|
| 295 | break; | 
|---|
| 296 | start = usb_dump_iad_descriptor(start, end, | 
|---|
| 297 | iad: config->intf_assoc[i]); | 
|---|
| 298 | } | 
|---|
| 299 | for (i = 0; i < config->desc.bNumInterfaces; i++) { | 
|---|
| 300 | intfc = config->intf_cache[i]; | 
|---|
| 301 | interface = config->interface[i]; | 
|---|
| 302 | for (j = 0; j < intfc->num_altsetting; j++) { | 
|---|
| 303 | start = usb_dump_interface(speed, | 
|---|
| 304 | start, end, intfc, iface: interface, setno: j); | 
|---|
| 305 | } | 
|---|
| 306 | } | 
|---|
| 307 | return start; | 
|---|
| 308 | } | 
|---|
| 309 |  | 
|---|
| 310 | /* | 
|---|
| 311 | * Dump the different USB descriptors. | 
|---|
| 312 | */ | 
|---|
| 313 | static char *usb_dump_device_descriptor(char *start, char *end, | 
|---|
| 314 | const struct usb_device_descriptor *desc) | 
|---|
| 315 | { | 
|---|
| 316 | u16 bcdUSB = le16_to_cpu(desc->bcdUSB); | 
|---|
| 317 | u16 bcdDevice = le16_to_cpu(desc->bcdDevice); | 
|---|
| 318 |  | 
|---|
| 319 | if (start > end) | 
|---|
| 320 | return start; | 
|---|
| 321 | start += sprintf(buf: start, fmt: format_device1, | 
|---|
| 322 | bcdUSB >> 8, bcdUSB & 0xff, | 
|---|
| 323 | desc->bDeviceClass, | 
|---|
| 324 | class_decode(class: desc->bDeviceClass), | 
|---|
| 325 | desc->bDeviceSubClass, | 
|---|
| 326 | desc->bDeviceProtocol, | 
|---|
| 327 | desc->bMaxPacketSize0, | 
|---|
| 328 | desc->bNumConfigurations); | 
|---|
| 329 | if (start > end) | 
|---|
| 330 | return start; | 
|---|
| 331 | start += sprintf(buf: start, fmt: format_device2, | 
|---|
| 332 | le16_to_cpu(desc->idVendor), | 
|---|
| 333 | le16_to_cpu(desc->idProduct), | 
|---|
| 334 | bcdDevice >> 8, bcdDevice & 0xff); | 
|---|
| 335 | return start; | 
|---|
| 336 | } | 
|---|
| 337 |  | 
|---|
| 338 | /* | 
|---|
| 339 | * Dump the different strings that this device holds. | 
|---|
| 340 | */ | 
|---|
| 341 | static char *usb_dump_device_strings(char *start, char *end, | 
|---|
| 342 | struct usb_device *dev) | 
|---|
| 343 | { | 
|---|
| 344 | if (start > end) | 
|---|
| 345 | return start; | 
|---|
| 346 | if (dev->manufacturer) | 
|---|
| 347 | start += sprintf(buf: start, fmt: format_string_manufacturer, | 
|---|
| 348 | dev->manufacturer); | 
|---|
| 349 | if (start > end) | 
|---|
| 350 | goto out; | 
|---|
| 351 | if (dev->product) | 
|---|
| 352 | start += sprintf(buf: start, fmt: format_string_product, dev->product); | 
|---|
| 353 | if (start > end) | 
|---|
| 354 | goto out; | 
|---|
| 355 | #ifdef ALLOW_SERIAL_NUMBER | 
|---|
| 356 | if (dev->serial) | 
|---|
| 357 | start += sprintf(buf: start, fmt: format_string_serialnumber, | 
|---|
| 358 | dev->serial); | 
|---|
| 359 | #endif | 
|---|
| 360 | out: | 
|---|
| 361 | return start; | 
|---|
| 362 | } | 
|---|
| 363 |  | 
|---|
| 364 | static char *usb_dump_desc(char *start, char *end, struct usb_device *dev) | 
|---|
| 365 | { | 
|---|
| 366 | int i; | 
|---|
| 367 |  | 
|---|
| 368 | start = usb_dump_device_descriptor(start, end, desc: &dev->descriptor); | 
|---|
| 369 |  | 
|---|
| 370 | start = usb_dump_device_strings(start, end, dev); | 
|---|
| 371 |  | 
|---|
| 372 | for (i = 0; i < dev->descriptor.bNumConfigurations; i++) { | 
|---|
| 373 | start = usb_dump_config(speed: dev->speed, | 
|---|
| 374 | start, end, config: dev->config + i, | 
|---|
| 375 | /* active ? */ | 
|---|
| 376 | active: (dev->config + i) == dev->actconfig); | 
|---|
| 377 | } | 
|---|
| 378 | return start; | 
|---|
| 379 | } | 
|---|
| 380 |  | 
|---|
| 381 | /*****************************************************************/ | 
|---|
| 382 |  | 
|---|
| 383 | /* This is a recursive function. Parameters: | 
|---|
| 384 | * buffer - the user-space buffer to write data into | 
|---|
| 385 | * nbytes - the maximum number of bytes to write | 
|---|
| 386 | * skip_bytes - the number of bytes to skip before writing anything | 
|---|
| 387 | * file_offset - the offset into the devices file on completion | 
|---|
| 388 | * The caller must own the device lock. | 
|---|
| 389 | */ | 
|---|
| 390 | static ssize_t usb_device_dump(char __user **buffer, size_t *nbytes, | 
|---|
| 391 | loff_t *skip_bytes, loff_t *file_offset, | 
|---|
| 392 | struct usb_device *usbdev, struct usb_bus *bus, | 
|---|
| 393 | int level, int index, int count) | 
|---|
| 394 | { | 
|---|
| 395 | int chix; | 
|---|
| 396 | int ret, cnt = 0; | 
|---|
| 397 | int parent_devnum = 0; | 
|---|
| 398 | char *pages_start, *data_end, *speed; | 
|---|
| 399 | unsigned int length; | 
|---|
| 400 | ssize_t total_written = 0; | 
|---|
| 401 | struct usb_device *childdev = NULL; | 
|---|
| 402 |  | 
|---|
| 403 | /* don't bother with anything else if we're not writing any data */ | 
|---|
| 404 | if (*nbytes <= 0) | 
|---|
| 405 | return 0; | 
|---|
| 406 |  | 
|---|
| 407 | if (level > MAX_TOPO_LEVEL) | 
|---|
| 408 | return 0; | 
|---|
| 409 | /* allocate 2^1 pages = 8K (on i386); | 
|---|
| 410 | * should be more than enough for one device */ | 
|---|
| 411 | pages_start = (char *)__get_free_pages(GFP_NOIO, 1); | 
|---|
| 412 | if (!pages_start) | 
|---|
| 413 | return -ENOMEM; | 
|---|
| 414 |  | 
|---|
| 415 | if (usbdev->parent && usbdev->parent->devnum != -1) | 
|---|
| 416 | parent_devnum = usbdev->parent->devnum; | 
|---|
| 417 | /* | 
|---|
| 418 | * So the root hub's parent is 0 and any device that is | 
|---|
| 419 | * plugged into the root hub has a parent of 0. | 
|---|
| 420 | */ | 
|---|
| 421 | switch (usbdev->speed) { | 
|---|
| 422 | case USB_SPEED_LOW: | 
|---|
| 423 | speed = "1.5"; break; | 
|---|
| 424 | case USB_SPEED_UNKNOWN:		/* usb 1.1 root hub code */ | 
|---|
| 425 | case USB_SPEED_FULL: | 
|---|
| 426 | speed = "12"; break; | 
|---|
| 427 | case USB_SPEED_HIGH: | 
|---|
| 428 | speed = "480"; break; | 
|---|
| 429 | case USB_SPEED_SUPER: | 
|---|
| 430 | speed = "5000"; break; | 
|---|
| 431 | case USB_SPEED_SUPER_PLUS: | 
|---|
| 432 | speed = "10000"; break; | 
|---|
| 433 | default: | 
|---|
| 434 | speed = "??"; | 
|---|
| 435 | } | 
|---|
| 436 | data_end = pages_start + sprintf(buf: pages_start, fmt: format_topo, | 
|---|
| 437 | bus->busnum, level, parent_devnum, | 
|---|
| 438 | index, count, usbdev->devnum, | 
|---|
| 439 | speed, usbdev->maxchild); | 
|---|
| 440 | /* | 
|---|
| 441 | * level = topology-tier level; | 
|---|
| 442 | * parent_devnum = parent device number; | 
|---|
| 443 | * index = parent's connector number; | 
|---|
| 444 | * count = device count at this level | 
|---|
| 445 | */ | 
|---|
| 446 | /* If this is the root hub, display the bandwidth information */ | 
|---|
| 447 | if (level == 0) { | 
|---|
| 448 | int	max; | 
|---|
| 449 |  | 
|---|
| 450 | /* super/high speed reserves 80%, full/low reserves 90% */ | 
|---|
| 451 | if (usbdev->speed == USB_SPEED_HIGH || | 
|---|
| 452 | usbdev->speed >= USB_SPEED_SUPER) | 
|---|
| 453 | max = 800; | 
|---|
| 454 | else | 
|---|
| 455 | max = FRAME_TIME_MAX_USECS_ALLOC; | 
|---|
| 456 |  | 
|---|
| 457 | /* report "average" periodic allocation over a microsecond. | 
|---|
| 458 | * the schedules are actually bursty, HCDs need to deal with | 
|---|
| 459 | * that and just compute/report this average. | 
|---|
| 460 | */ | 
|---|
| 461 | data_end += sprintf(buf: data_end, fmt: format_bandwidth, | 
|---|
| 462 | bus->bandwidth_allocated, max, | 
|---|
| 463 | (100 * bus->bandwidth_allocated + max / 2) | 
|---|
| 464 | / max, | 
|---|
| 465 | bus->bandwidth_int_reqs, | 
|---|
| 466 | bus->bandwidth_isoc_reqs); | 
|---|
| 467 |  | 
|---|
| 468 | } | 
|---|
| 469 | data_end = usb_dump_desc(start: data_end, end: pages_start + (2 * PAGE_SIZE) - 256, | 
|---|
| 470 | dev: usbdev); | 
|---|
| 471 |  | 
|---|
| 472 | if (data_end > (pages_start + (2 * PAGE_SIZE) - 256)) | 
|---|
| 473 | data_end += sprintf(buf: data_end, fmt: "(truncated)\n"); | 
|---|
| 474 |  | 
|---|
| 475 | length = data_end - pages_start; | 
|---|
| 476 | /* if we can start copying some data to the user */ | 
|---|
| 477 | if (length > *skip_bytes) { | 
|---|
| 478 | length -= *skip_bytes; | 
|---|
| 479 | if (length > *nbytes) | 
|---|
| 480 | length = *nbytes; | 
|---|
| 481 | if (copy_to_user(to: *buffer, from: pages_start + *skip_bytes, n: length)) { | 
|---|
| 482 | free_pages(addr: (unsigned long)pages_start, order: 1); | 
|---|
| 483 | return -EFAULT; | 
|---|
| 484 | } | 
|---|
| 485 | *nbytes -= length; | 
|---|
| 486 | *file_offset += length; | 
|---|
| 487 | total_written += length; | 
|---|
| 488 | *buffer += length; | 
|---|
| 489 | *skip_bytes = 0; | 
|---|
| 490 | } else | 
|---|
| 491 | *skip_bytes -= length; | 
|---|
| 492 |  | 
|---|
| 493 | free_pages(addr: (unsigned long)pages_start, order: 1); | 
|---|
| 494 |  | 
|---|
| 495 | /* Now look at all of this device's children. */ | 
|---|
| 496 | usb_hub_for_each_child(usbdev, chix, childdev) { | 
|---|
| 497 | usb_lock_device(childdev); | 
|---|
| 498 | ret = usb_device_dump(buffer, nbytes, skip_bytes, | 
|---|
| 499 | file_offset, usbdev: childdev, bus, | 
|---|
| 500 | level: level + 1, index: chix - 1, count: ++cnt); | 
|---|
| 501 | usb_unlock_device(childdev); | 
|---|
| 502 | if (ret == -EFAULT) | 
|---|
| 503 | return total_written; | 
|---|
| 504 | total_written += ret; | 
|---|
| 505 | } | 
|---|
| 506 | return total_written; | 
|---|
| 507 | } | 
|---|
| 508 |  | 
|---|
| 509 | static ssize_t usb_device_read(struct file *file, char __user *buf, | 
|---|
| 510 | size_t nbytes, loff_t *ppos) | 
|---|
| 511 | { | 
|---|
| 512 | struct usb_bus *bus; | 
|---|
| 513 | ssize_t ret, total_written = 0; | 
|---|
| 514 | loff_t skip_bytes = *ppos; | 
|---|
| 515 | int id; | 
|---|
| 516 |  | 
|---|
| 517 | if (*ppos < 0) | 
|---|
| 518 | return -EINVAL; | 
|---|
| 519 | if (nbytes <= 0) | 
|---|
| 520 | return 0; | 
|---|
| 521 |  | 
|---|
| 522 | mutex_lock(lock: &usb_bus_idr_lock); | 
|---|
| 523 | /* print devices for all busses */ | 
|---|
| 524 | idr_for_each_entry(&usb_bus_idr, bus, id) { | 
|---|
| 525 | /* recurse through all children of the root hub */ | 
|---|
| 526 | if (!bus_to_hcd(bus)->rh_registered) | 
|---|
| 527 | continue; | 
|---|
| 528 | usb_lock_device(bus->root_hub); | 
|---|
| 529 | ret = usb_device_dump(buffer: &buf, nbytes: &nbytes, skip_bytes: &skip_bytes, file_offset: ppos, | 
|---|
| 530 | usbdev: bus->root_hub, bus, level: 0, index: 0, count: 0); | 
|---|
| 531 | usb_unlock_device(bus->root_hub); | 
|---|
| 532 | if (ret < 0) { | 
|---|
| 533 | mutex_unlock(lock: &usb_bus_idr_lock); | 
|---|
| 534 | return ret; | 
|---|
| 535 | } | 
|---|
| 536 | total_written += ret; | 
|---|
| 537 | } | 
|---|
| 538 | mutex_unlock(lock: &usb_bus_idr_lock); | 
|---|
| 539 | return total_written; | 
|---|
| 540 | } | 
|---|
| 541 |  | 
|---|
| 542 | const struct file_operations usbfs_devices_fops = { | 
|---|
| 543 | .llseek =	no_seek_end_llseek, | 
|---|
| 544 | .read =		usb_device_read, | 
|---|
| 545 | }; | 
|---|
| 546 |  | 
|---|