|
|
- #!/bin/sh
- # Copyright (C) 2016 Velocloud Inc
- # Copyright (C) 2016 Aleksander Morgado <aleksander@aleksander.es>
-
- ################################################################################
-
- . /lib/functions.sh
- . /lib/netifd/netifd-proto.sh
-
- ################################################################################
- # Runtime state
-
- MODEMMANAGER_RUNDIR="/var/run/modemmanager"
- MODEMMANAGER_PID_FILE="${MODEMMANAGER_RUNDIR}/modemmanager.pid"
- MODEMMANAGER_CDCWDM_CACHE="${MODEMMANAGER_RUNDIR}/cdcwdm.cache"
- MODEMMANAGER_SYSFS_CACHE="${MODEMMANAGER_RUNDIR}/sysfs.cache"
- MODEMMANAGER_EVENTS_CACHE="${MODEMMANAGER_RUNDIR}/events.cache"
-
- ################################################################################
- # Common logging
-
- mm_log() {
- local level="$1"; shift
- logger -p "daemon.${level}" -t "ModemManager[$$]" "hotplug: $*"
- }
-
- ################################################################################
- # Receives as input argument the full sysfs path of the device
- # Returns the physical device sysfs path
- #
- # NOTE: this method only works when the device exists, i.e. it cannot be used
- # on removal hotplug events
-
- mm_find_physdev_sysfs_path() {
- local tmp_path="$1"
-
- while true; do
- tmp_path=$(dirname "${tmp_path}")
-
- # avoid infinite loops iterating
- [ -z "${tmp_path}" ] || [ "${tmp_path}" = "/" ] && return
-
- # For USB devices, the physical device will be that with a idVendor
- # and idProduct pair of files
- [ -f "${tmp_path}"/idVendor ] && [ -f "${tmp_path}"/idProduct ] && {
- tmp_path=$(readlink -f "$tmp_path")
- echo "${tmp_path}"
- return
- }
-
- # For PCI devices, the physical device will be that with a vendor
- # and device pair of files
- [ -f "${tmp_path}"/vendor ] && [ -f "${tmp_path}"/device ] && {
- tmp_path=$(readlink -f "$tmp_path")
- echo "${tmp_path}"
- return
- }
- done
- }
-
- ################################################################################
-
- # Returns the cdc-wdm name retrieved from sysfs
- mm_track_cdcwdm() {
- local wwan="$1"
- local cdcwdm
-
- cdcwdm=$(ls "/sys/class/net/${wwan}/device/usbmisc/")
- [ -n "${cdcwdm}" ] || return
-
- # We have to cache it for later, as we won't be able to get the
- # associated cdc-wdm device on a remove event
- echo "${wwan} ${cdcwdm}" >> "${MODEMMANAGER_CDCWDM_CACHE}"
-
- echo "${cdcwdm}"
- }
-
- # Returns the cdc-wdm name retrieved from the cache
- mm_untrack_cdcwdm() {
- local wwan="$1"
- local cdcwdm
-
- # Look for the cached associated cdc-wdm device
- [ -f "${MODEMMANAGER_CDCWDM_CACHE}" ] || return
-
- cdcwdm=$(awk -v wwan="${wwan}" '!/^#/ && $0 ~ wwan { print $2 }' "${MODEMMANAGER_CDCWDM_CACHE}")
- [ -n "${cdcwdm}" ] || return
-
- # Remove from cache
- sed -i "/${wwan} ${cdcwdm}/d" "${MODEMMANAGER_CDCWDM_CACHE}"
-
- echo "${cdcwdm}"
- }
-
- ################################################################################
- # ModemManager needs some time from the ports being added until a modem object
- # is exposed in DBus. With the logic here we do an explicit wait of N seconds
- # for ModemManager to expose the new modem object, making sure that the wait is
- # unique per device (i.e. per physical device sysfs path).
-
- # Gets the modem wait status as retrieved from the cache
- mm_get_modem_wait_status() {
- local sysfspath="$1"
-
- # If no sysfs cache file, we're done
- [ -f "${MODEMMANAGER_SYSFS_CACHE}" ] || return
-
- # Get status of the sysfs path
- awk -v sysfspath="${sysfspath}" '!/^#/ && $0 ~ sysfspath { print $2 }' "${MODEMMANAGER_SYSFS_CACHE}"
- }
-
- # Clear the modem wait status from the cache, if any
- mm_clear_modem_wait_status() {
- local sysfspath="$1"
-
- local escaped_sysfspath
-
- [ -f "${MODEMMANAGER_SYSFS_CACHE}" ] && {
- # escape '/', '\' and '&' for sed...
- escaped_sysfspath=$(echo "$sysfspath" | sed -e 's/[\/&]/\\&/g')
- sed -i "/${escaped_sysfspath}/d" "${MODEMMANAGER_SYSFS_CACHE}"
- }
- }
-
- # Sets the modem wait status in the cache
- mm_set_modem_wait_status() {
- local sysfspath="$1"
- local status="$2"
-
- # Remove sysfs line before adding the new one with the new state
- mm_clear_modem_wait_status "${sysfspath}"
-
- # Add the new status
- echo "${sysfspath} ${status}" >> "${MODEMMANAGER_SYSFS_CACHE}"
- }
-
- # Callback for config_foreach()
- mm_get_modem_config_foreach_cb() {
- local cfg="$1"
- local sysfspath="$2"
-
- local proto
- config_get proto "${cfg}" proto
- [ "${proto}" = modemmanager ] || return 0
-
- local dev
- dev=$(uci_get network "${cfg}" device)
- [ "${dev}" = "${sysfspath}" ] || return 0
-
- echo "${cfg}"
- }
-
- # Returns the name of the interface configured for this device
- mm_get_modem_config() {
- local sysfspath="$1"
-
- # Look for configuration for the given sysfs path
- config_load network
- config_foreach mm_get_modem_config_foreach_cb interface "${sysfspath}"
- }
-
- # Wait for a modem in the specified sysfspath
- mm_wait_for_modem() {
- local cfg="$1"
- local sysfspath="$2"
-
- # TODO: config max wait
- local n=45
- local step=5
-
- while [ $n -ge 0 ]; do
- [ -d "${sysfspath}" ] || {
- mm_log "error" "ignoring modem detection request: no device at ${sysfspath}"
- proto_set_available "${cfg}" 0
- return 1
- }
-
- # Check if the modem exists at the given sysfs path
- if ! mmcli -m "${sysfspath}" > /dev/null 2>&1
- then
- mm_log "error" "modem not detected at sysfs path"
- else
- mm_log "info" "modem exported successfully at ${sysfspath}"
- mm_log "info" "setting interface '${cfg}' as available"
- proto_set_available "${cfg}" 1
- return 0
- fi
-
- sleep $step
- n=$((n-step))
- done
-
- mm_log "error" "timed out waiting for the modem to get exported at ${sysfspath}"
- proto_set_available "${cfg}" 0
- return 2
- }
-
- mm_report_modem_wait() {
- local sysfspath=$1
-
- local parent_sysfspath status
-
- parent_sysfspath=$(mm_find_physdev_sysfs_path "$sysfspath")
- [ -n "${parent_sysfspath}" ] || {
- mm_log "error" "parent device sysfspath not found"
- return
- }
-
- status=$(mm_get_modem_wait_status "${parent_sysfspath}")
- case "${status}" in
- "")
- local cfg
-
- cfg=$(mm_get_modem_config "${parent_sysfspath}")
- if [ -n "${cfg}" ]; then
- mm_log "info" "interface '${cfg}' is set to configure device '${parent_sysfspath}'"
- mm_log "info" "now waiting for modem at sysfs path ${parent_sysfspath}"
- mm_set_modem_wait_status "${parent_sysfspath}" "processed"
- # Launch subshell for the explicit wait
- ( mm_wait_for_modem "${cfg}" "${parent_sysfspath}" ) > /dev/null 2>&1 &
- else
- mm_log "info" "no need to wait for modem at sysfs path ${parent_sysfspath}"
- mm_set_modem_wait_status "${parent_sysfspath}" "ignored"
- fi
- ;;
- "processed")
- mm_log "info" "already waiting for modem at sysfs path ${parent_sysfspath}"
- ;;
- "ignored")
- ;;
- *)
- mm_log "error" "unknown status read for device at sysfs path ${parent_sysfspath}"
- ;;
- esac
- }
-
- ################################################################################
- # Cleanup interfaces
-
- mm_cleanup_interface_cb() {
- local cfg="$1"
-
- local proto
- config_get proto "${cfg}" proto
- [ "${proto}" = modemmanager ] || return 0
-
- proto_set_available "${cfg}" 0
- }
-
- mm_cleanup_interfaces() {
- config_load network
- config_foreach mm_cleanup_interface_cb interface
- }
-
- mm_cleanup_interface_by_sysfspath() {
- local dev="$1"
-
- local cfg
- cfg=$(mm_get_modem_config "$dev")
- [ -n "${cfg}" ] || return
-
- mm_log "info" "setting interface '$cfg' as unavailable"
- proto_set_available "${cfg}" 0
- }
-
- ################################################################################
- # Event reporting
-
- # Receives as input the action, the device name and the subsystem
- mm_report_event() {
- local action="$1"
- local name="$2"
- local subsystem="$3"
- local sysfspath="$4"
-
- # Track/untrack events in cache
- case "${action}" in
- "add")
- # On add events, store event details in cache (if not exists yet)
- grep -qs "${name},${subsystem}" "${MODEMMANAGER_EVENTS_CACHE}" || \
- echo "${action},${name},${subsystem},${sysfspath}" >> "${MODEMMANAGER_EVENTS_CACHE}"
- ;;
- "remove")
- # On remove events, remove old events from cache (match by subsystem+name)
- sed -i "/${name},${subsystem}/d" "${MODEMMANAGER_EVENTS_CACHE}"
- ;;
- esac
-
- # Report the event
- mm_log "debug" "event reported: action=${action}, name=${name}, subsystem=${subsystem}"
- mmcli --report-kernel-event="action=${action},name=${name},subsystem=${subsystem}" 1>/dev/null 2>&1 &
-
- # Wait for added modem if a sysfspath is given
- [ -n "${sysfspath}" ] && [ "$action" = "add" ] && mm_report_modem_wait "${sysfspath}"
- }
-
- mm_report_event_from_cache_line() {
- local event_line="$1"
-
- local action name subsystem sysfspath
- action=$(echo "${event_line}" | awk -F ',' '{ print $1 }')
- name=$(echo "${event_line}" | awk -F ',' '{ print $2 }')
- subsystem=$(echo "${event_line}" | awk -F ',' '{ print $3 }')
- sysfspath=$(echo "${event_line}" | awk -F ',' '{ print $4 }')
-
- mm_log "debug" "cached event found: action=${action}, name=${name}, subsystem=${subsystem}, sysfspath=${sysfspath}"
- mm_report_event "${action}" "${name}" "${subsystem}" "${sysfspath}"
- }
-
- mm_report_events_from_cache() {
- # Remove the sysfs cache
- rm -f "${MODEMMANAGER_SYSFS_CACHE}"
-
- local n=60
- local step=1
- local mmrunning=0
-
- # Wait for ModemManager to be available in the bus
- while [ $n -ge 0 ]; do
- sleep $step
- mm_log "info" "checking if ModemManager is available..."
-
- if ! mmcli -L >/dev/null 2>&1
- then
- mm_log "info" "ModemManager not yet available"
- else
- mmrunning=1
- break
- fi
- n=$((n-step))
- done
-
- [ ${mmrunning} -eq 1 ] || {
- mm_log "error" "couldn't report initial kernel events: ModemManager not running"
- return
- }
-
- # Report cached kernel events
- while IFS= read -r event_line; do
- mm_report_event_from_cache_line "${event_line}"
- done < ${MODEMMANAGER_EVENTS_CACHE}
- }
|