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