diff options
author | Vernon Mauery <vernux@us.ibm.com> | 2010-10-05 18:47:18 -0400 |
---|---|---|
committer | Matthew Garrett <mjg@redhat.com> | 2010-10-21 10:10:46 -0400 |
commit | 35f0ce032b0f2d6974da516b5a113f49b7b70b09 (patch) | |
tree | c988974a4e0cb06e0bff53f29e6541d21631e16a /drivers/platform | |
parent | 260586d2b444909380137de6c6423e5b44edf4db (diff) |
IBM Real-Time "SMI Free" mode driver -v7
After a period of RFC for this driver, I think it is ready
for inclusion in the platform-driver-x86 tree, hopefully to
be staged in the next merge window into Linus's tree.
--Vernon
------------------------------------------------------------
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.
Since the driver is specific to IBM SystemX hardware (x86-
based servers) it only builds on x86 builds. To reduce the
risk of loading on the wrong hardware, the module uses DMI
information and checks a list of servers that are known to
work.
Signed-off-by: Vernon Mauery <vernux@us.ibm.com>
Signed-off-by: Matthew Garrett <mjg@redhat.com>
Diffstat (limited to 'drivers/platform')
-rw-r--r-- | drivers/platform/x86/Kconfig | 16 | ||||
-rw-r--r-- | drivers/platform/x86/Makefile | 2 | ||||
-rw-r--r-- | drivers/platform/x86/ibm_rtl.c | 341 |
3 files changed, 358 insertions, 1 deletions
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 91e431b6f20f..faec777b1ed4 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig | |||
@@ -615,6 +615,22 @@ config INTEL_IPS | |||
615 | functionality. If in doubt, say Y here; it will only load on | 615 | functionality. If in doubt, say Y here; it will only load on |
616 | supported platforms. | 616 | supported platforms. |
617 | 617 | ||
618 | config IBM_RTL | ||
619 | tristate "Device driver to enable PRTL support" | ||
620 | depends on X86 && PCI | ||
621 | ---help--- | ||
622 | Enable support for IBM Premium Real Time Mode (PRTM). | ||
623 | This module will allow you the enter and exit PRTM in the BIOS via | ||
624 | sysfs on platforms that support this feature. System in PRTM will | ||
625 | not receive CPU-generated SMIs for recoverable errors. Use of this | ||
626 | feature without proper support may void your hardware warranty. | ||
627 | |||
628 | If the proper BIOS support is found the driver will load and create | ||
629 | /sys/devices/system/ibm_rtl/. The "state" variable will indicate | ||
630 | whether or not the BIOS is in PRTM. | ||
631 | state = 0 (BIOS SMIs on) | ||
632 | state = 1 (BIOS SMIs off) | ||
633 | |||
618 | config XO1_RFKILL | 634 | config XO1_RFKILL |
619 | tristate "OLPC XO-1 software RF kill switch" | 635 | tristate "OLPC XO-1 software RF kill switch" |
620 | depends on OLPC | 636 | depends on OLPC |
diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index c15e96336e65..9950ccc940b5 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile | |||
@@ -32,4 +32,4 @@ obj-$(CONFIG_RAR_REGISTER) += intel_rar_register.o | |||
32 | obj-$(CONFIG_INTEL_IPS) += intel_ips.o | 32 | obj-$(CONFIG_INTEL_IPS) += intel_ips.o |
33 | obj-$(CONFIG_GPIO_INTEL_PMIC) += intel_pmic_gpio.o | 33 | obj-$(CONFIG_GPIO_INTEL_PMIC) += intel_pmic_gpio.o |
34 | obj-$(CONFIG_XO1_RFKILL) += xo1-rfkill.o | 34 | obj-$(CONFIG_XO1_RFKILL) += xo1-rfkill.o |
35 | 35 | 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 000000000000..3c2c6b91ecb3 --- /dev/null +++ b/drivers/platform/x86/ibm_rtl.c | |||
@@ -0,0 +1,341 @@ | |||
1 | /* | ||
2 | * IBM Real-Time Linux driver | ||
3 | * | ||
4 | * This program is free software; you can redistribute it and/or modify | ||
5 | * it under the terms of the GNU General Public License as published by | ||
6 | * the Free Software Foundation; either version 2 of the License, or | ||
7 | * (at your option) any later version. | ||
8 | * | ||
9 | * This program is distributed in the hope that it will be useful, | ||
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
12 | * GNU General Public License for more details. | ||
13 | * | ||
14 | * You should have received a copy of the GNU General Public License | ||
15 | * along with this program; if not, write to the Free Software | ||
16 | * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. | ||
17 | * | ||
18 | * Copyright (C) IBM Corporation, 2010 | ||
19 | * | ||
20 | * Author: Keith Mannthey <kmannth@us.ibm.com> | ||
21 | * Vernon Mauery <vernux@us.ibm.com> | ||
22 | * | ||
23 | */ | ||
24 | |||
25 | #include <linux/kernel.h> | ||
26 | #include <linux/delay.h> | ||
27 | #include <linux/module.h> | ||
28 | #include <linux/io.h> | ||
29 | #include <linux/sysdev.h> | ||
30 | #include <linux/dmi.h> | ||
31 | #include <linux/mutex.h> | ||
32 | #include <asm/bios_ebda.h> | ||
33 | |||
34 | static bool force; | ||
35 | module_param(force, bool, 0); | ||
36 | MODULE_PARM_DESC(force, "Force driver load, ignore DMI data"); | ||
37 | |||
38 | static bool debug; | ||
39 | module_param(debug, bool, 0644); | ||
40 | MODULE_PARM_DESC(debug, "Show debug output"); | ||
41 | |||
42 | MODULE_LICENSE("GPL"); | ||
43 | MODULE_AUTHOR("Keith Mannthey <kmmanth@us.ibm.com>"); | ||
44 | MODULE_AUTHOR("Vernon Mauery <vernux@us.ibm.com>"); | ||
45 | |||
46 | #define RTL_ADDR_TYPE_IO 1 | ||
47 | #define RTL_ADDR_TYPE_MMIO 2 | ||
48 | |||
49 | #define RTL_CMD_ENTER_PRTM 1 | ||
50 | #define RTL_CMD_EXIT_PRTM 2 | ||
51 | |||
52 | /* The RTL table as presented by the EBDA: */ | ||
53 | struct ibm_rtl_table { | ||
54 | char signature[5]; /* signature should be "_RTL_" */ | ||
55 | u8 version; | ||
56 | u8 rt_status; | ||
57 | u8 command; | ||
58 | u8 command_status; | ||
59 | u8 cmd_address_type; | ||
60 | u8 cmd_granularity; | ||
61 | u8 cmd_offset; | ||
62 | u16 reserve1; | ||
63 | u32 cmd_port_address; /* platform dependent address */ | ||
64 | u32 cmd_port_value; /* platform dependent value */ | ||
65 | } __attribute__((packed)); | ||
66 | |||
67 | /* to locate "_RTL_" signature do a masked 5-byte integer compare */ | ||
68 | #define RTL_SIGNATURE 0x0000005f4c54525fULL | ||
69 | #define RTL_MASK 0x000000ffffffffffULL | ||
70 | |||
71 | #define RTL_DEBUG(A, ...) do { \ | ||
72 | if (debug) \ | ||
73 | pr_info("ibm-rtl: " A, ##__VA_ARGS__ ); \ | ||
74 | } while (0) | ||
75 | |||
76 | static DEFINE_MUTEX(rtl_lock); | ||
77 | static struct ibm_rtl_table __iomem *rtl_table; | ||
78 | static void __iomem *ebda_map; | ||
79 | static void __iomem *rtl_cmd_addr; | ||
80 | static u8 rtl_cmd_type; | ||
81 | static u8 rtl_cmd_width; | ||
82 | |||
83 | static void __iomem *rtl_port_map(phys_addr_t addr, unsigned long len) | ||
84 | { | ||
85 | if (rtl_cmd_type == RTL_ADDR_TYPE_MMIO) | ||
86 | return ioremap(addr, len); | ||
87 | return ioport_map(addr, len); | ||
88 | } | ||
89 | |||
90 | static void rtl_port_unmap(void __iomem *addr) | ||
91 | { | ||
92 | if (addr && rtl_cmd_type == RTL_ADDR_TYPE_MMIO) | ||
93 | iounmap(addr); | ||
94 | else | ||
95 | ioport_unmap(addr); | ||
96 | } | ||
97 | |||
98 | static int ibm_rtl_write(u8 value) | ||
99 | { | ||
100 | int ret = 0, count = 0; | ||
101 | static u32 cmd_port_val; | ||
102 | |||
103 | RTL_DEBUG("%s(%d)\n", __FUNCTION__, value); | ||
104 | |||
105 | value = value == 1 ? RTL_CMD_ENTER_PRTM : RTL_CMD_EXIT_PRTM; | ||
106 | |||
107 | mutex_lock(&rtl_lock); | ||
108 | |||
109 | if (ioread8(&rtl_table->rt_status) != value) { | ||
110 | iowrite8(value, &rtl_table->command); | ||
111 | |||
112 | switch (rtl_cmd_width) { | ||
113 | case 8: | ||
114 | cmd_port_val = ioread8(&rtl_table->cmd_port_value); | ||
115 | RTL_DEBUG("cmd_port_val = %u\n", cmd_port_val); | ||
116 | iowrite8((u8)cmd_port_val, rtl_cmd_addr); | ||
117 | break; | ||
118 | case 16: | ||
119 | cmd_port_val = ioread16(&rtl_table->cmd_port_value); | ||
120 | RTL_DEBUG("cmd_port_val = %u\n", cmd_port_val); | ||
121 | iowrite16((u16)cmd_port_val, rtl_cmd_addr); | ||
122 | break; | ||
123 | case 32: | ||
124 | cmd_port_val = ioread32(&rtl_table->cmd_port_value); | ||
125 | RTL_DEBUG("cmd_port_val = %u\n", cmd_port_val); | ||
126 | iowrite32(cmd_port_val, rtl_cmd_addr); | ||
127 | break; | ||
128 | } | ||
129 | |||
130 | while (ioread8(&rtl_table->command)) { | ||
131 | msleep(10); | ||
132 | if (count++ > 500) { | ||
133 | pr_err("ibm-rtl: Hardware not responding to " | ||
134 | "mode switch request\n"); | ||
135 | ret = -EIO; | ||
136 | break; | ||
137 | } | ||
138 | |||
139 | } | ||
140 | |||
141 | if (ioread8(&rtl_table->command_status)) { | ||
142 | RTL_DEBUG("command_status reports failed command\n"); | ||
143 | ret = -EIO; | ||
144 | } | ||
145 | } | ||
146 | |||
147 | mutex_unlock(&rtl_lock); | ||
148 | return ret; | ||
149 | } | ||
150 | |||
151 | static ssize_t rtl_show_version(struct sysdev_class * dev, | ||
152 | struct sysdev_class_attribute *attr, | ||
153 | char *buf) | ||
154 | { | ||
155 | return sprintf(buf, "%d\n", (int)ioread8(&rtl_table->version)); | ||
156 | } | ||
157 | |||
158 | static ssize_t rtl_show_state(struct sysdev_class *dev, | ||
159 | struct sysdev_class_attribute *attr, | ||
160 | char *buf) | ||
161 | { | ||
162 | return sprintf(buf, "%d\n", ioread8(&rtl_table->rt_status)); | ||
163 | } | ||
164 | |||
165 | static ssize_t rtl_set_state(struct sysdev_class *dev, | ||
166 | struct sysdev_class_attribute *attr, | ||
167 | const char *buf, | ||
168 | size_t count) | ||
169 | { | ||
170 | ssize_t ret; | ||
171 | |||
172 | if (count < 1 || count > 2) | ||
173 | return -EINVAL; | ||
174 | |||
175 | switch (buf[0]) { | ||
176 | case '0': | ||
177 | ret = ibm_rtl_write(0); | ||
178 | break; | ||
179 | case '1': | ||
180 | ret = ibm_rtl_write(1); | ||
181 | break; | ||
182 | default: | ||
183 | ret = -EINVAL; | ||
184 | } | ||
185 | if (ret >= 0) | ||
186 | ret = count; | ||
187 | |||
188 | return ret; | ||
189 | } | ||
190 | |||
191 | static struct sysdev_class class_rtl = { | ||
192 | .name = "ibm_rtl", | ||
193 | }; | ||
194 | |||
195 | static SYSDEV_CLASS_ATTR(version, S_IRUGO, rtl_show_version, NULL); | ||
196 | static SYSDEV_CLASS_ATTR(state, 0600, rtl_show_state, rtl_set_state); | ||
197 | |||
198 | static struct sysdev_class_attribute *rtl_attributes[] = { | ||
199 | &attr_version, | ||
200 | &attr_state, | ||
201 | NULL | ||
202 | }; | ||
203 | |||
204 | |||
205 | static int rtl_setup_sysfs(void) { | ||
206 | int ret, i; | ||
207 | ret = sysdev_class_register(&class_rtl); | ||
208 | |||
209 | if (!ret) { | ||
210 | for (i = 0; rtl_attributes[i]; i ++) | ||
211 | sysdev_class_create_file(&class_rtl, rtl_attributes[i]); | ||
212 | } | ||
213 | return ret; | ||
214 | } | ||
215 | |||
216 | static void rtl_teardown_sysfs(void) { | ||
217 | int i; | ||
218 | for (i = 0; rtl_attributes[i]; i ++) | ||
219 | sysdev_class_remove_file(&class_rtl, rtl_attributes[i]); | ||
220 | sysdev_class_unregister(&class_rtl); | ||
221 | } | ||
222 | |||
223 | static int dmi_check_cb(const struct dmi_system_id *id) | ||
224 | { | ||
225 | RTL_DEBUG("found IBM server '%s'\n", id->ident); | ||
226 | return 0; | ||
227 | } | ||
228 | |||
229 | #define ibm_dmi_entry(NAME, TYPE) \ | ||
230 | { \ | ||
231 | .ident = NAME, \ | ||
232 | .matches = { \ | ||
233 | DMI_MATCH(DMI_SYS_VENDOR, "IBM"), \ | ||
234 | DMI_MATCH(DMI_PRODUCT_NAME, TYPE), \ | ||
235 | }, \ | ||
236 | .callback = dmi_check_cb \ | ||
237 | } | ||
238 | |||
239 | static struct dmi_system_id __initdata ibm_rtl_dmi_table[] = { | ||
240 | ibm_dmi_entry("BladeCenter LS21", "7971"), | ||
241 | ibm_dmi_entry("BladeCenter LS22", "7901"), | ||
242 | ibm_dmi_entry("BladeCenter HS21 XM", "7995"), | ||
243 | ibm_dmi_entry("BladeCenter HS22", "7870"), | ||
244 | ibm_dmi_entry("BladeCenter HS22V", "7871"), | ||
245 | ibm_dmi_entry("System x3550 M2", "7946"), | ||
246 | ibm_dmi_entry("System x3650 M2", "7947"), | ||
247 | ibm_dmi_entry("System x3550 M3", "7944"), | ||
248 | ibm_dmi_entry("System x3650 M3", "7945"), | ||
249 | { } | ||
250 | }; | ||
251 | |||
252 | static int __init ibm_rtl_init(void) { | ||
253 | unsigned long ebda_addr, ebda_size; | ||
254 | unsigned int ebda_kb; | ||
255 | int ret = -ENODEV, i; | ||
256 | |||
257 | if (force) | ||
258 | pr_warning("ibm-rtl: module loaded by force\n"); | ||
259 | /* first ensure that we are running on IBM HW */ | ||
260 | else if (!dmi_check_system(ibm_rtl_dmi_table)) | ||
261 | return -ENODEV; | ||
262 | |||
263 | /* Get the address for the Extended BIOS Data Area */ | ||
264 | ebda_addr = get_bios_ebda(); | ||
265 | if (!ebda_addr) { | ||
266 | RTL_DEBUG("no BIOS EBDA found\n"); | ||
267 | return -ENODEV; | ||
268 | } | ||
269 | |||
270 | ebda_map = ioremap(ebda_addr, 4); | ||
271 | if (!ebda_map) | ||
272 | return -ENOMEM; | ||
273 | |||
274 | /* First word in the EDBA is the Size in KB */ | ||
275 | ebda_kb = ioread16(ebda_map); | ||
276 | RTL_DEBUG("EBDA is %d kB\n", ebda_kb); | ||
277 | |||
278 | if (ebda_kb == 0) | ||
279 | goto out; | ||
280 | |||
281 | iounmap(ebda_map); | ||
282 | ebda_size = ebda_kb*1024; | ||
283 | |||
284 | /* Remap the whole table */ | ||
285 | ebda_map = ioremap(ebda_addr, ebda_size); | ||
286 | if (!ebda_map) | ||
287 | return -ENOMEM; | ||
288 | |||
289 | /* search for the _RTL_ signature at the start of the table */ | ||
290 | for (i = 0 ; i < ebda_size/sizeof(unsigned int); i++) { | ||
291 | struct ibm_rtl_table __iomem * tmp; | ||
292 | tmp = (struct ibm_rtl_table __iomem *) (ebda_map+i); | ||
293 | if ((readq(&tmp->signature) & RTL_MASK) == RTL_SIGNATURE) { | ||
294 | phys_addr_t addr; | ||
295 | unsigned int plen; | ||
296 | RTL_DEBUG("found RTL_SIGNATURE at %#llx\n", (u64)tmp); | ||
297 | rtl_table = tmp; | ||
298 | /* The address, value, width and offset are platform | ||
299 | * dependent and found in the ibm_rtl_table */ | ||
300 | rtl_cmd_width = ioread8(&rtl_table->cmd_granularity); | ||
301 | rtl_cmd_type = ioread8(&rtl_table->cmd_address_type); | ||
302 | RTL_DEBUG("rtl_cmd_width = %u, rtl_cmd_type = %u\n", | ||
303 | rtl_cmd_width, rtl_cmd_type); | ||
304 | addr = ioread32(&rtl_table->cmd_port_address); | ||
305 | RTL_DEBUG("addr = %#llx\n", addr); | ||
306 | plen = rtl_cmd_width/sizeof(char); | ||
307 | rtl_cmd_addr = rtl_port_map(addr, plen); | ||
308 | RTL_DEBUG("rtl_cmd_addr = %#llx\n", (u64)rtl_cmd_addr); | ||
309 | if (!rtl_cmd_addr) { | ||
310 | ret = -ENOMEM; | ||
311 | break; | ||
312 | } | ||
313 | ret = rtl_setup_sysfs(); | ||
314 | break; | ||
315 | } | ||
316 | } | ||
317 | |||
318 | out: | ||
319 | if (ret) { | ||
320 | iounmap(ebda_map); | ||
321 | rtl_port_unmap(rtl_cmd_addr); | ||
322 | } | ||
323 | |||
324 | return ret; | ||
325 | } | ||
326 | |||
327 | static void __exit ibm_rtl_exit(void) | ||
328 | { | ||
329 | if (rtl_table) { | ||
330 | RTL_DEBUG("cleaning up"); | ||
331 | /* do not leave the machine in SMI-free mode */ | ||
332 | ibm_rtl_write(0); | ||
333 | /* unmap, unlink and remove all traces */ | ||
334 | rtl_teardown_sysfs(); | ||
335 | iounmap(ebda_map); | ||
336 | rtl_port_unmap(rtl_cmd_addr); | ||
337 | } | ||
338 | } | ||
339 | |||
340 | module_init(ibm_rtl_init); | ||
341 | module_exit(ibm_rtl_exit); | ||