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