I've uncovered two bugs in USB modalias generation (file2alias.c).
Patches are attached for both. You can see my original post (I forgot
to CC linux-usb) here: http://lkml.org/lkml/2009/11/11/245
The first is when a device ignores the USB spec and specifies bcdDevice
in hex rather than BCD format. This forces the driver to match against
a hex formatted bcdDevice (see ibmcam for an example). The end result
is that file2alias generates an invalid modalias (ex. d000[10-9]). The
patch detects when a driver uses a hex formatted bcdDevice_(lo|hi) and
adjusts the output to hex format accordingly.
The second bug is when incrementing/decrementing on a BCD formatted
integer (i.e. 0x09++ should be 0x10 not 0x0A). This patch just adds a
function for incrementing/decrementing BCD integers by converting to
decimal, doing the increment/decrement and then converting back to BCD.
Questions/comments welcome.
Nathaniel McCallum
diff --git a/scripts/mod/file2alias.c b/scripts/mod/file2alias.c
index 85ad782..f25c09b 100644
--- a/scripts/mod/file2alias.c
+++ b/scripts/mod/file2alias.c
@@ -160,6 +160,45 @@ static void do_usb_entry(struct usb_device_id *id,
"MODULE_ALIAS(\"%s\");\n", alias);
}
+/* Handles increment/decrement of BCD formatted integers */
+/* Returns the previous value, so it works like i++ or i-- */
+static unsigned int incbcd(unsigned int *bcd,
+ int inc,
+ unsigned char max,
+ size_t chars)
+{
+ unsigned int init = *bcd, i, j;
+ unsigned long long c, dec = 0;
+
+ /* If bcd is not in BCD format, just increment */
+ if (max > 0x9) {
+ *bcd += inc;
+ return init;
+ }
+
+ /* Convert BCD to Decimal */
+ for (i=0 ; i < chars ; i++) {
+ c = (*bcd >> (i << 2)) & 0xf;
+ c = c > 9 ? 9 : c; /* force to bcd just in case */
+ for (j=0 ; j < i ; j++)
+ c = c * 10;
+ dec += c;
+ }
+
+ /* Do our increment/decrement */
+ dec += inc;
+ *bcd = 0;
+
+ /* Convert back to BCD */
+ for (i=0 ; i < chars ; i++) {
+ for (c=1,j=0 ; j < i ; j++)
+ c = c * 10;
+ c = (dec / c) % 10;
+ *bcd += c << (i << 2);
+ }
+ return init;
+}
+
static void do_usb_entry_multi(struct usb_device_id *id, struct module *mod)
{
unsigned int devlo, devhi;
@@ -208,10 +247,16 @@ static void do_usb_entry_multi(struct usb_device_id *id, struct module *mod)
}
if (clo > 0x0)
- do_usb_entry(id, devlo++, ndigits, clo, max, max, mod);
+ do_usb_entry(id,
+ incbcd(&devlo, 1, max,
+ sizeof(id->bcdDevice_lo) * 2),
+ ndigits, clo, max, max, mod);
if (chi < max)
- do_usb_entry(id, devhi--, ndigits, 0x0, chi, max, mod);
+ do_usb_entry(id,
+ incbcd(&devhi, -1, max,
+ sizeof(id->bcdDevice_lo) * 2),
+ ndigits, 0x0, chi, max, mod);
}
}
diff --git a/scripts/mod/file2alias.c b/scripts/mod/file2alias.c
index 62a9025..85ad782 100644
--- a/scripts/mod/file2alias.c
+++ b/scripts/mod/file2alias.c
@@ -104,7 +104,7 @@ static void device_id_check(const char *modname, const char *device_id,
static void do_usb_entry(struct usb_device_id *id,
unsigned int bcdDevice_initial, int bcdDevice_initial_digits,
unsigned char range_lo, unsigned char range_hi,
- struct module *mod)
+ unsigned char max, struct module *mod)
{
char alias[500];
strcpy(alias, "usb:");
@@ -118,9 +118,22 @@ static void do_usb_entry(struct usb_device_id *id,
sprintf(alias + strlen(alias), "%0*X",
bcdDevice_initial_digits, bcdDevice_initial);
if (range_lo == range_hi)
- sprintf(alias + strlen(alias), "%u", range_lo);
- else if (range_lo > 0 || range_hi < 9)
- sprintf(alias + strlen(alias), "[%u-%u]", range_lo, range_hi);
+ sprintf(alias + strlen(alias), "%X", range_lo);
+ else if (range_lo > 0 || range_hi < max) {
+ if (range_lo > 0x9 || range_hi < 0xA)
+ sprintf(alias + strlen(alias),
+ "[%X-%X]",
+ range_lo,
+ range_hi);
+ else {
+ sprintf(alias + strlen(alias),
+ range_lo < 0x9 ? "[%X-9" : "[%X",
+ range_lo);
+ sprintf(alias + strlen(alias),
+ range_hi > 0xA ? "a-%X]" : "%X]",
+ range_lo);
+ }
+ }
if (bcdDevice_initial_digits < (sizeof(id->bcdDevice_lo) * 2 - 1))
strcat(alias, "*");
@@ -150,7 +163,7 @@ static void do_usb_entry(struct usb_device_id *id,
static void do_usb_entry_multi(struct usb_device_id *id, struct module *mod)
{
unsigned int devlo, devhi;
- unsigned char chi, clo;
+ unsigned char chi, clo, max;
int ndigits;
id->match_flags = TO_NATIVE(id->match_flags);
@@ -162,6 +175,17 @@ static void do_usb_entry_multi(struct usb_device_id *id, struct module *mod)
devhi = id->match_flags & USB_DEVICE_ID_MATCH_DEV_HI ?
TO_NATIVE(id->bcdDevice_hi) : ~0x0U;
+ /* Figure out if this entry is in bcd or hex format */
+ max = 0x9; /* Default to decimal format */
+ for (ndigits = 0 ; ndigits < sizeof(id->bcdDevice_lo) * 2 ; ndigits++) {
+ clo = (devlo >> (ndigits << 2)) & 0xf;
+ chi = ((devhi > 0x9999 ? 0x9999 : devhi) >> (ndigits << 2)) & 0xf;
+ if (clo > max || chi > max) {
+ max = 0xf;
+ break;
+ }
+ }
+
/*
* Some modules (visor) have empty slots as placeholder for
* run-time specification that results in catch-all alias
@@ -173,21 +197,21 @@ static void do_usb_entry_multi(struct usb_device_id *id, struct module *mod)
for (ndigits = sizeof(id->bcdDevice_lo) * 2 - 1; devlo <= devhi; ndigits--) {
clo = devlo & 0xf;
chi = devhi & 0xf;
- if (chi > 9) /* it's bcd not hex */
- chi = 9;
+ if (chi > max) /* If we are in bcd mode, truncate if necessary */
+ chi = max;
devlo >>= 4;
devhi >>= 4;
if (devlo == devhi || !ndigits) {
- do_usb_entry(id, devlo, ndigits, clo, chi, mod);
+ do_usb_entry(id, devlo, ndigits, clo, chi, max, mod);
break;
}
- if (clo > 0)
- do_usb_entry(id, devlo++, ndigits, clo, 9, mod);
+ if (clo > 0x0)
+ do_usb_entry(id, devlo++, ndigits, clo, max, max, mod);
- if (chi < 9)
- do_usb_entry(id, devhi--, ndigits, 0, chi, mod);
+ if (chi < max)
+ do_usb_entry(id, devhi--, ndigits, 0x0, chi, max, mod);
}
}