Add Thread network HAL
Bug: b/203492431
Test: Build and run the VTS test and run otbr-agent on Android emulator.
(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:b82b0478059dd6203f43d5917558e34fd4ed44d5)
Merged-In: If58b5a8c75e40376ae6c6e93554afe750496308b
Change-Id: If58b5a8c75e40376ae6c6e93554afe750496308b
diff --git a/threadnetwork/aidl/Android.bp b/threadnetwork/aidl/Android.bp
new file mode 100644
index 0000000..480ac0f
--- /dev/null
+++ b/threadnetwork/aidl/Android.bp
@@ -0,0 +1,22 @@
+aidl_interface {
+ name: "android.hardware.threadnetwork",
+ vendor_available: true,
+ srcs: [
+ "android/hardware/threadnetwork/*.aidl",
+ ],
+
+ stability: "vintf",
+
+ backend: {
+ java: {
+ platform_apis: true,
+ },
+ ndk: {
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.threadnetwork",
+ ],
+ min_sdk_version: "33",
+ },
+ },
+}
diff --git a/threadnetwork/aidl/OWNERS b/threadnetwork/aidl/OWNERS
new file mode 100644
index 0000000..e3111c8
--- /dev/null
+++ b/threadnetwork/aidl/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 1288834
+zhanglongxia@google.com
+xyk@google.com
diff --git a/threadnetwork/aidl/aidl_api/android.hardware.threadnetwork/current/android/hardware/threadnetwork/IThreadChip.aidl b/threadnetwork/aidl/aidl_api/android.hardware.threadnetwork/current/android/hardware/threadnetwork/IThreadChip.aidl
new file mode 100644
index 0000000..e4d4cbe
--- /dev/null
+++ b/threadnetwork/aidl/aidl_api/android.hardware.threadnetwork/current/android/hardware/threadnetwork/IThreadChip.aidl
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.threadnetwork;
+@VintfStability
+interface IThreadChip {
+ void open(in android.hardware.threadnetwork.IThreadChipCallback callback);
+ void close();
+ void reset();
+ void sendSpinelFrame(in byte[] frame);
+ const int ERROR_FAILED = 1;
+ const int ERROR_INVALID_ARGS = 2;
+ const int ERROR_NO_BUFS = 3;
+ const int ERROR_BUSY = 4;
+}
diff --git a/threadnetwork/aidl/aidl_api/android.hardware.threadnetwork/current/android/hardware/threadnetwork/IThreadChipCallback.aidl b/threadnetwork/aidl/aidl_api/android.hardware.threadnetwork/current/android/hardware/threadnetwork/IThreadChipCallback.aidl
new file mode 100644
index 0000000..e86b3ec
--- /dev/null
+++ b/threadnetwork/aidl/aidl_api/android.hardware.threadnetwork/current/android/hardware/threadnetwork/IThreadChipCallback.aidl
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.threadnetwork;
+@VintfStability
+interface IThreadChipCallback {
+ oneway void onReceiveSpinelFrame(in byte[] frame);
+}
diff --git a/threadnetwork/aidl/android/hardware/threadnetwork/IThreadChip.aidl b/threadnetwork/aidl/android/hardware/threadnetwork/IThreadChip.aidl
new file mode 100644
index 0000000..eebaa46
--- /dev/null
+++ b/threadnetwork/aidl/android/hardware/threadnetwork/IThreadChip.aidl
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.threadnetwork;
+
+import android.hardware.threadnetwork.IThreadChipCallback;
+
+/**
+ * Controls a Thread radio chip on the device.
+ */
+
+@VintfStability
+interface IThreadChip {
+ /**
+ * The operation failed for the internal error.
+ */
+ const int ERROR_FAILED = 1;
+
+ /**
+ * The invalid arguments.
+ */
+ const int ERROR_INVALID_ARGS = 2;
+
+ /**
+ * Insufficient buffers available to send frames.
+ */
+ const int ERROR_NO_BUFS = 3;
+
+ /**
+ * Service is busy and could not service the operation.
+ */
+ const int ERROR_BUSY = 4;
+
+ /**
+ * This method initializes the Thread HAL instance. If open completes
+ * successfully, then the Thread HAL instance is ready to accept spinel
+ * messages through sendSpinelFrame() API.
+ *
+ * @param callback A IThreadChipCallback callback instance.
+ *
+ * @throws ServiceSpecificException with one of the following values:
+ * - ERROR_FAILED The interface cannot be opened due to an internal error.
+ * - ERROR_INVALID_ARGS The callback handle is invalid (for example, it is null).
+ * - ERROR_BUSY This interface is in use.
+ */
+ void open(in IThreadChipCallback callback);
+
+ /**
+ * Close the Thread HAL instance. Must free all resources.
+ */
+ void close();
+
+ /**
+ * This method resets the Thread HAL internal state. The callback registered by
+ * `open()` won’t be reset and the resource allocated by `open()` won’t be free.
+ *
+ */
+ void reset();
+
+ /**
+ * This method sends a spinel frame to the Thread HAL.
+ *
+ * This method should block until the frame is sent out successfully or
+ * the method throws errors immediately.
+ *
+ * Spinel Protocol:
+ * https://github.com/openthread/openthread/blob/main/src/lib/spinel/spinel.h
+ *
+ * @param frame The spinel frame to be sent.
+ *
+ * @throws ServiceSpecificException with one of the following values:
+ * - ERROR_FAILED The Thread HAL failed to send the frame for an internal reason.
+ * - ERROR_NO_BUFS Insufficient buffer space to send the frame.
+ * - ERROR_BUSY The Thread HAL is busy.
+ */
+ void sendSpinelFrame(in byte[] frame);
+}
diff --git a/threadnetwork/aidl/android/hardware/threadnetwork/IThreadChipCallback.aidl b/threadnetwork/aidl/android/hardware/threadnetwork/IThreadChipCallback.aidl
new file mode 100644
index 0000000..046edc3
--- /dev/null
+++ b/threadnetwork/aidl/android/hardware/threadnetwork/IThreadChipCallback.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.threadnetwork;
+
+@VintfStability
+interface IThreadChipCallback {
+ /**
+ * This method is called when a spinel frame is received. Thread network
+ * will process the received spinel frame.
+ *
+ * Spinel Protocol:
+ * https://github.com/openthread/openthread/blob/main/src/lib/spinel/spinel.h
+ *
+ * @param frame The received spinel frame.
+ */
+ oneway void onReceiveSpinelFrame(in byte[] frame);
+}
diff --git a/threadnetwork/aidl/default/Android.bp b/threadnetwork/aidl/default/Android.bp
new file mode 100644
index 0000000..201306d
--- /dev/null
+++ b/threadnetwork/aidl/default/Android.bp
@@ -0,0 +1,56 @@
+//
+// Copyright (c) 2022 Google LLC.
+// All rights reserved.
+//
+// This document is the property of Google LLC, Inc. It is
+// considered proprietary and confidential information.
+//
+// This document may not be reproduced or transmitted in any form,
+// in whole or in part, without the express written permission of
+// Google LLC.
+
+cc_defaults {
+ name: "threadnetwork_service_default",
+ vintf_fragments: ["threadnetwork-default.xml"],
+ vendor: true,
+ relative_install_path: "hw",
+
+ shared_libs: [
+ "android.hardware.threadnetwork-V1-ndk",
+ "libbase",
+ "libbinder_ndk",
+ "libcutils",
+ "liblog",
+ "libutils",
+ ],
+
+ cppflags: [
+ "-Wno-non-virtual-dtor",
+ ],
+
+ static_libs: [
+ "openthread-common",
+ "openthread-hdlc",
+ "openthread-platform",
+ "openthread-posix",
+ "openthread-url",
+ ],
+
+ srcs: [
+ "main.cpp",
+ "service.cpp",
+ "thread_chip.cpp",
+ "utils.cpp",
+ ],
+}
+
+cc_binary {
+ name: "android.hardware.threadnetwork-service.sim",
+ defaults: ["threadnetwork_service_default"],
+ init_rc: ["android.hardware.threadnetwork-service.sim.rc"],
+}
+
+cc_binary {
+ name: "android.hardware.threadnetwork-service",
+ defaults: ["threadnetwork_service_default"],
+}
diff --git a/threadnetwork/aidl/default/android.hardware.threadnetwork-service.sim.rc b/threadnetwork/aidl/default/android.hardware.threadnetwork-service.sim.rc
new file mode 100644
index 0000000..2fb409c
--- /dev/null
+++ b/threadnetwork/aidl/default/android.hardware.threadnetwork-service.sim.rc
@@ -0,0 +1,3 @@
+service vendor.threadnetwork_hal /vendor/bin/hw/android.hardware.threadnetwork-service.sim spinel+hdlc+forkpty:///vendor/bin/ot-rcp?forkpty-arg=1
+ class hal
+ user thread_network
diff --git a/threadnetwork/aidl/default/main.cpp b/threadnetwork/aidl/default/main.cpp
new file mode 100644
index 0000000..9f2a789
--- /dev/null
+++ b/threadnetwork/aidl/default/main.cpp
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android-base/logging.h>
+
+#include "service.hpp"
+#include "utils.hpp"
+
+int main(int argc, char* argv[]) {
+ CHECK_GT(argc, 1);
+ aidl::android::hardware::threadnetwork::Service service(&argv[1], argc - 1);
+
+ ALOGI("Thread Network HAL is running");
+
+ service.startLoop();
+ return EXIT_FAILURE; // should not reach
+}
diff --git a/threadnetwork/aidl/default/service.cpp b/threadnetwork/aidl/default/service.cpp
new file mode 100644
index 0000000..44bb99a
--- /dev/null
+++ b/threadnetwork/aidl/default/service.cpp
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "service.hpp"
+
+#include <android-base/logging.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+
+#include "thread_chip.hpp"
+#include "utils.hpp"
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace threadnetwork {
+
+Service::Service(char* urls[], int numUrls) : mBinderFd(-1) {
+ CHECK_NE(urls, nullptr);
+ CHECK_GT(numUrls, 0);
+
+ for (int i = 0; i < numUrls; i++) {
+ auto threadChip = ndk::SharedRefBase::make<ThreadChip>(i, urls[i]);
+ CHECK_NE(threadChip, nullptr);
+ mThreadChips.push_back(std::move(threadChip));
+ }
+
+ binder_status_t status = ABinderProcess_setupPolling(&mBinderFd);
+ CHECK_EQ(status, ::STATUS_OK);
+ CHECK_GE(mBinderFd, 0);
+}
+
+void Service::Update(otSysMainloopContext& context) {
+ FD_SET(mBinderFd, &context.mReadFdSet);
+ context.mMaxFd = std::max(context.mMaxFd, mBinderFd);
+}
+
+void Service::Process(const otSysMainloopContext& context) {
+ if (FD_ISSET(mBinderFd, &context.mReadFdSet)) {
+ ABinderProcess_handlePolledCommands();
+ }
+}
+
+void Service::startLoop(void) {
+ const struct timeval kPollTimeout = {1, 0};
+ otSysMainloopContext context;
+ int rval;
+
+ ot::Posix::Mainloop::Manager::Get().Add(*this);
+
+ while (true) {
+ context.mMaxFd = -1;
+ context.mTimeout = kPollTimeout;
+
+ FD_ZERO(&context.mReadFdSet);
+ FD_ZERO(&context.mWriteFdSet);
+ FD_ZERO(&context.mErrorFdSet);
+
+ ot::Posix::Mainloop::Manager::Get().Update(context);
+
+ rval = select(context.mMaxFd + 1, &context.mReadFdSet, &context.mWriteFdSet,
+ &context.mErrorFdSet, &context.mTimeout);
+
+ if (rval >= 0) {
+ ot::Posix::Mainloop::Manager::Get().Process(context);
+ } else if (errno != EINTR) {
+ ALOGE("select() failed: %s", strerror(errno));
+ break;
+ }
+ }
+}
+} // namespace threadnetwork
+} // namespace hardware
+} // namespace android
+} // namespace aidl
diff --git a/threadnetwork/aidl/default/service.hpp b/threadnetwork/aidl/default/service.hpp
new file mode 100644
index 0000000..4137049
--- /dev/null
+++ b/threadnetwork/aidl/default/service.hpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "mainloop.hpp"
+#include "thread_chip.hpp"
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace threadnetwork {
+
+class Service : public ot::Posix::Mainloop::Source {
+ public:
+ Service(char* urls[], int numUrls);
+
+ void Update(otSysMainloopContext& context) override;
+ void Process(const otSysMainloopContext& context) override;
+ void startLoop(void);
+
+ private:
+ int mBinderFd;
+ std::vector<std::shared_ptr<ThreadChip>> mThreadChips;
+};
+} // namespace threadnetwork
+} // namespace hardware
+} // namespace android
+} // namespace aidl
diff --git a/threadnetwork/aidl/default/thread_chip.cpp b/threadnetwork/aidl/default/thread_chip.cpp
new file mode 100644
index 0000000..b5cc7eb
--- /dev/null
+++ b/threadnetwork/aidl/default/thread_chip.cpp
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "thread_chip.hpp"
+
+#include <android-base/logging.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+
+#include "utils.hpp"
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace threadnetwork {
+
+ThreadChip::ThreadChip(uint8_t id, char* url)
+ : mUrl(),
+ mInterface(handleReceivedFrame, this, mRxFrameBuffer),
+ mRxFrameBuffer(),
+ mCallback(nullptr) {
+ const std::string name(std::string() + IThreadChip::descriptor + "/chip" + std::to_string(id));
+ binder_status_t status;
+
+ ALOGI("ServiceName: %s, Url: %s", name.c_str(), url);
+ CHECK_EQ(mUrl.Init(url), 0);
+ status = AServiceManager_addService(asBinder().get(), name.c_str());
+ CHECK_EQ(status, STATUS_OK);
+}
+
+void ThreadChip::clientDeathCallback(void* context) {
+ reinterpret_cast<ThreadChip*>(context)->clientDeathCallback();
+}
+
+void ThreadChip::clientDeathCallback(void) {
+ ALOGW("Thread Network HAL client is dead.");
+ close();
+}
+
+void ThreadChip::handleReceivedFrame(void* context) {
+ static_cast<ThreadChip*>(context)->handleReceivedFrame();
+}
+
+void ThreadChip::handleReceivedFrame(void) {
+ if (mCallback != nullptr) {
+ mCallback->onReceiveSpinelFrame(std::vector<uint8_t>(
+ mRxFrameBuffer.GetFrame(), mRxFrameBuffer.GetFrame() + mRxFrameBuffer.GetLength()));
+ }
+
+ mRxFrameBuffer.DiscardFrame();
+}
+
+ndk::ScopedAStatus ThreadChip::open(const std::shared_ptr<IThreadChipCallback>& in_callback) {
+ ndk::ScopedAStatus status;
+ AIBinder* binder;
+
+ VerifyOrExit(mCallback == nullptr,
+ status = errorStatus(ERROR_BUSY, "Interface is already opened"));
+ VerifyOrExit(in_callback != nullptr,
+ status = errorStatus(ERROR_INVALID_ARGS, "The callback is NULL"));
+ binder = in_callback->asBinder().get();
+ VerifyOrExit(binder != nullptr,
+ status = errorStatus(ERROR_FAILED, "Failed to get the callback binder"));
+ mBinderDeathRecipient = AIBinder_DeathRecipient_new(clientDeathCallback);
+ VerifyOrExit(AIBinder_linkToDeath(binder, mBinderDeathRecipient, this) == STATUS_OK,
+ status = errorStatus(ERROR_FAILED, "Failed to link the binder to death"));
+ VerifyOrExit(mInterface.Init(mUrl) == OT_ERROR_NONE,
+ status = errorStatus(ERROR_FAILED, "Failed to initialize the interface"));
+
+ mCallback = in_callback;
+ ot::Posix::Mainloop::Manager::Get().Add(*this);
+ status = ndk::ScopedAStatus::ok();
+
+exit:
+ if (!status.isOk())
+ {
+ if (mBinderDeathRecipient != nullptr)
+ {
+ AIBinder_DeathRecipient_delete(mBinderDeathRecipient);
+ mBinderDeathRecipient = nullptr;
+ }
+ ALOGW("Open failed, error: %s", status.getDescription().c_str());
+ }
+ else
+ {
+ ALOGI("open()");
+ }
+
+ return status;
+}
+
+ndk::ScopedAStatus ThreadChip::close() {
+ VerifyOrExit(mCallback != nullptr);
+ mCallback = nullptr;
+ mInterface.Deinit();
+
+ ot::Posix::Mainloop::Manager::Get().Remove(*this);
+
+ AIBinder_DeathRecipient_delete(mBinderDeathRecipient);
+ mBinderDeathRecipient = nullptr;
+
+exit:
+ ALOGI("close()");
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus ThreadChip::sendSpinelFrame(const std::vector<uint8_t>& in_frame) {
+ ndk::ScopedAStatus status;
+ otError error;
+
+ VerifyOrExit(mCallback != nullptr,
+ status = errorStatus(ERROR_FAILED, "The interface is not open"));
+
+ error = mInterface.SendFrame(reinterpret_cast<const uint8_t*>(in_frame.data()),
+ in_frame.size());
+ if (error == OT_ERROR_NONE) {
+ status = ndk::ScopedAStatus::ok();
+ } else if (error == OT_ERROR_NO_BUFS) {
+ status = errorStatus(ERROR_NO_BUFS, "Insufficient buffer space to send");
+ } else if (error == OT_ERROR_BUSY) {
+ status = errorStatus(ERROR_BUSY, "The interface is busy");
+ } else {
+ status = errorStatus(ERROR_FAILED, "Failed to send the spinel frame");
+ }
+
+exit:
+ if (!status.isOk())
+ {
+ ALOGW("Send spinel frame failed, error: %s", status.getDescription().c_str());
+ }
+
+ return status;
+}
+
+ndk::ScopedAStatus ThreadChip::reset() {
+ mInterface.OnRcpReset();
+ ALOGI("reset()");
+ return ndk::ScopedAStatus::ok();
+}
+
+void ThreadChip::Update(otSysMainloopContext& context) {
+ if (mCallback != nullptr) {
+ mInterface.UpdateFdSet(context.mReadFdSet, context.mWriteFdSet, context.mMaxFd,
+ context.mTimeout);
+ }
+}
+
+void ThreadChip::Process(const otSysMainloopContext& context) {
+ struct RadioProcessContext radioContext;
+
+ if (mCallback != nullptr) {
+ radioContext.mReadFdSet = &context.mReadFdSet;
+ radioContext.mWriteFdSet = &context.mWriteFdSet;
+ mInterface.Process(radioContext);
+ }
+}
+
+ndk::ScopedAStatus ThreadChip::errorStatus(int32_t error, const char* message) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(error, message));
+}
+} // namespace threadnetwork
+} // namespace hardware
+} // namespace android
+} // namespace aidl
diff --git a/threadnetwork/aidl/default/thread_chip.hpp b/threadnetwork/aidl/default/thread_chip.hpp
new file mode 100644
index 0000000..d444374
--- /dev/null
+++ b/threadnetwork/aidl/default/thread_chip.hpp
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <aidl/android/hardware/threadnetwork/BnThreadChip.h>
+#include <aidl/android/hardware/threadnetwork/IThreadChipCallback.h>
+
+#include "hdlc_interface.hpp"
+#include "lib/spinel/spinel_interface.hpp"
+#include "mainloop.hpp"
+
+#include <android/binder_ibinder.h>
+#include <utils/Mutex.h>
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace threadnetwork {
+
+class ThreadChip : public BnThreadChip, ot::Posix::Mainloop::Source {
+ public:
+ ThreadChip(uint8_t id, char* url);
+
+ ndk::ScopedAStatus open(const std::shared_ptr<IThreadChipCallback>& in_callback) override;
+ ndk::ScopedAStatus close() override;
+ ndk::ScopedAStatus sendSpinelFrame(const std::vector<uint8_t>& in_frame) override;
+ ndk::ScopedAStatus reset() override;
+ void Update(otSysMainloopContext& context) override;
+ void Process(const otSysMainloopContext& context) override;
+
+ private:
+ static void clientDeathCallback(void* context);
+ void clientDeathCallback(void);
+ static void handleReceivedFrame(void* context);
+ void handleReceivedFrame(void);
+ ndk::ScopedAStatus errorStatus(int32_t error, const char* message);
+
+ ot::Url::Url mUrl;
+ ot::Posix::HdlcInterface mInterface;
+ ot::Spinel::SpinelInterface::RxFrameBuffer mRxFrameBuffer;
+ std::shared_ptr<IThreadChipCallback> mCallback;
+ AIBinder_DeathRecipient* mBinderDeathRecipient;
+};
+
+} // namespace threadnetwork
+} // namespace hardware
+} // namespace android
+} // namespace aidl
diff --git a/threadnetwork/aidl/default/threadnetwork-default.xml b/threadnetwork/aidl/default/threadnetwork-default.xml
new file mode 100644
index 0000000..d7dee1e
--- /dev/null
+++ b/threadnetwork/aidl/default/threadnetwork-default.xml
@@ -0,0 +1,6 @@
+<manifest version="1.0" type="device">
+ <hal format="aidl">
+ <name>android.hardware.threadnetwork</name>
+ <fqname>IThreadChip/chip0</fqname>
+ </hal>
+</manifest>
diff --git a/threadnetwork/aidl/default/utils.cpp b/threadnetwork/aidl/default/utils.cpp
new file mode 100644
index 0000000..a8f3464
--- /dev/null
+++ b/threadnetwork/aidl/default/utils.cpp
@@ -0,0 +1,36 @@
+
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "utils.hpp"
+
+#include <openthread/logging.h>
+
+void otLogCritPlat(const char* format, ...) {
+ va_list args;
+
+ va_start(args, format);
+ __android_log_vprint(ANDROID_LOG_FATAL, LOG_TAG, format, args);
+ va_end(args);
+}
+
+void otLogWarnPlat(const char* format, ...) {
+ va_list args;
+
+ va_start(args, format);
+ __android_log_vprint(ANDROID_LOG_WARN, LOG_TAG, format, args);
+ va_end(args);
+}
diff --git a/threadnetwork/aidl/default/utils.hpp b/threadnetwork/aidl/default/utils.hpp
new file mode 100644
index 0000000..279c0ba
--- /dev/null
+++ b/threadnetwork/aidl/default/utils.hpp
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#define LOG_TAG "threadnetwork_hal"
+#include <utils/Log.h>
diff --git a/threadnetwork/aidl/vts/Android.bp b/threadnetwork/aidl/vts/Android.bp
new file mode 100644
index 0000000..864e885
--- /dev/null
+++ b/threadnetwork/aidl/vts/Android.bp
@@ -0,0 +1,38 @@
+//
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+cc_test {
+ name: "VtsHalThreadNetworkTargetTest",
+ defaults: [
+ "VtsHalTargetTestDefaults",
+ "use_libaidlvintf_gtest_helper_static",
+ ],
+ srcs: [
+ "VtsHalThreadNetworkTargetTest.cpp",
+ ],
+
+ shared_libs: [
+ "libbinder",
+ "libbinder_ndk",
+ ],
+ static_libs: [
+ "android.hardware.threadnetwork-V1-ndk",
+ ],
+ test_suites: [
+ "general-tests",
+ "vts",
+ ],
+}
diff --git a/threadnetwork/aidl/vts/VtsHalThreadNetworkTargetTest.cpp b/threadnetwork/aidl/vts/VtsHalThreadNetworkTargetTest.cpp
new file mode 100644
index 0000000..04c6dea
--- /dev/null
+++ b/threadnetwork/aidl/vts/VtsHalThreadNetworkTargetTest.cpp
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "ThreadNetworkHalTargetTest"
+
+#include <future>
+
+#include <aidl/Gtest.h>
+#include <aidl/Vintf.h>
+#include <android-base/logging.h>
+#include <android/binder_auto_utils.h>
+#include <android/binder_manager.h>
+#include <binder/IServiceManager.h>
+#include <binder/ProcessState.h>
+#include <log/log.h>
+
+#include <aidl/android/hardware/threadnetwork/BnThreadChipCallback.h>
+#include <aidl/android/hardware/threadnetwork/IThreadChip.h>
+
+using aidl::android::hardware::threadnetwork::BnThreadChipCallback;
+using aidl::android::hardware::threadnetwork::IThreadChip;
+using android::ProcessState;
+using ndk::ScopedAStatus;
+using ndk::SpAIBinder;
+
+namespace {
+constexpr static int kCallbackTimeoutMs = 5000;
+} // namespace
+
+class ThreadChipCallback : public BnThreadChipCallback {
+ public:
+ ThreadChipCallback(const std::function<void(const std::vector<uint8_t>&)>& on_spinel_message_cb)
+ : on_spinel_message_cb_(on_spinel_message_cb) {}
+
+ ScopedAStatus onReceiveSpinelFrame(const std::vector<uint8_t>& in_aFrame) {
+ on_spinel_message_cb_(in_aFrame);
+ return ScopedAStatus::ok();
+ }
+
+ private:
+ std::function<void(const std::vector<uint8_t>&)> on_spinel_message_cb_;
+};
+
+class ThreadNetworkAidl : public testing::TestWithParam<std::string> {
+ public:
+ virtual void SetUp() override {
+ std::string serviceName = GetParam();
+
+ ALOGI("serviceName: %s", serviceName.c_str());
+
+ thread_chip = IThreadChip::fromBinder(
+ SpAIBinder(AServiceManager_waitForService(serviceName.c_str())));
+ ASSERT_NE(thread_chip, nullptr);
+ }
+
+ virtual void TearDown() override { thread_chip->close(); }
+
+ std::shared_ptr<IThreadChip> thread_chip;
+};
+
+TEST_P(ThreadNetworkAidl, Open) {
+ std::shared_ptr<ThreadChipCallback> callback =
+ ndk::SharedRefBase::make<ThreadChipCallback>([](auto /* data */) {});
+
+ EXPECT_TRUE(thread_chip->open(callback).isOk());
+}
+
+TEST_P(ThreadNetworkAidl, Close) {
+ std::shared_ptr<ThreadChipCallback> callback =
+ ndk::SharedRefBase::make<ThreadChipCallback>([](auto /* data */) {});
+
+ EXPECT_TRUE(thread_chip->open(callback).isOk());
+ EXPECT_TRUE(thread_chip->close().isOk());
+}
+
+TEST_P(ThreadNetworkAidl, Reset) {
+ std::shared_ptr<ThreadChipCallback> callback =
+ ndk::SharedRefBase::make<ThreadChipCallback>([](auto /* data */) {});
+
+ EXPECT_TRUE(thread_chip->open(callback).isOk());
+ EXPECT_TRUE(thread_chip->reset().isOk());
+}
+
+TEST_P(ThreadNetworkAidl, SendSpinelFrame) {
+ const uint8_t kCmdOffset = 2;
+ const uint8_t kMajorVersionOffset = 3;
+ const uint8_t kMinorVersionOffset = 4;
+ const std::vector<uint8_t> kGetSpinelProtocolVersion({0x81, 0x02, 0x01});
+ const std::vector<uint8_t> kGetSpinelProtocolVersionResponse({0x81, 0x06, 0x01, 0x04, 0x03});
+ uint8_t min_major_version = kGetSpinelProtocolVersionResponse[kMajorVersionOffset];
+ uint8_t min_minor_version = kGetSpinelProtocolVersionResponse[kMinorVersionOffset];
+ uint8_t major_version;
+ uint8_t minor_version;
+ std::promise<void> open_cb_promise;
+ std::future<void> open_cb_future{open_cb_promise.get_future()};
+ std::shared_ptr<ThreadChipCallback> callback;
+ std::vector<uint8_t> received_frame;
+ std::chrono::milliseconds timeout{kCallbackTimeoutMs};
+
+ callback = ndk::SharedRefBase::make<ThreadChipCallback>(
+ [&](const std::vector<uint8_t>& in_aFrame) {
+ if (in_aFrame.size() == kGetSpinelProtocolVersionResponse.size() &&
+ in_aFrame[kCmdOffset] == kGetSpinelProtocolVersionResponse[kCmdOffset]) {
+ major_version = in_aFrame[kMajorVersionOffset];
+ minor_version = in_aFrame[kMinorVersionOffset];
+ open_cb_promise.set_value();
+ }
+ });
+
+ ASSERT_NE(callback, nullptr);
+
+ EXPECT_TRUE(thread_chip->open(callback).isOk());
+
+ EXPECT_TRUE(thread_chip->sendSpinelFrame(kGetSpinelProtocolVersion).isOk());
+ EXPECT_EQ(open_cb_future.wait_for(timeout), std::future_status::ready);
+
+ EXPECT_GE(major_version, min_major_version);
+ if (major_version == min_major_version) {
+ EXPECT_GE(minor_version, min_minor_version);
+ }
+}
+
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(ThreadNetworkAidl);
+INSTANTIATE_TEST_SUITE_P(
+ Thread, ThreadNetworkAidl,
+ testing::ValuesIn(android::getAidlHalInstanceNames(IThreadChip::descriptor)),
+ android::PrintInstanceNameToString);
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ ProcessState::self()->setThreadPoolMaxThreadCount(1);
+ ProcessState::self()->startThreadPool();
+ return RUN_ALL_TESTS();
+}