blob: d3d29740e97ed0fc71a149c59ed4c7187a02e950 [file] [log] [blame]
// Copyright 2017 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_LIB_DDKTL_INCLUDE_DDKTL_DEVICE_H_
#define SRC_LIB_DDKTL_INCLUDE_DDKTL_DEVICE_H_
#include <lib/component/incoming/cpp/protocol.h>
#include <lib/ddk/device.h>
#include <lib/ddk/driver.h>
#include <lib/fdf/cpp/channel.h>
#include <lib/fidl/cpp/wire/connect_service.h>
#include <lib/fidl/cpp/wire/message.h>
#include <lib/fidl/cpp/wire/traits.h>
#include <lib/fidl/cpp/wire/wire_messaging.h>
#include <lib/fidl_driver/cpp/transport.h>
#include <lib/stdcompat/span.h>
#include <lib/zx/channel.h>
#include <lib/zx/result.h>
#include <lib/zx/vmo.h>
#include <zircon/assert.h>
#include <deque>
#include <type_traits>
#include <ddktl/composite-node-spec.h>
#include <ddktl/device-internal.h>
#include <ddktl/fidl.h>
#include <ddktl/init-txn.h>
#include <ddktl/metadata.h>
#include <ddktl/resume-txn.h>
#include <ddktl/suspend-txn.h>
#include <ddktl/unbind-txn.h>
// ddk::Device<D, ...>
//
// Notes:
//
// ddk::Device<D, ...> is a mixin class that simplifies writing DDK drivers in
// C++. The DDK's zx_device_t defines a set of function pointer callbacks that
// can be implemented to define standard behavior (e.g., message),
// as well as to implement device lifecycle events (e.g., unbind/release). The
// mixin classes are used to set up the function pointer table to call methods
// from the user's class automatically.
//
// Every ddk::Device subclass must implement the following release callback to
// cleanup resources:
//
// void DdkRelease();
//
//
// :: Available mixins ::
// +----------------------------+----------------------------------------------------+
// | Mixin class | Required function implementation |
// +----------------------------+----------------------------------------------------+
// | ddk::GetProtocolable | zx_status_t DdkGetProtocol(uint32_t proto_id, |
// | | void* out) |
// | | |
// | ddk::Initializable | void DdkInit(ddk::InitTxn txn) |
// | | |
// | ddk::Unbindable | void DdkUnbind(ddk::UnbindTxn txn) |
// | | |
// | ddk::PerformanceTunable | zx_status_t DdkSetPerformanceState( |
// | | uint32_t requested_state,|
// | | uint32_t* out_state) |
// | | |
// | ddk::AutoSuspendable | zx_status_t DdkConfigureAutoSuspend(bool enable, |
// | | uint8_t requested_sleep_state)|
// | | |
// | ddk::Messageable<P>::Mixin | Methods defined by fidl::WireServer<P> |
// | | |
// | ddk::Suspendable | void DdkSuspend(ddk::SuspendTxn txn) |
// | | |
// | ddk::Resumable | zx_status_t DdkResume(uint8_t requested_state, |
// | | uint8_t* out_state) |
// | | |
// | ddk::Rxrpcable | zx_status_t DdkRxrpc(zx_handle_t channel) |
// | | |
// | ddk::MadeVisible | zx_status_t DdkMadeVisible() |
// +----------------------------+----------------------------------------------------+
//
// :: Example ::
//
// // Define our device type using a type alias.
// class MyDevice;
// using DeviceType = ddk::Device<MyDevice, ddk::Unbindable, ddk::Suspendable>;
//
// class MyDevice : public DeviceType {
// public:
// MyDevice(zx_device_t* parent)
// : DeviceType(parent) {}
//
// zx_status_t Bind() {
// // Any other setup required by MyDevice. The device_add_args_t will be filled out by the
// // base class.
// return DdkAdd("my-device-name");
// }
//
// // Methods required by the ddk mixins
// void DdkUnbind(ddk::UnbindTxn txn);
// void DdkSuspend(ddk::SuspendTxn txn);
// void DdkRelease();
// };
//
// extern "C" zx_status_t my_bind(zx_device_t* device,
// void** cookie) {
// auto dev = make_unique<MyDevice>(device);
// auto status = dev->Bind();
// if (status == ZX_OK) {
// // devmgr is now in charge of the memory for dev
// dev.release();
// }
// return status;
// }
//
// See also: protocol mixins for setting protocol_id and protocol_ops.
namespace ddk {
struct AnyProtocol {
const void* ops;
void* ctx;
};
// base_mixin is a tag that all mixins must inherit from.
using base_mixin = internal::base_mixin;
// base_protocol is a tag used by protocol implementations
using base_protocol = internal::base_protocol;
// DDK Device mixins
template <typename D>
class GetProtocolable : public base_mixin {
protected:
static constexpr void InitOp(zx_protocol_device_t* proto) {
internal::CheckGetProtocolable<D>();
proto->get_protocol = GetProtocol;
}
private:
static zx_status_t GetProtocol(void* ctx, uint32_t proto_id, void* out) {
return static_cast<D*>(ctx)->DdkGetProtocol(proto_id, out);
}
};
template <typename D>
class Initializable : public base_mixin {
protected:
static constexpr void InitOp(zx_protocol_device_t* proto) {
internal::CheckInitializable<D>();
proto->init = Init;
}
private:
static void Init(void* ctx) {
auto dev = static_cast<D*>(ctx);
InitTxn txn(dev->zxdev());
dev->DdkInit(std::move(txn));
}
};
template <typename D>
class Unbindable : public base_mixin {
protected:
static constexpr void InitOp(zx_protocol_device_t* proto) {
internal::CheckUnbindable<D>();
proto->unbind = Unbind;
}
private:
static void Unbind(void* ctx) {
auto dev = static_cast<D*>(ctx);
UnbindTxn txn(dev->zxdev());
dev->DdkUnbind(std::move(txn));
}
};
template <typename D, typename Protocol>
class MessageableInternal : public fidl::WireServer<Protocol>, public base_mixin {
protected:
static constexpr void InitOp(zx_protocol_device_t* proto) { proto->message = Message; }
private:
static void Message(void* ctx, fidl_incoming_msg_t msg, device_fidl_txn_t txn) {
fidl::WireDispatch<Protocol>(static_cast<D*>(ctx),
fidl::IncomingHeaderAndMessage::FromEncodedCMessage(msg),
ddk::FromDeviceFIDLTransaction(txn));
}
};
template <typename Protocol>
struct Messageable {
// This is necessary for currying as this mixin requires two type parameters, which are passed
// at different times.
template <typename D>
using Mixin = MessageableInternal<D, Protocol>;
};
template <typename D>
class Suspendable : public base_mixin {
protected:
static constexpr void InitOp(zx_protocol_device_t* proto) {
internal::CheckSuspendable<D>();
proto->suspend = Suspend_New;
}
private:
static void Suspend_New(void* ctx, uint8_t requested_state, bool enable_wake,
uint8_t suspend_reason) {
auto dev = static_cast<D*>(ctx);
SuspendTxn txn(dev->zxdev(), requested_state, enable_wake, suspend_reason);
static_cast<D*>(ctx)->DdkSuspend(std::move(txn));
}
};
template <typename D>
class AutoSuspendable : public base_mixin {
protected:
static constexpr void InitOp(zx_protocol_device_t* proto) {
internal::CheckConfigureAutoSuspend<D>();
proto->configure_auto_suspend = Configure_Auto_Suspend;
}
private:
static zx_status_t Configure_Auto_Suspend(void* ctx, bool enable, uint8_t requested_sleep_state) {
return static_cast<D*>(ctx)->DdkConfigureAutoSuspend(enable, requested_sleep_state);
}
};
template <typename D>
class Resumable : public base_mixin {
protected:
static constexpr void InitOp(zx_protocol_device_t* proto) {
internal::CheckResumable<D>();
proto->resume = Resume_New;
}
private:
static void Resume_New(void* ctx, uint32_t requested_state) {
auto dev = static_cast<D*>(ctx);
ResumeTxn txn(dev->zxdev(), requested_state);
static_cast<D*>(ctx)->DdkResume(std::move(txn));
}
};
template <typename D>
class Rxrpcable : public base_mixin {
protected:
static constexpr void InitOp(zx_protocol_device_t* proto) {
internal::CheckRxrpcable<D>();
proto->rxrpc = Rxrpc;
}
private:
static zx_status_t Rxrpc(void* ctx, zx_handle_t channel) {
return static_cast<D*>(ctx)->DdkRxrpc(channel);
}
};
template <typename D>
class ChildPreReleaseable : public base_mixin {
protected:
static constexpr void InitOp(zx_protocol_device_t* proto) {
internal::CheckChildPreReleaseable<D>();
proto->child_pre_release = ChildPreRelease;
}
private:
static void ChildPreRelease(void* ctx, void* child_ctx) {
static_cast<D*>(ctx)->DdkChildPreRelease(child_ctx);
}
};
template <typename D>
class MadeVisibleable : public base_mixin {
protected:
static constexpr void InitOp(zx_protocol_device_t* proto) {
internal::CheckMadeVisibleable<D>();
proto->made_visible = MadeVisible;
}
private:
static void MadeVisible(void* ctx) { static_cast<D*>(ctx)->DdkMadeVisible(); }
};
class MetadataList {
public:
MetadataList() = default;
MetadataList& operator=(const MetadataList& other) {
data_list_.clear();
metadata_list_.clear();
ZX_ASSERT(other.metadata_list_.size() == other.data_list_.size());
for (size_t i = 0; i < other.metadata_list_.size(); ++i) {
data_list_.push_back(other.data_list_[i]);
metadata_list_.push_back({other.metadata_list_[i].type, data_list_.back()->data(),
other.metadata_list_[i].length});
}
return *this;
}
MetadataList(const MetadataList& other) { *this = other; }
zx_status_t AddMetadata(zx_device_t* dev, uint32_t type) {
auto metadata_blob = GetMetadataBlob(dev, type);
if (!metadata_blob.is_ok()) {
return metadata_blob.error_value();
}
data_list_.emplace_back(
std::make_shared<std::vector<uint8_t>>(metadata_blob->begin(), metadata_blob->end()));
metadata_list_.push_back({type, data_list_.back()->data(), metadata_blob->size()});
return ZX_OK;
}
device_metadata_t* data() { return metadata_list_.data(); }
size_t count() { return metadata_list_.size(); }
private:
std::vector<std::shared_ptr<std::vector<uint8_t>>> data_list_;
std::vector<device_metadata_t> metadata_list_;
};
class DeviceAddArgs {
public:
explicit DeviceAddArgs(const char* name) {
args_.name = name;
args_.version = DEVICE_ADD_ARGS_VERSION;
}
DeviceAddArgs& operator=(const DeviceAddArgs& other) {
metadata_list_ = other.metadata_list_;
args_ = other.args_;
args_.metadata_list = metadata_list_.data();
args_.metadata_count = metadata_list_.count();
return *this;
}
DeviceAddArgs(const DeviceAddArgs& other) { *this = other; }
DeviceAddArgs& set_name(const char* name) {
args_.name = name;
return *this;
}
DeviceAddArgs& set_flags(uint32_t flags) {
args_.flags = flags;
return *this;
}
DeviceAddArgs& set_context(void* ctx) {
args_.ctx = ctx;
return *this;
}
DeviceAddArgs& set_props(cpp20::span<const zx_device_prop_t> props) {
args_.props = props.data();
args_.prop_count = static_cast<uint32_t>(props.size());
return *this;
}
DeviceAddArgs& set_str_props(cpp20::span<const zx_device_str_prop_t> props) {
args_.str_props = props.data();
args_.str_prop_count = static_cast<uint32_t>(props.size());
return *this;
}
DeviceAddArgs& set_proto_id(uint32_t proto_id) {
args_.proto_id = proto_id;
return *this;
}
DeviceAddArgs& set_ops(zx_protocol_device_t* ops) {
args_.ops = ops;
return *this;
}
DeviceAddArgs& set_inspect_vmo(zx::vmo inspect_vmo) {
args_.inspect_vmo = inspect_vmo.release();
return *this;
}
DeviceAddArgs& forward_metadata(zx_device_t* dev, uint32_t type) {
if (ZX_OK == metadata_list_.AddMetadata(dev, type)) {
args_.metadata_list = metadata_list_.data();
args_.metadata_count = metadata_list_.count();
}
return *this;
}
DeviceAddArgs& set_outgoing_dir(zx::channel outgoing_dir) {
args_.outgoing_dir_channel = outgoing_dir.release();
return *this;
}
DeviceAddArgs& set_fidl_service_offers(cpp20::span<const char*> fidl_service_offers) {
args_.fidl_service_offers = fidl_service_offers.data();
args_.fidl_service_offer_count = fidl_service_offers.size();
return *this;
}
DeviceAddArgs& set_runtime_service_offers(cpp20::span<const char*> runtime_service_offers) {
args_.runtime_service_offers = runtime_service_offers.data();
args_.runtime_service_offer_count = runtime_service_offers.size();
return *this;
}
DeviceAddArgs& set_power_states(cpp20::span<const device_power_state_info_t> power_states) {
args_.power_states = power_states.data();
args_.power_state_count = static_cast<uint8_t>(power_states.size());
return *this;
}
const device_add_args_t& get() const { return args_; }
private:
MetadataList metadata_list_;
device_add_args_t args_ = {};
};
// Device is templated on the list of mixins that define which DDK device
// methods are implemented. Note that internal::base_device *must* be the
// left-most base class in order to ensure that its constructor runs before the
// mixin constructors. This ensures that ddk_device_proto_ is zero-initialized
// before setting the fields in the mixins.
template <class D, template <typename> class... Mixins>
class Device : public ::ddk::internal::base_device<D, Mixins...> {
public:
zx_status_t DdkAdd(const char* name, device_add_args_t args) {
if (this->zxdev_ != nullptr) {
return ZX_ERR_BAD_STATE;
}
args.version = DEVICE_ADD_ARGS_VERSION;
args.name = name;
// Since we are stashing this as a D*, we can use ctx in all
// the callback functions and cast it directly to a D*.
args.ctx = static_cast<D*>(this);
args.ops = &this->ddk_device_proto_;
AddProtocol(&args);
if (name) {
this->name_ = name;
}
return device_add(this->parent_, &args, &this->zxdev_);
}
zx_status_t DdkAdd(const DeviceAddArgs& args) { return DdkAdd(args.get().name, args.get()); }
zx_status_t DdkAdd(const char* name, uint32_t flags = 0) {
return DdkAdd(ddk::DeviceAddArgs(name).set_flags(flags));
}
zx_status_t DdkAddCompositeNodeSpec(const char* name, const CompositeNodeSpec& spec) {
return device_add_composite_spec(this->parent_, name, &spec.get());
}
// Schedules the removal of the device and its descendents.
// Each device will evenutally have its unbind hook (if implemented) and release hook invoked.
void DdkAsyncRemove() {
ZX_ASSERT(this->zxdev_ != nullptr);
zx_device_t* dev = this->zxdev_;
device_async_remove(dev);
}
zx_status_t DdkGetMetadataSize(uint32_t type, size_t* out_size) {
// Uses parent() instead of zxdev() as metadata is usually checked
// before DdkAdd(). There are few use cases to actually call it on self.
return device_get_metadata_size(parent(), type, out_size);
}
zx_status_t DdkGetMetadata(uint32_t type, void* buf, size_t buf_len, size_t* actual) {
// Uses parent() instead of zxdev() as metadata is usually checked
// before DdkAdd(). There are few use cases to actually call it on self.
return device_get_metadata(parent(), type, buf, buf_len, actual);
}
zx_status_t DdkAddMetadata(uint32_t type, const void* data, size_t length) {
return device_add_metadata(zxdev(), type, data, length);
}
zx_status_t DdkGetFragmentMetadata(const char* name, uint32_t type, void* buf, size_t buf_len,
size_t* actual) {
// Uses parent() instead of zxdev() as metadata is usually checked
// before DdkAdd(). There are few use cases to actually call it on self.
return device_get_fragment_metadata(parent(), name, type, buf, buf_len, actual);
}
zx_status_t DdkGetFragmentProtocol(const char* name, uint32_t proto_id, void* out) {
return device_get_fragment_protocol(zxdev(), name, proto_id, out);
}
template <typename ServiceMember,
typename = std::enable_if_t<fidl::IsServiceMemberV<ServiceMember>>>
zx::result<fidl::ClientEnd<typename ServiceMember::ProtocolType>> DdkConnectFidlProtocol() const {
static_assert(std::is_same_v<typename ServiceMember::ProtocolType::Transport,
fidl::internal::ChannelTransport>);
return DdkConnectFidlProtocol<ServiceMember>(parent());
}
template <typename ServiceMember,
typename = std::enable_if_t<fidl::IsServiceMemberV<ServiceMember>>>
static zx::result<fidl::ClientEnd<typename ServiceMember::ProtocolType>> DdkConnectFidlProtocol(
zx_device_t* parent) {
static_assert(std::is_same_v<typename ServiceMember::ProtocolType::Transport,
fidl::internal::ChannelTransport>);
auto endpoints = fidl::CreateEndpoints<typename ServiceMember::ProtocolType>();
if (endpoints.is_error()) {
return endpoints.take_error();
}
auto status =
device_connect_fidl_protocol2(parent, ServiceMember::ServiceName, ServiceMember::Name,
endpoints->server.TakeChannel().release());
if (status != ZX_OK) {
return zx::error(status);
}
return zx::ok(std::move(endpoints->client));
}
template <typename ServiceMember,
typename = std::enable_if_t<fidl::IsServiceMemberV<ServiceMember>>>
zx::result<fidl::ClientEnd<typename ServiceMember::ProtocolType>> DdkConnectFragmentFidlProtocol(
const char* fragment_name) const {
static_assert(std::is_same_v<typename ServiceMember::ProtocolType::Transport,
fidl::internal::ChannelTransport>);
return DdkConnectFragmentFidlProtocol<ServiceMember>(parent(), fragment_name);
}
template <typename ServiceMember,
typename = std::enable_if_t<fidl::IsServiceMemberV<ServiceMember>>>
static zx::result<fidl::ClientEnd<typename ServiceMember::ProtocolType>>
DdkConnectFragmentFidlProtocol(zx_device_t* parent, const char* fragment_name) {
static_assert(std::is_same_v<typename ServiceMember::ProtocolType::Transport,
fidl::internal::ChannelTransport>);
auto endpoints = fidl::CreateEndpoints<typename ServiceMember::ProtocolType>();
if (endpoints.is_error()) {
return endpoints.take_error();
}
auto status = device_connect_fragment_fidl_protocol(
parent, fragment_name, ServiceMember::ServiceName, ServiceMember::Name,
endpoints->server.TakeChannel().release());
if (status != ZX_OK) {
return zx::error(status);
}
return zx::ok(std::move(endpoints->client));
}
template <typename ServiceMember>
zx::result<fdf::ClientEnd<typename ServiceMember::ProtocolType>> DdkConnectRuntimeProtocol()
const {
static_assert(fidl::IsServiceMemberV<ServiceMember>);
static_assert(std::is_same_v<typename ServiceMember::ProtocolType::Transport,
fidl::internal::DriverTransport>);
return DdkConnectRuntimeProtocol<ServiceMember>(parent());
}
template <typename ServiceMember,
typename = std::enable_if_t<fidl::IsServiceMemberV<ServiceMember>>>
zx::result<fdf::ClientEnd<typename ServiceMember::ProtocolType>>
DdkConnectFragmentRuntimeProtocol(const char* fragment_name) const {
static_assert(std::is_same_v<typename ServiceMember::ProtocolType::Transport,
fidl::internal::DriverTransport>);
return DdkConnectFragmentRuntimeProtocol<ServiceMember>(parent(), fragment_name);
}
template <typename ServiceMember>
static zx::result<fdf::ClientEnd<typename ServiceMember::ProtocolType>> DdkConnectRuntimeProtocol(
zx_device_t* parent) {
static_assert(fidl::IsServiceMemberV<ServiceMember>);
static_assert(std::is_same_v<typename ServiceMember::ProtocolType::Transport,
fidl::internal::DriverTransport>);
auto endpoints = fdf::CreateEndpoints<typename ServiceMember::ProtocolType>();
if (endpoints.is_error()) {
return endpoints.take_error();
}
auto status =
device_connect_runtime_protocol(parent, ServiceMember::ServiceName, ServiceMember::Name,
endpoints->server.TakeChannel().release());
if (status != ZX_OK) {
return zx::error(status);
}
return zx::ok(std::move(endpoints->client));
}
template <typename ServiceMember>
static zx::result<fdf::ClientEnd<typename ServiceMember::ProtocolType>>
DdkConnectFragmentRuntimeProtocol(zx_device_t* parent, const char* fragment_name) {
static_assert(fidl::IsServiceMemberV<ServiceMember>);
static_assert(std::is_same_v<typename ServiceMember::ProtocolType::Transport,
fidl::internal::DriverTransport>);
auto endpoints = fdf::CreateEndpoints<typename ServiceMember::ProtocolType>();
if (endpoints.is_error()) {
return endpoints.take_error();
}
auto status = device_connect_fragment_runtime_protocol(
parent, fragment_name, ServiceMember::ServiceName, ServiceMember::Name,
endpoints->server.TakeChannel().release());
if (status != ZX_OK) {
return zx::error(status);
}
return zx::ok(std::move(endpoints->client));
}
const char* name() const { return this->name_.c_str(); }
// The opaque pointer representing this device.
zx_device_t* zxdev() const { return this->zxdev_; }
// The opaque pointer representing the device's parent.
zx_device_t* parent() const { return this->parent_; }
protected:
explicit Device(zx_device_t* parent) : internal::base_device<D, Mixins...>(parent) {
internal::CheckMixins<Mixins<D>...>();
internal::CheckReleasable<D>();
}
private:
// Add the protocol id and ops if D inherits from a base_protocol implementation.
template <typename T = D>
void AddProtocol(
device_add_args_t* args,
typename std::enable_if<internal::is_base_protocol<T>::value, T>::type* dummy = 0) {
auto dev = static_cast<D*>(this);
ZX_ASSERT(dev->ddk_proto_id_ > 0);
args->proto_id = dev->ddk_proto_id_;
args->proto_ops = dev->ddk_proto_ops_;
}
// If D does not inherit from a base_protocol implementation, do nothing.
template <typename T = D>
void AddProtocol(
device_add_args_t* args,
typename std::enable_if<!internal::is_base_protocol<T>::value, T>::type* dummy = 0) {}
};
} // namespace ddk
#endif // SRC_LIB_DDKTL_INCLUDE_DDKTL_DEVICE_H_