summaryrefslogtreecommitdiffstats
path: root/drivers/acpi/osi.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/acpi/osi.c')
-rw-r--r--drivers/acpi/osi.c513
1 files changed, 513 insertions, 0 deletions
diff --git a/drivers/acpi/osi.c b/drivers/acpi/osi.c
new file mode 100644
index 000000000..9f6853809
--- /dev/null
+++ b/drivers/acpi/osi.c
@@ -0,0 +1,513 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * osi.c - _OSI implementation
+ *
+ * Copyright (C) 2016 Intel Corporation
+ * Author: Lv Zheng <lv.zheng@intel.com>
+ */
+
+/* Uncomment next line to get verbose printout */
+/* #define DEBUG */
+#define pr_fmt(fmt) "ACPI: " fmt
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/acpi.h>
+#include <linux/dmi.h>
+#include <linux/platform_data/x86/apple.h>
+
+#include "internal.h"
+
+
+#define OSI_STRING_LENGTH_MAX 64
+#define OSI_STRING_ENTRIES_MAX 16
+
+struct acpi_osi_entry {
+ char string[OSI_STRING_LENGTH_MAX];
+ bool enable;
+};
+
+static struct acpi_osi_config {
+ u8 default_disabling;
+ unsigned int linux_enable:1;
+ unsigned int linux_dmi:1;
+ unsigned int linux_cmdline:1;
+ unsigned int darwin_enable:1;
+ unsigned int darwin_dmi:1;
+ unsigned int darwin_cmdline:1;
+} osi_config;
+
+static struct acpi_osi_config osi_config;
+static struct acpi_osi_entry
+osi_setup_entries[OSI_STRING_ENTRIES_MAX] __initdata = {
+ {"Module Device", true},
+ {"Processor Device", true},
+ {"3.0 _SCP Extensions", true},
+ {"Processor Aggregator Device", true},
+ /*
+ * Linux-Dell-Video is used by BIOS to disable RTD3 for NVidia graphics
+ * cards as RTD3 is not supported by drivers now. Systems with NVidia
+ * cards will hang without RTD3 disabled.
+ *
+ * Once NVidia drivers officially support RTD3, this _OSI strings can
+ * be removed if both new and old graphics cards are supported.
+ */
+ {"Linux-Dell-Video", true},
+ /*
+ * Linux-Lenovo-NV-HDMI-Audio is used by BIOS to power on NVidia's HDMI
+ * audio device which is turned off for power-saving in Windows OS.
+ * This power management feature observed on some Lenovo Thinkpad
+ * systems which will not be able to output audio via HDMI without
+ * a BIOS workaround.
+ */
+ {"Linux-Lenovo-NV-HDMI-Audio", true},
+ /*
+ * Linux-HPI-Hybrid-Graphics is used by BIOS to enable dGPU to
+ * output video directly to external monitors on HP Inc. mobile
+ * workstations as Nvidia and AMD VGA drivers provide limited
+ * hybrid graphics supports.
+ */
+ {"Linux-HPI-Hybrid-Graphics", true},
+};
+
+static u32 acpi_osi_handler(acpi_string interface, u32 supported)
+{
+ if (!strcmp("Linux", interface)) {
+ pr_notice_once(FW_BUG
+ "BIOS _OSI(Linux) query %s%s\n",
+ osi_config.linux_enable ? "honored" : "ignored",
+ osi_config.linux_cmdline ? " via cmdline" :
+ osi_config.linux_dmi ? " via DMI" : "");
+ }
+ if (!strcmp("Darwin", interface)) {
+ pr_notice_once(
+ "BIOS _OSI(Darwin) query %s%s\n",
+ osi_config.darwin_enable ? "honored" : "ignored",
+ osi_config.darwin_cmdline ? " via cmdline" :
+ osi_config.darwin_dmi ? " via DMI" : "");
+ }
+
+ return supported;
+}
+
+void __init acpi_osi_setup(char *str)
+{
+ struct acpi_osi_entry *osi;
+ bool enable = true;
+ int i;
+
+ if (!acpi_gbl_create_osi_method)
+ return;
+
+ if (str == NULL || *str == '\0') {
+ pr_info("_OSI method disabled\n");
+ acpi_gbl_create_osi_method = FALSE;
+ return;
+ }
+
+ if (*str == '!') {
+ str++;
+ if (*str == '\0') {
+ /* Do not override acpi_osi=!* */
+ if (!osi_config.default_disabling)
+ osi_config.default_disabling =
+ ACPI_DISABLE_ALL_VENDOR_STRINGS;
+ return;
+ } else if (*str == '*') {
+ osi_config.default_disabling = ACPI_DISABLE_ALL_STRINGS;
+ for (i = 0; i < OSI_STRING_ENTRIES_MAX; i++) {
+ osi = &osi_setup_entries[i];
+ osi->enable = false;
+ }
+ return;
+ } else if (*str == '!') {
+ osi_config.default_disabling = 0;
+ return;
+ }
+ enable = false;
+ }
+
+ for (i = 0; i < OSI_STRING_ENTRIES_MAX; i++) {
+ osi = &osi_setup_entries[i];
+ if (!strcmp(osi->string, str)) {
+ osi->enable = enable;
+ break;
+ } else if (osi->string[0] == '\0') {
+ osi->enable = enable;
+ strncpy(osi->string, str, OSI_STRING_LENGTH_MAX);
+ break;
+ }
+ }
+}
+
+static void __init __acpi_osi_setup_darwin(bool enable)
+{
+ osi_config.darwin_enable = !!enable;
+ if (enable) {
+ acpi_osi_setup("!");
+ acpi_osi_setup("Darwin");
+ } else {
+ acpi_osi_setup("!!");
+ acpi_osi_setup("!Darwin");
+ }
+}
+
+static void __init acpi_osi_setup_darwin(bool enable)
+{
+ /* Override acpi_osi_dmi_blacklisted() */
+ osi_config.darwin_dmi = 0;
+ osi_config.darwin_cmdline = 1;
+ __acpi_osi_setup_darwin(enable);
+}
+
+/*
+ * The story of _OSI(Linux)
+ *
+ * From pre-history through Linux-2.6.22, Linux responded TRUE upon a BIOS
+ * OSI(Linux) query.
+ *
+ * Unfortunately, reference BIOS writers got wind of this and put
+ * OSI(Linux) in their example code, quickly exposing this string as
+ * ill-conceived and opening the door to an un-bounded number of BIOS
+ * incompatibilities.
+ *
+ * For example, OSI(Linux) was used on resume to re-POST a video card on
+ * one system, because Linux at that time could not do a speedy restore in
+ * its native driver. But then upon gaining quick native restore
+ * capability, Linux has no way to tell the BIOS to skip the time-consuming
+ * POST -- putting Linux at a permanent performance disadvantage. On
+ * another system, the BIOS writer used OSI(Linux) to infer native OS
+ * support for IPMI! On other systems, OSI(Linux) simply got in the way of
+ * Linux claiming to be compatible with other operating systems, exposing
+ * BIOS issues such as skipped device initialization.
+ *
+ * So "Linux" turned out to be a really poor chose of OSI string, and from
+ * Linux-2.6.23 onward we respond FALSE.
+ *
+ * BIOS writers should NOT query _OSI(Linux) on future systems. Linux will
+ * complain on the console when it sees it, and return FALSE. To get Linux
+ * to return TRUE for your system will require a kernel source update to
+ * add a DMI entry, or boot with "acpi_osi=Linux"
+ */
+static void __init __acpi_osi_setup_linux(bool enable)
+{
+ osi_config.linux_enable = !!enable;
+ if (enable)
+ acpi_osi_setup("Linux");
+ else
+ acpi_osi_setup("!Linux");
+}
+
+static void __init acpi_osi_setup_linux(bool enable)
+{
+ /* Override acpi_osi_dmi_blacklisted() */
+ osi_config.linux_dmi = 0;
+ osi_config.linux_cmdline = 1;
+ __acpi_osi_setup_linux(enable);
+}
+
+/*
+ * Modify the list of "OS Interfaces" reported to BIOS via _OSI
+ *
+ * empty string disables _OSI
+ * string starting with '!' disables that string
+ * otherwise string is added to list, augmenting built-in strings
+ */
+static void __init acpi_osi_setup_late(void)
+{
+ struct acpi_osi_entry *osi;
+ char *str;
+ int i;
+ acpi_status status;
+
+ if (osi_config.default_disabling) {
+ status = acpi_update_interfaces(osi_config.default_disabling);
+ if (ACPI_SUCCESS(status))
+ pr_info("Disabled all _OSI OS vendors%s\n",
+ osi_config.default_disabling ==
+ ACPI_DISABLE_ALL_STRINGS ?
+ " and feature groups" : "");
+ }
+
+ for (i = 0; i < OSI_STRING_ENTRIES_MAX; i++) {
+ osi = &osi_setup_entries[i];
+ str = osi->string;
+ if (*str == '\0')
+ break;
+ if (osi->enable) {
+ status = acpi_install_interface(str);
+ if (ACPI_SUCCESS(status))
+ pr_info("Added _OSI(%s)\n", str);
+ } else {
+ status = acpi_remove_interface(str);
+ if (ACPI_SUCCESS(status))
+ pr_info("Deleted _OSI(%s)\n", str);
+ }
+ }
+}
+
+static int __init osi_setup(char *str)
+{
+ if (str && !strcmp("Linux", str))
+ acpi_osi_setup_linux(true);
+ else if (str && !strcmp("!Linux", str))
+ acpi_osi_setup_linux(false);
+ else if (str && !strcmp("Darwin", str))
+ acpi_osi_setup_darwin(true);
+ else if (str && !strcmp("!Darwin", str))
+ acpi_osi_setup_darwin(false);
+ else
+ acpi_osi_setup(str);
+
+ return 1;
+}
+__setup("acpi_osi=", osi_setup);
+
+bool acpi_osi_is_win8(void)
+{
+ return acpi_gbl_osi_data >= ACPI_OSI_WIN_8;
+}
+EXPORT_SYMBOL(acpi_osi_is_win8);
+
+static void __init acpi_osi_dmi_darwin(void)
+{
+ pr_notice("DMI detected to setup _OSI(\"Darwin\"): Apple hardware\n");
+ osi_config.darwin_dmi = 1;
+ __acpi_osi_setup_darwin(true);
+}
+
+static void __init acpi_osi_dmi_linux(bool enable,
+ const struct dmi_system_id *d)
+{
+ pr_notice("DMI detected to setup _OSI(\"Linux\"): %s\n", d->ident);
+ osi_config.linux_dmi = 1;
+ __acpi_osi_setup_linux(enable);
+}
+
+static int __init dmi_enable_osi_linux(const struct dmi_system_id *d)
+{
+ acpi_osi_dmi_linux(true, d);
+
+ return 0;
+}
+
+static int __init dmi_disable_osi_vista(const struct dmi_system_id *d)
+{
+ pr_notice("DMI detected: %s\n", d->ident);
+ acpi_osi_setup("!Windows 2006");
+ acpi_osi_setup("!Windows 2006 SP1");
+ acpi_osi_setup("!Windows 2006 SP2");
+
+ return 0;
+}
+
+static int __init dmi_disable_osi_win7(const struct dmi_system_id *d)
+{
+ pr_notice("DMI detected: %s\n", d->ident);
+ acpi_osi_setup("!Windows 2009");
+
+ return 0;
+}
+
+static int __init dmi_disable_osi_win8(const struct dmi_system_id *d)
+{
+ pr_notice("DMI detected: %s\n", d->ident);
+ acpi_osi_setup("!Windows 2012");
+
+ return 0;
+}
+
+/*
+ * Linux default _OSI response behavior is determined by this DMI table.
+ *
+ * Note that _OSI("Linux")/_OSI("Darwin") determined here can be overridden
+ * by acpi_osi=!Linux/acpi_osi=!Darwin command line options.
+ */
+static const struct dmi_system_id acpi_osi_dmi_table[] __initconst = {
+ {
+ .callback = dmi_disable_osi_vista,
+ .ident = "Fujitsu Siemens",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "ESPRIMO Mobile V5505"),
+ },
+ },
+ {
+ /*
+ * There have a NVIF method in MSI GX723 DSDT need call by Nvidia
+ * driver (e.g. nouveau) when user press brightness hotkey.
+ * Currently, nouveau driver didn't do the job and it causes there
+ * have a infinite while loop in DSDT when user press hotkey.
+ * We add MSI GX723's dmi information to this table for workaround
+ * this issue.
+ * Will remove MSI GX723 from the table after nouveau grows support.
+ */
+ .callback = dmi_disable_osi_vista,
+ .ident = "MSI GX723",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Micro-Star International"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "GX723"),
+ },
+ },
+ {
+ .callback = dmi_disable_osi_vista,
+ .ident = "Sony VGN-NS10J_S",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Sony Corporation"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "VGN-NS10J_S"),
+ },
+ },
+ {
+ .callback = dmi_disable_osi_vista,
+ .ident = "Sony VGN-SR290J",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Sony Corporation"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "VGN-SR290J"),
+ },
+ },
+ {
+ .callback = dmi_disable_osi_vista,
+ .ident = "VGN-NS50B_L",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Sony Corporation"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "VGN-NS50B_L"),
+ },
+ },
+ {
+ .callback = dmi_disable_osi_vista,
+ .ident = "VGN-SR19XN",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Sony Corporation"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "VGN-SR19XN"),
+ },
+ },
+ {
+ .callback = dmi_disable_osi_vista,
+ .ident = "Toshiba Satellite L355",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "TOSHIBA"),
+ DMI_MATCH(DMI_PRODUCT_VERSION, "Satellite L355"),
+ },
+ },
+ {
+ .callback = dmi_disable_osi_win7,
+ .ident = "ASUS K50IJ",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK Computer Inc."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "K50IJ"),
+ },
+ },
+ {
+ .callback = dmi_disable_osi_vista,
+ .ident = "Toshiba P305D",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "TOSHIBA"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Satellite P305D"),
+ },
+ },
+ {
+ .callback = dmi_disable_osi_vista,
+ .ident = "Toshiba NB100",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "TOSHIBA"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "NB100"),
+ },
+ },
+
+ /*
+ * The wireless hotkey does not work on those machines when
+ * returning true for _OSI("Windows 2012")
+ */
+ {
+ .callback = dmi_disable_osi_win8,
+ .ident = "Dell Inspiron 7737",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 7737"),
+ },
+ },
+ {
+ .callback = dmi_disable_osi_win8,
+ .ident = "Dell Inspiron 7537",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 7537"),
+ },
+ },
+ {
+ .callback = dmi_disable_osi_win8,
+ .ident = "Dell Inspiron 5437",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 5437"),
+ },
+ },
+ {
+ .callback = dmi_disable_osi_win8,
+ .ident = "Dell Inspiron 3437",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 3437"),
+ },
+ },
+ {
+ .callback = dmi_disable_osi_win8,
+ .ident = "Dell Vostro 3446",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Vostro 3446"),
+ },
+ },
+ {
+ .callback = dmi_disable_osi_win8,
+ .ident = "Dell Vostro 3546",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Vostro 3546"),
+ },
+ },
+
+ /*
+ * BIOS invocation of _OSI(Linux) is almost always a BIOS bug.
+ * Linux ignores it, except for the machines enumerated below.
+ */
+
+ /*
+ * Without this EEEpc exports a non working WMI interface, with
+ * this it exports a working "good old" eeepc_laptop interface,
+ * fixing both brightness control, and rfkill not working.
+ */
+ {
+ .callback = dmi_enable_osi_linux,
+ .ident = "Asus EEE PC 1015PX",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK Computer INC."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "1015PX"),
+ },
+ },
+ {}
+};
+
+static __init void acpi_osi_dmi_blacklisted(void)
+{
+ dmi_check_system(acpi_osi_dmi_table);
+
+ /* Enable _OSI("Darwin") for Apple platforms. */
+ if (x86_apple_machine)
+ acpi_osi_dmi_darwin();
+}
+
+int __init early_acpi_osi_init(void)
+{
+ acpi_osi_dmi_blacklisted();
+
+ return 0;
+}
+
+int __init acpi_osi_init(void)
+{
+ acpi_install_interface_handler(acpi_osi_handler);
+ acpi_osi_setup_late();
+
+ return 0;
+}