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