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 -----BEGIN PGP SIGNATURE----- Version: GnuPG v1 iQIcBAABAgAGBQJdVhtCAAoJEAvIV27ZiWZcP80P/0XIMAV5pFFXDuMNUciNg07v 6E0GZbbEViY07D4XvwUcZx/b2nJzHi66H9kVTZ757QfsEiAQEyviG8NlYinwDUmD yOxdgg3g8BtNieahqEwUjewW7NwyERbrCNIhUYGsr2wC+4D0Fycj03mUgfKaziwv HWF3b7kXOGbZiUE4N/UogKzYsG+Pm8k+h33wjiagXctiZvu4w/FRhGcpS2fu0AZ3 Oetp+2HPRwHM+eBQW8fLNlV9yV1tAODFKzVMv+JGHWpx+XJgzUwFSI1s/Q2ioN3k vEiXrmSaR1XT52yEe8qviV4NCD0XEtWeVmIMa8ZplsbmRzshTUcRPYO4nOJBiB6j tffPXv40AXI0vUwQnZ21NLbt+pfNWbxdMQV8rQWtmD4WmCg+7W11ARMCOA0eppAu GU8NXrNUO6+LU/w0snOLByEY+x3X9Vst5X0yIvS1uooEqtDK6RFGVy/KLAnWmPk5 GracF2utVudo0SnzANywWmvI1xML1n7tgknunUpE8tbmogHiaNAJvI6xExh6aQqh bkBN5LBFWVHtGyCJbGGemC7ddADjaxjFxiErW4zayAlTWUs9EQNYTRfoV7oBEKff Zb1XiT3vfhYYje5FRZvV5pvuskWceQd9Pi5JsYm57NEcW2SAGe653ADMWIfsjOxe /eM48ilxlGbP2SYxSmoL =BnY2 -----END PGP SIGNATURE----- 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: Arnd Bergmann <arnd@arndb.de>
This commit is contained in:
commit
34614c30bf
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue