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