| 1 | |
| 2 | |
| 3 |
|
| 4 |
|
| 5 | #include <kunit/test.h>
|
| 6 | #include <linux/array_size.h>
|
| 7 | #include <linux/bits.h>
|
| 8 | #include <linux/math.h>
|
| 9 | #include <linux/sprintf.h>
|
| 10 |
|
| 11 | struct percent_value_case {
|
| 12 | u8 pc;
|
| 13 | u8 width;
|
| 14 | u16 value;
|
| 15 | };
|
| 16 |
|
| 17 | |
| 18 | * Mysterious inscriptions taken from the union of ARM DDI 0598D.b,
|
| 19 | * "Arm Architecture Reference Manual Supplement - Memory System
|
| 20 | * Resource Partitioning and Monitoring (MPAM), for A-profile
|
| 21 | * architecture", Section 9.8, "About the fixed-point fractional
|
| 22 | * format" (exact percentage entries only) and ARM IHI0099B.a
|
| 23 | * "MPAM system component specification", Section 9.3,
|
| 24 | * "The fixed-point fractional format":
|
| 25 |
|
| 26 | static const struct percent_value_case percent_value_cases[] = {
|
| 27 |
|
| 28 | { 1, 8, 1 }, { 1, 12, 0x27 }, { 1, 16, 0x28e },
|
| 29 | { 25, 8, 0x3f }, { 25, 12, 0x3ff }, { 25, 16, 0x3fff },
|
| 30 | { 33, 8, 0x53 }, { 33, 12, 0x546 }, { 33, 16, 0x5479 },
|
| 31 | { 35, 8, 0x58 }, { 35, 12, 0x598 }, { 35, 16, 0x5998 },
|
| 32 | { 45, 8, 0x72 }, { 45, 12, 0x732 }, { 45, 16, 0x7332 },
|
| 33 | { 50, 8, 0x7f }, { 50, 12, 0x7ff }, { 50, 16, 0x7fff },
|
| 34 | { 52, 8, 0x84 }, { 52, 12, 0x850 }, { 52, 16, 0x851d },
|
| 35 | { 55, 8, 0x8b }, { 55, 12, 0x8cb }, { 55, 16, 0x8ccb },
|
| 36 | { 58, 8, 0x93 }, { 58, 12, 0x946 }, { 58, 16, 0x9479 },
|
| 37 | { 75, 8, 0xbf }, { 75, 12, 0xbff }, { 75, 16, 0xbfff },
|
| 38 | { 80, 8, 0xcb }, { 80, 12, 0xccb }, { 80, 16, 0xcccb },
|
| 39 | { 88, 8, 0xe0 }, { 88, 12, 0xe13 }, { 88, 16, 0xe146 },
|
| 40 | { 95, 8, 0xf2 }, { 95, 12, 0xf32 }, { 95, 16, 0xf332 },
|
| 41 | { 100, 8, 0xff }, { 100, 12, 0xfff }, { 100, 16, 0xffff },
|
| 42 | };
|
| 43 |
|
| 44 | static void test_percent_value_desc(const struct percent_value_case *param,
|
| 45 | char *desc)
|
| 46 | {
|
| 47 | snprintf(desc, KUNIT_PARAM_DESC_SIZE,
|
| 48 | "pc=%d, width=%d, value=0x%.*x\n",
|
| 49 | param->pc, param->width,
|
| 50 | DIV_ROUND_UP(param->width, 4), param->value);
|
| 51 | }
|
| 52 |
|
| 53 | KUNIT_ARRAY_PARAM(test_percent_value, percent_value_cases,
|
| 54 | test_percent_value_desc);
|
| 55 |
|
| 56 | struct percent_value_test_info {
|
| 57 | u32 pc;
|
| 58 | u32 value;
|
| 59 | u32 max_value;
|
| 60 | unsigned int shift;
|
| 61 | };
|
| 62 |
|
| 63 | |
| 64 | * Convert a reference percentage to a fixed-point MAX value and
|
| 65 | * vice-versa, based on param (not test->param_value!)
|
| 66 |
|
| 67 | static void __prepare_percent_value_test(struct kunit *test,
|
| 68 | struct percent_value_test_info *res,
|
| 69 | const struct percent_value_case *param)
|
| 70 | {
|
| 71 | struct mpam_props fake_props = { };
|
| 72 |
|
| 73 |
|
| 74 | KUNIT_ASSERT_GE(test, param->width, 1);
|
| 75 | KUNIT_ASSERT_LE(test, param->width, 16);
|
| 76 | KUNIT_ASSERT_LT(test, param->value, 1 << param->width);
|
| 77 |
|
| 78 | mpam_set_feature(mpam_feat_mbw_max, &fake_props);
|
| 79 | fake_props.bwa_wd = param->width;
|
| 80 |
|
| 81 | res->shift = 16 - param->width;
|
| 82 | res->max_value = GENMASK_U32(param->width - 1, 0);
|
| 83 | res->value = percent_to_mbw_max(param->pc, &fake_props);
|
| 84 | res->pc = mbw_max_to_percent(param->value << res->shift, &fake_props);
|
| 85 | }
|
| 86 |
|
| 87 | static void test_get_mba_granularity(struct kunit *test)
|
| 88 | {
|
| 89 | int ret;
|
| 90 | struct mpam_props fake_props = { };
|
| 91 |
|
| 92 |
|
| 93 | mpam_set_feature(mpam_feat_mbw_max, &fake_props);
|
| 94 |
|
| 95 | fake_props.bwa_wd = 0;
|
| 96 | KUNIT_EXPECT_FALSE(test, mba_class_use_mbw_max(&fake_props));
|
| 97 |
|
| 98 | fake_props.bwa_wd = 1;
|
| 99 | KUNIT_EXPECT_TRUE(test, mba_class_use_mbw_max(&fake_props));
|
| 100 |
|
| 101 |
|
| 102 | fake_props.bwa_wd = 16;
|
| 103 | KUNIT_EXPECT_TRUE(test, mba_class_use_mbw_max(&fake_props));
|
| 104 |
|
| 105 |
|
| 106 | fake_props.bwa_wd = 0;
|
| 107 | ret = get_mba_granularity(&fake_props);
|
| 108 | KUNIT_EXPECT_EQ(test, ret, 0);
|
| 109 |
|
| 110 | fake_props.bwa_wd = 1;
|
| 111 | ret = get_mba_granularity(&fake_props);
|
| 112 | KUNIT_EXPECT_EQ(test, ret, 50);
|
| 113 |
|
| 114 | fake_props.bwa_wd = 2;
|
| 115 | ret = get_mba_granularity(&fake_props);
|
| 116 | KUNIT_EXPECT_EQ(test, ret, 25);
|
| 117 |
|
| 118 | fake_props.bwa_wd = 3;
|
| 119 | ret = get_mba_granularity(&fake_props);
|
| 120 | KUNIT_EXPECT_EQ(test, ret, 13);
|
| 121 |
|
| 122 | fake_props.bwa_wd = 6;
|
| 123 | ret = get_mba_granularity(&fake_props);
|
| 124 | KUNIT_EXPECT_EQ(test, ret, 2);
|
| 125 |
|
| 126 | fake_props.bwa_wd = 7;
|
| 127 | ret = get_mba_granularity(&fake_props);
|
| 128 | KUNIT_EXPECT_EQ(test, ret, 1);
|
| 129 |
|
| 130 |
|
| 131 | fake_props.bwa_wd = 16;
|
| 132 | ret = get_mba_granularity(&fake_props);
|
| 133 | KUNIT_EXPECT_EQ(test, ret, 1);
|
| 134 | }
|
| 135 |
|
| 136 | static void test_mbw_max_to_percent(struct kunit *test)
|
| 137 | {
|
| 138 | const struct percent_value_case *param = test->param_value;
|
| 139 | struct percent_value_test_info res;
|
| 140 |
|
| 141 | |
| 142 | * Since the reference values in percent_value_cases[] all
|
| 143 | * correspond to exact percentages, round-to-nearest will
|
| 144 | * always give the exact percentage back when the MPAM max
|
| 145 | * value has precision of 0.5% or finer. (Always true for the
|
| 146 | * reference data, since they all specify 8 bits or more of
|
| 147 | * precision.
|
| 148 | *
|
| 149 | * So, keep it simple and demand an exact match:
|
| 150 |
|
| 151 | __prepare_percent_value_test(test, &res, param);
|
| 152 | KUNIT_EXPECT_EQ(test, res.pc, param->pc);
|
| 153 | }
|
| 154 |
|
| 155 | static void test_percent_to_mbw_max(struct kunit *test)
|
| 156 | {
|
| 157 | const struct percent_value_case *param = test->param_value;
|
| 158 | struct percent_value_test_info res;
|
| 159 |
|
| 160 | __prepare_percent_value_test(test, &res, param);
|
| 161 |
|
| 162 | KUNIT_EXPECT_GE(test, res.value, param->value << res.shift);
|
| 163 | KUNIT_EXPECT_LE(test, res.value, (param->value + 1) << res.shift);
|
| 164 | KUNIT_EXPECT_LE(test, res.value, res.max_value << res.shift);
|
| 165 |
|
| 166 |
|
| 167 |
|
| 168 | if (param->pc == 0)
|
| 169 | KUNIT_EXPECT_EQ(test, res.value, 0);
|
| 170 |
|
| 171 | if (param->pc == 100)
|
| 172 | KUNIT_EXPECT_EQ(test, res.value, res.max_value << res.shift);
|
| 173 | }
|
| 174 |
|
| 175 | static const void *test_all_bwa_wd_gen_params(struct kunit *test, const void *prev,
|
| 176 | char *desc)
|
| 177 | {
|
| 178 | uintptr_t param = (uintptr_t)prev;
|
| 179 |
|
| 180 | if (param > 15)
|
| 181 | return NULL;
|
| 182 |
|
| 183 | param++;
|
| 184 |
|
| 185 | snprintf(desc, KUNIT_PARAM_DESC_SIZE, "wd=%u\n", (unsigned int)param);
|
| 186 |
|
| 187 | return (void *)param;
|
| 188 | }
|
| 189 |
|
| 190 | static unsigned int test_get_bwa_wd(struct kunit *test)
|
| 191 | {
|
| 192 | uintptr_t param = (uintptr_t)test->param_value;
|
| 193 |
|
| 194 | KUNIT_ASSERT_GE(test, param, 1);
|
| 195 | KUNIT_ASSERT_LE(test, param, 16);
|
| 196 |
|
| 197 | return param;
|
| 198 | }
|
| 199 |
|
| 200 | static void test_mbw_max_to_percent_limits(struct kunit *test)
|
| 201 | {
|
| 202 | struct mpam_props fake_props = {0};
|
| 203 | u32 max_value;
|
| 204 |
|
| 205 | mpam_set_feature(mpam_feat_mbw_max, &fake_props);
|
| 206 | fake_props.bwa_wd = test_get_bwa_wd(test);
|
| 207 | max_value = GENMASK(15, 16 - fake_props.bwa_wd);
|
| 208 |
|
| 209 | KUNIT_EXPECT_EQ(test, mbw_max_to_percent(max_value, &fake_props),
|
| 210 | MAX_MBA_BW);
|
| 211 | KUNIT_EXPECT_EQ(test, mbw_max_to_percent(0, &fake_props),
|
| 212 | get_mba_min(&fake_props));
|
| 213 |
|
| 214 | |
| 215 | * Rounding policy dependent 0% sanity-check:
|
| 216 | * With round-to-nearest, the minimum mbw_max value really
|
| 217 | * should map to 0% if there are at least 200 steps.
|
| 218 | * (100 steps may be enough for some other rounding policies.)
|
| 219 |
|
| 220 | if (fake_props.bwa_wd >= 8)
|
| 221 | KUNIT_EXPECT_EQ(test, mbw_max_to_percent(0, &fake_props), 0);
|
| 222 |
|
| 223 | if (fake_props.bwa_wd < 8 &&
|
| 224 | mbw_max_to_percent(0, &fake_props) == 0)
|
| 225 | kunit_warn(test, "wd=%d: Testsuite/driver Rounding policy mismatch?",
|
| 226 | fake_props.bwa_wd);
|
| 227 | }
|
| 228 |
|
| 229 | |
| 230 | * Check that converting a percentage to mbw_max and back again (or, as
|
| 231 | * appropriate, vice-versa) always restores the original value:
|
| 232 |
|
| 233 | static void test_percent_max_roundtrip_stability(struct kunit *test)
|
| 234 | {
|
| 235 | struct mpam_props fake_props = {0};
|
| 236 | unsigned int shift;
|
| 237 | u32 pc, max, pc2, max2;
|
| 238 |
|
| 239 | mpam_set_feature(mpam_feat_mbw_max, &fake_props);
|
| 240 | fake_props.bwa_wd = test_get_bwa_wd(test);
|
| 241 | shift = 16 - fake_props.bwa_wd;
|
| 242 |
|
| 243 | |
| 244 | * Converting a valid value from the coarser scale to the finer
|
| 245 | * scale and back again must yield the original value:
|
| 246 |
|
| 247 | if (fake_props.bwa_wd >= 7) {
|
| 248 |
|
| 249 | for (pc = get_mba_min(&fake_props); pc <= MAX_MBA_BW; pc++) {
|
| 250 | max = percent_to_mbw_max(pc, &fake_props);
|
| 251 | pc2 = mbw_max_to_percent(max, &fake_props);
|
| 252 | KUNIT_EXPECT_EQ(test, pc2, pc);
|
| 253 | }
|
| 254 | } else {
|
| 255 |
|
| 256 | for (max = 0; max < 1 << 16; max += 1 << shift) {
|
| 257 | pc = mbw_max_to_percent(max, &fake_props);
|
| 258 | max2 = percent_to_mbw_max(pc, &fake_props);
|
| 259 | KUNIT_EXPECT_EQ(test, max2, max);
|
| 260 | }
|
| 261 | }
|
| 262 | }
|
| 263 |
|
| 264 | static void test_percent_to_max_rounding(struct kunit *test)
|
| 265 | {
|
| 266 | const struct percent_value_case *param = test->param_value;
|
| 267 | unsigned int num_rounded_up = 0, total = 0;
|
| 268 | struct percent_value_test_info res;
|
| 269 |
|
| 270 | for (param = percent_value_cases, total = 0;
|
| 271 | param < &percent_value_cases[ARRAY_SIZE(percent_value_cases)];
|
| 272 | param++, total++) {
|
| 273 | __prepare_percent_value_test(test, &res, param);
|
| 274 | if (res.value > param->value << res.shift)
|
| 275 | num_rounded_up++;
|
| 276 | }
|
| 277 |
|
| 278 | |
| 279 | * The MPAM driver applies a round-to-nearest policy, whereas a
|
| 280 | * round-down policy seems to have been applied in the
|
| 281 | * reference table from which the test vectors were selected.
|
| 282 | *
|
| 283 | * For a large and well-distributed suite of test vectors,
|
| 284 | * about half should be rounded up and half down compared with
|
| 285 | * the reference table. The actual test vectors are few in
|
| 286 | * number and probably not very well distributed however, so
|
| 287 | * tolerate a round-up rate of between 1/4 and 3/4 before
|
| 288 | * crying foul:
|
| 289 |
|
| 290 |
|
| 291 | kunit_info(test, "Round-up rate: %u%% (%u/%u)\n",
|
| 292 | DIV_ROUND_CLOSEST(num_rounded_up * 100, total),
|
| 293 | num_rounded_up, total);
|
| 294 |
|
| 295 | KUNIT_EXPECT_GE(test, 4 * num_rounded_up, 1 * total);
|
| 296 | KUNIT_EXPECT_LE(test, 4 * num_rounded_up, 3 * total);
|
| 297 | }
|
| 298 |
|
| 299 | static struct kunit_case mpam_resctrl_test_cases[] = {
|
| 300 | KUNIT_CASE(test_get_mba_granularity),
|
| 301 | KUNIT_CASE_PARAM(test_mbw_max_to_percent, test_percent_value_gen_params),
|
| 302 | KUNIT_CASE_PARAM(test_percent_to_mbw_max, test_percent_value_gen_params),
|
| 303 | KUNIT_CASE_PARAM(test_mbw_max_to_percent_limits, test_all_bwa_wd_gen_params),
|
| 304 | KUNIT_CASE(test_percent_to_max_rounding),
|
| 305 | KUNIT_CASE_PARAM(test_percent_max_roundtrip_stability,
|
| 306 | test_all_bwa_wd_gen_params),
|
| 307 | {}
|
| 308 | };
|
| 309 |
|
| 310 | static struct kunit_suite mpam_resctrl_test_suite = {
|
| 311 | .name = "mpam_resctrl_test_suite",
|
| 312 | .test_cases = mpam_resctrl_test_cases,
|
| 313 | };
|
| 314 |
|
| 315 | kunit_test_suites(&mpam_resctrl_test_suite);
|