1 /** 2 * Copyright © 2019 IBM Corporation 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 #include "pldm_interface.hpp" 17 18 #include <libpldm/base.h> 19 #include <libpldm/file_io.h> 20 #include <systemd/sd-bus.h> 21 #include <unistd.h> 22 23 #include <fstream> 24 #include <phosphor-logging/log.hpp> 25 26 namespace openpower::pels 27 { 28 29 namespace service 30 { 31 constexpr auto pldm = "xyz.openbmc_project.PLDM"; 32 } 33 34 namespace object_path 35 { 36 constexpr auto pldm = "/xyz/openbmc_project/pldm"; 37 } 38 39 namespace interface 40 { 41 constexpr auto pldm_requester = "xyz.openbmc_project.PLDM.Requester"; 42 } 43 44 using namespace phosphor::logging; 45 using namespace sdeventplus; 46 using namespace sdeventplus::source; 47 48 constexpr auto eidPath = "/usr/share/pldm/host_eid"; 49 constexpr mctp_eid_t defaultEIDValue = 9; 50 51 constexpr uint16_t pelFileType = 0; 52 53 PLDMInterface::~PLDMInterface() 54 { 55 sd_bus_unref(_bus); 56 closeFD(); 57 } 58 59 void PLDMInterface::closeFD() 60 { 61 if (_fd >= 0) 62 { 63 close(_fd); 64 _fd = -1; 65 } 66 } 67 68 void PLDMInterface::readEID() 69 { 70 _eid = defaultEIDValue; 71 72 std::ifstream eidFile{eidPath}; 73 if (!eidFile.good()) 74 { 75 log<level::ERR>("Could not open host EID file"); 76 } 77 else 78 { 79 std::string eid; 80 eidFile >> eid; 81 if (!eid.empty()) 82 { 83 _eid = atoi(eid.c_str()); 84 } 85 else 86 { 87 log<level::ERR>("EID file was empty"); 88 } 89 } 90 } 91 92 void PLDMInterface::open() 93 { 94 _fd = pldm_open(); 95 if (_fd < 0) 96 { 97 auto e = errno; 98 log<level::ERR>("pldm_open failed", entry("ERRNO=%d", e), 99 entry("RC=%d\n", _fd)); 100 throw std::exception{}; 101 } 102 } 103 104 void PLDMInterface::instanceIDCallback(sd_bus_message* msg) 105 { 106 if (!_inProgress) 107 { 108 // A cancelCmd was run, just return 109 log<level::INFO>( 110 "A command was canceled while waiting for the instance ID"); 111 return; 112 } 113 114 bool failed = false; 115 116 auto rc = sd_bus_message_get_errno(msg); 117 if (rc) 118 { 119 log<level::ERR>("GetInstanceId D-Bus method failed", 120 entry("ERRNO=%d", rc)); 121 failed = true; 122 } 123 else 124 { 125 uint8_t id; 126 rc = sd_bus_message_read_basic(msg, 'y', &id); 127 if (rc < 0) 128 { 129 log<level::ERR>("Could not read instance ID out of message", 130 entry("ERROR=%d", rc)); 131 failed = true; 132 } 133 else 134 { 135 _instanceID = id; 136 } 137 } 138 139 if (failed) 140 { 141 _inProgress = false; 142 callResponseFunc(ResponseStatus::failure); 143 } 144 else 145 { 146 startCommand(); 147 } 148 } 149 150 int iidCallback(sd_bus_message* msg, void* data, sd_bus_error* err) 151 { 152 auto* interface = static_cast<PLDMInterface*>(data); 153 interface->instanceIDCallback(msg); 154 return 0; 155 } 156 157 void PLDMInterface::startCommand() 158 { 159 try 160 { 161 closeFD(); 162 163 open(); 164 165 registerReceiveCallback(); 166 167 doSend(); 168 169 _receiveTimer.restartOnce(_receiveTimeout); 170 } 171 catch (const std::exception& e) 172 { 173 cleanupCmd(); 174 175 callResponseFunc(ResponseStatus::failure); 176 } 177 } 178 179 void PLDMInterface::startReadInstanceID() 180 { 181 auto rc = sd_bus_call_method_async( 182 _bus, NULL, service::pldm, object_path::pldm, interface::pldm_requester, 183 "GetInstanceId", iidCallback, this, "y", _eid); 184 185 if (rc < 0) 186 { 187 log<level::ERR>("Error calling sd_bus_call_method_async", 188 entry("RC=%d", rc), entry("MSG=%s", strerror(-rc))); 189 throw std::exception{}; 190 } 191 } 192 193 CmdStatus PLDMInterface::sendNewLogCmd(uint32_t id, uint32_t size) 194 { 195 _pelID = id; 196 _pelSize = size; 197 _inProgress = true; 198 199 try 200 { 201 // Kick off the async call to get the instance ID if 202 // necessary, otherwise start the command itself. 203 if (!_instanceID) 204 { 205 startReadInstanceID(); 206 } 207 else 208 { 209 startCommand(); 210 } 211 } 212 catch (const std::exception& e) 213 { 214 _inProgress = false; 215 return CmdStatus::failure; 216 } 217 218 return CmdStatus::success; 219 } 220 221 void PLDMInterface::registerReceiveCallback() 222 { 223 _source = std::make_unique<IO>( 224 _event, _fd, EPOLLIN, 225 std::bind(std::mem_fn(&PLDMInterface::receive), this, 226 std::placeholders::_1, std::placeholders::_2, 227 std::placeholders::_3)); 228 } 229 230 void PLDMInterface::doSend() 231 { 232 std::array<uint8_t, sizeof(pldm_msg_hdr) + sizeof(pelFileType) + 233 sizeof(_pelID) + sizeof(uint64_t)> 234 requestMsg; 235 236 auto request = reinterpret_cast<pldm_msg*>(requestMsg.data()); 237 238 auto rc = encode_new_file_req(*_instanceID, pelFileType, _pelID, _pelSize, 239 request); 240 if (rc != PLDM_SUCCESS) 241 { 242 log<level::ERR>("encode_new_file_req failed", entry("RC=%d", rc)); 243 throw std::exception{}; 244 } 245 246 rc = pldm_send(_eid, _fd, requestMsg.data(), requestMsg.size()); 247 if (rc < 0) 248 { 249 auto e = errno; 250 log<level::ERR>("pldm_send failed", entry("RC=%d", rc), 251 entry("ERRNO=%d", e)); 252 253 throw std::exception{}; 254 } 255 } 256 257 void PLDMInterface::receive(IO& io, int fd, uint32_t revents) 258 { 259 if (!(revents & EPOLLIN)) 260 { 261 return; 262 } 263 264 uint8_t* responseMsg = nullptr; 265 size_t responseSize = 0; 266 ResponseStatus status = ResponseStatus::success; 267 268 auto rc = pldm_recv(_eid, fd, *_instanceID, &responseMsg, &responseSize); 269 if (rc < 0) 270 { 271 if (rc == PLDM_REQUESTER_INSTANCE_ID_MISMATCH) 272 { 273 // We got a response to someone else's message. Ignore it. 274 return; 275 } 276 else if (rc == PLDM_REQUESTER_NOT_RESP_MSG) 277 { 278 // Due to the MCTP loopback, we may get notified of the message 279 // we just sent. 280 return; 281 } 282 283 auto e = errno; 284 log<level::ERR>("pldm_recv failed", entry("RC=%d", rc), 285 entry("ERRNO=%d", e)); 286 status = ResponseStatus::failure; 287 288 responseMsg = nullptr; 289 } 290 291 cleanupCmd(); 292 293 // Can't use this instance ID anymore. 294 _instanceID = std::nullopt; 295 296 if (status == ResponseStatus::success) 297 { 298 uint8_t completionCode = 0; 299 auto response = reinterpret_cast<pldm_msg*>(responseMsg); 300 301 auto decodeRC = decode_new_file_resp(response, PLDM_NEW_FILE_RESP_BYTES, 302 &completionCode); 303 if (decodeRC < 0) 304 { 305 log<level::ERR>("decode_new_file_resp failed", 306 entry("RC=%d", decodeRC)); 307 status = ResponseStatus::failure; 308 } 309 else 310 { 311 if (completionCode != PLDM_SUCCESS) 312 { 313 log<level::ERR>("Bad PLDM completion code", 314 entry("COMPLETION_CODE=%d", completionCode)); 315 status = ResponseStatus::failure; 316 } 317 } 318 } 319 320 callResponseFunc(status); 321 322 if (responseMsg) 323 { 324 free(responseMsg); 325 } 326 } 327 328 void PLDMInterface::receiveTimerExpired() 329 { 330 log<level::ERR>("Timed out waiting for PLDM response"); 331 332 // Cleanup, but keep the instance ID because the host didn't 333 // respond so we can still use it. 334 cleanupCmd(); 335 336 callResponseFunc(ResponseStatus::failure); 337 } 338 339 void PLDMInterface::cancelCmd() 340 { 341 _instanceID = std::nullopt; 342 cleanupCmd(); 343 } 344 345 void PLDMInterface::cleanupCmd() 346 { 347 _inProgress = false; 348 _source.reset(); 349 350 if (_receiveTimer.isEnabled()) 351 { 352 _receiveTimer.setEnabled(false); 353 } 354 355 closeFD(); 356 } 357 358 } // namespace openpower::pels 359