Home | History | Annotate | Line # | Download | only in usbhidctl
usbhid.c revision 1.19
      1 /*      $NetBSD: usbhid.c,v 1.19 2001/12/22 12:34:41 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)
    574 			err(1, "Device read error");
    575 		if (dlen != (size_t)readlen)
    576 			errx(1, "Unexpected response length: %lu != %lu",
    577 			     (unsigned long)readlen, (unsigned long)dlen);
    578 
    579 		collind = 0;
    580 		hdata = hid_start_parse(rd, 1 << hid_input);
    581 		if (hdata == NULL)
    582 			errx(1, "Failed to start parser");
    583 
    584 		while (hid_get_item(hdata, &hitem)) {
    585 			struct Susbvar *matchvar;
    586 
    587 			switch (hitem.kind) {
    588 			case hid_collection:
    589 				if (collind >= (sizeof(colls) / sizeof(*colls)))
    590 					errx(1, "Excessive nested collections");
    591 				colls[collind++] = hitem.usage;
    592 				break;
    593 			case hid_endcollection:
    594 				if (collind == 0)
    595 					errx(1, "Excessive collection ends");
    596 				collind--;
    597 				break;
    598 			case hid_input:
    599 				break;
    600 			case hid_output:
    601 			case hid_feature:
    602 				errx(1, "Unexpected non-input item returned");
    603 			}
    604 
    605 			matchvar = hidmatch(colls, collind, &hitem,
    606 					    varlist, vlsize);
    607 
    608 			if (matchvar != NULL)
    609 				matchvar->opfunc(&hitem, matchvar,
    610 						 colls, collind,
    611 						 inreport.buffer->data);
    612 		}
    613 		hid_end_parse(hdata);
    614 		printf("\n");
    615 	}
    616 	/* NOTREACHED */
    617 }
    618 
    619 static void
    620 devshow(int hidfd, report_desc_t rd, struct Susbvar *varlist, size_t vlsize,
    621 	int kindset)
    622 {
    623 	struct hid_data *hdata;
    624 	size_t collind, repind, vlind;
    625 	struct hid_item hitem;
    626 	u_int32_t colls[128];
    627 	struct Sreport reports[REPORT_MAXVAL + 1];
    628 
    629 
    630 	for (repind = 0; repind < (sizeof(reports) / sizeof(*reports));
    631 	     repind++) {
    632 		reports[repind].status = srs_uninit;
    633 		reports[repind].buffer = NULL;
    634 	}
    635 
    636 	collind = 0;
    637 	hdata = hid_start_parse(rd, kindset |
    638 				(1 << hid_collection) |
    639 				(1 << hid_endcollection));
    640 	if (hdata == NULL)
    641 		errx(1, "Failed to start parser");
    642 
    643 	while (hid_get_item(hdata, &hitem)) {
    644 		struct Susbvar *matchvar;
    645 		int repindex;
    646 
    647 		repindex = -1;
    648 		switch (hitem.kind) {
    649 		case hid_collection:
    650 			if (collind >= (sizeof(colls) / sizeof(*colls)))
    651 				errx(1, "Excessive nested collections");
    652 			colls[collind++] = hitem.usage;
    653 			break;
    654 		case hid_endcollection:
    655 			if (collind == 0)
    656 				errx(1, "Excessive collection ends");
    657 			collind--;
    658 			break;
    659 		case hid_input:
    660 			repindex = REPORT_INPUT;
    661 			break;
    662 		case hid_output:
    663 			repindex = REPORT_OUTPUT;
    664 			break;
    665 		case hid_feature:
    666 			repindex = REPORT_FEATURE;
    667 			break;
    668 		}
    669 
    670 		matchvar = hidmatch(colls, collind, &hitem, varlist, vlsize);
    671 
    672 		if (matchvar != NULL) {
    673 			u_char *bufdata;
    674 			struct Sreport *repptr;
    675 
    676 			matchvar->mflags |= MATCH_WASMATCHED;
    677 
    678 			if (repindex >= 0)
    679 				repptr = &reports[repindex];
    680 			else
    681 				repptr = NULL;
    682 
    683 			if (repptr != NULL &&
    684 			    !(matchvar->mflags & MATCH_NODATA))
    685 				getreport(repptr, hidfd, rd, repindex);
    686 
    687 			bufdata = (repptr == NULL || repptr->buffer == NULL) ?
    688 				NULL : repptr->buffer->data;
    689 
    690 			if (matchvar->opfunc(&hitem, matchvar, colls, collind,
    691 					     bufdata))
    692 				repptr->status = srs_dirty;
    693 		}
    694 	}
    695 	hid_end_parse(hdata);
    696 
    697 	for (repind = 0; repind < (sizeof(reports) / sizeof(*reports));
    698 	     repind++) {
    699 		setreport(&reports[repind], hidfd, repind);
    700 		freereport(&reports[repind]);
    701 	}
    702 
    703 	/* Warn about any items that we couldn't find a match for */
    704 	for (vlind = 0; vlind < vlsize; vlind++) {
    705 		struct Susbvar *var;
    706 
    707 		var = &varlist[vlind];
    708 
    709 		if (var->variable != NULL &&
    710 		    !(var->mflags & MATCH_WASMATCHED))
    711 			warnx("Failed to match: %.*s", (int)var->varlen,
    712 			      var->variable);
    713 	}
    714 }
    715 
    716 static void
    717 usage(void)
    718 {
    719 	const char *progname = getprogname();
    720 
    721 	fprintf(stderr, "Usage: %s -f device [-t tablefile] [-l] [-v] -a\n",
    722 	    progname);
    723 	fprintf(stderr, "       %s -f device [-t tablefile] [-v] -r\n",
    724 	    progname);
    725 	fprintf(stderr,
    726 	    "       %s -f device [-t tablefile] [-l] [-n] [-v] name ...\n",
    727 	    progname);
    728 	fprintf(stderr,
    729 	    "       %s -f device [-t tablefile] -w name=value ...\n",
    730 	    progname);
    731 	exit(1);
    732 }
    733 
    734 int
    735 main(int argc, char **argv)
    736 {
    737 	char const *dev;
    738 	char const *table;
    739 	size_t varnum;
    740 	int aflag, lflag, nflag, rflag, wflag;
    741 	int ch, hidfd;
    742 	report_desc_t repdesc;
    743 	char devnamebuf[PATH_MAX];
    744 	struct Susbvar variables[128];
    745 
    746 	/*
    747 	 * Zero if not in a verbose mode.  Greater levels of verbosity
    748 	 * are indicated by values larger than one.
    749 	 */
    750 	unsigned int verbose;
    751 
    752 	wflag = aflag = nflag = verbose = rflag = lflag = 0;
    753 	dev = NULL;
    754 	table = NULL;
    755 	while ((ch = getopt(argc, argv, "?af:lnrt:vw")) != -1) {
    756 		switch (ch) {
    757 		case 'a':
    758 			aflag = 1;
    759 			break;
    760 		case 'f':
    761 			dev = optarg;
    762 			break;
    763 		case 'l':
    764 			lflag = 1;
    765 			break;
    766 		case 'n':
    767 			nflag = 1;
    768 			break;
    769 		case 'r':
    770 			rflag = 1;
    771 			break;
    772 		case 't':
    773 			table = optarg;
    774 			break;
    775 		case 'v':
    776 			verbose++;
    777 			break;
    778 		case 'w':
    779 			wflag = 1;
    780 			break;
    781 		case '?':
    782 		default:
    783 			usage();
    784 			/* NOTREACHED */
    785 		}
    786 	}
    787 	argc -= optind;
    788 	argv += optind;
    789 	if (dev == NULL || (lflag && (wflag || rflag))) {
    790 		/*
    791 		 * No device specified, or attempting to loop and set
    792 		 * or dump report at the same time
    793 		 */
    794 		usage();
    795 		/* NOTREACHED */
    796 	}
    797 
    798 	for (varnum = 0; varnum < (size_t)argc; varnum++) {
    799 		char const *name, *valuesep;
    800 		struct Susbvar *svar;
    801 
    802 		svar = &variables[varnum];
    803 		name = argv[varnum];
    804 		valuesep = strchr(name, DELIM_SET);
    805 
    806 		svar->variable = name;
    807 		svar->mflags = 0;
    808 
    809 		if (valuesep == NULL) {
    810 			/* Read variable */
    811 			if (wflag)
    812 				errx(1, "Must not specify -w to read variables");
    813 			svar->value = NULL;
    814 			svar->varlen = strlen(name);
    815 
    816 			if (nflag) {
    817 				/* Display value of variable only */
    818 				svar->opfunc = varop_value;
    819 			} else {
    820 				/* Display name and value of variable */
    821 				svar->opfunc = varop_display;
    822 
    823 				if (verbose >= 1)
    824 					/* Show page names in verbose modes */
    825 					svar->mflags |= MATCH_SHOWPAGENAME;
    826 			}
    827 		} else {
    828 			/* Write variable */
    829 			if (!wflag)
    830 				errx(2, "Must specify -w to set variables");
    831 			svar->mflags |= MATCH_WRITABLE;
    832 			if (verbose >= 1)
    833 				/*
    834 				 * Allow displaying of set value in
    835 				 * verbose mode.  This isn't
    836 				 * particularly useful though, so
    837 				 * don't bother documenting it.
    838 				 */
    839 				svar->mflags |= MATCH_SHOWVALUES;
    840 			svar->varlen = valuesep - name;
    841 			svar->value = valuesep + 1;
    842 			svar->opfunc = varop_modify;
    843 		}
    844 	}
    845 
    846 	if (aflag || rflag) {
    847 		struct Susbvar *svar;
    848 
    849 		svar = &variables[varnum++];
    850 
    851 		svar->variable = NULL;
    852 		svar->mflags = MATCH_ALL;
    853 
    854 		if (rflag) {
    855 			/*
    856 			 * Dump report descriptor.  Do dump collection
    857 			 * items also, and hint that it won't be
    858 			 * necessary to get the item status.
    859 			 */
    860 			svar->opfunc = varop_report;
    861 			svar->mflags |= MATCH_COLLECTIONS | MATCH_NODATA;
    862 
    863 			switch (verbose) {
    864 			default:
    865 				/* Level 2: Show item numerics and constants */
    866 				svar->mflags |= MATCH_SHOWNUMERIC;
    867 				/* FALLTHROUGH */
    868 			case 1:
    869 				/* Level 1: Just show constants */
    870 				svar->mflags |= MATCH_CONSTANTS;
    871 				/* FALLTHROUGH */
    872 			case 0:
    873 				break;
    874 			}
    875 		} else {
    876 			/* Display name and value of variable */
    877 			svar->opfunc = varop_display;
    878 
    879 			switch (verbose) {
    880 			default:
    881 				/* Level 2: Show constants and page names */
    882 				svar->mflags |= MATCH_CONSTANTS;
    883 				/* FALLTHROUGH */
    884 			case 1:
    885 				/* Level 1: Just show page names */
    886 				svar->mflags |= MATCH_SHOWPAGENAME;
    887 				/* FALLTHROUGH */
    888 			case 0:
    889 				break;
    890 			}
    891 		}
    892 	}
    893 
    894 	if (varnum == 0) {
    895 		/* Nothing to do...  Display usage information. */
    896 		usage();
    897 		/* NOTREACHED */
    898 	}
    899 
    900 	hid_init(table);
    901 
    902 	if (dev[0] != '/') {
    903 		snprintf(devnamebuf, sizeof(devnamebuf), "/dev/%s%s",
    904 			 isdigit(dev[0]) ? "uhid" : "", dev);
    905 		dev = devnamebuf;
    906 	}
    907 
    908 	hidfd = open(dev, O_RDWR);
    909 	if (hidfd < 0)
    910 		err(1, "%s", dev);
    911 
    912 	repdesc = hid_get_report_desc(hidfd);
    913 	if (repdesc == 0)
    914 		errx(1, "USB_GET_REPORT_DESC");
    915 
    916 	if (lflag) {
    917 		devloop(hidfd, repdesc, variables, varnum);
    918 		/* NOTREACHED */
    919 	}
    920 
    921 	if (rflag)
    922 		/* Report mode header */
    923 		printf("Report descriptor:\n");
    924 
    925 	devshow(hidfd, repdesc, variables, varnum,
    926 		1 << hid_input |
    927 		1 << hid_output |
    928 		1 << hid_feature);
    929 
    930 #if 0
    931 	{
    932 		size_t repindex;
    933 		for (repindex = 0;
    934 		     repindex < (sizeof(reptoparam) / sizeof(*reptoparam));
    935 		     repindex++)
    936 			devshow(hidfd, repdesc, variables, varnum,
    937 				1 << reptoparam[repindex].hid_kind);
    938 	}
    939 #endif
    940 
    941 	if (rflag) {
    942 		/* Report mode trailer */
    943 		size_t repindex;
    944 		for (repindex = 0;
    945 		     repindex < (sizeof(reptoparam) / sizeof(*reptoparam));
    946 		     repindex++) {
    947 			int report_id, size;
    948 			size = hid_report_size(repdesc,
    949 					       reptoparam[repindex].hid_kind,
    950 					       &report_id);
    951 			size -= report_id != 0;
    952 			printf("Total %7s size %s%d bytes\n",
    953 			       reptoparam[repindex].name,
    954 			       report_id && size ? "1+" : "", size);
    955 		}
    956 	}
    957 
    958 	hid_dispose_report_desc(repdesc);
    959 	exit(0);
    960 	/* NOTREACHED */
    961 }
    962