2019-05-27 09:55:06 +03:00
// SPDX-License-Identifier: GPL-2.0-or-later
2016-05-03 11:49:01 +03:00
/*
* osi . c - _OSI implementation
*
* Copyright ( C ) 2016 Intel Corporation
* Author : Lv Zheng < lv . zheng @ intel . com >
*/
/* Uncomment next line to get verbose printout */
/* #define DEBUG */
# define pr_fmt(fmt) "ACPI: " fmt
# include <linux/module.h>
# include <linux/kernel.h>
# include <linux/acpi.h>
# include <linux/dmi.h>
2017-08-01 15:10:41 +03:00
# include <linux/platform_data/x86/apple.h>
2016-05-03 11:49:01 +03:00
# include "internal.h"
# define OSI_STRING_LENGTH_MAX 64
# define OSI_STRING_ENTRIES_MAX 16
struct acpi_osi_entry {
char string [ OSI_STRING_LENGTH_MAX ] ;
bool enable ;
} ;
static struct acpi_osi_config {
u8 default_disabling ;
unsigned int linux_enable : 1 ;
unsigned int linux_dmi : 1 ;
unsigned int linux_cmdline : 1 ;
unsigned int darwin_enable : 1 ;
unsigned int darwin_dmi : 1 ;
unsigned int darwin_cmdline : 1 ;
} osi_config ;
static struct acpi_osi_config osi_config ;
static struct acpi_osi_entry
osi_setup_entries [ OSI_STRING_ENTRIES_MAX ] __initdata = {
{ " Module Device " , true } ,
{ " Processor Device " , true } ,
{ " 3.0 _SCP Extensions " , true } ,
{ " Processor Aggregator Device " , true } ,
2018-03-01 06:27:55 +03:00
/*
* Linux - Dell - Video is used by BIOS to disable RTD3 for NVidia graphics
* cards as RTD3 is not supported by drivers now . Systems with NVidia
* cards will hang without RTD3 disabled .
*
* Once NVidia drivers officially support RTD3 , this _OSI strings can
* be removed if both new and old graphics cards are supported .
*/
{ " Linux-Dell-Video " , true } ,
2018-07-18 21:46:55 +03:00
/*
* Linux - Lenovo - NV - HDMI - Audio is used by BIOS to power on NVidia ' s HDMI
* audio device which is turned off for power - saving in Windows OS .
* This power management feature observed on some Lenovo Thinkpad
* systems which will not be able to output audio via HDMI without
* a BIOS workaround .
*/
{ " Linux-Lenovo-NV-HDMI-Audio " , true } ,
2018-12-18 07:00:54 +03:00
/*
* Linux - HPI - Hybrid - Graphics is used by BIOS to enable dGPU to
* output video directly to external monitors on HP Inc . mobile
* workstations as Nvidia and AMD VGA drivers provide limited
* hybrid graphics supports .
*/
{ " Linux-HPI-Hybrid-Graphics " , true } ,
2016-05-03 11:49:01 +03:00
} ;
static u32 acpi_osi_handler ( acpi_string interface , u32 supported )
{
if ( ! strcmp ( " Linux " , interface ) ) {
pr_notice_once ( FW_BUG
" BIOS _OSI(Linux) query %s%s \n " ,
osi_config . linux_enable ? " honored " : " ignored " ,
osi_config . linux_cmdline ? " via cmdline " :
osi_config . linux_dmi ? " via DMI " : " " ) ;
}
if ( ! strcmp ( " Darwin " , interface ) ) {
pr_notice_once (
" BIOS _OSI(Darwin) query %s%s \n " ,
osi_config . darwin_enable ? " honored " : " ignored " ,
osi_config . darwin_cmdline ? " via cmdline " :
osi_config . darwin_dmi ? " via DMI " : " " ) ;
}
return supported ;
}
void __init acpi_osi_setup ( char * str )
{
struct acpi_osi_entry * osi ;
bool enable = true ;
int i ;
if ( ! acpi_gbl_create_osi_method )
return ;
if ( str = = NULL | | * str = = ' \0 ' ) {
pr_info ( " _OSI method disabled \n " ) ;
acpi_gbl_create_osi_method = FALSE ;
return ;
}
if ( * str = = ' ! ' ) {
str + + ;
if ( * str = = ' \0 ' ) {
/* Do not override acpi_osi=!* */
if ( ! osi_config . default_disabling )
osi_config . default_disabling =
ACPI_DISABLE_ALL_VENDOR_STRINGS ;
return ;
} else if ( * str = = ' * ' ) {
osi_config . default_disabling = ACPI_DISABLE_ALL_STRINGS ;
for ( i = 0 ; i < OSI_STRING_ENTRIES_MAX ; i + + ) {
osi = & osi_setup_entries [ i ] ;
osi - > enable = false ;
}
return ;
} else if ( * str = = ' ! ' ) {
osi_config . default_disabling = 0 ;
return ;
}
enable = false ;
}
for ( i = 0 ; i < OSI_STRING_ENTRIES_MAX ; i + + ) {
osi = & osi_setup_entries [ i ] ;
if ( ! strcmp ( osi - > string , str ) ) {
osi - > enable = enable ;
break ;
} else if ( osi - > string [ 0 ] = = ' \0 ' ) {
osi - > enable = enable ;
strncpy ( osi - > string , str , OSI_STRING_LENGTH_MAX ) ;
break ;
}
}
}
static void __init __acpi_osi_setup_darwin ( bool enable )
{
osi_config . darwin_enable = ! ! enable ;
if ( enable ) {
acpi_osi_setup ( " ! " ) ;
acpi_osi_setup ( " Darwin " ) ;
} else {
acpi_osi_setup ( " !! " ) ;
acpi_osi_setup ( " !Darwin " ) ;
}
}
static void __init acpi_osi_setup_darwin ( bool enable )
{
/* Override acpi_osi_dmi_blacklisted() */
osi_config . darwin_dmi = 0 ;
osi_config . darwin_cmdline = 1 ;
__acpi_osi_setup_darwin ( enable ) ;
}
/*
* The story of _OSI ( Linux )
*
* From pre - history through Linux - 2.6 .22 , Linux responded TRUE upon a BIOS
* OSI ( Linux ) query .
*
* Unfortunately , reference BIOS writers got wind of this and put
* OSI ( Linux ) in their example code , quickly exposing this string as
* ill - conceived and opening the door to an un - bounded number of BIOS
* incompatibilities .
*
* For example , OSI ( Linux ) was used on resume to re - POST a video card on
* one system , because Linux at that time could not do a speedy restore in
* its native driver . But then upon gaining quick native restore
* capability , Linux has no way to tell the BIOS to skip the time - consuming
* POST - - putting Linux at a permanent performance disadvantage . On
* another system , the BIOS writer used OSI ( Linux ) to infer native OS
* support for IPMI ! On other systems , OSI ( Linux ) simply got in the way of
* Linux claiming to be compatible with other operating systems , exposing
* BIOS issues such as skipped device initialization .
*
* So " Linux " turned out to be a really poor chose of OSI string , and from
* Linux - 2.6 .23 onward we respond FALSE .
*
* BIOS writers should NOT query _OSI ( Linux ) on future systems . Linux will
* complain on the console when it sees it , and return FALSE . To get Linux
* to return TRUE for your system will require a kernel source update to
* add a DMI entry , or boot with " acpi_osi=Linux "
*/
static void __init __acpi_osi_setup_linux ( bool enable )
{
osi_config . linux_enable = ! ! enable ;
if ( enable )
acpi_osi_setup ( " Linux " ) ;
else
acpi_osi_setup ( " !Linux " ) ;
}
static void __init acpi_osi_setup_linux ( bool enable )
{
/* Override acpi_osi_dmi_blacklisted() */
osi_config . linux_dmi = 0 ;
osi_config . linux_cmdline = 1 ;
__acpi_osi_setup_linux ( enable ) ;
}
/*
* Modify the list of " OS Interfaces " reported to BIOS via _OSI
*
* empty string disables _OSI
* string starting with ' ! ' disables that string
* otherwise string is added to list , augmenting built - in strings
*/
static void __init acpi_osi_setup_late ( void )
{
struct acpi_osi_entry * osi ;
char * str ;
int i ;
acpi_status status ;
if ( osi_config . default_disabling ) {
status = acpi_update_interfaces ( osi_config . default_disabling ) ;
if ( ACPI_SUCCESS ( status ) )
pr_info ( " Disabled all _OSI OS vendors%s \n " ,
osi_config . default_disabling = =
ACPI_DISABLE_ALL_STRINGS ?
" and feature groups " : " " ) ;
}
for ( i = 0 ; i < OSI_STRING_ENTRIES_MAX ; i + + ) {
osi = & osi_setup_entries [ i ] ;
str = osi - > string ;
if ( * str = = ' \0 ' )
break ;
if ( osi - > enable ) {
status = acpi_install_interface ( str ) ;
if ( ACPI_SUCCESS ( status ) )
pr_info ( " Added _OSI(%s) \n " , str ) ;
} else {
status = acpi_remove_interface ( str ) ;
if ( ACPI_SUCCESS ( status ) )
pr_info ( " Deleted _OSI(%s) \n " , str ) ;
}
}
}
static int __init osi_setup ( char * str )
{
if ( str & & ! strcmp ( " Linux " , str ) )
acpi_osi_setup_linux ( true ) ;
else if ( str & & ! strcmp ( " !Linux " , str ) )
acpi_osi_setup_linux ( false ) ;
else if ( str & & ! strcmp ( " Darwin " , str ) )
acpi_osi_setup_darwin ( true ) ;
else if ( str & & ! strcmp ( " !Darwin " , str ) )
acpi_osi_setup_darwin ( false ) ;
else
acpi_osi_setup ( str ) ;
return 1 ;
}
__setup ( " acpi_osi= " , osi_setup ) ;
bool acpi_osi_is_win8 ( void )
{
return acpi_gbl_osi_data > = ACPI_OSI_WIN_8 ;
}
EXPORT_SYMBOL ( acpi_osi_is_win8 ) ;
2017-08-01 15:10:41 +03:00
static void __init acpi_osi_dmi_darwin ( void )
2016-05-03 11:49:01 +03:00
{
2017-08-01 15:10:41 +03:00
pr_notice ( " DMI detected to setup _OSI( \" Darwin \" ): Apple hardware \n " ) ;
2016-05-03 11:49:01 +03:00
osi_config . darwin_dmi = 1 ;
2017-08-01 15:10:41 +03:00
__acpi_osi_setup_darwin ( true ) ;
2016-05-03 11:49:01 +03:00
}
2017-06-29 12:49:25 +03:00
static void __init acpi_osi_dmi_linux ( bool enable ,
const struct dmi_system_id * d )
2016-05-03 11:49:01 +03:00
{
pr_notice ( " DMI detected to setup _OSI( \" Linux \" ): %s \n " , d - > ident ) ;
osi_config . linux_dmi = 1 ;
__acpi_osi_setup_linux ( enable ) ;
}
static int __init dmi_enable_osi_linux ( const struct dmi_system_id * d )
{
acpi_osi_dmi_linux ( true , d ) ;
return 0 ;
}
static int __init dmi_disable_osi_vista ( const struct dmi_system_id * d )
{
pr_notice ( " DMI detected: %s \n " , d - > ident ) ;
acpi_osi_setup ( " !Windows 2006 " ) ;
acpi_osi_setup ( " !Windows 2006 SP1 " ) ;
acpi_osi_setup ( " !Windows 2006 SP2 " ) ;
return 0 ;
}
static int __init dmi_disable_osi_win7 ( const struct dmi_system_id * d )
{
pr_notice ( " DMI detected: %s \n " , d - > ident ) ;
acpi_osi_setup ( " !Windows 2009 " ) ;
return 0 ;
}
static int __init dmi_disable_osi_win8 ( const struct dmi_system_id * d )
{
pr_notice ( " DMI detected: %s \n " , d - > ident ) ;
acpi_osi_setup ( " !Windows 2012 " ) ;
return 0 ;
}
/*
* Linux default _OSI response behavior is determined by this DMI table .
*
* Note that _OSI ( " Linux " ) / _OSI ( " Darwin " ) determined here can be overridden
* by acpi_osi = ! Linux / acpi_osi = ! Darwin command line options .
*/
2017-09-14 12:59:30 +03:00
static const struct dmi_system_id acpi_osi_dmi_table [ ] __initconst = {
2016-05-03 11:49:01 +03:00
{
. callback = dmi_disable_osi_vista ,
. ident = " Fujitsu Siemens " ,
. matches = {
DMI_MATCH ( DMI_SYS_VENDOR , " FUJITSU SIEMENS " ) ,
DMI_MATCH ( DMI_PRODUCT_NAME , " ESPRIMO Mobile V5505 " ) ,
} ,
} ,
{
/*
* There have a NVIF method in MSI GX723 DSDT need call by Nvidia
* driver ( e . g . nouveau ) when user press brightness hotkey .
* Currently , nouveau driver didn ' t do the job and it causes there
* have a infinite while loop in DSDT when user press hotkey .
* We add MSI GX723 ' s dmi information to this table for workaround
* this issue .
* Will remove MSI GX723 from the table after nouveau grows support .
*/
. callback = dmi_disable_osi_vista ,
. ident = " MSI GX723 " ,
. matches = {
DMI_MATCH ( DMI_SYS_VENDOR , " Micro-Star International " ) ,
DMI_MATCH ( DMI_PRODUCT_NAME , " GX723 " ) ,
} ,
} ,
{
. callback = dmi_disable_osi_vista ,
. ident = " Sony VGN-NS10J_S " ,
. matches = {
DMI_MATCH ( DMI_SYS_VENDOR , " Sony Corporation " ) ,
DMI_MATCH ( DMI_PRODUCT_NAME , " VGN-NS10J_S " ) ,
} ,
} ,
{
. callback = dmi_disable_osi_vista ,
. ident = " Sony VGN-SR290J " ,
. matches = {
DMI_MATCH ( DMI_SYS_VENDOR , " Sony Corporation " ) ,
DMI_MATCH ( DMI_PRODUCT_NAME , " VGN-SR290J " ) ,
} ,
} ,
{
. callback = dmi_disable_osi_vista ,
. ident = " VGN-NS50B_L " ,
. matches = {
DMI_MATCH ( DMI_SYS_VENDOR , " Sony Corporation " ) ,
DMI_MATCH ( DMI_PRODUCT_NAME , " VGN-NS50B_L " ) ,
} ,
} ,
{
. callback = dmi_disable_osi_vista ,
. ident = " VGN-SR19XN " ,
. matches = {
DMI_MATCH ( DMI_SYS_VENDOR , " Sony Corporation " ) ,
DMI_MATCH ( DMI_PRODUCT_NAME , " VGN-SR19XN " ) ,
} ,
} ,
{
. callback = dmi_disable_osi_vista ,
. ident = " Toshiba Satellite L355 " ,
. matches = {
DMI_MATCH ( DMI_SYS_VENDOR , " TOSHIBA " ) ,
DMI_MATCH ( DMI_PRODUCT_VERSION , " Satellite L355 " ) ,
} ,
} ,
{
. callback = dmi_disable_osi_win7 ,
. ident = " ASUS K50IJ " ,
. matches = {
DMI_MATCH ( DMI_SYS_VENDOR , " ASUSTeK Computer Inc. " ) ,
DMI_MATCH ( DMI_PRODUCT_NAME , " K50IJ " ) ,
} ,
} ,
{
. callback = dmi_disable_osi_vista ,
. ident = " Toshiba P305D " ,
. matches = {
DMI_MATCH ( DMI_SYS_VENDOR , " TOSHIBA " ) ,
DMI_MATCH ( DMI_PRODUCT_NAME , " Satellite P305D " ) ,
} ,
} ,
{
. callback = dmi_disable_osi_vista ,
. ident = " Toshiba NB100 " ,
. matches = {
DMI_MATCH ( DMI_SYS_VENDOR , " TOSHIBA " ) ,
DMI_MATCH ( DMI_PRODUCT_NAME , " NB100 " ) ,
} ,
} ,
/*
* The wireless hotkey does not work on those machines when
* returning true for _OSI ( " Windows 2012 " )
*/
{
. callback = dmi_disable_osi_win8 ,
. ident = " Dell Inspiron 7737 " ,
. matches = {
DMI_MATCH ( DMI_SYS_VENDOR , " Dell Inc. " ) ,
DMI_MATCH ( DMI_PRODUCT_NAME , " Inspiron 7737 " ) ,
} ,
} ,
{
. callback = dmi_disable_osi_win8 ,
. ident = " Dell Inspiron 7537 " ,
. matches = {
DMI_MATCH ( DMI_SYS_VENDOR , " Dell Inc. " ) ,
DMI_MATCH ( DMI_PRODUCT_NAME , " Inspiron 7537 " ) ,
} ,
} ,
{
. callback = dmi_disable_osi_win8 ,
. ident = " Dell Inspiron 5437 " ,
. matches = {
DMI_MATCH ( DMI_SYS_VENDOR , " Dell Inc. " ) ,
DMI_MATCH ( DMI_PRODUCT_NAME , " Inspiron 5437 " ) ,
} ,
} ,
{
. callback = dmi_disable_osi_win8 ,
. ident = " Dell Inspiron 3437 " ,
. matches = {
DMI_MATCH ( DMI_SYS_VENDOR , " Dell Inc. " ) ,
DMI_MATCH ( DMI_PRODUCT_NAME , " Inspiron 3437 " ) ,
} ,
} ,
{
. callback = dmi_disable_osi_win8 ,
. ident = " Dell Vostro 3446 " ,
. matches = {
DMI_MATCH ( DMI_SYS_VENDOR , " Dell Inc. " ) ,
DMI_MATCH ( DMI_PRODUCT_NAME , " Vostro 3446 " ) ,
} ,
} ,
{
. callback = dmi_disable_osi_win8 ,
. ident = " Dell Vostro 3546 " ,
. matches = {
DMI_MATCH ( DMI_SYS_VENDOR , " Dell Inc. " ) ,
DMI_MATCH ( DMI_PRODUCT_NAME , " Vostro 3546 " ) ,
} ,
} ,
/*
* BIOS invocation of _OSI ( Linux ) is almost always a BIOS bug .
* Linux ignores it , except for the machines enumerated below .
*/
/*
2019-11-05 13:15:01 +03:00
* Without this EEEpc exports a non working WMI interface , with
* this it exports a working " good old " eeepc_laptop interface ,
* fixing both brightness control , and rfkill not working .
2016-05-03 11:49:01 +03:00
*/
{
. callback = dmi_enable_osi_linux ,
. ident = " Asus EEE PC 1015PX " ,
. matches = {
DMI_MATCH ( DMI_SYS_VENDOR , " ASUSTeK Computer INC. " ) ,
DMI_MATCH ( DMI_PRODUCT_NAME , " 1015PX " ) ,
} ,
} ,
{ }
} ;
static __init void acpi_osi_dmi_blacklisted ( void )
{
dmi_check_system ( acpi_osi_dmi_table ) ;
2017-08-01 15:10:41 +03:00
/* Enable _OSI("Darwin") for Apple platforms. */
if ( x86_apple_machine )
acpi_osi_dmi_darwin ( ) ;
2016-05-03 11:49:01 +03:00
}
int __init early_acpi_osi_init ( void )
{
acpi_osi_dmi_blacklisted ( ) ;
return 0 ;
}
int __init acpi_osi_init ( void )
{
acpi_install_interface_handler ( acpi_osi_handler ) ;
acpi_osi_setup_late ( ) ;
return 0 ;
}