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