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), 45 objectServer(objectServer), 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 = 100.0 * (static_cast<double>(getInt) / pwmMax); 109 curVal = getScaled; 110 controlInterface->signal_property("Target"); 111 sensorInterface->signal_property("Value"); 112 } 113 return curVal; 114 }); 115 // pwm sensor interface is in percent 116 sensorInterface->register_property("MaxValue", static_cast<double>(100)); 117 sensorInterface->register_property("MinValue", static_cast<double>(0)); 118 sensorInterface->register_property("Unit", sensor_paths::unitPercent); 119 120 controlInterface = objectServer.add_interface( 121 "/xyz/openbmc_project/control/fanpwm/" + name, 122 "xyz.openbmc_project.Control.FanPwm"); 123 controlInterface->register_property( 124 "Target", static_cast<uint64_t>(pwmValue), 125 [this](const uint64_t& req, uint64_t& resp) { 126 if (req > static_cast<uint64_t>(targetIfaceMax)) 127 { 128 throw std::runtime_error("Value out of range"); 129 return -1; 130 } 131 if (req == resp) 132 { 133 return 1; 134 } 135 auto scaledValue = static_cast<double>(req) / targetIfaceMax; 136 auto roundValue = std::round(scaledValue * pwmMax); 137 setValue(static_cast<uint32_t>(roundValue)); 138 resp = req; 139 140 sensorInterface->signal_property("Value"); 141 142 return 1; 143 }, 144 [this](uint64_t& curVal) { 145 auto getInt = getValue(); 146 auto scaledValue = static_cast<double>(getInt) / pwmMax; 147 auto roundValue = std::round(scaledValue * targetIfaceMax); 148 auto value = static_cast<uint64_t>(roundValue); 149 if (curVal != value) 150 { 151 curVal = value; 152 controlInterface->signal_property("Target"); 153 sensorInterface->signal_property("Value"); 154 } 155 return curVal; 156 }); 157 158 sensorInterface->initialize(); 159 controlInterface->initialize(); 160 161 if (isValueMutable) 162 { 163 valueMutabilityInterface = 164 std::make_shared<sdbusplus::asio::dbus_interface>( 165 conn, sensorInterface->get_object_path(), 166 valueMutabilityInterfaceName); 167 valueMutabilityInterface->register_property("Mutable", true); 168 if (!valueMutabilityInterface->initialize()) 169 { 170 std::cerr 171 << "error initializing sensor value mutability interface\n"; 172 valueMutabilityInterface = nullptr; 173 } 174 } 175 176 association = objectServer.add_interface( 177 "/xyz/openbmc_project/sensors/fan_pwm/" + name, association::interface); 178 179 // PowerSupply sensors should be associated with chassis board path 180 // and inventory along with psu object. 181 if (sensorType == "PSU") 182 { 183 createInventoryAssoc(conn, association, sensorConfiguration); 184 } 185 else 186 { 187 createAssociation(association, sensorConfiguration); 188 } 189 } 190 PwmSensor::~PwmSensor() 191 { 192 objectServer.remove_interface(sensorInterface); 193 objectServer.remove_interface(controlInterface); 194 objectServer.remove_interface(association); 195 } 196 197 void PwmSensor::setValue(uint32_t value) 198 { 199 std::ofstream ref(sysPath); 200 if (!ref.good()) 201 { 202 throw std::runtime_error("Bad Write File"); 203 } 204 ref << value; 205 } 206 207 // on success returns pwm, on failure throws except on initialization, where it 208 // prints an error and returns 0 209 uint32_t PwmSensor::getValue(bool errThrow) 210 { 211 std::ifstream ref(sysPath); 212 if (!ref.good()) 213 { 214 return -1; 215 } 216 std::string line; 217 if (!std::getline(ref, line)) 218 { 219 return -1; 220 } 221 try 222 { 223 uint32_t value = std::stoi(line); 224 return value; 225 } 226 catch (const std::invalid_argument&) 227 { 228 std::cerr << "Error reading pwm at " << sysPath << "\n"; 229 // throw if not initial read to be caught by dbus bindings 230 if (errThrow) 231 { 232 throw std::runtime_error("Bad Read"); 233 } 234 } 235 return 0; 236 } 237