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