=================================================================== RCS file: /cvsrepo/anoncvs/cvs/src/usr.bin/usbhidctl/usbhid.c,v retrieving revision 1.3 retrieving revision 1.4 diff -u -r1.3 -r1.4 --- src/usr.bin/usbhidctl/usbhid.c 2002/05/02 20:12:07 1.3 +++ src/usr.bin/usbhidctl/usbhid.c 2002/05/10 00:09:17 1.4 @@ -1,8 +1,8 @@ -/* $OpenBSD: usbhid.c,v 1.3 2002/05/02 20:12:07 nate Exp $ */ -/* $NetBSD: usbhid.c,v 1.17 2001/03/28 03:17:42 simonb Exp $ */ +/* $OpenBSD: usbhid.c,v 1.4 2002/05/10 00:09:17 nate Exp $ */ +/* $NetBSD: usbhid.c,v 1.22 2002/02/20 20:30:42 christos Exp $ */ /* - * Copyright (c) 2000 The NetBSD Foundation, Inc. + * Copyright (c) 2001 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation @@ -53,14 +53,18 @@ #include #include +/* + * Zero if not in a verbose mode. Greater levels of verbosity + * are indicated by values larger than one. + */ +unsigned int verbose; + /* Parser tokens */ #define DELIM_USAGE '.' #define DELIM_PAGE ':' #define DELIM_SET '=' -/* Zero if not in a verbose mode. Greater levels of verbosity are - indicated by values larger than one. */ -static unsigned int verbose; +static int reportid; struct Susbvar { /* Variable name, not NUL terminated */ @@ -77,6 +81,7 @@ #define MATCH_SHOWPAGENAME (1 << 5) #define MATCH_SHOWNUMERIC (1 << 6) #define MATCH_WRITABLE (1 << 7) +#define MATCH_SHOWVALUES (1 << 8) unsigned int mflags; /* Workspace for hidmatch() */ @@ -108,11 +113,183 @@ #define REPORT_MAXVAL 2 }; +/* + * Extract 16-bit unsigned usage ID from a numeric string. Returns -1 + * if string failed to parse correctly. + */ +static int +strtousage(const char *nptr, size_t nlen) +{ + char *endptr; + long result; + char numstr[16]; + + if (nlen >= sizeof(numstr) || !isdigit((unsigned char)*nptr)) + return -1; + + /* + * We use strtol() here, but unfortunately strtol() requires a + * NUL terminated string - which we don't have - at least not + * officially. + */ + memcpy(numstr, nptr, nlen); + numstr[nlen] = '\0'; + + result = strtol(numstr, &endptr, 0); + + if (result < 0 || result > 0xffff || endptr != &numstr[nlen]) + return -1; + + return result; +} + +struct usagedata { + char const *page_name; + char const *usage_name; + size_t page_len; + size_t usage_len; + int isfinal; + u_int32_t usage_id; +}; + +/* + * Test a rule against the current usage data. Returns -1 on no + * match, 0 on partial match and 1 on complete match. + */ +static int +hidtestrule(struct Susbvar *var, struct usagedata *cache) +{ + char const *varname; + ssize_t matchindex, pagesplit; + size_t strind, varlen; + int numusage; + u_int32_t usage_id; + + matchindex = var->matchindex; + varname = var->variable; + varlen = var->varlen; + + usage_id = cache->usage_id; + + /* + * Parse the current variable name, locating the end of the + * current 'usage', and possibly where the usage page name + * ends. + */ + pagesplit = -1; + for (strind = matchindex; strind < varlen; strind++) { + if (varname[strind] == DELIM_USAGE) + break; + if (varname[strind] == DELIM_PAGE) + pagesplit = strind; + } + + if (cache->isfinal && strind != varlen) + /* + * Variable name is too long (hit delimiter instead of + * end-of-variable). + */ + return -1; + + if (pagesplit >= 0) { + /* + * Page name was specified, determine whether it was + * symbolic or numeric. + */ + char const *strstart; + int numpage; + + strstart = &varname[matchindex]; + + numpage = strtousage(strstart, pagesplit - matchindex); + + if (numpage >= 0) { + /* Valid numeric */ + + if (numpage != HID_PAGE(usage_id)) + /* Numeric didn't match page ID */ + return -1; + } else { + /* Not a valid numeric */ + + /* + * Load and cache the page name if and only if + * it hasn't already been loaded (it's a + * fairly expensive operation). + */ + if (cache->page_name == NULL) { + cache->page_name = hid_usage_page(HID_PAGE(usage_id)); + cache->page_len = strlen(cache->page_name); + } + + /* + * Compare specified page name to actual page + * name. + */ + if (cache->page_len != + (size_t)(pagesplit - matchindex) || + memcmp(cache->page_name, + &varname[matchindex], + cache->page_len) != 0) + /* Mismatch, page name wrong */ + return -1; + } + + /* Page matches, discard page name */ + matchindex = pagesplit + 1; + } + + numusage = strtousage(&varname[matchindex], strind - matchindex); + + if (numusage >= 0) { + /* Valid numeric */ + + if (numusage != HID_USAGE(usage_id)) + /* Numeric didn't match usage ID */ + return -1; + } else { + /* Not a valid numeric */ + + /* Load and cache the usage name */ + if (cache->usage_name == NULL) { + cache->usage_name = hid_usage_in_page(usage_id); + cache->usage_len = strlen(cache->usage_name); + } + + /* + * Compare specified usage name to actual usage name + */ + if (cache->usage_len != (size_t)(strind - matchindex) || + memcmp(cache->usage_name, &varname[matchindex], + cache->usage_len) != 0) + /* Mismatch, usage name wrong */ + return -1; + } + + if (cache->isfinal) + /* Match */ + return 1; + + /* + * Partial match: Move index past this usage string + + * delimiter + */ + var->matchindex = strind + 1; + + return 0; +} + +/* + * hidmatch() determines whether the item specified in 'item', and + * nested within a heirarchy of collections specified in 'collist' + * matches any of the rules in the list 'varlist'. Returns the + * matching rule on success, or NULL on no match. + */ static struct Susbvar* hidmatch(u_int32_t const *collist, size_t collen, struct hid_item *item, struct Susbvar *varlist, size_t vlsize) { - size_t vlind, colind, vlactive; + size_t colind, vlactive, vlind; int iscollection; /* @@ -161,96 +338,52 @@ } } + /* + * Loop through each usage in the collection list, including + * the 'item' itself on the final iteration. For each usage, + * test which variables named in the rule list are still + * applicable - if any. + */ for (colind = 0; vlactive > 0 && colind <= collen; colind++) { - char const *usage_name, *page_name; - size_t usage_len, page_len; - int final; - u_int32_t usage_id; + struct usagedata cache; - final = (colind == collen); - - if (final) - usage_id = item->usage; + cache.isfinal = (colind == collen); + if (cache.isfinal) + cache.usage_id = item->usage; else - usage_id = collist[colind]; + cache.usage_id = collist[colind]; - usage_name = hid_usage_in_page(usage_id); - usage_len = strlen(usage_name); + cache.usage_name = NULL; + cache.page_name = NULL; - page_name = NULL; - + /* + * Loop through each rule, testing whether the rule is + * still applicable or not. For each rule, + * 'matchindex' retains the current match state as an + * index into the variable name string, or -1 if this + * rule has been proven not to match. + */ for (vlind = 0; vlind < vlsize; vlind++) { - ssize_t matchindex, pagesplit; - size_t varlen, strind; - char const *varname; struct Susbvar *var; + int matchres; var = &varlist[vlind]; - matchindex = var->matchindex; - varname = var->variable; - varlen = var->varlen; - - if (matchindex < 0) + if (var->matchindex < 0) /* Mismatch at a previous level */ continue; - pagesplit = -1; - for (strind = matchindex; strind < varlen; strind++) { - if (varname[strind] == DELIM_USAGE) - break; - if (varname[strind] == DELIM_PAGE) - pagesplit = strind; - } + matchres = hidtestrule(var, &cache); - if (final && strind != varlen) { - /* - * Variable name is too long (hit - * delimiter instead of - * end-of-variable) - */ + if (matchres < 0) { + /* Bad match */ var->matchindex = -1; vlactive--; continue; - } - - if (pagesplit >= 0) { - if (page_name == NULL) { - page_name = hid_usage_page(HID_PAGE(usage_id)); - page_len = strlen(page_name); - } - if (page_len != - (size_t)(pagesplit - matchindex) || - memcmp(page_name, &varname[matchindex], - page_len) != 0) { - /* Mismatch, page name wrong */ - var->matchindex = -1; - vlactive--; - continue; - } - - /* Page matches, discard page name */ - matchindex = pagesplit + 1; - } - - if (usage_len != strind - matchindex || - memcmp(usage_name, &varname[matchindex], - usage_len) != 0) { - /* Mismatch, usage name wrong */ - var->matchindex = -1; - vlactive--; - continue; - } - - if (final) - /* Match */ + } else if (matchres > 0) { + /* Complete match */ return var; - - /* - * Partial match: Move index past this usage - * string + delimiter - */ - var->matchindex = matchindex + usage_len + 1; + } } } @@ -262,8 +395,7 @@ { int reptsize; - reptsize = hid_report_size(rd, reptoparam[repindex].hid_kind, - &report->report_id); + reptsize = hid_report_size(rd, reptoparam[repindex].hid_kind, reportid); if (reptsize < 0) errx(1, "Negative report size"); report->size = reptsize; @@ -302,7 +434,8 @@ report->buffer->ucr_report = reptoparam[repindex].uhid_report; if (ioctl(hidfd, USB_GET_REPORT, report->buffer) < 0) - err(1, "USB_GET_REPORT"); + err(1, "USB_GET_REPORT (probably not supported by " + "device)"); } } @@ -335,19 +468,30 @@ u_int32_t const *collist, size_t collen, u_char *buf) { size_t colitem; + int val, i; - for (colitem = 0; colitem < collen; colitem++) { + for (i = 0; i < item->report_count; i++) { + for (colitem = 0; colitem < collen; colitem++) { + if (var->mflags & MATCH_SHOWPAGENAME) + printf("%s:", + hid_usage_page(HID_PAGE(collist[colitem]))); + printf("%s.", hid_usage_in_page(collist[colitem])); + } if (var->mflags & MATCH_SHOWPAGENAME) - printf("%s:", - hid_usage_page(HID_PAGE(collist[colitem]))); - printf("%s.", hid_usage_in_page(collist[colitem])); + printf("%s:", hid_usage_page(HID_PAGE(item->usage))); + val = hid_get_data(buf, item); + item->pos += item->report_size; + if (item->usage_minimum != 0 || item->usage_maximum != 0) { + val += item->usage_minimum; + printf("%s=1", hid_usage_in_page(val)); + } else { + printf("%s=%d%s", hid_usage_in_page(item->usage), + val, item->flags & HIO_CONST ? " (const)" : ""); + } + if (item->report_count > 1) + printf(" [%d]", i); + printf("\n"); } - - if (var->mflags & MATCH_SHOWPAGENAME) - printf("%s:", hid_usage_page(HID_PAGE(item->usage))); - printf("%s=%d%s\n", hid_usage_in_page(item->usage), - hid_get_data(buf, item), - (item->flags & HIO_CONST) ? " (const)" : ""); return 0; } @@ -362,12 +506,8 @@ hid_set_data(buf, item, dataval); - if (verbose >= 1) - /* - * Allow displaying of set value in verbose mode. - * This isn't particularly useful though, so don't - * bother documenting it. - */ + if (var->mflags & MATCH_SHOWVALUES) + /* Display set value */ varop_display(item, var, collist, collen, buf); return 1; @@ -376,14 +516,28 @@ static void reportitem(char const *label, struct hid_item const *item, unsigned int mflags) { - printf("%s size=%d count=%d page=%s usage=%s%s", label, + int isconst = item->flags & HIO_CONST, + isvar = item->flags & HIO_VARIABLE; + printf("%s size=%d count=%d%s%s page=%s", label, item->report_size, item->report_count, - hid_usage_page(HID_PAGE(item->usage)), - hid_usage_in_page(item->usage), - item->flags & HIO_CONST ? " Const" : ""); - if (mflags & MATCH_SHOWNUMERIC) - printf(" (%u/0x%x)", - HID_PAGE(item->usage), HID_USAGE(item->usage)); + isconst ? " Const" : "", + !isvar && !isconst ? " Array" : "", + hid_usage_page(HID_PAGE(item->usage))); + if (item->usage_minimum != 0 || item->usage_maximum != 0) { + printf(" usage=%s..%s", hid_usage_in_page(item->usage_minimum), + hid_usage_in_page(item->usage_maximum)); + if (mflags & MATCH_SHOWNUMERIC) + printf(" (%u:0x%x..%u:0x%x)", + HID_PAGE(item->usage_minimum), + HID_USAGE(item->usage_minimum), + HID_PAGE(item->usage_maximum), + HID_USAGE(item->usage_maximum)); + } else { + printf(" usage=%s", hid_usage_in_page(item->usage)); + if (mflags & MATCH_SHOWNUMERIC) + printf(" (%u:0x%x)", + HID_PAGE(item->usage), HID_USAGE(item->usage)); + } printf(", logical range %d..%d", item->logical_minimum, item->logical_maximum); if (item->physical_minimum != item->physical_maximum) @@ -402,9 +556,14 @@ { switch (item->kind) { case hid_collection: - printf("Collection page=%s usage=%s\n", + printf("Collection page=%s usage=%s", hid_usage_page(HID_PAGE(item->usage)), hid_usage_in_page(item->usage)); + if (var->mflags & MATCH_SHOWNUMERIC) + printf(" (%u:0x%x)\n", + HID_PAGE(item->usage), HID_USAGE(item->usage)); + else + printf("\n"); break; case hid_endcollection: printf("End collection\n"); @@ -426,13 +585,12 @@ static void devloop(int hidfd, report_desc_t rd, struct Susbvar *varlist, size_t vlsize) { + u_char *dbuf; struct hid_data *hdata; + size_t collind, dlen; struct hid_item hitem; u_int32_t colls[128]; struct Sreport inreport; - size_t dlen; - u_char *dbuf; - size_t collind; allocreport(&inreport, rd, REPORT_INPUT); @@ -446,12 +604,14 @@ ssize_t readlen; readlen = read(hidfd, dbuf, dlen); - if (readlen < 0 || dlen != (size_t)readlen) - err(1, "bad read %ld != %ld", - (long)readlen, (long)dlen); + if (readlen < 0) + err(1, "Device read error"); + if (dlen != (size_t)readlen) + errx(1, "Unexpected response length: %lu != %lu", + (unsigned long)readlen, (unsigned long)dlen); collind = 0; - hdata = hid_start_parse(rd, 1 << hid_input); + hdata = hid_start_parse(rd, 1 << hid_input, reportid); if (hdata == NULL) errx(1, "Failed to start parser"); @@ -476,6 +636,9 @@ errx(1, "Unexpected non-input item returned"); } + if (reportid != -1 && hitem.report_ID != reportid) + continue; + matchvar = hidmatch(colls, collind, &hitem, varlist, vlsize); @@ -495,10 +658,9 @@ int kindset) { struct hid_data *hdata; + size_t collind, repind, vlind; struct hid_item hitem; u_int32_t colls[128]; - size_t collind, repind, vlind; - struct Sreport reports[REPORT_MAXVAL + 1]; @@ -509,9 +671,7 @@ } collind = 0; - hdata = hid_start_parse(rd, kindset | - (1 << hid_collection) | - (1 << hid_endcollection)); + hdata = hid_start_parse(rd, kindset, reportid); if (hdata == NULL) errx(1, "Failed to start parser"); @@ -519,6 +679,9 @@ struct Susbvar *matchvar; int repindex; + if (verbose > 3) + printf("item: kind=%d repid=%d usage=0x%x\n", + hitem.kind, hitem.report_ID, hitem.usage); repindex = -1; switch (hitem.kind) { case hid_collection: @@ -542,6 +705,9 @@ break; } + if (reportid != -1 && hitem.report_ID != reportid) + continue; + matchvar = hidmatch(colls, collind, &hitem, varlist, vlsize); if (matchvar != NULL) { @@ -609,13 +775,13 @@ int main(int argc, char **argv) { - int hidfd; - report_desc_t repdesc; - char devnamebuf[PATH_MAX]; char const *dev; - int ch, wflag, aflag, nflag, rflag, lflag; - size_t varnum; char const *table; + size_t varnum; + int aflag, lflag, nflag, rflag, wflag; + int ch, hidfd; + report_desc_t repdesc; + char devnamebuf[PATH_MAX]; struct Susbvar variables[128]; wflag = aflag = nflag = verbose = rflag = lflag = 0; @@ -698,6 +864,14 @@ if (!wflag) errx(2, "Must specify -w to set variables"); svar->mflags |= MATCH_WRITABLE; + if (verbose >= 1) + /* + * Allow displaying of set value in + * verbose mode. This isn't + * particularly useful though, so + * don't bother documenting it. + */ + svar->mflags |= MATCH_SHOWVALUES; svar->varlen = valuesep - name; svar->value = valuesep + 1; svar->opfunc = varop_modify; @@ -770,6 +944,10 @@ if (hidfd < 0) err(1, "%s", dev); + if (ioctl(hidfd, USB_GET_REPORT_ID, &reportid) < 0) + reportid = -1; + if (verbose > 1) + printf("report ID=%d\n", reportid); repdesc = hid_get_report_desc(hidfd); if (repdesc == 0) errx(1, "USB_GET_REPORT_DESC"); @@ -788,31 +966,18 @@ 1 << hid_output | 1 << hid_feature); -#if 0 - { - size_t repindex; - for (repindex = 0; - repindex < (sizeof(reptoparam) / sizeof(*reptoparam)); - repindex++) - devshow(hidfd, repdesc, variables, varnum, - 1 << reptoparam[repindex].hid_kind); - } -#endif - if (rflag) { /* Report mode trailer */ size_t repindex; for (repindex = 0; repindex < (sizeof(reptoparam) / sizeof(*reptoparam)); repindex++) { - int report_id, size; + int size; size = hid_report_size(repdesc, reptoparam[repindex].hid_kind, - &report_id); - size -= report_id != 0; - printf("Total %7s size %s%d bytes\n", - reptoparam[repindex].name, - report_id && size ? "1+" : "", size); + reportid); + printf("Total %7s size %d bytes\n", + reptoparam[repindex].name, size); } }