Re: [RFC][Patch] IBM Real-Time "SMI Free" mode driver -v4

From: Vernon Mauery
Date: Thu Sep 23 2010 - 18:53:54 EST


IBM Real-Time "SMI Free" mode driver

This driver supports the Real-Time Linux (RTL) BIOS feature.
The RTL feature allows non-fatal System Management Interrupts
(SMIs) to be disabled on supported IBM platforms and is
intended to be coupled with a user-space daemon to monitor
the hardware in a way that can be prioritized and scheduled
to better suit the requirements for the system.

The Device is presented as a special "_RTL_" table to the OS
in the Extended BIOS Data Area. There is a simple protocol
for entering and exiting the mode at runtime. This driver
creates a simple sysfs interface to allow a simple entry and
exit from RTL mode in the UFI/BIOS.

This patch has taken the feedback in response to previous posts
by Keith Mannthey <kmannth@xxxxxxxxxx> in addition to a couple
of more improvements, including using a DMI probe to reduce the
probability of getting into trouble on non-IBM systems.

Signed-off-by: Vernon Mauery <vernux@xxxxxxxxxx>

--

diff --git a/Documentation/ABI/testing/sysfs-devices-system-ibm-rtl b/Documentation/ABI/testing/sysfs-devices-system-ibm-rtl
new file mode 100644
index 0000000..b82deea
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-devices-system-ibm-rtl
@@ -0,0 +1,22 @@
+What: state
+Date: Sep 2010
+KernelVersion: 2.6.37
+Contact: Vernon Mauery <vernux@xxxxxxxxxx>
+Description: The state file allows a means by which to change in and
+ out of Premium Real-Time Mode (PRTM), as well as the
+ ability to query the current state.
+ 0 => PRTM off
+ 1 => PRTM enabled
+Users: The ibm-prtm userspace daemon uses this interface.
+
+
+What: version
+Date: Sep 2010
+KernelVersion: 2.6.37
+Contact: Vernon Mauery <vernux@xxxxxxxxxx>
+Description: The version file provides a means by which to query
+ the RTL table version that lives in the Extended
+ BIOS Data Area (EBDA).
+Users: The ibm-prtm userspace daemon uses this interface.
+
+
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
index cff7cc2..5fba368 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -590,4 +590,20 @@ config INTEL_IPS
functionality. If in doubt, say Y here; it will only load on
supported platforms.
+config IBM_RTL
+ tristate "Device driver to enable PRTL support"
+ depends on X86 && PCI
+ ---help---
+ Enable support for IBM Premium Real Time Mode (PRTM).
+ This module will allow you the enter and exit PRTM in the BIOS via
+ sysfs on platforms that support this feature. System in PRTM will
+ not recive CPU-generated SMIs for recoverable errors. Use of this
+ feature without proper support may void your hardware warranty.
+
+ If the proper BIOS support is found the driver will load and create
+ /sys/devices/system/ibm_rtl/. The "state" variable will indicate
+ whether or not the BIOS is in PRTM.
+ state = 0 (BIOS SMIs on)
+ state = 1 (BIOS SMIs off)
+
endif # X86_PLATFORM_DEVICES
diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
index 85fb2b8..50fae7a 100644
--- a/drivers/platform/x86/Makefile
+++ b/drivers/platform/x86/Makefile
@@ -30,4 +30,5 @@ obj-$(CONFIG_INTEL_SCU_IPC) += intel_scu_ipc.o
obj-$(CONFIG_RAR_REGISTER) += intel_rar_register.o
obj-$(CONFIG_INTEL_IPS) += intel_ips.o
obj-$(CONFIG_GPIO_INTEL_PMIC) += intel_pmic_gpio.o
+obj-$(CONFIG_IBM_RTL) += ibm_rtl.o
diff --git a/drivers/platform/x86/ibm_rtl.c b/drivers/platform/x86/ibm_rtl.c
new file mode 100644
index 0000000..c7a991f
--- /dev/null
+++ b/drivers/platform/x86/ibm_rtl.c
@@ -0,0 +1,352 @@
+/*
+ * 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@xxxxxxxxxx>
+ * Vernon Mauery <vernux@xxxxxxxxxx>
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/io.h>
+#include <linux/sysdev.h>
+#include <linux/dmi.h>
+#include <linux/mutex.h>
+#include <asm/bios_ebda.h>
+
+static int force;
+module_param(force, bool, 0);
+MODULE_PARM_DESC(force, "Force driver load, ignore DMI data");
+
+static int debug;
+module_param(debug, bool, 0644);
+MODULE_PARM_DESC(debug, "Show debug output");
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Keith Mannthey <kmmanth@xxxxxxxxxx>");
+MODULE_AUTHOR("Vernon Mauery <vernux@xxxxxxxxxx>");
+
+enum rtl_addr_type {
+ RTL_ADDR_TYPE_IO = 1,
+ RTL_ADDR_TYPE_MMIO,
+} __attribute__((packed));
+
+enum rtl_cmd_type {
+ RTL_CMD_NOP = 0,
+ RTL_CMD_ENTER_PRTM,
+ RTL_CMD_EXIT_PRTM,
+} __attribute__((packed));
+
+/* The RTL table as presented by the EBDA: */
+struct ibm_rtl_table {
+ char signature[5];
+ u8 version;
+ u8 rt_status;
+ enum rtl_cmd_type command;
+ u8 command_status;
+ enum rtl_addr_type cmd_address_type;
+ u8 cmd_granularity;
+ u8 cmd_offset;
+ u16 reserve1;
+ u8 cmd_port_address[4]; /* platform dependent address */
+ u8 cmd_port_value[4]; /* platform dependent value */
+};
+
+#define RTL_SIGNATURE (('L'<<24)|('T'<<16)|('R'<<8)|'_')
+
+#define ERROR(A, B...) printk(KERN_ERR "ibm-rtl: " A, ##B )
+#define WARNING(A, B...) printk(KERN_WARNING "ibm-rtl: " A, ##B )
+#define DEBUG(A, B...) do { \
+ if (debug) \
+ printk(KERN_INFO "ibm-rtl: " A, ##B ); \
+} while (0)
+
+static DEFINE_MUTEX(rtl_lock);
+static struct ibm_rtl_table __iomem *rtl_table = NULL;
+static void __iomem *ebda_map;
+static void __iomem *rtl_cmd_iomem_addr = NULL;
+static u32 rtl_cmd_port_addr;
+static enum rtl_addr_type rtl_cmd_type;
+static u8 rtl_cmd_width;
+
+static int ibm_rtl_write(u8 value)
+{
+ int ret = 0, count = 0;
+ static u32 cmd_port_val;
+
+ DEBUG("%s(%d)\n", __FUNCTION__, value);
+
+ mutex_lock(&rtl_lock);
+
+ if (ioread8(&rtl_table->rt_status) != value) {
+ if (value == 1)
+ iowrite8(RTL_CMD_ENTER_PRTM, &rtl_table->command);
+ else
+ iowrite8(RTL_CMD_EXIT_PRTM, &rtl_table->command);
+
+ DEBUG("rtl_cmd_port_addr = %#x\n", rtl_cmd_port_addr);
+ DEBUG("rtl_cmd_type = %d\n", rtl_cmd_type);
+
+ switch (rtl_cmd_width) {
+ case 8:
+ cmd_port_val = ioread8(&rtl_table->cmd_port_value);
+ DEBUG("cmd_port_val = %u\n", cmd_port_val);
+ if (rtl_cmd_type == RTL_ADDR_TYPE_MMIO)
+ iowrite8((u8)cmd_port_val, rtl_cmd_iomem_addr);
+ else
+ outb((u8)cmd_port_val, rtl_cmd_port_addr);
+ break;
+ case 16:
+ cmd_port_val = ioread16(&rtl_table->cmd_port_value);
+ DEBUG("cmd_port_val = %u\n", cmd_port_val);
+ if (rtl_cmd_type == RTL_ADDR_TYPE_MMIO)
+ iowrite16((u16)cmd_port_val, rtl_cmd_iomem_addr);
+ else
+ outw((u16)cmd_port_val, rtl_cmd_port_addr);
+ break;
+ case 32:
+ cmd_port_val = ioread32(&rtl_table->cmd_port_value);
+ DEBUG("cmd_port_val = %u\n", cmd_port_val);
+ if (rtl_cmd_type == RTL_ADDR_TYPE_MMIO)
+ iowrite32(cmd_port_val, rtl_cmd_iomem_addr);
+ else
+ outl(cmd_port_val, rtl_cmd_port_addr);
+ break;
+ }
+
+ while (ioread8(&rtl_table->command)) {
+ msleep(10);
+ if (count++ > 500) {
+ ERROR("Hardware not responding to "
+ "mode switch request\n");
+ ret = -EIO;
+ break;
+ }
+
+ }
+
+ if (ioread8(&rtl_table->command_status)) {
+ DEBUG("command_status reports failed command\n");
+ ret = -EIO;
+ }
+ }
+
+ mutex_unlock(&rtl_lock);
+ return ret;
+}
+
+static ssize_t rtl_show_version(struct sysdev_class * dev,
+ struct sysdev_class_attribute *attr,
+ char *buf)
+{
+ return sprintf(buf, "%d\n", (int)ioread8(&rtl_table->version));
+}
+
+static ssize_t rtl_show_state(struct sysdev_class *dev,
+ struct sysdev_class_attribute *attr,
+ char *buf)
+{
+ return sprintf(buf, "%d\n", ioread8(&rtl_table->rt_status));
+}
+
+static ssize_t rtl_set_state(struct sysdev_class *dev,
+ struct sysdev_class_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 sysdev_class class_rtl = {
+ .name = "ibm_rtl",
+};
+
+static SYSDEV_CLASS_ATTR(version, S_IRUGO, rtl_show_version, NULL);
+static SYSDEV_CLASS_ATTR(state, 0600, rtl_show_state, rtl_set_state);
+
+static struct sysdev_class_attribute *rtl_attributes[] = {
+ &attr_version,
+ &attr_state,
+ NULL
+};
+
+
+static int rtl_setup_sysfs(void) {
+ int ret, i;
+ ret = sysdev_class_register(&class_rtl);
+
+ if (!ret) {
+ for (i = 0; rtl_attributes[i]; i ++)
+ sysdev_class_create_file(&class_rtl, rtl_attributes[i]);
+ }
+ return ret;
+}
+
+static void rtl_teardown_sysfs(void) {
+ int i;
+ for (i = 0; rtl_attributes[i]; i ++)
+ sysdev_class_remove_file(&class_rtl, rtl_attributes[i]);
+ sysdev_class_unregister(&class_rtl);
+}
+
+static int dmi_check_cb(const struct dmi_system_id *id)
+{
+ DEBUG("found IBM server '%s'\n", id->ident);
+ return 0;
+}
+
+#define ibm_dmi_entry(NAME, TYPE) \
+{ \
+ .ident = NAME, \
+ .matches = { \
+ DMI_MATCH(DMI_SYS_VENDOR, "IBM"), \
+ DMI_MATCH(DMI_PRODUCT_NAME, TYPE), \
+ }, \
+ .callback = dmi_check_cb \
+}
+
+static struct dmi_system_id __initdata ibm_rtl_dmi_table[] = {
+ ibm_dmi_entry("BladeCenter LS21", "7971"),
+ ibm_dmi_entry("BladeCenter LS22", "7901"),
+ ibm_dmi_entry("BladeCenter HS21 XM", "7995"),
+ ibm_dmi_entry("BladeCenter HS22", "7870"),
+ ibm_dmi_entry("BladeCenter HS22V", "7871"),
+ ibm_dmi_entry("System x3550 M2", "7946"),
+ ibm_dmi_entry("System x3650 M2", "7947"),
+ ibm_dmi_entry("System x3550 M3", "7944"),
+ ibm_dmi_entry("System x3650 M3", "7945"),
+ { }
+};
+
+static int __init ibm_rtl_init(void) {
+ unsigned long ebda_addr, ebda_size;
+ unsigned int ebda_kb;
+ int ret = -ENODEV, i;
+
+ if (force)
+ WARNING("module loaded by force!\n");
+ /* first ensure that we are running on IBM HW */
+ else if (!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) {
+ 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);
+ 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 (ioread32(&tmp->signature) == RTL_SIGNATURE) {
+ DEBUG("found RTL_SIGNATURE at %#llx\n", (u64)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);
+ DEBUG("rtl_cmd_width = %u, rtl_cmd_type = %u\n",
+ rtl_cmd_width, rtl_cmd_type);
+ if (rtl_cmd_type == RTL_ADDR_TYPE_MMIO) {
+ u32 addr;
+ addr = ioread32(&rtl_table->cmd_port_address);
+ rtl_cmd_iomem_addr = ioremap(addr, 4);
+ DEBUG("rtl_cmd_iomem_addr = %#llx\n",
+ (u64)rtl_cmd_iomem_addr);
+ if (!rtl_cmd_iomem_addr) {
+ ret = -ENOMEM;
+ break;
+ }
+ } else {
+ rtl_cmd_port_addr =
+ ioread32(&rtl_table->cmd_port_address);
+ DEBUG("rtl_cmd_port_addr = %#x\n",
+ rtl_cmd_port_addr);
+ }
+ ret = rtl_setup_sysfs();
+ break;
+ }
+ }
+
+out:
+ if (ret) {
+ iounmap(ebda_map);
+ if (rtl_cmd_iomem_addr)
+ iounmap(rtl_cmd_iomem_addr);
+ }
+
+ return ret;
+}
+
+static void __exit ibm_rtl_exit(void)
+{
+ if (rtl_table) {
+ 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);
+ if (rtl_cmd_iomem_addr)
+ iounmap(rtl_cmd_iomem_addr);
+ }
+}
+
+module_init(ibm_rtl_init);
+module_exit(ibm_rtl_exit);
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/