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
usba_devdb_initialization()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
usba_devdb_destroy()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
usba_devdb_get_var_type(char * str)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
usba_devdb_get_conf_rec(struct _buf * file,usba_configrec_t ** rec)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
usba_devdb_free_rec(usba_configrec_t * rec)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
usb_devdb_compare_pathnames(char * p1,char * p2)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
usba_devdb_compare(const void * p1,const void * p2)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
usba_devdb_build_device_database()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
usba_devdb_destroy_device_database()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 *
usba_devdb_get_user_preferences(int idVendor,int idProduct,char * serialno,char * pathname)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
usba_devdb_refresh()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