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 25 } // namespace 26 27 namespace ipmi 28 { 29 30 namespace sel 31 { 32 33 namespace internal 34 { 35 36 inline bool isRecordOEM(uint8_t recordType) 37 { 38 return recordType != systemEventRecord; 39 } 40 41 /** Parse the entry with format like key=val */ 42 std::pair<std::string, std::string> parseEntry(const std::string& entry) 43 { 44 constexpr auto equalSign = "="; 45 auto pos = entry.find(equalSign); 46 assert(pos != std::string::npos); 47 auto key = entry.substr(0, pos); 48 auto val = entry.substr(pos + 1); 49 return {key, val}; 50 } 51 52 std::map<std::string, std::string> 53 parseAdditionalData(const AdditionalData& data) 54 { 55 std::map<std::string, std::string> ret; 56 57 for (const auto& d : data) 58 { 59 ret.insert(parseEntry(d)); 60 } 61 return ret; 62 } 63 64 uint8_t convert(const std::string_view& str, int base = 10) 65 { 66 int ret; 67 std::from_chars(str.data(), str.data() + str.size(), ret, base); 68 return static_cast<uint8_t>(ret); 69 } 70 71 // Convert the string to a vector of uint8_t, where the str is formatted as hex 72 std::vector<uint8_t> convertVec(const std::string_view& str) 73 { 74 std::vector<uint8_t> ret; 75 auto len = str.size() / 2; 76 ret.reserve(len); 77 for (size_t i = 0; i < len; ++i) 78 { 79 ret.emplace_back(convert(str.substr(i * 2, 2), 16)); 80 } 81 return ret; 82 } 83 84 GetSELEntryResponse 85 prepareSELEntry(const std::string& objPath, 86 ipmi::sensor::InvObjectIDMap::const_iterator iter) 87 { 88 GetSELEntryResponse record{}; 89 90 sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()}; 91 auto service = ipmi::getService(bus, logEntryIntf, objPath); 92 93 // Read all the log entry properties. 94 auto methodCall = bus.new_method_call(service.c_str(), objPath.c_str(), 95 propIntf, "GetAll"); 96 methodCall.append(logEntryIntf); 97 98 auto reply = bus.call(methodCall); 99 if (reply.is_method_error()) 100 { 101 log<level::ERR>("Error in reading logging property entries"); 102 elog<InternalFailure>(); 103 } 104 105 std::map<PropertyName, PropertyType> entryData; 106 reply.read(entryData); 107 108 // Read Id from the log entry. 109 static constexpr auto propId = "Id"; 110 auto iterId = entryData.find(propId); 111 if (iterId == entryData.end()) 112 { 113 log<level::ERR>("Error in reading Id of logging entry"); 114 elog<InternalFailure>(); 115 } 116 117 // Read Timestamp from the log entry. 118 static constexpr auto propTimeStamp = "Timestamp"; 119 auto iterTimeStamp = entryData.find(propTimeStamp); 120 if (iterTimeStamp == entryData.end()) 121 { 122 log<level::ERR>("Error in reading Timestamp of logging entry"); 123 elog<InternalFailure>(); 124 } 125 std::chrono::milliseconds chronoTimeStamp( 126 std::get<uint64_t>(iterTimeStamp->second)); 127 128 if (iter == invSensors.end()) 129 { 130 // It is expected to be a custom SEL entry 131 record.event.oemCD.recordID = 132 static_cast<uint16_t>(std::get<uint32_t>(iterId->second)); 133 static constexpr auto propAdditionalData = "AdditionalData"; 134 // static constexpr auto strEventDir = "EVENT_DIR"; 135 // static constexpr auto strGenerateId = "GENERATOR_ID"; 136 static constexpr auto strRecordType = "RECORD_TYPE"; 137 static constexpr auto strSensorData = "SENSOR_DATA"; 138 // static constexpr auto strSensorPath = "SENSOR_PATH"; 139 iterId = entryData.find(propAdditionalData); 140 if (iterId == entryData.end()) 141 { 142 log<level::ERR>("Error finding AdditionalData"); 143 elog<InternalFailure>(); 144 } 145 const auto& addData = std::get<AdditionalData>(iterId->second); 146 auto m = parseAdditionalData(addData); 147 auto recordType = convert(m[strRecordType]); 148 auto isOEM = isRecordOEM(recordType); 149 if (isOEM) 150 { 151 if (recordType >= 0xC0 && recordType < 0xE0) 152 { 153 record.event.oemCD.timeStamp = static_cast<uint32_t>( 154 std::chrono::duration_cast<std::chrono::seconds>( 155 chronoTimeStamp) 156 .count()); 157 record.event.oemCD.recordType = recordType; 158 // The ManufactureID and OEM Defined are packed in the sensor 159 // data 160 auto sensorData = convertVec(m[strSensorData]); 161 // Fill the 9 bytes of Manufacture ID and oemDefined 162 memcpy(&record.event.oemCD.manufacturerID, sensorData.data(), 163 std::min(sensorData.size(), static_cast<size_t>(9))); 164 } 165 else if (recordType >= 0xE0) 166 { 167 // TODO 168 } 169 } 170 else 171 { 172 // TODO 173 } 174 } 175 else 176 { 177 178 record.event.eventRecord.recordID = 179 static_cast<uint16_t>(std::get<uint32_t>(iterId->second)); 180 record.event.eventRecord.timeStamp = static_cast<uint32_t>( 181 std::chrono::duration_cast<std::chrono::seconds>(chronoTimeStamp) 182 .count()); 183 184 static constexpr auto generatorID = 0x2000; 185 static constexpr auto eventMsgRevision = 0x04; 186 187 record.event.eventRecord.recordType = systemEventRecord; 188 record.event.eventRecord.generatorID = generatorID; 189 record.event.eventRecord.eventMsgRevision = eventMsgRevision; 190 191 record.event.eventRecord.sensorType = iter->second.sensorType; 192 record.event.eventRecord.sensorNum = iter->second.sensorID; 193 record.event.eventRecord.eventData1 = iter->second.eventOffset; 194 195 // Read Resolved from the log entry. 196 static constexpr auto propResolved = "Resolved"; 197 auto iterResolved = entryData.find(propResolved); 198 if (iterResolved == entryData.end()) 199 { 200 log<level::ERR>("Error in reading Resolved field of logging entry"); 201 elog<InternalFailure>(); 202 } 203 204 static constexpr auto deassertEvent = 0x80; 205 206 // Evaluate if the event is assertion or deassertion event 207 if (std::get<bool>(iterResolved->second)) 208 { 209 record.event.eventRecord.eventType = 210 deassertEvent | iter->second.eventReadingType; 211 } 212 else 213 { 214 record.event.eventRecord.eventType = iter->second.eventReadingType; 215 } 216 } 217 218 return record; 219 } 220 221 } // namespace internal 222 223 GetSELEntryResponse convertLogEntrytoSEL(const std::string& objPath) 224 { 225 sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()}; 226 227 static constexpr auto assocIntf = 228 "xyz.openbmc_project.Association.Definitions"; 229 static constexpr auto assocProp = "Associations"; 230 231 auto service = ipmi::getService(bus, assocIntf, objPath); 232 233 // Read the Associations interface. 234 auto methodCall = 235 bus.new_method_call(service.c_str(), objPath.c_str(), propIntf, "Get"); 236 methodCall.append(assocIntf); 237 methodCall.append(assocProp); 238 239 auto reply = bus.call(methodCall); 240 if (reply.is_method_error()) 241 { 242 log<level::ERR>("Error in reading Associations interface"); 243 elog<InternalFailure>(); 244 } 245 246 using AssociationList = 247 std::vector<std::tuple<std::string, std::string, std::string>>; 248 249 std::variant<AssociationList> list; 250 reply.read(list); 251 252 auto& assocs = std::get<AssociationList>(list); 253 254 /* 255 * Check if the log entry has any callout associations, if there is a 256 * callout association try to match the inventory path to the corresponding 257 * IPMI sensor. 258 */ 259 for (const auto& item : assocs) 260 { 261 if (std::get<0>(item).compare(CALLOUT_FWD_ASSOCIATION) == 0) 262 { 263 auto iter = invSensors.find(std::get<2>(item)); 264 if (iter == invSensors.end()) 265 { 266 iter = invSensors.find(BOARD_SENSOR); 267 if (iter == invSensors.end()) 268 { 269 log<level::ERR>("Motherboard sensor not found"); 270 elog<InternalFailure>(); 271 } 272 } 273 274 return internal::prepareSELEntry(objPath, iter); 275 } 276 } 277 278 // If there are no callout associations link the log entry to system event 279 // sensor 280 auto iter = invSensors.find(SYSTEM_SENSOR); 281 return internal::prepareSELEntry(objPath, iter); 282 } 283 284 std::chrono::seconds getEntryTimeStamp(const std::string& objPath) 285 { 286 sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()}; 287 288 auto service = ipmi::getService(bus, logEntryIntf, objPath); 289 290 using namespace std::string_literals; 291 static const auto propTimeStamp = "Timestamp"s; 292 293 auto methodCall = 294 bus.new_method_call(service.c_str(), objPath.c_str(), propIntf, "Get"); 295 methodCall.append(logEntryIntf); 296 methodCall.append(propTimeStamp); 297 298 auto reply = bus.call(methodCall); 299 if (reply.is_method_error()) 300 { 301 log<level::ERR>("Error in reading Timestamp from Entry interface"); 302 elog<InternalFailure>(); 303 } 304 305 std::variant<uint64_t> timeStamp; 306 reply.read(timeStamp); 307 308 std::chrono::milliseconds chronoTimeStamp(std::get<uint64_t>(timeStamp)); 309 310 return std::chrono::duration_cast<std::chrono::seconds>(chronoTimeStamp); 311 } 312 313 void readLoggingObjectPaths(ObjectPaths& paths) 314 { 315 sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()}; 316 auto depth = 0; 317 paths.clear(); 318 319 auto mapperCall = bus.new_method_call(mapperBusName, mapperObjPath, 320 mapperIntf, "GetSubTreePaths"); 321 mapperCall.append(logBasePath); 322 mapperCall.append(depth); 323 mapperCall.append(ObjectPaths({logEntryIntf})); 324 325 auto reply = bus.call(mapperCall); 326 if (reply.is_method_error()) 327 { 328 log<level::INFO>("Error in reading logging entry object paths"); 329 } 330 else 331 { 332 reply.read(paths); 333 334 std::sort(paths.begin(), paths.end(), 335 [](const std::string& a, const std::string& b) { 336 namespace fs = std::filesystem; 337 fs::path pathA(a); 338 fs::path pathB(b); 339 auto idA = std::stoul(pathA.filename().string()); 340 auto idB = std::stoul(pathB.filename().string()); 341 342 return idA < idB; 343 }); 344 } 345 } 346 347 } // namespace sel 348 349 } // namespace ipmi 350