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/oem/ibm/file_io.h> 20 #include <unistd.h> 21 22 #include <phosphor-logging/lg2.hpp> 23 24 #include <fstream> 25 26 namespace openpower::pels 27 { 28 29 using namespace sdeventplus; 30 using namespace sdeventplus::source; 31 32 constexpr auto eidPath = "/usr/share/pldm/host_eid"; 33 constexpr mctp_eid_t defaultEIDValue = 9; 34 35 constexpr uint16_t pelFileType = 0; 36 37 PLDMInterface::~PLDMInterface() 38 { 39 freeIID(); 40 pldm_instance_db_destroy(_pldm_idb); 41 closeFD(); 42 } 43 44 void PLDMInterface::closeFD() 45 { 46 if (_fd >= 0) 47 { 48 close(_fd); 49 _fd = -1; 50 } 51 } 52 53 void PLDMInterface::readEID() 54 { 55 _eid = defaultEIDValue; 56 57 std::ifstream eidFile{eidPath}; 58 if (!eidFile.good()) 59 { 60 lg2::error("Could not open host EID file"); 61 } 62 else 63 { 64 std::string eid; 65 eidFile >> eid; 66 if (!eid.empty()) 67 { 68 _eid = atoi(eid.c_str()); 69 } 70 else 71 { 72 lg2::error("EID file was empty"); 73 } 74 } 75 } 76 77 void PLDMInterface::open() 78 { 79 _fd = pldm_open(); 80 if (_fd < 0) 81 { 82 auto e = errno; 83 lg2::error("pldm_open failed. errno = {ERRNO}, rc = {RC}", "ERRNO", e, 84 "RC", _fd); 85 throw std::runtime_error{"pldm_open failed"}; 86 } 87 } 88 89 void PLDMInterface::startCommand() 90 { 91 try 92 { 93 closeFD(); 94 95 open(); 96 97 registerReceiveCallback(); 98 99 doSend(); 100 101 _receiveTimer.restartOnce(_receiveTimeout); 102 } 103 catch (const std::exception& e) 104 { 105 lg2::error("startCommand exception: {ERROR}", "ERROR", e); 106 107 cleanupCmd(); 108 109 throw; 110 } 111 } 112 113 void PLDMInterface::allocIID() 114 { 115 if (_instanceID) 116 { 117 return; 118 } 119 120 pldm_instance_id_t iid = 0; 121 auto rc = pldm_instance_id_alloc(_pldm_idb, _eid, &iid); 122 123 if (rc == -EAGAIN) 124 { 125 throw std::runtime_error("No free instance ids"); 126 } 127 else if (rc) 128 { 129 throw std::system_category().default_error_condition(rc); 130 } 131 132 _instanceID = iid; 133 } 134 135 void PLDMInterface::freeIID() 136 { 137 if (!_instanceID) 138 { 139 return; 140 } 141 142 auto rc = pldm_instance_id_free(_pldm_idb, _eid, *_instanceID); 143 144 if (rc == -EINVAL) 145 { 146 throw std::runtime_error("Instance ID " + std::to_string(*_instanceID) + 147 " for TID " + std::to_string(_eid) + 148 " was not previously allocated"); 149 } 150 else if (rc) 151 { 152 throw std::system_category().default_error_condition(rc); 153 } 154 155 _instanceID = std::nullopt; 156 } 157 158 CmdStatus PLDMInterface::sendNewLogCmd(uint32_t id, uint32_t size) 159 { 160 _pelID = id; 161 _pelSize = size; 162 _inProgress = true; 163 164 try 165 { 166 // Allocate the instance ID, as needed. 167 if (!_instanceID) 168 { 169 allocIID(); 170 } 171 startCommand(); 172 } 173 catch (const std::exception& e) 174 { 175 _inProgress = false; 176 return CmdStatus::failure; 177 } 178 179 return CmdStatus::success; 180 } 181 182 void PLDMInterface::registerReceiveCallback() 183 { 184 _source = std::make_unique<IO>( 185 _event, _fd, EPOLLIN, 186 std::bind(std::mem_fn(&PLDMInterface::receive), this, 187 std::placeholders::_1, std::placeholders::_2, 188 std::placeholders::_3)); 189 } 190 191 void PLDMInterface::doSend() 192 { 193 std::array<uint8_t, sizeof(pldm_msg_hdr) + sizeof(pelFileType) + 194 sizeof(_pelID) + sizeof(uint64_t)> 195 requestMsg; 196 197 auto request = reinterpret_cast<pldm_msg*>(requestMsg.data()); 198 199 auto rc = encode_new_file_req(*_instanceID, pelFileType, _pelID, _pelSize, 200 request); 201 if (rc != PLDM_SUCCESS) 202 { 203 lg2::error("encode_new_file_req failed, rc = {RC}", "RC", rc); 204 throw std::runtime_error{"encode_new_file_req failed"}; 205 } 206 207 rc = pldm_send(_eid, _fd, requestMsg.data(), requestMsg.size()); 208 if (rc < 0) 209 { 210 auto e = errno; 211 lg2::error("pldm_send failed, rc = {RC}, errno = {ERRNO}", "RC", rc, 212 "ERRNO", e); 213 throw std::runtime_error{"pldm_send failed"}; 214 } 215 } 216 217 void PLDMInterface::receive(IO& /*io*/, int fd, uint32_t revents) 218 { 219 if (!(revents & EPOLLIN)) 220 { 221 return; 222 } 223 224 uint8_t* responseMsg = nullptr; 225 size_t responseSize = 0; 226 ResponseStatus status = ResponseStatus::success; 227 228 auto rc = pldm_recv(_eid, fd, *_instanceID, &responseMsg, &responseSize); 229 if (rc < 0) 230 { 231 if (rc == PLDM_REQUESTER_INSTANCE_ID_MISMATCH) 232 { 233 // We got a response to someone else's message. Ignore it. 234 return; 235 } 236 else if (rc == PLDM_REQUESTER_NOT_RESP_MSG) 237 { 238 // Due to the MCTP loopback, we may get notified of the message 239 // we just sent. 240 return; 241 } 242 243 auto e = errno; 244 lg2::error("pldm_recv failed, rc = {RC}, errno = {ERRNO}", "RC", 245 static_cast<std::underlying_type_t<pldm_requester_rc_t>>(rc), 246 "ERRNO", e); 247 status = ResponseStatus::failure; 248 249 responseMsg = nullptr; 250 } 251 252 cleanupCmd(); 253 254 // Can't use this instance ID anymore. 255 freeIID(); 256 257 if (status == ResponseStatus::success) 258 { 259 uint8_t completionCode = 0; 260 auto response = reinterpret_cast<pldm_msg*>(responseMsg); 261 262 auto decodeRC = decode_new_file_resp(response, PLDM_NEW_FILE_RESP_BYTES, 263 &completionCode); 264 if (decodeRC < 0) 265 { 266 lg2::error("decode_new_file_resp failed, rc = {RC}", "RC", 267 decodeRC); 268 status = ResponseStatus::failure; 269 } 270 else 271 { 272 if (completionCode != PLDM_SUCCESS) 273 { 274 lg2::error("Bad PLDM completion code {CODE}", "CODE", 275 completionCode); 276 status = ResponseStatus::failure; 277 } 278 } 279 } 280 281 callResponseFunc(status); 282 283 if (responseMsg) 284 { 285 free(responseMsg); 286 } 287 } 288 289 void PLDMInterface::receiveTimerExpired() 290 { 291 lg2::error("Timed out waiting for PLDM response"); 292 293 // Cleanup, but keep the instance ID because the host didn't 294 // respond so we can still use it. 295 cleanupCmd(); 296 297 callResponseFunc(ResponseStatus::failure); 298 } 299 300 void PLDMInterface::cancelCmd() 301 { 302 freeIID(); 303 cleanupCmd(); 304 } 305 306 void PLDMInterface::cleanupCmd() 307 { 308 _inProgress = false; 309 _source.reset(); 310 311 if (_receiveTimer.isEnabled()) 312 { 313 _receiveTimer.setEnabled(false); 314 } 315 316 closeFD(); 317 } 318 319 } // namespace openpower::pels 320