blob: 2b51ddb16018406b389f69701cf00f8605d2b8d0 [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/wait.h>
#include <lib/fit/function.h>
#include <lib/zx/channel.h>
#include <lib/zx/event.h>
#include <lib/zx/job.h>
#include <lib/zx/process.h>
#include <lib/zx/socket.h>
#include <lib/zx/vmo.h>
#include <utility>
#include "device.h"
#include "metadata.h"
namespace devmgr {
class DevhostLoaderService;
class Devhost {
public:
enum Flags : uint32_t {
kDying = 1 << 0,
kSuspend = 1 << 1,
};
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_;
}
};
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_HANDLE_INVALID;
zx::process proc_;
zx_koid_t koid_ = 0;
mutable int32_t refcount_ = 0;
uint32_t flags_ = 0;
Devhost* parent_ = nullptr;
// 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 class Flags : uint32_t {
kRunning = 0u,
kSuspend = 1u,
};
SuspendContext() = default;
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_;
};
// clang-format off
// 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
// clang-format on
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;
};
struct CoordinatorConfig {
// Initial root resource from the kernel.
zx::resource root_resource;
// Job for sysinfo.
zx::job sysinfo_job;
// Job for all devhosts.
zx::job devhost_job;
// Event that controls the fshost.
zx::event fshost_event;
// Async dispatcher for the coordinator.
async_dispatcher_t* dispatcher;
// Whether we require /system.
bool require_system;
// Whether we require ASan drivers.
bool asan_drivers;
};
class Coordinator {
public:
Coordinator(const Coordinator&) = delete;
Coordinator& operator=(const Coordinator&) = delete;
Coordinator(Coordinator&&) = delete;
Coordinator& operator=(Coordinator&&) = delete;
explicit Coordinator(CoordinatorConfig config);
~Coordinator();
zx_status_t InitializeCoreDevices();
zx_status_t SetBootdata(const zx::unowned_vmo& vmo);
bool InSuspend() const;
void ScanSystemDrivers();
void BindDrivers();
void UseFallbackDrivers();
void DriverAdded(Driver* drv, const char* version);
void DriverAddedInit(Driver* drv, const char* version);
zx_status_t LibnameToVmo(const fbl::String& libname, zx::vmo* out_vmo) const;
// Used to implement fuchsia::device::manager::Coordinator.
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 RemoveDevice(Device* dev, bool forced);
zx_status_t MakeVisible(Device* dev);
zx_status_t BindDevice(Device* dev, fbl::StringPiece drvlibname);
zx_status_t GetTopologicalPath(const Device* dev, char* out, size_t max) const;
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 GetMetadataSize(Device* dev, uint32_t type, size_t* size);
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 DmCommand(size_t len, const char* cmd);
zx_status_t DmOpenVirtcon(zx::channel virtcon_receiver) const;
void DmMexec(zx::vmo kernel, zx::vmo bootdata);
zx_status_t HandleDeviceRead(Device* dev);
void HandleNewDevice(Device* dev);
zx_status_t PrepareProxy(Device* dev);
void DumpState() const;
const zx::resource& root_resource() const { return config_.root_resource; }
const zx::event& fshost_event() const { return config_.fshost_event; }
async_dispatcher_t* dispatcher() const { return config_.dispatcher; }
bool require_system() const { return config_.require_system; }
void set_running(bool running) { running_ = running; }
void set_loader_service(DevhostLoaderService* loader_service) {
loader_service_ = loader_service;
}
void set_virtcon_channel(zx::channel virtcon_channel) {
virtcon_channel_ = std::move(virtcon_channel);
}
void set_dmctl_socket(zx::socket dmctl_socket) { dmctl_socket_ = std::move(dmctl_socket); }
fbl::DoublyLinkedList<Driver*, Driver::Node>& drivers() { return drivers_; }
const fbl::DoublyLinkedList<Driver*, Driver::Node>& drivers() const { return drivers_; }
fbl::DoublyLinkedList<Device*, Device::AllDevicesNode>& devices() { return devices_; }
const fbl::DoublyLinkedList<Device*, Device::AllDevicesNode>& devices() const {
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_; }
const SuspendContext& suspend_context() const { 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:
CoordinatorConfig config_;
bool running_ = false;
bool launched_first_devhost_ = false;
DevhostLoaderService* loader_service_ = nullptr;
// Channel for creating new virtual consoles.
zx::channel virtcon_channel_;
// This socket is used by DmPrintf for output, and DmPrintf can be called in
// the context of a const member function, therefore it is also const. Given
// that, we must make dmctl_socket_ mutable.
mutable zx::socket dmctl_socket_;
zx::vmo bootdata_vmo_;
// 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 DmPrintf(const char* fmt, ...) const;
void DumpDevice(const Device* dev, size_t indent) const;
void DumpDeviceProps(const Device* dev) const;
void DumpGlobalDeviceProps() const;
void DumpDrivers() const;
void BuildSuspendList();
void Suspend(SuspendContext ctx);
void Suspend(uint32_t flags);
fbl::unique_ptr<Driver> ValidateDriver(fbl::unique_ptr<Driver> drv);
const Driver* LibnameToDriver(const fbl::String& libname) const;
zx_status_t NewDevhost(const char* name, Devhost* parent, Devhost** out);
void ReleaseDevhost(Devhost* dh);
void ReleaseDevice(Device* dev);
zx_status_t BindDriver(Driver* drv);
zx_status_t AttemptBind(const Driver* drv, Device* dev);
void BindSystemDrivers();
void DriverAddedSys(Driver* drv, const char* version);
};
void coordinator_setup(Coordinator* coordinator, DevmgrArgs args);
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);
// 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