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