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
...@@ -456,6 +456,17 @@ struct hisi_lpc_acpi_cell { ...@@ -456,6 +456,17 @@ struct hisi_lpc_acpi_cell {
size_t pdata_size; 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 * hisi_lpc_acpi_probe - probe children for ACPI FW
* @hostdev: LPC host device pointer * @hostdev: LPC host device pointer
...@@ -555,8 +566,7 @@ static int hisi_lpc_acpi_probe(struct device *hostdev) ...@@ -555,8 +566,7 @@ static int hisi_lpc_acpi_probe(struct device *hostdev)
return 0; return 0;
fail: fail:
device_for_each_child(hostdev, NULL, hisi_lpc_acpi_remove(hostdev);
hisi_lpc_acpi_remove_subdev);
return ret; return ret;
} }
...@@ -569,6 +579,10 @@ static int hisi_lpc_acpi_probe(struct device *dev) ...@@ -569,6 +579,10 @@ static int hisi_lpc_acpi_probe(struct device *dev)
{ {
return -ENODEV; return -ENODEV;
} }
static void hisi_lpc_acpi_remove(struct device *hostdev)
{
}
#endif // CONFIG_ACPI #endif // CONFIG_ACPI
/* /*
...@@ -606,24 +620,27 @@ static int hisi_lpc_probe(struct platform_device *pdev) ...@@ -606,24 +620,27 @@ static int hisi_lpc_probe(struct platform_device *pdev)
range->fwnode = dev->fwnode; range->fwnode = dev->fwnode;
range->flags = LOGIC_PIO_INDIRECT; range->flags = LOGIC_PIO_INDIRECT;
range->size = PIO_INDIRECT_SIZE; range->size = PIO_INDIRECT_SIZE;
range->hostdata = lpcdev;
range->ops = &hisi_lpc_ops;
lpcdev->io_host = range;
ret = logic_pio_register_range(range); ret = logic_pio_register_range(range);
if (ret) { if (ret) {
dev_err(dev, "register IO range failed (%d)!\n", ret); dev_err(dev, "register IO range failed (%d)!\n", ret);
return ret; return ret;
} }
lpcdev->io_host = range;
/* register the LPC host PIO resources */ /* register the LPC host PIO resources */
if (acpi_device) if (acpi_device)
ret = hisi_lpc_acpi_probe(dev); ret = hisi_lpc_acpi_probe(dev);
else else
ret = of_platform_populate(dev->of_node, NULL, NULL, dev); ret = of_platform_populate(dev->of_node, NULL, NULL, dev);
if (ret) if (ret) {
logic_pio_unregister_range(range);
return ret; return ret;
}
lpcdev->io_host->hostdata = lpcdev; dev_set_drvdata(dev, lpcdev);
lpcdev->io_host->ops = &hisi_lpc_ops;
io_end = lpcdev->io_host->io_start + lpcdev->io_host->size; io_end = lpcdev->io_host->io_start + lpcdev->io_host->size;
dev_info(dev, "registered range [%pa - %pa]\n", dev_info(dev, "registered range [%pa - %pa]\n",
...@@ -632,6 +649,23 @@ static int hisi_lpc_probe(struct platform_device *pdev) ...@@ -632,6 +649,23 @@ static int hisi_lpc_probe(struct platform_device *pdev)
return ret; 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[] = { static const struct of_device_id hisi_lpc_of_match[] = {
{ .compatible = "hisilicon,hip06-lpc", }, { .compatible = "hisilicon,hip06-lpc", },
{ .compatible = "hisilicon,hip07-lpc", }, { .compatible = "hisilicon,hip07-lpc", },
...@@ -645,5 +679,6 @@ static struct platform_driver hisi_lpc_driver = { ...@@ -645,5 +679,6 @@ static struct platform_driver hisi_lpc_driver = {
.acpi_match_table = ACPI_PTR(hisi_lpc_acpi_match), .acpi_match_table = ACPI_PTR(hisi_lpc_acpi_match),
}, },
.probe = hisi_lpc_probe, .probe = hisi_lpc_probe,
.remove = hisi_lpc_remove,
}; };
builtin_platform_driver(hisi_lpc_driver); builtin_platform_driver(hisi_lpc_driver);
...@@ -117,6 +117,7 @@ struct logic_pio_hwaddr *find_io_range_by_fwnode(struct fwnode_handle *fwnode); ...@@ -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, unsigned long logic_pio_trans_hwaddr(struct fwnode_handle *fwnode,
resource_size_t hw_addr, resource_size_t size); resource_size_t hw_addr, resource_size_t size);
int logic_pio_register_range(struct logic_pio_hwaddr *newrange); 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); resource_size_t logic_pio_to_hwaddr(unsigned long pio);
unsigned long logic_pio_trans_cpuaddr(resource_size_t hw_addr); unsigned long logic_pio_trans_cpuaddr(resource_size_t hw_addr);
......
...@@ -35,7 +35,7 @@ int logic_pio_register_range(struct logic_pio_hwaddr *new_range) ...@@ -35,7 +35,7 @@ int logic_pio_register_range(struct logic_pio_hwaddr *new_range)
struct logic_pio_hwaddr *range; struct logic_pio_hwaddr *range;
resource_size_t start; resource_size_t start;
resource_size_t end; resource_size_t end;
resource_size_t mmio_sz = 0; resource_size_t mmio_end = 0;
resource_size_t iio_sz = MMIO_UPPER_LIMIT; resource_size_t iio_sz = MMIO_UPPER_LIMIT;
int ret = 0; int ret = 0;
...@@ -46,7 +46,7 @@ int logic_pio_register_range(struct logic_pio_hwaddr *new_range) ...@@ -46,7 +46,7 @@ int logic_pio_register_range(struct logic_pio_hwaddr *new_range)
end = new_range->hw_start + new_range->size; end = new_range->hw_start + new_range->size;
mutex_lock(&io_range_mutex); 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) { if (range->fwnode == new_range->fwnode) {
/* range already there */ /* range already there */
goto end_register; goto end_register;
...@@ -56,7 +56,7 @@ int logic_pio_register_range(struct logic_pio_hwaddr *new_range) ...@@ -56,7 +56,7 @@ int logic_pio_register_range(struct logic_pio_hwaddr *new_range)
/* for MMIO ranges we need to check for overlap */ /* for MMIO ranges we need to check for overlap */
if (start >= range->hw_start + range->size || if (start >= range->hw_start + range->size ||
end < range->hw_start) { end < range->hw_start) {
mmio_sz += range->size; mmio_end = range->io_start + range->size;
} else { } else {
ret = -EFAULT; ret = -EFAULT;
goto end_register; goto end_register;
...@@ -69,16 +69,16 @@ int logic_pio_register_range(struct logic_pio_hwaddr *new_range) ...@@ -69,16 +69,16 @@ int logic_pio_register_range(struct logic_pio_hwaddr *new_range)
/* range not registered yet, check for available space */ /* range not registered yet, check for available space */
if (new_range->flags == LOGIC_PIO_CPU_MMIO) { 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 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; ret = -E2BIG;
goto end_register; goto end_register;
} }
new_range->size = SZ_64K; new_range->size = SZ_64K;
pr_warn("Requested IO range too big, new size set to 64K\n"); 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) { } else if (new_range->flags == LOGIC_PIO_INDIRECT) {
if (iio_sz + new_range->size - 1 > IO_SPACE_LIMIT) { if (iio_sz + new_range->size - 1 > IO_SPACE_LIMIT) {
ret = -E2BIG; ret = -E2BIG;
...@@ -98,6 +98,20 @@ int logic_pio_register_range(struct logic_pio_hwaddr *new_range) ...@@ -98,6 +98,20 @@ int logic_pio_register_range(struct logic_pio_hwaddr *new_range)
return ret; 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 * find_io_range_by_fwnode - find logical PIO range for given FW node
* @fwnode: FW node handle associated with logical PIO range * @fwnode: FW node handle associated with logical PIO range
...@@ -108,26 +122,38 @@ int logic_pio_register_range(struct logic_pio_hwaddr *new_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 *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) { list_for_each_entry_rcu(range, &io_range_list, list) {
if (range->fwnode == fwnode) if (range->fwnode == fwnode) {
return range; found_range = range;
break;
}
} }
return NULL; rcu_read_unlock();
return found_range;
} }
/* Return a registered range given an input PIO token */ /* Return a registered range given an input PIO token */
static struct logic_pio_hwaddr *find_io_range(unsigned long pio) 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) { list_for_each_entry_rcu(range, &io_range_list, list) {
if (in_range(pio, range->io_start, range->size)) if (in_range(pio, range->io_start, range->size)) {
return range; found_range = range;
break;
}
} }
pr_err("PIO entry token %lx invalid\n", pio); rcu_read_unlock();
return NULL;
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) ...@@ -180,14 +206,23 @@ unsigned long logic_pio_trans_cpuaddr(resource_size_t addr)
{ {
struct logic_pio_hwaddr *range; struct logic_pio_hwaddr *range;
rcu_read_lock();
list_for_each_entry_rcu(range, &io_range_list, list) { list_for_each_entry_rcu(range, &io_range_list, list) {
if (range->flags != LOGIC_PIO_CPU_MMIO) if (range->flags != LOGIC_PIO_CPU_MMIO)
continue; continue;
if (in_range(addr, range->hw_start, range->size)) if (in_range(addr, range->hw_start, range->size)) {
return addr - range->hw_start + range->io_start; 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", rcu_read_unlock();
(unsigned long long) addr);
pr_err("addr %pa not registered in io_range_list\n", &addr);
return ~0UL; return ~0UL;
} }
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment