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