blob: 1fe944d96f5982112e24452eee482bb414a98ee6 [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.
#pragma once
#include <ddk/binding.h>
#include <ddk/device.h>
#include <ddk/driver.h>
#include <fbl/intrusive_double_list.h>
#include <fbl/string.h>
#include <fbl/unique_ptr.h>
#include <fbl/vector.h>
#include <lib/async/cpp/task.h>
#include <lib/async/cpp/wait.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/fit/function.h>
#include <lib/zx/channel.h>
#include <lib/zx/job.h>
#include <lib/zx/process.h>
#include <lib/zx/socket.h>
#include <lib/zx/vmo.h>
#include <utility>
#include "metadata.h"
namespace devmgr {
class Coordinator;
class Devhost;
class DevhostLoaderService;
struct Devnode;
class SuspendContext;
class PendingOperation {
public:
enum struct Op : uint32_t {
kBind = 1,
kSuspend = 2,
};
PendingOperation(Op op, SuspendContext* context) : op_(op), context_(context) {}
struct Node {
static fbl::DoublyLinkedListNodeState<fbl::unique_ptr<PendingOperation>>& node_state(
PendingOperation& obj) {
return obj.node_;
}
};
Op op() const { return op_; }
SuspendContext* context() const { return context_; }
private:
fbl::DoublyLinkedListNodeState<fbl::unique_ptr<PendingOperation>> node_;
Op op_;
SuspendContext* context_;
};
#define DEV_HOST_DYING 1
#define DEV_HOST_SUSPEND 2
struct Device {
explicit Device(Coordinator* coord);
~Device();
// Begins waiting in |dispatcher| on |dev->wait|. This transfers a
// reference of |dev| to the dispatcher. The dispatcher returns ownership
// when the of that reference when the handler is invoked.
// TODO(teisenbe/kulakowski): Make this take a RefPtr
static zx_status_t BeginWait(Device* dev, async_dispatcher_t* dispatcher) {
// TODO(teisenbe/kulakowski): Once this takes a refptr, we should leak a
// ref in the success case (to present the ref owned by the dispatcher).
return dev->wait.Begin(dispatcher);
}
// Entrypoint for the RPC handler that captures the pointer ownership
// semantics.
void HandleRpcEntry(async_dispatcher_t* dispatcher, async::WaitBase* wait, zx_status_t status,
const zx_packet_signal_t* signal) {
// TODO(teisenbe/kulakowski): Perform the appropriate dance to construct
// a RefPtr from |this| without a net-increase in refcount, to represent
// the dispatcher passing ownership of its reference to the handler
HandleRpc(this, dispatcher, wait, status, signal);
}
// TODO(teisenbe/kulakowski): Make this take a RefPtr
static void HandleRpc(Device* dev, async_dispatcher_t* dispatcher,
async::WaitBase* wait, zx_status_t status,
const zx_packet_signal_t* signal);
Coordinator* coordinator;
zx::channel hrpc;
uint32_t flags = 0;
async::WaitMethod<Device, &Device::HandleRpcEntry> wait{this};
async::TaskClosure publish_task;
Devhost* host = nullptr;
const char* name = nullptr;
const char* libname = nullptr;
fbl::unique_ptr<const char[]> args;
// 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;
mutable int32_t refcount_ = 0;
uint32_t protocol_id = 0;
uint32_t prop_count = 0;
Devnode* self = nullptr;
Devnode* link = nullptr;
Device* parent = nullptr;
Device* proxy = nullptr;
// For attaching as an open connection to the proxy device,
// or once the device becomes visible.
zx::channel client_remote;
// listnode for this device in its parent's
// list-of-children
fbl::DoublyLinkedListNodeState<Device*> node;
struct Node {
static fbl::DoublyLinkedListNodeState<Device*>& node_state(
Device& obj) {
return obj.node;
}
};
// listnode for this device in its devhost's
// list-of-devices
fbl::DoublyLinkedListNodeState<Device*> dhnode;
struct DevhostNode {
static fbl::DoublyLinkedListNodeState<Device*>& node_state(
Device& obj) {
return obj.dhnode;
}
};
// list of all child devices of this device
fbl::DoublyLinkedList<Device*, Node> children;
// list of outstanding requests from the devcoord
// to this device's devhost, awaiting a response
fbl::DoublyLinkedList<fbl::unique_ptr<PendingOperation>, PendingOperation::Node> pending;
// listnode for this device in the all devices list
fbl::DoublyLinkedListNodeState<Device*> anode;
struct AllDevicesNode {
static fbl::DoublyLinkedListNodeState<Device*>& node_state(
Device& obj) {
return obj.anode;
}
};
// Metadata entries associated to this device.
fbl::DoublyLinkedList<fbl::unique_ptr<Metadata>, Metadata::Node> metadata;
fbl::unique_ptr<zx_device_prop_t[]> props;
// Allocation backing |name| and |libname|
fbl::unique_ptr<char[]> name_alloc_;
// The AddRef and Release functions follow the contract for fbl::RefPtr.
void AddRef() const {
++refcount_;
}
// Returns true when the last reference has been released.
bool Release() const {
const int32_t rc = refcount_;
--refcount_;
return rc == 1;
}
};
class Devhost {
public:
struct AllDevhostsNode {
static fbl::DoublyLinkedListNodeState<Devhost*>& node_state(
Devhost& obj) {
return obj.anode_;
}
};
struct SuspendNode {
static fbl::DoublyLinkedListNodeState<Devhost*>& node_state(
Devhost& obj) {
return obj.snode_;
}
};
struct Node {
static fbl::DoublyLinkedListNodeState<Devhost*>& node_state(
Devhost& obj) {
return obj.node_;
}
};
Devhost();
zx_handle_t hrpc() const { return hrpc_; }
void set_hrpc(zx_handle_t hrpc) { hrpc_ = hrpc; }
zx::unowned_process proc() const { return zx::unowned_process(proc_); }
void set_proc(zx_handle_t proc) { proc_.reset(proc); }
zx_koid_t koid() const { return koid_; }
void set_koid(zx_koid_t koid) { koid_ = koid; }
// Note: this is a non-const reference to make |= etc. ergonomic.
uint32_t& flags() { return flags_; }
Devhost* parent() const { return parent_; }
void set_parent(Devhost* parent) { parent_ = parent; }
fbl::DoublyLinkedList<Device*, Device::DevhostNode>& devices() { return devices_; }
fbl::DoublyLinkedList<Devhost*, Node>& children() { return children_; }
// The AddRef and Release functions follow the contract for fbl::RefPtr.
void AddRef() const {
++refcount_;
}
// Returns true when the last reference has been released.
bool Release() const {
const int32_t rc = refcount_;
--refcount_;
return rc == 1;
}
private:
async::Wait wait_;
zx_handle_t hrpc_;
zx::process proc_;
zx_koid_t koid_;
mutable int32_t refcount_;
uint32_t flags_;
Devhost* parent_;
// list of all devices on this devhost
fbl::DoublyLinkedList<Device*, Device::DevhostNode> devices_;
// listnode for this devhost in the all devhosts list
fbl::DoublyLinkedListNodeState<Devhost*> anode_;
// listnode for this devhost in the order-to-suspend list
fbl::DoublyLinkedListNodeState<Devhost*> snode_;
// listnode for this devhost in its parent devhost's list-of-children
fbl::DoublyLinkedListNodeState<Devhost*> node_;
// list of all child devhosts of this devhost
fbl::DoublyLinkedList<Devhost*, Node> children_;
};
class SuspendContext {
public:
enum struct Flags : uint32_t {
kRunning = 0u,
kSuspend = 1u,
};
SuspendContext() {
}
SuspendContext(Coordinator* coordinator,
Flags flags, uint32_t sflags, zx::socket socket,
zx::vmo kernel = zx::vmo(),
zx::vmo bootdata = zx::vmo()) :
coordinator_(coordinator), flags_(flags), sflags_(sflags),
socket_(std::move(socket)), kernel_(std::move(kernel)),
bootdata_(std::move(bootdata)) {
}
~SuspendContext() {
devhosts_.clear();
}
SuspendContext(SuspendContext&&) = default;
SuspendContext& operator=(SuspendContext&&) = default;
Coordinator* coordinator() { return coordinator_; }
zx_status_t status() const { return status_; }
void set_status(zx_status_t status) { status_ = status; }
Flags flags() const { return flags_; }
void set_flags(Flags flags) { flags_ = flags; }
uint32_t sflags() const { return sflags_; }
Devhost* dh() const { return dh_; }
void set_dh(Devhost* dh) { dh_ = dh; }
using DevhostList = fbl::DoublyLinkedList<Devhost*, Devhost::SuspendNode>;
DevhostList& devhosts() { return devhosts_; }
const DevhostList& devhosts() const { return devhosts_; }
const zx::vmo& kernel() const { return kernel_; }
const zx::vmo& bootdata() const { return bootdata_; }
// Close the socket whose ownership was handed to this SuspendContext.
void CloseSocket() {
socket_.reset();
}
// The AddRef and Release functions follow the contract for fbl::RefPtr.
void AddRef() const {
++count_;
}
// Returns true when the last message reference has been released.
bool Release() const {
const int32_t rc = count_;
--count_;
return rc == 1;
}
private:
Coordinator* coordinator_ = nullptr;
zx_status_t status_ = ZX_OK;
Flags flags_ = Flags::kRunning;
// suspend flags
uint32_t sflags_ = 0u;
// outstanding msgs
mutable uint32_t count_ = 0u;
// next devhost to process
Devhost* dh_ = nullptr;
fbl::DoublyLinkedList<Devhost*, Devhost::SuspendNode> devhosts_;
// socket to notify on for 'dm reboot' and 'dm poweroff'
zx::socket socket_;
// mexec arguments
zx::vmo kernel_;
zx::vmo bootdata_;
};
// This device is never destroyed
#define DEV_CTX_IMMORTAL 0x01
// This device requires that children are created in a
// new devhost attached to a proxy device
#define DEV_CTX_MUST_ISOLATE 0x02
// This device may be bound multiple times
#define DEV_CTX_MULTI_BIND 0x04
// This device is bound and not eligible for binding
// again until unbound. Not allowed on MULTI_BIND ctx.
#define DEV_CTX_BOUND 0x08
// Device has been remove()'d
#define DEV_CTX_DEAD 0x10
// Device has been removed but its rpc channel is not
// torn down yet. The rpc transport will call remove
// when it notices at which point the device will leave
// the zombie state and drop the reference associated
// with the rpc channel, allowing complete destruction.
#define DEV_CTX_ZOMBIE 0x20
// Device is a proxy -- its "parent" is the device it's
// a proxy to.
#define DEV_CTX_PROXY 0x40
// 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 0x80
struct Driver {
Driver() = default;
fbl::String name;
fbl::unique_ptr<const zx_bind_inst_t[]> binding;
// Binding size in number of bytes, not number of entries
// TODO: Change it to number of entries
uint32_t binding_size = 0;
uint32_t flags = 0;
zx::vmo dso_vmo;
fbl::DoublyLinkedListNodeState<Driver*> node;
struct Node {
static fbl::DoublyLinkedListNodeState<Driver*>& node_state(
Driver& obj) {
return obj.node;
}
};
fbl::String libname;
};
#define DRIVER_NAME_LEN_MAX 64
zx_status_t devfs_publish(Device* parent, Device* dev);
void devfs_unpublish(Device* dev);
void devfs_advertise(Device* dev);
void devfs_advertise_modified(Device* dev);
zx_status_t devfs_connect(Device* dev, zx::channel client_remote);
// Values parsed out of argv. All paths described below are absolute paths.
struct DevmgrArgs {
// Load drivers from these directories. If this is empty, the default will
// be used.
fbl::Vector<const char*> driver_search_paths;
// Load the drivers with these paths. The specified drivers do not need to
// be in directories in |driver_search_paths|.
fbl::Vector<const char*> load_drivers;
// Use this driver as the sys_device driver. If nullptr, the default will
// be used.
const char* sys_device_driver = nullptr;
};
class Coordinator {
public:
Coordinator(const Coordinator&) = delete;
Coordinator& operator=(const Coordinator&) = delete;
Coordinator(Coordinator&&) = delete;
Coordinator& operator=(Coordinator&&) = delete;
Coordinator(zx::job devhost_job, async_dispatcher_t* dispatcher, bool require_system);
zx_status_t InitializeCoreDevices();
zx_status_t HandleDmctlWrite(size_t len, const char* cmd);
const Driver* LibnameToDriver(const char* libname) const;
zx_status_t LibnameToVmo(const char* libname, zx::vmo* out_vmo) const;
bool InSuspend() const;
void DumpDevice(const Device* dev, size_t indent) const;
void DumpState() const;
void DumpDeviceProps(const Device* dev) const;
void DumpGlobalDeviceProps() const;
void DumpDrivers() const;
zx_status_t GetTopoPath(const Device* dev, char* out, size_t max) const;
zx_status_t NewDevhost(const char* name, Devhost* parent, Devhost** out);
void ReleaseDevhost(Devhost* dh);
void ReleaseDevice(Device* dev);
zx_status_t AddDevice(Device* parent, zx::channel rpc, const uint64_t* props_data,
size_t props_count, fbl::StringPiece name, uint32_t protocol_id,
fbl::StringPiece driver_path, fbl::StringPiece args, bool invisible,
zx::channel client_remote);
zx_status_t MakeVisible(Device* dev);
zx_status_t RemoveDevice(Device* dev, bool forced);
zx_status_t LoadFirmware(Device* dev, const char* path, zx::vmo* vmo, size_t* size);
zx_status_t GetMetadata(Device* dev, uint32_t type, void* buffer, size_t buflen,
size_t* actual);
zx_status_t AddMetadata(Device* dev, uint32_t type, const void* data, uint32_t length);
zx_status_t PublishMetadata(Device* dev, const char* path, uint32_t type, const void* data,
uint32_t length);
zx_status_t BindDevice(Device* dev, fbl::StringPiece drvlibname);
void BindDriver(Driver* drv);
zx_status_t HandleDeviceRead(Device* dev);
zx_status_t PrepareProxy(Device* dev);
zx_status_t AttemptBind(const Driver* drv, Device* dev);
void HandleNewDevice(Device* dev);
void ScanSystemDrivers();
void BindSystemDrivers();
void BindDrivers();
void UseFallbackDrivers();
void Mexec(zx::vmo kernel, zx::vmo bootdata);
void Suspend(uint32_t flags);
void ContinueSuspend(SuspendContext* ctx);
Devhost* BuildSuspendList(SuspendContext* ctx);
void DriverAdded(Driver* drv, const char* version);
void DriverAddedInit(Driver* drv, const char* version);
void DriverAddedSys(Driver* drv, const char* version);
async_dispatcher_t* dispatcher() const { return dispatcher_; }
bool require_system() const { return require_system_; }
void set_running(bool running) { running_ = running; }
void set_loader_service(DevhostLoaderService* loader_service) {
loader_service_ = loader_service;
}
fbl::DoublyLinkedList<Device*, Device::AllDevicesNode>& devices() { return devices_; }
Device& root_device() { return root_device_; }
Device& misc_device() { return misc_device_; }
Device& sys_device() { return sys_device_; }
Device& test_device() { return test_device_; }
SuspendContext& suspend_context() { return suspend_context_; }
bool suspend_fallback() const { return suspend_fallback_; }
void set_suspend_fallback(bool suspend_fallback) { suspend_fallback_ = suspend_fallback; }
bool suspend_debug() const { return suspend_debug_; }
void set_suspend_debug(bool suspend_debug) { suspend_debug_ = suspend_debug; }
bool system_available() const { return system_available_; }
void set_system_available(bool system_available) { system_available_ = system_available; }
bool system_loaded() const { return system_loaded_; }
private:
zx::job devhost_job_;
async_dispatcher_t* dispatcher_;
bool require_system_;
bool running_ = false;
DevhostLoaderService* loader_service_ = nullptr;
// All Drivers
fbl::DoublyLinkedList<Driver*, Driver::Node> drivers_;
// Drivers to try last
fbl::DoublyLinkedList<Driver*, Driver::Node> fallback_drivers_;
// List of drivers loaded from /system by system_driver_loader()
fbl::DoublyLinkedList<Driver*, Driver::Node> system_drivers_;
// All Devices (excluding static immortal devices)
fbl::DoublyLinkedList<Device*, Device::AllDevicesNode> devices_;
// All DevHosts
fbl::DoublyLinkedList<Devhost*, Devhost::AllDevhostsNode> devhosts_;
Device root_device_{this};
Device misc_device_{this};
Device sys_device_{this};
Device test_device_{this};
SuspendContext suspend_context_;
fbl::DoublyLinkedList<fbl::unique_ptr<Metadata>, Metadata::Node> published_metadata_;
bool suspend_fallback_ = false;
bool suspend_debug_ = false;
bool system_available_ = false;
bool system_loaded_ = false;
};
void coordinator_setup(Coordinator* coordinator, DevmgrArgs args);
void devmgr_set_bootdata(zx::unowned_vmo vmo);
using DriverLoadCallback = fit::function<void(Driver* driver, const char* version)>;
void load_driver(const char* path, DriverLoadCallback func);
void find_loadable_drivers(const char* path, DriverLoadCallback func);
bool dc_is_bindable(const Driver* drv, uint32_t protocol_id,
zx_device_prop_t* props, size_t prop_count,
bool autobind);
extern bool dc_asan_drivers;
extern bool dc_launched_first_devhost;
// Methods for composing FIDL RPCs to the devhosts
zx_status_t dh_send_remove_device(const Device* dev);
zx_status_t dh_send_create_device(Device* dev, Devhost* dh, zx::channel rpc, zx::vmo driver,
const char* args, zx::handle rpc_proxy);
zx_status_t dh_send_create_device_stub(Devhost* dh, zx::channel rpc, uint32_t protocol_id);
zx_status_t dh_send_bind_driver(Device* dev, const char* libname, zx::vmo driver);
zx_status_t dh_send_connect_proxy(const Device* dev, zx::channel proxy);
zx_status_t dh_send_suspend(const Device* dev, uint32_t flags);
} // namespace devmgr