1 | // SPDX-License-Identifier: GPL-2.0-or-later |
---|---|
2 | /* |
3 | * osi.c - _OSI implementation |
4 | * |
5 | * Copyright (C) 2016 Intel Corporation |
6 | * Author: Lv Zheng <lv.zheng@intel.com> |
7 | */ |
8 | |
9 | /* Uncomment next line to get verbose printout */ |
10 | /* #define DEBUG */ |
11 | #define pr_fmt(fmt) "ACPI: " fmt |
12 | |
13 | #include <linux/module.h> |
14 | #include <linux/kernel.h> |
15 | #include <linux/acpi.h> |
16 | #include <linux/dmi.h> |
17 | #include <linux/platform_data/x86/apple.h> |
18 | |
19 | #include "internal.h" |
20 | |
21 | |
22 | #define OSI_STRING_LENGTH_MAX 64 |
23 | #define OSI_STRING_ENTRIES_MAX 16 |
24 | |
25 | struct acpi_osi_entry { |
26 | char string[OSI_STRING_LENGTH_MAX]; |
27 | bool enable; |
28 | }; |
29 | |
30 | static struct acpi_osi_config { |
31 | u8 default_disabling; |
32 | unsigned int linux_enable:1; |
33 | unsigned int linux_dmi:1; |
34 | unsigned int linux_cmdline:1; |
35 | unsigned int darwin_enable:1; |
36 | unsigned int darwin_dmi:1; |
37 | unsigned int darwin_cmdline:1; |
38 | } osi_config; |
39 | |
40 | static struct acpi_osi_config osi_config; |
41 | static struct acpi_osi_entry |
42 | osi_setup_entries[OSI_STRING_ENTRIES_MAX] __initdata = { |
43 | {"Module Device", true}, |
44 | {"Processor Device", true}, |
45 | {"3.0 _SCP Extensions", true}, |
46 | {"Processor Aggregator Device", true}, |
47 | }; |
48 | |
49 | static u32 acpi_osi_handler(acpi_string interface, u32 supported) |
50 | { |
51 | if (!strcmp("Linux", interface)) { |
52 | pr_notice_once(FW_BUG |
53 | "BIOS _OSI(Linux) query %s%s\n", |
54 | osi_config.linux_enable ? "honored": "ignored", |
55 | osi_config.linux_cmdline ? " via cmdline": |
56 | osi_config.linux_dmi ? " via DMI": ""); |
57 | } |
58 | if (!strcmp("Darwin", interface)) { |
59 | pr_notice_once( |
60 | "BIOS _OSI(Darwin) query %s%s\n", |
61 | osi_config.darwin_enable ? "honored": "ignored", |
62 | osi_config.darwin_cmdline ? " via cmdline": |
63 | osi_config.darwin_dmi ? " via DMI": ""); |
64 | } |
65 | |
66 | return supported; |
67 | } |
68 | |
69 | void __init acpi_osi_setup(char *str) |
70 | { |
71 | struct acpi_osi_entry *osi; |
72 | bool enable = true; |
73 | int i; |
74 | |
75 | if (!acpi_gbl_create_osi_method) |
76 | return; |
77 | |
78 | if (str == NULL || *str == '\0') { |
79 | pr_info("_OSI method disabled\n"); |
80 | acpi_gbl_create_osi_method = FALSE; |
81 | return; |
82 | } |
83 | |
84 | if (*str == '!') { |
85 | str++; |
86 | if (*str == '\0') { |
87 | /* Do not override acpi_osi=!* */ |
88 | if (!osi_config.default_disabling) |
89 | osi_config.default_disabling = |
90 | ACPI_DISABLE_ALL_VENDOR_STRINGS; |
91 | return; |
92 | } else if (*str == '*') { |
93 | osi_config.default_disabling = ACPI_DISABLE_ALL_STRINGS; |
94 | for (i = 0; i < OSI_STRING_ENTRIES_MAX; i++) { |
95 | osi = &osi_setup_entries[i]; |
96 | osi->enable = false; |
97 | } |
98 | return; |
99 | } else if (*str == '!') { |
100 | osi_config.default_disabling = 0; |
101 | return; |
102 | } |
103 | enable = false; |
104 | } |
105 | |
106 | for (i = 0; i < OSI_STRING_ENTRIES_MAX; i++) { |
107 | osi = &osi_setup_entries[i]; |
108 | if (!strcmp(osi->string, str)) { |
109 | osi->enable = enable; |
110 | break; |
111 | } else if (osi->string[0] == '\0') { |
112 | osi->enable = enable; |
113 | strscpy(osi->string, str, OSI_STRING_LENGTH_MAX); |
114 | break; |
115 | } |
116 | } |
117 | } |
118 | |
119 | static void __init __acpi_osi_setup_darwin(bool enable) |
120 | { |
121 | osi_config.darwin_enable = !!enable; |
122 | if (enable) { |
123 | acpi_osi_setup(str: "!"); |
124 | acpi_osi_setup(str: "Darwin"); |
125 | } else { |
126 | acpi_osi_setup(str: "!!"); |
127 | acpi_osi_setup(str: "!Darwin"); |
128 | } |
129 | } |
130 | |
131 | static void __init acpi_osi_setup_darwin(bool enable) |
132 | { |
133 | /* Override acpi_osi_dmi_blacklisted() */ |
134 | osi_config.darwin_dmi = 0; |
135 | osi_config.darwin_cmdline = 1; |
136 | __acpi_osi_setup_darwin(enable); |
137 | } |
138 | |
139 | /* |
140 | * The story of _OSI(Linux) |
141 | * |
142 | * From pre-history through Linux-2.6.22, Linux responded TRUE upon a BIOS |
143 | * OSI(Linux) query. |
144 | * |
145 | * Unfortunately, reference BIOS writers got wind of this and put |
146 | * OSI(Linux) in their example code, quickly exposing this string as |
147 | * ill-conceived and opening the door to an un-bounded number of BIOS |
148 | * incompatibilities. |
149 | * |
150 | * For example, OSI(Linux) was used on resume to re-POST a video card on |
151 | * one system, because Linux at that time could not do a speedy restore in |
152 | * its native driver. But then upon gaining quick native restore |
153 | * capability, Linux has no way to tell the BIOS to skip the time-consuming |
154 | * POST -- putting Linux at a permanent performance disadvantage. On |
155 | * another system, the BIOS writer used OSI(Linux) to infer native OS |
156 | * support for IPMI! On other systems, OSI(Linux) simply got in the way of |
157 | * Linux claiming to be compatible with other operating systems, exposing |
158 | * BIOS issues such as skipped device initialization. |
159 | * |
160 | * So "Linux" turned out to be a really poor chose of OSI string, and from |
161 | * Linux-2.6.23 onward we respond FALSE. |
162 | * |
163 | * BIOS writers should NOT query _OSI(Linux) on future systems. Linux will |
164 | * complain on the console when it sees it, and return FALSE. To get Linux |
165 | * to return TRUE for your system will require a kernel source update to |
166 | * add a DMI entry, or boot with "acpi_osi=Linux" |
167 | */ |
168 | static void __init __acpi_osi_setup_linux(bool enable) |
169 | { |
170 | osi_config.linux_enable = !!enable; |
171 | if (enable) |
172 | acpi_osi_setup(str: "Linux"); |
173 | else |
174 | acpi_osi_setup(str: "!Linux"); |
175 | } |
176 | |
177 | static void __init acpi_osi_setup_linux(bool enable) |
178 | { |
179 | /* Override acpi_osi_dmi_blacklisted() */ |
180 | osi_config.linux_dmi = 0; |
181 | osi_config.linux_cmdline = 1; |
182 | __acpi_osi_setup_linux(enable); |
183 | } |
184 | |
185 | /* |
186 | * Modify the list of "OS Interfaces" reported to BIOS via _OSI |
187 | * |
188 | * empty string disables _OSI |
189 | * string starting with '!' disables that string |
190 | * otherwise string is added to list, augmenting built-in strings |
191 | */ |
192 | static void __init acpi_osi_setup_late(void) |
193 | { |
194 | struct acpi_osi_entry *osi; |
195 | char *str; |
196 | int i; |
197 | acpi_status status; |
198 | |
199 | if (osi_config.default_disabling) { |
200 | status = acpi_update_interfaces(action: osi_config.default_disabling); |
201 | if (ACPI_SUCCESS(status)) |
202 | pr_info("Disabled all _OSI OS vendors%s\n", |
203 | osi_config.default_disabling == |
204 | ACPI_DISABLE_ALL_STRINGS ? |
205 | " and feature groups": ""); |
206 | } |
207 | |
208 | for (i = 0; i < OSI_STRING_ENTRIES_MAX; i++) { |
209 | osi = &osi_setup_entries[i]; |
210 | str = osi->string; |
211 | if (*str == '\0') |
212 | break; |
213 | if (osi->enable) { |
214 | status = acpi_install_interface(interface_name: str); |
215 | if (ACPI_SUCCESS(status)) |
216 | pr_info("Added _OSI(%s)\n", str); |
217 | } else { |
218 | status = acpi_remove_interface(interface_name: str); |
219 | if (ACPI_SUCCESS(status)) |
220 | pr_info("Deleted _OSI(%s)\n", str); |
221 | } |
222 | } |
223 | } |
224 | |
225 | static int __init osi_setup(char *str) |
226 | { |
227 | if (str && !strcmp("Linux", str)) |
228 | acpi_osi_setup_linux(enable: true); |
229 | else if (str && !strcmp("!Linux", str)) |
230 | acpi_osi_setup_linux(enable: false); |
231 | else if (str && !strcmp("Darwin", str)) |
232 | acpi_osi_setup_darwin(enable: true); |
233 | else if (str && !strcmp("!Darwin", str)) |
234 | acpi_osi_setup_darwin(enable: false); |
235 | else |
236 | acpi_osi_setup(str); |
237 | |
238 | return 1; |
239 | } |
240 | __setup("acpi_osi=", osi_setup); |
241 | |
242 | bool acpi_osi_is_win8(void) |
243 | { |
244 | return acpi_gbl_osi_data >= ACPI_OSI_WIN_8; |
245 | } |
246 | EXPORT_SYMBOL(acpi_osi_is_win8); |
247 | |
248 | static void __init acpi_osi_dmi_darwin(void) |
249 | { |
250 | pr_notice("DMI detected to setup _OSI(\"Darwin\"): Apple hardware\n"); |
251 | osi_config.darwin_dmi = 1; |
252 | __acpi_osi_setup_darwin(enable: true); |
253 | } |
254 | |
255 | static void __init acpi_osi_dmi_linux(bool enable, |
256 | const struct dmi_system_id *d) |
257 | { |
258 | pr_notice("DMI detected to setup _OSI(\"Linux\"): %s\n", d->ident); |
259 | osi_config.linux_dmi = 1; |
260 | __acpi_osi_setup_linux(enable); |
261 | } |
262 | |
263 | static int __init dmi_enable_osi_linux(const struct dmi_system_id *d) |
264 | { |
265 | acpi_osi_dmi_linux(enable: true, d); |
266 | |
267 | return 0; |
268 | } |
269 | |
270 | static int __init dmi_disable_osi_vista(const struct dmi_system_id *d) |
271 | { |
272 | pr_notice("DMI detected: %s\n", d->ident); |
273 | acpi_osi_setup(str: "!Windows 2006"); |
274 | acpi_osi_setup(str: "!Windows 2006 SP1"); |
275 | acpi_osi_setup(str: "!Windows 2006 SP2"); |
276 | |
277 | return 0; |
278 | } |
279 | |
280 | static int __init dmi_disable_osi_win7(const struct dmi_system_id *d) |
281 | { |
282 | pr_notice("DMI detected: %s\n", d->ident); |
283 | acpi_osi_setup(str: "!Windows 2009"); |
284 | |
285 | return 0; |
286 | } |
287 | |
288 | static int __init dmi_disable_osi_win8(const struct dmi_system_id *d) |
289 | { |
290 | pr_notice("DMI detected: %s\n", d->ident); |
291 | acpi_osi_setup(str: "!Windows 2012"); |
292 | |
293 | return 0; |
294 | } |
295 | |
296 | /* |
297 | * Linux default _OSI response behavior is determined by this DMI table. |
298 | * |
299 | * Note that _OSI("Linux")/_OSI("Darwin") determined here can be overridden |
300 | * by acpi_osi=!Linux/acpi_osi=!Darwin command line options. |
301 | */ |
302 | static const struct dmi_system_id acpi_osi_dmi_table[] __initconst = { |
303 | { |
304 | .callback = dmi_disable_osi_vista, |
305 | .ident = "Fujitsu Siemens", |
306 | .matches = { |
307 | DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"), |
308 | DMI_MATCH(DMI_PRODUCT_NAME, "ESPRIMO Mobile V5505"), |
309 | }, |
310 | }, |
311 | { |
312 | /* |
313 | * There have a NVIF method in MSI GX723 DSDT need call by Nvidia |
314 | * driver (e.g. nouveau) when user press brightness hotkey. |
315 | * Currently, nouveau driver didn't do the job and it causes there |
316 | * have a infinite while loop in DSDT when user press hotkey. |
317 | * We add MSI GX723's dmi information to this table for workaround |
318 | * this issue. |
319 | * Will remove MSI GX723 from the table after nouveau grows support. |
320 | */ |
321 | .callback = dmi_disable_osi_vista, |
322 | .ident = "MSI GX723", |
323 | .matches = { |
324 | DMI_MATCH(DMI_SYS_VENDOR, "Micro-Star International"), |
325 | DMI_MATCH(DMI_PRODUCT_NAME, "GX723"), |
326 | }, |
327 | }, |
328 | { |
329 | .callback = dmi_disable_osi_vista, |
330 | .ident = "Sony VGN-NS10J_S", |
331 | .matches = { |
332 | DMI_MATCH(DMI_SYS_VENDOR, "Sony Corporation"), |
333 | DMI_MATCH(DMI_PRODUCT_NAME, "VGN-NS10J_S"), |
334 | }, |
335 | }, |
336 | { |
337 | .callback = dmi_disable_osi_vista, |
338 | .ident = "Sony VGN-SR290J", |
339 | .matches = { |
340 | DMI_MATCH(DMI_SYS_VENDOR, "Sony Corporation"), |
341 | DMI_MATCH(DMI_PRODUCT_NAME, "VGN-SR290J"), |
342 | }, |
343 | }, |
344 | { |
345 | .callback = dmi_disable_osi_vista, |
346 | .ident = "VGN-NS50B_L", |
347 | .matches = { |
348 | DMI_MATCH(DMI_SYS_VENDOR, "Sony Corporation"), |
349 | DMI_MATCH(DMI_PRODUCT_NAME, "VGN-NS50B_L"), |
350 | }, |
351 | }, |
352 | { |
353 | .callback = dmi_disable_osi_vista, |
354 | .ident = "VGN-SR19XN", |
355 | .matches = { |
356 | DMI_MATCH(DMI_SYS_VENDOR, "Sony Corporation"), |
357 | DMI_MATCH(DMI_PRODUCT_NAME, "VGN-SR19XN"), |
358 | }, |
359 | }, |
360 | { |
361 | .callback = dmi_disable_osi_vista, |
362 | .ident = "Toshiba Satellite L355", |
363 | .matches = { |
364 | DMI_MATCH(DMI_SYS_VENDOR, "TOSHIBA"), |
365 | DMI_MATCH(DMI_PRODUCT_VERSION, "Satellite L355"), |
366 | }, |
367 | }, |
368 | { |
369 | .callback = dmi_disable_osi_win7, |
370 | .ident = "ASUS K50IJ", |
371 | .matches = { |
372 | DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK Computer Inc."), |
373 | DMI_MATCH(DMI_PRODUCT_NAME, "K50IJ"), |
374 | }, |
375 | }, |
376 | { |
377 | .callback = dmi_disable_osi_vista, |
378 | .ident = "Toshiba P305D", |
379 | .matches = { |
380 | DMI_MATCH(DMI_SYS_VENDOR, "TOSHIBA"), |
381 | DMI_MATCH(DMI_PRODUCT_NAME, "Satellite P305D"), |
382 | }, |
383 | }, |
384 | { |
385 | .callback = dmi_disable_osi_vista, |
386 | .ident = "Toshiba NB100", |
387 | .matches = { |
388 | DMI_MATCH(DMI_SYS_VENDOR, "TOSHIBA"), |
389 | DMI_MATCH(DMI_PRODUCT_NAME, "NB100"), |
390 | }, |
391 | }, |
392 | |
393 | /* |
394 | * The wireless hotkey does not work on those machines when |
395 | * returning true for _OSI("Windows 2012") |
396 | */ |
397 | { |
398 | .callback = dmi_disable_osi_win8, |
399 | .ident = "Dell Inspiron 7737", |
400 | .matches = { |
401 | DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), |
402 | DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 7737"), |
403 | }, |
404 | }, |
405 | { |
406 | .callback = dmi_disable_osi_win8, |
407 | .ident = "Dell Inspiron 7537", |
408 | .matches = { |
409 | DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), |
410 | DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 7537"), |
411 | }, |
412 | }, |
413 | { |
414 | .callback = dmi_disable_osi_win8, |
415 | .ident = "Dell Inspiron 5437", |
416 | .matches = { |
417 | DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), |
418 | DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 5437"), |
419 | }, |
420 | }, |
421 | { |
422 | .callback = dmi_disable_osi_win8, |
423 | .ident = "Dell Inspiron 3437", |
424 | .matches = { |
425 | DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), |
426 | DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 3437"), |
427 | }, |
428 | }, |
429 | { |
430 | .callback = dmi_disable_osi_win8, |
431 | .ident = "Dell Vostro 3446", |
432 | .matches = { |
433 | DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), |
434 | DMI_MATCH(DMI_PRODUCT_NAME, "Vostro 3446"), |
435 | }, |
436 | }, |
437 | { |
438 | .callback = dmi_disable_osi_win8, |
439 | .ident = "Dell Vostro 3546", |
440 | .matches = { |
441 | DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), |
442 | DMI_MATCH(DMI_PRODUCT_NAME, "Vostro 3546"), |
443 | }, |
444 | }, |
445 | |
446 | /* |
447 | * BIOS invocation of _OSI(Linux) is almost always a BIOS bug. |
448 | * Linux ignores it, except for the machines enumerated below. |
449 | */ |
450 | |
451 | /* |
452 | * Without this EEEpc exports a non working WMI interface, with |
453 | * this it exports a working "good old" eeepc_laptop interface, |
454 | * fixing both brightness control, and rfkill not working. |
455 | */ |
456 | { |
457 | .callback = dmi_enable_osi_linux, |
458 | .ident = "Asus EEE PC 1015PX", |
459 | .matches = { |
460 | DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK Computer INC."), |
461 | DMI_MATCH(DMI_PRODUCT_NAME, "1015PX"), |
462 | }, |
463 | }, |
464 | {} |
465 | }; |
466 | |
467 | static __init void acpi_osi_dmi_blacklisted(void) |
468 | { |
469 | dmi_check_system(list: acpi_osi_dmi_table); |
470 | |
471 | /* Enable _OSI("Darwin") for Apple platforms. */ |
472 | if (x86_apple_machine) |
473 | acpi_osi_dmi_darwin(); |
474 | } |
475 | |
476 | int __init early_acpi_osi_init(void) |
477 | { |
478 | acpi_osi_dmi_blacklisted(); |
479 | |
480 | return 0; |
481 | } |
482 | |
483 | int __init acpi_osi_init(void) |
484 | { |
485 | acpi_install_interface_handler(handler: acpi_osi_handler); |
486 | acpi_osi_setup_late(); |
487 | |
488 | return 0; |
489 | } |
490 |
Definitions
- acpi_osi_entry
- acpi_osi_config
- osi_config
- osi_config
- osi_setup_entries
- acpi_osi_handler
- acpi_osi_setup
- __acpi_osi_setup_darwin
- acpi_osi_setup_darwin
- __acpi_osi_setup_linux
- acpi_osi_setup_linux
- acpi_osi_setup_late
- osi_setup
- acpi_osi_is_win8
- acpi_osi_dmi_darwin
- acpi_osi_dmi_linux
- dmi_enable_osi_linux
- dmi_disable_osi_vista
- dmi_disable_osi_win7
- dmi_disable_osi_win8
- acpi_osi_dmi_table
- acpi_osi_dmi_blacklisted
- early_acpi_osi_init
Improve your Profiling and Debugging skills
Find out more