lib: logic_pio: Fix RCU usage [Linux 4.19.70]

This Linux kernel change "lib: logic_pio: Fix RCU usage" is included in the Linux 4.19.70 release. This change is authored by John Garry <john.garry [at] huawei.com> on Tue Jul 30 21:29:52 2019 +0800. The commit for this change in Linux stable tree is b865c2c (patch) which is from upstream commit 06709e8. The same Linux upstream change may have been applied to various maintained Linux releases and you can find all Linux releases containing changes from upstream 06709e8.

lib: logic_pio: Fix RCU usage

commit 06709e81c668f5f56c65b806895b278517bd44e0 upstream.

The traversing of io_range_list with list_for_each_entry_rcu()
is not properly protected by rcu_read_lock() and rcu_read_unlock(),
so add them.

These functions mark the critical section scope where the list is
protected for the reader, it cannot be  "reclaimed". Any updater - in
this case, the logical PIO registration functions - cannot update the
list until the reader exits this critical section.

In addition, the list traversing used in logic_pio_register_range()
does not need to use the rcu variant.

This is because we are already using io_range_mutex to guarantee mutual
exclusion from mutating the list.

Cc: stable@vger.kernel.org
Fixes: 031e3601869c ("lib: Add generic PIO mapping method")
Signed-off-by: John Garry <john.garry@huawei.com>
Signed-off-by: Wei Xu <xuwei5@hisilicon.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

There are 49 lines of Linux source code added/deleted in this change. Code changes to Linux kernel are as follows.

 lib/logic_pio.c | 49 +++++++++++++++++++++++++++++++++++--------------
 1 file changed, 35 insertions(+), 14 deletions(-)

diff --git a/lib/logic_pio.c b/lib/logic_pio.c
index feea48f..7612963 100644
--- a/lib/logic_pio.c
+++ b/lib/logic_pio.c
@@ -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;
@@ -108,26 +108,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 +192,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;
 }

Leave a Reply

Your email address will not be published. Required fields are marked *