1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Copyright (C) 2011 matt mooney <mfm@muteddisk.com> |
4 | * 2005-2007 Takahiro Hirofuchi |
5 | */ |
6 | |
7 | #include <libudev.h> |
8 | |
9 | #include <errno.h> |
10 | #include <stdio.h> |
11 | #include <stdlib.h> |
12 | #include <string.h> |
13 | |
14 | #include <getopt.h> |
15 | |
16 | #include "usbip_common.h" |
17 | #include "utils.h" |
18 | #include "usbip.h" |
19 | #include "sysfs_utils.h" |
20 | |
21 | enum unbind_status { |
22 | UNBIND_ST_OK, |
23 | UNBIND_ST_USBIP_HOST, |
24 | UNBIND_ST_FAILED |
25 | }; |
26 | |
27 | static const char usbip_bind_usage_string[] = |
28 | "usbip bind <args>\n" |
29 | " -b, --busid=<busid> Bind " USBIP_HOST_DRV_NAME ".ko to device " |
30 | "on <busid>\n" ; |
31 | |
32 | void usbip_bind_usage(void) |
33 | { |
34 | printf("usage: %s" , usbip_bind_usage_string); |
35 | } |
36 | |
37 | /* call at unbound state */ |
38 | static int bind_usbip(char *busid) |
39 | { |
40 | char attr_name[] = "bind" ; |
41 | char bind_attr_path[SYSFS_PATH_MAX]; |
42 | int rc = -1; |
43 | |
44 | snprintf(bind_attr_path, sizeof(bind_attr_path), "%s/%s/%s/%s/%s/%s" , |
45 | SYSFS_MNT_PATH, SYSFS_BUS_NAME, SYSFS_BUS_TYPE, |
46 | SYSFS_DRIVERS_NAME, USBIP_HOST_DRV_NAME, attr_name); |
47 | |
48 | rc = write_sysfs_attribute(bind_attr_path, busid, strlen(busid)); |
49 | if (rc < 0) { |
50 | err("error binding device %s to driver: %s" , busid, |
51 | strerror(errno)); |
52 | return -1; |
53 | } |
54 | |
55 | return 0; |
56 | } |
57 | |
58 | /* buggy driver may cause dead lock */ |
59 | static int unbind_other(char *busid) |
60 | { |
61 | enum unbind_status status = UNBIND_ST_OK; |
62 | |
63 | char attr_name[] = "unbind" ; |
64 | char unbind_attr_path[SYSFS_PATH_MAX]; |
65 | int rc = -1; |
66 | |
67 | struct udev *udev; |
68 | struct udev_device *dev; |
69 | const char *driver; |
70 | const char *bDevClass; |
71 | |
72 | /* Create libudev context. */ |
73 | udev = udev_new(); |
74 | |
75 | /* Get the device. */ |
76 | dev = udev_device_new_from_subsystem_sysname(udev, "usb" , busid); |
77 | if (!dev) { |
78 | dbg("unable to find device with bus ID %s" , busid); |
79 | goto err_close_busid_dev; |
80 | } |
81 | |
82 | /* Check what kind of device it is. */ |
83 | bDevClass = udev_device_get_sysattr_value(dev, "bDeviceClass" ); |
84 | if (!bDevClass) { |
85 | dbg("unable to get bDevClass device attribute" ); |
86 | goto err_close_busid_dev; |
87 | } |
88 | |
89 | if (!strncmp(bDevClass, "09" , strlen(bDevClass))) { |
90 | dbg("skip unbinding of hub" ); |
91 | goto err_close_busid_dev; |
92 | } |
93 | |
94 | /* Get the device driver. */ |
95 | driver = udev_device_get_driver(dev); |
96 | if (!driver) { |
97 | /* No driver bound to this device. */ |
98 | goto out; |
99 | } |
100 | |
101 | if (!strncmp(USBIP_HOST_DRV_NAME, driver, |
102 | strlen(USBIP_HOST_DRV_NAME))) { |
103 | /* Already bound to usbip-host. */ |
104 | status = UNBIND_ST_USBIP_HOST; |
105 | goto out; |
106 | } |
107 | |
108 | /* Unbind device from driver. */ |
109 | snprintf(unbind_attr_path, sizeof(unbind_attr_path), "%s/%s/%s/%s/%s/%s" , |
110 | SYSFS_MNT_PATH, SYSFS_BUS_NAME, SYSFS_BUS_TYPE, |
111 | SYSFS_DRIVERS_NAME, driver, attr_name); |
112 | |
113 | rc = write_sysfs_attribute(unbind_attr_path, busid, strlen(busid)); |
114 | if (rc < 0) { |
115 | err("error unbinding device %s from driver" , busid); |
116 | goto err_close_busid_dev; |
117 | } |
118 | |
119 | goto out; |
120 | |
121 | err_close_busid_dev: |
122 | status = UNBIND_ST_FAILED; |
123 | out: |
124 | udev_device_unref(dev); |
125 | udev_unref(udev); |
126 | |
127 | return status; |
128 | } |
129 | |
130 | static int bind_device(char *busid) |
131 | { |
132 | int rc; |
133 | struct udev *udev; |
134 | struct udev_device *dev; |
135 | const char *devpath; |
136 | |
137 | /* Check whether the device with this bus ID exists. */ |
138 | udev = udev_new(); |
139 | dev = udev_device_new_from_subsystem_sysname(udev, "usb" , busid); |
140 | if (!dev) { |
141 | err("device with the specified bus ID does not exist" ); |
142 | return -1; |
143 | } |
144 | devpath = udev_device_get_devpath(dev); |
145 | udev_unref(udev); |
146 | |
147 | /* If the device is already attached to vhci_hcd - bail out */ |
148 | if (strstr(devpath, USBIP_VHCI_DRV_NAME)) { |
149 | err("bind loop detected: device: %s is attached to %s\n" , |
150 | devpath, USBIP_VHCI_DRV_NAME); |
151 | return -1; |
152 | } |
153 | |
154 | rc = unbind_other(busid); |
155 | if (rc == UNBIND_ST_FAILED) { |
156 | err("could not unbind driver from device on busid %s" , busid); |
157 | return -1; |
158 | } else if (rc == UNBIND_ST_USBIP_HOST) { |
159 | err("device on busid %s is already bound to %s" , busid, |
160 | USBIP_HOST_DRV_NAME); |
161 | return -1; |
162 | } |
163 | |
164 | rc = modify_match_busid(busid, add: 1); |
165 | if (rc < 0) { |
166 | err("unable to bind device on %s" , busid); |
167 | return -1; |
168 | } |
169 | |
170 | rc = bind_usbip(busid); |
171 | if (rc < 0) { |
172 | err("could not bind device to %s" , USBIP_HOST_DRV_NAME); |
173 | modify_match_busid(busid, add: 0); |
174 | return -1; |
175 | } |
176 | |
177 | info("bind device on busid %s: complete" , busid); |
178 | |
179 | return 0; |
180 | } |
181 | |
182 | int usbip_bind(int argc, char *argv[]) |
183 | { |
184 | static const struct option opts[] = { |
185 | { "busid" , required_argument, NULL, 'b' }, |
186 | { NULL, 0, NULL, 0 } |
187 | }; |
188 | |
189 | int opt; |
190 | int ret = -1; |
191 | |
192 | for (;;) { |
193 | opt = getopt_long(argc, argv, "b:" , opts, NULL); |
194 | |
195 | if (opt == -1) |
196 | break; |
197 | |
198 | switch (opt) { |
199 | case 'b': |
200 | ret = bind_device(busid: optarg); |
201 | goto out; |
202 | default: |
203 | goto err_out; |
204 | } |
205 | } |
206 | |
207 | err_out: |
208 | usbip_bind_usage(); |
209 | out: |
210 | return ret; |
211 | } |
212 | |