1 /** 2 * Copyright © 2017 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 <chrono> 17 #include <functional> 18 #include <fstream> 19 #include <cereal/cereal.hpp> 20 #include <cereal/archives/json.hpp> 21 #include <experimental/filesystem> 22 #include <phosphor-logging/log.hpp> 23 #include <phosphor-logging/elog.hpp> 24 #include <phosphor-logging/elog-errors.hpp> 25 #include <stdexcept> 26 #include <xyz/openbmc_project/Common/error.hpp> 27 #include "config.h" 28 #include "zone.hpp" 29 #include "utility.hpp" 30 #include "sdbusplus.hpp" 31 32 namespace phosphor 33 { 34 namespace fan 35 { 36 namespace control 37 { 38 39 using namespace std::chrono; 40 using namespace phosphor::fan; 41 using namespace phosphor::logging; 42 namespace fs = std::experimental::filesystem; 43 using InternalFailure = sdbusplus::xyz::openbmc_project::Common:: 44 Error::InternalFailure; 45 46 Zone::Zone(Mode mode, 47 sdbusplus::bus::bus& bus, 48 const std::string& path, 49 const sdeventplus::Event& event, 50 const ZoneDefinition& def) : 51 ThermalObject(bus, path.c_str(), true), 52 _bus(bus), 53 _path(path), 54 _ifaces({"xyz.openbmc_project.Control.ThermalMode"}), 55 _fullSpeed(std::get<fullSpeedPos>(def)), 56 _zoneNum(std::get<zoneNumPos>(def)), 57 _defFloorSpeed(std::get<floorSpeedPos>(def)), 58 _defCeilingSpeed(std::get<fullSpeedPos>(def)), 59 _incDelay(std::get<incDelayPos>(def)), 60 _decInterval(std::get<decIntervalPos>(def)), 61 _incTimer(event, std::bind(&Zone::incTimerExpired, this)), 62 _decTimer(event, std::bind(&Zone::decTimerExpired, this)), 63 _eventLoop(event) 64 { 65 auto& fanDefs = std::get<fanListPos>(def); 66 67 for (auto& def : fanDefs) 68 { 69 _fans.emplace_back(std::make_unique<Fan>(bus, def)); 70 } 71 72 // Do not enable set speed events when in init mode 73 if (mode == Mode::control) 74 { 75 // Restore thermal control current mode state 76 restoreCurrentMode(); 77 78 // Emit objects added in control mode only 79 this->emit_object_added(); 80 81 // Update target speed to current zone target speed 82 if (!_fans.empty()) 83 { 84 _targetSpeed = _fans.front()->getTargetSpeed(); 85 } 86 // Setup signal trigger for set speed events 87 for (auto& event : std::get<setSpeedEventsPos>(def)) 88 { 89 initEvent(event); 90 } 91 // Start timer for fan speed decreases 92 _decTimer.restart(_decInterval); 93 } 94 } 95 96 void Zone::setSpeed(uint64_t speed) 97 { 98 if (_isActive) 99 { 100 _targetSpeed = speed; 101 for (auto& fan : _fans) 102 { 103 fan->setSpeed(_targetSpeed); 104 } 105 } 106 } 107 108 void Zone::setFullSpeed() 109 { 110 if (_fullSpeed != 0) 111 { 112 _targetSpeed = _fullSpeed; 113 for (auto& fan : _fans) 114 { 115 fan->setSpeed(_targetSpeed); 116 } 117 } 118 } 119 120 void Zone::setActiveAllow(const Group* group, bool isActiveAllow) 121 { 122 _active[*(group)] = isActiveAllow; 123 if (!isActiveAllow) 124 { 125 _isActive = false; 126 } 127 else 128 { 129 // Check all entries are set to allow control active 130 auto actPred = [](auto const& entry) {return entry.second;}; 131 _isActive = std::all_of(_active.begin(), 132 _active.end(), 133 actPred); 134 } 135 } 136 137 void Zone::removeService(const Group* group, 138 const std::string& name) 139 { 140 try 141 { 142 auto& sNames = _services.at(*group); 143 auto it = std::find_if( 144 sNames.begin(), 145 sNames.end(), 146 [&name](auto const& entry) 147 { 148 return name == std::get<namePos>(entry); 149 } 150 ); 151 if (it != std::end(sNames)) 152 { 153 // Remove service name from group 154 sNames.erase(it); 155 } 156 } 157 catch (const std::out_of_range& oore) 158 { 159 // No services for group found 160 } 161 } 162 163 void Zone::setServiceOwner(const Group* group, 164 const std::string& name, 165 const bool hasOwner) 166 { 167 try 168 { 169 auto& sNames = _services.at(*group); 170 auto it = std::find_if( 171 sNames.begin(), 172 sNames.end(), 173 [&name](auto const& entry) 174 { 175 return name == std::get<namePos>(entry); 176 } 177 ); 178 if (it != std::end(sNames)) 179 { 180 std::get<hasOwnerPos>(*it) = hasOwner; 181 } 182 else 183 { 184 _services[*group].emplace_back(name, hasOwner); 185 } 186 } 187 catch (const std::out_of_range& oore) 188 { 189 _services[*group].emplace_back(name, hasOwner); 190 } 191 } 192 193 void Zone::setServices(const Group* group) 194 { 195 // Remove the empty service name if exists 196 removeService(group, ""); 197 for (auto it = group->begin(); it != group->end(); ++it) 198 { 199 std::string name; 200 bool hasOwner = false; 201 try 202 { 203 name = getService(it->first, 204 std::get<intfPos>(it->second)); 205 hasOwner = util::SDBusPlus::callMethodAndRead<bool>( 206 _bus, 207 "org.freedesktop.DBus", 208 "/org/freedesktop/DBus", 209 "org.freedesktop.DBus", 210 "NameHasOwner", 211 name); 212 } 213 catch (const util::DBusMethodError& e) 214 { 215 // Failed to get service name owner state 216 hasOwner = false; 217 } 218 setServiceOwner(group, name, hasOwner); 219 } 220 } 221 222 void Zone::setFloor(uint64_t speed) 223 { 224 // Check all entries are set to allow floor to be set 225 auto pred = [](auto const& entry) {return entry.second;}; 226 auto setFloor = std::all_of(_floorChange.begin(), 227 _floorChange.end(), 228 pred); 229 if (setFloor) 230 { 231 _floorSpeed = speed; 232 // Floor speed above target, update target to floor speed 233 if (_targetSpeed < _floorSpeed) 234 { 235 requestSpeedIncrease(_floorSpeed - _targetSpeed); 236 } 237 } 238 } 239 240 void Zone::requestSpeedIncrease(uint64_t targetDelta) 241 { 242 // Only increase speed when delta is higher than 243 // the current increase delta for the zone and currently under ceiling 244 if (targetDelta > _incSpeedDelta && 245 _targetSpeed < _ceilingSpeed) 246 { 247 auto requestTarget = getRequestSpeedBase(); 248 requestTarget = (targetDelta - _incSpeedDelta) + requestTarget; 249 _incSpeedDelta = targetDelta; 250 // Target speed can not go above a defined ceiling speed 251 if (requestTarget > _ceilingSpeed) 252 { 253 requestTarget = _ceilingSpeed; 254 } 255 setSpeed(requestTarget); 256 // Retart timer countdown for fan speed increase 257 _incTimer.restartOnce(_incDelay); 258 } 259 } 260 261 void Zone::incTimerExpired() 262 { 263 // Clear increase delta when timer expires allowing additional speed 264 // increase requests or speed decreases to occur 265 _incSpeedDelta = 0; 266 } 267 268 void Zone::requestSpeedDecrease(uint64_t targetDelta) 269 { 270 // Only decrease the lowest target delta requested 271 if (_decSpeedDelta == 0 || targetDelta < _decSpeedDelta) 272 { 273 _decSpeedDelta = targetDelta; 274 } 275 } 276 277 void Zone::decTimerExpired() 278 { 279 // Check all entries are set to allow a decrease 280 auto pred = [](auto const& entry) {return entry.second;}; 281 auto decAllowed = std::all_of(_decAllowed.begin(), 282 _decAllowed.end(), 283 pred); 284 285 // Only decrease speeds when allowed, 286 // where no requested increases exist and 287 // the increase timer is not running 288 // (i.e. not in the middle of increasing) 289 if (decAllowed && _incSpeedDelta == 0 && !_incTimer.isEnabled()) 290 { 291 auto requestTarget = getRequestSpeedBase(); 292 // Request target speed should not start above ceiling 293 if (requestTarget > _ceilingSpeed) 294 { 295 requestTarget = _ceilingSpeed; 296 } 297 // Target speed can not go below the defined floor speed 298 if ((requestTarget < _decSpeedDelta) || 299 (requestTarget - _decSpeedDelta < _floorSpeed)) 300 { 301 requestTarget = _floorSpeed; 302 } 303 else 304 { 305 requestTarget = requestTarget - _decSpeedDelta; 306 } 307 setSpeed(requestTarget); 308 } 309 // Clear decrease delta when timer expires 310 _decSpeedDelta = 0; 311 // Decrease timer is restarted since its repeating 312 } 313 314 void Zone::initEvent(const SetSpeedEvent& event) 315 { 316 sdbusplus::message::message nullMsg{nullptr}; 317 318 for (auto& sig : std::get<signalsPos>(event)) 319 { 320 // Setup signal matches of the property for event 321 std::unique_ptr<EventData> eventData = 322 std::make_unique<EventData>( 323 std::get<groupPos>(event), 324 std::get<sigMatchPos>(sig), 325 std::get<sigHandlerPos>(sig), 326 std::get<actionsPos>(event) 327 ); 328 329 // When match is empty, handle if zone object member 330 if (std::get<sigMatchPos>(sig).empty()) 331 { 332 fs::path path{CONTROL_OBJPATH}; 333 path /= std::to_string(_zoneNum); 334 335 // Set event data for each host group member 336 for (auto it = std::get<groupPos>(event).begin(); 337 it != std::get<groupPos>(event).end(); ++it) 338 { 339 if (it->first == path.string()) 340 { 341 // Group member interface in list owned by zone 342 if (std::find(_ifaces.begin(), _ifaces.end(), 343 std::get<intfPos>(it->second)) != _ifaces.end()) 344 { 345 // Store path,interface,property as a managed object 346 _objects[it->first] 347 [std::get<intfPos>(it->second)] 348 [std::get<propPos>(it->second)] = 349 eventData.get(); 350 } 351 } 352 } 353 } 354 355 // Initialize the event signal using handler 356 std::get<sigHandlerPos>(sig)(_bus, nullMsg, *this); 357 358 // Subscribe to signal match 359 std::unique_ptr<sdbusplus::server::match::match> match = nullptr; 360 if (!std::get<sigMatchPos>(sig).empty()) 361 { 362 match = std::make_unique<sdbusplus::server::match::match>( 363 _bus, 364 std::get<sigMatchPos>(sig).c_str(), 365 std::bind(std::mem_fn(&Zone::handleEvent), 366 this, 367 std::placeholders::_1, 368 eventData.get()) 369 ); 370 } 371 372 _signalEvents.emplace_back(std::move(eventData), std::move(match)); 373 } 374 // Attach a timer to run the action of an event 375 auto timerConf = std::get<timerConfPos>(event); 376 if (std::get<intervalPos>(timerConf) != seconds(0)) 377 { 378 addTimer(std::get<groupPos>(event), 379 std::get<actionsPos>(event), 380 timerConf); 381 } 382 // Run action functions for initial event state 383 std::for_each( 384 std::get<actionsPos>(event).begin(), 385 std::get<actionsPos>(event).end(), 386 [this, &event](auto const& action) 387 { 388 action(*this, 389 std::get<groupPos>(event)); 390 }); 391 } 392 393 void Zone::removeEvent(const SetSpeedEvent& event) 394 { 395 // Remove signals of the event 396 for (auto& sig : std::get<signalsPos>(event)) 397 { 398 auto it = findSignal(sig, 399 std::get<groupPos>(event), 400 std::get<actionsPos>(event)); 401 if (it != std::end(getSignalEvents())) 402 { 403 removeSignal(it); 404 } 405 } 406 // Remove timers of the event 407 if (std::get<intervalPos>(std::get<timerConfPos>(event)) != seconds(0)) 408 { 409 auto it = findTimer(std::get<groupPos>(event), 410 std::get<actionsPos>(event)); 411 if (it != std::end(getTimerEvents())) 412 { 413 removeTimer(it); 414 } 415 } 416 } 417 418 std::vector<SignalEvent>::iterator Zone::findSignal( 419 const Signal& signal, 420 const Group& eGroup, 421 const std::vector<Action>& eActions) 422 { 423 // Find the signal in the event to be removed 424 for (auto it = _signalEvents.begin(); it != _signalEvents.end(); ++ it) 425 { 426 const auto& seEventData = *std::get<signalEventDataPos>(*it); 427 if (eGroup == std::get<eventGroupPos>(seEventData) && 428 std::get<sigMatchPos>(signal) == 429 std::get<eventMatchPos>(seEventData) && 430 std::get<sigHandlerPos>(signal).target_type().name() == 431 std::get<eventHandlerPos>(seEventData).target_type().name() && 432 eActions.size() == std::get<eventActionsPos>(seEventData).size()) 433 { 434 // TODO openbmc/openbmc#2328 - Use the function target 435 // for comparison 436 auto actsEqual = [](auto const& a1, 437 auto const& a2) 438 { 439 return a1.target_type().name() == 440 a2.target_type().name(); 441 }; 442 if (std::equal(eActions.begin(), 443 eActions.end(), 444 std::get<eventActionsPos>(seEventData).begin(), 445 actsEqual)) 446 { 447 return it; 448 } 449 } 450 } 451 452 return _signalEvents.end(); 453 } 454 455 std::vector<TimerEvent>::iterator Zone::findTimer( 456 const Group& eventGroup, 457 const std::vector<Action>& eventActions) 458 { 459 for (auto it = _timerEvents.begin(); it != _timerEvents.end(); ++it) 460 { 461 const auto& teEventData = *std::get<timerEventDataPos>(*it); 462 if (std::get<eventActionsPos>(teEventData).size() == 463 eventActions.size()) 464 { 465 // TODO openbmc/openbmc#2328 - Use the action function target 466 // for comparison 467 auto actsEqual = [](auto const& a1, 468 auto const& a2) 469 { 470 return a1.target_type().name() == 471 a2.target_type().name(); 472 }; 473 if (std::get<eventGroupPos>(teEventData) == eventGroup && 474 std::equal(eventActions.begin(), 475 eventActions.end(), 476 std::get<eventActionsPos>(teEventData).begin(), 477 actsEqual)) 478 { 479 return it; 480 } 481 } 482 } 483 484 return _timerEvents.end(); 485 } 486 487 void Zone::addTimer(const Group& group, 488 const std::vector<Action>& actions, 489 const TimerConf& tConf) 490 { 491 auto eventData = std::make_unique<EventData>( 492 group, 493 "", 494 nullptr, 495 actions 496 ); 497 Timer timer( 498 _eventLoop, 499 std::bind(&Zone::timerExpired, 500 this, 501 std::cref(std::get<Group>(*eventData)), 502 std::cref(std::get<std::vector<Action>>(*eventData)))); 503 if (std::get<TimerType>(tConf) == TimerType::repeating) 504 { 505 timer.restart(std::get<intervalPos>(tConf)); 506 } 507 else if (std::get<TimerType>(tConf) == TimerType::oneshot) 508 { 509 timer.restartOnce(std::get<intervalPos>(tConf)); 510 } 511 else 512 { 513 throw std::invalid_argument("Invalid Timer Type"); 514 } 515 _timerEvents.emplace_back(std::move(eventData), std::move(timer)); 516 } 517 518 void Zone::timerExpired(const Group& eventGroup, 519 const std::vector<Action>& eventActions) 520 { 521 // Perform the actions 522 std::for_each(eventActions.begin(), 523 eventActions.end(), 524 [this, &eventGroup](auto const& action) 525 { 526 action(*this, eventGroup); 527 }); 528 } 529 530 void Zone::handleEvent(sdbusplus::message::message& msg, 531 const EventData* eventData) 532 { 533 // Handle the callback 534 std::get<eventHandlerPos>(*eventData)(_bus, msg, *this); 535 // Perform the actions 536 std::for_each( 537 std::get<eventActionsPos>(*eventData).begin(), 538 std::get<eventActionsPos>(*eventData).end(), 539 [this, &eventData](auto const& action) 540 { 541 action(*this, 542 std::get<eventGroupPos>(*eventData)); 543 }); 544 } 545 546 const std::string& Zone::getService(const std::string& path, 547 const std::string& intf) 548 { 549 // Retrieve service from cache 550 auto srvIter = _servTree.find(path); 551 if (srvIter != _servTree.end()) 552 { 553 for (auto& serv : srvIter->second) 554 { 555 auto it = std::find_if( 556 serv.second.begin(), 557 serv.second.end(), 558 [&intf](auto const& interface) 559 { 560 return intf == interface; 561 }); 562 if (it != std::end(serv.second)) 563 { 564 // Service found 565 return serv.first; 566 } 567 } 568 // Interface not found in cache, add and return 569 return addServices(path, intf, 0); 570 } 571 else 572 { 573 // Path not found in cache, add and return 574 return addServices(path, intf, 0); 575 } 576 } 577 578 const std::string& Zone::addServices(const std::string& path, 579 const std::string& intf, 580 int32_t depth) 581 { 582 static const std::string empty = ""; 583 auto it = _servTree.end(); 584 585 // Get all subtree objects for the given interface 586 auto objects = util::SDBusPlus::getSubTree(_bus, "/", intf, depth); 587 // Add what's returned to the cache of path->services 588 for (auto& pIter : objects) 589 { 590 auto pathIter = _servTree.find(pIter.first); 591 if (pathIter != _servTree.end()) 592 { 593 // Path found in cache 594 for (auto& sIter : pIter.second) 595 { 596 auto servIter = pathIter->second.find(sIter.first); 597 if (servIter != pathIter->second.end()) 598 { 599 // Service found in cache 600 for (auto& iIter : sIter.second) 601 { 602 if (std::find(servIter->second.begin(), 603 servIter->second.end(), 604 iIter) == servIter->second.end()) 605 { 606 // Add interface to cache 607 servIter->second.emplace_back(iIter); 608 } 609 } 610 } 611 else 612 { 613 // Service not found in cache 614 pathIter->second.insert(sIter); 615 } 616 } 617 } 618 else 619 { 620 _servTree.insert(pIter); 621 } 622 // When the paths match, since a single interface constraint is given, 623 // that is the service to return 624 if (path == pIter.first) 625 { 626 it = _servTree.find(pIter.first); 627 } 628 } 629 630 if (it != _servTree.end()) 631 { 632 return it->second.begin()->first; 633 } 634 635 return empty; 636 } 637 638 std::string Zone::current(std::string value) 639 { 640 auto current = value; 641 if (current != ThermalObject::current()) 642 { 643 current = ThermalObject::current(value); 644 saveCurrentMode(); 645 // Trigger event(s) for custom mode property change 646 fs::path path{CONTROL_OBJPATH}; 647 path /= std::to_string(_zoneNum); 648 auto eData = _objects[path.string()] 649 ["xyz.openbmc_project.Control.ThermalMode"] 650 ["Current"]; 651 if (eData != nullptr) 652 { 653 sdbusplus::message::message nullMsg{nullptr}; 654 handleEvent(nullMsg, eData); 655 } 656 } 657 return current; 658 } 659 660 void Zone::saveCurrentMode() 661 { 662 fs::path path{CONTROL_PERSIST_ROOT_PATH}; 663 // Append zone and property description 664 path /= std::to_string(_zoneNum); 665 path /= "CurrentMode"; 666 std::ofstream ofs(path.c_str(), std::ios::binary); 667 cereal::JSONOutputArchive oArch(ofs); 668 oArch(ThermalObject::current()); 669 } 670 671 void Zone::restoreCurrentMode() 672 { 673 std::string current = "Default"; 674 fs::path path{CONTROL_PERSIST_ROOT_PATH}; 675 path /= std::to_string(_zoneNum); 676 path /= "CurrentMode"; 677 fs::create_directories(path.parent_path()); 678 679 try 680 { 681 if (fs::exists(path)) 682 { 683 std::ifstream ifs(path.c_str(), std::ios::in | std::ios::binary); 684 cereal::JSONInputArchive iArch(ifs); 685 iArch(current); 686 } 687 } 688 catch (std::exception& e) 689 { 690 log<level::ERR>(e.what()); 691 fs::remove(path); 692 current = "Default"; 693 } 694 695 this->current(current); 696 } 697 698 } 699 } 700 } 701