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