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