2020-06-22 14:05:44

by Rafael J. Wysocki

[permalink] [raw]
Subject: [RFT][PATCH v2 2/4] ACPI: OSL: Add support for deferred unmapping of ACPI memory

From: "Rafael J. Wysocki" <[email protected]>

Implement acpi_os_unmap_deferred() and
acpi_os_release_unused_mappings() and set ACPI_USE_DEFERRED_UNMAPPING
to allow ACPICA to use deferred unmapping of memory in
acpi_ex_system_memory_space_handler() so as to avoid RCU-related
performance issues with memory opregions.

Reported-by: Dan Williams <[email protected]>
Signed-off-by: Rafael J. Wysocki <[email protected]>
---
drivers/acpi/osl.c | 160 +++++++++++++++++++++++-------
include/acpi/platform/aclinuxex.h | 4 +
2 files changed, 128 insertions(+), 36 deletions(-)

diff --git a/drivers/acpi/osl.c b/drivers/acpi/osl.c
index 762c5d50b8fe..28863d908fa8 100644
--- a/drivers/acpi/osl.c
+++ b/drivers/acpi/osl.c
@@ -77,12 +77,16 @@ struct acpi_ioremap {
void __iomem *virt;
acpi_physical_address phys;
acpi_size size;
- unsigned long refcount;
+ union {
+ unsigned long refcount;
+ struct list_head gc;
+ } track;
};

static LIST_HEAD(acpi_ioremaps);
static DEFINE_MUTEX(acpi_ioremap_lock);
#define acpi_ioremap_lock_held() lock_is_held(&acpi_ioremap_lock.dep_map)
+static LIST_HEAD(unused_mappings);

static void __init acpi_request_region (struct acpi_generic_address *gas,
unsigned int length, char *desc)
@@ -250,7 +254,7 @@ void __iomem *acpi_os_get_iomem(acpi_physical_address phys, unsigned int size)
map = acpi_map_lookup(phys, size);
if (map) {
virt = map->virt + (phys - map->phys);
- map->refcount++;
+ map->track.refcount++;
}
mutex_unlock(&acpi_ioremap_lock);
return virt;
@@ -335,7 +339,7 @@ void __iomem __ref
/* Check if there's a suitable mapping already. */
map = acpi_map_lookup(phys, size);
if (map) {
- map->refcount++;
+ map->track.refcount++;
goto out;
}

@@ -358,7 +362,7 @@ void __iomem __ref
map->virt = virt;
map->phys = pg_off;
map->size = pg_sz;
- map->refcount = 1;
+ map->track.refcount = 1;

list_add_tail_rcu(&map->list, &acpi_ioremaps);

@@ -375,40 +379,41 @@ void *__ref acpi_os_map_memory(acpi_physical_address phys, acpi_size size)
EXPORT_SYMBOL_GPL(acpi_os_map_memory);

/* Must be called with mutex_lock(&acpi_ioremap_lock) */
-static unsigned long acpi_os_drop_map_ref(struct acpi_ioremap *map)
+static bool acpi_os_drop_map_ref(struct acpi_ioremap *map, bool defer)
{
- unsigned long refcount = --map->refcount;
+ if (--map->track.refcount)
+ return true;

- if (!refcount)
- list_del_rcu(&map->list);
- return refcount;
+ list_del_rcu(&map->list);
+
+ if (defer) {
+ INIT_LIST_HEAD(&map->track.gc);
+ list_add_tail(&map->track.gc, &unused_mappings);
+ return true;
+ }
+
+ return false;
}

-static void acpi_os_map_cleanup(struct acpi_ioremap *map)
+static void __acpi_os_map_cleanup(struct acpi_ioremap *map)
{
- synchronize_rcu_expedited();
acpi_unmap(map->phys, map->virt);
kfree(map);
}

-/**
- * acpi_os_unmap_iomem - Drop a memory mapping reference.
- * @virt: Start of the address range to drop a reference to.
- * @size: Size of the address range to drop a reference to.
- *
- * Look up the given virtual address range in the list of existing ACPI memory
- * mappings, drop a reference to it and unmap it if there are no more active
- * references to it.
- *
- * During early init (when acpi_permanent_mmap has not been set yet) this
- * routine simply calls __acpi_unmap_table() to get the job done. Since
- * __acpi_unmap_table() is an __init function, the __ref annotation is needed
- * here.
- */
-void __ref acpi_os_unmap_iomem(void __iomem *virt, acpi_size size)
+static void acpi_os_map_cleanup(struct acpi_ioremap *map)
+{
+ if (!map)
+ return;
+
+ synchronize_rcu_expedited();
+ __acpi_os_map_cleanup(map);
+}
+
+static void __ref __acpi_os_unmap_iomem(void __iomem *virt, acpi_size size,
+ bool defer)
{
struct acpi_ioremap *map;
- unsigned long refcount;

if (!acpi_permanent_mmap) {
__acpi_unmap_table(virt, size);
@@ -416,26 +421,102 @@ void __ref acpi_os_unmap_iomem(void __iomem *virt, acpi_size size)
}

mutex_lock(&acpi_ioremap_lock);
+
map = acpi_map_lookup_virt(virt, size);
if (!map) {
mutex_unlock(&acpi_ioremap_lock);
WARN(true, PREFIX "%s: bad address %p\n", __func__, virt);
return;
}
- refcount = acpi_os_drop_map_ref(map);
+ if (acpi_os_drop_map_ref(map, defer))
+ map = NULL;
+
mutex_unlock(&acpi_ioremap_lock);

- if (!refcount)
- acpi_os_map_cleanup(map);
+ acpi_os_map_cleanup(map);
+}
+
+/**
+ * acpi_os_unmap_iomem - Drop a memory mapping reference.
+ * @virt: Start of the address range to drop a reference to.
+ * @size: Size of the address range to drop a reference to.
+ *
+ * Look up the given virtual address range in the list of existing ACPI memory
+ * mappings, drop a reference to it and unmap it if there are no more active
+ * references to it.
+ *
+ * During early init (when acpi_permanent_mmap has not been set yet) this
+ * routine simply calls __acpi_unmap_table() to get the job done. Since
+ * __acpi_unmap_table() is an __init function, the __ref annotation is needed
+ * here.
+ */
+void __ref acpi_os_unmap_iomem(void __iomem *virt, acpi_size size)
+{
+ __acpi_os_unmap_iomem(virt, size, false);
}
EXPORT_SYMBOL_GPL(acpi_os_unmap_iomem);

void __ref acpi_os_unmap_memory(void *virt, acpi_size size)
{
- return acpi_os_unmap_iomem((void __iomem *)virt, size);
+ acpi_os_unmap_iomem((void __iomem *)virt, size);
}
EXPORT_SYMBOL_GPL(acpi_os_unmap_memory);

+/**
+ * acpi_os_unmap_deferred - Drop a memory mapping reference.
+ * @virt: Start of the address range to drop a reference to.
+ * @size: Size of the address range to drop a reference to.
+ *
+ * Look up the given virtual address range in the list of existing ACPI memory
+ * mappings, drop a reference to it and if there are no more active references
+ * to it, put it in the list of unused memory mappings.
+ *
+ * During early init (when acpi_permanent_mmap has not been set yet) this
+ * routine behaves like acpi_os_unmap_memory().
+ */
+void __ref acpi_os_unmap_deferred(void *virt, acpi_size size)
+{
+ __acpi_os_unmap_iomem((void __iomem *)virt, size, true);
+}
+
+/**
+ * acpi_os_release_unused_mappings - Release unused ACPI memory mappings.
+ */
+void acpi_os_release_unused_mappings(void)
+{
+ struct list_head list;
+
+ INIT_LIST_HEAD(&list);
+
+ /*
+ * First avoid looking at mappings that may be added to the "unused"
+ * list while the synchronize_rcu() below is running.
+ */
+ mutex_lock(&acpi_ioremap_lock);
+
+ list_splice_init(&unused_mappings, &list);
+
+ mutex_unlock(&acpi_ioremap_lock);
+
+ if (list_empty(&list))
+ return;
+
+ /*
+ * Wait for the possible users of the mappings in the "unused" list to
+ * stop using them.
+ */
+ synchronize_rcu();
+
+ /* Release the unused mappings in the list. */
+ while (!list_empty(&list)) {
+ struct acpi_ioremap *map;
+
+ map = list_entry(list.next, struct acpi_ioremap, track.gc);
+ list_del(&map->track.gc);
+ __acpi_os_map_cleanup(map);
+ }
+}
+
int acpi_os_map_generic_address(struct acpi_generic_address *gas)
{
u64 addr;
@@ -461,7 +542,6 @@ void acpi_os_unmap_generic_address(struct acpi_generic_address *gas)
{
u64 addr;
struct acpi_ioremap *map;
- unsigned long refcount;

if (gas->space_id != ACPI_ADR_SPACE_SYSTEM_MEMORY)
return;
@@ -472,16 +552,18 @@ void acpi_os_unmap_generic_address(struct acpi_generic_address *gas)
return;

mutex_lock(&acpi_ioremap_lock);
+
map = acpi_map_lookup(addr, gas->bit_width / 8);
if (!map) {
mutex_unlock(&acpi_ioremap_lock);
return;
}
- refcount = acpi_os_drop_map_ref(map);
+ if (acpi_os_drop_map_ref(map, false))
+ map = NULL;
+
mutex_unlock(&acpi_ioremap_lock);

- if (!refcount)
- acpi_os_map_cleanup(map);
+ acpi_os_map_cleanup(map);
}
EXPORT_SYMBOL(acpi_os_unmap_generic_address);

@@ -1566,11 +1648,17 @@ static acpi_status acpi_deactivate_mem_region(acpi_handle handle, u32 level,
acpi_status acpi_release_memory(acpi_handle handle, struct resource *res,
u32 level)
{
+ acpi_status ret;
+
if (!(res->flags & IORESOURCE_MEM))
return AE_TYPE;

- return acpi_walk_namespace(ACPI_TYPE_REGION, handle, level,
+ ret = acpi_walk_namespace(ACPI_TYPE_REGION, handle, level,
acpi_deactivate_mem_region, NULL, res, NULL);
+
+ acpi_os_release_unused_mappings();
+
+ return ret;
}
EXPORT_SYMBOL_GPL(acpi_release_memory);

diff --git a/include/acpi/platform/aclinuxex.h b/include/acpi/platform/aclinuxex.h
index 04f88f2de781..e13f364d6c69 100644
--- a/include/acpi/platform/aclinuxex.h
+++ b/include/acpi/platform/aclinuxex.h
@@ -138,6 +138,10 @@ static inline void acpi_os_terminate_debugger(void)
/*
* OSL interfaces added by Linux
*/
+void acpi_os_unmap_deferred(void *virt, acpi_size size);
+void acpi_os_release_unused_mappings(void);
+
+#define ACPI_USE_DEFERRED_UNMAPPING

#endif /* __KERNEL__ */

--
2.26.2





2020-06-22 14:58:24

by Andy Shevchenko

[permalink] [raw]
Subject: Re: [RFT][PATCH v2 2/4] ACPI: OSL: Add support for deferred unmapping of ACPI memory

On Mon, Jun 22, 2020 at 5:06 PM Rafael J. Wysocki <[email protected]> wrote:
>
> From: "Rafael J. Wysocki" <[email protected]>
>
> Implement acpi_os_unmap_deferred() and
> acpi_os_release_unused_mappings() and set ACPI_USE_DEFERRED_UNMAPPING
> to allow ACPICA to use deferred unmapping of memory in
> acpi_ex_system_memory_space_handler() so as to avoid RCU-related
> performance issues with memory opregions.

...

> +static bool acpi_os_drop_map_ref(struct acpi_ioremap *map, bool defer)
> {
> - unsigned long refcount = --map->refcount;
> + if (--map->track.refcount)
> + return true;
>
> - if (!refcount)
> - list_del_rcu(&map->list);
> - return refcount;
> + list_del_rcu(&map->list);
> +

> + if (defer) {
> + INIT_LIST_HEAD(&map->track.gc);
> + list_add_tail(&map->track.gc, &unused_mappings);

> + return true;
> + }
> +
> + return false;

A nit:

Effectively it returns a value of defer.

return defer;

> }

...

> @@ -416,26 +421,102 @@ void __ref acpi_os_unmap_iomem(void __iomem *virt, acpi_size size)
> }
>
> mutex_lock(&acpi_ioremap_lock);
> +
> map = acpi_map_lookup_virt(virt, size);

A nit: should it be somewhere else (I mean in another patch)?

> if (!map) {

...

> + /* Release the unused mappings in the list. */
> + while (!list_empty(&list)) {
> + struct acpi_ioremap *map;
> +
> + map = list_entry(list.next, struct acpi_ioremap, track.gc);

A nt: if __acpi_os_map_cleanup() (actually acpi_unmap() according to
the code) has no side effects, can we use list_for_each_entry_safe()
here?

> + list_del(&map->track.gc);
> + __acpi_os_map_cleanup(map);
> + }
> +}

...

> @@ -472,16 +552,18 @@ void acpi_os_unmap_generic_address(struct acpi_generic_address *gas)
> return;
>
> mutex_lock(&acpi_ioremap_lock);
> +
> map = acpi_map_lookup(addr, gas->bit_width / 8);

A nit: should it be somewhere else (I mean in another patch)?

--
With Best Regards,
Andy Shevchenko

2020-06-22 15:32:15

by Rafael J. Wysocki

[permalink] [raw]
Subject: Re: [RFT][PATCH v2 2/4] ACPI: OSL: Add support for deferred unmapping of ACPI memory

On Mon, Jun 22, 2020 at 4:56 PM Andy Shevchenko
<[email protected]> wrote:
>
> On Mon, Jun 22, 2020 at 5:06 PM Rafael J. Wysocki <[email protected]> wrote:
> >
> > From: "Rafael J. Wysocki" <[email protected]>
> >
> > Implement acpi_os_unmap_deferred() and
> > acpi_os_release_unused_mappings() and set ACPI_USE_DEFERRED_UNMAPPING
> > to allow ACPICA to use deferred unmapping of memory in
> > acpi_ex_system_memory_space_handler() so as to avoid RCU-related
> > performance issues with memory opregions.
>
> ...
>
> > +static bool acpi_os_drop_map_ref(struct acpi_ioremap *map, bool defer)
> > {
> > - unsigned long refcount = --map->refcount;
> > + if (--map->track.refcount)
> > + return true;
> >
> > - if (!refcount)
> > - list_del_rcu(&map->list);
> > - return refcount;
> > + list_del_rcu(&map->list);
> > +
>
> > + if (defer) {
> > + INIT_LIST_HEAD(&map->track.gc);
> > + list_add_tail(&map->track.gc, &unused_mappings);
>
> > + return true;
> > + }
> > +
> > + return false;
>
> A nit:
>
> Effectively it returns a value of defer.
>
> return defer;
>
> > }

Do you mean that one line of code could be saved? Yes, it could.

>
> ...
>
> > @@ -416,26 +421,102 @@ void __ref acpi_os_unmap_iomem(void __iomem *virt, acpi_size size)
> > }
> >
> > mutex_lock(&acpi_ioremap_lock);
> > +
> > map = acpi_map_lookup_virt(virt, size);
>
> A nit: should it be somewhere else (I mean in another patch)?

Do you mean the extra empty line?

No, I don't think so, or the code style after this patch would not
look consistent.

> > if (!map) {
>
> ...
>
> > + /* Release the unused mappings in the list. */
> > + while (!list_empty(&list)) {
> > + struct acpi_ioremap *map;
> > +
> > + map = list_entry(list.next, struct acpi_ioremap, track.gc);
>
> A nt: if __acpi_os_map_cleanup() (actually acpi_unmap() according to
> the code) has no side effects, can we use list_for_each_entry_safe()
> here?

I actually prefer a do .. while version of this which saves the
initial check (which has been carried out already).

> > + list_del(&map->track.gc);
> > + __acpi_os_map_cleanup(map);
> > + }
> > +}
>
> ...
>
> > @@ -472,16 +552,18 @@ void acpi_os_unmap_generic_address(struct acpi_generic_address *gas)
> > return;
> >
> > mutex_lock(&acpi_ioremap_lock);
> > +
> > map = acpi_map_lookup(addr, gas->bit_width / 8);
>
> A nit: should it be somewhere else (I mean in another patch)?

Nope.

Thanks!

2020-06-22 15:49:33

by Andy Shevchenko

[permalink] [raw]
Subject: Re: [RFT][PATCH v2 2/4] ACPI: OSL: Add support for deferred unmapping of ACPI memory

On Mon, Jun 22, 2020 at 6:28 PM Rafael J. Wysocki <[email protected]> wrote:
> On Mon, Jun 22, 2020 at 4:56 PM Andy Shevchenko
> <[email protected]> wrote:
> > On Mon, Jun 22, 2020 at 5:06 PM Rafael J. Wysocki <[email protected]> wrote:

...

> > > + return true;
> > > + }
> > > +
> > > + return false;
> >
> > A nit:
> >
> > Effectively it returns a value of defer.
> >
> > return defer;
> >
> > > }
>
> Do you mean that one line of code could be saved? Yes, it could.

Yes. The question here would it make a cleaner way for the reader to
understand the returned value?

(For the rest, nevermind, choose whatever suits better in your opinion)

--
With Best Regards,
Andy Shevchenko