Package: initrd-tools Version: 0.1.74 Severity: wishlist
Hi, This is a proposal to improve mkinitrd by reusing code from the hotplug project to determine which modules are needed on the initrd image to support block devices. It is an alternative to an earlier proposal to make devices in sysfs point to the module that implements their driver. It improves on the original proposal in the following ways: - no kernel changes required, so will work with older 2.6 kernels - can cope with differences between modules in running kernel and new kernel; as an example, it understands that a USB stick in 2.6.8 needs usb-storage, but in 2.6.9 will need the ub module. The original proposal and the problems motivating it are discussed in: - http://marc.theaimsgroup.com/?l=linux-scsi&m=109570145108639&w=2 - http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=263169 The method is simple: mkinitrd runs under an old kernel on some hardware, and it needs to find the minimal set of modules needed to boot a new kernel on that hardware. Sysfs describes the hardware, complete with device identifiers, and a module map provided with the new kernel describes which modules are needed to operate a particular piece of hardware. Finally, modules.dep shows which support modules must be loaded before a module can be loaded. Hotplug uses this same information to load modules for a new device into a running kernel. In fact hotplug and mkinitrd are two sides of the same coin: hotplug finds modules for available hardware given a running kernel, and mkinitrd finds modules for some kernel given available hardware. So I copied the module table processing from hotplug and adapted it to use tables from a different kernel. After that, it's only a matter of identifying which hardware devices are used to implement block devices; essentially that's just /sys/block/*/device/. One little twist: if you're using for example a USB stick, you need not only the USB driver, but also the PCI driver for the bus that the USB host is attached to. The pathname of the device in /sys/devices identifies all such parent devices. The attached script implements this method, and seems to give more or less correct answers on a machine with SATA drive (although I wonder about the location of ide0 in sysfs): [EMAIL PROTECTED]:/dat/tmp$ uname -r 2.6.8-1-686 [EMAIL PROTECTED]:/dat/tmp$ sh joke4 =============== /sys/block/hda devno 3:0 at: /sys/devices/ide0/0.0 needs: =============== /sys/block/sda devno 8:0 at: /sys/devices/pci0000:00/0000:00:1f.2/host0/0:0:0:0 needs: ata_piix =============== /sys/block/sdb devno 8:16 at: /sys/devices/pci0000:00/0000:00:1d.7/usb5/5-4/5-4.3/5-4.3:1.0/host7/7:0:0:0 needs: ehci-hcd usbcore usbcore usb-storage recommended module load order: /lib/modules/2.6.8-1-686/kernel/drivers/scsi/libata.ko /lib/modules/2.6.8-1-686/kernel/drivers/scsi/scsi_mod.ko /lib/modules/2.6.8-1-686/kernel/drivers/scsi/ata_piix.ko /lib/modules/2.6.8-1-686/kernel/drivers/usb/core/usbcore.ko /lib/modules/2.6.8-1-686/kernel/drivers/usb/host/ehci-hcd.ko /lib/modules/2.6.8-1-686/kernel/drivers/ide/ide-core.ko /lib/modules/2.6.8-1-686/kernel/drivers/usb/storage/usb-storage.ko [EMAIL PROTECTED]:/dat/tmp$ sh joke4 2.6.10-rc2-e =============== /sys/block/hda devno 3:0 at: /sys/devices/ide0/0.0 needs: =============== /sys/block/sda devno 8:0 at: /sys/devices/pci0000:00/0000:00:1f.2/host0/0:0:0:0 needs: ata_piix =============== /sys/block/sdb devno 8:16 at: /sys/devices/pci0000:00/0000:00:1d.7/usb5/5-4/5-4.3/5-4.3:1.0/host7/7:0:0:0 needs: ehci-hcd usbcore usbcore ub recommended module load order: /lib/modules/2.6.10-rc2-e/kernel/drivers/scsi/libata.ko /lib/modules/2.6.10-rc2-e/kernel/drivers/scsi/scsi_mod.ko /lib/modules/2.6.10-rc2-e/kernel/drivers/scsi/ata_piix.ko /lib/modules/2.6.10-rc2-e/kernel/drivers/usb/core/usbcore.ko /lib/modules/2.6.10-rc2-e/kernel/drivers/usb/host/ehci-hcd.ko /lib/modules/2.6.10-rc2-e/kernel/drivers/block/ub.ko [EMAIL PROTECTED]:/dat/tmp$ I'm looking for comments: could this approach be made to work, and if so what would be the best way to go about moving from this proof of concept to something that could be used as part of a complete mkinitrd? Regards, Erik # # Given a kernel version (default the running kernel) # print an ordered minimal list of all files that must # be loaded into that kernel in order for all block # devices to be operational. # # A large part of this script is copied from the hotplug package, # http://linux-hotplug.sourceforge.net/ # # This software may be used and distributed according to the terms # of the GNU General Public License, incorporated herein by reference. # # # # Each modules.usbmap format line corresponds to one entry in a # MODULE_DEVICE_TABLE(usb,...) declaration in a kernel file. # # Think of it as a database column with up to three "match specs" # to associate kernel modules with particular devices or classes # of device. The match specs provide a reasonably good filtering # mechanism, but some driver probe() routines need to provide # extra filtering. # usb_convert_vars () { # work around 2.2.early brokenness # munges the usb_bcdDevice such that it is a integer rather # than a float: e.g. 1.0 become 0100 DEFAULT_IFS="$IFS" PRODUCT=`echo $PRODUCT | sed -e "s+\.\([0-9]\)$+.\10+" -e "s/\.$/00/" \ -e "s+/\([0-9]\)\.\([0-9][0-9]\)+/0\1\2+" \ -e "s+/\([0-9][0-9]\)\.\([0-9][0-9]\)+/\1\2+"` set $(echo $PRODUCT | sed -e 's+\([^/]*\)/\([^/]*\)/\(.*\)+\1 \2 \3+') usb_idVendor=$((0x$1)) usb_idProduct=$((0x$2)) usb_bcdDevice=$((0x$3)) if [ "$TYPE" != "" ]; then IFS=/ set $TYPE '' usb_bDeviceClass=$1 usb_bDeviceSubClass=$2 usb_bDeviceProtocol=$3 IFS="$DEFAULT_IFS" elif [ -r $SYSFS/$DEVPATH/bDeviceClass ]; then usb_bDeviceClass=$((0x$(cat $SYSFS/$DEVPATH/bDeviceClass))) usb_bDeviceSubClass=$((0x$(cat $SYSFS/$DEVPATH/bDeviceSubClass))) usb_bDeviceProtocol=$((0x$(cat $SYSFS/$DEVPATH/bDeviceProtocol))) else # out-of-range values usb_bDeviceClass=1000 usb_bDeviceSubClass=1000 usb_bDeviceProtocol=1000 fi if [ "$INTERFACE" != "" ]; then IFS=/ set $INTERFACE '' usb_bInterfaceClass=$1 usb_bInterfaceSubClass=$2 usb_bInterfaceProtocol=$3 IFS="$DEFAULT_IFS" elif [ -r $SYSFS/$DEVPATH/bInterfaceClass ]; then usb_bInterfaceClass=$((0x$(cat $SYSFS/$DEVPATH/bInterfaceClass))) usb_bInterfaceSubClass=$((0x$(cat $SYSFS/$DEVPATH/bInterfaceSubClass))) usb_bInterfaceProtocol=$((0x$(cat $SYSFS/$DEVPATH/bInterfaceProtocol))) else # out-of-range values usb_bInterfaceClass=1000 usb_bInterfaceSubClass=1000 usb_bInterfaceProtocol=1000 fi } USB_MATCH_VENDOR=$((0x0001)) USB_MATCH_PRODUCT=$((0x0002)) USB_MATCH_DEV_LO=$((0x0004)) USB_MATCH_DEV_HI=$((0x0008)) USB_MATCH_DEV_CLASS=$((0x0010)) USB_MATCH_DEV_SUBCLASS=$((0x0020)) USB_MATCH_DEV_PROTOCOL=$((0x0040)) USB_MATCH_INT_CLASS=$((0x0080)) USB_MATCH_INT_SUBCLASS=$((0x0100)) USB_MATCH_INT_PROTOCOL=$((0x0200)) # # stdin is "modules.usbmap" syntax # on return, all matching modules have been added to $DRIVERS # usb_map_modules () { local line module # look at each usb_device_id entry # collect all matches in $DRIVERS while read line do # comments are lines that start with "#" ... # be careful, they still get parsed by bash! case "$line" in \#*) continue ;; "") continue ;; esac set $line module=$1 match_flags=$(($2)) idVendor=$(($3)) idProduct=$(($4)) bcdDevice_lo=$(($5)) bcdDevice_hi=$(($6)) bDeviceClass=$(($7)) bDeviceSubClass=$(($8)) bDeviceProtocol=$(($9)) shift 9 bInterfaceClass=$(($1)) bInterfaceSubClass=$(($2)) bInterfaceProtocol=$(($3)) : checkmatch $module : idVendor $idVendor $usb_idVendor if [ $USB_MATCH_VENDOR -eq $(( $match_flags & $USB_MATCH_VENDOR )) ] && [ $idVendor -ne $usb_idVendor ]; then continue fi : idProduct $idProduct $usb_idProduct if [ $USB_MATCH_PRODUCT -eq $(( $match_flags & $USB_MATCH_PRODUCT )) ] && [ $idProduct -ne $usb_idProduct ]; then continue fi : bcdDevice range $bcdDevice_hi $bcdDevice_lo actual $usb_bcdDevice if [ $USB_MATCH_DEV_LO -eq $(( $match_flags & $USB_MATCH_DEV_LO )) ] && [ $usb_bcdDevice -lt $bcdDevice_lo ]; then continue fi # bcdDevice_lo <= bcdDevice <= bcdDevice_hi if [ $USB_MATCH_DEV_HI -eq $(( $match_flags & $USB_MATCH_DEV_HI )) ] && [ $usb_bcdDevice -gt $bcdDevice_hi ]; then continue fi : bDeviceClass $bDeviceClass $usb_bDeviceClass if [ $USB_MATCH_DEV_CLASS -eq $(( $match_flags & $USB_MATCH_DEV_CLASS )) ] && [ $bDeviceClass -ne $usb_bDeviceClass ]; then continue fi : bDeviceSubClass $bDeviceSubClass $usb_bDeviceSubClass if [ $USB_MATCH_DEV_SUBCLASS -eq $(( $match_flags & $USB_MATCH_DEV_SUBCLASS )) ] && [ $bDeviceSubClass -ne $usb_bDeviceSubClass ]; then continue fi : bDeviceProtocol $bDeviceProtocol $usb_bDeviceProtocol if [ $USB_MATCH_DEV_PROTOCOL -eq $(( $match_flags & $USB_MATCH_DEV_PROTOCOL )) ] && [ $bDeviceProtocol -ne $usb_bDeviceProtocol ]; then continue fi # NOTE: for now, this only checks the first of perhaps # several interfaces for this device. : bInterfaceClass $bInterfaceClass $usb_bInterfaceClass if [ $USB_MATCH_INT_CLASS -eq $(( $match_flags & $USB_MATCH_INT_CLASS )) ] && [ $bInterfaceClass -ne $usb_bInterfaceClass ]; then continue fi : bInterfaceSubClass $bInterfaceSubClass $usb_bInterfaceSubClass if [ $USB_MATCH_INT_SUBCLASS -eq $(( $match_flags & $USB_MATCH_INT_SUBCLASS )) ] && [ $bInterfaceSubClass -ne $usb_bInterfaceSubClass ]; then continue fi : bInterfaceProtocol $bInterfaceProtocol $usb_bInterfaceProtocol if [ $USB_MATCH_INT_PROTOCOL -eq $(( $match_flags & $USB_MATCH_INT_PROTOCOL )) ] && [ $bInterfaceProtocol -ne $usb_bInterfaceProtocol ]; then continue fi # It was a match! DRIVERS="$module $DRIVERS" : drivers $DRIVERS done } # # Given path to USB device, add any required modules # to $DRIVERS, based on modules.usbmap # usb_device() { PRODUCT=0/0/0 TYPE= INTERFACE= DEVPATH= DEVICE= export ACTION PRODUCT TYPE INTERFACE DEVPATH DEVICE DEVPATH=$1 DEVPATH=${1#$SYSFS} usb_convert_vars usb_map_modules < $MAP_USB } # # Given path to USB interface, add any required modules # to $DRIVERS, based on modules.usbmap # usb_interface() { PRODUCT=0/0/0 TYPE= INTERFACE= DEVPATH= DEVICE= export ACTION PRODUCT TYPE INTERFACE DEVPATH DEVICE DEVPATH=$1 dir=$DEVPATH/.. # XXX I don't know why this is needed, but it works PRODUCT="$(cat $dir/idVendor)/$(cat $dir/idProduct)/$(cat $dir/bcdDevice)" # echo "++++++++ $PRODUCT" DEVPATH=${DEVPATH#$SYSFS} usb_convert_vars usb_map_modules < $MAP_USB } # # Each modules.usbmap format line corresponds to one entry in a # MODULE_DEVICE_TABLE(pci,...) declaration in a kernel file. # # Think of it as a database column with up to three "match specs" # to associate kernel modules with particular devices or classes # of device. The match specs provide a reasonably good filtering # mechanism, but some driver probe() routines need to provide # extra filtering. # pci_convert_vars () { pci_class=$((0x$PCI_CLASS)) set $(echo $PCI_ID | sed -e 's/\([^:]*\):\(.*\)/\1 \2/') pci_id_vendor=$((0x$1)) pci_id_device=$((0x$2)) set $(echo $PCI_SUBSYS_ID | sed -e 's/\([^:]*\):\(.*\)/\1 \2/') pci_subid_vendor=$((0x$1)) pci_subid_device=$((0x$2)) } PCI_ANY=$((0xffffffff)) # # stdin is "modules.pcimap" syntax # on return, ONE matching module was added to $DRIVERS # pci_map_modules () { local module ignored # comment line lists (current) pci_device_id field names read ignored # look at each pci_device_id entry # collect one match in $DRIVERS while read module vendor device subvendor subdevice class class_mask ignored do # comments are lines that start with "#" ... # be careful, they still get parsed by bash! case "$module" in \#*) continue ;; esac # convert the fields from hex to dec vendor=$(($vendor)); device=$(($device)) subvendor=$(($subvendor)); subdevice=$(($subdevice)) class=$(($class)); class_mask=$(($class_mask)) : checkmatch $module : vendor $vendor $pci_id_vendor if [ $vendor -ne $PCI_ANY -a $vendor -ne $pci_id_vendor ]; then continue fi : device $device $pci_id_device if [ $device -ne $PCI_ANY -a $device -ne $pci_id_device ]; then continue fi : sub-vendor $subvendor $pci_subid_vendor if [ $subvendor -ne $PCI_ANY -a $subvendor -ne $pci_subid_vendor ]; then continue fi : sub-device $subdevice $pci_subid_device if [ $subdevice -ne $PCI_ANY -a $subdevice -ne $pci_subid_device ]; then continue fi class_temp=$(($pci_class & $class_mask)) if [ $class_temp -eq $class ]; then DRIVERS="$module $DRIVERS" : drivers $DRIVERS fi done } # # Given path to PCI device, add any required modules # to $DRIVERS, based on modules.pcimap # pci_device() { ACTION=add PCI_CLASS=0 PCI_ID=0:0 PCI_SLOT_NAME=0:0.0 PCI_SUBSYS_ID=0:0 export ACTION PCI_CLASS PCI_ID PCI_SLOT_NAME PCI_SUBSYS_ID PCI_DEVICE=$1 set `echo $PCI_DEVICE \ | sed -e 's/\([^:]*\):\(.*\):\(.*\)\.\(.*\)/\1 \2 \3 \4/'` PCI_SLOT_NAME=$2:$3.$4 PCI_CLASS="`cat $PCI_DEVICE/class`" PCI_CLASS=${PCI_CLASS#0x} vendor_id=`cat $PCI_DEVICE/vendor` device_id=`cat $PCI_DEVICE/device` PCI_ID="${vendor_id#0x}:${device_id#0x}" sub_vendor_id=`cat $PCI_DEVICE/subsystem_vendor` sub_device_id=`cat $PCI_DEVICE/subsystem_device` PCI_SUBSYS_ID="${sub_vendor_id#0x}:${sub_device_id#0x}" pci_convert_vars pci_map_modules < $MAP_PCI } # # Given path to a device, determine all devices that # connect it to the core. For each device, determine # the type (PCI, USB); add directly required modules # to $ALL_DRIVERS. Ignores possible support modules. # phys_device() { path=$1 DRIVERS="" while [ "$path" != '/' ] do # echo "in $path" if [ -f "$path/subsystem_vendor" ] then # echo "PCI device: $path" pci_device $path fi if [ -f "$path/bDeviceClass" ] then # echo "USB device: $path" usb_device $path fi if [ -f "$path/bInterfaceClass" ] then # echo "USB interface: $path" usb_interface $path fi path=`dirname $path` done echo "needs: $DRIVERS" ALL_DRIVERS="$ALL_DRIVERS $DRIVERS" } # # Iterate over all block devices, # return a list of required drivers in $ALL_DRIVERS. # A block device may require multiple drivers, # eg when an USB stick is only reachable via an USB controller on a PCI bus. # Duplicates possible # block_devs() { ALL_DRIVERS="" for blockdev in $SYSFS/block/* do if [ -e $blockdev/device ] then device=`readlink -f $blockdev/device` # ignore partitions for now; # this is a nice place to filter for root and swap. devno=$(cat $blockdev/dev) echo =============== $blockdev echo "devno $devno at: $device" phys_device $device fi done } # # Given a list of modules, make $ALL_FILES a list of files # containing those modules. # find_files() { ALL_FILES="" for DRIVER in $* do FILE=$(find $MODULE_DIR/kernel/drivers -name "$DRIVER.ko" -print) ALL_FILES="$ALL_FILES $FILE" done } # # Given a filename, make $PRE_REQ a list of all files that must # be loaded into the kernel before the file can be loaded # succesfully. # find_dep() { FILE=$1 while read KEY REST do if [ "$KEY" = "$FILE:" ] then # echo ++++++++++ BINGO: $REST PRE_REQ="$REST" break fi done < $MODULE_DIR/modules.dep } # # Given a list of filenames, add before each file X a list # of all files that need to be loaded before X; return # list in $THE_WORKS. # find_dependencies() { THE_WORKS="" for FILE in $* do find_dep $FILE THE_WORKS="$THE_WORKS $PRE_REQ $FILE" done } # # Given a list of filenames, make a list where only # the first occurence of each element is present; # return in $THE_ESSENTIALS. # prune() { THE_ESSENTIALS="" for FILE in $* do BASE=`basename $FILE` BASE=${BASE%.ko} BASE=$(echo $BASE | sed -e 's/-/_/') eval "SEEN=\$SEEN_$BASE" if [ "$SEEN" = "" ] then THE_ESSENTIALS="$THE_ESSENTIALS $FILE" fi eval "SEEN_$BASE=1" done } main() { if [ "$1" != "" ] then VERSION="$1" else VERSION=`uname -r` fi SYSFS=/sys MODULE_DIR=/lib/modules/$VERSION MAP_USB=$MODULE_DIR/modules.usbmap MAP_PCI=$MODULE_DIR/modules.pcimap block_devs find_files $ALL_DRIVERS find_dependencies $ALL_FILES prune $THE_WORKS echo "recommended module load order:" for f in $THE_ESSENTIALS do echo " $f" done } main $*