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