diff --git a/examples/network-manager-app/linux/BUILD.gn b/examples/network-manager-app/linux/BUILD.gn index 1dee694c38616e..bd11f78b219d37 100644 --- a/examples/network-manager-app/linux/BUILD.gn +++ b/examples/network-manager-app/linux/BUILD.gn @@ -15,7 +15,11 @@ import("//build_overrides/build.gni") import("//build_overrides/chip.gni") -executable("network-manager-app") { +declare_args() { + matter_enable_ubus = false +} + +executable("matter-network-manager-app") { sources = [ "include/CHIPProjectAppConfig.h", "main.cpp", @@ -27,12 +31,31 @@ executable("network-manager-app") { "${chip_root}/src/lib", ] + libs = [] + defines = [] include_dirs = [ "include" ] + + if (matter_enable_ubus) { + defines += [ "MATTER_ENABLE_UBUS=1" ] + sources += [ + "UbusManager.cpp", + "UbusManager.h", + "UbusSchema.c", + "UbusSchema.h", + "UloopHandler.cpp", + "UloopHandler.h", + ] + libs += [ + "ubox", + "ubus", + ] + } + output_dir = root_out_dir } group("linux") { - deps = [ ":network-manager-app" ] + deps = [ ":matter-network-manager-app" ] } group("default") { diff --git a/examples/network-manager-app/linux/UbusManager.cpp b/examples/network-manager-app/linux/UbusManager.cpp new file mode 100644 index 00000000000000..2ec22c74487a8c --- /dev/null +++ b/examples/network-manager-app/linux/UbusManager.cpp @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2024 Project CHIP Authors + * All rights reserved. + * + * 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 "UbusManager.h" + +#include "UbusSchema.h" +#include "UloopHandler.h" +#include + +#include + +using namespace chip; + +CHIP_ERROR UbusManager::Init() +{ + VerifyOrReturnError(!mInitialized, CHIP_ERROR_INCORRECT_STATE); + + UloopHandler::Register(); + + int status; + if ((status = ubus_connect_ctx(&mContext, nullptr))) + { + ChipLogError(AppServer, "Failed to connect to ubus: %s", ubus_strerror(status)); + UloopHandler::Unregister(); + return CHIP_ERROR_INTERNAL; + } + + mObjectType = kMatterUbusObjectType; + mObject.type = &mObjectType; // can't use kMatterUbusObjectType directly, must be mutable + mObject.name = mObjectType.name; + mObject.methods = mObjectType.methods; + mObject.n_methods = mObjectType.n_methods; + if ((status = ubus_add_object(&mContext, &mObject))) + { + ChipLogError(AppServer, "Failed to register ubus service: %s", ubus_strerror(status)); + ubus_shutdown(&mContext); + UloopHandler::Unregister(); + return CHIP_ERROR_INTERNAL; + } + + ubus_add_uloop(&mContext); + mInitialized = true; + + return CHIP_NO_ERROR; +} + +void UbusManager::Shutdown() +{ + VerifyOrReturn(mInitialized); + mInitialized = false; + ubus_shutdown(&mContext); + UloopHandler::Unregister(); +} + +int MatterUbusHandleStatus(struct ubus_context * ctx, struct ubus_object * obj, struct ubus_request_data * req, const char * method, + struct blob_attr * msg) +{ + FabricTable const & fabricTable = Server::GetInstance().GetFabricTable(); + + blob_buf blob{}; + blob_buf_init(&blob, 0); + blobmsg_add_u8(&blob, "commissioned", fabricTable.FabricCount() > 0); + ubus_send_reply(ctx, req, blob.head); + blob_buf_free(&blob); + return 0; +} diff --git a/examples/network-manager-app/linux/UbusManager.h b/examples/network-manager-app/linux/UbusManager.h new file mode 100644 index 00000000000000..2cd14daac5c000 --- /dev/null +++ b/examples/network-manager-app/linux/UbusManager.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2024 Project CHIP Authors + * All rights reserved. + * + * 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 + +class UbusManager +{ +public: + UbusManager() = default; + ~UbusManager() { Shutdown(); } + + CHIP_ERROR Init(); + void Shutdown(); + +private: + bool mInitialized = false; + ubus_context mContext; + ubus_object_type mObjectType; + ubus_object mObject; +}; diff --git a/examples/network-manager-app/linux/UbusSchema.c b/examples/network-manager-app/linux/UbusSchema.c new file mode 100644 index 00000000000000..c8aaae8df1ffc8 --- /dev/null +++ b/examples/network-manager-app/linux/UbusSchema.c @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2024 Project CHIP Authors + * All rights reserved. + * + * 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 "UbusSchema.h" + +const struct ubus_method kMatterMethods[] = { + UBUS_METHOD_NOARG("status", &MatterUbusHandleStatus), +}; + +const struct ubus_object_type kMatterUbusObjectType = UBUS_OBJECT_TYPE("matter", kMatterMethods); diff --git a/examples/network-manager-app/linux/UbusSchema.h b/examples/network-manager-app/linux/UbusSchema.h new file mode 100644 index 00000000000000..58624686a3ab0a --- /dev/null +++ b/examples/network-manager-app/linux/UbusSchema.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2024 Project CHIP Authors + * All rights reserved. + * + * 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 + +#ifdef __cplusplus +extern "C" { +#endif + +// Note these constants are defined in a C file because the macros +// used to generate these structures don't compile correctly in C++. + +extern const struct ubus_object_type kMatterUbusObjectType; + +int MatterUbusHandleStatus(struct ubus_context * ctx, struct ubus_object * obj, struct ubus_request_data * req, const char * method, + struct blob_attr * msg); + +#ifdef __cplusplus +} +#endif diff --git a/examples/network-manager-app/linux/UloopHandler.cpp b/examples/network-manager-app/linux/UloopHandler.cpp new file mode 100644 index 00000000000000..c40f22c14beb34 --- /dev/null +++ b/examples/network-manager-app/linux/UloopHandler.cpp @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2024 Project CHIP Authors + * All rights reserved. + * + * 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 "UloopHandler.h" + +#include +#include + +#include + +extern "C" { +#include +} + +namespace chip { + +namespace { +System::LayerSocketsLoop & SystemLayer() +{ + return static_cast(DeviceLayer::SystemLayer()); +} +} // namespace + +UloopHandler UloopHandler::gInstance; +int UloopHandler::gRegistered = 0; + +CHIP_ERROR UloopHandler::Register() +{ + VerifyOrReturnError(gRegistered++ == 0, CHIP_NO_ERROR); + + if (uloop_fd_set_cb != nullptr) + { + gRegistered = 0; + return CHIP_ERROR_INCORRECT_STATE; + } + + ChipLogDetail(chipSystemLayer, "Registering uloop handler"); + uloop_fd_set_cb = &UloopFdSet; + uloop_init(); + SystemLayer().AddLoopHandler(gInstance); + + return CHIP_NO_ERROR; +} + +void UloopHandler::Unregister() +{ + auto count = gRegistered--; + VerifyOrDie(count > 0); + VerifyOrReturn(count == 1); + + ChipLogDetail(chipSystemLayer, "Unregistering uloop handler"); + SystemLayer().RemoveLoopHandler(gInstance); + uloop_done(); + uloop_fd_set_cb = nullptr; +} + +System::Clock::Timestamp UloopHandler::PrepareEvents(System::Clock::Timestamp now) +{ + std::chrono::duration timeout(uloop_get_next_timeout()); + VerifyOrReturnValue(timeout.count() >= 0, System::Clock::Timestamp::max()); + ChipLogDetail(chipSystemLayer, "Next uloop timeout: %d", timeout.count()); + return now + std::chrono::duration_cast(timeout); +} + +void UloopHandler::HandleEvents() +{ + uloop_run_timeout(0); +} + +void UloopHandler::UloopFdSet(struct uloop_fd * fd, unsigned int events) +{ + ChipLogDetail(chipSystemLayer, "Uloop fd_set(%d, %d)", fd->fd, events); + + auto & system = SystemLayer(); + System::SocketWatchToken watch; + CHIP_ERROR err = system.StartWatchingSocket(fd->fd, &watch); // or find existing watch + if (events != 0) + { + int changed = fd->flags ^ events; + if (changed & ULOOP_READ) + { + if (events & ULOOP_READ) + { + system.RequestCallbackOnPendingRead(watch); + } + else + { + system.ClearCallbackOnPendingRead(watch); + } + } + if (changed & ULOOP_WRITE) + { + if (events & ULOOP_WRITE) + { + system.RequestCallbackOnPendingWrite(watch); + } + else + { + system.ClearCallbackOnPendingWrite(watch); + } + } + } + else + { + VerifyOrReturn(err == CHIP_NO_ERROR); // shouldn't happen, but ignore + SuccessOrExit(err = system.StopWatchingSocket(&watch)); + } + + return; +exit: + ChipLogError(chipSystemLayer, "Failed to handle uloop fd_set(%d, %d): %" CHIP_ERROR_FORMAT, fd->fd, events, err.Format()); +} + +} // namespace chip diff --git a/examples/network-manager-app/linux/UloopHandler.h b/examples/network-manager-app/linux/UloopHandler.h new file mode 100644 index 00000000000000..e3f0a9ff13d14e --- /dev/null +++ b/examples/network-manager-app/linux/UloopHandler.h @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2024 Project CHIP Authors + * All rights reserved. + * + * 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 + +extern "C" struct uloop_fd; + +namespace chip { + +/** Integrates uloop into the SystemLayer event loop. */ +class UloopHandler final : private System::EventLoopHandler +{ +public: + /** + * Starts driving uloop via the SystemLayer event loop. + * May be called multiple times. + * Each successful call to Register() must be balanced by a call to Unregister(). + */ + static CHIP_ERROR Register(); + static void Unregister(); + +private: + constexpr UloopHandler() = default; + + System::Clock::Timestamp PrepareEvents(System::Clock::Timestamp now) override; + void HandleEvents() override; + + static void UloopFdSet(struct uloop_fd * fd, unsigned int events); + + static UloopHandler gInstance; + static int gRegistered; +}; + +} // namespace chip diff --git a/examples/network-manager-app/linux/args.gni b/examples/network-manager-app/linux/args.gni index 53bb53d2b1b2a1..a67e7994a20a3f 100644 --- a/examples/network-manager-app/linux/args.gni +++ b/examples/network-manager-app/linux/args.gni @@ -21,4 +21,4 @@ chip_project_config_include_dirs = [ "${chip_root}/config/standalone", ] -chip_config_network_layer_ble = false +chip_enable_wifi = false diff --git a/examples/network-manager-app/linux/include/CHIPProjectAppConfig.h b/examples/network-manager-app/linux/include/CHIPProjectAppConfig.h index 6fa4c82568eb70..a11f26dcc91963 100644 --- a/examples/network-manager-app/linux/include/CHIPProjectAppConfig.h +++ b/examples/network-manager-app/linux/include/CHIPProjectAppConfig.h @@ -18,8 +18,12 @@ #pragma once -#define CHIP_DEVICE_CONFIG_DEVICE_TYPE 0xFFF10010 // TODO: ID-TBD +#define CHIP_DEVICE_CONFIG_DEVICE_TYPE 144 // 0x0090 Network Infrastructure Manager #define CHIP_DEVICE_CONFIG_DEVICE_NAME "Network Infrastructure Manager" +#ifndef MATTER_ENABLE_UBUS +#define MATTER_ENABLE_UBUS 0 +#endif + // Inherit defaults from config/standalone/CHIPProjectConfig.h #include diff --git a/examples/network-manager-app/linux/main.cpp b/examples/network-manager-app/linux/main.cpp index ee6763a6d3d47d..e7c8382ca4d946 100644 --- a/examples/network-manager-app/linux/main.cpp +++ b/examples/network-manager-app/linux/main.cpp @@ -20,28 +20,61 @@ #include #include +#if MATTER_ENABLE_UBUS +#include "UbusManager.h" +#endif // MATTER_ENABLE_UBUS + using namespace chip; using namespace chip::app; using namespace chip::app::Clusters; -void ApplicationInit() {} -void ApplicationShutdown() {} - ByteSpan ByteSpanFromCharSpan(CharSpan span) { return ByteSpan(Uint8::from_const_char(span.data()), span.size()); } -int main(int argc, char * argv[]) +void ApplicationInit() { - if (ChipLinuxAppInit(argc, argv) != 0) - { - return -1; - } - WiFiNetworkManagementServer::Instance().SetNetworkCredentials(ByteSpanFromCharSpan("MatterAP"_span), ByteSpanFromCharSpan("Setec Astronomy"_span)); +} + +class ApplicationMainLoop final : public DefaultAppMainLoopImplementation +{ +public: + void RunMainLoop() override; + int Status() { return mStarted ? 0 : 1; } + +private: + bool mStarted = false; +}; + +void ApplicationMainLoop::RunMainLoop() +{ + CHIP_ERROR err = CHIP_NO_ERROR; + +#if MATTER_ENABLE_UBUS + UbusManager ubus; + SuccessOrExit(err = ubus.Init()); +#endif // MATTER_ENABLE_UBUS + + mStarted = true; + DefaultAppMainLoopImplementation::RunMainLoop(); + return; + +exit: + __attribute__((unused)); // label may be unused + ChipLogError(Zcl, "Failed to start application run loop: %" CHIP_ERROR_FORMAT, err.Format()); +} + +void ApplicationShutdown() {} + +int main(int argc, char * argv[]) +{ + int ret = ChipLinuxAppInit(argc, argv); + VerifyOrReturnValue(ret == 0, ret); - ChipLinuxAppMainLoop(); - return 0; + ApplicationMainLoop application; + ChipLinuxAppMainLoop(&application); + return application.Status(); } diff --git a/src/lib/support/IntrusiveList.h b/src/lib/support/IntrusiveList.h index 961113e8d1ee6b..be9c139babc3dc 100644 --- a/src/lib/support/IntrusiveList.h +++ b/src/lib/support/IntrusiveList.h @@ -84,7 +84,7 @@ enum class IntrusiveMode class IntrusiveListNodePrivateBase { public: - IntrusiveListNodePrivateBase() : mPrev(nullptr), mNext(nullptr) {} + constexpr IntrusiveListNodePrivateBase() : mPrev(nullptr), mNext(nullptr) {} ~IntrusiveListNodePrivateBase() { VerifyOrDie(!IsInList()); } // Note: The copy construct/assignment is not provided because the list node state is not copyable. @@ -98,7 +98,7 @@ class IntrusiveListNodePrivateBase private: friend class IntrusiveListBase; - IntrusiveListNodePrivateBase(IntrusiveListNodePrivateBase * prev, IntrusiveListNodePrivateBase * next) : + constexpr IntrusiveListNodePrivateBase(IntrusiveListNodePrivateBase * prev, IntrusiveListNodePrivateBase * next) : mPrev(prev), mNext(next) {} @@ -284,7 +284,7 @@ class IntrusiveListBase // ^ | // \------------------------------------------/ // - IntrusiveListBase() : mNode(&mNode, &mNode) {} + constexpr IntrusiveListBase() : mNode(&mNode, &mNode) {} ~IntrusiveListBase() { VerifyOrDie(Empty()); @@ -399,7 +399,7 @@ template #if CHIP_SYSTEM_CONFIG_USE_SOCKETS +#include #include #endif // CHIP_SYSTEM_CONFIG_USE_SOCKETS @@ -242,6 +243,7 @@ class LayerSockets : public Layer * Initialize watching for events on a file descriptor. * * Returns an opaque token through @a tokenOut that must be passed to subsequent operations for this file descriptor. + * Multiple calls to start watching the same file descriptor will return the same token. * StopWatchingSocket() must be called before closing the file descriptor. */ virtual CHIP_ERROR StartWatchingSocket(int fd, SocketWatchToken * tokenOut) = 0; @@ -287,6 +289,23 @@ class LayerSockets : public Layer virtual SocketWatchToken InvalidSocketWatchToken() = 0; }; +class LayerSocketsLoop; + +/** + * Enables the participation of subordinate event loops in the SystemLayer event loop. + */ +class EventLoopHandler : public chip::IntrusiveListNodeBase<> +{ +public: + virtual ~EventLoopHandler() {} + + /** Prepares events and returns the next requested wake time. */ + virtual Clock::Timestamp PrepareEvents(Clock::Timestamp now) { return Clock::Timestamp::max(); } + + /** Handles / dispatches pending events. */ + virtual void HandleEvents() = 0; +}; + class LayerSocketsLoop : public LayerSockets { public: @@ -297,6 +316,9 @@ class LayerSocketsLoop : public LayerSockets virtual void HandleEvents() = 0; virtual void EventLoopEnds() = 0; + virtual void AddLoopHandler(EventLoopHandler & handler) = 0; + virtual void RemoveLoopHandler(EventLoopHandler & handler) = 0; + #if CHIP_SYSTEM_CONFIG_USE_DISPATCH virtual void SetDispatchQueue(dispatch_queue_t dispatchQueue) = 0; virtual dispatch_queue_t GetDispatchQueue() = 0; diff --git a/src/system/SystemLayerImplSelect.cpp b/src/system/SystemLayerImplSelect.cpp index 86ff1cb62da42f..106c086ede1acd 100644 --- a/src/system/SystemLayerImplSelect.cpp +++ b/src/system/SystemLayerImplSelect.cpp @@ -370,8 +370,9 @@ CHIP_ERROR LayerImplSelect::StartWatchingSocket(int fd, SocketWatchToken * token { if (w.mFD == fd) { - // Duplicate registration is an error. - return CHIP_ERROR_INVALID_ARGUMENT; + // Already registered, return the existing token + *tokenOut = reinterpret_cast(&w); + return CHIP_NO_ERROR; } if ((w.mFD == kInvalidFd) && (watch == nullptr)) { @@ -621,6 +622,15 @@ void LayerImplSelect::PrepareEvents() awakenTime = timer->AwakenTime(); } + for (auto & loop : mLoopHandlers) + { + Clock::Timestamp requestedWakeTime = loop.PrepareEvents(currentTime); + if (requestedWakeTime < awakenTime) + { + awakenTime = requestedWakeTime; + } + } + const Clock::Timestamp sleepTime = (awakenTime > currentTime) ? (awakenTime - currentTime) : Clock::kZero; Clock::ToTimeval(sleepTime, mNextTimeout); @@ -685,16 +695,21 @@ void LayerImplSelect::HandleEvents() for (auto & w : mSocketWatchPool) { - if (w.mFD != kInvalidFd) + if (w.mFD != kInvalidFd && w.mCallback != nullptr) { SocketEvents events = SocketEventsFromFDs(w.mFD, mSelected.mReadSet, mSelected.mWriteSet, mSelected.mErrorSet); - if (events.HasAny() && w.mCallback != nullptr) + if (events.HasAny()) { w.mCallback(events, w.mCallbackData); } } } + for (auto & loop : mLoopHandlers) + { + loop.HandleEvents(); + } + #if CHIP_SYSTEM_CONFIG_POSIX_LOCKING mHandleSelectThread = PTHREAD_NULL; #endif // CHIP_SYSTEM_CONFIG_POSIX_LOCKING diff --git a/src/system/SystemLayerImplSelect.h b/src/system/SystemLayerImplSelect.h index 1bab3db9b5f39c..d487ab60287530 100644 --- a/src/system/SystemLayerImplSelect.h +++ b/src/system/SystemLayerImplSelect.h @@ -87,6 +87,9 @@ class LayerImplSelect : public LayerSocketsLoop void HandleEvents() override; void EventLoopEnds() override {} + void AddLoopHandler(EventLoopHandler & handler) override { mLoopHandlers.PushBack(&handler); } + void RemoveLoopHandler(EventLoopHandler & handler) override { mLoopHandlers.Remove(&handler); } + #if CHIP_SYSTEM_CONFIG_USE_DISPATCH void SetDispatchQueue(dispatch_queue_t dispatchQueue) override { mDispatchQueue = dispatchQueue; }; dispatch_queue_t GetDispatchQueue() override { return mDispatchQueue; }; @@ -135,6 +138,8 @@ class LayerImplSelect : public LayerSocketsLoop TimerList mExpiredTimers; timeval mNextTimeout; + IntrusiveList mLoopHandlers; + // Members for select loop struct SelectSets {