/**
 * imr_is_enabled - true if an IMR is enabled false otherwise.
 *
 * Determines if an IMR is enabled based on address range and read/write
 * mask. An IMR set with an address range set to zero and a read/write
 * access mask set to all is considered to be disabled. An IMR in any
 * other state - for example set to zero but without read/write access
 * all is considered to be enabled. This definition of disabled is how
 * firmware switches off an IMR and is maintained in kernel for
 * consistency.
 *
 * @imr:	pointer to IMR descriptor.
 * @return:	true if IMR enabled false if disabled.
 */
static inline int imr_is_enabled(struct imr_regs *imr)
{
	return !(imr->rmask == IMR_READ_ACCESS_ALL &&
		 imr->wmask == IMR_WRITE_ACCESS_ALL &&
		 imr_to_phys(imr->addr_lo) == 0 &&
		 imr_to_phys(imr->addr_hi) == 0);
}
Beispiel #2
0
/**
 * imr_write - write an IMR at a given index.
 *
 * Requires caller to hold imr mutex.
 * Note lock bits need to be written independently of address bits.
 *
 * @idev:	pointer to imr_device structure.
 * @imr_id:	IMR entry to write.
 * @imr:	IMR structure representing address and access masks.
 * @lock:	indicates if the IMR lock bit should be applied.
 * @return:	0 on success or error code passed from mbi_iosf on failure.
 */
static int imr_write(struct imr_device *idev, u32 imr_id,
		     struct imr_regs *imr, bool lock)
{
	unsigned long flags;
	u32 reg = imr_id * IMR_NUM_REGS + idev->reg_base;
	int ret;

	local_irq_save(flags);

	ret = iosf_mbi_write(QRK_MBI_UNIT_MM, MBI_REG_WRITE, reg++, imr->addr_lo);
	if (ret)
		goto failed;

	ret = iosf_mbi_write(QRK_MBI_UNIT_MM, MBI_REG_WRITE, reg++, imr->addr_hi);
	if (ret)
		goto failed;

	ret = iosf_mbi_write(QRK_MBI_UNIT_MM, MBI_REG_WRITE, reg++, imr->rmask);
	if (ret)
		goto failed;

	ret = iosf_mbi_write(QRK_MBI_UNIT_MM, MBI_REG_WRITE, reg++, imr->wmask);
	if (ret)
		goto failed;

	/* Lock bit must be set separately to addr_lo address bits. */
	if (lock) {
		imr->addr_lo |= IMR_LOCK;
		ret = iosf_mbi_write(QRK_MBI_UNIT_MM, MBI_REG_WRITE,
				     reg - IMR_NUM_REGS, imr->addr_lo);
		if (ret)
			goto failed;
	}

	local_irq_restore(flags);
	return 0;
failed:
	/*
	 * If writing to the IOSF failed then we're in an unknown state,
	 * likely a very bad state. An IMR in an invalid state will almost
	 * certainly lead to a memory access violation.
	 */
	local_irq_restore(flags);
	WARN(ret, "IOSF-MBI write fail range 0x%08x-0x%08x unreliable\n",
	     imr_to_phys(imr->addr_lo), imr_to_phys(imr->addr_hi) + IMR_MASK);

	return ret;
}
/**
 * imr_dbgfs_state_show - print state of IMR registers.
 *
 * @s:		pointer to seq_file for output.
 * @unused:	unused parameter.
 * @return:	0 on success or error code passed from mbi_iosf on failure.
 */
static int imr_dbgfs_state_show(struct seq_file *s, void *unused)
{
	phys_addr_t base;
	phys_addr_t end;
	int i;
	struct imr_device *idev = s->private;
	struct imr_regs imr;
	size_t size;
	int ret = -ENODEV;

	mutex_lock(&idev->lock);

	for (i = 0; i < idev->max_imr; i++) {

		ret = imr_read(idev, i, &imr);
		if (ret)
			break;

		/*
		 * Remember to add IMR_ALIGN bytes to size to indicate the
		 * inherent IMR_ALIGN size bytes contained in the masked away
		 * lower ten bits.
		 */
		if (imr_is_enabled(&imr)) {
			base = imr_to_phys(imr.addr_lo);
			end = imr_to_phys(imr.addr_hi) + IMR_MASK;
			size = end - base + 1;
		} else {
			base = 0;
			end = 0;
			size = 0;
		}
		seq_printf(s, "imr%02i: base=%pa, end=%pa, size=0x%08zx "
			   "rmask=0x%08x, wmask=0x%08x, %s, %s\n", i,
			   &base, &end, size, imr.rmask, imr.wmask,
			   imr_is_enabled(&imr) ? "enabled " : "disabled",
			   imr.addr_lo & IMR_LOCK ? "locked" : "unlocked");
	}

	mutex_unlock(&idev->lock);
	return ret;
}
/**
 * __imr_remove_range - delete an Isolated Memory Region.
 *
 * This function allows you to delete an IMR by its index specified by reg or
 * by address range specified by base and size respectively. If you specify an
 * index on its own the base and size parameters are ignored.
 * imr_remove_range(0, base, size); delete IMR at index 0 base/size ignored.
 * imr_remove_range(-1, base, size); delete IMR from base to base+size.
 *
 * @reg:	imr index to remove.
 * @base:	physical base address of region aligned to 1 KiB.
 * @size:	physical size of region in bytes aligned to 1 KiB.
 * @return:	-EINVAL on invalid range or out or range id
 *		-ENODEV if reg is valid but no IMR exists or is locked
 *		0 on success.
 */
static int __imr_remove_range(int reg, phys_addr_t base, size_t size)
{
	phys_addr_t end;
	bool found = false;
	unsigned int i;
	struct imr_device *idev = &imr_dev;
	struct imr_regs imr;
	size_t raw_size;
	int ret = 0;

	if (WARN_ONCE(idev->init == false, "driver not initialized"))
		return -ENODEV;

	/*
	 * Validate address range if deleting by address, else we are
	 * deleting by index where base and size will be ignored.
	 */
	if (reg == -1) {
		ret = imr_check_params(base, size);
		if (ret)
			return ret;
	}

	/* Tweak the size value. */
	raw_size = imr_raw_size(size);
	end = base + raw_size;

	mutex_lock(&idev->lock);

	if (reg >= 0) {
		/* If a specific IMR is given try to use it. */
		ret = imr_read(idev, reg, &imr);
		if (ret)
			goto failed;

		if (!imr_is_enabled(&imr) || imr.addr_lo & IMR_LOCK) {
			ret = -ENODEV;
			goto failed;
		}
		found = true;
	} else {
		/* Search for match based on address range. */
		for (i = 0; i < idev->max_imr; i++) {
			ret = imr_read(idev, i, &imr);
			if (ret)
				goto failed;

			if (!imr_is_enabled(&imr) || imr.addr_lo & IMR_LOCK)
				continue;

			if ((imr_to_phys(imr.addr_lo) == base) &&
			    (imr_to_phys(imr.addr_hi) == end)) {
				found = true;
				reg = i;
				break;
			}
		}
	}

	if (!found) {
		ret = -ENODEV;
		goto failed;
	}

	pr_debug("remove %d phys %pa-%pa size %zx\n", reg, &base, &end, raw_size);

	/* Tear down the IMR. */
	imr.addr_lo = 0;
	imr.addr_hi = 0;
	imr.rmask = IMR_READ_ACCESS_ALL;
	imr.wmask = IMR_WRITE_ACCESS_ALL;

	ret = imr_write(idev, reg, &imr);

failed:
	mutex_unlock(&idev->lock);
	return ret;
}
/**
 * imr_address_overlap - detects an address overlap.
 *
 * @addr:	address to check against an existing IMR.
 * @imr:	imr being checked.
 * @return:	true for overlap false for no overlap.
 */
static inline int imr_address_overlap(phys_addr_t addr, struct imr_regs *imr)
{
	return addr >= imr_to_phys(imr->addr_lo) && addr <= imr_to_phys(imr->addr_hi);
}