1 /* 2 // Copyright (c) 2018 Intel 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 17 #include "PwmSensor.hpp" 18 19 #include "SensorPaths.hpp" 20 #include "Utils.hpp" 21 #include "sensor.hpp" 22 23 #include <sdbusplus/asio/connection.hpp> 24 #include <sdbusplus/asio/object_server.hpp> 25 26 #include <cmath> 27 #include <cstdint> 28 #include <fstream> 29 #include <iostream> 30 #include <memory> 31 #include <stdexcept> 32 #include <string> 33 34 static constexpr double sysPwmMax = 255.0; 35 static constexpr double psuPwmMax = 100.0; 36 static constexpr double defaultPwm = 30.0; 37 static constexpr double targetIfaceMax = sysPwmMax; 38 39 PwmSensor::PwmSensor(const std::string& pwmname, const std::string& sysPath, 40 std::shared_ptr<sdbusplus::asio::connection>& conn, 41 sdbusplus::asio::object_server& objectServer, 42 const std::string& sensorConfiguration, 43 const std::string& sensorType, bool isValueMutable) : 44 sysPath(sysPath), objectServer(objectServer), 45 name(sensor_paths::escapePathForDbus(pwmname)) 46 { 47 // add interface under sensor and Control.FanPwm as Control is used 48 // in obmc project, also add sensor so it can be viewed as a sensor 49 sensorInterface = objectServer.add_interface( 50 "/xyz/openbmc_project/sensors/fan_pwm/" + name, 51 "xyz.openbmc_project.Sensor.Value"); 52 uint32_t pwmValue = getValue(false); 53 if (sensorType == "PSU") 54 { 55 pwmMax = psuPwmMax; 56 } 57 else 58 { 59 pwmMax = sysPwmMax; 60 } 61 62 if (pwmValue == 0U) 63 { 64 // default pwm to non 0 65 pwmValue = static_cast<uint32_t>(pwmMax * (defaultPwm / 100.0)); 66 setValue(pwmValue); 67 } 68 double fValue = 100.0 * (static_cast<double>(pwmValue) / pwmMax); 69 sensorInterface->register_property( 70 "Value", fValue, 71 [this](const double& req, double& resp) { 72 if (!std::isfinite(req)) 73 { 74 // Reject attempted change, if to NaN or other non-numeric 75 return -1; 76 } 77 if (req > 100.0 || req < 0.0) 78 { 79 // TODO(): It does not seem desirable to halt daemon here, 80 // probably should just reject the change, continue running? 81 throw std::runtime_error("Value out of range"); 82 return -1; 83 } 84 85 double reqValue = (req / 100.0) * pwmMax; 86 double respValue = (resp / 100.0) * pwmMax; 87 auto reqInt = static_cast<uint32_t>(std::round(reqValue)); 88 auto respInt = static_cast<uint32_t>(std::round(respValue)); 89 // Avoid floating-point equality, compare as integers 90 if (reqInt == respInt) 91 { 92 return 1; 93 } 94 setValue(reqInt); 95 resp = req; 96 97 controlInterface->signal_property("Target"); 98 99 return 1; 100 }, 101 [this](double& curVal) { 102 double currScaled = (curVal / 100.0) * pwmMax; 103 auto currInt = static_cast<uint32_t>(std::round(currScaled)); 104 auto getInt = getValue(); 105 // Avoid floating-point equality, compare as integers 106 if (currInt != getInt) 107 { 108 double getScaled = 109 100.0 * (static_cast<double>(getInt) / pwmMax); 110 curVal = getScaled; 111 controlInterface->signal_property("Target"); 112 sensorInterface->signal_property("Value"); 113 } 114 return curVal; 115 }); 116 // pwm sensor interface is in percent 117 sensorInterface->register_property("MaxValue", static_cast<double>(100)); 118 sensorInterface->register_property("MinValue", static_cast<double>(0)); 119 sensorInterface->register_property("Unit", sensor_paths::unitPercent); 120 121 controlInterface = objectServer.add_interface( 122 "/xyz/openbmc_project/control/fanpwm/" + name, 123 "xyz.openbmc_project.Control.FanPwm"); 124 controlInterface->register_property( 125 "Target", static_cast<uint64_t>(pwmValue), 126 [this](const uint64_t& req, uint64_t& resp) { 127 if (req > static_cast<uint64_t>(targetIfaceMax)) 128 { 129 throw std::runtime_error("Value out of range"); 130 return -1; 131 } 132 if (req == resp) 133 { 134 return 1; 135 } 136 auto scaledValue = static_cast<double>(req) / targetIfaceMax; 137 auto roundValue = std::round(scaledValue * pwmMax); 138 setValue(static_cast<uint32_t>(roundValue)); 139 resp = req; 140 141 sensorInterface->signal_property("Value"); 142 143 return 1; 144 }, 145 [this](uint64_t& curVal) { 146 auto getInt = getValue(); 147 auto scaledValue = static_cast<double>(getInt) / pwmMax; 148 auto roundValue = std::round(scaledValue * targetIfaceMax); 149 auto value = static_cast<uint64_t>(roundValue); 150 if (curVal != value) 151 { 152 curVal = value; 153 controlInterface->signal_property("Target"); 154 sensorInterface->signal_property("Value"); 155 } 156 return curVal; 157 }); 158 159 sensorInterface->initialize(); 160 controlInterface->initialize(); 161 162 if (isValueMutable) 163 { 164 valueMutabilityInterface = 165 std::make_shared<sdbusplus::asio::dbus_interface>( 166 conn, sensorInterface->get_object_path(), 167 valueMutabilityInterfaceName); 168 valueMutabilityInterface->register_property("Mutable", true); 169 if (!valueMutabilityInterface->initialize()) 170 { 171 std::cerr 172 << "error initializing sensor value mutability interface\n"; 173 valueMutabilityInterface = nullptr; 174 } 175 } 176 177 association = objectServer.add_interface( 178 "/xyz/openbmc_project/sensors/fan_pwm/" + name, association::interface); 179 180 // PowerSupply sensors should be associated with chassis board path 181 // and inventory along with psu object. 182 if (sensorType == "PSU") 183 { 184 createInventoryAssoc(conn, association, sensorConfiguration); 185 } 186 else 187 { 188 createAssociation(association, sensorConfiguration); 189 } 190 } 191 PwmSensor::~PwmSensor() 192 { 193 objectServer.remove_interface(sensorInterface); 194 objectServer.remove_interface(controlInterface); 195 objectServer.remove_interface(association); 196 } 197 198 void PwmSensor::setValue(uint32_t value) 199 { 200 std::ofstream ref(sysPath); 201 if (!ref.good()) 202 { 203 throw std::runtime_error("Bad Write File"); 204 } 205 ref << value; 206 } 207 208 // on success returns pwm, on failure throws except on initialization, where it 209 // prints an error and returns 0 210 uint32_t PwmSensor::getValue(bool errThrow) 211 { 212 std::ifstream ref(sysPath); 213 if (!ref.good()) 214 { 215 return -1; 216 } 217 std::string line; 218 if (!std::getline(ref, line)) 219 { 220 return -1; 221 } 222 try 223 { 224 uint32_t value = std::stoi(line); 225 return value; 226 } 227 catch (const std::invalid_argument&) 228 { 229 std::cerr << "Error reading pwm at " << sysPath << "\n"; 230 // throw if not initial read to be caught by dbus bindings 231 if (errThrow) 232 { 233 throw std::runtime_error("Bad Read"); 234 } 235 } 236 return 0; 237 } 238