xref: /dragonfly/tools/tools/ath/athprom/athprom.c (revision 25a2db75)
1 /*-
2  * Copyright (c) 2008 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: src/tools/tools/ath/athprom/athprom.c,v 1.1 2008/12/07 19:17:33 sam Exp $
30  */
31 #include "diag.h"
32 
33 #include "ah.h"
34 #include "ah_internal.h"
35 #include "ah_eeprom_v1.h"
36 #include "ah_eeprom_v3.h"
37 #include "ah_eeprom_v14.h"
38 
39 #define	IS_VERS(op, v)		(eeprom.ee_version op (v))
40 
41 #include <getopt.h>
42 #include <errno.h>
43 #include <err.h>
44 #include <stdlib.h>
45 #include <string.h>
46 
47 #ifndef DIR_TEMPLATE
48 #define	DIR_TEMPLATE	"/usr/local/libdata/athprom"
49 #endif
50 
51 struct	ath_diag atd;
52 int	s;
53 const char *progname;
54 union {
55 	HAL_EEPROM legacy;		/* format v3.x ... v5.x */
56 	struct ar5416eeprom v14;	/* 11n format v14.x ... */
57 } eep;
58 #define	eeprom	eep.legacy
59 #define	eepromN	eep.v14
60 
61 static void parseTemplate(FILE *ftemplate, FILE *fd);
62 static uint16_t eeread(uint16_t);
63 static void eewrite(uint16_t, uint16_t);
64 
65 static void
66 usage()
67 {
68 	fprintf(stderr, "usage: %s [-i ifname] [-t pathname] [offset | offset=value]\n", progname);
69 	exit(-1);
70 }
71 
72 static FILE *
73 opentemplate(const char *dir)
74 {
75 	char filename[PATH_MAX];
76 	FILE *fd;
77 
78 	/* find the template using the eeprom version */
79 	snprintf(filename, sizeof(filename), "%s/eeprom-%d.%d",
80 	    dir, eeprom.ee_version >> 12, eeprom.ee_version & 0xfff);
81 	fd = fopen(filename, "r");
82 	if (fd == NULL && errno == ENOENT) {
83 		/* retry with just the major version */
84 		snprintf(filename, sizeof(filename), "%s/eeprom-%d",
85 		    dir, eeprom.ee_version >> 12);
86 		fd = fopen(filename, "r");
87 		if (fd != NULL)		/* XXX verbose */
88 			warnx("Using template file %s", filename);
89 	}
90 	return fd;
91 }
92 
93 int
94 main(int argc, char *argv[])
95 {
96 	FILE *fd = NULL;
97 	const char *ifname;
98 	int c;
99 
100 	s = socket(AF_INET, SOCK_DGRAM, 0);
101 	if (s < 0)
102 		err(1, "socket");
103 	ifname = getenv("ATH");
104 	if (!ifname)
105 		ifname = ATH_DEFAULT;
106 
107 	progname = argv[0];
108 	while ((c = getopt(argc, argv, "i:t:")) != -1)
109 		switch (c) {
110 		case 'i':
111 			ifname = optarg;
112 			break;
113 		case 't':
114 			fd = fopen(optarg, "r");
115 			if (fd == NULL)
116 				err(-1, "Cannot open %s", optarg);
117 			break;
118 		default:
119 			usage();
120 			/*NOTREACHED*/
121 		}
122 	argc -= optind;
123 	argv += optind;
124 
125 	strncpy(atd.ad_name, ifname, sizeof (atd.ad_name));
126 
127 	if (argc != 0) {
128 		for (; argc > 0; argc--, argv++) {
129 			uint16_t off, val, oval;
130 			char line[256];
131 			char *cp;
132 
133 			cp = strchr(argv[0], '=');
134 			if (cp != NULL)
135 				*cp = '\0';
136 			off = (uint16_t) strtoul(argv[0], NULL, 0);
137 			if (off == 0 && errno == EINVAL)
138 				errx(1, "%s: invalid eeprom offset %s",
139 					progname, argv[0]);
140 			if (cp == NULL) {
141 				printf("%04x: %04x\n", off, eeread(off));
142 			} else {
143 				val = (uint16_t) strtoul(cp+1, NULL, 0);
144 				if (val == 0 && errno == EINVAL)
145 					errx(1, "%s: invalid eeprom value %s",
146 					    progname, cp+1);
147 				oval = eeread(off);
148 				printf("Write %04x: %04x = %04x? ",
149 					off, oval, val);
150 				fflush(stdout);
151 				if (fgets(line, sizeof(line), stdin) != NULL &&
152 				    line[0] == 'y')
153 					eewrite(off, val);
154 			}
155 		}
156 	} else {
157 		atd.ad_id = HAL_DIAG_EEPROM;
158 		atd.ad_out_data = (caddr_t) &eep;
159 		atd.ad_out_size = sizeof(eep);
160 		if (ioctl(s, SIOCGATHDIAG, &atd) < 0)
161 			err(1, atd.ad_name);
162 		if (fd == NULL) {
163 			fd = opentemplate(DIR_TEMPLATE);
164 			if (fd == NULL)
165 				fd = opentemplate(".");
166 			if (fd == NULL)
167 				errx(-1, "Cannot locate template file for "
168 				    "v%d.%d EEPROM", eeprom.ee_version >> 12,
169 				    eeprom.ee_version & 0xfff);
170 		}
171 		parseTemplate(fd, stdout);
172 		fclose(fd);
173 	}
174 	return 0;
175 }
176 
177 static u_int16_t
178 eeread(u_int16_t off)
179 {
180 	u_int16_t eedata;
181 
182 	atd.ad_id = HAL_DIAG_EEREAD | ATH_DIAG_IN | ATH_DIAG_DYN;
183 	atd.ad_in_size = sizeof(off);
184 	atd.ad_in_data = (caddr_t) &off;
185 	atd.ad_out_size = sizeof(eedata);
186 	atd.ad_out_data = (caddr_t) &eedata;
187 	if (ioctl(s, SIOCGATHDIAG, &atd) < 0)
188 		err(1, atd.ad_name);
189 	return eedata;
190 }
191 
192 static void
193 eewrite(uint16_t off, uint16_t value)
194 {
195 	HAL_DIAG_EEVAL eeval;
196 
197 	eeval.ee_off = off;
198 	eeval.ee_data = value;
199 
200 	atd.ad_id = HAL_DIAG_EEWRITE | ATH_DIAG_IN;
201 	atd.ad_in_size = sizeof(eeval);
202 	atd.ad_in_data = (caddr_t) &eeval;
203 	atd.ad_out_size = 0;
204 	atd.ad_out_data = NULL;
205 	if (ioctl(s, SIOCGATHDIAG, &atd) < 0)
206 		err(1, atd.ad_name);
207 }
208 
209 #define	MAXID	128
210 int	lineno;
211 int	bol;
212 int	curmode = -1;
213 int	curchan;
214 int	curpdgain;	/* raw pdgain index */
215 int	curlpdgain;	/* logical pdgain index */
216 int	curpcdac;
217 int	curctl;
218 int	numChannels;
219 const RAW_DATA_STRUCT_2413 *pRaw;
220 const TRGT_POWER_INFO *pPowerInfo;
221 const DATA_PER_CHANNEL *pDataPerChannel;
222 const EEPROM_POWER_EXPN_5112 *pExpnPower;
223 int	singleXpd;
224 
225 static int
226 token(FILE *fd, char id[], int maxid, const char *what)
227 {
228 	int c, i;
229 
230 	i = 0;
231 	for (;;) {
232 		c = getc(fd);
233 		if (c == EOF)
234 			return EOF;
235 		if (!isalnum(c) && c != '_') {
236 			ungetc(c, fd);
237 			break;
238 		}
239 		if (i == maxid-1) {
240 			warnx("line %d, %s too long", lineno, what);
241 			break;
242 		}
243 		id[i++] = c;
244 	}
245 	id[i] = '\0';
246 	if (i != 0)
247 		bol = 0;
248 	return i;
249 }
250 
251 static int
252 skipto(FILE *fd, const char *what)
253 {
254 	char id[MAXID];
255 	int c;
256 
257 	for (;;) {
258 		c = getc(fd);
259 		if (c == EOF)
260 			goto bad;
261 		if (c == '.' && bol) {		/* .directive */
262 			if (token(fd, id, MAXID, ".directive") == EOF)
263 				goto bad;
264 			if (strcasecmp(id, what) == 0)
265 				break;
266 			continue;
267 		}
268 		if (c == '\\') {		/* escape next character */
269 			c = getc(fd);
270 			if (c == EOF)
271 				goto bad;
272 		}
273 		bol = (c == '\n');
274 		if (bol)
275 			lineno++;
276 	}
277 	return 0;
278 bad:
279 	warnx("EOF with no matching .%s", what);
280 	return EOF;
281 }
282 
283 static int
284 skipws(FILE *fd)
285 {
286 	int c, i;
287 
288 	i = 0;
289 	while ((c = getc(fd)) != EOF && isblank(c))
290 		i++;
291 	if (c != EOF)
292 		ungetc(c, fd);
293 	if (i != 0)
294 		bol = 0;
295 	return 0;
296 }
297 
298 static void
299 _setmode(int mode)
300 {
301 	EEPROM_POWER_EXPN_5112 *exp;
302 
303 	curmode = mode;
304 	curchan = -1;
305 	curctl = -1;
306 	curpdgain = -1;
307 	curlpdgain = -1;
308 	curpcdac = -1;
309 	switch (curmode) {
310 	case headerInfo11A:
311 		pPowerInfo = eeprom.ee_trgtPwr_11a;
312 		pDataPerChannel = eeprom.ee_dataPerChannel11a;
313 		break;
314 	case headerInfo11B:
315 		pPowerInfo = eeprom.ee_trgtPwr_11b;
316 		pDataPerChannel = eeprom.ee_dataPerChannel11b;
317 		break;
318 	case headerInfo11G:
319 		pPowerInfo = eeprom.ee_trgtPwr_11g;
320 		pDataPerChannel = eeprom.ee_dataPerChannel11g;
321 		break;
322 	}
323 	if (IS_VERS(<, AR_EEPROM_VER4_0))		/* nothing to do */
324 		return;
325 	if (IS_VERS(<, AR_EEPROM_VER5_0)) {
326 		exp = &eeprom.ee_modePowerArray5112[curmode];
327 		/* fetch indirect data*/
328 		atd.ad_id = HAL_DIAG_EEPROM_EXP_11A+curmode;
329 		atd.ad_out_size = roundup(
330 			sizeof(u_int16_t) * exp->numChannels, sizeof(u_int32_t))
331 		    + sizeof(EXPN_DATA_PER_CHANNEL_5112) * exp->numChannels;
332 		atd.ad_out_data = (caddr_t) malloc(atd.ad_out_size);
333 		if (ioctl(s, SIOCGATHDIAG, &atd) < 0)
334 			err(1, atd.ad_name);
335 		exp->pChannels = (void *) atd.ad_out_data;
336 		exp->pDataPerChannel = (void *)((char *)atd.ad_out_data +
337 		   roundup(sizeof(u_int16_t) * exp->numChannels, sizeof(u_int32_t)));
338 		pExpnPower = exp;
339 		numChannels = pExpnPower->numChannels;
340 		if (exp->xpdMask != 0x9) {
341 			for (singleXpd = 0; singleXpd < NUM_XPD_PER_CHANNEL; singleXpd++)
342 				if (exp->xpdMask == (1<<singleXpd))
343 					break;
344 		} else
345 			singleXpd = 0;
346 	} else if (IS_VERS(<, AR_EEPROM_VER14_2)) {
347 		pRaw = &eeprom.ee_rawDataset2413[curmode];
348 		numChannels = pRaw->numChannels;
349 	}
350 }
351 
352 int
353 nextctl(int start)
354 {
355 	int i;
356 
357 	for (i = start; i < eeprom.ee_numCtls && eeprom.ee_ctl[i]; i++) {
358 		switch (eeprom.ee_ctl[i] & 3) {
359 		case 0: case 3:
360 			if (curmode != headerInfo11A)
361 				continue;
362 			break;
363 		case 1:
364 			if (curmode != headerInfo11B)
365 				continue;
366 			break;
367 		case 2:
368 			if (curmode != headerInfo11G)
369 				continue;
370 			break;
371 		}
372 		return i;
373 	}
374 	return -1;
375 }
376 
377 static void
378 printAntennaControl(FILE *fd, int ant)
379 {
380 	fprintf(fd, "0x%02X", eeprom.ee_antennaControl[ant][curmode]);
381 }
382 
383 static void
384 printEdge(FILE *fd, int edge)
385 {
386 	const RD_EDGES_POWER *pRdEdgePwrInfo =
387 	    &eeprom.ee_rdEdgesPower[curctl*NUM_EDGES];
388 
389 	if (pRdEdgePwrInfo[edge].rdEdge == 0)
390 		fprintf(fd, " -- ");
391 	else
392 		fprintf(fd, "%04d", pRdEdgePwrInfo[edge].rdEdge);
393 }
394 
395 static void
396 printEdgePower(FILE *fd, int edge)
397 {
398 	const RD_EDGES_POWER *pRdEdgePwrInfo =
399 	    &eeprom.ee_rdEdgesPower[curctl*NUM_EDGES];
400 
401 	if (pRdEdgePwrInfo[edge].rdEdge == 0)
402 		fprintf(fd, " -- ");
403 	else
404                 fprintf(fd, "%2d.%d",
405 		    pRdEdgePwrInfo[edge].twice_rdEdgePower / 2,
406                     (pRdEdgePwrInfo[edge].twice_rdEdgePower % 2) * 5);
407 }
408 
409 static void
410 printEdgeFlag(FILE *fd, int edge)
411 {
412 	const RD_EDGES_POWER *pRdEdgePwrInfo =
413 	    &eeprom.ee_rdEdgesPower[curctl*NUM_EDGES];
414 
415 	if (pRdEdgePwrInfo[edge].rdEdge == 0)
416 		fprintf(fd, "--");
417 	else
418                 fprintf(fd, " %1d", pRdEdgePwrInfo[edge].flag);
419 }
420 
421 static int16_t
422 getMaxPowerV5(const RAW_DATA_PER_CHANNEL_2413 *data)
423 {
424 	uint32_t i;
425 	uint16_t numVpd;
426 
427 	for (i = 0; i < MAX_NUM_PDGAINS_PER_CHANNEL; i++) {
428 		numVpd = data->pDataPerPDGain[i].numVpd;
429 		if (numVpd > 0)
430 			return data->pDataPerPDGain[i].pwr_t4[numVpd-1];
431 	}
432 	return 0;
433 }
434 
435 static void
436 printQuarterDbmPower(FILE *fd, int16_t power25dBm)
437 {
438 	fprintf(fd, "%2d.%02d", power25dBm / 4, (power25dBm % 4) * 25);
439 }
440 
441 static void
442 printHalfDbmPower(FILE *fd, int16_t power5dBm)
443 {
444 	fprintf(fd, "%2d.%d", power5dBm / 2, (power5dBm % 2) * 5);
445 }
446 
447 static void
448 printVpd(FILE *fd, int vpd)
449 {
450 	fprintf(fd, "[%3d]", vpd);
451 }
452 
453 static void
454 printPcdacValue(FILE *fd, int v)
455 {
456 	fprintf(fd, "%2d.%02d", v / EEP_SCALE, v % EEP_SCALE);
457 }
458 
459 static void
460 undef(const char *what)
461 {
462 	warnx("%s undefined for version %d.%d format EEPROM", what,
463 	    eeprom.ee_version >> 12, eeprom.ee_version & 0xfff);
464 }
465 
466 static int
467 pdgain(int lpdgain)
468 {
469 	uint32_t mask;
470 	int i, l = lpdgain;
471 
472 	if (IS_VERS(<, AR_EEPROM_VER5_0))
473 		mask = pExpnPower->xpdMask;
474 	else
475 		mask = pRaw->xpd_mask;
476 	for (i = 0; mask != 0; mask >>= 1, i++)
477 		if ((mask & 1) && l-- == 0)
478 			return i;
479 	warnx("can't find logical pdgain %d", lpdgain);
480 	return -1;
481 }
482 
483 #define	COUNTRY_ERD_FLAG        0x8000
484 #define WORLDWIDE_ROAMING_FLAG  0x4000
485 
486 void
487 eevar(FILE *fd, const char *var)
488 {
489 #define	streq(a,b)	(strcasecmp(a,b) == 0)
490 #define	strneq(a,b,n)	(strncasecmp(a,b,n) == 0)
491 	if (streq(var, "mode")) {
492 		fprintf(fd, "%s",
493 		    curmode == headerInfo11A ? "11a" :
494 		    curmode == headerInfo11B ? "11b" :
495 		    curmode == headerInfo11G ? "11g" : "???");
496 	} else if (streq(var, "version")) {
497 		fprintf(fd, "%04x", eeprom.ee_version);
498 	} else if (streq(var, "V_major")) {
499 		fprintf(fd, "%2d", eeprom.ee_version >> 12);
500 	} else if (streq(var, "V_minor")) {
501 		fprintf(fd, "%2d", eeprom.ee_version & 0xfff);
502 	} else if (streq(var, "earStart")) {
503 		fprintf(fd, "%03x", eeprom.ee_earStart);
504 	} else if (streq(var, "tpStart")) {
505 		fprintf(fd, "%03x", eeprom.ee_targetPowersStart);
506 	} else if (streq(var, "eepMap")) {
507 		fprintf(fd, "%3d", eeprom.ee_eepMap);
508 	} else if (streq(var, "exist32KHzCrystal")) {
509 		fprintf(fd, "%3d", eeprom.ee_exist32kHzCrystal);
510 	} else if (streq(var, "eepMap2PowerCalStart")) {
511 		fprintf(fd , "%3d", eeprom.ee_eepMap2PowerCalStart);
512 	} else if (streq(var, "Amode")) {
513 		fprintf(fd , "%1d", eeprom.ee_Amode);
514 	} else if (streq(var, "Bmode")) {
515 		fprintf(fd , "%1d", eeprom.ee_Bmode);
516 	} else if (streq(var, "Gmode")) {
517 		fprintf(fd , "%1d", eeprom.ee_Gmode);
518 	} else if (streq(var, "regdomain")) {
519 		if ((eeprom.ee_regdomain & COUNTRY_ERD_FLAG) == 0)
520 			fprintf(fd, "%03X ", eeprom.ee_regdomain >> 15);
521 		else
522 			fprintf(fd, "%-3dC", eeprom.ee_regdomain & 0xfff);
523 	} else if (streq(var, "turbo2Disable")) {
524 		fprintf(fd, "%1d", eeprom.ee_turbo2Disable);
525 	} else if (streq(var, "turbo5Disable")) {
526 		fprintf(fd, "%1d", eeprom.ee_turbo5Disable);
527 	} else if (streq(var, "rfKill")) {
528 		fprintf(fd, "%1d", eeprom.ee_rfKill);
529 	} else if (streq(var, "disableXr5")) {
530 		fprintf(fd, "%1d", eeprom.ee_disableXr5);
531 	} else if (streq(var, "disableXr2")) {
532 		fprintf(fd, "%1d", eeprom.ee_disableXr2);
533 	} else if (streq(var, "turbo2WMaxPower5")) {
534 		fprintf(fd, "%2d", eeprom.ee_turbo2WMaxPower5);
535 	} else if (streq(var, "cckOfdmDelta")) {
536 		fprintf(fd, "%2d", eeprom.ee_cckOfdmPwrDelta);
537 	} else if (streq(var, "gainI")) {
538 		fprintf(fd, "%2d", eeprom.ee_gainI[curmode]);
539 	} else if (streq(var, "WWR")) {
540 		fprintf(fd, "%1x",
541 		    (eeprom.ee_regdomain & WORLDWIDE_ROAMING_FLAG) != 0);
542 	} else if (streq(var, "falseDetectBackoff")) {
543 		fprintf(fd, "0x%02x", eeprom.ee_falseDetectBackoff[curmode]);
544 	} else if (streq(var, "deviceType")) {
545 		fprintf(fd, "%1x", eeprom.ee_deviceType);
546 	} else if (streq(var, "switchSettling")) {
547 		if (IS_VERS(<, AR_EEPROM_VER14_2))
548 			fprintf(fd, "0x%02x", eeprom.ee_switchSettling[curmode]);
549 		else
550 			fprintf(fd, "%3d", eepromN.modalHeader[curmode].switchSettling);
551 	} else if (streq(var, "adcDesiredSize")) {
552 		if (IS_VERS(<, AR_EEPROM_VER14_2))
553 			fprintf(fd, "%2d", eeprom.ee_adcDesiredSize[curmode]);
554 		else
555 			fprintf(fd, "%3d", eepromN.modalHeader[curmode].adcDesiredSize);
556 	} else if (streq(var, "xlnaGain")) {
557 		fprintf(fd, "0x%02x", eeprom.ee_xlnaGain[curmode]);
558 	} else if (streq(var, "txEndToXLNAOn")) {
559 		fprintf(fd, "0x%02x", eeprom.ee_txEndToXLNAOn[curmode]);
560 	} else if (streq(var, "thresh62")) {
561 		if (IS_VERS(<, AR_EEPROM_VER14_2))
562 			fprintf(fd, "0x%02x", eeprom.ee_thresh62[curmode]);
563 		else
564 			fprintf(fd, "%3d", eepromN.modalHeader[curmode].thresh62);
565 	} else if (streq(var, "txEndToRxOn")) {
566 		fprintf(fd, "%3d", eepromN.modalHeader[curmode].txEndToRxOn);
567 	} else if (streq(var, "txEndToXPAOff")) {
568 		if (IS_VERS(<, AR_EEPROM_VER14_2))
569 			fprintf(fd, "0x%02x", eeprom.ee_txEndToXPAOff[curmode]);
570 		else
571 			fprintf(fd, "%3d", eepromN.modalHeader[curmode].txEndToXpaOff);
572 	} else if (streq(var, "txFrameToXPAOn")) {
573 		if (IS_VERS(<, AR_EEPROM_VER14_2))
574 			fprintf(fd, "0x%02x", eeprom.ee_txFrameToXPAOn[curmode]);
575 		else
576 			fprintf(fd, "%3d", eepromN.modalHeader[curmode].txEndToRxOn);
577 	} else if (streq(var, "pgaDesiredSize")) {
578 		if (IS_VERS(<, AR_EEPROM_VER14_2))
579 			fprintf(fd, "%2d", eeprom.ee_pgaDesiredSize[curmode]);
580 		else
581 			fprintf(fd, "%3d", eepromN.modalHeader[curmode].pgaDesiredSize);
582 	} else if (streq(var, "noiseFloorThresh")) {
583 		fprintf(fd, "%3d", eeprom.ee_noiseFloorThresh[curmode]);
584 	} else if (strneq(var, "noiseFloorThreshCh", 18)) {
585 		fprintf(fd, "%3d", eepromN.modalHeader[curmode].noiseFloorThreshCh[atoi(var+18)]);
586 	} else if (strneq(var, "xlnaGainCh", 10)) {
587 		fprintf(fd, "%3d", eepromN.modalHeader[curmode].xlnaGainCh[atoi(var+10)]);
588 	} else if (streq(var, "xgain")) {
589 		fprintf(fd, "0x%02x", eeprom.ee_xgain[curmode]);
590 	} else if (streq(var, "xpd")) {
591 		if (IS_VERS(<, AR_EEPROM_VER14_2))
592 			fprintf(fd, "%1d", eeprom.ee_xpd[curmode]);
593 		else
594 			fprintf(fd, "%3d", eepromN.modalHeader[curmode].xpd);
595 	} else if (streq(var, "txrxAtten")) {
596 		fprintf(fd, "0x%02x", eeprom.ee_txrxAtten[curmode]);
597 	} else if (streq(var, "capField")) {
598 		fprintf(fd, "0x%04X", eeprom.ee_capField);
599 	} else if (streq(var, "txrxAttenTurbo")) {
600 		fprintf(fd, "0x%02x",
601 		    eeprom.ee_txrxAtten[curmode != headerInfo11A]);
602 	} else if (streq(var, "switchSettlingTurbo")) {
603 		fprintf(fd, "0x%02X",
604 		    eeprom.ee_switchSettlingTurbo[curmode != headerInfo11A]);
605 	} else if (streq(var, "adcDesiredSizeTurbo")) {
606 		fprintf(fd, "%2d",
607 		    eeprom.ee_adcDesiredSizeTurbo[curmode != headerInfo11A]);
608 	} else if (streq(var, "pgaDesiredSizeTurbo")) {
609 		fprintf(fd, "%2d",
610 		    eeprom.ee_pgaDesiredSizeTurbo[curmode != headerInfo11A]);
611 	} else if (streq(var, "rxtxMarginTurbo")) {
612 		fprintf(fd, "0x%02x",
613 		    eeprom.ee_rxtxMarginTurbo[curmode != headerInfo11A]);
614 	} else if (strneq(var, "antennaControl", 14)) {
615 		printAntennaControl(fd, atoi(var+14));
616 	} else if (strneq(var, "antCtrlChain", 12)) {
617 		fprintf(fd, "0x%08X",
618 		    eepromN.modalHeader[curmode].antCtrlChain[atoi(var+12)]);
619 	} else if (strneq(var, "antGainCh", 9)) {
620 		fprintf(fd, "%3d",
621 		    eepromN.modalHeader[curmode].antennaGainCh[atoi(var+9)]);
622 	} else if (strneq(var, "txRxAttenCh", 11)) {
623 		fprintf(fd, "%3d",
624 		    eepromN.modalHeader[curmode].txRxAttenCh[atoi(var+11)]);
625 	} else if (strneq(var, "rxTxMarginCh", 12)) {
626 		fprintf(fd, "%3d",
627 		    eepromN.modalHeader[curmode].rxTxMarginCh[atoi(var+12)]);
628 	} else if (streq(var, "xpdGain")) {
629 		fprintf(fd, "%3d", eepromN.modalHeader[curmode].xpdGain);
630 	} else if (strneq(var, "iqCalICh", 8)) {
631 		fprintf(fd, "%3d",
632 		    eepromN.modalHeader[curmode].iqCalICh[atoi(var+8)]);
633 	} else if (strneq(var, "iqCalQCh", 8)) {
634 		fprintf(fd, "%3d",
635 		    eepromN.modalHeader[curmode].iqCalQCh[atoi(var+8)]);
636 	} else if (streq(var, "pdGainOverlap")) {
637 		printHalfDbmPower(fd, eepromN.modalHeader[curmode].pdGainOverlap);
638 	} else if (streq(var, "ob1")) {
639 		fprintf(fd, "%1d", eeprom.ee_ob1);
640 	} else if (streq(var, "ob2")) {
641 		fprintf(fd, "%1d", eeprom.ee_ob2);
642 	} else if (streq(var, "ob3")) {
643 		fprintf(fd, "%1d", eeprom.ee_ob3);
644 	} else if (streq(var, "ob4")) {
645 		fprintf(fd, "%1d", eeprom.ee_ob4);
646 	} else if (streq(var, "db1")) {
647 		fprintf(fd, "%1d", eeprom.ee_db1);
648 	} else if (streq(var, "db2")) {
649 		fprintf(fd, "%1d", eeprom.ee_db2);
650 	} else if (streq(var, "db3")) {
651 		fprintf(fd, "%1d", eeprom.ee_db3);
652 	} else if (streq(var, "db4")) {
653 		fprintf(fd, "%1d", eeprom.ee_db4);
654 	} else if (streq(var, "obFor24")) {
655                 fprintf(fd, "%1d", eeprom.ee_obFor24);
656 	} else if (streq(var, "ob2GHz0")) {
657                 fprintf(fd, "%1d", eeprom.ee_ob2GHz[0]);
658 	} else if (streq(var, "dbFor24")) {
659                 fprintf(fd, "%1d", eeprom.ee_dbFor24);
660 	} else if (streq(var, "db2GHz0")) {
661                 fprintf(fd, "%1d", eeprom.ee_db2GHz[0]);
662 	} else if (streq(var, "obFor24g")) {
663                 fprintf(fd, "%1d", eeprom.ee_obFor24g);
664 	} else if (streq(var, "ob2GHz1")) {
665                 fprintf(fd, "%1d", eeprom.ee_ob2GHz[1]);
666 	} else if (streq(var, "dbFor24g")) {
667                 fprintf(fd, "%1d", eeprom.ee_dbFor24g);
668 	} else if (streq(var, "db2GHz1")) {
669                 fprintf(fd, "%1d", eeprom.ee_db2GHz[1]);
670 	} else if (streq(var, "ob")) {
671 		fprintf(fd, "%3d", eepromN.modalHeader[curmode].ob);
672 	} else if (streq(var, "db")) {
673 		fprintf(fd, "%3d", eepromN.modalHeader[curmode].db);
674 	} else if (streq(var, "xpaBiasLvl")) {
675 		fprintf(fd, "%3d", eepromN.modalHeader[curmode].xpaBiasLvl);
676 	} else if (streq(var, "pwrDecreaseFor2Chain")) {
677 		printHalfDbmPower(fd, eepromN.modalHeader[curmode].pwrDecreaseFor2Chain);
678 	} else if (streq(var, "pwrDecreaseFor3Chain")) {
679 		printHalfDbmPower(fd, eepromN.modalHeader[curmode].pwrDecreaseFor3Chain);
680 	} else if (streq(var, "txFrameToDataStart")) {
681 		fprintf(fd, "%3d", eepromN.modalHeader[curmode].txFrameToDataStart);
682 	} else if (streq(var, "txFrameToPaOn")) {
683 		fprintf(fd, "%3d", eepromN.modalHeader[curmode].txFrameToPaOn);
684 	} else if (streq(var, "ht40PowerIncForPdadc")) {
685 		fprintf(fd, "%3d", eepromN.modalHeader[curmode].ht40PowerIncForPdadc);
686 	} else if (streq(var, "checksum")) {
687                 fprintf(fd, "0x%04X", eepromN.baseEepHeader.checksum);
688 	} else if (streq(var, "length")) {
689                 fprintf(fd, "0x%04X", eepromN.baseEepHeader.length);
690 	} else if (streq(var, "regDmn0")) {
691                 fprintf(fd, "0x%04X", eepromN.baseEepHeader.regDmn[0]);
692 	} else if (streq(var, "regDmn1")) {
693                 fprintf(fd, "0x%04X", eepromN.baseEepHeader.regDmn[1]);
694 	} else if (streq(var, "txMask")) {
695                 fprintf(fd, "0x%04X", eepromN.baseEepHeader.txMask);
696 	} else if (streq(var, "rxMask")) {
697                 fprintf(fd, "0x%04X", eepromN.baseEepHeader.rxMask);
698 	} else if (streq(var, "rfSilent")) {
699                 fprintf(fd, "0x%04X", eepromN.baseEepHeader.rfSilent);
700 	} else if (streq(var, "btOptions")) {
701                 fprintf(fd, "0x%04X", eepromN.baseEepHeader.blueToothOptions);
702 	} else if (streq(var, "deviceCap")) {
703                 fprintf(fd, "0x%04X", eepromN.baseEepHeader.deviceCap);
704 	} else if (strneq(var, "macaddr", 7)) {
705                 fprintf(fd, "%02X",
706 		    eepromN.baseEepHeader.macAddr[atoi(var+7)]);
707 	} else if (streq(var, "opCapFlags")) {
708                 fprintf(fd, "0x%02X", eepromN.baseEepHeader.opCapFlags);
709 	} else if (streq(var, "eepMisc")) {
710                 fprintf(fd, "0x%02X", eepromN.baseEepHeader.eepMisc);
711 	} else if (strneq(var, "binBuildNumber", 14)) {
712                 fprintf(fd, "%3d",
713 		    (eepromN.baseEepHeader.binBuildNumber >> (8*atoi(var+14)))
714 		    & 0xff);
715 	} else if (strneq(var, "custData", 8)) {
716 		fprintf(fd, "%2.2X", eepromN.custData[atoi(var+8)]);
717 	} else if (streq(var, "xpd_mask")) {
718 		if (IS_VERS(<, AR_EEPROM_VER5_0))
719 			fprintf(fd, "0x%02x", pExpnPower->xpdMask);
720 		else
721 			fprintf(fd, "0x%02x", pRaw->xpd_mask);
722 	} else if (streq(var, "numChannels")) {
723 		if (IS_VERS(<, AR_EEPROM_VER5_0))
724 			fprintf(fd, "%2d", pExpnPower->numChannels);
725 		else
726 			fprintf(fd, "%2d", pRaw->numChannels);
727 	} else if (streq(var, "freq")) {
728 		if (IS_VERS(<, AR_EEPROM_VER5_0))
729 			fprintf(fd, "%4d", pExpnPower->pChannels[curchan]);
730 		else
731 			fprintf(fd, "%4d", pRaw->pChannels[curchan]);
732 	} else if (streq(var, "maxpow")) {
733 		int16_t maxPower_t4;
734 		if (IS_VERS(<, AR_EEPROM_VER5_0)) {
735 			maxPower_t4 = pExpnPower->pDataPerChannel[curchan].maxPower_t4;
736 		} else {
737 			maxPower_t4 = pRaw->pDataPerChannel[curchan].maxPower_t4;
738 			if (maxPower_t4 == 0)
739 				maxPower_t4 = getMaxPowerV5(&pRaw->pDataPerChannel[curchan]);
740 		}
741 		printQuarterDbmPower(fd, maxPower_t4);
742 	} else if (streq(var, "pd_gain")) {
743 		fprintf(fd, "%4d", pRaw->pDataPerChannel[curchan].
744 		    pDataPerPDGain[curpdgain].pd_gain);
745 	} else if (strneq(var, "maxpwr", 6)) {
746 		int vpd = atoi(var+6);
747 		if (vpd < pRaw->pDataPerChannel[curchan].pDataPerPDGain[curpdgain].numVpd)
748 			printQuarterDbmPower(fd, pRaw->pDataPerChannel[curchan].
749 			    pDataPerPDGain[curpdgain].pwr_t4[vpd]);
750 		else
751 			fprintf(fd, "     ");
752 	} else if (strneq(var, "pwr_t4_", 7)) {
753 		printQuarterDbmPower(fd, pExpnPower->pDataPerChannel[curchan].
754 		    pDataPerXPD[singleXpd].pwr_t4[atoi(var+7)]);
755 	} else if (strneq(var, "Vpd", 3)) {
756 		int vpd = atoi(var+3);
757 		if (vpd < pRaw->pDataPerChannel[curchan].pDataPerPDGain[curpdgain].numVpd)
758 			printVpd(fd, pRaw->pDataPerChannel[curchan].
759 			    pDataPerPDGain[curpdgain].Vpd[vpd]);
760 		else
761 			fprintf(fd, "     ");
762 	} else if (streq(var, "CTL")) {
763 		fprintf(fd, "0x%2x", eeprom.ee_ctl[curctl] & 0xff);
764 	} else if (streq(var, "ctlType")) {
765 		static const char *ctlType[16] = {
766 		    "11a base", "11b", "11g", "11a TURBO", "108g",
767 		    "2GHT20", "5GHT20", "2GHT40", "5GHT40",
768 		    "0x9", "0xa", "0xb", "0xc", "0xd", "0xe", "0xf",
769 		};
770 		fprintf(fd, "%8s", ctlType[eeprom.ee_ctl[curctl] & CTL_MODE_M]);
771 	} else if (streq(var, "ctlRD")) {
772 		static const char *ctlRD[8] = {
773 		    "0x00", " FCC", "0x20", "ETSI",
774 		    " MKK", "0x50", "0x60", "0x70"
775 		};
776 		fprintf(fd, "%s", ctlRD[(eeprom.ee_ctl[curctl] >> 4) & 7]);
777 	} else if (strneq(var, "rdEdgePower", 11)) {
778 		printEdgePower(fd, atoi(var+11));
779 	} else if (strneq(var, "rdEdgeFlag", 10)) {
780 		printEdgeFlag(fd, atoi(var+10));
781 	} else if (strneq(var, "rdEdge", 6)) {
782 		printEdge(fd, atoi(var+6));
783 	} else if (strneq(var, "testChannel", 11)) {
784 		fprintf(fd, "%4d", pPowerInfo[atoi(var+11)].testChannel);
785 	} else if (strneq(var, "pwr6_24_", 8)) {
786 		printHalfDbmPower(fd, pPowerInfo[atoi(var+8)].twicePwr6_24);
787 	} else if (strneq(var, "pwr36_", 6)) {
788 		printHalfDbmPower(fd, pPowerInfo[atoi(var+6)].twicePwr36);
789 	} else if (strneq(var, "pwr48_", 6)) {
790 		printHalfDbmPower(fd, pPowerInfo[atoi(var+6)].twicePwr48);
791 	} else if (strneq(var, "pwr54_", 6)) {
792 		printHalfDbmPower(fd, pPowerInfo[atoi(var+6)].twicePwr54);
793 	} else if (strneq(var, "channelValue", 12)) {
794 		fprintf(fd, "%4d", pDataPerChannel[atoi(var+12)].channelValue);
795 	} else if (strneq(var, "pcdacMin", 8)) {
796 		fprintf(fd, "%02d", pDataPerChannel[atoi(var+8)].pcdacMin);
797 	} else if (strneq(var, "pcdacMax", 8)) {
798 		fprintf(fd, "%02d", pDataPerChannel[atoi(var+8)].pcdacMax);
799 	} else if (strneq(var, "pcdac", 5)) {
800 		if (IS_VERS(<, AR_EEPROM_VER4_0)) {
801 			fprintf(fd, "%02d", pDataPerChannel[atoi(var+5)].
802 			    PcdacValues[curpcdac]);
803 		} else if (IS_VERS(<, AR_EEPROM_VER5_0)) {
804 			fprintf(fd, "%02d",
805 			    pExpnPower->pDataPerChannel[curchan].
806 				pDataPerXPD[singleXpd].pcdac[atoi(var+5)]);
807 		} else
808 			undef("pcdac");
809 	} else if (strneq(var, "pwrValue", 8)) {
810 		printPcdacValue(fd,
811 		    pDataPerChannel[atoi(var+8)].PwrValues[curpcdac]);
812 	} else if (streq(var, "singleXpd")) {
813 		fprintf(fd, "%2d", singleXpd);
814 	} else
815 		warnx("line %u, unknown EEPROM variable \"%s\"", lineno, var);
816 #undef strneq
817 #undef streq
818 }
819 
820 static void
821 ifmode(FILE *ftemplate, const char *mode)
822 {
823 	if (strcasecmp(mode, "11a") == 0) {
824 		if (IS_VERS(<, AR_EEPROM_VER14_2)) {
825 			if (eeprom.ee_Amode)
826 				_setmode(headerInfo11A);
827 			else
828 				skipto(ftemplate, "endmode");
829 			return;
830 		}
831 		if (IS_VERS(>=, AR_EEPROM_VER14_2)) {
832 			if (eepromN.baseEepHeader.opCapFlags & AR5416_OPFLAGS_11A)
833 				_setmode(headerInfo11A);
834 			else
835 				skipto(ftemplate, "endmode");
836 			return;
837 		}
838 	} else if (strcasecmp(mode, "11g") == 0) {
839 		if (IS_VERS(<, AR_EEPROM_VER14_2)) {
840 			if (eeprom.ee_Gmode)
841 				_setmode(headerInfo11G);
842 			else
843 				skipto(ftemplate, "endmode");
844 			return;
845 		}
846 		if (IS_VERS(>=, AR_EEPROM_VER14_2)) {
847 			if (eepromN.baseEepHeader.opCapFlags & AR5416_OPFLAGS_11G)
848 				_setmode(headerInfo11B);	/* NB: 2.4GHz */
849 			else
850 				skipto(ftemplate, "endmode");
851 			return;
852 		}
853 	} else if (strcasecmp(mode, "11b") == 0) {
854 		if (IS_VERS(<, AR_EEPROM_VER14_2)) {
855 			if (eeprom.ee_Bmode)
856 				_setmode(headerInfo11B);
857 			else
858 				skipto(ftemplate, "endmode");
859 			return;
860 		}
861 	}
862 	warnx("line %d, unknown/unexpected mode \"%s\" ignored",
863 	    lineno, mode);
864 	skipto(ftemplate, "endmode");
865 }
866 
867 static void
868 parseTemplate(FILE *ftemplate, FILE *fd)
869 {
870 	int c, i;
871 	char id[MAXID];
872 	long forchan, forpdgain, forctl, forpcdac;
873 
874 	lineno = 1;
875 	bol = 1;
876 	while ((c = getc(ftemplate)) != EOF) {
877 		if (c == '#') {			/* comment */
878 	skiptoeol:
879 			while ((c = getc(ftemplate)) != EOF && c != '\n')
880 				;
881 			if (c == EOF)
882 				return;
883 			lineno++;
884 			bol = 1;
885 			continue;
886 		}
887 		if (c == '.' && bol) {		/* .directive */
888 			if (token(ftemplate, id, MAXID, ".directive") == EOF)
889 				return;
890 			/* process directive */
891 			if (strcasecmp(id, "ifmode") == 0) {
892 				skipws(ftemplate);
893 				if (token(ftemplate, id, MAXID, "id") == EOF)
894 					return;
895 				ifmode(ftemplate, id);
896 			} else if (strcasecmp(id, "endmode") == 0) {
897 				/* XXX free malloc'd indirect data */
898 				curmode = -1;	/* NB: undefined */
899 			} else if (strcasecmp(id, "forchan") == 0) {
900 				forchan = ftell(ftemplate) - sizeof("forchan");
901 				if (curchan == -1)
902 					curchan = 0;
903 			} else if (strcasecmp(id, "endforchan") == 0) {
904 				if (++curchan < numChannels)
905 					fseek(ftemplate, forchan, SEEK_SET);
906 				else
907 					curchan = -1;
908 			} else if (strcasecmp(id, "ifpdgain") == 0) {
909 				skipws(ftemplate);
910 				if (token(ftemplate, id, MAXID, "pdgain") == EOF)
911 					return;
912 				curlpdgain = strtoul(id, NULL, 0);
913 				if (curlpdgain >= pRaw->pDataPerChannel[curchan].numPdGains) {
914 					skipto(ftemplate, "endpdgain");
915 					curlpdgain = -1;
916 				} else
917 					curpdgain = pdgain(curlpdgain);
918 			} else if (strcasecmp(id, "endpdgain") == 0) {
919 				curlpdgain = curpdgain = -1;
920 			} else if (strcasecmp(id, "forpdgain") == 0) {
921 				forpdgain = ftell(ftemplate) - sizeof("forpdgain");
922 				if (curlpdgain == -1) {
923 					skipws(ftemplate);
924 					if (token(ftemplate, id, MAXID, "pdgain") == EOF)
925 						return;
926 					curlpdgain = strtoul(id, NULL, 0);
927 					if (curlpdgain >= pRaw->pDataPerChannel[curchan].numPdGains) {
928 						skipto(ftemplate, "endforpdgain");
929 						curlpdgain = -1;
930 					} else
931 						curpdgain = pdgain(curlpdgain);
932 				}
933 			} else if (strcasecmp(id, "endforpdgain") == 0) {
934 				if (++curpdgain < pRaw->pDataPerChannel[curchan].numPdGains)
935 					fseek(ftemplate, forpdgain, SEEK_SET);
936 				else
937 					curpdgain = -1;
938 			} else if (strcasecmp(id, "forpcdac") == 0) {
939 				forpcdac = ftell(ftemplate) - sizeof("forpcdac");
940 				if (curpcdac == -1)
941 					curpcdac = 0;
942 			} else if (strcasecmp(id, "endforpcdac") == 0) {
943 				if (++curpcdac < pDataPerChannel[0].numPcdacValues)
944 					fseek(ftemplate, forpcdac, SEEK_SET);
945 				else
946 					curpcdac = -1;
947 			} else if (strcasecmp(id, "forctl") == 0) {
948 				forctl = ftell(ftemplate) - sizeof("forchan");
949 				if (curctl == -1)
950 					curctl = nextctl(0);
951 			} else if (strcasecmp(id, "endforctl") == 0) {
952 				curctl = nextctl(curctl+1);
953 				if (curctl != -1)
954 					fseek(ftemplate, forctl, SEEK_SET);
955 			} else {
956 				warnx("line %d, unknown directive %s ignored",
957 				    lineno, id);
958 			}
959 			goto skiptoeol;
960 		}
961 		if (c == '$') {			/* $variable reference */
962 			if (token(ftemplate, id, MAXID, "$var") == EOF)
963 				return;
964 			/* XXX not valid if variable depends on curmode */
965 			eevar(fd, id);
966 			continue;
967 		}
968 		if (c == '\\') {		/* escape next character */
969 			c = getc(ftemplate);
970 			if (c == EOF)
971 				return;
972 		}
973 		fputc(c, fd);
974 		bol = (c == '\n');
975 		if (bol)
976 			lineno++;
977 	}
978 }
979