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 #pragma ident "%Z%%M% %I% %E% SMI" 27 28 #define USBA_FRAMEWORK 29 #include <sys/ksynch.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, MAX(ustr - p2, hstr - p1)); 361 if (rval < 0) { 362 363 return (-1); 364 } else if (rval > 0) { 365 366 return (+1); 367 } else { 368 /* now compare the ports */ 369 hstr = p1 + strlen(p1) -1; 370 ustr = p2 + strlen(p2) -1; 371 372 if (*hstr < *ustr) { 373 374 return (-1); 375 } else if (*hstr > *ustr) { 376 377 return (+1); 378 } else { 379 /* finally got a match */ 380 381 return (0); 382 } 383 } 384 } 385 } else if ((p1 == NULL) && (p2 == NULL)) { 386 387 return (0); 388 } else { 389 if (p1 == NULL) { 390 391 return (-1); 392 } else { 393 394 return (+1); 395 } 396 } 397 } 398 399 400 /* 401 * usba_devdb_compare 402 * Compares the two nodes. Returns -1 when p1 < p2, 0 when p1 == p2 403 * and +1 when p1 > p2. This function is invoked by avl_find 404 * Here p1 is always the node that we are trying to insert or match in 405 * the device database. 406 */ 407 static int 408 usba_devdb_compare(const void *p1, const void *p2) 409 { 410 usba_configrec_t *u1, *u2; 411 int rval; 412 413 u1 = ((usba_devdb_info_t *)p1)->usb_dev; 414 u2 = ((usba_devdb_info_t *)p2)->usb_dev; 415 416 USB_DPRINTF_L4(DPRINT_MASK_DEVDB, usba_devdb_log_handle, 417 "usba_devdb_compare: p1=0x%p u1=0x%p p2=0x%p u2=0x%p", 418 p1, (void *)u1, p2, u2); 419 420 /* first match vendor id */ 421 if (u1->idVendor < u2->idVendor) { 422 423 return (-1); 424 } else if (u1->idVendor > u2->idVendor) { 425 426 return (+1); 427 } else { 428 /* idvendor match, now check idproduct */ 429 if (u1->idProduct < u2->idProduct) { 430 431 return (-1); 432 } else if (u1->idProduct > u2->idProduct) { 433 434 return (+1); 435 } else { 436 /* idproduct match, now check serial no. */ 437 if (u1->serialno && u2->serialno) { 438 rval = strcmp(u1->serialno, u2->serialno); 439 if (rval > 0) { 440 441 return (+1); 442 } else if (rval < 0) { 443 444 return (-1); 445 } else { 446 /* srno. matches */ 447 448 return (usb_devdb_compare_pathnames( 449 u1->pathname, u2->pathname)); 450 } 451 } else if ((u1->serialno == NULL) && 452 (u2->serialno == NULL)) { 453 454 return (usb_devdb_compare_pathnames( 455 u1->pathname, u2->pathname)); 456 } else { 457 if (u1->serialno == NULL) { 458 459 return (-1); 460 } else { 461 462 return (+1); 463 } 464 } 465 } 466 } 467 } 468 469 470 /* 471 * usba_devdb_build_device_database 472 * Builds a height balanced tree of all the records present in the file. 473 * Records that are "not enabled" and are duplicate are discarded. 474 */ 475 static int 476 usba_devdb_build_device_database() 477 { 478 struct _buf *file; 479 usba_configrec_t *user_rec; 480 avl_index_t where; 481 usba_devdb_info_t *dbnode; 482 token_t token; 483 484 USB_DPRINTF_L4(DPRINT_MASK_DEVDB, usba_devdb_log_handle, 485 "usba_devdb_build_device_database: Start"); 486 487 file = kobj_open_file(usbconf_file); 488 if (file != (struct _buf *)-1) { 489 490 do { 491 user_rec = NULL; 492 token = usba_devdb_get_conf_rec(file, &user_rec); 493 494 if (user_rec != NULL) { 495 496 if ((user_rec->selection == NULL) || 497 (strcasecmp(user_rec->selection, 498 "enable") != 0)) { 499 /* we don't store disabled entries */ 500 usba_devdb_free_rec(user_rec); 501 502 continue; 503 } 504 505 dbnode = (usba_devdb_info_t *)kmem_zalloc( 506 sizeof (usba_devdb_info_t), KM_SLEEP); 507 dbnode->usb_dev = user_rec; 508 509 if (avl_find(&usba_devdb, dbnode, &where) == 510 NULL) { 511 /* insert new node */ 512 avl_insert(&usba_devdb, dbnode, where); 513 } else { 514 /* 515 * we don't maintain duplicate entries 516 */ 517 usba_devdb_free_rec(user_rec); 518 kmem_free(dbnode, 519 sizeof (usba_devdb_info_t)); 520 } 521 } 522 523 } while (token != EOF); 524 525 kobj_close_file(file); 526 } 527 528 USB_DPRINTF_L4(DPRINT_MASK_DEVDB, usba_devdb_log_handle, 529 "usba_devdb_build_device_database: End"); 530 531 /* XXX: return the no. of errors encountered */ 532 return (0); 533 } 534 535 536 /* 537 * usba_devdb_destroy_device_database 538 * Destory all records in the tree 539 */ 540 static void 541 usba_devdb_destroy_device_database() 542 { 543 usba_devdb_info_t *dbnode; 544 void *cookie = NULL; 545 546 USB_DPRINTF_L4(DPRINT_MASK_DEVDB, usba_devdb_log_handle, 547 "usba_devdb_destroy_device_database"); 548 549 /* while there are nodes in the tree, keep destroying them */ 550 while ((dbnode = (usba_devdb_info_t *) 551 avl_destroy_nodes(&usba_devdb, &cookie)) != NULL) { 552 /* 553 * destroy record 554 * destroy tree node 555 */ 556 usba_devdb_free_rec(dbnode->usb_dev); 557 kmem_free(dbnode, sizeof (usba_devdb_info_t)); 558 } 559 avl_destroy(&usba_devdb); 560 } 561 562 563 /* 564 * usba_devdb_get_user_preferences 565 * Returns configrec structure to the caller that contains user 566 * preferences for the device pointed by the parameters. 567 * The first search is for a record that has serial number and/or 568 * a pathname. If search fails, we search for a rule that is generic 569 * i.e. without serial no. and pathname. 570 */ 571 usba_configrec_t * 572 usba_devdb_get_user_preferences(int idVendor, int idProduct, char *serialno, 573 char *pathname) 574 { 575 usba_configrec_t *req_rec; 576 usba_devdb_info_t *req_node, *dbnode; 577 avl_index_t where; 578 579 USB_DPRINTF_L4(DPRINT_MASK_DEVDB, usba_devdb_log_handle, 580 "usba_devdb_get_user_preferences"); 581 582 req_rec = kmem_zalloc(sizeof (usba_configrec_t), KM_SLEEP); 583 req_node = kmem_zalloc(sizeof (usba_devdb_info_t), KM_SLEEP); 584 585 /* fill in the requested parameters */ 586 req_rec->idVendor = idVendor; 587 req_rec->idProduct = idProduct; 588 req_rec->serialno = serialno; 589 req_rec->pathname = pathname; 590 591 req_node->usb_dev = req_rec; 592 593 rw_enter(&usba_devdb_lock, RW_READER); 594 595 /* try to find a perfect match in the device database */ 596 dbnode = (usba_devdb_info_t *)avl_find(&usba_devdb, req_node, &where); 597 #ifdef __lock_lint 598 (void) usba_devdb_compare(req_node, dbnode); 599 #endif 600 if (dbnode == NULL) { 601 /* look for a generic rule */ 602 req_rec->serialno = req_rec->pathname = NULL; 603 dbnode = (usba_devdb_info_t *)avl_find(&usba_devdb, req_node, 604 &where); 605 #ifdef __lock_lint 606 (void) usba_devdb_compare(req_node, dbnode); 607 #endif 608 } 609 rw_exit(&usba_devdb_lock); 610 611 kmem_free(req_rec, sizeof (usba_configrec_t)); 612 kmem_free(req_node, sizeof (usba_devdb_info_t)); 613 614 if (dbnode) { 615 return (dbnode->usb_dev); 616 } else { 617 return (NULL); 618 } 619 } 620 621 622 /* 623 * usba_devdb_refresh 624 * Reinitializes the device database. It destroys the old one and creates 625 * a new one by re-reading the file. 626 */ 627 int 628 usba_devdb_refresh() 629 { 630 rw_enter(&usba_devdb_lock, RW_WRITER); 631 632 usba_build_devdb = B_TRUE; 633 634 /* destroy all nodes in the existing database */ 635 usba_devdb_destroy_device_database(); 636 637 /* now build a new one */ 638 (void) usba_devdb_build_device_database(); 639 640 usba_build_devdb = B_FALSE; 641 642 rw_exit(&usba_devdb_lock); 643 644 return (0); 645 } 646