Merge tag 'hisi-fixes-for-5.3' of git://github.com/hisilicon/linux-hisi into arm...
authorArnd Bergmann <arnd@arndb.de>
Thu, 29 Aug 2019 15:23:51 +0000 (17:23 +0200)
committerArnd Bergmann <arnd@arndb.de>
Thu, 29 Aug 2019 15:23:52 +0000 (17:23 +0200)
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>
drivers/bus/hisi_lpc.c
include/linux/logic_pio.h
lib/logic_pio.c

index 19d7b6ff2f1793b23d1e80507d733b4de3f8d9b8..20c957185af203ff6e5dbc0e466198188ed3bfb1 100644 (file)
@@ -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);
index cbd9d8495690212fc689d3ce3a7caf0812d45523..88e1e6304a7193bca45a14500f7ade4632dbd335 100644 (file)
@@ -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);
 
index feea48fd1a0dd6ae7913b6fad19cc52e20664785..905027574e5d8041d1394dc4e5582061ed084e2f 100644 (file)
@@ -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 @@ end_register:
        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 @@ end_register:
  */
 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;
 }