1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Copyright (C) 2005-2007 Takahiro Hirofuchi |
4 | */ |
5 | |
6 | #include "usbip_common.h" |
7 | #include "vhci_driver.h" |
8 | #include <limits.h> |
9 | #include <netdb.h> |
10 | #include <libudev.h> |
11 | #include <dirent.h> |
12 | #include "sysfs_utils.h" |
13 | |
14 | #undef PROGNAME |
15 | #define PROGNAME "libusbip" |
16 | |
17 | struct usbip_vhci_driver *vhci_driver; |
18 | struct udev *udev_context; |
19 | |
20 | static struct usbip_imported_device * |
21 | imported_device_init(struct usbip_imported_device *idev, char *busid) |
22 | { |
23 | struct udev_device *sudev; |
24 | |
25 | sudev = udev_device_new_from_subsystem_sysname(udev_context, |
26 | "usb" , busid); |
27 | if (!sudev) { |
28 | dbg("udev_device_new_from_subsystem_sysname failed: %s" , busid); |
29 | goto err; |
30 | } |
31 | read_usb_device(sdev: sudev, udev: &idev->udev); |
32 | udev_device_unref(sudev); |
33 | |
34 | return idev; |
35 | |
36 | err: |
37 | return NULL; |
38 | } |
39 | |
40 | static int parse_status(const char *value) |
41 | { |
42 | int ret = 0; |
43 | char *c; |
44 | |
45 | /* skip a header line */ |
46 | c = strchr(value, '\n'); |
47 | if (!c) |
48 | return -1; |
49 | c++; |
50 | |
51 | while (*c != '\0') { |
52 | int port, status, speed, devid; |
53 | int sockfd; |
54 | char lbusid[SYSFS_BUS_ID_SIZE]; |
55 | struct usbip_imported_device *idev; |
56 | char hub[3]; |
57 | |
58 | ret = sscanf(c, "%2s %d %d %d %x %u %31s\n" , |
59 | hub, &port, &status, &speed, |
60 | &devid, &sockfd, lbusid); |
61 | |
62 | if (ret < 5) { |
63 | dbg("sscanf failed: %d" , ret); |
64 | BUG(); |
65 | } |
66 | |
67 | dbg("hub %s port %d status %d speed %d devid %x" , |
68 | hub, port, status, speed, devid); |
69 | dbg("sockfd %u lbusid %s" , sockfd, lbusid); |
70 | |
71 | /* if a device is connected, look at it */ |
72 | idev = &vhci_driver->idev[port]; |
73 | memset(idev, 0, sizeof(*idev)); |
74 | |
75 | if (strncmp("hs" , hub, 2) == 0) |
76 | idev->hub = HUB_SPEED_HIGH; |
77 | else /* strncmp("ss", hub, 2) == 0 */ |
78 | idev->hub = HUB_SPEED_SUPER; |
79 | |
80 | idev->port = port; |
81 | idev->status = status; |
82 | |
83 | idev->devid = devid; |
84 | |
85 | idev->busnum = (devid >> 16); |
86 | idev->devnum = (devid & 0x0000ffff); |
87 | |
88 | if (idev->status != VDEV_ST_NULL |
89 | && idev->status != VDEV_ST_NOTASSIGNED) { |
90 | idev = imported_device_init(idev, busid: lbusid); |
91 | if (!idev) { |
92 | dbg("imported_device_init failed" ); |
93 | return -1; |
94 | } |
95 | } |
96 | |
97 | /* go to the next line */ |
98 | c = strchr(c, '\n'); |
99 | if (!c) |
100 | break; |
101 | c++; |
102 | } |
103 | |
104 | dbg("exit" ); |
105 | |
106 | return 0; |
107 | } |
108 | |
109 | #define MAX_STATUS_NAME 18 |
110 | |
111 | static int refresh_imported_device_list(void) |
112 | { |
113 | const char *attr_status; |
114 | char status[MAX_STATUS_NAME+1] = "status" ; |
115 | int i, ret; |
116 | |
117 | for (i = 0; i < vhci_driver->ncontrollers; i++) { |
118 | if (i > 0) |
119 | snprintf(status, sizeof(status), "status.%d" , i); |
120 | |
121 | attr_status = udev_device_get_sysattr_value(vhci_driver->hc_device, |
122 | status); |
123 | if (!attr_status) { |
124 | err("udev_device_get_sysattr_value failed" ); |
125 | return -1; |
126 | } |
127 | |
128 | dbg("controller %d" , i); |
129 | |
130 | ret = parse_status(value: attr_status); |
131 | if (ret != 0) |
132 | return ret; |
133 | } |
134 | |
135 | return 0; |
136 | } |
137 | |
138 | static int get_nports(struct udev_device *hc_device) |
139 | { |
140 | const char *attr_nports; |
141 | |
142 | attr_nports = udev_device_get_sysattr_value(hc_device, "nports" ); |
143 | if (!attr_nports) { |
144 | err("udev_device_get_sysattr_value nports failed" ); |
145 | return -1; |
146 | } |
147 | |
148 | return (int)strtoul(attr_nports, NULL, 10); |
149 | } |
150 | |
151 | static int vhci_hcd_filter(const struct dirent *dirent) |
152 | { |
153 | return !strncmp(dirent->d_name, "vhci_hcd." , 9); |
154 | } |
155 | |
156 | static int get_ncontrollers(void) |
157 | { |
158 | struct dirent **namelist; |
159 | struct udev_device *platform; |
160 | int n; |
161 | |
162 | platform = udev_device_get_parent(vhci_driver->hc_device); |
163 | if (platform == NULL) |
164 | return -1; |
165 | |
166 | n = scandir(udev_device_get_syspath(platform), &namelist, vhci_hcd_filter, NULL); |
167 | if (n < 0) |
168 | err("scandir failed" ); |
169 | else { |
170 | for (int i = 0; i < n; i++) |
171 | free(namelist[i]); |
172 | free(namelist); |
173 | } |
174 | |
175 | return n; |
176 | } |
177 | |
178 | /* |
179 | * Read the given port's record. |
180 | * |
181 | * To avoid buffer overflow we will read the entire line and |
182 | * validate each part's size. The initial buffer is padded by 4 to |
183 | * accommodate the 2 spaces, 1 newline and an additional character |
184 | * which is needed to properly validate the 3rd part without it being |
185 | * truncated to an acceptable length. |
186 | */ |
187 | static int read_record(int rhport, char *host, unsigned long host_len, |
188 | char *port, unsigned long port_len, char *busid) |
189 | { |
190 | int part; |
191 | FILE *file; |
192 | char path[PATH_MAX+1]; |
193 | char *buffer, *start, *end; |
194 | char delim[] = {' ', ' ', '\n'}; |
195 | int max_len[] = {(int)host_len, (int)port_len, SYSFS_BUS_ID_SIZE}; |
196 | size_t buffer_len = host_len + port_len + SYSFS_BUS_ID_SIZE + 4; |
197 | |
198 | buffer = malloc(buffer_len); |
199 | if (!buffer) |
200 | return -1; |
201 | |
202 | snprintf(path, PATH_MAX, VHCI_STATE_PATH"/port%d" , rhport); |
203 | |
204 | file = fopen(path, "r" ); |
205 | if (!file) { |
206 | err("fopen" ); |
207 | free(buffer); |
208 | return -1; |
209 | } |
210 | |
211 | if (fgets(buffer, buffer_len, file) == NULL) { |
212 | err("fgets" ); |
213 | free(buffer); |
214 | fclose(file); |
215 | return -1; |
216 | } |
217 | fclose(file); |
218 | |
219 | /* validate the length of each of the 3 parts */ |
220 | start = buffer; |
221 | for (part = 0; part < 3; part++) { |
222 | end = strchr(start, delim[part]); |
223 | if (end == NULL || (end - start) > max_len[part]) { |
224 | free(buffer); |
225 | return -1; |
226 | } |
227 | start = end + 1; |
228 | } |
229 | |
230 | if (sscanf(buffer, "%s %s %s\n" , host, port, busid) != 3) { |
231 | err("sscanf" ); |
232 | free(buffer); |
233 | return -1; |
234 | } |
235 | |
236 | free(buffer); |
237 | |
238 | return 0; |
239 | } |
240 | |
241 | /* ---------------------------------------------------------------------- */ |
242 | |
243 | int usbip_vhci_driver_open(void) |
244 | { |
245 | int nports; |
246 | struct udev_device *hc_device; |
247 | |
248 | udev_context = udev_new(); |
249 | if (!udev_context) { |
250 | err("udev_new failed" ); |
251 | return -1; |
252 | } |
253 | |
254 | /* will be freed in usbip_driver_close() */ |
255 | hc_device = |
256 | udev_device_new_from_subsystem_sysname(udev_context, |
257 | USBIP_VHCI_BUS_TYPE, |
258 | USBIP_VHCI_DEVICE_NAME); |
259 | if (!hc_device) { |
260 | err("udev_device_new_from_subsystem_sysname failed" ); |
261 | goto err; |
262 | } |
263 | |
264 | nports = get_nports(hc_device); |
265 | if (nports <= 0) { |
266 | err("no available ports" ); |
267 | goto err; |
268 | } |
269 | dbg("available ports: %d" , nports); |
270 | |
271 | vhci_driver = calloc(1, sizeof(struct usbip_vhci_driver) + |
272 | nports * sizeof(struct usbip_imported_device)); |
273 | if (!vhci_driver) { |
274 | err("vhci_driver allocation failed" ); |
275 | goto err; |
276 | } |
277 | |
278 | vhci_driver->nports = nports; |
279 | vhci_driver->hc_device = hc_device; |
280 | vhci_driver->ncontrollers = get_ncontrollers(); |
281 | dbg("available controllers: %d" , vhci_driver->ncontrollers); |
282 | |
283 | if (vhci_driver->ncontrollers <=0) { |
284 | err("no available usb controllers" ); |
285 | goto err; |
286 | } |
287 | |
288 | if (refresh_imported_device_list()) |
289 | goto err; |
290 | |
291 | return 0; |
292 | |
293 | err: |
294 | udev_device_unref(hc_device); |
295 | |
296 | if (vhci_driver) |
297 | free(vhci_driver); |
298 | |
299 | vhci_driver = NULL; |
300 | |
301 | udev_unref(udev_context); |
302 | |
303 | return -1; |
304 | } |
305 | |
306 | |
307 | void usbip_vhci_driver_close(void) |
308 | { |
309 | if (!vhci_driver) |
310 | return; |
311 | |
312 | udev_device_unref(vhci_driver->hc_device); |
313 | |
314 | free(vhci_driver); |
315 | |
316 | vhci_driver = NULL; |
317 | |
318 | udev_unref(udev_context); |
319 | } |
320 | |
321 | |
322 | int usbip_vhci_refresh_device_list(void) |
323 | { |
324 | |
325 | if (refresh_imported_device_list()) |
326 | goto err; |
327 | |
328 | return 0; |
329 | err: |
330 | dbg("failed to refresh device list" ); |
331 | return -1; |
332 | } |
333 | |
334 | |
335 | int usbip_vhci_get_free_port(uint32_t speed) |
336 | { |
337 | for (int i = 0; i < vhci_driver->nports; i++) { |
338 | |
339 | switch (speed) { |
340 | case USB_SPEED_SUPER: |
341 | if (vhci_driver->idev[i].hub != HUB_SPEED_SUPER) |
342 | continue; |
343 | break; |
344 | default: |
345 | if (vhci_driver->idev[i].hub != HUB_SPEED_HIGH) |
346 | continue; |
347 | break; |
348 | } |
349 | |
350 | if (vhci_driver->idev[i].status == VDEV_ST_NULL) |
351 | return vhci_driver->idev[i].port; |
352 | } |
353 | |
354 | return -1; |
355 | } |
356 | |
357 | int usbip_vhci_attach_device2(uint8_t port, int sockfd, uint32_t devid, |
358 | uint32_t speed) { |
359 | char buff[200]; /* what size should be ? */ |
360 | char attach_attr_path[SYSFS_PATH_MAX]; |
361 | char attr_attach[] = "attach" ; |
362 | const char *path; |
363 | int ret; |
364 | |
365 | snprintf(buff, sizeof(buff), "%u %d %u %u" , |
366 | port, sockfd, devid, speed); |
367 | dbg("writing: %s" , buff); |
368 | |
369 | path = udev_device_get_syspath(vhci_driver->hc_device); |
370 | snprintf(attach_attr_path, sizeof(attach_attr_path), "%s/%s" , |
371 | path, attr_attach); |
372 | dbg("attach attribute path: %s" , attach_attr_path); |
373 | |
374 | ret = write_sysfs_attribute(attr_path: attach_attr_path, new_value: buff, len: strlen(buff)); |
375 | if (ret < 0) { |
376 | dbg("write_sysfs_attribute failed" ); |
377 | return -1; |
378 | } |
379 | |
380 | dbg("attached port: %d" , port); |
381 | |
382 | return 0; |
383 | } |
384 | |
385 | static unsigned long get_devid(uint8_t busnum, uint8_t devnum) |
386 | { |
387 | return (busnum << 16) | devnum; |
388 | } |
389 | |
390 | /* will be removed */ |
391 | int usbip_vhci_attach_device(uint8_t port, int sockfd, uint8_t busnum, |
392 | uint8_t devnum, uint32_t speed) |
393 | { |
394 | int devid = get_devid(busnum, devnum); |
395 | |
396 | return usbip_vhci_attach_device2(port, sockfd, devid, speed); |
397 | } |
398 | |
399 | int usbip_vhci_detach_device(uint8_t port) |
400 | { |
401 | char detach_attr_path[SYSFS_PATH_MAX]; |
402 | char attr_detach[] = "detach" ; |
403 | char buff[200]; /* what size should be ? */ |
404 | const char *path; |
405 | int ret; |
406 | |
407 | snprintf(buff, sizeof(buff), "%u" , port); |
408 | dbg("writing: %s" , buff); |
409 | |
410 | path = udev_device_get_syspath(vhci_driver->hc_device); |
411 | snprintf(detach_attr_path, sizeof(detach_attr_path), "%s/%s" , |
412 | path, attr_detach); |
413 | dbg("detach attribute path: %s" , detach_attr_path); |
414 | |
415 | ret = write_sysfs_attribute(attr_path: detach_attr_path, new_value: buff, len: strlen(buff)); |
416 | if (ret < 0) { |
417 | dbg("write_sysfs_attribute failed" ); |
418 | return -1; |
419 | } |
420 | |
421 | dbg("detached port: %d" , port); |
422 | |
423 | return 0; |
424 | } |
425 | |
426 | int usbip_vhci_imported_device_dump(struct usbip_imported_device *idev) |
427 | { |
428 | char product_name[100]; |
429 | char host[NI_MAXHOST] = "unknown host" ; |
430 | char serv[NI_MAXSERV] = "unknown port" ; |
431 | char remote_busid[SYSFS_BUS_ID_SIZE]; |
432 | int ret; |
433 | int read_record_error = 0; |
434 | |
435 | if (idev->status == VDEV_ST_NULL || idev->status == VDEV_ST_NOTASSIGNED) |
436 | return 0; |
437 | |
438 | ret = read_record(rhport: idev->port, host: host, host_len: sizeof(host), port: serv, port_len: sizeof(serv), |
439 | busid: remote_busid); |
440 | if (ret) { |
441 | err("read_record" ); |
442 | read_record_error = 1; |
443 | } |
444 | |
445 | printf("Port %02d: <%s> at %s\n" , idev->port, |
446 | usbip_status_string(status: idev->status), |
447 | usbip_speed_string(num: idev->udev.speed)); |
448 | |
449 | usbip_names_get_product(buff: product_name, size: sizeof(product_name), |
450 | vendor: idev->udev.idVendor, product: idev->udev.idProduct); |
451 | |
452 | printf(" %s\n" , product_name); |
453 | |
454 | if (!read_record_error) { |
455 | printf("%10s -> usbip://%s:%s/%s\n" , idev->udev.busid, |
456 | host, serv, remote_busid); |
457 | printf("%10s -> remote bus/dev %03d/%03d\n" , " " , |
458 | idev->busnum, idev->devnum); |
459 | } else { |
460 | printf("%10s -> unknown host, remote port and remote busid\n" , |
461 | idev->udev.busid); |
462 | printf("%10s -> remote bus/dev %03d/%03d\n" , " " , |
463 | idev->busnum, idev->devnum); |
464 | } |
465 | |
466 | return 0; |
467 | } |
468 | |