在驱动程序中使用检查

本指南介绍了如何使用 Inspect 检测 Fuchsia 驱动程序框架 (DFv2) 驱动程序,以及如何从驱动程序组件查询 Inspect 数据。

概览

在 Fuchsia 的驱动程序框架 (DFv2) 中,驱动程序是组件。驱动程序直接参与 Fuchsia 的组件诊断架构,并使用标准 fuchsia.inspect.InspectSink 功能发布检查数据,就像任何其他 Fuchsia 组件一样。

DFv2 中检查功能的主要特征:

  • 标准功能路由:驱动程序组件清单使用标准 inspect/client.shard.cml 分片请求 fuchsia.inspect.InspectSink。
  • 归因于组件的检查:检查层次结构归因于每个驱动程序组件的组件别名(例如,在 bootstrap/boot-drivers 或 bootstrap/base-drivers 下)。
  • 标准工具:使用 ffx inspect 或 iquery 通过标准组件选择器或组件网址匹配来检查驱动程序检查数据。

前提条件

如果您不熟悉 Inspect 的基础知识,请查看以下指南:

在驱动程序中包含检查

1. 更新组件清单 (.cml)

在驱动程序的 .cml 文件中添加 Inspect 客户端分片:

{
    include: [
        "inspect/client.shard.cml",
        "syslog/client.shard.cml",
    ],
    program: {
        runner: "driver",
        binary: "driver/my_driver.so",
        bind: "meta/bind/my_driver.bindbc",
    },
}

inspect/client.shard.cml 分片将 fuchsia.inspect.InspectSink 协议从父诊断目录路由到驱动程序的传入命名空间。

2. 添加 build 依赖项

C++

将 //sdk/lib/inspect/component/cpp 添加到驱动程序的 BUILD.gn 中:

fuchsia_cc_driver("my_driver") {
  deps = [
    "//sdk/lib/driver/component/cpp",
    "//sdk/lib/inspect/component/cpp",
  ]
}

如果使用 Bazel 进行构建,请在 BUILD.bazel 中添加 @fuchsia_sdk//pkg/inspect_component_cpp:

fuchsia_cc_driver(
    name = "my_driver",
    deps = [
        "@fuchsia_sdk//pkg/driver_component_cpp",
        "@fuchsia_sdk//pkg/inspect_component_cpp",
    ],
)

Rust

在驱动程序的 BUILD.gn 中,取决于 //sdk/lib/driver/component/rust、//src/lib/diagnostics/inspect/rust 和 //src/lib/fuchsia-async:

fuchsia_rust_driver("my_driver") {
  deps = [
    "//sdk/lib/driver/component/rust",
    "//src/lib/diagnostics/inspect/rust",
    "//src/lib/fuchsia-async",
  ]
}

3. 初始化并发布 Inspect

C++

在 C++ 中,继承自 fdf::DriverBase2 的驱动程序可以使用 context.CreateInspector(this) 发布检查树:

#include <lib/driver/component/cpp/driver_base2.h>
#include <lib/driver/component/cpp/driver_export2.h>
#include <lib/inspect/component/cpp/component.h>

class MyDriver : public fdf::DriverBase2 {
 public:
  MyDriver() : fdf::DriverBase2("my_driver") {}

  zx::result<> Start(fdf::DriverContext context) override {
    // 1. Create and publish the ComponentInspector.
    // Must be called before context.take_incoming() if transferring ownership.
    component_inspector_ = context.CreateInspector(this);

    // 2. Build the Inspect hierarchy from the root node.
    hardware_node_ = component_inspector_->root().CreateChild("hardware_status");
    revision_prop_ = hardware_node_.CreateUint("revision_id", 0x10);
    requests_count_ = hardware_node_.CreateUint("requests_count", 0);

    // 3. For values that do not change, use Record* helpers to avoid storing handles:
    hardware_node_.RecordString("serial_number", "ABC-1234");

    // 4. (Optional) Report component health:
    component_inspector_->Health().Ok();

    return zx::ok();
  }

  void HandleDeviceRequest() {
    requests_count_.Add(1);
  }

  // Accessor for unit tests:
  const inspect::Inspector& inspector() const {
    return component_inspector_->inspector();
  }

 private:
  std::optional<inspect::ComponentInspector> component_inspector_;
  inspect::Node hardware_node_;
  inspect::UintProperty revision_prop_;
  inspect::UintProperty requests_count_;
};

FUCHSIA_DRIVER_EXPORT2(MyDriver);

要点:

  • context.CreateInspector(this):自动连接到传入命名空间中的 fuchsia.inspect.InspectSink,并发布检查树,并将树名称设置为驱动程序的 name()。在通过 context.take_incoming() 获取命名空间之前,调用 context.CreateInspector(this)。
  • 节点和属性生命周期 (RAII):检查属性是 RAII 对象。如果属性会随时间变化(例如 requests_count_),请将属性对象存储为类成员或存储在 inspect::ValueList 中。如果您不保持媒体资源的有效性,系统会将其从“检查 VMO”中移除。对于不可变的值,请使用 Record* 辅助方法(例如 RecordString、RecordUint),这些方法可将值直接提交到 VMO,而无需对象句柄。
  • 组件健康状况:使用 component_inspector_->Health().Ok()、.Starting() 或 .Unhealthy(reason) 报告标准组件健康状况。

Rust

在实现 fdf_component::Driver 的 Rust 驱动程序中:

use fdf_component::{Driver, DriverContext, DriverError};
use fuchsia_async::Scope;
use fuchsia_inspect::{Inspector, NumericProperty, UintProperty};

pub struct MyDriver {
    // Keep the scope alive for the lifetime of the driver so that
    // the Inspect publishing task continues running.
    _inspect_scope: Scope,
    inspector: Inspector,
    requests_count: UintProperty,
}

impl Driver for MyDriver {
    const NAME: &str = "my_driver";

    async fn start(mut context: DriverContext) -> Result<Self, DriverError> {
        let inspector = Inspector::default();

        // Build hierarchy
        let hardware = inspector.root().create_child("hardware_status");
        hardware.record_string("serial_number", "ABC-1234");
        let requests_count = hardware.create_uint("requests_count", 0);
        inspector.root().record(hardware);

        // Publish Inspect via InspectSink.
        let inspect_scope = Scope::new_with_name("my_driver_inspect");
        context.publish_inspect(&inspector, inspect_scope.to_handle())?;

        Ok(Self {
            _inspect_scope: inspect_scope,
            inspector,
            requests_count,
        })
    }
}

impl MyDriver {
    pub fn handle_device_request(&self) {
        self.requests_count.add(1);
    }
}

要点:

  • 保留 Scope:context.publish_inspect 在提供的 ScopeHandle 上生成后台任务。将 Scope 存储在驱动程序结构中,以便在 start() 完成时不会取消任务。
  • 树名称:Rust 的 context.publish_inspect 使用默认树名称 (None) 发布。与通过 CreateInspector 发布的 C++ 驱动程序不同,除非使用自定义发布选项,否则在查询时不要传递 --name 树过滤条件。

查询驱动程序检查数据

由于驱动程序是组件,因此您可以使用标准的 Fuchsia 诊断命令查询其 Inspect 数据。

驱动程序别名

驱动程序组件 moniker 反映了设备拓扑和驱动程序运行的集合:

  • 启动驱动程序:bootstrap/boot-drivers:<device_node_path>(例如 bootstrap/boot-drivers:dev.sys.platform.00_00_2d)
  • 封装的驱动程序(基本):bootstrap/base-drivers:<device_node_path>
  • 打包的驱动程序(完整/通用):bootstrap/full-drivers:<device_node_path>

使用 ffx inspect

在宿主开发工作站上:

  • 按驱动程序组件网址 / 组件名称查询: sh ffx inspect show my_driver.cm

  • 查询所有驱动程序组件: sh ffx inspect show "bootstrap/*-drivers*:root"

  • 按 C++ 驱动程序树名称过滤: sh ffx inspect show --name my_driver "bootstrap/*-drivers*:root"

  • 按 moniker 查询特定驱动程序: 由于集合子名称以英文冒号分隔,因此必须在选择器中转义组件 moniker 中的英文冒号: sh ffx inspect show "bootstrap/boot-drivers\:suspend"

使用 iquery 达到目标

通过串行或 ffx target ssh 连接到目标设备时:

iquery show my_driver.cm
iquery show "bootstrap/*-drivers*:root"

测试驱动程序 Inspect

单元测试

在使用驱动程序单元测试库的 C++ 驱动程序单元测试中,直接从驱动程序的检查器验证 Inspect 树:

#include <lib/driver/testing/cpp/driver_test.h>
#include <lib/fpromise/single_threaded_executor.h>
#include <lib/inspect/cpp/reader.h>
#include <lib/inspect/testing/cpp/inspect.h>
#include <gtest/gtest.h>

TEST_F(MyDriverTestFixture, InspectMetrics) {
  // Run driver methods that modify inspect state...

  dut_.RunInDriverContext([&](MyDriver& driver) {
    auto hierarchy = fpromise::run_single_threaded(
        inspect::ReadFromInspector(driver.inspector()))
        .take_value();

    const auto* hw_node = hierarchy.GetByPath({"hardware_status"});
    ASSERT_NE(hw_node, nullptr);

    const auto* prop = hw_node->node().get_property<inspect::UintPropertyValue>("requests_count");
    ASSERT_NE(prop, nullptr);
    EXPECT_EQ(prop->value(), 1u);
  });
}

使用 DriverTestRealm 进行集成测试

在 DriverTestRealm 内运行的集成测试中,使用 diagnostics_reader::ArchiveReader 查询 Inspect。请注意,在选择器字符串中,分隔集合和子 moniker 的英文冒号必须进行转义 (\\:):

use diagnostics_assertions::assert_data_tree;
use diagnostics_reader::ArchiveReader;

let moniker = format!("realm_builder\\:{}/driver_test_realm/boot-drivers\\:dev.sys.my_node", instance.root.child_name());

let hierarchy = ArchiveReader::inspect()
    .add_selector(format!("{}:[name=my_driver]root", moniker))
    .snapshot()
    .await?
    .into_iter()
    .next()
    .and_then(|result| result.payload)
    .expect("driver inspect hierarchy not found");

assert_data_tree!(hierarchy, root: contains {
    hardware_status: contains {
        requests_count: 0u64,
    }
});