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