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