blob: d704fd1198c922368e79185593c03095f83c2f56 [file] [log] [blame]
// Copyright 2020 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 "inspect.h"
#include <ddk/driver.h>
#include <driver-info/driver-info.h>
#include <fs/vfs_types.h>
#include <fs/vmo_file.h>
#include <fs/vnode.h>
#include "device.h"
#include "init_task.h"
#include "resume_task.h"
#include "suspend_task.h"
#include "unbind_task.h"
zx::status<> InspectDevfs::InitInspectFile(const fbl::RefPtr<Device>& dev) {
if (dev->inspect_file() != nullptr) {
return zx::error(ZX_ERR_INTERNAL);
}
if (!dev->inspect().vmo()) {
// Device doesn't have an inspect VMO to publish.
return zx::ok();
}
dev->inspect_file() = fbl::MakeRefCounted<fs::VmoFile>(dev->inspect().vmo(), 0, ZX_PAGE_SIZE);
return zx::ok();
}
zx::status<> InspectDevfs::Publish(const fbl::RefPtr<Device>& dev) {
if (!dev->inspect().vmo()) {
// Device doesn't have an inspect VMO to publish.
return zx::ok();
}
if (dev->inspect_file() == nullptr) {
return zx::error(ZX_ERR_INTERNAL);
}
return AddClassDirEntry(dev);
}
zx::status<> InspectDevfs::InitInspectFileAndPublish(const fbl::RefPtr<Device>& dev) {
zx::status<> status = InitInspectFile(dev);
if (status.is_error()) {
return status.take_error();
}
return Publish(dev);
}
// TODO(surajmalhotra): Ideally this would take a RefPtr, but currently this is
// invoked in the dtor for Device.
void InspectDevfs::Unpublish(Device* dev) {
// Remove reference in class directory if it exists
auto [dir, seqcount] = GetProtoDir(dev->protocol_id());
if (dir != nullptr) {
dir->RemoveEntry(dev->link_name(), dev->inspect_file().get());
// Keep only those protocol directories which are not empty to avoid clutter
RemoveEmptyProtoDir(dev->protocol_id());
}
dev->inspect_file() = nullptr;
}
InspectDevfs::InspectDevfs(const fbl::RefPtr<fs::PseudoDir>& root_dir,
fbl::RefPtr<fs::PseudoDir> class_dir)
: root_dir_(root_dir), class_dir_(class_dir) {
std::copy(std::begin(kProtoInfos), std::end(kProtoInfos), proto_infos_.begin());
}
zx::status<InspectDevfs> InspectDevfs::Create(const fbl::RefPtr<fs::PseudoDir>& root_dir) {
auto class_dir = fbl::MakeRefCounted<fs::PseudoDir>();
zx::status<> status = zx::make_status(root_dir->AddEntry("class", class_dir));
if (status.is_error()) {
return status.take_error();
}
InspectDevfs devfs(root_dir, class_dir);
return zx::ok(std::move(devfs));
}
std::tuple<fbl::RefPtr<fs::PseudoDir>, uint32_t*> InspectDevfs::GetProtoDir(uint32_t id) {
for (auto& info : proto_infos_) {
if (info.id == id) {
return {info.devnode, &info.seqcount};
}
}
return {nullptr, nullptr};
}
std::tuple<fbl::RefPtr<fs::PseudoDir>, uint32_t*> InspectDevfs::GetOrCreateProtoDir(uint32_t id) {
for (auto& info : proto_infos_) {
if (info.id == id) {
// Create protocol directory if one doesn't exist
if (!info.devnode) {
auto node = fbl::MakeRefCounted<fs::PseudoDir>();
if (class_dir_->AddEntry(info.name, node) != ZX_OK) {
return {nullptr, nullptr};
}
info.devnode = std::move(node);
}
return {info.devnode, &info.seqcount};
}
}
return {nullptr, nullptr};
}
void InspectDevfs::RemoveEmptyProtoDir(uint32_t id) {
for (auto& info : proto_infos_) {
if (info.id == id && info.devnode && info.devnode->IsEmpty()) {
class_dir_->RemoveEntry(info.name, info.devnode.get());
info.devnode = nullptr;
}
}
}
zx::status<> InspectDevfs::AddClassDirEntry(const fbl::RefPtr<Device>& dev) {
// Create link in /dev/class/... if this id has a published class
auto [dir, seqcount] = GetOrCreateProtoDir(dev->protocol_id());
if (dir == nullptr) {
// No class dir for this type, so ignore it
return zx::ok();
}
char tmp[32];
const char* name = nullptr;
if (dev->protocol_id() != ZX_PROTOCOL_CONSOLE) {
for (unsigned n = 0; n < 1000; n++) {
snprintf(tmp, sizeof(tmp), "%03u.inspect", ((*seqcount)++) % 1000);
fbl::RefPtr<fs::Vnode> node;
if (dir->Lookup(tmp, &node) == ZX_ERR_NOT_FOUND) {
name = tmp;
break;
}
}
if (name == nullptr) {
return zx::error(ZX_ERR_ALREADY_EXISTS);
}
} else {
snprintf(tmp, sizeof(tmp), "%.*s.inspect", (int)dev->name().length(), dev->name().data());
name = tmp;
}
zx::status<> status = zx::make_status(dir->AddEntry(name, dev->inspect_file()));
if (status.is_error()) {
return status;
}
dev->set_link_name(name);
return zx::ok();
}
InspectManager::InspectManager(async_dispatcher_t* dispatcher) {
inspect_vmo_ = inspect_.DuplicateVmo();
diagnostics_dir_ = fbl::MakeRefCounted<fs::PseudoDir>();
auto driver_manager_dir = fbl::MakeRefCounted<fs::PseudoDir>();
diagnostics_dir_->AddEntry("driver_manager", driver_manager_dir);
driver_host_dir_ = fbl::MakeRefCounted<fs::PseudoDir>();
driver_manager_dir->AddEntry("driver_host", driver_host_dir_);
uint64_t vmo_size;
ZX_ASSERT(inspect_vmo_.get_size(&vmo_size) == ZX_OK);
auto vmo_file = fbl::MakeRefCounted<fs::VmoFile>(inspect_vmo_, 0, vmo_size);
driver_manager_dir->AddEntry("dm.inspect", vmo_file);
auto status = InspectDevfs::Create(diagnostics_dir_);
ZX_ASSERT(status.is_ok());
devfs_ = std::move(status.value());
if (dispatcher) {
zx::channel local;
zx::channel::create(0, &diagnostics_client_, &local);
diagnostics_vfs_ = std::make_unique<fs::SynchronousVfs>(dispatcher);
diagnostics_vfs_->ServeDirectory(diagnostics_dir_, std::move(local));
}
devices_ = root_node().CreateChild("devices");
device_count_ = root_node().CreateUint("device_count", 0);
}
DeviceInspect::DeviceInspect(inspect::Node& devices, inspect::UintProperty& device_count,
std::string name, zx::vmo inspect_vmo)
: device_count_node_(device_count), vmo_(std::move(inspect_vmo)) {
device_node_ = devices.CreateChild(name);
// Increment device count.
device_count_node_.Add(1);
// create properties with default values
state_ = device_node_.CreateString("state", "");
local_id_ = device_node_.CreateUint("driver_host_local_id", 0);
}
DeviceInspect::~DeviceInspect() {
// Decrement device count.
device_count_node_.Subtract(1);
}
void DeviceInspect::set_properties(const fbl::Array<const zx_device_prop_t>& props) {
inspect::Node properties_array;
// Add a node only if there are any `props`
if (props.size()) {
properties_array = device_node_.CreateChild("properties");
}
for (uint32_t i = 0; i < props.size(); ++i) {
const zx_device_prop_t* p = &props[i];
const char* param_name = di_bind_param_name(p->id);
auto property = properties_array.CreateChild(std::to_string(i));
property.CreateUint("value", p->value, &static_values_);
if (param_name) {
property.CreateString("id", param_name, &static_values_);
} else {
property.CreateString("id", std::to_string(p->id), &static_values_);
}
static_values_.emplace(std::move(property));
}
// Place the node into value list as props will not change in the lifetime of the device.
if (props.size()) {
static_values_.emplace(std::move(properties_array));
}
}