diff options
author | Corey Minyard <cminyard@mvista.com> | 2017-06-09 22:19:52 -0400 |
---|---|---|
committer | Corey Minyard <cminyard@mvista.com> | 2017-06-19 13:49:36 -0400 |
commit | 9f88145f1871456de67ae6a242ac2661187bd4ff (patch) | |
tree | 7062dcbf1cf77363031afbdfaabe9e07d8552770 | |
parent | cdea46566bb21ce309725a024208322a409055cc (diff) |
ipmi: Create a platform device for a DMI-specified IPMI interface
Create a platform device for each IPMI device in the DMI table,
a separate kind of device for SSIF types and for KCS, BT, and
SMIC types. This is so auto-loading IPMI devices will work
from just SMBIOS tables.
This also adds the ability to extract the slave address from
the SMBIOS tables, so that when the driver uses ACPI-specified
interfaces, it can still extract the slave address from SMBIOS.
Signed-off-by: Corey Minyard <cminyard@mvista.com>
Cc: Andy Lutomirski <luto@kernel.org>
-rw-r--r-- | drivers/char/ipmi/Kconfig | 4 | ||||
-rw-r--r-- | drivers/char/ipmi/Makefile | 1 | ||||
-rw-r--r-- | drivers/char/ipmi/ipmi_dmi.c | 273 | ||||
-rw-r--r-- | drivers/char/ipmi/ipmi_dmi.h | 12 |
4 files changed, 290 insertions, 0 deletions
diff --git a/drivers/char/ipmi/Kconfig b/drivers/char/ipmi/Kconfig index 90f3edffb067..f6fa056a52fc 100644 --- a/drivers/char/ipmi/Kconfig +++ b/drivers/char/ipmi/Kconfig | |||
@@ -5,6 +5,7 @@ | |||
5 | menuconfig IPMI_HANDLER | 5 | menuconfig IPMI_HANDLER |
6 | tristate 'IPMI top-level message handler' | 6 | tristate 'IPMI top-level message handler' |
7 | depends on HAS_IOMEM | 7 | depends on HAS_IOMEM |
8 | select IPMI_DMI_DECODE if DMI | ||
8 | help | 9 | help |
9 | This enables the central IPMI message handler, required for IPMI | 10 | This enables the central IPMI message handler, required for IPMI |
10 | to work. | 11 | to work. |
@@ -16,6 +17,9 @@ menuconfig IPMI_HANDLER | |||
16 | 17 | ||
17 | If unsure, say N. | 18 | If unsure, say N. |
18 | 19 | ||
20 | config IPMI_DMI_DECODE | ||
21 | bool | ||
22 | |||
19 | if IPMI_HANDLER | 23 | if IPMI_HANDLER |
20 | 24 | ||
21 | config IPMI_PANIC_EVENT | 25 | config IPMI_PANIC_EVENT |
diff --git a/drivers/char/ipmi/Makefile b/drivers/char/ipmi/Makefile index 0d98cd91def1..eefb0b301e83 100644 --- a/drivers/char/ipmi/Makefile +++ b/drivers/char/ipmi/Makefile | |||
@@ -7,6 +7,7 @@ ipmi_si-y := ipmi_si_intf.o ipmi_kcs_sm.o ipmi_smic_sm.o ipmi_bt_sm.o | |||
7 | obj-$(CONFIG_IPMI_HANDLER) += ipmi_msghandler.o | 7 | obj-$(CONFIG_IPMI_HANDLER) += ipmi_msghandler.o |
8 | obj-$(CONFIG_IPMI_DEVICE_INTERFACE) += ipmi_devintf.o | 8 | obj-$(CONFIG_IPMI_DEVICE_INTERFACE) += ipmi_devintf.o |
9 | obj-$(CONFIG_IPMI_SI) += ipmi_si.o | 9 | obj-$(CONFIG_IPMI_SI) += ipmi_si.o |
10 | obj-$(CONFIG_IPMI_DMI_DECODE) += ipmi_dmi.o | ||
10 | obj-$(CONFIG_IPMI_SSIF) += ipmi_ssif.o | 11 | obj-$(CONFIG_IPMI_SSIF) += ipmi_ssif.o |
11 | obj-$(CONFIG_IPMI_POWERNV) += ipmi_powernv.o | 12 | obj-$(CONFIG_IPMI_POWERNV) += ipmi_powernv.o |
12 | obj-$(CONFIG_IPMI_WATCHDOG) += ipmi_watchdog.o | 13 | obj-$(CONFIG_IPMI_WATCHDOG) += ipmi_watchdog.o |
diff --git a/drivers/char/ipmi/ipmi_dmi.c b/drivers/char/ipmi/ipmi_dmi.c new file mode 100644 index 000000000000..2a84401dea05 --- /dev/null +++ b/drivers/char/ipmi/ipmi_dmi.c | |||
@@ -0,0 +1,273 @@ | |||
1 | /* | ||
2 | * A hack to create a platform device from a DMI entry. This will | ||
3 | * allow autoloading of the IPMI drive based on SMBIOS entries. | ||
4 | */ | ||
5 | |||
6 | #include <linux/ipmi.h> | ||
7 | #include <linux/init.h> | ||
8 | #include <linux/dmi.h> | ||
9 | #include <linux/platform_device.h> | ||
10 | #include <linux/property.h> | ||
11 | #include "ipmi_dmi.h" | ||
12 | |||
13 | struct ipmi_dmi_info { | ||
14 | int type; | ||
15 | u32 flags; | ||
16 | unsigned long addr; | ||
17 | u8 slave_addr; | ||
18 | struct ipmi_dmi_info *next; | ||
19 | }; | ||
20 | |||
21 | static struct ipmi_dmi_info *ipmi_dmi_infos; | ||
22 | |||
23 | static int ipmi_dmi_nr __initdata; | ||
24 | |||
25 | static void __init dmi_add_platform_ipmi(unsigned long base_addr, | ||
26 | u32 flags, | ||
27 | u8 slave_addr, | ||
28 | int irq, | ||
29 | int offset, | ||
30 | int type) | ||
31 | { | ||
32 | struct platform_device *pdev; | ||
33 | struct resource r[4]; | ||
34 | unsigned int num_r = 1, size; | ||
35 | struct property_entry p[4] = { | ||
36 | PROPERTY_ENTRY_U8("slave-addr", slave_addr), | ||
37 | PROPERTY_ENTRY_U8("ipmi-type", type), | ||
38 | PROPERTY_ENTRY_U16("i2c-addr", base_addr), | ||
39 | { } | ||
40 | }; | ||
41 | char *name, *override; | ||
42 | int rv; | ||
43 | struct ipmi_dmi_info *info; | ||
44 | |||
45 | info = kmalloc(sizeof(*info), GFP_KERNEL); | ||
46 | if (!info) { | ||
47 | pr_warn("ipmi:dmi: Could not allocate dmi info\n"); | ||
48 | } else { | ||
49 | info->type = type; | ||
50 | info->flags = flags; | ||
51 | info->addr = base_addr; | ||
52 | info->slave_addr = slave_addr; | ||
53 | info->next = ipmi_dmi_infos; | ||
54 | ipmi_dmi_infos = info; | ||
55 | } | ||
56 | |||
57 | name = "dmi-ipmi-si"; | ||
58 | override = "ipmi_si"; | ||
59 | switch (type) { | ||
60 | case IPMI_DMI_TYPE_SSIF: | ||
61 | name = "dmi-ipmi-ssif"; | ||
62 | override = "ipmi_ssif"; | ||
63 | offset = 1; | ||
64 | size = 1; | ||
65 | break; | ||
66 | case IPMI_DMI_TYPE_BT: | ||
67 | size = 3; | ||
68 | break; | ||
69 | case IPMI_DMI_TYPE_KCS: | ||
70 | case IPMI_DMI_TYPE_SMIC: | ||
71 | size = 2; | ||
72 | break; | ||
73 | default: | ||
74 | pr_err("ipmi:dmi: Invalid IPMI type: %d", type); | ||
75 | return; | ||
76 | } | ||
77 | |||
78 | pdev = platform_device_alloc(name, ipmi_dmi_nr); | ||
79 | if (!pdev) { | ||
80 | pr_err("ipmi:dmi: Error allocation IPMI platform device"); | ||
81 | return; | ||
82 | } | ||
83 | pdev->driver_override = override; | ||
84 | |||
85 | if (type == IPMI_DMI_TYPE_SSIF) | ||
86 | goto add_properties; | ||
87 | |||
88 | memset(r, 0, sizeof(r)); | ||
89 | |||
90 | r[0].start = base_addr; | ||
91 | r[0].end = r[0].start + offset - 1; | ||
92 | r[0].name = "IPMI Address 1"; | ||
93 | r[0].flags = flags; | ||
94 | |||
95 | if (size > 1) { | ||
96 | r[1].start = r[0].start + offset; | ||
97 | r[1].end = r[1].start + offset - 1; | ||
98 | r[1].name = "IPMI Address 2"; | ||
99 | r[1].flags = flags; | ||
100 | num_r++; | ||
101 | } | ||
102 | |||
103 | if (size > 2) { | ||
104 | r[2].start = r[1].start + offset; | ||
105 | r[2].end = r[2].start + offset - 1; | ||
106 | r[2].name = "IPMI Address 3"; | ||
107 | r[2].flags = flags; | ||
108 | num_r++; | ||
109 | } | ||
110 | |||
111 | if (irq) { | ||
112 | r[num_r].start = irq; | ||
113 | r[num_r].end = irq; | ||
114 | r[num_r].name = "IPMI IRQ"; | ||
115 | r[num_r].flags = IORESOURCE_IRQ; | ||
116 | num_r++; | ||
117 | } | ||
118 | |||
119 | rv = platform_device_add_resources(pdev, r, num_r); | ||
120 | if (rv) { | ||
121 | dev_err(&pdev->dev, | ||
122 | "ipmi:dmi: Unable to add resources: %d\n", rv); | ||
123 | goto err; | ||
124 | } | ||
125 | |||
126 | add_properties: | ||
127 | rv = platform_device_add_properties(pdev, p); | ||
128 | if (rv) { | ||
129 | dev_err(&pdev->dev, | ||
130 | "ipmi:dmi: Unable to add properties: %d\n", rv); | ||
131 | goto err; | ||
132 | } | ||
133 | |||
134 | rv = platform_device_add(pdev); | ||
135 | if (rv) { | ||
136 | dev_err(&pdev->dev, "ipmi:dmi: Unable to add device: %d\n", rv); | ||
137 | goto err; | ||
138 | } | ||
139 | |||
140 | ipmi_dmi_nr++; | ||
141 | return; | ||
142 | |||
143 | err: | ||
144 | platform_device_put(pdev); | ||
145 | } | ||
146 | |||
147 | /* | ||
148 | * Look up the slave address for a given interface. This is here | ||
149 | * because ACPI doesn't have a slave address while SMBIOS does, but we | ||
150 | * prefer using ACPI so the ACPI code can use the IPMI namespace. | ||
151 | * This function allows an ACPI-specified IPMI device to look up the | ||
152 | * slave address from the DMI table. | ||
153 | */ | ||
154 | int ipmi_dmi_get_slave_addr(int type, u32 flags, unsigned long base_addr) | ||
155 | { | ||
156 | struct ipmi_dmi_info *info = ipmi_dmi_infos; | ||
157 | |||
158 | while (info) { | ||
159 | if (info->type == type && | ||
160 | info->flags == flags && | ||
161 | info->addr == base_addr) | ||
162 | return info->slave_addr; | ||
163 | info = info->next; | ||
164 | } | ||
165 | |||
166 | return 0; | ||
167 | } | ||
168 | EXPORT_SYMBOL(ipmi_dmi_get_slave_addr); | ||
169 | |||
170 | #define DMI_IPMI_MIN_LENGTH 0x10 | ||
171 | #define DMI_IPMI_VER2_LENGTH 0x12 | ||
172 | #define DMI_IPMI_TYPE 4 | ||
173 | #define DMI_IPMI_SLAVEADDR 6 | ||
174 | #define DMI_IPMI_ADDR 8 | ||
175 | #define DMI_IPMI_ACCESS 0x10 | ||
176 | #define DMI_IPMI_IRQ 0x11 | ||
177 | #define DMI_IPMI_IO_MASK 0xfffe | ||
178 | |||
179 | static void __init dmi_decode_ipmi(const struct dmi_header *dm) | ||
180 | { | ||
181 | const u8 *data = (const u8 *) dm; | ||
182 | u32 flags = IORESOURCE_IO; | ||
183 | unsigned long base_addr; | ||
184 | u8 len = dm->length; | ||
185 | u8 slave_addr; | ||
186 | int irq = 0, offset; | ||
187 | int type; | ||
188 | |||
189 | if (len < DMI_IPMI_MIN_LENGTH) | ||
190 | return; | ||
191 | |||
192 | type = data[DMI_IPMI_TYPE]; | ||
193 | slave_addr = data[DMI_IPMI_SLAVEADDR]; | ||
194 | |||
195 | memcpy(&base_addr, data + DMI_IPMI_ADDR, sizeof(unsigned long)); | ||
196 | if (len >= DMI_IPMI_VER2_LENGTH) { | ||
197 | if (type == IPMI_DMI_TYPE_SSIF) { | ||
198 | offset = 0; | ||
199 | flags = 0; | ||
200 | base_addr = data[DMI_IPMI_ADDR] >> 1; | ||
201 | if (base_addr == 0) { | ||
202 | /* | ||
203 | * Some broken systems put the I2C address in | ||
204 | * the slave address field. We try to | ||
205 | * accommodate them here. | ||
206 | */ | ||
207 | base_addr = data[DMI_IPMI_SLAVEADDR] >> 1; | ||
208 | slave_addr = 0; | ||
209 | } | ||
210 | } else { | ||
211 | if (base_addr & 1) { | ||
212 | /* I/O */ | ||
213 | base_addr &= DMI_IPMI_IO_MASK; | ||
214 | } else { | ||
215 | /* Memory */ | ||
216 | flags = IORESOURCE_MEM; | ||
217 | } | ||
218 | |||
219 | /* | ||
220 | * If bit 4 of byte 0x10 is set, then the lsb | ||
221 | * for the address is odd. | ||
222 | */ | ||
223 | base_addr |= (data[DMI_IPMI_ACCESS] >> 4) & 1; | ||
224 | |||
225 | irq = data[DMI_IPMI_IRQ]; | ||
226 | |||
227 | /* | ||
228 | * The top two bits of byte 0x10 hold the | ||
229 | * register spacing. | ||
230 | */ | ||
231 | switch ((data[DMI_IPMI_ACCESS] >> 6) & 3) { | ||
232 | case 0: /* Byte boundaries */ | ||
233 | offset = 1; | ||
234 | break; | ||
235 | case 1: /* 32-bit boundaries */ | ||
236 | offset = 4; | ||
237 | break; | ||
238 | case 2: /* 16-byte boundaries */ | ||
239 | offset = 16; | ||
240 | break; | ||
241 | default: | ||
242 | pr_err("ipmi:dmi: Invalid offset: 0"); | ||
243 | return; | ||
244 | } | ||
245 | } | ||
246 | } else { | ||
247 | /* Old DMI spec. */ | ||
248 | /* | ||
249 | * Note that technically, the lower bit of the base | ||
250 | * address should be 1 if the address is I/O and 0 if | ||
251 | * the address is in memory. So many systems get that | ||
252 | * wrong (and all that I have seen are I/O) so we just | ||
253 | * ignore that bit and assume I/O. Systems that use | ||
254 | * memory should use the newer spec, anyway. | ||
255 | */ | ||
256 | base_addr = base_addr & DMI_IPMI_IO_MASK; | ||
257 | offset = 1; | ||
258 | } | ||
259 | |||
260 | dmi_add_platform_ipmi(base_addr, flags, slave_addr, irq, | ||
261 | offset, type); | ||
262 | } | ||
263 | |||
264 | static int __init scan_for_dmi_ipmi(void) | ||
265 | { | ||
266 | const struct dmi_device *dev = NULL; | ||
267 | |||
268 | while ((dev = dmi_find_device(DMI_DEV_TYPE_IPMI, NULL, dev))) | ||
269 | dmi_decode_ipmi((const struct dmi_header *) dev->device_data); | ||
270 | |||
271 | return 0; | ||
272 | } | ||
273 | subsys_initcall(scan_for_dmi_ipmi); | ||
diff --git a/drivers/char/ipmi/ipmi_dmi.h b/drivers/char/ipmi/ipmi_dmi.h new file mode 100644 index 000000000000..0a1afe5ceb1e --- /dev/null +++ b/drivers/char/ipmi/ipmi_dmi.h | |||
@@ -0,0 +1,12 @@ | |||
1 | /* | ||
2 | * DMI defines for use by IPMI | ||
3 | */ | ||
4 | |||
5 | #define IPMI_DMI_TYPE_KCS 0x01 | ||
6 | #define IPMI_DMI_TYPE_SMIC 0x02 | ||
7 | #define IPMI_DMI_TYPE_BT 0x03 | ||
8 | #define IPMI_DMI_TYPE_SSIF 0x04 | ||
9 | |||
10 | #ifdef CONFIG_IPMI_DMI_DECODE | ||
11 | int ipmi_dmi_get_slave_addr(int type, u32 flags, unsigned long base_addr); | ||
12 | #endif | ||