Skip to content

Commit

Permalink
Merge tag 'hisi-fixes-for-5.3' of git://github.com/hisilicon/linux-hi…
Browse files Browse the repository at this point in the history
…si 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/[email protected]
Signed-off-by: Arnd Bergmann <[email protected]>
  • Loading branch information
arndb committed Aug 29, 2019
2 parents 305cd70 + 10e62b4 commit 34614c3
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 25 deletions.
47 changes: 41 additions & 6 deletions drivers/bus/hisi_lpc.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
}

Expand All @@ -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

/*
Expand Down Expand Up @@ -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",
Expand All @@ -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", },
Expand All @@ -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 change: 1 addition & 0 deletions include/linux/logic_pio.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
73 changes: 54 additions & 19 deletions lib/logic_pio.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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
Expand All @@ -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;
}

/**
Expand Down Expand Up @@ -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;
}

Expand Down

0 comments on commit 34614c3

Please sign in to comment.