blob: 3396fc950e525456c2c061631297a4b0761e094f [file] [log] [blame]
// 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);