1 /*
2 // Copyright (c) 2017 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 #pragma once
18 #include <cmath>
19 #include <iostream>
20 #include <phosphor-logging/log.hpp>
21 
22 namespace ipmi
23 {
24 static constexpr int16_t maxInt10 = 0x1FF;
25 static constexpr int16_t minInt10 = -0x200;
26 static constexpr int8_t maxInt4 = 7;
27 static constexpr int8_t minInt4 = -8;
28 
29 static inline bool getSensorAttributes(const double max, const double min,
30                                        int16_t& mValue, int8_t& rExp,
31                                        int16_t& bValue, int8_t& bExp,
32                                        bool& bSigned)
33 {
34     // computing y = (10^rRexp) * (Mx + (B*(10^Bexp)))
35     // check for 0, assume always positive
36     double mDouble;
37     double bDouble;
38     if (max <= min)
39     {
40         phosphor::logging::log<phosphor::logging::level::DEBUG>(
41             "getSensorAttributes: Max must be greater than min");
42         return false;
43     }
44 
45     mDouble = (max - min) / 0xFF;
46 
47     if (min < 0)
48     {
49         bSigned = true;
50         bDouble = floor(0.5 + ((max + min) / 2));
51     }
52     else
53     {
54         bSigned = false;
55         bDouble = min;
56     }
57 
58     rExp = 0;
59 
60     // M too big for 10 bit variable
61     while (mDouble > maxInt10)
62     {
63         if (rExp >= maxInt4)
64         {
65             phosphor::logging::log<phosphor::logging::level::DEBUG>(
66                 "rExp Too big, Max and Min range too far",
67                 phosphor::logging::entry("REXP=%d", rExp));
68             return false;
69         }
70         mDouble /= 10;
71         rExp++;
72     }
73 
74     // M too small, loop until we lose less than 1 eight bit count of precision
75     while (((mDouble - floor(mDouble)) / mDouble) > (1.0 / 255))
76     {
77         if (rExp <= minInt4)
78         {
79             phosphor::logging::log<phosphor::logging::level::DEBUG>(
80                 "rExp Too Small, Max and Min range too close");
81             return false;
82         }
83         // check to see if we reached the limit of where we can adjust back the
84         // B value
85         if (bDouble / std::pow(10, rExp + minInt4 - 1) > bDouble)
86         {
87             if (mDouble < 1.0)
88             {
89                 phosphor::logging::log<phosphor::logging::level::DEBUG>(
90                     "Could not find mValue and B value with enough "
91                     "precision.");
92                 return false;
93             }
94             break;
95         }
96         // can't multiply M any more, max precision reached
97         else if (mDouble * 10 > maxInt10)
98         {
99             break;
100         }
101         mDouble *= 10;
102         rExp--;
103     }
104 
105     bDouble /= std::pow(10, rExp);
106     bExp = 0;
107 
108     // B too big for 10 bit variable
109     while (bDouble > maxInt10 || bDouble < minInt10)
110     {
111         if (bExp >= maxInt4)
112         {
113             phosphor::logging::log<phosphor::logging::level::DEBUG>(
114                 "bExp Too Big, Max and Min range need to be adjusted");
115             return false;
116         }
117         bDouble /= 10;
118         bExp++;
119     }
120 
121     while (((fabs(bDouble) - floor(fabs(bDouble))) / fabs(bDouble)) >
122            (1.0 / 255))
123     {
124         if (bExp <= minInt4)
125         {
126             phosphor::logging::log<phosphor::logging::level::DEBUG>(
127                 "bExp Too Small, Max and Min range need to be adjusted");
128             return false;
129         }
130         bDouble *= 10;
131         bExp -= 1;
132     }
133 
134     mValue = static_cast<int16_t>(std::round(mDouble)) & maxInt10;
135     bValue = static_cast<int16_t>(bDouble) & maxInt10;
136 
137     return true;
138 }
139 
140 static inline uint8_t
141     scaleIPMIValueFromDouble(const double value, const uint16_t mValue,
142                              const int8_t rExp, const uint16_t bValue,
143                              const int8_t bExp, const bool bSigned)
144 {
145     uint32_t scaledValue =
146         (value - (bValue * std::pow(10, bExp) * std::pow(10, rExp))) /
147         (mValue * std::pow(10, rExp));
148 
149     if (scaledValue > std::numeric_limits<uint8_t>::max() ||
150         scaledValue < std::numeric_limits<uint8_t>::lowest())
151     {
152         throw std::out_of_range("Value out of range");
153     }
154     if (bSigned)
155     {
156         return static_cast<int8_t>(scaledValue);
157     }
158     else
159     {
160         return static_cast<uint8_t>(scaledValue);
161     }
162 }
163 
164 static inline uint8_t getScaledIPMIValue(const double value, const double max,
165                                          const double min)
166 {
167     int16_t mValue = 0;
168     int8_t rExp = 0;
169     int16_t bValue = 0;
170     int8_t bExp = 0;
171     bool bSigned = 0;
172     bool result = 0;
173 
174     result = getSensorAttributes(max, min, mValue, rExp, bValue, bExp, bSigned);
175     if (!result)
176     {
177         throw std::runtime_error("Illegal sensor attributes");
178     }
179     return scaleIPMIValueFromDouble(value, mValue, rExp, bValue, bExp, bSigned);
180 }
181 
182 } // namespace ipmi
183