Merge tag 'arm64-upstream' of git://git.kernel.org/pub/scm/linux/kernel/git/arm64...
[linux-2.6-microblaze.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
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("!");
124                 acpi_osi_setup("Darwin");
125         } else {
126                 acpi_osi_setup("!!");
127                 acpi_osi_setup("!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("Linux");
173         else
174                 acpi_osi_setup("!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(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(str);
215                         if (ACPI_SUCCESS(status))
216                                 pr_info("Added _OSI(%s)\n", str);
217                 } else {
218                         status = acpi_remove_interface(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(true);
229         else if (str && !strcmp("!Linux", str))
230                 acpi_osi_setup_linux(false);
231         else if (str && !strcmp("Darwin", str))
232                 acpi_osi_setup_darwin(true);
233         else if (str && !strcmp("!Darwin", str))
234                 acpi_osi_setup_darwin(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(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(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("!Windows 2006");
274         acpi_osi_setup("!Windows 2006 SP1");
275         acpi_osi_setup("!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("!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("!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(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(acpi_osi_handler);
486         acpi_osi_setup_late();
487
488         return 0;
489 }