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