18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Copyright (C) 2016 IBM Corporation
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Authors:
68c2ecf20Sopenharmony_ci * Thiago Jung Bauermann <bauerman@linux.vnet.ibm.com>
78c2ecf20Sopenharmony_ci */
88c2ecf20Sopenharmony_ci
98c2ecf20Sopenharmony_ci#include <linux/slab.h>
108c2ecf20Sopenharmony_ci#include <linux/kexec.h>
118c2ecf20Sopenharmony_ci#include <linux/of.h>
128c2ecf20Sopenharmony_ci#include <linux/memblock.h>
138c2ecf20Sopenharmony_ci#include <linux/libfdt.h>
148c2ecf20Sopenharmony_ci
158c2ecf20Sopenharmony_cistatic int get_addr_size_cells(int *addr_cells, int *size_cells)
168c2ecf20Sopenharmony_ci{
178c2ecf20Sopenharmony_ci	struct device_node *root;
188c2ecf20Sopenharmony_ci
198c2ecf20Sopenharmony_ci	root = of_find_node_by_path("/");
208c2ecf20Sopenharmony_ci	if (!root)
218c2ecf20Sopenharmony_ci		return -EINVAL;
228c2ecf20Sopenharmony_ci
238c2ecf20Sopenharmony_ci	*addr_cells = of_n_addr_cells(root);
248c2ecf20Sopenharmony_ci	*size_cells = of_n_size_cells(root);
258c2ecf20Sopenharmony_ci
268c2ecf20Sopenharmony_ci	of_node_put(root);
278c2ecf20Sopenharmony_ci
288c2ecf20Sopenharmony_ci	return 0;
298c2ecf20Sopenharmony_ci}
308c2ecf20Sopenharmony_ci
318c2ecf20Sopenharmony_cistatic int do_get_kexec_buffer(const void *prop, int len, unsigned long *addr,
328c2ecf20Sopenharmony_ci			       size_t *size)
338c2ecf20Sopenharmony_ci{
348c2ecf20Sopenharmony_ci	int ret, addr_cells, size_cells;
358c2ecf20Sopenharmony_ci
368c2ecf20Sopenharmony_ci	ret = get_addr_size_cells(&addr_cells, &size_cells);
378c2ecf20Sopenharmony_ci	if (ret)
388c2ecf20Sopenharmony_ci		return ret;
398c2ecf20Sopenharmony_ci
408c2ecf20Sopenharmony_ci	if (len < 4 * (addr_cells + size_cells))
418c2ecf20Sopenharmony_ci		return -ENOENT;
428c2ecf20Sopenharmony_ci
438c2ecf20Sopenharmony_ci	*addr = of_read_number(prop, addr_cells);
448c2ecf20Sopenharmony_ci	*size = of_read_number(prop + 4 * addr_cells, size_cells);
458c2ecf20Sopenharmony_ci
468c2ecf20Sopenharmony_ci	return 0;
478c2ecf20Sopenharmony_ci}
488c2ecf20Sopenharmony_ci
498c2ecf20Sopenharmony_ci/**
508c2ecf20Sopenharmony_ci * ima_get_kexec_buffer - get IMA buffer from the previous kernel
518c2ecf20Sopenharmony_ci * @addr:	On successful return, set to point to the buffer contents.
528c2ecf20Sopenharmony_ci * @size:	On successful return, set to the buffer size.
538c2ecf20Sopenharmony_ci *
548c2ecf20Sopenharmony_ci * Return: 0 on success, negative errno on error.
558c2ecf20Sopenharmony_ci */
568c2ecf20Sopenharmony_ciint ima_get_kexec_buffer(void **addr, size_t *size)
578c2ecf20Sopenharmony_ci{
588c2ecf20Sopenharmony_ci	int ret, len;
598c2ecf20Sopenharmony_ci	unsigned long tmp_addr;
608c2ecf20Sopenharmony_ci	size_t tmp_size;
618c2ecf20Sopenharmony_ci	const void *prop;
628c2ecf20Sopenharmony_ci
638c2ecf20Sopenharmony_ci	prop = of_get_property(of_chosen, "linux,ima-kexec-buffer", &len);
648c2ecf20Sopenharmony_ci	if (!prop)
658c2ecf20Sopenharmony_ci		return -ENOENT;
668c2ecf20Sopenharmony_ci
678c2ecf20Sopenharmony_ci	ret = do_get_kexec_buffer(prop, len, &tmp_addr, &tmp_size);
688c2ecf20Sopenharmony_ci	if (ret)
698c2ecf20Sopenharmony_ci		return ret;
708c2ecf20Sopenharmony_ci
718c2ecf20Sopenharmony_ci	*addr = __va(tmp_addr);
728c2ecf20Sopenharmony_ci	*size = tmp_size;
738c2ecf20Sopenharmony_ci
748c2ecf20Sopenharmony_ci	return 0;
758c2ecf20Sopenharmony_ci}
768c2ecf20Sopenharmony_ci
778c2ecf20Sopenharmony_ci/**
788c2ecf20Sopenharmony_ci * ima_free_kexec_buffer - free memory used by the IMA buffer
798c2ecf20Sopenharmony_ci */
808c2ecf20Sopenharmony_ciint ima_free_kexec_buffer(void)
818c2ecf20Sopenharmony_ci{
828c2ecf20Sopenharmony_ci	int ret;
838c2ecf20Sopenharmony_ci	unsigned long addr;
848c2ecf20Sopenharmony_ci	size_t size;
858c2ecf20Sopenharmony_ci	struct property *prop;
868c2ecf20Sopenharmony_ci
878c2ecf20Sopenharmony_ci	prop = of_find_property(of_chosen, "linux,ima-kexec-buffer", NULL);
888c2ecf20Sopenharmony_ci	if (!prop)
898c2ecf20Sopenharmony_ci		return -ENOENT;
908c2ecf20Sopenharmony_ci
918c2ecf20Sopenharmony_ci	ret = do_get_kexec_buffer(prop->value, prop->length, &addr, &size);
928c2ecf20Sopenharmony_ci	if (ret)
938c2ecf20Sopenharmony_ci		return ret;
948c2ecf20Sopenharmony_ci
958c2ecf20Sopenharmony_ci	ret = of_remove_property(of_chosen, prop);
968c2ecf20Sopenharmony_ci	if (ret)
978c2ecf20Sopenharmony_ci		return ret;
988c2ecf20Sopenharmony_ci
998c2ecf20Sopenharmony_ci	return memblock_free(addr, size);
1008c2ecf20Sopenharmony_ci
1018c2ecf20Sopenharmony_ci}
1028c2ecf20Sopenharmony_ci
1038c2ecf20Sopenharmony_ci/**
1048c2ecf20Sopenharmony_ci * remove_ima_buffer - remove the IMA buffer property and reservation from @fdt
1058c2ecf20Sopenharmony_ci *
1068c2ecf20Sopenharmony_ci * The IMA measurement buffer is of no use to a subsequent kernel, so we always
1078c2ecf20Sopenharmony_ci * remove it from the device tree.
1088c2ecf20Sopenharmony_ci */
1098c2ecf20Sopenharmony_civoid remove_ima_buffer(void *fdt, int chosen_node)
1108c2ecf20Sopenharmony_ci{
1118c2ecf20Sopenharmony_ci	int ret, len;
1128c2ecf20Sopenharmony_ci	unsigned long addr;
1138c2ecf20Sopenharmony_ci	size_t size;
1148c2ecf20Sopenharmony_ci	const void *prop;
1158c2ecf20Sopenharmony_ci
1168c2ecf20Sopenharmony_ci	prop = fdt_getprop(fdt, chosen_node, "linux,ima-kexec-buffer", &len);
1178c2ecf20Sopenharmony_ci	if (!prop)
1188c2ecf20Sopenharmony_ci		return;
1198c2ecf20Sopenharmony_ci
1208c2ecf20Sopenharmony_ci	ret = do_get_kexec_buffer(prop, len, &addr, &size);
1218c2ecf20Sopenharmony_ci	fdt_delprop(fdt, chosen_node, "linux,ima-kexec-buffer");
1228c2ecf20Sopenharmony_ci	if (ret)
1238c2ecf20Sopenharmony_ci		return;
1248c2ecf20Sopenharmony_ci
1258c2ecf20Sopenharmony_ci	ret = delete_fdt_mem_rsv(fdt, addr, size);
1268c2ecf20Sopenharmony_ci	if (!ret)
1278c2ecf20Sopenharmony_ci		pr_debug("Removed old IMA buffer reservation.\n");
1288c2ecf20Sopenharmony_ci}
1298c2ecf20Sopenharmony_ci
1308c2ecf20Sopenharmony_ci#ifdef CONFIG_IMA_KEXEC
1318c2ecf20Sopenharmony_ci/**
1328c2ecf20Sopenharmony_ci * arch_ima_add_kexec_buffer - do arch-specific steps to add the IMA buffer
1338c2ecf20Sopenharmony_ci *
1348c2ecf20Sopenharmony_ci * Architectures should use this function to pass on the IMA buffer
1358c2ecf20Sopenharmony_ci * information to the next kernel.
1368c2ecf20Sopenharmony_ci *
1378c2ecf20Sopenharmony_ci * Return: 0 on success, negative errno on error.
1388c2ecf20Sopenharmony_ci */
1398c2ecf20Sopenharmony_ciint arch_ima_add_kexec_buffer(struct kimage *image, unsigned long load_addr,
1408c2ecf20Sopenharmony_ci			      size_t size)
1418c2ecf20Sopenharmony_ci{
1428c2ecf20Sopenharmony_ci	image->arch.ima_buffer_addr = load_addr;
1438c2ecf20Sopenharmony_ci	image->arch.ima_buffer_size = size;
1448c2ecf20Sopenharmony_ci
1458c2ecf20Sopenharmony_ci	return 0;
1468c2ecf20Sopenharmony_ci}
1478c2ecf20Sopenharmony_ci
1488c2ecf20Sopenharmony_cistatic int write_number(void *p, u64 value, int cells)
1498c2ecf20Sopenharmony_ci{
1508c2ecf20Sopenharmony_ci	if (cells == 1) {
1518c2ecf20Sopenharmony_ci		u32 tmp;
1528c2ecf20Sopenharmony_ci
1538c2ecf20Sopenharmony_ci		if (value > U32_MAX)
1548c2ecf20Sopenharmony_ci			return -EINVAL;
1558c2ecf20Sopenharmony_ci
1568c2ecf20Sopenharmony_ci		tmp = cpu_to_be32(value);
1578c2ecf20Sopenharmony_ci		memcpy(p, &tmp, sizeof(tmp));
1588c2ecf20Sopenharmony_ci	} else if (cells == 2) {
1598c2ecf20Sopenharmony_ci		u64 tmp;
1608c2ecf20Sopenharmony_ci
1618c2ecf20Sopenharmony_ci		tmp = cpu_to_be64(value);
1628c2ecf20Sopenharmony_ci		memcpy(p, &tmp, sizeof(tmp));
1638c2ecf20Sopenharmony_ci	} else
1648c2ecf20Sopenharmony_ci		return -EINVAL;
1658c2ecf20Sopenharmony_ci
1668c2ecf20Sopenharmony_ci	return 0;
1678c2ecf20Sopenharmony_ci}
1688c2ecf20Sopenharmony_ci
1698c2ecf20Sopenharmony_ci/**
1708c2ecf20Sopenharmony_ci * setup_ima_buffer - add IMA buffer information to the fdt
1718c2ecf20Sopenharmony_ci * @image:		kexec image being loaded.
1728c2ecf20Sopenharmony_ci * @fdt:		Flattened device tree for the next kernel.
1738c2ecf20Sopenharmony_ci * @chosen_node:	Offset to the chosen node.
1748c2ecf20Sopenharmony_ci *
1758c2ecf20Sopenharmony_ci * Return: 0 on success, or negative errno on error.
1768c2ecf20Sopenharmony_ci */
1778c2ecf20Sopenharmony_ciint setup_ima_buffer(const struct kimage *image, void *fdt, int chosen_node)
1788c2ecf20Sopenharmony_ci{
1798c2ecf20Sopenharmony_ci	int ret, addr_cells, size_cells, entry_size;
1808c2ecf20Sopenharmony_ci	u8 value[16];
1818c2ecf20Sopenharmony_ci
1828c2ecf20Sopenharmony_ci	remove_ima_buffer(fdt, chosen_node);
1838c2ecf20Sopenharmony_ci	if (!image->arch.ima_buffer_size)
1848c2ecf20Sopenharmony_ci		return 0;
1858c2ecf20Sopenharmony_ci
1868c2ecf20Sopenharmony_ci	ret = get_addr_size_cells(&addr_cells, &size_cells);
1878c2ecf20Sopenharmony_ci	if (ret)
1888c2ecf20Sopenharmony_ci		return ret;
1898c2ecf20Sopenharmony_ci
1908c2ecf20Sopenharmony_ci	entry_size = 4 * (addr_cells + size_cells);
1918c2ecf20Sopenharmony_ci
1928c2ecf20Sopenharmony_ci	if (entry_size > sizeof(value))
1938c2ecf20Sopenharmony_ci		return -EINVAL;
1948c2ecf20Sopenharmony_ci
1958c2ecf20Sopenharmony_ci	ret = write_number(value, image->arch.ima_buffer_addr, addr_cells);
1968c2ecf20Sopenharmony_ci	if (ret)
1978c2ecf20Sopenharmony_ci		return ret;
1988c2ecf20Sopenharmony_ci
1998c2ecf20Sopenharmony_ci	ret = write_number(value + 4 * addr_cells, image->arch.ima_buffer_size,
2008c2ecf20Sopenharmony_ci			   size_cells);
2018c2ecf20Sopenharmony_ci	if (ret)
2028c2ecf20Sopenharmony_ci		return ret;
2038c2ecf20Sopenharmony_ci
2048c2ecf20Sopenharmony_ci	ret = fdt_setprop(fdt, chosen_node, "linux,ima-kexec-buffer", value,
2058c2ecf20Sopenharmony_ci			  entry_size);
2068c2ecf20Sopenharmony_ci	if (ret < 0)
2078c2ecf20Sopenharmony_ci		return -EINVAL;
2088c2ecf20Sopenharmony_ci
2098c2ecf20Sopenharmony_ci	ret = fdt_add_mem_rsv(fdt, image->arch.ima_buffer_addr,
2108c2ecf20Sopenharmony_ci			      image->arch.ima_buffer_size);
2118c2ecf20Sopenharmony_ci	if (ret)
2128c2ecf20Sopenharmony_ci		return -EINVAL;
2138c2ecf20Sopenharmony_ci
2148c2ecf20Sopenharmony_ci	pr_debug("IMA buffer at 0x%llx, size = 0x%zx\n",
2158c2ecf20Sopenharmony_ci		 image->arch.ima_buffer_addr, image->arch.ima_buffer_size);
2168c2ecf20Sopenharmony_ci
2178c2ecf20Sopenharmony_ci	return 0;
2188c2ecf20Sopenharmony_ci}
2198c2ecf20Sopenharmony_ci#endif /* CONFIG_IMA_KEXEC */
220