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