resource: add release_mem_region_adjustable()
authorToshi Kani <toshi.kani@hp.com>
Mon, 29 Apr 2013 22:08:19 +0000 (15:08 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Mon, 29 Apr 2013 22:54:37 +0000 (15:54 -0700)
Add release_mem_region_adjustable(), which releases a requested region
from a currently busy memory resource.  This interface adjusts the
matched memory resource accordingly even if the requested region does
not match exactly but still fits into.

This new interface is intended for memory hot-delete.  During bootup,
memory resources are inserted from the boot descriptor table, such as
EFI Memory Table and e820.  Each memory resource entry usually covers
the whole contigous memory range.  Memory hot-delete request, on the
other hand, may target to a particular range of memory resource, and its
size can be much smaller than the whole contiguous memory.  Since the
existing release interfaces like __release_region() require a requested
region to be exactly matched to a resource entry, they do not allow a
partial resource to be released.

This new interface is restrictive (i.e.  release under certain
conditions), which is consistent with other release interfaces,
__release_region() and __release_resource().  Additional release
conditions, such as an overlapping region to a resource entry, can be
supported after they are confirmed as valid cases.

There is no change to the existing interfaces since their restriction is
valid for I/O resources.

[akpm@linux-foundation.org: use GFP_ATOMIC under write_lock()]
[akpm@linux-foundation.org: switch back to GFP_KERNEL, less buggily]
[akpm@linux-foundation.org: remove unneeded and wrong kfree(), per Toshi]
Signed-off-by: Toshi Kani <toshi.kani@hp.com>
Reviewed-by : Yasuaki Ishimatsu <isimatu.yasuaki@jp.fujitsu.com>
Cc: David Rientjes <rientjes@google.com>
Reviewed-by: Ram Pai <linuxram@us.ibm.com>
Cc: T Makphaibulchoke <tmac@hp.com>
Cc: Wen Congyang <wency@cn.fujitsu.com>
Cc: Tang Chen <tangchen@cn.fujitsu.com>
Cc: Jiang Liu <jiang.liu@huawei.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
include/linux/ioport.h
kernel/resource.c

index 85ac9b9b72a24acc54ebab8241d279fe2332d4b6..89b7c24a36e9fd7a8af6fea77bf7986507e86c65 100644 (file)
@@ -192,6 +192,10 @@ extern struct resource * __request_region(struct resource *,
 extern int __check_region(struct resource *, resource_size_t, resource_size_t);
 extern void __release_region(struct resource *, resource_size_t,
                                resource_size_t);
+#ifdef CONFIG_MEMORY_HOTREMOVE
+extern int release_mem_region_adjustable(struct resource *, resource_size_t,
+                               resource_size_t);
+#endif
 
 static inline int __deprecated check_region(resource_size_t s,
                                                resource_size_t n)
index ae246f97c5d3ae9e800e973f3d15787437130d39..4aef8867fd4ba3105452046e3d1ada3ff45bdc4d 100644 (file)
@@ -1021,6 +1021,109 @@ void __release_region(struct resource *parent, resource_size_t start,
 }
 EXPORT_SYMBOL(__release_region);
 
+#ifdef CONFIG_MEMORY_HOTREMOVE
+/**
+ * release_mem_region_adjustable - release a previously reserved memory region
+ * @parent: parent resource descriptor
+ * @start: resource start address
+ * @size: resource region size
+ *
+ * This interface is intended for memory hot-delete.  The requested region
+ * is released from a currently busy memory resource.  The requested region
+ * must either match exactly or fit into a single busy resource entry.  In
+ * the latter case, the remaining resource is adjusted accordingly.
+ * Existing children of the busy memory resource must be immutable in the
+ * request.
+ *
+ * Note:
+ * - Additional release conditions, such as overlapping region, can be
+ *   supported after they are confirmed as valid cases.
+ * - When a busy memory resource gets split into two entries, the code
+ *   assumes that all children remain in the lower address entry for
+ *   simplicity.  Enhance this logic when necessary.
+ */
+int release_mem_region_adjustable(struct resource *parent,
+                       resource_size_t start, resource_size_t size)
+{
+       struct resource **p;
+       struct resource *res;
+       struct resource *new_res;
+       resource_size_t end;
+       int ret = -EINVAL;
+
+       end = start + size - 1;
+       if ((start < parent->start) || (end > parent->end))
+               return ret;
+
+       /* The kzalloc() result gets checked later */
+       new_res = kzalloc(sizeof(struct resource), GFP_KERNEL);
+
+       p = &parent->child;
+       write_lock(&resource_lock);
+
+       while ((res = *p)) {
+               if (res->start >= end)
+                       break;
+
+               /* look for the next resource if it does not fit into */
+               if (res->start > start || res->end < end) {
+                       p = &res->sibling;
+                       continue;
+               }
+
+               if (!(res->flags & IORESOURCE_MEM))
+                       break;
+
+               if (!(res->flags & IORESOURCE_BUSY)) {
+                       p = &res->child;
+                       continue;
+               }
+
+               /* found the target resource; let's adjust accordingly */
+               if (res->start == start && res->end == end) {
+                       /* free the whole entry */
+                       *p = res->sibling;
+                       kfree(res);
+                       ret = 0;
+               } else if (res->start == start && res->end != end) {
+                       /* adjust the start */
+                       ret = __adjust_resource(res, end + 1,
+                                               res->end - end);
+               } else if (res->start != start && res->end == end) {
+                       /* adjust the end */
+                       ret = __adjust_resource(res, res->start,
+                                               start - res->start);
+               } else {
+                       /* split into two entries */
+                       if (!new_res) {
+                               ret = -ENOMEM;
+                               break;
+                       }
+                       new_res->name = res->name;
+                       new_res->start = end + 1;
+                       new_res->end = res->end;
+                       new_res->flags = res->flags;
+                       new_res->parent = res->parent;
+                       new_res->sibling = res->sibling;
+                       new_res->child = NULL;
+
+                       ret = __adjust_resource(res, res->start,
+                                               start - res->start);
+                       if (ret)
+                               break;
+                       res->sibling = new_res;
+                       new_res = NULL;
+               }
+
+               break;
+       }
+
+       write_unlock(&resource_lock);
+       kfree(new_res);
+       return ret;
+}
+#endif /* CONFIG_MEMORY_HOTREMOVE */
+
 /*
  * Managed region resource
  */