/* SPDX-License-Identifier: GPL-2.0-only */ /* * Storage Key migration tests * * There are two variants of this test: * - sequential: set storage keys on some pages, migrates the VM and then * verifies that the storage keys are still as we expect. * - parallel: start migration and set and check storage keys on some * pages while migration is in process. * * Copyright IBM Corp. 2022 * * Authors: * Nico Boehr */ #include #include #include #include #include #include #include #include struct verify_result { bool verify_failed; union skey expected_key; union skey actual_key; unsigned long page_mismatch_idx; unsigned long page_mismatch_addr; }; #define NUM_PAGES 128 static uint8_t pagebuf[NUM_PAGES * PAGE_SIZE] __attribute__((aligned(PAGE_SIZE))); static struct verify_result result; static unsigned int thread_iters; static bool thread_should_exit; static bool thread_exited; static enum { TEST_INVALID, TEST_SEQUENTIAL, TEST_PARALLEL } arg_test_to_run; /* * Set storage key test pattern on pagebuf with a seed for the storage keys. * * Each page's storage key is generated by taking the page's index in pagebuf, * XOR-ing that with the given seed and then multipling the result with two. * * Only the lower seven bits of the seed are considered. */ static void set_test_pattern(unsigned char seed) { unsigned char key_to_set; unsigned long i; for (i = 0; i < NUM_PAGES; i++) { /* * Storage keys are 7 bit, lowest bit is always returned as zero * by iske. * This loop will set all 7 bits which means we set fetch * protection as well as reference and change indication for * some keys. */ key_to_set = (i ^ seed) * 2; set_storage_key(pagebuf + i * PAGE_SIZE, key_to_set, 1); } } /* * Verify storage keys on pagebuf. * Storage keys must have been set by set_test_pattern on pagebuf before. * set_test_pattern must have been called with the same seed value. * * If storage keys match the expected result, will return a verify_result * with verify_failed false. All other fields are then invalid. * If there is a mismatch, returned struct will have verify_failed true and will * be filled with the details on the first mismatch encountered. */ static struct verify_result verify_test_pattern(unsigned char seed) { union skey expected_key, actual_key; struct verify_result result = { .verify_failed = true }; uint8_t *cur_page; unsigned long i; for (i = 0; i < NUM_PAGES; i++) { cur_page = pagebuf + i * PAGE_SIZE; actual_key.val = get_storage_key(cur_page); expected_key.val = (i ^ seed) * 2; /* * The PoP neither gives a guarantee that the reference bit is * accurate nor that it won't be cleared by hardware. Hence we * don't rely on it and just clear the bits to avoid compare * errors. */ actual_key.str.rf = 0; expected_key.str.rf = 0; if (actual_key.val != expected_key.val) { result.expected_key.val = expected_key.val; result.actual_key.val = actual_key.val; result.page_mismatch_idx = i; result.page_mismatch_addr = (unsigned long)cur_page; return result; } } result.verify_failed = false; return result; } static void report_verify_result(const struct verify_result * result) { if (result->verify_failed) report_fail("page skey mismatch: first page idx = %lu, addr = 0x%lx, " "expected_key = 0x%02x, actual_key = 0x%02x", result->page_mismatch_idx, result->page_mismatch_addr, result->expected_key.val, result->actual_key.val); else report_pass("skeys match"); } static void test_skey_migration_sequential(void) { report_prefix_push("sequential"); set_test_pattern(0); migrate_once(); result = verify_test_pattern(0); report_verify_result(&result); report_prefix_pop(); } static void set_skeys_thread(void) { while (!READ_ONCE(thread_should_exit)) { set_test_pattern(thread_iters); result = verify_test_pattern(thread_iters); /* * Always increment even if the verify fails. This ensures primary CPU knows where * we left off and can do an additional verify round after migration finished. */ thread_iters++; if (result.verify_failed) break; } WRITE_ONCE(thread_exited, 1); } static void test_skey_migration_parallel(void) { report_prefix_push("parallel"); if (smp_query_num_cpus() == 1) { report_skip("need at least 2 cpus for this test"); migrate_skip(); goto error; } smp_cpu_setup(1, PSW_WITH_CUR_MASK(set_skeys_thread)); migrate_once(); WRITE_ONCE(thread_should_exit, 1); while (!READ_ONCE(thread_exited)) ; /* Ensure we read result and thread_iters below from memory after thread exited */ mb(); report_info("thread completed %u iterations", thread_iters); report_prefix_push("during migration"); report_verify_result(&result); report_prefix_pop(); /* * Verification of skeys occurs on the thread. We don't know if we * were still migrating during the verification. * To be sure, make another verification round after the migration * finished to catch skeys which might not have been migrated * correctly. */ report_prefix_push("after migration"); assert(thread_iters > 0); result = verify_test_pattern(thread_iters - 1); report_verify_result(&result); report_prefix_pop(); error: report_prefix_pop(); } static void print_usage(void) { report_info("Usage: migration-skey [--parallel|--sequential]"); } static void parse_args(int argc, char **argv) { if (argc < 2) { /* default to sequential since it only needs one cpu */ arg_test_to_run = TEST_SEQUENTIAL; return; } if (!strcmp("--parallel", argv[1])) arg_test_to_run = TEST_PARALLEL; else if (!strcmp("--sequential", argv[1])) arg_test_to_run = TEST_SEQUENTIAL; else arg_test_to_run = TEST_INVALID; } int main(int argc, char **argv) { report_prefix_push("migration-skey"); if (test_facility(169)) { report_skip("storage key removal facility is active"); migrate_skip(); goto error; } parse_args(argc, argv); switch (arg_test_to_run) { case TEST_SEQUENTIAL: test_skey_migration_sequential(); break; case TEST_PARALLEL: test_skey_migration_parallel(); break; default: print_usage(); migrate_skip(); break; } error: report_prefix_pop(); return report_summary(); }