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