diff options
Diffstat (limited to 'arch/i386/kernel/dmi_scan.c')
-rw-r--r-- | arch/i386/kernel/dmi_scan.c | 487 |
1 files changed, 487 insertions, 0 deletions
diff --git a/arch/i386/kernel/dmi_scan.c b/arch/i386/kernel/dmi_scan.c new file mode 100644 index 000000000000..6ed7e28f306c --- /dev/null +++ b/arch/i386/kernel/dmi_scan.c | |||
@@ -0,0 +1,487 @@ | |||
1 | #include <linux/types.h> | ||
2 | #include <linux/kernel.h> | ||
3 | #include <linux/string.h> | ||
4 | #include <linux/init.h> | ||
5 | #include <linux/module.h> | ||
6 | #include <linux/slab.h> | ||
7 | #include <linux/acpi.h> | ||
8 | #include <asm/io.h> | ||
9 | #include <linux/pm.h> | ||
10 | #include <asm/system.h> | ||
11 | #include <linux/dmi.h> | ||
12 | #include <linux/bootmem.h> | ||
13 | |||
14 | |||
15 | struct dmi_header | ||
16 | { | ||
17 | u8 type; | ||
18 | u8 length; | ||
19 | u16 handle; | ||
20 | }; | ||
21 | |||
22 | #undef DMI_DEBUG | ||
23 | |||
24 | #ifdef DMI_DEBUG | ||
25 | #define dmi_printk(x) printk x | ||
26 | #else | ||
27 | #define dmi_printk(x) | ||
28 | #endif | ||
29 | |||
30 | static char * __init dmi_string(struct dmi_header *dm, u8 s) | ||
31 | { | ||
32 | u8 *bp=(u8 *)dm; | ||
33 | bp+=dm->length; | ||
34 | if(!s) | ||
35 | return ""; | ||
36 | s--; | ||
37 | while(s>0 && *bp) | ||
38 | { | ||
39 | bp+=strlen(bp); | ||
40 | bp++; | ||
41 | s--; | ||
42 | } | ||
43 | return bp; | ||
44 | } | ||
45 | |||
46 | /* | ||
47 | * We have to be cautious here. We have seen BIOSes with DMI pointers | ||
48 | * pointing to completely the wrong place for example | ||
49 | */ | ||
50 | |||
51 | static int __init dmi_table(u32 base, int len, int num, void (*decode)(struct dmi_header *)) | ||
52 | { | ||
53 | u8 *buf; | ||
54 | struct dmi_header *dm; | ||
55 | u8 *data; | ||
56 | int i=0; | ||
57 | |||
58 | buf = bt_ioremap(base, len); | ||
59 | if(buf==NULL) | ||
60 | return -1; | ||
61 | |||
62 | data = buf; | ||
63 | |||
64 | /* | ||
65 | * Stop when we see all the items the table claimed to have | ||
66 | * OR we run off the end of the table (also happens) | ||
67 | */ | ||
68 | |||
69 | while(i<num && data-buf+sizeof(struct dmi_header)<=len) | ||
70 | { | ||
71 | dm=(struct dmi_header *)data; | ||
72 | /* | ||
73 | * We want to know the total length (formated area and strings) | ||
74 | * before decoding to make sure we won't run off the table in | ||
75 | * dmi_decode or dmi_string | ||
76 | */ | ||
77 | data+=dm->length; | ||
78 | while(data-buf<len-1 && (data[0] || data[1])) | ||
79 | data++; | ||
80 | if(data-buf<len-1) | ||
81 | decode(dm); | ||
82 | data+=2; | ||
83 | i++; | ||
84 | } | ||
85 | bt_iounmap(buf, len); | ||
86 | return 0; | ||
87 | } | ||
88 | |||
89 | |||
90 | inline static int __init dmi_checksum(u8 *buf) | ||
91 | { | ||
92 | u8 sum=0; | ||
93 | int a; | ||
94 | |||
95 | for(a=0; a<15; a++) | ||
96 | sum+=buf[a]; | ||
97 | return (sum==0); | ||
98 | } | ||
99 | |||
100 | static int __init dmi_iterate(void (*decode)(struct dmi_header *)) | ||
101 | { | ||
102 | u8 buf[15]; | ||
103 | char __iomem *p, *q; | ||
104 | |||
105 | /* | ||
106 | * no iounmap() for that ioremap(); it would be a no-op, but it's | ||
107 | * so early in setup that sucker gets confused into doing what | ||
108 | * it shouldn't if we actually call it. | ||
109 | */ | ||
110 | p = ioremap(0xF0000, 0x10000); | ||
111 | if (p == NULL) | ||
112 | return -1; | ||
113 | for (q = p; q < p + 0x10000; q += 16) { | ||
114 | memcpy_fromio(buf, q, 15); | ||
115 | if(memcmp(buf, "_DMI_", 5)==0 && dmi_checksum(buf)) | ||
116 | { | ||
117 | u16 num=buf[13]<<8|buf[12]; | ||
118 | u16 len=buf[7]<<8|buf[6]; | ||
119 | u32 base=buf[11]<<24|buf[10]<<16|buf[9]<<8|buf[8]; | ||
120 | |||
121 | /* | ||
122 | * DMI version 0.0 means that the real version is taken from | ||
123 | * the SMBIOS version, which we don't know at this point. | ||
124 | */ | ||
125 | if(buf[14]!=0) | ||
126 | printk(KERN_INFO "DMI %d.%d present.\n", | ||
127 | buf[14]>>4, buf[14]&0x0F); | ||
128 | else | ||
129 | printk(KERN_INFO "DMI present.\n"); | ||
130 | dmi_printk((KERN_INFO "%d structures occupying %d bytes.\n", | ||
131 | num, len)); | ||
132 | dmi_printk((KERN_INFO "DMI table at 0x%08X.\n", | ||
133 | base)); | ||
134 | if(dmi_table(base,len, num, decode)==0) | ||
135 | return 0; | ||
136 | } | ||
137 | } | ||
138 | return -1; | ||
139 | } | ||
140 | |||
141 | static char *dmi_ident[DMI_STRING_MAX]; | ||
142 | |||
143 | /* | ||
144 | * Save a DMI string | ||
145 | */ | ||
146 | |||
147 | static void __init dmi_save_ident(struct dmi_header *dm, int slot, int string) | ||
148 | { | ||
149 | char *d = (char*)dm; | ||
150 | char *p = dmi_string(dm, d[string]); | ||
151 | if(p==NULL || *p == 0) | ||
152 | return; | ||
153 | if (dmi_ident[slot]) | ||
154 | return; | ||
155 | dmi_ident[slot] = alloc_bootmem(strlen(p)+1); | ||
156 | if(dmi_ident[slot]) | ||
157 | strcpy(dmi_ident[slot], p); | ||
158 | else | ||
159 | printk(KERN_ERR "dmi_save_ident: out of memory.\n"); | ||
160 | } | ||
161 | |||
162 | /* | ||
163 | * Ugly compatibility crap. | ||
164 | */ | ||
165 | #define dmi_blacklist dmi_system_id | ||
166 | #define NO_MATCH { DMI_NONE, NULL} | ||
167 | #define MATCH DMI_MATCH | ||
168 | |||
169 | /* | ||
170 | * Toshiba keyboard likes to repeat keys when they are not repeated. | ||
171 | */ | ||
172 | |||
173 | static __init int broken_toshiba_keyboard(struct dmi_blacklist *d) | ||
174 | { | ||
175 | printk(KERN_WARNING "Toshiba with broken keyboard detected. If your keyboard sometimes generates 3 keypresses instead of one, see http://davyd.ucc.asn.au/projects/toshiba/README\n"); | ||
176 | return 0; | ||
177 | } | ||
178 | |||
179 | |||
180 | #ifdef CONFIG_ACPI_SLEEP | ||
181 | static __init int reset_videomode_after_s3(struct dmi_blacklist *d) | ||
182 | { | ||
183 | /* See acpi_wakeup.S */ | ||
184 | extern long acpi_video_flags; | ||
185 | acpi_video_flags |= 2; | ||
186 | return 0; | ||
187 | } | ||
188 | #endif | ||
189 | |||
190 | |||
191 | #ifdef CONFIG_ACPI_BOOT | ||
192 | extern int acpi_force; | ||
193 | |||
194 | static __init __attribute__((unused)) int dmi_disable_acpi(struct dmi_blacklist *d) | ||
195 | { | ||
196 | if (!acpi_force) { | ||
197 | printk(KERN_NOTICE "%s detected: acpi off\n",d->ident); | ||
198 | disable_acpi(); | ||
199 | } else { | ||
200 | printk(KERN_NOTICE | ||
201 | "Warning: DMI blacklist says broken, but acpi forced\n"); | ||
202 | } | ||
203 | return 0; | ||
204 | } | ||
205 | |||
206 | /* | ||
207 | * Limit ACPI to CPU enumeration for HT | ||
208 | */ | ||
209 | static __init __attribute__((unused)) int force_acpi_ht(struct dmi_blacklist *d) | ||
210 | { | ||
211 | if (!acpi_force) { | ||
212 | printk(KERN_NOTICE "%s detected: force use of acpi=ht\n", d->ident); | ||
213 | disable_acpi(); | ||
214 | acpi_ht = 1; | ||
215 | } else { | ||
216 | printk(KERN_NOTICE | ||
217 | "Warning: acpi=force overrules DMI blacklist: acpi=ht\n"); | ||
218 | } | ||
219 | return 0; | ||
220 | } | ||
221 | #endif | ||
222 | |||
223 | #ifdef CONFIG_ACPI_PCI | ||
224 | static __init int disable_acpi_irq(struct dmi_blacklist *d) | ||
225 | { | ||
226 | if (!acpi_force) { | ||
227 | printk(KERN_NOTICE "%s detected: force use of acpi=noirq\n", | ||
228 | d->ident); | ||
229 | acpi_noirq_set(); | ||
230 | } | ||
231 | return 0; | ||
232 | } | ||
233 | static __init int disable_acpi_pci(struct dmi_blacklist *d) | ||
234 | { | ||
235 | if (!acpi_force) { | ||
236 | printk(KERN_NOTICE "%s detected: force use of pci=noacpi\n", | ||
237 | d->ident); | ||
238 | acpi_disable_pci(); | ||
239 | } | ||
240 | return 0; | ||
241 | } | ||
242 | #endif | ||
243 | |||
244 | /* | ||
245 | * Process the DMI blacklists | ||
246 | */ | ||
247 | |||
248 | |||
249 | /* | ||
250 | * This will be expanded over time to force things like the APM | ||
251 | * interrupt mask settings according to the laptop | ||
252 | */ | ||
253 | |||
254 | static __initdata struct dmi_blacklist dmi_blacklist[]={ | ||
255 | |||
256 | { broken_toshiba_keyboard, "Toshiba Satellite 4030cdt", { /* Keyboard generates spurious repeats */ | ||
257 | MATCH(DMI_PRODUCT_NAME, "S4030CDT/4.3"), | ||
258 | NO_MATCH, NO_MATCH, NO_MATCH | ||
259 | } }, | ||
260 | #ifdef CONFIG_ACPI_SLEEP | ||
261 | { reset_videomode_after_s3, "Toshiba Satellite 4030cdt", { /* Reset video mode after returning from ACPI S3 sleep */ | ||
262 | MATCH(DMI_PRODUCT_NAME, "S4030CDT/4.3"), | ||
263 | NO_MATCH, NO_MATCH, NO_MATCH | ||
264 | } }, | ||
265 | #endif | ||
266 | |||
267 | #ifdef CONFIG_ACPI_BOOT | ||
268 | /* | ||
269 | * If your system is blacklisted here, but you find that acpi=force | ||
270 | * works for you, please contact acpi-devel@sourceforge.net | ||
271 | */ | ||
272 | |||
273 | /* | ||
274 | * Boxes that need ACPI disabled | ||
275 | */ | ||
276 | |||
277 | { dmi_disable_acpi, "IBM Thinkpad", { | ||
278 | MATCH(DMI_BOARD_VENDOR, "IBM"), | ||
279 | MATCH(DMI_BOARD_NAME, "2629H1G"), | ||
280 | NO_MATCH, NO_MATCH }}, | ||
281 | |||
282 | /* | ||
283 | * Boxes that need acpi=ht | ||
284 | */ | ||
285 | |||
286 | { force_acpi_ht, "FSC Primergy T850", { | ||
287 | MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"), | ||
288 | MATCH(DMI_PRODUCT_NAME, "PRIMERGY T850"), | ||
289 | NO_MATCH, NO_MATCH }}, | ||
290 | |||
291 | { force_acpi_ht, "DELL GX240", { | ||
292 | MATCH(DMI_BOARD_VENDOR, "Dell Computer Corporation"), | ||
293 | MATCH(DMI_BOARD_NAME, "OptiPlex GX240"), | ||
294 | NO_MATCH, NO_MATCH }}, | ||
295 | |||
296 | { force_acpi_ht, "HP VISUALIZE NT Workstation", { | ||
297 | MATCH(DMI_BOARD_VENDOR, "Hewlett-Packard"), | ||
298 | MATCH(DMI_PRODUCT_NAME, "HP VISUALIZE NT Workstation"), | ||
299 | NO_MATCH, NO_MATCH }}, | ||
300 | |||
301 | { force_acpi_ht, "Compaq Workstation W8000", { | ||
302 | MATCH(DMI_SYS_VENDOR, "Compaq"), | ||
303 | MATCH(DMI_PRODUCT_NAME, "Workstation W8000"), | ||
304 | NO_MATCH, NO_MATCH }}, | ||
305 | |||
306 | { force_acpi_ht, "ASUS P4B266", { | ||
307 | MATCH(DMI_BOARD_VENDOR, "ASUSTeK Computer INC."), | ||
308 | MATCH(DMI_BOARD_NAME, "P4B266"), | ||
309 | NO_MATCH, NO_MATCH }}, | ||
310 | |||
311 | { force_acpi_ht, "ASUS P2B-DS", { | ||
312 | MATCH(DMI_BOARD_VENDOR, "ASUSTeK Computer INC."), | ||
313 | MATCH(DMI_BOARD_NAME, "P2B-DS"), | ||
314 | NO_MATCH, NO_MATCH }}, | ||
315 | |||
316 | { force_acpi_ht, "ASUS CUR-DLS", { | ||
317 | MATCH(DMI_BOARD_VENDOR, "ASUSTeK Computer INC."), | ||
318 | MATCH(DMI_BOARD_NAME, "CUR-DLS"), | ||
319 | NO_MATCH, NO_MATCH }}, | ||
320 | |||
321 | { force_acpi_ht, "ABIT i440BX-W83977", { | ||
322 | MATCH(DMI_BOARD_VENDOR, "ABIT <http://www.abit.com>"), | ||
323 | MATCH(DMI_BOARD_NAME, "i440BX-W83977 (BP6)"), | ||
324 | NO_MATCH, NO_MATCH }}, | ||
325 | |||
326 | { force_acpi_ht, "IBM Bladecenter", { | ||
327 | MATCH(DMI_BOARD_VENDOR, "IBM"), | ||
328 | MATCH(DMI_BOARD_NAME, "IBM eServer BladeCenter HS20"), | ||
329 | NO_MATCH, NO_MATCH }}, | ||
330 | |||
331 | { force_acpi_ht, "IBM eServer xSeries 360", { | ||
332 | MATCH(DMI_BOARD_VENDOR, "IBM"), | ||
333 | MATCH(DMI_BOARD_NAME, "eServer xSeries 360"), | ||
334 | NO_MATCH, NO_MATCH }}, | ||
335 | |||
336 | { force_acpi_ht, "IBM eserver xSeries 330", { | ||
337 | MATCH(DMI_BOARD_VENDOR, "IBM"), | ||
338 | MATCH(DMI_BOARD_NAME, "eserver xSeries 330"), | ||
339 | NO_MATCH, NO_MATCH }}, | ||
340 | |||
341 | { force_acpi_ht, "IBM eserver xSeries 440", { | ||
342 | MATCH(DMI_BOARD_VENDOR, "IBM"), | ||
343 | MATCH(DMI_PRODUCT_NAME, "eserver xSeries 440"), | ||
344 | NO_MATCH, NO_MATCH }}, | ||
345 | |||
346 | #endif // CONFIG_ACPI_BOOT | ||
347 | |||
348 | #ifdef CONFIG_ACPI_PCI | ||
349 | /* | ||
350 | * Boxes that need ACPI PCI IRQ routing disabled | ||
351 | */ | ||
352 | |||
353 | { disable_acpi_irq, "ASUS A7V", { | ||
354 | MATCH(DMI_BOARD_VENDOR, "ASUSTeK Computer INC"), | ||
355 | MATCH(DMI_BOARD_NAME, "<A7V>"), | ||
356 | /* newer BIOS, Revision 1011, does work */ | ||
357 | MATCH(DMI_BIOS_VERSION, "ASUS A7V ACPI BIOS Revision 1007"), | ||
358 | NO_MATCH }}, | ||
359 | |||
360 | /* | ||
361 | * Boxes that need ACPI PCI IRQ routing and PCI scan disabled | ||
362 | */ | ||
363 | { disable_acpi_pci, "ASUS PR-DLS", { /* _BBN 0 bug */ | ||
364 | MATCH(DMI_BOARD_VENDOR, "ASUSTeK Computer INC."), | ||
365 | MATCH(DMI_BOARD_NAME, "PR-DLS"), | ||
366 | MATCH(DMI_BIOS_VERSION, "ASUS PR-DLS ACPI BIOS Revision 1010"), | ||
367 | MATCH(DMI_BIOS_DATE, "03/21/2003") }}, | ||
368 | |||
369 | { disable_acpi_pci, "Acer TravelMate 36x Laptop", { | ||
370 | MATCH(DMI_SYS_VENDOR, "Acer"), | ||
371 | MATCH(DMI_PRODUCT_NAME, "TravelMate 360"), | ||
372 | NO_MATCH, NO_MATCH | ||
373 | } }, | ||
374 | |||
375 | #endif | ||
376 | |||
377 | { NULL, } | ||
378 | }; | ||
379 | |||
380 | /* | ||
381 | * Process a DMI table entry. Right now all we care about are the BIOS | ||
382 | * and machine entries. For 2.5 we should pull the smbus controller info | ||
383 | * out of here. | ||
384 | */ | ||
385 | |||
386 | static void __init dmi_decode(struct dmi_header *dm) | ||
387 | { | ||
388 | #ifdef DMI_DEBUG | ||
389 | u8 *data = (u8 *)dm; | ||
390 | #endif | ||
391 | |||
392 | switch(dm->type) | ||
393 | { | ||
394 | case 0: | ||
395 | dmi_printk(("BIOS Vendor: %s\n", | ||
396 | dmi_string(dm, data[4]))); | ||
397 | dmi_save_ident(dm, DMI_BIOS_VENDOR, 4); | ||
398 | dmi_printk(("BIOS Version: %s\n", | ||
399 | dmi_string(dm, data[5]))); | ||
400 | dmi_save_ident(dm, DMI_BIOS_VERSION, 5); | ||
401 | dmi_printk(("BIOS Release: %s\n", | ||
402 | dmi_string(dm, data[8]))); | ||
403 | dmi_save_ident(dm, DMI_BIOS_DATE, 8); | ||
404 | break; | ||
405 | case 1: | ||
406 | dmi_printk(("System Vendor: %s\n", | ||
407 | dmi_string(dm, data[4]))); | ||
408 | dmi_save_ident(dm, DMI_SYS_VENDOR, 4); | ||
409 | dmi_printk(("Product Name: %s\n", | ||
410 | dmi_string(dm, data[5]))); | ||
411 | dmi_save_ident(dm, DMI_PRODUCT_NAME, 5); | ||
412 | dmi_printk(("Version: %s\n", | ||
413 | dmi_string(dm, data[6]))); | ||
414 | dmi_save_ident(dm, DMI_PRODUCT_VERSION, 6); | ||
415 | dmi_printk(("Serial Number: %s\n", | ||
416 | dmi_string(dm, data[7]))); | ||
417 | break; | ||
418 | case 2: | ||
419 | dmi_printk(("Board Vendor: %s\n", | ||
420 | dmi_string(dm, data[4]))); | ||
421 | dmi_save_ident(dm, DMI_BOARD_VENDOR, 4); | ||
422 | dmi_printk(("Board Name: %s\n", | ||
423 | dmi_string(dm, data[5]))); | ||
424 | dmi_save_ident(dm, DMI_BOARD_NAME, 5); | ||
425 | dmi_printk(("Board Version: %s\n", | ||
426 | dmi_string(dm, data[6]))); | ||
427 | dmi_save_ident(dm, DMI_BOARD_VERSION, 6); | ||
428 | break; | ||
429 | } | ||
430 | } | ||
431 | |||
432 | void __init dmi_scan_machine(void) | ||
433 | { | ||
434 | int err = dmi_iterate(dmi_decode); | ||
435 | if(err == 0) | ||
436 | dmi_check_system(dmi_blacklist); | ||
437 | else | ||
438 | printk(KERN_INFO "DMI not present.\n"); | ||
439 | } | ||
440 | |||
441 | |||
442 | /** | ||
443 | * dmi_check_system - check system DMI data | ||
444 | * @list: array of dmi_system_id structures to match against | ||
445 | * | ||
446 | * Walk the blacklist table running matching functions until someone | ||
447 | * returns non zero or we hit the end. Callback function is called for | ||
448 | * each successfull match. Returns the number of matches. | ||
449 | */ | ||
450 | int dmi_check_system(struct dmi_system_id *list) | ||
451 | { | ||
452 | int i, count = 0; | ||
453 | struct dmi_system_id *d = list; | ||
454 | |||
455 | while (d->ident) { | ||
456 | for (i = 0; i < ARRAY_SIZE(d->matches); i++) { | ||
457 | int s = d->matches[i].slot; | ||
458 | if (s == DMI_NONE) | ||
459 | continue; | ||
460 | if (dmi_ident[s] && strstr(dmi_ident[s], d->matches[i].substr)) | ||
461 | continue; | ||
462 | /* No match */ | ||
463 | goto fail; | ||
464 | } | ||
465 | if (d->callback && d->callback(d)) | ||
466 | break; | ||
467 | count++; | ||
468 | fail: d++; | ||
469 | } | ||
470 | |||
471 | return count; | ||
472 | } | ||
473 | |||
474 | EXPORT_SYMBOL(dmi_check_system); | ||
475 | |||
476 | /** | ||
477 | * dmi_get_system_info - return DMI data value | ||
478 | * @field: data index (see enum dmi_filed) | ||
479 | * | ||
480 | * Returns one DMI data value, can be used to perform | ||
481 | * complex DMI data checks. | ||
482 | */ | ||
483 | char * dmi_get_system_info(int field) | ||
484 | { | ||
485 | return dmi_ident[field]; | ||
486 | } | ||
487 | |||