xref: /netbsd/usr.bin/usbhidctl/usbhid.c (revision bf9ec67e)
1 /*      $NetBSD: usbhid.c,v 1.22 2002/02/20 20:30:42 christos Exp $ */
2 
3 /*
4  * Copyright (c) 2001 The NetBSD Foundation, Inc.
5  * All rights reserved.
6  *
7  * This code is derived from software contributed to The NetBSD Foundation
8  * by David Sainty <David.Sainty@dtsp.co.nz>
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  * 3. All advertising materials mentioning features or use of this software
19  *    must display the following acknowledgement:
20  *        This product includes software developed by the NetBSD
21  *        Foundation, Inc. and its contributors.
22  * 4. Neither the name of The NetBSD Foundation nor the names of its
23  *    contributors may be used to endorse or promote products derived
24  *    from this software without specific prior written permission.
25  *
26  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
27  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
28  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
29  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
30  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
31  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
32  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
33  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
34  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
35  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
36  * POSSIBILITY OF SUCH DAMAGE.
37  */
38 
39 #include <sys/types.h>
40 
41 #include <dev/usb/usb.h>
42 #include <dev/usb/usbhid.h>
43 
44 #include <ctype.h>
45 #include <err.h>
46 #include <errno.h>
47 #include <fcntl.h>
48 #include <limits.h>
49 #include <stdio.h>
50 #include <stdlib.h>
51 #include <string.h>
52 #include <unistd.h>
53 #include <usbhid.h>
54 
55 /*
56  * Zero if not in a verbose mode.  Greater levels of verbosity
57  * are indicated by values larger than one.
58  */
59 unsigned int verbose;
60 
61 /* Parser tokens */
62 #define DELIM_USAGE '.'
63 #define DELIM_PAGE ':'
64 #define DELIM_SET '='
65 
66 static int reportid;
67 
68 struct Susbvar {
69 	/* Variable name, not NUL terminated */
70 	char const *variable;
71 	size_t varlen;
72 
73 	char const *value; /* Value to set variable to */
74 
75 #define MATCH_ALL		(1 << 0)
76 #define MATCH_COLLECTIONS	(1 << 1)
77 #define MATCH_NODATA		(1 << 2)
78 #define MATCH_CONSTANTS		(1 << 3)
79 #define MATCH_WASMATCHED	(1 << 4)
80 #define MATCH_SHOWPAGENAME	(1 << 5)
81 #define MATCH_SHOWNUMERIC	(1 << 6)
82 #define MATCH_WRITABLE		(1 << 7)
83 #define MATCH_SHOWVALUES	(1 << 8)
84 	unsigned int mflags;
85 
86 	/* Workspace for hidmatch() */
87 	ssize_t matchindex;
88 
89 	int (*opfunc)(struct hid_item *item, struct Susbvar *var,
90 		      u_int32_t const *collist, size_t collen, u_char *buf);
91 };
92 
93 struct Sreport {
94 	struct usb_ctl_report *buffer;
95 
96 	enum {srs_uninit, srs_clean, srs_dirty} status;
97 	int report_id;
98 	size_t size;
99 };
100 
101 static struct {
102 	int uhid_report;
103 	hid_kind_t hid_kind;
104 	char const *name;
105 } const reptoparam[] = {
106 #define REPORT_INPUT 0
107 	{ UHID_INPUT_REPORT, hid_input, "input" },
108 #define REPORT_OUTPUT 1
109 	{ UHID_OUTPUT_REPORT, hid_output, "output" },
110 #define REPORT_FEATURE 2
111 	{ UHID_FEATURE_REPORT, hid_feature, "feature" }
112 #define REPORT_MAXVAL 2
113 };
114 
115 /*
116  * Extract 16-bit unsigned usage ID from a numeric string.  Returns -1
117  * if string failed to parse correctly.
118  */
119 static int
120 strtousage(const char *nptr, size_t nlen)
121 {
122 	char *endptr;
123 	long result;
124 	char numstr[16];
125 
126 	if (nlen >= sizeof(numstr) || !isdigit((unsigned char)*nptr))
127 		return -1;
128 
129 	/*
130 	 * We use strtol() here, but unfortunately strtol() requires a
131 	 * NUL terminated string - which we don't have - at least not
132 	 * officially.
133 	 */
134 	memcpy(numstr, nptr, nlen);
135 	numstr[nlen] = '\0';
136 
137 	result = strtol(numstr, &endptr, 0);
138 
139 	if (result < 0 || result > 0xffff || endptr != &numstr[nlen])
140 		return -1;
141 
142 	return result;
143 }
144 
145 struct usagedata {
146 	char const *page_name;
147 	char const *usage_name;
148 	size_t page_len;
149 	size_t usage_len;
150 	int isfinal;
151 	u_int32_t usage_id;
152 };
153 
154 /*
155  * Test a rule against the current usage data.  Returns -1 on no
156  * match, 0 on partial match and 1 on complete match.
157  */
158 static int
159 hidtestrule(struct Susbvar *var, struct usagedata *cache)
160 {
161 	char const *varname;
162 	ssize_t matchindex, pagesplit;
163 	size_t strind, varlen;
164 	int numusage;
165 	u_int32_t usage_id;
166 
167 	matchindex = var->matchindex;
168 	varname = var->variable;
169 	varlen = var->varlen;
170 
171 	usage_id = cache->usage_id;
172 
173 	/*
174 	 * Parse the current variable name, locating the end of the
175 	 * current 'usage', and possibly where the usage page name
176 	 * ends.
177 	 */
178 	pagesplit = -1;
179 	for (strind = matchindex; strind < varlen; strind++) {
180 		if (varname[strind] == DELIM_USAGE)
181 			break;
182 		if (varname[strind] == DELIM_PAGE)
183 			pagesplit = strind;
184 	}
185 
186 	if (cache->isfinal && strind != varlen)
187 		/*
188 		 * Variable name is too long (hit delimiter instead of
189 		 * end-of-variable).
190 		 */
191 		return -1;
192 
193 	if (pagesplit >= 0) {
194 		/*
195 		 * Page name was specified, determine whether it was
196 		 * symbolic or numeric.
197 		 */
198 		char const *strstart;
199 		int numpage;
200 
201 		strstart = &varname[matchindex];
202 
203 		numpage = strtousage(strstart, pagesplit - matchindex);
204 
205 		if (numpage >= 0) {
206 			/* Valid numeric */
207 
208 			if (numpage != HID_PAGE(usage_id))
209 				/* Numeric didn't match page ID */
210 				return -1;
211 		} else {
212 			/* Not a valid numeric */
213 
214 			/*
215 			 * Load and cache the page name if and only if
216 			 * it hasn't already been loaded (it's a
217 			 * fairly expensive operation).
218 			 */
219 			if (cache->page_name == NULL) {
220 				cache->page_name = hid_usage_page(HID_PAGE(usage_id));
221 				cache->page_len = strlen(cache->page_name);
222 			}
223 
224 			/*
225 			 * Compare specified page name to actual page
226 			 * name.
227 			 */
228 			if (cache->page_len !=
229 			    (size_t)(pagesplit - matchindex) ||
230 			    memcmp(cache->page_name,
231 				   &varname[matchindex],
232 				   cache->page_len) != 0)
233 				/* Mismatch, page name wrong */
234 				return -1;
235 		}
236 
237 		/* Page matches, discard page name */
238 		matchindex = pagesplit + 1;
239 	}
240 
241 	numusage = strtousage(&varname[matchindex], strind - matchindex);
242 
243 	if (numusage >= 0) {
244 		/* Valid numeric */
245 
246 		if (numusage != HID_USAGE(usage_id))
247 			/* Numeric didn't match usage ID */
248 			return -1;
249 	} else {
250 		/* Not a valid numeric */
251 
252 		/* Load and cache the usage name */
253 		if (cache->usage_name == NULL) {
254 			cache->usage_name = hid_usage_in_page(usage_id);
255 			cache->usage_len = strlen(cache->usage_name);
256 		}
257 
258 		/*
259 		 * Compare specified usage name to actual usage name
260 		 */
261 		if (cache->usage_len != (size_t)(strind - matchindex) ||
262 		    memcmp(cache->usage_name, &varname[matchindex],
263 			   cache->usage_len) != 0)
264 			/* Mismatch, usage name wrong */
265 			return -1;
266 	}
267 
268 	if (cache->isfinal)
269 		/* Match */
270 		return 1;
271 
272 	/*
273 	 * Partial match: Move index past this usage string +
274 	 * delimiter
275 	 */
276 	var->matchindex = strind + 1;
277 
278 	return 0;
279 }
280 
281 /*
282  * hidmatch() determines whether the item specified in 'item', and
283  * nested within a heirarchy of collections specified in 'collist'
284  * matches any of the rules in the list 'varlist'.  Returns the
285  * matching rule on success, or NULL on no match.
286  */
287 static struct Susbvar*
288 hidmatch(u_int32_t const *collist, size_t collen, struct hid_item *item,
289 	 struct Susbvar *varlist, size_t vlsize)
290 {
291 	size_t colind, vlactive, vlind;
292 	int iscollection;
293 
294 	/*
295 	 * Keep track of how many variables are still "active".  When
296 	 * the active count reaches zero, don't bother to continue
297 	 * looking for matches.
298 	 */
299 	vlactive = vlsize;
300 
301 	iscollection = item->kind == hid_collection ||
302 		item->kind == hid_endcollection;
303 
304 	for (vlind = 0; vlind < vlsize; vlind++) {
305 		struct Susbvar *var;
306 
307 		var = &varlist[vlind];
308 
309 		var->matchindex = 0;
310 
311 		if (!(var->mflags & MATCH_COLLECTIONS) && iscollection) {
312 			/* Don't match collections for this variable */
313 			var->matchindex = -1;
314 			vlactive--;
315 		} else if (!iscollection && !(var->mflags & MATCH_CONSTANTS) &&
316 			   (item->flags & HIO_CONST)) {
317 			/*
318 			 * Don't match constants for this variable,
319 			 * but ignore the constant bit on collections.
320 			 */
321 			var->matchindex = -1;
322 			vlactive--;
323 		} else if ((var->mflags & MATCH_WRITABLE) &&
324 			   ((item->kind != hid_output &&
325 			     item->kind != hid_feature) ||
326 			    (item->flags & HIO_CONST))) {
327 			/*
328 			 * If we are only matching writable items, if
329 			 * this is not an output or feature kind, or
330 			 * it is a constant, reject it.
331 			 */
332 			var->matchindex = -1;
333 			vlactive--;
334 		} else if (var->mflags & MATCH_ALL) {
335 			/* Match immediately */
336 			return &varlist[vlind];
337 		}
338 	}
339 
340 	/*
341 	 * Loop through each usage in the collection list, including
342 	 * the 'item' itself on the final iteration.  For each usage,
343 	 * test which variables named in the rule list are still
344 	 * applicable - if any.
345 	 */
346 	for (colind = 0; vlactive > 0 && colind <= collen; colind++) {
347 		struct usagedata cache;
348 
349 		cache.isfinal = (colind == collen);
350 		if (cache.isfinal)
351 			cache.usage_id = item->usage;
352 		else
353 			cache.usage_id = collist[colind];
354 
355 		cache.usage_name = NULL;
356 		cache.page_name = NULL;
357 
358 		/*
359 		 * Loop through each rule, testing whether the rule is
360 		 * still applicable or not.  For each rule,
361 		 * 'matchindex' retains the current match state as an
362 		 * index into the variable name string, or -1 if this
363 		 * rule has been proven not to match.
364 		 */
365 		for (vlind = 0; vlind < vlsize; vlind++) {
366 			struct Susbvar *var;
367 			int matchres;
368 
369 			var = &varlist[vlind];
370 
371 			if (var->matchindex < 0)
372 				/* Mismatch at a previous level */
373 				continue;
374 
375 			matchres = hidtestrule(var, &cache);
376 
377 			if (matchres < 0) {
378 				/* Bad match */
379 				var->matchindex = -1;
380 				vlactive--;
381 				continue;
382 			} else if (matchres > 0) {
383 				/* Complete match */
384 				return var;
385 			}
386 		}
387 	}
388 
389 	return NULL;
390 }
391 
392 static void
393 allocreport(struct Sreport *report, report_desc_t rd, int repindex)
394 {
395 	int reptsize;
396 
397 	reptsize = hid_report_size(rd, reptoparam[repindex].hid_kind, reportid);
398 	if (reptsize < 0)
399 		errx(1, "Negative report size");
400 	report->size = reptsize;
401 
402 	if (report->size > 0) {
403 		/*
404 		 * Allocate a buffer with enough space for the
405 		 * report in the variable-sized data field.
406 		 */
407 		report->buffer = malloc(sizeof(*report->buffer) -
408 					sizeof(report->buffer->ucr_data) +
409 					report->size);
410 		if (report->buffer == NULL)
411 			err(1, NULL);
412 	} else
413 		report->buffer = NULL;
414 
415 	report->status = srs_clean;
416 }
417 
418 static void
419 freereport(struct Sreport *report)
420 {
421 	if (report->buffer != NULL)
422 		free(report->buffer);
423 	report->status = srs_uninit;
424 }
425 
426 static void
427 getreport(struct Sreport *report, int hidfd, report_desc_t rd, int repindex)
428 {
429 	if (report->status == srs_uninit) {
430 		allocreport(report, rd, repindex);
431 		if (report->size == 0)
432 			return;
433 
434 		report->buffer->ucr_report = reptoparam[repindex].uhid_report;
435 		if (ioctl(hidfd, USB_GET_REPORT, report->buffer) < 0)
436 			err(1, "USB_GET_REPORT (probably not supported by "
437 			    "device)");
438 	}
439 }
440 
441 static void
442 setreport(struct Sreport *report, int hidfd, int repindex)
443 {
444 	if (report->status == srs_dirty) {
445 		report->buffer->ucr_report = reptoparam[repindex].uhid_report;
446 
447 		if (ioctl(hidfd, USB_SET_REPORT, report->buffer) < 0)
448 			err(1, "USB_SET_REPORT(%s)",
449 			    reptoparam[repindex].name);
450 
451 		report->status = srs_clean;
452 	}
453 }
454 
455 /* ARGSUSED1 */
456 static int
457 varop_value(struct hid_item *item, struct Susbvar *var,
458 	    u_int32_t const *collist, size_t collen, u_char *buf)
459 {
460 	printf("%d\n", hid_get_data(buf, item));
461 	return 0;
462 }
463 
464 /* ARGSUSED1 */
465 static int
466 varop_display(struct hid_item *item, struct Susbvar *var,
467 	      u_int32_t const *collist, size_t collen, u_char *buf)
468 {
469 	size_t colitem;
470 	int val, i;
471 
472 	for (i = 0; i < item->report_count; i++) {
473 		for (colitem = 0; colitem < collen; colitem++) {
474 			if (var->mflags & MATCH_SHOWPAGENAME)
475 				printf("%s:",
476 				    hid_usage_page(HID_PAGE(collist[colitem])));
477 			printf("%s.", hid_usage_in_page(collist[colitem]));
478 		}
479 		if (var->mflags & MATCH_SHOWPAGENAME)
480 			printf("%s:", hid_usage_page(HID_PAGE(item->usage)));
481 		val = hid_get_data(buf, item);
482 		item->pos += item->report_size;
483 		if (item->usage_minimum != 0 || item->usage_maximum != 0) {
484 			val += item->usage_minimum;
485 			printf("%s=1", hid_usage_in_page(val));
486 		} else {
487 			printf("%s=%d%s", hid_usage_in_page(item->usage),
488 			       val, item->flags & HIO_CONST ? " (const)" : "");
489 		}
490 		if (item->report_count > 1)
491 			printf(" [%d]", i);
492 		printf("\n");
493 	}
494 	return 0;
495 }
496 
497 /* ARGSUSED1 */
498 static int
499 varop_modify(struct hid_item *item, struct Susbvar *var,
500 	     u_int32_t const *collist, size_t collen, u_char *buf)
501 {
502 	u_int dataval;
503 
504 	dataval = (u_int)strtol(var->value, NULL, 10);
505 
506 	hid_set_data(buf, item, dataval);
507 
508 	if (var->mflags & MATCH_SHOWVALUES)
509 		/* Display set value */
510 		varop_display(item, var, collist, collen, buf);
511 
512 	return 1;
513 }
514 
515 static void
516 reportitem(char const *label, struct hid_item const *item, unsigned int mflags)
517 {
518 	int isconst = item->flags & HIO_CONST,
519 	    isvar = item->flags & HIO_VARIABLE;
520 	printf("%s size=%d count=%d%s%s page=%s", label,
521 	       item->report_size, item->report_count,
522 	       isconst ? " Const" : "",
523 	       !isvar && !isconst ? " Array" : "",
524 	       hid_usage_page(HID_PAGE(item->usage)));
525 	if (item->usage_minimum != 0 || item->usage_maximum != 0) {
526 		printf(" usage=%s..%s", hid_usage_in_page(item->usage_minimum),
527 		       hid_usage_in_page(item->usage_maximum));
528 		if (mflags & MATCH_SHOWNUMERIC)
529 			printf(" (%u:0x%x..%u:0x%x)",
530 			       HID_PAGE(item->usage_minimum),
531 			       HID_USAGE(item->usage_minimum),
532 			       HID_PAGE(item->usage_maximum),
533 			       HID_USAGE(item->usage_maximum));
534 	} else {
535 		printf(" usage=%s", hid_usage_in_page(item->usage));
536 		if (mflags & MATCH_SHOWNUMERIC)
537 			printf(" (%u:0x%x)",
538 			       HID_PAGE(item->usage), HID_USAGE(item->usage));
539 	}
540 	printf(", logical range %d..%d",
541 	       item->logical_minimum, item->logical_maximum);
542 	if (item->physical_minimum != item->physical_maximum)
543 		printf(", physical range %d..%d",
544 		       item->physical_minimum, item->physical_maximum);
545 	if (item->unit)
546 		printf(", unit=0x%02x exp=%d", item->unit,
547 		       item->unit_exponent);
548 	printf("\n");
549 }
550 
551 /* ARGSUSED1 */
552 static int
553 varop_report(struct hid_item *item, struct Susbvar *var,
554 	     u_int32_t const *collist, size_t collen, u_char *buf)
555 {
556 	switch (item->kind) {
557 	case hid_collection:
558 		printf("Collection page=%s usage=%s",
559 		       hid_usage_page(HID_PAGE(item->usage)),
560 		       hid_usage_in_page(item->usage));
561 		if (var->mflags & MATCH_SHOWNUMERIC)
562 			printf(" (%u:0x%x)\n",
563 			       HID_PAGE(item->usage), HID_USAGE(item->usage));
564 		else
565 			printf("\n");
566 		break;
567 	case hid_endcollection:
568 		printf("End collection\n");
569 		break;
570 	case hid_input:
571 		reportitem("Input  ", item, var->mflags);
572 		break;
573 	case hid_output:
574 		reportitem("Output ", item, var->mflags);
575 		break;
576 	case hid_feature:
577 		reportitem("Feature", item, var->mflags);
578 		break;
579 	}
580 
581 	return 0;
582 }
583 
584 static void
585 devloop(int hidfd, report_desc_t rd, struct Susbvar *varlist, size_t vlsize)
586 {
587 	u_char *dbuf;
588 	struct hid_data *hdata;
589 	size_t collind, dlen;
590 	struct hid_item hitem;
591 	u_int32_t colls[128];
592 	struct Sreport inreport;
593 
594 	allocreport(&inreport, rd, REPORT_INPUT);
595 
596 	if (inreport.size <= 0)
597 		errx(1, "Input report descriptor invalid length");
598 
599 	dlen = inreport.size;
600 	dbuf = inreport.buffer->ucr_data;
601 
602 	for (;;) {
603 		ssize_t readlen;
604 
605 		readlen = read(hidfd, dbuf, dlen);
606 		if (readlen < 0)
607 			err(1, "Device read error");
608 		if (dlen != (size_t)readlen)
609 			errx(1, "Unexpected response length: %lu != %lu",
610 			     (unsigned long)readlen, (unsigned long)dlen);
611 
612 		collind = 0;
613 		hdata = hid_start_parse(rd, 1 << hid_input, reportid);
614 		if (hdata == NULL)
615 			errx(1, "Failed to start parser");
616 
617 		while (hid_get_item(hdata, &hitem)) {
618 			struct Susbvar *matchvar;
619 
620 			switch (hitem.kind) {
621 			case hid_collection:
622 				if (collind >= (sizeof(colls) / sizeof(*colls)))
623 					errx(1, "Excessive nested collections");
624 				colls[collind++] = hitem.usage;
625 				break;
626 			case hid_endcollection:
627 				if (collind == 0)
628 					errx(1, "Excessive collection ends");
629 				collind--;
630 				break;
631 			case hid_input:
632 				break;
633 			case hid_output:
634 			case hid_feature:
635 				errx(1, "Unexpected non-input item returned");
636 			}
637 
638 			if (reportid != -1 && hitem.report_ID != reportid)
639 				continue;
640 
641 			matchvar = hidmatch(colls, collind, &hitem,
642 					    varlist, vlsize);
643 
644 			if (matchvar != NULL)
645 				matchvar->opfunc(&hitem, matchvar,
646 						 colls, collind,
647 						 inreport.buffer->ucr_data);
648 		}
649 		hid_end_parse(hdata);
650 		printf("\n");
651 	}
652 	/* NOTREACHED */
653 }
654 
655 static void
656 devshow(int hidfd, report_desc_t rd, struct Susbvar *varlist, size_t vlsize,
657 	int kindset)
658 {
659 	struct hid_data *hdata;
660 	size_t collind, repind, vlind;
661 	struct hid_item hitem;
662 	u_int32_t colls[128];
663 	struct Sreport reports[REPORT_MAXVAL + 1];
664 
665 
666 	for (repind = 0; repind < (sizeof(reports) / sizeof(*reports));
667 	     repind++) {
668 		reports[repind].status = srs_uninit;
669 		reports[repind].buffer = NULL;
670 	}
671 
672 	collind = 0;
673 	hdata = hid_start_parse(rd, kindset, reportid);
674 	if (hdata == NULL)
675 		errx(1, "Failed to start parser");
676 
677 	while (hid_get_item(hdata, &hitem)) {
678 		struct Susbvar *matchvar;
679 		int repindex;
680 
681 		if (verbose > 3)
682 			printf("item: kind=%d repid=%d usage=0x%x\n",
683 			       hitem.kind, hitem.report_ID, hitem.usage);
684 		repindex = -1;
685 		switch (hitem.kind) {
686 		case hid_collection:
687 			if (collind >= (sizeof(colls) / sizeof(*colls)))
688 				errx(1, "Excessive nested collections");
689 			colls[collind++] = hitem.usage;
690 			break;
691 		case hid_endcollection:
692 			if (collind == 0)
693 				errx(1, "Excessive collection ends");
694 			collind--;
695 			break;
696 		case hid_input:
697 			repindex = REPORT_INPUT;
698 			break;
699 		case hid_output:
700 			repindex = REPORT_OUTPUT;
701 			break;
702 		case hid_feature:
703 			repindex = REPORT_FEATURE;
704 			break;
705 		}
706 
707 		if (reportid != -1 && hitem.report_ID != reportid)
708 			continue;
709 
710 		matchvar = hidmatch(colls, collind, &hitem, varlist, vlsize);
711 
712 		if (matchvar != NULL) {
713 			u_char *bufdata;
714 			struct Sreport *repptr;
715 
716 			matchvar->mflags |= MATCH_WASMATCHED;
717 
718 			if (repindex >= 0)
719 				repptr = &reports[repindex];
720 			else
721 				repptr = NULL;
722 
723 			if (repptr != NULL &&
724 			    !(matchvar->mflags & MATCH_NODATA))
725 				getreport(repptr, hidfd, rd, repindex);
726 
727 			bufdata = (repptr == NULL || repptr->buffer == NULL) ?
728 				NULL : repptr->buffer->ucr_data;
729 
730 			if (matchvar->opfunc(&hitem, matchvar, colls, collind,
731 					     bufdata))
732 				repptr->status = srs_dirty;
733 		}
734 	}
735 	hid_end_parse(hdata);
736 
737 	for (repind = 0; repind < (sizeof(reports) / sizeof(*reports));
738 	     repind++) {
739 		setreport(&reports[repind], hidfd, repind);
740 		freereport(&reports[repind]);
741 	}
742 
743 	/* Warn about any items that we couldn't find a match for */
744 	for (vlind = 0; vlind < vlsize; vlind++) {
745 		struct Susbvar *var;
746 
747 		var = &varlist[vlind];
748 
749 		if (var->variable != NULL &&
750 		    !(var->mflags & MATCH_WASMATCHED))
751 			warnx("Failed to match: %.*s", (int)var->varlen,
752 			      var->variable);
753 	}
754 }
755 
756 static void
757 usage(void)
758 {
759 	const char *progname = getprogname();
760 
761 	fprintf(stderr, "Usage: %s -f device [-t tablefile] [-l] [-v] -a\n",
762 	    progname);
763 	fprintf(stderr, "       %s -f device [-t tablefile] [-v] -r\n",
764 	    progname);
765 	fprintf(stderr,
766 	    "       %s -f device [-t tablefile] [-l] [-n] [-v] name ...\n",
767 	    progname);
768 	fprintf(stderr,
769 	    "       %s -f device [-t tablefile] -w name=value ...\n",
770 	    progname);
771 	exit(1);
772 }
773 
774 int
775 main(int argc, char **argv)
776 {
777 	char const *dev;
778 	char const *table;
779 	size_t varnum;
780 	int aflag, lflag, nflag, rflag, wflag;
781 	int ch, hidfd;
782 	report_desc_t repdesc;
783 	char devnamebuf[PATH_MAX];
784 	struct Susbvar variables[128];
785 
786 	wflag = aflag = nflag = verbose = rflag = lflag = 0;
787 	dev = NULL;
788 	table = NULL;
789 	while ((ch = getopt(argc, argv, "?af:lnrt:vw")) != -1) {
790 		switch (ch) {
791 		case 'a':
792 			aflag = 1;
793 			break;
794 		case 'f':
795 			dev = optarg;
796 			break;
797 		case 'l':
798 			lflag = 1;
799 			break;
800 		case 'n':
801 			nflag = 1;
802 			break;
803 		case 'r':
804 			rflag = 1;
805 			break;
806 		case 't':
807 			table = optarg;
808 			break;
809 		case 'v':
810 			verbose++;
811 			break;
812 		case 'w':
813 			wflag = 1;
814 			break;
815 		case '?':
816 		default:
817 			usage();
818 			/* NOTREACHED */
819 		}
820 	}
821 	argc -= optind;
822 	argv += optind;
823 	if (dev == NULL || (lflag && (wflag || rflag))) {
824 		/*
825 		 * No device specified, or attempting to loop and set
826 		 * or dump report at the same time
827 		 */
828 		usage();
829 		/* NOTREACHED */
830 	}
831 
832 	for (varnum = 0; varnum < (size_t)argc; varnum++) {
833 		char const *name, *valuesep;
834 		struct Susbvar *svar;
835 
836 		svar = &variables[varnum];
837 		name = argv[varnum];
838 		valuesep = strchr(name, DELIM_SET);
839 
840 		svar->variable = name;
841 		svar->mflags = 0;
842 
843 		if (valuesep == NULL) {
844 			/* Read variable */
845 			if (wflag)
846 				errx(1, "Must not specify -w to read variables");
847 			svar->value = NULL;
848 			svar->varlen = strlen(name);
849 
850 			if (nflag) {
851 				/* Display value of variable only */
852 				svar->opfunc = varop_value;
853 			} else {
854 				/* Display name and value of variable */
855 				svar->opfunc = varop_display;
856 
857 				if (verbose >= 1)
858 					/* Show page names in verbose modes */
859 					svar->mflags |= MATCH_SHOWPAGENAME;
860 			}
861 		} else {
862 			/* Write variable */
863 			if (!wflag)
864 				errx(2, "Must specify -w to set variables");
865 			svar->mflags |= MATCH_WRITABLE;
866 			if (verbose >= 1)
867 				/*
868 				 * Allow displaying of set value in
869 				 * verbose mode.  This isn't
870 				 * particularly useful though, so
871 				 * don't bother documenting it.
872 				 */
873 				svar->mflags |= MATCH_SHOWVALUES;
874 			svar->varlen = valuesep - name;
875 			svar->value = valuesep + 1;
876 			svar->opfunc = varop_modify;
877 		}
878 	}
879 
880 	if (aflag || rflag) {
881 		struct Susbvar *svar;
882 
883 		svar = &variables[varnum++];
884 
885 		svar->variable = NULL;
886 		svar->mflags = MATCH_ALL;
887 
888 		if (rflag) {
889 			/*
890 			 * Dump report descriptor.  Do dump collection
891 			 * items also, and hint that it won't be
892 			 * necessary to get the item status.
893 			 */
894 			svar->opfunc = varop_report;
895 			svar->mflags |= MATCH_COLLECTIONS | MATCH_NODATA;
896 
897 			switch (verbose) {
898 			default:
899 				/* Level 2: Show item numerics and constants */
900 				svar->mflags |= MATCH_SHOWNUMERIC;
901 				/* FALLTHROUGH */
902 			case 1:
903 				/* Level 1: Just show constants */
904 				svar->mflags |= MATCH_CONSTANTS;
905 				/* FALLTHROUGH */
906 			case 0:
907 				break;
908 			}
909 		} else {
910 			/* Display name and value of variable */
911 			svar->opfunc = varop_display;
912 
913 			switch (verbose) {
914 			default:
915 				/* Level 2: Show constants and page names */
916 				svar->mflags |= MATCH_CONSTANTS;
917 				/* FALLTHROUGH */
918 			case 1:
919 				/* Level 1: Just show page names */
920 				svar->mflags |= MATCH_SHOWPAGENAME;
921 				/* FALLTHROUGH */
922 			case 0:
923 				break;
924 			}
925 		}
926 	}
927 
928 	if (varnum == 0) {
929 		/* Nothing to do...  Display usage information. */
930 		usage();
931 		/* NOTREACHED */
932 	}
933 
934 	hid_init(table);
935 
936 	if (dev[0] != '/') {
937 		snprintf(devnamebuf, sizeof(devnamebuf), "/dev/%s%s",
938 			 isdigit(dev[0]) ? "uhid" : "", dev);
939 		dev = devnamebuf;
940 	}
941 
942 	hidfd = open(dev, O_RDWR);
943 	if (hidfd < 0)
944 		err(1, "%s", dev);
945 
946 	if (ioctl(hidfd, USB_GET_REPORT_ID, &reportid) < 0)
947 		reportid = -1;
948 	if (verbose > 1)
949 		printf("report ID=%d\n", reportid);
950 	repdesc = hid_get_report_desc(hidfd);
951 	if (repdesc == 0)
952 		errx(1, "USB_GET_REPORT_DESC");
953 
954 	if (lflag) {
955 		devloop(hidfd, repdesc, variables, varnum);
956 		/* NOTREACHED */
957 	}
958 
959 	if (rflag)
960 		/* Report mode header */
961 		printf("Report descriptor:\n");
962 
963 	devshow(hidfd, repdesc, variables, varnum,
964 		1 << hid_input |
965 		1 << hid_output |
966 		1 << hid_feature);
967 
968 	if (rflag) {
969 		/* Report mode trailer */
970 		size_t repindex;
971 		for (repindex = 0;
972 		     repindex < (sizeof(reptoparam) / sizeof(*reptoparam));
973 		     repindex++) {
974 			int size;
975 			size = hid_report_size(repdesc,
976 					       reptoparam[repindex].hid_kind,
977 					       reportid);
978 			printf("Total %7s size %d bytes\n",
979 			       reptoparam[repindex].name, size);
980 		}
981 	}
982 
983 	hid_dispose_report_desc(repdesc);
984 	exit(0);
985 	/* NOTREACHED */
986 }
987