diff options
author | Xin Li <delphij@google.com> | 2024-01-17 22:13:58 -0800 |
---|---|---|
committer | Xin Li <delphij@google.com> | 2024-01-17 22:13:58 -0800 |
commit | 28d03a2a1cabbe01d7bcb6cf5166c10e50d3c2c6 (patch) | |
tree | c1643be8ab17fc607cea748a8bb1d621a5964873 /pw_i2c | |
parent | ec2628a6ba2d0ecbe3ac10c8c772f6fc6acc345d (diff) | |
parent | f054515492af5132f685cb23fe11891ee77104c9 (diff) | |
download | pigweed-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.bp | 59 | ||||
-rw-r--r-- | pw_i2c/BUILD.bazel | 50 | ||||
-rw-r--r-- | pw_i2c/BUILD.gn | 35 | ||||
-rw-r--r-- | pw_i2c/CMakeLists.txt | 185 | ||||
-rw-r--r-- | pw_i2c/address_test.cc | 4 | ||||
-rw-r--r-- | pw_i2c/docs.rst | 62 | ||||
-rw-r--r-- | pw_i2c/i2c.options | 19 | ||||
-rw-r--r-- | pw_i2c/i2c.proto | 52 | ||||
-rw-r--r-- | pw_i2c/i2c_service.cc | 84 | ||||
-rw-r--r-- | pw_i2c/i2c_service_test.cc | 244 | ||||
-rw-r--r-- | pw_i2c/public/pw_i2c/device.h | 1 | ||||
-rw-r--r-- | pw_i2c/public/pw_i2c/i2c_service.h | 50 | ||||
-rw-r--r-- | pw_i2c/public/pw_i2c/initiator.h | 32 | ||||
-rw-r--r-- | pw_i2c/public/pw_i2c/register_device.h | 2 | ||||
-rw-r--r-- | pw_i2c/register_device.cc | 4 |
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: |