1/*
2 *
3 *  BlueZ - Bluetooth protocol stack for Linux
4 *
5 *  Copyright (C) 2003-2010  Marcel Holtmann <marcel@holtmann.org>
6 *
7 *
8 *  This program is free software; you can redistribute it and/or modify
9 *  it under the terms of the GNU General Public License as published by
10 *  the Free Software Foundation; either version 2 of the License, or
11 *  (at your option) any later version.
12 *
13 *  This program is distributed in the hope that it will be useful,
14 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
15 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 *  GNU General Public License for more details.
17 *
18 *  You should have received a copy of the GNU General Public License
19 *  along with this program; if not, write to the Free Software
20 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
21 *
22 */
23
24#ifdef HAVE_CONFIG_H
25#include <config.h>
26#endif
27
28#include <stdio.h>
29#include <errno.h>
30#include <fcntl.h>
31#include <stdint.h>
32#include <string.h>
33#include <getopt.h>
34#include <sys/ioctl.h>
35
36#include <usb.h>
37
38#ifdef NEED_USB_GET_BUSSES
39static inline struct usb_bus *usb_get_busses(void)
40{
41	return usb_busses;
42}
43#endif
44
45#ifndef USB_DIR_OUT
46#define USB_DIR_OUT	0x00
47#endif
48
49static char devpath[PATH_MAX + 1] = "/dev";
50
51struct hiddev_devinfo {
52	unsigned int bustype;
53	unsigned int busnum;
54	unsigned int devnum;
55	unsigned int ifnum;
56	short vendor;
57	short product;
58	short version;
59	unsigned num_applications;
60};
61
62struct hiddev_report_info {
63	unsigned report_type;
64	unsigned report_id;
65	unsigned num_fields;
66};
67
68typedef __signed__ int __s32;
69
70struct hiddev_usage_ref {
71	unsigned report_type;
72	unsigned report_id;
73	unsigned field_index;
74	unsigned usage_index;
75	unsigned usage_code;
76	__s32 value;
77};
78
79#define HIDIOCGDEVINFO		_IOR('H', 0x03, struct hiddev_devinfo)
80#define HIDIOCINITREPORT	_IO('H', 0x05)
81#define HIDIOCSREPORT		_IOW('H', 0x08, struct hiddev_report_info)
82#define HIDIOCSUSAGE		_IOW('H', 0x0C, struct hiddev_usage_ref)
83
84#define HID_REPORT_TYPE_OUTPUT	2
85
86#define HCI 0
87#define HID 1
88
89struct device_info {
90	struct usb_device *dev;
91	int mode;
92	uint16_t vendor;
93	uint16_t product;
94};
95
96static int switch_csr(struct device_info *devinfo)
97{
98	struct usb_dev_handle *udev;
99	int err;
100
101	udev = usb_open(devinfo->dev);
102	if (!udev)
103		return -errno;
104
105	err = usb_control_msg(udev, USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
106				0, devinfo->mode, 0, NULL, 0, 10000);
107
108	if (err == 0) {
109		err = -1;
110		errno = EALREADY;
111	} else {
112		if (errno == ETIMEDOUT)
113			err = 0;
114	}
115
116	usb_close(udev);
117
118	return err;
119}
120
121static int send_report(int fd, const char *buf, size_t size)
122{
123	struct hiddev_report_info rinfo;
124	struct hiddev_usage_ref uref;
125	unsigned int i;
126	int err;
127
128	for (i = 0; i < size; i++) {
129		memset(&uref, 0, sizeof(uref));
130		uref.report_type = HID_REPORT_TYPE_OUTPUT;
131		uref.report_id   = 0x10;
132		uref.field_index = 0;
133		uref.usage_index = i;
134		uref.usage_code  = 0xff000001;
135		uref.value       = buf[i] & 0x000000ff;
136		err = ioctl(fd, HIDIOCSUSAGE, &uref);
137		if (err < 0)
138			return err;
139	}
140
141	memset(&rinfo, 0, sizeof(rinfo));
142	rinfo.report_type = HID_REPORT_TYPE_OUTPUT;
143	rinfo.report_id   = 0x10;
144	rinfo.num_fields  = 1;
145	err = ioctl(fd, HIDIOCSREPORT, &rinfo);
146
147	return err;
148}
149
150static int switch_logitech(struct device_info *devinfo)
151{
152	char devname[PATH_MAX + 1];
153	int i, fd, err = -1;
154
155	for (i = 0; i < 16; i++) {
156		struct hiddev_devinfo dinfo;
157		char rep1[] = { 0xff, 0x80, 0x80, 0x01, 0x00, 0x00 };
158		char rep2[] = { 0xff, 0x80, 0x00, 0x00, 0x30, 0x00 };
159		char rep3[] = { 0xff, 0x81, 0x80, 0x00, 0x00, 0x00 };
160
161		sprintf(devname, "%s/hiddev%d", devpath, i);
162		fd = open(devname, O_RDWR);
163		if (fd < 0) {
164			sprintf(devname, "%s/usb/hiddev%d", devpath, i);
165			fd = open(devname, O_RDWR);
166			if (fd < 0) {
167				sprintf(devname, "%s/usb/hid/hiddev%d", devpath, i);
168				fd = open(devname, O_RDWR);
169				if (fd < 0)
170					continue;
171			}
172		}
173
174		memset(&dinfo, 0, sizeof(dinfo));
175		err = ioctl(fd, HIDIOCGDEVINFO, &dinfo);
176		if (err < 0 || (int) dinfo.busnum != atoi(devinfo->dev->bus->dirname) ||
177				(int) dinfo.devnum != atoi(devinfo->dev->filename)) {
178			close(fd);
179			continue;
180		}
181
182		err = ioctl(fd, HIDIOCINITREPORT, 0);
183		if (err < 0) {
184			close(fd);
185			break;
186		}
187
188		err = send_report(fd, rep1, sizeof(rep1));
189		if (err < 0) {
190			close(fd);
191			break;
192		}
193
194		err = send_report(fd, rep2, sizeof(rep2));
195		if (err < 0) {
196			close(fd);
197			break;
198		}
199
200		err = send_report(fd, rep3, sizeof(rep3));
201		close(fd);
202		break;
203	}
204
205	return err;
206}
207
208static int switch_dell(struct device_info *devinfo)
209{
210	char report[] = { 0x7f, 0x00, 0x00, 0x00 };
211
212	struct usb_dev_handle *handle;
213	int err;
214
215	switch (devinfo->mode) {
216	case HCI:
217		report[1] = 0x13;
218		break;
219	case HID:
220		report[1] = 0x14;
221		break;
222	}
223
224	handle = usb_open(devinfo->dev);
225	if (!handle)
226		return -EIO;
227
228	/* Don't need to check return, as might not be in use */
229	usb_detach_kernel_driver_np(handle, 0);
230
231	if (usb_claim_interface(handle, 0) < 0) {
232		usb_close(handle);
233		return -EIO;
234	}
235
236	err = usb_control_msg(handle,
237		USB_ENDPOINT_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
238			USB_REQ_SET_CONFIGURATION, 0x7f | (0x03 << 8), 0,
239						report, sizeof(report), 5000);
240
241	if (err == 0) {
242		err = -1;
243		errno = EALREADY;
244	} else {
245		if (errno == ETIMEDOUT)
246			err = 0;
247	}
248
249	usb_close(handle);
250
251	return err;
252}
253
254static int find_device(struct device_info* devinfo)
255{
256	struct usb_bus *bus;
257	struct usb_device *dev;
258
259	usb_find_busses();
260	usb_find_devices();
261
262	for (bus = usb_get_busses(); bus; bus = bus->next)
263		for (dev = bus->devices; dev; dev = dev->next) {
264			if (dev->descriptor.idVendor == devinfo->vendor &&
265			    dev->descriptor.idProduct == devinfo->product) {
266				devinfo->dev=dev;
267				return 1;
268			}
269		}
270	return 0;
271}
272
273static void usage(char* error)
274{
275	if (error)
276		fprintf(stderr,"\n%s\n", error);
277	else
278		printf("hid2hci - Bluetooth HID to HCI mode switching utility\n\n");
279
280	printf("Usage:\n"
281		"\thid2hci [options]\n"
282		"\n");
283
284	printf("Options:\n"
285		"\t-h, --help           Display help\n"
286		"\t-q, --quiet          Don't display any messages\n"
287		"\t-r, --mode=          Mode to switch to [hid, hci]\n"
288		"\t-v, --vendor=        Vendor ID to act upon\n"
289		"\t-p, --product=       Product ID to act upon\n"
290		"\t-m, --method=        Method to use to switch [csr, logitech, dell]\n"
291		"\n");
292	if (error)
293		exit(1);
294}
295
296static struct option main_options[] = {
297	{ "help",	no_argument, 0, 'h' },
298	{ "quiet",	no_argument, 0, 'q' },
299	{ "mode",	required_argument, 0, 'r' },
300	{ "vendor",	required_argument, 0, 'v' },
301	{ "product",	required_argument, 0, 'p' },
302	{ "method",	required_argument, 0, 'm' },
303	{ 0, 0, 0, 0 }
304};
305
306int main(int argc, char *argv[])
307{
308	struct device_info dev = { NULL, HCI, 0, 0 };
309	int opt, quiet = 0;
310	int (*method)(struct device_info *dev) = NULL;
311
312	while ((opt = getopt_long(argc, argv, "+r:v:p:m:qh", main_options, NULL)) != -1) {
313		switch (opt) {
314		case 'r':
315			if (optarg && !strcmp(optarg, "hid"))
316				dev.mode = HID;
317			else if (optarg && !strcmp(optarg, "hci"))
318				dev.mode = HCI;
319			else
320				usage("ERROR: Undefined radio mode\n");
321			break;
322		case 'v':
323			sscanf(optarg, "%4hx", &dev.vendor);
324			break;
325		case 'p':
326			sscanf(optarg, "%4hx", &dev.product);
327			break;
328		case 'm':
329			if (optarg && !strcmp(optarg, "csr"))
330				method = switch_csr;
331			else if (optarg && !strcmp(optarg, "logitech"))
332				method = switch_logitech;
333			else if (optarg && !strcmp(optarg, "dell"))
334				method = switch_dell;
335			else
336				usage("ERROR: Undefined switching method\n");
337			break;
338		case 'q':
339			quiet = 1;
340			break;
341		case 'h':
342			usage(NULL);
343		default:
344			exit(0);
345		}
346	}
347
348	if (!quiet && (!dev.vendor || !dev.product || !method))
349		usage("ERROR: Vendor ID, Product ID, and Switching Method must all be defined.\n");
350
351	argc -= optind;
352	argv += optind;
353	optind = 0;
354
355	usb_init();
356
357	if (!find_device(&dev)) {
358		if (!quiet)
359			fprintf(stderr, "Device %04x:%04x not found on USB bus.\n",
360				dev.vendor, dev.product);
361		exit(1);
362	}
363
364	if (!quiet)
365		printf("Attempting to switch device %04x:%04x to %s mode ",
366			dev.vendor, dev.product, dev.mode ? "HID" : "HCI");
367	fflush(stdout);
368
369	if (method(&dev) < 0 && !quiet)
370		printf("failed (%s)\n", strerror(errno));
371	else if (!quiet)
372		printf("was successful\n");
373
374	return errno;
375}
376