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
1817e21c20SJosh Lehan #include <algorithm>
1972867debSJason M. Bills #include <cmath>
20ae339045SVernon Mauery #include <cstdint>
2172867debSJason M. Bills #include <iostream>
223f7c5e40SJason M. Bills
233f7c5e40SJason M. Bills namespace ipmi
243f7c5e40SJason M. Bills {
2572867debSJason M. Bills static constexpr int16_t maxInt10 = 0x1FF;
2639417c7bSJames Feist static constexpr int16_t minInt10 = -0x200;
2772867debSJason M. Bills static constexpr int8_t maxInt4 = 7;
2872867debSJason M. Bills static constexpr int8_t minInt4 = -8;
2972867debSJason M. Bills
3017e21c20SJosh Lehan // Helper function to avoid repeated complicated expression
3117e21c20SJosh Lehan // TODO(): Refactor to add a proper sensorutils.cpp file,
3217e21c20SJosh Lehan // instead of putting everything in this header as it is now,
3317e21c20SJosh Lehan // so that helper functions can be correctly hidden from callers.
baseInRange(double base)3417e21c20SJosh Lehan static inline bool baseInRange(double base)
3517e21c20SJosh Lehan {
3617e21c20SJosh Lehan auto min10 = static_cast<double>(minInt10);
3717e21c20SJosh Lehan auto max10 = static_cast<double>(maxInt10);
3817e21c20SJosh Lehan
3917e21c20SJosh Lehan return ((base >= min10) && (base <= max10));
4017e21c20SJosh Lehan }
4117e21c20SJosh Lehan
4217e21c20SJosh Lehan // Helper function for internal use by getSensorAttributes()
4317e21c20SJosh Lehan // Ensures floating-point "base" is within bounds,
4417e21c20SJosh Lehan // and adjusts integer exponent "expShift" accordingly.
4517e21c20SJosh Lehan // To minimize data loss when later truncating to integer,
4617e21c20SJosh Lehan // the floating-point "base" will be as large as possible,
4717e21c20SJosh Lehan // but still within the bounds (minInt10,maxInt10).
4817e21c20SJosh Lehan // The bounds of "expShift" are (minInt4,maxInt4).
4917e21c20SJosh Lehan // Consider this equation: n = base * (10.0 ** expShift)
5017e21c20SJosh Lehan // This function will try to maximize "base",
5117e21c20SJosh Lehan // adjusting "expShift" to keep the value "n" unchanged,
5217e21c20SJosh Lehan // while keeping base and expShift within bounds.
5317e21c20SJosh Lehan // Returns true if successful, modifies values in-place
scaleFloatExp(double & base,int8_t & expShift)5417e21c20SJosh Lehan static inline bool scaleFloatExp(double& base, int8_t& expShift)
5517e21c20SJosh Lehan {
5617e21c20SJosh Lehan // Comparing with zero should be OK, zero is special in floating-point
5717e21c20SJosh Lehan // If base is exactly zero, no adjustment of the exponent is necessary
5817e21c20SJosh Lehan if (base == 0.0)
5917e21c20SJosh Lehan {
6017e21c20SJosh Lehan return true;
6117e21c20SJosh Lehan }
6217e21c20SJosh Lehan
6317e21c20SJosh Lehan // As long as base value is within allowed range, expand precision
6417e21c20SJosh Lehan // This will help to avoid loss when later rounding to integer
6517e21c20SJosh Lehan while (baseInRange(base))
6617e21c20SJosh Lehan {
6717e21c20SJosh Lehan if (expShift <= minInt4)
6817e21c20SJosh Lehan {
6917e21c20SJosh Lehan // Already at the minimum expShift, can not decrement it more
7017e21c20SJosh Lehan break;
7117e21c20SJosh Lehan }
7217e21c20SJosh Lehan
7317e21c20SJosh Lehan // Multiply by 10, but shift decimal point to the left, no net change
7417e21c20SJosh Lehan base *= 10.0;
7517e21c20SJosh Lehan --expShift;
7617e21c20SJosh Lehan }
7717e21c20SJosh Lehan
7817e21c20SJosh Lehan // As long as base value is *not* within range, shrink precision
7917e21c20SJosh Lehan // This will pull base value closer to zero, thus within range
8017e21c20SJosh Lehan while (!(baseInRange(base)))
8117e21c20SJosh Lehan {
8217e21c20SJosh Lehan if (expShift >= maxInt4)
8317e21c20SJosh Lehan {
8417e21c20SJosh Lehan // Already at the maximum expShift, can not increment it more
8517e21c20SJosh Lehan break;
8617e21c20SJosh Lehan }
8717e21c20SJosh Lehan
8817e21c20SJosh Lehan // Divide by 10, but shift decimal point to the right, no net change
8917e21c20SJosh Lehan base /= 10.0;
9017e21c20SJosh Lehan ++expShift;
9117e21c20SJosh Lehan }
9217e21c20SJosh Lehan
9317e21c20SJosh Lehan // If the above loop was not able to pull it back within range,
9417e21c20SJosh Lehan // the base value is beyond what expShift can represent, return false.
9517e21c20SJosh Lehan return baseInRange(base);
9617e21c20SJosh Lehan }
9717e21c20SJosh Lehan
9817e21c20SJosh Lehan // Helper function for internal use by getSensorAttributes()
9917e21c20SJosh Lehan // Ensures integer "ibase" is no larger than necessary,
10017e21c20SJosh Lehan // by normalizing it so that the decimal point shift is in the exponent,
10117e21c20SJosh Lehan // whenever possible.
10217e21c20SJosh Lehan // This provides more consistent results,
10317e21c20SJosh Lehan // as many equivalent solutions are collapsed into one consistent solution.
10417e21c20SJosh Lehan // If integer "ibase" is a clean multiple of 10,
10517e21c20SJosh Lehan // divide it by 10 (this is lossless), so it is closer to zero.
10617e21c20SJosh Lehan // Also modify floating-point "dbase" at the same time,
10717e21c20SJosh Lehan // as both integer and floating-point base share the same expShift.
10817e21c20SJosh Lehan // Example: (ibase=300, expShift=2) becomes (ibase=3, expShift=4)
10917e21c20SJosh Lehan // because the underlying value is the same: 200*(10**2) == 2*(10**4)
11017e21c20SJosh Lehan // Always successful, modifies values in-place
normalizeIntExp(int16_t & ibase,int8_t & expShift,double & dbase)11117e21c20SJosh Lehan static inline void normalizeIntExp(int16_t& ibase, int8_t& expShift,
11217e21c20SJosh Lehan double& dbase)
11317e21c20SJosh Lehan {
11417e21c20SJosh Lehan for (;;)
11517e21c20SJosh Lehan {
11617e21c20SJosh Lehan // If zero, already normalized, ensure exponent also zero
11717e21c20SJosh Lehan if (ibase == 0)
11817e21c20SJosh Lehan {
11917e21c20SJosh Lehan expShift = 0;
12017e21c20SJosh Lehan break;
12117e21c20SJosh Lehan }
12217e21c20SJosh Lehan
12317e21c20SJosh Lehan // If not cleanly divisible by 10, already normalized
12417e21c20SJosh Lehan if ((ibase % 10) != 0)
12517e21c20SJosh Lehan {
12617e21c20SJosh Lehan break;
12717e21c20SJosh Lehan }
12817e21c20SJosh Lehan
12917e21c20SJosh Lehan // If exponent already at max, already normalized
13017e21c20SJosh Lehan if (expShift >= maxInt4)
13117e21c20SJosh Lehan {
13217e21c20SJosh Lehan break;
13317e21c20SJosh Lehan }
13417e21c20SJosh Lehan
13517e21c20SJosh Lehan // Bring values closer to zero, correspondingly shift exponent,
13617e21c20SJosh Lehan // without changing the underlying number that this all represents,
13717e21c20SJosh Lehan // similar to what is done by scaleFloatExp().
13817e21c20SJosh Lehan // The floating-point base must be kept in sync with the integer base,
13917e21c20SJosh Lehan // as both floating-point and integer share the same exponent.
14017e21c20SJosh Lehan ibase /= 10;
14117e21c20SJosh Lehan dbase /= 10.0;
14217e21c20SJosh Lehan ++expShift;
14317e21c20SJosh Lehan }
14417e21c20SJosh Lehan }
14517e21c20SJosh Lehan
14617e21c20SJosh Lehan // The IPMI equation:
14717e21c20SJosh Lehan // y = (Mx + (B * 10^(bExp))) * 10^(rExp)
14817e21c20SJosh Lehan // Section 36.3 of this document:
14917e21c20SJosh Lehan // https://www.intel.com/content/dam/www/public/us/en/documents/product-briefs/ipmi-second-gen-interface-spec-v2-rev1-1.pdf
15017e21c20SJosh Lehan //
15117e21c20SJosh Lehan // The goal is to exactly match the math done by the ipmitool command,
15217e21c20SJosh Lehan // at the other side of the interface:
15317e21c20SJosh Lehan // https://github.com/ipmitool/ipmitool/blob/42a023ff0726c80e8cc7d30315b987fe568a981d/lib/ipmi_sdr.c#L360
15417e21c20SJosh Lehan //
15517e21c20SJosh Lehan // To use with Wolfram Alpha, make all variables single letters
15617e21c20SJosh Lehan // bExp becomes E, rExp becomes R
15717e21c20SJosh Lehan // https://www.wolframalpha.com/input/?i=y%3D%28%28M*x%29%2B%28B*%2810%5EE%29%29%29*%2810%5ER%29
getSensorAttributes(const double max,const double min,int16_t & mValue,int8_t & rExp,int16_t & bValue,int8_t & bExp,bool & bSigned)158*1bcced08SPatrick Williams static inline bool getSensorAttributes(
159*1bcced08SPatrick Williams const double max, const double min, int16_t& mValue, int8_t& rExp,
160*1bcced08SPatrick Williams int16_t& bValue, int8_t& bExp, bool& bSigned)
16172867debSJason M. Bills {
16217e21c20SJosh Lehan if (!(std::isfinite(min)))
16317e21c20SJosh Lehan {
16417e21c20SJosh Lehan std::cerr << "getSensorAttributes: Min value is unusable\n";
16517e21c20SJosh Lehan return false;
16617e21c20SJosh Lehan }
16717e21c20SJosh Lehan if (!(std::isfinite(max)))
16817e21c20SJosh Lehan {
16917e21c20SJosh Lehan std::cerr << "getSensorAttributes: Max value is unusable\n";
17017e21c20SJosh Lehan return false;
17117e21c20SJosh Lehan }
17217e21c20SJosh Lehan
17317e21c20SJosh Lehan // Because NAN has already been tested for, this comparison works
17439417c7bSJames Feist if (max <= min)
17572867debSJason M. Bills {
17653870d73SVernon Mauery std::cerr << "getSensorAttributes: Max must be greater than min\n";
17772867debSJason M. Bills return false;
17872867debSJason M. Bills }
17939417c7bSJames Feist
18017e21c20SJosh Lehan // Given min and max, we must solve for M, B, bExp, rExp
18117e21c20SJosh Lehan // y comes in from D-Bus (the actual sensor reading)
18217e21c20SJosh Lehan // x is calculated from y by scaleIPMIValueFromDouble() below
18317e21c20SJosh Lehan // If y is min, x should equal = 0 (or -128 if signed)
18417e21c20SJosh Lehan // If y is max, x should equal 255 (or 127 if signed)
18517e21c20SJosh Lehan double fullRange = max - min;
18617e21c20SJosh Lehan double lowestX;
18772867debSJason M. Bills
18817e21c20SJosh Lehan rExp = 0;
18917e21c20SJosh Lehan bExp = 0;
19017e21c20SJosh Lehan
19117e21c20SJosh Lehan // TODO(): The IPMI document is ambiguous, as to whether
19217e21c20SJosh Lehan // the resulting byte should be signed or unsigned,
19317e21c20SJosh Lehan // essentially leaving it up to the caller.
19417e21c20SJosh Lehan // The document just refers to it as "raw reading",
19517e21c20SJosh Lehan // or "byte of reading", without giving further details.
19617e21c20SJosh Lehan // Previous code set it signed if min was less than zero,
19717e21c20SJosh Lehan // so I'm sticking with that, until I learn otherwise.
19817e21c20SJosh Lehan if (min < 0.0)
19972867debSJason M. Bills {
20017e21c20SJosh Lehan // TODO(): It would be worth experimenting with the range (-127,127),
20117e21c20SJosh Lehan // instead of the range (-128,127), because this
20217e21c20SJosh Lehan // would give good symmetry around zero, and make results look better.
20317e21c20SJosh Lehan // Divide by 254 instead of 255, and change -128 to -127 elsewhere.
20472867debSJason M. Bills bSigned = true;
20517e21c20SJosh Lehan lowestX = -128.0;
20672867debSJason M. Bills }
20772867debSJason M. Bills else
20872867debSJason M. Bills {
20972867debSJason M. Bills bSigned = false;
21017e21c20SJosh Lehan lowestX = 0.0;
21172867debSJason M. Bills }
21272867debSJason M. Bills
21317e21c20SJosh Lehan // Step 1: Set y to (max - min), set x to 255, set B to 0, solve for M
21417e21c20SJosh Lehan // This works, regardless of signed or unsigned,
21517e21c20SJosh Lehan // because total range is the same.
21617e21c20SJosh Lehan double dM = fullRange / 255.0;
21772867debSJason M. Bills
21817e21c20SJosh Lehan // Step 2: Constrain M, and set rExp accordingly
21917e21c20SJosh Lehan if (!(scaleFloatExp(dM, rExp)))
22072867debSJason M. Bills {
22117e21c20SJosh Lehan std::cerr << "getSensorAttributes: Multiplier range exceeds scale (M="
22217e21c20SJosh Lehan << dM << ", rExp=" << (int)rExp << ")\n";
22372867debSJason M. Bills return false;
22472867debSJason M. Bills }
22572867debSJason M. Bills
22617e21c20SJosh Lehan mValue = static_cast<int16_t>(std::round(dM));
22717e21c20SJosh Lehan
22817e21c20SJosh Lehan normalizeIntExp(mValue, rExp, dM);
22917e21c20SJosh Lehan
23017e21c20SJosh Lehan // The multiplier can not be zero, for obvious reasons
23117e21c20SJosh Lehan if (mValue == 0)
23272867debSJason M. Bills {
23317e21c20SJosh Lehan std::cerr << "getSensorAttributes: Multiplier range below scale\n";
23472867debSJason M. Bills return false;
23572867debSJason M. Bills }
23617e21c20SJosh Lehan
23717e21c20SJosh Lehan // Step 3: set y to min, set x to min, keep M and rExp, solve for B
23817e21c20SJosh Lehan // If negative, x will be -128 (the most negative possible byte), not 0
23917e21c20SJosh Lehan
24017e21c20SJosh Lehan // Solve the IPMI equation for B, instead of y
24117e21c20SJosh 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
24217e21c20SJosh Lehan // B = 10^(-rExp - bExp) (y - M 10^rExp x)
24317e21c20SJosh Lehan // TODO(): Compare with this alternative solution from SageMathCell
24417e21c20SJosh Lehan // https://sagecell.sagemath.org/?z=eJyrtC1LLNJQr1TX5KqAMCuATF8I0xfIdIIwnYDMIteKAggPxAIKJMEFkiACxfk5Zaka0ZUKtrYKGhq-CloKFZoK2goaTkCWhqGBgpaWAkilpqYmQgBklmasjoKTJgDAECTH&lang=sage&interacts=eJyLjgUAARUAuQ==
24517e21c20SJosh Lehan double dB = std::pow(10.0, ((-rExp) - bExp)) *
24617e21c20SJosh Lehan (min - ((dM * std::pow(10.0, rExp) * lowestX)));
24717e21c20SJosh Lehan
24817e21c20SJosh Lehan // Step 4: Constrain B, and set bExp accordingly
24917e21c20SJosh Lehan if (!(scaleFloatExp(dB, bExp)))
25072867debSJason M. Bills {
251*1bcced08SPatrick Williams std::cerr << "getSensorAttributes: Offset (B=" << dB << ", bExp="
252*1bcced08SPatrick Williams << (int)bExp << ") exceeds multiplier scale (M=" << dM
25317e21c20SJosh Lehan << ", rExp=" << (int)rExp << ")\n";
25472867debSJason M. Bills return false;
25572867debSJason M. Bills }
25672867debSJason M. Bills
25717e21c20SJosh Lehan bValue = static_cast<int16_t>(std::round(dB));
25872867debSJason M. Bills
25917e21c20SJosh Lehan normalizeIntExp(bValue, bExp, dB);
26072867debSJason M. Bills
26117e21c20SJosh Lehan // Unlike the multiplier, it is perfectly OK for bValue to be zero
26272867debSJason M. Bills return true;
26372867debSJason M. Bills }
26472867debSJason M. Bills
scaleIPMIValueFromDouble(const double value,const int16_t mValue,const int8_t rExp,const int16_t bValue,const int8_t bExp,const bool bSigned)265*1bcced08SPatrick Williams static inline uint8_t scaleIPMIValueFromDouble(
266*1bcced08SPatrick Williams const double value, const int16_t mValue, const int8_t rExp,
267*1bcced08SPatrick Williams const int16_t bValue, const int8_t bExp, const bool bSigned)
26872867debSJason M. Bills {
26917e21c20SJosh Lehan // Avoid division by zero below
27017e21c20SJosh Lehan if (mValue == 0)
27117e21c20SJosh Lehan {
27217e21c20SJosh Lehan throw std::out_of_range("Scaling multiplier is uninitialized");
27317e21c20SJosh Lehan }
27439417c7bSJames Feist
27517e21c20SJosh Lehan auto dM = static_cast<double>(mValue);
27617e21c20SJosh Lehan auto dB = static_cast<double>(bValue);
27717e21c20SJosh Lehan
27817e21c20SJosh Lehan // Solve the IPMI equation for x, instead of y
27917e21c20SJosh 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
28017e21c20SJosh Lehan // x = (10^(-rExp) (y - B 10^(rExp + bExp)))/M and M 10^rExp!=0
28117e21c20SJosh Lehan // TODO(): Compare with this alternative solution from SageMathCell
28217e21c20SJosh Lehan // https://sagecell.sagemath.org/?z=eJyrtC1LLNJQr1TX5KqAMCuATF8I0xfIdIIwnYDMIteKAggPxAIKJMEFkiACxfk5Zaka0ZUKtrYKGhq-CloKFZoK2goaTkCWhqGBgpaWAkilpqYmQgBklmasDlAlAMB8JP0=&lang=sage&interacts=eJyLjgUAARUAuQ==
28317e21c20SJosh Lehan double dX =
28417e21c20SJosh Lehan (std::pow(10.0, -rExp) * (value - (dB * std::pow(10.0, rExp + bExp)))) /
28517e21c20SJosh Lehan dM;
28617e21c20SJosh Lehan
28717e21c20SJosh Lehan auto scaledValue = static_cast<int32_t>(std::round(dX));
28817e21c20SJosh Lehan
28917e21c20SJosh Lehan int32_t minClamp;
29017e21c20SJosh Lehan int32_t maxClamp;
29117e21c20SJosh Lehan
29217e21c20SJosh Lehan // Because of rounding and integer truncation of scaling factors,
29317e21c20SJosh Lehan // sometimes the resulting byte is slightly out of range.
29417e21c20SJosh Lehan // Still allow this, but clamp the values to range.
295f6a07837SJames Feist if (bSigned)
296f6a07837SJames Feist {
29717e21c20SJosh Lehan minClamp = std::numeric_limits<int8_t>::lowest();
29817e21c20SJosh Lehan maxClamp = std::numeric_limits<int8_t>::max();
299f6a07837SJames Feist }
300f6a07837SJames Feist else
301f6a07837SJames Feist {
30217e21c20SJosh Lehan minClamp = std::numeric_limits<uint8_t>::lowest();
30317e21c20SJosh Lehan maxClamp = std::numeric_limits<uint8_t>::max();
30439417c7bSJames Feist }
30517e21c20SJosh Lehan
30617e21c20SJosh Lehan auto clampedValue = std::clamp(scaledValue, minClamp, maxClamp);
30717e21c20SJosh Lehan
30817e21c20SJosh Lehan // This works for both signed and unsigned,
30917e21c20SJosh Lehan // because it is the same underlying byte storage.
31017e21c20SJosh Lehan return static_cast<uint8_t>(clampedValue);
31172867debSJason M. Bills }
31272867debSJason M. Bills
getScaledIPMIValue(const double value,const double max,const double min)31372867debSJason M. Bills static inline uint8_t getScaledIPMIValue(const double value, const double max,
31472867debSJason M. Bills const double min)
31572867debSJason M. Bills {
31672867debSJason M. Bills int16_t mValue = 0;
31772867debSJason M. Bills int8_t rExp = 0;
31872867debSJason M. Bills int16_t bValue = 0;
31972867debSJason M. Bills int8_t bExp = 0;
32017e21c20SJosh Lehan bool bSigned = false;
32172867debSJason M. Bills
322*1bcced08SPatrick Williams bool result =
323*1bcced08SPatrick Williams getSensorAttributes(max, min, mValue, rExp, bValue, bExp, bSigned);
32472867debSJason M. Bills if (!result)
32572867debSJason M. Bills {
32639417c7bSJames Feist throw std::runtime_error("Illegal sensor attributes");
32772867debSJason M. Bills }
32817e21c20SJosh Lehan
32972867debSJason M. Bills return scaleIPMIValueFromDouble(value, mValue, rExp, bValue, bExp, bSigned);
33072867debSJason M. Bills }
33172867debSJason M. Bills
3323f7c5e40SJason M. Bills } // namespace ipmi
333