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