summaryrefslogtreecommitdiffstats
path: root/src/import/generic/memory/lib/utils/power_thermal/gen_throttle.H
blob: 893a7e37bf7d4014a03681e78b1b4ba84db6dd53 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
/* IBM_PROLOG_BEGIN_TAG                                                   */
/* This is an automatically generated prolog.                             */
/*                                                                        */
/* $Source: src/import/generic/memory/lib/utils/power_thermal/gen_throttle.H $ */
/*                                                                        */
/* OpenPOWER HostBoot Project                                             */
/*                                                                        */
/* Contributors Listed Below - COPYRIGHT 2019                             */
/* [+] International Business Machines Corp.                              */
/*                                                                        */
/*                                                                        */
/* Licensed under the Apache License, Version 2.0 (the "License");        */
/* you may not use this file except in compliance with the License.       */
/* You may obtain a copy of the License at                                */
/*                                                                        */
/*     http://www.apache.org/licenses/LICENSE-2.0                         */
/*                                                                        */
/* Unless required by applicable law or agreed to in writing, software    */
/* distributed under the License is distributed on an "AS IS" BASIS,      */
/* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or        */
/* implied. See the License for the specific language governing           */
/* permissions and limitations under the License.                         */
/*                                                                        */
/* IBM_PROLOG_END_TAG                                                     */
///
/// @file gen_throttle.H
/// @brief throttle API
///

// *HWP HWP Owner: Andre Marin <aamarin@us.ibm.com>
// *HWP HWP Backup: Louis Stermole <stermole@us.ibm.com>
// *HWP Team: Memory
// *HWP Level: 3
// *HWP Consumed by: FSP:HB

#ifndef _MSS_GEN_POWER_THROTTLE_
#define _MSS_GEN_POWER_THROTTLE_

#include <fapi2.H>
#include <generic/memory/lib/utils/shared/mss_generic_consts.H>
#include <generic/memory/lib/utils/power_thermal/gen_throttle_traits.H>
#include <generic/memory/lib/utils/count_dimm.H>
#include <generic/memory/lib/mss_generic_system_attribute_getters.H>
#include <generic/memory/lib/mss_generic_attribute_setters.H>
#include <generic/memory/lib/utils/mss_math.H>
#include <generic/memory/lib/utils/pos.H>

namespace mss
{

namespace power_thermal
{

///
/// @brief throttle constants used in the power_thermal functions
///
enum throttle_const : size_t
{
    /// Dram data bus utilization is bus utilization / 4
    DRAM_BUS_UTILS = 4,

    /// 10000 to convert to and from c%
    UTIL_CONVERSION = 10000,

    /// Conversion to percentage
    PERCENT_CONVERSION = 100,

};

///
/// @brief Calculate N (address operations) allowed within a window of M DRAM clocks
/// @param[in] i_databus_util databus utilization percentage (e.g. 5% = 5)
/// @param[in] i_num_dram_clocks window of M DRAM clocks
/// @return number of throttled commands allowed
/// @note Uses N/M Throttling.
/// Equation:  N = (DRAM data bus utilization * M) / (4 * 10000)
///
inline uint32_t throttled_cmds(const uint32_t i_databus_util, const uint32_t i_num_dram_clocks)
{
    constexpr uint64_t l_divisor = DRAM_BUS_UTILS * UTIL_CONVERSION;
    const uint64_t l_dividend = i_databus_util * i_num_dram_clocks;
    const uint64_t l_result = l_dividend / l_divisor;

    //Make sure N is not equal to 0, or we brick the dram until reboot
    return ((l_result == 0) ? 1 : l_result);
}

///
/// @brief Calculate the port databus utilization based off of N throttles and M dram clocks
/// @tparam MC mss::mc_type
/// @tparam TT throttle_traits throttle traits for the given mc_type
/// @tparam T output type
/// @param[in] i_n_throttles N (address operations) allowed within a window of M DRAM clocks
/// @param[in] i_num_dram_clocks window of M DRAM clocks
/// @param[out] o_calc_util
/// @return FAPI2_RC_SUCCESS iff method was a success
/// @note Uses N/M Throttling.
/// @note DRAM databus utilization = N * 4 * 10000 / M
///
template<mss::mc_type MC = DEFAULT_MC_TYPE, typename TT = throttle_traits<MC>, typename T>
fapi2::ReturnCode calc_util_from_throttles(const uint16_t i_n_throttles,
        const uint32_t i_num_dram_clocks,
        T& o_calc_util)
{
    fapi2::current_err = fapi2::FAPI2_RC_SUCCESS;
    constexpr uint32_t l_multiplier = DRAM_BUS_UTILS * UTIL_CONVERSION;
    const uint64_t l_calc_util_uint64 = static_cast<uint64_t>((static_cast<double>(i_n_throttles) * l_multiplier) /
                                        i_num_dram_clocks);
    FAPI_ASSERT( (i_num_dram_clocks != 0),
                 fapi2::MSS_M_DRAM_CLOCKS_EQUALS_ZERO(),
                 "ATTR_MSS_MRW_MEM_M_DRAM_CLOCKS was not set and equals zero");

    o_calc_util = (static_cast<double>(i_n_throttles) * l_multiplier) / i_num_dram_clocks;

    // Best way to check for overflow if o_calc_util can be a double?
    // If o_calc_util overflows, the value inside will be below the expected outcome
    // So compare o_calc_util with the calculated value, but store calculated value in largest storage
    // Compare ">=" because o_calc_util can be a double, and so we can't compare just equality due to truncation
    FAPI_ASSERT( o_calc_util >= l_calc_util_uint64,
                 fapi2::MSS_OUTPUT_OVERFLOW_CALC_UTIL()
                 .set_RESULT(o_calc_util),
                 "Overflow of output variable in calc_util_from_throttles throttles: %d, multiplier %d, dram_clocks %d",
                 i_n_throttles,
                 l_multiplier,
                 i_num_dram_clocks);

    // Check for the minimum
    if(o_calc_util < TT::MIN_UTIL)
    {
        FAPI_INF("Calculated utilization (%f) is less than the minimum utilization: %lu. Setting to minimum value",
                 o_calc_util, TT::MIN_UTIL);
        o_calc_util = TT::MIN_UTIL;
    }

    FAPI_INF("In calc_util_from_throttles, calculated %f for output utilization from throttles:%d, dram_clocks%d",
             o_calc_util, i_n_throttles, i_num_dram_clocks);

fapi_try_exit:
    return fapi2::current_err;
}

///
/// @brief Perform thermal calculations as part of the effective configuration
/// @tparam MC mss::mc_type
/// @tparam T the fapi2 target type of the target
/// @tparam TT throttle_traits throttle traits for the given mc_type
/// @param[in] i_target the MCS target in which the runtime throttles will be reset
/// @return FAPI2_RC_SUCCESS iff ok
///
template<mss::mc_type MC = DEFAULT_MC_TYPE, fapi2::TargetType T, typename TT = throttle_traits<MC>>
fapi2::ReturnCode restore_runtime_throttles( const fapi2::Target<T>& i_target )
{

    uint32_t l_max_databus = 0;
    uint32_t l_throttle_m_clocks = 0;

    FAPI_TRY( mss::attr::get_mrw_mem_m_dram_clocks(l_throttle_m_clocks) );
    FAPI_TRY( mss::attr::get_mrw_max_dram_databus_util(l_max_databus) );

    //Set runtime throttles to unthrottled value, using max dram utilization and M throttle
    //Do I need to check to see if any DIMMS configured  on the port?
    for (const auto& l_port : mss::find_targets<TT::PORT_TARGET_TYPE>(i_target))
    {
        uint16_t l_run_throttle = 0;

        if (mss::count_dimm (l_port) != 0)
        {
            l_run_throttle = mss::power_thermal::throttled_cmds (l_max_databus, l_throttle_m_clocks);
        }

        FAPI_TRY( mss::attr::set_runtime_mem_throttled_n_commands_per_port( l_port, l_run_throttle) );
        FAPI_TRY( mss::attr::set_runtime_mem_throttled_n_commands_per_slot( l_port, l_run_throttle) );
    }

fapi_try_exit:
    return fapi2::current_err;
}

///
/// @brief Update the runtime throttles to the worst case of the general throttle values and the runtime values
/// @tparam MC mss::mc_type
/// @tparam T the fapi2 target type of the target
/// @tparam TT throttle_traits throttle traits for the given mc_type
/// @param[in] i_target the MCS target in which the runtime throttles will be set
/// @return FAPI2_RC_SUCCESS iff ok
///
template<mss::mc_type MC = DEFAULT_MC_TYPE, fapi2::TargetType T, typename TT = throttle_traits<MC>>
fapi2::ReturnCode update_runtime_throttle(const fapi2::Target<T>& i_target)

{

    if (mss::count_dimm(i_target) == 0)
    {
        return fapi2::FAPI2_RC_SUCCESS;
    }

    for (const auto& l_port : mss::find_targets<TT::PORT_TARGET_TYPE>(i_target))
    {

        uint16_t l_run_slot = 0;
        uint16_t l_run_port = 0;
        uint16_t l_calc_slot = 0;
        uint16_t l_calc_port = 0;

        FAPI_TRY(mss::attr::get_runtime_mem_throttled_n_commands_per_slot(l_port, l_run_slot));
        FAPI_TRY(mss::attr::get_runtime_mem_throttled_n_commands_per_port(l_port, l_run_port));
        FAPI_TRY(mss::attr::get_mem_throttled_n_commands_per_slot(l_port, l_calc_slot));
        FAPI_TRY(mss::attr::get_mem_throttled_n_commands_per_port(l_port, l_calc_port));

        //Choose the worst case between runtime and calculated throttles
        //Have to make sure the calc_slot isn't equal to 0 though
        l_run_slot = (l_calc_slot != 0) ?
                     std::min(l_run_slot, l_calc_slot) : l_run_slot;
        l_run_port = (l_calc_port != 0) ?
                     std::min(l_run_port, l_calc_port) : l_run_port;

        FAPI_INF("New runtime throttles for %s for slot are %d, port are  %d",
                 mss::c_str(l_port),
                 l_run_slot,
                 l_run_port);

        FAPI_TRY( mss::attr::set_runtime_mem_throttled_n_commands_per_port(l_port, l_run_port) );
        FAPI_TRY( mss::attr::set_runtime_mem_throttled_n_commands_per_slot(l_port, l_run_slot) );
    }


fapi_try_exit:
    return fapi2::current_err;
}



///
/// @brief Update the runtime throttles to the worst case of the general throttle values and the runtime values
/// @tparam MC mss::mc_type
/// @tparam T the fapi2 target type of the target
/// @tparam TT throttle_traits throttle traits for the given mc_type
/// @param[in] i_target the MCS target in which the runtime throttles will be set
/// @return FAPI2_RC_SUCCESS iff ok
///
template<mss::mc_type MC = DEFAULT_MC_TYPE, fapi2::TargetType T, typename TT = throttle_traits<MC>>
fapi2::ReturnCode update_runtime_throttles(const std::vector< fapi2::Target<T> >& i_targets)

{
    for (const auto& l_mc : i_targets)
    {
        FAPI_TRY(update_runtime_throttle(l_mc));
    }

fapi_try_exit:
    return fapi2::current_err;
}


///
/// @class throttle
/// @brief Determine power_thermal throttles for memory
/// @tparam MC mss::mc_type
/// @tparam TT throttle_traits throttle traits for the given mc_type
///
template<mss::mc_type MC = DEFAULT_MC_TYPE, typename TT = throttle_traits<MC>>
class throttle
{
    private:
        ///
        /// @brief Calculate the power (cW) of inputs and the power curve
        /// @tparam T the type of i_util and return value
        /// @param[in] i_util the databus utilization that the power will be based on
        /// @param[in] l_pos the dimm position for the power value being calculated.
        /// @return Integral type T
        ///
        template<typename T>
        inline T calc_power (const T i_util, const size_t i_pos, fapi2::ReturnCode& o_rc ) const
        {
            o_rc = fapi2::FAPI2_RC_SUCCESS;
            FAPI_ASSERT( (i_pos < TT::DIMMS_PER_PORT),
                         fapi2::MSS_POWER_THERMAL_DIMM_INDEX_OUT_OF_BOUND()
                         .set_INPUT_SIZE(i_pos)
                         .set_MAX_SIZE(TT::DIMMS_PER_PORT),
                         "The dimm is index is out of bound for the port index: %d, max: %d for port %s",
                         i_pos, TT::DIMMS_PER_PORT, mss::c_str(iv_target) );

            return ((i_util / UTIL_CONVERSION) * iv_pwr_slope[i_pos]) + iv_pwr_int[i_pos];

        fapi_try_exit:
            o_rc = fapi2::current_err;
            return 0;
        }

        ///
        /// @brief Raise the o_value by the percent passed in
        /// @param[in] i_uplift the percent the o_Value should be raised by
        /// @param[out] o_value the value that will be modified
        ///
        inline void calc_power_uplift (const uint8_t i_uplift, double& o_value) const
        {
            o_value *= (1 + (static_cast<double>(i_uplift) / PERCENT_CONVERSION));
        }

    public:
        const fapi2::Target<TT::PORT_TARGET_TYPE>& iv_target;

        uint32_t iv_databus_port_max;

        uint8_t iv_power_uplift_idle;
        uint8_t iv_power_uplift;

        uint16_t iv_runtime_n_slot;
        uint16_t iv_runtime_n_port;
        uint32_t iv_m_clocks;
        uint32_t iv_dimm_thermal_limit[TT::DIMMS_PER_PORT] = {};
        uint16_t iv_pwr_slope[TT::DIMMS_PER_PORT] = {};
        uint16_t iv_pwr_int[TT::DIMMS_PER_PORT] = {};
        uint16_t iv_n_slot;
        uint16_t iv_n_port;
        uint32_t iv_port_power_limit;
        uint32_t iv_calc_port_maxpower;

        //default ctor deleted
        throttle() = delete;

        ///
        /// @brief Constructor
        /// @param[in] i_target port target to call power thermal stuff on
        /// @param[out] o_rc fapi2::ReturnCode fapi2::FAPI2_RC_SUCCESS iff ctor was successful
        ///
        throttle( const fapi2::Target<TT::PORT_TARGET_TYPE>& i_port, fapi2::ReturnCode& o_rc);

        //
        // @brief Destructor
        //
        ~throttle() = default;

        ///
        /// @brief Calculates the min and max power usage for a port
        /// @param[in] i_idle_util the utilization of the databus in idle mode
        /// @param[in] i_max_util  the utilization of the port at maximum possible (mrw or calculated)
        /// @param[out] o_port_power_idle max value of port power in cW
        /// @param[out] o_port_power_max max value of port power in cW
        /// @return fapi2::ReturnCode - FAPI2_RC_SUCCESS iff the split is OK
        /// @note Called twice in p9_mss_bulk_pwr_throttles
        ///
        fapi2::ReturnCode calc_port_power( const double i_idle_util [TT::DIMMS_PER_PORT],
                                           const double i_max_util [TT::DIMMS_PER_PORT],
                                           double& o_port_power_idle,
                                           double& o_port_power_max) const;
        ///
        /// @brief Calculates max and min power usages based off of DIMM power curves
        /// @param[in] i_databus_port_max max databus utilization for the port (either calculated or mrw)
        /// @param[in] i_port_power_calc_idle double of the port's power consumption at idle
        /// @param[out] o_dimm_power_idle array of dimm power in cW
        /// @param[out] o_dimm_power_max array of dimm power in cW
        /// @return fapi2::ReturnCode - FAPI2_RC_SUCCESS iff the split is OK
        /// @note Called in p9_mss_bulk_pwr_throttles
        /// @note used for the thermal throttles
        ///
        fapi2::ReturnCode calc_dimm_power(const double i_databus_idle,
                                          const double i_databus_max,
                                          double o_dimm_power_idle [TT::DIMMS_PER_PORT],
                                          double o_dimm_power_max [TT::DIMMS_PER_PORT]) const;

        ///
        /// @brief Calculate the power curve in order to calculate databus utilization
        /// @param[in] i_power_idle double of the port's power consumption at idle
        /// @param[in] i_power_max double of the port's power consumption at max utilization
        /// @param[out] o_power_slope
        /// @param[out] o_power_int
        /// @return fapi2::ReturnCode - FAPI2_RC_SUCCESS iff the split is OK
        /// @note Called in p9_mss_bulk_pwr_throttles
        /// @note Power curve needed to calculate the utilization
        ///
        fapi2::ReturnCode calc_power_curve(const double i_power_idle,
                                           const double i_power_max,
                                           uint32_t& o_power_slope,
                                           uint32_t& o_power_int) const;
        ///
        /// @brief Calculate the databus utilization given the power curve
        /// @param[in] i_slope the slope of power curve
        /// @param[in] i_int the intercept of power curve
        /// @param[in] i_power_limit either iv_port_power_limit or thermal_power_limit depending on throttle type
        /// @param[out] o_port_util the port's databus utilization
        /// @note Called in p9_mss_bulk_pwr_throttles
        /// @note Chooses worst case between the maximum allowed databus utilization and the calculated value
        ///
        void calc_util_usage(const uint32_t i_slope,
                             const uint32_t i_int,
                             const uint32_t i_power_limit,
                             double& o_util) const;
        ///
        /// @brief set iv_n_port, iv_n_slot, iv_calc_port_maxpower
        /// @param[in] i_util_port pass in the calculated port databus utilization
        /// @return fapi2::ReturnCode - FAPI2_RC_SUCCESS iff get is OK
        ///
        fapi2::ReturnCode calc_slots_and_power (const double i_util_port);

        ///
        /// @brief calculated the output power estimate from the calculated N throttle
        /// @param[in] i_n_slot the N throttle per slot
        /// @param[in] i_n_port the N throttle per port
        /// @param[out] o_power the calculated power
        /// @return fapi2::ReturnCode - FAPI2_RC_SUCCESS iff the split is OK
        ///
        fapi2::ReturnCode calc_power_from_n (const uint16_t i_n_slot, const uint16_t i_n_port, uint32_t& o_power) const;

        ///
        /// @brief Converts the port maximum databus util to a dimm level based on powerslopes and dimms installed
        /// @param[in] i_databus_port_max max databus utilization for the port (either calculated or mrw)
        /// @param[out] o_databus_dimm_max array of dimm utilization values
        /// @return fapi2::ReturnCode - FAPI2_RC_SUCCESS iff the split is OK
        /// @note Called in p9_mss_bulk_pwr_throttles
        /// @used to calculate the port power based off of DIMM power curves
        ///
        fapi2::ReturnCode calc_databus( const double i_databus_port_max,
                                        double o_databus_dimm_max [TT::DIMMS_PER_PORT]);
        ///
        /// @brief Converts the port and slot util to a dimm level based on powerslopes and number of dimms installed
        /// @param[in] i_util_slot databus utilization for the slot
        /// @param[in] i_util_port databus utilization for the port
        /// @param[out] o_util_dimm_max array of dimm utilization values
        /// @return fapi2::ReturnCode - FAPI2_RC_SUCCESS iff the split is OK
        /// @note determines worst case utilization per dimms, takes into account port and combine slot throttles
        ///
        fapi2::ReturnCode calc_split_util(
            const double i_util_slot,
            const double i_util_port,
            double o_util_dimm_max [TT::DIMMS_PER_PORT]) const;


        ///
        /// @brief Calculate ATTR_MSS_CHANNEL_PAIR_MAXPOWER and  ATTR_MSS_MEM_THROTTLED_N_COMMANDS_PER_SLOT,
        /// @return fapi2::ReturnCode - FAPI2_RC_SUCCESS iff get is OK
        /// @note Called in p9_mss_bulk_pwr_throttles
        /// @note determines the throttle levels based off of the port's power curve, max databus utilization,
        /// and memwat target.
        /// @note currently sets the slot and port throttles to the same value
        ///
        fapi2::ReturnCode power_regulator_throttles ();

        ///
        /// @brief Set ATTR_MSS_MEM_THROTTLED_N_COMMANDS_PER_SLOT,
        /// @return fapi2::ReturnCode - FAPI2_RC_SUCCESS iff get is OK
        /// @note Called in p9_mss_bulk_pwr_throttles
        /// @note Sets the throttle levels based off of the dimm's thermal limits
        /// @note both DIMM's on a port are set to the same throttle level
        ///
        fapi2::ReturnCode thermal_throttles ();
};


///
/// @brief Constructor
/// @tparam MC mss::mc_type
/// @tparam TT throttle_traits throttle traits for the given mc_type
/// @param[in] i_target MCS target to call power thermal stuff on
/// @param[out] o_rc, a return code which determines the success of the constructor
///
template<mss::mc_type MC, typename TT>
throttle<MC, TT>::throttle( const fapi2::Target<TT::PORT_TARGET_TYPE>& i_port, fapi2::ReturnCode& o_rc) :
    iv_target(i_port),
    iv_databus_port_max(0),
    iv_runtime_n_slot(0),
    iv_runtime_n_port(0),
    iv_n_slot(0),
    iv_n_port(0),
    iv_port_power_limit(0),
    iv_calc_port_maxpower(0)
{
    FAPI_TRY( mss::attr::get_mrw_max_dram_databus_util(iv_databus_port_max), "%s Error in throttle ctor",
              mss::c_str(i_port) );
    FAPI_TRY( mss::attr::get_mrw_dimm_power_curve_percent_uplift(iv_power_uplift), "%s Error in throttle ctor",
              mss::c_str(i_port) );
    FAPI_TRY( mss::attr::get_mrw_dimm_power_curve_percent_uplift_idle(iv_power_uplift_idle), "%s Error in throttle ctor",
              mss::c_str(i_port) );
    FAPI_TRY( mss::attr::get_dimm_thermal_limit( iv_target, iv_dimm_thermal_limit), "%s Error in throttle ctor",
              mss::c_str(i_port) );
    FAPI_TRY( mss::attr::get_total_pwr_intercept( iv_target, iv_pwr_int), "%s Error in throttle ctor", mss::c_str(i_port) );
    FAPI_TRY( mss::attr::get_total_pwr_slope( iv_target, iv_pwr_slope), "%s Error in throttle ctor", mss::c_str(i_port) );
    FAPI_TRY( mss::attr::get_runtime_mem_throttled_n_commands_per_slot(iv_target, iv_runtime_n_slot ),
              "%s Error in throttle ctor",
              mss::c_str(i_port) );
    FAPI_TRY( mss::attr::get_runtime_mem_throttled_n_commands_per_port(iv_target, iv_runtime_n_port ),
              "%s Error in throttle ctor",
              mss::c_str(i_port) );
    FAPI_TRY( mss::attr::get_mrw_mem_m_dram_clocks(iv_m_clocks), "%s Error in throttle ctor", mss::c_str(i_port) );

    //Port power limit = sum of dimm power limits
    for ( const auto& l_dimm : mss::find_targets<fapi2::TARGET_TYPE_DIMM>(iv_target) )
    {
        uint32_t l_dimm_limit = 0;
        FAPI_TRY( mss::attr::get_mem_watt_target( l_dimm, l_dimm_limit) );
        iv_port_power_limit += l_dimm_limit;
    }

    FAPI_INF("Setting up throttle for target %s, Values are: max databus is %d, uplifts are %d %d, runtime throttles are %d %d",
             mss::c_str(iv_target),
             iv_databus_port_max,
             iv_power_uplift,
             iv_power_uplift_idle,
             iv_runtime_n_slot,
             iv_runtime_n_port);

    FAPI_INF("The dimm power limit is %d, dram clocks are %d, dimm power curve slopes are %d %d for %s",
             iv_port_power_limit,
             iv_m_clocks,
             iv_pwr_slope[0],
             iv_pwr_slope[1],
             mss::c_str(iv_target));

    FAPI_INF("DIMM power curve intercepts are %d %d, DIMM power thermal limits are %d %d for %s",
             iv_pwr_int[0],
             iv_pwr_int[1],
             iv_dimm_thermal_limit[0],
             iv_dimm_thermal_limit[1],
             mss::c_str(iv_target));

    FAPI_ASSERT( (iv_databus_port_max != 0),
                 fapi2::MSS_NO_DATABUS_UTILIZATION()
                 .set_PORT_DATABUS_UTIL(iv_databus_port_max)
                 .set_DIMM_COUNT(mss::count_dimm(iv_target)),
                 "Failed to get max databus utilization for target %s",
                 mss::c_str(iv_target));

    FAPI_ASSERT( (iv_port_power_limit != 0),
                 fapi2::MSS_NO_PORT_POWER_LIMIT()
                 .set_COUNT_DIMMS( mss::count_dimm(iv_target))
                 .set_PORT_POWER_LIMIT( iv_port_power_limit),
                 "Error calculating port_power_limit on target %s with %d DIMMs installed",
                 mss::c_str(iv_target),
                 iv_port_power_limit);

    //Checking to make sure all of the attributes are valid
    for ( const auto& l_dimm : mss::find_targets<fapi2::TARGET_TYPE_DIMM>(iv_target) )
    {
        const auto l_pos = mss::index(l_dimm);
        FAPI_ASSERT( (iv_pwr_int[l_pos] != 0),
                     fapi2::MSS_POWER_INTERCEPT_NOT_SET(),
                     "The attribute ATTR_MSS_TOTAL_PWR_INTERCEPT equals 0 for %s",
                     mss::c_str(l_dimm));

        FAPI_ASSERT( (iv_pwr_slope[l_pos] != 0),
                     fapi2::MSS_POWER_SLOPE_NOT_SET(),
                     "The attribute ATTR_MSS_TOTAL_PWR_SLOPE equals 0 for %s",
                     mss::c_str(l_dimm));
    }

fapi_try_exit:
    o_rc = fapi2::current_err;
    return;
}

///
/// @brief Set ATTR_MSS_CHANNEL_PAIR_MAXPOWER, ATTR_MSS_MEM_THROTTLED_N_COMMANDS_PER_SLOT and _PER_PORT
/// @return fapi2::ReturnCode - FAPI2_RC_SUCCESS iff get is OK
/// @note Called in p9_mss_bulk_pwr_throttles
/// @note determines the throttle levels based off of the port's power curve,
/// @note the _per_slot throttles are set to the _per_port values
/// @note throttles are all equalized and set to the worst case value
///
template<mss::mc_type MC, typename TT>
fapi2::ReturnCode throttle<MC, TT>::power_regulator_throttles ()
{
    double l_port_power_calc_idle = 0;
    double l_port_power_calc_max = 0;
    uint32_t l_port_power_slope = 0;
    uint32_t l_port_power_int = 0;
    double l_calc_util_port = 0;
    double  l_databus_dimm_max[TT::DIMMS_PER_PORT] = {};
    double  l_calc_databus_port_idle[TT::DIMMS_PER_PORT] = {TT::IDLE_UTIL, TT::IDLE_UTIL};

    FAPI_INF("Starting power regulator throttles for %s", mss::c_str(iv_target));

    //Decide utilization for each dimm based off of dimm count and power slopes
    FAPI_TRY( calc_databus(iv_databus_port_max, l_databus_dimm_max),
              "Failed to calculate each DIMMs' percentage of dram databus utilization for target %s, max port databus is %d",
              mss::c_str(iv_target),
              iv_databus_port_max);

    //Use the dimm utilizations and dimm power slopes to calculate port min and max power
    FAPI_TRY( calc_port_power(l_calc_databus_port_idle,
                              l_databus_dimm_max,
                              l_port_power_calc_idle,
                              l_port_power_calc_max),
              "Failed to calculate the max and idle power for port %s",
              mss::c_str(iv_target));

    FAPI_INF("POWER throttles: %s max port power is %f", mss::c_str(iv_target), l_port_power_calc_max);

    //Calculate the power curve slope and intercept using the port's min and max power values
    FAPI_TRY(calc_power_curve(l_port_power_calc_idle,
                              l_port_power_calc_max,
                              l_port_power_slope,
                              l_port_power_int),
             "Failed to calculate the power curve for port %s, calculated port power max is %f, idle is %f",
             mss::c_str(iv_target),
             l_port_power_calc_max,
             l_port_power_calc_idle);

    FAPI_INF("%s POWER Port power limit is %d", mss::c_str(iv_target), iv_port_power_limit);
    //Calculate the port's utilization to get under watt target using the port's calculated slopes
    calc_util_usage(l_port_power_slope,
                    l_port_power_int,
                    iv_port_power_limit,
                    l_calc_util_port);

    FAPI_INF("%s POWER calc util port is %f",  mss::c_str(iv_target), l_calc_util_port);

    //Calculate the new slot values and the max power value for the port
    FAPI_TRY( calc_slots_and_power( l_calc_util_port),
              "%s Error calculating the final throttles and power values for target with passed in port utilization %f",
              mss::c_str(iv_target),
              l_calc_util_port);

fapi_try_exit:
    return fapi2::current_err;
}

///
/// @brief set iv_n_port, iv_n_slot, iv_calc_port_maxpower
/// @param[in] i_util_port pass in the calculated port databus utilization
/// @return fapi2::ReturnCode - FAPI2_RC_SUCCESS iff get is OK
///
template<mss::mc_type MC, typename TT>
fapi2::ReturnCode throttle<MC, TT>::calc_slots_and_power (const double i_util_port)
{
    //Calculate the Port N throttles
    iv_n_port = power_thermal::throttled_cmds(i_util_port, iv_m_clocks);

    //Set iv_n_slot to the lower value between the slot runtime and iv_n_port
    iv_n_slot = (iv_runtime_n_slot != 0) ? std::min (iv_n_port, iv_runtime_n_slot) : iv_n_port;

    //Choose the lowest value of the runtime and the calculated
    iv_n_port = (iv_runtime_n_port != 0) ? std::min (iv_n_port, iv_runtime_n_port) : iv_n_port;

    //Use the throttle value to calculate the power that gets to exactly that value
    FAPI_TRY( calc_power_from_n(iv_n_slot, iv_n_port, iv_calc_port_maxpower));

fapi_try_exit:
    return fapi2::current_err;
}

///
/// @brief Set ATTR_MSS_MEM_THROTTLED_N_COMMANDS_PER_SLOT and PER_PORT
/// @return fapi2::ReturnCode - FAPI2_RC_SUCCESS iff get is OK
/// @note Called in p9_mss_bulk_pwr_throttles
/// @note Sets the throttle levels based off of the dimm's thermal limits
/// @note both DIMM's on a port are set to the same throttle level
///
template<mss::mc_type MC, typename TT>
fapi2::ReturnCode throttle<MC, TT>::thermal_throttles ()
{
    double l_dimm_power_idle [TT::DIMMS_PER_PORT] = {};
    double l_dimm_power_max [TT::DIMMS_PER_PORT] = {};
    uint32_t l_dimm_power_slope [TT::DIMMS_PER_PORT] = {};
    uint32_t l_dimm_power_int [TT::DIMMS_PER_PORT] = {};
    double l_calc_util [TT::DIMMS_PER_PORT] = {};
    const auto l_count = count_dimm (iv_target);

    //Calculate the dimm power range for each dimm at max utilization for each
    FAPI_TRY( calc_dimm_power(TT::IDLE_UTIL,
                              iv_databus_port_max,
                              l_dimm_power_idle,
                              l_dimm_power_max));

    //Let's calculate the N throttle for each DIMM
    for ( const auto& l_dimm : mss::find_targets<fapi2::TARGET_TYPE_DIMM>(iv_target) )
    {
        uint16_t l_temp_n_slot = 0;
        const uint8_t l_pos = mss::index(l_dimm);
        //Calculate the power curve taking the thermal limit into account
        FAPI_TRY( calc_power_curve(l_dimm_power_idle[l_pos],
                                   l_dimm_power_max[l_pos],
                                   l_dimm_power_slope[l_pos],
                                   l_dimm_power_int[l_pos]),
                  "Failed to calculate the power curve for dimm %s, calculated dimm power curve slope is %d, intercept %d",
                  mss::c_str(l_dimm),
                  l_dimm_power_slope[l_pos],
                  l_dimm_power_int[l_pos]);

        //Calculate the databus utilization at the calculated power curve
        calc_util_usage(l_dimm_power_slope[l_pos],
                        l_dimm_power_int[l_pos],
                        iv_dimm_thermal_limit[l_pos],
                        l_calc_util[l_pos]);

        FAPI_INF("THERMAL throttles: %s dram databus utilization is %f", mss::c_str(l_dimm), l_calc_util[l_pos]);

        l_temp_n_slot = power_thermal::throttled_cmds (l_calc_util[l_pos], iv_m_clocks);

        //Set to the min between the two value
        //If iv_n_slot == 0 (so uninitialized), set it to the calculated slot value
        //The l_n_slot value can't be equal to 0 because there's a dimm installed
        if ((l_temp_n_slot < iv_n_slot) || (iv_n_slot == 0))
        {
            iv_n_slot = l_temp_n_slot;
        }
    }

    //Set to lowest value between calculated and runtime
    FAPI_INF("THERMAL throttles: runtime slot is %d, calc n slot is %d for %s", iv_runtime_n_slot, iv_n_slot,
             mss::c_str(iv_target));
    //Taking the min of the SLOT * (# of dimms on the port) and the iv_runtime_port throttle value
    //Thermal throttling happens after the POWER calculations. the iv_runtime_n_port value shouldn't be set to 0
    iv_n_port = std::min(iv_runtime_n_port, static_cast<uint16_t>(iv_n_slot * l_count));
    iv_n_port = (iv_n_port == 0) ? TT::MIN_THROTTLE : iv_n_port;

    iv_n_slot = std::min(iv_n_slot, iv_runtime_n_slot);
    iv_n_slot = (iv_n_slot == 0) ? TT::MIN_THROTTLE : iv_n_slot;

    //Now time to get and set iv_calc_port_max from the calculated N throttle
    FAPI_TRY( calc_power_from_n(iv_n_slot, iv_n_port, iv_calc_port_maxpower),
              "Failed to calculate the final max port maxpower. Slot throttle value is %d, port value is %d for %s",
              iv_n_slot,
              iv_n_port,
              mss::c_str(iv_target));

    return fapi2::FAPI2_RC_SUCCESS;
fapi_try_exit:
    FAPI_ERR("Error calculating mss::power_thermal::thermal_throttles() for %s", mss::c_str(iv_target));
    return fapi2::current_err;
}

///
/// @brief Calculates the min and max power usage for a port based off of power curves and utilizations
/// @param[in] i_idle_util the utilization of the databus in idle mode (0% most likely)
/// @param[in] i_max_util the utilization of the dimm at maximum possible percentage (mrw or calculated)
/// @param[out] o_port_power_idle max value of port power in cW
/// @param[out] o_port_power_max max value of port power in cW
/// @return fapi2::FAPI2_RC_SUCCESS iff the method was a success
/// @note Called twice in p9_mss_bulk_pwr_throttles
/// @note uses dimm power curves from class variables
///
template<mss::mc_type MC, typename TT>
fapi2::ReturnCode throttle<MC, TT>::calc_port_power(const double i_idle_util [TT::DIMMS_PER_PORT],
        const double i_max_util [TT::DIMMS_PER_PORT],
        double& o_port_power_idle,
        double& o_port_power_max) const
{
    //Playing it safe
    o_port_power_idle = 0;
    o_port_power_max = 0;
    fapi2::ReturnCode l_rc;

    //Calculate the port power curve info by summing the dimms on the port
    for ( const auto& l_dimm : mss::find_targets<fapi2::TARGET_TYPE_DIMM>(iv_target) )
    {
        const auto l_pos = mss::index(l_dimm);
        //Printing as decimals because HB messes up floats
        FAPI_INF("%s max dram databus for DIMM in pos %d is %d, databus for idle is %d",
                 mss::c_str(iv_target),
                 l_pos,
                 static_cast<uint64_t>( i_max_util[l_pos]),
                 static_cast<uint64_t>( i_idle_util[l_pos]) );
        //Sum up the dimm's power to calculate the port power curve
        o_port_power_idle += calc_power(i_idle_util[l_pos], l_pos, l_rc);
        FAPI_TRY(l_rc, "calc_power failed");
        o_port_power_max  += calc_power(i_max_util[l_pos], l_pos, l_rc);
        FAPI_TRY(l_rc, "calc_power failed");
    }

    //Raise the powers by the uplift percent
    calc_power_uplift(iv_power_uplift_idle, o_port_power_idle);
    calc_power_uplift(iv_power_uplift, o_port_power_max);

    FAPI_ASSERT( (o_port_power_max > 0),
                 fapi2::MSS_NO_PORT_POWER()
                 .set_COUNT_DIMMS(mss::count_dimm(iv_target))
                 .set_MAX_UTILIZATION_DIMM_0(i_max_util[0])
                 .set_MAX_UTILIZATION_DIMM_1(i_max_util[1]),
                 "No Port Power limit was calculated for %s, %d DIMMs installed, utilizations: DIMM 0 %d, DIMM 1 %d",
                 mss::c_str(iv_target),
                 mss::count_dimm(iv_target),
                 i_max_util[0],
                 i_max_util[1]);

    //FAPI_ASSERTs don't set the current err to good
    return fapi2::FAPI2_RC_SUCCESS;
fapi_try_exit:
    return fapi2::current_err;
}

///
/// @brief Calculates max and min power usages based off of DIMM power curves
/// @param[in] i_databus_idle idle databus utilization (either calculated or mrw)
/// @param[in] i_databus_max max databus utilization (either calculated or mrw)
/// @param[out] o_dimm_power_idle array of dimm power in cW
/// @param[out] o_dimm_power_max array of dimm power in cW
/// @return fapi2::ReturnCode - FAPI2_RC_SUCCESS iff the split is OK
/// @note Called in p9_mss_bulk_pwr_throttles
/// @note used for the thermal throttles
///
template<mss::mc_type MC, typename TT>
fapi2::ReturnCode throttle<MC, TT>::calc_dimm_power(const double i_databus_idle,
        const double i_databus_max,
        double o_dimm_power_idle [TT::DIMMS_PER_PORT],
        double o_dimm_power_max [TT::DIMMS_PER_PORT]) const
{
    for ( const auto& l_dimm : mss::find_targets<fapi2::TARGET_TYPE_DIMM>(iv_target) )
    {
        fapi2::ReturnCode l_rc;
        const uint8_t l_pos = mss::index(l_dimm);
        o_dimm_power_idle[l_pos] = calc_power(i_databus_idle, l_pos, l_rc);
        FAPI_TRY(l_rc, "calc_power failed");
        o_dimm_power_max[l_pos]  = calc_power(i_databus_max, l_pos, l_rc);
        FAPI_TRY(l_rc, "calc_power failed");

        //Raise the powers by the uplift percent
        calc_power_uplift(iv_power_uplift_idle, o_dimm_power_idle[l_pos]);
        calc_power_uplift(iv_power_uplift, o_dimm_power_max[l_pos]);

        FAPI_INF("Calc_dimm_power: dimm (%d) power max is %f, %f for  dimm slope of  %d, intercept of %d for %s",
                 l_pos,
                 o_dimm_power_max[l_pos],
                 o_dimm_power_max[l_pos],
                 iv_pwr_slope[l_pos],
                 iv_pwr_int[l_pos],
                 mss::c_str(l_dimm));
    }

    return fapi2::FAPI2_RC_SUCCESS;

fapi_try_exit:
    FAPI_INF("Error calculating mss::power_thermal::calc_dimm_power for %s", mss::c_str(iv_target));
    return fapi2::current_err;
}

///
/// @brief Calculate the port power curve in order to calculate the port utilization
/// @param[in] i_power_idle double of the port's power consumption at idle
/// @param[in] i_power_max double of the port's power consumption at max utilization
/// @param[out] o_slope
/// @param[out] o_int
/// @note Called in p9_mss_bulk_pwr_throttles
/// @note Port power curve needed to calculate the port utilization
///
template<mss::mc_type MC, typename TT>
fapi2::ReturnCode throttle<MC, TT>::calc_power_curve(const double i_power_idle,
        const double i_power_max,
        uint32_t& o_slope,
        uint32_t& o_int) const
{
    auto l_min_util = TT::MIN_UTIL;
    const double l_divisor = ((static_cast<double>(iv_databus_port_max) / UTIL_CONVERSION) - TT::IDLE_UTIL);
    FAPI_ASSERT ((l_divisor > 0),
                 fapi2::MSS_CALC_POWER_CURVE_DIVIDE_BY_ZERO()
                 .set_PORT_DATABUS_UTIL(iv_databus_port_max)
                 .set_UTIL_CONVERSION(UTIL_CONVERSION)
                 .set_IDLE_UTIL(l_min_util)
                 .set_RESULT(l_divisor),
                 "Calculated zero for the divisor in calc_power_curve on target %s",
                 mss::c_str(iv_target) );

    o_slope = (i_power_max - i_power_idle) / l_divisor;
    o_int = i_power_idle - (o_slope * TT::IDLE_UTIL);
    FAPI_INF("Calc_power_curve: power idle is %f, max is %f, slope is %d, int is %d for %s",
             i_power_idle,
             i_power_max,
             o_slope,
             o_int,
             mss::c_str(iv_target));
    return fapi2::FAPI2_RC_SUCCESS;

fapi_try_exit:
    FAPI_INF("Error calculating mss::power_thermal::calc_power_curve for %s", mss::c_str(iv_target));
    return fapi2::current_err;

}

///
/// @brief Calculate the databus utilization given the power curve
/// @param[in] i_slope
/// @param[in] i_int
/// @param[in] i_power_limit either the port_power_limit or the dimm thermal power limit
/// @param[out] o_port_util the port's databus utilization
/// @note Called in p9_mss_bulk_pwr_throttles
/// @note Chooses worst case between the maximum allowed databus utilization and the calculated value
///
template<mss::mc_type MC, typename TT>
void throttle<MC, TT>::calc_util_usage(const uint32_t i_slope,
                                       const uint32_t i_int,
                                       const uint32_t i_power_limit,
                                       double& o_util) const
{
    // Return 0 utilization if our intercept is above the power limit
    o_util = (i_power_limit > i_int) ? (((static_cast<double>(i_power_limit) - i_int) / i_slope ) * UTIL_CONVERSION) : 0;

    // Cast to uint32 for edge case where it has decimals
    o_util = (static_cast<uint32_t>(o_util) < iv_databus_port_max) ? static_cast<uint32_t>(o_util) : iv_databus_port_max;

    // Check for the minimum threshnold and update if need be
    if(o_util < TT::MIN_UTIL)
    {
        FAPI_INF("Calculated utilization (%f) is less than the minimum utilization: %lu. Setting to minimum value for %s",
                 o_util,
                 TT::MIN_UTIL, mss::c_str(iv_target));
        o_util = TT::MIN_UTIL;
    }
}

///
/// @brief calculated the output power estimate from the calculated N throttle
/// @param[in] i_n_slot the throttle per slot in terms of N commands
/// @param[in] i_n_port the throttle per port in terms of N commands
/// @param[out] o_power the calculated power
/// @return fapi2::ReturnCode iff it was a success
///
template<mss::mc_type MC, typename TT>
fapi2::ReturnCode throttle<MC, TT>::calc_power_from_n (const uint16_t i_n_slot,
        const uint16_t i_n_port,
        uint32_t& o_power) const
{
    double l_calc_util_port = 0;
    double l_calc_util_slot = 0;
    double l_calc_databus_port_max[TT::DIMMS_PER_PORT] = {};
    double l_calc_databus_port_idle[TT::DIMMS_PER_PORT] = {};
    double l_port_power_max = 0;
    double l_port_power_idle = 0;

    FAPI_TRY( calc_util_from_throttles(i_n_slot, iv_m_clocks, l_calc_util_slot),
              "%s Error calculating utilization from slot throttle %d and mem clocks %d",
              mss::c_str(iv_target),
              i_n_slot,
              iv_m_clocks);
    FAPI_TRY( calc_util_from_throttles(i_n_port, iv_m_clocks, l_calc_util_port),
              "%s Error calculating utilization from port throttle %d and mem clocks %d",
              mss::c_str(iv_target),
              i_n_port,
              iv_m_clocks);

    //Determine the utilization for each DIMM that will maximize the port power
    FAPI_TRY( calc_split_util(l_calc_util_slot, l_calc_util_port, l_calc_databus_port_max),
              "Error splitting the utilization for target %s with slot utilizatio %f and port util %f",
              mss::c_str(iv_target),
              l_calc_util_slot,
              l_calc_util_port);

    FAPI_TRY( calc_port_power(l_calc_databus_port_idle,
                              l_calc_databus_port_max,
                              l_port_power_idle,
                              l_port_power_max),
              "Error calculating the port power value for %s. Slot value is %d, port value is %d",
              mss::c_str(iv_target),
              i_n_slot,
              i_n_port);

    o_power = mss::round_up (l_port_power_max);

fapi_try_exit:
    return fapi2::current_err;
}

///
/// @brief Converts the port maximum databus to a dimm level based on powerslopes and dimms installed
/// @param[in] i_databus_port_max max databus utilization for the port (either calculated or mrw)
/// @param[out] o_databus_dimm_max array of dimm utilization values
/// @return fapi2::ReturnCode - FAPI2_RC_SUCCESS iff the split is OK
/// @note Called in p9_mss_bulk_pwr_throttles
/// @used to calculate the port power based off of DIMM power curves
///
template<mss::mc_type MC, typename TT>
fapi2::ReturnCode throttle<MC, TT>::calc_databus (const double i_databus_port_max,
        double o_databus_dimm_max [TT::DIMMS_PER_PORT])
{
    const uint8_t l_count_dimms = count_dimm(iv_target);

    //No work for no dimms
    if (l_count_dimms == 0)
    {
        return fapi2::FAPI2_RC_SUCCESS;
    }

    for (const auto& l_dimm : mss::find_targets<fapi2::TARGET_TYPE_DIMM>(iv_target))
    {
        //Left early if count_dimms == 0
        o_databus_dimm_max[mss::index(l_dimm)] = i_databus_port_max / l_count_dimms;
    }

    //If the power slopes aren't equal, set the dimm with the highest power slope
    //Should be correct even if only one DIMM is installed
    if (iv_pwr_slope[0] != iv_pwr_slope[1])
    {
        o_databus_dimm_max[0] = (iv_pwr_slope[0] > iv_pwr_slope[1]) ? i_databus_port_max : 0;
        o_databus_dimm_max[1] = (iv_pwr_slope[1] > iv_pwr_slope[0]) ? i_databus_port_max : 0;
    }

    //Make sure both are not 0
    FAPI_ASSERT ( (o_databus_dimm_max[0] != 0) || (o_databus_dimm_max[1] != 0),
                  fapi2::MSS_NO_DATABUS_UTILIZATION()
                  .set_PORT_DATABUS_UTIL(i_databus_port_max)
                  .set_DIMM_COUNT(l_count_dimms),
                  "Failed to calculated databus utilization for target %s",
                  mss::c_str(iv_target));

    return fapi2::FAPI2_RC_SUCCESS;
fapi_try_exit:
    return fapi2::current_err;
}

///
/// @brief Converts the port and slot util to a dimm level based on powerslopes and number of dimms installed
/// @param[in] i_util_slot databus utilization for the slot
/// @param[in] i_util_port databus utilization for the port
/// @param[out] o_util_dimm_max array of dimm utilization values
/// @return fapi2::ReturnCode - FAPI2_RC_SUCCESS iff the split is OK
/// @note determines worst case utilization per dimms, takes into account port and combine slot throttles
/// @note used in calculating the port power, not for calculating the slot and port utilization
///
template<mss::mc_type MC, typename TT>
fapi2::ReturnCode throttle<MC, TT>::calc_split_util(
    const double i_util_slot,
    const double i_util_port,
    double o_util_dimm_max [TT::DIMMS_PER_PORT]) const
{
    fapi2::current_err = fapi2::FAPI2_RC_SUCCESS;
    const uint8_t l_count_dimms = count_dimm (iv_target);
    //The total utilization to be used is limited by either what the port can allow or what the dimms can use
    FAPI_ASSERT( (i_util_slot <= i_util_port),
                 fapi2::MSS_SLOT_UTIL_EXCEEDS_PORT()
                 .set_SLOT_UTIL(i_util_slot)
                 .set_PORT_UTIL(i_util_port),
                 "The slot utilization (%f) exceeds the port's utilization (%f) for %s",
                 i_util_slot,
                 i_util_port,
                 mss::c_str(iv_target));

    if (l_count_dimms == 0)
    {
        return fapi2::FAPI2_RC_SUCCESS;
    }

    //assumptions slot <= port, l_count_dimms <=2
    if (i_util_slot * l_count_dimms > i_util_port)
    {
        FAPI_INF("In mss::power_thermal::calc_split i_util_slot is %f, i_util_port is %f, l_count_dimms is %d for %s",
                 i_util_slot,
                 i_util_port,
                 l_count_dimms,
                 mss::c_str(iv_target));
        const uint8_t l_high_pos = (iv_pwr_slope[0] >= iv_pwr_slope[1]) ? 0 : 1;

        //Highest power_slope gets the higher utilization
        o_util_dimm_max[l_high_pos] = std::min(i_util_slot, i_util_port);
        //Set the other dimm to the left over utilization (i_util_port - i_util_slot)
        o_util_dimm_max[(!l_high_pos)] = (l_count_dimms == TT::DIMMS_PER_PORT) ? (i_util_port - o_util_dimm_max[l_high_pos]) :
                                         0;

        FAPI_INF("Split utilization for target %s, DIMM in %d gets %f, DIMM in %d gets %f",
                 mss::c_str(iv_target),
                 l_high_pos,
                 o_util_dimm_max[l_high_pos],
                 !l_high_pos,
                 o_util_dimm_max[!l_high_pos]);
    }
    else
    {
        //If only 1 dimm, i_util_port == i_util_slot
        //If 2 dimms, 2*i_util_slot <= i_util_pot
        //Either way, limit utilization by the slot value
        for (const auto& l_dimm : mss::find_targets<fapi2::TARGET_TYPE_DIMM>(iv_target))
        {
            const size_t l_pos = mss::index(l_dimm);
            o_util_dimm_max[l_pos] = i_util_slot;
        }
    }

    //make sure both are not 0
    FAPI_ASSERT ( (o_util_dimm_max[0] != 0) || (o_util_dimm_max[1] != 0),
                  fapi2::MSS_NO_DATABUS_UTILIZATION()
                  .set_PORT_DATABUS_UTIL(i_util_port)
                  .set_DIMM_COUNT(mss::count_dimm(iv_target)),
                  "Failed to calculated util utilization for target %s",
                  mss::c_str(iv_target));
fapi_try_exit:
    return fapi2::current_err;
}


///
/// @brief Equalize the throttles and estimated power at those throttle levels
/// @tparam MC mss::mc_type
/// @tparam T the fapi2 MC target type of the target
/// @tparam TT throttle_traits throttle traits for the given mc_type
/// @param[in] i_targets vector of MCS targets all on the same VDDR domain
/// @param[in] i_throttle_type denotes if this was done for POWER (VMEM) or THERMAL (VMEM+VPP) throttles
/// @param[out] o_exceeded_power vector of MCA targets where the estimated power exceeded the maximum allowed
/// @return FAPI2_RC_SUCCESS iff ok
/// @note sets the throttles and power to the worst case
/// Called by p9_mss_bulk_pwr_throttles and by p9_mss_utils_to_throttle (so by IPL or by OCC)
///
template<mss::mc_type MC = DEFAULT_MC_TYPE, fapi2::TargetType T, typename TT = throttle_traits<MC>>
fapi2::ReturnCode equalize_throttles (const std::vector< fapi2::Target<T> >& i_targets,
                                      const throttle_type i_throttle_type,
                                      std::vector< fapi2::Target<TT::PORT_TARGET_TYPE> >& o_exceeded_power)

{
    o_exceeded_power.clear();

    //Set to max values so every compare will change to min value
    uint16_t l_min_slot = ~(0);
    uint16_t l_min_port = ~(0);

    //Loop through all of the MC targets to find the worst case throttle value (lowest) for the slot and port
    for (const auto& l_mc : i_targets)
    {
        for (const auto& l_port : mss::find_targets<TT::PORT_TARGET_TYPE>(l_mc))
        {
            uint16_t l_calc_slot = 0;
            uint16_t l_calc_port = 0;
            uint16_t l_run_slot = 0;
            uint16_t l_run_port = 0;

            if (mss::count_dimm(l_port) == 0)
            {
                continue;
            }

            FAPI_TRY(mss::attr::get_mem_throttled_n_commands_per_slot(l_port, l_calc_slot));
            FAPI_TRY(mss::attr::get_mem_throttled_n_commands_per_port(l_port, l_calc_port));
            FAPI_TRY(mss::attr::get_runtime_mem_throttled_n_commands_per_slot(l_port, l_run_slot));
            FAPI_TRY(mss::attr::get_runtime_mem_throttled_n_commands_per_port(l_port, l_run_port));

            //Find the smaller of the three values (calculated slot, runtime slot, and min slot)
            l_min_slot = (l_calc_slot != 0) ? std::min( std::min (l_calc_slot, l_run_slot),
                         l_min_slot) : l_min_slot;
            l_min_port = (l_calc_port != 0) ? std::min( std::min( l_calc_port, l_run_port),
                         l_min_port) : l_min_port;
        }
    }

    FAPI_INF("Calculated min slot is %d, min port is %d for the system", l_min_slot, l_min_port);

    //Now set every port to have those values
    {
        for (const auto& l_mc : i_targets)
        {
            for (const auto& l_port : mss::find_targets<TT::PORT_TARGET_TYPE>(l_mc))
            {
                uint16_t l_fin_slot = 0;
                uint16_t l_fin_port  = 0;
                uint32_t l_fin_power  = 0;;

                if (mss::count_dimm(l_port) == 0)
                {
                    continue;
                }

                // Declaring above to avoid fapi2 jump
                uint64_t l_power_limit = 0;

                l_fin_slot = l_min_slot;
                l_fin_port = l_min_port;

                //Need to create throttle object for each mca in order to get dimm configuration and power curves
                //To calculate the slot/port utilization and total port power consumption
                fapi2::ReturnCode l_rc = fapi2::FAPI2_RC_SUCCESS;

                const auto l_dummy = mss::power_thermal::throttle<MC>(l_port, l_rc);
                FAPI_TRY(l_rc, "Failed creating a throttle object in equalize_throttles for %s", mss::c_str(l_port));

                FAPI_TRY( l_dummy.calc_power_from_n(l_fin_slot, l_fin_port, l_fin_power),
                          "Failed calculating the power value for throttles: slot %d, port %d for target %s",
                          l_fin_slot,
                          l_fin_port,
                          mss::c_str(l_port));

                // You may ask why this is not a variable within the throttle struct
                // It's because POWER throttling is on a per port basis while the THERMAL throttle is per dimm
                // Didn't feel like adding a variable just for this check
                l_power_limit = (i_throttle_type == throttle_type::POWER) ?
                                l_dummy.iv_port_power_limit : (l_dummy.iv_dimm_thermal_limit[0] + l_dummy.iv_dimm_thermal_limit[1]);

                FAPI_INF("%s Calculated power is %d, limit is %ld", mss::c_str(l_port), l_fin_power, l_power_limit);

                //If there's an error with calculating port power, the wrong watt target was passed in
                //Returns an error but doesn't deconfigure anything. Calling function can log if it wants to
                //Called by OCC and by p9_mss_eff_config_thermal, thus different ways for error handling
                //Continue setting throttles to prevent a possible throttle == 0
                //The error will be the last bad port found
                if (l_fin_power > l_power_limit)
                {
                    //Need this because of pos traits and templating stuff
                    uint64_t l_fail = mss::fapi_pos(l_port);
                    //Set the failing port. OCC just needs one failing port, doesn't need all of them
                    FAPI_TRY( FAPI_ATTR_SET( fapi2::ATTR_MSS_MEM_PORT_POS_OF_FAIL_THROTTLE,
                                             fapi2::Target<fapi2::TARGET_TYPE_SYSTEM>(),
                                             l_fail) );

                    FAPI_ASSERT_NOEXIT( false,
                                        fapi2::MSS_CALC_PORT_POWER_EXCEEDS_MAX()
                                        .set_CALCULATED_PORT_POWER(l_fin_power)
                                        .set_MAX_POWER_ALLOWED(l_power_limit)
                                        .set_PORT_POS(mss::pos(l_port))
                                        .set_PORT_TARGET(l_port),
                                        "Error calculating the final port power value for target %s, calculated power is %d, max value can be %d",
                                        mss::c_str(l_port),
                                        l_fin_power,
                                        l_power_limit);

                    o_exceeded_power.push_back(l_port);
                }

                FAPI_INF("%s Final throttles values for slot %d, for port %d, power value %d",
                         mss::c_str(l_port),
                         l_fin_port,
                         l_fin_slot,
                         l_fin_power);

                //Even if there's an error, still calculate and set the throttles.
                //OCC will set to safemode if there's an error
                //Better to set the throttles than leave them 0, and potentially brick the memory
                FAPI_TRY( mss::attr::set_mem_throttled_n_commands_per_port( l_port, l_fin_port) );
                FAPI_TRY( mss::attr::set_mem_throttled_n_commands_per_slot( l_port, l_fin_slot) );
                FAPI_TRY( mss::attr::set_port_maxpower( l_port, l_fin_power) );
            }
        }
    }
    return fapi2::FAPI2_RC_SUCCESS;
fapi_try_exit:
    FAPI_ERR("Error equalizing memory throttles");
    return fapi2::current_err;
}

} //ns power_thermal
}// mss

#endif
OpenPOWER on IntegriCloud