1 /*
2  * Copyright (C) 2011 matt mooney <mfm@muteddisk.com>
3  *               2005-2007 Takahiro Hirofuchi
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program. If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include <sysfs/libsysfs.h>
20 
21 #include <errno.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 
26 #include <getopt.h>
27 
28 #include "usbip_common.h"
29 #include "utils.h"
30 #include "usbip.h"
31 
32 enum unbind_status {
33 	UNBIND_ST_OK,
34 	UNBIND_ST_USBIP_HOST,
35 	UNBIND_ST_FAILED
36 };
37 
38 static const char usbip_bind_usage_string[] =
39 	"usbip bind <args>\n"
40 	"    -b, --busid=<busid>    Bind " USBIP_HOST_DRV_NAME ".ko to device "
41 	"on <busid>\n";
42 
usbip_bind_usage(void)43 void usbip_bind_usage(void)
44 {
45 	printf("usage: %s", usbip_bind_usage_string);
46 }
47 
48 /* call at unbound state */
bind_usbip(char * busid)49 static int bind_usbip(char *busid)
50 {
51 	char bus_type[] = "usb";
52 	char attr_name[] = "bind";
53 	char sysfs_mntpath[SYSFS_PATH_MAX];
54 	char bind_attr_path[SYSFS_PATH_MAX];
55 	char intf_busid[SYSFS_BUS_ID_SIZE];
56 	struct sysfs_device *busid_dev;
57 	struct sysfs_attribute *bind_attr;
58 	struct sysfs_attribute *bConfValue;
59 	struct sysfs_attribute *bNumIntfs;
60 	int i, failed = 0;
61 	int rc, ret = -1;
62 
63 	rc = sysfs_get_mnt_path(sysfs_mntpath, SYSFS_PATH_MAX);
64 	if (rc < 0) {
65 		err("sysfs must be mounted: %s", strerror(errno));
66 		return -1;
67 	}
68 
69 	snprintf(bind_attr_path, sizeof(bind_attr_path), "%s/%s/%s/%s/%s/%s",
70 		 sysfs_mntpath, SYSFS_BUS_NAME, bus_type, SYSFS_DRIVERS_NAME,
71 		 USBIP_HOST_DRV_NAME, attr_name);
72 
73 	bind_attr = sysfs_open_attribute(bind_attr_path);
74 	if (!bind_attr) {
75 		dbg("problem getting bind attribute: %s", strerror(errno));
76 		return -1;
77 	}
78 
79 	busid_dev = sysfs_open_device(bus_type, busid);
80 	if (!busid_dev) {
81 		dbg("sysfs_open_device %s failed: %s", busid, strerror(errno));
82 		goto err_close_bind_attr;
83 	}
84 
85 	bConfValue = sysfs_get_device_attr(busid_dev, "bConfigurationValue");
86 	bNumIntfs  = sysfs_get_device_attr(busid_dev, "bNumInterfaces");
87 
88 	if (!bConfValue || !bNumIntfs) {
89 		dbg("problem getting device attributes: %s",
90 		    strerror(errno));
91 		goto err_close_busid_dev;
92 	}
93 
94 	for (i = 0; i < atoi(bNumIntfs->value); i++) {
95 		snprintf(intf_busid, SYSFS_BUS_ID_SIZE, "%s:%.1s.%d", busid,
96 			 bConfValue->value, i);
97 
98 		rc = sysfs_write_attribute(bind_attr, intf_busid,
99 					   SYSFS_BUS_ID_SIZE);
100 		if (rc < 0) {
101 			dbg("bind driver at %s failed", intf_busid);
102 			failed = 1;
103 		}
104 	}
105 
106 	if (!failed)
107 		ret = 0;
108 
109 err_close_busid_dev:
110 	sysfs_close_device(busid_dev);
111 err_close_bind_attr:
112 	sysfs_close_attribute(bind_attr);
113 
114 	return ret;
115 }
116 
117 /* buggy driver may cause dead lock */
unbind_other(char * busid)118 static int unbind_other(char *busid)
119 {
120 	char bus_type[] = "usb";
121 	char intf_busid[SYSFS_BUS_ID_SIZE];
122 	struct sysfs_device *busid_dev;
123 	struct sysfs_device *intf_dev;
124 	struct sysfs_driver *intf_drv;
125 	struct sysfs_attribute *unbind_attr;
126 	struct sysfs_attribute *bConfValue;
127 	struct sysfs_attribute *bDevClass;
128 	struct sysfs_attribute *bNumIntfs;
129 	int i, rc;
130 	enum unbind_status status = UNBIND_ST_OK;
131 
132 	busid_dev = sysfs_open_device(bus_type, busid);
133 	if (!busid_dev) {
134 		dbg("sysfs_open_device %s failed: %s", busid, strerror(errno));
135 		return -1;
136 	}
137 
138 	bConfValue = sysfs_get_device_attr(busid_dev, "bConfigurationValue");
139 	bDevClass  = sysfs_get_device_attr(busid_dev, "bDeviceClass");
140 	bNumIntfs  = sysfs_get_device_attr(busid_dev, "bNumInterfaces");
141 	if (!bConfValue || !bDevClass || !bNumIntfs) {
142 		dbg("problem getting device attributes: %s",
143 		    strerror(errno));
144 		goto err_close_busid_dev;
145 	}
146 
147 	if (!strncmp(bDevClass->value, "09", bDevClass->len)) {
148 		dbg("skip unbinding of hub");
149 		goto err_close_busid_dev;
150 	}
151 
152 	for (i = 0; i < atoi(bNumIntfs->value); i++) {
153 		snprintf(intf_busid, SYSFS_BUS_ID_SIZE, "%s:%.1s.%d", busid,
154 			 bConfValue->value, i);
155 		intf_dev = sysfs_open_device(bus_type, intf_busid);
156 		if (!intf_dev) {
157 			dbg("could not open interface device: %s",
158 			    strerror(errno));
159 			goto err_close_busid_dev;
160 		}
161 
162 		dbg("%s -> %s", intf_dev->name,  intf_dev->driver_name);
163 
164 		if (!strncmp("unknown", intf_dev->driver_name, SYSFS_NAME_LEN))
165 			/* unbound interface */
166 			continue;
167 
168 		if (!strncmp(USBIP_HOST_DRV_NAME, intf_dev->driver_name,
169 			     SYSFS_NAME_LEN)) {
170 			/* already bound to usbip-host */
171 			status = UNBIND_ST_USBIP_HOST;
172 			continue;
173 		}
174 
175 		/* unbinding */
176 		intf_drv = sysfs_open_driver(bus_type, intf_dev->driver_name);
177 		if (!intf_drv) {
178 			dbg("could not open interface driver on %s: %s",
179 			    intf_dev->name, strerror(errno));
180 			goto err_close_intf_dev;
181 		}
182 
183 		unbind_attr = sysfs_get_driver_attr(intf_drv, "unbind");
184 		if (!unbind_attr) {
185 			dbg("problem getting interface driver attribute: %s",
186 			    strerror(errno));
187 			goto err_close_intf_drv;
188 		}
189 
190 		rc = sysfs_write_attribute(unbind_attr, intf_dev->bus_id,
191 					   SYSFS_BUS_ID_SIZE);
192 		if (rc < 0) {
193 			/* NOTE: why keep unbinding other interfaces? */
194 			dbg("unbind driver at %s failed", intf_dev->bus_id);
195 			status = UNBIND_ST_FAILED;
196 		}
197 
198 		sysfs_close_driver(intf_drv);
199 		sysfs_close_device(intf_dev);
200 	}
201 
202 	goto out;
203 
204 err_close_intf_drv:
205 	sysfs_close_driver(intf_drv);
206 err_close_intf_dev:
207 	sysfs_close_device(intf_dev);
208 err_close_busid_dev:
209 	status = UNBIND_ST_FAILED;
210 out:
211 	sysfs_close_device(busid_dev);
212 
213 	return status;
214 }
215 
bind_device(char * busid)216 static int bind_device(char *busid)
217 {
218 	int rc;
219 
220 	rc = unbind_other(busid);
221 	if (rc == UNBIND_ST_FAILED) {
222 		err("could not unbind driver from device on busid %s", busid);
223 		return -1;
224 	} else if (rc == UNBIND_ST_USBIP_HOST) {
225 		err("device on busid %s is already bound to %s", busid,
226 		    USBIP_HOST_DRV_NAME);
227 		return -1;
228 	}
229 
230 	rc = modify_match_busid(busid, 1);
231 	if (rc < 0) {
232 		err("unable to bind device on %s", busid);
233 		return -1;
234 	}
235 
236 	rc = bind_usbip(busid);
237 	if (rc < 0) {
238 		err("could not bind device to %s", USBIP_HOST_DRV_NAME);
239 		modify_match_busid(busid, 0);
240 		return -1;
241 	}
242 
243 	printf("bind device on busid %s: complete\n", busid);
244 
245 	return 0;
246 }
247 
usbip_bind(int argc,char * argv[])248 int usbip_bind(int argc, char *argv[])
249 {
250 	static const struct option opts[] = {
251 		{ "busid", required_argument, NULL, 'b' },
252 		{ NULL,    0,                 NULL,  0  }
253 	};
254 
255 	int opt;
256 	int ret = -1;
257 
258 	for (;;) {
259 		opt = getopt_long(argc, argv, "b:", opts, NULL);
260 
261 		if (opt == -1)
262 			break;
263 
264 		switch (opt) {
265 		case 'b':
266 			ret = bind_device(optarg);
267 			goto out;
268 		default:
269 			goto err_out;
270 		}
271 	}
272 
273 err_out:
274 	usbip_bind_usage();
275 out:
276 	return ret;
277 }
278