1215195Semax/*
2215195Semax * ath3kfw.c
3215195Semax */
4215195Semax
5215195Semax/*-
6215195Semax * Copyright (c) 2010 Maksim Yevmenkin <m_evmenkin@yahoo.com>
7215195Semax * All rights reserved.
8215195Semax *
9215195Semax * Redistribution and use in source and binary forms, with or without
10215195Semax * modification, are permitted provided that the following conditions
11215195Semax * are met:
12215195Semax * 1. Redistributions of source code must retain the above copyright
13215195Semax *    notice, this list of conditions and the following disclaimer.
14215195Semax * 2. Redistributions in binary form must reproduce the above copyright
15215195Semax *    notice, this list of conditions and the following disclaimer in the
16215195Semax *    documentation and/or other materials provided with the distribution.
17215195Semax *
18215195Semax * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
19215195Semax * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20215195Semax * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21215195Semax * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
22215195Semax * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23215195Semax * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24215195Semax * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25215195Semax * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26215195Semax * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27215195Semax * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28215195Semax * SUCH DAMAGE.
29215195Semax *
30215195Semax * $FreeBSD$
31215195Semax */
32215195Semax
33215195Semax#include <sys/types.h>
34215195Semax#include <errno.h>
35215195Semax#include <fcntl.h>
36215195Semax#include <libusb20_desc.h>
37215195Semax#include <libusb20.h>
38215195Semax#include <stdarg.h>
39215195Semax#include <stdio.h>
40215195Semax#include <stdlib.h>
41215195Semax#include <string.h>
42215195Semax#include <syslog.h>
43215195Semax#include <unistd.h>
44215195Semax
45215195Semax#define ATH3KFW			"ath3kfw"
46215195Semax#define ATH3KFW_VENDOR_ID	0x0cf3
47215195Semax#define ATH3KFW_PRODUCT_ID	0x3000
48215195Semax#define ATH3KFW_FW		"/usr/local/etc/ath3k-1.fw"
49215195Semax#define ATH3KFW_BULK_EP		0x02
50215195Semax#define	ATH3KFW_REQ_DFU_DNLOAD	1
51215195Semax#define	ATH3KFW_MAX_BSIZE	4096
52215195Semax
53215195Semaxstatic int	parse_ugen_name		(char const *ugen, uint8_t *bus,
54215195Semax					 uint8_t *addr);
55215195Semaxstatic int	find_device		(struct libusb20_backend *be,
56215195Semax					 uint8_t bus, uint8_t addr,
57215195Semax					 struct libusb20_device **dev);
58215195Semaxstatic int	download_firmware	(struct libusb20_device *dev,
59215195Semax					 char const *firmware);
60215195Semaxstatic void	usage			(void);
61215195Semax
62215195Semax/*
63215195Semax * Firmware downloader for Atheros AR3011 based USB Bluetooth devices
64215195Semax */
65215195Semax
66215195Semaxint
67215195Semaxmain(int argc, char **argv)
68215195Semax{
69215195Semax	uint8_t			bus, addr;
70215195Semax	char const		*firmware;
71215195Semax	struct libusb20_backend	*be;
72215195Semax	struct libusb20_device	*dev;
73215195Semax	int			n;
74215195Semax
75215195Semax	openlog(ATH3KFW, LOG_NDELAY|LOG_PERROR|LOG_PID, LOG_USER);
76215195Semax
77215195Semax	bus = 0;
78215195Semax	addr = 0;
79215195Semax	firmware = ATH3KFW_FW;
80215195Semax
81215195Semax	while ((n = getopt(argc, argv, "d:f:h")) != -1) {
82215195Semax		switch (n) {
83215195Semax		case 'd': /* ugen device name */
84215195Semax			if (parse_ugen_name(optarg, &bus, &addr) < 0)
85215195Semax				usage();
86215195Semax			break;
87215195Semax
88215195Semax		case 'f': /* firmware file */
89215195Semax			firmware = optarg;
90215195Semax			break;
91215195Semax
92215195Semax		case 'h':
93215195Semax		default:
94215195Semax			usage();
95215195Semax			break;
96215195Semax			/* NOT REACHED */
97215195Semax		}
98215195Semax	}
99215195Semax
100215195Semax	be = libusb20_be_alloc_default();
101215195Semax	if (be == NULL) {
102215195Semax		syslog(LOG_ERR, "libusb20_be_alloc_default() failed");
103215195Semax		return (-1);
104215195Semax	}
105215195Semax
106215195Semax	if (find_device(be, bus, addr, &dev) < 0) {
107215195Semax		syslog(LOG_ERR, "ugen%d.%d is not recognized as " \
108215195Semax			"Atheros AR3011 based device " \
109215195Semax			"(possibly caused by lack of permissions)", bus, addr);
110215195Semax		return (-1);
111215195Semax	}
112215195Semax
113215195Semax	if (download_firmware(dev, firmware) < 0) {
114215195Semax		syslog(LOG_ERR, "could not download %s firmare to ugen%d.%d",
115215195Semax			firmware, bus, addr);
116215195Semax		return (-1);
117215195Semax	}
118215195Semax
119215195Semax	libusb20_be_free(be);
120215195Semax	closelog();
121215195Semax
122215195Semax	return (0);
123215195Semax}
124215195Semax
125215195Semax/*
126215195Semax * Parse ugen name and extract device's bus and address
127215195Semax */
128215195Semax
129215195Semaxstatic int
130215195Semaxparse_ugen_name(char const *ugen, uint8_t *bus, uint8_t *addr)
131215195Semax{
132215195Semax	char	*ep;
133215195Semax
134215195Semax	if (strncmp(ugen, "ugen", 4) != 0)
135215195Semax		return (-1);
136215195Semax
137215195Semax	*bus = (uint8_t) strtoul(ugen + 4, &ep, 10);
138215195Semax	if (*ep != '.')
139215195Semax		return (-1);
140215195Semax
141215195Semax	*addr = (uint8_t) strtoul(ep + 1, &ep, 10);
142215195Semax	if (*ep != '\0')
143215195Semax		return (-1);
144215195Semax
145215195Semax	return (0);
146215195Semax}
147215195Semax
148215195Semax/*
149215195Semax * Find USB device
150215195Semax */
151215195Semax
152215195Semaxstatic int
153215195Semaxfind_device(struct libusb20_backend *be, uint8_t bus, uint8_t addr,
154215195Semax		struct libusb20_device **dev)
155215195Semax{
156215195Semax	struct LIBUSB20_DEVICE_DESC_DECODED	*desc;
157215195Semax
158215195Semax	*dev = NULL;
159215195Semax
160215195Semax	while ((*dev = libusb20_be_device_foreach(be, *dev)) != NULL) {
161215195Semax		if (libusb20_dev_get_bus_number(*dev) != bus ||
162215195Semax		    libusb20_dev_get_address(*dev) != addr)
163215195Semax			continue;
164215195Semax
165215195Semax		desc = libusb20_dev_get_device_desc(*dev);
166215195Semax		if (desc == NULL)
167215195Semax			continue;
168215195Semax
169215195Semax		if (desc->idVendor != ATH3KFW_VENDOR_ID ||
170215195Semax		    desc->idProduct != ATH3KFW_PRODUCT_ID)
171215195Semax			continue;
172215195Semax
173215195Semax		break;
174215195Semax	}
175215195Semax
176215195Semax	return ((*dev == NULL)? -1 : 0);
177215195Semax}
178215195Semax
179215195Semax/*
180215195Semax * Download firmware
181215195Semax */
182215195Semax
183215195Semaxstatic int
184215195Semaxdownload_firmware(struct libusb20_device *dev, char const *firmware)
185215195Semax{
186215195Semax	struct libusb20_transfer		*bulk;
187215195Semax	struct LIBUSB20_CONTROL_SETUP_DECODED	req;
188215195Semax	int					fd, n, error;
189215195Semax	uint8_t					buf[ATH3KFW_MAX_BSIZE];
190215195Semax
191215195Semax	error = -1;
192215195Semax
193215195Semax	if (libusb20_dev_open(dev, 1) != 0) {
194215195Semax		syslog(LOG_ERR, "libusb20_dev_open() failed");
195215195Semax		return (error);
196215195Semax	}
197215195Semax
198215195Semax	if ((bulk = libusb20_tr_get_pointer(dev, 0)) == NULL) {
199215195Semax		syslog(LOG_ERR, "libusb20_tr_get_pointer() failed");
200215195Semax		goto out;
201215195Semax	}
202215195Semax
203215195Semax	if (libusb20_tr_open(bulk, ATH3KFW_MAX_BSIZE, 1, ATH3KFW_BULK_EP) != 0) {
204215195Semax		syslog(LOG_ERR, "libusb20_tr_open(%d, 1, %d) failed",
205215195Semax			ATH3KFW_MAX_BSIZE, ATH3KFW_BULK_EP);
206215195Semax		goto out;
207215195Semax	}
208215195Semax
209215195Semax	if ((fd = open(firmware, O_RDONLY)) < 0) {
210215195Semax		syslog(LOG_ERR, "open(%s) failed. %s",
211215195Semax			firmware, strerror(errno));
212215195Semax		goto out1;
213215195Semax	}
214215195Semax
215215195Semax	n = read(fd, buf, 20);
216215195Semax	if (n != 20) {
217215195Semax		syslog(LOG_ERR, "read(%s, 20) failed. %s",
218215195Semax			firmware, strerror(errno));
219215195Semax		goto out2;
220215195Semax	}
221215195Semax
222215195Semax	LIBUSB20_INIT(LIBUSB20_CONTROL_SETUP, &req);
223215195Semax	req.bmRequestType = LIBUSB20_REQUEST_TYPE_VENDOR;
224215195Semax	req.bRequest = ATH3KFW_REQ_DFU_DNLOAD;
225215195Semax	req.wLength = 20;
226215195Semax
227215195Semax	if (libusb20_dev_request_sync(dev, &req, buf, NULL, 5000, 0) != 0) {
228215195Semax		syslog(LOG_ERR, "libusb20_dev_request_sync() failed");
229215195Semax		goto out2;
230215195Semax	}
231215195Semax
232215195Semax	for (;;) {
233215195Semax		n = read(fd, buf, sizeof(buf));
234215195Semax		if (n < 0) {
235215195Semax			syslog(LOG_ERR, "read(%s, %d) failed. %s",
236215195Semax				firmware, (int) sizeof(buf), strerror(errno));
237215195Semax			goto out2;
238215195Semax		}
239215195Semax		if (n == 0)
240215195Semax			break;
241215195Semax
242215195Semax		libusb20_tr_setup_bulk(bulk, buf, n, 3000);
243215195Semax		libusb20_tr_start(bulk);
244215195Semax
245215195Semax		while (libusb20_dev_process(dev) == 0) {
246215195Semax			if (libusb20_tr_pending(bulk) == 0)
247215195Semax				break;
248215195Semax
249215195Semax			libusb20_dev_wait_process(dev, -1);
250215195Semax		}
251215195Semax
252215195Semax		if (libusb20_tr_get_status(bulk) != 0) {
253215195Semax			syslog(LOG_ERR, "bulk transfer failed with status %d",
254215195Semax				libusb20_tr_get_status(bulk));
255215195Semax			goto out2;
256215195Semax		}
257215195Semax	}
258215195Semax
259215195Semax	error = 0;
260215195Semaxout2:
261215195Semax	close(fd);
262215195Semaxout1:
263215195Semax	libusb20_tr_close(bulk);
264215195Semaxout:
265215195Semax	libusb20_dev_close(dev);
266215195Semax
267215195Semax	return (error);
268215195Semax}
269215195Semax
270215195Semax/*
271215195Semax * Display usage and exit
272215195Semax */
273215195Semax
274215195Semaxstatic void
275215195Semaxusage(void)
276215195Semax{
277215195Semax	printf(
278215195Semax"Usage: %s -d ugenX.Y -f firmware_file\n"
279215195Semax"Usage: %s -h\n" \
280215195Semax"Where:\n" \
281215195Semax"\t-d ugenX.Y           ugen device name\n" \
282215195Semax"\t-f firmware image    firmware image file name for download\n" \
283215195Semax"\t-h                   display this message\n", ATH3KFW, ATH3KFW);
284215195Semax
285215195Semax        exit(255);
286215195Semax}
287215195Semax
288