packages/net/modemmanager/files/modemmanager.common
Florian Eckert 45a56a8899 modemmanager: refactoring hotplug debug logging
The output of the hotplug is very chatty and floods the log with
messages that are not necessary in functioning operation.

So that the log can be filtered. A log level was added to each message
as the first opiton on mm_log function call.

In addition, the facility of the hotplug script has been set to daemon,
which in my view fits better than user.

Signed-off-by: Florian Eckert <fe@dev.tdt.de>
2021-12-14 09:17:34 +01:00

342 lines
9.4 KiB
Bash

#!/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}
}