[i2c_sample] Add test i2c controller
Add a test controller that interacts with the i2c
temperature driver via the i2c protocol. The test
controller receives commands in the form of read/write
transactions through the protocol.
It returns a fake temperature for read transactions.
Each time the controller receives a measure command, it
increase the temperature by 5. Once it receives a reset
command, it reset back to the starting temperature
Bug: 98633
Change-Id: I518bef9977a0d9ca96bd3419a32b70f77abc082a
Reviewed-on: https://fuchsia-review.googlesource.com/c/sdk-samples/drivers/+/679311
Reviewed-by: David Gilhooley <dgilhooley@google.com>
Commit-Queue: Sarah Chan <spqchan@google.com>
Reviewed-by: Renato Mangini Dias <mangini@google.com>
diff --git a/src/BUILD.bazel b/src/BUILD.bazel
index cff01c4..cccb7b1 100644
--- a/src/BUILD.bazel
+++ b/src/BUILD.bazel
@@ -13,6 +13,8 @@
repo_name = "fuchsiasamples.com",
deps = [
"//src/example_driver:pkg",
+ "//src/i2c_temperature:pkg",
+ "//src/i2c_temperature:test_controller_pkg",
"//src/qemu_edu:eductl_pkg",
"//src/qemu_edu:pkg",
"//src/qemu_edu:test_pkg",
diff --git a/src/i2c_temperature/BUILD.bazel b/src/i2c_temperature/BUILD.bazel
index 75ae20f..77ef42b 100644
--- a/src/i2c_temperature/BUILD.bazel
+++ b/src/i2c_temperature/BUILD.bazel
@@ -86,3 +86,55 @@
":component",
],
)
+
+fuchsia_driver_bytecode_bind_rules(
+ name = "test_controller_bind_bytecode",
+ output = "test_controller.bindbc",
+ rules = "test_i2c_controller.bind",
+ deps = [
+ "@fuchsia_sdk//bind/fuchsia.acpi",
+ ],
+)
+
+cc_binary(
+ name = "test_controller",
+ srcs = [
+ "constants.h",
+ "test_i2c_controller.cc",
+ "test_i2c_controller.h",
+ ],
+ linkshared = True,
+ deps = [
+ "@fuchsia_sdk//fidl/fuchsia.device.fs:fuchsia.device.fs_llcpp_cc",
+ "@fuchsia_sdk//fidl/fuchsia.driver.compat:fuchsia.driver.compat_llcpp_cc",
+ "@fuchsia_sdk//fidl/fuchsia.hardware.i2c:fuchsia.hardware.i2c_llcpp_cc",
+ "@fuchsia_sdk//fidl/zx:zx_cc",
+ "@fuchsia_sdk//pkg/async-cpp",
+ "@fuchsia_sdk//pkg/driver2-llcpp",
+ "@fuchsia_sdk//pkg/fidl-llcpp-experimental-driver-only",
+ "@fuchsia_sdk//pkg/sys_component_llcpp",
+ "@fuchsia_sdk//pkg/zx-experimental-driver-only",
+ ],
+)
+
+fuchsia_component_manifest(
+ name = "test_controller_manifest",
+ src = "meta/test_i2c_controller.cml",
+)
+
+fuchsia_driver_component(
+ name = "test_controller_component",
+ bind_bytecode = ":test_controller_bind_bytecode",
+ driver_lib = ":test_controller",
+ manifest = ":test_controller_manifest",
+)
+
+fuchsia_package(
+ name = "test_controller_pkg",
+ package_name = "test_i2c_controller",
+ visibility = ["//visibility:public"],
+ deps = [
+ ":test_controller_component",
+ ],
+)
+
diff --git a/src/i2c_temperature/meta/test_i2c_controller.cml b/src/i2c_temperature/meta/test_i2c_controller.cml
new file mode 100644
index 0000000..3ee96c4
--- /dev/null
+++ b/src/i2c_temperature/meta/test_i2c_controller.cml
@@ -0,0 +1,28 @@
+{
+ program: {
+ runner: 'driver',
+ binary: 'lib/libtest_controller.so',
+ bind: 'meta/bind/test_controller.bindbc'
+ },
+ use: [
+ {
+ protocol: [
+ 'fuchsia.logger.LogSink',
+ ],
+ },
+ {
+ directory: 'fuchsia.driver.compat.Service-default',
+ rights: ['rw*'],
+ path: '/fuchsia.driver.compat.Service/default',
+ }
+ ],
+ capabilities: [
+ { protocol: 'fuchsia.hardware.i2c.Device' },
+ ],
+ expose: [
+ {
+ protocol: 'fuchsia.hardware.i2c.Device',
+ from: 'self',
+ },
+ ],
+}
diff --git a/src/i2c_temperature/test_i2c_controller.bind b/src/i2c_temperature/test_i2c_controller.bind
new file mode 100644
index 0000000..4179540
--- /dev/null
+++ b/src/i2c_temperature/test_i2c_controller.bind
@@ -0,0 +1,13 @@
+// Copyright 2022 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.
+
+using fuchsia.acpi;
+
+// Bind to an unused ACPI node in Qemu or Atlas.
+// TODO(fxb/100716): Bind to a test node once it's available.
+if fuchsia.acpi.hid == "QEMU0002" {
+ true;
+} else {
+ fuchsia.acpi.hid == "GOOG000A";
+}
diff --git a/src/i2c_temperature/test_i2c_controller.cc b/src/i2c_temperature/test_i2c_controller.cc
new file mode 100644
index 0000000..3396fc9
--- /dev/null
+++ b/src/i2c_temperature/test_i2c_controller.cc
@@ -0,0 +1,185 @@
+// Copyright 2022 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 "src/i2c_temperature/test_i2c_controller.h"
+
+#include <endian.h>
+#include <fidl/fuchsia.component.decl/cpp/wire.h>
+#include <lib/driver2/record_cpp.h>
+
+#include "src/i2c_temperature/constants.h"
+
+namespace fdf {
+using namespace fuchsia_driver_framework;
+} // namespace fdf
+
+namespace test_i2c_controller {
+
+namespace fcd = fuchsia_component_decl;
+
+namespace {
+
+constexpr uint32_t kStartingTemp = 20;
+constexpr uint32_t kTempIncrement = 5;
+
+} // namespace
+
+zx::status<std::unique_ptr<TestI2cController>> TestI2cController::Start(
+ fdf::wire::DriverStartArgs& start_args, fdf::UnownedDispatcher dispatcher,
+ fidl::WireSharedClient<fdf::Node> node, driver::Namespace ns, driver::Logger logger) {
+ auto driver = std::make_unique<TestI2cController>(dispatcher->async_dispatcher(), std::move(node),
+ std::move(ns), std::move(logger));
+ auto result = driver->Run(std::move(start_args.outgoing_dir()));
+ if (result.is_error()) {
+ return result.take_error();
+ }
+
+ return zx::ok(std::move(driver));
+}
+
+zx::status<> TestI2cController::Run(fidl::ServerEnd<fuchsia_io::Directory> outgoing_dir) {
+ temperature_ = kStartingTemp;
+
+ auto service = [this](fidl::ServerEnd<fuchsia_hardware_i2c::Device> server_end) {
+ fidl::BindServer(dispatcher_, std::move(server_end), this);
+ };
+
+ auto status = outgoing_.AddProtocol<fuchsia_hardware_i2c::Device>(std::move(service));
+ if (status.status_value() != ZX_OK) {
+ FDF_SLOG(ERROR, "Failed to add protocol", KV("status", status.status_string()));
+ return status;
+ }
+
+ status = AddChild();
+ if (status.status_value() != ZX_OK) {
+ return status;
+ }
+
+ return outgoing_.Serve(std::move(outgoing_dir));
+}
+
+zx::status<> TestI2cController::AddChild() {
+ fidl::Arena arena;
+
+ // Offer `fuchsia.hardware.i2c.Device` to the driver that binds to the node.
+ auto protocol_offer =
+ fcd::wire::OfferProtocol::Builder(arena)
+ .source_name(arena, fidl::DiscoverableProtocolName<fuchsia_hardware_i2c::Device>)
+ .target_name(arena, fidl::DiscoverableProtocolName<fuchsia_hardware_i2c::Device>)
+ .dependency_type(fcd::wire::DependencyType::kStrong)
+ .Build();
+ fcd::wire::Offer offer = fcd::wire::Offer::WithProtocol(arena, protocol_offer);
+
+ // Set the properties of the node that a driver will bind to.
+ // TODO(fxb/98831): Replace this once bind library protocol definitions are available.
+ auto properties = fidl::VectorView<fdf::wire::NodeProperty>(arena, 2);
+ properties[0] = fdf::wire::NodeProperty::Builder(arena)
+ .key(fdf::wire::NodePropertyKey::WithIntValue(1 /* BIND_PROTOCOL */))
+ .value(fdf::wire::NodePropertyValue::WithIntValue(24))
+ .Build();
+ properties[1] = fdf::wire::NodeProperty::Builder(arena)
+ .key(fdf::wire::NodePropertyKey::WithIntValue(0x0A02 /* BIND_I2C_ADDRESS */))
+ .value(fdf::wire::NodePropertyValue::WithIntValue(0xff))
+ .Build();
+
+ auto args = fdf::wire::NodeAddArgs::Builder(arena)
+ .name(arena, "i2c-child")
+ .offers(fidl::VectorView<fcd::wire::Offer>::FromExternal(&offer, 1))
+ .properties(properties)
+ .Build();
+
+ // Create endpoints of the `NodeController` for the node.
+ auto endpoints = fidl::CreateEndpoints<fdf::NodeController>();
+ if (endpoints.is_error()) {
+ FDF_SLOG(ERROR, "Failed to create endpoint", KV("status", endpoints.status_string()));
+ return zx::error(endpoints.status_value());
+ }
+
+ auto result = node_.sync()->AddChild(args, std::move(endpoints->server), {});
+ if (!result.ok()) {
+ FDF_SLOG(ERROR, "Failed to add child", KV("status", result.status_string()));
+ return zx::error(result.status());
+ }
+
+ return zx::ok();
+}
+
+void TestI2cController::HandleWrite(const fidl::VectorView<uint8_t> write_data) {
+ // For simplicity, assume that we'll only receive integer commands from
+ // I2cTemperatureDriver.
+ if (write_data.count() > 4) {
+ return;
+ }
+
+ uint8_t bytes[4];
+ for (uint32_t i = 0; i < write_data.count(); i++) {
+ bytes[i] = write_data.at(i);
+ }
+
+ int value;
+ std::memcpy(&value, bytes, sizeof(int));
+ value = be16toh(value);
+
+ if (value == i2c_temperature::kSoftResetCommand) {
+ FDF_SLOG(INFO, "Reset command received");
+ temperature_ = kStartingTemp;
+ } else if (value == i2c_temperature::kStartMeasurementCommand) {
+ FDF_SLOG(INFO, "Measurement command received");
+ temperature_ += kTempIncrement;
+ } else {
+ FDF_SLOG(WARNING, "Received unknown command ", KV("command", value));
+ }
+}
+
+void TestI2cController::Transfer(TransferRequestView request, TransferCompleter::Sync& completer) {
+ FDF_SLOG(INFO, "Received transfer request");
+
+ if (request->transactions.count() < 1) {
+ completer.ReplyError(ZX_ERR_INVALID_ARGS);
+ return;
+ }
+
+ // Create a vector of read transfers
+ fidl::Arena allocator;
+ std::vector<fidl::VectorView<uint8_t>> read_data;
+
+ for (size_t i = 0; i < request->transactions.count(); ++i) {
+ if (!request->transactions[i].has_data_transfer()) {
+ completer.ReplyError(ZX_ERR_INVALID_ARGS);
+ return;
+ }
+
+ const auto& transfer = request->transactions[i].data_transfer();
+
+ // Handle write transaction.
+ if (transfer.is_write_data()) {
+ if (transfer.write_data().count() <= 0) {
+ completer.ReplyError(ZX_ERR_INVALID_ARGS);
+ return;
+ }
+
+ HandleWrite(transfer.write_data());
+ continue;
+ }
+
+ // Handle read transaction.
+ auto read_value = htobe16(temperature_);
+ auto read_bytes = fidl::VectorView<uint8_t>(allocator, 4);
+ for (size_t i = 0; i < 4; i++) {
+ read_bytes[i] = read_value >> (i * 8) & 0xff;
+ }
+ read_data.push_back(read_bytes);
+ }
+
+ auto read_results = fidl::VectorView<fidl::VectorView<uint8_t>>(allocator, read_data.size());
+ for (size_t i = 0; i < read_data.size(); i++) {
+ read_results[i] = read_data[i];
+ }
+
+ completer.ReplySuccess(read_results);
+}
+
+} // namespace test_i2c_controller
+
+FUCHSIA_DRIVER_RECORD_CPP_V1(test_i2c_controller::TestI2cController);
diff --git a/src/i2c_temperature/test_i2c_controller.h b/src/i2c_temperature/test_i2c_controller.h
new file mode 100644
index 0000000..403fe81
--- /dev/null
+++ b/src/i2c_temperature/test_i2c_controller.h
@@ -0,0 +1,66 @@
+// Copyright 2022 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.
+
+#ifndef FUCHSIA_SDK_EXAMPLES_SRC_I2C_TEMPERATURE_TESTING_TEST_I2C_CONTROLLER_H_
+#define FUCHSIA_SDK_EXAMPLES_SRC_I2C_TEMPERATURE_TESTING_TEST_I2C_CONTROLLER_H_
+
+#include <fidl/fuchsia.driver.framework/cpp/wire.h>
+#include <fidl/fuchsia.hardware.i2c/cpp/wire.h>
+#include <lib/async/cpp/executor.h>
+#include <lib/driver2/namespace.h>
+#include <lib/driver2/structured_logger.h>
+#include <lib/fdf/cpp/dispatcher.h>
+#include <lib/sys/component/llcpp/outgoing_directory.h>
+
+namespace test_i2c_controller {
+
+// Test driver that interacts with the i2c temperature driver. It implements the I2c protocol
+// and emulates a behavior where it receives commands for a temperature and returns a fake
+// temperature. The temperature it returns increase by 5 each time it receives a measurement
+// command. When it receives a reset command, the temperature is reset back to the starting
+// value.
+class TestI2cController : public fidl::WireServer<fuchsia_hardware_i2c::Device> {
+ public:
+ TestI2cController(async_dispatcher_t* dispatcher,
+ fidl::WireSharedClient<fuchsia_driver_framework::Node> node,
+ driver::Namespace ns, driver::Logger logger)
+ : dispatcher_(dispatcher),
+ outgoing_(component::OutgoingDirectory::Create(dispatcher)),
+ ns_(std::move(ns)),
+ logger_(std::move(logger)),
+ node_(std::move(node)) {}
+
+ virtual ~TestI2cController() = default;
+
+ static constexpr const char* Name() { return "test-i2c-controller"; }
+
+ static zx::status<std::unique_ptr<TestI2cController>> Start(
+ fuchsia_driver_framework::wire::DriverStartArgs& start_args,
+ fdf::UnownedDispatcher dispatcher,
+ fidl::WireSharedClient<fuchsia_driver_framework::Node> node, driver::Namespace ns,
+ driver::Logger logger);
+
+ void Transfer(TransferRequestView request, TransferCompleter::Sync& completer);
+
+ private:
+ zx::status<> Run(fidl::ServerEnd<fuchsia_io::Directory> outgoing_dir);
+ zx::status<> AddChild();
+
+ void HandleWrite(const fidl::VectorView<uint8_t> write_data);
+
+ uint32_t temperature_;
+
+ async_dispatcher_t* const dispatcher_;
+
+ component::OutgoingDirectory outgoing_;
+ driver::Namespace ns_;
+ driver::Logger logger_;
+
+ fidl::WireSharedClient<fuchsia_driver_framework::Node> node_;
+ fidl::WireSharedClient<fuchsia_driver_framework::NodeController> controller_;
+};
+
+} // namespace test_i2c_controller
+
+#endif // FUCHSIA_SDK_EXAMPLES_SRC_I2C_TEMPERATURE_TESTING_TEST_I2C_CONTROLLER_H_