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/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>