18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/**
38c2ecf20Sopenharmony_ci * imr.c -- Intel Isolated Memory Region driver
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright(c) 2013 Intel Corporation.
68c2ecf20Sopenharmony_ci * Copyright(c) 2015 Bryan O'Donoghue <pure.logic@nexus-software.ie>
78c2ecf20Sopenharmony_ci *
88c2ecf20Sopenharmony_ci * IMR registers define an isolated region of memory that can
98c2ecf20Sopenharmony_ci * be masked to prohibit certain system agents from accessing memory.
108c2ecf20Sopenharmony_ci * When a device behind a masked port performs an access - snooped or
118c2ecf20Sopenharmony_ci * not, an IMR may optionally prevent that transaction from changing
128c2ecf20Sopenharmony_ci * the state of memory or from getting correct data in response to the
138c2ecf20Sopenharmony_ci * operation.
148c2ecf20Sopenharmony_ci *
158c2ecf20Sopenharmony_ci * Write data will be dropped and reads will return 0xFFFFFFFF, the
168c2ecf20Sopenharmony_ci * system will reset and system BIOS will print out an error message to
178c2ecf20Sopenharmony_ci * inform the user that an IMR has been violated.
188c2ecf20Sopenharmony_ci *
198c2ecf20Sopenharmony_ci * This code is based on the Linux MTRR code and reference code from
208c2ecf20Sopenharmony_ci * Intel's Quark BSP EFI, Linux and grub code.
218c2ecf20Sopenharmony_ci *
228c2ecf20Sopenharmony_ci * See quark-x1000-datasheet.pdf for register definitions.
238c2ecf20Sopenharmony_ci * http://www.intel.com/content/dam/www/public/us/en/documents/datasheets/quark-x1000-datasheet.pdf
248c2ecf20Sopenharmony_ci */
258c2ecf20Sopenharmony_ci
268c2ecf20Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
278c2ecf20Sopenharmony_ci
288c2ecf20Sopenharmony_ci#include <asm-generic/sections.h>
298c2ecf20Sopenharmony_ci#include <asm/cpu_device_id.h>
308c2ecf20Sopenharmony_ci#include <asm/imr.h>
318c2ecf20Sopenharmony_ci#include <asm/iosf_mbi.h>
328c2ecf20Sopenharmony_ci#include <asm/io.h>
338c2ecf20Sopenharmony_ci
348c2ecf20Sopenharmony_ci#include <linux/debugfs.h>
358c2ecf20Sopenharmony_ci#include <linux/init.h>
368c2ecf20Sopenharmony_ci#include <linux/mm.h>
378c2ecf20Sopenharmony_ci#include <linux/types.h>
388c2ecf20Sopenharmony_ci
398c2ecf20Sopenharmony_cistruct imr_device {
408c2ecf20Sopenharmony_ci	bool		init;
418c2ecf20Sopenharmony_ci	struct mutex	lock;
428c2ecf20Sopenharmony_ci	int		max_imr;
438c2ecf20Sopenharmony_ci	int		reg_base;
448c2ecf20Sopenharmony_ci};
458c2ecf20Sopenharmony_ci
468c2ecf20Sopenharmony_cistatic struct imr_device imr_dev;
478c2ecf20Sopenharmony_ci
488c2ecf20Sopenharmony_ci/*
498c2ecf20Sopenharmony_ci * IMR read/write mask control registers.
508c2ecf20Sopenharmony_ci * See quark-x1000-datasheet.pdf sections 12.7.4.5 and 12.7.4.6 for
518c2ecf20Sopenharmony_ci * bit definitions.
528c2ecf20Sopenharmony_ci *
538c2ecf20Sopenharmony_ci * addr_hi
548c2ecf20Sopenharmony_ci * 31		Lock bit
558c2ecf20Sopenharmony_ci * 30:24	Reserved
568c2ecf20Sopenharmony_ci * 23:2		1 KiB aligned lo address
578c2ecf20Sopenharmony_ci * 1:0		Reserved
588c2ecf20Sopenharmony_ci *
598c2ecf20Sopenharmony_ci * addr_hi
608c2ecf20Sopenharmony_ci * 31:24	Reserved
618c2ecf20Sopenharmony_ci * 23:2		1 KiB aligned hi address
628c2ecf20Sopenharmony_ci * 1:0		Reserved
638c2ecf20Sopenharmony_ci */
648c2ecf20Sopenharmony_ci#define IMR_LOCK	BIT(31)
658c2ecf20Sopenharmony_ci
668c2ecf20Sopenharmony_cistruct imr_regs {
678c2ecf20Sopenharmony_ci	u32 addr_lo;
688c2ecf20Sopenharmony_ci	u32 addr_hi;
698c2ecf20Sopenharmony_ci	u32 rmask;
708c2ecf20Sopenharmony_ci	u32 wmask;
718c2ecf20Sopenharmony_ci};
728c2ecf20Sopenharmony_ci
738c2ecf20Sopenharmony_ci#define IMR_NUM_REGS	(sizeof(struct imr_regs)/sizeof(u32))
748c2ecf20Sopenharmony_ci#define IMR_SHIFT	8
758c2ecf20Sopenharmony_ci#define imr_to_phys(x)	((x) << IMR_SHIFT)
768c2ecf20Sopenharmony_ci#define phys_to_imr(x)	((x) >> IMR_SHIFT)
778c2ecf20Sopenharmony_ci
788c2ecf20Sopenharmony_ci/**
798c2ecf20Sopenharmony_ci * imr_is_enabled - true if an IMR is enabled false otherwise.
808c2ecf20Sopenharmony_ci *
818c2ecf20Sopenharmony_ci * Determines if an IMR is enabled based on address range and read/write
828c2ecf20Sopenharmony_ci * mask. An IMR set with an address range set to zero and a read/write
838c2ecf20Sopenharmony_ci * access mask set to all is considered to be disabled. An IMR in any
848c2ecf20Sopenharmony_ci * other state - for example set to zero but without read/write access
858c2ecf20Sopenharmony_ci * all is considered to be enabled. This definition of disabled is how
868c2ecf20Sopenharmony_ci * firmware switches off an IMR and is maintained in kernel for
878c2ecf20Sopenharmony_ci * consistency.
888c2ecf20Sopenharmony_ci *
898c2ecf20Sopenharmony_ci * @imr:	pointer to IMR descriptor.
908c2ecf20Sopenharmony_ci * @return:	true if IMR enabled false if disabled.
918c2ecf20Sopenharmony_ci */
928c2ecf20Sopenharmony_cistatic inline int imr_is_enabled(struct imr_regs *imr)
938c2ecf20Sopenharmony_ci{
948c2ecf20Sopenharmony_ci	return !(imr->rmask == IMR_READ_ACCESS_ALL &&
958c2ecf20Sopenharmony_ci		 imr->wmask == IMR_WRITE_ACCESS_ALL &&
968c2ecf20Sopenharmony_ci		 imr_to_phys(imr->addr_lo) == 0 &&
978c2ecf20Sopenharmony_ci		 imr_to_phys(imr->addr_hi) == 0);
988c2ecf20Sopenharmony_ci}
998c2ecf20Sopenharmony_ci
1008c2ecf20Sopenharmony_ci/**
1018c2ecf20Sopenharmony_ci * imr_read - read an IMR at a given index.
1028c2ecf20Sopenharmony_ci *
1038c2ecf20Sopenharmony_ci * Requires caller to hold imr mutex.
1048c2ecf20Sopenharmony_ci *
1058c2ecf20Sopenharmony_ci * @idev:	pointer to imr_device structure.
1068c2ecf20Sopenharmony_ci * @imr_id:	IMR entry to read.
1078c2ecf20Sopenharmony_ci * @imr:	IMR structure representing address and access masks.
1088c2ecf20Sopenharmony_ci * @return:	0 on success or error code passed from mbi_iosf on failure.
1098c2ecf20Sopenharmony_ci */
1108c2ecf20Sopenharmony_cistatic int imr_read(struct imr_device *idev, u32 imr_id, struct imr_regs *imr)
1118c2ecf20Sopenharmony_ci{
1128c2ecf20Sopenharmony_ci	u32 reg = imr_id * IMR_NUM_REGS + idev->reg_base;
1138c2ecf20Sopenharmony_ci	int ret;
1148c2ecf20Sopenharmony_ci
1158c2ecf20Sopenharmony_ci	ret = iosf_mbi_read(QRK_MBI_UNIT_MM, MBI_REG_READ, reg++, &imr->addr_lo);
1168c2ecf20Sopenharmony_ci	if (ret)
1178c2ecf20Sopenharmony_ci		return ret;
1188c2ecf20Sopenharmony_ci
1198c2ecf20Sopenharmony_ci	ret = iosf_mbi_read(QRK_MBI_UNIT_MM, MBI_REG_READ, reg++, &imr->addr_hi);
1208c2ecf20Sopenharmony_ci	if (ret)
1218c2ecf20Sopenharmony_ci		return ret;
1228c2ecf20Sopenharmony_ci
1238c2ecf20Sopenharmony_ci	ret = iosf_mbi_read(QRK_MBI_UNIT_MM, MBI_REG_READ, reg++, &imr->rmask);
1248c2ecf20Sopenharmony_ci	if (ret)
1258c2ecf20Sopenharmony_ci		return ret;
1268c2ecf20Sopenharmony_ci
1278c2ecf20Sopenharmony_ci	return iosf_mbi_read(QRK_MBI_UNIT_MM, MBI_REG_READ, reg++, &imr->wmask);
1288c2ecf20Sopenharmony_ci}
1298c2ecf20Sopenharmony_ci
1308c2ecf20Sopenharmony_ci/**
1318c2ecf20Sopenharmony_ci * imr_write - write an IMR at a given index.
1328c2ecf20Sopenharmony_ci *
1338c2ecf20Sopenharmony_ci * Requires caller to hold imr mutex.
1348c2ecf20Sopenharmony_ci * Note lock bits need to be written independently of address bits.
1358c2ecf20Sopenharmony_ci *
1368c2ecf20Sopenharmony_ci * @idev:	pointer to imr_device structure.
1378c2ecf20Sopenharmony_ci * @imr_id:	IMR entry to write.
1388c2ecf20Sopenharmony_ci * @imr:	IMR structure representing address and access masks.
1398c2ecf20Sopenharmony_ci * @return:	0 on success or error code passed from mbi_iosf on failure.
1408c2ecf20Sopenharmony_ci */
1418c2ecf20Sopenharmony_cistatic int imr_write(struct imr_device *idev, u32 imr_id, struct imr_regs *imr)
1428c2ecf20Sopenharmony_ci{
1438c2ecf20Sopenharmony_ci	unsigned long flags;
1448c2ecf20Sopenharmony_ci	u32 reg = imr_id * IMR_NUM_REGS + idev->reg_base;
1458c2ecf20Sopenharmony_ci	int ret;
1468c2ecf20Sopenharmony_ci
1478c2ecf20Sopenharmony_ci	local_irq_save(flags);
1488c2ecf20Sopenharmony_ci
1498c2ecf20Sopenharmony_ci	ret = iosf_mbi_write(QRK_MBI_UNIT_MM, MBI_REG_WRITE, reg++, imr->addr_lo);
1508c2ecf20Sopenharmony_ci	if (ret)
1518c2ecf20Sopenharmony_ci		goto failed;
1528c2ecf20Sopenharmony_ci
1538c2ecf20Sopenharmony_ci	ret = iosf_mbi_write(QRK_MBI_UNIT_MM, MBI_REG_WRITE, reg++, imr->addr_hi);
1548c2ecf20Sopenharmony_ci	if (ret)
1558c2ecf20Sopenharmony_ci		goto failed;
1568c2ecf20Sopenharmony_ci
1578c2ecf20Sopenharmony_ci	ret = iosf_mbi_write(QRK_MBI_UNIT_MM, MBI_REG_WRITE, reg++, imr->rmask);
1588c2ecf20Sopenharmony_ci	if (ret)
1598c2ecf20Sopenharmony_ci		goto failed;
1608c2ecf20Sopenharmony_ci
1618c2ecf20Sopenharmony_ci	ret = iosf_mbi_write(QRK_MBI_UNIT_MM, MBI_REG_WRITE, reg++, imr->wmask);
1628c2ecf20Sopenharmony_ci	if (ret)
1638c2ecf20Sopenharmony_ci		goto failed;
1648c2ecf20Sopenharmony_ci
1658c2ecf20Sopenharmony_ci	local_irq_restore(flags);
1668c2ecf20Sopenharmony_ci	return 0;
1678c2ecf20Sopenharmony_cifailed:
1688c2ecf20Sopenharmony_ci	/*
1698c2ecf20Sopenharmony_ci	 * If writing to the IOSF failed then we're in an unknown state,
1708c2ecf20Sopenharmony_ci	 * likely a very bad state. An IMR in an invalid state will almost
1718c2ecf20Sopenharmony_ci	 * certainly lead to a memory access violation.
1728c2ecf20Sopenharmony_ci	 */
1738c2ecf20Sopenharmony_ci	local_irq_restore(flags);
1748c2ecf20Sopenharmony_ci	WARN(ret, "IOSF-MBI write fail range 0x%08x-0x%08x unreliable\n",
1758c2ecf20Sopenharmony_ci	     imr_to_phys(imr->addr_lo), imr_to_phys(imr->addr_hi) + IMR_MASK);
1768c2ecf20Sopenharmony_ci
1778c2ecf20Sopenharmony_ci	return ret;
1788c2ecf20Sopenharmony_ci}
1798c2ecf20Sopenharmony_ci
1808c2ecf20Sopenharmony_ci/**
1818c2ecf20Sopenharmony_ci * imr_dbgfs_state_show - print state of IMR registers.
1828c2ecf20Sopenharmony_ci *
1838c2ecf20Sopenharmony_ci * @s:		pointer to seq_file for output.
1848c2ecf20Sopenharmony_ci * @unused:	unused parameter.
1858c2ecf20Sopenharmony_ci * @return:	0 on success or error code passed from mbi_iosf on failure.
1868c2ecf20Sopenharmony_ci */
1878c2ecf20Sopenharmony_cistatic int imr_dbgfs_state_show(struct seq_file *s, void *unused)
1888c2ecf20Sopenharmony_ci{
1898c2ecf20Sopenharmony_ci	phys_addr_t base;
1908c2ecf20Sopenharmony_ci	phys_addr_t end;
1918c2ecf20Sopenharmony_ci	int i;
1928c2ecf20Sopenharmony_ci	struct imr_device *idev = s->private;
1938c2ecf20Sopenharmony_ci	struct imr_regs imr;
1948c2ecf20Sopenharmony_ci	size_t size;
1958c2ecf20Sopenharmony_ci	int ret = -ENODEV;
1968c2ecf20Sopenharmony_ci
1978c2ecf20Sopenharmony_ci	mutex_lock(&idev->lock);
1988c2ecf20Sopenharmony_ci
1998c2ecf20Sopenharmony_ci	for (i = 0; i < idev->max_imr; i++) {
2008c2ecf20Sopenharmony_ci
2018c2ecf20Sopenharmony_ci		ret = imr_read(idev, i, &imr);
2028c2ecf20Sopenharmony_ci		if (ret)
2038c2ecf20Sopenharmony_ci			break;
2048c2ecf20Sopenharmony_ci
2058c2ecf20Sopenharmony_ci		/*
2068c2ecf20Sopenharmony_ci		 * Remember to add IMR_ALIGN bytes to size to indicate the
2078c2ecf20Sopenharmony_ci		 * inherent IMR_ALIGN size bytes contained in the masked away
2088c2ecf20Sopenharmony_ci		 * lower ten bits.
2098c2ecf20Sopenharmony_ci		 */
2108c2ecf20Sopenharmony_ci		if (imr_is_enabled(&imr)) {
2118c2ecf20Sopenharmony_ci			base = imr_to_phys(imr.addr_lo);
2128c2ecf20Sopenharmony_ci			end = imr_to_phys(imr.addr_hi) + IMR_MASK;
2138c2ecf20Sopenharmony_ci			size = end - base + 1;
2148c2ecf20Sopenharmony_ci		} else {
2158c2ecf20Sopenharmony_ci			base = 0;
2168c2ecf20Sopenharmony_ci			end = 0;
2178c2ecf20Sopenharmony_ci			size = 0;
2188c2ecf20Sopenharmony_ci		}
2198c2ecf20Sopenharmony_ci		seq_printf(s, "imr%02i: base=%pa, end=%pa, size=0x%08zx "
2208c2ecf20Sopenharmony_ci			   "rmask=0x%08x, wmask=0x%08x, %s, %s\n", i,
2218c2ecf20Sopenharmony_ci			   &base, &end, size, imr.rmask, imr.wmask,
2228c2ecf20Sopenharmony_ci			   imr_is_enabled(&imr) ? "enabled " : "disabled",
2238c2ecf20Sopenharmony_ci			   imr.addr_lo & IMR_LOCK ? "locked" : "unlocked");
2248c2ecf20Sopenharmony_ci	}
2258c2ecf20Sopenharmony_ci
2268c2ecf20Sopenharmony_ci	mutex_unlock(&idev->lock);
2278c2ecf20Sopenharmony_ci	return ret;
2288c2ecf20Sopenharmony_ci}
2298c2ecf20Sopenharmony_ciDEFINE_SHOW_ATTRIBUTE(imr_dbgfs_state);
2308c2ecf20Sopenharmony_ci
2318c2ecf20Sopenharmony_ci/**
2328c2ecf20Sopenharmony_ci * imr_debugfs_register - register debugfs hooks.
2338c2ecf20Sopenharmony_ci *
2348c2ecf20Sopenharmony_ci * @idev:	pointer to imr_device structure.
2358c2ecf20Sopenharmony_ci */
2368c2ecf20Sopenharmony_cistatic void imr_debugfs_register(struct imr_device *idev)
2378c2ecf20Sopenharmony_ci{
2388c2ecf20Sopenharmony_ci	debugfs_create_file("imr_state", 0444, NULL, idev,
2398c2ecf20Sopenharmony_ci			    &imr_dbgfs_state_fops);
2408c2ecf20Sopenharmony_ci}
2418c2ecf20Sopenharmony_ci
2428c2ecf20Sopenharmony_ci/**
2438c2ecf20Sopenharmony_ci * imr_check_params - check passed address range IMR alignment and non-zero size
2448c2ecf20Sopenharmony_ci *
2458c2ecf20Sopenharmony_ci * @base:	base address of intended IMR.
2468c2ecf20Sopenharmony_ci * @size:	size of intended IMR.
2478c2ecf20Sopenharmony_ci * @return:	zero on valid range -EINVAL on unaligned base/size.
2488c2ecf20Sopenharmony_ci */
2498c2ecf20Sopenharmony_cistatic int imr_check_params(phys_addr_t base, size_t size)
2508c2ecf20Sopenharmony_ci{
2518c2ecf20Sopenharmony_ci	if ((base & IMR_MASK) || (size & IMR_MASK)) {
2528c2ecf20Sopenharmony_ci		pr_err("base %pa size 0x%08zx must align to 1KiB\n",
2538c2ecf20Sopenharmony_ci			&base, size);
2548c2ecf20Sopenharmony_ci		return -EINVAL;
2558c2ecf20Sopenharmony_ci	}
2568c2ecf20Sopenharmony_ci	if (size == 0)
2578c2ecf20Sopenharmony_ci		return -EINVAL;
2588c2ecf20Sopenharmony_ci
2598c2ecf20Sopenharmony_ci	return 0;
2608c2ecf20Sopenharmony_ci}
2618c2ecf20Sopenharmony_ci
2628c2ecf20Sopenharmony_ci/**
2638c2ecf20Sopenharmony_ci * imr_raw_size - account for the IMR_ALIGN bytes that addr_hi appends.
2648c2ecf20Sopenharmony_ci *
2658c2ecf20Sopenharmony_ci * IMR addr_hi has a built in offset of plus IMR_ALIGN (0x400) bytes from the
2668c2ecf20Sopenharmony_ci * value in the register. We need to subtract IMR_ALIGN bytes from input sizes
2678c2ecf20Sopenharmony_ci * as a result.
2688c2ecf20Sopenharmony_ci *
2698c2ecf20Sopenharmony_ci * @size:	input size bytes.
2708c2ecf20Sopenharmony_ci * @return:	reduced size.
2718c2ecf20Sopenharmony_ci */
2728c2ecf20Sopenharmony_cistatic inline size_t imr_raw_size(size_t size)
2738c2ecf20Sopenharmony_ci{
2748c2ecf20Sopenharmony_ci	return size - IMR_ALIGN;
2758c2ecf20Sopenharmony_ci}
2768c2ecf20Sopenharmony_ci
2778c2ecf20Sopenharmony_ci/**
2788c2ecf20Sopenharmony_ci * imr_address_overlap - detects an address overlap.
2798c2ecf20Sopenharmony_ci *
2808c2ecf20Sopenharmony_ci * @addr:	address to check against an existing IMR.
2818c2ecf20Sopenharmony_ci * @imr:	imr being checked.
2828c2ecf20Sopenharmony_ci * @return:	true for overlap false for no overlap.
2838c2ecf20Sopenharmony_ci */
2848c2ecf20Sopenharmony_cistatic inline int imr_address_overlap(phys_addr_t addr, struct imr_regs *imr)
2858c2ecf20Sopenharmony_ci{
2868c2ecf20Sopenharmony_ci	return addr >= imr_to_phys(imr->addr_lo) && addr <= imr_to_phys(imr->addr_hi);
2878c2ecf20Sopenharmony_ci}
2888c2ecf20Sopenharmony_ci
2898c2ecf20Sopenharmony_ci/**
2908c2ecf20Sopenharmony_ci * imr_add_range - add an Isolated Memory Region.
2918c2ecf20Sopenharmony_ci *
2928c2ecf20Sopenharmony_ci * @base:	physical base address of region aligned to 1KiB.
2938c2ecf20Sopenharmony_ci * @size:	physical size of region in bytes must be aligned to 1KiB.
2948c2ecf20Sopenharmony_ci * @read_mask:	read access mask.
2958c2ecf20Sopenharmony_ci * @write_mask:	write access mask.
2968c2ecf20Sopenharmony_ci * @return:	zero on success or negative value indicating error.
2978c2ecf20Sopenharmony_ci */
2988c2ecf20Sopenharmony_ciint imr_add_range(phys_addr_t base, size_t size,
2998c2ecf20Sopenharmony_ci		  unsigned int rmask, unsigned int wmask)
3008c2ecf20Sopenharmony_ci{
3018c2ecf20Sopenharmony_ci	phys_addr_t end;
3028c2ecf20Sopenharmony_ci	unsigned int i;
3038c2ecf20Sopenharmony_ci	struct imr_device *idev = &imr_dev;
3048c2ecf20Sopenharmony_ci	struct imr_regs imr;
3058c2ecf20Sopenharmony_ci	size_t raw_size;
3068c2ecf20Sopenharmony_ci	int reg;
3078c2ecf20Sopenharmony_ci	int ret;
3088c2ecf20Sopenharmony_ci
3098c2ecf20Sopenharmony_ci	if (WARN_ONCE(idev->init == false, "driver not initialized"))
3108c2ecf20Sopenharmony_ci		return -ENODEV;
3118c2ecf20Sopenharmony_ci
3128c2ecf20Sopenharmony_ci	ret = imr_check_params(base, size);
3138c2ecf20Sopenharmony_ci	if (ret)
3148c2ecf20Sopenharmony_ci		return ret;
3158c2ecf20Sopenharmony_ci
3168c2ecf20Sopenharmony_ci	/* Tweak the size value. */
3178c2ecf20Sopenharmony_ci	raw_size = imr_raw_size(size);
3188c2ecf20Sopenharmony_ci	end = base + raw_size;
3198c2ecf20Sopenharmony_ci
3208c2ecf20Sopenharmony_ci	/*
3218c2ecf20Sopenharmony_ci	 * Check for reserved IMR value common to firmware, kernel and grub
3228c2ecf20Sopenharmony_ci	 * indicating a disabled IMR.
3238c2ecf20Sopenharmony_ci	 */
3248c2ecf20Sopenharmony_ci	imr.addr_lo = phys_to_imr(base);
3258c2ecf20Sopenharmony_ci	imr.addr_hi = phys_to_imr(end);
3268c2ecf20Sopenharmony_ci	imr.rmask = rmask;
3278c2ecf20Sopenharmony_ci	imr.wmask = wmask;
3288c2ecf20Sopenharmony_ci	if (!imr_is_enabled(&imr))
3298c2ecf20Sopenharmony_ci		return -ENOTSUPP;
3308c2ecf20Sopenharmony_ci
3318c2ecf20Sopenharmony_ci	mutex_lock(&idev->lock);
3328c2ecf20Sopenharmony_ci
3338c2ecf20Sopenharmony_ci	/*
3348c2ecf20Sopenharmony_ci	 * Find a free IMR while checking for an existing overlapping range.
3358c2ecf20Sopenharmony_ci	 * Note there's no restriction in silicon to prevent IMR overlaps.
3368c2ecf20Sopenharmony_ci	 * For the sake of simplicity and ease in defining/debugging an IMR
3378c2ecf20Sopenharmony_ci	 * memory map we exclude IMR overlaps.
3388c2ecf20Sopenharmony_ci	 */
3398c2ecf20Sopenharmony_ci	reg = -1;
3408c2ecf20Sopenharmony_ci	for (i = 0; i < idev->max_imr; i++) {
3418c2ecf20Sopenharmony_ci		ret = imr_read(idev, i, &imr);
3428c2ecf20Sopenharmony_ci		if (ret)
3438c2ecf20Sopenharmony_ci			goto failed;
3448c2ecf20Sopenharmony_ci
3458c2ecf20Sopenharmony_ci		/* Find overlap @ base or end of requested range. */
3468c2ecf20Sopenharmony_ci		ret = -EINVAL;
3478c2ecf20Sopenharmony_ci		if (imr_is_enabled(&imr)) {
3488c2ecf20Sopenharmony_ci			if (imr_address_overlap(base, &imr))
3498c2ecf20Sopenharmony_ci				goto failed;
3508c2ecf20Sopenharmony_ci			if (imr_address_overlap(end, &imr))
3518c2ecf20Sopenharmony_ci				goto failed;
3528c2ecf20Sopenharmony_ci		} else {
3538c2ecf20Sopenharmony_ci			reg = i;
3548c2ecf20Sopenharmony_ci		}
3558c2ecf20Sopenharmony_ci	}
3568c2ecf20Sopenharmony_ci
3578c2ecf20Sopenharmony_ci	/* Error out if we have no free IMR entries. */
3588c2ecf20Sopenharmony_ci	if (reg == -1) {
3598c2ecf20Sopenharmony_ci		ret = -ENOMEM;
3608c2ecf20Sopenharmony_ci		goto failed;
3618c2ecf20Sopenharmony_ci	}
3628c2ecf20Sopenharmony_ci
3638c2ecf20Sopenharmony_ci	pr_debug("add %d phys %pa-%pa size %zx mask 0x%08x wmask 0x%08x\n",
3648c2ecf20Sopenharmony_ci		 reg, &base, &end, raw_size, rmask, wmask);
3658c2ecf20Sopenharmony_ci
3668c2ecf20Sopenharmony_ci	/* Enable IMR at specified range and access mask. */
3678c2ecf20Sopenharmony_ci	imr.addr_lo = phys_to_imr(base);
3688c2ecf20Sopenharmony_ci	imr.addr_hi = phys_to_imr(end);
3698c2ecf20Sopenharmony_ci	imr.rmask = rmask;
3708c2ecf20Sopenharmony_ci	imr.wmask = wmask;
3718c2ecf20Sopenharmony_ci
3728c2ecf20Sopenharmony_ci	ret = imr_write(idev, reg, &imr);
3738c2ecf20Sopenharmony_ci	if (ret < 0) {
3748c2ecf20Sopenharmony_ci		/*
3758c2ecf20Sopenharmony_ci		 * In the highly unlikely event iosf_mbi_write failed
3768c2ecf20Sopenharmony_ci		 * attempt to rollback the IMR setup skipping the trapping
3778c2ecf20Sopenharmony_ci		 * of further IOSF write failures.
3788c2ecf20Sopenharmony_ci		 */
3798c2ecf20Sopenharmony_ci		imr.addr_lo = 0;
3808c2ecf20Sopenharmony_ci		imr.addr_hi = 0;
3818c2ecf20Sopenharmony_ci		imr.rmask = IMR_READ_ACCESS_ALL;
3828c2ecf20Sopenharmony_ci		imr.wmask = IMR_WRITE_ACCESS_ALL;
3838c2ecf20Sopenharmony_ci		imr_write(idev, reg, &imr);
3848c2ecf20Sopenharmony_ci	}
3858c2ecf20Sopenharmony_cifailed:
3868c2ecf20Sopenharmony_ci	mutex_unlock(&idev->lock);
3878c2ecf20Sopenharmony_ci	return ret;
3888c2ecf20Sopenharmony_ci}
3898c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(imr_add_range);
3908c2ecf20Sopenharmony_ci
3918c2ecf20Sopenharmony_ci/**
3928c2ecf20Sopenharmony_ci * __imr_remove_range - delete an Isolated Memory Region.
3938c2ecf20Sopenharmony_ci *
3948c2ecf20Sopenharmony_ci * This function allows you to delete an IMR by its index specified by reg or
3958c2ecf20Sopenharmony_ci * by address range specified by base and size respectively. If you specify an
3968c2ecf20Sopenharmony_ci * index on its own the base and size parameters are ignored.
3978c2ecf20Sopenharmony_ci * imr_remove_range(0, base, size); delete IMR at index 0 base/size ignored.
3988c2ecf20Sopenharmony_ci * imr_remove_range(-1, base, size); delete IMR from base to base+size.
3998c2ecf20Sopenharmony_ci *
4008c2ecf20Sopenharmony_ci * @reg:	imr index to remove.
4018c2ecf20Sopenharmony_ci * @base:	physical base address of region aligned to 1 KiB.
4028c2ecf20Sopenharmony_ci * @size:	physical size of region in bytes aligned to 1 KiB.
4038c2ecf20Sopenharmony_ci * @return:	-EINVAL on invalid range or out or range id
4048c2ecf20Sopenharmony_ci *		-ENODEV if reg is valid but no IMR exists or is locked
4058c2ecf20Sopenharmony_ci *		0 on success.
4068c2ecf20Sopenharmony_ci */
4078c2ecf20Sopenharmony_cistatic int __imr_remove_range(int reg, phys_addr_t base, size_t size)
4088c2ecf20Sopenharmony_ci{
4098c2ecf20Sopenharmony_ci	phys_addr_t end;
4108c2ecf20Sopenharmony_ci	bool found = false;
4118c2ecf20Sopenharmony_ci	unsigned int i;
4128c2ecf20Sopenharmony_ci	struct imr_device *idev = &imr_dev;
4138c2ecf20Sopenharmony_ci	struct imr_regs imr;
4148c2ecf20Sopenharmony_ci	size_t raw_size;
4158c2ecf20Sopenharmony_ci	int ret = 0;
4168c2ecf20Sopenharmony_ci
4178c2ecf20Sopenharmony_ci	if (WARN_ONCE(idev->init == false, "driver not initialized"))
4188c2ecf20Sopenharmony_ci		return -ENODEV;
4198c2ecf20Sopenharmony_ci
4208c2ecf20Sopenharmony_ci	/*
4218c2ecf20Sopenharmony_ci	 * Validate address range if deleting by address, else we are
4228c2ecf20Sopenharmony_ci	 * deleting by index where base and size will be ignored.
4238c2ecf20Sopenharmony_ci	 */
4248c2ecf20Sopenharmony_ci	if (reg == -1) {
4258c2ecf20Sopenharmony_ci		ret = imr_check_params(base, size);
4268c2ecf20Sopenharmony_ci		if (ret)
4278c2ecf20Sopenharmony_ci			return ret;
4288c2ecf20Sopenharmony_ci	}
4298c2ecf20Sopenharmony_ci
4308c2ecf20Sopenharmony_ci	/* Tweak the size value. */
4318c2ecf20Sopenharmony_ci	raw_size = imr_raw_size(size);
4328c2ecf20Sopenharmony_ci	end = base + raw_size;
4338c2ecf20Sopenharmony_ci
4348c2ecf20Sopenharmony_ci	mutex_lock(&idev->lock);
4358c2ecf20Sopenharmony_ci
4368c2ecf20Sopenharmony_ci	if (reg >= 0) {
4378c2ecf20Sopenharmony_ci		/* If a specific IMR is given try to use it. */
4388c2ecf20Sopenharmony_ci		ret = imr_read(idev, reg, &imr);
4398c2ecf20Sopenharmony_ci		if (ret)
4408c2ecf20Sopenharmony_ci			goto failed;
4418c2ecf20Sopenharmony_ci
4428c2ecf20Sopenharmony_ci		if (!imr_is_enabled(&imr) || imr.addr_lo & IMR_LOCK) {
4438c2ecf20Sopenharmony_ci			ret = -ENODEV;
4448c2ecf20Sopenharmony_ci			goto failed;
4458c2ecf20Sopenharmony_ci		}
4468c2ecf20Sopenharmony_ci		found = true;
4478c2ecf20Sopenharmony_ci	} else {
4488c2ecf20Sopenharmony_ci		/* Search for match based on address range. */
4498c2ecf20Sopenharmony_ci		for (i = 0; i < idev->max_imr; i++) {
4508c2ecf20Sopenharmony_ci			ret = imr_read(idev, i, &imr);
4518c2ecf20Sopenharmony_ci			if (ret)
4528c2ecf20Sopenharmony_ci				goto failed;
4538c2ecf20Sopenharmony_ci
4548c2ecf20Sopenharmony_ci			if (!imr_is_enabled(&imr) || imr.addr_lo & IMR_LOCK)
4558c2ecf20Sopenharmony_ci				continue;
4568c2ecf20Sopenharmony_ci
4578c2ecf20Sopenharmony_ci			if ((imr_to_phys(imr.addr_lo) == base) &&
4588c2ecf20Sopenharmony_ci			    (imr_to_phys(imr.addr_hi) == end)) {
4598c2ecf20Sopenharmony_ci				found = true;
4608c2ecf20Sopenharmony_ci				reg = i;
4618c2ecf20Sopenharmony_ci				break;
4628c2ecf20Sopenharmony_ci			}
4638c2ecf20Sopenharmony_ci		}
4648c2ecf20Sopenharmony_ci	}
4658c2ecf20Sopenharmony_ci
4668c2ecf20Sopenharmony_ci	if (!found) {
4678c2ecf20Sopenharmony_ci		ret = -ENODEV;
4688c2ecf20Sopenharmony_ci		goto failed;
4698c2ecf20Sopenharmony_ci	}
4708c2ecf20Sopenharmony_ci
4718c2ecf20Sopenharmony_ci	pr_debug("remove %d phys %pa-%pa size %zx\n", reg, &base, &end, raw_size);
4728c2ecf20Sopenharmony_ci
4738c2ecf20Sopenharmony_ci	/* Tear down the IMR. */
4748c2ecf20Sopenharmony_ci	imr.addr_lo = 0;
4758c2ecf20Sopenharmony_ci	imr.addr_hi = 0;
4768c2ecf20Sopenharmony_ci	imr.rmask = IMR_READ_ACCESS_ALL;
4778c2ecf20Sopenharmony_ci	imr.wmask = IMR_WRITE_ACCESS_ALL;
4788c2ecf20Sopenharmony_ci
4798c2ecf20Sopenharmony_ci	ret = imr_write(idev, reg, &imr);
4808c2ecf20Sopenharmony_ci
4818c2ecf20Sopenharmony_cifailed:
4828c2ecf20Sopenharmony_ci	mutex_unlock(&idev->lock);
4838c2ecf20Sopenharmony_ci	return ret;
4848c2ecf20Sopenharmony_ci}
4858c2ecf20Sopenharmony_ci
4868c2ecf20Sopenharmony_ci/**
4878c2ecf20Sopenharmony_ci * imr_remove_range - delete an Isolated Memory Region by address
4888c2ecf20Sopenharmony_ci *
4898c2ecf20Sopenharmony_ci * This function allows you to delete an IMR by an address range specified
4908c2ecf20Sopenharmony_ci * by base and size respectively.
4918c2ecf20Sopenharmony_ci * imr_remove_range(base, size); delete IMR from base to base+size.
4928c2ecf20Sopenharmony_ci *
4938c2ecf20Sopenharmony_ci * @base:	physical base address of region aligned to 1 KiB.
4948c2ecf20Sopenharmony_ci * @size:	physical size of region in bytes aligned to 1 KiB.
4958c2ecf20Sopenharmony_ci * @return:	-EINVAL on invalid range or out or range id
4968c2ecf20Sopenharmony_ci *		-ENODEV if reg is valid but no IMR exists or is locked
4978c2ecf20Sopenharmony_ci *		0 on success.
4988c2ecf20Sopenharmony_ci */
4998c2ecf20Sopenharmony_ciint imr_remove_range(phys_addr_t base, size_t size)
5008c2ecf20Sopenharmony_ci{
5018c2ecf20Sopenharmony_ci	return __imr_remove_range(-1, base, size);
5028c2ecf20Sopenharmony_ci}
5038c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(imr_remove_range);
5048c2ecf20Sopenharmony_ci
5058c2ecf20Sopenharmony_ci/**
5068c2ecf20Sopenharmony_ci * imr_clear - delete an Isolated Memory Region by index
5078c2ecf20Sopenharmony_ci *
5088c2ecf20Sopenharmony_ci * This function allows you to delete an IMR by an address range specified
5098c2ecf20Sopenharmony_ci * by the index of the IMR. Useful for initial sanitization of the IMR
5108c2ecf20Sopenharmony_ci * address map.
5118c2ecf20Sopenharmony_ci * imr_ge(base, size); delete IMR from base to base+size.
5128c2ecf20Sopenharmony_ci *
5138c2ecf20Sopenharmony_ci * @reg:	imr index to remove.
5148c2ecf20Sopenharmony_ci * @return:	-EINVAL on invalid range or out or range id
5158c2ecf20Sopenharmony_ci *		-ENODEV if reg is valid but no IMR exists or is locked
5168c2ecf20Sopenharmony_ci *		0 on success.
5178c2ecf20Sopenharmony_ci */
5188c2ecf20Sopenharmony_cistatic inline int imr_clear(int reg)
5198c2ecf20Sopenharmony_ci{
5208c2ecf20Sopenharmony_ci	return __imr_remove_range(reg, 0, 0);
5218c2ecf20Sopenharmony_ci}
5228c2ecf20Sopenharmony_ci
5238c2ecf20Sopenharmony_ci/**
5248c2ecf20Sopenharmony_ci * imr_fixup_memmap - Tear down IMRs used during bootup.
5258c2ecf20Sopenharmony_ci *
5268c2ecf20Sopenharmony_ci * BIOS and Grub both setup IMRs around compressed kernel, initrd memory
5278c2ecf20Sopenharmony_ci * that need to be removed before the kernel hands out one of the IMR
5288c2ecf20Sopenharmony_ci * encased addresses to a downstream DMA agent such as the SD or Ethernet.
5298c2ecf20Sopenharmony_ci * IMRs on Galileo are setup to immediately reset the system on violation.
5308c2ecf20Sopenharmony_ci * As a result if you're running a root filesystem from SD - you'll need
5318c2ecf20Sopenharmony_ci * the boot-time IMRs torn down or you'll find seemingly random resets when
5328c2ecf20Sopenharmony_ci * using your filesystem.
5338c2ecf20Sopenharmony_ci *
5348c2ecf20Sopenharmony_ci * @idev:	pointer to imr_device structure.
5358c2ecf20Sopenharmony_ci * @return:
5368c2ecf20Sopenharmony_ci */
5378c2ecf20Sopenharmony_cistatic void __init imr_fixup_memmap(struct imr_device *idev)
5388c2ecf20Sopenharmony_ci{
5398c2ecf20Sopenharmony_ci	phys_addr_t base = virt_to_phys(&_text);
5408c2ecf20Sopenharmony_ci	size_t size = virt_to_phys(&__end_rodata) - base;
5418c2ecf20Sopenharmony_ci	unsigned long start, end;
5428c2ecf20Sopenharmony_ci	int i;
5438c2ecf20Sopenharmony_ci	int ret;
5448c2ecf20Sopenharmony_ci
5458c2ecf20Sopenharmony_ci	/* Tear down all existing unlocked IMRs. */
5468c2ecf20Sopenharmony_ci	for (i = 0; i < idev->max_imr; i++)
5478c2ecf20Sopenharmony_ci		imr_clear(i);
5488c2ecf20Sopenharmony_ci
5498c2ecf20Sopenharmony_ci	start = (unsigned long)_text;
5508c2ecf20Sopenharmony_ci	end = (unsigned long)__end_rodata - 1;
5518c2ecf20Sopenharmony_ci
5528c2ecf20Sopenharmony_ci	/*
5538c2ecf20Sopenharmony_ci	 * Setup an unlocked IMR around the physical extent of the kernel
5548c2ecf20Sopenharmony_ci	 * from the beginning of the .text secton to the end of the
5558c2ecf20Sopenharmony_ci	 * .rodata section as one physically contiguous block.
5568c2ecf20Sopenharmony_ci	 *
5578c2ecf20Sopenharmony_ci	 * We don't round up @size since it is already PAGE_SIZE aligned.
5588c2ecf20Sopenharmony_ci	 * See vmlinux.lds.S for details.
5598c2ecf20Sopenharmony_ci	 */
5608c2ecf20Sopenharmony_ci	ret = imr_add_range(base, size, IMR_CPU, IMR_CPU);
5618c2ecf20Sopenharmony_ci	if (ret < 0) {
5628c2ecf20Sopenharmony_ci		pr_err("unable to setup IMR for kernel: %zu KiB (%lx - %lx)\n",
5638c2ecf20Sopenharmony_ci			size / 1024, start, end);
5648c2ecf20Sopenharmony_ci	} else {
5658c2ecf20Sopenharmony_ci		pr_info("protecting kernel .text - .rodata: %zu KiB (%lx - %lx)\n",
5668c2ecf20Sopenharmony_ci			size / 1024, start, end);
5678c2ecf20Sopenharmony_ci	}
5688c2ecf20Sopenharmony_ci
5698c2ecf20Sopenharmony_ci}
5708c2ecf20Sopenharmony_ci
5718c2ecf20Sopenharmony_cistatic const struct x86_cpu_id imr_ids[] __initconst = {
5728c2ecf20Sopenharmony_ci	X86_MATCH_VENDOR_FAM_MODEL(INTEL, 5, INTEL_FAM5_QUARK_X1000, NULL),
5738c2ecf20Sopenharmony_ci	{}
5748c2ecf20Sopenharmony_ci};
5758c2ecf20Sopenharmony_ci
5768c2ecf20Sopenharmony_ci/**
5778c2ecf20Sopenharmony_ci * imr_init - entry point for IMR driver.
5788c2ecf20Sopenharmony_ci *
5798c2ecf20Sopenharmony_ci * return: -ENODEV for no IMR support 0 if good to go.
5808c2ecf20Sopenharmony_ci */
5818c2ecf20Sopenharmony_cistatic int __init imr_init(void)
5828c2ecf20Sopenharmony_ci{
5838c2ecf20Sopenharmony_ci	struct imr_device *idev = &imr_dev;
5848c2ecf20Sopenharmony_ci
5858c2ecf20Sopenharmony_ci	if (!x86_match_cpu(imr_ids) || !iosf_mbi_available())
5868c2ecf20Sopenharmony_ci		return -ENODEV;
5878c2ecf20Sopenharmony_ci
5888c2ecf20Sopenharmony_ci	idev->max_imr = QUARK_X1000_IMR_MAX;
5898c2ecf20Sopenharmony_ci	idev->reg_base = QUARK_X1000_IMR_REGBASE;
5908c2ecf20Sopenharmony_ci	idev->init = true;
5918c2ecf20Sopenharmony_ci
5928c2ecf20Sopenharmony_ci	mutex_init(&idev->lock);
5938c2ecf20Sopenharmony_ci	imr_debugfs_register(idev);
5948c2ecf20Sopenharmony_ci	imr_fixup_memmap(idev);
5958c2ecf20Sopenharmony_ci	return 0;
5968c2ecf20Sopenharmony_ci}
5978c2ecf20Sopenharmony_cidevice_initcall(imr_init);
598