You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

332 lines
9.0 KiB

  1. #!/bin/sh
  2. # Copyright (C) 2016 Velocloud Inc
  3. # Copyright (C) 2016 Aleksander Morgado <aleksander@aleksander.es>
  4. ################################################################################
  5. . /lib/functions.sh
  6. . /lib/netifd/netifd-proto.sh
  7. ################################################################################
  8. # Runtime state
  9. MODEMMANAGER_RUNDIR="/var/run/modemmanager"
  10. MODEMMANAGER_PID_FILE="${MODEMMANAGER_RUNDIR}/modemmanager.pid"
  11. MODEMMANAGER_CDCWDM_CACHE="${MODEMMANAGER_RUNDIR}/cdcwdm.cache"
  12. MODEMMANAGER_SYSFS_CACHE="${MODEMMANAGER_RUNDIR}/sysfs.cache"
  13. MODEMMANAGER_EVENTS_CACHE="${MODEMMANAGER_RUNDIR}/events.cache"
  14. ################################################################################
  15. # Common logging
  16. mm_log() {
  17. logger -t "ModemManager" "hotplug: $*"
  18. }
  19. ################################################################################
  20. # Receives as input argument the full sysfs path of the device
  21. # Returns the physical device sysfs path
  22. #
  23. # NOTE: this method only works when the device exists, i.e. it cannot be used
  24. # on removal hotplug events
  25. mm_find_physdev_sysfs_path() {
  26. local tmp_path="$1"
  27. while true; do
  28. tmp_path=$(dirname "${tmp_path}")
  29. # avoid infinite loops iterating
  30. [ -z "${tmp_path}" ] || [ "${tmp_path}" = "/" ] && return
  31. # the physical device will be that with a idVendor and idProduct pair of files
  32. [ -f "${tmp_path}"/idVendor ] && [ -f "${tmp_path}"/idProduct ] && {
  33. tmp_path=$(readlink -f "$tmp_path")
  34. echo "${tmp_path}"
  35. return
  36. }
  37. done
  38. }
  39. ################################################################################
  40. # Returns the cdc-wdm name retrieved from sysfs
  41. mm_track_cdcwdm() {
  42. local wwan="$1"
  43. local cdcwdm
  44. cdcwdm=$(ls "/sys/class/net/${wwan}/device/usbmisc/")
  45. [ -n "${cdcwdm}" ] || return
  46. # We have to cache it for later, as we won't be able to get the
  47. # associated cdc-wdm device on a remove event
  48. echo "${wwan} ${cdcwdm}" >> "${MODEMMANAGER_CDCWDM_CACHE}"
  49. echo "${cdcwdm}"
  50. }
  51. # Returns the cdc-wdm name retrieved from the cache
  52. mm_untrack_cdcwdm() {
  53. local wwan="$1"
  54. local cdcwdm
  55. # Look for the cached associated cdc-wdm device
  56. [ -f "${MODEMMANAGER_CDCWDM_CACHE}" ] || return
  57. cdcwdm=$(awk -v wwan="${wwan}" '!/^#/ && $0 ~ wwan { print $2 }' "${MODEMMANAGER_CDCWDM_CACHE}")
  58. [ -n "${cdcwdm}" ] || return
  59. # Remove from cache
  60. sed -i "/${wwan} ${cdcwdm}/d" "${MODEMMANAGER_CDCWDM_CACHE}"
  61. echo "${cdcwdm}"
  62. }
  63. ################################################################################
  64. # ModemManager needs some time from the ports being added until a modem object
  65. # is exposed in DBus. With the logic here we do an explicit wait of N seconds
  66. # for ModemManager to expose the new modem object, making sure that the wait is
  67. # unique per device (i.e. per physical device sysfs path).
  68. # Gets the modem wait status as retrieved from the cache
  69. mm_get_modem_wait_status() {
  70. local sysfspath="$1"
  71. # If no sysfs cache file, we're done
  72. [ -f "${MODEMMANAGER_SYSFS_CACHE}" ] || return
  73. # Get status of the sysfs path
  74. awk -v sysfspath="${sysfspath}" '!/^#/ && $0 ~ sysfspath { print $2 }' "${MODEMMANAGER_SYSFS_CACHE}"
  75. }
  76. # Clear the modem wait status from the cache, if any
  77. mm_clear_modem_wait_status() {
  78. local sysfspath="$1"
  79. local escaped_sysfspath
  80. [ -f "${MODEMMANAGER_SYSFS_CACHE}" ] && {
  81. # escape '/', '\' and '&' for sed...
  82. escaped_sysfspath=$(echo "$sysfspath" | sed -e 's/[\/&]/\\&/g')
  83. sed -i "/${escaped_sysfspath}/d" "${MODEMMANAGER_SYSFS_CACHE}"
  84. }
  85. }
  86. # Sets the modem wait status in the cache
  87. mm_set_modem_wait_status() {
  88. local sysfspath="$1"
  89. local status="$2"
  90. # Remove sysfs line before adding the new one with the new state
  91. mm_clear_modem_wait_status "${sysfspath}"
  92. # Add the new status
  93. echo "${sysfspath} ${status}" >> "${MODEMMANAGER_SYSFS_CACHE}"
  94. }
  95. # Callback for config_foreach()
  96. mm_get_modem_config_foreach_cb() {
  97. local cfg="$1"
  98. local sysfspath="$2"
  99. local proto
  100. config_get proto "${cfg}" proto
  101. [ "${proto}" = modemmanager ] || return 0
  102. local dev
  103. dev=$(uci_get network "${cfg}" device)
  104. [ "${dev}" = "${sysfspath}" ] || return 0
  105. echo "${cfg}"
  106. }
  107. # Returns the name of the interface configured for this device
  108. mm_get_modem_config() {
  109. local sysfspath="$1"
  110. # Look for configuration for the given sysfs path
  111. config_load network
  112. config_foreach mm_get_modem_config_foreach_cb interface "${sysfspath}"
  113. }
  114. # Wait for a modem in the specified sysfspath
  115. mm_wait_for_modem() {
  116. local cfg="$1"
  117. local sysfspath="$2"
  118. # TODO: config max wait
  119. local n=45
  120. local step=5
  121. while [ $n -ge 0 ]; do
  122. [ -d "${sysfspath}" ] || {
  123. mm_log "error: ignoring modem detection request: no device at ${sysfspath}"
  124. proto_set_available "${cfg}" 0
  125. return 1
  126. }
  127. # Check if the modem exists at the given sysfs path
  128. if ! mmcli -m "${sysfspath}" > /dev/null 2>&1
  129. then
  130. mm_log "error: modem not detected at sysfs path"
  131. else
  132. mm_log "modem exported successfully at ${sysfspath}"
  133. mm_log "setting interface '${cfg}' as available"
  134. proto_set_available "${cfg}" 1
  135. return 0
  136. fi
  137. sleep $step
  138. n=$((n-step))
  139. done
  140. mm_log "error: timed out waiting for the modem to get exported at ${sysfspath}"
  141. proto_set_available "${cfg}" 0
  142. return 2
  143. }
  144. mm_report_modem_wait() {
  145. local sysfspath=$1
  146. local parent_sysfspath status
  147. parent_sysfspath=$(mm_find_physdev_sysfs_path "$sysfspath")
  148. [ -n "${parent_sysfspath}" ] || {
  149. mm_log "error: parent device sysfspath not found"
  150. return
  151. }
  152. status=$(mm_get_modem_wait_status "${parent_sysfspath}")
  153. case "${status}" in
  154. "")
  155. local cfg
  156. cfg=$(mm_get_modem_config "${parent_sysfspath}")
  157. if [ -n "${cfg}" ]; then
  158. mm_log "interface '${cfg}' is set to configure device '${parent_sysfspath}'"
  159. mm_log "now waiting for modem at sysfs path ${parent_sysfspath}"
  160. mm_set_modem_wait_status "${parent_sysfspath}" "processed"
  161. # Launch subshell for the explicit wait
  162. ( mm_wait_for_modem "${cfg}" "${parent_sysfspath}" ) > /dev/null 2>&1 &
  163. else
  164. mm_log "no need to wait for modem at sysfs path ${parent_sysfspath}"
  165. mm_set_modem_wait_status "${parent_sysfspath}" "ignored"
  166. fi
  167. ;;
  168. "processed")
  169. mm_log "already waiting for modem at sysfs path ${parent_sysfspath}"
  170. ;;
  171. "ignored")
  172. ;;
  173. *)
  174. mm_log "error: unknown status read for device at sysfs path ${parent_sysfspath}"
  175. ;;
  176. esac
  177. }
  178. ################################################################################
  179. # Cleanup interfaces
  180. mm_cleanup_interface_cb() {
  181. local cfg="$1"
  182. local proto
  183. config_get proto "${cfg}" proto
  184. [ "${proto}" = modemmanager ] || return 0
  185. proto_set_available "${cfg}" 0
  186. }
  187. mm_cleanup_interfaces() {
  188. config_load network
  189. config_foreach mm_cleanup_interface_cb interface
  190. }
  191. mm_cleanup_interface_by_sysfspath() {
  192. local dev="$1"
  193. local cfg
  194. cfg=$(mm_get_modem_config "$dev")
  195. [ -n "${cfg}" ] || return
  196. mm_log "setting interface '$cfg' as unavailable"
  197. proto_set_available "${cfg}" 0
  198. }
  199. ################################################################################
  200. # Event reporting
  201. # Receives as input the action, the device name and the subsystem
  202. mm_report_event() {
  203. local action="$1"
  204. local name="$2"
  205. local subsystem="$3"
  206. local sysfspath="$4"
  207. # Track/untrack events in cache
  208. case "${action}" in
  209. "add")
  210. # On add events, store event details in cache (if not exists yet)
  211. grep -qs "${name},${subsystem}" "${MODEMMANAGER_EVENTS_CACHE}" || \
  212. echo "${action},${name},${subsystem},${sysfspath}" >> "${MODEMMANAGER_EVENTS_CACHE}"
  213. ;;
  214. "remove")
  215. # On remove events, remove old events from cache (match by subsystem+name)
  216. sed -i "/${name},${subsystem}/d" "${MODEMMANAGER_EVENTS_CACHE}"
  217. ;;
  218. esac
  219. # Report the event
  220. mm_log "event reported: action=${action}, name=${name}, subsystem=${subsystem}"
  221. mmcli --report-kernel-event="action=${action},name=${name},subsystem=${subsystem}" 1>/dev/null 2>&1 &
  222. # Wait for added modem if a sysfspath is given
  223. [ -n "${sysfspath}" ] && [ "$action" = "add" ] && mm_report_modem_wait "${sysfspath}"
  224. }
  225. mm_report_event_from_cache_line() {
  226. local event_line="$1"
  227. local action name subsystem sysfspath
  228. action=$(echo "${event_line}" | awk -F ',' '{ print $1 }')
  229. name=$(echo "${event_line}" | awk -F ',' '{ print $2 }')
  230. subsystem=$(echo "${event_line}" | awk -F ',' '{ print $3 }')
  231. sysfspath=$(echo "${event_line}" | awk -F ',' '{ print $4 }')
  232. mm_log "cached event found: action=${action}, name=${name}, subsystem=${subsystem}, sysfspath=${sysfspath}"
  233. mm_report_event "${action}" "${name}" "${subsystem}" "${sysfspath}"
  234. }
  235. mm_report_events_from_cache() {
  236. # Remove the sysfs cache
  237. rm -f "${MODEMMANAGER_SYSFS_CACHE}"
  238. local n=10
  239. local step=1
  240. local mmrunning=0
  241. # Wait for ModemManager to be available in the bus
  242. while [ $n -ge 0 ]; do
  243. sleep $step
  244. mm_log "checking if ModemManager is available..."
  245. if ! mmcli -L >/dev/null 2>&1
  246. then
  247. mm_log "ModemManager not yet available"
  248. else
  249. mmrunning=1
  250. break
  251. fi
  252. n=$((n-step))
  253. done
  254. [ ${mmrunning} -eq 1 ] || {
  255. mm_log "error: couldn't report initial kernel events: ModemManager not running"
  256. return
  257. }
  258. # Report cached kernel events
  259. while IFS= read -r event_line; do
  260. mm_report_event_from_cache_line "${event_line}"
  261. done < ${MODEMMANAGER_EVENTS_CACHE}
  262. }