Implement support for reading USB quirks from the kernel environment.

Refer to the usb_quirk(4) manual page for more details on how to use
this new feature.

Submitted by:	Maxime Soule <btik-fbsd@scoubidou.com>
PR:		203249
MFC after:	2 weeks
This commit is contained in:
Hans Petter Selasky 2015-09-24 17:37:30 +00:00
parent a00dbfa82b
commit a130076f26
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=288180
2 changed files with 192 additions and 4 deletions

View File

@ -16,7 +16,7 @@
.\"
.\" $FreeBSD$
.\"
.Dd May 7, 2015
.Dd September 24, 2015
.Dt USB_QUIRK 4
.Os
.Sh NAME
@ -177,7 +177,53 @@ ejects after HID command
.Pp
See
.Pa /sys/dev/usb/quirk/usb_quirk.h
for the complete list of supported quirks.
or run "usbconfig dump_quirk_names" for the complete list of supported quirks.
.Sh LOADER TUNABLE
The following tunable can be set at the
.Xr loader 8
prompt before booting the kernel, or stored in
.Xr loader.conf 5 .
.Bl -tag -width indent
.It Va hw.usb.quirk.%d
The value is a string whose format is:
.Bd -literal -offset indent
.Qo VendorId ProductId LowRevision HighRevision UQ_QUIRK,... Qc
.Ed
.Pp
Installs the quirks
.Ic UQ_QUIRK,...
for all USB devices matching
.Ic VendorId ,
.Ic ProductId
and has a hardware revision between and including
.Ic LowRevision
and
.Ic HighRevision .
.Pp
.Ic VendorId ,
.Ic ProductId ,
.Ic LowRevision
and
.Ic HighRevision
are all 16 bits numbers which can be decimal or hexadecimal based.
.Pp
A maximum of 100 variables
.Ic hw.usb.quirk.0, .1, ..., .99
can be defined.
.Pp
If a matching entry is found in the kernel's internal quirks table, it
is replaced by the new definition.
.Pp
Else a new entry is created given that the quirk table is not full.
.Pp
The kernel iterates over the
.Ic hw.usb.quirk.N
variables starting at
.Ic N = 0
and stops at
.Ic N = 99
or the first non-existing one.
.El
.Sh EXAMPLES
After attaching a
.Nm u3g
@ -186,6 +232,13 @@ device which appears as a USB device on
.Bd -literal -offset indent
usbconfig -d ugen0.3 add_quirk UQ_MSC_EJECT_WAIT
.Ed
.Pp
To install a quirk at boot time, place one or several lines like the
following in
.Xr loader.conf 5 :
.Bd -literal -offset indent
hw.usb.quirk.0="0x04d9 0xfa50 0 0xffff UQ_KBD_IGNORE"
.Ed
.Sh SEE ALSO
.Xr usbconfig 8
.Sh HISTORY

View File

@ -61,6 +61,7 @@ MODULE_VERSION(usb_quirk, 1);
#define USB_DEV_QUIRKS_MAX 384
#define USB_SUB_QUIRKS_MAX 8
#define USB_QUIRK_ENVROOT "hw.usb.quirk."
struct usb_quirk_entry {
uint16_t vid;
@ -608,8 +609,32 @@ static const char *usb_quirk_str[USB_QUIRK_MAX] = {
static const char *
usb_quirkstr(uint16_t quirk)
{
return ((quirk < USB_QUIRK_MAX) ?
usb_quirk_str[quirk] : "USB_QUIRK_UNKNOWN");
return ((quirk < USB_QUIRK_MAX && usb_quirk_str[quirk] != NULL) ?
usb_quirk_str[quirk] : "UQ_UNKNOWN");
}
/*------------------------------------------------------------------------*
* usb_strquirk
*
* This function converts a string into a USB quirk code.
*
* Returns:
* Less than USB_QUIRK_MAX: Quirk code
* Else: Quirk code not found
*------------------------------------------------------------------------*/
static uint16_t
usb_strquirk(const char *str, size_t len)
{
const char *quirk;
uint16_t x;
for (x = 0; x != USB_QUIRK_MAX; x++) {
quirk = usb_quirkstr(x);
if (strncmp(str, quirk, len) == 0 &&
quirk[len] == 0)
break;
}
return (x);
}
/*------------------------------------------------------------------------*
@ -854,12 +879,122 @@ usb_quirk_ioctl(unsigned long cmd, caddr_t data,
return (ENOIOCTL);
}
/*------------------------------------------------------------------------*
* usb_quirk_strtou16
*
* Helper function to scan a 16-bit integer.
*------------------------------------------------------------------------*/
static uint16_t
usb_quirk_strtou16(const char **pptr, const char *name, const char *what)
{
unsigned long value;
char *end;
value = strtoul(*pptr, &end, 0);
if (value > 65535 || *pptr == end || (*end != ' ' && *end != '\t')) {
printf("%s: %s 16-bit %s value set to zero\n",
name, what, *end == 0 ? "incomplete" : "invalid");
return (0);
}
*pptr = end + 1;
return ((uint16_t)value);
}
/*------------------------------------------------------------------------*
* usb_quirk_add_entry_from_str
*
* Add a USB quirk entry from string.
* "VENDOR PRODUCT LO_REV HI_REV QUIRK[,QUIRK[,...]]"
*------------------------------------------------------------------------*/
static void
usb_quirk_add_entry_from_str(const char *name, const char *env)
{
struct usb_quirk_entry entry = { };
struct usb_quirk_entry *new;
uint16_t quirk_idx;
uint16_t quirk;
const char *end;
/* check for invalid environment variable */
if (name == NULL || env == NULL)
return;
if (bootverbose)
printf("Adding USB QUIRK '%s' = '%s'\n", name, env);
/* parse device information */
entry.vid = usb_quirk_strtou16(&env, name, "Vendor ID");
entry.pid = usb_quirk_strtou16(&env, name, "Product ID");
entry.lo_rev = usb_quirk_strtou16(&env, name, "Low revision");
entry.hi_rev = usb_quirk_strtou16(&env, name, "High revision");
/* parse quirk information */
quirk_idx = 0;
while (*env != 0 && quirk_idx != USB_SUB_QUIRKS_MAX) {
/* skip whitespace before quirks */
while (*env == ' ' || *env == '\t')
env++;
/* look for quirk separation character */
end = strchr(env, ',');
if (end == NULL)
end = env + strlen(env);
/* lookup quirk in string table */
quirk = usb_strquirk(env, end - env);
if (quirk < USB_QUIRK_MAX) {
entry.quirks[quirk_idx++] = quirk;
} else {
printf("%s: unknown USB quirk '%.*s' (skipped)\n",
name, (int)(end - env), env);
}
env = end;
/* skip quirk delimiter, if any */
if (*env != 0)
env++;
}
/* register quirk */
if (quirk_idx != 0) {
if (*env != 0) {
printf("%s: Too many USB quirks, only %d allowed!\n",
name, USB_SUB_QUIRKS_MAX);
}
mtx_lock(&usb_quirk_mtx);
new = usb_quirk_get_entry(entry.vid, entry.pid,
entry.lo_rev, entry.hi_rev, 1);
if (new == NULL)
printf("%s: USB quirks table is full!\n", name);
else
memcpy(new->quirks, entry.quirks, sizeof(entry.quirks));
mtx_unlock(&usb_quirk_mtx);
} else {
printf("%s: No USB quirks found!\n", name);
}
}
static void
usb_quirk_init(void *arg)
{
char envkey[sizeof(USB_QUIRK_ENVROOT) + 2]; /* 2 digits max, 0 to 99 */
int i;
/* initialize mutex */
mtx_init(&usb_quirk_mtx, "USB quirk", NULL, MTX_DEF);
/* look for quirks defined by the environment variable */
for (i = 0; i != 100; i++) {
snprintf(envkey, sizeof(envkey), USB_QUIRK_ENVROOT "%d", i);
/* Stop at first undefined var */
if (!testenv(envkey))
break;
/* parse environment variable */
usb_quirk_add_entry_from_str(envkey, kern_getenv(envkey));
}
/* register our function */
usb_test_quirk_p = &usb_test_quirk_by_info;
usb_quirk_ioctl_p = &usb_quirk_ioctl;