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 <phosphor-logging/lg2.hpp> 24 25 #include <fstream> 26 27 namespace openpower::pels 28 { 29 30 namespace service 31 { 32 constexpr auto pldm = "xyz.openbmc_project.PLDM"; 33 } 34 35 namespace object_path 36 { 37 constexpr auto pldm = "/xyz/openbmc_project/pldm"; 38 } 39 40 namespace interface 41 { 42 constexpr auto pldm_requester = "xyz.openbmc_project.PLDM.Requester"; 43 } 44 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 lg2::error("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 lg2::error("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 lg2::error("pldm_open failed. errno = {ERRNO}, rc = {RC}", "ERRNO", e, 99 "RC", _fd); 100 throw std::runtime_error{"pldm_open failed"}; 101 } 102 } 103 104 void PLDMInterface::instanceIDCallback(sd_bus_message* msg) 105 { 106 if (!_inProgress) 107 { 108 lg2::info("A command was canceled while waiting for the instance ID"); 109 return; 110 } 111 112 bool failed = false; 113 114 auto rc = sd_bus_message_get_errno(msg); 115 if (rc) 116 { 117 lg2::error("GetInstanceId D-Bus method failed, rc = {RC}", "RC", rc); 118 failed = true; 119 } 120 else 121 { 122 uint8_t id; 123 rc = sd_bus_message_read_basic(msg, 'y', &id); 124 if (rc < 0) 125 { 126 lg2::error("Could not read instance ID out of message, rc = {RC}", 127 "RC", rc); 128 failed = true; 129 } 130 else 131 { 132 _instanceID = id; 133 } 134 } 135 136 if (failed) 137 { 138 _inProgress = false; 139 callResponseFunc(ResponseStatus::failure); 140 } 141 else 142 { 143 try 144 { 145 startCommand(); 146 } 147 catch (const std::exception& e) 148 { 149 callResponseFunc(ResponseStatus::failure); 150 } 151 } 152 } 153 154 int iidCallback(sd_bus_message* msg, void* data, sd_bus_error* /*err*/) 155 { 156 auto* interface = static_cast<PLDMInterface*>(data); 157 interface->instanceIDCallback(msg); 158 return 0; 159 } 160 161 void PLDMInterface::startCommand() 162 { 163 try 164 { 165 closeFD(); 166 167 open(); 168 169 registerReceiveCallback(); 170 171 doSend(); 172 173 _receiveTimer.restartOnce(_receiveTimeout); 174 } 175 catch (const std::exception& e) 176 { 177 lg2::error("startCommand exception: {ERROR}", "ERROR", e); 178 179 cleanupCmd(); 180 181 throw; 182 } 183 } 184 185 void PLDMInterface::startReadInstanceID() 186 { 187 auto rc = sd_bus_call_method_async( 188 _bus, NULL, service::pldm, object_path::pldm, interface::pldm_requester, 189 "GetInstanceId", iidCallback, this, "y", _eid); 190 191 if (rc < 0) 192 { 193 lg2::error( 194 "Error calling sd_bus_call_method_async, rc = {RC}, msg = {MSG}", 195 "RC", rc, "MSG", strerror(-rc)); 196 throw std::runtime_error{"sd_bus_call_method_async failed"}; 197 } 198 } 199 200 CmdStatus PLDMInterface::sendNewLogCmd(uint32_t id, uint32_t size) 201 { 202 _pelID = id; 203 _pelSize = size; 204 _inProgress = true; 205 206 try 207 { 208 // Kick off the async call to get the instance ID if 209 // necessary, otherwise start the command itself. 210 if (!_instanceID) 211 { 212 startReadInstanceID(); 213 } 214 else 215 { 216 startCommand(); 217 } 218 } 219 catch (const std::exception& e) 220 { 221 _inProgress = false; 222 return CmdStatus::failure; 223 } 224 225 return CmdStatus::success; 226 } 227 228 void PLDMInterface::registerReceiveCallback() 229 { 230 _source = std::make_unique<IO>( 231 _event, _fd, EPOLLIN, 232 std::bind(std::mem_fn(&PLDMInterface::receive), this, 233 std::placeholders::_1, std::placeholders::_2, 234 std::placeholders::_3)); 235 } 236 237 void PLDMInterface::doSend() 238 { 239 std::array<uint8_t, sizeof(pldm_msg_hdr) + sizeof(pelFileType) + 240 sizeof(_pelID) + sizeof(uint64_t)> 241 requestMsg; 242 243 auto request = reinterpret_cast<pldm_msg*>(requestMsg.data()); 244 245 auto rc = encode_new_file_req(*_instanceID, pelFileType, _pelID, _pelSize, 246 request); 247 if (rc != PLDM_SUCCESS) 248 { 249 lg2::error("encode_new_file_req failed, rc = {RC}", "RC", rc); 250 throw std::runtime_error{"encode_new_file_req failed"}; 251 } 252 253 rc = pldm_send(_eid, _fd, requestMsg.data(), requestMsg.size()); 254 if (rc < 0) 255 { 256 auto e = errno; 257 lg2::error("pldm_send failed, rc = {RC}, errno = {ERRNO}", "RC", rc, 258 "ERRNO", e); 259 throw std::runtime_error{"pldm_send failed"}; 260 } 261 } 262 263 void PLDMInterface::receive(IO& /*io*/, int fd, uint32_t revents) 264 { 265 if (!(revents & EPOLLIN)) 266 { 267 return; 268 } 269 270 uint8_t* responseMsg = nullptr; 271 size_t responseSize = 0; 272 ResponseStatus status = ResponseStatus::success; 273 274 auto rc = pldm_recv(_eid, fd, *_instanceID, &responseMsg, &responseSize); 275 if (rc < 0) 276 { 277 if (rc == PLDM_REQUESTER_INSTANCE_ID_MISMATCH) 278 { 279 // We got a response to someone else's message. Ignore it. 280 return; 281 } 282 else if (rc == PLDM_REQUESTER_NOT_RESP_MSG) 283 { 284 // Due to the MCTP loopback, we may get notified of the message 285 // we just sent. 286 return; 287 } 288 289 auto e = errno; 290 lg2::error("pldm_recv failed, rc = {RC}, errno = {ERRNO}", "RC", 291 static_cast<std::underlying_type_t<pldm_requester_rc_t>>(rc), 292 "ERRNO", e); 293 status = ResponseStatus::failure; 294 295 responseMsg = nullptr; 296 } 297 298 cleanupCmd(); 299 300 // Can't use this instance ID anymore. 301 _instanceID = std::nullopt; 302 303 if (status == ResponseStatus::success) 304 { 305 uint8_t completionCode = 0; 306 auto response = reinterpret_cast<pldm_msg*>(responseMsg); 307 308 auto decodeRC = decode_new_file_resp(response, PLDM_NEW_FILE_RESP_BYTES, 309 &completionCode); 310 if (decodeRC < 0) 311 { 312 lg2::error("decode_new_file_resp failed, rc = {RC}", "RC", 313 decodeRC); 314 status = ResponseStatus::failure; 315 } 316 else 317 { 318 if (completionCode != PLDM_SUCCESS) 319 { 320 lg2::error("Bad PLDM completion code {CODE}", "CODE", 321 completionCode); 322 status = ResponseStatus::failure; 323 } 324 } 325 } 326 327 callResponseFunc(status); 328 329 if (responseMsg) 330 { 331 free(responseMsg); 332 } 333 } 334 335 void PLDMInterface::receiveTimerExpired() 336 { 337 lg2::error("Timed out waiting for PLDM response"); 338 339 // Cleanup, but keep the instance ID because the host didn't 340 // respond so we can still use it. 341 cleanupCmd(); 342 343 callResponseFunc(ResponseStatus::failure); 344 } 345 346 void PLDMInterface::cancelCmd() 347 { 348 _instanceID = std::nullopt; 349 cleanupCmd(); 350 } 351 352 void PLDMInterface::cleanupCmd() 353 { 354 _inProgress = false; 355 _source.reset(); 356 357 if (_receiveTimer.isEnabled()) 358 { 359 _receiveTimer.setEnabled(false); 360 } 361 362 closeFD(); 363 } 364 365 } // namespace openpower::pels 366