* 'release' of git://git.kernel.org/pub/scm/linux/kernel/git/lenb/linux: cpuidle: Single/Global registration of idle states cpuidle: Split cpuidle_state structure and move per-cpu statistics fields cpuidle: Remove CPUIDLE_FLAG_IGNORE and dev->prepare() cpuidle: Move dev->last_residency update to driver enter routine; remove dev->last_state ACPI: Fix CONFIG_ACPI_DOCK=n compiler warning ACPI: Export FADT pm_profile integer value to userspace thermal: Prevent polling from happening during system suspend ACPI: Drop ACPI_NO_HARDWARE_INIT ACPI atomicio: Convert width in bits to bytes in __acpi_ioremap_fast() PNPACPI: Simplify disabled resource registration ACPI: Fix possible recursive locking in hwregs.c ACPI: use kstrdup() mrst pmu: update comment tools/power turbostat: less verbose debugging
		
			
				
	
	
		
			365 lines
		
	
	
	
		
			8.3 KiB
			
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			365 lines
		
	
	
	
		
			8.3 KiB
			
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
 * atomicio.c - ACPI IO memory pre-mapping/post-unmapping, then
 | 
						|
 * accessing in atomic context.
 | 
						|
 *
 | 
						|
 * This is used for NMI handler to access IO memory area, because
 | 
						|
 * ioremap/iounmap can not be used in NMI handler. The IO memory area
 | 
						|
 * is pre-mapped in process context and accessed in NMI handler.
 | 
						|
 *
 | 
						|
 * Copyright (C) 2009-2010, Intel Corp.
 | 
						|
 *	Author: Huang Ying <ying.huang@intel.com>
 | 
						|
 *
 | 
						|
 * This program is free software; you can redistribute it and/or
 | 
						|
 * modify it under the terms of the GNU General Public License version
 | 
						|
 * 2 as published by the Free Software Foundation.
 | 
						|
 *
 | 
						|
 * This program is distributed in the hope that it will be useful,
 | 
						|
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
						|
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
						|
 * GNU General Public License for more details.
 | 
						|
 *
 | 
						|
 * You should have received a copy of the GNU General Public License
 | 
						|
 * along with this program; if not, write to the Free Software
 | 
						|
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 | 
						|
 */
 | 
						|
 | 
						|
#include <linux/kernel.h>
 | 
						|
#include <linux/export.h>
 | 
						|
#include <linux/init.h>
 | 
						|
#include <linux/acpi.h>
 | 
						|
#include <linux/io.h>
 | 
						|
#include <linux/kref.h>
 | 
						|
#include <linux/rculist.h>
 | 
						|
#include <linux/interrupt.h>
 | 
						|
#include <linux/slab.h>
 | 
						|
#include <acpi/atomicio.h>
 | 
						|
 | 
						|
#define ACPI_PFX "ACPI: "
 | 
						|
 | 
						|
static LIST_HEAD(acpi_iomaps);
 | 
						|
/*
 | 
						|
 * Used for mutual exclusion between writers of acpi_iomaps list, for
 | 
						|
 * synchronization between readers and writer, RCU is used.
 | 
						|
 */
 | 
						|
static DEFINE_SPINLOCK(acpi_iomaps_lock);
 | 
						|
 | 
						|
struct acpi_iomap {
 | 
						|
	struct list_head list;
 | 
						|
	void __iomem *vaddr;
 | 
						|
	unsigned long size;
 | 
						|
	phys_addr_t paddr;
 | 
						|
	struct kref ref;
 | 
						|
};
 | 
						|
 | 
						|
/* acpi_iomaps_lock or RCU read lock must be held before calling */
 | 
						|
static struct acpi_iomap *__acpi_find_iomap(phys_addr_t paddr,
 | 
						|
					    unsigned long size)
 | 
						|
{
 | 
						|
	struct acpi_iomap *map;
 | 
						|
 | 
						|
	list_for_each_entry_rcu(map, &acpi_iomaps, list) {
 | 
						|
		if (map->paddr + map->size >= paddr + size &&
 | 
						|
		    map->paddr <= paddr)
 | 
						|
			return map;
 | 
						|
	}
 | 
						|
	return NULL;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Atomic "ioremap" used by NMI handler, if the specified IO memory
 | 
						|
 * area is not pre-mapped, NULL will be returned.
 | 
						|
 *
 | 
						|
 * acpi_iomaps_lock or RCU read lock must be held before calling
 | 
						|
 */
 | 
						|
static void __iomem *__acpi_ioremap_fast(phys_addr_t paddr,
 | 
						|
					 unsigned long size)
 | 
						|
{
 | 
						|
	struct acpi_iomap *map;
 | 
						|
 | 
						|
	map = __acpi_find_iomap(paddr, size/8);
 | 
						|
	if (map)
 | 
						|
		return map->vaddr + (paddr - map->paddr);
 | 
						|
	else
 | 
						|
		return NULL;
 | 
						|
}
 | 
						|
 | 
						|
/* acpi_iomaps_lock must be held before calling */
 | 
						|
static void __iomem *__acpi_try_ioremap(phys_addr_t paddr,
 | 
						|
					unsigned long size)
 | 
						|
{
 | 
						|
	struct acpi_iomap *map;
 | 
						|
 | 
						|
	map = __acpi_find_iomap(paddr, size);
 | 
						|
	if (map) {
 | 
						|
		kref_get(&map->ref);
 | 
						|
		return map->vaddr + (paddr - map->paddr);
 | 
						|
	} else
 | 
						|
		return NULL;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Used to pre-map the specified IO memory area. First try to find
 | 
						|
 * whether the area is already pre-mapped, if it is, increase the
 | 
						|
 * reference count (in __acpi_try_ioremap) and return; otherwise, do
 | 
						|
 * the real ioremap, and add the mapping into acpi_iomaps list.
 | 
						|
 */
 | 
						|
static void __iomem *acpi_pre_map(phys_addr_t paddr,
 | 
						|
				  unsigned long size)
 | 
						|
{
 | 
						|
	void __iomem *vaddr;
 | 
						|
	struct acpi_iomap *map;
 | 
						|
	unsigned long pg_sz, flags;
 | 
						|
	phys_addr_t pg_off;
 | 
						|
 | 
						|
	spin_lock_irqsave(&acpi_iomaps_lock, flags);
 | 
						|
	vaddr = __acpi_try_ioremap(paddr, size);
 | 
						|
	spin_unlock_irqrestore(&acpi_iomaps_lock, flags);
 | 
						|
	if (vaddr)
 | 
						|
		return vaddr;
 | 
						|
 | 
						|
	pg_off = paddr & PAGE_MASK;
 | 
						|
	pg_sz = ((paddr + size + PAGE_SIZE - 1) & PAGE_MASK) - pg_off;
 | 
						|
	vaddr = ioremap(pg_off, pg_sz);
 | 
						|
	if (!vaddr)
 | 
						|
		return NULL;
 | 
						|
	map = kmalloc(sizeof(*map), GFP_KERNEL);
 | 
						|
	if (!map)
 | 
						|
		goto err_unmap;
 | 
						|
	INIT_LIST_HEAD(&map->list);
 | 
						|
	map->paddr = pg_off;
 | 
						|
	map->size = pg_sz;
 | 
						|
	map->vaddr = vaddr;
 | 
						|
	kref_init(&map->ref);
 | 
						|
 | 
						|
	spin_lock_irqsave(&acpi_iomaps_lock, flags);
 | 
						|
	vaddr = __acpi_try_ioremap(paddr, size);
 | 
						|
	if (vaddr) {
 | 
						|
		spin_unlock_irqrestore(&acpi_iomaps_lock, flags);
 | 
						|
		iounmap(map->vaddr);
 | 
						|
		kfree(map);
 | 
						|
		return vaddr;
 | 
						|
	}
 | 
						|
	list_add_tail_rcu(&map->list, &acpi_iomaps);
 | 
						|
	spin_unlock_irqrestore(&acpi_iomaps_lock, flags);
 | 
						|
 | 
						|
	return map->vaddr + (paddr - map->paddr);
 | 
						|
err_unmap:
 | 
						|
	iounmap(vaddr);
 | 
						|
	return NULL;
 | 
						|
}
 | 
						|
 | 
						|
/* acpi_iomaps_lock must be held before calling */
 | 
						|
static void __acpi_kref_del_iomap(struct kref *ref)
 | 
						|
{
 | 
						|
	struct acpi_iomap *map;
 | 
						|
 | 
						|
	map = container_of(ref, struct acpi_iomap, ref);
 | 
						|
	list_del_rcu(&map->list);
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Used to post-unmap the specified IO memory area. The iounmap is
 | 
						|
 * done only if the reference count goes zero.
 | 
						|
 */
 | 
						|
static void acpi_post_unmap(phys_addr_t paddr, unsigned long size)
 | 
						|
{
 | 
						|
	struct acpi_iomap *map;
 | 
						|
	unsigned long flags;
 | 
						|
	int del;
 | 
						|
 | 
						|
	spin_lock_irqsave(&acpi_iomaps_lock, flags);
 | 
						|
	map = __acpi_find_iomap(paddr, size);
 | 
						|
	BUG_ON(!map);
 | 
						|
	del = kref_put(&map->ref, __acpi_kref_del_iomap);
 | 
						|
	spin_unlock_irqrestore(&acpi_iomaps_lock, flags);
 | 
						|
 | 
						|
	if (!del)
 | 
						|
		return;
 | 
						|
 | 
						|
	synchronize_rcu();
 | 
						|
	iounmap(map->vaddr);
 | 
						|
	kfree(map);
 | 
						|
}
 | 
						|
 | 
						|
/* In NMI handler, should set silent = 1 */
 | 
						|
static int acpi_check_gar(struct acpi_generic_address *reg,
 | 
						|
			  u64 *paddr, int silent)
 | 
						|
{
 | 
						|
	u32 width, space_id;
 | 
						|
 | 
						|
	width = reg->bit_width;
 | 
						|
	space_id = reg->space_id;
 | 
						|
	/* Handle possible alignment issues */
 | 
						|
	memcpy(paddr, ®->address, sizeof(*paddr));
 | 
						|
	if (!*paddr) {
 | 
						|
		if (!silent)
 | 
						|
			pr_warning(FW_BUG ACPI_PFX
 | 
						|
			"Invalid physical address in GAR [0x%llx/%u/%u]\n",
 | 
						|
				   *paddr, width, space_id);
 | 
						|
		return -EINVAL;
 | 
						|
	}
 | 
						|
 | 
						|
	if ((width != 8) && (width != 16) && (width != 32) && (width != 64)) {
 | 
						|
		if (!silent)
 | 
						|
			pr_warning(FW_BUG ACPI_PFX
 | 
						|
				   "Invalid bit width in GAR [0x%llx/%u/%u]\n",
 | 
						|
				   *paddr, width, space_id);
 | 
						|
		return -EINVAL;
 | 
						|
	}
 | 
						|
 | 
						|
	if (space_id != ACPI_ADR_SPACE_SYSTEM_MEMORY &&
 | 
						|
	    space_id != ACPI_ADR_SPACE_SYSTEM_IO) {
 | 
						|
		if (!silent)
 | 
						|
			pr_warning(FW_BUG ACPI_PFX
 | 
						|
			"Invalid address space type in GAR [0x%llx/%u/%u]\n",
 | 
						|
				   *paddr, width, space_id);
 | 
						|
		return -EINVAL;
 | 
						|
	}
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
/* Pre-map, working on GAR */
 | 
						|
int acpi_pre_map_gar(struct acpi_generic_address *reg)
 | 
						|
{
 | 
						|
	u64 paddr;
 | 
						|
	void __iomem *vaddr;
 | 
						|
	int rc;
 | 
						|
 | 
						|
	if (reg->space_id != ACPI_ADR_SPACE_SYSTEM_MEMORY)
 | 
						|
		return 0;
 | 
						|
 | 
						|
	rc = acpi_check_gar(reg, &paddr, 0);
 | 
						|
	if (rc)
 | 
						|
		return rc;
 | 
						|
 | 
						|
	vaddr = acpi_pre_map(paddr, reg->bit_width / 8);
 | 
						|
	if (!vaddr)
 | 
						|
		return -EIO;
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
EXPORT_SYMBOL_GPL(acpi_pre_map_gar);
 | 
						|
 | 
						|
/* Post-unmap, working on GAR */
 | 
						|
int acpi_post_unmap_gar(struct acpi_generic_address *reg)
 | 
						|
{
 | 
						|
	u64 paddr;
 | 
						|
	int rc;
 | 
						|
 | 
						|
	if (reg->space_id != ACPI_ADR_SPACE_SYSTEM_MEMORY)
 | 
						|
		return 0;
 | 
						|
 | 
						|
	rc = acpi_check_gar(reg, &paddr, 0);
 | 
						|
	if (rc)
 | 
						|
		return rc;
 | 
						|
 | 
						|
	acpi_post_unmap(paddr, reg->bit_width / 8);
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
EXPORT_SYMBOL_GPL(acpi_post_unmap_gar);
 | 
						|
 | 
						|
/*
 | 
						|
 * Can be used in atomic (including NMI) or process context. RCU read
 | 
						|
 * lock can only be released after the IO memory area accessing.
 | 
						|
 */
 | 
						|
static int acpi_atomic_read_mem(u64 paddr, u64 *val, u32 width)
 | 
						|
{
 | 
						|
	void __iomem *addr;
 | 
						|
 | 
						|
	rcu_read_lock();
 | 
						|
	addr = __acpi_ioremap_fast(paddr, width);
 | 
						|
	switch (width) {
 | 
						|
	case 8:
 | 
						|
		*val = readb(addr);
 | 
						|
		break;
 | 
						|
	case 16:
 | 
						|
		*val = readw(addr);
 | 
						|
		break;
 | 
						|
	case 32:
 | 
						|
		*val = readl(addr);
 | 
						|
		break;
 | 
						|
#ifdef readq
 | 
						|
	case 64:
 | 
						|
		*val = readq(addr);
 | 
						|
		break;
 | 
						|
#endif
 | 
						|
	default:
 | 
						|
		return -EINVAL;
 | 
						|
	}
 | 
						|
	rcu_read_unlock();
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static int acpi_atomic_write_mem(u64 paddr, u64 val, u32 width)
 | 
						|
{
 | 
						|
	void __iomem *addr;
 | 
						|
 | 
						|
	rcu_read_lock();
 | 
						|
	addr = __acpi_ioremap_fast(paddr, width);
 | 
						|
	switch (width) {
 | 
						|
	case 8:
 | 
						|
		writeb(val, addr);
 | 
						|
		break;
 | 
						|
	case 16:
 | 
						|
		writew(val, addr);
 | 
						|
		break;
 | 
						|
	case 32:
 | 
						|
		writel(val, addr);
 | 
						|
		break;
 | 
						|
#ifdef writeq
 | 
						|
	case 64:
 | 
						|
		writeq(val, addr);
 | 
						|
		break;
 | 
						|
#endif
 | 
						|
	default:
 | 
						|
		return -EINVAL;
 | 
						|
	}
 | 
						|
	rcu_read_unlock();
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
/* GAR accessing in atomic (including NMI) or process context */
 | 
						|
int acpi_atomic_read(u64 *val, struct acpi_generic_address *reg)
 | 
						|
{
 | 
						|
	u64 paddr;
 | 
						|
	int rc;
 | 
						|
 | 
						|
	rc = acpi_check_gar(reg, &paddr, 1);
 | 
						|
	if (rc)
 | 
						|
		return rc;
 | 
						|
 | 
						|
	*val = 0;
 | 
						|
	switch (reg->space_id) {
 | 
						|
	case ACPI_ADR_SPACE_SYSTEM_MEMORY:
 | 
						|
		return acpi_atomic_read_mem(paddr, val, reg->bit_width);
 | 
						|
	case ACPI_ADR_SPACE_SYSTEM_IO:
 | 
						|
		return acpi_os_read_port(paddr, (u32 *)val, reg->bit_width);
 | 
						|
	default:
 | 
						|
		return -EINVAL;
 | 
						|
	}
 | 
						|
}
 | 
						|
EXPORT_SYMBOL_GPL(acpi_atomic_read);
 | 
						|
 | 
						|
int acpi_atomic_write(u64 val, struct acpi_generic_address *reg)
 | 
						|
{
 | 
						|
	u64 paddr;
 | 
						|
	int rc;
 | 
						|
 | 
						|
	rc = acpi_check_gar(reg, &paddr, 1);
 | 
						|
	if (rc)
 | 
						|
		return rc;
 | 
						|
 | 
						|
	switch (reg->space_id) {
 | 
						|
	case ACPI_ADR_SPACE_SYSTEM_MEMORY:
 | 
						|
		return acpi_atomic_write_mem(paddr, val, reg->bit_width);
 | 
						|
	case ACPI_ADR_SPACE_SYSTEM_IO:
 | 
						|
		return acpi_os_write_port(paddr, val, reg->bit_width);
 | 
						|
	default:
 | 
						|
		return -EINVAL;
 | 
						|
	}
 | 
						|
}
 | 
						|
EXPORT_SYMBOL_GPL(acpi_atomic_write);
 |