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