365 lines · 8.1 KB · C
RawDownload
// 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 <linux/kho/abi/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),			      fdt_totalsize(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_objs(*state->folios, max_nr);	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, NULL);	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){	/* unpreserve and free the data stored in folios */	kho_test_unpreserve_data(&kho_test_state);	for (int i = 0; i < kho_test_state.nr_folios; i++)		folio_put(kho_test_state.folios[i]); 	kvfree(kho_test_state.folios); 	/* Unpreserve and release the FDT folio */	kho_unpreserve_folio(kho_test_state.fdt);	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");