1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 /* 22 * Copyright 2008 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 * Copyright 2019, Joyent, Inc. 25 */ 26 27 28 #define USBA_FRAMEWORK 29 #include <sys/ksynch.h> 30 #include <sys/strsun.h> 31 #include <sys/usb/usba/usba_impl.h> 32 #include <sys/usb/usba/usba_devdb_impl.h> 33 34 static usb_log_handle_t usba_devdb_log_handle; 35 uint_t usba_devdb_errlevel = USB_LOG_L4; 36 uint_t usba_devdb_errmask = (uint_t)-1; 37 38 boolean_t usba_build_devdb = B_FALSE; 39 40 avl_tree_t usba_devdb; /* tree of records */ 41 static krwlock_t usba_devdb_lock; /* lock protecting the tree */ 42 43 _NOTE(RWLOCK_PROTECTS_DATA(usba_devdb_lock, usba_devdb)) 44 45 /* 46 * Reader Writer locks have problem with warlock. warlock is unable to 47 * decode that the structure is local and doesn't need locking 48 */ 49 _NOTE(SCHEME_PROTECTS_DATA("unshared", usba_devdb_info)) 50 _NOTE(SCHEME_PROTECTS_DATA("unshared", usba_configrec)) 51 52 /* function prototypes */ 53 static int usb_devdb_compare_pathnames(char *, char *); 54 static int usba_devdb_compare(const void *, const void *); 55 static int usba_devdb_build_device_database(); 56 static void usba_devdb_destroy_device_database(); 57 58 /* 59 * usba_devdb_initialization 60 * Initialize this module that builds the usb device database 61 */ 62 void 63 usba_devdb_initialization() 64 { 65 usba_devdb_log_handle = usb_alloc_log_hdl(NULL, "devdb", 66 &usba_devdb_errlevel, &usba_devdb_errmask, NULL, 0); 67 68 USB_DPRINTF_L4(DPRINT_MASK_DEVDB, usba_devdb_log_handle, 69 "usba_devdb_initialization"); 70 71 rw_init(&usba_devdb_lock, NULL, RW_DRIVER, NULL); 72 73 rw_enter(&usba_devdb_lock, RW_WRITER); 74 75 usba_build_devdb = B_TRUE; 76 77 /* now create the avl tree */ 78 avl_create(&usba_devdb, usba_devdb_compare, 79 sizeof (usba_devdb_info_t), 80 offsetof(struct usba_devdb_info, avl_link)); 81 82 (void) usba_devdb_build_device_database(); 83 84 usba_build_devdb = B_FALSE; 85 86 rw_exit(&usba_devdb_lock); 87 } 88 89 90 /* 91 * usba_devdb_destroy 92 * Free up all the resources being used by this module 93 */ 94 void 95 usba_devdb_destroy() 96 { 97 USB_DPRINTF_L4(DPRINT_MASK_DEVDB, usba_devdb_log_handle, 98 "usba_devdb_destroy"); 99 100 rw_enter(&usba_devdb_lock, RW_WRITER); 101 102 usba_devdb_destroy_device_database(); 103 104 rw_exit(&usba_devdb_lock); 105 106 rw_destroy(&usba_devdb_lock); 107 108 usb_free_log_hdl(usba_devdb_log_handle); 109 } 110 111 112 /* 113 * usba_devdb_get_var_type: 114 * returns the field from the token 115 */ 116 static config_field_t 117 usba_devdb_get_var_type(char *str) 118 { 119 usba_cfg_var_t *cfgvar; 120 121 cfgvar = &usba_cfg_varlist[0]; 122 while (cfgvar->field != USB_NONE) { 123 if (strcasecmp(cfgvar->name, str) == 0) { 124 break; 125 } else { 126 cfgvar++; 127 } 128 } 129 130 return (cfgvar->field); 131 } 132 133 134 /* 135 * usba_devdb_get_conf_rec: 136 * Fetch one record from the file 137 */ 138 static token_t 139 usba_devdb_get_conf_rec(struct _buf *file, usba_configrec_t **rec) 140 { 141 token_t token; 142 char tokval[MAXPATHLEN]; 143 usba_configrec_t *cfgrec; 144 config_field_t cfgvar = USB_NONE; 145 u_longlong_t llptr; 146 u_longlong_t value; 147 enum { 148 USB_NEWVAR, USB_CONFIG_VAR, USB_VAR_EQUAL, USB_VAR_VALUE, 149 USB_ERROR 150 } parse_state = USB_NEWVAR; 151 152 cfgrec = (usba_configrec_t *)kmem_zalloc( 153 sizeof (usba_configrec_t), KM_SLEEP); 154 cfgrec->idVendor = cfgrec->idProduct = cfgrec->cfg_index = -1; 155 156 token = kobj_lex(file, tokval, sizeof (tokval)); 157 while ((token != EOF) && (token != SEMICOLON)) { 158 switch (token) { 159 case STAR: 160 case POUND: 161 /* skip comments */ 162 kobj_find_eol(file); 163 break; 164 case NEWLINE: 165 kobj_newline(file); 166 break; 167 case NAME: 168 case STRING: 169 switch (parse_state) { 170 case USB_NEWVAR: 171 cfgvar = usba_devdb_get_var_type(tokval); 172 if (cfgvar == USB_NONE) { 173 parse_state = USB_ERROR; 174 kobj_file_err(CE_WARN, file, 175 "Syntax Error: Invalid field %s", 176 tokval); 177 } else { 178 parse_state = USB_CONFIG_VAR; 179 } 180 break; 181 case USB_VAR_VALUE: 182 if ((cfgvar == USB_VENDOR) || 183 (cfgvar == USB_PRODUCT) || 184 (cfgvar == USB_CFGNDX)) { 185 parse_state = USB_ERROR; 186 kobj_file_err(CE_WARN, file, 187 "Syntax Error: Invalid value %s" 188 " for field: %s\n", tokval, 189 usba_cfg_varlist[cfgvar].name); 190 } else if (kobj_get_string(&llptr, tokval)) { 191 switch (cfgvar) { 192 case USB_SELECTION: 193 cfgrec->selection = 194 (char *)(uintptr_t)llptr; 195 parse_state = USB_NEWVAR; 196 break; 197 case USB_SRNO: 198 cfgrec->serialno = 199 (char *)(uintptr_t)llptr; 200 parse_state = USB_NEWVAR; 201 break; 202 case USB_PATH: 203 cfgrec->pathname = 204 (char *)(uintptr_t)llptr; 205 parse_state = USB_NEWVAR; 206 break; 207 case USB_DRIVER: 208 cfgrec->driver = 209 (char *)(uintptr_t)llptr; 210 parse_state = USB_NEWVAR; 211 break; 212 default: 213 parse_state = USB_ERROR; 214 } 215 } else { 216 parse_state = USB_ERROR; 217 kobj_file_err(CE_WARN, file, 218 "Syntax Error: Invalid value %s" 219 " for field: %s\n", tokval, 220 usba_cfg_varlist[cfgvar].name); 221 } 222 break; 223 case USB_ERROR: 224 /* just skip */ 225 break; 226 default: 227 parse_state = USB_ERROR; 228 kobj_file_err(CE_WARN, file, 229 "Syntax Error: at %s", tokval); 230 break; 231 } 232 break; 233 case EQUALS: 234 if (parse_state == USB_CONFIG_VAR) { 235 if (cfgvar == USB_NONE) { 236 parse_state = USB_ERROR; 237 kobj_file_err(CE_WARN, file, 238 "Syntax Error: unexpected '='"); 239 } else { 240 parse_state = USB_VAR_VALUE; 241 } 242 } else if (parse_state != USB_ERROR) { 243 kobj_file_err(CE_WARN, file, 244 "Syntax Error: unexpected '='"); 245 parse_state = USB_ERROR; 246 } 247 break; 248 case HEXVAL: 249 case DECVAL: 250 if ((parse_state == USB_VAR_VALUE) && (cfgvar != 251 USB_NONE)) { 252 (void) kobj_getvalue(tokval, &value); 253 switch (cfgvar) { 254 case USB_VENDOR: 255 cfgrec->idVendor = (int)value; 256 parse_state = USB_NEWVAR; 257 break; 258 case USB_PRODUCT: 259 cfgrec->idProduct = (int)value; 260 parse_state = USB_NEWVAR; 261 break; 262 case USB_CFGNDX: 263 cfgrec->cfg_index = (int)value; 264 parse_state = USB_NEWVAR; 265 break; 266 default: 267 kobj_file_err(CE_WARN, file, 268 "Syntax Error: Invalid value for " 269 "%s", 270 usba_cfg_varlist[cfgvar].name); 271 } 272 } else if (parse_state != USB_ERROR) { 273 parse_state = USB_ERROR; 274 kobj_file_err(CE_WARN, file, "Syntax Error:" 275 "unexpected hex/decimal: %s", tokval); 276 } 277 break; 278 default: 279 kobj_file_err(CE_WARN, file, "Syntax Error: at: %s", 280 tokval); 281 parse_state = USB_ERROR; 282 break; 283 } 284 token = kobj_lex(file, tokval, sizeof (tokval)); 285 } 286 *rec = cfgrec; 287 288 return (token); 289 } 290 291 292 /* 293 * usba_devdb_free_rec: 294 * Free the record allocated in usba_devdb_get_conf_rec. 295 * We use kobj_free_string as kobj_get_string allocates memory 296 * in mod_sysfile_arena. 297 */ 298 static void 299 usba_devdb_free_rec(usba_configrec_t *rec) 300 { 301 if (rec->selection) { 302 kobj_free_string(rec->selection, strlen(rec->selection) + 1); 303 } 304 if (rec->serialno) { 305 kobj_free_string(rec->serialno, strlen(rec->serialno) + 1); 306 } 307 if (rec->pathname) { 308 kobj_free_string(rec->pathname, strlen(rec->pathname) + 1); 309 } 310 if (rec->driver) { 311 kobj_free_string(rec->driver, strlen(rec->driver) + 1); 312 } 313 kmem_free(rec, sizeof (usba_configrec_t)); 314 } 315 316 317 318 /* 319 * usb_devdb_compare_pathnames: 320 * Compare the two pathnames. If we are building the tree, we do a 321 * straight string compare to enable correct tree generation. If we 322 * are searching for a matching node, we compare only the selected 323 * portion of the pathname to give a correct match. 324 */ 325 static int 326 usb_devdb_compare_pathnames(char *p1, char *p2) 327 { 328 int rval; 329 char *ustr, *hstr; 330 331 USB_DPRINTF_L4(DPRINT_MASK_DEVDB, usba_devdb_log_handle, 332 "usb_devdb_compare_pathnames: p1=0x%p p2=0x%p", 333 (void *)p1, (void *)p2); 334 335 if (p1 && p2) { 336 if (usba_build_devdb == B_TRUE) { 337 /* this is a straight string compare */ 338 rval = strcmp(p1, p2); 339 if (rval < 0) { 340 341 return (-1); 342 } else if (rval > 0) { 343 344 return (+1); 345 } else { 346 347 return (0); 348 } 349 } else { 350 /* 351 * Comparing on this is tricky. 352 * p1 is the string hubd is looking for & 353 * p2 is the string in the device db. 354 * At this point hubd knows: ../hubd@P/device@P 355 * while user will specify ..../hubd@P/keyboard@P 356 * First compare till .../hubd@P 357 * Second compare is just P in "device@P" 358 */ 359 ustr = strrchr(p2, '/'); 360 hstr = strrchr(p1, '/'); 361 rval = strncmp(p1, p2, 362 MAX(_PTRDIFF(ustr, p2), 363 _PTRDIFF(hstr, p1))); 364 if (rval < 0) { 365 366 return (-1); 367 } else if (rval > 0) { 368 369 return (+1); 370 } else { 371 /* now compare the ports */ 372 hstr = p1 + strlen(p1) -1; 373 ustr = p2 + strlen(p2) -1; 374 375 if (*hstr < *ustr) { 376 377 return (-1); 378 } else if (*hstr > *ustr) { 379 380 return (+1); 381 } else { 382 /* finally got a match */ 383 384 return (0); 385 } 386 } 387 } 388 } else if ((p1 == NULL) && (p2 == NULL)) { 389 390 return (0); 391 } else { 392 if (p1 == NULL) { 393 394 return (-1); 395 } else { 396 397 return (+1); 398 } 399 } 400 } 401 402 403 /* 404 * usba_devdb_compare 405 * Compares the two nodes. Returns -1 when p1 < p2, 0 when p1 == p2 406 * and +1 when p1 > p2. This function is invoked by avl_find 407 * Here p1 is always the node that we are trying to insert or match in 408 * the device database. 409 */ 410 static int 411 usba_devdb_compare(const void *p1, const void *p2) 412 { 413 usba_configrec_t *u1, *u2; 414 int rval; 415 416 u1 = ((usba_devdb_info_t *)p1)->usb_dev; 417 u2 = ((usba_devdb_info_t *)p2)->usb_dev; 418 419 USB_DPRINTF_L4(DPRINT_MASK_DEVDB, usba_devdb_log_handle, 420 "usba_devdb_compare: p1=0x%p u1=0x%p p2=0x%p u2=0x%p", 421 p1, (void *)u1, p2, (void *)u2); 422 423 /* first match vendor id */ 424 if (u1->idVendor < u2->idVendor) { 425 426 return (-1); 427 } else if (u1->idVendor > u2->idVendor) { 428 429 return (+1); 430 } else { 431 /* idvendor match, now check idproduct */ 432 if (u1->idProduct < u2->idProduct) { 433 434 return (-1); 435 } else if (u1->idProduct > u2->idProduct) { 436 437 return (+1); 438 } else { 439 /* idproduct match, now check serial no. */ 440 if (u1->serialno && u2->serialno) { 441 rval = strcmp(u1->serialno, u2->serialno); 442 if (rval > 0) { 443 444 return (+1); 445 } else if (rval < 0) { 446 447 return (-1); 448 } else { 449 /* srno. matches */ 450 451 return (usb_devdb_compare_pathnames( 452 u1->pathname, u2->pathname)); 453 } 454 } else if ((u1->serialno == NULL) && 455 (u2->serialno == NULL)) { 456 457 return (usb_devdb_compare_pathnames( 458 u1->pathname, u2->pathname)); 459 } else { 460 if (u1->serialno == NULL) { 461 462 return (-1); 463 } else { 464 465 return (+1); 466 } 467 } 468 } 469 } 470 } 471 472 473 /* 474 * usba_devdb_build_device_database 475 * Builds a height balanced tree of all the records present in the file. 476 * Records that are "not enabled" and are duplicate are discarded. 477 */ 478 static int 479 usba_devdb_build_device_database() 480 { 481 struct _buf *file; 482 usba_configrec_t *user_rec; 483 avl_index_t where; 484 usba_devdb_info_t *dbnode; 485 token_t token; 486 487 USB_DPRINTF_L4(DPRINT_MASK_DEVDB, usba_devdb_log_handle, 488 "usba_devdb_build_device_database: Start"); 489 490 file = kobj_open_file(usbconf_file); 491 if (file != (struct _buf *)-1) { 492 493 do { 494 user_rec = NULL; 495 token = usba_devdb_get_conf_rec(file, &user_rec); 496 497 if (user_rec != NULL) { 498 499 if ((user_rec->selection == NULL) || 500 (strcasecmp(user_rec->selection, 501 "enable") != 0)) { 502 /* we don't store disabled entries */ 503 usba_devdb_free_rec(user_rec); 504 505 continue; 506 } 507 508 dbnode = (usba_devdb_info_t *)kmem_zalloc( 509 sizeof (usba_devdb_info_t), KM_SLEEP); 510 dbnode->usb_dev = user_rec; 511 512 if (avl_find(&usba_devdb, dbnode, &where) == 513 NULL) { 514 /* insert new node */ 515 avl_insert(&usba_devdb, dbnode, where); 516 } else { 517 /* 518 * we don't maintain duplicate entries 519 */ 520 usba_devdb_free_rec(user_rec); 521 kmem_free(dbnode, 522 sizeof (usba_devdb_info_t)); 523 } 524 } 525 526 } while (token != EOF); 527 528 kobj_close_file(file); 529 } 530 531 USB_DPRINTF_L4(DPRINT_MASK_DEVDB, usba_devdb_log_handle, 532 "usba_devdb_build_device_database: End"); 533 534 /* XXX: return the no. of errors encountered */ 535 return (0); 536 } 537 538 539 /* 540 * usba_devdb_destroy_device_database 541 * Destory all records in the tree 542 */ 543 static void 544 usba_devdb_destroy_device_database() 545 { 546 usba_devdb_info_t *dbnode; 547 void *cookie = NULL; 548 549 USB_DPRINTF_L4(DPRINT_MASK_DEVDB, usba_devdb_log_handle, 550 "usba_devdb_destroy_device_database"); 551 552 /* while there are nodes in the tree, keep destroying them */ 553 while ((dbnode = (usba_devdb_info_t *) 554 avl_destroy_nodes(&usba_devdb, &cookie)) != NULL) { 555 /* 556 * destroy record 557 * destroy tree node 558 */ 559 usba_devdb_free_rec(dbnode->usb_dev); 560 kmem_free(dbnode, sizeof (usba_devdb_info_t)); 561 } 562 avl_destroy(&usba_devdb); 563 } 564 565 566 /* 567 * usba_devdb_get_user_preferences 568 * Returns configrec structure to the caller that contains user 569 * preferences for the device pointed by the parameters. 570 * The first search is for a record that has serial number and/or 571 * a pathname. If search fails, we search for a rule that is generic 572 * i.e. without serial no. and pathname. 573 */ 574 usba_configrec_t * 575 usba_devdb_get_user_preferences(int idVendor, int idProduct, char *serialno, 576 char *pathname) 577 { 578 usba_configrec_t *req_rec; 579 usba_devdb_info_t *req_node, *dbnode; 580 avl_index_t where; 581 582 USB_DPRINTF_L4(DPRINT_MASK_DEVDB, usba_devdb_log_handle, 583 "usba_devdb_get_user_preferences"); 584 585 req_rec = kmem_zalloc(sizeof (usba_configrec_t), KM_SLEEP); 586 req_node = kmem_zalloc(sizeof (usba_devdb_info_t), KM_SLEEP); 587 588 /* fill in the requested parameters */ 589 req_rec->idVendor = idVendor; 590 req_rec->idProduct = idProduct; 591 req_rec->serialno = serialno; 592 req_rec->pathname = pathname; 593 594 req_node->usb_dev = req_rec; 595 596 rw_enter(&usba_devdb_lock, RW_READER); 597 598 /* try to find a perfect match in the device database */ 599 dbnode = (usba_devdb_info_t *)avl_find(&usba_devdb, req_node, &where); 600 #ifdef __lock_lint 601 (void) usba_devdb_compare(req_node, dbnode); 602 #endif 603 if (dbnode == NULL) { 604 /* look for a generic rule */ 605 req_rec->serialno = req_rec->pathname = NULL; 606 dbnode = (usba_devdb_info_t *)avl_find(&usba_devdb, req_node, 607 &where); 608 #ifdef __lock_lint 609 (void) usba_devdb_compare(req_node, dbnode); 610 #endif 611 } 612 rw_exit(&usba_devdb_lock); 613 614 kmem_free(req_rec, sizeof (usba_configrec_t)); 615 kmem_free(req_node, sizeof (usba_devdb_info_t)); 616 617 if (dbnode) { 618 return (dbnode->usb_dev); 619 } else { 620 return (NULL); 621 } 622 } 623 624 625 /* 626 * usba_devdb_refresh 627 * Reinitializes the device database. It destroys the old one and creates 628 * a new one by re-reading the file. 629 */ 630 int 631 usba_devdb_refresh() 632 { 633 rw_enter(&usba_devdb_lock, RW_WRITER); 634 635 usba_build_devdb = B_TRUE; 636 637 /* destroy all nodes in the existing database */ 638 usba_devdb_destroy_device_database(); 639 640 /* now build a new one */ 641 (void) usba_devdb_build_device_database(); 642 643 usba_build_devdb = B_FALSE; 644 645 rw_exit(&usba_devdb_lock); 646 647 return (0); 648 } 649