treewide: Replace GPLv2 boilerplate/reference with SPDX - rule 157
[sfrench/cifs-2.6.git] / drivers / acpi / osi.c
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 static u32 acpi_osi_handler(acpi_string interface, u32 supported)
74 {
75         if (!strcmp("Linux", interface)) {
76                 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         if (!strcmp("Darwin", interface)) {
83                 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         return supported;
91 }
92
93 void __init acpi_osi_setup(char *str)
94 {
95         struct acpi_osi_entry *osi;
96         bool enable = true;
97         int i;
98
99         if (!acpi_gbl_create_osi_method)
100                 return;
101
102         if (str == NULL || *str == '\0') {
103                 pr_info("_OSI method disabled\n");
104                 acpi_gbl_create_osi_method = FALSE;
105                 return;
106         }
107
108         if (*str == '!') {
109                 str++;
110                 if (*str == '\0') {
111                         /* Do not override acpi_osi=!* */
112                         if (!osi_config.default_disabling)
113                                 osi_config.default_disabling =
114                                         ACPI_DISABLE_ALL_VENDOR_STRINGS;
115                         return;
116                 } else if (*str == '*') {
117                         osi_config.default_disabling = ACPI_DISABLE_ALL_STRINGS;
118                         for (i = 0; i < OSI_STRING_ENTRIES_MAX; i++) {
119                                 osi = &osi_setup_entries[i];
120                                 osi->enable = false;
121                         }
122                         return;
123                 } else if (*str == '!') {
124                         osi_config.default_disabling = 0;
125                         return;
126                 }
127                 enable = false;
128         }
129
130         for (i = 0; i < OSI_STRING_ENTRIES_MAX; i++) {
131                 osi = &osi_setup_entries[i];
132                 if (!strcmp(osi->string, str)) {
133                         osi->enable = enable;
134                         break;
135                 } else if (osi->string[0] == '\0') {
136                         osi->enable = enable;
137                         strncpy(osi->string, str, OSI_STRING_LENGTH_MAX);
138                         break;
139                 }
140         }
141 }
142
143 static void __init __acpi_osi_setup_darwin(bool enable)
144 {
145         osi_config.darwin_enable = !!enable;
146         if (enable) {
147                 acpi_osi_setup("!");
148                 acpi_osi_setup("Darwin");
149         } else {
150                 acpi_osi_setup("!!");
151                 acpi_osi_setup("!Darwin");
152         }
153 }
154
155 static void __init acpi_osi_setup_darwin(bool enable)
156 {
157         /* Override acpi_osi_dmi_blacklisted() */
158         osi_config.darwin_dmi = 0;
159         osi_config.darwin_cmdline = 1;
160         __acpi_osi_setup_darwin(enable);
161 }
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 static void __init __acpi_osi_setup_linux(bool enable)
193 {
194         osi_config.linux_enable = !!enable;
195         if (enable)
196                 acpi_osi_setup("Linux");
197         else
198                 acpi_osi_setup("!Linux");
199 }
200
201 static void __init acpi_osi_setup_linux(bool enable)
202 {
203         /* Override acpi_osi_dmi_blacklisted() */
204         osi_config.linux_dmi = 0;
205         osi_config.linux_cmdline = 1;
206         __acpi_osi_setup_linux(enable);
207 }
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 static void __init acpi_osi_setup_late(void)
217 {
218         struct acpi_osi_entry *osi;
219         char *str;
220         int i;
221         acpi_status status;
222
223         if (osi_config.default_disabling) {
224                 status = acpi_update_interfaces(osi_config.default_disabling);
225                 if (ACPI_SUCCESS(status))
226                         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         for (i = 0; i < OSI_STRING_ENTRIES_MAX; i++) {
233                 osi = &osi_setup_entries[i];
234                 str = osi->string;
235                 if (*str == '\0')
236                         break;
237                 if (osi->enable) {
238                         status = acpi_install_interface(str);
239                         if (ACPI_SUCCESS(status))
240                                 pr_info("Added _OSI(%s)\n", str);
241                 } else {
242                         status = acpi_remove_interface(str);
243                         if (ACPI_SUCCESS(status))
244                                 pr_info("Deleted _OSI(%s)\n", str);
245                 }
246         }
247 }
248
249 static int __init osi_setup(char *str)
250 {
251         if (str && !strcmp("Linux", str))
252                 acpi_osi_setup_linux(true);
253         else if (str && !strcmp("!Linux", str))
254                 acpi_osi_setup_linux(false);
255         else if (str && !strcmp("Darwin", str))
256                 acpi_osi_setup_darwin(true);
257         else if (str && !strcmp("!Darwin", str))
258                 acpi_osi_setup_darwin(false);
259         else
260                 acpi_osi_setup(str);
261
262         return 1;
263 }
264 __setup("acpi_osi=", osi_setup);
265
266 bool acpi_osi_is_win8(void)
267 {
268         return acpi_gbl_osi_data >= ACPI_OSI_WIN_8;
269 }
270 EXPORT_SYMBOL(acpi_osi_is_win8);
271
272 static void __init acpi_osi_dmi_darwin(void)
273 {
274         pr_notice("DMI detected to setup _OSI(\"Darwin\"): Apple hardware\n");
275         osi_config.darwin_dmi = 1;
276         __acpi_osi_setup_darwin(true);
277 }
278
279 static void __init acpi_osi_dmi_linux(bool enable,
280                                       const struct dmi_system_id *d)
281 {
282         pr_notice("DMI detected to setup _OSI(\"Linux\"): %s\n", d->ident);
283         osi_config.linux_dmi = 1;
284         __acpi_osi_setup_linux(enable);
285 }
286
287 static int __init dmi_enable_osi_linux(const struct dmi_system_id *d)
288 {
289         acpi_osi_dmi_linux(true, d);
290
291         return 0;
292 }
293
294 static int __init dmi_disable_osi_vista(const struct dmi_system_id *d)
295 {
296         pr_notice("DMI detected: %s\n", d->ident);
297         acpi_osi_setup("!Windows 2006");
298         acpi_osi_setup("!Windows 2006 SP1");
299         acpi_osi_setup("!Windows 2006 SP2");
300
301         return 0;
302 }
303
304 static int __init dmi_disable_osi_win7(const struct dmi_system_id *d)
305 {
306         pr_notice("DMI detected: %s\n", d->ident);
307         acpi_osi_setup("!Windows 2009");
308
309         return 0;
310 }
311
312 static int __init dmi_disable_osi_win8(const struct dmi_system_id *d)
313 {
314         pr_notice("DMI detected: %s\n", d->ident);
315         acpi_osi_setup("!Windows 2012");
316
317         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 this EEEpc exports a non working WMI interface, with
477          * this it exports a working "good old" eeepc_laptop interface, fixing
478          * 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 static __init void acpi_osi_dmi_blacklisted(void)
492 {
493         dmi_check_system(acpi_osi_dmi_table);
494
495         /* Enable _OSI("Darwin") for Apple platforms. */
496         if (x86_apple_machine)
497                 acpi_osi_dmi_darwin();
498 }
499
500 int __init early_acpi_osi_init(void)
501 {
502         acpi_osi_dmi_blacklisted();
503
504         return 0;
505 }
506
507 int __init acpi_osi_init(void)
508 {
509         acpi_install_interface_handler(acpi_osi_handler);
510         acpi_osi_setup_late();
511
512         return 0;
513 }