Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 34614c30 authored by Arnd Bergmann's avatar Arnd Bergmann
Browse files

Merge tag 'hisi-fixes-for-5.3' of git://github.com/hisilicon/linux-hisi into arm/fixes

Hisilicon fixes for v5.3-rc

- Fixed RCU usage in logical PIO
- Added a function to unregister a logical PIO range in logical PIO
  to support the fixes in the hisi-lpc driver
- Fixed and optimized hisi-lpc driver to avoid potential use-after-free
  and driver unbind crash

* tag 'hisi-fixes-for-5.3' of git://github.com/hisilicon/linux-hisi:
  bus: hisi_lpc: Add .remove method to avoid driver unbind crash
  bus: hisi_lpc: Unregister logical PIO range to avoid potential use-after-free
  lib: logic_pio: Add logic_pio_unregister_range()
  lib: logic_pio: Avoid possible overlap for unregistering regions
  lib: logic_pio: Fix RCU usage

Link: https://lore.kernel.org/r/5D562335.7000902@hisilicon.com


Signed-off-by: default avatarArnd Bergmann <arnd@arndb.de>
parents 305cd70e 10e62b47
Loading
Loading
Loading
Loading
+41 −6
Original line number Diff line number Diff line
@@ -456,6 +456,17 @@ struct hisi_lpc_acpi_cell {
	size_t pdata_size;
};

static void hisi_lpc_acpi_remove(struct device *hostdev)
{
	struct acpi_device *adev = ACPI_COMPANION(hostdev);
	struct acpi_device *child;

	device_for_each_child(hostdev, NULL, hisi_lpc_acpi_remove_subdev);

	list_for_each_entry(child, &adev->children, node)
		acpi_device_clear_enumerated(child);
}

/*
 * hisi_lpc_acpi_probe - probe children for ACPI FW
 * @hostdev: LPC host device pointer
@@ -555,8 +566,7 @@ static int hisi_lpc_acpi_probe(struct device *hostdev)
	return 0;

fail:
	device_for_each_child(hostdev, NULL,
			      hisi_lpc_acpi_remove_subdev);
	hisi_lpc_acpi_remove(hostdev);
	return ret;
}

@@ -569,6 +579,10 @@ static int hisi_lpc_acpi_probe(struct device *dev)
{
	return -ENODEV;
}

static void hisi_lpc_acpi_remove(struct device *hostdev)
{
}
#endif // CONFIG_ACPI

/*
@@ -606,24 +620,27 @@ static int hisi_lpc_probe(struct platform_device *pdev)
	range->fwnode = dev->fwnode;
	range->flags = LOGIC_PIO_INDIRECT;
	range->size = PIO_INDIRECT_SIZE;
	range->hostdata = lpcdev;
	range->ops = &hisi_lpc_ops;
	lpcdev->io_host = range;

	ret = logic_pio_register_range(range);
	if (ret) {
		dev_err(dev, "register IO range failed (%d)!\n", ret);
		return ret;
	}
	lpcdev->io_host = range;

	/* register the LPC host PIO resources */
	if (acpi_device)
		ret = hisi_lpc_acpi_probe(dev);
	else
		ret = of_platform_populate(dev->of_node, NULL, NULL, dev);
	if (ret)
	if (ret) {
		logic_pio_unregister_range(range);
		return ret;
	}

	lpcdev->io_host->hostdata = lpcdev;
	lpcdev->io_host->ops = &hisi_lpc_ops;
	dev_set_drvdata(dev, lpcdev);

	io_end = lpcdev->io_host->io_start + lpcdev->io_host->size;
	dev_info(dev, "registered range [%pa - %pa]\n",
@@ -632,6 +649,23 @@ static int hisi_lpc_probe(struct platform_device *pdev)
	return ret;
}

static int hisi_lpc_remove(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;
	struct acpi_device *acpi_device = ACPI_COMPANION(dev);
	struct hisi_lpc_dev *lpcdev = dev_get_drvdata(dev);
	struct logic_pio_hwaddr *range = lpcdev->io_host;

	if (acpi_device)
		hisi_lpc_acpi_remove(dev);
	else
		of_platform_depopulate(dev);

	logic_pio_unregister_range(range);

	return 0;
}

static const struct of_device_id hisi_lpc_of_match[] = {
	{ .compatible = "hisilicon,hip06-lpc", },
	{ .compatible = "hisilicon,hip07-lpc", },
@@ -645,5 +679,6 @@ static struct platform_driver hisi_lpc_driver = {
		.acpi_match_table = ACPI_PTR(hisi_lpc_acpi_match),
	},
	.probe = hisi_lpc_probe,
	.remove = hisi_lpc_remove,
};
builtin_platform_driver(hisi_lpc_driver);
+1 −0
Original line number Diff line number Diff line
@@ -117,6 +117,7 @@ struct logic_pio_hwaddr *find_io_range_by_fwnode(struct fwnode_handle *fwnode);
unsigned long logic_pio_trans_hwaddr(struct fwnode_handle *fwnode,
			resource_size_t hw_addr, resource_size_t size);
int logic_pio_register_range(struct logic_pio_hwaddr *newrange);
void logic_pio_unregister_range(struct logic_pio_hwaddr *range);
resource_size_t logic_pio_to_hwaddr(unsigned long pio);
unsigned long logic_pio_trans_cpuaddr(resource_size_t hw_addr);

+54 −19
Original line number Diff line number Diff line
@@ -35,7 +35,7 @@ int logic_pio_register_range(struct logic_pio_hwaddr *new_range)
	struct logic_pio_hwaddr *range;
	resource_size_t start;
	resource_size_t end;
	resource_size_t mmio_sz = 0;
	resource_size_t mmio_end = 0;
	resource_size_t iio_sz = MMIO_UPPER_LIMIT;
	int ret = 0;

@@ -46,7 +46,7 @@ int logic_pio_register_range(struct logic_pio_hwaddr *new_range)
	end = new_range->hw_start + new_range->size;

	mutex_lock(&io_range_mutex);
	list_for_each_entry_rcu(range, &io_range_list, list) {
	list_for_each_entry(range, &io_range_list, list) {
		if (range->fwnode == new_range->fwnode) {
			/* range already there */
			goto end_register;
@@ -56,7 +56,7 @@ int logic_pio_register_range(struct logic_pio_hwaddr *new_range)
			/* for MMIO ranges we need to check for overlap */
			if (start >= range->hw_start + range->size ||
			    end < range->hw_start) {
				mmio_sz += range->size;
				mmio_end = range->io_start + range->size;
			} else {
				ret = -EFAULT;
				goto end_register;
@@ -69,16 +69,16 @@ int logic_pio_register_range(struct logic_pio_hwaddr *new_range)

	/* range not registered yet, check for available space */
	if (new_range->flags == LOGIC_PIO_CPU_MMIO) {
		if (mmio_sz + new_range->size - 1 > MMIO_UPPER_LIMIT) {
		if (mmio_end + new_range->size - 1 > MMIO_UPPER_LIMIT) {
			/* if it's too big check if 64K space can be reserved */
			if (mmio_sz + SZ_64K - 1 > MMIO_UPPER_LIMIT) {
			if (mmio_end + SZ_64K - 1 > MMIO_UPPER_LIMIT) {
				ret = -E2BIG;
				goto end_register;
			}
			new_range->size = SZ_64K;
			pr_warn("Requested IO range too big, new size set to 64K\n");
		}
		new_range->io_start = mmio_sz;
		new_range->io_start = mmio_end;
	} else if (new_range->flags == LOGIC_PIO_INDIRECT) {
		if (iio_sz + new_range->size - 1 > IO_SPACE_LIMIT) {
			ret = -E2BIG;
@@ -98,6 +98,20 @@ int logic_pio_register_range(struct logic_pio_hwaddr *new_range)
	return ret;
}

/**
 * logic_pio_unregister_range - unregister a logical PIO range for a host
 * @range: pointer to the IO range which has been already registered.
 *
 * Unregister a previously-registered IO range node.
 */
void logic_pio_unregister_range(struct logic_pio_hwaddr *range)
{
	mutex_lock(&io_range_mutex);
	list_del_rcu(&range->list);
	mutex_unlock(&io_range_mutex);
	synchronize_rcu();
}

/**
 * find_io_range_by_fwnode - find logical PIO range for given FW node
 * @fwnode: FW node handle associated with logical PIO range
@@ -108,26 +122,38 @@ int logic_pio_register_range(struct logic_pio_hwaddr *new_range)
 */
struct logic_pio_hwaddr *find_io_range_by_fwnode(struct fwnode_handle *fwnode)
{
	struct logic_pio_hwaddr *range;
	struct logic_pio_hwaddr *range, *found_range = NULL;

	rcu_read_lock();
	list_for_each_entry_rcu(range, &io_range_list, list) {
		if (range->fwnode == fwnode)
			return range;
		if (range->fwnode == fwnode) {
			found_range = range;
			break;
		}
	}
	return NULL;
	rcu_read_unlock();

	return found_range;
}

/* Return a registered range given an input PIO token */
static struct logic_pio_hwaddr *find_io_range(unsigned long pio)
{
	struct logic_pio_hwaddr *range;
	struct logic_pio_hwaddr *range, *found_range = NULL;

	rcu_read_lock();
	list_for_each_entry_rcu(range, &io_range_list, list) {
		if (in_range(pio, range->io_start, range->size))
			return range;
		if (in_range(pio, range->io_start, range->size)) {
			found_range = range;
			break;
		}
	}
	pr_err("PIO entry token %lx invalid\n", pio);
	return NULL;
	rcu_read_unlock();

	if (!found_range)
		pr_err("PIO entry token 0x%lx invalid\n", pio);

	return found_range;
}

/**
@@ -180,14 +206,23 @@ unsigned long logic_pio_trans_cpuaddr(resource_size_t addr)
{
	struct logic_pio_hwaddr *range;

	rcu_read_lock();
	list_for_each_entry_rcu(range, &io_range_list, list) {
		if (range->flags != LOGIC_PIO_CPU_MMIO)
			continue;
		if (in_range(addr, range->hw_start, range->size))
			return addr - range->hw_start + range->io_start;
		if (in_range(addr, range->hw_start, range->size)) {
			unsigned long cpuaddr;

			cpuaddr = addr - range->hw_start + range->io_start;

			rcu_read_unlock();
			return cpuaddr;
		}
	}
	pr_err("addr %llx not registered in io_range_list\n",
	       (unsigned long long) addr);
	rcu_read_unlock();

	pr_err("addr %pa not registered in io_range_list\n", &addr);

	return ~0UL;
}