modemmanager: replace modem available check on start with the new monitore service
authorFlorian Eckert <fe@dev.tdt.de>
Wed, 17 Jan 2024 10:07:25 +0000 (11:07 +0100)
committerFlorian Eckert <fe@dev.tdt.de>
Fri, 19 Jan 2024 07:47:34 +0000 (08:47 +0100)
Before this change, the status of the sysfs paths from the kernel events
was cached with a cache file. This is necessary to mark configured modems
as available for the netifd.

Using the new monitor service via the mmcli command 'mmcli -M' simplifies
the whole process. There is no need to start sub shells in the background
anymore that monitors whether the modem has already been added to the
ModemManager.

For this purpose, a new service was added that reacts on add and remove
events for modems in the ModemManager and, if necessary, marks the logical
netifd interface as available.

Signed-off-by: Florian Eckert <fe@dev.tdt.de>
net/modemmanager/Makefile
net/modemmanager/files/25-modemmanager-usb [deleted file]
net/modemmanager/files/modemmanager.common
net/modemmanager/files/modemmanager.init
net/modemmanager/files/usr/sbin/ModemManager-monitor [new file with mode: 0644]
net/modemmanager/files/usr/sbin/ModemManager-wrapper

index f327b148605042126e4062bf626ccb4630bf990f..77975781732216c94d7ca3e0d565d1cd9ed291cf 100644 (file)
@@ -92,6 +92,7 @@ define Package/modemmanager/install
        $(INSTALL_DIR) $(1)/usr/sbin
        $(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/sbin/ModemManager $(1)/usr/sbin
        $(INSTALL_BIN) ./files/usr/sbin/ModemManager-wrapper $(1)/usr/sbin
+       $(INSTALL_BIN) ./files/usr/sbin/ModemManager-monitor $(1)/usr/sbin
 
        $(INSTALL_DIR) $(1)/usr/bin
        $(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/bin/mmcli $(1)/usr/bin
@@ -118,9 +119,6 @@ define Package/modemmanager/install
        $(INSTALL_DIR) $(1)/etc/init.d
        $(INSTALL_BIN) ./files/modemmanager.init $(1)/etc/init.d/modemmanager
 
-       $(INSTALL_DIR) $(1)/etc/hotplug.d/usb
-       $(INSTALL_DATA) ./files/25-modemmanager-usb $(1)/etc/hotplug.d/usb
-
        $(INSTALL_DIR) $(1)/etc/hotplug.d/net
        $(INSTALL_DATA) ./files/25-modemmanager-net $(1)/etc/hotplug.d/net
 
diff --git a/net/modemmanager/files/25-modemmanager-usb b/net/modemmanager/files/25-modemmanager-usb
deleted file mode 100644 (file)
index 93d0bf7..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-#!/bin/sh
-# Copyright (C) 2019 Aleksander Morgado <aleksander@aleksander.es>
-
-# We need to process only full USB device removal events, we don't
-# want to process specific interface removal events.
-[ "$ACTION" = remove ] || exit
-[ -z "${INTERFACE}" ] || exit
-
-# Load common utilities
-. /usr/share/ModemManager/modemmanager.common
-
-mm_clear_modem_wait_status "/sys${DEVPATH}"
-mm_cleanup_interface_by_sysfspath "/sys${DEVPATH}"
index 4daffb2c1820187e35890b6d3d99672a6a21bf6f..b3f86db9acee5606b60b9e5a0aae17984a931c92 100644 (file)
@@ -13,7 +13,7 @@
 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_MONITOR_CACHE="${MODEMMANAGER_RUNDIR}/monitor.cache"
 MODEMMANAGER_EVENTS_CACHE="${MODEMMANAGER_RUNDIR}/events.cache"
 
 ################################################################################
@@ -92,48 +92,6 @@ mm_untrack_cdcwdm() {
        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"
@@ -155,112 +113,6 @@ mm_get_modem_config() {
        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_interfaces() {
-       local sysfs_path status
-
-       # Do nothing if there is no sysfs cache
-       [ -f "${MODEMMANAGER_SYSFS_CACHE}" ] || return
-
-       while IFS= read -r sysfs_cache_line; do
-               sysfs_path=$(echo "${sysfs_cache_line}" | awk '{print $1}')
-               status=$(echo "${sysfs_cache_line}" | awk '{print $2}')
-
-               if [ "${status}" = "processed" ]; then
-                       mm_log "debug" "call cleanup for: ${sysfs_path}"
-                       mm_cleanup_interface_by_sysfspath "${sysfs_path}"
-               fi
-       done < ${MODEMMANAGER_SYSFS_CACHE}
-}
-
-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
 
@@ -295,13 +147,9 @@ mm_report_event() {
        # Report the event
        mm_log "debug" "Report event: action=${action}, name=${name}, subsystem=${subsystem}"
        result=$(mmcli --report-kernel-event="action=${action},name=${name},subsystem=${subsystem}" 2>&1)
-       if [ "$?" -eq "0" ]; then
-               # Wait for added modem if a sysfspath is given
-               [ -n "${sysfspath}" ] && [ "$action" = "add" ] && mm_report_modem_wait "${sysfspath}"
-       else
+       if [ "$?" -ne "0" ]; then
                mm_log "error" "Couldn't report kernel event: ${result}"
        fi
-
 }
 
 mm_report_event_from_cache_line() {
index a036d884da4f2c11d6253aec396b4d4d8d7f2ec6..ccc1953aeaf6e45ced3a9cd63ea6988167112c8e 100755 (executable)
@@ -21,11 +21,15 @@ start_service() {
        # wrapper script called '/usr/sbin/ModemManager-wrapper'.
        #
        . /usr/share/ModemManager/modemmanager.common
-       procd_open_instance
+       procd_open_instance "service"
        procd_set_param command /usr/sbin/ModemManager-wrapper
        procd_append_param command --log-level="$LOG_LEVEL"
        [ "$LOG_LEVEL" = "DEBUG" ] && procd_append_param command --debug
        procd_set_param respawn "${respawn_threshold:-3600}" "${respawn_timeout:-5}" "${respawn_retry:-5}"
        procd_set_param pidfile "${MODEMMANAGER_PID_FILE}"
        procd_close_instance
+       procd_open_instance "monitor"
+       procd_set_param command /usr/sbin/ModemManager-monitor
+       procd_set_param respawn "${respawn_threshold:-3600}" "${respawn_timeout:-5}" "${respawn_retry:-5}"
+       procd_close_instance
 }
diff --git a/net/modemmanager/files/usr/sbin/ModemManager-monitor b/net/modemmanager/files/usr/sbin/ModemManager-monitor
new file mode 100644 (file)
index 0000000..8a88ab5
--- /dev/null
@@ -0,0 +1,155 @@
+#!/bin/sh
+
+. /lib/functions.sh
+. /lib/netifd/netifd-proto.sh
+. /usr/share/ModemManager/modemmanager.common
+
+trap_with_arg() {
+       func="$1" ; shift
+       for sig ; do
+               # shellcheck disable=SC2064
+               trap "$func $sig" "$sig"
+       done
+}
+
+func_trap() {
+       local monitor_cache_line object
+
+       logger "ModemManager-monitor[$$]" "Sending signal ${1} ..."
+
+       # Set all configured logical interfaces to unavailable
+       while IFS= read -r monitor_cache_line; do
+               object=$(echo "${monitor_cache_line}" | awk '{print $1}')
+               mm_monitor_cache_remove "$object"
+       done < ${MODEMMANAGER_MONITOR_CACHE}
+
+       kill "-${1}" "$CHILD" 2>/dev/null
+}
+
+mm_monitor_get_sysfspath() {
+       local object="$1"
+
+       # If no monitor cache file, we're done
+       [ -f "${MODEMMANAGER_MONITOR_CACHE}" ] || return
+
+       awk -v object="${object}" '!/^#/ && $0 ~ object { print $2 }' "${MODEMMANAGER_MONITOR_CACHE}"
+}
+
+mm_monitor_cache_remove() {
+       local object="$1"
+
+       local device cfg
+
+       device=$(mm_monitor_get_sysfspath "${object}")
+
+       cfg=$(mm_get_modem_config "${device}")
+       if [ -n "${cfg}" ]; then
+               mm_log "debug" "interface '${cfg}' set '${device}' state unavailable"
+               proto_set_available "${cfg}" 0
+       fi
+
+       mm_log "debug" "delete object '$object' from monitore cache"
+
+       # On monitor remove event, remove old events from cache
+       # Also substitute object path '/org/freedesktop/ModemManager1/Modem/<number>'
+       # all '/' with '\/' to make sed happy with shell expansion
+       sed -i "/${object//\//\\/}/d" "${MODEMMANAGER_MONITOR_CACHE}"
+}
+
+mm_monitor_cache_add() {
+       local object="$1"
+       local modemstatus device sysfspath cfg
+
+       modemstatus="$(mmcli --modem="${object}" --output-keyvalue)"
+
+       device=$(modemmanager_get_field "${modemstatus}" "modem.generic.device")
+       [ -n "${device}" ] || {
+               mm_log "err" "No 'device' for object '$object' not found..."
+               return 1
+       }
+
+       sysfspath=$(modemmanager_get_field "${modemstatus}" "modem.generic.physdev")
+       [ -n "${sysfspath}" ] || {
+               mm_log "err" "No 'sysfspath' for object '$object' not found..."
+               return 2
+       }
+
+       mm_log "debug" "add object '$object' to monitore cache (device=${device},sysfspath=${sysfspath})"
+
+       # On monitor add event, store event details in cache (if not exists yet)
+       grep -qs "${sysfspath}" "${MODEMMANAGER_MONITOR_CACHE}" || \
+               echo "${object} ${device} ${sysfspath}" >> "${MODEMMANAGER_MONITOR_CACHE}"
+
+       cfg=$(mm_get_modem_config "${device}")
+       if [ -n "${cfg}" ]; then
+               mm_log "info" "interface '${cfg}' set '${device}' state available"
+               proto_set_available "${cfg}" 1
+       fi
+}
+
+mm_monitor_cache_del() {
+       local object="$1"
+
+       mm_monitor_cache_remove "$object"
+}
+
+mm_monitor_cache() {
+       local line="$1"
+       local event object modemstatus device pyhsdev
+
+       event="$(echo "$line" | cut -d " " -f 1)"
+       object="$(echo "$line" | cut -d " " -f 2)"
+
+       case "$event" in
+               "(+)")
+                       mm_monitor_cache_add "$object"
+                       ;;
+               "(-)")
+                       mm_monitor_cache_del "$object"
+                       ;;
+       esac
+}
+
+main() {
+
+       local n=60
+       local step=1
+       local mmrunning=0
+
+       trap_with_arg func_trap INT TERM KILL
+
+       mkdir -p "${MODEMMANAGER_RUNDIR}"
+       chmod 0755 "${MODEMMANAGER_RUNDIR}"
+
+       # 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 ! /usr/bin/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
+       }
+
+       /usr/bin/mmcli -M | {
+               local line
+               while read -r line; do
+                       mm_log "debug" "Monitor cache line: ${line}"
+                       mm_monitor_cache "$line"
+               done
+       } &
+       CHILD="$!"
+
+       wait $CHILD
+}
+
+main "$@"
index 5ca530b16550f0445dcedf3c9d7f8e21cb593c5d..b0f36c267aaca5c75374240bad50782d62b8cda3 100644 (file)
@@ -27,9 +27,6 @@ main() {
        mm_report_events_from_cache
 
        wait "$CHILD"
-
-       # Set all configured interfaces as unavailable
-       mm_cleanup_interfaces
 }
 
 main "$@"