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::xyz::openbmc_project::Common::Error::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 auto reply = bus.call(methodCall); 194 if (reply.is_method_error()) 195 { 196 log<level::ERR>("Error in reading logging property entries"); 197 elog<InternalFailure>(); 198 } 199 200 entryDataMap entryData; 201 reply.read(entryData); 202 203 // Read Id from the log entry. 204 static constexpr auto propId = "Id"; 205 auto iterId = entryData.find(propId); 206 if (iterId == entryData.end()) 207 { 208 log<level::ERR>("Error in reading Id of logging entry"); 209 elog<InternalFailure>(); 210 } 211 212 // Read Timestamp from the log entry. 213 static constexpr auto propTimeStamp = "Timestamp"; 214 auto iterTimeStamp = entryData.find(propTimeStamp); 215 if (iterTimeStamp == entryData.end()) 216 { 217 log<level::ERR>("Error in reading Timestamp of logging entry"); 218 elog<InternalFailure>(); 219 } 220 std::chrono::milliseconds chronoTimeStamp( 221 std::get<uint64_t>(iterTimeStamp->second)); 222 223 bool isFromSELLogger = false; 224 additionalDataMap m; 225 226 // The recordID are with the same offset between different types, 227 // so we are safe to set the recordID here 228 record.event.eventRecord.recordID = 229 static_cast<uint16_t>(std::get<uint32_t>(iterId->second)); 230 231 iterId = entryData.find(propAdditionalData); 232 if (iterId != entryData.end()) 233 { 234 // Check if it's a SEL from phosphor-sel-logger which shall contain 235 // the record ID, etc 236 const auto& addData = std::get<AdditionalData>(iterId->second); 237 m = parseAdditionalData(addData); 238 auto recordTypeIter = m.find(strRecordType); 239 if (recordTypeIter != m.end()) 240 { 241 // It is a SEL from phosphor-sel-logger 242 isFromSELLogger = true; 243 } 244 else 245 { 246 // Not a SEL from phosphor-sel-logger, it shall have a valid 247 // invSensor 248 if (iter == invSensors.end()) 249 { 250 log<level::ERR>("System event sensor not found"); 251 elog<InternalFailure>(); 252 } 253 } 254 } 255 256 if (isFromSELLogger) 257 { 258 // It is expected to be a custom SEL entry 259 auto recordType = static_cast<uint8_t>(convert(m[strRecordType])); 260 auto isOEM = isRecordOEM(recordType); 261 if (isOEM) 262 { 263 constructOEMSEL(recordType, chronoTimeStamp, m, record); 264 } 265 else 266 { 267 constructSEL(recordType, chronoTimeStamp, m, entryData, record); 268 } 269 } 270 else 271 { 272 record.event.eventRecord.timeStamp = static_cast<uint32_t>( 273 std::chrono::duration_cast<std::chrono::seconds>(chronoTimeStamp) 274 .count()); 275 276 record.event.eventRecord.recordType = systemEventRecord; 277 record.event.eventRecord.generatorID = generatorID; 278 record.event.eventRecord.eventMsgRevision = eventMsgRevision; 279 280 record.event.eventRecord.sensorType = iter->second.sensorType; 281 record.event.eventRecord.sensorNum = iter->second.sensorID; 282 record.event.eventRecord.eventData1 = iter->second.eventOffset; 283 284 // Read Resolved from the log entry. 285 auto iterResolved = entryData.find(propResolved); 286 if (iterResolved == entryData.end()) 287 { 288 log<level::ERR>("Error in reading Resolved field of logging entry"); 289 elog<InternalFailure>(); 290 } 291 292 // Evaluate if the event is assertion or deassertion event 293 if (std::get<bool>(iterResolved->second)) 294 { 295 record.event.eventRecord.eventType = deassertEvent | 296 iter->second.eventReadingType; 297 } 298 else 299 { 300 record.event.eventRecord.eventType = iter->second.eventReadingType; 301 } 302 } 303 304 return record; 305 } 306 307 } // namespace internal 308 309 GetSELEntryResponse convertLogEntrytoSEL(const std::string& objPath) 310 { 311 sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()}; 312 313 static constexpr auto assocIntf = 314 "xyz.openbmc_project.Association.Definitions"; 315 static constexpr auto assocProp = "Associations"; 316 317 auto service = ipmi::getService(bus, assocIntf, objPath); 318 319 // Read the Associations interface. 320 auto methodCall = bus.new_method_call(service.c_str(), objPath.c_str(), 321 propIntf, "Get"); 322 methodCall.append(assocIntf); 323 methodCall.append(assocProp); 324 325 auto reply = bus.call(methodCall); 326 if (reply.is_method_error()) 327 { 328 log<level::ERR>("Error in reading Associations interface"); 329 elog<InternalFailure>(); 330 } 331 332 using AssociationList = 333 std::vector<std::tuple<std::string, std::string, std::string>>; 334 335 std::variant<AssociationList> list; 336 reply.read(list); 337 338 auto& assocs = std::get<AssociationList>(list); 339 340 /* 341 * Check if the log entry has any callout associations, if there is a 342 * callout association try to match the inventory path to the corresponding 343 * IPMI sensor. 344 */ 345 for (const auto& item : assocs) 346 { 347 if (std::get<0>(item).compare(CALLOUT_FWD_ASSOCIATION) == 0) 348 { 349 auto iter = invSensors.find(std::get<2>(item)); 350 if (iter == invSensors.end()) 351 { 352 iter = invSensors.find(BOARD_SENSOR); 353 if (iter == invSensors.end()) 354 { 355 log<level::ERR>("Motherboard sensor not found"); 356 elog<InternalFailure>(); 357 } 358 } 359 360 return internal::prepareSELEntry(objPath, iter); 361 } 362 } 363 364 // If there are no callout associations link the log entry to system event 365 // sensor 366 auto iter = invSensors.find(SYSTEM_SENSOR); 367 return internal::prepareSELEntry(objPath, iter); 368 } 369 370 std::chrono::seconds getEntryTimeStamp(const std::string& objPath) 371 { 372 sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()}; 373 374 auto service = ipmi::getService(bus, logEntryIntf, objPath); 375 376 using namespace std::string_literals; 377 static const auto propTimeStamp = "Timestamp"s; 378 379 auto methodCall = bus.new_method_call(service.c_str(), objPath.c_str(), 380 propIntf, "Get"); 381 methodCall.append(logEntryIntf); 382 methodCall.append(propTimeStamp); 383 384 auto reply = bus.call(methodCall); 385 if (reply.is_method_error()) 386 { 387 log<level::ERR>("Error in reading Timestamp from Entry interface"); 388 elog<InternalFailure>(); 389 } 390 391 std::variant<uint64_t> timeStamp; 392 reply.read(timeStamp); 393 394 std::chrono::milliseconds chronoTimeStamp(std::get<uint64_t>(timeStamp)); 395 396 return std::chrono::duration_cast<std::chrono::seconds>(chronoTimeStamp); 397 } 398 399 void readLoggingObjectPaths(ObjectPaths& paths) 400 { 401 sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()}; 402 auto depth = 0; 403 paths.clear(); 404 405 auto mapperCall = bus.new_method_call(mapperBusName, mapperObjPath, 406 mapperIntf, "GetSubTreePaths"); 407 mapperCall.append(logBasePath); 408 mapperCall.append(depth); 409 mapperCall.append(ObjectPaths({logEntryIntf})); 410 411 try 412 { 413 auto reply = bus.call(mapperCall); 414 reply.read(paths); 415 } 416 catch (const sdbusplus::exception_t& e) 417 { 418 if (strcmp(e.name(), 419 "xyz.openbmc_project.Common.Error.ResourceNotFound")) 420 { 421 throw; 422 } 423 } 424 425 std::sort(paths.begin(), paths.end(), 426 [](const std::string& a, const std::string& b) { 427 namespace fs = std::filesystem; 428 fs::path pathA(a); 429 fs::path pathB(b); 430 auto idA = std::stoul(pathA.filename().string()); 431 auto idB = std::stoul(pathB.filename().string()); 432 433 return idA < idB; 434 }); 435 } 436 437 } // namespace sel 438 439 } // namespace ipmi 440