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