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/elog-errors.hpp>
11 #include <phosphor-logging/elog.hpp>
12 #include <phosphor-logging/log.hpp>
13 
14 #include <algorithm>
15 #include <format>
16 #include <memory>
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
dump_hex(const std::vector<std::uint8_t> & data,const unsigned int data_len)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 += std::format("{:04X}: ", i);
43         }
44         else if (i % 4 == 0)
45         {
46             s += " ";
47         }
48 
49         s += std::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 
OccCommand(uint8_t instance,const char * path)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         std::format("OccCommand::OccCommand(path={}, devicePath={}", this->path,
70                     devicePath)
71             .c_str());
72 }
73 
openDevice()74 void OccCommand::openDevice()
75 {
76     using namespace sdbusplus::org::open_power::OCC::Device::Error;
77 
78     log<level::DEBUG>(
79         std::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 openErrno = errno;
85         log<level::ERR>(
86             std::format(
87                 "OccCommand::openDevice: open failed (errno={}, path={})",
88                 openErrno, devicePath)
89                 .c_str());
90     }
91     else
92     {
93         log<level::DEBUG>("OccCommand::openDevice: open success");
94     }
95 
96     return;
97 }
98 
closeDevice()99 void OccCommand::closeDevice()
100 {
101     if (fd >= 0)
102     {
103         log<level::DEBUG>("OccCommand::closeDevice: calling close()");
104         close(fd);
105         fd = -1;
106     }
107 }
108 
send(const std::vector<uint8_t> & command,std::vector<uint8_t> & response)109 CmdStatus OccCommand::send(const std::vector<uint8_t>& command,
110                            std::vector<uint8_t>& response)
111 {
112     using namespace sdbusplus::org::open_power::OCC::Device::Error;
113     CmdStatus status = CmdStatus::FAILURE;
114 
115     response.clear();
116 
117     log<level::DEBUG>("OccCommand::send: calling openDevice()");
118     openDevice();
119 
120     if (fd < 0)
121     {
122         // OCC is inactive; empty response
123         return CmdStatus::COMM_FAILURE;
124     }
125 
126     const uint8_t cmd_type = command[0];
127 #ifdef TRACE_PACKETS
128     log<level::INFO>(
129         std::format("OCC{}: Sending 0x{:02X} command (length={}, {})",
130                     occInstance, cmd_type, command.size(), devicePath)
131             .c_str());
132     dump_hex(command);
133 #else
134     log<level::DEBUG>("OccCommand::send: calling write()");
135 #endif
136 
137     int retries = 1; // Allow a retry if a command fails to get valid response
138     do
139     {
140         auto rc = write(fd, command.data(), command.size());
141         const int writeErrno = errno;
142         if ((rc < 0) || (rc != (int)command.size()))
143         {
144             log<level::ERR>(
145                 std::format(
146                     "OccCommand::send: write(OCC{}, command:0x{:02X}) failed with errno={} (retries={})",
147                     occInstance, cmd_type, writeErrno, retries)
148                     .c_str());
149             status = CmdStatus::COMM_FAILURE;
150             // retry if available
151             continue;
152         }
153         else
154         {
155             log<level::DEBUG>("OccCommand::send: write succeeded");
156         }
157 
158         // Now read the response. This would be the content of occ-sram
159         while (1)
160         {
161             uint8_t data{};
162             auto len = read(fd, &data, sizeof(data));
163             const int readErrno = errno;
164             if (len > 0)
165             {
166                 response.emplace_back(data);
167             }
168             else if (len < 0 && readErrno == EAGAIN)
169             {
170                 // We may have data coming still.
171                 // This driver does not need a sleep for a retry.
172                 continue;
173             }
174             else if (len == 0)
175             {
176                 log<level::DEBUG>("OccCommand::send: read completed");
177                 // We have read all that we can.
178                 status = CmdStatus::SUCCESS;
179                 break;
180             }
181             else
182             {
183                 log<level::ERR>(
184                     std::format(
185                         "OccCommand::send: read(OCC{}, command:0x{:02X}) failed with errno={} (rspSize={}, retries={})",
186                         occInstance, cmd_type, readErrno, response.size(),
187                         retries)
188                         .c_str());
189                 status = CmdStatus::COMM_FAILURE;
190                 break;
191             }
192         }
193         if (status != CmdStatus::SUCCESS)
194         {
195             // retry if available
196             continue;
197         }
198 
199         if (response.size() > 2)
200         {
201 #ifdef TRACE_PACKETS
202             log<level::INFO>(
203                 std::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             // OCC checksum is the 2 byte sum of data (ignoring overflow)
215             uint16_t calcChecksum = 0;
216             for (unsigned int index = 0; index < csumIndex; ++index)
217             {
218                 calcChecksum += response[index];
219             }
220             if (calcChecksum != rspChecksum)
221             {
222                 log<level::ERR>(
223                     std::format("OCC{}: Checksum Mismatch: response "
224                                 "0x{:04X}, calculated 0x{:04X}",
225                                 occInstance, rspChecksum, calcChecksum)
226                         .c_str());
227                 dump_hex(response);
228                 status = CmdStatus::COMM_FAILURE;
229             }
230             else
231             {
232                 // Validate response was for the specified command
233                 if (command[0] == response[1])
234                 {
235                     // Valid response received
236 
237                     // Strip off 2 byte checksum
238                     response.pop_back();
239                     response.pop_back();
240                     break;
241                 }
242                 else
243                 {
244                     log<level::ERR>(
245                         std::format(
246                             "OccCommand::send: Response command mismatch "
247                             "(sent: "
248                             "0x{:02X}, rsp: 0x{:02X}, rsp seq#: 0x{:02X}",
249                             command[0], response[1], response[0])
250                             .c_str());
251                     dump_hex(response, 64);
252                 }
253             }
254         }
255         else
256         {
257             log<level::ERR>(
258                 std::format(
259                     "OccCommand::send: Invalid OCC{} response length: {}",
260                     occInstance, response.size())
261                     .c_str());
262             status = CmdStatus::FAILURE;
263             dump_hex(response);
264         }
265 
266         if (retries > 0)
267         {
268             log<level::ERR>("OccCommand::send: Command will be retried");
269             response.clear();
270         }
271     } while (retries-- > 0);
272 
273     closeDevice();
274 
275     return status;
276 }
277 
278 // Called at OCC Status change signal
activeStatusEvent(sdbusplus::message_t & msg)279 void OccCommand::activeStatusEvent(sdbusplus::message_t& msg)
280 {
281     std::string statusInterface;
282     std::map<std::string, std::variant<bool>> msgData;
283     msg.read(statusInterface, msgData);
284 
285     auto propertyMap = msgData.find("OccActive");
286     if (propertyMap != msgData.end())
287     {
288         // Extract the OccActive property
289         if (std::get<bool>(propertyMap->second))
290         {
291             occActive = true;
292         }
293         else
294         {
295             occActive = false;
296 
297             this->closeDevice();
298         }
299     }
300     return;
301 }
302 
303 } // namespace occ
304 } // namespace open_power
305