From b3ba1efec2a58f4dc0647f4c0099c27d6ab92595 Mon Sep 17 00:00:00 2001 From: Myron Stowe Date: Thu, 21 Oct 2010 14:23:48 -0600 Subject: ACPI: Fix ioremap size for MMIO reads and writes The size used for I/O remapping MMIO read and write accesses has not accounted for the basis of ACPI's Generic Address Structure (GAS) 'Register Bit Width' field which is bits, not bytes. This patch adjusts the ioremap() 'size' argument accordingly. ACPI "Generic Register" reference: ACPI Specification, Revision 4.0, Section 5.2.3.1, "Generic Address Structure". Signed-off-by: Myron Stowe Signed-off-by: Len Brown --- drivers/acpi/osl.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers/acpi/osl.c') diff --git a/drivers/acpi/osl.c b/drivers/acpi/osl.c index 65b25a303b86..58842fb9fd90 100644 --- a/drivers/acpi/osl.c +++ b/drivers/acpi/osl.c @@ -496,7 +496,7 @@ acpi_os_read_memory(acpi_physical_address phys_addr, u32 * value, u32 width) u32 dummy; void __iomem *virt_addr; - virt_addr = ioremap(phys_addr, width); + virt_addr = ioremap(phys_addr, width / 8); if (!value) value = &dummy; @@ -524,7 +524,7 @@ acpi_os_write_memory(acpi_physical_address phys_addr, u32 value, u32 width) { void __iomem *virt_addr; - virt_addr = ioremap(phys_addr, width); + virt_addr = ioremap(phys_addr, width / 8); switch (width) { case 8: -- cgit v1.2.3 From 620242ae8c3d9c0b1a77451744fb2d855d1e7342 Mon Sep 17 00:00:00 2001 From: Myron Stowe Date: Thu, 21 Oct 2010 14:23:53 -0600 Subject: ACPI: Maintain a list of ACPI memory mapped I/O remappings For memory mapped I/O (MMIO) remappings, add a list to maintain the remappings and augment the corresponding mapping and unmapping interface routines (acpi_os_map_memory() and acpi_os_unmap_memory()) to dynamically add to, and delete from, the list. The current ACPI I/O accessing methods - acpi_read() and acpi_write() - end up calling ioremap() when accessing MMIO. This prevents use of these methods within interrupt context (IRQ and/or NMI), since ioremap() may block to allocate memory. Maintaining a list of MMIO remappings enables accesses to such areas from within interrupt context provided they have been pre-mapped. Signed-off-by: Myron Stowe Signed-off-by: Len Brown --- drivers/acpi/osl.c | 128 ++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 113 insertions(+), 15 deletions(-) (limited to 'drivers/acpi/osl.c') diff --git a/drivers/acpi/osl.c b/drivers/acpi/osl.c index 58842fb9fd90..bd72129e35f2 100644 --- a/drivers/acpi/osl.c +++ b/drivers/acpi/osl.c @@ -95,6 +95,20 @@ struct acpi_res_list { static LIST_HEAD(resource_list_head); static DEFINE_SPINLOCK(acpi_res_lock); +/* + * This list of permanent mappings is for memory that may be accessed from + * interrupt context, where we can't do the ioremap(). + */ +struct acpi_ioremap { + struct list_head list; + void __iomem *virt; + acpi_physical_address phys; + acpi_size size; +}; + +static LIST_HEAD(acpi_ioremaps); +static DEFINE_SPINLOCK(acpi_ioremap_lock); + #define OSI_STRING_LENGTH_MAX 64 /* arbitrary */ static char osi_additional_string[OSI_STRING_LENGTH_MAX]; @@ -260,29 +274,95 @@ acpi_physical_address __init acpi_os_get_root_pointer(void) } } +/* Must be called with 'acpi_ioremap_lock' lock held. */ +static void __iomem * +acpi_map_vaddr_lookup(acpi_physical_address phys, acpi_size size) +{ + struct acpi_ioremap *map; + + list_for_each_entry(map, &acpi_ioremaps, list) + if (map->phys <= phys && + phys + size <= map->phys + map->size) + return map->virt + (phys - map->phys); + + return NULL; +} + +/* Must be called with 'acpi_ioremap_lock' lock held. */ +static struct acpi_ioremap * +acpi_map_lookup_virt(void __iomem *virt, acpi_size size) +{ + struct acpi_ioremap *map; + + list_for_each_entry(map, &acpi_ioremaps, list) + if (map->virt == virt && map->size == size) + return map; + + return NULL; +} + void __iomem *__init_refok acpi_os_map_memory(acpi_physical_address phys, acpi_size size) { + struct acpi_ioremap *map; + unsigned long flags; + void __iomem *virt; + if (phys > ULONG_MAX) { printk(KERN_ERR PREFIX "Cannot map memory that high\n"); return NULL; } - if (acpi_gbl_permanent_mmap) - /* - * ioremap checks to ensure this is in reserved space - */ - return ioremap((unsigned long)phys, size); - else + + if (!acpi_gbl_permanent_mmap) return __acpi_map_table((unsigned long)phys, size); + + map = kzalloc(sizeof(*map), GFP_KERNEL); + if (!map) + return NULL; + + virt = ioremap(phys, size); + if (!virt) { + kfree(map); + return NULL; + } + + INIT_LIST_HEAD(&map->list); + map->virt = virt; + map->phys = phys; + map->size = size; + + spin_lock_irqsave(&acpi_ioremap_lock, flags); + list_add_tail(&map->list, &acpi_ioremaps); + spin_unlock_irqrestore(&acpi_ioremap_lock, flags); + + return virt; } EXPORT_SYMBOL_GPL(acpi_os_map_memory); void __ref acpi_os_unmap_memory(void __iomem *virt, acpi_size size) { - if (acpi_gbl_permanent_mmap) - iounmap(virt); - else + struct acpi_ioremap *map; + unsigned long flags; + + if (!acpi_gbl_permanent_mmap) { __acpi_unmap_table(virt, size); + return; + } + + spin_lock_irqsave(&acpi_ioremap_lock, flags); + map = acpi_map_lookup_virt(virt, size); + if (!map) { + spin_unlock_irqrestore(&acpi_ioremap_lock, flags); + printk(KERN_ERR PREFIX "%s: bad address %p\n", __func__, virt); + dump_stack(); + return; + } + + list_del(&map->list); + spin_unlock_irqrestore(&acpi_ioremap_lock, flags); + + iounmap(map->virt); + kfree(map); } EXPORT_SYMBOL_GPL(acpi_os_unmap_memory); @@ -495,8 +575,16 @@ acpi_os_read_memory(acpi_physical_address phys_addr, u32 * value, u32 width) { u32 dummy; void __iomem *virt_addr; - - virt_addr = ioremap(phys_addr, width / 8); + int size = width / 8, unmap = 0; + unsigned long flags; + + spin_lock_irqsave(&acpi_ioremap_lock, flags); + virt_addr = acpi_map_vaddr_lookup(phys_addr, size); + spin_unlock_irqrestore(&acpi_ioremap_lock, flags); + if (!virt_addr) { + virt_addr = ioremap(phys_addr, size); + unmap = 1; + } if (!value) value = &dummy; @@ -514,7 +602,8 @@ acpi_os_read_memory(acpi_physical_address phys_addr, u32 * value, u32 width) BUG(); } - iounmap(virt_addr); + if (unmap) + iounmap(virt_addr); return AE_OK; } @@ -523,8 +612,16 @@ acpi_status acpi_os_write_memory(acpi_physical_address phys_addr, u32 value, u32 width) { void __iomem *virt_addr; - - virt_addr = ioremap(phys_addr, width / 8); + int size = width / 8, unmap = 0; + unsigned long flags; + + spin_lock_irqsave(&acpi_ioremap_lock, flags); + virt_addr = acpi_map_vaddr_lookup(phys_addr, size); + spin_unlock_irqrestore(&acpi_ioremap_lock, flags); + if (!virt_addr) { + virt_addr = ioremap(phys_addr, size); + unmap = 1; + } switch (width) { case 8: @@ -540,7 +637,8 @@ acpi_os_write_memory(acpi_physical_address phys_addr, u32 value, u32 width) BUG(); } - iounmap(virt_addr); + if (unmap) + iounmap(virt_addr); return AE_OK; } -- cgit v1.2.3 From 29718521237a1b1607ea05b49243100ea2044337 Mon Sep 17 00:00:00 2001 From: Myron Stowe Date: Thu, 21 Oct 2010 14:23:59 -0600 Subject: ACPI: Add interfaces for ioremapping/iounmapping ACPI registers Add remapping and unmapping interfaces for ACPI registers that are backed by memory mapped I/O (MMIO). These interfaces, along with the MMIO remapping list, enable accesses of such registers from within interrupt context. ACPI Generic Address Structure (GAS) reference (ACPI's fixed/generic hardware registers use the GAS format): ACPI Specification, Revision 4.0, Section 5.2.3.1, "Generic Address Structure". Signed-off-by: Myron Stowe Signed-off-by: Len Brown --- drivers/acpi/osl.c | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) (limited to 'drivers/acpi/osl.c') diff --git a/drivers/acpi/osl.c b/drivers/acpi/osl.c index bd72129e35f2..fc6c5d21c3eb 100644 --- a/drivers/acpi/osl.c +++ b/drivers/acpi/osl.c @@ -372,6 +372,44 @@ void __init early_acpi_os_unmap_memory(void __iomem *virt, acpi_size size) __acpi_unmap_table(virt, size); } +int acpi_os_map_generic_address(struct acpi_generic_address *addr) +{ + void __iomem *virt; + + if (addr->space_id != ACPI_ADR_SPACE_SYSTEM_MEMORY) + return 0; + + if (!addr->address || !addr->bit_width) + return -EINVAL; + + virt = acpi_os_map_memory(addr->address, addr->bit_width / 8); + if (!virt) + return -EIO; + + return 0; +} +EXPORT_SYMBOL_GPL(acpi_os_map_generic_address); + +void acpi_os_unmap_generic_address(struct acpi_generic_address *addr) +{ + void __iomem *virt; + unsigned long flags; + acpi_size size = addr->bit_width / 8; + + if (addr->space_id != ACPI_ADR_SPACE_SYSTEM_MEMORY) + return; + + if (!addr->address || !addr->bit_width) + return; + + spin_lock_irqsave(&acpi_ioremap_lock, flags); + virt = acpi_map_vaddr_lookup(addr->address, size); + spin_unlock_irqrestore(&acpi_ioremap_lock, flags); + + acpi_os_unmap_memory(virt, size); +} +EXPORT_SYMBOL_GPL(acpi_os_unmap_generic_address); + #ifdef ACPI_FUTURE_USAGE acpi_status acpi_os_get_physical_address(void *virt, acpi_physical_address * phys) -- cgit v1.2.3 From d362edaf5386acedad4319a6721bb1540b74dcf7 Mon Sep 17 00:00:00 2001 From: Myron Stowe Date: Thu, 21 Oct 2010 14:24:04 -0600 Subject: ACPI: Pre-map 'system event' related register blocks During ACPI initialization, pre-map fixed hardware registers that are accessed during ACPI's 'system event' related IRQ handing. ACPI's 'system event' handing accesses specific fixed hardware registers; namely PM1a event, PM1b event, GPE0, and GPE1 register blocks which are declared within the FADT. If these registers are backed by MMIO, as opposed to I/O port space, accessing them within interrupt context will cause a panic as acpi_os_read_memory() depends on ioremap() in such cases - BZ 18012. By utilizing the functionality provided in the previous two patches - ACPI: Maintain a list of ACPI memory mapped I/O remappings, and, ACPI: Add interfaces for ioremapping/iounmapping ACPI registers - accesses to ACPI MMIO areas will now be safe from within interrupt contexts (IRQ and/or NMI) provided the area was pre-mapped. This solves BZ 18012. ACPI "System Event" reference(s): ACPI Specification, Revision 4.0, Section 3 "ACPI Overview", 3.8 "System Events", 5.6 "ACPI Event Programming Model". Reference: https://bugzilla.kernel.org/show_bug.cgi?id=18012 Reported-by: Signed-off-by: Myron Stowe Signed-off-by: Len Brown --- drivers/acpi/osl.c | 71 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 40 insertions(+), 31 deletions(-) (limited to 'drivers/acpi/osl.c') diff --git a/drivers/acpi/osl.c b/drivers/acpi/osl.c index fc6c5d21c3eb..c63d4cb37dab 100644 --- a/drivers/acpi/osl.c +++ b/drivers/acpi/osl.c @@ -199,36 +199,6 @@ static int __init acpi_reserve_resources(void) } device_initcall(acpi_reserve_resources); -acpi_status __init acpi_os_initialize(void) -{ - return AE_OK; -} - -acpi_status acpi_os_initialize1(void) -{ - kacpid_wq = create_workqueue("kacpid"); - kacpi_notify_wq = create_workqueue("kacpi_notify"); - kacpi_hotplug_wq = create_workqueue("kacpi_hotplug"); - BUG_ON(!kacpid_wq); - BUG_ON(!kacpi_notify_wq); - BUG_ON(!kacpi_hotplug_wq); - return AE_OK; -} - -acpi_status acpi_os_terminate(void) -{ - if (acpi_irq_handler) { - acpi_os_remove_interrupt_handler(acpi_irq_irq, - acpi_irq_handler); - } - - destroy_workqueue(kacpid_wq); - destroy_workqueue(kacpi_notify_wq); - destroy_workqueue(kacpi_hotplug_wq); - - return AE_OK; -} - void acpi_os_printf(const char *fmt, ...) { va_list args; @@ -1598,5 +1568,44 @@ acpi_os_validate_address ( } return AE_OK; } - #endif + +acpi_status __init acpi_os_initialize(void) +{ + acpi_os_map_generic_address(&acpi_gbl_FADT.xpm1a_event_block); + acpi_os_map_generic_address(&acpi_gbl_FADT.xpm1b_event_block); + acpi_os_map_generic_address(&acpi_gbl_FADT.xgpe0_block); + acpi_os_map_generic_address(&acpi_gbl_FADT.xgpe1_block); + + return AE_OK; +} + +acpi_status acpi_os_initialize1(void) +{ + kacpid_wq = create_workqueue("kacpid"); + kacpi_notify_wq = create_workqueue("kacpi_notify"); + kacpi_hotplug_wq = create_workqueue("kacpi_hotplug"); + BUG_ON(!kacpid_wq); + BUG_ON(!kacpi_notify_wq); + BUG_ON(!kacpi_hotplug_wq); + return AE_OK; +} + +acpi_status acpi_os_terminate(void) +{ + if (acpi_irq_handler) { + acpi_os_remove_interrupt_handler(acpi_irq_irq, + acpi_irq_handler); + } + + acpi_os_unmap_generic_address(&acpi_gbl_FADT.xgpe1_block); + acpi_os_unmap_generic_address(&acpi_gbl_FADT.xgpe0_block); + acpi_os_unmap_generic_address(&acpi_gbl_FADT.xpm1b_event_block); + acpi_os_unmap_generic_address(&acpi_gbl_FADT.xpm1a_event_block); + + destroy_workqueue(kacpid_wq); + destroy_workqueue(kacpi_notify_wq); + destroy_workqueue(kacpi_hotplug_wq); + + return AE_OK; +} -- cgit v1.2.3 From 78cdb3ed4053798c894899b15d2255fb880edad4 Mon Sep 17 00:00:00 2001 From: Myron Stowe Date: Thu, 21 Oct 2010 14:24:09 -0600 Subject: ACPI: Convert simple locking to RCU based locking Convert the simple locking introduced earlier for the ACPI MMIO remappings list to an RCU based locking scheme. Signed-off-by: Myron Stowe Signed-off-by: Len Brown --- drivers/acpi/osl.c | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) (limited to 'drivers/acpi/osl.c') diff --git a/drivers/acpi/osl.c b/drivers/acpi/osl.c index c63d4cb37dab..32826893c2e6 100644 --- a/drivers/acpi/osl.c +++ b/drivers/acpi/osl.c @@ -244,13 +244,13 @@ acpi_physical_address __init acpi_os_get_root_pointer(void) } } -/* Must be called with 'acpi_ioremap_lock' lock held. */ +/* Must be called with 'acpi_ioremap_lock' or RCU read lock held. */ static void __iomem * acpi_map_vaddr_lookup(acpi_physical_address phys, acpi_size size) { struct acpi_ioremap *map; - list_for_each_entry(map, &acpi_ioremaps, list) + list_for_each_entry_rcu(map, &acpi_ioremaps, list) if (map->phys <= phys && phys + size <= map->phys + map->size) return map->virt + (phys - map->phys); @@ -258,13 +258,13 @@ acpi_map_vaddr_lookup(acpi_physical_address phys, acpi_size size) return NULL; } -/* Must be called with 'acpi_ioremap_lock' lock held. */ +/* Must be called with 'acpi_ioremap_lock' or RCU read lock held. */ static struct acpi_ioremap * acpi_map_lookup_virt(void __iomem *virt, acpi_size size) { struct acpi_ioremap *map; - list_for_each_entry(map, &acpi_ioremaps, list) + list_for_each_entry_rcu(map, &acpi_ioremaps, list) if (map->virt == virt && map->size == size) return map; @@ -302,7 +302,7 @@ acpi_os_map_memory(acpi_physical_address phys, acpi_size size) map->size = size; spin_lock_irqsave(&acpi_ioremap_lock, flags); - list_add_tail(&map->list, &acpi_ioremaps); + list_add_tail_rcu(&map->list, &acpi_ioremaps); spin_unlock_irqrestore(&acpi_ioremap_lock, flags); return virt; @@ -328,9 +328,10 @@ void __ref acpi_os_unmap_memory(void __iomem *virt, acpi_size size) return; } - list_del(&map->list); + list_del_rcu(&map->list); spin_unlock_irqrestore(&acpi_ioremap_lock, flags); + synchronize_rcu(); iounmap(map->virt); kfree(map); } @@ -584,11 +585,10 @@ acpi_os_read_memory(acpi_physical_address phys_addr, u32 * value, u32 width) u32 dummy; void __iomem *virt_addr; int size = width / 8, unmap = 0; - unsigned long flags; - spin_lock_irqsave(&acpi_ioremap_lock, flags); + rcu_read_lock(); virt_addr = acpi_map_vaddr_lookup(phys_addr, size); - spin_unlock_irqrestore(&acpi_ioremap_lock, flags); + rcu_read_unlock(); if (!virt_addr) { virt_addr = ioremap(phys_addr, size); unmap = 1; @@ -621,11 +621,10 @@ acpi_os_write_memory(acpi_physical_address phys_addr, u32 value, u32 width) { void __iomem *virt_addr; int size = width / 8, unmap = 0; - unsigned long flags; - spin_lock_irqsave(&acpi_ioremap_lock, flags); + rcu_read_lock(); virt_addr = acpi_map_vaddr_lookup(phys_addr, size); - spin_unlock_irqrestore(&acpi_ioremap_lock, flags); + rcu_read_unlock(); if (!virt_addr) { virt_addr = ioremap(phys_addr, size); unmap = 1; -- cgit v1.2.3 From 4a3cba5e72a5232842ff7c1ca691ec3450af64b9 Mon Sep 17 00:00:00 2001 From: Myron Stowe Date: Thu, 21 Oct 2010 14:24:14 -0600 Subject: ACPI: Page based coalescing of I/O remappings optimization This patch optimizes ACPI MMIO remappings by keeping track of the remappings on a PAGE_SIZE granularity. When an ioremap() occurs, the underlying infrastructure works on a 'page' based granularity. As such, an ioremap() request for 1 byte for example, will end up mapping in an entire (PAGE_SIZE) page. Huang Ying took advantage of this in commit 15651291a2f8c11e7e6a42d8bfde7a213ff13262 by checking if subsequent ioremap() requests reside within any of the list's existing remappings still in place, and if so, incrementing a reference count on the existing mapping as opposed to performing another ioremap(). Signed-off-by: Myron Stowe Signed-off-by: Len Brown --- drivers/acpi/osl.c | 62 ++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 51 insertions(+), 11 deletions(-) (limited to 'drivers/acpi/osl.c') diff --git a/drivers/acpi/osl.c b/drivers/acpi/osl.c index 32826893c2e6..885e222bcabd 100644 --- a/drivers/acpi/osl.c +++ b/drivers/acpi/osl.c @@ -104,6 +104,7 @@ struct acpi_ioremap { void __iomem *virt; acpi_physical_address phys; acpi_size size; + struct kref ref; }; static LIST_HEAD(acpi_ioremaps); @@ -245,15 +246,28 @@ acpi_physical_address __init acpi_os_get_root_pointer(void) } /* Must be called with 'acpi_ioremap_lock' or RCU read lock held. */ -static void __iomem * -acpi_map_vaddr_lookup(acpi_physical_address phys, acpi_size size) +static struct acpi_ioremap * +acpi_map_lookup(acpi_physical_address phys, acpi_size size) { struct acpi_ioremap *map; list_for_each_entry_rcu(map, &acpi_ioremaps, list) if (map->phys <= phys && phys + size <= map->phys + map->size) - return map->virt + (phys - map->phys); + return map; + + return NULL; +} + +/* Must be called with 'acpi_ioremap_lock' or RCU read lock held. */ +static void __iomem * +acpi_map_vaddr_lookup(acpi_physical_address phys, unsigned int size) +{ + struct acpi_ioremap *map; + + map = acpi_map_lookup(phys, size); + if (map) + return map->virt + (phys - map->phys); return NULL; } @@ -265,7 +279,8 @@ acpi_map_lookup_virt(void __iomem *virt, acpi_size size) struct acpi_ioremap *map; list_for_each_entry_rcu(map, &acpi_ioremaps, list) - if (map->virt == virt && map->size == size) + if (map->virt <= virt && + virt + size <= map->virt + map->size) return map; return NULL; @@ -274,9 +289,10 @@ acpi_map_lookup_virt(void __iomem *virt, acpi_size size) void __iomem *__init_refok acpi_os_map_memory(acpi_physical_address phys, acpi_size size) { - struct acpi_ioremap *map; - unsigned long flags; + struct acpi_ioremap *map, *tmp_map; + unsigned long flags, pg_sz; void __iomem *virt; + phys_addr_t pg_off; if (phys > ULONG_MAX) { printk(KERN_ERR PREFIX "Cannot map memory that high\n"); @@ -290,7 +306,9 @@ acpi_os_map_memory(acpi_physical_address phys, acpi_size size) if (!map) return NULL; - virt = ioremap(phys, size); + pg_off = round_down(phys, PAGE_SIZE); + pg_sz = round_up(phys + size, PAGE_SIZE) - pg_off; + virt = ioremap(pg_off, pg_sz); if (!virt) { kfree(map); return NULL; @@ -298,21 +316,40 @@ acpi_os_map_memory(acpi_physical_address phys, acpi_size size) INIT_LIST_HEAD(&map->list); map->virt = virt; - map->phys = phys; - map->size = size; + map->phys = pg_off; + map->size = pg_sz; + kref_init(&map->ref); spin_lock_irqsave(&acpi_ioremap_lock, flags); + /* Check if page has already been mapped. */ + tmp_map = acpi_map_lookup(phys, size); + if (tmp_map) { + kref_get(&tmp_map->ref); + spin_unlock_irqrestore(&acpi_ioremap_lock, flags); + iounmap(map->virt); + kfree(map); + return tmp_map->virt + (phys - tmp_map->phys); + } list_add_tail_rcu(&map->list, &acpi_ioremaps); spin_unlock_irqrestore(&acpi_ioremap_lock, flags); - return virt; + return map->virt + (phys - map->phys); } EXPORT_SYMBOL_GPL(acpi_os_map_memory); +static void acpi_kref_del_iomap(struct kref *ref) +{ + struct acpi_ioremap *map; + + map = container_of(ref, struct acpi_ioremap, ref); + list_del_rcu(&map->list); +} + void __ref acpi_os_unmap_memory(void __iomem *virt, acpi_size size) { struct acpi_ioremap *map; unsigned long flags; + int del; if (!acpi_gbl_permanent_mmap) { __acpi_unmap_table(virt, size); @@ -328,9 +365,12 @@ void __ref acpi_os_unmap_memory(void __iomem *virt, acpi_size size) return; } - list_del_rcu(&map->list); + del = kref_put(&map->ref, acpi_kref_del_iomap); spin_unlock_irqrestore(&acpi_ioremap_lock, flags); + if (!del) + return; + synchronize_rcu(); iounmap(map->virt); kfree(map); -- cgit v1.2.3