diff --git a/threadnetwork/aidl/default/Android.bp b/threadnetwork/aidl/default/Android.bp index 816f89225b..82a76e0669 100644 --- a/threadnetwork/aidl/default/Android.bp +++ b/threadnetwork/aidl/default/Android.bp @@ -37,6 +37,7 @@ cc_binary { srcs: [ "main.cpp", "service.cpp", + "socket_interface.cpp", "thread_chip.cpp", "utils.cpp", ], @@ -63,6 +64,7 @@ cc_fuzz { ], srcs: [ + "socket_interface.cpp", "thread_chip.cpp", "utils.cpp", "fuzzer.cpp", diff --git a/threadnetwork/aidl/default/socket_interface.cpp b/threadnetwork/aidl/default/socket_interface.cpp new file mode 100644 index 0000000000..f874209aaf --- /dev/null +++ b/threadnetwork/aidl/default/socket_interface.cpp @@ -0,0 +1,301 @@ +/* + * Copyright (C) 2024 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. + */ + +/** + * @file + * This file includes the implementation for the Socket interface to radio + * (RCP). + */ + +#include "socket_interface.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "common/code_utils.hpp" +#include "openthread/openthread-system.h" +#include "platform-posix.h" + +namespace aidl { +namespace android { +namespace hardware { +namespace threadnetwork { + +SocketInterface::SocketInterface(const ot::Url::Url& aRadioUrl) + : mReceiveFrameCallback(nullptr), + mReceiveFrameContext(nullptr), + mReceiveFrameBuffer(nullptr), + mSockFd(-1), + mRadioUrl(aRadioUrl) { + memset(&mInterfaceMetrics, 0, sizeof(mInterfaceMetrics)); + mInterfaceMetrics.mRcpInterfaceType = kSpinelInterfaceTypeVendor; +} + +otError SocketInterface::Init(ReceiveFrameCallback aCallback, void* aCallbackContext, + RxFrameBuffer& aFrameBuffer) { + otError error = OT_ERROR_NONE; + + VerifyOrExit(mSockFd == -1, error = OT_ERROR_ALREADY); + + WaitForSocketFileCreated(mRadioUrl.GetPath()); + + mSockFd = OpenFile(mRadioUrl); + VerifyOrExit(mSockFd != -1, error = OT_ERROR_FAILED); + + mReceiveFrameCallback = aCallback; + mReceiveFrameContext = aCallbackContext; + mReceiveFrameBuffer = &aFrameBuffer; + +exit: + return error; +} + +SocketInterface::~SocketInterface(void) { + Deinit(); +} + +void SocketInterface::Deinit(void) { + CloseFile(); + + mReceiveFrameCallback = nullptr; + mReceiveFrameContext = nullptr; + mReceiveFrameBuffer = nullptr; +} + +otError SocketInterface::SendFrame(const uint8_t* aFrame, uint16_t aLength) { + Write(aFrame, aLength); + + return OT_ERROR_NONE; +} + +otError SocketInterface::WaitForFrame(uint64_t aTimeoutUs) { + otError error = OT_ERROR_NONE; + struct timeval timeout; + timeout.tv_sec = static_cast(aTimeoutUs / US_PER_S); + timeout.tv_usec = static_cast(aTimeoutUs % US_PER_S); + + fd_set readFds; + fd_set errorFds; + int rval; + + FD_ZERO(&readFds); + FD_ZERO(&errorFds); + FD_SET(mSockFd, &readFds); + FD_SET(mSockFd, &errorFds); + + rval = TEMP_FAILURE_RETRY(select(mSockFd + 1, &readFds, nullptr, &errorFds, &timeout)); + + if (rval > 0) { + if (FD_ISSET(mSockFd, &readFds)) { + Read(); + } else if (FD_ISSET(mSockFd, &errorFds)) { + DieNowWithMessage("RCP error", OT_EXIT_FAILURE); + } else { + DieNow(OT_EXIT_FAILURE); + } + } else if (rval == 0) { + ExitNow(error = OT_ERROR_RESPONSE_TIMEOUT); + } else { + DieNowWithMessage("wait response", OT_EXIT_FAILURE); + } + +exit: + return error; +} + +void SocketInterface::UpdateFdSet(void* aMainloopContext) { + otSysMainloopContext* context = reinterpret_cast(aMainloopContext); + + assert(context != nullptr); + + FD_SET(mSockFd, &context->mReadFdSet); + + if (context->mMaxFd < mSockFd) { + context->mMaxFd = mSockFd; + } +} + +void SocketInterface::Process(const void* aMainloopContext) { + const otSysMainloopContext* context = + reinterpret_cast(aMainloopContext); + + assert(context != nullptr); + + if (FD_ISSET(mSockFd, &context->mReadFdSet)) { + Read(); + } +} + +void SocketInterface::Read(void) { + uint8_t buffer[kMaxFrameSize]; + + ssize_t rval = TEMP_FAILURE_RETRY(read(mSockFd, buffer, sizeof(buffer))); + + if (rval > 0) { + ProcessReceivedData(buffer, static_cast(rval)); + } else if (rval < 0) { + DieNow(OT_EXIT_ERROR_ERRNO); + } else { + otLogCritPlat("Socket connection is closed by remote."); + exit(OT_EXIT_FAILURE); + } +} + +void SocketInterface::Write(const uint8_t* aFrame, uint16_t aLength) { + ssize_t rval = TEMP_FAILURE_RETRY(write(mSockFd, aFrame, aLength)); + VerifyOrDie(rval >= 0, OT_EXIT_ERROR_ERRNO); + VerifyOrDie(rval > 0, OT_EXIT_FAILURE); +} + +void SocketInterface::ProcessReceivedData(const uint8_t* aBuffer, uint16_t aLength) { + while (aLength--) { + uint8_t byte = *aBuffer++; + if (mReceiveFrameBuffer->CanWrite(sizeof(uint8_t))) { + IgnoreError(mReceiveFrameBuffer->WriteByte(byte)); + } else { + HandleSocketFrame(this, OT_ERROR_NO_BUFS); + return; + } + } + HandleSocketFrame(this, OT_ERROR_NONE); +} + +void SocketInterface::HandleSocketFrame(void* aContext, otError aError) { + static_cast(aContext)->HandleSocketFrame(aError); +} + +void SocketInterface::HandleSocketFrame(otError aError) { + VerifyOrExit((mReceiveFrameCallback != nullptr) && (mReceiveFrameBuffer != nullptr)); + + if (aError == OT_ERROR_NONE) { + mReceiveFrameCallback(mReceiveFrameContext); + } else { + mReceiveFrameBuffer->DiscardFrame(); + otLogWarnPlat("Process socket frame failed: %s", otThreadErrorToString(aError)); + } + +exit: + return; +} + +int SocketInterface::OpenFile(const ot::Url::Url& aRadioUrl) { + int fd = -1; + sockaddr_un serverAddress; + + VerifyOrExit(sizeof(serverAddress.sun_path) > strlen(aRadioUrl.GetPath()), + otLogCritPlat("Invalid file path length")); + strncpy(serverAddress.sun_path, aRadioUrl.GetPath(), sizeof(serverAddress.sun_path)); + serverAddress.sun_family = AF_UNIX; + + fd = socket(AF_UNIX, SOCK_SEQPACKET, 0); + VerifyOrExit(fd != -1, otLogCritPlat("open(): errno=%s", strerror(errno))); + + if (connect(fd, reinterpret_cast(&serverAddress), sizeof(serverAddress)) == + -1) { + otLogCritPlat("connect(): errno=%s", strerror(errno)); + close(fd); + fd = -1; + } + +exit: + return fd; +} + +void SocketInterface::CloseFile(void) { + VerifyOrExit(mSockFd != -1); + + VerifyOrExit(0 == close(mSockFd), otLogCritPlat("close(): errno=%s", strerror(errno))); + VerifyOrExit(wait(nullptr) != -1 || errno == ECHILD, + otLogCritPlat("wait(): errno=%s", strerror(errno))); + + mSockFd = -1; + +exit: + return; +} + +void SocketInterface::WaitForSocketFileCreated(const char* aPath) { + int inotifyFd; + int wd; + int lastSlashIdx; + std::string folderPath; + std::string socketPath(aPath); + + VerifyOrExit(!IsSocketFileExisted(aPath)); + + inotifyFd = inotify_init(); + VerifyOrDie(inotifyFd != -1, OT_EXIT_ERROR_ERRNO); + + lastSlashIdx = socketPath.find_last_of('/'); + VerifyOrDie(lastSlashIdx != std::string::npos, OT_EXIT_ERROR_ERRNO); + + folderPath = socketPath.substr(0, lastSlashIdx); + wd = inotify_add_watch(inotifyFd, folderPath.c_str(), IN_CREATE); + VerifyOrDie(wd != -1, OT_EXIT_ERROR_ERRNO); + + otLogInfoPlat("Waiting for socket file %s be created...", aPath); + + while (true) { + fd_set fds; + FD_ZERO(&fds); + FD_SET(inotifyFd, &fds); + struct timeval timeout = {kMaxSelectTimeMs / MS_PER_S, + (kMaxSelectTimeMs % MS_PER_S) * MS_PER_S}; + + int rval = select(inotifyFd + 1, &fds, nullptr, nullptr, &timeout); + VerifyOrDie(rval >= 0, OT_EXIT_ERROR_ERRNO); + + if (rval == 0 && IsSocketFileExisted(aPath)) { + break; + } + + if (FD_ISSET(inotifyFd, &fds)) { + char buffer[sizeof(struct inotify_event)]; + ssize_t bytesRead = read(inotifyFd, buffer, sizeof(buffer)); + + VerifyOrDie(bytesRead >= 0, OT_EXIT_ERROR_ERRNO); + + struct inotify_event* event = reinterpret_cast(buffer); + if ((event->mask & IN_CREATE) && IsSocketFileExisted(aPath)) { + break; + } + } + } + + close(inotifyFd); + +exit: + otLogInfoPlat("Socket file: %s is created", aPath); + return; +} + +bool SocketInterface::IsSocketFileExisted(const char* aPath) { + struct stat st; + return stat(aPath, &st) == 0 && S_ISSOCK(st.st_mode); +} + +} // namespace threadnetwork +} // namespace hardware +} // namespace android +} // namespace aidl diff --git a/threadnetwork/aidl/default/socket_interface.hpp b/threadnetwork/aidl/default/socket_interface.hpp new file mode 100644 index 0000000000..f88e92670c --- /dev/null +++ b/threadnetwork/aidl/default/socket_interface.hpp @@ -0,0 +1,258 @@ +/* + * Copyright (C) 2024 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. + */ + +/** + * @file + * This file includes definitions for the Socket interface interface to radio + * (RCP). + */ + +#include "lib/spinel/spinel_interface.hpp" +#include "lib/url/url.hpp" + +namespace aidl { +namespace android { +namespace hardware { +namespace threadnetwork { + +/** + * Defines a Socket interface to the Radio Co-processor (RCP) + * + */ +class SocketInterface : public ot::Spinel::SpinelInterface { + public: + /** + * Initializes the object. + * + * @param[in] aRadioUrl RadioUrl parsed from radio url. + * + */ + explicit SocketInterface(const ot::Url::Url& aRadioUrl); + + /** + * This destructor deinitializes the object. + * + */ + ~SocketInterface(); + + /** + * Initializes the interface to the Radio Co-processor (RCP) + * + * @note This method should be called before reading and sending Spinel + * frames to the interface. + * + * @param[in] aCallback Callback on frame received + * @param[in] aCallbackContext Callback context + * @param[in] aFrameBuffer A reference to a `RxFrameBuffer` object. + * + * @retval OT_ERROR_NONE The interface is initialized successfully + * @retval OT_ERROR_ALREADY The interface is already initialized. + * @retval OT_ERROR_FAILED Failed to initialize the interface. + * + */ + otError Init(ReceiveFrameCallback aCallback, void* aCallbackContext, + RxFrameBuffer& aFrameBuffer); + + /** + * Deinitializes the interface to the RCP. + * + */ + void Deinit(void); + + /** + * Sends a Spinel frame to Radio Co-processor (RCP) over the + * socket. + * + * @param[in] aFrame A pointer to buffer containing the Spinel frame to + * send. + * @param[in] aLength The length (number of bytes) in the frame. + * + * @retval OT_ERROR_NONE Successfully sent the Spinel frame. + * @retval OT_ERROR_FAILED Failed to send a frame. + * + */ + otError SendFrame(const uint8_t* aFrame, uint16_t aLength); + + /** + * Waits for receiving part or all of Spinel frame within specified + * interval. + * + * @param[in] aTimeout The timeout value in microseconds. + * + * @retval OT_ERROR_NONE Part or all of Spinel frame is + * received. + * @retval OT_ERROR_RESPONSE_TIMEOUT No Spinel frame is received within @p + * aTimeout. + * @retval OT_EXIT_FAILURE RCP error + * + */ + otError WaitForFrame(uint64_t aTimeoutUs); + + /** + * Updates the file descriptor sets with file descriptors used by the radio + * driver. + * + * @param[in,out] aMainloopContext A pointer to the mainloop context + * containing fd_sets. + * + */ + void UpdateFdSet(void* aMainloopContext); + + /** + * Performs radio driver processing. + * + * @param[in] aMainloopContext A pointer to the mainloop context + * containing fd_sets. + * + */ + void Process(const void* aMainloopContext); + + /** + * Returns the bus speed between the host and the radio. + * + * @return Bus speed in bits/second. + * + */ + uint32_t GetBusSpeed(void) const { return 1000000; } + + /** + * Hardware resets the RCP. + * + * @retval OT_ERROR_NONE Successfully reset the RCP. + * @retval OT_ERROR_NOT_IMPLEMENT The hardware reset is not implemented. + * + */ + otError HardwareReset(void) { return OT_ERROR_NOT_IMPLEMENTED; } + + /** + * Returns the RCP interface metrics. + * + * @return The RCP interface metrics. + * + */ + const otRcpInterfaceMetrics* GetRcpInterfaceMetrics(void) const { return &mInterfaceMetrics; } + + /** + * Indicates whether or not the given interface matches this interface name. + * + * @param[in] aInterfaceName A pointer to the interface name. + * + * @retval TRUE The given interface name matches this interface name. + * @retval FALSE The given interface name doesn't match this interface + * name. + */ + static bool IsInterfaceNameMatch(const char* aInterfaceName) { + static const char kInterfaceName[] = "spinel+socket"; + return (strncmp(aInterfaceName, kInterfaceName, strlen(kInterfaceName)) == 0); + } + + private: + /** + * Instructs `SocketInterface` to read data from radio over the + * socket. + * + * If a full Spinel frame is received, this method invokes the + * `HandleSocketFrame()` (on the `aCallback` object from constructor) to + * pass the received frame to be processed. + * + */ + void Read(void); + + /** + * Writes a given frame to the socket. + * + * @param[in] aFrame A pointer to buffer containing the frame to write. + * @param[in] aLength The length (number of bytes) in the frame. + * + */ + void Write(const uint8_t* aFrame, uint16_t aLength); + + /** + * Process received data. + * + * If a full frame is finished processing and we obtain the raw Spinel + * frame, this method invokes the `HandleSocketFrame()` (on the `aCallback` + * object from constructor) to pass the received frame to be processed. + * + * @param[in] aBuffer A pointer to buffer containing data. + * @param[in] aLength The length (number of bytes) in the buffer. + * + */ + void ProcessReceivedData(const uint8_t* aBuffer, uint16_t aLength); + + static void HandleSocketFrame(void* aContext, otError aError); + void HandleSocketFrame(otError aError); + + /** + * Opens file specified by aRadioUrl. + * + * @param[in] aRadioUrl A reference to object containing path to file and + * data for configuring the connection with tty type file. + * + * @retval The file descriptor of newly opened file. + * @retval -1 Fail to open file. + * + */ + int OpenFile(const ot::Url::Url& aRadioUrl); + + /** + * Closes file associated with the file descriptor. + * + */ + void CloseFile(void); + + /** + * Check if socket file is created. + * + * @param[in] aPath Socket file path name. + * + * @retval TRUE The required socket file is created. + * @retval FALSE The required socket file is not created. + * + */ + bool IsSocketFileExisted(const char* aPath); + + /** + * Wait until the socket file is created. + * + * @param[in] aPath Socket file path name. + * + */ + void WaitForSocketFileCreated(const char* aPath); + + enum { + kMaxSelectTimeMs = 2000, ///< Maximum wait time in Milliseconds for file + ///< descriptor to become available. + }; + + ReceiveFrameCallback mReceiveFrameCallback; + void* mReceiveFrameContext; + RxFrameBuffer* mReceiveFrameBuffer; + + int mSockFd; + const ot::Url::Url& mRadioUrl; + + otRcpInterfaceMetrics mInterfaceMetrics; + + // Non-copyable, intentionally not implemented. + SocketInterface(const SocketInterface&); + SocketInterface& operator=(const SocketInterface&); +}; + +} // namespace threadnetwork +} // namespace hardware +} // namespace android +} // namespace aidl diff --git a/threadnetwork/aidl/default/thread_chip.cpp b/threadnetwork/aidl/default/thread_chip.cpp index ed34e630b8..d1e1d4ceaf 100644 --- a/threadnetwork/aidl/default/thread_chip.cpp +++ b/threadnetwork/aidl/default/thread_chip.cpp @@ -24,6 +24,7 @@ #include #include "hdlc_interface.hpp" +#include "socket_interface.hpp" #include "spi_interface.hpp" namespace aidl { @@ -43,6 +44,8 @@ ThreadChip::ThreadChip(char* url) : mUrl(), mRxFrameBuffer(), mCallback(nullptr) mSpinelInterface = std::make_shared(mUrl); } else if (ot::Posix::HdlcInterface::IsInterfaceNameMatch(interfaceName)) { mSpinelInterface = std::make_shared(mUrl); + } else if (SocketInterface::IsInterfaceNameMatch(interfaceName)) { + mSpinelInterface = std::make_shared(mUrl); } else { ALOGE("The interface \"%s\" is not supported", interfaceName); exit(EXIT_FAILURE);