[PATCH] Module alias and device table support.

Rusty Russell
2003-01-31T00:11:25Z

This patch adds MODULE_ALIAS("foo") capability, and uses it to
automatically generate sensible aliases from device tables. The
post-processing is a little rough, but works.

Name: Module alias and device table support
Author: Rusty Russell
Status: Tested on 2.5.59

D: Introduces "MODULE_ALIAS" which modules can use to embed their own
D: aliases for modprobe to use. Also adds a "finishing" step to modules to
D: supplement their aliases based on MODULE_TABLE declarations, eg.
D: 'usb:v0506p4601dl*dh*dc*dsc*dp*ic*isc*ip*' for drivers/usb/net/pegasus.o

diff -urNp --exclude TAGS -X /home/rusty/current-dontdiff --minimal linux-2.5.59/include/linux/isapnp.h working-2.5.59-alias/include/linux/isapnp.h
--- linux-2.5.59/include/linux/isapnp.h 2003-01-14 10:13:08.000000000 +1100
+++ working-2.5.59-alias/include/linux/isapnp.h 2003-01-30 16:13:12.000000000 +1100
@@ -69,8 +69,9 @@

/* export used IDs outside module */
#define ISAPNP_CARD_TABLE(name) \
- MODULE_GENERIC_TABLE(isapnp_card, name)
+ MODULE_TABLE(isapnp_card, name)

+/* If you change this, you must update scripts/table2alias.c. */
struct isapnp_card_id {
unsigned long driver_data; /* data private to the driver */
unsigned short card_vendor, card_device;
@@ -85,6 +86,7 @@ struct isapnp_card_id {
#define ISAPNP_DEVICE_SINGLE_END \
.card_vendor = 0, .card_device = 0

+/* If you change this, you must update scripts/table2alias.c. */
struct isapnp_device_id {
unsigned short card_vendor, card_device;
unsigned short vendor, function;
diff -urNp --exclude TAGS -X /home/rusty/current-dontdiff --minimal linux-2.5.59/include/linux/module.h working-2.5.59-alias/include/linux/module.h
--- linux-2.5.59/include/linux/module.h 2003-01-17 17:01:18.000000000 +1100
+++ working-2.5.59-alias/include/linux/module.h 2003-01-30 17:56:37.000000000 +1100
@@ -50,13 +50,14 @@ search_extable(const struct exception_ta
unsigned long value);

#ifdef MODULE
+#define ___module_cat(a,b) a ## b
+#define __module_cat(a,b) ___module_cat(a,b)
+/* For userspace: you can also call me... */
+#define MODULE_ALIAS(alias) \
+ static const char __module_cat(__alias_,__LINE__)[] \
+ __attribute__((section(".modalias"),unused)) = alias

-/* For replacement modutils, use an alias not a pointer. */
#define MODULE_GENERIC_TABLE(gtype,name) \
-static const unsigned long __module_##gtype##_size \
- __attribute__ ((unused)) = sizeof(struct gtype##_id); \
-static const struct gtype##_id * __module_##gtype##_table \
- __attribute__ ((unused)) = name; \
extern const struct gtype##_id __mod_##gtype##_table \
__attribute__ ((unused, alias(__stringify(name))))

@@ -96,6 +97,7 @@ extern const struct gtype##_id __mod_##g

#else /* !MODULE */

+#define MODULE_ALIAS(alias)
#define MODULE_GENERIC_TABLE(gtype,name)
#define THIS_MODULE ((struct module *)0)
#define MOD_INC_USE_COUNT do { } while (0)
diff -urNp --exclude TAGS -X /home/rusty/current-dontdiff --minimal linux-2.5.59/include/linux/pci.h working-2.5.59-alias/include/linux/pci.h
--- linux-2.5.59/include/linux/pci.h 2003-01-02 14:48:00.000000000 +1100
+++ working-2.5.59-alias/include/linux/pci.h 2003-01-30 16:13:12.000000000 +1100
@@ -491,6 +491,7 @@ struct pbus_set_ranges_data
unsigned long prefetch_start, prefetch_end;
};

+/* If you change this, you must update scripts/table2alias.c. */
struct pci_device_id {
unsigned int vendor, device; /* Vendor and device ID or PCI_ANY_ID */
unsigned int subvendor, subdevice; /* Subsystem ID's or PCI_ANY_ID */
diff -urNp --exclude TAGS -X /home/rusty/current-dontdiff --minimal linux-2.5.59/include/linux/usb.h working-2.5.59-alias/include/linux/usb.h
--- linux-2.5.59/include/linux/usb.h 2003-01-17 17:01:18.000000000 +1100
+++ working-2.5.59-alias/include/linux/usb.h 2003-01-30 16:13:12.000000000 +1100
@@ -371,6 +371,7 @@ static inline int usb_make_path (struct
* matches towards the beginning of your table, so that driver_info can
* record quirks of specific products.
*/
+/* If you change this, you must update scripts/table2alias.c. */
struct usb_device_id {
/* which fields to match against? */
__u16 match_flags;
diff -urNp --exclude TAGS -X /home/rusty/current-dontdiff --minimal linux-2.5.59/scripts/Makefile working-2.5.59-alias/scripts/Makefile
--- linux-2.5.59/scripts/Makefile 2003-01-02 12:45:31.000000000 +1100
+++ working-2.5.59-alias/scripts/Makefile 2003-01-30 16:13:12.000000000 +1100
@@ -8,7 +8,7 @@
# docproc: Preprocess .tmpl file in order to generate .sgml documentation
# conmakehash: Create arrays for initializing the kernel console tables

-host-progs := fixdep split-include conmakehash docproc kallsyms
+host-progs := fixdep split-include conmakehash docproc kallsyms table2alias
build-targets := $(host-progs)

# Let clean descend into subdirs
diff -urNp --exclude TAGS -X /home/rusty/current-dontdiff --minimal linux-2.5.59/scripts/Makefile.build working-2.5.59-alias/scripts/Makefile.build
--- linux-2.5.59/scripts/Makefile.build 2003-01-17 17:01:18.000000000 +1100
+++ working-2.5.59-alias/scripts/Makefile.build 2003-01-30 17:51:31.000000000 +1100
@@ -175,11 +175,16 @@ endif
quiet_cmd_link_multi-y = LD $@
cmd_link_multi-y = $(LD) $(LDFLAGS) $(EXTRA_LDFLAGS) -r -o $@ $(filter $(addprefix $(obj)/,$($(subst $(obj)/,,$(@:.o=-objs))) $($(subst $(obj)/,,$(@:.o=-y)))),$^)

+ifdef CONFIG_HOTPLUG
+module_link_hotplug-multi = `$(CONFIG_SHELL) scripts/extract_aliases $@.aliases "$(modname_flags)" $(filter $(addprefix $(obj)/,$($(subst $(obj)/,,$(@:.ko=-objs))) $($(subst $(obj)/,,$(@:.ko=-y)))),$^)`
+module_link_hotplug-single = `$(CONFIG_SHELL) scripts/extract_aliases $@.aliases "$(modname_flags)" $<`
+endif
+
quiet_cmd_link_multi-m = LD [M] $@
-cmd_link_multi-m = $(LD) $(LDFLAGS) $(EXTRA_LDFLAGS) $(LDFLAGS_MODULE) -o $@ $(filter $(addprefix $(obj)/,$($(subst $(obj)/,,$(@:.ko=-objs))) $($(subst $(obj)/,,$(@:.ko=-y)))),$^) init/vermagic.o
+cmd_link_multi-m = $(LD) $(LDFLAGS) $(EXTRA_LDFLAGS) $(LDFLAGS_MODULE) -o $@ $(filter $(addprefix $(obj)/,$($(subst $(obj)/,,$(@:.ko=-objs))) $($(subst $(obj)/,,$(@:.ko=-y)))),$^) $(module_link_hotplug-multi) init/vermagic.o

quiet_cmd_link_single-m = LD [M] $@
-cmd_link_single-m = $(LD) $(LDFLAGS) $(EXTRA_LDFLAGS) $(LDFLAGS_MODULE) -o $@ $< init/vermagic.o
+cmd_link_single-m = $(LD) $(LDFLAGS) $(EXTRA_LDFLAGS) $(LDFLAGS_MODULE) -o $@ $< init/vermagic.o $(module_link_hotplug-single)

# Don't rebuilt vermagic.o unless we actually are in the init/ dir
ifneq ($(obj),init)
diff -urNp --exclude TAGS -X /home/rusty/current-dontdiff --minimal linux-2.5.59/scripts/extract_aliases working-2.5.59-alias/scripts/extract_aliases
--- linux-2.5.59/scripts/extract_aliases 1970-01-01 10:00:00.000000000 +1000
+++ working-2.5.59-alias/scripts/extract_aliases 2003-01-30 18:06:59.000000000 +1100
@@ -0,0 +1,21 @@
+#! /bin/sh
+
+# Look for module tables, and if found, put them in the object file
+# and print its name.
+set -e
+
+OUTPUT="$1"
+MODNAME_FLAGS="$2"
+shift 2
+
+$NM --no-sort --print-size --radix=d --print-file-name "$@" |
+ grep '__mod_[a-z_]*_table' |
+ while IFS=": " read FILE OFFSET SIZE TYPE NAME; do
+ scripts/table2alias $NAME $FILE $OFFSET $SIZE
+ done > $OUTPUT.c
+
+if [ -s $OUTPUT.c ]; then
+ $CC $CFLAGS $NOSTDINC_FLAGS $EXTRA_CFLAGS $MODNAME_FLAGS -DMODULE -include include/linux/module.h -c -o $OUTPUT.o $OUTPUT.c
+ echo $OUTPUT.o
+fi
+rm $OUTPUT.c
diff -urNp --exclude TAGS -X /home/rusty/current-dontdiff --minimal linux-2.5.59/scripts/table2alias.c working-2.5.59-alias/scripts/table2alias.c
--- linux-2.5.59/scripts/table2alias.c 1970-01-01 10:00:00.000000000 +1000
+++ working-2.5.59-alias/scripts/table2alias.c 2003-01-30 17:49:45.000000000 +1100
@@ -0,0 +1,244 @@
+/* Simple code to turn various tables into module aliases.
+ This deals with kernel datastructures where they should be
+ dealt with: in the kernel source.
+ (C) 2002 Rusty Russell IBM Corporation.
+*/
+#include <stdint.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* We need __LITTLE_ENDIAN/__BIG_ENDIAN and BITS_PER_LONG */
+#define __KERNEL__
+#include "../include/asm/byteorder.h"
+#include "../include/asm/types.h"
+
+#if BITS_PER_LONG == 32
+typedef uint32_t kernel_long_t;
+#elif BITS_PER_LONG == 64
+typedef uint64_t kernel_long_t;
+#else
+#error Unknown BITS_PER_LONG
+#endif
+
+/* If we're cross-compiling, we could have any wierd endian
+ combination. Keep it simple. */
+static kernel_long_t __to_native(unsigned char *ptr, unsigned int size)
+{
+ unsigned int i;
+ kernel_long_t ret = 0;
+
+#ifdef __LITTLE_ENDIAN
+ for (i = 0; i < size; i++)
+ ret += ((unsigned long long)ptr[size - 1 - i]) << (i * 8);
+#elif defined(__BIG_ENDIAN)
+ for (i = 0; i < size; i++)
+ ret += ((unsigned long long)ptr[i]) << (i * 8);
+#else
+#error Must be big or little endian.
+#endif
+ return ret;
+}
+
+#define TO_NATIVE(x) (x) = __to_native((void *)&(x), sizeof(x))
+
+#define USB_DEVICE_ID_MATCH_VENDOR 0x0001
+#define USB_DEVICE_ID_MATCH_PRODUCT 0x0002
+#define USB_DEVICE_ID_MATCH_DEV_LO 0x0004
+#define USB_DEVICE_ID_MATCH_DEV_HI 0x0008
+#define USB_DEVICE_ID_MATCH_DEV_CLASS 0x0010
+#define USB_DEVICE_ID_MATCH_DEV_SUBCLASS 0x0020
+#define USB_DEVICE_ID_MATCH_DEV_PROTOCOL 0x0040
+#define USB_DEVICE_ID_MATCH_INT_CLASS 0x0080
+#define USB_DEVICE_ID_MATCH_INT_SUBCLASS 0x0100
+#define USB_DEVICE_ID_MATCH_INT_PROTOCOL 0x0200
+
+struct usb_device_id {
+ /* which fields to match against? */
+ uint16_t match_flags;
+
+ /* Used for product specific matches; range is inclusive */
+ uint16_t idVendor;
+ uint16_t idProduct;
+ uint16_t bcdDevice_lo;
+ uint16_t bcdDevice_hi;
+
+ /* Used for device class matches */
+ uint8_t bDeviceClass;
+ uint8_t bDeviceSubClass;
+ uint8_t bDeviceProtocol;
+
+ /* Used for interface class matches */
+ uint8_t bInterfaceClass;
+ uint8_t bInterfaceSubClass;
+ uint8_t bInterfaceProtocol;
+
+ /* not matched against */
+ kernel_long_t driver_info;
+};
+
+#define ADD(str, sep, cond, field) \
+do { \
+ strcat(str, sep); \
+ if (cond) \
+ sprintf(str + strlen(str), \
+ sizeof(field) == 1 ? "%02X" : \
+ sizeof(field) == 2 ? "%04X" : \
+ sizeof(field) == 4 ? "%08X" : "", \
+ field); \
+ else \
+ sprintf(str + strlen(str), "*"); \
+} while(0)
+
+/* Looks like "usb:vNpNdlNdhNdcNdscNdpNicNiscNipN" */
+static void do_usb_table(struct usb_device_id *ids, unsigned int size,
+ const char *filename)
+{
+ unsigned int i;
+ char alias[200];
+
+ /* Should be exact multiple. */
+ if (size % sizeof(ids[0]))
+ fprintf(stderr, "WARNING: %s USB ids size has %u left\n",
+ filename, size % sizeof(ids[0]));
+ for (i = 0; i < size / sizeof(ids[0]); i++) {
+ TO_NATIVE(ids[i].match_flags);
+ TO_NATIVE(ids[i].idVendor);
+ TO_NATIVE(ids[i].idProduct);
+ TO_NATIVE(ids[i].bcdDevice_lo);
+ TO_NATIVE(ids[i].bcdDevice_hi);
+
+ strcpy(alias, "usb:");
+ ADD(alias, "v", ids[i].match_flags&USB_DEVICE_ID_MATCH_VENDOR,
+ ids[i].idVendor);
+ ADD(alias, "p", ids[i].match_flags&USB_DEVICE_ID_MATCH_PRODUCT,
+ ids[i].idProduct);
+ ADD(alias, "dl", ids[i].match_flags&USB_DEVICE_ID_MATCH_DEV_LO,
+ ids[i].bcdDevice_lo);
+ ADD(alias, "dh", ids[i].match_flags&USB_DEVICE_ID_MATCH_DEV_HI,
+ ids[i].bcdDevice_hi);
+ ADD(alias, "dc", ids[i].match_flags&USB_DEVICE_ID_MATCH_DEV_CLASS,
+ ids[i].bDeviceClass);
+ ADD(alias, "dsc",
+ ids[i].match_flags&USB_DEVICE_ID_MATCH_DEV_SUBCLASS,
+ ids[i].bDeviceSubClass);
+ ADD(alias, "dp",
+ ids[i].match_flags&USB_DEVICE_ID_MATCH_DEV_PROTOCOL,
+ ids[i].bDeviceProtocol);
+ ADD(alias, "ic",
+ ids[i].match_flags&USB_DEVICE_ID_MATCH_INT_CLASS,
+ ids[i].bInterfaceClass);
+ ADD(alias, "isc",
+ ids[i].match_flags&USB_DEVICE_ID_MATCH_INT_SUBCLASS,
+ ids[i].bInterfaceSubClass);
+ ADD(alias, "ip",
+ ids[i].match_flags&USB_DEVICE_ID_MATCH_INT_PROTOCOL,
+ ids[i].bInterfaceProtocol);
+ /* Always end in a wildcard, for future extension */
+ if (alias[strlen(alias)-1] != '*')
+ strcat(alias, "*");
+ printf("MODULE_ALIAS(\"%s\");\n", alias);
+ }
+}
+
+#define PCI_ANY_ID (~0)
+
+struct pci_device_id {
+ unsigned int vendor, device; /* Vendor and device ID or PCI_ANY_ID */
+ unsigned int subvendor, subdevice; /* Subsystem ID's or PCI_ANY_ID */
+ unsigned int class, class_mask; /* (class,subclass,prog-if) triplet */
+ kernel_long_t driver_data; /* Data private to the driver */
+};
+
+/* Looks like: pci:vNdNsvNsdNcN. */
+static void do_pci_table(struct pci_device_id *ids, unsigned int size,
+ const char *filename)
+{
+ unsigned int i;
+ char alias[200];
+
+ /* Should be exact multiple. */
+ if (size % sizeof(ids[0]))
+ fprintf(stderr, "WARNING: %s PCI ids size has %u left\n",
+ filename, size % sizeof(ids[0]));
+ for (i = 0; i < size / sizeof(ids[0]); i++) {
+ TO_NATIVE(ids[i].vendor);
+ TO_NATIVE(ids[i].device);
+ TO_NATIVE(ids[i].subvendor);
+ TO_NATIVE(ids[i].subdevice);
+ TO_NATIVE(ids[i].class);
+ TO_NATIVE(ids[i].class_mask);
+
+ strcpy(alias, "pci:");
+ ADD(alias, "v", ids[i].vendor != PCI_ANY_ID, ids[i].vendor);
+ ADD(alias, "d", ids[i].device != PCI_ANY_ID, ids[i].device);
+ ADD(alias, "sv", ids[i].subvendor != PCI_ANY_ID, ids[i].subvendor);
+ ADD(alias, "sd", ids[i].subdevice != PCI_ANY_ID, ids[i].subdevice);
+ if (ids[i].class_mask != 0 && ids[i].class_mask != ~0) {
+ fprintf(stderr,
+ "Can't handle strange class_mask in %s:%04X\n",
+ filename, ids[i].class_mask);
+ exit(1);
+ }
+ ADD(alias, "c", ids[i].class != PCI_ANY_ID, ids[i].subvendor);
+ /* Always end in a wildcard, for future extension */
+ if (alias[strlen(alias)-1] != '*')
+ strcat(alias, "*");
+ printf("MODULE_ALIAS(\"%s\");\n", alias);
+ }
+}
+
+int main(int argc, char *argv[])
+{
+ int ret, fd;
+ unsigned int size, offset;
+ void *file;
+
+ if (argc != 5) {
+ fprintf(stderr,
+ "Usage: table2alias <type> <file> <offset> <size>\n"
+ " Where type is __mod_{pci, usb}_device_table.\n");
+ exit(1);
+ }
+
+ /* Suck it in. */
+ offset = atoi(argv[3]);
+ size = atoi(argv[4]);
+ file = malloc(size);
+
+ fd = open(argv[2], O_RDONLY);
+ if (fd < 0) {
+ fprintf(stderr, "opening %s: %s\n", argv[2], strerror(errno));
+ exit(1);
+ }
+ if (lseek(fd, offset, SEEK_SET) == (off_t)-1) {
+ fprintf(stderr, "seeking to %u in %s: %s\n",
+ offset, argv[2], strerror(errno));
+ exit(1);
+ }
+
+ offset = 0;
+ while (offset < size) {
+ ret = read(fd, file+offset, size-offset);
+ if (ret < 0) {
+ fprintf(stderr, "reading from %s: %s\n",
+ argv[2], strerror(errno));
+ exit(1);
+ }
+ offset += ret;
+ }
+
+ if (strcmp(argv[1], "__mod_usb_device_table") == 0)
+ do_usb_table(file, size, argv[2]);
+ else if (strcmp(argv[1], "__mod_pci_device_table") == 0)
+ do_pci_table(file, size, argv[2]);
+ else {
+ fprintf(stderr, "table2alias: unknown type %s\n", argv[1]);
+ exit(1);
+ }
+ exit(0);
+}

--
Anyone who quotes me in their sig is an idiot. -- Rusty Russell.