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