diff --git a/net/modemmanager/Makefile b/net/modemmanager/Makefile index f327b1486..c4c9e8af4 100644 --- a/net/modemmanager/Makefile +++ b/net/modemmanager/Makefile @@ -9,7 +9,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=modemmanager PKG_SOURCE_VERSION:=1.22.0 -PKG_RELEASE:=8 +PKG_RELEASE:=9 PKG_SOURCE_PROTO:=git PKG_SOURCE_URL:=https://gitlab.freedesktop.org/mobile-broadband/ModemManager.git @@ -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/10-report-down b/net/modemmanager/files/10-report-down index 88b010cf0..0ebe87de5 100755 --- a/net/modemmanager/files/10-report-down +++ b/net/modemmanager/files/10-report-down @@ -16,9 +16,8 @@ STATE="$4" [ "${STATE}" = "disconnected" ] || exit 0 -. /usr/share/ModemManager/modemmanager.common . /lib/netifd/netifd-proto.sh -INCLUDE_ONLY=1 . /lib/netifd/proto/modemmanager.sh +. /usr/share/ModemManager/modemmanager.common MODEM_STATUS=$(mmcli --modem="${MODEM_PATH}" --output-keyvalue) [ -n "${MODEM_STATUS}" ] || exit 1 diff --git a/net/modemmanager/files/25-modemmanager-usb b/net/modemmanager/files/25-modemmanager-usb deleted file mode 100644 index 93d0bf70a..000000000 --- a/net/modemmanager/files/25-modemmanager-usb +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/sh -# Copyright (C) 2019 Aleksander Morgado - -# 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}" diff --git a/net/modemmanager/files/modemmanager.common b/net/modemmanager/files/modemmanager.common index 2ba2036ce..ab5f92b98 100644 --- a/net/modemmanager/files/modemmanager.common +++ b/net/modemmanager/files/modemmanager.common @@ -6,7 +6,6 @@ . /lib/functions.sh . /lib/netifd/netifd-proto.sh -INCLUDE_ONLY=1 . /lib/netifd/proto/modemmanager.sh ################################################################################ # Runtime state @@ -14,7 +13,7 @@ INCLUDE_ONLY=1 . /lib/netifd/proto/modemmanager.sh 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" ################################################################################ @@ -22,6 +21,8 @@ MODEMMANAGER_EVENTS_CACHE="${MODEMMANAGER_RUNDIR}/events.cache" mm_log() { local level="$1"; shift + + [ "${level}" = "debug" ] && return logger -p "daemon.${level}" -t "ModemManager[$$]" "hotplug: $*" } @@ -93,48 +94,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" @@ -156,112 +115,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 @@ -296,13 +149,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() { @@ -351,3 +200,69 @@ mm_report_events_from_cache() { mm_report_event_from_cache_line "${event_line}" done < ${MODEMMANAGER_EVENTS_CACHE} } + +# This method expects as first argument a list of key-value pairs, as returned by mmcli --output-keyvalue +# The second argument must be exactly the name of the field to read +# +# Sample output: +# $ mmcli -m 0 -K +# modem.dbus-path : /org/freedesktop/ModemManager1/Modem/0 +# modem.generic.device-identifier : ed6eff2e3e0f90463da1c2a755b2acacd1335752 +# modem.generic.manufacturer : Dell Inc. +# modem.generic.model : DW5821e Snapdragon X20 LTE +# modem.generic.revision : T77W968.F1.0.0.4.0.GC.009\n026 +# modem.generic.carrier-configuration : GCF +# modem.generic.carrier-configuration-revision : 08E00009 +# modem.generic.hardware-revision : DW5821e Snapdragon X20 LTE +# .... +modemmanager_get_field() { + local list=$1 + local field=$2 + local value="" + + [ -z "${list}" ] || [ -z "${field}" ] && return + + # there is always at least a whitespace after each key, and we use that as part of the + # key matching we do (e.g. to avoid getting 'modem.generic.state-failed-reason' as a result + # when grepping for 'modem.generic.state'. + line=$(echo "${list}" | grep "${field} ") + value=$(echo ${line#*:}) + + # not found? + [ -n "${value}" ] || return 2 + + # only print value if set + [ "${value}" != "--" ] && echo "${value}" + return 0 +} + +# build a comma-separated list of values from the list +modemmanager_get_multivalue_field() { + local list=$1 + local field=$2 + local value="" + local length idx item + + [ -z "${list}" ] || [ -z "${field}" ] && return + + length=$(modemmanager_get_field "${list}" "${field}.length") + [ -n "${length}" ] || return 0 + [ "$length" -ge 1 ] || return 0 + + idx=1 + while [ $idx -le "$length" ]; do + item=$(modemmanager_get_field "${list}" "${field}.value\[$idx\]") + [ -n "${item}" ] && [ "${item}" != "--" ] && { + [ -n "${value}" ] && value="${value}, " + value="${value}${item}" + } + idx=$((idx + 1)) + done + + # nothing built? + [ -n "${value}" ] || return 2 + + # only print value if set + echo "${value}" + return 0 +} diff --git a/net/modemmanager/files/modemmanager.init b/net/modemmanager/files/modemmanager.init index a036d884d..ccc1953ae 100755 --- a/net/modemmanager/files/modemmanager.init +++ b/net/modemmanager/files/modemmanager.init @@ -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/modemmanager.proto b/net/modemmanager/files/modemmanager.proto index 671f0db15..b059b4c59 100755 --- a/net/modemmanager/files/modemmanager.proto +++ b/net/modemmanager/files/modemmanager.proto @@ -8,6 +8,7 @@ . /lib/functions.sh . ../netifd-proto.sh . ./ppp.sh + . /usr/share/ModemManager/modemmanager.common init_proto "$@" } @@ -24,72 +25,6 @@ cdr2mask () echo "${1-0}"."${2-0}"."${3-0}"."${4-0}" } -# This method expects as first argument a list of key-value pairs, as returned by mmcli --output-keyvalue -# The second argument must be exactly the name of the field to read -# -# Sample output: -# $ mmcli -m 0 -K -# modem.dbus-path : /org/freedesktop/ModemManager1/Modem/0 -# modem.generic.device-identifier : ed6eff2e3e0f90463da1c2a755b2acacd1335752 -# modem.generic.manufacturer : Dell Inc. -# modem.generic.model : DW5821e Snapdragon X20 LTE -# modem.generic.revision : T77W968.F1.0.0.4.0.GC.009\n026 -# modem.generic.carrier-configuration : GCF -# modem.generic.carrier-configuration-revision : 08E00009 -# modem.generic.hardware-revision : DW5821e Snapdragon X20 LTE -# .... -modemmanager_get_field() { - local list=$1 - local field=$2 - local value="" - - [ -z "${list}" ] || [ -z "${field}" ] && return - - # there is always at least a whitespace after each key, and we use that as part of the - # key matching we do (e.g. to avoid getting 'modem.generic.state-failed-reason' as a result - # when grepping for 'modem.generic.state'. - line=$(echo "${list}" | grep "${field} ") - value=$(echo ${line#*:}) - - # not found? - [ -n "${value}" ] || return 2 - - # only print value if set - [ "${value}" != "--" ] && echo "${value}" - return 0 -} - -# build a comma-separated list of values from the list -modemmanager_get_multivalue_field() { - local list=$1 - local field=$2 - local value="" - local length idx item - - [ -z "${list}" ] || [ -z "${field}" ] && return - - length=$(modemmanager_get_field "${list}" "${field}.length") - [ -n "${length}" ] || return 0 - [ "$length" -ge 1 ] || return 0 - - idx=1 - while [ $idx -le "$length" ]; do - item=$(modemmanager_get_field "${list}" "${field}.value\[$idx\]") - [ -n "${item}" ] && [ "${item}" != "--" ] && { - [ -n "${value}" ] && value="${value}, " - value="${value}${item}" - } - idx=$((idx + 1)) - done - - # nothing built? - [ -n "${value}" ] || return 2 - - # only print value if set - echo "${value}" - return 0 -} - modemmanager_cleanup_connection() { local modemstatus="$1" diff --git a/net/modemmanager/files/usr/sbin/ModemManager-monitor b/net/modemmanager/files/usr/sbin/ModemManager-monitor new file mode 100644 index 000000000..8a88ab514 --- /dev/null +++ b/net/modemmanager/files/usr/sbin/ModemManager-monitor @@ -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/' + # 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 "$@" diff --git a/net/modemmanager/files/usr/sbin/ModemManager-wrapper b/net/modemmanager/files/usr/sbin/ModemManager-wrapper index 97c2a826d..b0f36c267 100644 --- a/net/modemmanager/files/usr/sbin/ModemManager-wrapper +++ b/net/modemmanager/files/usr/sbin/ModemManager-wrapper @@ -1,5 +1,7 @@ #!/bin/sh +. /usr/share/ModemManager/modemmanager.common + trap_with_arg() { func="$1" ; shift for sig ; do @@ -14,8 +16,6 @@ func_trap() { } main() { - . /usr/share/ModemManager/modemmanager.common - trap_with_arg func_trap INT TERM KILL mkdir -p "${MODEMMANAGER_RUNDIR}" @@ -27,9 +27,6 @@ main() { mm_report_events_from_cache wait "$CHILD" - - # Set all configured interfaces as unavailable - mm_cleanup_interfaces } main "$@"