This provides unified readq()/writeq() helper functions for 32-bit
drivers.
For some cases, readq/writeq without atomicity is harmful, and order of
io access has to be specified explicitly.  So in this patch, new two
header files which contain non-atomic readq/writeq are added.
 - <asm-generic/io-64-nonatomic-lo-hi.h> provides non-atomic readq/
   writeq with the order of lower address -> higher address
 - <asm-generic/io-64-nonatomic-hi-lo.h> provides non-atomic readq/
   writeq with reversed order
This allows us to remove some readq()s that were added drivers when the
default non-atomic ones were removed in commit dbee8a0aff ("x86:
remove 32-bit versions of readq()/writeq()")
The drivers which need readq/writeq but can do with the non-atomic ones
must add the line:
  #include <asm-generic/io-64-nonatomic-lo-hi.h> /* or hi-lo.h */
But this will be nop in 64-bit environments, and no other #ifdefs are
required.  So I believe that this patch can solve the problem of
 1. driver-specific readq/writeq
 2. atomicity and order of io access
This patch is tested with building allyesconfig and allmodconfig as
ARCH=x86 and ARCH=i386 on top of tip/master.
Cc: Kashyap Desai <Kashyap.Desai@lsi.com>
Cc: Len Brown <lenb@kernel.org>
Cc: Ravi Anand <ravi.anand@qlogic.com>
Cc: Vikas Chaudhary <vikas.chaudhary@qlogic.com>
Cc: Matthew Garrett <mjg@redhat.com>
Cc: Jason Uhlenkott <juhlenko@akamai.com>
Cc: James Bottomley <James.Bottomley@parallels.com>
Cc: Thomas Gleixner <tglx@linutronix.de>
Cc: "H. Peter Anvin" <hpa@zytor.com>
Cc: Roland Dreier <roland@purestorage.com>
Cc: James Bottomley <jbottomley@parallels.com>
Cc: Alan Cox <alan@lxorguk.ukuu.org.uk>
Cc: Matthew Wilcox <matthew.r.wilcox@intel.com>
Cc: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Hitoshi Mitake <h.mitake@gmail.com>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
		
	
			
		
			
				
	
	
		
			328 lines
		
	
	
	
		
			8.1 KiB
			
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			328 lines
		
	
	
	
		
			8.1 KiB
			
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
 * IBM Real-Time Linux driver
 | 
						|
 *
 | 
						|
 * This program is free software; you can redistribute it and/or modify
 | 
						|
 * it under the terms of the GNU General Public License as published by
 | 
						|
 * the Free Software Foundation; either version 2 of the License, or
 | 
						|
 * (at your option) any later version.
 | 
						|
 *
 | 
						|
 * 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.
 | 
						|
 *
 | 
						|
 * Copyright (C) IBM Corporation, 2010
 | 
						|
 *
 | 
						|
 * Author: Keith Mannthey <kmannth@us.ibm.com>
 | 
						|
 *         Vernon Mauery <vernux@us.ibm.com>
 | 
						|
 *
 | 
						|
 */
 | 
						|
 | 
						|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
 | 
						|
 | 
						|
#include <linux/kernel.h>
 | 
						|
#include <linux/delay.h>
 | 
						|
#include <linux/module.h>
 | 
						|
#include <linux/io.h>
 | 
						|
#include <linux/dmi.h>
 | 
						|
#include <linux/efi.h>
 | 
						|
#include <linux/mutex.h>
 | 
						|
#include <asm/bios_ebda.h>
 | 
						|
 | 
						|
#include <asm-generic/io-64-nonatomic-lo-hi.h>
 | 
						|
 | 
						|
static bool force;
 | 
						|
module_param(force, bool, 0);
 | 
						|
MODULE_PARM_DESC(force, "Force driver load, ignore DMI data");
 | 
						|
 | 
						|
static bool debug;
 | 
						|
module_param(debug, bool, 0644);
 | 
						|
MODULE_PARM_DESC(debug, "Show debug output");
 | 
						|
 | 
						|
MODULE_LICENSE("GPL");
 | 
						|
MODULE_AUTHOR("Keith Mannthey <kmmanth@us.ibm.com>");
 | 
						|
MODULE_AUTHOR("Vernon Mauery <vernux@us.ibm.com>");
 | 
						|
 | 
						|
#define RTL_ADDR_TYPE_IO    1
 | 
						|
#define RTL_ADDR_TYPE_MMIO  2
 | 
						|
 | 
						|
#define RTL_CMD_ENTER_PRTM  1
 | 
						|
#define RTL_CMD_EXIT_PRTM   2
 | 
						|
 | 
						|
/* The RTL table as presented by the EBDA: */
 | 
						|
struct ibm_rtl_table {
 | 
						|
	char signature[5]; /* signature should be "_RTL_" */
 | 
						|
	u8 version;
 | 
						|
	u8 rt_status;
 | 
						|
	u8 command;
 | 
						|
	u8 command_status;
 | 
						|
	u8 cmd_address_type;
 | 
						|
	u8 cmd_granularity;
 | 
						|
	u8 cmd_offset;
 | 
						|
	u16 reserve1;
 | 
						|
	u32 cmd_port_address; /* platform dependent address */
 | 
						|
	u32 cmd_port_value;   /* platform dependent value */
 | 
						|
} __attribute__((packed));
 | 
						|
 | 
						|
/* to locate "_RTL_" signature do a masked 5-byte integer compare */
 | 
						|
#define RTL_SIGNATURE 0x0000005f4c54525fULL
 | 
						|
#define RTL_MASK      0x000000ffffffffffULL
 | 
						|
 | 
						|
#define RTL_DEBUG(fmt, ...)				\
 | 
						|
do {							\
 | 
						|
	if (debug)					\
 | 
						|
		pr_info(fmt, ##__VA_ARGS__);		\
 | 
						|
} while (0)
 | 
						|
 | 
						|
static DEFINE_MUTEX(rtl_lock);
 | 
						|
static struct ibm_rtl_table __iomem *rtl_table;
 | 
						|
static void __iomem *ebda_map;
 | 
						|
static void __iomem *rtl_cmd_addr;
 | 
						|
static u8 rtl_cmd_type;
 | 
						|
static u8 rtl_cmd_width;
 | 
						|
 | 
						|
static void __iomem *rtl_port_map(phys_addr_t addr, unsigned long len)
 | 
						|
{
 | 
						|
	if (rtl_cmd_type == RTL_ADDR_TYPE_MMIO)
 | 
						|
		return ioremap(addr, len);
 | 
						|
	return ioport_map(addr, len);
 | 
						|
}
 | 
						|
 | 
						|
static void rtl_port_unmap(void __iomem *addr)
 | 
						|
{
 | 
						|
	if (addr && rtl_cmd_type == RTL_ADDR_TYPE_MMIO)
 | 
						|
		iounmap(addr);
 | 
						|
	else
 | 
						|
		ioport_unmap(addr);
 | 
						|
}
 | 
						|
 | 
						|
static int ibm_rtl_write(u8 value)
 | 
						|
{
 | 
						|
	int ret = 0, count = 0;
 | 
						|
	static u32 cmd_port_val;
 | 
						|
 | 
						|
	RTL_DEBUG("%s(%d)\n", __func__, value);
 | 
						|
 | 
						|
	value = value == 1 ? RTL_CMD_ENTER_PRTM : RTL_CMD_EXIT_PRTM;
 | 
						|
 | 
						|
	mutex_lock(&rtl_lock);
 | 
						|
 | 
						|
	if (ioread8(&rtl_table->rt_status) != value) {
 | 
						|
		iowrite8(value, &rtl_table->command);
 | 
						|
 | 
						|
		switch (rtl_cmd_width) {
 | 
						|
		case 8:
 | 
						|
			cmd_port_val = ioread8(&rtl_table->cmd_port_value);
 | 
						|
			RTL_DEBUG("cmd_port_val = %u\n", cmd_port_val);
 | 
						|
			iowrite8((u8)cmd_port_val, rtl_cmd_addr);
 | 
						|
			break;
 | 
						|
		case 16:
 | 
						|
			cmd_port_val = ioread16(&rtl_table->cmd_port_value);
 | 
						|
			RTL_DEBUG("cmd_port_val = %u\n", cmd_port_val);
 | 
						|
			iowrite16((u16)cmd_port_val, rtl_cmd_addr);
 | 
						|
			break;
 | 
						|
		case 32:
 | 
						|
			cmd_port_val = ioread32(&rtl_table->cmd_port_value);
 | 
						|
			RTL_DEBUG("cmd_port_val = %u\n", cmd_port_val);
 | 
						|
			iowrite32(cmd_port_val, rtl_cmd_addr);
 | 
						|
			break;
 | 
						|
		}
 | 
						|
 | 
						|
		while (ioread8(&rtl_table->command)) {
 | 
						|
			msleep(10);
 | 
						|
			if (count++ > 500) {
 | 
						|
				pr_err("Hardware not responding to "
 | 
						|
				       "mode switch request\n");
 | 
						|
				ret = -EIO;
 | 
						|
				break;
 | 
						|
			}
 | 
						|
 | 
						|
		}
 | 
						|
 | 
						|
		if (ioread8(&rtl_table->command_status)) {
 | 
						|
			RTL_DEBUG("command_status reports failed command\n");
 | 
						|
			ret = -EIO;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	mutex_unlock(&rtl_lock);
 | 
						|
	return ret;
 | 
						|
}
 | 
						|
 | 
						|
static ssize_t rtl_show_version(struct device *dev,
 | 
						|
                                struct device_attribute *attr,
 | 
						|
                                char *buf)
 | 
						|
{
 | 
						|
	return sprintf(buf, "%d\n", (int)ioread8(&rtl_table->version));
 | 
						|
}
 | 
						|
 | 
						|
static ssize_t rtl_show_state(struct device *dev,
 | 
						|
                              struct device_attribute *attr,
 | 
						|
                              char *buf)
 | 
						|
{
 | 
						|
	return sprintf(buf, "%d\n", ioread8(&rtl_table->rt_status));
 | 
						|
}
 | 
						|
 | 
						|
static ssize_t rtl_set_state(struct device *dev,
 | 
						|
                             struct device_attribute *attr,
 | 
						|
                             const char *buf,
 | 
						|
                             size_t count)
 | 
						|
{
 | 
						|
	ssize_t ret;
 | 
						|
 | 
						|
	if (count < 1 || count > 2)
 | 
						|
		return -EINVAL;
 | 
						|
 | 
						|
	switch (buf[0]) {
 | 
						|
	case '0':
 | 
						|
		ret = ibm_rtl_write(0);
 | 
						|
		break;
 | 
						|
	case '1':
 | 
						|
		ret = ibm_rtl_write(1);
 | 
						|
		break;
 | 
						|
	default:
 | 
						|
		ret = -EINVAL;
 | 
						|
	}
 | 
						|
	if (ret >= 0)
 | 
						|
		ret = count;
 | 
						|
 | 
						|
	return ret;
 | 
						|
}
 | 
						|
 | 
						|
static struct bus_type rtl_subsys = {
 | 
						|
	.name = "ibm_rtl",
 | 
						|
	.dev_name = "ibm_rtl",
 | 
						|
};
 | 
						|
 | 
						|
static DEVICE_ATTR(version, S_IRUGO, rtl_show_version, NULL);
 | 
						|
static DEVICE_ATTR(state, 0600, rtl_show_state, rtl_set_state);
 | 
						|
 | 
						|
static struct device_attribute *rtl_attributes[] = {
 | 
						|
	&dev_attr_version,
 | 
						|
	&dev_attr_state,
 | 
						|
	NULL
 | 
						|
};
 | 
						|
 | 
						|
 | 
						|
static int rtl_setup_sysfs(void) {
 | 
						|
	int ret, i;
 | 
						|
 | 
						|
	ret = subsys_system_register(&rtl_subsys, NULL);
 | 
						|
	if (!ret) {
 | 
						|
		for (i = 0; rtl_attributes[i]; i ++)
 | 
						|
			device_create_file(rtl_subsys.dev_root, rtl_attributes[i]);
 | 
						|
	}
 | 
						|
	return ret;
 | 
						|
}
 | 
						|
 | 
						|
static void rtl_teardown_sysfs(void) {
 | 
						|
	int i;
 | 
						|
	for (i = 0; rtl_attributes[i]; i ++)
 | 
						|
		device_remove_file(rtl_subsys.dev_root, rtl_attributes[i]);
 | 
						|
	bus_unregister(&rtl_subsys);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static struct dmi_system_id __initdata ibm_rtl_dmi_table[] = {
 | 
						|
	{                                                  \
 | 
						|
		.matches = {                               \
 | 
						|
			DMI_MATCH(DMI_SYS_VENDOR, "IBM"),  \
 | 
						|
		},                                         \
 | 
						|
	},
 | 
						|
	{ }
 | 
						|
};
 | 
						|
 | 
						|
static int __init ibm_rtl_init(void) {
 | 
						|
	unsigned long ebda_addr, ebda_size;
 | 
						|
	unsigned int ebda_kb;
 | 
						|
	int ret = -ENODEV, i;
 | 
						|
 | 
						|
	if (force)
 | 
						|
		pr_warn("module loaded by force\n");
 | 
						|
	/* first ensure that we are running on IBM HW */
 | 
						|
	else if (efi_enabled || !dmi_check_system(ibm_rtl_dmi_table))
 | 
						|
		return -ENODEV;
 | 
						|
 | 
						|
	/* Get the address for the Extended BIOS Data Area */
 | 
						|
	ebda_addr = get_bios_ebda();
 | 
						|
	if (!ebda_addr) {
 | 
						|
		RTL_DEBUG("no BIOS EBDA found\n");
 | 
						|
		return -ENODEV;
 | 
						|
	}
 | 
						|
 | 
						|
	ebda_map = ioremap(ebda_addr, 4);
 | 
						|
	if (!ebda_map)
 | 
						|
		return -ENOMEM;
 | 
						|
 | 
						|
	/* First word in the EDBA is the Size in KB */
 | 
						|
	ebda_kb = ioread16(ebda_map);
 | 
						|
	RTL_DEBUG("EBDA is %d kB\n", ebda_kb);
 | 
						|
 | 
						|
	if (ebda_kb == 0)
 | 
						|
		goto out;
 | 
						|
 | 
						|
	iounmap(ebda_map);
 | 
						|
	ebda_size = ebda_kb*1024;
 | 
						|
 | 
						|
	/* Remap the whole table */
 | 
						|
	ebda_map = ioremap(ebda_addr, ebda_size);
 | 
						|
	if (!ebda_map)
 | 
						|
		return -ENOMEM;
 | 
						|
 | 
						|
	/* search for the _RTL_ signature at the start of the table */
 | 
						|
	for (i = 0 ; i < ebda_size/sizeof(unsigned int); i++) {
 | 
						|
		struct ibm_rtl_table __iomem * tmp;
 | 
						|
		tmp = (struct ibm_rtl_table __iomem *) (ebda_map+i);
 | 
						|
		if ((readq(&tmp->signature) & RTL_MASK) == RTL_SIGNATURE) {
 | 
						|
			phys_addr_t addr;
 | 
						|
			unsigned int plen;
 | 
						|
			RTL_DEBUG("found RTL_SIGNATURE at %p\n", tmp);
 | 
						|
			rtl_table = tmp;
 | 
						|
			/* The address, value, width and offset are platform
 | 
						|
			 * dependent and found in the ibm_rtl_table */
 | 
						|
			rtl_cmd_width = ioread8(&rtl_table->cmd_granularity);
 | 
						|
			rtl_cmd_type = ioread8(&rtl_table->cmd_address_type);
 | 
						|
			RTL_DEBUG("rtl_cmd_width = %u, rtl_cmd_type = %u\n",
 | 
						|
				  rtl_cmd_width, rtl_cmd_type);
 | 
						|
			addr = ioread32(&rtl_table->cmd_port_address);
 | 
						|
			RTL_DEBUG("addr = %#llx\n", (unsigned long long)addr);
 | 
						|
			plen = rtl_cmd_width/sizeof(char);
 | 
						|
			rtl_cmd_addr = rtl_port_map(addr, plen);
 | 
						|
			RTL_DEBUG("rtl_cmd_addr = %p\n", rtl_cmd_addr);
 | 
						|
			if (!rtl_cmd_addr) {
 | 
						|
				ret = -ENOMEM;
 | 
						|
				break;
 | 
						|
			}
 | 
						|
			ret = rtl_setup_sysfs();
 | 
						|
			break;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
out:
 | 
						|
	if (ret) {
 | 
						|
		iounmap(ebda_map);
 | 
						|
		rtl_port_unmap(rtl_cmd_addr);
 | 
						|
	}
 | 
						|
 | 
						|
	return ret;
 | 
						|
}
 | 
						|
 | 
						|
static void __exit ibm_rtl_exit(void)
 | 
						|
{
 | 
						|
	if (rtl_table) {
 | 
						|
		RTL_DEBUG("cleaning up");
 | 
						|
		/* do not leave the machine in SMI-free mode */
 | 
						|
		ibm_rtl_write(0);
 | 
						|
		/* unmap, unlink and remove all traces */
 | 
						|
		rtl_teardown_sysfs();
 | 
						|
		iounmap(ebda_map);
 | 
						|
		rtl_port_unmap(rtl_cmd_addr);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
module_init(ibm_rtl_init);
 | 
						|
module_exit(ibm_rtl_exit);
 |