blob: 8d6d8ecfe61c0934ade6a5d151602b40abe6b8eb [file] [log] [blame]
/* 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 <nrb@linux.ibm.com>
*/
#include <libcflat.h>
#include <migrate.h>
#include <asm/facility.h>
#include <asm/page.h>
#include <asm/mem.h>
#include <asm/barrier.h>
#include <hardware.h>
#include <smp.h>
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");
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");
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();
break;
}
error:
migrate_once();
report_prefix_pop();
return report_summary();
}