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