[i2c_sample] Add I2c temperature sample driver
The sample driver emulates a temperature sensor
that reads the temperature through an I2C protocol.
The driver can be interacted through a
sample.i2ctemperature FIDL protocol.
Bug: 98633
Change-Id: I6d430ee8e11a343e00953a3d553e3dc56f66dd52
Reviewed-on: https://fuchsia-review.googlesource.com/c/sdk-samples/drivers/+/679384
Reviewed-by: David Gilhooley <dgilhooley@google.com>
Commit-Queue: Sarah Chan <spqchan@google.com>
Reviewed-by: Suraj Malhotra <surajmalhotra@google.com>
diff --git a/src/i2c_temperature/BUILD.bazel b/src/i2c_temperature/BUILD.bazel
new file mode 100644
index 0000000..75ae20f
--- /dev/null
+++ b/src/i2c_temperature/BUILD.bazel
@@ -0,0 +1,88 @@
+# 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.
+
+load(
+ "@rules_fuchsia//fuchsia:defs.bzl",
+ "fuchsia_cc_binary",
+ "fuchsia_component_manifest",
+ "fuchsia_driver_bytecode_bind_rules",
+ "fuchsia_driver_component",
+ "fuchsia_fidl_library",
+ "fuchsia_fidl_llcpp_library",
+ "fuchsia_package",
+)
+
+fuchsia_fidl_library(
+ name = "sample.i2ctemperature",
+ srcs = [
+ "i2c_temperature.fidl",
+ ],
+ library = "sample.i2ctemperature",
+ visibility = ["//visibility:public"],
+ deps = [
+ "@fuchsia_sdk//fidl/zx:zx",
+ ],
+)
+
+fuchsia_fidl_llcpp_library(
+ name = "sample.i2ctemperature_cc",
+ library = ":sample.i2ctemperature",
+ visibility = ["//visibility:public"],
+ deps = [
+ "@fuchsia_sdk//fidl/zx:zx_llcpp_cc",
+ "@fuchsia_sdk//pkg/fidl-llcpp-experimental-driver-only",
+ ],
+)
+
+fuchsia_driver_bytecode_bind_rules(
+ name = "bind_bytecode",
+ output = "i2c_temperature.bindbc",
+ rules = "i2c_temperature.bind",
+ deps = [
+ "@fuchsia_sdk//bind/fuchsia.i2c",
+ ],
+)
+
+cc_binary(
+ name = "i2c_temperature",
+ srcs = [
+ "constants.h",
+ "i2c_channel.cc",
+ "i2c_channel.h",
+ "i2c_temperature.cc",
+ "i2c_temperature.h",
+ ],
+ linkshared = True,
+ deps = [
+ ":sample.i2ctemperature_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 = "manifest",
+ src = "meta/i2c_temperature.cml",
+)
+
+fuchsia_driver_component(
+ name = "component",
+ bind_bytecode = ":bind_bytecode",
+ driver_lib = ":i2c_temperature",
+ manifest = ":manifest",
+)
+
+fuchsia_package(
+ name = "pkg",
+ package_name = "i2c_temperature",
+ visibility = ["//visibility:public"],
+ deps = [
+ ":component",
+ ],
+)
diff --git a/src/i2c_temperature/constants.h b/src/i2c_temperature/constants.h
new file mode 100644
index 0000000..501945b
--- /dev/null
+++ b/src/i2c_temperature/constants.h
@@ -0,0 +1,11 @@
+// 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.
+
+namespace i2c_temperature {
+
+// Command values sent to the I2C Device.
+constexpr uint16_t kSoftResetCommand = 0x805d;
+constexpr uint16_t kStartMeasurementCommand = 0x7866;
+
+} // namespace i2c_temperature
diff --git a/src/i2c_temperature/i2c_channel.cc b/src/i2c_temperature/i2c_channel.cc
new file mode 100644
index 0000000..8d25781
--- /dev/null
+++ b/src/i2c_temperature/i2c_channel.cc
@@ -0,0 +1,80 @@
+// 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/i2c_channel.h"
+
+#include <endian.h>
+
+zx::status<uint16_t> I2cChannel::Read16() {
+ uint16_t value;
+ auto status = WriteReadSync(nullptr, 0, reinterpret_cast<uint8_t*>(&value), sizeof(value));
+
+ if (status.is_error()) {
+ return status.take_error();
+ }
+
+ return zx::ok(be16toh(value));
+}
+
+zx::status<> I2cChannel::Write16(uint16_t value) {
+ value = htobe16(value);
+ return WriteReadSync(reinterpret_cast<uint8_t*>(&value), sizeof(value), nullptr, 0);
+}
+
+zx::status<> I2cChannel::WriteReadSync(const uint8_t* tx_buf, size_t tx_len, uint8_t* rx_buf,
+ size_t rx_len) {
+ if (tx_len > fuchsia_hardware_i2c::wire::kMaxTransferSize ||
+ rx_len > fuchsia_hardware_i2c::wire::kMaxTransferSize) {
+ return zx::error(ZX_ERR_OUT_OF_RANGE);
+ }
+
+ fidl::Arena arena;
+ fidl::VectorView<uint8_t> write_data(arena, tx_len);
+ if (tx_len) {
+ memcpy(write_data.mutable_data(), tx_buf, tx_len);
+ }
+
+ auto write_transfer = fuchsia_hardware_i2c::wire::DataTransfer::WithWriteData(arena, write_data);
+ auto read_transfer =
+ fuchsia_hardware_i2c::wire::DataTransfer::WithReadSize(static_cast<uint32_t>(rx_len));
+
+ fuchsia_hardware_i2c::wire::Transaction transactions[2];
+ size_t index = 0;
+ if (tx_len > 0) {
+ transactions[index++] = fuchsia_hardware_i2c::wire::Transaction::Builder(arena)
+ .data_transfer(write_transfer)
+ .Build();
+ }
+
+ if (rx_len > 0) {
+ transactions[index++] = fuchsia_hardware_i2c::wire::Transaction::Builder(arena)
+ .data_transfer(read_transfer)
+ .Build();
+ }
+
+ if (index == 0) {
+ return zx::error(ZX_ERR_INVALID_ARGS);
+ }
+
+ const auto reply = fidl_client_.sync()->Transfer(
+ fidl::VectorView<fuchsia_hardware_i2c::wire::Transaction>::FromExternal(transactions, index));
+ if (!reply.ok()) {
+ return zx::error(reply.status());
+ }
+ if (reply->result.is_err()) {
+ return zx::error(reply->result.err());
+ }
+
+ if (rx_len > 0) {
+ const auto& read_data = reply->result.response().read_data;
+ // Truncate the returned buffer to match the behavior of the Banjo version.
+ if (read_data.count() != 1) {
+ return zx::error(ZX_ERR_IO);
+ }
+
+ memcpy(rx_buf, read_data[0].data(), std::min(rx_len, read_data[0].count()));
+ }
+
+ return zx::ok();
+}
diff --git a/src/i2c_temperature/i2c_channel.h b/src/i2c_temperature/i2c_channel.h
new file mode 100644
index 0000000..b737369
--- /dev/null
+++ b/src/i2c_temperature/i2c_channel.h
@@ -0,0 +1,28 @@
+// 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 <fidl/fuchsia.hardware.i2c/cpp/wire.h>
+
+// Sends read and write transactions through a fuchsia.hardware.i2c client.
+class I2cChannel {
+ public:
+ explicit I2cChannel(fidl::WireClient<fuchsia_hardware_i2c::Device> client_end)
+ : fidl_client_(std::move(client_end)) {}
+
+ I2cChannel(I2cChannel&& other) noexcept = default;
+ I2cChannel& operator=(I2cChannel&& other) noexcept = default;
+
+ ~I2cChannel() = default;
+
+ // Functions that perform read/write transactions through the client.
+ // The values are converted from big-endian order to host byte order for write
+ // and vice versa for read.
+ zx::status<uint16_t> Read16();
+ zx::status<> Write16(uint16_t value);
+
+ private:
+ zx::status<> WriteReadSync(const uint8_t* tx_buf, size_t tx_len, uint8_t* rx_buf, size_t rx_len);
+
+ fidl::WireClient<fuchsia_hardware_i2c::Device> fidl_client_;
+};
diff --git a/src/i2c_temperature/i2c_temperature.bind b/src/i2c_temperature/i2c_temperature.bind
new file mode 100644
index 0000000..bbf5f17
--- /dev/null
+++ b/src/i2c_temperature/i2c_temperature.bind
@@ -0,0 +1,8 @@
+// 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.i2c;
+
+fuchsia.BIND_PROTOCOL == fuchsia.i2c.BIND_PROTOCOL.DEVICE;
+fuchsia.BIND_I2C_ADDRESS == 0xFF;
\ No newline at end of file
diff --git a/src/i2c_temperature/i2c_temperature.cc b/src/i2c_temperature/i2c_temperature.cc
new file mode 100644
index 0000000..1e77aa7
--- /dev/null
+++ b/src/i2c_temperature/i2c_temperature.cc
@@ -0,0 +1,114 @@
+// 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/i2c_temperature.h"
+
+#include <lib/driver2/record_cpp.h>
+
+#include "src/i2c_temperature/constants.h"
+
+namespace fio = fuchsia_io;
+
+namespace i2c_temperature {
+
+// static
+zx::status<std::unique_ptr<I2cTemperatureDriver>> I2cTemperatureDriver::Start(
+ fuchsia_driver_framework::wire::DriverStartArgs &start_args, fdf::UnownedDispatcher dispatcher,
+ fidl::WireSharedClient<fuchsia_driver_framework::Node> node, driver::Namespace ns,
+ driver::Logger logger) {
+ auto driver = std::make_unique<I2cTemperatureDriver>(
+ 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<> I2cTemperatureDriver::Run(fidl::ServerEnd<fio::Directory> outgoing_dir) {
+ auto status = outgoing_.AddProtocol<sample_i2ctemperature::Device>(this, Name());
+ if (status.status_value() != ZX_OK) {
+ FDF_SLOG(ERROR, "Failed to add protocol", KV("status", status.status_string()));
+ return status;
+ }
+
+ status = SetupI2cChannel();
+ if (status.is_error()) {
+ return status.take_error();
+ }
+
+ // Reset the sensor.
+ auto result = i2c_channel_->Write16(kSoftResetCommand);
+ if (result.is_error()) {
+ FDF_SLOG(ERROR, "Failed to send reset command: ", KV("status", result.status_string()));
+ }
+
+ return zx::ok();
+}
+
+zx::status<> I2cTemperatureDriver::SetupI2cChannel() {
+ if (i2c_channel_) {
+ return zx::ok();
+ }
+
+ auto client_endpoint = ns_.Connect<fuchsia_hardware_i2c::Device>();
+ if (client_endpoint.status_value() != ZX_OK) {
+ FDF_SLOG(ERROR, "Failed to setup I2cChannel", KV("status", client_endpoint.status_string()));
+ return client_endpoint.take_error();
+ }
+
+ auto i2c_client = fidl::WireClient(std::move(*client_endpoint), dispatcher_);
+ i2c_channel_ = std::make_unique<I2cChannel>(std::move(i2c_client));
+ return zx::ok();
+}
+
+void I2cTemperatureDriver::ReadTemperature(ReadTemperatureRequestView request,
+ ReadTemperatureCompleter::Sync &completer) {
+ auto channel_status = SetupI2cChannel();
+ if (channel_status.is_error()) {
+ completer.ReplyError(channel_status.status_value());
+ return;
+ }
+
+ // Send a command to start the measurement.
+ auto status = i2c_channel_->Write16(kStartMeasurementCommand);
+ if (status.is_error()) {
+ FDF_SLOG(ERROR, "Failed to send measurement command: ", KV("status", status.status_string()));
+ completer.ReplyError(status.status_value());
+ return;
+ }
+
+ // Read the temperature measurement.
+ auto temp_result = i2c_channel_->Read16();
+ if (temp_result.is_error()) {
+ FDF_SLOG(ERROR, "Failed to read temperature: ", KV("status", temp_result.status_string()));
+ completer.ReplyError(temp_result.status_value());
+ return;
+ }
+
+ auto temp_value = static_cast<uint32_t>(temp_result.value());
+ FDF_SLOG(INFO, "Received temperature: ", KV("value", temp_value));
+ return completer.ReplySuccess(temp_value);
+}
+
+void I2cTemperatureDriver::ResetSensor(ResetSensorRequestView request,
+ ResetSensorCompleter::Sync &completer) {
+ auto channel_status = SetupI2cChannel();
+ if (channel_status.is_error()) {
+ completer.ReplyError(channel_status.status_value());
+ return;
+ }
+
+ auto result = i2c_channel_->Write16(kSoftResetCommand);
+ if (result.is_error()) {
+ FDF_SLOG(ERROR, "Failed to send reset command: ", KV("status", result.status_string()));
+ completer.ReplyError(result.status_value());
+ return;
+ }
+}
+
+} // namespace i2c_temperature
+
+FUCHSIA_DRIVER_RECORD_CPP_V1(i2c_temperature::I2cTemperatureDriver);
diff --git a/src/i2c_temperature/i2c_temperature.fidl b/src/i2c_temperature/i2c_temperature.fidl
new file mode 100644
index 0000000..dc93cdc
--- /dev/null
+++ b/src/i2c_temperature/i2c_temperature.fidl
@@ -0,0 +1,19 @@
+// 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.
+
+library sample.i2ctemperature;
+
+using zx;
+
+// Protocol for interacting with I2cTemperature.
+protocol Device {
+ /// Sends a command to measure the temperature, then read
+ /// the value.
+ ReadTemperature() -> (struct {
+ temperature float32;
+ }) error zx.status;
+
+ /// Sends a command to reset the sensor.
+ ResetSensor() -> (struct {}) error zx.status;
+};
diff --git a/src/i2c_temperature/i2c_temperature.h b/src/i2c_temperature/i2c_temperature.h
new file mode 100644
index 0000000..c6b9f73
--- /dev/null
+++ b/src/i2c_temperature/i2c_temperature.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_CC_I2C_TEMPERATURE_H_
+#define FUCHSIA_SDK_EXAMPLES_CC_I2C_TEMPERATURE_H_
+
+#include <fidl/fuchsia.driver.framework/cpp/wire.h>
+#include <fidl/fuchsia.hardware.i2c/cpp/wire.h>
+#include <fidl/sample.i2ctemperature/cpp/wire.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>
+#include <lib/zx/status.h>
+
+#include "i2c_channel.h"
+
+namespace i2c_temperature {
+
+// Sample driver that demonstrates how to communicate through an I2C protocol.
+class I2cTemperatureDriver : public fidl::WireServer<sample_i2ctemperature::Device> {
+ public:
+ I2cTemperatureDriver(async_dispatcher_t* dispatcher,
+ fidl::WireSharedClient<fuchsia_driver_framework::Node> node,
+ driver::Namespace ns, driver::Logger logger)
+ : dispatcher_(dispatcher),
+ node_(std::move(node)),
+ outgoing_(component::OutgoingDirectory::Create(dispatcher)),
+ ns_(std::move(ns)),
+ logger_(std::move(logger)) {}
+
+ virtual ~I2cTemperatureDriver() = default;
+
+ static constexpr const char* Name() { return "i2c-temperature"; }
+
+ static zx::status<std::unique_ptr<I2cTemperatureDriver>> Start(
+ fuchsia_driver_framework::wire::DriverStartArgs& start_args,
+ fdf::UnownedDispatcher dispatcher,
+ fidl::WireSharedClient<fuchsia_driver_framework::Node> node, driver::Namespace ns,
+ driver::Logger logger);
+
+ // fuchsia.hardware.i2ctemperature/Device:
+ void ReadTemperature(ReadTemperatureRequestView request,
+ ReadTemperatureCompleter::Sync& completer);
+ void ResetSensor(ResetSensorRequestView request, ResetSensorCompleter::Sync& completer);
+
+ private:
+ zx::status<> Run(fidl::ServerEnd<fuchsia_io::Directory> outgoing_dir);
+
+ zx::status<> SetupI2cChannel();
+
+ async_dispatcher_t* const dispatcher_;
+
+ fidl::WireSharedClient<fuchsia_driver_framework::Node> node_;
+
+ component::OutgoingDirectory outgoing_;
+ driver::Namespace ns_;
+ driver::Logger logger_;
+
+ std::unique_ptr<I2cChannel> i2c_channel_;
+};
+
+} // namespace i2c_temperature
+
+#endif // FUCHSIA_SDK_EXAMPLES_CC_I2C_TEMPERATURE_H_
diff --git a/src/i2c_temperature/meta/i2c_temperature.cml b/src/i2c_temperature/meta/i2c_temperature.cml
new file mode 100644
index 0000000..8d5fcf3
--- /dev/null
+++ b/src/i2c_temperature/meta/i2c_temperature.cml
@@ -0,0 +1,20 @@
+{
+ program: {
+ runner: 'driver',
+ binary: 'lib/libi2c_temperature.so',
+ bind: 'meta/bind/i2c_temperature.bindbc'
+ },
+ use: [
+ {
+ protocol: [
+ 'fuchsia.logger.LogSink',
+ 'fuchsia.hardware.i2c.Device',
+ ],
+ },
+ {
+ directory: 'fuchsia.driver.compat.Service-default',
+ rights: ['rw*'],
+ path: '/fuchsia.driver.compat.Service/default',
+ }
+ ],
+}