1 #include "config.h" 2 3 #include "selutility.hpp" 4 5 #include <ipmid/api.hpp> 6 #include <ipmid/types.hpp> 7 #include <ipmid/utils.hpp> 8 #include <phosphor-logging/elog-errors.hpp> 9 #include <xyz/openbmc_project/Common/error.hpp> 10 11 #include <charconv> 12 #include <chrono> 13 #include <filesystem> 14 #include <vector> 15 16 extern const ipmi::sensor::InvObjectIDMap invSensors; 17 using namespace phosphor::logging; 18 using InternalFailure = 19 sdbusplus::error::xyz::openbmc_project::common::InternalFailure; 20 21 namespace 22 { 23 24 constexpr auto systemEventRecord = 0x02; 25 constexpr auto generatorID = 0x2000; 26 constexpr auto eventMsgRevision = 0x04; 27 constexpr auto assertEvent = 0x00; 28 constexpr auto deassertEvent = 0x80; 29 constexpr auto selDataSize = 3; 30 constexpr auto oemCDDataSize = 9; 31 constexpr auto oemEFDataSize = 13; 32 33 constexpr auto propAdditionalData = "AdditionalData"; 34 constexpr auto propResolved = "Resolved"; 35 36 constexpr auto strEventDir = "EVENT_DIR"; 37 constexpr auto strGenerateId = "GENERATOR_ID"; 38 constexpr auto strRecordType = "RECORD_TYPE"; 39 constexpr auto strSensorData = "SENSOR_DATA"; 40 constexpr auto strSensorPath = "SENSOR_PATH"; 41 42 } // namespace 43 44 namespace ipmi 45 { 46 47 namespace sel 48 { 49 50 namespace internal 51 { 52 53 inline bool isRecordOEM(uint8_t recordType) 54 { 55 return recordType != systemEventRecord; 56 } 57 58 using additionalDataMap = std::map<std::string, std::string>; 59 using entryDataMap = std::map<PropertyName, PropertyType>; 60 /** Parse the entry with format like key=val */ 61 std::pair<std::string, std::string> parseEntry(const std::string& entry) 62 { 63 constexpr auto equalSign = "="; 64 auto pos = entry.find(equalSign); 65 assert(pos != std::string::npos); 66 auto key = entry.substr(0, pos); 67 auto val = entry.substr(pos + 1); 68 return {key, val}; 69 } 70 71 additionalDataMap parseAdditionalData(const AdditionalData& data) 72 { 73 std::map<std::string, std::string> ret; 74 75 for (const auto& d : data) 76 { 77 ret.insert(parseEntry(d)); 78 } 79 return ret; 80 } 81 82 int convert(const std::string_view& str, int base = 10) 83 { 84 int ret = 0; 85 std::from_chars(str.data(), str.data() + str.size(), ret, base); 86 return ret; 87 } 88 89 // Convert the string to a vector of uint8_t, where the str is formatted as hex 90 std::vector<uint8_t> convertVec(const std::string_view& str) 91 { 92 std::vector<uint8_t> ret; 93 auto len = str.size() / 2; 94 ret.reserve(len); 95 for (size_t i = 0; i < len; ++i) 96 { 97 ret.emplace_back( 98 static_cast<uint8_t>(convert(str.substr(i * 2, 2), 16))); 99 } 100 return ret; 101 } 102 103 /** Construct OEM SEL record according to IPMI spec 32.2, 32.3. */ 104 void constructOEMSEL(uint8_t recordType, std::chrono::milliseconds timestamp, 105 const additionalDataMap& m, GetSELEntryResponse& record) 106 { 107 auto dataIter = m.find(strSensorData); 108 assert(dataIter != m.end()); 109 auto sensorData = convertVec(dataIter->second); 110 if (recordType >= 0xC0 && recordType < 0xE0) 111 { 112 record.event.oemCD.timeStamp = static_cast<uint32_t>( 113 std::chrono::duration_cast<std::chrono::seconds>(timestamp) 114 .count()); 115 record.event.oemCD.recordType = recordType; 116 // The ManufactureID and OEM Defined are packed in the sensor data 117 // Fill the 9 bytes of Manufacture ID and oemDefined 118 memcpy(&record.event.oemCD.manufacturerID, sensorData.data(), 119 std::min(sensorData.size(), static_cast<size_t>(oemCDDataSize))); 120 } 121 else if (recordType >= 0xE0) 122 { 123 record.event.oemEF.recordType = recordType; 124 // The remaining 13 bytes are the OEM Defined data 125 memcpy(&record.event.oemEF.oemDefined, sensorData.data(), 126 std::min(sensorData.size(), static_cast<size_t>(oemEFDataSize))); 127 } 128 } 129 130 void constructSEL(uint8_t recordType, std::chrono::milliseconds timestamp, 131 const additionalDataMap& m, const entryDataMap&, 132 GetSELEntryResponse& record) 133 { 134 if (recordType != systemEventRecord) 135 { 136 log<level::ERR>("Invalid recordType"); 137 elog<InternalFailure>(); 138 } 139 140 // Default values when there is no matched sensor 141 record.event.eventRecord.sensorType = 0; 142 record.event.eventRecord.sensorNum = 0xFF; 143 record.event.eventRecord.eventType = 0; 144 145 auto iter = m.find(strSensorPath); 146 assert(iter != m.end()); 147 const auto& sensorPath = iter->second; 148 auto sensorIter = invSensors.find(sensorPath); 149 150 if (sensorIter != invSensors.end()) 151 { 152 // There is a matched sensor 153 record.event.eventRecord.sensorType = sensorIter->second.sensorType; 154 record.event.eventRecord.sensorNum = sensorIter->second.sensorID; 155 156 iter = m.find(strEventDir); 157 assert(iter != m.end()); 158 auto eventDir = static_cast<uint8_t>(convert(iter->second)); 159 uint8_t assert = eventDir ? assertEvent : deassertEvent; 160 record.event.eventRecord.eventType = 161 assert | sensorIter->second.eventReadingType; 162 } 163 record.event.eventRecord.recordType = recordType; 164 record.event.eventRecord.timeStamp = static_cast<uint32_t>( 165 std::chrono::duration_cast<std::chrono::seconds>(timestamp).count()); 166 iter = m.find(strGenerateId); 167 assert(iter != m.end()); 168 record.event.eventRecord.generatorID = 169 static_cast<uint16_t>(convert(iter->second)); 170 record.event.eventRecord.eventMsgRevision = eventMsgRevision; 171 iter = m.find(strSensorData); 172 assert(iter != m.end()); 173 auto sensorData = convertVec(iter->second); 174 // The remaining 3 bytes are the sensor data 175 memcpy(&record.event.eventRecord.eventData1, sensorData.data(), 176 std::min(sensorData.size(), static_cast<size_t>(selDataSize))); 177 } 178 179 GetSELEntryResponse 180 prepareSELEntry(const std::string& objPath, 181 ipmi::sensor::InvObjectIDMap::const_iterator iter) 182 { 183 GetSELEntryResponse record{}; 184 185 sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()}; 186 auto service = ipmi::getService(bus, logEntryIntf, objPath); 187 188 // Read all the log entry properties. 189 auto methodCall = bus.new_method_call(service.c_str(), objPath.c_str(), 190 propIntf, "GetAll"); 191 methodCall.append(logEntryIntf); 192 193 entryDataMap entryData; 194 try 195 { 196 auto reply = bus.call(methodCall); 197 reply.read(entryData); 198 } 199 catch (const std::exception& e) 200 { 201 log<level::ERR>("Error in reading logging property entries", 202 entry("ERROR=%s", e.what())); 203 elog<InternalFailure>(); 204 } 205 206 // Read Id from the log entry. 207 static constexpr auto propId = "Id"; 208 auto iterId = entryData.find(propId); 209 if (iterId == entryData.end()) 210 { 211 log<level::ERR>("Error in reading Id of logging entry"); 212 elog<InternalFailure>(); 213 } 214 215 // Read Timestamp from the log entry. 216 static constexpr auto propTimeStamp = "Timestamp"; 217 auto iterTimeStamp = entryData.find(propTimeStamp); 218 if (iterTimeStamp == entryData.end()) 219 { 220 log<level::ERR>("Error in reading Timestamp of logging entry"); 221 elog<InternalFailure>(); 222 } 223 std::chrono::milliseconds chronoTimeStamp( 224 std::get<uint64_t>(iterTimeStamp->second)); 225 226 bool isFromSELLogger = false; 227 additionalDataMap m; 228 229 // The recordID are with the same offset between different types, 230 // so we are safe to set the recordID here 231 record.event.eventRecord.recordID = 232 static_cast<uint16_t>(std::get<uint32_t>(iterId->second)); 233 234 iterId = entryData.find(propAdditionalData); 235 if (iterId != entryData.end()) 236 { 237 // Check if it's a SEL from phosphor-sel-logger which shall contain 238 // the record ID, etc 239 const auto& addData = std::get<AdditionalData>(iterId->second); 240 m = parseAdditionalData(addData); 241 auto recordTypeIter = m.find(strRecordType); 242 if (recordTypeIter != m.end()) 243 { 244 // It is a SEL from phosphor-sel-logger 245 isFromSELLogger = true; 246 } 247 else 248 { 249 // Not a SEL from phosphor-sel-logger, it shall have a valid 250 // invSensor 251 if (iter == invSensors.end()) 252 { 253 log<level::ERR>("System event sensor not found"); 254 elog<InternalFailure>(); 255 } 256 } 257 } 258 259 if (isFromSELLogger) 260 { 261 // It is expected to be a custom SEL entry 262 auto recordType = static_cast<uint8_t>(convert(m[strRecordType])); 263 auto isOEM = isRecordOEM(recordType); 264 if (isOEM) 265 { 266 constructOEMSEL(recordType, chronoTimeStamp, m, record); 267 } 268 else 269 { 270 constructSEL(recordType, chronoTimeStamp, m, entryData, record); 271 } 272 } 273 else 274 { 275 record.event.eventRecord.timeStamp = static_cast<uint32_t>( 276 std::chrono::duration_cast<std::chrono::seconds>(chronoTimeStamp) 277 .count()); 278 279 record.event.eventRecord.recordType = systemEventRecord; 280 record.event.eventRecord.generatorID = generatorID; 281 record.event.eventRecord.eventMsgRevision = eventMsgRevision; 282 283 record.event.eventRecord.sensorType = iter->second.sensorType; 284 record.event.eventRecord.sensorNum = iter->second.sensorID; 285 record.event.eventRecord.eventData1 = iter->second.eventOffset; 286 287 // Read Resolved from the log entry. 288 auto iterResolved = entryData.find(propResolved); 289 if (iterResolved == entryData.end()) 290 { 291 log<level::ERR>("Error in reading Resolved field of logging entry"); 292 elog<InternalFailure>(); 293 } 294 295 // Evaluate if the event is assertion or deassertion event 296 if (std::get<bool>(iterResolved->second)) 297 { 298 record.event.eventRecord.eventType = deassertEvent | 299 iter->second.eventReadingType; 300 } 301 else 302 { 303 record.event.eventRecord.eventType = iter->second.eventReadingType; 304 } 305 } 306 307 return record; 308 } 309 310 } // namespace internal 311 312 GetSELEntryResponse convertLogEntrytoSEL(const std::string& objPath) 313 { 314 sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()}; 315 316 static constexpr auto assocIntf = 317 "xyz.openbmc_project.Association.Definitions"; 318 static constexpr auto assocProp = "Associations"; 319 320 auto service = ipmi::getService(bus, assocIntf, objPath); 321 322 // Read the Associations interface. 323 auto methodCall = bus.new_method_call(service.c_str(), objPath.c_str(), 324 propIntf, "Get"); 325 methodCall.append(assocIntf); 326 methodCall.append(assocProp); 327 328 using AssociationList = 329 std::vector<std::tuple<std::string, std::string, std::string>>; 330 331 std::variant<AssociationList> list; 332 try 333 { 334 auto reply = bus.call(methodCall); 335 reply.read(list); 336 } 337 catch (const std::exception& e) 338 { 339 log<level::ERR>("Error in reading Associations interface", 340 entry("ERROR=%s", e.what())); 341 elog<InternalFailure>(); 342 } 343 344 auto& assocs = std::get<AssociationList>(list); 345 346 /* 347 * Check if the log entry has any callout associations, if there is a 348 * callout association try to match the inventory path to the corresponding 349 * IPMI sensor. 350 */ 351 for (const auto& item : assocs) 352 { 353 if (std::get<0>(item).compare(CALLOUT_FWD_ASSOCIATION) == 0) 354 { 355 auto iter = invSensors.find(std::get<2>(item)); 356 if (iter == invSensors.end()) 357 { 358 iter = invSensors.find(BOARD_SENSOR); 359 if (iter == invSensors.end()) 360 { 361 log<level::ERR>("Motherboard sensor not found"); 362 elog<InternalFailure>(); 363 } 364 } 365 366 return internal::prepareSELEntry(objPath, iter); 367 } 368 } 369 370 // If there are no callout associations link the log entry to system event 371 // sensor 372 auto iter = invSensors.find(SYSTEM_SENSOR); 373 return internal::prepareSELEntry(objPath, iter); 374 } 375 376 std::chrono::seconds getEntryTimeStamp(const std::string& objPath) 377 { 378 sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()}; 379 380 auto service = ipmi::getService(bus, logEntryIntf, objPath); 381 382 using namespace std::string_literals; 383 static const auto propTimeStamp = "Timestamp"s; 384 385 auto methodCall = bus.new_method_call(service.c_str(), objPath.c_str(), 386 propIntf, "Get"); 387 methodCall.append(logEntryIntf); 388 methodCall.append(propTimeStamp); 389 390 std::variant<uint64_t> timeStamp; 391 try 392 { 393 auto reply = bus.call(methodCall); 394 reply.read(timeStamp); 395 } 396 catch (const std::exception& e) 397 { 398 log<level::ERR>("Error in reading Timestamp from Entry interface", 399 entry("ERROR=%s", e.what())); 400 elog<InternalFailure>(); 401 } 402 403 std::chrono::milliseconds chronoTimeStamp(std::get<uint64_t>(timeStamp)); 404 405 return std::chrono::duration_cast<std::chrono::seconds>(chronoTimeStamp); 406 } 407 408 void readLoggingObjectPaths(ObjectPaths& paths) 409 { 410 sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()}; 411 auto depth = 0; 412 paths.clear(); 413 414 auto mapperCall = bus.new_method_call(mapperBusName, mapperObjPath, 415 mapperIntf, "GetSubTreePaths"); 416 mapperCall.append(logBasePath); 417 mapperCall.append(depth); 418 mapperCall.append(ObjectPaths({logEntryIntf})); 419 420 try 421 { 422 auto reply = bus.call(mapperCall); 423 reply.read(paths); 424 } 425 catch (const sdbusplus::exception_t& e) 426 { 427 if (strcmp(e.name(), 428 "xyz.openbmc_project.Common.Error.ResourceNotFound")) 429 { 430 throw; 431 } 432 } 433 434 std::sort(paths.begin(), paths.end(), 435 [](const std::string& a, const std::string& b) { 436 namespace fs = std::filesystem; 437 fs::path pathA(a); 438 fs::path pathB(b); 439 auto idA = std::stoul(pathA.filename().string()); 440 auto idB = std::stoul(pathB.filename().string()); 441 442 return idA < idB; 443 }); 444 } 445 446 } // namespace sel 447 448 } // namespace ipmi 449