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.

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