Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-or-later
0002 /*
0003  *  osi.c - _OSI implementation
0004  *
0005  *  Copyright (C) 2016 Intel Corporation
0006  *    Author: Lv Zheng <lv.zheng@intel.com>
0007  */
0008 
0009 /* Uncomment next line to get verbose printout */
0010 /* #define DEBUG */
0011 #define pr_fmt(fmt) "ACPI: " fmt
0012 
0013 #include <linux/module.h>
0014 #include <linux/kernel.h>
0015 #include <linux/acpi.h>
0016 #include <linux/dmi.h>
0017 #include <linux/platform_data/x86/apple.h>
0018 
0019 #include "internal.h"
0020 
0021 
0022 #define OSI_STRING_LENGTH_MAX   64
0023 #define OSI_STRING_ENTRIES_MAX  16
0024 
0025 struct acpi_osi_entry {
0026     char string[OSI_STRING_LENGTH_MAX];
0027     bool enable;
0028 };
0029 
0030 static struct acpi_osi_config {
0031     u8      default_disabling;
0032     unsigned int    linux_enable:1;
0033     unsigned int    linux_dmi:1;
0034     unsigned int    linux_cmdline:1;
0035     unsigned int    darwin_enable:1;
0036     unsigned int    darwin_dmi:1;
0037     unsigned int    darwin_cmdline:1;
0038 } osi_config;
0039 
0040 static struct acpi_osi_config osi_config;
0041 static struct acpi_osi_entry
0042 osi_setup_entries[OSI_STRING_ENTRIES_MAX] __initdata = {
0043     {"Module Device", true},
0044     {"Processor Device", true},
0045     {"3.0 _SCP Extensions", true},
0046     {"Processor Aggregator Device", true},
0047     /*
0048      * Linux-Dell-Video is used by BIOS to disable RTD3 for NVidia graphics
0049      * cards as RTD3 is not supported by drivers now.  Systems with NVidia
0050      * cards will hang without RTD3 disabled.
0051      *
0052      * Once NVidia drivers officially support RTD3, this _OSI strings can
0053      * be removed if both new and old graphics cards are supported.
0054      */
0055     {"Linux-Dell-Video", true},
0056     /*
0057      * Linux-Lenovo-NV-HDMI-Audio is used by BIOS to power on NVidia's HDMI
0058      * audio device which is turned off for power-saving in Windows OS.
0059      * This power management feature observed on some Lenovo Thinkpad
0060      * systems which will not be able to output audio via HDMI without
0061      * a BIOS workaround.
0062      */
0063     {"Linux-Lenovo-NV-HDMI-Audio", true},
0064     /*
0065      * Linux-HPI-Hybrid-Graphics is used by BIOS to enable dGPU to
0066      * output video directly to external monitors on HP Inc. mobile
0067      * workstations as Nvidia and AMD VGA drivers provide limited
0068      * hybrid graphics supports.
0069      */
0070     {"Linux-HPI-Hybrid-Graphics", true},
0071 };
0072 
0073 static u32 acpi_osi_handler(acpi_string interface, u32 supported)
0074 {
0075     if (!strcmp("Linux", interface)) {
0076         pr_notice_once(FW_BUG
0077             "BIOS _OSI(Linux) query %s%s\n",
0078             osi_config.linux_enable ? "honored" : "ignored",
0079             osi_config.linux_cmdline ? " via cmdline" :
0080             osi_config.linux_dmi ? " via DMI" : "");
0081     }
0082     if (!strcmp("Darwin", interface)) {
0083         pr_notice_once(
0084             "BIOS _OSI(Darwin) query %s%s\n",
0085             osi_config.darwin_enable ? "honored" : "ignored",
0086             osi_config.darwin_cmdline ? " via cmdline" :
0087             osi_config.darwin_dmi ? " via DMI" : "");
0088     }
0089 
0090     return supported;
0091 }
0092 
0093 void __init acpi_osi_setup(char *str)
0094 {
0095     struct acpi_osi_entry *osi;
0096     bool enable = true;
0097     int i;
0098 
0099     if (!acpi_gbl_create_osi_method)
0100         return;
0101 
0102     if (str == NULL || *str == '\0') {
0103         pr_info("_OSI method disabled\n");
0104         acpi_gbl_create_osi_method = FALSE;
0105         return;
0106     }
0107 
0108     if (*str == '!') {
0109         str++;
0110         if (*str == '\0') {
0111             /* Do not override acpi_osi=!* */
0112             if (!osi_config.default_disabling)
0113                 osi_config.default_disabling =
0114                     ACPI_DISABLE_ALL_VENDOR_STRINGS;
0115             return;
0116         } else if (*str == '*') {
0117             osi_config.default_disabling = ACPI_DISABLE_ALL_STRINGS;
0118             for (i = 0; i < OSI_STRING_ENTRIES_MAX; i++) {
0119                 osi = &osi_setup_entries[i];
0120                 osi->enable = false;
0121             }
0122             return;
0123         } else if (*str == '!') {
0124             osi_config.default_disabling = 0;
0125             return;
0126         }
0127         enable = false;
0128     }
0129 
0130     for (i = 0; i < OSI_STRING_ENTRIES_MAX; i++) {
0131         osi = &osi_setup_entries[i];
0132         if (!strcmp(osi->string, str)) {
0133             osi->enable = enable;
0134             break;
0135         } else if (osi->string[0] == '\0') {
0136             osi->enable = enable;
0137             strncpy(osi->string, str, OSI_STRING_LENGTH_MAX);
0138             break;
0139         }
0140     }
0141 }
0142 
0143 static void __init __acpi_osi_setup_darwin(bool enable)
0144 {
0145     osi_config.darwin_enable = !!enable;
0146     if (enable) {
0147         acpi_osi_setup("!");
0148         acpi_osi_setup("Darwin");
0149     } else {
0150         acpi_osi_setup("!!");
0151         acpi_osi_setup("!Darwin");
0152     }
0153 }
0154 
0155 static void __init acpi_osi_setup_darwin(bool enable)
0156 {
0157     /* Override acpi_osi_dmi_blacklisted() */
0158     osi_config.darwin_dmi = 0;
0159     osi_config.darwin_cmdline = 1;
0160     __acpi_osi_setup_darwin(enable);
0161 }
0162 
0163 /*
0164  * The story of _OSI(Linux)
0165  *
0166  * From pre-history through Linux-2.6.22, Linux responded TRUE upon a BIOS
0167  * OSI(Linux) query.
0168  *
0169  * Unfortunately, reference BIOS writers got wind of this and put
0170  * OSI(Linux) in their example code, quickly exposing this string as
0171  * ill-conceived and opening the door to an un-bounded number of BIOS
0172  * incompatibilities.
0173  *
0174  * For example, OSI(Linux) was used on resume to re-POST a video card on
0175  * one system, because Linux at that time could not do a speedy restore in
0176  * its native driver. But then upon gaining quick native restore
0177  * capability, Linux has no way to tell the BIOS to skip the time-consuming
0178  * POST -- putting Linux at a permanent performance disadvantage. On
0179  * another system, the BIOS writer used OSI(Linux) to infer native OS
0180  * support for IPMI!  On other systems, OSI(Linux) simply got in the way of
0181  * Linux claiming to be compatible with other operating systems, exposing
0182  * BIOS issues such as skipped device initialization.
0183  *
0184  * So "Linux" turned out to be a really poor chose of OSI string, and from
0185  * Linux-2.6.23 onward we respond FALSE.
0186  *
0187  * BIOS writers should NOT query _OSI(Linux) on future systems. Linux will
0188  * complain on the console when it sees it, and return FALSE. To get Linux
0189  * to return TRUE for your system  will require a kernel source update to
0190  * add a DMI entry, or boot with "acpi_osi=Linux"
0191  */
0192 static void __init __acpi_osi_setup_linux(bool enable)
0193 {
0194     osi_config.linux_enable = !!enable;
0195     if (enable)
0196         acpi_osi_setup("Linux");
0197     else
0198         acpi_osi_setup("!Linux");
0199 }
0200 
0201 static void __init acpi_osi_setup_linux(bool enable)
0202 {
0203     /* Override acpi_osi_dmi_blacklisted() */
0204     osi_config.linux_dmi = 0;
0205     osi_config.linux_cmdline = 1;
0206     __acpi_osi_setup_linux(enable);
0207 }
0208 
0209 /*
0210  * Modify the list of "OS Interfaces" reported to BIOS via _OSI
0211  *
0212  * empty string disables _OSI
0213  * string starting with '!' disables that string
0214  * otherwise string is added to list, augmenting built-in strings
0215  */
0216 static void __init acpi_osi_setup_late(void)
0217 {
0218     struct acpi_osi_entry *osi;
0219     char *str;
0220     int i;
0221     acpi_status status;
0222 
0223     if (osi_config.default_disabling) {
0224         status = acpi_update_interfaces(osi_config.default_disabling);
0225         if (ACPI_SUCCESS(status))
0226             pr_info("Disabled all _OSI OS vendors%s\n",
0227                 osi_config.default_disabling ==
0228                 ACPI_DISABLE_ALL_STRINGS ?
0229                 " and feature groups" : "");
0230     }
0231 
0232     for (i = 0; i < OSI_STRING_ENTRIES_MAX; i++) {
0233         osi = &osi_setup_entries[i];
0234         str = osi->string;
0235         if (*str == '\0')
0236             break;
0237         if (osi->enable) {
0238             status = acpi_install_interface(str);
0239             if (ACPI_SUCCESS(status))
0240                 pr_info("Added _OSI(%s)\n", str);
0241         } else {
0242             status = acpi_remove_interface(str);
0243             if (ACPI_SUCCESS(status))
0244                 pr_info("Deleted _OSI(%s)\n", str);
0245         }
0246     }
0247 }
0248 
0249 static int __init osi_setup(char *str)
0250 {
0251     if (str && !strcmp("Linux", str))
0252         acpi_osi_setup_linux(true);
0253     else if (str && !strcmp("!Linux", str))
0254         acpi_osi_setup_linux(false);
0255     else if (str && !strcmp("Darwin", str))
0256         acpi_osi_setup_darwin(true);
0257     else if (str && !strcmp("!Darwin", str))
0258         acpi_osi_setup_darwin(false);
0259     else
0260         acpi_osi_setup(str);
0261 
0262     return 1;
0263 }
0264 __setup("acpi_osi=", osi_setup);
0265 
0266 bool acpi_osi_is_win8(void)
0267 {
0268     return acpi_gbl_osi_data >= ACPI_OSI_WIN_8;
0269 }
0270 EXPORT_SYMBOL(acpi_osi_is_win8);
0271 
0272 static void __init acpi_osi_dmi_darwin(void)
0273 {
0274     pr_notice("DMI detected to setup _OSI(\"Darwin\"): Apple hardware\n");
0275     osi_config.darwin_dmi = 1;
0276     __acpi_osi_setup_darwin(true);
0277 }
0278 
0279 static void __init acpi_osi_dmi_linux(bool enable,
0280                       const struct dmi_system_id *d)
0281 {
0282     pr_notice("DMI detected to setup _OSI(\"Linux\"): %s\n", d->ident);
0283     osi_config.linux_dmi = 1;
0284     __acpi_osi_setup_linux(enable);
0285 }
0286 
0287 static int __init dmi_enable_osi_linux(const struct dmi_system_id *d)
0288 {
0289     acpi_osi_dmi_linux(true, d);
0290 
0291     return 0;
0292 }
0293 
0294 static int __init dmi_disable_osi_vista(const struct dmi_system_id *d)
0295 {
0296     pr_notice("DMI detected: %s\n", d->ident);
0297     acpi_osi_setup("!Windows 2006");
0298     acpi_osi_setup("!Windows 2006 SP1");
0299     acpi_osi_setup("!Windows 2006 SP2");
0300 
0301     return 0;
0302 }
0303 
0304 static int __init dmi_disable_osi_win7(const struct dmi_system_id *d)
0305 {
0306     pr_notice("DMI detected: %s\n", d->ident);
0307     acpi_osi_setup("!Windows 2009");
0308 
0309     return 0;
0310 }
0311 
0312 static int __init dmi_disable_osi_win8(const struct dmi_system_id *d)
0313 {
0314     pr_notice("DMI detected: %s\n", d->ident);
0315     acpi_osi_setup("!Windows 2012");
0316 
0317     return 0;
0318 }
0319 
0320 /*
0321  * Linux default _OSI response behavior is determined by this DMI table.
0322  *
0323  * Note that _OSI("Linux")/_OSI("Darwin") determined here can be overridden
0324  * by acpi_osi=!Linux/acpi_osi=!Darwin command line options.
0325  */
0326 static const struct dmi_system_id acpi_osi_dmi_table[] __initconst = {
0327     {
0328     .callback = dmi_disable_osi_vista,
0329     .ident = "Fujitsu Siemens",
0330     .matches = {
0331              DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"),
0332              DMI_MATCH(DMI_PRODUCT_NAME, "ESPRIMO Mobile V5505"),
0333         },
0334     },
0335     {
0336     /*
0337      * There have a NVIF method in MSI GX723 DSDT need call by Nvidia
0338      * driver (e.g. nouveau) when user press brightness hotkey.
0339      * Currently, nouveau driver didn't do the job and it causes there
0340      * have a infinite while loop in DSDT when user press hotkey.
0341      * We add MSI GX723's dmi information to this table for workaround
0342      * this issue.
0343      * Will remove MSI GX723 from the table after nouveau grows support.
0344      */
0345     .callback = dmi_disable_osi_vista,
0346     .ident = "MSI GX723",
0347     .matches = {
0348              DMI_MATCH(DMI_SYS_VENDOR, "Micro-Star International"),
0349              DMI_MATCH(DMI_PRODUCT_NAME, "GX723"),
0350         },
0351     },
0352     {
0353     .callback = dmi_disable_osi_vista,
0354     .ident = "Sony VGN-NS10J_S",
0355     .matches = {
0356              DMI_MATCH(DMI_SYS_VENDOR, "Sony Corporation"),
0357              DMI_MATCH(DMI_PRODUCT_NAME, "VGN-NS10J_S"),
0358         },
0359     },
0360     {
0361     .callback = dmi_disable_osi_vista,
0362     .ident = "Sony VGN-SR290J",
0363     .matches = {
0364              DMI_MATCH(DMI_SYS_VENDOR, "Sony Corporation"),
0365              DMI_MATCH(DMI_PRODUCT_NAME, "VGN-SR290J"),
0366         },
0367     },
0368     {
0369     .callback = dmi_disable_osi_vista,
0370     .ident = "VGN-NS50B_L",
0371     .matches = {
0372              DMI_MATCH(DMI_SYS_VENDOR, "Sony Corporation"),
0373              DMI_MATCH(DMI_PRODUCT_NAME, "VGN-NS50B_L"),
0374         },
0375     },
0376     {
0377     .callback = dmi_disable_osi_vista,
0378     .ident = "VGN-SR19XN",
0379     .matches = {
0380              DMI_MATCH(DMI_SYS_VENDOR, "Sony Corporation"),
0381              DMI_MATCH(DMI_PRODUCT_NAME, "VGN-SR19XN"),
0382         },
0383     },
0384     {
0385     .callback = dmi_disable_osi_vista,
0386     .ident = "Toshiba Satellite L355",
0387     .matches = {
0388              DMI_MATCH(DMI_SYS_VENDOR, "TOSHIBA"),
0389              DMI_MATCH(DMI_PRODUCT_VERSION, "Satellite L355"),
0390         },
0391     },
0392     {
0393     .callback = dmi_disable_osi_win7,
0394     .ident = "ASUS K50IJ",
0395     .matches = {
0396              DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK Computer Inc."),
0397              DMI_MATCH(DMI_PRODUCT_NAME, "K50IJ"),
0398         },
0399     },
0400     {
0401     .callback = dmi_disable_osi_vista,
0402     .ident = "Toshiba P305D",
0403     .matches = {
0404              DMI_MATCH(DMI_SYS_VENDOR, "TOSHIBA"),
0405              DMI_MATCH(DMI_PRODUCT_NAME, "Satellite P305D"),
0406         },
0407     },
0408     {
0409     .callback = dmi_disable_osi_vista,
0410     .ident = "Toshiba NB100",
0411     .matches = {
0412              DMI_MATCH(DMI_SYS_VENDOR, "TOSHIBA"),
0413              DMI_MATCH(DMI_PRODUCT_NAME, "NB100"),
0414         },
0415     },
0416 
0417     /*
0418      * The wireless hotkey does not work on those machines when
0419      * returning true for _OSI("Windows 2012")
0420      */
0421     {
0422     .callback = dmi_disable_osi_win8,
0423     .ident = "Dell Inspiron 7737",
0424     .matches = {
0425             DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
0426             DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 7737"),
0427         },
0428     },
0429     {
0430     .callback = dmi_disable_osi_win8,
0431     .ident = "Dell Inspiron 7537",
0432     .matches = {
0433             DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
0434             DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 7537"),
0435         },
0436     },
0437     {
0438     .callback = dmi_disable_osi_win8,
0439     .ident = "Dell Inspiron 5437",
0440     .matches = {
0441             DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
0442             DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 5437"),
0443         },
0444     },
0445     {
0446     .callback = dmi_disable_osi_win8,
0447     .ident = "Dell Inspiron 3437",
0448     .matches = {
0449             DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
0450             DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 3437"),
0451         },
0452     },
0453     {
0454     .callback = dmi_disable_osi_win8,
0455     .ident = "Dell Vostro 3446",
0456     .matches = {
0457             DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
0458             DMI_MATCH(DMI_PRODUCT_NAME, "Vostro 3446"),
0459         },
0460     },
0461     {
0462     .callback = dmi_disable_osi_win8,
0463     .ident = "Dell Vostro 3546",
0464     .matches = {
0465             DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
0466             DMI_MATCH(DMI_PRODUCT_NAME, "Vostro 3546"),
0467         },
0468     },
0469 
0470     /*
0471      * BIOS invocation of _OSI(Linux) is almost always a BIOS bug.
0472      * Linux ignores it, except for the machines enumerated below.
0473      */
0474 
0475     /*
0476      * Without this EEEpc exports a non working WMI interface, with
0477      * this it exports a working "good old" eeepc_laptop interface,
0478      * fixing both brightness control, and rfkill not working.
0479      */
0480     {
0481     .callback = dmi_enable_osi_linux,
0482     .ident = "Asus EEE PC 1015PX",
0483     .matches = {
0484              DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK Computer INC."),
0485              DMI_MATCH(DMI_PRODUCT_NAME, "1015PX"),
0486         },
0487     },
0488     {}
0489 };
0490 
0491 static __init void acpi_osi_dmi_blacklisted(void)
0492 {
0493     dmi_check_system(acpi_osi_dmi_table);
0494 
0495     /* Enable _OSI("Darwin") for Apple platforms. */
0496     if (x86_apple_machine)
0497         acpi_osi_dmi_darwin();
0498 }
0499 
0500 int __init early_acpi_osi_init(void)
0501 {
0502     acpi_osi_dmi_blacklisted();
0503 
0504     return 0;
0505 }
0506 
0507 int __init acpi_osi_init(void)
0508 {
0509     acpi_install_interface_handler(acpi_osi_handler);
0510     acpi_osi_setup_late();
0511 
0512     return 0;
0513 }