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