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