From 24e527475f91bf52fe75e1342635ac08446c106c Mon Sep 17 00:00:00 2001 From: Zhanglong Xia Date: Wed, 14 Jun 2023 05:25:55 +0000 Subject: [PATCH] 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 --- .../compatibility_matrix.8.xml | 8 + threadnetwork/aidl/Android.bp | 22 +++ threadnetwork/aidl/OWNERS | 3 + .../hardware/threadnetwork/IThreadChip.aidl | 45 +++++ .../threadnetwork/IThreadChipCallback.aidl | 38 ++++ .../hardware/threadnetwork/IThreadChip.aidl | 90 +++++++++ .../threadnetwork/IThreadChipCallback.aidl | 31 +++ threadnetwork/aidl/default/Android.bp | 56 ++++++ ...roid.hardware.threadnetwork-service.sim.rc | 3 + threadnetwork/aidl/default/main.cpp | 30 +++ threadnetwork/aidl/default/service.cpp | 87 +++++++++ threadnetwork/aidl/default/service.hpp | 40 ++++ threadnetwork/aidl/default/thread_chip.cpp | 177 ++++++++++++++++++ threadnetwork/aidl/default/thread_chip.hpp | 62 ++++++ .../aidl/default/threadnetwork-default.xml | 6 + threadnetwork/aidl/default/utils.cpp | 36 ++++ threadnetwork/aidl/default/utils.hpp | 20 ++ threadnetwork/aidl/vts/Android.bp | 38 ++++ .../vts/VtsHalThreadNetworkTargetTest.cpp | 147 +++++++++++++++ 19 files changed, 939 insertions(+) create mode 100644 threadnetwork/aidl/Android.bp create mode 100644 threadnetwork/aidl/OWNERS create mode 100644 threadnetwork/aidl/aidl_api/android.hardware.threadnetwork/current/android/hardware/threadnetwork/IThreadChip.aidl create mode 100644 threadnetwork/aidl/aidl_api/android.hardware.threadnetwork/current/android/hardware/threadnetwork/IThreadChipCallback.aidl create mode 100644 threadnetwork/aidl/android/hardware/threadnetwork/IThreadChip.aidl create mode 100644 threadnetwork/aidl/android/hardware/threadnetwork/IThreadChipCallback.aidl create mode 100644 threadnetwork/aidl/default/Android.bp create mode 100644 threadnetwork/aidl/default/android.hardware.threadnetwork-service.sim.rc create mode 100644 threadnetwork/aidl/default/main.cpp create mode 100644 threadnetwork/aidl/default/service.cpp create mode 100644 threadnetwork/aidl/default/service.hpp create mode 100644 threadnetwork/aidl/default/thread_chip.cpp create mode 100644 threadnetwork/aidl/default/thread_chip.hpp create mode 100644 threadnetwork/aidl/default/threadnetwork-default.xml create mode 100644 threadnetwork/aidl/default/utils.cpp create mode 100644 threadnetwork/aidl/default/utils.hpp create mode 100644 threadnetwork/aidl/vts/Android.bp create mode 100644 threadnetwork/aidl/vts/VtsHalThreadNetworkTargetTest.cpp diff --git a/compatibility_matrices/compatibility_matrix.8.xml b/compatibility_matrices/compatibility_matrix.8.xml index 93beb92a90..9fbf93e9a5 100644 --- a/compatibility_matrices/compatibility_matrix.8.xml +++ b/compatibility_matrices/compatibility_matrix.8.xml @@ -834,4 +834,12 @@ .* + + android.hardware.threadnetwork + 1 + + IThreadChip + chip[0-9]+ + + diff --git a/threadnetwork/aidl/Android.bp b/threadnetwork/aidl/Android.bp new file mode 100644 index 0000000000..480ac0f377 --- /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 0000000000..e3111c8cac --- /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 0000000000..e4d4cbe788 --- /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 -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 0000000000..e86b3ec8b0 --- /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 -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 0000000000..eebaa46983 --- /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 0000000000..046edc3ead --- /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 0000000000..201306d053 --- /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 0000000000..2fb409cd93 --- /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 0000000000..9f2a789cfa --- /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 + +#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 0000000000..44bb99a4d5 --- /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 +#include +#include + +#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(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 0000000000..413704996c --- /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> 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 0000000000..b5cc7ebb58 --- /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 +#include +#include + +#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(context)->clientDeathCallback(); +} + +void ThreadChip::clientDeathCallback(void) { + ALOGW("Thread Network HAL client is dead."); + close(); +} + +void ThreadChip::handleReceivedFrame(void* context) { + static_cast(context)->handleReceivedFrame(); +} + +void ThreadChip::handleReceivedFrame(void) { + if (mCallback != nullptr) { + mCallback->onReceiveSpinelFrame(std::vector( + mRxFrameBuffer.GetFrame(), mRxFrameBuffer.GetFrame() + mRxFrameBuffer.GetLength())); + } + + mRxFrameBuffer.DiscardFrame(); +} + +ndk::ScopedAStatus ThreadChip::open(const std::shared_ptr& 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& in_frame) { + ndk::ScopedAStatus status; + otError error; + + VerifyOrExit(mCallback != nullptr, + status = errorStatus(ERROR_FAILED, "The interface is not open")); + + error = mInterface.SendFrame(reinterpret_cast(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 0000000000..d444374f9e --- /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 +#include + +#include "hdlc_interface.hpp" +#include "lib/spinel/spinel_interface.hpp" +#include "mainloop.hpp" + +#include +#include + +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& in_callback) override; + ndk::ScopedAStatus close() override; + ndk::ScopedAStatus sendSpinelFrame(const std::vector& 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 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 0000000000..d7dee1e95b --- /dev/null +++ b/threadnetwork/aidl/default/threadnetwork-default.xml @@ -0,0 +1,6 @@ + + + android.hardware.threadnetwork + IThreadChip/chip0 + + diff --git a/threadnetwork/aidl/default/utils.cpp b/threadnetwork/aidl/default/utils.cpp new file mode 100644 index 0000000000..a8f3464a8c --- /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 + +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 0000000000..279c0ba446 --- /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 diff --git a/threadnetwork/aidl/vts/Android.bp b/threadnetwork/aidl/vts/Android.bp new file mode 100644 index 0000000000..864e885a37 --- /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 0000000000..04c6deaf2a --- /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 + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +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&)>& on_spinel_message_cb) + : on_spinel_message_cb_(on_spinel_message_cb) {} + + ScopedAStatus onReceiveSpinelFrame(const std::vector& in_aFrame) { + on_spinel_message_cb_(in_aFrame); + return ScopedAStatus::ok(); + } + + private: + std::function&)> on_spinel_message_cb_; +}; + +class ThreadNetworkAidl : public testing::TestWithParam { + 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 thread_chip; +}; + +TEST_P(ThreadNetworkAidl, Open) { + std::shared_ptr callback = + ndk::SharedRefBase::make([](auto /* data */) {}); + + EXPECT_TRUE(thread_chip->open(callback).isOk()); +} + +TEST_P(ThreadNetworkAidl, Close) { + std::shared_ptr callback = + ndk::SharedRefBase::make([](auto /* data */) {}); + + EXPECT_TRUE(thread_chip->open(callback).isOk()); + EXPECT_TRUE(thread_chip->close().isOk()); +} + +TEST_P(ThreadNetworkAidl, Reset) { + std::shared_ptr callback = + ndk::SharedRefBase::make([](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 kGetSpinelProtocolVersion({0x81, 0x02, 0x01}); + const std::vector 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 open_cb_promise; + std::future open_cb_future{open_cb_promise.get_future()}; + std::shared_ptr callback; + std::vector received_frame; + std::chrono::milliseconds timeout{kCallbackTimeoutMs}; + + callback = ndk::SharedRefBase::make( + [&](const std::vector& 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(); +}