blob: 584f0ef8528a2aebce89697c3c90f0f37a2e3ab7 [file] [log] [blame]
// 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 <lib/syslog/logger.h>
#include <stdarg.h>
#include <stdio.h>
#include <zircon/compiler.h>
#include <zircon/device/vfs.h>
#include <utility>
#include <ddk/debug.h>
#include <ddk/device.h>
#include <ddk/driver.h>
#include <ddk/fragment-device.h>
#include <fbl/auto_lock.h>
#include "driver_host.h"
#include "scheduler_profile.h"
// 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_INSTANCE | DEVICE_ADD_MUST_ISOLATE | \
DEVICE_ADD_INVISIBLE | DEVICE_ADD_ALLOW_MULTI_COMPOSITE)
__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) {
zx_status_t r;
fbl::RefPtr<zx_device_t> dev;
if (!parent) {
return ZX_ERR_INVALID_ARGS;
}
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;
}
if ((args->flags & DEVICE_ADD_INSTANCE) &&
(args->flags & (DEVICE_ADD_MUST_ISOLATE | DEVICE_ADD_INVISIBLE))) {
return ZX_ERR_INVALID_ARGS;
}
// If the device will be added in the same driver_host and visible,
// we can connect the client immediately after adding the device.
// Otherwise we will pass this channel to the devcoordinator via internal::device_add.
zx::channel client_remote(args->client_remote);
{
auto api_ctx = internal::ContextForApi();
fbl::AutoLock lock(&api_ctx->api_lock());
r = api_ctx->DeviceCreate(drv, args->name, args->ctx, args->ops, &dev);
if (r != ZX_OK) {
return r;
}
if (args->proto_id) {
dev->protocol_id = args->proto_id;
dev->protocol_ops = args->proto_ops;
}
if (args->flags & DEVICE_ADD_NON_BINDABLE) {
dev->flags |= DEV_FLAG_UNBINDABLE;
}
if (args->flags & DEVICE_ADD_INVISIBLE) {
dev->flags |= DEV_FLAG_INVISIBLE;
}
if (args->flags & DEVICE_ADD_ALLOW_MULTI_COMPOSITE) {
dev->flags |= DEV_FLAG_ALLOW_MULTI_COMPOSITE;
}
if (!args->power_states) {
// TODO(fxb/34081): Remove when all drivers declare power states
// Temporarily allocate working and non-working power states
device_power_state_info_t power_states[2] = {};
power_states[0].state_id = fuchsia_device_DevicePowerState_DEVICE_POWER_STATE_D0;
power_states[1].state_id = fuchsia_device_DevicePowerState_DEVICE_POWER_STATE_D3COLD;
r = dev->SetPowerStates(power_states, 2);
} else {
r = dev->SetPowerStates(args->power_states, args->power_state_count);
}
if (r != ZX_OK) {
return r;
}
if (args->performance_states && (args->performance_state_count != 0)) {
r = dev->SetPerformanceStates(args->performance_states, args->performance_state_count);
} else {
device_performance_state_info_t perf_power_states[1];
perf_power_states[0].state_id = fuchsia_device_DEVICE_PERFORMANCE_STATE_P0;
r = dev->SetPerformanceStates(perf_power_states, 1);
}
if (r != ZX_OK) {
return r;
}
// Set default system to device power state mapping. This can be later
// updated by the system power manager.
zx_device::SystemPowerStateMapping states_mapping{};
for (auto& entry : states_mapping) {
entry.dev_state = ::llcpp::fuchsia::device::DevicePowerState::DEVICE_POWER_STATE_D3COLD;
entry.wakeup_enable = false;
}
r = dev->SetSystemPowerStateMapping(states_mapping);
if (r != ZX_OK) {
return r;
}
// 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();
}
if (args->flags & DEVICE_ADD_MUST_ISOLATE) {
r = api_ctx->DeviceAdd(dev, parent_ref, args->props, args->prop_count, args->proxy_args,
std::move(client_remote));
} else if (args->flags & DEVICE_ADD_INSTANCE) {
dev->flags |= DEV_FLAG_INSTANCE | DEV_FLAG_UNBINDABLE;
r = api_ctx->DeviceAdd(dev, parent_ref, nullptr, 0, nullptr,
zx::channel() /* client_remote */);
} else {
bool pass_client_remote = args->flags & DEVICE_ADD_INVISIBLE;
r = api_ctx->DeviceAdd(dev, parent_ref, args->props, args->prop_count, nullptr,
pass_client_remote ? std::move(client_remote) : zx::channel());
}
if (r != ZX_OK) {
if (out) {
*out = nullptr;
}
dev.reset();
}
}
if (dev && client_remote.is_valid()) {
// This needs to be called outside the api lock, as device_open will be called
internal::ContextForApi()->DeviceConnect(dev, ZX_FS_RIGHT_READABLE | ZX_FS_RIGHT_WRITABLE,
std::move(client_remote));
// Leak the reference that was written to |out|, it will be recovered in device_remove().
// For device instances we mimic the behavior of |open| by not leaking the reference,
// effectively passing owenership to the new connection.
if (!(args->flags & DEVICE_ADD_INSTANCE)) {
__UNUSED auto ptr = fbl::ExportToRawPtr(&dev);
}
} else {
// Leak the reference that was written to |out|, it will be recovered in device_remove().
__UNUSED auto ptr = fbl::ExportToRawPtr(&dev);
}
return r;
}
__EXPORT void device_init_reply(zx_device_t* dev, zx_status_t status,
const device_init_reply_args_t* args) {
fbl::AutoLock lock(&internal::ContextForApi()->api_lock());
fbl::RefPtr<zx_device_t> dev_ref(dev);
internal::ContextForApi()->DeviceInitReply(dev_ref, status, args);
}
__EXPORT zx_status_t device_remove_deprecated(zx_device_t* 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 may just be
// scheduling a removal, and do not know when that will happen.
fbl::RefPtr<zx_device_t> dev_ref(dev);
return internal::ContextForApi()->DeviceRemoveDeprecated(dev_ref);
}
__EXPORT zx_status_t device_remove(zx_device_t* dev) { return device_remove_deprecated(dev); }
__EXPORT zx_status_t device_rebind(zx_device_t* dev) {
fbl::AutoLock lock(&internal::ContextForApi()->api_lock());
fbl::RefPtr<zx_device_t> dev_ref(dev);
return internal::ContextForApi()->DeviceRebind(dev_ref);
}
__EXPORT void device_make_visible(zx_device_t* dev, const device_make_visible_args_t* args) {
fbl::AutoLock lock(&internal::ContextForApi()->api_lock());
fbl::RefPtr<zx_device_t> dev_ref(dev);
internal::ContextForApi()->MakeVisible(dev_ref, args);
}
__EXPORT void device_async_remove(zx_device_t* 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) {
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) {
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) {
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_get_profile(zx_device_t* dev, uint32_t priority, const char* name,
zx_handle_t* out_profile) {
return internal::get_scheduler_profile(priority, name, out_profile);
}
__EXPORT zx_status_t device_get_deadline_profile(zx_device_t* device, uint64_t capacity,
uint64_t deadline, uint64_t period,
const char* name, zx_handle_t* out_profile) {
return internal::get_scheduler_deadline_profile(capacity, deadline, period, name, out_profile);
}
__EXPORT const char* device_get_name(zx_device_t* dev) { return dev->name; }
__EXPORT zx_device_t* device_get_parent(zx_device_t* dev) {
// The caller should not hold on to this past the lifetime of |dev|.
return dev->parent.get();
}
struct GenericProtocol {
void* ops;
void* ctx;
};
__EXPORT zx_status_t device_get_protocol(const zx_device_t* dev, uint32_t proto_id, void* out) {
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) {
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) {
if (dev->ops->close_protocol_session_multibindable) {
return dev->ops->close_protocol_session_multibindable(dev->ctx, proto);
}
return ZX_ERR_NOT_SUPPORTED;
}
__EXPORT void device_state_clr_set(zx_device_t* dev, zx_signals_t clearflag, zx_signals_t setflag) {
dev->event.signal(clearflag, setflag);
}
__EXPORT zx_off_t device_get_size(zx_device_t* dev) { return dev->GetSizeOp(); }
// LibDriver Misc Interfaces
// Please do not use get_root_resource() in new code. See ZX-1467.
__EXPORT zx_handle_t get_root_resource() {
return internal::ContextForApi()->root_resource().get();
}
__EXPORT zx_status_t load_firmware(zx_device_t* dev, const char* path, zx_handle_t* fw,
size_t* size) {
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(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_status_t device_schedule_unbind_children(const fbl::RefPtr<zx_device_t>& dev) {
fbl::AutoLock lock(&internal::ContextForApi()->api_lock());
return internal::ContextForApi()->ScheduleUnbindChildren(dev);
}
zx_status_t device_run_compatibility_tests(const fbl::RefPtr<zx_device_t>& dev,
int64_t hook_wait_time) {
fbl::AutoLock lock(&internal::ContextForApi()->api_lock());
return internal::ContextForApi()->DeviceRunCompatibilityTests(dev, hook_wait_time);
}
zx_status_t device_open(const fbl::RefPtr<zx_device_t>& dev, fbl::RefPtr<zx_device_t>* out,
uint32_t flags) {
fbl::AutoLock lock(&internal::ContextForApi()->api_lock());
return internal::ContextForApi()->DeviceOpen(dev, out, flags);
}
// This function is intended to consume the reference produced by device_open()
zx_status_t device_close(fbl::RefPtr<zx_device_t> dev, uint32_t flags) {
fbl::AutoLock lock(&internal::ContextForApi()->api_lock());
return internal::ContextForApi()->DeviceClose(std::move(dev), flags);
}
__EXPORT zx_status_t device_get_metadata(zx_device_t* dev, uint32_t type, void* buf, size_t buflen,
size_t* actual) {
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) {
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) {
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_publish_metadata(zx_device_t* dev, const char* path, uint32_t type,
const void* data, size_t length) {
fbl::AutoLock lock(&internal::ContextForApi()->api_lock());
auto dev_ref = fbl::RefPtr(dev);
return internal::ContextForApi()->PublishMetadata(dev_ref, path, type, data, length);
}
__EXPORT zx_status_t device_add_composite(zx_device_t* dev, const char* name,
const composite_device_desc_t* comp_desc) {
fbl::AutoLock lock(&internal::ContextForApi()->api_lock());
auto dev_ref = fbl::RefPtr(dev);
return internal::ContextForApi()->DeviceAddComposite(dev_ref, name, comp_desc);
}
__EXPORT zx_status_t device_schedule_work(zx_device_t* dev, void (*callback)(void*), void* cookie) {
fbl::AutoLock lock(&internal::ContextForApi()->api_lock());
auto dev_ref = fbl::RefPtr(dev);
return internal::ContextForApi()->ScheduleWork(dev_ref, callback, cookie);
}
__EXPORT bool driver_log_severity_enabled_internal(const zx_driver_t* drv, fx_log_severity_t flag) {
if (drv != nullptr) {
return fx_logger_get_min_severity(drv->logger()) <= 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_logf_internal(const zx_driver_t* drv, fx_log_severity_t flag, const char* msg,
...) {
va_list args;
va_start(args, msg);
if (drv != nullptr && flag != DDK_LOG_SERIAL) {
fx_logger_logvf(drv->logger(), flag, drv->name(), msg, args);
} 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);
}
va_end(args);
}
__EXPORT void device_fidl_transaction_take_ownership(fidl_txn_t* txn, device_fidl_txn_t* new_txn) {
auto fidl_txn = FromDdkInternalTransaction(ddk::internal::Transaction::FromTxn(txn));
ZX_ASSERT_MSG(std::holds_alternative<fidl::Transaction*>(fidl_txn),
"Can only take ownership of transaction once\n");
auto result = std::get<fidl::Transaction*>(fidl_txn)->TakeOwnership();
auto new_ddk_txn = MakeDdkInternalTransaction(std::move(result));
*new_txn = *new_ddk_txn.DeviceFidlTxn();
}