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