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 wireless hotkey does not work on those machines when
394 * returning true for _OSI("Windows 2012")
395 */
396 {
397 .callback = dmi_disable_osi_win8,
398 .ident = "Dell Inspiron 7737",
399 .matches = {
400 DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
401 DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 7737"),
402 },
403 },
404 {
405 .callback = dmi_disable_osi_win8,
406 .ident = "Dell Inspiron 7537",
407 .matches = {
408 DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
409 DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 7537"),
410 },
411 },
412 {
413 .callback = dmi_disable_osi_win8,
414 .ident = "Dell Inspiron 5437",
415 .matches = {
416 DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
417 DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 5437"),
418 },
419 },
420 {
421 .callback = dmi_disable_osi_win8,
422 .ident = "Dell Inspiron 3437",
423 .matches = {
424 DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
425 DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 3437"),
426 },
427 },
428 {
429 .callback = dmi_disable_osi_win8,
430 .ident = "Dell Vostro 3446",
431 .matches = {
432 DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
433 DMI_MATCH(DMI_PRODUCT_NAME, "Vostro 3446"),
434 },
435 },
436 {
437 .callback = dmi_disable_osi_win8,
438 .ident = "Dell Vostro 3546",
439 .matches = {
440 DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
441 DMI_MATCH(DMI_PRODUCT_NAME, "Vostro 3546"),
442 },
443 },
444
445 /*
446 * BIOS invocation of _OSI(Linux) is almost always a BIOS bug.
447 * Linux ignores it, except for the machines enumerated below.
448 */
449
450 /*
451 * Without this EEEpc exports a non working WMI interface, with
452 * this it exports a working "good old" eeepc_laptop interface,
453 * fixing both brightness control, and rfkill not working.
454 */
455 {
456 .callback = dmi_enable_osi_linux,
457 .ident = "Asus EEE PC 1015PX",
458 .matches = {
459 DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK Computer INC."),
460 DMI_MATCH(DMI_PRODUCT_NAME, "1015PX"),
461 },
462 },
463 {}
464 };
465
acpi_osi_dmi_blacklisted(void)466 static __init void acpi_osi_dmi_blacklisted(void)
467 {
468 dmi_check_system(acpi_osi_dmi_table);
469
470 /* Enable _OSI("Darwin") for Apple platforms. */
471 if (x86_apple_machine)
472 acpi_osi_dmi_darwin();
473 }
474
early_acpi_osi_init(void)475 int __init early_acpi_osi_init(void)
476 {
477 acpi_osi_dmi_blacklisted();
478
479 return 0;
480 }
481
acpi_osi_init(void)482 int __init acpi_osi_init(void)
483 {
484 acpi_install_interface_handler(acpi_osi_handler);
485 acpi_osi_setup_late();
486
487 return 0;
488 }
489