blob: 04d6e225844b5213f3990cb7d66da1acdea7d1f1 [file] [log] [blame]
// Copyright 2018 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 SRC_DEVICES_I2C_LIB_DEVICE_PROTOCOL_I2C_CHANNEL_INCLUDE_LIB_DEVICE_PROTOCOL_I2C_CHANNEL_H_
#define SRC_DEVICES_I2C_LIB_DEVICE_PROTOCOL_I2C_CHANNEL_INCLUDE_LIB_DEVICE_PROTOCOL_I2C_CHANNEL_H_
#include <fidl/fuchsia.hardware.i2c/cpp/wire.h>
#include <fuchsia/hardware/i2c/cpp/banjo.h>
#include <lib/device-protocol/i2c.h>
#include <lib/sync/completion.h>
#include <zircon/types.h>
#include <algorithm>
#include <optional>
namespace ddk {
// TODO(fxbug.dev/96293): Merge I2cFidlChannel back into I2cChannel and delete I2cChannelBase once
// all clients are using FIDL.
class I2cChannelBase {
public:
struct StatusRetries {
zx_status_t status;
uint8_t retries;
};
virtual ~I2cChannelBase() = default;
// Performs typical i2c Read: writes device register address (1 byte) followed
// by len reads into buf.
zx_status_t ReadSync(uint8_t addr, uint8_t* buf, size_t len) {
return WriteReadSync(&addr, 1, buf, len);
}
// Writes len bytes from buffer with no trailing read
zx_status_t WriteSync(const uint8_t* buf, size_t len) {
return WriteReadSync(buf, len, nullptr, 0);
}
virtual zx_status_t WriteReadSync(const uint8_t* tx_buf, size_t tx_len, uint8_t* rx_buf,
size_t rx_len) = 0;
// ReadSync with retries, returns status and retry attempts.
StatusRetries ReadSyncRetries(uint8_t addr, uint8_t* buf, size_t len, uint8_t retries,
zx::duration delay) {
return WriteReadSyncRetries(&addr, 1, buf, len, retries, delay);
}
// WriteSync with retries, returns status and retry attempts.
StatusRetries WriteSyncRetries(const uint8_t* buf, size_t len, uint8_t retries,
zx::duration delay) {
return WriteReadSyncRetries(buf, len, nullptr, 0, retries, delay);
}
// WriteReadSync with retries, returns status and retry attempts.
StatusRetries WriteReadSyncRetries(const uint8_t* tx_buf, size_t tx_len, uint8_t* rx_buf,
size_t rx_len, uint8_t retries, zx::duration delay) {
uint8_t attempt = 0;
auto status = WriteReadSync(tx_buf, tx_len, rx_buf, rx_len);
while (status != ZX_OK && attempt < retries) {
zx::nanosleep(zx::deadline_after(delay));
attempt++;
status = WriteReadSync(tx_buf, tx_len, rx_buf, rx_len);
}
return {status, attempt};
}
};
class I2cFidlChannel : public I2cChannelBase {
public:
explicit I2cFidlChannel(fidl::ClientEnd<fuchsia_hardware_i2c::Device> client_end)
: fidl_client_(std::move(client_end)) {}
I2cFidlChannel(I2cFidlChannel&& other) noexcept = default;
I2cFidlChannel& operator=(I2cFidlChannel&& other) noexcept = default;
~I2cFidlChannel() override = default;
fidl::WireResult<fuchsia_hardware_i2c::Device::Transfer> Transfer(
fidl::VectorView<fuchsia_hardware_i2c::wire::Transaction> transactions) {
return fidl_client_->Transfer(transactions);
}
zx_status_t WriteReadSync(const uint8_t* tx_buf, size_t tx_len, uint8_t* rx_buf,
size_t rx_len) override {
if (tx_len > fuchsia_hardware_i2c::wire::kMaxTransferSize ||
rx_len > fuchsia_hardware_i2c::wire::kMaxTransferSize) {
return 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_ERR_INVALID_ARGS;
}
const auto reply = fidl_client_->Transfer(
fidl::VectorView<fuchsia_hardware_i2c::wire::Transaction>::FromExternal(transactions,
index));
if (!reply.ok()) {
return reply.status();
}
if (reply->is_error()) {
return reply->error_value();
}
if (rx_len > 0) {
const auto& read_data = reply->value()->read_data;
// Truncate the returned buffer to match the behavior of the Banjo version.
if (read_data.count() != 1) {
return ZX_ERR_IO;
}
memcpy(rx_buf, read_data[0].data(), std::min(rx_len, read_data[0].count()));
}
return ZX_OK;
}
void Transact(const i2c_op_t* op_list, size_t op_count, i2c_transact_callback callback,
void* cookie) {
if (op_count > fuchsia_hardware_i2c::wire::kMaxCountTransactions) {
callback(cookie, ZX_ERR_OUT_OF_RANGE, nullptr, 0);
return;
}
fidl::Arena arena;
fidl::VectorView<fuchsia_hardware_i2c::wire::Transaction> transactions(arena, op_count);
size_t read_count = 0;
for (size_t i = 0; i < op_count; i++) {
if (op_list[i].data_size > fuchsia_hardware_i2c::wire::kMaxTransferSize) {
callback(cookie, ZX_ERR_INVALID_ARGS, nullptr, 0);
return;
}
const auto size = static_cast<uint32_t>(op_list[i].data_size);
if (op_list[i].is_read) {
read_count++;
transactions[i] =
fuchsia_hardware_i2c::wire::Transaction::Builder(arena)
.data_transfer(fuchsia_hardware_i2c::wire::DataTransfer::WithReadSize(size))
.stop(op_list[i].stop)
.Build();
} else {
fidl::VectorView<uint8_t> write_data(arena, size);
memcpy(write_data.mutable_data(), op_list[i].data_buffer, size);
transactions[i] =
fuchsia_hardware_i2c::wire::Transaction::Builder(arena)
.data_transfer(
fuchsia_hardware_i2c::wire::DataTransfer::WithWriteData(arena, write_data))
.stop(op_list[i].stop)
.Build();
}
}
const auto reply = fidl_client_->Transfer(transactions);
if (!reply.ok()) {
callback(cookie, reply.status(), nullptr, 0);
return;
}
if (reply.value().is_error()) {
callback(cookie, reply.value().error_value(), nullptr, 0);
return;
}
const fidl::VectorView<fidl::VectorView<uint8_t>>& read_data = reply.value().value()->read_data;
if (read_data.count() != read_count) {
callback(cookie, ZX_ERR_INTERNAL, nullptr, 0);
return;
}
i2c_op_t read_ops[fuchsia_hardware_i2c::wire::kMaxCountTransactions];
for (size_t i = 0; i < read_count; i++) {
read_ops[i] = {
.data_buffer = read_data[i].data(),
.data_size = read_data[i].count(),
.is_read = true,
.stop = false,
};
}
callback(cookie, ZX_OK, read_ops, read_count);
}
private:
fidl::WireSyncClient<fuchsia_hardware_i2c::Device> fidl_client_;
};
// TODO(fxbug.dev/96293): Remove Banjo support once all clients have been switched to FIDL.
class I2cChannel : public I2cChannelBase {
public:
I2cChannel() = default;
I2cChannel(const i2c_protocol_t* proto) : banjo_client_(proto) {}
I2cChannel(fidl::ClientEnd<fuchsia_hardware_i2c::Device> client)
: fidl_client_(std::move(client)) {}
I2cChannel(zx_device_t* parent) : banjo_client_(parent) { ConnectFidlIfNeeded(parent, nullptr); }
I2cChannel(zx_device_t* parent, const char* fragment_name)
: banjo_client_(parent, fragment_name) {
ConnectFidlIfNeeded(parent, fragment_name);
}
I2cChannel(I2cChannel&& other) noexcept = default;
I2cChannel& operator=(I2cChannel&& other) noexcept = default;
I2cChannel(const I2cChannel& other) = delete;
I2cChannel& operator=(const I2cChannel& other) = delete;
~I2cChannel() override = default;
zx_status_t WriteReadSync(const uint8_t* tx_buf, size_t tx_len, uint8_t* rx_buf,
size_t rx_len) override {
if (banjo_client_.is_valid()) {
i2c_protocol_t proto;
banjo_client_.GetProto(&proto);
return i2c_write_read_sync(&proto, tx_buf, tx_len, rx_buf, rx_len);
}
if (fidl_client_.has_value()) {
return fidl_client_->WriteReadSync(tx_buf, tx_len, rx_buf, rx_len);
}
ZX_ASSERT_MSG(false, "No Banjo or FIDL client is available");
}
void GetProto(i2c_protocol_t* proto) const {
ZX_ASSERT_MSG(banjo_client_.is_valid(), "No Banjo client is available");
banjo_client_.GetProto(proto);
}
bool is_valid() const { return banjo_client_.is_valid() || fidl_client_.has_value(); }
// Note: Currently Transact() calls to FIDL clients are synchronous.
// TODO(fxbug.dev/96293): Add support for async FIDL calls if needed.
void Transact(const i2c_op_t* op_list, size_t op_count, i2c_transact_callback callback,
void* cookie) {
if (banjo_client_.is_valid()) {
banjo_client_.Transact(op_list, op_count, callback, cookie);
} else if (fidl_client_.has_value()) {
fidl_client_->Transact(op_list, op_count, callback, cookie);
} else {
ZX_ASSERT_MSG(false, "No Banjo or FIDL client is available");
}
}
zx_status_t GetMaxTransferSize(uint64_t* out_size) const {
ZX_ASSERT_MSG(!fidl_client_.has_value(),
"GetMaxTransferSize() is not implemented for FIDL clients");
return banjo_client_.GetMaxTransferSize(out_size);
}
private:
void ConnectFidlIfNeeded(zx_device_t* parent, const char* fragment_name) {
if (banjo_client_.is_valid()) {
return;
}
auto endpoints = fidl::CreateEndpoints<fuchsia_hardware_i2c::Device>();
if (endpoints.is_error()) {
return;
}
zx_status_t status;
if (fragment_name == nullptr) {
status = device_connect_fidl_protocol(
parent, fidl::DiscoverableProtocolName<fuchsia_hardware_i2c::Device>,
endpoints->server.TakeChannel().release());
} else {
status = device_connect_fragment_fidl_protocol(
parent, fragment_name, fidl::DiscoverableProtocolName<fuchsia_hardware_i2c::Device>,
endpoints->server.TakeChannel().release());
}
if (status != ZX_OK) {
return;
}
fidl_client_.emplace(std::move(endpoints->client));
}
I2cProtocolClient banjo_client_;
std::optional<I2cFidlChannel> fidl_client_;
};
} // namespace ddk
#endif // SRC_DEVICES_I2C_LIB_DEVICE_PROTOCOL_I2C_CHANNEL_INCLUDE_LIB_DEVICE_PROTOCOL_I2C_CHANNEL_H_