1/*-
2 * Copyright (c) 2006 Sam Leffler, Errno Consulting
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer,
10 *    without modification.
11 * 2. Redistributions in binary form must reproduce at minimum a disclaimer
12 *    similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any
13 *    redistribution must be conditioned upon including a substantially
14 *    similar Disclaimer requirement for further binary redistribution.
15 *
16 * NO WARRANTY
17 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY
20 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
21 * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY,
22 * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
25 * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
27 * THE POSSIBILITY OF SUCH DAMAGES.
28 *
29 * $FreeBSD$
30 */
31
32/*
33 * Atheros AR5523 USB Station Firmware downloader.
34 *
35 *    uathload -d ugen-device [firmware-file]
36 *
37 * Intended to be called from devd on device discovery.
38 */
39#include <sys/types.h>
40#include <sys/stat.h>
41#include <sys/endian.h>
42#include <sys/mman.h>
43
44#include <sys/ioctl.h>
45#include <dev/usb/usb.h>
46#include <dev/usb/usb_ioctl.h>
47
48#include <err.h>
49#include <fcntl.h>
50#include <libgen.h>
51#include <paths.h>
52#include <stdio.h>
53#include <string.h>
54#include <strings.h>
55#include <unistd.h>
56
57/* all fields are big endian */
58struct uath_fwmsg {
59	uint32_t	flags;
60#define UATH_WRITE_BLOCK	(1 << 4)
61
62	uint32_t	len;
63#define UATH_MAX_FWBLOCK_SIZE	2048
64
65	uint32_t	total;
66	uint32_t	remain;
67	uint32_t	rxtotal;
68	uint32_t	pad[123];
69} __packed;
70
71#define UATH_DATA_TIMEOUT	10000
72#define UATH_CMD_TIMEOUT	1000
73
74#define	VERBOSE(_fmt, ...) do {			\
75	if (verbose) {				\
76		printf(_fmt, __VA_ARGS__);	\
77		fflush(stdout);			\
78	}					\
79} while (0)
80
81extern	uint8_t _binary_ar5523_bin_start;
82extern	uint8_t _binary_ar5523_bin_end;
83
84static int
85getdevname(const char *devname, char *msgdev, char *datadev)
86{
87	char *bn, *dn;
88
89	dn = dirname(devname);
90	if (dn == NULL)
91		return (-1);
92	bn = basename(devname);
93	if (bn == NULL || strncmp(bn, "ugen", 4))
94		return (-1);
95	bn += 4;
96
97	/* NB: pipes are hardcoded */
98	snprintf(msgdev, 256, "%s/usb/%s.1", dn, bn);
99	snprintf(datadev, 256, "%s/usb/%s.2", dn, bn);
100	return (0);
101}
102
103static void
104usage(void)
105{
106	errx(-1, "usage: uathload [-v] -d devname [firmware]");
107}
108
109int
110main(int argc, char *argv[])
111{
112	const char *fwname, *devname;
113	char msgdev[256], datadev[256];
114	struct uath_fwmsg txmsg, rxmsg;
115	char *txdata;
116	struct stat sb;
117	int msg, data, fw, timeout, b, c;
118	int bufsize = 512, verbose = 0;
119	ssize_t len;
120
121	devname = NULL;
122	while ((c = getopt(argc, argv, "d:v")) != -1) {
123		switch (c) {
124		case 'd':
125			devname = optarg;
126			break;
127		case 'v':
128			verbose = 1;
129			break;
130		default:
131			usage();
132			/*NOTREACHED*/
133		}
134	}
135	argc -= optind;
136	argv += optind;
137
138	if (devname == NULL)
139		errx(-1, "No device name; use -d to specify the ugen device");
140	if (argc > 1)
141		usage();
142
143	if (argc == 1) {
144		fwname = argv[0];
145		fw = open(fwname, O_RDONLY, 0);
146		if (fw < 0)
147			err(-1, "open(%s)", fwname);
148		if (fstat(fw, &sb) < 0)
149			err(-1, "fstat(%s)", fwname);
150		txdata = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fw, 0);
151		if (txdata == MAP_FAILED)
152			err(-1, "mmap(%s)", fwname);
153		len = sb.st_size;
154	} else {
155		fwname = "ar5523.bin (builtin)";
156		fw = -1;
157		txdata = &_binary_ar5523_bin_start;
158		len = &_binary_ar5523_bin_end - &_binary_ar5523_bin_start;
159	}
160	/* XXX verify device is an AR5005 part */
161	if (getdevname(devname, msgdev, datadev))
162		err(-1, "getdevname error");
163
164	msg = open(msgdev, O_RDWR, 0);
165	if (msg < 0)
166		err(-1, "open(%s)", msgdev);
167	timeout = UATH_DATA_TIMEOUT;
168	if (ioctl(msg, USB_SET_RX_TIMEOUT, &timeout) < 0)
169		err(-1, "%s: USB_SET_RX_TIMEOUT(%u)", msgdev, UATH_DATA_TIMEOUT);
170	if (ioctl(msg, USB_SET_RX_BUFFER_SIZE, &bufsize) < 0)
171		err(-1, "%s: USB_SET_RX_BUFFER_SIZE(%u)", msgdev, bufsize);
172
173	data = open(datadev, O_WRONLY, 0);
174	if (data < 0)
175		err(-1, "open(%s)", datadev);
176	timeout = UATH_DATA_TIMEOUT;
177	if (ioctl(data, USB_SET_TX_TIMEOUT, &timeout) < 0)
178		err(-1, "%s: USB_SET_TX_TIMEOUT(%u)", datadev,
179		    UATH_DATA_TIMEOUT);
180
181	VERBOSE("Load firmware %s to %s\n", fwname, devname);
182
183	bzero(&txmsg, sizeof (struct uath_fwmsg));
184	txmsg.flags = htobe32(UATH_WRITE_BLOCK);
185	txmsg.total = htobe32(len);
186
187	b = 0;
188	while (len > 0) {
189		int mlen;
190
191		mlen = len;
192		if (mlen > UATH_MAX_FWBLOCK_SIZE)
193			mlen = UATH_MAX_FWBLOCK_SIZE;
194		txmsg.remain = htobe32(len - mlen);
195		txmsg.len = htobe32(mlen);
196
197		/* send firmware block meta-data */
198		VERBOSE("send block %2u: %zd bytes remaining", b, len - mlen);
199		if (write(msg, &txmsg, sizeof(txmsg)) != sizeof(txmsg)) {
200			VERBOSE("%s", "\n");
201			err(-1, "error sending msg (%s)", msgdev);
202			break;
203		}
204
205		/* send firmware block data */
206		VERBOSE("%s", "\n             : data...");
207		if (write(data, txdata, mlen) != mlen) {
208			VERBOSE("%s", "\n");
209			err(-1, "error sending data (%s)", datadev);
210			break;
211		}
212
213		/* wait for ack from firmware */
214		VERBOSE("%s", "\n             : wait for ack...");
215		bzero(&rxmsg, sizeof(rxmsg));
216		if (read(msg, &rxmsg, sizeof(rxmsg)) != sizeof(rxmsg)) {
217			VERBOSE("%s", "\n");
218			err(-1, "error reading msg (%s)", msgdev);
219			break;
220		}
221
222		VERBOSE("flags=0x%x total=%d\n",
223		    be32toh(rxmsg.flags), be32toh(rxmsg.rxtotal));
224		len -= mlen;
225		txdata += mlen;
226		b++;
227	}
228	sleep(1);
229	close(fw);
230	close(msg);
231	close(data);
232	return 0;
233}
234