/** * Copyright © 2017 IBM Corporation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include "zone.hpp" #include "utility.hpp" #include "sdbusplus.hpp" namespace phosphor { namespace fan { namespace control { using namespace std::chrono; using namespace phosphor::fan; using namespace phosphor::logging; using InternalFailure = sdbusplus::xyz::openbmc_project::Common:: Error::InternalFailure; Zone::Zone(Mode mode, sdbusplus::bus::bus& bus, phosphor::fan::event::EventPtr& events, const ZoneDefinition& def) : _bus(bus), _fullSpeed(std::get(def)), _zoneNum(std::get(def)), _defFloorSpeed(std::get(def)), _defCeilingSpeed(std::get(def)), _incDelay(std::get(def)), _decInterval(std::get(def)), _incTimer(events, [this](){ this->incTimerExpired(); }), _decTimer(events, [this](){ this->decTimerExpired(); }), _sdEvents(events) { auto& fanDefs = std::get(def); for (auto& def : fanDefs) { _fans.emplace_back(std::make_unique(bus, def)); } // Do not enable set speed events when in init mode if (mode != Mode::init) { // Setup signal trigger for set speed events for (auto& event : std::get(def)) { initEvent(event); } // Start timer for fan speed decreases if (!_decTimer.running() && _decInterval != seconds::zero()) { _decTimer.start(_decInterval, util::Timer::TimerType::repeating); } } } void Zone::setSpeed(uint64_t speed) { if (_isActive) { _targetSpeed = speed; for (auto& fan : _fans) { fan->setSpeed(_targetSpeed); } } } void Zone::setFullSpeed() { if (_fullSpeed != 0) { _targetSpeed = _fullSpeed; for (auto& fan : _fans) { fan->setSpeed(_targetSpeed); } } } void Zone::setActiveAllow(const Group* group, bool isActiveAllow) { _active[*(group)] = isActiveAllow; if (!isActiveAllow) { _isActive = false; } else { // Check all entries are set to allow control active auto actPred = [](auto const& entry) {return entry.second;}; _isActive = std::all_of(_active.begin(), _active.end(), actPred); } } void Zone::setFloor(uint64_t speed) { _floorSpeed = speed; // Floor speed above target, update target to floor speed if (_targetSpeed < _floorSpeed) { requestSpeedIncrease(_floorSpeed - _targetSpeed); } } void Zone::requestSpeedIncrease(uint64_t targetDelta) { // Only increase speed when delta is higher than // the current increase delta for the zone and currently under ceiling if (targetDelta > _incSpeedDelta && _targetSpeed < _ceilingSpeed) { auto requestTarget = getRequestSpeedBase(); requestTarget = (targetDelta - _incSpeedDelta) + requestTarget; _incSpeedDelta = targetDelta; // Target speed can not go above a defined ceiling speed if (requestTarget > _ceilingSpeed) { requestTarget = _ceilingSpeed; } // Cancel current timer countdown if (_incTimer.running()) { _incTimer.stop(); } setSpeed(requestTarget); // Start timer countdown for fan speed increase _incTimer.start(_incDelay, util::Timer::TimerType::oneshot); } } void Zone::incTimerExpired() { // Clear increase delta when timer expires allowing additional speed // increase requests or speed decreases to occur _incSpeedDelta = 0; } void Zone::requestSpeedDecrease(uint64_t targetDelta) { // Only decrease the lowest target delta requested if (_decSpeedDelta == 0 || targetDelta < _decSpeedDelta) { _decSpeedDelta = targetDelta; } } void Zone::decTimerExpired() { // Only decrease speeds when no requested increases exist and // the increase timer is not running (i.e. not in the middle of increasing) if (_incSpeedDelta == 0 && !_incTimer.running()) { auto requestTarget = getRequestSpeedBase(); // Target speed can not go below the defined floor speed if ((requestTarget < _decSpeedDelta) || (requestTarget - _decSpeedDelta < _floorSpeed)) { requestTarget = _floorSpeed; } else { requestTarget = requestTarget - _decSpeedDelta; } setSpeed(requestTarget); } // Clear decrease delta when timer expires _decSpeedDelta = 0; // Decrease timer is restarted since its repeating } void Zone::initEvent(const SetSpeedEvent& event) { // Get the current value for each property for (auto& group : std::get(event)) { try { refreshProperty(_bus, group.first, std::get(group.second), std::get(group.second)); } catch (const InternalFailure& ife) { log( "Unable to find property", entry("PATH=%s", group.first.c_str()), entry("INTERFACE=%s", std::get(group.second).c_str()), entry("PROPERTY=%s", std::get(group.second).c_str())); } } // Setup signal matches for property change events for (auto& prop : std::get(event)) { std::unique_ptr eventData = std::make_unique( EventData { std::get(event), std::get(prop), std::get(event) } ); std::unique_ptr match = std::make_unique( _bus, std::get(prop).c_str(), std::bind(std::mem_fn(&Zone::handleEvent), this, std::placeholders::_1, eventData.get()) ); _signalEvents.emplace_back(std::move(eventData), std::move(match)); } // Attach a timer to run the action of an event auto eventTimer = std::get(event); if (std::get(eventTimer) != seconds(0)) { std::unique_ptr timer = std::make_unique( _sdEvents, [this, action = &(std::get(event)), group = &(std::get(event))]() { this->timerExpired(*group, *action); }); if (!timer->running()) { timer->start(std::get(eventTimer), util::Timer::TimerType::repeating); } _timerEvents.emplace_back(std::move(timer)); } // Run action functions for initial event state std::for_each( std::get(event).begin(), std::get(event).end(), [this, &event](auto const& action) { action(*this, std::get(event)); }); } void Zone::removeEvent(const SetSpeedEvent& event) { // Find the signal event to be removed auto it = std::find_if( _signalEvents.begin(), _signalEvents.end(), [&event](auto const& se) { auto seEventData = *std::get(se); if (std::get(seEventData).size() != std::get(event).size()) { return false; } else { // TODO openbmc/openbmc#2328 - Use the action function target // for comparison auto actsEqual = [](auto const& a1, auto const& a2) { return a1.target_type().name() == a2.target_type().name(); }; return ( std::get(seEventData) == std::get(event) && std::equal(std::get(event).begin(), std::get(event).end(), std::get(seEventData).begin(), actsEqual) ); } }); if (it != std::end(_signalEvents)) { std::get(*it).reset(); std::get(*it).reset(); _signalEvents.erase(it); } } void Zone::refreshProperty(sdbusplus::bus::bus& bus, const std::string& path, const std::string& iface, const std::string& prop) { PropertyVariantType property; getProperty(_bus, path, iface, prop, property); setPropertyValue(path.c_str(), iface.c_str(), prop.c_str(), property); } void Zone::getProperty(sdbusplus::bus::bus& bus, const std::string& path, const std::string& iface, const std::string& prop, PropertyVariantType& value) { auto serv = util::SDBusPlus::getService(bus, path, iface); auto hostCall = bus.new_method_call(serv.c_str(), path.c_str(), "org.freedesktop.DBus.Properties", "Get"); hostCall.append(iface); hostCall.append(prop); auto hostResponseMsg = bus.call(hostCall); if (hostResponseMsg.is_method_error()) { log("Host call response error for retrieving property"); elog(); } hostResponseMsg.read(value); } void Zone::timerExpired(Group eventGroup, std::vector eventActions) { // Perform the actions std::for_each(eventActions.begin(), eventActions.end(), [this, &eventGroup](auto const& action) { action(*this, eventGroup); }); } void Zone::handleEvent(sdbusplus::message::message& msg, const EventData* eventData) { // Handle the callback std::get(*eventData)(_bus, msg, *this); // Perform the actions std::for_each( std::get(*eventData).begin(), std::get(*eventData).end(), [this, &eventData](auto const& action) { action(*this, std::get(*eventData)); }); } } } }