]>
Commit | Line | Data |
---|---|---|
b2441318 | 1 | // SPDX-License-Identifier: GPL-2.0 |
9f88145f CM |
2 | /* |
3 | * A hack to create a platform device from a DMI entry. This will | |
4 | * allow autoloading of the IPMI drive based on SMBIOS entries. | |
5 | */ | |
6 | ||
7 | #include <linux/ipmi.h> | |
8 | #include <linux/init.h> | |
9 | #include <linux/dmi.h> | |
10 | #include <linux/platform_device.h> | |
11 | #include <linux/property.h> | |
12 | #include "ipmi_dmi.h" | |
13 | ||
14 | struct ipmi_dmi_info { | |
15 | int type; | |
16 | u32 flags; | |
17 | unsigned long addr; | |
18 | u8 slave_addr; | |
19 | struct ipmi_dmi_info *next; | |
20 | }; | |
21 | ||
22 | static struct ipmi_dmi_info *ipmi_dmi_infos; | |
23 | ||
24 | static int ipmi_dmi_nr __initdata; | |
25 | ||
26 | static void __init dmi_add_platform_ipmi(unsigned long base_addr, | |
27 | u32 flags, | |
28 | u8 slave_addr, | |
29 | int irq, | |
30 | int offset, | |
31 | int type) | |
32 | { | |
33 | struct platform_device *pdev; | |
34 | struct resource r[4]; | |
35 | unsigned int num_r = 1, size; | |
36 | struct property_entry p[4] = { | |
37 | PROPERTY_ENTRY_U8("slave-addr", slave_addr), | |
38 | PROPERTY_ENTRY_U8("ipmi-type", type), | |
39 | PROPERTY_ENTRY_U16("i2c-addr", base_addr), | |
40 | { } | |
41 | }; | |
42 | char *name, *override; | |
43 | int rv; | |
44 | struct ipmi_dmi_info *info; | |
45 | ||
46 | info = kmalloc(sizeof(*info), GFP_KERNEL); | |
47 | if (!info) { | |
48 | pr_warn("ipmi:dmi: Could not allocate dmi info\n"); | |
49 | } else { | |
50 | info->type = type; | |
51 | info->flags = flags; | |
52 | info->addr = base_addr; | |
53 | info->slave_addr = slave_addr; | |
54 | info->next = ipmi_dmi_infos; | |
55 | ipmi_dmi_infos = info; | |
56 | } | |
57 | ||
58 | name = "dmi-ipmi-si"; | |
59 | override = "ipmi_si"; | |
60 | switch (type) { | |
61 | case IPMI_DMI_TYPE_SSIF: | |
62 | name = "dmi-ipmi-ssif"; | |
63 | override = "ipmi_ssif"; | |
64 | offset = 1; | |
65 | size = 1; | |
66 | break; | |
67 | case IPMI_DMI_TYPE_BT: | |
68 | size = 3; | |
69 | break; | |
70 | case IPMI_DMI_TYPE_KCS: | |
71 | case IPMI_DMI_TYPE_SMIC: | |
72 | size = 2; | |
73 | break; | |
74 | default: | |
75 | pr_err("ipmi:dmi: Invalid IPMI type: %d", type); | |
76 | return; | |
77 | } | |
78 | ||
79 | pdev = platform_device_alloc(name, ipmi_dmi_nr); | |
80 | if (!pdev) { | |
81 | pr_err("ipmi:dmi: Error allocation IPMI platform device"); | |
82 | return; | |
83 | } | |
84 | pdev->driver_override = override; | |
85 | ||
86 | if (type == IPMI_DMI_TYPE_SSIF) | |
87 | goto add_properties; | |
88 | ||
89 | memset(r, 0, sizeof(r)); | |
90 | ||
91 | r[0].start = base_addr; | |
92 | r[0].end = r[0].start + offset - 1; | |
93 | r[0].name = "IPMI Address 1"; | |
94 | r[0].flags = flags; | |
95 | ||
96 | if (size > 1) { | |
97 | r[1].start = r[0].start + offset; | |
98 | r[1].end = r[1].start + offset - 1; | |
99 | r[1].name = "IPMI Address 2"; | |
100 | r[1].flags = flags; | |
101 | num_r++; | |
102 | } | |
103 | ||
104 | if (size > 2) { | |
105 | r[2].start = r[1].start + offset; | |
106 | r[2].end = r[2].start + offset - 1; | |
107 | r[2].name = "IPMI Address 3"; | |
108 | r[2].flags = flags; | |
109 | num_r++; | |
110 | } | |
111 | ||
112 | if (irq) { | |
113 | r[num_r].start = irq; | |
114 | r[num_r].end = irq; | |
115 | r[num_r].name = "IPMI IRQ"; | |
116 | r[num_r].flags = IORESOURCE_IRQ; | |
117 | num_r++; | |
118 | } | |
119 | ||
120 | rv = platform_device_add_resources(pdev, r, num_r); | |
121 | if (rv) { | |
122 | dev_err(&pdev->dev, | |
123 | "ipmi:dmi: Unable to add resources: %d\n", rv); | |
124 | goto err; | |
125 | } | |
126 | ||
127 | add_properties: | |
128 | rv = platform_device_add_properties(pdev, p); | |
129 | if (rv) { | |
130 | dev_err(&pdev->dev, | |
131 | "ipmi:dmi: Unable to add properties: %d\n", rv); | |
132 | goto err; | |
133 | } | |
134 | ||
135 | rv = platform_device_add(pdev); | |
136 | if (rv) { | |
137 | dev_err(&pdev->dev, "ipmi:dmi: Unable to add device: %d\n", rv); | |
138 | goto err; | |
139 | } | |
140 | ||
141 | ipmi_dmi_nr++; | |
142 | return; | |
143 | ||
144 | err: | |
145 | platform_device_put(pdev); | |
146 | } | |
147 | ||
148 | /* | |
149 | * Look up the slave address for a given interface. This is here | |
150 | * because ACPI doesn't have a slave address while SMBIOS does, but we | |
151 | * prefer using ACPI so the ACPI code can use the IPMI namespace. | |
152 | * This function allows an ACPI-specified IPMI device to look up the | |
153 | * slave address from the DMI table. | |
154 | */ | |
155 | int ipmi_dmi_get_slave_addr(int type, u32 flags, unsigned long base_addr) | |
156 | { | |
157 | struct ipmi_dmi_info *info = ipmi_dmi_infos; | |
158 | ||
159 | while (info) { | |
160 | if (info->type == type && | |
161 | info->flags == flags && | |
162 | info->addr == base_addr) | |
163 | return info->slave_addr; | |
164 | info = info->next; | |
165 | } | |
166 | ||
167 | return 0; | |
168 | } | |
169 | EXPORT_SYMBOL(ipmi_dmi_get_slave_addr); | |
170 | ||
171 | #define DMI_IPMI_MIN_LENGTH 0x10 | |
172 | #define DMI_IPMI_VER2_LENGTH 0x12 | |
173 | #define DMI_IPMI_TYPE 4 | |
174 | #define DMI_IPMI_SLAVEADDR 6 | |
175 | #define DMI_IPMI_ADDR 8 | |
176 | #define DMI_IPMI_ACCESS 0x10 | |
177 | #define DMI_IPMI_IRQ 0x11 | |
178 | #define DMI_IPMI_IO_MASK 0xfffe | |
179 | ||
180 | static void __init dmi_decode_ipmi(const struct dmi_header *dm) | |
181 | { | |
182 | const u8 *data = (const u8 *) dm; | |
183 | u32 flags = IORESOURCE_IO; | |
184 | unsigned long base_addr; | |
185 | u8 len = dm->length; | |
186 | u8 slave_addr; | |
187 | int irq = 0, offset; | |
188 | int type; | |
189 | ||
190 | if (len < DMI_IPMI_MIN_LENGTH) | |
191 | return; | |
192 | ||
193 | type = data[DMI_IPMI_TYPE]; | |
194 | slave_addr = data[DMI_IPMI_SLAVEADDR]; | |
195 | ||
196 | memcpy(&base_addr, data + DMI_IPMI_ADDR, sizeof(unsigned long)); | |
197 | if (len >= DMI_IPMI_VER2_LENGTH) { | |
198 | if (type == IPMI_DMI_TYPE_SSIF) { | |
199 | offset = 0; | |
200 | flags = 0; | |
201 | base_addr = data[DMI_IPMI_ADDR] >> 1; | |
202 | if (base_addr == 0) { | |
203 | /* | |
204 | * Some broken systems put the I2C address in | |
205 | * the slave address field. We try to | |
206 | * accommodate them here. | |
207 | */ | |
208 | base_addr = data[DMI_IPMI_SLAVEADDR] >> 1; | |
209 | slave_addr = 0; | |
210 | } | |
211 | } else { | |
212 | if (base_addr & 1) { | |
213 | /* I/O */ | |
214 | base_addr &= DMI_IPMI_IO_MASK; | |
215 | } else { | |
216 | /* Memory */ | |
217 | flags = IORESOURCE_MEM; | |
218 | } | |
219 | ||
220 | /* | |
221 | * If bit 4 of byte 0x10 is set, then the lsb | |
222 | * for the address is odd. | |
223 | */ | |
224 | base_addr |= (data[DMI_IPMI_ACCESS] >> 4) & 1; | |
225 | ||
226 | irq = data[DMI_IPMI_IRQ]; | |
227 | ||
228 | /* | |
229 | * The top two bits of byte 0x10 hold the | |
230 | * register spacing. | |
231 | */ | |
232 | switch ((data[DMI_IPMI_ACCESS] >> 6) & 3) { | |
233 | case 0: /* Byte boundaries */ | |
234 | offset = 1; | |
235 | break; | |
236 | case 1: /* 32-bit boundaries */ | |
237 | offset = 4; | |
238 | break; | |
239 | case 2: /* 16-byte boundaries */ | |
240 | offset = 16; | |
241 | break; | |
242 | default: | |
243 | pr_err("ipmi:dmi: Invalid offset: 0"); | |
244 | return; | |
245 | } | |
246 | } | |
247 | } else { | |
248 | /* Old DMI spec. */ | |
249 | /* | |
250 | * Note that technically, the lower bit of the base | |
251 | * address should be 1 if the address is I/O and 0 if | |
252 | * the address is in memory. So many systems get that | |
253 | * wrong (and all that I have seen are I/O) so we just | |
254 | * ignore that bit and assume I/O. Systems that use | |
255 | * memory should use the newer spec, anyway. | |
256 | */ | |
257 | base_addr = base_addr & DMI_IPMI_IO_MASK; | |
258 | offset = 1; | |
259 | } | |
260 | ||
261 | dmi_add_platform_ipmi(base_addr, flags, slave_addr, irq, | |
262 | offset, type); | |
263 | } | |
264 | ||
265 | static int __init scan_for_dmi_ipmi(void) | |
266 | { | |
267 | const struct dmi_device *dev = NULL; | |
268 | ||
269 | while ((dev = dmi_find_device(DMI_DEV_TYPE_IPMI, NULL, dev))) | |
270 | dmi_decode_ipmi((const struct dmi_header *) dev->device_data); | |
271 | ||
272 | return 0; | |
273 | } | |
274 | subsys_initcall(scan_for_dmi_ipmi); |