#!/bin/sh
# tlp - handle added usb devices
#
# Copyright (c) 2016 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_VENDORS="0bdb 05c6 1199"

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

    # 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=""
    chg=0

    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
        # wait for subdevices to populate
        sleep 0.5

        # check for hid subdevices
        for subdev in $usbdev/*:*; do
            if [ -d $subdev ] && [ "$(cat $subdev/bInterfaceClass)" = "03" ]; then
                control="on"
                exc="_hid_black"
                break
            fi
        done

        if [ -z "$exc" ]; then
            # check for wwan vendor ids
            USB_BLACKLIST_WWAN=${USB_BLACKLIST_WWAN:-1} # default is exclude

            if [ $USB_BLACKLIST_WWAN = "1" ]; then
                vendor="$(cat $usbdev/idVendor)"
                if wordinlist "$vendor" "$USB_WWAN_VENDORS"; then
                    control="on"
                    exc="_wwan_black"
                fi
            fi
        fi
    fi

    if [ -f $usbdev/power/control ]; then
        if [ "$(cat $usbdev/power/control)" != "$control" ]; then
            # Write actual changes only
            echo "$control" > $usbdev/power/control
            chg=1
        fi
    else
        # level is deprecated
        if [ "$(cat $usbdev/power/level)" != "$control" ]; then
            # Write actual changes only
            echo "$control" > $usbdev/power/level
            chg=1
        fi
        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]"
    elif [ $chg -eq 1 ]; then
        # default: change control but not autosuspend_delay, i.e. keep kernel default setting
        echo_debug "usb" "udev_usb.$control$exc: $busdev ID $usbid $usbdev [$ctrlf]"
    else
        # we didn't change anything actually
        echo_debug "usb" "udev_usb.$control$exc: $busdev ID $usbid $usbdev"
    fi
fi

exit 0
