blob: 700ff5910fb67b8fb4361af15677d8da44ce615a [file] [log] [blame]
// 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.
#include "shutdown_manager.h"
#include <fidl/fuchsia.boot/cpp/wire.h>
#include <fidl/fuchsia.kernel/cpp/wire.h>
#include <lib/fidl/cpp/wire/channel.h> // fidl::WireCall
#include <lib/zbi-format/zbi.h>
#include <lib/zbitl/error-string.h>
#include <lib/zbitl/image.h>
#include <lib/zbitl/item.h>
#include <lib/zbitl/vmo.h>
#include <zircon/processargs.h> // PA_LIFECYCLE
#include <zircon/syscalls/system.h>
#include <src/bringup/lib/mexec/mexec.h>
#include <src/devices/lib/log/log.h>
#include <src/lib/fsl/vmo/sized_vmo.h>
#include <src/lib/fsl/vmo/vector.h>
namespace driver_manager {
namespace {
struct MexecVmos {
zx::vmo kernel_zbi;
zx::vmo data_zbi;
};
zx::result<MexecVmos> GetMexecZbis(zx::unowned_resource mexec_resource) {
zx::result client = component::Connect<fuchsia_device_manager::SystemStateTransition>();
if (client.is_error()) {
LOGF(ERROR, "Failed to connect to StateStateTransition: %s", client.status_string());
return client.take_error();
}
fidl::Result result = fidl::Call(*client)->GetMexecZbis();
if (result.is_error()) {
LOGF(ERROR, "Failed to get mexec zbis: %s", result.error_value().FormatDescription().c_str());
zx_status_t status = result.error_value().is_domain_error()
? result.error_value().domain_error()
: result.error_value().framework_error().status();
return zx::error(status);
}
zx::vmo& kernel_zbi = result->kernel_zbi();
zx::vmo& data_zbi = result->data_zbi();
if (zx_status_t status = mexec::PrepareDataZbi(std::move(mexec_resource), data_zbi.borrow());
status != ZX_OK) {
LOGF(ERROR, "Failed to prepare mexec data ZBI: %s", zx_status_get_string(status));
return zx::error(status);
}
zx::result connect_result = component::Connect<fuchsia_boot::Items>();
if (connect_result.is_error()) {
LOGF(ERROR, "Failed to connect to fuchsia.boot::Items: %s", connect_result.status_string());
return connect_result.take_error();
}
fidl::WireSyncClient<fuchsia_boot::Items> items(std::move(connect_result).value());
// Driver metadata that the driver framework generally expects to be present.
constexpr std::array kItemsToAppend{ZBI_TYPE_DRV_MAC_ADDRESS, ZBI_TYPE_DRV_PARTITION_MAP,
ZBI_TYPE_DRV_BOARD_PRIVATE, ZBI_TYPE_DRV_BOARD_INFO};
zbitl::Image data_image{data_zbi.borrow()};
for (uint32_t type : kItemsToAppend) {
std::string_view name = zbitl::TypeName(type);
// TODO(https://fxbug.dev/42053781): Use a method that returns all matching items of
// a given type instead of guessing possible `extra` values.
for (uint32_t extra : std::array{0, 1, 2}) {
fidl::WireResult result = items->Get(type, extra);
if (!result.ok()) {
return zx::error(result.status());
}
if (!result.value().payload.is_valid()) {
// Absence is signified with an empty result value.
LOGF(INFO, "No %.*s item (%#xu) present to append to mexec data ZBI",
static_cast<int>(name.size()), name.data(), type);
continue;
}
fsl::SizedVmo payload(std::move(result.value().payload), result.value().length);
std::vector<char> contents;
if (!fsl::VectorFromVmo(payload, &contents)) {
LOGF(ERROR, "Failed to read contents of %.*s item (%#xu)", static_cast<int>(name.size()),
name.data(), type);
return zx::error(ZX_ERR_INTERNAL);
}
if (fit::result result = data_image.Append(zbi_header_t{.type = type, .extra = extra},
zbitl::AsBytes(contents));
result.is_error()) {
LOGF(ERROR, "Failed to append %.*s item (%#xu) to mexec data ZBI: %s",
static_cast<int>(name.size()), name.data(), type,
zbitl::ViewErrorString(result.error_value()).c_str());
return zx::error(ZX_ERR_INTERNAL);
}
}
}
return zx::ok(MexecVmos{
.kernel_zbi = std::move(kernel_zbi),
.data_zbi = std::move(data_zbi),
});
}
SystemPowerState GetSystemPowerState() {
zx::result client = component::Connect<fuchsia_device_manager::SystemStateTransition>();
if (client.is_error()) {
LOGF(ERROR, "Failed to connect to StateStateTransition: %s, falling back to default",
client.status_string());
return SystemPowerState::kReboot;
}
fidl::Result result = fidl::Call(*client)->GetTerminationSystemState();
if (result.is_error()) {
LOGF(ERROR, "Failed to get termination system state: %s, falling back to default",
result.error_value().FormatDescription().c_str());
return SystemPowerState::kReboot;
}
return result->state();
}
template <typename SyncCompleter>
fit::callback<void(zx_status_t)> ToCallback(SyncCompleter& completer) {
return [completer = completer.ToAsync()](zx_status_t status) mutable { completer.Close(status); };
}
// Get the power resource from the root resource service. Not receiving the
// startup handle is logged, but not fatal. In test environments, it would not
// be present.
zx::result<zx::resource> get_power_resource() {
zx::result client_end = component::Connect<fuchsia_kernel::PowerResource>();
if (client_end.is_error()) {
return client_end.take_error();
}
fidl::WireResult result = fidl::WireCall(client_end.value())->Get();
if (!result.ok()) {
return zx::error(result.status());
}
return zx::ok(std::move(result.value().resource));
}
// Get the mexec resource from the mexec resource service. Not receiving the
// startup handle is logged, but not fatal. In test environments, it would not
// be present.
zx::result<zx::resource> get_mexec_resource() {
zx::result client_end = component::Connect<fuchsia_kernel::MexecResource>();
if (client_end.is_error()) {
return client_end.take_error();
}
fidl::WireResult result = fidl::WireCall(client_end.value())->Get();
if (!result.ok()) {
return zx::error(result.status());
}
return zx::ok(std::move(result.value().resource));
}
} // anonymous namespace
ShutdownManager::ShutdownManager(NodeRemover* node_remover, async_dispatcher_t* dispatcher)
: node_remover_(node_remover),
devfs_lifecycle_(
[this](fit::callback<void(zx_status_t)> cb) { SignalBootShutdown(std::move(cb)); }),
devfs_with_pkg_lifecycle_(
[this](fit::callback<void(zx_status_t)> cb) { SignalPackageShutdown(std::move(cb)); }),
dispatcher_(dispatcher) {
if (zx::result power_resource = get_power_resource(); power_resource.is_error()) {
LOGF(INFO, "Failed to get root resource, assuming test environment and continuing (%s)",
power_resource.status_string());
} else {
power_resource_ = std::move(power_resource.value());
}
if (zx::result mexec_resource = get_mexec_resource(); mexec_resource.is_error()) {
LOGF(INFO, "Failed to get mexec resource, assuming test environment and continuing (%s)",
mexec_resource.status_string());
} else {
mexec_resource_ = std::move(mexec_resource.value());
}
}
// Invoked when the channel is closed or on any binding-related error.
// If we were not shutting down, we should start shutting down, because
// we no longer have a way to get signals to shutdown the system.
void ShutdownManager::OnUnbound(const char* connection, fidl::UnbindInfo info) {
if (info.is_user_initiated()) {
LOGF(DEBUG, "%s connection to ShutdownManager got unbound: %s", connection,
info.FormatDescription().c_str());
} else {
LOGF(ERROR, "%s connection to ShutdownManager got unbound: %s", connection,
info.FormatDescription().c_str());
}
SignalBootShutdown(nullptr);
}
void ShutdownManager::Publish(component::OutgoingDirectory& outgoing) {
auto result = outgoing.AddUnmanagedProtocol<fuchsia_device_manager::Administrator>(
admin_bindings_.CreateHandler(this, dispatcher_, fidl::kIgnoreBindingClosure));
ZX_ASSERT_MSG(result.is_ok(), "%s", result.status_string());
result = outgoing.AddUnmanagedProtocol<fuchsia_process_lifecycle::Lifecycle>(
lifecycle_bindings_.CreateHandler(&devfs_lifecycle_, dispatcher_,
fidl::kIgnoreBindingClosure),
"fuchsia.device.fs.lifecycle.Lifecycle");
ZX_ASSERT_MSG(result.is_ok(), "%s", result.status_string());
result = outgoing.AddUnmanagedProtocol<fuchsia_process_lifecycle::Lifecycle>(
lifecycle_bindings_.CreateHandler(&devfs_with_pkg_lifecycle_, dispatcher_,
fidl::kIgnoreBindingClosure),
"fuchsia.device.fs.with.pkg.lifecycle.Lifecycle");
ZX_ASSERT_MSG(result.is_ok(), "%s", result.status_string());
// Bind to lifecycle server
fidl::ServerEnd<fuchsia_process_lifecycle::Lifecycle> lifecycle_server(
zx::channel(zx_take_startup_handle(PA_LIFECYCLE)));
if (lifecycle_server.is_valid()) {
lifecycle_bindings_.AddBinding(dispatcher_, std::move(lifecycle_server), this,
[](ShutdownManager* server, fidl::UnbindInfo info) {
server->OnUnbound("Lifecycle", info);
});
} else {
LOGF(INFO,
"No valid handle found for lifecycle events, assuming test environment and continuing");
}
}
void ShutdownManager::OnPackageShutdownComplete() {
LOGF(INFO, "Package shutdown complete");
ZX_ASSERT(shutdown_state_ == State::kPackageStopping);
shutdown_state_ = State::kPackageStopped;
for (auto& callback : package_shutdown_complete_callbacks_) {
callback(ZX_OK);
}
package_shutdown_complete_callbacks_.clear();
if (received_boot_shutdown_signal_) {
shutdown_state_ = State::kBootStopping;
// In the middle of package shutdown we were told to shutdown everything.
node_remover_->ShutdownAllDrivers(
fit::bind_member(this, &ShutdownManager::OnBootShutdownComplete));
}
}
void ShutdownManager::OnBootShutdownComplete() {
ZX_ASSERT(shutdown_state_ == State::kBootStopping);
shutdown_state_ = State::kStopped;
SystemExecute();
for (auto& callback : boot_shutdown_complete_callbacks_) {
callback(ZX_OK);
}
boot_shutdown_complete_callbacks_.clear();
}
void ShutdownManager::UnregisterSystemStorageForShutdown(
UnregisterSystemStorageForShutdownCompleter::Sync& completer) {
SignalPackageShutdown(
[completer = completer.ToAsync()](zx_status_t status) mutable { completer.Reply(status); });
}
void ShutdownManager::SignalPackageShutdown(fit::callback<void(zx_status_t)> cb) {
// Expected case: we get the call during kPackageStopping, or right before.
// Store the completer for when we finish.
if (shutdown_state_ == State::kRunning || shutdown_state_ == State::kPackageStopping) {
package_shutdown_complete_callbacks_.emplace_back(std::move(cb));
if (shutdown_state_ == State::kRunning) {
shutdown_state_ = State::kPackageStopping;
node_remover_->ShutdownPkgDrivers(
fit::bind_member(this, &ShutdownManager::OnPackageShutdownComplete));
}
} else {
// Otherwise, we already finished package shutdown or we have already jumped
// to doing a full shutdown. Notify the callback.
cb(ZX_OK);
}
}
void ShutdownManager::SuspendWithoutExit(SuspendWithoutExitCompleter::Sync& completer) {
LOGF(FATAL, "SuspendWithoutExit not supported");
}
void ShutdownManager::Stop(StopCompleter::Sync& completer) {
lifecycle_stop_ = true;
SignalBootShutdown(ToCallback(completer));
}
void ShutdownManager::SignalBootShutdown(fit::callback<void(zx_status_t)> cb) {
if (cb) {
if (shutdown_state_ == State::kStopped) {
cb(ZX_OK);
} else {
boot_shutdown_complete_callbacks_.emplace_back(std::move(cb));
}
}
received_boot_shutdown_signal_ = true;
// Expected case: we get the call while running, or after we shutdown the package drivers.
if (shutdown_state_ == State::kRunning || shutdown_state_ == State::kPackageStopped) {
shutdown_state_ = State::kBootStopping;
node_remover_->ShutdownAllDrivers(
fit::bind_member(this, &ShutdownManager::OnBootShutdownComplete));
} else if (shutdown_state_ == State::kBootStopping) {
LOGF(ERROR, "SignalBootShutdown() called during shutdown.");
}
}
void ShutdownManager::SystemExecute() {
auto shutdown_system_state = GetSystemPowerState();
LOGF(INFO, "Suspend fallback with flags %#08hhx", shutdown_system_state);
const char* what = "zx_system_powerctl";
zx_status_t status = ZX_OK;
if (!mexec_resource_.is_valid() || !power_resource_.is_valid()) {
LOGF(WARNING, "Invalid Power/mexec resources. Assuming test.");
if (lifecycle_stop_) {
exit(0);
}
return;
}
switch (shutdown_system_state) {
case SystemPowerState::kReboot:
status = zx_system_powerctl(power_resource_.get(), ZX_SYSTEM_POWERCTL_REBOOT, nullptr);
break;
case SystemPowerState::kRebootBootloader:
status =
zx_system_powerctl(power_resource_.get(), ZX_SYSTEM_POWERCTL_REBOOT_BOOTLOADER, nullptr);
break;
case SystemPowerState::kRebootRecovery:
status =
zx_system_powerctl(power_resource_.get(), ZX_SYSTEM_POWERCTL_REBOOT_RECOVERY, nullptr);
break;
case SystemPowerState::kRebootKernelInitiated:
status = zx_system_powerctl(power_resource_.get(),
ZX_SYSTEM_POWERCTL_ACK_KERNEL_INITIATED_REBOOT, nullptr);
if (status == ZX_OK) {
// Sleep indefinitely to give the kernel a chance to reboot the system. This results in a
// cleaner reboot because it prevents driver_manager from exiting. If driver_manager exits
// the other parts of the system exit, bringing down the root job. Crashing the root job
// is innocuous at this point, but we try to avoid it to reduce log noise and possible
// confusion.
while (true) {
sleep(5 * 60);
// We really shouldn't still be running, so log if we are. Use `printf`
// because messages from the devices are probably only visible over
// serial at this point.
printf("driver_manager: unexpectedly still running after successful reboot syscall\n");
}
}
break;
case SystemPowerState::kPoweroff:
status = zx_system_powerctl(power_resource_.get(), ZX_SYSTEM_POWERCTL_SHUTDOWN, nullptr);
break;
case SystemPowerState::kMexec: {
LOGF(INFO, "About to mexec...");
zx::result<MexecVmos> mexec_vmos = GetMexecZbis(mexec_resource_.borrow());
status = mexec_vmos.status_value();
if (status == ZX_OK) {
status = mexec::BootZbi(mexec_resource_.borrow(), std::move(mexec_vmos->kernel_zbi),
std::move(mexec_vmos->data_zbi));
}
what = "zx_system_mexec";
break;
}
case SystemPowerState::kFullyOn:
case SystemPowerState::kSuspendRam:
LOGF(ERROR, "Unexpected shutdown state requested: %hhu", shutdown_system_state);
break;
}
// This is mainly for test dev:
if (lifecycle_stop_) {
LOGF(INFO, "Exiting driver manager gracefully");
// TODO(fxb:52627) This event handler should teardown devices and driver hosts
// properly for system state transitions where driver manager needs to go down.
// Exiting like so, will not run all the destructors and clean things up properly.
// Instead the main devcoordinator loop should be quit.
exit(0);
}
// Warning - and not an error - as a large number of tests unfortunately rely
// on this syscall actually failing.
LOGF(WARNING, "%s: %s", what, zx_status_get_string(status));
}
} // namespace driver_manager