| // Copyright 2022 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #ifndef SRC_DEVICES_BIN_DRIVER_MANAGER_SHUTDOWN_SHUTDOWN_MANAGER_H_ |
| #define SRC_DEVICES_BIN_DRIVER_MANAGER_SHUTDOWN_SHUTDOWN_MANAGER_H_ |
| |
| #include <fidl/fuchsia.device.manager/cpp/fidl.h> |
| #include <fidl/fuchsia.process.lifecycle/cpp/wire.h> |
| #include <lib/async-loop/cpp/loop.h> |
| #include <lib/component/incoming/cpp/protocol.h> |
| #include <lib/component/outgoing/cpp/outgoing_directory.h> |
| #include <lib/fdio/directory.h> |
| #include <lib/zx/resource.h> |
| #include <lib/zx/vmo.h> |
| |
| #include <list> |
| |
| #include "src/devices/bin/driver_manager/shutdown/node_remover.h" |
| |
| namespace driver_manager { |
| using fuchsia_device_manager::SystemPowerState; |
| |
| // Theory of operation of ShutdownManager: |
| // There are a number of ways shutdown can be initiated: |
| // - The process could be terminated, resulting in a signal from the Lifecycle channel |
| // - The administrator interface could signal UnregisterSystemStorageForShutdown, or |
| // SuspendWithoutExit |
| // - Any of the fidl connections could be dropped |
| // These events can cause one of two stages of the driver shutdown to be triggered: |
| // Package Shutdown: The shutdown manager signals the node_remover to shut down all package |
| // drivers; ie: drivers that depend on storage and fshost. |
| // Boot/All Shutdown: The shutdown manager signals the node_remover to shut down all drivers. |
| // |
| // When the node_remover signals it has completed removing the package drivers, |
| // The Shutdown Manager will transition to kPackageStopped. If something has signaled the |
| // Shutdown Manager to shutdown the boot drivers in that time, the shutdown manager will |
| // transition to shutting down boot drivers immediately after the package drivers are removed. |
| // Otherwise, the Shutdown Manager will wait for an invocation of SignalBootShutdown before |
| // shutting down boot drivers. |
| // Either way, when boot drivers are fully shutdown, the Shutdown Manager will signal the |
| // system to stop in some manner, dictated by what is returned by `GetSystemPowerState`. |
| // The default state, which is invoked if there is some error, is REBOOT. |
| // Any errors in the shutdown process are logged, but ulimately do not stop the shutdown. |
| // The ShutdownManager is not thread safe. It assumes that all channels will be dispatched |
| // on the same single threaded dispatcher, and that all callbacks will also be called on |
| // that same thread. |
| class ShutdownManager : public fidl::WireServer<fuchsia_device_manager::Administrator>, |
| public fidl::WireServer<fuchsia_process_lifecycle::Lifecycle> { |
| public: |
| enum class State : uint32_t { |
| // The system is running, nothing is being stopped. |
| kRunning = 0u, |
| // The devices whose's drivers live in storage are stopped or in the middle of being |
| // stopped. |
| kPackageStopping = 1u, |
| // Package drivers have been stopped, but we haven't started shutting down boot drivers yet. |
| kPackageStopped = 2u, |
| // The entire system is in the middle of being stopped. |
| kBootStopping = 3u, |
| // The entire system is stopped. |
| kStopped = 4u, |
| }; |
| |
| ShutdownManager(NodeRemover* node_remover, async_dispatcher_t* dispatcher); |
| |
| void Publish(component::OutgoingDirectory& outgoing); |
| |
| // Called by the node_remover when it finishes removing drivers in storage. |
| // Should only be called when in state: kPackageStopping. |
| // This function will transition the state to State::kBootStopping. |
| void OnPackageShutdownComplete(); |
| |
| // Called by the node_remover when it finishes removing boot drivers. |
| // Should only be called when in state: kBootStopping. |
| // This function will transition the state to State::kStopped. |
| void OnBootShutdownComplete(); |
| |
| private: |
| // Signal state for when devfs and fshost are shutdown. |
| class Lifecycle : public fidl::WireServer<fuchsia_process_lifecycle::Lifecycle> { |
| public: |
| explicit Lifecycle(fit::callback<void(fit::callback<void(zx_status_t)>)> on_stop) |
| : on_stop_(std::move(on_stop)) {} |
| |
| void Stop(StopCompleter::Sync& completer) override { |
| on_stop_([completer = completer.ToAsync()](zx_status_t status) mutable { |
| completer.Close(status); |
| }); |
| } |
| |
| private: |
| fit::callback<void(fit::callback<void(zx_status_t)>)> on_stop_; |
| }; |
| |
| // All external shutdown signals ultimately call either `SignalBootShutdown` or |
| // `SignalPackageShutdown`. These two functions interact with the `ShutdownManager` state |
| // machine and signal the node_remover to remove nodes. |
| // SignalPackageShutdown interacts with the `ShutdownManager` state machine thusly: |
| // State: | Action |
| // --------------------------------------------- |
| // kRunning: | Transition to kPackageStopping. |
| // | Signal the nove_remover to remove package drivers. |
| // | Add callback to list to be called when all package drivers are removed |
| // kPackageStopping | Add callback to list to be called when all package drivers are removed |
| // All other states | Immediately call callback |
| void SignalPackageShutdown(fit::callback<void(zx_status_t)> cb); |
| // When the shutdown manager receives the SignalBootShutdown: |
| // State: | Action |
| // --------------------------------------------- |
| // kRunning or | Transition to kBootStopping. |
| // kPackageStopped | Signal the nove_remover to remove all drivers. |
| // | Add callback to list to be called when all drivers are removed |
| // kPackageStopping | Add callback to list to be called when all drivers are removed |
| // | Set flag so that when the packages are fully removed, we will |
| // | continue to remove the boot drivers |
| // kBootStopping | Add callback to list to be called when all drivers are removed |
| // All other states | Immediately call callback |
| void SignalBootShutdown(fit::callback<void(zx_status_t)> cb); |
| |
| // fuchsia.device.manager/Administrator interface |
| // TODO(https://fxbug.dev/42147562): Remove this API. |
| // This is a temporary API until DriverManager can ensure that base drivers |
| // will be shut down automatically before fshost exits. This will happen |
| // once drivers-as-components is implemented. |
| // In the meantime, this API should only be called by fshost, and it must |
| // be called before fshost exits. This function iterates over the devices |
| // and suspends any device whose driver lives in storage. This API must be |
| // called by fshost before it shuts down. Otherwise the devices that live |
| // in storage may page fault as it access memory that should be provided by |
| // the exited fshost. This function will not return until the devices are |
| // suspended. If there are no devices that live in storage, this function |
| // will immediatetly return. |
| void UnregisterSystemStorageForShutdown( |
| UnregisterSystemStorageForShutdownCompleter::Sync& completer) override; |
| |
| // Tell DriverManager to go through the suspend process, but don't exit |
| // afterwards. This is used in tests to check that suspend works correctly. |
| void SuspendWithoutExit(SuspendWithoutExitCompleter::Sync& completer) override; |
| |
| // fuchsia.process.lifecycle/Lifecycle interface |
| // The process must clean up its state in preparation for termination, and |
| // must close the channel hosting the `Lifecycle` protocol when it is |
| // ready to be terminated. The process should exit after it completes its |
| // cleanup. At the discretion of the system the process may be terminated |
| // before it closes the `Lifecycle` channel. |
| void Stop(StopCompleter::Sync& completer) override; |
| |
| // Execute the shutdown strategy set in shutdown_system_state_. |
| // This should be done after all attempts at shutting down drivers has been made. |
| void SystemExecute(); |
| |
| // Called when one of our connections is dropped. |
| void OnUnbound(const char* connection, fidl::UnbindInfo info); |
| |
| // The driver runner should always be valid while the shutdown manager exists. |
| // TODO(https://fxbug.dev/42065671): ensure that this pointer is valid |
| NodeRemover* node_remover_; |
| |
| // Tracks when the devfs component is stopped by component manager. We shutdown all drivers upon |
| // receiving this signal. |
| Lifecycle devfs_lifecycle_; |
| // Tracks when the devfs-with-pkg component is stopped by component manager. We shutdown all |
| // packaged drivers upon receiving this signal. |
| Lifecycle devfs_with_pkg_lifecycle_; |
| |
| fidl::ServerBindingGroup<fuchsia_device_manager::Administrator> admin_bindings_; |
| fidl::ServerBindingGroup<fuchsia_process_lifecycle::Lifecycle> lifecycle_bindings_; |
| std::list<fit::callback<void(zx_status_t)>> package_shutdown_complete_callbacks_; |
| std::list<fit::callback<void(zx_status_t)>> boot_shutdown_complete_callbacks_; |
| |
| State shutdown_state_ = State::kRunning; |
| // After package shutdown completes, wait for separate boot shutdown signal |
| bool received_boot_shutdown_signal_ = false; |
| |
| async_dispatcher_t* dispatcher_; |
| zx::resource mexec_resource_, power_resource_; |
| // Tracks if we received a stop signal from the fuchsia_process_lifecycle::Lifecycle channel. |
| bool lifecycle_stop_ = false; |
| }; |
| |
| } // namespace driver_manager |
| #endif // SRC_DEVICES_BIN_DRIVER_MANAGER_SHUTDOWN_SHUTDOWN_MANAGER_H_ |