1 #include "config.h"
2
3 #include "occ_command.hpp"
4
5 #include <errno.h>
6 #include <fcntl.h>
7 #include <unistd.h>
8
9 #include <org/open_power/OCC/Device/error.hpp>
10 #include <phosphor-logging/lg2.hpp>
11
12 #include <algorithm>
13 #include <format>
14 #include <string>
15
16 // #define TRACE_PACKETS
17
18 namespace open_power
19 {
20 namespace occ
21 {
22
23 // Trace block of data in hex
dump_hex(const std::vector<std::uint8_t> & data,const unsigned int data_len)24 void dump_hex(const std::vector<std::uint8_t>& data,
25 const unsigned int data_len)
26 {
27 unsigned int dump_length = data.size();
28 if ((data_len > 0) && (data_len < dump_length))
29 {
30 dump_length = data_len;
31 }
32 std::string s;
33 for (uint32_t i = 0; i < dump_length; i++)
34 {
35 if (i % 16 == 0)
36 {
37 s += std::format("{:04X}: ", i);
38 }
39 else if (i % 4 == 0)
40 {
41 s += " ";
42 }
43
44 s += std::format("{:02X}", data.at(i));
45
46 if ((i % 16 == 15) || (i == (dump_length - 1)))
47 {
48 lg2::info("{STRING}", "STRING", s);
49 s.clear();
50 }
51 }
52 }
53
OccCommand(uint8_t instance,const char * path)54 OccCommand::OccCommand(uint8_t instance, const char* path) :
55 occInstance(instance), path(path),
56 devicePath(OCC_DEV_PATH + std::to_string((this->path.back() - '0') + 1)),
57 activeStatusSignal(
58 utils::getBus(),
59 sdbusRule::propertiesChanged(path, "org.open_power.OCC.Status"),
60 std::bind(std::mem_fn(&OccCommand::activeStatusEvent), this,
61 std::placeholders::_1))
62 {
63 lg2::debug("OccCommand::OccCommand(path={PATH}, devicePath={DEVICE}",
64 "PATH", this->path, "DEVICE", devicePath);
65 }
66
openDevice()67 void OccCommand::openDevice()
68 {
69 using namespace sdbusplus::org::open_power::OCC::Device::Error;
70
71 lg2::debug("OccCommand::openDevice: calling open {PATH}", "PATH",
72 devicePath);
73 fd = open(devicePath.c_str(), O_RDWR | O_NONBLOCK);
74 if (fd < 0)
75 {
76 const int openErrno = errno;
77 lg2::error(
78 "OccCommand::openDevice: open failed (errno={ERR}, path={PATH})",
79 "ERR", openErrno, "PATH", devicePath);
80 }
81 else
82 {
83 lg2::debug("OccCommand::openDevice: open success");
84 }
85
86 return;
87 }
88
closeDevice()89 void OccCommand::closeDevice()
90 {
91 if (fd >= 0)
92 {
93 lg2::debug("OccCommand::closeDevice: calling close()");
94 close(fd);
95 fd = -1;
96 }
97 }
98
send(const std::vector<uint8_t> & command,std::vector<uint8_t> & response)99 CmdStatus OccCommand::send(const std::vector<uint8_t>& command,
100 std::vector<uint8_t>& response)
101 {
102 using namespace sdbusplus::org::open_power::OCC::Device::Error;
103 CmdStatus status = CmdStatus::FAILURE;
104
105 response.clear();
106
107 lg2::debug("OccCommand::send: calling openDevice()");
108 openDevice();
109
110 if (fd < 0)
111 {
112 // OCC is inactive; empty response
113 return CmdStatus::COMM_FAILURE;
114 }
115
116 const uint8_t cmd_type = command[0];
117 #ifdef TRACE_PACKETS
118 lg2::info("OCC{INST}: Sending {CMD} command (length={LEN}, {PATH})", "INST",
119 occInstance, "CMD", lg2::hex, cmd_type, "LEN", command.size(),
120 "PATH", devicePath);
121 dump_hex(command);
122 #else
123 lg2::debug("OccCommand::send: calling write()");
124 #endif
125
126 int retries = 1; // Allow a retry if a command fails to get valid response
127 do
128 {
129 auto rc = write(fd, command.data(), command.size());
130 const int writeErrno = errno;
131 if ((rc < 0) || (rc != (int)command.size()))
132 {
133 lg2::error(
134 "OccCommand::send: write(OCC{INST}, command:{CMD}) failed with errno={ERR} (retries={RETRIES})",
135 "INST", occInstance, "CMD", lg2::hex, cmd_type, "ERR",
136 writeErrno, "RETRIES", retries);
137 status = CmdStatus::COMM_FAILURE;
138 // retry if available
139 continue;
140 }
141 else
142 {
143 lg2::debug("OccCommand::send: write succeeded");
144 }
145
146 // Now read the response. This would be the content of occ-sram
147 while (1)
148 {
149 uint8_t data{};
150 auto len = read(fd, &data, sizeof(data));
151 const int readErrno = errno;
152 if (len > 0)
153 {
154 response.emplace_back(data);
155 }
156 else if (len < 0 && readErrno == EAGAIN)
157 {
158 // We may have data coming still.
159 // This driver does not need a sleep for a retry.
160 continue;
161 }
162 else if (len == 0)
163 {
164 lg2::debug("OccCommand::send: read completed");
165 // We have read all that we can.
166 status = CmdStatus::SUCCESS;
167 break;
168 }
169 else
170 {
171 lg2::error(
172 "OccCommand::send: read(OCC{INST}, command:{CMD) failed with errno={ERR} (rspSize={LEN}, retries={RETRIES})",
173 "INST", occInstance, "CMD", lg2::hex, cmd_type, "ERR",
174 readErrno, "LEN", response.size(), "RETRIES", retries);
175 status = CmdStatus::COMM_FAILURE;
176 break;
177 }
178 }
179 if (status != CmdStatus::SUCCESS)
180 {
181 // retry if available
182 continue;
183 }
184
185 if (response.size() > 2)
186 {
187 #ifdef TRACE_PACKETS
188 lg2::info(
189 "OCC{INST}: Received {CMD} response (length={LEN} w/checksum)",
190 "INST", occInstance, "CMD", lg2::hex, cmd_type, "LEN",
191 response.size());
192 dump_hex(response, 64);
193 #endif
194
195 // Validate checksum (last 2 bytes of response)
196 const unsigned int csumIndex = response.size() - 2;
197 const uint32_t rspChecksum =
198 (response[csumIndex] << 8) + response[csumIndex + 1];
199 // OCC checksum is the 2 byte sum of data (ignoring overflow)
200 uint16_t calcChecksum = 0;
201 for (unsigned int index = 0; index < csumIndex; ++index)
202 {
203 calcChecksum += response[index];
204 }
205 if (calcChecksum != rspChecksum)
206 {
207 lg2::error("OCC{INST}: Checksum Mismatch: response "
208 "{RCVD}, calculated {EXPECT}",
209 "INST", occInstance, "RCVD", rspChecksum, "EXPECT",
210 calcChecksum);
211 dump_hex(response);
212 status = CmdStatus::COMM_FAILURE;
213 }
214 else
215 {
216 // Validate response was for the specified command
217 if (command[0] == response[1])
218 {
219 // Valid response received
220
221 // Strip off 2 byte checksum
222 response.pop_back();
223 response.pop_back();
224 break;
225 }
226 else
227 {
228 lg2::error("OccCommand::send: Response command mismatch "
229 "(sent: "
230 "{CMD}, rsp: {STATUS}, rsp seq#: {SEQ}",
231 "CMD", lg2::hex, command[0], "STATUS", lg2::hex,
232 response[1], "SEQ", lg2::hex, response[0]);
233 dump_hex(response, 64);
234 }
235 }
236 }
237 else
238 {
239 lg2::error(
240 "OccCommand::send: Invalid OCC{INST} response length: {LEN}",
241 "INST", occInstance, "LEN", response.size());
242 status = CmdStatus::FAILURE;
243 dump_hex(response);
244 }
245
246 if (retries > 0)
247 {
248 lg2::error("OccCommand::send: Command will be retried");
249 response.clear();
250 }
251 } while (retries-- > 0);
252
253 closeDevice();
254
255 return status;
256 }
257
258 // Called at OCC Status change signal
activeStatusEvent(sdbusplus::message_t & msg)259 void OccCommand::activeStatusEvent(sdbusplus::message_t& msg)
260 {
261 std::string statusInterface;
262 std::map<std::string, std::variant<bool>> msgData;
263 msg.read(statusInterface, msgData);
264
265 auto propertyMap = msgData.find("OccActive");
266 if (propertyMap != msgData.end())
267 {
268 // Extract the OccActive property
269 if (std::get<bool>(propertyMap->second))
270 {
271 occActive = true;
272 }
273 else
274 {
275 occActive = false;
276
277 this->closeDevice();
278 }
279 }
280 return;
281 }
282
283 } // namespace occ
284 } // namespace open_power
285