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