blob: 90891f58e2f443cfbe0c84f4e7ae50ee39d682d1 [file] [log] [blame]
Patrick Venturef7186a62018-11-21 14:08:04 -08001/*
2 * Copyright 2018 Google Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
Patrick Venture38e8c6e2018-10-24 09:23:35 -070017#include "i2c.hpp"
18
19#include <fcntl.h>
20#include <linux/i2c-dev.h>
21#include <linux/i2c.h>
22#include <sys/ioctl.h>
23#include <unistd.h>
24
25#include <array>
26#include <cerrno>
Benjamin Fairb7d43112020-05-20 09:39:11 -070027#include <cstdio>
Patrick Venture38e8c6e2018-10-24 09:23:35 -070028#include <cstring>
William A. Kennington IIId7f368a2019-02-07 14:54:38 -080029#include <ipmid/iana.hpp>
30#include <ipmid/oemopenbmc.hpp>
Patrick Venture38e8c6e2018-10-24 09:23:35 -070031#include <memory>
32
33namespace oem
34{
35namespace i2c
36{
37
38// Instance object.
39std::unique_ptr<I2c> globalOemI2c;
40
41// Block read (I2C_M_RECV_LEN) reads count byte, then that many bytes,
42// then possibly a checksum byte. The specified byte count limit,
43// I2C_SMBUS_BLOCK_MAX, is 32, but it seems intractible to prove
44// every I2C implementation uses that limit. So to prevent overflow,
45// allocate buffers based on the largest possible count byte of 255.
46constexpr size_t maxRecvLenBuf = 1 + 255 + 1;
47typedef std::array<uint8_t, maxRecvLenBuf> BlockBuf;
48
49struct ParsedStep
50{
51 const uint8_t* reqData;
52 size_t length;
53 DevAddr devAddr;
54 bool isRead;
55 // When nonzero, device supplies count for this step, as in SMBUS block
56 // mode. Value will tell driver how many extra bytes to read for entire
57 // block: 1 for count byte w/o PEC, 2 if there is also a PEC byte.
58 uint16_t blockExtra;
59 bool noStart;
60};
61
62struct ParsedReq
63{
64 BusId localbus;
65 bool usePec;
66 size_t numSteps;
67 std::array<ParsedStep, maxSteps> step;
68};
69
70static ipmi_ret_t parseReqHdr(const uint8_t* reqBuf, size_t reqLen,
71 size_t* bytesUsed, i2c::ParsedReq* req)
72{
73 // Request header selects bus & flags for operation;
74 // additional bytes beyond are to be interpreted as steps.
75 if (reqLen < *bytesUsed + requestHeaderLen)
76 {
77 std::fprintf(stderr, "i2c::parse reqLen=%zu?\n", reqLen);
78 return IPMI_CC_REQ_DATA_LEN_INVALID;
79 }
80 // Deserialize request header bytes.
81 req->localbus = reqBuf[requestHeaderBus];
82 auto reqFlags = reqBuf[requestHeaderFlags];
83 *bytesUsed += requestHeaderLen;
84
85 // Decode flags.
86 req->usePec = !!(reqFlags & requestFlagsUsePec);
87 return IPMI_CC_OK;
88}
89
90static ipmi_ret_t parseReqStep(const uint8_t* reqBuf, size_t reqLen,
91 size_t* bytesUsed, i2c::ParsedReq* req)
92{
93 size_t bytesLeft = reqLen - *bytesUsed;
94 if (req->numSteps >= maxSteps || bytesLeft < stepHeaderLen)
95 {
96 std::fprintf(stderr, "i2c::parse[%zu] bytesLeft=%zu?\n", req->numSteps,
97 bytesLeft);
98 return IPMI_CC_REQ_DATA_LEN_INVALID;
99 }
100 const uint8_t* stepHdr = reqBuf + *bytesUsed;
101 auto step = &req->step[req->numSteps++];
102
103 // Deserialize request step header bytes.
104 uint8_t devAndDir = stepHdr[stepHeaderDevAndDir];
105 uint8_t stepFlags = stepHdr[stepHeaderFlags];
106 step->length = stepHdr[stepHeaderParm];
107 bytesLeft -= stepHeaderLen;
108 *bytesUsed += stepHeaderLen;
109
110 // Decode device addr & direction.
111 step->devAddr = devAndDir >> 1;
112 step->isRead = !!(devAndDir & 1);
113
114 // Decode step flags.
115 step->noStart = !!(stepFlags & stepFlagsNoStart);
116
117 if (step->isRead)
118 {
119 // Read could select blockExtra.
120 if (stepFlags & stepFlagsRecvLen)
121 {
122 step->blockExtra = req->usePec ? 2 : 1;
123 }
124 }
125 else
126 {
127 // For write, requested byte count must follow.
128 if (bytesLeft < step->length)
129 {
130 std::fprintf(stderr, "i2c::parse[%zu] bytesLeft=%zu, parm=%zu?\n",
131 req->numSteps, bytesLeft, step->length);
132 return IPMI_CC_REQ_DATA_LEN_INVALID;
133 }
134 step->reqData = reqBuf + *bytesUsed;
135 *bytesUsed += step->length;
136 }
137 return IPMI_CC_OK;
138}
139
140// Parse i2c request.
141static ipmi_ret_t parse(const uint8_t* reqBuf, size_t reqLen,
142 i2c::ParsedReq* req)
143{
144 size_t bytesUsed = 0;
145 auto rc = parseReqHdr(reqBuf, reqLen, &bytesUsed, req);
146 if (rc != IPMI_CC_OK)
147 {
148 return rc;
149 }
150 do
151 {
152 rc = parseReqStep(reqBuf, reqLen, &bytesUsed, req);
153 if (rc != IPMI_CC_OK)
154 {
155 return rc;
156 }
157 } while (bytesUsed < reqLen);
158 return IPMI_CC_OK;
159}
160
161// Convert parsed request to I2C messages.
162static ipmi_ret_t buildI2cMsgs(const i2c::ParsedReq& req,
163 std::unique_ptr<i2c::BlockBuf> rxBuf[],
164 struct i2c_msg msgs[],
165 struct i2c_rdwr_ioctl_data* msgset)
166{
167 size_t minReplyLen = 0;
168
169 for (size_t i = 0; i < req.numSteps; ++msgset->nmsgs, ++i)
170 {
171 const auto& step = req.step[i];
172 auto* msg = &msgs[i];
173 msg->addr = step.devAddr;
174
175 if (!step.isRead)
176 {
177 msg->flags = 0;
178 msg->len = step.length;
179 msg->buf = const_cast<uint8_t*>(step.reqData);
180 continue;
181 }
182 rxBuf[i] = std::make_unique<i2c::BlockBuf>();
183 msg->buf = rxBuf[i]->data();
184
185 if (step.blockExtra == 0)
186 {
187 msg->flags = I2C_M_RD;
188 msg->len = step.length;
189 minReplyLen += msg->len;
190 }
191 else
192 {
193 // Special buffer setup needed for block read:
194 // . 1. msg len must allow for maximum possible transfer,
195 // . 2. blockExtra must be preloaded into buf[0]
196 // The internal i2c_transfer API is slightly different;
197 // the rdwr ioctl handler adapts by moving blockExtra
198 // into msg.len where the driver will expect to find it.
199 //
200 // References:
201 // drivers/i2c/i2c-dev.c: i2cdev_ioctl_rdwr()
202 msg->flags = I2C_M_RD | I2C_M_RECV_LEN;
203 msg->len = maxRecvLenBuf;
204 msg->buf[0] = step.blockExtra;
205 minReplyLen += step.blockExtra;
206 }
207 }
208
209 if (minReplyLen > i2c::largestReply)
210 {
211 std::fprintf(stderr, "I2c::transfer minReplyLen=%zu?\n", minReplyLen);
212 return IPMI_CC_RESPONSE_ERROR; // Won't fit in response message
213 }
214
215#ifdef __IPMI_DEBUG__
216 for (size_t i = 0; i < req.numSteps; ++i)
217 {
218 auto* msg = &msgs[i];
219 std::fprintf(stderr, "I2c::transfer msg[%zu]: %02x %04x %d\n", i,
220 msg->addr, msg->flags, msg->len);
221 }
222#endif
223
224 return IPMI_CC_OK;
225}
226
227static int openBus(BusId localbus)
228{
229 char busCharDev[16];
230 std::snprintf(busCharDev, sizeof(busCharDev) - 1, "/dev/i2c-%d", localbus);
231 int busFd = open(busCharDev, O_RDWR);
232 if (busFd < 0)
233 {
234 std::fprintf(stderr,
235 "NetFn:[0x2E], OEM:[0x002B79], Cmd:[0x02], "
236 "I2C Bus Open(\"%s\"): \"%s\"\n",
237 busCharDev, strerror(-busFd));
238 }
239 return busFd;
240}
241
242} // namespace i2c
243
Willy Tu49ac48b2021-12-13 21:32:46 -0800244ipmi_ret_t I2c::transfer(ipmi_cmd_t, const uint8_t* reqBuf, uint8_t* replyBuf,
245 size_t* dataLen)
Patrick Venture38e8c6e2018-10-24 09:23:35 -0700246{
247 // Parse message header.
248 auto reqLen = *dataLen;
249 *dataLen = 0;
250 i2c::ParsedReq req = {};
251 auto rc = parse(reqBuf, reqLen, &req);
252 if (rc != IPMI_CC_OK)
253 {
254 return rc;
255 }
256
257 // Build full msgset
258 std::unique_ptr<i2c::BlockBuf> rxBuf[i2c::maxSteps];
259 struct i2c_msg msgs[i2c::maxSteps] = {};
260 struct i2c_rdwr_ioctl_data msgset = {
261 .msgs = msgs,
262 .nmsgs = 0,
263 };
264 rc = buildI2cMsgs(req, rxBuf, msgs, &msgset);
265 if (rc != IPMI_CC_OK)
266 {
267 return rc;
268 }
269
270 // Try to open i2c bus
271 int busFd = i2c::openBus(req.localbus);
272 if (busFd < 0)
273 {
274 return IPMI_CC_UNSPECIFIED_ERROR;
275 }
276 int ioError = ioctl(busFd, I2C_RDWR, &msgset);
277
278 // Done with busFd, so close it immediately to avoid leaking it.
279 (void)close(busFd);
280 if (ioError < 0)
281 {
282 std::fprintf(stderr, "I2c::transfer I2C_RDWR ioError=%d?\n", ioError);
283 return IPMI_CC_UNSPECIFIED_ERROR; // I2C_RDWR I/O error
284 }
285
286 // If we read any data, append it, in the order we read it.
287 uint8_t* nextReplyByte = replyBuf;
288 size_t replyLen = 0;
289 for (size_t i = 0; i < req.numSteps; ++i)
290 {
291 const auto& step = req.step[i];
292 if (step.isRead)
293 {
294 const auto* msg = &msgs[i];
295 size_t lenRead =
296 step.blockExtra ? *msg->buf + step.blockExtra : msg->len;
297 replyLen += lenRead;
298 if (replyLen > i2c::largestReply)
299 {
300 std::fprintf(stderr, "I2c::transfer[%zu] replyLen=%zu?\n", i,
301 replyLen);
302 return IPMI_CC_RESPONSE_ERROR; // Won't fit in response message
303 }
304 std::memcpy(nextReplyByte, msg->buf, lenRead);
305 nextReplyByte += lenRead;
306 }
307 }
308 *dataLen = replyLen;
309 return IPMI_CC_OK;
310}
311
312void I2c::registerWith(Router* oemRouter)
313{
314 Handler f = [this](ipmi_cmd_t cmd, const uint8_t* reqBuf, uint8_t* replyBuf,
315 size_t* dataLen) {
316 return transfer(cmd, reqBuf, replyBuf, dataLen);
317 };
318
319 std::fprintf(stderr, "Registering OEM:[%#08X], Cmd:[%#04X] for I2C\n",
320 googOemNumber, Cmd::i2cCmd);
321 oemRouter->registerHandler(googOemNumber, Cmd::i2cCmd, f);
322
323 std::fprintf(stderr, "Registering OEM:[%#08X], Cmd:[%#04X] for I2C\n",
324 obmcOemNumber, Cmd::i2cCmd);
325 oemRouter->registerHandler(obmcOemNumber, Cmd::i2cCmd, f);
326}
327
328namespace i2c
329{
330// Currently ipmid dynamically loads providers such as these;
331// this creates our singleton upon load.
332void setupGlobalOemI2c() __attribute__((constructor));
333
334void setupGlobalOemI2c()
335{
336 globalOemI2c = std::make_unique<I2c>();
337 globalOemI2c->registerWith(oem::mutableRouter());
338}
339} // namespace i2c
340} // namespace oem