Files
linux/lib/test_kho.c
Pratyush Yadav 11047466ef test_kho: always print restore status
Currently the KHO test only prints a message on success, and remains
silent on failure.  This makes it difficult to notice a failing test.  A
failing test is usually more interesting than a successful one.

Always print the test status after attempting restore.

Link: https://lkml.kernel.org/r/20251118181046.23321-1-pratyush@kernel.org
Signed-off-by: Pratyush Yadav <pratyush@kernel.org>
Reviewed-by: Pasha Tatashin <pasha.tatashin@soleen.com>
Reviewed-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
Acked-by: SeongJae Park <sj@kerneel.org>
Cc: Alexander Graf <graf@amazon.com>
Cc: Pratyush Yadav <pratyush@kernel.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
2025-11-27 14:24:42 -08:00

360 lines
7.8 KiB
C

// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Test module for KHO
* Copyright (c) 2025 Microsoft Corporation.
*
* Authors:
* Saurabh Sengar <ssengar@microsoft.com>
* Mike Rapoport <rppt@kernel.org>
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/mm.h>
#include <linux/gfp.h>
#include <linux/slab.h>
#include <linux/kexec.h>
#include <linux/libfdt.h>
#include <linux/module.h>
#include <linux/printk.h>
#include <linux/vmalloc.h>
#include <linux/kexec_handover.h>
#include <net/checksum.h>
#define KHO_TEST_MAGIC 0x4b484f21 /* KHO! */
#define KHO_TEST_FDT "kho_test"
#define KHO_TEST_COMPAT "kho-test-v1"
static long max_mem = (PAGE_SIZE << MAX_PAGE_ORDER) * 2;
module_param(max_mem, long, 0644);
struct kho_test_state {
unsigned int nr_folios;
struct folio **folios;
phys_addr_t *folios_info;
struct kho_vmalloc folios_info_phys;
int nr_folios_preserved;
struct folio *fdt;
__wsum csum;
};
static struct kho_test_state kho_test_state;
static void kho_test_unpreserve_data(struct kho_test_state *state)
{
for (int i = 0; i < state->nr_folios_preserved; i++)
kho_unpreserve_folio(state->folios[i]);
kho_unpreserve_vmalloc(&state->folios_info_phys);
vfree(state->folios_info);
}
static int kho_test_preserve_data(struct kho_test_state *state)
{
struct kho_vmalloc folios_info_phys;
phys_addr_t *folios_info;
int err;
folios_info = vmalloc_array(state->nr_folios, sizeof(*folios_info));
if (!folios_info)
return -ENOMEM;
err = kho_preserve_vmalloc(folios_info, &folios_info_phys);
if (err)
goto err_free_info;
state->folios_info_phys = folios_info_phys;
state->folios_info = folios_info;
for (int i = 0; i < state->nr_folios; i++) {
struct folio *folio = state->folios[i];
unsigned int order = folio_order(folio);
folios_info[i] = virt_to_phys(folio_address(folio)) | order;
err = kho_preserve_folio(folio);
if (err)
goto err_unpreserve;
state->nr_folios_preserved++;
}
return 0;
err_unpreserve:
/*
* kho_test_unpreserve_data frees folio_info, bail out immediately to
* avoid double free
*/
kho_test_unpreserve_data(state);
return err;
err_free_info:
vfree(folios_info);
return err;
}
static int kho_test_prepare_fdt(struct kho_test_state *state, ssize_t fdt_size)
{
const char compatible[] = KHO_TEST_COMPAT;
unsigned int magic = KHO_TEST_MAGIC;
void *fdt = folio_address(state->fdt);
int err;
err = fdt_create(fdt, fdt_size);
err |= fdt_finish_reservemap(fdt);
err |= fdt_begin_node(fdt, "");
err |= fdt_property(fdt, "compatible", compatible, sizeof(compatible));
err |= fdt_property(fdt, "magic", &magic, sizeof(magic));
err |= fdt_begin_node(fdt, "data");
err |= fdt_property(fdt, "nr_folios", &state->nr_folios,
sizeof(state->nr_folios));
err |= fdt_property(fdt, "folios_info", &state->folios_info_phys,
sizeof(state->folios_info_phys));
err |= fdt_property(fdt, "csum", &state->csum, sizeof(state->csum));
err |= fdt_end_node(fdt);
err |= fdt_end_node(fdt);
err |= fdt_finish(fdt);
return err;
}
static int kho_test_preserve(struct kho_test_state *state)
{
ssize_t fdt_size;
int err;
fdt_size = state->nr_folios * sizeof(phys_addr_t) + PAGE_SIZE;
state->fdt = folio_alloc(GFP_KERNEL, get_order(fdt_size));
if (!state->fdt)
return -ENOMEM;
err = kho_preserve_folio(state->fdt);
if (err)
goto err_free_fdt;
err = kho_test_preserve_data(state);
if (err)
goto err_unpreserve_fdt;
err = kho_test_prepare_fdt(state, fdt_size);
if (err)
goto err_unpreserve_data;
err = kho_add_subtree(KHO_TEST_FDT, folio_address(state->fdt));
if (err)
goto err_unpreserve_data;
return 0;
err_unpreserve_data:
kho_test_unpreserve_data(state);
err_unpreserve_fdt:
kho_unpreserve_folio(state->fdt);
err_free_fdt:
folio_put(state->fdt);
return err;
}
static int kho_test_generate_data(struct kho_test_state *state)
{
size_t alloc_size = 0;
__wsum csum = 0;
while (alloc_size < max_mem) {
int order = get_random_u32() % NR_PAGE_ORDERS;
struct folio *folio;
unsigned int size;
void *addr;
/*
* Since get_order() rounds up, make sure that actual
* allocation is smaller so that we won't exceed max_mem
*/
if (alloc_size + (PAGE_SIZE << order) > max_mem) {
order = get_order(max_mem - alloc_size);
if (order)
order--;
}
size = PAGE_SIZE << order;
folio = folio_alloc(GFP_KERNEL | __GFP_NORETRY, order);
if (!folio)
goto err_free_folios;
state->folios[state->nr_folios++] = folio;
addr = folio_address(folio);
get_random_bytes(addr, size);
csum = csum_partial(addr, size, csum);
alloc_size += size;
}
state->csum = csum;
return 0;
err_free_folios:
for (int i = 0; i < state->nr_folios; i++)
folio_put(state->folios[i]);
state->nr_folios = 0;
return -ENOMEM;
}
static int kho_test_save(void)
{
struct kho_test_state *state = &kho_test_state;
struct folio **folios;
unsigned long max_nr;
int err;
max_mem = PAGE_ALIGN(max_mem);
max_nr = max_mem >> PAGE_SHIFT;
folios = kvmalloc_array(max_nr, sizeof(*state->folios), GFP_KERNEL);
if (!folios)
return -ENOMEM;
state->folios = folios;
err = kho_test_generate_data(state);
if (err)
goto err_free_folios;
err = kho_test_preserve(state);
if (err)
goto err_free_folios;
return 0;
err_free_folios:
kvfree(folios);
return err;
}
static int kho_test_restore_data(const void *fdt, int node)
{
const struct kho_vmalloc *folios_info_phys;
const unsigned int *nr_folios;
phys_addr_t *folios_info;
const __wsum *old_csum;
__wsum csum = 0;
int len;
node = fdt_path_offset(fdt, "/data");
nr_folios = fdt_getprop(fdt, node, "nr_folios", &len);
if (!nr_folios || len != sizeof(*nr_folios))
return -EINVAL;
old_csum = fdt_getprop(fdt, node, "csum", &len);
if (!old_csum || len != sizeof(*old_csum))
return -EINVAL;
folios_info_phys = fdt_getprop(fdt, node, "folios_info", &len);
if (!folios_info_phys || len != sizeof(*folios_info_phys))
return -EINVAL;
folios_info = kho_restore_vmalloc(folios_info_phys);
if (!folios_info)
return -EINVAL;
for (int i = 0; i < *nr_folios; i++) {
unsigned int order = folios_info[i] & ~PAGE_MASK;
phys_addr_t phys = folios_info[i] & PAGE_MASK;
unsigned int size = PAGE_SIZE << order;
struct folio *folio;
folio = kho_restore_folio(phys);
if (!folio)
break;
if (folio_order(folio) != order)
break;
csum = csum_partial(folio_address(folio), size, csum);
folio_put(folio);
}
vfree(folios_info);
if (csum != *old_csum)
return -EINVAL;
return 0;
}
static int kho_test_restore(phys_addr_t fdt_phys)
{
void *fdt = phys_to_virt(fdt_phys);
const unsigned int *magic;
int node, len, err;
node = fdt_path_offset(fdt, "/");
if (node < 0)
return -EINVAL;
if (fdt_node_check_compatible(fdt, node, KHO_TEST_COMPAT))
return -EINVAL;
magic = fdt_getprop(fdt, node, "magic", &len);
if (!magic || len != sizeof(*magic))
return -EINVAL;
if (*magic != KHO_TEST_MAGIC)
return -EINVAL;
err = kho_test_restore_data(fdt, node);
if (err)
return err;
return 0;
}
static int __init kho_test_init(void)
{
phys_addr_t fdt_phys;
int err;
if (!kho_is_enabled())
return 0;
err = kho_retrieve_subtree(KHO_TEST_FDT, &fdt_phys);
if (!err) {
err = kho_test_restore(fdt_phys);
if (err)
pr_err("KHO restore failed\n");
else
pr_info("KHO restore succeeded\n");
return err;
}
if (err != -ENOENT) {
pr_warn("failed to retrieve %s FDT: %d\n", KHO_TEST_FDT, err);
return err;
}
return kho_test_save();
}
module_init(kho_test_init);
static void kho_test_cleanup(void)
{
for (int i = 0; i < kho_test_state.nr_folios; i++)
folio_put(kho_test_state.folios[i]);
kvfree(kho_test_state.folios);
vfree(kho_test_state.folios_info);
folio_put(kho_test_state.fdt);
}
static void __exit kho_test_exit(void)
{
kho_remove_subtree(folio_address(kho_test_state.fdt));
kho_test_cleanup();
}
module_exit(kho_test_exit);
MODULE_AUTHOR("Mike Rapoport <rppt@kernel.org>");
MODULE_DESCRIPTION("KHO test module");
MODULE_LICENSE("GPL");