linux/drivers/acpi/osi.c
<<
>>
Prefs
   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
  38struct acpi_osi_entry {
  39        char string[OSI_STRING_LENGTH_MAX];
  40        bool enable;
  41};
  42
  43static 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
  53static struct acpi_osi_config osi_config;
  54static struct acpi_osi_entry
  55osi_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
  62static 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
  82void __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
 132static 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
 144static 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 */
 181static 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
 190static 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 */
 205static 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
 238static 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
 255bool acpi_osi_is_win8(void)
 256{
 257        return acpi_gbl_osi_data >= ACPI_OSI_WIN_8;
 258}
 259EXPORT_SYMBOL(acpi_osi_is_win8);
 260
 261static 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
 268static 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
 276static 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
 283static 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
 293static 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
 301static 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 */
 315static 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
 480static __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
 489int __init early_acpi_osi_init(void)
 490{
 491        acpi_osi_dmi_blacklisted();
 492
 493        return 0;
 494}
 495
 496int __init acpi_osi_init(void)
 497{
 498        acpi_install_interface_handler(acpi_osi_handler);
 499        acpi_osi_setup_late();
 500
 501        return 0;
 502}
 503