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