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