13f7c5e40SJason M. Bills /* 23f7c5e40SJason M. Bills // Copyright (c) 2017 2018 Intel Corporation 33f7c5e40SJason M. Bills // 43f7c5e40SJason M. Bills // Licensed under the Apache License, Version 2.0 (the "License"); 53f7c5e40SJason M. Bills // you may not use this file except in compliance with the License. 63f7c5e40SJason M. Bills // You may obtain a copy of the License at 73f7c5e40SJason M. Bills // 83f7c5e40SJason M. Bills // http://www.apache.org/licenses/LICENSE-2.0 93f7c5e40SJason M. Bills // 103f7c5e40SJason M. Bills // Unless required by applicable law or agreed to in writing, software 113f7c5e40SJason M. Bills // distributed under the License is distributed on an "AS IS" BASIS, 123f7c5e40SJason M. Bills // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 133f7c5e40SJason M. Bills // See the License for the specific language governing permissions and 143f7c5e40SJason M. Bills // limitations under the License. 153f7c5e40SJason M. Bills */ 163f7c5e40SJason M. Bills 173f7c5e40SJason M. Bills #pragma once 18*17e21c20SJosh Lehan #include <algorithm> 1972867debSJason M. Bills #include <cmath> 2072867debSJason M. Bills #include <iostream> 213f7c5e40SJason M. Bills 223f7c5e40SJason M. Bills namespace ipmi 233f7c5e40SJason M. Bills { 2472867debSJason M. Bills static constexpr int16_t maxInt10 = 0x1FF; 2539417c7bSJames Feist static constexpr int16_t minInt10 = -0x200; 2672867debSJason M. Bills static constexpr int8_t maxInt4 = 7; 2772867debSJason M. Bills static constexpr int8_t minInt4 = -8; 2872867debSJason M. Bills 29*17e21c20SJosh Lehan // Helper function to avoid repeated complicated expression 30*17e21c20SJosh Lehan // TODO(): Refactor to add a proper sensorutils.cpp file, 31*17e21c20SJosh Lehan // instead of putting everything in this header as it is now, 32*17e21c20SJosh Lehan // so that helper functions can be correctly hidden from callers. 33*17e21c20SJosh Lehan static inline bool baseInRange(double base) 34*17e21c20SJosh Lehan { 35*17e21c20SJosh Lehan auto min10 = static_cast<double>(minInt10); 36*17e21c20SJosh Lehan auto max10 = static_cast<double>(maxInt10); 37*17e21c20SJosh Lehan 38*17e21c20SJosh Lehan return ((base >= min10) && (base <= max10)); 39*17e21c20SJosh Lehan } 40*17e21c20SJosh Lehan 41*17e21c20SJosh Lehan // Helper function for internal use by getSensorAttributes() 42*17e21c20SJosh Lehan // Ensures floating-point "base" is within bounds, 43*17e21c20SJosh Lehan // and adjusts integer exponent "expShift" accordingly. 44*17e21c20SJosh Lehan // To minimize data loss when later truncating to integer, 45*17e21c20SJosh Lehan // the floating-point "base" will be as large as possible, 46*17e21c20SJosh Lehan // but still within the bounds (minInt10,maxInt10). 47*17e21c20SJosh Lehan // The bounds of "expShift" are (minInt4,maxInt4). 48*17e21c20SJosh Lehan // Consider this equation: n = base * (10.0 ** expShift) 49*17e21c20SJosh Lehan // This function will try to maximize "base", 50*17e21c20SJosh Lehan // adjusting "expShift" to keep the value "n" unchanged, 51*17e21c20SJosh Lehan // while keeping base and expShift within bounds. 52*17e21c20SJosh Lehan // Returns true if successful, modifies values in-place 53*17e21c20SJosh Lehan static inline bool scaleFloatExp(double& base, int8_t& expShift) 54*17e21c20SJosh Lehan { 55*17e21c20SJosh Lehan auto min10 = static_cast<double>(minInt10); 56*17e21c20SJosh Lehan auto max10 = static_cast<double>(maxInt10); 57*17e21c20SJosh Lehan 58*17e21c20SJosh Lehan // Comparing with zero should be OK, zero is special in floating-point 59*17e21c20SJosh Lehan // If base is exactly zero, no adjustment of the exponent is necessary 60*17e21c20SJosh Lehan if (base == 0.0) 61*17e21c20SJosh Lehan { 62*17e21c20SJosh Lehan return true; 63*17e21c20SJosh Lehan } 64*17e21c20SJosh Lehan 65*17e21c20SJosh Lehan // As long as base value is within allowed range, expand precision 66*17e21c20SJosh Lehan // This will help to avoid loss when later rounding to integer 67*17e21c20SJosh Lehan while (baseInRange(base)) 68*17e21c20SJosh Lehan { 69*17e21c20SJosh Lehan if (expShift <= minInt4) 70*17e21c20SJosh Lehan { 71*17e21c20SJosh Lehan // Already at the minimum expShift, can not decrement it more 72*17e21c20SJosh Lehan break; 73*17e21c20SJosh Lehan } 74*17e21c20SJosh Lehan 75*17e21c20SJosh Lehan // Multiply by 10, but shift decimal point to the left, no net change 76*17e21c20SJosh Lehan base *= 10.0; 77*17e21c20SJosh Lehan --expShift; 78*17e21c20SJosh Lehan } 79*17e21c20SJosh Lehan 80*17e21c20SJosh Lehan // As long as base value is *not* within range, shrink precision 81*17e21c20SJosh Lehan // This will pull base value closer to zero, thus within range 82*17e21c20SJosh Lehan while (!(baseInRange(base))) 83*17e21c20SJosh Lehan { 84*17e21c20SJosh Lehan if (expShift >= maxInt4) 85*17e21c20SJosh Lehan { 86*17e21c20SJosh Lehan // Already at the maximum expShift, can not increment it more 87*17e21c20SJosh Lehan break; 88*17e21c20SJosh Lehan } 89*17e21c20SJosh Lehan 90*17e21c20SJosh Lehan // Divide by 10, but shift decimal point to the right, no net change 91*17e21c20SJosh Lehan base /= 10.0; 92*17e21c20SJosh Lehan ++expShift; 93*17e21c20SJosh Lehan } 94*17e21c20SJosh Lehan 95*17e21c20SJosh Lehan // If the above loop was not able to pull it back within range, 96*17e21c20SJosh Lehan // the base value is beyond what expShift can represent, return false. 97*17e21c20SJosh Lehan return baseInRange(base); 98*17e21c20SJosh Lehan } 99*17e21c20SJosh Lehan 100*17e21c20SJosh Lehan // Helper function for internal use by getSensorAttributes() 101*17e21c20SJosh Lehan // Ensures integer "ibase" is no larger than necessary, 102*17e21c20SJosh Lehan // by normalizing it so that the decimal point shift is in the exponent, 103*17e21c20SJosh Lehan // whenever possible. 104*17e21c20SJosh Lehan // This provides more consistent results, 105*17e21c20SJosh Lehan // as many equivalent solutions are collapsed into one consistent solution. 106*17e21c20SJosh Lehan // If integer "ibase" is a clean multiple of 10, 107*17e21c20SJosh Lehan // divide it by 10 (this is lossless), so it is closer to zero. 108*17e21c20SJosh Lehan // Also modify floating-point "dbase" at the same time, 109*17e21c20SJosh Lehan // as both integer and floating-point base share the same expShift. 110*17e21c20SJosh Lehan // Example: (ibase=300, expShift=2) becomes (ibase=3, expShift=4) 111*17e21c20SJosh Lehan // because the underlying value is the same: 200*(10**2) == 2*(10**4) 112*17e21c20SJosh Lehan // Always successful, modifies values in-place 113*17e21c20SJosh Lehan static inline void normalizeIntExp(int16_t& ibase, int8_t& expShift, 114*17e21c20SJosh Lehan double& dbase) 115*17e21c20SJosh Lehan { 116*17e21c20SJosh Lehan for (;;) 117*17e21c20SJosh Lehan { 118*17e21c20SJosh Lehan // If zero, already normalized, ensure exponent also zero 119*17e21c20SJosh Lehan if (ibase == 0) 120*17e21c20SJosh Lehan { 121*17e21c20SJosh Lehan expShift = 0; 122*17e21c20SJosh Lehan break; 123*17e21c20SJosh Lehan } 124*17e21c20SJosh Lehan 125*17e21c20SJosh Lehan // If not cleanly divisible by 10, already normalized 126*17e21c20SJosh Lehan if ((ibase % 10) != 0) 127*17e21c20SJosh Lehan { 128*17e21c20SJosh Lehan break; 129*17e21c20SJosh Lehan } 130*17e21c20SJosh Lehan 131*17e21c20SJosh Lehan // If exponent already at max, already normalized 132*17e21c20SJosh Lehan if (expShift >= maxInt4) 133*17e21c20SJosh Lehan { 134*17e21c20SJosh Lehan break; 135*17e21c20SJosh Lehan } 136*17e21c20SJosh Lehan 137*17e21c20SJosh Lehan // Bring values closer to zero, correspondingly shift exponent, 138*17e21c20SJosh Lehan // without changing the underlying number that this all represents, 139*17e21c20SJosh Lehan // similar to what is done by scaleFloatExp(). 140*17e21c20SJosh Lehan // The floating-point base must be kept in sync with the integer base, 141*17e21c20SJosh Lehan // as both floating-point and integer share the same exponent. 142*17e21c20SJosh Lehan ibase /= 10; 143*17e21c20SJosh Lehan dbase /= 10.0; 144*17e21c20SJosh Lehan ++expShift; 145*17e21c20SJosh Lehan } 146*17e21c20SJosh Lehan } 147*17e21c20SJosh Lehan 148*17e21c20SJosh Lehan // The IPMI equation: 149*17e21c20SJosh Lehan // y = (Mx + (B * 10^(bExp))) * 10^(rExp) 150*17e21c20SJosh Lehan // Section 36.3 of this document: 151*17e21c20SJosh Lehan // https://www.intel.com/content/dam/www/public/us/en/documents/product-briefs/ipmi-second-gen-interface-spec-v2-rev1-1.pdf 152*17e21c20SJosh Lehan // 153*17e21c20SJosh Lehan // The goal is to exactly match the math done by the ipmitool command, 154*17e21c20SJosh Lehan // at the other side of the interface: 155*17e21c20SJosh Lehan // https://github.com/ipmitool/ipmitool/blob/42a023ff0726c80e8cc7d30315b987fe568a981d/lib/ipmi_sdr.c#L360 156*17e21c20SJosh Lehan // 157*17e21c20SJosh Lehan // To use with Wolfram Alpha, make all variables single letters 158*17e21c20SJosh Lehan // bExp becomes E, rExp becomes R 159*17e21c20SJosh Lehan // https://www.wolframalpha.com/input/?i=y%3D%28%28M*x%29%2B%28B*%2810%5EE%29%29%29*%2810%5ER%29 16072867debSJason M. Bills static inline bool getSensorAttributes(const double max, const double min, 16172867debSJason M. Bills int16_t& mValue, int8_t& rExp, 16272867debSJason M. Bills int16_t& bValue, int8_t& bExp, 16372867debSJason M. Bills bool& bSigned) 16472867debSJason M. Bills { 165*17e21c20SJosh Lehan if (!(std::isfinite(min))) 166*17e21c20SJosh Lehan { 167*17e21c20SJosh Lehan std::cerr << "getSensorAttributes: Min value is unusable\n"; 168*17e21c20SJosh Lehan return false; 169*17e21c20SJosh Lehan } 170*17e21c20SJosh Lehan if (!(std::isfinite(max))) 171*17e21c20SJosh Lehan { 172*17e21c20SJosh Lehan std::cerr << "getSensorAttributes: Max value is unusable\n"; 173*17e21c20SJosh Lehan return false; 174*17e21c20SJosh Lehan } 175*17e21c20SJosh Lehan 176*17e21c20SJosh Lehan // Because NAN has already been tested for, this comparison works 17739417c7bSJames Feist if (max <= min) 17872867debSJason M. Bills { 17953870d73SVernon Mauery std::cerr << "getSensorAttributes: Max must be greater than min\n"; 18072867debSJason M. Bills return false; 18172867debSJason M. Bills } 18239417c7bSJames Feist 183*17e21c20SJosh Lehan // Given min and max, we must solve for M, B, bExp, rExp 184*17e21c20SJosh Lehan // y comes in from D-Bus (the actual sensor reading) 185*17e21c20SJosh Lehan // x is calculated from y by scaleIPMIValueFromDouble() below 186*17e21c20SJosh Lehan // If y is min, x should equal = 0 (or -128 if signed) 187*17e21c20SJosh Lehan // If y is max, x should equal 255 (or 127 if signed) 188*17e21c20SJosh Lehan double fullRange = max - min; 189*17e21c20SJosh Lehan double lowestX; 19072867debSJason M. Bills 191*17e21c20SJosh Lehan rExp = 0; 192*17e21c20SJosh Lehan bExp = 0; 193*17e21c20SJosh Lehan 194*17e21c20SJosh Lehan // TODO(): The IPMI document is ambiguous, as to whether 195*17e21c20SJosh Lehan // the resulting byte should be signed or unsigned, 196*17e21c20SJosh Lehan // essentially leaving it up to the caller. 197*17e21c20SJosh Lehan // The document just refers to it as "raw reading", 198*17e21c20SJosh Lehan // or "byte of reading", without giving further details. 199*17e21c20SJosh Lehan // Previous code set it signed if min was less than zero, 200*17e21c20SJosh Lehan // so I'm sticking with that, until I learn otherwise. 201*17e21c20SJosh Lehan if (min < 0.0) 20272867debSJason M. Bills { 203*17e21c20SJosh Lehan // TODO(): It would be worth experimenting with the range (-127,127), 204*17e21c20SJosh Lehan // instead of the range (-128,127), because this 205*17e21c20SJosh Lehan // would give good symmetry around zero, and make results look better. 206*17e21c20SJosh Lehan // Divide by 254 instead of 255, and change -128 to -127 elsewhere. 20772867debSJason M. Bills bSigned = true; 208*17e21c20SJosh Lehan lowestX = -128.0; 20972867debSJason M. Bills } 21072867debSJason M. Bills else 21172867debSJason M. Bills { 21272867debSJason M. Bills bSigned = false; 213*17e21c20SJosh Lehan lowestX = 0.0; 21472867debSJason M. Bills } 21572867debSJason M. Bills 216*17e21c20SJosh Lehan // Step 1: Set y to (max - min), set x to 255, set B to 0, solve for M 217*17e21c20SJosh Lehan // This works, regardless of signed or unsigned, 218*17e21c20SJosh Lehan // because total range is the same. 219*17e21c20SJosh Lehan double dM = fullRange / 255.0; 22072867debSJason M. Bills 221*17e21c20SJosh Lehan // Step 2: Constrain M, and set rExp accordingly 222*17e21c20SJosh Lehan if (!(scaleFloatExp(dM, rExp))) 22372867debSJason M. Bills { 224*17e21c20SJosh Lehan std::cerr << "getSensorAttributes: Multiplier range exceeds scale (M=" 225*17e21c20SJosh Lehan << dM << ", rExp=" << (int)rExp << ")\n"; 22672867debSJason M. Bills return false; 22772867debSJason M. Bills } 22872867debSJason M. Bills 229*17e21c20SJosh Lehan mValue = static_cast<int16_t>(std::round(dM)); 230*17e21c20SJosh Lehan 231*17e21c20SJosh Lehan normalizeIntExp(mValue, rExp, dM); 232*17e21c20SJosh Lehan 233*17e21c20SJosh Lehan // The multiplier can not be zero, for obvious reasons 234*17e21c20SJosh Lehan if (mValue == 0) 23572867debSJason M. Bills { 236*17e21c20SJosh Lehan std::cerr << "getSensorAttributes: Multiplier range below scale\n"; 23772867debSJason M. Bills return false; 23872867debSJason M. Bills } 239*17e21c20SJosh Lehan 240*17e21c20SJosh Lehan // Step 3: set y to min, set x to min, keep M and rExp, solve for B 241*17e21c20SJosh Lehan // If negative, x will be -128 (the most negative possible byte), not 0 242*17e21c20SJosh Lehan 243*17e21c20SJosh Lehan // Solve the IPMI equation for B, instead of y 244*17e21c20SJosh Lehan // https://www.wolframalpha.com/input/?i=solve+y%3D%28%28M*x%29%2B%28B*%2810%5EE%29%29%29*%2810%5ER%29+for+B 245*17e21c20SJosh Lehan // B = 10^(-rExp - bExp) (y - M 10^rExp x) 246*17e21c20SJosh Lehan // TODO(): Compare with this alternative solution from SageMathCell 247*17e21c20SJosh Lehan // https://sagecell.sagemath.org/?z=eJyrtC1LLNJQr1TX5KqAMCuATF8I0xfIdIIwnYDMIteKAggPxAIKJMEFkiACxfk5Zaka0ZUKtrYKGhq-CloKFZoK2goaTkCWhqGBgpaWAkilpqYmQgBklmasjoKTJgDAECTH&lang=sage&interacts=eJyLjgUAARUAuQ== 248*17e21c20SJosh Lehan double dB = std::pow(10.0, ((-rExp) - bExp)) * 249*17e21c20SJosh Lehan (min - ((dM * std::pow(10.0, rExp) * lowestX))); 250*17e21c20SJosh Lehan 251*17e21c20SJosh Lehan // Step 4: Constrain B, and set bExp accordingly 252*17e21c20SJosh Lehan if (!(scaleFloatExp(dB, bExp))) 25372867debSJason M. Bills { 254*17e21c20SJosh Lehan std::cerr << "getSensorAttributes: Offset (B=" << dB 255*17e21c20SJosh Lehan << ", bExp=" << (int)bExp 256*17e21c20SJosh Lehan << ") exceeds multiplier scale (M=" << dM 257*17e21c20SJosh Lehan << ", rExp=" << (int)rExp << ")\n"; 25872867debSJason M. Bills return false; 25972867debSJason M. Bills } 26072867debSJason M. Bills 261*17e21c20SJosh Lehan bValue = static_cast<int16_t>(std::round(dB)); 26272867debSJason M. Bills 263*17e21c20SJosh Lehan normalizeIntExp(bValue, bExp, dB); 26472867debSJason M. Bills 265*17e21c20SJosh Lehan // Unlike the multiplier, it is perfectly OK for bValue to be zero 26672867debSJason M. Bills return true; 26772867debSJason M. Bills } 26872867debSJason M. Bills 26972867debSJason M. Bills static inline uint8_t 270*17e21c20SJosh Lehan scaleIPMIValueFromDouble(const double value, const int16_t mValue, 271*17e21c20SJosh Lehan const int8_t rExp, const int16_t bValue, 27272867debSJason M. Bills const int8_t bExp, const bool bSigned) 27372867debSJason M. Bills { 274*17e21c20SJosh Lehan // Avoid division by zero below 275*17e21c20SJosh Lehan if (mValue == 0) 276*17e21c20SJosh Lehan { 277*17e21c20SJosh Lehan throw std::out_of_range("Scaling multiplier is uninitialized"); 278*17e21c20SJosh Lehan } 27939417c7bSJames Feist 280*17e21c20SJosh Lehan auto dM = static_cast<double>(mValue); 281*17e21c20SJosh Lehan auto dB = static_cast<double>(bValue); 282*17e21c20SJosh Lehan 283*17e21c20SJosh Lehan // Solve the IPMI equation for x, instead of y 284*17e21c20SJosh Lehan // https://www.wolframalpha.com/input/?i=solve+y%3D%28%28M*x%29%2B%28B*%2810%5EE%29%29%29*%2810%5ER%29+for+x 285*17e21c20SJosh Lehan // x = (10^(-rExp) (y - B 10^(rExp + bExp)))/M and M 10^rExp!=0 286*17e21c20SJosh Lehan // TODO(): Compare with this alternative solution from SageMathCell 287*17e21c20SJosh Lehan // https://sagecell.sagemath.org/?z=eJyrtC1LLNJQr1TX5KqAMCuATF8I0xfIdIIwnYDMIteKAggPxAIKJMEFkiACxfk5Zaka0ZUKtrYKGhq-CloKFZoK2goaTkCWhqGBgpaWAkilpqYmQgBklmasDlAlAMB8JP0=&lang=sage&interacts=eJyLjgUAARUAuQ== 288*17e21c20SJosh Lehan double dX = 289*17e21c20SJosh Lehan (std::pow(10.0, -rExp) * (value - (dB * std::pow(10.0, rExp + bExp)))) / 290*17e21c20SJosh Lehan dM; 291*17e21c20SJosh Lehan 292*17e21c20SJosh Lehan auto scaledValue = static_cast<int32_t>(std::round(dX)); 293*17e21c20SJosh Lehan 294*17e21c20SJosh Lehan int32_t minClamp; 295*17e21c20SJosh Lehan int32_t maxClamp; 296*17e21c20SJosh Lehan 297*17e21c20SJosh Lehan // Because of rounding and integer truncation of scaling factors, 298*17e21c20SJosh Lehan // sometimes the resulting byte is slightly out of range. 299*17e21c20SJosh Lehan // Still allow this, but clamp the values to range. 300f6a07837SJames Feist if (bSigned) 301f6a07837SJames Feist { 302*17e21c20SJosh Lehan minClamp = std::numeric_limits<int8_t>::lowest(); 303*17e21c20SJosh Lehan maxClamp = std::numeric_limits<int8_t>::max(); 304f6a07837SJames Feist } 305f6a07837SJames Feist else 306f6a07837SJames Feist { 307*17e21c20SJosh Lehan minClamp = std::numeric_limits<uint8_t>::lowest(); 308*17e21c20SJosh Lehan maxClamp = std::numeric_limits<uint8_t>::max(); 30939417c7bSJames Feist } 310*17e21c20SJosh Lehan 311*17e21c20SJosh Lehan auto clampedValue = std::clamp(scaledValue, minClamp, maxClamp); 312*17e21c20SJosh Lehan 313*17e21c20SJosh Lehan // This works for both signed and unsigned, 314*17e21c20SJosh Lehan // because it is the same underlying byte storage. 315*17e21c20SJosh Lehan return static_cast<uint8_t>(clampedValue); 31672867debSJason M. Bills } 31772867debSJason M. Bills 31872867debSJason M. Bills static inline uint8_t getScaledIPMIValue(const double value, const double max, 31972867debSJason M. Bills const double min) 32072867debSJason M. Bills { 32172867debSJason M. Bills int16_t mValue = 0; 32272867debSJason M. Bills int8_t rExp = 0; 32372867debSJason M. Bills int16_t bValue = 0; 32472867debSJason M. Bills int8_t bExp = 0; 325*17e21c20SJosh Lehan bool bSigned = false; 32672867debSJason M. Bills 327*17e21c20SJosh Lehan bool result = 328*17e21c20SJosh Lehan getSensorAttributes(max, min, mValue, rExp, bValue, bExp, bSigned); 32972867debSJason M. Bills if (!result) 33072867debSJason M. Bills { 33139417c7bSJames Feist throw std::runtime_error("Illegal sensor attributes"); 33272867debSJason M. Bills } 333*17e21c20SJosh Lehan 33472867debSJason M. Bills return scaleIPMIValueFromDouble(value, mValue, rExp, bValue, bExp, bSigned); 33572867debSJason M. Bills } 33672867debSJason M. Bills 3373f7c5e40SJason M. Bills } // namespace ipmi 338