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