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