xref: /openbmc/linux/drivers/acpi/osi.c (revision fed8b7e3)
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