| // 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); |