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