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