xref: /aosp_15_r20/external/pigweed/pw_i2c_linux/initiator.cc (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1*61c4878aSAndroid Build Coastguard Worker // Copyright 2023 The Pigweed Authors
2*61c4878aSAndroid Build Coastguard Worker //
3*61c4878aSAndroid Build Coastguard Worker // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4*61c4878aSAndroid Build Coastguard Worker // use this file except in compliance with the License. You may obtain a copy of
5*61c4878aSAndroid Build Coastguard Worker // the License at
6*61c4878aSAndroid Build Coastguard Worker //
7*61c4878aSAndroid Build Coastguard Worker //     https://www.apache.org/licenses/LICENSE-2.0
8*61c4878aSAndroid Build Coastguard Worker //
9*61c4878aSAndroid Build Coastguard Worker // Unless required by applicable law or agreed to in writing, software
10*61c4878aSAndroid Build Coastguard Worker // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11*61c4878aSAndroid Build Coastguard Worker // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12*61c4878aSAndroid Build Coastguard Worker // License for the specific language governing permissions and limitations under
13*61c4878aSAndroid Build Coastguard Worker // the License.
14*61c4878aSAndroid Build Coastguard Worker #define PW_LOG_MODULE_NAME "I2C"
15*61c4878aSAndroid Build Coastguard Worker 
16*61c4878aSAndroid Build Coastguard Worker #include "pw_i2c_linux/initiator.h"
17*61c4878aSAndroid Build Coastguard Worker 
18*61c4878aSAndroid Build Coastguard Worker #include <fcntl.h>
19*61c4878aSAndroid Build Coastguard Worker #include <linux/i2c-dev.h>
20*61c4878aSAndroid Build Coastguard Worker #include <linux/i2c.h>
21*61c4878aSAndroid Build Coastguard Worker #include <sys/ioctl.h>
22*61c4878aSAndroid Build Coastguard Worker #include <sys/types.h>
23*61c4878aSAndroid Build Coastguard Worker #include <unistd.h>
24*61c4878aSAndroid Build Coastguard Worker 
25*61c4878aSAndroid Build Coastguard Worker #include <array>
26*61c4878aSAndroid Build Coastguard Worker #include <cerrno>
27*61c4878aSAndroid Build Coastguard Worker #include <cinttypes>
28*61c4878aSAndroid Build Coastguard Worker #include <mutex>
29*61c4878aSAndroid Build Coastguard Worker 
30*61c4878aSAndroid Build Coastguard Worker #include "pw_assert/check.h"
31*61c4878aSAndroid Build Coastguard Worker #include "pw_chrono/system_clock.h"
32*61c4878aSAndroid Build Coastguard Worker #include "pw_i2c/address.h"
33*61c4878aSAndroid Build Coastguard Worker #include "pw_log/log.h"
34*61c4878aSAndroid Build Coastguard Worker #include "pw_status/status.h"
35*61c4878aSAndroid Build Coastguard Worker #include "pw_status/try.h"
36*61c4878aSAndroid Build Coastguard Worker 
37*61c4878aSAndroid Build Coastguard Worker namespace pw::i2c {
38*61c4878aSAndroid Build Coastguard Worker namespace {
39*61c4878aSAndroid Build Coastguard Worker 
40*61c4878aSAndroid Build Coastguard Worker using ::pw::chrono::SystemClock;
41*61c4878aSAndroid Build Coastguard Worker 
42*61c4878aSAndroid Build Coastguard Worker // Returns an appropriate status code for the given fault_code (i.e. `errno`).
43*61c4878aSAndroid Build Coastguard Worker // For unexpected fault codes, logs messages to aid in debugging.
44*61c4878aSAndroid Build Coastguard Worker // Reference: https://www.kernel.org/doc/html/latest/i2c/fault-codes.html
PwStatusAndLog(int i2c_errno,uint8_t device_address)45*61c4878aSAndroid Build Coastguard Worker Status PwStatusAndLog(int i2c_errno, uint8_t device_address) {
46*61c4878aSAndroid Build Coastguard Worker   switch (i2c_errno) {
47*61c4878aSAndroid Build Coastguard Worker     case EAGAIN:
48*61c4878aSAndroid Build Coastguard Worker       // Lost arbitration on a multi-controller bus.
49*61c4878aSAndroid Build Coastguard Worker       // This is a normal condition on multi-controller busses.
50*61c4878aSAndroid Build Coastguard Worker       return Status::Aborted();
51*61c4878aSAndroid Build Coastguard Worker     case ENOENT:
52*61c4878aSAndroid Build Coastguard Worker     case ENODEV:
53*61c4878aSAndroid Build Coastguard Worker     case ENXIO:
54*61c4878aSAndroid Build Coastguard Worker     case EREMOTEIO:
55*61c4878aSAndroid Build Coastguard Worker       // Generally indicates the device is unavailable or faulty.
56*61c4878aSAndroid Build Coastguard Worker       // This is a normal condition when incorrect address is specified.
57*61c4878aSAndroid Build Coastguard Worker       //
58*61c4878aSAndroid Build Coastguard Worker       // Return Unavailable instead of NotFound as per the requirements of
59*61c4878aSAndroid Build Coastguard Worker       // pw::i2c::Initiator.
60*61c4878aSAndroid Build Coastguard Worker       PW_LOG_INFO("I2C device unavailable at address 0x%" PRIx8,
61*61c4878aSAndroid Build Coastguard Worker                   device_address);
62*61c4878aSAndroid Build Coastguard Worker       return Status::Unavailable();
63*61c4878aSAndroid Build Coastguard Worker     case ESHUTDOWN:
64*61c4878aSAndroid Build Coastguard Worker       // It's not really clear what would cause a bus to be "suspended".
65*61c4878aSAndroid Build Coastguard Worker       PW_LOG_WARN("I2C bus is suspended");
66*61c4878aSAndroid Build Coastguard Worker       return Status::FailedPrecondition();
67*61c4878aSAndroid Build Coastguard Worker     default:
68*61c4878aSAndroid Build Coastguard Worker       // All other errors are unexpected and don't have a well-defined code.
69*61c4878aSAndroid Build Coastguard Worker       PW_LOG_ERROR("I2C transaction failed for address 0x%" PRIx8 ": errno=%d",
70*61c4878aSAndroid Build Coastguard Worker                    device_address,
71*61c4878aSAndroid Build Coastguard Worker                    i2c_errno);
72*61c4878aSAndroid Build Coastguard Worker       return Status::Unknown();
73*61c4878aSAndroid Build Coastguard Worker   }
74*61c4878aSAndroid Build Coastguard Worker }
75*61c4878aSAndroid Build Coastguard Worker 
76*61c4878aSAndroid Build Coastguard Worker }  // namespace
77*61c4878aSAndroid Build Coastguard Worker 
78*61c4878aSAndroid Build Coastguard Worker // Open the file at the given path and validate that it is a valid bus device.
OpenI2cBus(const char * bus_path)79*61c4878aSAndroid Build Coastguard Worker Result<int> LinuxInitiator::OpenI2cBus(const char* bus_path) {
80*61c4878aSAndroid Build Coastguard Worker   int fd = open(bus_path, O_RDWR);
81*61c4878aSAndroid Build Coastguard Worker   if (fd < 0) {
82*61c4878aSAndroid Build Coastguard Worker     PW_LOG_ERROR(
83*61c4878aSAndroid Build Coastguard Worker         "Unable to open I2C bus device [%s]: errno=%d", bus_path, errno);
84*61c4878aSAndroid Build Coastguard Worker     return Status::InvalidArgument();
85*61c4878aSAndroid Build Coastguard Worker   }
86*61c4878aSAndroid Build Coastguard Worker 
87*61c4878aSAndroid Build Coastguard Worker   // Verify that the bus supports full I2C functionality.
88*61c4878aSAndroid Build Coastguard Worker   unsigned long functionality = 0;
89*61c4878aSAndroid Build Coastguard Worker   if (ioctl(fd, I2C_FUNCS, &functionality) != 0) {
90*61c4878aSAndroid Build Coastguard Worker     PW_LOG_ERROR("Unable to read I2C functionality for bus [%s]: errno=%d",
91*61c4878aSAndroid Build Coastguard Worker                  bus_path,
92*61c4878aSAndroid Build Coastguard Worker                  errno);
93*61c4878aSAndroid Build Coastguard Worker     close(fd);
94*61c4878aSAndroid Build Coastguard Worker     return Status::InvalidArgument();
95*61c4878aSAndroid Build Coastguard Worker   }
96*61c4878aSAndroid Build Coastguard Worker 
97*61c4878aSAndroid Build Coastguard Worker   if ((functionality & I2C_FUNC_I2C) == 0) {
98*61c4878aSAndroid Build Coastguard Worker     PW_LOG_ERROR("I2C bus [%s] does not support full I2C functionality",
99*61c4878aSAndroid Build Coastguard Worker                  bus_path);
100*61c4878aSAndroid Build Coastguard Worker     close(fd);
101*61c4878aSAndroid Build Coastguard Worker     return Status::InvalidArgument();
102*61c4878aSAndroid Build Coastguard Worker   }
103*61c4878aSAndroid Build Coastguard Worker   return fd;
104*61c4878aSAndroid Build Coastguard Worker }
105*61c4878aSAndroid Build Coastguard Worker 
LinuxInitiator(int fd)106*61c4878aSAndroid Build Coastguard Worker LinuxInitiator::LinuxInitiator(int fd) : fd_(fd) { PW_DCHECK(fd_ >= 0); }
107*61c4878aSAndroid Build Coastguard Worker 
~LinuxInitiator()108*61c4878aSAndroid Build Coastguard Worker LinuxInitiator::~LinuxInitiator() {
109*61c4878aSAndroid Build Coastguard Worker   PW_DCHECK(fd_ >= 0);
110*61c4878aSAndroid Build Coastguard Worker   close(fd_);
111*61c4878aSAndroid Build Coastguard Worker }
112*61c4878aSAndroid Build Coastguard Worker 
DoWriteReadFor(Address device_address,ConstByteSpan tx_buffer,ByteSpan rx_buffer,SystemClock::duration timeout)113*61c4878aSAndroid Build Coastguard Worker Status LinuxInitiator::DoWriteReadFor(Address device_address,
114*61c4878aSAndroid Build Coastguard Worker                                       ConstByteSpan tx_buffer,
115*61c4878aSAndroid Build Coastguard Worker                                       ByteSpan rx_buffer,
116*61c4878aSAndroid Build Coastguard Worker                                       SystemClock::duration timeout) {
117*61c4878aSAndroid Build Coastguard Worker   auto start_time = SystemClock::now();
118*61c4878aSAndroid Build Coastguard Worker 
119*61c4878aSAndroid Build Coastguard Worker   // Validate arguments.
120*61c4878aSAndroid Build Coastguard Worker   const auto address = device_address.GetSevenBit();
121*61c4878aSAndroid Build Coastguard Worker   if (tx_buffer.empty() && rx_buffer.empty()) {
122*61c4878aSAndroid Build Coastguard Worker     PW_LOG_ERROR("At least one of tx_buffer or rx_buffer must be not empty");
123*61c4878aSAndroid Build Coastguard Worker     return Status::InvalidArgument();
124*61c4878aSAndroid Build Coastguard Worker   }
125*61c4878aSAndroid Build Coastguard Worker 
126*61c4878aSAndroid Build Coastguard Worker   // Try to acquire access to the bus.
127*61c4878aSAndroid Build Coastguard Worker   if (!mutex_.try_lock_for(timeout)) {
128*61c4878aSAndroid Build Coastguard Worker     return Status::DeadlineExceeded();
129*61c4878aSAndroid Build Coastguard Worker   }
130*61c4878aSAndroid Build Coastguard Worker   std::lock_guard lock(mutex_, std::adopt_lock);
131*61c4878aSAndroid Build Coastguard Worker   const auto elapsed = SystemClock::now() - start_time;
132*61c4878aSAndroid Build Coastguard Worker   return DoWriteReadForLocked(address, tx_buffer, rx_buffer, timeout - elapsed);
133*61c4878aSAndroid Build Coastguard Worker }
134*61c4878aSAndroid Build Coastguard Worker 
135*61c4878aSAndroid Build Coastguard Worker // Perform an I2C write, read, or combined write+read transaction.
136*61c4878aSAndroid Build Coastguard Worker //
137*61c4878aSAndroid Build Coastguard Worker // Preconditions:
138*61c4878aSAndroid Build Coastguard Worker //  - `this->mutex_` is acquired
139*61c4878aSAndroid Build Coastguard Worker //  - `this->fd_` is open for read/write and supports full I2C functionality.
140*61c4878aSAndroid Build Coastguard Worker //  - `address` is a 7-bit device address
141*61c4878aSAndroid Build Coastguard Worker //  - At least one of `tx_buffer` or `rx_buffer` is not empty.
142*61c4878aSAndroid Build Coastguard Worker //
143*61c4878aSAndroid Build Coastguard Worker // The transaction will be retried if we can't get access to the bus, until
144*61c4878aSAndroid Build Coastguard Worker // the timeout is reached. There will be no retries if `timeout` is zero or
145*61c4878aSAndroid Build Coastguard Worker // negative.
DoWriteReadForLocked(uint8_t address,ConstByteSpan tx_buffer,ByteSpan rx_buffer,chrono::SystemClock::duration timeout)146*61c4878aSAndroid Build Coastguard Worker Status LinuxInitiator::DoWriteReadForLocked(
147*61c4878aSAndroid Build Coastguard Worker     uint8_t address,
148*61c4878aSAndroid Build Coastguard Worker     ConstByteSpan tx_buffer,
149*61c4878aSAndroid Build Coastguard Worker     ByteSpan rx_buffer,
150*61c4878aSAndroid Build Coastguard Worker     chrono::SystemClock::duration timeout) {
151*61c4878aSAndroid Build Coastguard Worker   const auto start_time = SystemClock::now();
152*61c4878aSAndroid Build Coastguard Worker 
153*61c4878aSAndroid Build Coastguard Worker   // Prepare messages for either a read, write, or combined transaction.
154*61c4878aSAndroid Build Coastguard Worker   // Populate `ioctl_data` with either one or two `i2c_msg` operations.
155*61c4878aSAndroid Build Coastguard Worker   // Use the `messages` buffer to store the operations.
156*61c4878aSAndroid Build Coastguard Worker   i2c_rdwr_ioctl_data ioctl_data{};
157*61c4878aSAndroid Build Coastguard Worker   std::array<i2c_msg, 2> messages{};
158*61c4878aSAndroid Build Coastguard Worker   if (!tx_buffer.empty() && rx_buffer.empty()) {
159*61c4878aSAndroid Build Coastguard Worker     messages[0] = i2c_msg{
160*61c4878aSAndroid Build Coastguard Worker         .addr = address,
161*61c4878aSAndroid Build Coastguard Worker         .flags = 0,  // Read transaction
162*61c4878aSAndroid Build Coastguard Worker         .len = static_cast<uint16_t>(tx_buffer.size()),
163*61c4878aSAndroid Build Coastguard Worker         .buf = reinterpret_cast<uint8_t*>(
164*61c4878aSAndroid Build Coastguard Worker             const_cast<std::byte*>(tx_buffer.data())),  // NOLINT: read-only
165*61c4878aSAndroid Build Coastguard Worker     };
166*61c4878aSAndroid Build Coastguard Worker     ioctl_data = {
167*61c4878aSAndroid Build Coastguard Worker         .msgs = messages.data(),
168*61c4878aSAndroid Build Coastguard Worker         .nmsgs = 1,
169*61c4878aSAndroid Build Coastguard Worker     };
170*61c4878aSAndroid Build Coastguard Worker   } else if (!rx_buffer.empty() && tx_buffer.empty()) {
171*61c4878aSAndroid Build Coastguard Worker     messages[0] = i2c_msg{
172*61c4878aSAndroid Build Coastguard Worker         .addr = address,
173*61c4878aSAndroid Build Coastguard Worker         .flags = I2C_M_RD,
174*61c4878aSAndroid Build Coastguard Worker         .len = static_cast<uint16_t>(rx_buffer.size()),
175*61c4878aSAndroid Build Coastguard Worker         .buf = reinterpret_cast<uint8_t*>(rx_buffer.data()),
176*61c4878aSAndroid Build Coastguard Worker     };
177*61c4878aSAndroid Build Coastguard Worker     ioctl_data = {
178*61c4878aSAndroid Build Coastguard Worker         .msgs = messages.data(),
179*61c4878aSAndroid Build Coastguard Worker         .nmsgs = 1,
180*61c4878aSAndroid Build Coastguard Worker     };
181*61c4878aSAndroid Build Coastguard Worker   } else {
182*61c4878aSAndroid Build Coastguard Worker     // DoWriteReadFor already checks that at least one buffer has data.
183*61c4878aSAndroid Build Coastguard Worker     // This is just an internal consistency check.
184*61c4878aSAndroid Build Coastguard Worker     PW_DCHECK(!rx_buffer.empty() && !tx_buffer.empty());
185*61c4878aSAndroid Build Coastguard Worker     messages[0] = i2c_msg{
186*61c4878aSAndroid Build Coastguard Worker         .addr = address,
187*61c4878aSAndroid Build Coastguard Worker         .flags = 0,  // Read transaction
188*61c4878aSAndroid Build Coastguard Worker         .len = static_cast<uint16_t>(tx_buffer.size()),
189*61c4878aSAndroid Build Coastguard Worker         .buf = reinterpret_cast<uint8_t*>(
190*61c4878aSAndroid Build Coastguard Worker             const_cast<std::byte*>(tx_buffer.data())),  // NOLINT: read-only
191*61c4878aSAndroid Build Coastguard Worker     };
192*61c4878aSAndroid Build Coastguard Worker     messages[1] = i2c_msg{
193*61c4878aSAndroid Build Coastguard Worker         .addr = address,
194*61c4878aSAndroid Build Coastguard Worker         .flags = I2C_M_RD,
195*61c4878aSAndroid Build Coastguard Worker         .len = static_cast<uint16_t>(rx_buffer.size()),
196*61c4878aSAndroid Build Coastguard Worker         .buf = reinterpret_cast<uint8_t*>(rx_buffer.data()),
197*61c4878aSAndroid Build Coastguard Worker     };
198*61c4878aSAndroid Build Coastguard Worker     ioctl_data = {
199*61c4878aSAndroid Build Coastguard Worker         .msgs = messages.data(),
200*61c4878aSAndroid Build Coastguard Worker         .nmsgs = 2,
201*61c4878aSAndroid Build Coastguard Worker     };
202*61c4878aSAndroid Build Coastguard Worker   }
203*61c4878aSAndroid Build Coastguard Worker   PW_LOG_DEBUG("Attempting I2C transaction with %" PRIu32 " operations",
204*61c4878aSAndroid Build Coastguard Worker                ioctl_data.nmsgs);
205*61c4878aSAndroid Build Coastguard Worker 
206*61c4878aSAndroid Build Coastguard Worker   // Attempt the transaction. If we can't get exclusive access to the bus,
207*61c4878aSAndroid Build Coastguard Worker   // then keep trying until we run out of time.
208*61c4878aSAndroid Build Coastguard Worker   do {
209*61c4878aSAndroid Build Coastguard Worker     if (ioctl(fd_, I2C_RDWR, &ioctl_data) < 0) {
210*61c4878aSAndroid Build Coastguard Worker       Status status = PwStatusAndLog(errno, address);
211*61c4878aSAndroid Build Coastguard Worker       if (status == Status::Aborted()) {
212*61c4878aSAndroid Build Coastguard Worker         // Lost arbitration and need to try again.
213*61c4878aSAndroid Build Coastguard Worker         PW_LOG_DEBUG("Retrying I2C transaction");
214*61c4878aSAndroid Build Coastguard Worker         continue;
215*61c4878aSAndroid Build Coastguard Worker       }
216*61c4878aSAndroid Build Coastguard Worker       return status;
217*61c4878aSAndroid Build Coastguard Worker     }
218*61c4878aSAndroid Build Coastguard Worker     return OkStatus();
219*61c4878aSAndroid Build Coastguard Worker   } while (SystemClock::now() - start_time < timeout);
220*61c4878aSAndroid Build Coastguard Worker 
221*61c4878aSAndroid Build Coastguard Worker   // Attempt transaction one last time. This thread may have been suspended
222*61c4878aSAndroid Build Coastguard Worker   // after the last attempt, but before the timeout actually expired. The
223*61c4878aSAndroid Build Coastguard Worker   // timeout is meant to be a minimum time period.
224*61c4878aSAndroid Build Coastguard Worker   if (ioctl(fd_, I2C_RDWR, &ioctl_data) < 0) {
225*61c4878aSAndroid Build Coastguard Worker     Status status = PwStatusAndLog(errno, address);
226*61c4878aSAndroid Build Coastguard Worker     if (status == Status::Aborted()) {
227*61c4878aSAndroid Build Coastguard Worker       PW_LOG_INFO("Timeout waiting for I2C bus access");
228*61c4878aSAndroid Build Coastguard Worker       return Status::DeadlineExceeded();
229*61c4878aSAndroid Build Coastguard Worker     }
230*61c4878aSAndroid Build Coastguard Worker     return status;
231*61c4878aSAndroid Build Coastguard Worker   }
232*61c4878aSAndroid Build Coastguard Worker   return OkStatus();
233*61c4878aSAndroid Build Coastguard Worker }
234*61c4878aSAndroid Build Coastguard Worker 
235*61c4878aSAndroid Build Coastguard Worker }  // namespace pw::i2c
236