xref: /openbmc/openpower-occ-control/occ_command.cpp (revision a8857c50864a60c372fdf631865a32e6eff53330)
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, sdbusplus::bus::bus& bus,
60                        const char* path) :
61     occInstance(instance),
62     path(path),
63     devicePath(OCC_DEV_PATH + std::to_string((this->path.back() - '0') + 1)),
64     activeStatusSignal(
65         bus, 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     auto rc = write(fd, command.data(), command.size());
144     if ((rc < 0) || (rc != (int)command.size()))
145     {
146         const int write_errno = errno;
147         log<level::ERR>("OccCommand::send: write failed");
148         // This would log and terminate since its not handled.
149         elog<WriteFailure>(
150             phosphor::logging::org::open_power::OCC::Device::WriteFailure::
151                 CALLOUT_ERRNO(write_errno),
152             phosphor::logging::org::open_power::OCC::Device::WriteFailure::
153                 CALLOUT_DEVICE_PATH(devicePath.c_str()));
154     }
155     else
156     {
157         log<level::DEBUG>("OccCommand::send: write succeeded");
158     }
159 
160     // Now read the response. This would be the content of occ-sram
161     while (1)
162     {
163         uint8_t data{};
164         auto len = read(fd, &data, sizeof(data));
165         const int read_errno = errno;
166         if (len > 0)
167         {
168             response.emplace_back(data);
169         }
170         else if (len < 0 && read_errno == EAGAIN)
171         {
172             // We may have data coming still.
173             // This driver does not need a sleep for a retry.
174             continue;
175         }
176         else if (len == 0)
177         {
178             log<level::DEBUG>("OccCommand::send: read completed");
179             // We have read all that we can.
180             status = CmdStatus::SUCCESS;
181             break;
182         }
183         else
184         {
185             log<level::ERR>("OccCommand::send: read failed");
186             // This would log and terminate since its not handled.
187             elog<ReadFailure>(
188                 phosphor::logging::org::open_power::OCC::Device::ReadFailure::
189                     CALLOUT_ERRNO(read_errno),
190                 phosphor::logging::org::open_power::OCC::Device::ReadFailure::
191                     CALLOUT_DEVICE_PATH(devicePath.c_str()));
192         }
193     }
194 
195     if (response.size() > 2)
196     {
197 #ifdef TRACE_PACKETS
198         log<level::INFO>(
199             fmt::format(
200                 "OCC{}: Received 0x{:02X} response (length={} w/checksum)",
201                 occInstance, cmd_type, response.size())
202                 .c_str());
203         dump_hex(response, 64);
204 #endif
205 
206         // Validate checksum (last 2 bytes of response)
207         const unsigned int csumIndex = response.size() - 2;
208         const uint32_t rspChecksum =
209             (response[csumIndex] << 8) + response[csumIndex + 1];
210         uint32_t calcChecksum = 0;
211         for (unsigned int index = 0; index < csumIndex; ++index)
212         {
213             calcChecksum += response[index];
214         }
215         while (calcChecksum > 0xFFFF)
216         {
217             calcChecksum = (calcChecksum & 0xFFFF) + (calcChecksum >> 16);
218         }
219         if (calcChecksum != rspChecksum)
220         {
221             log<level::ERR>(fmt::format("OCC{}: Checksum Mismatch: response "
222                                         "0x{:04X}, calculated 0x{:04X}",
223                                         occInstance, rspChecksum, calcChecksum)
224                                 .c_str());
225             dump_hex(response);
226             status = CmdStatus::INVALID_CHECKSUM;
227         }
228         else
229         {
230             // Strip off 2 byte checksum
231             response.pop_back();
232             response.pop_back();
233         }
234     }
235     else
236     {
237         log<level::ERR>(
238             fmt::format("OccCommand::send: Invalid OCC{} response length: {}",
239                         occInstance, response.size())
240                 .c_str());
241         status = CmdStatus::FAILURE;
242         dump_hex(response);
243     }
244 
245     closeDevice();
246 
247     return status;
248 }
249 
250 // Called at OCC Status change signal
251 void OccCommand::activeStatusEvent(sdbusplus::message::message& msg)
252 {
253     std::string statusInterface;
254     std::map<std::string, std::variant<bool>> msgData;
255     msg.read(statusInterface, msgData);
256 
257     auto propertyMap = msgData.find("OccActive");
258     if (propertyMap != msgData.end())
259     {
260         // Extract the OccActive property
261         if (std::get<bool>(propertyMap->second))
262         {
263             occActive = true;
264         }
265         else
266         {
267             occActive = false;
268 
269             this->closeDevice();
270         }
271     }
272     return;
273 }
274 
275 } // namespace occ
276 } // namespace open_power
277