#!/bin/bash

confdir=/etc/driverctl.d
bus=pci
probe=1
save=1
debug=0

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

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

function error()
{
    log $*
    exit 1
}

function usage()
{
    echo "Usage: driverctl [-v] [--noprobe] [--nosave] set-override <device> <driver>"
    echo "       driverctl [-v] [--noprobe] [--nosave] unset-override <device>"
    echo "       driverctl [-v] [--noprobe] load-override <device>"
    echo "       driverctl [-v] list-devices"
    exit 1
}

function unbind()
{
    if [ -L /sys/$devpath/driver ]; then
        debug "unbinding previous driver" $(basename $(readlink /sys/$devpath/driver))
        echo "$dev" > /sys/$devpath/driver/unbind
    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()
{
    for d in /sys/bus/$bus/devices/*; do
        if [ -f "$d/driver_override" ]; then
            echo -n "$(basename $d)"
            if [ -L "$d/driver" ]; then
                echo -n " $(basename $(readlink $d/driver))"
            else
                echo -n " (none)"
            fi
            if ! grep -q "(null)" "$d/driver_override"; then
                echo -n " [*]"
            fi

            if [ $debug -ne 0 ]; then
                echo -n " ($(udevadm info -q property $d | grep ID_MODEL_FROM_DATABASE | cut -d= -f2))"
            fi
            echo
        fi
    done
}

function set_override()
{
    if [ ! -f /sys/$devpath/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" > /sys/$devpath/driver_override

    if [ "$drv" != "none" ] && [ $probe -ne 0 ]; then 
        probe_driver
        if [ ! -L /sys/$devpath/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
        ;;
    --bus)
        bus=${2}
        shift
        ;;
    -h|--help|-*)
        usage
        ;;
    set-override)
        [ $# -ne 3 ] && usage
        cmd=$1
        dev=$2
        drv=$3
        break
        ;;
    load-override|unset-override)
        [ $# -ne 2 ] && usage
        cmd=$1
        dev=$2
        break
        ;;
    list-devices)
        list_devices
        exit 0
        ;;
    *)
        usage
        ;;
    esac
    shift
done

[ -n "$cmd" ] || usage

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"

case ${cmd} in
    load-override)
        if [ -s $confdir/$sddev ]; then
            drv=$(cat $confdir/$sddev)
	    set_override $dev $drv
        else
            exit 1
        fi
        ;;
    set-override)
        set_override $dev $drv
        [ "$save" -ne 0 ] && save_override
        ;;       
    unset-override)
        set_override $dev ""
        [ "$save" -ne 0 ] && save_override
        ;;
esac
