diff --git a/thread/README.md b/thread/README.md index 41b73acb07..f2bd3b284a 100644 --- a/thread/README.md +++ b/thread/README.md @@ -16,3 +16,7 @@ launch_cvd Open `https://localhost:8443/` in your web browser, you can find the Thread demoapp (with the Thread logo) in the cuttlefish instance. Open it and have fun with Thread! + +## More docs + +- [Make your Android Border Router](./docs/make-your-android-border-router.md) diff --git a/thread/docs/android-thread-arch.png b/thread/docs/android-thread-arch.png new file mode 100644 index 0000000000..ea408faf5f Binary files /dev/null and b/thread/docs/android-thread-arch.png differ diff --git a/thread/docs/build-an-android-border-router.md b/thread/docs/build-an-android-border-router.md new file mode 100644 index 0000000000..257999b10e --- /dev/null +++ b/thread/docs/build-an-android-border-router.md @@ -0,0 +1,526 @@ +# Build an Android Border Router + +If you are not an Android device or Thread chip vendor, you can stop reading +now. + +This document walks you through the steps to build a new Android-based Thread +Border Router device with the latest AOSP source code. By following this +document, you will learn: + +1. [the overall architecture and status of Thread support in Android](#architecture) +2. [how to create your own Thread HAL service](#build-your-thread-hal-service) +3. [how to make your device compatible with Google Home](#be-compatible-with-google-home) +4. [how to test your Thread Border Router](#testing) + +If you need support, file an issue in +[GitHub](https://github.com/openthread/ot-br-posix/issues) or open a +[Dicussion](https://github.com/orgs/openthread/discussions) if you have any +questions. + +Note: Before creating an issue or discussion, search to see if it has already +been reported or asked. + +## Overview + +The Android Thread stack is based on OpenThread and `ot-br-posix` which are +open-sourced by Google in GitHub. The same way OpenThread is developed in a +public GitHub repository, so the Android Thread stack is developed in the +public [AOSP codebase](https://cs.android.com/). All features and bug fixes +are submitted first in AOSP. This allows vendors to start adopting the latest +Thread versions without waiting for regular Android releases. + +### Architecture + +The whole Android Thread stack consists of two major components: the core +Thread stack in a generic system partition and the Thread HAL service in a +vendor partition. Device vendors typically need only to take care and build the +HAL service. + +![android-thread-arch](./android-thread-arch.png) + +Here is a brief summary of how the Android Thread stack works: +- There is a Java Thread system service in the system server which manages the + whole stack - provides the Thread system API, creates the `thread-wpan` + tunnel interface, registers the Thread network to the + [Connectivity service](https://developer.android.com/reference/android/content/Context#CONNECTIVITY_SERVICE) + and implements the Border Routing and Advertising Proxy functionalities. +- The core Thread / OpenThread stack is hosted in a non-privileged standalone + native process which is named `ot-daemon`. `ot-daemon` is directly managed + by the Java system service via private AIDL APIs and it accesses the Thread + hardware radio through the Thread HAL API. +- A vendor-provided Thread HAL service MUST implement the Thread HAL API. It + typically works as an + [RCP](https://openthread.io/platforms/co-processor#radio_co-processor_rcp) + and implements the + [spinel](https://openthread.io/platforms/co-processor#spinel_protocol) + protocol. + +Note: Both the Java system service and ot-daemon are delivered in a +[Tethering](https://source.android.com/docs/core/ota/modular-system/tethering#overview) +mainline module. For mobile devices, the binary is managed by Google and +delivered to devices via Google Play monthly. For non-mobile devices such as +TV, vendors are free to build from source or use a prebuilt. + +### Where is the code? + +- The Android Thread framework / API and service: https://cs.android.com/android/platform/superproject/main/+/main:packages/modules/Connectivity/thread/ +- The Thread HAL API and default service implementation: https://cs.android.com/android/platform/superproject/main/+/main:hardware/interfaces/threadnetwork/ +- Imported OpenThread repo: https://cs.android.com/android/platform/superproject/main/+/main:external/openthread/ +- Imported ot-br-posix repo: https://cs.android.com/android/platform/superproject/main/+/main:external/ot-br-posix/ + +## Set up development environment + +Android device vendors who have already established an Android development +environment for the device can skip this section. + +If you are new to Android ecosystem or you are a silicon vendor who wants to +make your Thread chip compatible with Android and provide support for device +vendors, keep reading. + +### Follow the Android developer codelab + +To set up your Android development environment for the first time, use the +following codelab: https://source.android.com/docs/setup/start. At the end of +this codelab, you will be able to build and run a simulated Cuttlefish device +from source code. + +## Build your Thread HAL service + +### Try Thread in Cuttlefish + +[Cuttlefish](https://source.android.com/docs/devices/Cuttlefish) is the virtual +Android device. Before starting building your own HAL service, it's better to +try Thread in Cuttlefish to understand how HAL works. + +A default Thread HAL service is provided in Cuttlefish and it's implemented +with the [simulated RCP](https://github.com/openthread/openthread/tree/main/examples/platforms/simulation) +which transceives packets via UDP socket to and from a simulated +Thread (802.15.4) radio. + +In the Cuttlefish instance, a "ThreadNetworkDemoApp" is pre-installed. Open +that app to join the Cuttlefish device into a default Thread network. + +![demoapp-screenshot](./demoapp-screenshot.png) + +Note: The Border Router functionalities will be started (and OMR address will +be created) only when the Cuttlefish device is connected to a virtual Wi-Fi +network. You can connect the Wi-Fi network in Settings. + +There are also the `ot-ctl` and `ot-cli-ftd` command line tools provided to +configure your Thread network for testing. Those tools support all the +OpenThread CLI commands that you may be familiar with already. + +You can grep for logs of the Cuttlefish Thread HAL service by: + +``` +$ adb logcat | egrep -i threadnetwork-service + +07-21 10:43:05.048 0 0 I init : Parsing file /apex/com.android.hardware.threadnetwork/etc/threadnetwork-service.rc... +07-21 10:59:27.233 580 580 W android.hardware.threadnetwork-service: ThreadChip binder is unlinked +07-21 10:59:27.233 580 580 I android.hardware.threadnetwork-service: Close IThreadChip successfully +07-21 10:59:27.385 580 580 I android.hardware.threadnetwork-service: Open IThreadChip successfully +``` + +Or grep for ot-daemon logs by: + +``` +$ adb logcat | egrep -i ot-daemon +07-21 10:43:48.741 0 0 I init : starting service 'ot-daemon'... +07-21 10:43:48.742 0 0 I init : Created socket '/dev/socket/ot-daemon/thread-wpan.sock', mode 660, user 1084, group 1084 +07-21 10:43:48.762 0 0 I init : ... started service 'ot-daemon' has pid 2473 +07-21 10:46:26.320 2473 2473 I ot-daemon: [I] P-Daemon------: Session socket is ready +07-21 10:46:30.290 2473 2473 W ot-daemon: [W] P-Daemon------: Daemon read: Connection reset by peer +07-21 10:48:07.264 2473 2473 I ot-daemon: [INFO]-BINDER--: Start joining... +07-21 10:48:07.267 2473 2473 I ot-daemon: [I] Settings------: Saved ActiveDataset +07-21 10:48:07.267 2473 2473 I ot-daemon: [I] DatasetManager: Active dataset set +07-21 10:48:07.273 2473 2473 I ot-daemon: [I] DnssdServer---: Started +07-21 10:48:07.273 2473 2473 I ot-daemon: [N] Mle-----------: Role disabled -> detached +07-21 10:48:07.273 2473 2473 I ot-daemon: [I] Mle-----------: AttachState Idle -> Start +07-21 10:48:07.273 2473 2473 I ot-daemon: [I] Notifier------: StateChanged (0x111fd11d) [Ip6+ Role LLAddr MLAddr KeySeqCntr Ip6Mult+ Channel PanId NetName ExtPanId ... +07-21 10:48:07.273 2473 2473 I ot-daemon: [I] Notifier------: StateChanged (0x111fd11d) ... NetworkKey PSKc SecPolicy NetifState ActDset] +``` + +Note: You can also capture Thread system server log with the tag +"ThreadNetworkService". + +The Cuttlefish Thread HAL service uses the default Thread HAL service plus the +OpenThread simulated RCP binary, see the next section for how it works. + +### The default HAL service + +A [default HAL service](https://cs.android.com/android/platform/superproject/main/+/main:hardware/interfaces/threadnetwork/aidl/default/) +is included along with the Thread HAL API. The default HAL service supports +both simulated and real RCP devices. It receives an optional RCP device URL and +if the URL is not provided, it defaults to the simulated RCP device. + +In file `hardware/interfaces/threadnetwork/aidl/default/threadnetwork-service.rc`: + +``` +service vendor.threadnetwork_hal /apex/com.android.hardware.threadnetwork/bin/hw/android.hardware.threadnetwork-service + class hal + user thread_network +``` + +This is equivalent to: + +``` +service vendor.threadnetwork_hal /apex/com.android.hardware.threadnetwork/bin/hw/android.hardware.threadnetwork-service spinel+hdlc+forkpty:///apex/com.android.hardware.threadnetwork/bin/ot-rcp?forkpty-arg=1 + class hal + user thread_network +``` + +For real RCP devices, it supports both SPI and UART interace and you can +specify the device with the schema `spinel+spi://`, `spinel+hdlc+uart://` and +`spinel+socket://` respectively. + +Note: `spinel+socket://` is a new spinel interface added in the default Thread +HAL, it supports transmitting spinel frame via an Unix socket. A full +socket-based radio URL may be like +`spinel+socket:///data/vendor/threadnetwork/thread_spinel_socket`. + +#### Understand the vendor APEX + +Similar to the Thread stack in the Tethering mainline module, the default Thread +HAL service in Cuttlefish is packaged in an APEX module as well. But it's a +vendor APEX module which will be installed to `/vendor/apex/` (The artifacts in +the module will be unzipped to `/apex/com.android.hardware.threadnetwork/`). + +```aidl +apex { + name: "com.android.hardware.threadnetwork", + manifest: "manifest.json", + file_contexts: "file_contexts", + key: "com.android.hardware.key", + certificate: ":com.android.hardware.certificate", + updatable: false, + vendor: true, + + binaries: [ + "android.hardware.threadnetwork-service", + "ot-rcp", + ], + + prebuilts: [ + "threadnetwork-default.xml", // vintf_fragment + "threadnetwork-service.rc", // init_rc + "android.hardware.thread_network.prebuilt.xml", // permission + ], +} +``` + +There are a few important configurations that you will need to pay attention or +make changes to when building your own HAL APEX module: + +- `file_contexts`: This describes the binary / data files delivered in this + APEX module or files the HAL service need to access (for example, the RCP + device). This allows you to specify specific sepolicy rules for your HAL + service to access the hardware RCP device. +- `binaries`: The binary file delivered in this APEX module +- `threadnetwork-service.rc`: How the HAL service will be started. You need to + specify the RCP device path here. +- `android.hardware.thread_network.prebuilt.xml`: Defines the + `android.hardware.thread_network` hardware feature. This is required for the + Android system to know that your device does have Thread hardware support. + Otherwise, the Android Thread stack won't be enabled. + +### Create your HAL service + +Whether you are an Android device developer or a silicon vendor, you should be +familiar with building OT RCP firmware for your Thread chip. The following +instructions assume that the hardware chip is correctly wired and +validated. + +The simplest way to build your HAL APEX is to create a new APEX with the +binaries and prebuilts of the default HAL APEX. For example, if your company is +Banana and the RCP device on your device is `/dev/ttyACM0`, your Thread HAL +APEX will look like this: + +- `Android.bp`: + ``` + prebuilt_etc { + name: "banana-threadnetwork-service.rc", + src: "banana-threadnetwork-service.rc", + installable: false, + } + + apex { + name: "com.banana.android.hardware.threadnetwork", + manifest: "manifest.json", + file_contexts: "file_contexts", + key: "com.android.hardware.key", + certificate: ":com.android.hardware.certificate", + updatable: false, + vendor: true, + + binaries: [ + "android.hardware.threadnetwork-service", + ], + + prebuilts: [ + "banana-threadnetwork-service.rc", + "threadnetwork-default.xml", + "android.hardware.thread_network.prebuilt.xml", + ], + } + ``` +- `file_contexts`: + ``` + (/.*)? u:object_r:vendor_file:s0 + /etc(/.*)? u:object_r:vendor_configs_file:s0 + /bin/hw/android\.hardware\.threadnetwork-service u:object_r:hal_threadnetwork_default_exec:s0 + /dev/ttyACM0 u:object_r:threadnetwork_rcp_device:s0 + ``` + The file paths in the first column are related to `/apex/com.android.hardware.threadnetwork/`. +- `threadnetwork-service.rc`: + ``` + service vendor.threadnetwork_hal /apex/com.android.hardware.threadnetwork/bin/hw/android.hardware.threadnetwork-service spinel+hdlc+uart:///dev/ttyACM0?uart-baudrate=115200 + class hal + user root + ``` +- `manifest.json`: + ``` + { + "name": "com.android.hardware.threadnetwork", + "version": 1 + } + ``` + +Note: The default Thread HAL service is not designed to be a generic system +component which works for all Android devices. If the default implementation +can't support your device, you are free to make a copy and change it for your +needs. In this case, it's just simpler to create a new APEX module without +overriding the default one. + +Assuming you are making a new device named Orange, your device specific +configuration directory will be like: + +``` +device/banana/orange/threadnetwork/ + sepolicy/ + Android.bp + file_contexts + manifest.json + threadnetwork-default.xml + threadnetwork-service.rc +``` + +See the next section for what sepolicy rules should be added in the `sepolicy/` +sub-directory. + +#### Sepolicy rules for RCP device + +By default, your Thread HAL service doesn't have access to the RCP device (for +example `/dev/ttyACM0`), custom sepolicy rules need to be added to the +`sepolicy/` directory. + +Create a new `sepolicy/threadnetwork_hal.te` file with below content: + +``` +type threadnetwork_rcp_device, dev_type; + +# Allows the Thread HAL service to read / write the Thread RCP device +allow hal_threadnetwork_default threadnetwork_rcp_device:chr_file rw_file_perms; +``` + +#### Put together + +Now you have finished almost all the code needs for adding Thread, the last +step is to add the Thread HAL APEX and sepolicy rules to your device's image. + +You can do this by adding below code to your device's `Makefile` (for example, +`device.mk`): + +``` +PRODUCT_PACKAGES += com.banana.hardware.threadnetwork +BOARD_SEPOLICY_DIRS += device/banana/orange/threadnetwork/sepolicy +``` + +Note: Unfortunately, APEX module doesn't support sepolicy rules, so you need +to explicitly specify the sepolicy directory separately. + +If everything works, now you will be able to see the Thread HAL service log similar to: + +``` +$ adb logcat | egrep -i threadnetwork-service +08-13 13:26:41.751 477 477 I android.hardware.threadnetwork-service: ServiceName: android.hardware.threadnetwork.IThreadChip/chip0, Url: spinel+spi +08-13 13:26:41.751 477 477 I android.hardware.threadnetwork-service: Thread Network HAL is running +08-13 13:26:55.165 477 477 I android.hardware.threadnetwork-service: Open IThreadChip successfully +``` + +And the `ot-daemon` log will be like: + +``` +$ adb logcat -s ot-daemon +08-13 13:26:55.157 1019 1019 I ot-daemon: [NOTE]-AGENT---: Running OTBR_AGENT/Unknown +08-13 13:26:55.157 1019 1019 I ot-daemon: [NOTE]-AGENT---: Thread version: 1.3.0 +08-13 13:26:55.157 1019 1019 I ot-daemon: [NOTE]-AGENT---: Thread interface: thread-wpan +08-13 13:26:55.157 1019 1019 I ot-daemon: [NOTE]-AGENT---: Backbone interface is not specified +08-13 13:26:55.157 1019 1019 I ot-daemon: [NOTE]-AGENT---: Radio URL: threadnetwork_hal://binder?none +08-13 13:26:55.157 1019 1019 I ot-daemon: [NOTE]-ILS-----: Infra link selected: +08-13 13:26:55.160 1019 1019 I ot-daemon: [I] Platform------: [HAL] Wait for getting the service android.hardware.threadnetwork.IThreadChip/chip0 ... +08-13 13:26:55.165 1019 1019 I ot-daemon: [I] Platform------: [HAL] Successfully got the service android.hardware.threadnetwork.IThreadChip/chip0 +08-13 13:26:55.275 1019 1019 I ot-daemon: [I] P-RadioSpinel-: RCP reset: RESET_UNKNOWN +08-13 13:26:55.276 1019 1019 I ot-daemon: [I] P-RadioSpinel-: Software reset RCP successfully +08-13 13:26:55.277 1019 1019 I ot-daemon: [I] P-RadioSpinel-: RCP reset: RESET_POWER_ON +08-13 13:26:55.322 1019 1019 I ot-daemon: [I] ChildSupervsn-: Timeout: 0 -> 190 +08-13 13:26:55.324 1019 1019 I ot-daemon: [I] RoutingManager: Initializing - InfraIfIndex:0 +08-13 13:26:55.324 1019 1019 I ot-daemon: [I] InfraIf-------: Init infra netif 0 +08-13 13:26:55.324 1019 1019 I ot-daemon: [I] Settings------: Read BrUlaPrefix fd7b:cc45:ff06::/48 +08-13 13:26:55.324 1019 1019 I ot-daemon: [N] RoutingManager: BR ULA prefix: fd7b:cc45:ff06::/48 (loaded) +08-13 13:26:55.324 1019 1019 I ot-daemon: [I] RoutingManager: Generated local OMR prefix: fd7b:cc45:ff06:1::/64 +08-13 13:26:55.324 1019 1019 I ot-daemon: [N] RoutingManager: Local on-link prefix: fdde:ad00:beef:cafe::/64 +08-13 13:26:55.324 1019 1019 I ot-daemon: [I] RoutingManager: Enabling +``` + +## Customization + +The Thread mainline module (it's actually a part of the "Tethering" module) +provides a few [overlayable configurations](https://source.android.com/docs/core/runtime/rros) +which can be specified by vendors to customize the stack behavior. See +[config_thread.xml](https://cs.android.com/android/platform/superproject/main/+/main:packages/modules/Connectivity/service/ServiceConnectivityResources/res/values/config_thread.xml) +for the full list. + +Typically, you must change the `config_thread_vendor_name`, +`config_thread_vendor_oui` and `config_thread_model_name` to your vendor or +product values. Those values will be included in the `_meshcop._udp` mDNS +service which is always advertised by a Thread Border Router. + +To add the overlay, you need to create a new `ConnectivityOverlayOrange` +runtime_resource_overlay target for your Orange device. Create a new +`ConnectivityOverlay/` directory under `device/banana/orange/rro_overlays` and +create below contents in it: + +``` +device/banana/orange/rro_overlays/ConnectivityOverlay/ + res + values + config_thread.xml + Android.bp + AndroidManifest.xml +``` + +- `Android.bp`: + ``` + package { + default_applicable_licenses: ["Android-Apache-2.0"], + } + + runtime_resource_overlay { + name: "ConnectivityOverlayOrange", + manifest: "AndroidManifest.xml", + resource_dirs: ["res"], + certificate: "platform", + product_specific: true, + sdk_version: "current", + } + ``` +- `AndroidManifest.xml`: + ``` + + + + + + + + ``` +- `config_thread.xml`: + ``` + Banana Inc. + AC:DE:48 + Orange + ``` + +Similar to the HAL APEX, you need to add the overlay app to your `device.mk` +file: + +``` +PRODUCT_PACKAGES += \ + ConnectivityOverlayOrange +``` + +If everything works, you will see that `ot-daemon` logs the vendor and model name +at the very beginning of the log: +``` +$ adb logcat -s ot-daemon +07-22 15:31:37.693 1472 1472 I ot-daemon: [I] P-Daemon------: Session socket is ready +07-22 15:31:37.693 1472 1472 I ot-daemon: [I] Cli-----------: Input: state +07-22 15:31:37.693 1472 1472 I ot-daemon: [I] Cli-----------: Output: disabled +07-22 15:31:37.693 1472 1472 I ot-daemon: [I] Cli-----------: Output: Done +07-22 15:31:37.693 1472 1472 W ot-daemon: [W] P-Daemon------: Daemon read: Connection reset by peer +07-22 15:31:50.091 1472 1472 I ot-daemon: [I] P-Daemon------: Session socket is ready +07-22 15:31:50.091 1472 1472 I ot-daemon: [I] Cli-----------: Input: factoryreset +07-22 15:31:50.092 1472 1472 I ot-daemon: [I] Settings------: Wiped all info +07-22 15:31:50.092 1472 1472 I ot-daemon: [INFO]-ADPROXY-: Stopped +07-22 15:31:50.092 1472 1472 I ot-daemon: [INFO]-DPROXY--: Stopped +07-22 15:31:50.092 1472 1472 I ot-daemon: [INFO]-BA------: Stop Thread Border Agent +07-22 15:31:50.092 1472 1472 I ot-daemon: [INFO]-BA------: Unpublish meshcop service Banana Inc. Orange #4833._meshcop._udp.local +07-22 15:31:50.092 1472 1472 I ot-daemon: [INFO]-MDNS----: Removing service Banana Inc. Orange #4833._meshcop._udp +07-22 15:31:50.092 1472 1472 I ot-daemon: [INFO]-MDNS----: Unpublishing service Banana Inc. Orange #4833._meshcop._udp listener ID = 0 +``` + +Note: In case the overlay doesn't work, check https://source.android.com/docs/core/runtime/rro-troubleshoot +for troubleshooting instructions. + +### Be compatible with Google Home + +Additionally, if you want to make your Border Router be used by the Google Home +ecosystem, you can specify this configuration in `config_thread.xml`: + +``` + + vgh=1 + +``` + +## Testing + +Your device should be compatible with the Thread 1.3+ Border Router +specification now. Before sending it to the Thread certification program, there +are a few Android xTS tests should be exercised to ensure the compatibility. + +- The VTS test makes sure Thread HAL service work as expected on your device. + You can run the tests with command + ``` + atest VtsHalThreadNetworkTargetTest + ``` +- The CTS test makes sure Thread APIs work as expected on your device. You can + run the tests with command + ``` + atest CtsThreadNetworkTestCases + ``` +- The integration test provides more quality guarantee of how the Thread + mainline code works on your device. You can run the tests with command + ``` + atest ThreadNetworkIntegrationTests + ``` + +You can also find more instructions of how to run VTS/CTS/MTS tests with those +released test suites: + +- https://source.android.com/docs/core/tests/vts +- https://source.android.com/docs/compatibility/cts/run +- https://docs.partner.android.com/mainline/test/mts (you need to be a partner to access this link) + +### Test with the Thread demo app + +Similar to the Cuttlefish device, you can add the Thread demo app to your system image: + +``` +# ThreadNetworkDemoApp for testing +PRODUCT_PACKAGES_DEBUG += ThreadNetworkDemoApp +``` + +Note that you should add it to only the debug / eng variant (for example, +`PRODUCT_PACKAGES_DEBUG`) given this is not supposed to be included in user +build for end consumers. diff --git a/thread/docs/demoapp-screenshot.png b/thread/docs/demoapp-screenshot.png new file mode 100644 index 0000000000..fa7f079e77 Binary files /dev/null and b/thread/docs/demoapp-screenshot.png differ