Blob Blame History Raw
#!/bin/bash

confdir=/etc/driverctl.d
bus=${SUBSYSTEM:-pci}
probe=1
save=1
debug=0

declare -A devclasses
devclasses=(["all"]=""
            ["storage"]="01"
            ["network"]="02"
            ["display"]="03"
            ["multimedia"]="04"
            ["memory"]="05"
            ["bridge"]="06"
            ["communication"]="07"
            ["system"]="08"
            ["input"]="09"
            ["docking"]="0a"
            ["processor"]="0b"
            ["serial"]="0c"
)

function log()
{
    echo "driverctl: $*" >&2
}

function debug()
{
    [ "$debug" -ne 0 ] && log "$@"
}

function error()
{
    log "$@"
    exit 1
}

function usage()
{
    echo "Usage: driverctl [OPTIONS...] {COMMAND}..."
    echo
    echo "Inspect or control default device driver bindings."
    echo
    echo "Supported commands:"
    echo "  set-override <device> <driver>    Make <driver> the default driver"
    echo "                                    for <device>"
    echo "  unset-override <device>           Remove any override for <device>"
    echo "  load-override <device>            Load an override previously specified"
    echo "                                    for <device>"
    echo "  list-devices                      List all overridable devices"
    echo "  list-overrides                    List all currently specified overrides"
    echo
    echo "Supported options:"
    echo " -h --help             Show this help"
    echo " -v --verbose --debug  Show verbose debug information"
    echo " -b --bus <bus>        Work on bus <bus> (default pci)"
    echo "    --noprobe          Do not reprobe when setting, unsetting, or"
    echo "                       loading an override"
    echo "    --nosave           Do not save changes when setting or unsetting"
    echo "                       an override"
    echo ""
}

function unbind()
{
    if [ -L "$syspath/driver" ]; then
        debug "unbinding previous driver $(basename "$(readlink "$syspath/driver")")"
        if ! echo "$dev" > "$syspath/driver/unbind"; then
            error "unbinding $dev failed"
        fi
    else
        debug "device $dev not bound" 
    fi
}

function probe_driver()
{
    debug "reprobing driver for $dev"
    echo "$dev" > "/sys/bus/$bus/drivers_probe"
}

function save_override()
{
    debug "saving driver override for $dev"
    if [ -n "$drv" ]; then
	[ -d "$confdir" ] || mkdir -p "$confdir"
        echo "$drv" > "$confdir/$sddev"
    else
        rm -f "$confdir/$sddev"
    fi
}

function list_devices()
{
    devices=()
    for d in "/sys/bus/$bus/devices"/*; do
        if [ -f "$d/driver_override" ]; then
            override="$(< "$d/driver_override")"
            if [ "$1" -eq 1 ] && [ "$override" == "(null)" ]; then
                continue
            fi
          
            line="$(basename "$d")"
            devices+=("$line")

            if [ -n "$2" ]; then
                class="$(< "$d/class")"
                [ "$2" == "${class:2:2}" ] || continue
            fi
            if [ -L "$d/driver" ]; then
                line+=" $(basename "$(readlink "$d/driver")")"
            else
                line+=" (none)"
            fi
            if [ "$1" -ne 1 ] && [ "$override" != "(null)" ]; then
                line+=" [*]"
            fi

            if [ $debug -ne 0 ]; then
                line+=" ($(udevadm info -q property "$d" | grep ID_MODEL_FROM_DATABASE | cut -d= -f2))"
            fi
            echo "$line"
        fi
    done
    if [ ${#devices[@]} -eq 0 ]; then
        error "No overridable devices found. Kernel too old?"
    fi
}

function set_override()
{
    if [ ! -f "$syspath/driver_override" ]; then
        error "device does not support driver override: $dev"
    fi
    if [ -n "$drv" ] && [ "$drv" != "none" ]; then
        debug "setting driver override for $dev: $drv"
        if [ ! -d "/sys/module/$drv" ]; then
            debug "loading driver $drv"
            /sbin/modprobe -q "$drv" || error "no such module: $drv"
        fi
    else
        debug "unsetting driver override for $dev"
    fi
    unbind
    echo "$drv" > "$syspath/driver_override"

    if [ "$drv" != "none" ] && [ $probe -ne 0 ]; then 
        probe_driver
        if [ ! -L "$syspath/driver" ]; then
            error "failed to bind device $dev to driver $drv"
        fi
    fi
}

while (($# > 0)); do
    case ${1} in
    --noprobe)
        probe=0
        ;;
    --nosave)
        save=0
        ;;
    --debug|--verbose|-v)
        debug=1
        ;;
    -b|--bus)
        bus=${2}
        shift
        ;;
    -h|--help|-*)
        usage
        exit 0
        ;;
    set-override)
        if [ $# -ne 3 ]; then
            usage
            exit 1
        fi

        cmd=$1
        dev=$2
        drv=$3
        break
        ;;
    load-override|unset-override)
        if [ $# -ne 2 ]; then
           usage
           exit 1
        fi

        cmd=$1
        dev=$2
        break
        ;;
    list-devices|list-overrides)
        if [ $# -gt 2 ]; then
            usage
            exit 1
        fi

        if [ -n "$2" ] && [ ! "${devclasses[$2]+_}" ]; then
            error "device type must be one of: ${!devclasses[*]}"
        fi
        cmd=$1
        devtype="${devclasses[${2:-all}]}"
        break
        ;;
    *)
        usage
        exit 1
        ;;
    esac
    shift
done

if [ -z "$cmd" ]; then
    usage
    exit 1
fi

if [ -n "$dev" ]; then
        case ${dev} in
        */*)
            bus=${dev%%/*}
            dev=${dev#*/}
            ;;
        esac
        if [ -n "${DEVPATH:-}" ]; then
            devpath="$DEVPATH"
        else
            devlink="/sys/bus/$bus/devices/$dev"
            [ -L "$devlink" ] || error "no such device: $dev"
            devpath=$(realpath "$devlink" | cut -c5-)
        fi
        syspath="/sys/$devpath"
        sddev="$bus-$dev"
fi

case ${cmd} in
    load-override)
        if [ -s "$confdir/$sddev" ]; then
            drv=$(< "$confdir/$sddev")
	    set_override "$dev" "$drv"
        else
            exit 1
        fi
        ;;
    list-devices)
        list_devices 0 "$devtype"
        ;;
    list-overrides)
        list_devices 1 "$devtype"
        ;;
    set-override)
        set_override "$dev" "$drv"
        if [ $save -ne 0 ]; then
           save_override
        fi

        ;;       
    unset-override)
        set_override "$dev" ""
        if [ $save -ne 0 ]; then
           save_override
        fi
        ;;
esac