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:
parent
a00dbfa82b
commit
a130076f26
@ -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
|
||||
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user