blob: 40a4c333a81cd2392eb00f2a543363237e51b78a [file] [log] [blame]
// Copyright 2019 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 <fuchsia/device/manager/c/fidl.h>
#include <fuchsia/device/manager/cpp/fidl.h>
#include <fuchsia/device/manager/llcpp/fidl.h>
#include <lib/async/cpp/task.h>
#include <lib/async/cpp/wait.h>
#include <lib/zx/channel.h>
#include <lib/zx/event.h>
#include <memory>
#include <variant>
#include <ddk/device.h>
#include <fbl/array.h>
#include <fbl/auto_lock.h>
#include <fbl/mutex.h>
#include <fbl/ref_counted.h>
#include <fbl/string.h>
#include <fs/vmo_file.h>
#include "async_loop_ref_counted_rpc_handler.h"
#include "composite_device.h"
#include "driver_test_reporter.h"
#include "inspect.h"
#include "metadata.h"
class Coordinator;
class DriverHost;
struct Devnode;
class InitTask;
class RemoveTask;
class SuspendContext;
class SuspendTask;
class ResumeTask;
class UnbindTask;
struct UnbindTaskOpts;
// clang-format off
// This device is never destroyed
#define DEV_CTX_IMMORTAL 0x0001
// This device requires that children are created in a
// new driver_host attached to a proxy device
#define DEV_CTX_MUST_ISOLATE 0x0002
// This device may be bound multiple times
#define DEV_CTX_MULTI_BIND 0x0004
// This device is bound and not eligible for binding
// again until unbound. Not allowed on MULTI_BIND ctx.
#define DEV_CTX_BOUND 0x0008
// Device has been remove()'d
#define DEV_CTX_DEAD 0x0010
// This device is a fragment of a composite device and
// can be part of multiple composite devices.
// Device is a proxy -- its "parent" is the device it's
// a proxy to.
#define DEV_CTX_PROXY 0x0040
// Device is not visible in devfs or bindable.
// Devices may be created in this state, but may not
// return to this state once made visible.
#define DEV_CTX_INVISIBLE 0x0080
// Device should not go through auto-bind process
#define DEV_CTX_SKIP_AUTOBIND 0x0100
// Signals used on the test event
constexpr zx::duration kDefaultTestTimeout = zx::sec(5);
// clang-format on
// Tags used for container membership identification
namespace internal {
struct DeviceChildListTag {};
struct DeviceDriverHostListTag {};
struct DeviceAllDevicesListTag {};
} // namespace internal
class Device
: public fbl::RefCounted<Device>,
public llcpp::fuchsia::device::manager::Coordinator::Interface,
public AsyncLoopRefCountedRpcHandler<Device>,
public fbl::ContainableBaseClasses<
fbl::TaggedDoublyLinkedListable<Device*, internal::DeviceChildListTag>,
fbl::TaggedDoublyLinkedListable<Device*, internal::DeviceDriverHostListTag>,
fbl::TaggedDoublyLinkedListable<fbl::RefPtr<Device>, internal::DeviceAllDevicesListTag>> {
using ChildListTag = internal::DeviceChildListTag;
using DriverHostListTag = internal::DeviceDriverHostListTag;
using AllDevicesListTag = internal::DeviceAllDevicesListTag;
void AddDevice(::zx::channel coordinator, ::zx::channel device_controller,
::fidl::VectorView<llcpp::fuchsia::device::manager::DeviceProperty> props,
::fidl::StringView name, uint32_t protocol_id, ::fidl::StringView driver_path,
::fidl::StringView args,
llcpp::fuchsia::device::manager::AddDeviceConfig device_add_config, bool has_init,
::zx::vmo inspect, ::zx::channel client_remote,
AddDeviceCompleter::Sync& _completer) override;
void ScheduleRemove(bool unbind_self, ScheduleRemoveCompleter::Sync& _completer) override;
void AddCompositeDevice(::fidl::StringView name,
llcpp::fuchsia::device::manager::CompositeDeviceDescriptor comp_desc,
AddCompositeDeviceCompleter::Sync& _completer) override;
void PublishMetadata(::fidl::StringView device_path, uint32_t key,
::fidl::VectorView<uint8_t> data,
PublishMetadataCompleter::Sync& _completer) override;
void MakeVisible(MakeVisibleCompleter::Sync& _completer) override;
void BindDevice(::fidl::StringView driver_path, BindDeviceCompleter::Sync& _completer) override;
void GetTopologicalPath(GetTopologicalPathCompleter::Sync& _completer) override;
void LoadFirmware(::fidl::StringView fw_path, LoadFirmwareCompleter::Sync& _completer) override;
void GetMetadata(uint32_t key, GetMetadataCompleter::Sync& _completer) override;
void GetMetadataSize(uint32_t key, GetMetadataSizeCompleter::Sync& _completer) override;
void AddMetadata(uint32_t key, ::fidl::VectorView<uint8_t> data,
AddMetadataCompleter::Sync& _completer) override;
void ScheduleUnbindChildren(ScheduleUnbindChildrenCompleter::Sync& _completer) override;
void RunCompatibilityTests(int64_t hook_wait_time,
RunCompatibilityTestsCompleter::Sync& _completer) override;
void DirectoryWatch(uint32_t mask, uint32_t options, ::zx::channel watcher,
DirectoryWatchCompleter::Sync& _completer) override;
// This iterator provides access to a list of devices that does not provide
// mechanisms for mutating that list. With this, a user can get mutable
// access to a device in the list. This is achieved by making the linked
// list iterator opaque. It is not safe to modify the underlying list while
// this iterator is in use.
template <typename IterType, typename DeviceType>
class ChildListIterator {
ChildListIterator() : state_(Done{}) {}
explicit ChildListIterator(DeviceType* device)
: state_(device->children_.begin()), device_(device) {
ChildListIterator operator++(int) {
auto other = *this;
return other;
bool operator==(const ChildListIterator& other) const { return state_ == other.state_; }
bool operator!=(const ChildListIterator& other) const { return !(state_ == other.state_); }
// The iterator implementation for the child list. This is the source of truth
// for what devices are children of the device.
ChildListIterator& operator++() {
[this](auto&& arg) {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, IterType>) {
} else if constexpr (std::is_same_v<T, Composite>) {
} else if constexpr (std::is_same_v<T, Done>) {
state_ = Done{};
return *this;
DeviceType& operator*() const {
return std::visit(
[](auto&& arg) -> DeviceType& {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, IterType>) {
return *arg;
} else if constexpr (std::is_same_v<T, Composite>) {
return *(arg->composite()->device());
} else {
// Advance the iterator to the next valid state or reach the done state.
// This is used to handle advancement between the different state variants.
void SkipInvalidStates() {
bool more = true;
while (more) {
more = std::visit(
[this](auto&& arg) {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, IterType>) {
// Check if there are any more children in the list. If
// there are, we're in a valid state and can stop.
// Otherwise, advance to the next variant and check if
// it's a valid state.
if (arg != device_->children_.end()) {
return false;
// If there are no more children, run through the Composite
// state next.
if (device_->parent_) {
state_ = Composite{device_->parent_->fragments().begin()};
} else {
state_ = Composite{};
return true;
} else if constexpr (std::is_same_v<T, Composite>) {
// Check if this device is an internal fragment device
// that bound to a composite fragment. If it is, and
// the composite has been constructed, the iterator
// should yield the composite.
if (device_->parent_) {
if (arg != device_->parent_->fragments().end() &&
arg->composite()->device() != nullptr) {
return false;
state_ = Done{};
return false;
} else if constexpr (std::is_same_v<T, Done>) {
return false;
using Composite = fbl::TaggedDoublyLinkedList<CompositeDeviceFragment*,
struct Done {
bool operator==(Done) const { return true; }
std::variant<IterType, Composite, Done> state_;
DeviceType* device_;
// This class exists to allow consumers of the Device class to write
// for (auto& child : dev->children())
// and get mutable access to the children without getting mutable access to
// the list.
template <typename DeviceType, typename IterType>
class ChildListIteratorFactory {
explicit ChildListIteratorFactory(DeviceType* device) : device_(device) {}
IterType begin() const { return IterType(device_); }
IterType end() const { return IterType(); }
bool is_empty() const { return begin() == end(); }
DeviceType* device_;
Device(Coordinator* coord, fbl::String name, fbl::String libname, fbl::String args,
fbl::RefPtr<Device> parent, uint32_t protocol_id, zx::vmo inspect,
zx::channel client_remote, bool wait_make_visible = false);
// Create a new device with the given parameters. This sets up its
// relationship with its parent and driver_host and adds its RPC channel to the
// coordinator's async loop. This does not add the device to the
// coordinator's devices_ list, or trigger publishing
static zx_status_t Create(Coordinator* coordinator, const fbl::RefPtr<Device>& parent,
fbl::String name, fbl::String driver_path, fbl::String args,
uint32_t protocol_id, fbl::Array<zx_device_prop_t> props,
zx::channel coordinator_rpc, zx::channel device_controller_rpc,
bool wait_make_visible, bool want_init_task, bool skip_autobind,
zx::vmo inspect, zx::channel client_remote,
fbl::RefPtr<Device>* device);
static zx_status_t CreateComposite(Coordinator* coordinator, fbl::RefPtr<DriverHost> driver_host,
const CompositeDevice& composite, zx::channel coordinator_rpc,
zx::channel device_controller_rpc,
fbl::RefPtr<Device>* device);
zx_status_t CreateProxy();
static void HandleRpc(fbl::RefPtr<Device>&& dev, async_dispatcher_t* dispatcher,
async::WaitBase* wait, zx_status_t status,
const zx_packet_signal_t* signal);
// We do not want to expose the list itself for mutation, even if the
// children are allowed to be mutated. We manage this by making the
// iterator opaque.
using NonConstChildListIterator =
ChildListIterator<fbl::TaggedDoublyLinkedList<Device*, ChildListTag>::iterator, Device>;
using ConstChildListIterator =
ChildListIterator<fbl::TaggedDoublyLinkedList<Device*, ChildListTag>::const_iterator,
const Device>;
using NonConstChildListIteratorFactory =
ChildListIteratorFactory<Device, NonConstChildListIterator>;
using ConstChildListIteratorFactory =
ChildListIteratorFactory<const Device, ConstChildListIterator>;
NonConstChildListIteratorFactory children() { return NonConstChildListIteratorFactory(this); }
ConstChildListIteratorFactory children() const { return ConstChildListIteratorFactory(this); }
// Signal that this device is ready for bind to happen. This should happen
// either immediately after the device is created, if it's created visible,
// or after it becomes visible.
zx_status_t SignalReadyForBind(zx::duration delay = zx::sec(0));
using InitCompletion = fit::callback<void(zx_status_t)>;
// Issue an Init request to this device. When the response comes in, the
// given completion will be invoked.
zx_status_t SendInit(InitCompletion completion);
using SuspendCompletion = fit::callback<void(zx_status_t)>;
// Issue a Suspend request to this device. When the response comes in, the
// given completion will be invoked.
zx_status_t SendSuspend(uint32_t flags, SuspendCompletion completion);
using ResumeCompletion = fit::callback<void(zx_status_t)>;
// Issue a Resume request to this device. When the response comes in, the
// given completion will be invoked.
zx_status_t SendResume(uint32_t target_system_state, ResumeCompletion completion);
using UnbindCompletion = fit::callback<void(zx_status_t)>;
using RemoveCompletion = fit::callback<void(zx_status_t)>;
// Issue an Unbind request to this device, which will run the unbind hook.
// When the response comes in, the given completion will be invoked.
zx_status_t SendUnbind(UnbindCompletion completion);
// Issue a CompleteRemoval request to this device.
// When the response comes in, the given completion will be invoked.
zx_status_t SendCompleteRemoval(RemoveCompletion completion);
// Break the relationship between this device object and its parent
void DetachFromParent();
// Sets the properties of this device. Returns an error if the properties
// array contains more than one property from the BIND_TOPO_* range.
zx_status_t SetProps(fbl::Array<const zx_device_prop_t> props);
const fbl::Array<const zx_device_prop_t>& props() const { return props_; }
const zx_device_prop_t* topo_prop() const { return topo_prop_; }
const fbl::RefPtr<Device>& parent() { return parent_; }
fbl::RefPtr<const Device> parent() const { return parent_; }
const fbl::RefPtr<Device>& proxy() { return proxy_; }
fbl::RefPtr<const Device> proxy() const { return proxy_; }
uint32_t protocol_id() const { return protocol_id_; }
DeviceInspect& inspect() { return *inspect_; }
bool is_bindable() const {
return !(flags & (DEV_CTX_BOUND | DEV_CTX_INVISIBLE)) && (state_ != Device::State::kDead);
bool should_skip_autobind() const { return flags & DEV_CTX_SKIP_AUTOBIND; }
bool is_visible() const { return !(flags & DEV_CTX_INVISIBLE); }
bool is_composite_bindable() const {
return false;
if ((flags & DEV_CTX_BOUND) && !(flags & DEV_CTX_ALLOW_MULTI_COMPOSITE)) {
return false;
return true;
void push_fragment(CompositeDeviceFragment* fragment) { fragments_.push_back(fragment); }
bool is_fragments_empty() { return fragments_.is_empty(); }
fbl::TaggedDoublyLinkedList<CompositeDeviceFragment*, CompositeDeviceFragment::DeviceListTag>&
fragments() {
return fragments_;
// If the device was created as a composite, this returns its description.
CompositeDevice* composite() const {
auto val = std::get_if<CompositeDevice*>(&composite_);
return val ? *val : nullptr;
void set_composite(CompositeDevice* composite) {
composite_ = composite;
void disassociate_from_composite() { composite_ = UnassociatedWithComposite{}; }
void set_host(fbl::RefPtr<DriverHost> host);
const fbl::RefPtr<DriverHost>& host() const { return host_; }
void set_local_id(uint64_t local_id) {
local_id_ = local_id;
uint64_t local_id() const { return local_id_; }
const fbl::DoublyLinkedList<std::unique_ptr<Metadata>>& metadata() const { return metadata_; }
void AddMetadata(std::unique_ptr<Metadata> md) { metadata_.push_front(std::move(md)); }
// Creates the init task for the device.
void CreateInitTask();
// Returns the in-progress init task if it exists, nullptr otherwise.
fbl::RefPtr<InitTask> GetActiveInit() { return active_init_; }
// Run the completion for the outstanding init, if any.
zx_status_t CompleteInit(zx_status_t status);
// Returns the in-progress suspend task if it exists, nullptr otherwise.
fbl::RefPtr<SuspendTask> GetActiveSuspend() { return active_suspend_; }
// Creates a new suspend task if necessary and returns a reference to it.
// If one is already in-progress, a reference to it is returned instead
fbl::RefPtr<SuspendTask> RequestSuspendTask(uint32_t suspend_flags);
fbl::RefPtr<ResumeTask> GetActiveResume() { return active_resume_; }
void SetActiveResume(fbl::RefPtr<ResumeTask> resume_task) { active_resume_ = resume_task; }
// Request Resume task
fbl::RefPtr<ResumeTask> RequestResumeTask(uint32_t system_resume_state);
// Run the completion for the outstanding suspend, if any. This method is
// only exposed currently because RemoveDevice is on Coordinator instead of
// Device.
void CompleteSuspend(zx_status_t status);
// Run the completion for the outstanding resume, if any.
void CompleteResume(zx_status_t status);
// Creates the unbind and remove tasks for the device if they do not already exist.
// |opts| is used to configure the unbind task.
void CreateUnbindRemoveTasks(UnbindTaskOpts opts);
// Returns the in-progress unbind task if it exists, nullptr otherwise.
// Unbind tasks are used to facilitate |Unbind| requests.
fbl::RefPtr<UnbindTask> GetActiveUnbind() { return active_unbind_; }
// Returns the in-progress remove task if it exists, nullptr otherwise.
// Remove tasks are used to facilitate |CompleteRemoval| requests.
fbl::RefPtr<RemoveTask> GetActiveRemove() { return active_remove_; }
// Run the completion for the outstanding unbind, if any.
zx_status_t CompleteUnbind(zx_status_t status = ZX_OK);
// Run the completion for the outstanding remove, if any.
zx_status_t CompleteRemove(zx_status_t status = ZX_OK);
// Drops the reference to the task.
// This should be called if the device will not send an init, unbind or remove request.
void DropInitTask() { active_init_ = nullptr; }
void DropUnbindTask() { active_unbind_ = nullptr; }
void DropRemoveTask() { active_remove_ = nullptr; }
zx_status_t DriverCompatibiltyTest();
zx::channel take_client_remote() { return std::move(client_remote_); }
const fbl::String& name() const { return name_; }
const fbl::String& libname() const { return libname_; }
const fbl::String& args() const { return args_; }
Coordinator* coordinator;
uint32_t flags = 0;
// The backoff between each driver retry. This grows exponentially.
zx::duration backoff = zx::msec(250);
// The number of retries left for the driver.
uint32_t retries = 4;
Devnode* self = nullptr;
Devnode* link = nullptr;
Devnode* devnode() { return self; }
const fbl::String& link_name() const { return link_name_; }
void set_link_name(fbl::String link_name) { link_name_ = std::move(link_name); }
fbl::RefPtr<fs::VmoFile>& inspect_file() { return inspect_file_; }
// TODO(teisenbe): We probably want more states.
#define STATE_VALUES(macro) \
macro(kActive) \
macro(kInitializing) /* The driver_host is in the process of running the device init hook.*/ \
macro(kSuspending) /* The driver_host is in the process of suspending the device.*/ \
macro(kSuspended) \
macro(kResuming) /* The driver_host is in the process of resuming the device.*/ \
macro(kResumed) /* Resume is complete. Will be marked active, after all children resume.*/ \
macro( \
kUnbinding) /* The driver_host is in the process of unbinding and removing the device.*/ \
macro(kDead) /* The device has been remove()'d*/
#define MAKE_ENUM_VALUE(state) state,
enum class State { STATE_VALUES(MAKE_ENUM_VALUE) };
#define MAKE_SWITCH_STATEMENT(state) \
case State::state: \
return #state;
static std::string StateToString(State state) {
void set_state(Device::State state) {
state_ = state;
State state() const { return state_; }
void clear_wait_make_visible() { wait_make_visible_ = false; }
bool wait_make_visible() const { return wait_make_visible_; }
void inc_num_removal_attempts() { num_removal_attempts_++; }
size_t num_removal_attempts() const { return num_removal_attempts_; }
void InitializeInspectValues();
enum class TestStateMachine {
kTestNotStarted = 1,
TestStateMachine test_state() {
fbl::AutoLock<fbl::Mutex> lock(&test_state_lock_);
return test_state_;
void set_test_state(TestStateMachine new_state) {
fbl::AutoLock<fbl::Mutex> lock(&test_state_lock_);
test_state_ = new_state;
void clear_active_resume() { active_resume_ = nullptr; }
void set_test_time(zx::duration& test_time) { test_time_ = test_time; }
void set_test_reply_required(bool required) { test_reply_required_ = required; }
zx::duration& test_time() { return test_time_; }
const char* GetTestDriverName();
zx::event& test_event() { return test_event_; }
// This is public for testing purposes.
std::unique_ptr<DriverTestReporter> test_reporter;
zx_status_t set_test_output(zx::channel test_output, async_dispatcher_t* dispatcher) {
test_output_ = std::move(test_output);
return test_wait_.Begin(dispatcher);
const fidl::InterfacePtr<fuchsia::device::manager::DeviceController>& device_controller() const {
return device_controller_;
const fidl::InterfaceRequest<fuchsia::device::manager::DeviceController> ConnectDeviceController(
async_dispatcher_t* dispatcher) {
return device_controller_.NewRequest(dispatcher);
void HandleTestOutput(async_dispatcher_t* dispatcher, async::WaitBase* wait, zx_status_t status,
const zx_packet_signal_t* signal);
// The driver sends output from run_unit_tests over this channel.
zx::channel test_output_;
// Async waiter that drives the consumption of test_output_. It is triggered when the channel is
// closed by the driver, signalling the end of the tests. We don't print log messages until the
// entire test is finished to avoid interleaving output from multiple drivers.
async::WaitMethod<Device, &Device::HandleTestOutput> test_wait_{this};
fidl::InterfacePtr<fuchsia::device::manager::DeviceController> device_controller_;
zx_status_t HandleRead();
int RunCompatibilityTests();
const fbl::String name_;
const fbl::String libname_;
const fbl::String args_;
fbl::RefPtr<Device> parent_;
const uint32_t protocol_id_;
fbl::RefPtr<Device> proxy_;
fbl::Array<const zx_device_prop_t> props_;
// If the device has a topological property in |props|, this points to it.
const zx_device_prop_t* topo_prop_ = nullptr;
async::TaskClosure publish_task_;
// List of all child devices of this device, except for composite devices.
// Composite devices are excluded because their multiple-parent nature
// precludes using the same intrusive nodes as single-parent devices.
fbl::TaggedDoublyLinkedList<Device*, ChildListTag> children_;
// Metadata entries associated to this device.
fbl::DoublyLinkedList<std::unique_ptr<Metadata>> metadata_;
// list of all fragments that this device bound to.
fbl::TaggedDoublyLinkedList<CompositeDeviceFragment*, CompositeDeviceFragment::DeviceListTag>
// - If this device is part of a composite device, this is inhabited by
// CompositeDeviceFragment* and it points to the fragment that matched it.
// Note that this is only set on the device that matched the fragment, not
// the "fragment device" added by the fragment driver.
// - If this device is a composite device, this is inhabited by
// CompositeDevice* and it points to the composite that describes it.
// - Otherwise, it is inhabited by UnassociatedWithComposite
struct UnassociatedWithComposite {};
std::variant<UnassociatedWithComposite, CompositeDevice*> composite_;
fbl::RefPtr<DriverHost> host_;
// The id of this device from the perspective of the driver_host. This can be
// used to communicate with the driver_host about this device.
uint64_t local_id_ = 0;
// The current state of the device
State state_ = State::kActive;
// If an init is in-progress, this task represents it.
fbl::RefPtr<InitTask> active_init_;
// If an init is in-progress, this completion will be invoked when it is
// completed. It will likely mark |active_init_| as completed and clear it.
InitCompletion init_completion_;
// If a suspend is in-progress, this task represents it.
fbl::RefPtr<SuspendTask> active_suspend_;
// If a suspend is in-progress, this completion will be invoked when it is
// completed. It will likely mark |active_suspend_| as completed and clear
// it.
SuspendCompletion suspend_completion_;
// If a resume is in-progress, this task represents it.
fbl::RefPtr<ResumeTask> active_resume_;
// If a Resume is in-progress, this completion will be invoked when it is
// completed.
ResumeCompletion resume_completion_;
// If an unbind is in-progress, this task represents it.
fbl::RefPtr<UnbindTask> active_unbind_;
// If an unbind is in-progress, this completion will be invoked when it is
// completed. It will likely mark |active_unbind_| as completed and clear
// it.
UnbindCompletion unbind_completion_;
// If a remove is in-progress, this task represents it.
fbl::RefPtr<RemoveTask> active_remove_;
// If a remove is in-progress, this completion will be invoked when it is
// completed. It will likely mark |active_remove_| as completed and clear
// it.
RemoveCompletion remove_completion_;
// Name of the inspect vmo file as it appears in diagnostics class directory
fbl::String link_name_;
fbl::RefPtr<fs::VmoFile> inspect_file_;
// For attaching as an open connection to the proxy device,
// or once the device becomes visible.
zx::channel client_remote_;
// If true, we should only make the device visible after DdkMakeVisible is called
// (and after the init task has completed).
bool wait_make_visible_ = false;
// For compatibility tests.
fbl::Mutex test_state_lock_;
TestStateMachine test_state_ __TA_GUARDED(test_state_lock_) = TestStateMachine::kTestNotStarted;
zx::event test_event_;
zx::duration test_time_;
fuchsia_device_manager_CompatibilityTestStatus test_status_;
bool test_reply_required_ = false;
// This lets us check for unexpected removals and is for testing use only.
size_t num_removal_attempts_ = 0;
std::optional<DeviceInspect> inspect_;