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