blob: 58861d9729cc180b4d0026c23f86bf370738b17b [file]
// Copyright 2016 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 <fidl/fuchsia.device.fs/cpp/wire.h>
#include <lib/component/incoming/cpp/clone.h>
#include <lib/ddk/debug.h>
#include <lib/ddk/device.h>
#include <lib/ddk/driver.h>
#include <lib/ddk/fragment-device.h>
#include <lib/fdf/cpp/channel.h>
#include <lib/fdf/cpp/protocol.h>
#include <lib/fdf/env.h>
#include <lib/fdio/directory.h>
#include <lib/fidl/cpp/wire/channel.h>
#include <lib/syslog/logger.h>
#include <lib/zx/process.h>
#include <stdarg.h>
#include <stdio.h>
#include <zircon/compiler.h>
#include <zircon/errors.h>
#include <utility>
#include <fbl/auto_lock.h>
#include "src/devices/bin/driver_host/composite_device.h"
#include "src/devices/bin/driver_host/driver_host.h"
#include "src/devices/bin/driver_host/fidl_proxy_device.h"
#include "src/devices/bin/driver_host/scheduler_profile.h"
namespace fio = fuchsia_io;
// These are the API entry-points from drivers
// They must take the internal::api_lock before calling internal::* internals
//
// Driver code MUST NOT directly call internal::* APIs
// LibDriver Device Interface
//
#define ALLOWED_FLAGS \
(DEVICE_ADD_NON_BINDABLE | DEVICE_ADD_MUST_ISOLATE | DEVICE_ADD_ALLOW_MULTI_COMPOSITE)
namespace internal {
static_assert(static_cast<uint8_t>(fuchsia_device::wire::DevicePowerState::kDevicePowerStateD0) ==
DEV_POWER_STATE_D0);
static_assert(static_cast<uint8_t>(fuchsia_device::wire::DevicePowerState::kDevicePowerStateD1) ==
DEV_POWER_STATE_D1);
static_assert(static_cast<uint8_t>(fuchsia_device::wire::DevicePowerState::kDevicePowerStateD2) ==
DEV_POWER_STATE_D2);
static_assert(
static_cast<uint8_t>(fuchsia_device::wire::DevicePowerState::kDevicePowerStateD3Hot) ==
DEV_POWER_STATE_D3HOT);
static_assert(
static_cast<uint8_t>(fuchsia_device::wire::DevicePowerState::kDevicePowerStateD3Cold) ==
DEV_POWER_STATE_D3COLD);
const device_power_state_info_t kDeviceDefaultPowerStates[2] = {
{.state_id = DEV_POWER_STATE_D0}, {.state_id = DEV_POWER_STATE_D3COLD}};
const device_performance_state_info_t kDeviceDefaultPerfStates[1] = {
{.state_id = DEV_PERFORMANCE_STATE_P0},
};
const zx_device::SystemPowerStateMapping kDeviceDefaultStateMapping = []() {
zx_device::SystemPowerStateMapping states_mapping{};
for (auto& entry : states_mapping) {
entry.dev_state = fuchsia_device::wire::DevicePowerState::kDevicePowerStateD3Cold;
entry.wakeup_enable = false;
}
return states_mapping;
}();
} // namespace internal
namespace {
#define DEBUG_ASSERT_VALID_DEVICE(dev) \
do { \
ZX_DEBUG_ASSERT_MSG(dev && dev->magic == DEV_MAGIC, "Dev pointer '%p' is not a real device", \
dev); \
} while (0)
} // namespace
__EXPORT zx_status_t device_add_from_driver(zx_driver_t* drv, zx_device_t* parent,
device_add_args_t* args, zx_device_t** out) {
if (!parent) {
return ZX_ERR_INVALID_ARGS;
}
DEBUG_ASSERT_VALID_DEVICE(parent);
fbl::RefPtr<zx_device> parent_ref(parent);
if (!args || args->version != DEVICE_ADD_ARGS_VERSION) {
return ZX_ERR_INVALID_ARGS;
}
if (!args->ops || args->ops->version != DEVICE_OPS_VERSION) {
return ZX_ERR_INVALID_ARGS;
}
if (args->flags & ~ALLOWED_FLAGS) {
return ZX_ERR_INVALID_ARGS;
}
// A device is not allowed to have a name that conflicts with
// a reserved name.
if (args->name == fuchsia_device_fs::wire::kDeviceControllerName ||
args->name == fuchsia_device_fs::wire::kDeviceProtocolName) {
return ZX_ERR_INVALID_ARGS;
}
zx::vmo inspect(args->inspect_vmo);
auto api_ctx = internal::ContextForApi();
fbl::AutoLock lock(&api_ctx->api_lock());
fbl::RefPtr<Driver> driver;
auto* current = static_cast<Driver*>(const_cast<void*>(fdf_env_get_current_driver()));
if (current != nullptr && current->zx_driver() == drv) {
// We try retrieve the current driver instance from the driver runtime first. If we are
// currently in a driver hook such as |bind| or |create| this will yield us the correct
// driver. It should also yeild us the correct driver in most other cases, however it's
// possible that it will yield the wrong driver if a device is added inside of a banjo call -
// this is why we also double check that the zx_driver objects line up.
driver = fbl::RefPtr<Driver>(current);
} else {
// Otherwise we fall back to assuming the driver is not in bind or create, and therefore the
// device being added is from the same driver instance as the parent. This can incorrectly
// occur if the driver adds a device to it's original parent inside of a dedicated thread it
// spawned.
driver = parent->driver;
}
fbl::RefPtr<zx_device_t> dev;
if (zx_status_t status =
api_ctx->DeviceCreate(std::move(driver), args->name, args->ctx, args->ops, &dev);
status != ZX_OK) {
return status;
}
if (args->proto_id) {
dev->set_protocol_id(args->proto_id);
dev->set_protocol_ops(args->proto_ops);
}
if (args->fidl_service_offers) {
dev->set_fidl_service_offers({args->fidl_service_offers, args->fidl_service_offer_count});
}
if (args->runtime_service_offers) {
dev->set_runtime_service_offers(
{args->runtime_service_offers, args->runtime_service_offer_count});
}
if (args->flags & DEVICE_ADD_NON_BINDABLE) {
dev->set_flag(DEV_FLAG_UNBINDABLE);
}
if (args->flags & DEVICE_ADD_ALLOW_MULTI_COMPOSITE) {
dev->set_flag(DEV_FLAG_ALLOW_MULTI_COMPOSITE);
}
if (args->flags & DEVICE_ADD_MUST_ISOLATE) {
dev->set_flag(DEV_FLAG_MUST_ISOLATE);
}
if (!args->power_states) {
// TODO(https://fxbug.dev/34801): Remove when all drivers declare power states.
// Temporarily allocate working and non-working power states
if (zx_status_t status = dev->SetPowerStates(internal::kDeviceDefaultPowerStates,
std::size(internal::kDeviceDefaultPowerStates));
status != ZX_OK) {
return status;
}
} else {
if (zx_status_t status = dev->SetPowerStates(args->power_states, args->power_state_count);
status != ZX_OK) {
return status;
}
}
if (args->performance_states && (args->performance_state_count != 0)) {
if (zx_status_t status =
dev->SetPerformanceStates(args->performance_states, args->performance_state_count);
status != ZX_OK) {
return status;
}
} else {
if (zx_status_t status = dev->SetPerformanceStates(
internal::kDeviceDefaultPerfStates, std::size(internal::kDeviceDefaultPerfStates));
status != ZX_OK) {
return status;
}
}
// Set default system to device power state mapping. This can be later
// updated by the system power manager.
if (zx_status_t status = dev->SetSystemPowerStateMapping(internal::kDeviceDefaultStateMapping);
status != ZX_OK) {
return status;
}
fidl::ClientEnd<fio::Directory> outgoing_dir(zx::channel(args->outgoing_dir_channel));
// The outgoing directory can be used for either out-of-process FIDL protocols,
// or in-process runtime protocols.
if (outgoing_dir) {
if (args->runtime_service_offer_count > 0) {
if (args->flags & DEVICE_ADD_MUST_ISOLATE) {
// Runtime protocols are only supported in-process.
return ZX_ERR_INVALID_ARGS;
}
// Clone `outgoing_dir` so that we still have a valid copy to pass to `DeviceAdd` below.
zx::result<fidl::ClientEnd<fuchsia_io::Directory>> clone_result =
component::Clone(outgoing_dir);
if (clone_result.is_error()) {
return clone_result.status_value();
}
dev->set_runtime_outgoing_dir(std::move(clone_result.value()));
}
}
// out must be set before calling DeviceAdd().
// DeviceAdd() may result in child devices being created before it returns,
// and those children may call ops on the device before device_add() returns.
// This leaked-ref will be accounted below.
if (out) {
*out = dev.get();
}
zx_status_t status =
api_ctx->DeviceAdd(dev, parent_ref, args, std::move(inspect), std::move(outgoing_dir));
if (status == ZX_OK) {
// Leak the reference that was written to |out|, it will be recovered in device_remove().
[[maybe_unused]] auto ptr = fbl::ExportToRawPtr(&dev);
} else {
if (out) {
*out = nullptr;
}
}
return status;
}
__EXPORT void device_init_reply(zx_device_t* dev, zx_status_t status,
const device_init_reply_args_t* args) {
DEBUG_ASSERT_VALID_DEVICE(dev);
fbl::AutoLock lock(&internal::ContextForApi()->api_lock());
fbl::RefPtr<zx_device_t> dev_ref(dev);
internal::ContextForApi()->DeviceInitReply(dev_ref, status, args);
}
__EXPORT void device_async_remove(zx_device_t* dev) {
DEBUG_ASSERT_VALID_DEVICE(dev);
fbl::AutoLock lock(&internal::ContextForApi()->api_lock());
// The leaked reference in device_add_from_driver() will be recovered when
// DriverManagerRemove() completes. We can't drop it here as we are just
// scheduling the removal, and do not know when that will happen.
fbl::RefPtr<zx_device_t> dev_ref(dev);
internal::ContextForApi()->DeviceRemove(dev_ref, true /* unbind_self */);
}
__EXPORT void device_unbind_reply(zx_device_t* dev) {
DEBUG_ASSERT_VALID_DEVICE(dev);
fbl::AutoLock lock(&internal::ContextForApi()->api_lock());
fbl::RefPtr<zx_device_t> dev_ref(dev);
internal::ContextForApi()->DeviceUnbindReply(dev_ref);
}
__EXPORT void device_suspend_reply(zx_device_t* dev, zx_status_t status, uint8_t out_state) {
DEBUG_ASSERT_VALID_DEVICE(dev);
fbl::AutoLock lock(&internal::ContextForApi()->api_lock());
fbl::RefPtr<zx_device_t> dev_ref(dev);
internal::ContextForApi()->DeviceSuspendReply(dev_ref, status, out_state);
}
__EXPORT void device_resume_reply(zx_device_t* dev, zx_status_t status, uint8_t out_power_state,
uint32_t out_perf_state) {
DEBUG_ASSERT_VALID_DEVICE(dev);
fbl::AutoLock lock(&internal::ContextForApi()->api_lock());
fbl::RefPtr<zx_device_t> dev_ref(dev);
internal::ContextForApi()->DeviceResumeReply(dev_ref, status, out_power_state, out_perf_state);
}
__EXPORT zx_status_t device_set_profile_by_role(zx_device_t* device, zx_handle_t thread,
const char* role, size_t role_size) {
DEBUG_ASSERT_VALID_DEVICE(device);
return internal::set_scheduler_profile_by_role(thread, role, role_size);
}
struct GenericProtocol {
const void* ops;
void* ctx;
};
__EXPORT zx_status_t device_get_protocol(const zx_device_t* dev, uint32_t proto_id, void* out) {
DEBUG_ASSERT_VALID_DEVICE(dev);
auto proto = static_cast<GenericProtocol*>(out);
if (dev->ops().get_protocol) {
return dev->ops().get_protocol(dev->ctx(), proto_id, out);
}
if ((proto_id == dev->protocol_id()) && (dev->protocol_ops() != nullptr)) {
proto->ops = dev->protocol_ops();
proto->ctx = dev->ctx();
return ZX_OK;
}
return ZX_ERR_NOT_SUPPORTED;
}
__EXPORT zx_status_t device_open_protocol_session_multibindable(const zx_device_t* dev,
uint32_t proto_id, void* out) {
DEBUG_ASSERT_VALID_DEVICE(dev);
if (dev->ops().open_protocol_session_multibindable) {
return dev->ops().open_protocol_session_multibindable(dev->ctx(), proto_id, out);
}
return ZX_ERR_NOT_SUPPORTED;
}
__EXPORT zx_status_t device_close_protocol_session_multibindable(const zx_device_t* dev,
void* proto) {
DEBUG_ASSERT_VALID_DEVICE(dev);
if (dev->ops().close_protocol_session_multibindable) {
return dev->ops().close_protocol_session_multibindable(dev->ctx(), proto);
}
return ZX_ERR_NOT_SUPPORTED;
}
__EXPORT zx_status_t device_connect_runtime_protocol(zx_device_t* dev, const char* service_name,
const char* protocol_name,
fdf_handle_t request) {
DEBUG_ASSERT_VALID_DEVICE(dev);
auto& outgoing = dev->runtime_outgoing_dir();
if (!outgoing) {
return ZX_ERR_NOT_SUPPORTED;
}
zx::channel client_token, server_token;
auto status = zx::channel::create(0, &client_token, &server_token);
if (status != ZX_OK) {
return status;
}
status = fdf::ProtocolConnect(std::move(client_token), fdf::Channel(request));
if (status != ZX_OK) {
return status;
}
fbl::StringBuffer<fuchsia_io::wire::kMaxPathLength> path;
// We use "default" as the service instance, as that's what we expect the parent driver
// to rename it to.
path.AppendPrintf("svc/%s/default/%s", service_name, protocol_name);
return fdio_service_connect_at(outgoing.channel().get(), path.c_str(), server_token.release());
}
__EXPORT zx_status_t device_connect_fragment_runtime_protocol(zx_device_t* dev,
const char* fragment_name,
const char* service_name,
const char* protocol_name,
fdf_handle_t request) {
DEBUG_ASSERT_VALID_DEVICE(dev);
zx::channel client_token, server_token;
auto status = zx::channel::create(0, &client_token, &server_token);
if (status != ZX_OK) {
return status;
}
status = fdf::ProtocolConnect(std::move(client_token), fdf::Channel(request));
if (status != ZX_OK) {
return status;
}
fbl::AutoLock lock(&internal::ContextForApi()->api_lock());
auto dev_ref = fbl::RefPtr(dev);
return internal::ContextForApi()->ConnectFidlProtocol(dev_ref, fragment_name, service_name,
protocol_name, std::move(server_token));
}
// LibDriver Misc Interfaces
// Please do not use get_root_resource() in new code. See fxbug.dev/31358.
__EXPORT zx_handle_t get_root_resource() {
return internal::ContextForApi()->root_resource().get();
}
__EXPORT zx_status_t load_firmware_from_driver(zx_driver_t* drv, zx_device_t* dev, const char* path,
zx_handle_t* fw, size_t* size) {
DEBUG_ASSERT_VALID_DEVICE(dev);
fbl::AutoLock lock(&internal::ContextForApi()->api_lock());
fbl::RefPtr<zx_device_t> dev_ref(dev);
// TODO(bwb): Can we propogate zx::vmo further up?
return internal::ContextForApi()->LoadFirmware(drv, dev_ref, path, fw, size);
}
// Interface Used by DevHost RPC Layer
zx_status_t device_bind(const fbl::RefPtr<zx_device_t>& dev, const char* drv_libname) {
fbl::AutoLock lock(&internal::ContextForApi()->api_lock());
return internal::ContextForApi()->DeviceBind(dev, drv_libname);
}
zx_status_t device_unbind(const fbl::RefPtr<zx_device_t>& dev) {
fbl::AutoLock lock(&internal::ContextForApi()->api_lock());
return internal::ContextForApi()->DeviceUnbind(dev);
}
zx_status_t device_schedule_remove(const fbl::RefPtr<zx_device_t>& dev, bool unbind_self) {
fbl::AutoLock lock(&internal::ContextForApi()->api_lock());
return internal::ContextForApi()->ScheduleRemove(dev, unbind_self);
}
zx::result<bool> device_schedule_unbind_children(const fbl::RefPtr<zx_device_t>& dev) {
fbl::AutoLock lock(&internal::ContextForApi()->api_lock());
return internal::ContextForApi()->ScheduleUnbindChildren(dev);
}
__EXPORT zx_status_t device_get_metadata(zx_device_t* dev, uint32_t type, void* buf, size_t buflen,
size_t* actual) {
DEBUG_ASSERT_VALID_DEVICE(dev);
fbl::AutoLock lock(&internal::ContextForApi()->api_lock());
auto dev_ref = fbl::RefPtr(dev);
return internal::ContextForApi()->GetMetadata(dev_ref, type, buf, buflen, actual);
}
__EXPORT zx_status_t device_get_metadata_size(zx_device_t* dev, uint32_t type, size_t* out_size) {
DEBUG_ASSERT_VALID_DEVICE(dev);
fbl::AutoLock lock(&internal::ContextForApi()->api_lock());
auto dev_ref = fbl::RefPtr(dev);
return internal::ContextForApi()->GetMetadataSize(dev_ref, type, out_size);
}
__EXPORT zx_status_t device_add_metadata(zx_device_t* dev, uint32_t type, const void* data,
size_t length) {
DEBUG_ASSERT_VALID_DEVICE(dev);
fbl::AutoLock lock(&internal::ContextForApi()->api_lock());
auto dev_ref = fbl::RefPtr(dev);
return internal::ContextForApi()->AddMetadata(dev_ref, type, data, length);
}
__EXPORT zx_status_t device_add_composite(zx_device_t* dev, const char* name,
const composite_device_desc_t* comp_desc) {
DEBUG_ASSERT_VALID_DEVICE(dev);
fbl::AutoLock lock(&internal::ContextForApi()->api_lock());
auto dev_ref = fbl::RefPtr(dev);
return internal::ContextForApi()->DeviceAddComposite(dev_ref, name, comp_desc);
}
__EXPORT bool driver_log_severity_enabled_internal(const zx_driver_t* drv, fx_log_severity_t flag) {
if (drv != nullptr) {
fbl::AutoLock lock(&internal::ContextForApi()->api_lock());
return drv->logger().GetSeverity() <= flag;
} else {
// If we have been invoked outside of the context of a driver, return true.
// Typically, this is due to being run within a test.
return true;
}
}
__EXPORT void driver_logvf_internal(const zx_driver_t* drv, fx_log_severity_t flag, const char* tag,
const char* file, int line, const char* msg, va_list args) {
if (drv != nullptr && flag != DDK_LOG_SERIAL) {
fbl::AutoLock lock(&internal::ContextForApi()->api_lock());
drv->logger().VLogWrite(flag, tag, msg, args, file, line);
} else {
// If we have been invoked outside of the context of a driver, or if |flag|
// is DDK_LOG_SERIAL, use vfprintf.
vfprintf(stderr, msg, args);
fputc('\n', stderr);
}
}
__EXPORT void driver_logf_internal(const zx_driver_t* drv, fx_log_severity_t flag, const char* tag,
const char* file, int line, const char* msg, ...) {
va_list args;
va_start(args, msg);
driver_logvf_internal(drv, flag, tag, file, line, msg, args);
va_end(args);
}
__EXPORT uint32_t device_get_fragment_count(zx_device_t* dev) {
DEBUG_ASSERT_VALID_DEVICE(dev);
if (!dev->is_composite()) {
return 0;
}
return dev->composite()->GetFragmentCount();
}
__EXPORT void device_get_fragments(zx_device_t* dev, composite_device_fragment_t* comp_list,
size_t comp_count, size_t* comp_actual) {
DEBUG_ASSERT_VALID_DEVICE(dev);
if (!dev->is_composite()) {
ZX_DEBUG_ASSERT(comp_actual != nullptr);
*comp_actual = 0;
return;
}
return dev->composite()->GetFragments(comp_list, comp_count, comp_actual);
}
__EXPORT zx_status_t device_get_fragment_protocol(zx_device_t* dev, const char* name,
uint32_t proto_id, void* out) {
DEBUG_ASSERT_VALID_DEVICE(dev);
if (!dev->is_composite()) {
return ZX_ERR_NOT_SUPPORTED;
}
zx_device_t* fragment;
if (!dev->composite()->GetFragment(name, &fragment)) {
return ZX_ERR_NOT_FOUND;
}
return device_get_protocol(fragment, proto_id, out);
}
__EXPORT zx_status_t device_get_fragment_metadata(zx_device_t* dev, const char* name, uint32_t type,
void* buf, size_t buflen, size_t* actual) {
DEBUG_ASSERT_VALID_DEVICE(dev);
if (!dev->is_composite()) {
return ZX_ERR_NOT_SUPPORTED;
}
zx_device_t* fragment;
if (!dev->composite()->GetFragment(name, &fragment)) {
return ZX_ERR_NOT_FOUND;
}
return device_get_metadata(fragment, type, buf, buflen, actual);
}
__EXPORT zx_status_t device_connect_fidl_protocol(zx_device_t* device, const char* protocol_name,
zx_handle_t request) {
DEBUG_ASSERT_VALID_DEVICE(device);
if (!protocol_name) {
return ZX_ERR_INVALID_ARGS;
}
fbl::AutoLock lock(&internal::ContextForApi()->api_lock());
return internal::ContextForApi()->ConnectFidlProtocol(fbl::RefPtr(device), {}, {}, protocol_name,
zx::channel{request});
}
__EXPORT zx_status_t device_get_variable(zx_device_t* device, const char* name, char* out,
size_t out_size, size_t* size_actual) {
DEBUG_ASSERT_VALID_DEVICE(device);
char* variable = getenv(name);
if (variable == nullptr) {
return ZX_ERR_NOT_FOUND;
}
size_t len = strlen(variable);
if (size_actual) {
*size_actual = len;
}
if (len > out_size) {
return ZX_ERR_BUFFER_TOO_SMALL;
}
strncpy(out, variable, out_size);
return ZX_OK;
}
__EXPORT zx_status_t device_add_composite_spec(zx_device_t* dev, const char* name,
const composite_node_spec_t* spec) {
DEBUG_ASSERT_VALID_DEVICE(dev);
fbl::AutoLock lock(&internal::ContextForApi()->api_lock());
return internal::ContextForApi()->DeviceAddCompositeNodeSpec(fbl::RefPtr(dev), name, spec);
}
__EXPORT zx_status_t device_connect_fidl_protocol2(zx_device_t* device, const char* service_name,
const char* protocol_name, zx_handle_t request) {
DEBUG_ASSERT_VALID_DEVICE(device);
if (!service_name || !protocol_name) {
return ZX_ERR_INVALID_ARGS;
}
fbl::AutoLock lock(&internal::ContextForApi()->api_lock());
return internal::ContextForApi()->ConnectFidlProtocol(fbl::RefPtr(device), {}, service_name,
protocol_name, zx::channel{request});
}
__EXPORT zx_status_t device_connect_fragment_fidl_protocol2(zx_device_t* device,
const char* fragment_name,
const char* service_name,
const char* protocol_name,
zx_handle_t request) {
DEBUG_ASSERT_VALID_DEVICE(device);
if (!fragment_name || !service_name || !protocol_name) {
return ZX_ERR_INVALID_ARGS;
}
fbl::AutoLock lock(&internal::ContextForApi()->api_lock());
return internal::ContextForApi()->ConnectFidlProtocol(
fbl::RefPtr(device), fragment_name, service_name, protocol_name, zx::channel{request});
}