blob: ec84f5c2ceaf1e26dd4a8f40dabccd1aa7bdccd0 [file] [log] [blame]
John Chunge2fae4b2024-11-13 18:10:31 -06001#include "serialcmd.hpp"
2
3#include <fmt/format.h>
4
5#include <phosphor-logging/lg2.hpp>
6#include <sdbusplus/bus.hpp>
7#include <sdbusplus/exception.hpp>
8#include <sdbusplus/message.hpp>
9#include <sdbusplus/slot.hpp>
10#include <stdplus/exception.hpp>
11#include <stdplus/fd/ops.hpp>
12
13#include <numeric>
14#include <ranges>
15#include <unordered_map>
16
17namespace serialbridge
18{
19
20/**
21 * @brief Table of special characters
22 */
23static const std::unordered_map<uint8_t, uint8_t> characters = {
24 {bmStart, 0xB0}, /* start */
25 {bmStop, 0xB5}, /* stop */
26 {bmHandshake, 0xB6}, /* packet handshake */
27 {bmEscape, 0xBA}, /* data escape */
28 {0x1B, 0x3B} /* escape */
29};
30
31/**
32 * @brief Calculate IPMI checksum
33 */
34uint8_t SerialChannel::calculateChecksum(std::span<uint8_t> data)
35{
36 uint8_t checksum;
37
38 checksum = std::accumulate(data.begin(), data.end(), 0);
39 checksum = (~checksum) + 1;
40
41 // return checksum
42 return checksum;
43}
44
45/**
46 * @brief Return unescaped character for the given one
47 */
48uint8_t SerialChannel::getUnescapedCharacter(uint8_t c)
49{
50 auto search =
51 std::find_if(characters.begin(), characters.end(),
52 [c](const auto& map_set) { return map_set.second == c; });
53
54 if (search == characters.end())
55 {
56 return c;
57 }
58
59 return search->first;
60}
61
62/**
63 * @brief Process IPMI Serial Request State Machine
64 */
65int SerialChannel::consumeIpmiSerialPacket(
66 std::span<uint8_t>& escapedDataBytes,
67 std::vector<uint8_t>& unescapedDataBytes)
68{
69 unescapedDataBytes.reserve(escapedDataBytes.size());
70
71 for (auto c : escapedDataBytes)
72 {
73 if (c == bmStart) // START
74 {
75 msgState = MsgState::msgInProgress;
76 }
77 else if (msgState == MsgState::msgIdle)
78 {
79 continue;
80 }
81 else if (msgState == MsgState::msgInEscape)
82 {
83 uint8_t unescapedCharacter;
84 unescapedCharacter = getUnescapedCharacter(c);
85
86 if (unescapedCharacter == c)
87 {
88 // error, then reset
89 msgState = MsgState::msgIdle;
90 unescapedDataBytes.clear();
91 continue;
92 }
93
94 unescapedDataBytes.push_back(unescapedCharacter);
95 msgState = MsgState::msgInProgress;
96 }
97 else if (c == bmEscape)
98 {
99 msgState = MsgState::msgInEscape;
100 continue;
101 }
102 else if (c == bmStop) // STOP
103 {
104 msgState = MsgState::msgIdle;
105 return true;
106 }
107 else if (c == bmHandshake) // Handshake
108 {
109 unescapedDataBytes.clear();
110 continue;
111 }
112 else if (msgState == MsgState::msgInProgress)
113 {
114 unescapedDataBytes.push_back(c);
115 }
116 }
117
118 return false;
119}
120
121/**
122 * @brief Encapsluate response to avoid escape character
123 */
124uint8_t SerialChannel::processEscapedCharacter(std::vector<uint8_t>& buffer,
125 const std::vector<uint8_t>& data)
126{
127 uint8_t checksum = 0;
128
129 std::ranges::for_each(data.begin(), data.end(),
130 [&buffer, &checksum](const auto& c) {
131 auto search = characters.find(c);
132 if (search != characters.end())
133 {
134 buffer.push_back(bmEscape);
135 buffer.push_back(search->second);
136 }
137 else
138 {
139 buffer.push_back(c);
140 }
141
142 checksum += c;
143 });
144
145 return checksum;
146}
147
148/**
149 * @brief Write function
150 */
151int SerialChannel::write(stdplus::Fd& uart, uint8_t rsAddr, uint8_t rqAddr,
152 uint8_t seq, sdbusplus::message_t&& m)
153{
154 std::span<uint8_t> out;
155 uint8_t checksum;
156
157 try
158 {
159 if (m.is_method_error())
160 {
161 // Extra copy to workaround lack of `const sd_bus_error` constructor
162 auto error = *m.get_error();
163 throw sdbusplus::exception::SdBusError(&error, "ipmid response");
164 }
165
166 uint8_t netFn = 0xff;
167 uint8_t lun = 0xff;
168 uint8_t cmd = 0xff;
169 uint8_t cc = 0xff;
170 std::vector<uint8_t> data;
171
172 m.read(std::tie(netFn, lun, cmd, cc, data));
173
174 uint8_t netFnLun = (netFn << netFnShift) | (lun & lunMask);
175 uint8_t seqLun = (seq << netFnShift) | (lun & lunMask);
176
177 std::vector<uint8_t> connectionHeader = {rqAddr, netFnLun};
178 std::vector<uint8_t> messageHeader = {rsAddr, seqLun, cmd, cc};
179
180 // Reserve the buffer size to avoid relloc and copy
181 responseBuffer.clear();
182 responseBuffer.reserve(
183 sizeof(struct IpmiSerialHeader) + 2 * data.size() +
184 4); // 4 for bmStart & bmStop & 2 checksums
185
186 // bmStart
187 responseBuffer.push_back(bmStart);
188
189 // Assemble connection header and checksum
190 checksum = processEscapedCharacter(responseBuffer, connectionHeader);
191 responseBuffer.push_back(-checksum); // checksum1
192
193 // Assemble response message and checksum
194 checksum = processEscapedCharacter(responseBuffer, messageHeader);
195 checksum +=
196 processEscapedCharacter(responseBuffer, std::vector<uint8_t>(data));
197 responseBuffer.push_back(-checksum); // checksum2
198
199 // bmStop
200 responseBuffer.push_back(bmStop);
201
202 out = std::span<uint8_t>(responseBuffer.begin(), responseBuffer.end());
203
204 if (verbose)
205 {
206 lg2::info(
207 "Write serial request message with len={LEN}, netfn={NETFN}, "
208 "lun={LUN}, cmd={CMD}, seq={SEQ}",
209 "LEN", responseBuffer.size(), "NETFN", netFn, "LUN", lun, "CMD",
210 cmd, "SEQ", seq);
211
212 std::string msgData = "Tx: ";
213 for (auto c : responseBuffer)
214 {
215 msgData += std::format("{:#x} ", c);
216 }
217 lg2::info(msgData.c_str());
218 }
219
220 stdplus::fd::writeExact(uart, out);
221 }
222 catch (const std::exception& e)
223 {
224 lg2::error("IPMI Response failure: {MSG}", "MSG", e);
225
226 return -1;
227 }
228
229 return out.size();
230}
231
232/**
233 * @brief Read function
234 */
235void SerialChannel::read(stdplus::Fd& uart, sdbusplus::bus_t& bus,
236 sdbusplus::slot_t& outstanding)
237{
238 std::array<uint8_t, ipmiSerialMaxBufferSize> buffer;
239 auto ipmiSerialPacket = stdplus::fd::read(uart, buffer);
240
241 if (ipmiSerialPacket.empty())
242 {
243 return;
244 }
245
246 if (outstanding)
247 {
248 lg2::error("Canceling outstanding request \n");
249 outstanding = sdbusplus::slot_t(nullptr);
250 }
251
252 // process ipmi serial packet
253 if (!consumeIpmiSerialPacket(ipmiSerialPacket, requestBuffer))
254 {
255 lg2::info("Wait for more data ... \n");
256 return;
257 }
258
259 // validate ipmi serial packet length
260 if (requestBuffer.size() <
261 (sizeof(struct IpmiSerialHeader) + ipmiSerialChecksumSize))
262 {
263 lg2::error("Invalid request length, ignoring \n");
264 requestBuffer.clear();
265 return;
266 }
267
268 // validate checksum1
269 if (calculateChecksum(std::span<uint8_t>(requestBuffer.begin(),
270 ipmiSerialConnectionHeaderLength)))
271 {
272 lg2::error("Invalid request checksum 1 \n");
273 requestBuffer.clear();
274 return;
275 }
276
277 // validate checksum2
278 if (calculateChecksum(std::span<uint8_t>(
279 &requestBuffer[ipmiSerialConnectionHeaderLength],
280 requestBuffer.size() - ipmiSerialConnectionHeaderLength)))
281 {
282 lg2::error("Invalid request checksum 2 \n");
283 requestBuffer.clear();
284 return;
285 }
286
287 auto m = bus.new_method_call("xyz.openbmc_project.Ipmi.Host",
288 "/xyz/openbmc_project/Ipmi",
289 "xyz.openbmc_project.Ipmi.Server", "execute");
290
291 std::map<std::string, std::variant<int>> options;
292 struct IpmiSerialHeader* header =
293 reinterpret_cast<struct IpmiSerialHeader*>(requestBuffer.data());
294
295 uint8_t rsAddr = header->rsAddr;
296 uint8_t netFn = header->rsNetFnLUN >> netFnShift;
297 uint8_t lun = header->rsNetFnLUN & lunMask;
298 uint8_t rqAddr = header->rqAddr;
299 uint8_t seq = header->rqSeqLUN >> netFnShift;
300 uint8_t cmd = header->cmd;
301
302 std::span reqSpan{requestBuffer.begin(),
303 requestBuffer.end() -
304 ipmiSerialChecksumSize}; // remove checksum 2
305 m.append(netFn, lun, cmd, reqSpan.subspan(sizeof(IpmiSerialHeader)),
306 options);
307
308 if (verbose)
309 {
310 lg2::info("Read serial request message with len={LEN}, netFn={NETFN}, "
311 "lun={LUN}, cmd={CMD}, seq={SEQ}",
312 "LEN", requestBuffer.size(), "NETFN", netFn, "LUN", lun,
313 "CMD", cmd, "SEQ", seq);
314
315 std::string msgData = "Rx: ";
316 for (auto c : requestBuffer)
317 {
318 msgData += std::format("{:#x} ", c);
319 }
320 lg2::info(msgData.c_str());
321 }
322
323 outstanding = m.call_async(stdplus::exception::ignore(
324 [&outstanding, this, &uart, _rsAddr{rsAddr}, _rqAddr{rqAddr},
325 _seq{seq}](sdbusplus::message_t&& m) {
326 outstanding = sdbusplus::slot_t(nullptr);
327
328 if (write(uart, _rsAddr, _rqAddr, _seq, std::move(m)) < 0)
329 {
330 lg2::error(
331 "Occur an error while attempting to send the response.");
332 }
333 }));
334
335 requestBuffer.clear();
336
337 return;
338}
339
340} // namespace serialbridge