aboutsummaryrefslogtreecommitdiff
path: root/pw_i2c
diff options
context:
space:
mode:
authorXin Li <delphij@google.com>2024-01-17 22:13:58 -0800
committerXin Li <delphij@google.com>2024-01-17 22:13:58 -0800
commit28d03a2a1cabbe01d7bcb6cf5166c10e50d3c2c6 (patch)
treec1643be8ab17fc607cea748a8bb1d621a5964873 /pw_i2c
parentec2628a6ba2d0ecbe3ac10c8c772f6fc6acc345d (diff)
parentf054515492af5132f685cb23fe11891ee77104c9 (diff)
downloadpigweed-28d03a2a1cabbe01d7bcb6cf5166c10e50d3c2c6.tar.gz
Merge Android 24Q1 Release (ab/11220357)temp_319669529
Bug: 319669529 Merged-In: Iba357b308a79d0c8b560acd4f72b5423c9c83294 Change-Id: Icdf552029fb97a34e83c6dd7799433fc473a2506
Diffstat (limited to 'pw_i2c')
-rw-r--r--pw_i2c/Android.bp59
-rw-r--r--pw_i2c/BUILD.bazel50
-rw-r--r--pw_i2c/BUILD.gn35
-rw-r--r--pw_i2c/CMakeLists.txt185
-rw-r--r--pw_i2c/address_test.cc4
-rw-r--r--pw_i2c/docs.rst62
-rw-r--r--pw_i2c/i2c.options19
-rw-r--r--pw_i2c/i2c.proto52
-rw-r--r--pw_i2c/i2c_service.cc84
-rw-r--r--pw_i2c/i2c_service_test.cc244
-rw-r--r--pw_i2c/public/pw_i2c/device.h1
-rw-r--r--pw_i2c/public/pw_i2c/i2c_service.h50
-rw-r--r--pw_i2c/public/pw_i2c/initiator.h32
-rw-r--r--pw_i2c/public/pw_i2c/register_device.h2
-rw-r--r--pw_i2c/register_device.cc4
15 files changed, 855 insertions, 28 deletions
diff --git a/pw_i2c/Android.bp b/pw_i2c/Android.bp
new file mode 100644
index 000000000..b10ae240e
--- /dev/null
+++ b/pw_i2c/Android.bp
@@ -0,0 +1,59 @@
+// Copyright 2023 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+package {
+ default_applicable_licenses: ["external_pigweed_license"],
+}
+
+genrule {
+ name: "pw_i2c_proto_with_prefix",
+ defaults: ["pw_rpc_add_prefix_to_proto"],
+ srcs: [
+ "i2c.options",
+ "i2c.proto",
+ ],
+ out: [
+ "pw_i2c/i2c.options",
+ "pw_i2c/i2c.proto",
+ ],
+}
+
+genrule {
+ name: "pw_i2c_pwpb_rpc_header",
+ defaults: ["pw_rpc_generate_pwpb_rpc_header_with_prefix"],
+ srcs: [":pw_i2c_proto_with_prefix"],
+ out: ["pw_i2c/i2c.rpc.pwpb.h"],
+}
+
+genrule {
+ name: "pw_i2c_pwpb_proto_header",
+ defaults: ["pw_rpc_generate_pwpb_proto_with_prefix"],
+ srcs: [":pw_i2c_proto_with_prefix"],
+ out: ["pw_i2c/i2c.pwpb.h"],
+}
+
+cc_library_headers {
+ name: "pw_i2c_service_pwpb_headers",
+ cpp_std: "c++20",
+ vendor_available: true,
+ host_supported: true,
+ generated_headers: [
+ "pw_i2c_pwpb_proto_header",
+ "pw_i2c_pwpb_rpc_header",
+ ],
+ export_generated_headers: [
+ "pw_i2c_pwpb_proto_header",
+ "pw_i2c_pwpb_rpc_header",
+ ],
+}
diff --git a/pw_i2c/BUILD.bazel b/pw_i2c/BUILD.bazel
index 98afc2230..a9d0af354 100644
--- a/pw_i2c/BUILD.bazel
+++ b/pw_i2c/BUILD.bazel
@@ -12,11 +12,17 @@
# License for the specific language governing permissions and limitations under
# the License.
+load("@rules_proto//proto:defs.bzl", "proto_library")
load(
"//pw_build:pigweed.bzl",
"pw_cc_library",
"pw_cc_test",
)
+load(
+ "//pw_protobuf_compiler:pw_proto_library.bzl",
+ "pw_proto_filegroup",
+ "pw_proto_library",
+)
package(default_visibility = ["//visibility:public"])
@@ -111,6 +117,7 @@ pw_cc_library(
pw_cc_library(
name = "initiator_gmock",
+ testonly = True,
hdrs = [
"public/pw_i2c/initiator_gmock.h",
],
@@ -157,3 +164,46 @@ pw_cc_test(
"//pw_unit_test",
],
)
+
+pw_proto_filegroup(
+ name = "i2c_proto_and_options",
+ srcs = ["i2c.proto"],
+ options_files = ["i2c.options"],
+)
+
+proto_library(
+ name = "i2c_proto",
+ srcs = [":i2c_proto_and_options"],
+)
+
+pw_proto_library(
+ name = "i2c_cc",
+ deps = [":i2c_proto"],
+)
+
+pw_cc_library(
+ name = "i2c_service",
+ srcs = ["i2c_service.cc"],
+ hdrs = ["public/pw_i2c/i2c_service.h"],
+ includes = ["public"],
+ deps = [
+ ":address",
+ ":i2c_cc.pwpb_rpc",
+ ":initiator",
+ "//pw_chrono:system_clock",
+ "//pw_containers:vector",
+ "//pw_status",
+ ],
+)
+
+pw_cc_test(
+ name = "i2c_service_test",
+ srcs = ["i2c_service_test.cc"],
+ deps = [
+ ":i2c_service",
+ ":initiator_mock",
+ "//pw_containers:vector",
+ "//pw_rpc/pwpb:test_method_context",
+ "//pw_status",
+ ],
+)
diff --git a/pw_i2c/BUILD.gn b/pw_i2c/BUILD.gn
index 700f8edad..4eed45af1 100644
--- a/pw_i2c/BUILD.gn
+++ b/pw_i2c/BUILD.gn
@@ -17,6 +17,7 @@ import("//build_overrides/pigweed.gni")
import("$dir_pw_build/target_types.gni")
import("$dir_pw_chrono/backend.gni")
import("$dir_pw_docgen/docs.gni")
+import("$dir_pw_protobuf_compiler/proto.gni")
import("$dir_pw_unit_test/test.gni")
config("public_include_path") {
@@ -70,6 +71,27 @@ pw_source_set("register_device") {
deps = [ "$dir_pw_assert" ]
}
+pw_proto_library("protos") {
+ sources = [ "i2c.proto" ]
+ inputs = [ "i2c.options" ]
+ prefix = "pw_i2c"
+}
+
+pw_source_set("i2c_service") {
+ public = [ "public/pw_i2c/i2c_service.h" ]
+ sources = [ "i2c_service.cc" ]
+ public_deps = [
+ ":protos.pwpb_rpc",
+ "$dir_pw_chrono:system_clock",
+ "$dir_pw_i2c:initiator",
+ ]
+ deps = [
+ "$dir_pw_containers:vector",
+ "$dir_pw_i2c:address",
+ "$dir_pw_status",
+ ]
+}
+
pw_source_set("mock") {
public_configs = [ ":public_include_path" ]
public = [ "public/pw_i2c/initiator_mock.h" ]
@@ -102,6 +124,7 @@ pw_test_group("tests") {
":device_test",
":initiator_mock_test",
":register_device_test",
+ ":i2c_service_test",
]
}
@@ -138,6 +161,18 @@ pw_test("initiator_mock_test") {
]
}
+pw_test("i2c_service_test") {
+ enable_if = pw_chrono_SYSTEM_CLOCK_BACKEND != ""
+ sources = [ "i2c_service_test.cc" ]
+ deps = [
+ ":i2c_service",
+ "$dir_pw_containers:vector",
+ "$dir_pw_i2c:mock",
+ "$dir_pw_rpc/pwpb:test_method_context",
+ "$dir_pw_status",
+ ]
+}
+
pw_doc_group("docs") {
sources = [ "docs.rst" ]
}
diff --git a/pw_i2c/CMakeLists.txt b/pw_i2c/CMakeLists.txt
new file mode 100644
index 000000000..39e0c51a0
--- /dev/null
+++ b/pw_i2c/CMakeLists.txt
@@ -0,0 +1,185 @@
+# Copyright 2023 The Pigweed Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+include($ENV{PW_ROOT}/pw_build/pigweed.cmake)
+include($ENV{PW_ROOT}/pw_protobuf_compiler/proto.cmake)
+
+pw_add_library(pw_i2c.address STATIC
+ HEADERS
+ public/pw_i2c/address.h
+ PUBLIC_INCLUDES
+ public
+ PUBLIC_DEPS
+ pw_assert
+ SOURCES
+ address.cc
+)
+
+pw_add_library(pw_i2c.initiator INTERFACE
+ HEADERS
+ public/pw_i2c/initiator.h
+ PUBLIC_INCLUDES
+ public
+ PUBLIC_DEPS
+ pw_bytes
+ pw_chrono.system_clock
+ pw_i2c.address
+ pw_status
+)
+
+pw_add_library(pw_i2c.device INTERFACE
+ HEADERS
+ public/pw_i2c/device.h
+ PUBLIC_INCLUDES
+ public
+ PUBLIC_DEPS
+ pw_bytes
+ pw_chrono.system_clock
+ pw_i2c.address
+ pw_i2c.initiator
+ pw_span
+ pw_status
+)
+
+pw_add_library(pw_i2c.register_device STATIC
+ HEADERS
+ public/pw_i2c/register_device.h
+ PUBLIC_INCLUDES
+ public
+ PUBLIC_DEPS
+ pw_bytes
+ pw_chrono.system_clock
+ pw_i2c.address
+ pw_i2c.device
+ pw_i2c.initiator
+ pw_result
+ pw_status
+ PRIVATE_DEPS
+ pw_assert
+ SOURCES
+ register_device.cc
+)
+
+pw_proto_library(pw_i2c.protos
+ SOURCES
+ i2c.proto
+ INPUTS
+ i2c.options
+ PREFIX
+ pw_i2c
+)
+
+pw_add_library(pw_i2c.i2c_service STATIC
+ HEADERS
+ public/pw_i2c/i2c_service.h
+ PUBLIC_INCLUDES
+ public
+ PUBLIC_DEPS
+ pw_chrono.system_clock
+ pw_i2c.initiator
+ pw_i2c.protos.pwpb_rpc
+ PRIVATE_DEPS
+ pw_containers.vector
+ pw_i2c.address
+ pw_status
+ SOURCES
+ i2c_service.cc
+)
+
+pw_add_library(pw_i2c.mock STATIC
+ HEADERS
+ public/pw_i2c/initiator_mock.h
+ PUBLIC_INCLUDES
+ public
+ PUBLIC_DEPS
+ pw_bytes
+ pw_containers
+ pw_containers.to_array
+ pw_i2c.initiator
+ PRIVATE_DEPS
+ pw_assert
+ pw_unit_test
+ SOURCES
+ initiator_mock.cc
+)
+
+pw_add_library(pw_i2c.gmock INTERFACE
+ HEADERS
+ public/pw_i2c/initiator_gmock.h
+ PUBLIC_INCLUDES
+ public
+ PUBLIC_DEPS
+ pw_i2c.initiator
+ pw_third_party.googletest
+)
+
+pw_add_test(pw_i2c.address_test
+ SOURCES
+ address_test.cc
+ PRIVATE_DEPS
+ pw_i2c.address
+ GROUPS
+ modules
+ pw_i2c
+)
+
+if(NOT "${pw_chrono.system_clock_BACKEND}" STREQUAL "")
+pw_add_test(pw_i2c.device_test
+ SOURCES
+ device_test.cc
+ PRIVATE_DEPS
+ pw_containers
+ pw_i2c.device
+ pw_i2c.mock
+ GROUPS
+ modules
+ pw_i2c
+)
+
+pw_add_test(pw_i2c.register_device_test
+ SOURCES
+ register_device_test.cc
+ PRIVATE_DEPS
+ pw_assert
+ pw_i2c.register_device
+ GROUPS
+ modules
+ pw_i2c
+)
+
+pw_add_test(pw_i2c.initiator_mock_test
+ SOURCES
+ initiator_mock_test.cc
+ PUBLIC_DEPS
+ pw_containers
+ pw_i2c.mock
+ GROUPS
+ modules
+ pw_i2c
+)
+
+pw_add_test(pw_i2c.i2c_service_test
+ SOURCES
+ i2c_service_test.cc
+ PUBLIC_DEPS
+ pw_containers.vector
+ pw_i2c.i2c_service
+ pw_i2c.mock
+ pw_rpc.test_utils
+ pw_status
+ GROUPS
+ modules
+ pw_i2c
+)
+endif()
diff --git a/pw_i2c/address_test.cc b/pw_i2c/address_test.cc
index 0d3a90e83..ce34bffb6 100644
--- a/pw_i2c/address_test.cc
+++ b/pw_i2c/address_test.cc
@@ -39,10 +39,10 @@ TEST(Address, TenBitRuntimeChecked) {
EXPECT_EQ(ten_bit.GetTenBit(), Address::kMaxTenBitAddress);
}
-// TODO(b/235289499): Verify assert behaviour when trying to get a 7bit address
+// TODO: b/235289499 - Verify assert behaviour when trying to get a 7bit address
// out of a 10bit address.
-// TODO(b/234882063): Add tests to ensure the constexpr constructors fail to
+// TODO: b/234882063 - Add tests to ensure the constexpr constructors fail to
// compile with invalid addresses once no-copmile tests are set up in Pigweed.
} // namespace pw::i2c
diff --git a/pw_i2c/docs.rst b/pw_i2c/docs.rst
index 24369689f..8af329c85 100644
--- a/pw_i2c/docs.rst
+++ b/pw_i2c/docs.rst
@@ -3,10 +3,9 @@
------
pw_i2c
------
-
.. warning::
- This module is under construction, not ready for use, and the documentation
- is incomplete.
+ This module is under construction, not ready for use, and the documentation
+ is incomplete.
pw_i2c contains interfaces and utility functions for using I2C.
@@ -15,13 +14,8 @@ Features
pw::i2c::Initiator
------------------
-.. inclusive-language: disable
-
-The common interface for initiating transactions with devices on an I2C bus.
-Other documentation sources may call this style of interface an I2C "master",
-"central" or "controller".
-
-.. inclusive-language: enable
+.. doxygenclass:: pw::i2c::Initiator
+ :members:
pw::i2c::Device
---------------
@@ -30,6 +24,13 @@ contains ``pw::i2c::Address`` and wraps the ``pw::i2c::Initiator`` API.
Common use case includes streaming arbitrary data (Read/Write). Only works
with devices with a single device address.
+.. note::
+ ``Device`` is intended to represent ownership of a specific responder.
+ Individual transactions are atomic (as described under ``Initiator``), but
+ there is no synchronization for sequences of transactions. Therefore, shared
+ access should be faciliated with higher level application abstractions. To
+ help enforce this, the ``Device`` object is only movable and not copyable.
+
pw::i2c::RegisterDevice
-----------------------
The common interface for interfacing with register devices. Contains methods
@@ -50,7 +51,7 @@ list. An example of this is shown below:
.. code-block:: cpp
using pw::i2c::Address;
- using pw::i2c::MakeExpectedTransactionlist;
+ using pw::i2c::MakeExpectedTransactionArray;
using pw::i2c::MockInitiator;
using pw::i2c::WriteTransaction;
using std::literals::chrono_literals::ms;
@@ -85,3 +86,42 @@ list. An example of this is shown below:
pw::i2c::GmockInitiator
-----------------------
gMock of Initiator used for testing and mocking out the Initiator.
+
+I2c Debug Service
+=================
+This module implements an I2C register access service for debugging and bringup.
+To use, provide it with a callback function that returns an ``Initiator`` for
+the specified ``bus_index``.
+
+Example invocations
+-------------------
+Using the pigweed console, you can invoke the service to perform an I2C read:
+
+.. code-block:: python
+
+ device.rpcs.pw.i2c.I2c.I2cRead(bus_index=0, target_address=0x22, register_address=b'\x0e', read_size=1)
+
+The above shows reading register 0x0e on a device located at
+I2C address 0x22.
+
+For responders that support 4 byte register width, you can specify as:
+
+.. code-block:: python
+
+ device.rpcs.pw.i2c.I2c.I2cRead(bus_index=0, target_address=<address>, register_address=b'\x00\x00\x00\x00', read_size=4)
+
+
+And similarly, for performing I2C write:
+
+.. code-block:: python
+
+ device.rpcs.pw.i2c.I2c.I2cWrite(bus_index=0, target_address=0x22,register_address=b'\x0e', value=b'\xbc')
+
+
+Similarly, multi-byte writes can also be specified with the bytes fields for
+`register_address` and `value`.
+
+I2C responders that require multi-byte access may expect a specific endianness.
+The order of bytes specified in the bytes field will match the order of bytes
+sent/received on the bus. Maximum supported value for multi-byte access is
+4 bytes.
diff --git a/pw_i2c/i2c.options b/pw_i2c/i2c.options
new file mode 100644
index 000000000..09e71ff60
--- /dev/null
+++ b/pw_i2c/i2c.options
@@ -0,0 +1,19 @@
+// Copyright 2023 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+pw.i2c.I2cWriteRequest.register_address max_size:4
+pw.i2c.I2cWriteRequest.value max_size:32
+pw.i2c.I2cReadRequest.register_address max_size:4
+pw.i2c.I2cReadResponse.value max_size:32
+
diff --git a/pw_i2c/i2c.proto b/pw_i2c/i2c.proto
new file mode 100644
index 000000000..d68654b72
--- /dev/null
+++ b/pw_i2c/i2c.proto
@@ -0,0 +1,52 @@
+// Copyright 2023 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+syntax = "proto3";
+
+package pw.i2c;
+
+message I2cWriteRequest {
+ // Which I2C initiator bus to communicate on.
+ uint32 bus_index = 1;
+ // 7-bit I2C target address to write to.
+ uint32 target_address = 2;
+ // Register address to write. Follow the endianness required by the
+ // responder for multi-byte address.
+ bytes register_address = 3;
+ // Value to write. Follow the endianness required by the responder.
+ bytes value = 4;
+}
+
+message I2cWriteResponse {}
+
+message I2cReadRequest {
+ // Which I2C initiator bus to communicate on.
+ uint32 bus_index = 1;
+ // 7-bit I2C target address to read from.
+ uint32 target_address = 2;
+ // Register address to write. Follow the endianness required by the
+ // responder for multi-byte address.
+ bytes register_address = 3;
+ // Expected number of bytes from the responder.
+ uint32 read_size = 4;
+}
+
+message I2cReadResponse {
+ bytes value = 1;
+}
+
+service I2c {
+ // Enable access to I2C devices implementing register read/writes.
+ rpc I2cWrite(I2cWriteRequest) returns (I2cWriteResponse) {}
+ rpc I2cRead(I2cReadRequest) returns (I2cReadResponse) {}
+}
diff --git a/pw_i2c/i2c_service.cc b/pw_i2c/i2c_service.cc
new file mode 100644
index 000000000..0dad6193b
--- /dev/null
+++ b/pw_i2c/i2c_service.cc
@@ -0,0 +1,84 @@
+// Copyright 2023 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+#include "pw_i2c/i2c_service.h"
+
+#include <algorithm>
+#include <chrono>
+
+#include "pw_assert/check.h"
+#include "pw_chrono/system_clock.h"
+#include "pw_containers/vector.h"
+#include "pw_i2c/address.h"
+#include "pw_rpc/pwpb/server_reader_writer.h"
+#include "pw_status/status.h"
+
+namespace pw::i2c {
+namespace {
+
+constexpr auto kI2cTimeout =
+ chrono::SystemClock::for_at_least(std::chrono::milliseconds(100));
+
+} // namespace
+
+void I2cService::I2cWrite(
+ const pwpb::I2cWriteRequest::Message& request,
+ rpc::PwpbUnaryResponder<pwpb::I2cWriteResponse::Message>& responder) {
+ Initiator* initiator = initiator_selector_(request.bus_index);
+ if (initiator == nullptr) {
+ responder.Finish({}, Status::InvalidArgument()).IgnoreError();
+ return;
+ }
+
+ constexpr auto kMaxWriteSize =
+ pwpb::I2cWriteRequest::kRegisterAddressMaxSize +
+ pwpb::I2cWriteRequest::kValueMaxSize;
+ Vector<std::byte, kMaxWriteSize> write_buffer{};
+ write_buffer.assign(std::begin(request.register_address),
+ std::end(request.register_address));
+ std::copy(std::begin(request.value),
+ std::end(request.value),
+ std::back_inserter(write_buffer));
+ auto result = initiator->WriteFor(
+ Address{static_cast<uint16_t>(request.target_address)},
+ write_buffer,
+ kI2cTimeout);
+ responder.Finish({}, result).IgnoreError();
+}
+
+void I2cService::I2cRead(
+ const pwpb::I2cReadRequest::Message& request,
+ rpc::PwpbUnaryResponder<pwpb::I2cReadResponse::Message>& responder) {
+ constexpr auto kMaxReadSize = pwpb::I2cReadResponse::kValueMaxSize;
+
+ Initiator* initiator = initiator_selector_(request.bus_index);
+ if (initiator == nullptr || request.read_size > kMaxReadSize) {
+ responder.Finish({}, Status::InvalidArgument()).IgnoreError();
+ return;
+ }
+ Vector<std::byte, kMaxReadSize> value{};
+ value.resize(request.read_size);
+ auto result = initiator->WriteReadFor(
+ Address{static_cast<uint16_t>(request.target_address)},
+ request.register_address,
+ {value.data(), value.size()},
+ kI2cTimeout);
+
+ if (result.ok()) {
+ responder.Finish({value}, OkStatus()).IgnoreError();
+ } else {
+ responder.Finish({}, result).IgnoreError();
+ }
+}
+
+} // namespace pw::i2c
diff --git a/pw_i2c/i2c_service_test.cc b/pw_i2c/i2c_service_test.cc
new file mode 100644
index 000000000..7f069da7a
--- /dev/null
+++ b/pw_i2c/i2c_service_test.cc
@@ -0,0 +1,244 @@
+// Copyright 2023 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+#include "pw_i2c/i2c_service.h"
+
+#include <algorithm>
+#include <chrono>
+
+#include "gtest/gtest.h"
+#include "pw_i2c/address.h"
+#include "pw_i2c/initiator.h"
+#include "pw_i2c/initiator_mock.h"
+#include "pw_rpc/pwpb/test_method_context.h"
+#include "pw_status/status.h"
+
+namespace pw::i2c {
+namespace {
+
+auto MakeSingletonSelector(Initiator* initiator) {
+ return [initiator](size_t pos) { return pos == 0 ? initiator : nullptr; };
+}
+
+TEST(I2cServiceTest, I2cWriteSingleByteOk) {
+ Vector<std::byte, 4> register_addr{};
+ Vector<std::byte, 4> register_value{};
+ constexpr auto kExpectWrite = bytes::Array<0x02, 0x03>();
+ register_addr.push_back(kExpectWrite[0]);
+ register_value.push_back(kExpectWrite[1]);
+ auto transactions = MakeExpectedTransactionArray(
+ {Transaction(OkStatus(),
+ Address{0x01},
+ kExpectWrite,
+ {},
+ std::chrono::milliseconds(100))});
+ MockInitiator i2c_initiator(transactions);
+
+ PW_PWPB_TEST_METHOD_CONTEXT(I2cService, I2cWrite)
+ context{MakeSingletonSelector(&i2c_initiator)};
+
+ context.call({.bus_index = 0,
+ .target_address = 0x01,
+ .register_address = register_addr,
+ .value = register_value});
+ EXPECT_TRUE(context.done());
+ EXPECT_EQ(context.status(), OkStatus());
+ EXPECT_EQ(i2c_initiator.Finalize(), OkStatus());
+}
+
+TEST(I2cServiceTest, I2cWriteMultiByteOk) {
+ constexpr int kWriteSize = 4;
+ Vector<std::byte, kWriteSize> register_addr{};
+ Vector<std::byte, kWriteSize> register_value{};
+ constexpr auto kExpectWrite =
+ bytes::Array<0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09>();
+ std::copy(kExpectWrite.begin(),
+ kExpectWrite.begin() + kWriteSize,
+ std::back_inserter(register_addr));
+ std::copy(kExpectWrite.begin() + kWriteSize,
+ kExpectWrite.end(),
+ std::back_inserter(register_value));
+ auto transactions = MakeExpectedTransactionArray(
+ {Transaction(OkStatus(),
+ Address{0x01},
+ kExpectWrite,
+ {},
+ std::chrono::milliseconds(100))});
+ MockInitiator i2c_initiator(transactions);
+
+ PW_PWPB_TEST_METHOD_CONTEXT(I2cService, I2cWrite)
+ context{MakeSingletonSelector(&i2c_initiator)};
+
+ context.call({.bus_index = 0,
+ .target_address = 0x01,
+ .register_address = register_addr,
+ .value = register_value});
+ EXPECT_TRUE(context.done());
+ EXPECT_EQ(context.status(), OkStatus());
+ EXPECT_EQ(i2c_initiator.Finalize(), OkStatus());
+}
+
+TEST(I2cServiceTest, I2cWriteInvalidBusIndex) {
+ Vector<std::byte, 4> register_addr{};
+ Vector<std::byte, 4> register_value{};
+
+ MockInitiator i2c_initiator({});
+
+ PW_PWPB_TEST_METHOD_CONTEXT(I2cService, I2cWrite)
+ context{MakeSingletonSelector(&i2c_initiator)};
+
+ context.call({.bus_index = 1,
+ .target_address = 0x01,
+ .register_address = register_addr,
+ .value = register_value});
+ EXPECT_TRUE(context.done());
+ EXPECT_EQ(context.status(), Status::InvalidArgument());
+ EXPECT_EQ(i2c_initiator.Finalize(), OkStatus());
+}
+
+TEST(I2cServiceTest, I2cReadSingleByteOk) {
+ constexpr auto kExpectWrite = bytes::Array<0x02>();
+ constexpr auto kExpectRead = bytes::Array<0x03>();
+ Vector<std::byte, 4> register_addr{};
+ register_addr.push_back(kExpectWrite[0]);
+
+ auto transactions = MakeExpectedTransactionArray(
+ {Transaction(OkStatus(),
+ Address{0x01},
+ kExpectWrite,
+ kExpectRead,
+ std::chrono::milliseconds(100))});
+ MockInitiator i2c_initiator(transactions);
+
+ PW_PWPB_TEST_METHOD_CONTEXT(I2cService, I2cRead)
+ context{MakeSingletonSelector(&i2c_initiator)};
+
+ context.call({.bus_index = 0,
+ .target_address = 0x01,
+ .register_address = register_addr,
+ .read_size = static_cast<uint32_t>(kExpectRead.size())});
+
+ EXPECT_TRUE(context.done());
+ EXPECT_EQ(context.status(), OkStatus());
+ for (size_t i = 0; i < kExpectRead.size(); ++i) {
+ EXPECT_EQ(kExpectRead[i], context.response().value[i]);
+ }
+ EXPECT_EQ(i2c_initiator.Finalize(), OkStatus());
+}
+
+TEST(I2cServiceTest, I2cReadMultiByteOk) {
+ constexpr auto kExpectWrite = bytes::Array<0x02, 0x04, 0x06, 0x08>();
+ constexpr auto kExpectRead = bytes::Array<0x03, 0x05, 0x07, 0x09>();
+ Vector<std::byte, 4> register_addr{};
+ std::copy(kExpectWrite.begin(),
+ kExpectWrite.end(),
+ std::back_inserter(register_addr));
+ auto transactions = MakeExpectedTransactionArray(
+ {Transaction(OkStatus(),
+ Address{0x01},
+ kExpectWrite,
+ kExpectRead,
+ std::chrono::milliseconds(100))});
+ MockInitiator i2c_initiator(transactions);
+
+ PW_PWPB_TEST_METHOD_CONTEXT(I2cService, I2cRead)
+ context{MakeSingletonSelector(&i2c_initiator)};
+
+ context.call({.bus_index = 0,
+ .target_address = 0x01,
+ .register_address = register_addr,
+ .read_size = static_cast<uint32_t>(kExpectRead.size())});
+
+ EXPECT_TRUE(context.done());
+ EXPECT_EQ(context.status(), OkStatus());
+ for (size_t i = 0; i < kExpectRead.size(); ++i) {
+ EXPECT_EQ(kExpectRead[i], context.response().value[i]);
+ }
+ EXPECT_EQ(i2c_initiator.Finalize(), OkStatus());
+}
+
+TEST(I2cServiceTest, I2cReadMaxByteOk) {
+ constexpr auto kExpectWrite = bytes::Array<0x02, 0x04, 0x06, 0x08>();
+ constexpr auto kExpectRead = bytes::Array<0x03, 0x05, 0x07, 0x09>();
+ static_assert(sizeof(kExpectRead) <= pwpb::I2cReadResponse::kValueMaxSize);
+
+ Vector<std::byte, 4> register_addr{};
+ std::copy(kExpectWrite.begin(),
+ kExpectWrite.end(),
+ std::back_inserter(register_addr));
+ auto transactions = MakeExpectedTransactionArray(
+ {Transaction(OkStatus(),
+ Address{0x01},
+ kExpectWrite,
+ kExpectRead,
+ std::chrono::milliseconds(100))});
+ MockInitiator i2c_initiator(transactions);
+
+ PW_PWPB_TEST_METHOD_CONTEXT(I2cService, I2cRead)
+ context{MakeSingletonSelector(&i2c_initiator)};
+
+ context.call({
+ .bus_index = 0,
+ .target_address = 0x01,
+ .register_address = register_addr,
+ .read_size = sizeof(kExpectRead),
+ });
+
+ EXPECT_TRUE(context.done());
+ EXPECT_EQ(context.status(), OkStatus());
+ // EXPECT_EQ(kExpectRead, context.response().value);
+ EXPECT_EQ(i2c_initiator.Finalize(), OkStatus());
+}
+
+TEST(I2cServiceTest, I2cReadMultiByteOutOfBounds) {
+ pwpb::I2cReadResponse::Message response_message;
+ constexpr auto kMaxReadSize = response_message.value.max_size();
+ constexpr auto kRegisterAddr = bytes::Array<0x02, 0x04, 0x06, 0x08>();
+ Vector<std::byte, 4> register_addr{};
+ std::copy(kRegisterAddr.begin(),
+ kRegisterAddr.end(),
+ std::back_inserter(register_addr));
+ MockInitiator i2c_initiator({});
+
+ PW_PWPB_TEST_METHOD_CONTEXT(I2cService, I2cRead)
+ context{MakeSingletonSelector(&i2c_initiator)};
+
+ context.call({.bus_index = 0,
+ .target_address = 0x01,
+ .register_address = register_addr,
+ .read_size = kMaxReadSize + 1});
+
+ EXPECT_TRUE(context.done());
+ EXPECT_EQ(context.status(), Status::InvalidArgument());
+ EXPECT_EQ(i2c_initiator.Finalize(), OkStatus());
+}
+
+TEST(I2cServiceTest, I2cReadInvalidBusIndex) {
+ Vector<std::byte, 4> register_addr{};
+ MockInitiator i2c_initiator({});
+
+ PW_PWPB_TEST_METHOD_CONTEXT(I2cService, I2cRead)
+ context{MakeSingletonSelector(&i2c_initiator)};
+
+ context.call({.bus_index = 1,
+ .target_address = 0x01,
+ .register_address = register_addr,
+ .read_size = 1});
+
+ EXPECT_TRUE(context.done());
+ EXPECT_EQ(context.status(), Status::InvalidArgument());
+ EXPECT_EQ(i2c_initiator.Finalize(), OkStatus());
+}
+
+} // namespace
+} // namespace pw::i2c
diff --git a/pw_i2c/public/pw_i2c/device.h b/pw_i2c/public/pw_i2c/device.h
index 41129e399..4a76ba57a 100644
--- a/pw_i2c/public/pw_i2c/device.h
+++ b/pw_i2c/public/pw_i2c/device.h
@@ -32,6 +32,7 @@ class Device {
: initiator_(initiator), device_address_(device_address) {}
Device(const Device&) = delete;
+ Device(Device&&) = default;
~Device() = default;
// Write bytes and then read bytes as either one atomic or two independent I2C
diff --git a/pw_i2c/public/pw_i2c/i2c_service.h b/pw_i2c/public/pw_i2c/i2c_service.h
new file mode 100644
index 000000000..7b029b1fd
--- /dev/null
+++ b/pw_i2c/public/pw_i2c/i2c_service.h
@@ -0,0 +1,50 @@
+// Copyright 2023 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+#pragma once
+
+#include <array>
+#include <cstddef>
+#include <memory>
+#include <utility>
+
+#include "pw_function/function.h"
+#include "pw_i2c/i2c.pwpb.h"
+#include "pw_i2c/i2c.rpc.pwpb.h"
+#include "pw_i2c/initiator.h"
+#include "pw_rpc/pwpb/server_reader_writer.h"
+
+namespace pw::i2c {
+
+// RPC service to perform I2C transactions.
+class I2cService final : public pw_rpc::pwpb::I2c::Service<I2cService> {
+ public:
+ // Callback which returns an initiator for the given position or nullptr if
+ // the position not valid on this device.
+ using InitiatorSelector = pw::Function<Initiator*(size_t pos)>;
+
+ explicit I2cService(InitiatorSelector&& initiator_selector)
+ : initiator_selector_(std::move(initiator_selector)) {}
+
+ void I2cWrite(
+ const pwpb::I2cWriteRequest::Message& request,
+ pw::rpc::PwpbUnaryResponder<pwpb::I2cWriteResponse::Message>& responder);
+ void I2cRead(
+ const pwpb::I2cReadRequest::Message& request,
+ pw::rpc::PwpbUnaryResponder<pwpb::I2cReadResponse::Message>& responder);
+
+ private:
+ InitiatorSelector initiator_selector_;
+};
+
+} // namespace pw::i2c
diff --git a/pw_i2c/public/pw_i2c/initiator.h b/pw_i2c/public/pw_i2c/initiator.h
index 8b09a67e0..7d9d6ddde 100644
--- a/pw_i2c/public/pw_i2c/initiator.h
+++ b/pw_i2c/public/pw_i2c/initiator.h
@@ -22,18 +22,26 @@
namespace pw::i2c {
-// Base driver interface for I2C initiating I2C transactions in a thread safe
-// manner. Other documentation sources may call this style of interface an I2C
-// "master", "central" or "controller". // inclusive-language: ignore
-//
-// The Initiator is not required to support 10bit addressing. If only 7bit
-// addressing is supported, the Initiator will assert when given an address
-// which is out of 7bit address range.
-//
-// The implementer of this pure virtual interface is responsible for ensuring
-// thread safety and enabling functionality such as initialization,
-// configuration, enabling/disabling, unsticking SDA, and detecting device
-// address registration collisions.
+/// @brief The common, base driver interface for initiating thread-safe
+/// transactions with devices on an I2C bus. Other documentation may call this
+/// style of interface an I2C "master", <!-- inclusive-language: disable -->
+/// "central", or "controller".
+///
+/// `Initiator` isn't required to support 10-bit addressing. If only 7-bit
+/// addressing is supported, `Initiator` asserts when given an address
+/// that is out of 7-bit address range.
+///
+/// The implementer of this pure virtual interface is responsible for ensuring
+/// thread safety and enabling functionality such as initialization,
+/// configuration, enabling/disabling, unsticking SDA, and detecting device
+/// address registration collisions.
+///
+/// @note `Initiator` uses internal synchronization, so it's safe to
+/// initiate transactions from multiple threads. However, write+read
+/// transactions may not be atomic with multiple controllers on the bus.
+/// Furthermore, devices may require specific sequences of transactions, and
+/// application logic must provide the synchronization to execute these
+/// sequences correctly.
class Initiator {
public:
virtual ~Initiator() = default;
diff --git a/pw_i2c/public/pw_i2c/register_device.h b/pw_i2c/public/pw_i2c/register_device.h
index 0f9b4df39..b71683c83 100644
--- a/pw_i2c/public/pw_i2c/register_device.h
+++ b/pw_i2c/public/pw_i2c/register_device.h
@@ -378,7 +378,7 @@ inline Status RegisterDevice::ReadRegisters32(
PW_TRY(
ReadRegisters(register_address, as_writable_bytes(return_data), timeout));
- // TODO(b/185952662): Extend endian in pw_byte to support this conversion
+ // TODO: b/185952662 - Extend endian in pw_byte to support this conversion
// as optimization.
// Post process endian information.
for (uint32_t& register_value : return_data) {
diff --git a/pw_i2c/register_device.cc b/pw_i2c/register_device.cc
index 9c3b15e29..055ddcc3b 100644
--- a/pw_i2c/register_device.cc
+++ b/pw_i2c/register_device.cc
@@ -28,7 +28,7 @@ void PutRegisterAddressInByteBuilder(
const uint32_t register_address,
const endian order,
RegisterAddressSize register_address_size) {
- // TODO(b/185952662): Simplify the call site by extending the byte builder
+ // TODO: b/185952662 - Simplify the call site by extending the byte builder
// and endian API.
switch (register_address_size) {
case RegisterAddressSize::k1Byte:
@@ -112,7 +112,7 @@ Status RegisterDevice::WriteRegisters(const uint32_t register_address,
case 4:
PutRegisterData32InByteBuilder(builder, register_data, data_order_)
- .IgnoreError(); // TODO(b/242598609): Handle Status properly
+ .IgnoreError(); // TODO: b/242598609 - Handle Status properly
break;
default: