2017-11-01 22:25:31 +03:00
/*
* SMI methods for use with dell - smbios
*
* Copyright ( c ) Red Hat < mjg @ redhat . com >
* Copyright ( c ) 2014 Gabriele Mazzotta < gabriele . mzt @ gmail . com >
* Copyright ( c ) 2014 Pali Rohár < pali . rohar @ gmail . com >
* Copyright ( c ) 2017 Dell Inc .
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation .
*/
# define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
# include <linux/dmi.h>
# include <linux/gfp.h>
# include <linux/io.h>
# include <linux/module.h>
# include <linux/mutex.h>
# include <linux/platform_device.h>
2018-09-27 00:50:20 +03:00
# include "dcdbas.h"
2017-11-01 22:25:31 +03:00
# include "dell-smbios.h"
static int da_command_address ;
static int da_command_code ;
static struct calling_interface_buffer * buffer ;
2018-06-21 21:15:24 +03:00
static struct platform_device * platform_device ;
2017-11-01 22:25:31 +03:00
static DEFINE_MUTEX ( smm_mutex ) ;
static const struct dmi_system_id dell_device_table [ ] __initconst = {
{
. ident = " Dell laptop " ,
. matches = {
DMI_MATCH ( DMI_SYS_VENDOR , " Dell Inc. " ) ,
DMI_MATCH ( DMI_CHASSIS_TYPE , " 8 " ) ,
} ,
} ,
{
. matches = {
DMI_MATCH ( DMI_SYS_VENDOR , " Dell Inc. " ) ,
DMI_MATCH ( DMI_CHASSIS_TYPE , " 9 " ) , /*Laptop*/
} ,
} ,
{
. matches = {
DMI_MATCH ( DMI_SYS_VENDOR , " Dell Inc. " ) ,
DMI_MATCH ( DMI_CHASSIS_TYPE , " 10 " ) , /*Notebook*/
} ,
} ,
{
. ident = " Dell Computer Corporation " ,
. matches = {
DMI_MATCH ( DMI_SYS_VENDOR , " Dell Computer Corporation " ) ,
DMI_MATCH ( DMI_CHASSIS_TYPE , " 8 " ) ,
} ,
} ,
{ }
} ;
MODULE_DEVICE_TABLE ( dmi , dell_device_table ) ;
2018-02-27 21:23:04 +03:00
static void parse_da_table ( const struct dmi_header * dm )
2017-11-01 22:25:31 +03:00
{
struct calling_interface_structure * table =
container_of ( dm , struct calling_interface_structure , header ) ;
/* 4 bytes of table header, plus 7 bytes of Dell header, plus at least
* 6 bytes of entry
*/
if ( dm - > length < 17 )
return ;
da_command_address = table - > cmdIOAddress ;
da_command_code = table - > cmdIOCode ;
}
2018-02-27 21:23:04 +03:00
static void find_cmd_address ( const struct dmi_header * dm , void * dummy )
2017-11-01 22:25:31 +03:00
{
switch ( dm - > type ) {
case 0xda : /* Calling interface */
parse_da_table ( dm ) ;
break ;
}
}
2018-06-21 21:15:24 +03:00
static int dell_smbios_smm_call ( struct calling_interface_buffer * input )
2017-11-01 22:25:31 +03:00
{
struct smi_cmd command ;
size_t size ;
size = sizeof ( struct calling_interface_buffer ) ;
command . magic = SMI_CMD_MAGIC ;
command . command_address = da_command_address ;
command . command_code = da_command_code ;
command . ebx = virt_to_phys ( buffer ) ;
command . ecx = 0x42534931 ;
mutex_lock ( & smm_mutex ) ;
memcpy ( buffer , input , size ) ;
dcdbas_smi_request ( & command ) ;
memcpy ( input , buffer , size ) ;
mutex_unlock ( & smm_mutex ) ;
return 0 ;
}
2017-11-01 22:25:33 +03:00
/* When enabled this indicates that SMM won't work */
static bool test_wsmt_enabled ( void )
{
struct calling_interface_token * wsmt ;
/* if token doesn't exist, SMM will work */
wsmt = dell_smbios_find_token ( WSMT_EN_TOKEN ) ;
if ( ! wsmt )
return false ;
/* If token exists, try to access over SMM but set a dummy return.
* - If WSMT disabled it will be overwritten by SMM
* - If WSMT enabled then dummy value will remain
*/
buffer - > cmd_class = CLASS_TOKEN_READ ;
buffer - > cmd_select = SELECT_TOKEN_STD ;
memset ( buffer , 0 , sizeof ( struct calling_interface_buffer ) ) ;
buffer - > input [ 0 ] = wsmt - > location ;
buffer - > output [ 0 ] = 99 ;
dell_smbios_smm_call ( buffer ) ;
if ( buffer - > output [ 0 ] = = 99 )
return true ;
return false ;
}
2018-02-27 21:23:04 +03:00
int init_dell_smbios_smm ( void )
2017-11-01 22:25:31 +03:00
{
int ret ;
/*
* Allocate buffer below 4 GB for SMI data - - only 32 - bit physical addr
* is passed to SMI handler .
*/
buffer = ( void * ) __get_free_page ( GFP_KERNEL | GFP_DMA32 ) ;
if ( ! buffer )
return - ENOMEM ;
dmi_walk ( find_cmd_address , NULL ) ;
2017-11-01 22:25:33 +03:00
if ( test_wsmt_enabled ( ) ) {
pr_debug ( " Disabling due to WSMT enabled \n " ) ;
ret = - ENODEV ;
goto fail_wsmt ;
}
2017-11-01 22:25:31 +03:00
platform_device = platform_device_alloc ( " dell-smbios " , 1 ) ;
if ( ! platform_device ) {
ret = - ENOMEM ;
goto fail_platform_device_alloc ;
}
ret = platform_device_add ( platform_device ) ;
if ( ret )
goto fail_platform_device_add ;
ret = dell_smbios_register_device ( & platform_device - > dev ,
& dell_smbios_smm_call ) ;
if ( ret )
goto fail_register ;
return 0 ;
fail_register :
platform_device_del ( platform_device ) ;
fail_platform_device_add :
platform_device_put ( platform_device ) ;
2017-11-01 22:25:33 +03:00
fail_wsmt :
2017-11-01 22:25:31 +03:00
fail_platform_device_alloc :
free_page ( ( unsigned long ) buffer ) ;
return ret ;
}
2018-02-27 21:23:04 +03:00
void exit_dell_smbios_smm ( void )
2017-11-01 22:25:31 +03:00
{
if ( platform_device ) {
dell_smbios_unregister_device ( & platform_device - > dev ) ;
platform_device_unregister ( platform_device ) ;
free_page ( ( unsigned long ) buffer ) ;
}
}