Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 66 additions & 18 deletions third_party/openthread-br/files/otbr-rcp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@
# Additional an RCP selector string can be specified. This can be
# - "any" (the default) matches any eligible device
# - <B>-<P>, e.g. "1-1.2.3" matches a specific bus/port path
# Naming a bus/port path waives the OpenThread product string requirement, so
# that dongles which do not identify themselves as OpenThread devices can be
# used. Such a device is never flashed: firmware is only installed on, or
# updated for, devices a firmware handler recognizes.
# If the --wait option is given, the script will wait for a
# suitable device to be hot-plugged if necessary.
#
Expand Down Expand Up @@ -56,21 +60,23 @@ main() {
done

local rcp=any
if [ $# -gt 0 -a "$1" != "--" ]; then
if [ $# -gt 0 ] && [ "$1" != "--" ]; then
rcp="$1"
validate_rcp_selector "$rcp" || usage
shift
fi

local launch=
if [ $# -gt 0 ]; then
[ "$1" = "--" -a $# -gt 1 ] || usage
[ "$1" = "--" ] && [ $# -gt 1 ] || usage
launch=1
shift
fi

# Find and/or wait for the RCP device
[ -z "$update" ] || load_firmware_handlers
# Find and/or wait for the RCP device. The firmware handlers are loaded
# even when no update was asked for: recognizing a device is what tells
# a boot loader apart from an RCP, so selection needs them too.
load_firmware_handlers
if [ -n "$wait" ]; then
log debug "Looking for RCP devices matching selector '$rcp'"
hotplug_wait find_rcp "$rcp" "$update"
Expand All @@ -81,7 +87,7 @@ main() {
local rcpdev="${REPLY% *}" installhid="${REPLY#* }"

# Install firmware if necessary
if [ -n "$update" -a "$installhid" != "-" ]; then
if [ -n "$update" ] && [ "$installhid" != "-" ]; then
install_rcp "$rcpdev" "$installhid" || return $?
rcpdev="${REPLY% *}" update=
fi
Expand Down Expand Up @@ -126,12 +132,12 @@ validate_rcp_selector() { # selector
find_rcp() { # selector [include-installable] => $REPLY
local rcpdev= installdev= installhid=
usb_device_foreach _find_rcp_cb "$@"
if [ $? -eq "$RC_BREAK" -a -n "$rcpdev" ]; then
if [ $? -eq "$RC_BREAK" ] && [ -n "$rcpdev" ]; then
log debug "Found RCP interface $rcpdev for selector '$1'"
REPLY="$rcpdev -"
return 0
fi
if [ -n "$2" -a -n "$installdev" -a -n "$installhid" ]; then
if [ -n "$2" ] && [ -n "$installdev" ] && [ -n "$installhid" ]; then
log debug "Found installable $installhid device $installdev for selector '$1'"
REPLY="$installdev $installhid"
return 0
Expand All @@ -140,30 +146,72 @@ find_rcp() { # selector [include-installable] => $REPLY
}
_find_rcp_cb() { # selector [include-installable]
# Must match the selector (if any)
local explicit=
case "$1" in
*-*) [ "$1" = "$DEVICENAME" ] || return 0
*-*) [ "$1" = "$DEVICENAME" ] || return 0; explicit=1
esac

# Check for a usable cdc_acm interface and OpenThread product string
usb_interface_foreach "" _find_rcp_if_cb
[ $? -eq "$RC_BREAK" -a -n "$rcpdev" ] \
&& usb_read_property '' product && REPLY=" $REPLY " && [ "${REPLY/ OpenThread /}" != "$REPLY" ] \
&& return "$RC_BREAK"
if [ $? -eq "$RC_BREAK" ] && [ -n "$rcpdev" ]; then
# A device that also presents mass storage is sitting in a UF2 or
# DFU boot loader, which exposes a serial port next to the drive it
# takes firmware on. Never an RCP, and this does not depend on a
# firmware handler recognizing it.
usb_interface_foreach "" _find_rcp_msc_cb
if [ $? -eq "$RC_BREAK" ]; then
log debug "USB device ${DEVICENAME} also presents mass storage, treating it as a boot loader"
rcpdev=
fi
fi
if [ -n "$rcpdev" ]; then
usb_read_property '' product && REPLY=" $REPLY " && [ "${REPLY/ OpenThread /}" != "$REPLY" ] \
&& return "$RC_BREAK"
# Without an explicit selector the product string is all there is
# to go on, so this device is not a candidate.
[ -n "$explicit" ] || rcpdev=
fi

# If requested, check if we could install RCP firmware on this device
if [ -n "$2" -a -n "$RCPFWHANDLERS" -a -z "$installdev" ]; then
local hid
# Check whether RCP firmware could be installed on this device. This runs
# even when the caller did not ask for installable devices, because a
# device a handler recognizes is one in its boot loader: it exposes a
# cdc_acm interface too, and wants flashing rather than to be handed to
# otbr-agent as a serial port.
if [ -n "$RCPFWHANDLERS" ] && [ -z "$installdev" ]; then
local hid rc
for hid in $RCPFWHANDLERS; do
call_firmware_handler "$hid" installable "$DEVICENAME" && installdev="$DEVICENAME" installhid="$hid" && break
call_firmware_handler "$hid" installable "$DEVICENAME"
rc=$?
# Only "not my device" lets the next handler have a look. Any
# other failure means the handler recognized the device and
# could not act on it, which still rules it out as an RCP.
[ "$rc" != "$RC_NOT_SUPPORTED" ] || continue
[ "$rc" != 0 ] && log warning "Firmware handler '$hid' failed on USB device $DEVICENAME (rc=$rc)"
[ -z "$2" ] || [ "$rc" != 0 ] || { installdev="$DEVICENAME"; installhid="$hid"; }
rcpdev=
break
done
fi

# An explicit bus position is the operator saying this device is the RCP,
# so a dongle that does not carry the word OpenThread in its product
# string (Home Assistant's ZBT-2 for one) can still be used -- but only
# once the handlers above have had their say.
if [ -n "$explicit" ] && [ -n "$rcpdev" ]; then
log notice "Using USB device ${rcpdev%:*} as RCP for selector '$1' without an OpenThread product string"
return "$RC_BREAK"
fi
}
_find_rcp_if_cb() {
usb_read_property '' INTERFACE && [ "$REPLY" = 2/2/0 ] || return 0
usb_read_property '' DRIVER && [ "$REPLY" = cdc_acm ] || return 0
rcpdev="$DEVICENAME"
return "$RC_BREAK"
}
_find_rcp_msc_cb() {
usb_read_property '' INTERFACE && [ "${REPLY%%/*}" = 8 ] || return 0
return "$RC_BREAK"
}

update_rcp() { # rcpdev rcptty rcpurl
local rcpdev="$1" rcptty="$2" rcpurl="$3"
Expand Down Expand Up @@ -211,15 +259,15 @@ usb_device_foreach() { # callback ... (with $DEVICENAME)
local _dev DEVICENAME
for _dev in /sys/bus/usb/devices/*; do
DEVICENAME="${_dev##*/}" # e.g. "1-1"
[ "${DEVICENAME%:*}" = "$DEVICENAME" -a -f "$_dev/bDeviceClass" ] || continue
[ "${DEVICENAME%:*}" = "$DEVICENAME" ] && [ -f "$_dev/bDeviceClass" ] || continue
"$@"; [ $? -ne "$RC_BREAK" ] || return "$RC_BREAK"
done
}

usb_interface_foreach() { # devicename callback ... (with $DEVICENAME)
local _dn="${1:-$DEVICENAME}" _if DEVICENAME; shift
for _if in "/sys/bus/usb/devices/$_dn/$_dn:"*; do
[ -d "$_if" -a -f "$_if/bInterfaceClass" ] || continue
[ -d "$_if" ] && [ -f "$_if/bInterfaceClass" ] || continue
DEVICENAME="${_if##*/}" # e.g. "1-1:1.0"
"$@"; [ $? -ne "$RC_BREAK" ] || return "$RC_BREAK"
done
Expand Down Expand Up @@ -296,7 +344,7 @@ load_firmware_handlers() { # => $RCPFWHANDLERS
local _script _hname _hid _type
RCPFWHANDLERS=
for _script in /usr/share/openthread-rcp/*.sh; do
[ -f "$_script" -a -r "$_script" ] || continue
[ -f "$_script" ] && [ -r "$_script" ] || continue
_hname="${_script##*/}"
_hid="${_hname%.sh}"; _hid="${_hid//-/_}"
if [ -n "${_hid//[a-z0-9_]}" ]; then
Expand Down