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& entryData, 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::bus 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 if (iter == invSensors.end()) 223 { 224 // It is expected to be a custom SEL entry 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 iterId = entryData.find(propAdditionalData); 230 if (iterId == entryData.end()) 231 { 232 log<level::ERR>("Error finding AdditionalData"); 233 elog<InternalFailure>(); 234 } 235 const auto& addData = std::get<AdditionalData>(iterId->second); 236 auto m = parseAdditionalData(addData); 237 auto recordType = static_cast<uint8_t>(convert(m[strRecordType])); 238 auto isOEM = isRecordOEM(recordType); 239 if (isOEM) 240 { 241 constructOEMSEL(recordType, chronoTimeStamp, m, record); 242 } 243 else 244 { 245 constructSEL(recordType, chronoTimeStamp, m, entryData, record); 246 } 247 } 248 else 249 { 250 251 record.event.eventRecord.recordID = 252 static_cast<uint16_t>(std::get<uint32_t>(iterId->second)); 253 record.event.eventRecord.timeStamp = static_cast<uint32_t>( 254 std::chrono::duration_cast<std::chrono::seconds>(chronoTimeStamp) 255 .count()); 256 257 record.event.eventRecord.recordType = systemEventRecord; 258 record.event.eventRecord.generatorID = generatorID; 259 record.event.eventRecord.eventMsgRevision = eventMsgRevision; 260 261 record.event.eventRecord.sensorType = iter->second.sensorType; 262 record.event.eventRecord.sensorNum = iter->second.sensorID; 263 record.event.eventRecord.eventData1 = iter->second.eventOffset; 264 265 // Read Resolved from the log entry. 266 auto iterResolved = entryData.find(propResolved); 267 if (iterResolved == entryData.end()) 268 { 269 log<level::ERR>("Error in reading Resolved field of logging entry"); 270 elog<InternalFailure>(); 271 } 272 273 // Evaluate if the event is assertion or deassertion event 274 if (std::get<bool>(iterResolved->second)) 275 { 276 record.event.eventRecord.eventType = 277 deassertEvent | iter->second.eventReadingType; 278 } 279 else 280 { 281 record.event.eventRecord.eventType = iter->second.eventReadingType; 282 } 283 } 284 285 return record; 286 } 287 288 } // namespace internal 289 290 GetSELEntryResponse convertLogEntrytoSEL(const std::string& objPath) 291 { 292 sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()}; 293 294 static constexpr auto assocIntf = 295 "xyz.openbmc_project.Association.Definitions"; 296 static constexpr auto assocProp = "Associations"; 297 298 auto service = ipmi::getService(bus, assocIntf, objPath); 299 300 // Read the Associations interface. 301 auto methodCall = 302 bus.new_method_call(service.c_str(), objPath.c_str(), propIntf, "Get"); 303 methodCall.append(assocIntf); 304 methodCall.append(assocProp); 305 306 auto reply = bus.call(methodCall); 307 if (reply.is_method_error()) 308 { 309 log<level::ERR>("Error in reading Associations interface"); 310 elog<InternalFailure>(); 311 } 312 313 using AssociationList = 314 std::vector<std::tuple<std::string, std::string, std::string>>; 315 316 std::variant<AssociationList> list; 317 reply.read(list); 318 319 auto& assocs = std::get<AssociationList>(list); 320 321 /* 322 * Check if the log entry has any callout associations, if there is a 323 * callout association try to match the inventory path to the corresponding 324 * IPMI sensor. 325 */ 326 for (const auto& item : assocs) 327 { 328 if (std::get<0>(item).compare(CALLOUT_FWD_ASSOCIATION) == 0) 329 { 330 auto iter = invSensors.find(std::get<2>(item)); 331 if (iter == invSensors.end()) 332 { 333 iter = invSensors.find(BOARD_SENSOR); 334 if (iter == invSensors.end()) 335 { 336 log<level::ERR>("Motherboard sensor not found"); 337 elog<InternalFailure>(); 338 } 339 } 340 341 return internal::prepareSELEntry(objPath, iter); 342 } 343 } 344 345 // If there are no callout associations link the log entry to system event 346 // sensor 347 auto iter = invSensors.find(SYSTEM_SENSOR); 348 return internal::prepareSELEntry(objPath, iter); 349 } 350 351 std::chrono::seconds getEntryTimeStamp(const std::string& objPath) 352 { 353 sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()}; 354 355 auto service = ipmi::getService(bus, logEntryIntf, objPath); 356 357 using namespace std::string_literals; 358 static const auto propTimeStamp = "Timestamp"s; 359 360 auto methodCall = 361 bus.new_method_call(service.c_str(), objPath.c_str(), propIntf, "Get"); 362 methodCall.append(logEntryIntf); 363 methodCall.append(propTimeStamp); 364 365 auto reply = bus.call(methodCall); 366 if (reply.is_method_error()) 367 { 368 log<level::ERR>("Error in reading Timestamp from Entry interface"); 369 elog<InternalFailure>(); 370 } 371 372 std::variant<uint64_t> timeStamp; 373 reply.read(timeStamp); 374 375 std::chrono::milliseconds chronoTimeStamp(std::get<uint64_t>(timeStamp)); 376 377 return std::chrono::duration_cast<std::chrono::seconds>(chronoTimeStamp); 378 } 379 380 void readLoggingObjectPaths(ObjectPaths& paths) 381 { 382 sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()}; 383 auto depth = 0; 384 paths.clear(); 385 386 auto mapperCall = bus.new_method_call(mapperBusName, mapperObjPath, 387 mapperIntf, "GetSubTreePaths"); 388 mapperCall.append(logBasePath); 389 mapperCall.append(depth); 390 mapperCall.append(ObjectPaths({logEntryIntf})); 391 392 auto reply = bus.call(mapperCall); 393 if (reply.is_method_error()) 394 { 395 log<level::INFO>("Error in reading logging entry object paths"); 396 } 397 else 398 { 399 reply.read(paths); 400 401 std::sort(paths.begin(), paths.end(), 402 [](const std::string& a, const std::string& b) { 403 namespace fs = std::filesystem; 404 fs::path pathA(a); 405 fs::path pathB(b); 406 auto idA = std::stoul(pathA.filename().string()); 407 auto idB = std::stoul(pathB.filename().string()); 408 409 return idA < idB; 410 }); 411 } 412 } 413 414 } // namespace sel 415 416 } // namespace ipmi 417