Home | History | Annotate | Download | only in usba
      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