xref: /illumos-gate/usr/src/uts/common/io/usb/usba/usba_devdb.c (revision 2a6e99a0f1f7d22c0396e8b2ce9b9babbd1056cf)
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