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