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 25 struct acpi_osi_entry { 26 char string[OSI_STRING_LENGTH_MAX]; 27 bool enable; 28 }; 29 30 static 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 40 static struct acpi_osi_config osi_config; 41 static struct acpi_osi_entry 42 osi_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 73 static 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 93 void __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 143 static 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 155 static 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 */ 192 static 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 201 static 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 */ 216 static 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 249 static 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 266 bool acpi_osi_is_win8(void) 267 { 268 return acpi_gbl_osi_data >= ACPI_OSI_WIN_8; 269 } 270 EXPORT_SYMBOL(acpi_osi_is_win8); 271 272 static 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 279 static 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 287 static 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 294 static 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 304 static 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 312 static 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 */ 326 static 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 491 static __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 500 int __init early_acpi_osi_init(void) 501 { 502 acpi_osi_dmi_blacklisted(); 503 504 return 0; 505 } 506 507 int __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