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