1 /** 2 * Copyright © 2019 IBM Corporation 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 #include "config.h" 17 18 #include "data_interface.hpp" 19 20 #include "util.hpp" 21 22 #include <fstream> 23 #include <xyz/openbmc_project/State/Boot/Progress/server.hpp> 24 25 namespace openpower 26 { 27 namespace pels 28 { 29 30 namespace service_name 31 { 32 constexpr auto objectMapper = "xyz.openbmc_project.ObjectMapper"; 33 constexpr auto vpdManager = "com.ibm.VPD.Manager"; 34 constexpr auto ledGroupManager = "xyz.openbmc_project.LED.GroupManager"; 35 } // namespace service_name 36 37 namespace object_path 38 { 39 constexpr auto objectMapper = "/xyz/openbmc_project/object_mapper"; 40 constexpr auto systemInv = "/xyz/openbmc_project/inventory/system"; 41 constexpr auto chassisInv = "/xyz/openbmc_project/inventory/system/chassis"; 42 constexpr auto baseInv = "/xyz/openbmc_project/inventory"; 43 constexpr auto bmcState = "/xyz/openbmc_project/state/bmc0"; 44 constexpr auto chassisState = "/xyz/openbmc_project/state/chassis0"; 45 constexpr auto hostState = "/xyz/openbmc_project/state/host0"; 46 constexpr auto pldm = "/xyz/openbmc_project/pldm"; 47 constexpr auto enableHostPELs = 48 "/xyz/openbmc_project/logging/send_event_logs_to_host"; 49 constexpr auto vpdManager = "/com/ibm/VPD/Manager"; 50 } // namespace object_path 51 52 namespace interface 53 { 54 constexpr auto dbusProperty = "org.freedesktop.DBus.Properties"; 55 constexpr auto objectMapper = "xyz.openbmc_project.ObjectMapper"; 56 constexpr auto invAsset = "xyz.openbmc_project.Inventory.Decorator.Asset"; 57 constexpr auto bootProgress = "xyz.openbmc_project.State.Boot.Progress"; 58 constexpr auto pldmRequester = "xyz.openbmc_project.PLDM.Requester"; 59 constexpr auto enable = "xyz.openbmc_project.Object.Enable"; 60 constexpr auto bmcState = "xyz.openbmc_project.State.BMC"; 61 constexpr auto chassisState = "xyz.openbmc_project.State.Chassis"; 62 constexpr auto hostState = "xyz.openbmc_project.State.Host"; 63 constexpr auto invMotherboard = 64 "xyz.openbmc_project.Inventory.Item.Board.Motherboard"; 65 constexpr auto viniRecordVPD = "com.ibm.ipzvpd.VINI"; 66 constexpr auto locCode = "com.ibm.ipzvpd.Location"; 67 constexpr auto compatible = 68 "xyz.openbmc_project.Configuration.IBMCompatibleSystem"; 69 constexpr auto vpdManager = "com.ibm.VPD.Manager"; 70 constexpr auto association = "xyz.openbmc_project.Association"; 71 constexpr auto ledGroup = "xyz.openbmc_project.Led.Group"; 72 } // namespace interface 73 74 using namespace sdbusplus::xyz::openbmc_project::State::Boot::server; 75 using sdbusplus::exception::SdBusError; 76 77 DataInterface::DataInterface(sdbusplus::bus::bus& bus) : _bus(bus) 78 { 79 readBMCFWVersion(); 80 readServerFWVersion(); 81 readBMCFWVersionID(); 82 readMotherboardCCIN(); 83 84 // Watch both the Model and SN properties on the system's Asset iface 85 _properties.emplace_back(std::make_unique<InterfaceWatcher<DataInterface>>( 86 bus, object_path::systemInv, interface::invAsset, *this, 87 [this](const auto& properties) { 88 auto model = properties.find("Model"); 89 if (model != properties.end()) 90 { 91 this->_machineTypeModel = std::get<std::string>(model->second); 92 } 93 94 auto sn = properties.find("SerialNumber"); 95 if (sn != properties.end()) 96 { 97 this->_machineSerialNumber = std::get<std::string>(sn->second); 98 } 99 })); 100 101 // Watch the BootProgress property 102 _properties.emplace_back(std::make_unique<PropertyWatcher<DataInterface>>( 103 bus, object_path::hostState, interface::bootProgress, "BootProgress", 104 *this, [this](const auto& value) { 105 auto status = Progress::convertProgressStagesFromString( 106 std::get<std::string>(value)); 107 108 if ((status == Progress::ProgressStages::SystemInitComplete) || 109 (status == Progress::ProgressStages::OSStart) || 110 (status == Progress::ProgressStages::OSRunning)) 111 { 112 setHostUp(true); 113 } 114 else 115 { 116 setHostUp(false); 117 } 118 })); 119 120 // Watch the host PEL enable property 121 _properties.emplace_back(std::make_unique<PropertyWatcher<DataInterface>>( 122 bus, object_path::enableHostPELs, interface::enable, "Enabled", *this, 123 [this](const auto& value) { 124 this->_sendPELsToHost = std::get<bool>(value); 125 })); 126 127 // Watch the BMCState property 128 _properties.emplace_back(std::make_unique<PropertyWatcher<DataInterface>>( 129 bus, object_path::bmcState, interface::bmcState, "CurrentBMCState", 130 *this, [this](const auto& value) { 131 this->_bmcState = std::get<std::string>(value); 132 })); 133 134 // Watch the chassis current and requested power state properties 135 _properties.emplace_back(std::make_unique<InterfaceWatcher<DataInterface>>( 136 bus, object_path::chassisState, interface::chassisState, *this, 137 [this](const auto& properties) { 138 auto state = properties.find("CurrentPowerState"); 139 if (state != properties.end()) 140 { 141 this->_chassisState = std::get<std::string>(state->second); 142 } 143 144 auto trans = properties.find("RequestedPowerTransition"); 145 if (trans != properties.end()) 146 { 147 this->_chassisTransition = std::get<std::string>(trans->second); 148 } 149 })); 150 151 // Watch the CurrentHostState property 152 _properties.emplace_back(std::make_unique<PropertyWatcher<DataInterface>>( 153 bus, object_path::hostState, interface::hostState, "CurrentHostState", 154 *this, [this](const auto& value) { 155 this->_hostState = std::get<std::string>(value); 156 })); 157 } 158 159 DBusPropertyMap 160 DataInterface::getAllProperties(const std::string& service, 161 const std::string& objectPath, 162 const std::string& interface) const 163 { 164 DBusPropertyMap properties; 165 166 auto method = _bus.new_method_call(service.c_str(), objectPath.c_str(), 167 interface::dbusProperty, "GetAll"); 168 method.append(interface); 169 auto reply = _bus.call(method); 170 171 reply.read(properties); 172 173 return properties; 174 } 175 176 void DataInterface::getProperty(const std::string& service, 177 const std::string& objectPath, 178 const std::string& interface, 179 const std::string& property, 180 DBusValue& value) const 181 { 182 183 auto method = _bus.new_method_call(service.c_str(), objectPath.c_str(), 184 interface::dbusProperty, "Get"); 185 method.append(interface, property); 186 auto reply = _bus.call(method); 187 188 reply.read(value); 189 } 190 191 DBusPathList DataInterface::getPaths(const DBusInterfaceList& interfaces) const 192 { 193 194 auto method = _bus.new_method_call( 195 service_name::objectMapper, object_path::objectMapper, 196 interface::objectMapper, "GetSubTreePaths"); 197 198 method.append(std::string{"/"}, 0, interfaces); 199 200 auto reply = _bus.call(method); 201 202 DBusPathList paths; 203 reply.read(paths); 204 205 return paths; 206 } 207 208 DBusService DataInterface::getService(const std::string& objectPath, 209 const std::string& interface) const 210 { 211 auto method = _bus.new_method_call(service_name::objectMapper, 212 object_path::objectMapper, 213 interface::objectMapper, "GetObject"); 214 215 method.append(objectPath, std::vector<std::string>({interface})); 216 217 auto reply = _bus.call(method); 218 219 std::map<DBusService, DBusInterfaceList> response; 220 reply.read(response); 221 222 if (!response.empty()) 223 { 224 return response.begin()->first; 225 } 226 227 return std::string{}; 228 } 229 230 void DataInterface::readBMCFWVersion() 231 { 232 _bmcFWVersion = 233 phosphor::logging::util::getOSReleaseValue("VERSION").value_or(""); 234 } 235 236 void DataInterface::readServerFWVersion() 237 { 238 // Not available yet 239 } 240 241 void DataInterface::readBMCFWVersionID() 242 { 243 _bmcFWVersionID = 244 phosphor::logging::util::getOSReleaseValue("VERSION_ID").value_or(""); 245 } 246 247 void DataInterface::readMotherboardCCIN() 248 { 249 try 250 { 251 // First, try to find the motherboard 252 auto motherboards = getPaths({interface::invMotherboard}); 253 if (motherboards.empty()) 254 { 255 throw std::runtime_error("No motherboards yet"); 256 } 257 258 // Found it, so now get the CCIN 259 _properties.emplace_back( 260 std::make_unique<PropertyWatcher<DataInterface>>( 261 _bus, motherboards.front(), interface::viniRecordVPD, "CC", 262 *this, 263 [this](const auto& ccin) { this->setMotherboardCCIN(ccin); })); 264 } 265 catch (const std::exception& e) 266 { 267 // No motherboard in the inventory yet - watch for it 268 _inventoryIfacesAddedMatch = std::make_unique<sdbusplus::bus::match_t>( 269 _bus, match_rules::interfacesAdded(object_path::baseInv), 270 std::bind(std::mem_fn(&DataInterface::motherboardIfaceAdded), this, 271 std::placeholders::_1)); 272 } 273 } 274 275 void DataInterface::motherboardIfaceAdded(sdbusplus::message::message& msg) 276 { 277 sdbusplus::message::object_path path; 278 DBusInterfaceMap interfaces; 279 280 msg.read(path, interfaces); 281 282 // This is watching the whole inventory, so check if it's what we want 283 if (interfaces.find(interface::invMotherboard) == interfaces.end()) 284 { 285 return; 286 } 287 288 // Done watching for any new inventory interfaces 289 _inventoryIfacesAddedMatch.reset(); 290 291 // Watch the motherboard CCIN, using the service from this signal 292 // for the initial property read. 293 _properties.emplace_back(std::make_unique<PropertyWatcher<DataInterface>>( 294 _bus, path, interface::viniRecordVPD, "CC", msg.get_sender(), *this, 295 [this](const auto& ccin) { this->setMotherboardCCIN(ccin); })); 296 } 297 298 void DataInterface::getHWCalloutFields(const std::string& inventoryPath, 299 std::string& fruPartNumber, 300 std::string& ccin, 301 std::string& serialNumber) const 302 { 303 // For now, attempt to get all of the properties directly on the path 304 // passed in. In the future, may need to make use of an algorithm 305 // to figure out which inventory objects actually hold these 306 // interfaces in the case of non FRUs, or possibly another service 307 // will provide this info. Any missing interfaces will result 308 // in exceptions being thrown. 309 310 auto service = getService(inventoryPath, interface::viniRecordVPD); 311 312 auto properties = 313 getAllProperties(service, inventoryPath, interface::viniRecordVPD); 314 315 auto value = std::get<std::vector<uint8_t>>(properties["FN"]); 316 fruPartNumber = std::string{value.begin(), value.end()}; 317 318 value = std::get<std::vector<uint8_t>>(properties["CC"]); 319 ccin = std::string{value.begin(), value.end()}; 320 321 value = std::get<std::vector<uint8_t>>(properties["SN"]); 322 serialNumber = std::string{value.begin(), value.end()}; 323 } 324 325 std::string 326 DataInterface::getLocationCode(const std::string& inventoryPath) const 327 { 328 auto service = getService(inventoryPath, interface::locCode); 329 330 DBusValue locCode; 331 getProperty(service, inventoryPath, interface::locCode, "LocationCode", 332 locCode); 333 334 return std::get<std::string>(locCode); 335 } 336 337 std::string 338 DataInterface::addLocationCodePrefix(const std::string& locationCode) 339 { 340 static const std::string locationCodePrefix{"Ufcs-"}; 341 342 // Technically there are 2 location code prefixes, Ufcs and Umts, so 343 // if it already starts with a U then don't need to do anything. 344 if (locationCode.front() != 'U') 345 { 346 return locationCodePrefix + locationCode; 347 } 348 349 return locationCode; 350 } 351 352 std::string DataInterface::expandLocationCode(const std::string& locationCode, 353 uint16_t node) const 354 { 355 auto method = 356 _bus.new_method_call(service_name::vpdManager, object_path::vpdManager, 357 interface::vpdManager, "GetExpandedLocationCode"); 358 359 method.append(addLocationCodePrefix(locationCode), 360 static_cast<uint16_t>(0)); 361 362 auto reply = _bus.call(method); 363 364 std::string expandedLocationCode; 365 reply.read(expandedLocationCode); 366 367 return expandedLocationCode; 368 } 369 370 std::string 371 DataInterface::getInventoryFromLocCode(const std::string& locationCode, 372 uint16_t node, bool expanded) const 373 { 374 std::string methodName = expanded ? "GetFRUsByExpandedLocationCode" 375 : "GetFRUsByUnexpandedLocationCode"; 376 377 auto method = 378 _bus.new_method_call(service_name::vpdManager, object_path::vpdManager, 379 interface::vpdManager, methodName.c_str()); 380 381 if (expanded) 382 { 383 method.append(locationCode); 384 } 385 else 386 { 387 method.append(addLocationCodePrefix(locationCode), node); 388 } 389 390 auto reply = _bus.call(method); 391 392 std::vector<sdbusplus::message::object_path> entries; 393 reply.read(entries); 394 395 // Get the shortest entry from the paths received, as this 396 // would be the path furthest up the inventory hierarchy so 397 // would be the parent FRU. There is guaranteed to at least 398 // be one entry if the call didn't fail. 399 std::string shortest{entries[0]}; 400 401 std::for_each(entries.begin(), entries.end(), 402 [&shortest](const auto& path) { 403 if (path.str.size() < shortest.size()) 404 { 405 shortest = path; 406 } 407 }); 408 409 return shortest; 410 } 411 412 std::string 413 DataInterface::getFaultLEDGroup(const std::string& inventoryPath) const 414 { 415 auto associationPath = inventoryPath + "/" + "fault_led_group"; 416 auto service = getService(associationPath, interface::association); 417 418 DBusValue endpoints; 419 getProperty(service, associationPath, interface::association, "endpoints", 420 endpoints); 421 auto paths = std::get<std::vector<std::string>>(endpoints); 422 if (paths.empty()) 423 { 424 throw std::runtime_error("Association endpoints property empty"); 425 } 426 427 return paths[0]; 428 } 429 430 void DataInterface::assertLEDGroup(const std::string& ledGroup, 431 bool value) const 432 { 433 DBusValue variant = value; 434 auto method = 435 _bus.new_method_call(service_name::ledGroupManager, ledGroup.c_str(), 436 interface::dbusProperty, "Set"); 437 method.append(interface::ledGroup, "Asserted", variant); 438 _bus.call(method); 439 } 440 441 std::vector<std::string> DataInterface::getSystemNames() const 442 { 443 DBusSubTree subtree; 444 DBusValue names; 445 446 auto method = _bus.new_method_call(service_name::objectMapper, 447 object_path::objectMapper, 448 interface::objectMapper, "GetSubTree"); 449 method.append(std::string{"/"}, 0, 450 std::vector<std::string>{interface::compatible}); 451 auto reply = _bus.call(method); 452 453 reply.read(subtree); 454 if (subtree.empty()) 455 { 456 throw std::runtime_error("Compatible interface not on D-Bus"); 457 } 458 459 const auto& object = *(subtree.begin()); 460 const auto& path = object.first; 461 const auto& service = object.second.begin()->first; 462 463 getProperty(service, path, interface::compatible, "Names", names); 464 465 return std::get<std::vector<std::string>>(names); 466 } 467 468 } // namespace pels 469 } // namespace openpower 470