#!/bin/sh
# tlp - handle added usb devices
#
# Copyright (c) 2015 Thomas Koch <linrunner at gmx.net>
# This software is licensed under the GPL v2 or later.

# Remark: the calling udev rule is triggered for "base" devices only,
#         not for the corresponding subdevices. 

# --- Constants
readonly LOGGER=logger

readonly USBD=/sys/bus/usb/devices
readonly USB_TIMEOUT=2
readonly USB_TIMEOUT_MS=2000
readonly USB_WWAN_DRIVERS="cdc_acm cdc_wdm cdc_ether hso qcserial sierra"
readonly DEFAULT_USB_DRIVER_BLACKLIST="usbhid"

readonly RUNDIR=/var/run/tlp
readonly USB_DONE=usb_done

readonly CONFFILE=/etc/default/tlp

# --- Subroutines
wordinlist () { # test if word in list
                # $1: word, $2: whitespace-separated list of words
    local word

    if [ -n "${1-}" ]; then
        for word in ${2-}; do
            [ "${word}" != "${1}" ] || return 0 # exact match
        done
    fi

    return 1 # no match
}

echo_debug () { # $1: tag; $2: msg; echo debug msg if tag matches
    if wordinlist "$1" "$TLP_DEBUG"; then
        $LOGGER -p debug -t "tlp[$$,$PPID]" "$2"
    fi
}

# --- MAIN

# read config
[ -f $CONFFILE ] || exit 0
. $CONFFILE

# exit if TLP or autosuspend disabled
[ "$TLP_ENABLE" = "1" ] && [ "$USB_AUTOSUSPEND" = "1" ] || exit 0

# USB autosuspend has two principal operation modes:
#
# Mode 1 (optional):
# - System startup is handled by tlp-functions:set_usb_suspend()
# - Startup completion is signaled by "flag file" $USB_DONE
# - Newly added devices are handled by this udev script
# - Mode 1 is enabled by the private config variable X_TLP_USB_MODE=1
#
# Mode 2 (default):
# - Everything - including system startup, but not shutdown - is handled by this udev script

# do exit if mode 1 and no startup completion flag
[ "$X_TLP_USB_MODE" = "1" ] && [ ! -f $RUNDIR/$USB_DONE ] && exit 0

# get args
usbdev=/sys$1

# handle device
if [ -f $usbdev/power/autosuspend ] || [ -f $usbdev/power/autosuspend_delay_ms ]; then
    # device is autosuspendable

    ( # run remainder in a detached subshell to avoid blocking udev,
      # close stdout/stderr
        exec 1> /dev/null 2>/dev/null

        # initialize driver blacklist from settings
        drv_bl=${USB_DRIVER_BLACKLIST:-$DEFAULT_USB_DRIVER_BLACKLIST}

        # add wwan driver blacklist if enabled
        USB_BLACKLIST_WWAN=${USB_BLACKLIST_WWAN:-1} # default is exclude

        if [ $USB_BLACKLIST_WWAN = "1" ]; then
            drv_bl="$drv_bl $USB_WWAN_DRIVERS"
        fi

        # wait for subdevices to populate via parallel udev events (not handled here)
        sleep 2.0

        # apply autosuspend
        ctrlf="control"
        autof="autosuspend_delay_ms"
        usbid="$(cat $usbdev/idVendor):$(cat $usbdev/idProduct)"
        busdev="Bus $(cat $usbdev/busnum) Dev $(cat $usbdev/devnum)"
        control="auto"
        exc=""

        if wordinlist "$usbid" "$USB_WHITELIST"; then
            # device is in whitelist -- whitelist always wins
            control="auto"
            exc="_dev_white"
        elif wordinlist "$usbid" "$USB_BLACKLIST"; then
            # device is in blacklist
            control="on"
            exc="_dev_black"
        else
            # check subdevices for blacklisted drivers
            for subdev in $usbdev/*:*; do
                # get driver name from subdev uevent file
                drv=$(sed -rn 's/^DRIVER=(.*)/\1/p' $subdev/uevent)

                # check against driver blacklist
                if wordinlist "$drv" "$drv_bl"; then
                    # driver is blacklisted
                    control="on"
                    exc="_drv_black"
                    break
                fi
            done
        fi

        if [ -f $usbdev/power/control ]; then
            echo "$control" > $usbdev/power/control
        else
            # level is deprecated
            echo "$control" > $usbdev/power/level
            ctrlf="level"
        fi

        if [ "$X_TLP_USB_SET_AUTOSUSPEND_DELAY" = "1" ]; then
            # set autosuspend_delay
            if [ -f $usbdev/power/autosuspend_delay_ms ]; then
                echo $USB_TIMEOUT_MS > $usbdev/power/autosuspend_delay_ms
            else
                # autosuspend is deprecated
                echo $USB_TIMEOUT > $usbdev/power/autosuspend
                autof="autosuspend"
            fi
            echo_debug "usb" "udev_usb.$control$exc: $busdev ID $usbid $usbdev [$ctrlf $autof]"
        else
            # default: do not change autosuspend_delay, i.e. keep kernel default setting
            echo_debug "usb" "udev_usb.$control$exc: $busdev ID $usbid $usbdev [$ctrlf]"
        fi

        exit 0
    ) &
fi

exit 0
