• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * hid2hci : switch the radio on devices that support
3  *           it from HID to HCI and back
4  *
5  *  Copyright (C) 2003-2010  Marcel Holtmann <marcel@holtmann.org>
6  *  Copyright (C) 2008-2009  Mario Limonciello <mario_limonciello@dell.com>
7  *  Copyright (C) 2009-2011  Kay Sievers <kay.sievers@vrfy.org>
8  *
9  *  This program is free software; you can redistribute it and/or modify
10  *  it under the terms of the GNU General Public License as published by
11  *  the Free Software Foundation; either version 2 of the License, or
12  *  (at your option) any later version.
13  *
14  *  This program is distributed in the hope that it will be useful,
15  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
16  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  *  GNU General Public License for more details.
18  *
19  *  You should have received a copy of the GNU General Public License
20  *  along with this program; if not, write to the Free Software
21  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
22  *
23  */
24 
25 #ifdef HAVE_CONFIG_H
26 #include <config.h>
27 #endif
28 
29 #include <stdio.h>
30 #include <errno.h>
31 #include <fcntl.h>
32 #include <stdint.h>
33 #include <string.h>
34 #include <getopt.h>
35 #include <sys/ioctl.h>
36 #include <linux/types.h>
37 #include <linux/hiddev.h>
38 #include <usb.h>
39 
40 #include "libudev.h"
41 
42 enum mode {
43 	HCI = 0,
44 	HID = 1,
45 };
46 
usb_switch_csr(struct usb_dev_handle * dev,enum mode mode)47 static int usb_switch_csr(struct usb_dev_handle *dev, enum mode mode)
48 {
49 	int err;
50 
51 	err = usb_control_msg(dev,
52 			USB_ENDPOINT_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
53 			0, mode, 0, NULL, 0, 10000);
54 	if (err == 0) {
55 		err = -1;
56 		errno = EALREADY;
57 	} else if (errno == ETIMEDOUT)
58 		err = 0;
59 
60 	return err;
61 }
62 
hid_logitech_send_report(int fd,const char * buf,size_t size)63 static int hid_logitech_send_report(int fd, const char *buf, size_t size)
64 {
65 	struct hiddev_report_info rinfo;
66 	struct hiddev_usage_ref uref;
67 	unsigned int i;
68 	int err;
69 
70 	for (i = 0; i < size; i++) {
71 		memset(&uref, 0, sizeof(uref));
72 		uref.report_type = HID_REPORT_TYPE_OUTPUT;
73 		uref.report_id   = 0x10;
74 		uref.field_index = 0;
75 		uref.usage_index = i;
76 		uref.usage_code  = 0xff000001;
77 		uref.value       = buf[i] & 0x000000ff;
78 		err = ioctl(fd, HIDIOCSUSAGE, &uref);
79 		if (err < 0)
80 			return err;
81 	}
82 
83 	memset(&rinfo, 0, sizeof(rinfo));
84 	rinfo.report_type = HID_REPORT_TYPE_OUTPUT;
85 	rinfo.report_id   = 0x10;
86 	rinfo.num_fields  = 1;
87 	err = ioctl(fd, HIDIOCSREPORT, &rinfo);
88 
89 	return err;
90 }
91 
hid_switch_logitech(const char * filename)92 static int hid_switch_logitech(const char *filename)
93 {
94 	char rep1[] = { 0xff, 0x80, 0x80, 0x01, 0x00, 0x00 };
95 	char rep2[] = { 0xff, 0x80, 0x00, 0x00, 0x30, 0x00 };
96 	char rep3[] = { 0xff, 0x81, 0x80, 0x00, 0x00, 0x00 };
97 	int fd;
98 	int err = -1;
99 
100 	fd = open(filename, O_RDWR);
101 	if (fd < 0)
102 		return err;
103 
104 	err = ioctl(fd, HIDIOCINITREPORT, 0);
105 	if (err < 0)
106 		goto out;
107 
108 	err = hid_logitech_send_report(fd, rep1, sizeof(rep1));
109 	if (err < 0)
110 		goto out;
111 
112 	err = hid_logitech_send_report(fd, rep2, sizeof(rep2));
113 	if (err < 0)
114 		goto out;
115 
116 	err = hid_logitech_send_report(fd, rep3, sizeof(rep3));
117 out:
118 	close(fd);
119 	return err;
120 }
121 
usb_switch_dell(struct usb_dev_handle * dev,enum mode mode)122 static int usb_switch_dell(struct usb_dev_handle *dev, enum mode mode)
123 {
124 	char report[] = { 0x7f, 0x00, 0x00, 0x00 };
125 	int err;
126 
127 	switch (mode) {
128 	case HCI:
129 		report[1] = 0x13;
130 		break;
131 	case HID:
132 		report[1] = 0x14;
133 		break;
134 	}
135 
136 	/* Don't need to check return, as might not be in use */
137 	usb_detach_kernel_driver_np(dev, 0);
138 
139 	if (usb_claim_interface(dev, 0) < 0)
140 		return -EIO;
141 
142 	err = usb_control_msg(dev,
143 			USB_ENDPOINT_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
144 			USB_REQ_SET_CONFIGURATION, 0x7f | (0x03 << 8), 0,
145 			report, sizeof(report), 5000);
146 
147 	if (err == 0) {
148 		err = -1;
149 		errno = EALREADY;
150 	} else {
151 		if (errno == ETIMEDOUT)
152 			err = 0;
153 	}
154 	return err;
155 }
156 
157 /*
158  * libusb needs to scan and open all devices, just to to find the
159  * device we already have. This should be fixed in libusb.
160  */
usb_device_open_from_udev(struct udev_device * usb_dev)161 static struct usb_device *usb_device_open_from_udev(struct udev_device *usb_dev)
162 {
163 	struct usb_bus *bus;
164 	const char *str;
165 	int busnum;
166 	int devnum;
167 
168 	str = udev_device_get_sysattr_value(usb_dev, "busnum");
169 	if (str == NULL)
170 		return NULL;
171 	busnum = strtol(str, NULL, 0);
172 
173 	str = udev_device_get_sysattr_value(usb_dev, "devnum");
174 	if (str == NULL)
175 		return NULL;
176 	devnum = strtol(str, NULL, 0);
177 
178 	usb_init();
179 	usb_find_busses();
180 	usb_find_devices();
181 
182 	for (bus = usb_get_busses(); bus; bus = bus->next) {
183 		struct usb_device *dev;
184 
185 		if (strtol(bus->dirname, NULL, 10) != busnum)
186 			continue;
187 
188 		for (dev = bus->devices; dev; dev = dev->next) {
189 			if (dev->devnum == devnum)
190 				return dev;
191 		}
192 	}
193 
194 	return NULL;
195 }
196 
find_device(struct udev_device * udev_dev)197 static struct usb_dev_handle *find_device(struct udev_device *udev_dev)
198 {
199 	struct usb_device *dev;
200 
201 	dev = usb_device_open_from_udev(udev_dev);
202 	if (dev == NULL)
203 		return NULL;
204 	return usb_open(dev);
205 }
206 
usage(const char * error)207 static void usage(const char *error)
208 {
209 	if (error)
210 		fprintf(stderr,"\n%s\n", error);
211 	else
212 		printf("hid2hci - Bluetooth HID to HCI mode switching utility\n\n");
213 
214 	printf("Usage: hid2hci [options]\n"
215 		"  --mode=               mode to switch to [hid|hci] (default hci)\n"
216 		"  --devpath=            sys device path\n"
217 		"  --method=             method to use to switch [csr|logitech-hid|dell]\n"
218 		"  --help\n\n");
219 }
220 
main(int argc,char * argv[])221 int main(int argc, char *argv[])
222 {
223 	static const struct option options[] = {
224 		{ "help", no_argument, NULL, 'h' },
225 		{ "mode", required_argument, NULL, 'm' },
226 		{ "devpath", required_argument, NULL, 'p' },
227 		{ "method", required_argument, NULL, 'M' },
228 		{ }
229 	};
230 	enum method {
231 		METHOD_UNDEF,
232 		METHOD_CSR,
233 		METHOD_LOGITECH_HID,
234 		METHOD_DELL,
235 	} method = METHOD_UNDEF;
236 	struct udev *udev;
237 	struct udev_device *udev_dev = NULL;
238 	char syspath[PATH_MAX];
239 	int (*usb_switch)(struct usb_dev_handle *dev, enum mode mode) = NULL;
240 	enum mode mode = HCI;
241 	const char *devpath = NULL;
242 	int err = -1;
243 	int rc = 1;
244 
245 	for (;;) {
246 		int option;
247 
248 		option = getopt_long(argc, argv, "m:p:M:h", options, NULL);
249 		if (option == -1)
250 			break;
251 
252 		switch (option) {
253 		case 'm':
254 			if (!strcmp(optarg, "hid")) {
255 				mode = HID;
256 			} else if (!strcmp(optarg, "hci")) {
257 				mode = HCI;
258 			} else {
259 				usage("error: undefined radio mode\n");
260 				exit(1);
261 			}
262 			break;
263 		case 'p':
264 			devpath = optarg;
265 			break;
266 		case 'M':
267 			if (!strcmp(optarg, "csr")) {
268 				method = METHOD_CSR;
269 				usb_switch = usb_switch_csr;
270 			} else if (!strcmp(optarg, "logitech-hid")) {
271 				method = METHOD_LOGITECH_HID;
272 			} else if (!strcmp(optarg, "dell")) {
273 				method = METHOD_DELL;
274 				usb_switch = usb_switch_dell;
275 			} else {
276 				usage("error: undefined switching method\n");
277 				exit(1);
278 			}
279 			break;
280 		case 'h':
281 			usage(NULL);
282 		}
283 	}
284 
285 	if (!devpath || method == METHOD_UNDEF) {
286 		usage("error: --devpath= and --method= must be defined\n");
287 		exit(1);
288 	}
289 
290 	udev = udev_new();
291 	if (udev == NULL)
292 		goto exit;
293 
294 	snprintf(syspath, sizeof(syspath), "%s/%s", udev_get_sys_path(udev), devpath);
295 	udev_dev = udev_device_new_from_syspath(udev, syspath);
296 	if (udev_dev == NULL) {
297 		fprintf(stderr, "error: could not find '%s'\n", devpath);
298 		goto exit;
299 	}
300 
301 	switch (method) {
302 	case METHOD_CSR:
303 	case METHOD_DELL: {
304 		struct udev_device *dev;
305 		struct usb_dev_handle *handle;
306 		const char *type;
307 
308 		/* get the parent usb_device if needed */
309 		dev = udev_dev;
310 		type = udev_device_get_devtype(dev);
311 		if (type == NULL || strcmp(type, "usb_device") != 0) {
312 			dev = udev_device_get_parent_with_subsystem_devtype(dev, "usb", "usb_device");
313 			if (dev == NULL) {
314 				fprintf(stderr, "error: could not find usb_device for '%s'\n", devpath);
315 				goto exit;
316 			}
317 		}
318 
319 		handle = find_device(dev);
320 		if (handle == NULL) {
321 			fprintf(stderr, "error: unable to handle '%s'\n",
322 				udev_device_get_syspath(dev));
323 			goto exit;
324 		}
325 		err = usb_switch(handle, mode);
326 		break;
327 	}
328 	case METHOD_LOGITECH_HID: {
329 		const char *device;
330 
331 		device = udev_device_get_devnode(udev_dev);
332 		if (device == NULL) {
333 			fprintf(stderr, "error: could not find hiddev device node\n");
334 			goto exit;
335 		}
336 		err = hid_switch_logitech(device);
337 		break;
338 	}
339 	default:
340 		break;
341 	}
342 
343 	if (err < 0)
344 		fprintf(stderr, "error: switching device '%s' failed.\n",
345 			udev_device_get_syspath(udev_dev));
346 exit:
347 	udev_device_unref(udev_dev);
348 	udev_unref(udev);
349 	return rc;
350 }
351