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 wireless hotkey does not work on those machines when 394 * returning true for _OSI("Windows 2012") 395 */ 396 { 397 .callback = dmi_disable_osi_win8, 398 .ident = "Dell Inspiron 7737", 399 .matches = { 400 DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 401 DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 7737"), 402 }, 403 }, 404 { 405 .callback = dmi_disable_osi_win8, 406 .ident = "Dell Inspiron 7537", 407 .matches = { 408 DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 409 DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 7537"), 410 }, 411 }, 412 { 413 .callback = dmi_disable_osi_win8, 414 .ident = "Dell Inspiron 5437", 415 .matches = { 416 DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 417 DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 5437"), 418 }, 419 }, 420 { 421 .callback = dmi_disable_osi_win8, 422 .ident = "Dell Inspiron 3437", 423 .matches = { 424 DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 425 DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 3437"), 426 }, 427 }, 428 { 429 .callback = dmi_disable_osi_win8, 430 .ident = "Dell Vostro 3446", 431 .matches = { 432 DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 433 DMI_MATCH(DMI_PRODUCT_NAME, "Vostro 3446"), 434 }, 435 }, 436 { 437 .callback = dmi_disable_osi_win8, 438 .ident = "Dell Vostro 3546", 439 .matches = { 440 DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 441 DMI_MATCH(DMI_PRODUCT_NAME, "Vostro 3546"), 442 }, 443 }, 444 445 /* 446 * BIOS invocation of _OSI(Linux) is almost always a BIOS bug. 447 * Linux ignores it, except for the machines enumerated below. 448 */ 449 450 /* 451 * Without this EEEpc exports a non working WMI interface, with 452 * this it exports a working "good old" eeepc_laptop interface, 453 * fixing both brightness control, and rfkill not working. 454 */ 455 { 456 .callback = dmi_enable_osi_linux, 457 .ident = "Asus EEE PC 1015PX", 458 .matches = { 459 DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK Computer INC."), 460 DMI_MATCH(DMI_PRODUCT_NAME, "1015PX"), 461 }, 462 }, 463 {} 464 }; 465 466 static __init void acpi_osi_dmi_blacklisted(void) 467 { 468 dmi_check_system(acpi_osi_dmi_table); 469 470 /* Enable _OSI("Darwin") for Apple platforms. */ 471 if (x86_apple_machine) 472 acpi_osi_dmi_darwin(); 473 } 474 475 int __init early_acpi_osi_init(void) 476 { 477 acpi_osi_dmi_blacklisted(); 478 479 return 0; 480 } 481 482 int __init acpi_osi_init(void) 483 { 484 acpi_install_interface_handler(acpi_osi_handler); 485 acpi_osi_setup_late(); 486 487 return 0; 488 } 489