Commit 78d51cf8 authored by Paul Mackerras's avatar Paul Mackerras Committed by Linus Torvalds

[PATCH] ppc64: Extend ioremap/iounmap infrastructure

The patch below implements the ability to query outstanding imalloc regions
for a given virtual address range.  (Imalloc is the allocator of virtual
space for ioremap.) The patch extends im_get_area() to allow a region
criterion of IM_REGION_SUPERSET.  For a particular "superset" virtual
address and size passed into im_get_area(), the function returns the first
outstanding region that is contained within this superset region.

The patch also changes iounmap_explicit() to allow for the unmapping of all
regions that fit under a "superset".

This ability is necessary for dynamic (runtime) removal of pci host bridges
(PHBs).  For a PHB removal, the platform specification (the RPA) requires
that all of its children slots already be dynamically removed.  Each of
these slot-level removals has fractured the imalloc region assigned to the
PHB at boot.  At PHB removal time, it is necessary to iounmap() the
remaining artifacts of the initial PHB region.
Signed-off-by: default avatarJohn Rose <johnrose@austin.ibm.com>
Signed-off-by: default avatarPaul Mackerras <paulus@samba.org>
Signed-off-by: default avatarAndrew Morton <akpm@osdl.org>
Signed-off-by: default avatarLinus Torvalds <torvalds@osdl.org>
parent 14925120
......@@ -37,33 +37,51 @@ static int get_free_im_addr(unsigned long size, unsigned long *im_addr)
return 0;
}
/* Return whether the region described by v_addr and size is a subset
* of the region described by parent
*/
static inline int im_region_is_subset(unsigned long v_addr, unsigned long size,
struct vm_struct *parent)
{
return (int) (v_addr >= (unsigned long) parent->addr &&
v_addr < (unsigned long) parent->addr + parent->size &&
size < parent->size);
}
/* Return whether the region described by v_addr and size is a superset
* of the region described by child
*/
static int im_region_is_superset(unsigned long v_addr, unsigned long size,
struct vm_struct *child)
{
struct vm_struct parent;
parent.addr = (void *) v_addr;
parent.size = size;
return im_region_is_subset((unsigned long) child->addr, child->size,
&parent);
}
/* Return whether the region described by v_addr and size overlaps
* the region described by vm. Overlapping regions meet the
* following conditions:
* 1) The regions share some part of the address space
* 2) The regions aren't identical
* 3) The first region is not a subset of the second
* 3) Neither region is a subset of the other
*/
static inline int im_region_overlaps(unsigned long v_addr, unsigned long size,
static int im_region_overlaps(unsigned long v_addr, unsigned long size,
struct vm_struct *vm)
{
if (im_region_is_superset(v_addr, size, vm))
return 0;
return (v_addr + size > (unsigned long) vm->addr + vm->size &&
v_addr < (unsigned long) vm->addr + vm->size) ||
(v_addr < (unsigned long) vm->addr &&
v_addr + size > (unsigned long) vm->addr);
}
/* Return whether the region described by v_addr and size is a subset
* of the region described by vm
*/
static inline int im_region_is_subset(unsigned long v_addr, unsigned long size,
struct vm_struct *vm)
{
return (int) (v_addr >= (unsigned long) vm->addr &&
v_addr < (unsigned long) vm->addr + vm->size &&
size < vm->size);
}
/* Determine imalloc status of region described by v_addr and size.
* Can return one of the following:
* IM_REGION_UNUSED - Entire region is unallocated in imalloc space.
......@@ -73,8 +91,9 @@ static inline int im_region_is_subset(unsigned long v_addr, unsigned long size,
* IM_REGION_EXISTS - Exact region already allocated in imalloc space.
* vm will be assigned to a ptr to the existing imlist
* member.
* IM_REGION_OVERLAPS - A portion of the region is already allocated in
* imalloc space.
* IM_REGION_OVERLAPS - Region overlaps an allocated region in imalloc space.
* IM_REGION_SUPERSET - Region is a superset of a region that is already
* allocated in imalloc space.
*/
static int im_region_status(unsigned long v_addr, unsigned long size,
struct vm_struct **vm)
......@@ -90,12 +109,20 @@ static int im_region_status(unsigned long v_addr, unsigned long size,
return IM_REGION_OVERLAP;
*vm = tmp;
if (im_region_is_subset(v_addr, size, tmp))
if (im_region_is_subset(v_addr, size, tmp)) {
/* Return with tmp pointing to superset */
return IM_REGION_SUBSET;
}
if (im_region_is_superset(v_addr, size, tmp)) {
/* Return with tmp pointing to first subset */
return IM_REGION_SUPERSET;
}
else if (v_addr == (unsigned long) tmp->addr &&
size == tmp->size)
size == tmp->size) {
/* Return with tmp pointing to exact region */
return IM_REGION_EXISTS;
}
}
*vm = NULL;
return IM_REGION_UNUSED;
......@@ -208,6 +235,10 @@ static struct vm_struct * __im_get_area(unsigned long req_addr,
tmp = split_im_region(req_addr, size, tmp);
break;
case IM_REGION_EXISTS:
/* Return requested region */
break;
case IM_REGION_SUPERSET:
/* Return first existing subset of requested region */
break;
default:
printk(KERN_ERR "%s() unexpected imalloc region status\n",
......
......@@ -392,9 +392,28 @@ void iounmap(void *addr)
return;
}
static int iounmap_subset_regions(void *addr, unsigned long size)
{
struct vm_struct *area;
/* Check whether subsets of this region exist */
area = im_get_area((unsigned long) addr, size, IM_REGION_SUPERSET);
if (area == NULL)
return 1;
while (area) {
iounmap(area->addr);
area = im_get_area((unsigned long) addr, size,
IM_REGION_SUPERSET);
}
return 0;
}
int iounmap_explicit(void *addr, unsigned long size)
{
struct vm_struct *area;
int rc;
/* addr could be in EEH or IO region, map it to IO region regardless.
*/
......@@ -407,10 +426,17 @@ int iounmap_explicit(void *addr, unsigned long size)
area = im_get_area((unsigned long) addr, size,
IM_REGION_EXISTS | IM_REGION_SUBSET);
if (area == NULL) {
printk(KERN_ERR "%s() cannot unmap nonexistent range 0x%lx\n",
/* Determine whether subset regions exist. If so, unmap */
rc = iounmap_subset_regions(addr, size);
if (rc) {
printk(KERN_ERR
"%s() cannot unmap nonexistent range 0x%lx\n",
__FUNCTION__, (unsigned long) addr);
return 1;
}
} else {
iounmap(area->addr);
}
iounmap(area->addr);
return 0;
......
......@@ -497,6 +497,7 @@ extern void hpte_init_iSeries(void);
#define IM_REGION_SUBSET 0x2
#define IM_REGION_EXISTS 0x4
#define IM_REGION_OVERLAP 0x8
#define IM_REGION_SUPERSET 0x10
extern struct vm_struct * im_get_free_area(unsigned long size);
extern struct vm_struct * im_get_area(unsigned long v_addr, unsigned long size,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment