| // 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_(inspector_.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)); |
| } |
| } |