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.

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