2016-01-08 01:49:22 +03:00
/*
* Intel Management Engine Interface ( Intel MEI ) Linux driver
* Copyright ( c ) 2015 , Intel Corporation .
*
* This program is free software ; you can redistribute it and / or modify it
* under the terms and conditions of the GNU General Public License ,
* version 2 , as published by the Free Software Foundation .
*
* This program is distributed in the hope it will be useful , but WITHOUT
* ANY WARRANTY ; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE . See the GNU General Public License for
* more details .
*/
# include <linux/module.h>
# include <linux/slab.h>
# include <linux/interrupt.h>
2016-01-08 01:49:23 +03:00
# include <linux/debugfs.h>
2016-02-07 23:46:48 +03:00
# include <linux/completion.h>
2016-01-08 01:49:22 +03:00
# include <linux/watchdog.h>
# include <linux/uuid.h>
# include <linux/mei_cl_bus.h>
/*
* iAMT Watchdog Device
*/
# define INTEL_AMT_WATCHDOG_ID "iamt_wdt"
# define MEI_WDT_DEFAULT_TIMEOUT 120 /* seconds */
# define MEI_WDT_MIN_TIMEOUT 120 /* seconds */
# define MEI_WDT_MAX_TIMEOUT 65535 /* seconds */
/* Commands */
# define MEI_MANAGEMENT_CONTROL 0x02
/* MEI Management Control version number */
# define MEI_MC_VERSION_NUMBER 0x10
/* Sub Commands */
# define MEI_MC_START_WD_TIMER_REQ 0x13
2016-02-07 23:46:48 +03:00
# define MEI_MC_START_WD_TIMER_RES 0x83
# define MEI_WDT_STATUS_SUCCESS 0
# define MEI_WDT_WDSTATE_NOT_REQUIRED 0x1
2016-01-08 01:49:22 +03:00
# define MEI_MC_STOP_WD_TIMER_REQ 0x14
/**
* enum mei_wdt_state - internal watchdog state
*
2016-02-07 23:46:48 +03:00
* @ MEI_WDT_PROBE : wd in probing stage
2016-01-08 01:49:22 +03:00
* @ MEI_WDT_IDLE : wd is idle and not opened
* @ MEI_WDT_START : wd was opened , start was called
* @ MEI_WDT_RUNNING : wd is expecting keep alive pings
* @ MEI_WDT_STOPPING : wd is stopping and will move to IDLE
2016-02-07 23:46:48 +03:00
* @ MEI_WDT_NOT_REQUIRED : wd device is not required
2016-01-08 01:49:22 +03:00
*/
enum mei_wdt_state {
2016-02-07 23:46:48 +03:00
MEI_WDT_PROBE ,
2016-01-08 01:49:22 +03:00
MEI_WDT_IDLE ,
MEI_WDT_START ,
MEI_WDT_RUNNING ,
MEI_WDT_STOPPING ,
2016-02-07 23:46:48 +03:00
MEI_WDT_NOT_REQUIRED ,
2016-01-08 01:49:22 +03:00
} ;
2016-01-08 01:49:23 +03:00
static const char * mei_wdt_state_str ( enum mei_wdt_state state )
{
switch ( state ) {
2016-02-07 23:46:48 +03:00
case MEI_WDT_PROBE :
return " PROBE " ;
2016-01-08 01:49:23 +03:00
case MEI_WDT_IDLE :
return " IDLE " ;
case MEI_WDT_START :
return " START " ;
case MEI_WDT_RUNNING :
return " RUNNING " ;
case MEI_WDT_STOPPING :
return " STOPPING " ;
2016-02-07 23:46:48 +03:00
case MEI_WDT_NOT_REQUIRED :
return " NOT_REQUIRED " ;
2016-01-08 01:49:23 +03:00
default :
return " unknown " ;
}
}
2016-01-08 01:49:22 +03:00
/**
* struct mei_wdt - mei watchdog driver
* @ wdd : watchdog device
*
* @ cldev : mei watchdog client device
* @ state : watchdog internal state
2016-02-07 23:46:48 +03:00
* @ resp_required : ping required response
* @ response : ping response completion
* @ unregister : unregister worker
* @ reg_lock : watchdog device registration lock
2016-01-08 01:49:22 +03:00
* @ timeout : watchdog current timeout
2016-01-08 01:49:23 +03:00
*
* @ dbgfs_dir : debugfs dir entry
2016-01-08 01:49:22 +03:00
*/
struct mei_wdt {
struct watchdog_device wdd ;
struct mei_cl_device * cldev ;
enum mei_wdt_state state ;
2016-02-07 23:46:48 +03:00
bool resp_required ;
struct completion response ;
struct work_struct unregister ;
struct mutex reg_lock ;
2016-01-08 01:49:22 +03:00
u16 timeout ;
2016-01-08 01:49:23 +03:00
# if IS_ENABLED(CONFIG_DEBUG_FS)
struct dentry * dbgfs_dir ;
# endif /* CONFIG_DEBUG_FS */
2016-01-08 01:49:22 +03:00
} ;
/*
* struct mei_mc_hdr - Management Control Command Header
*
* @ command : Management Control ( 0x2 )
* @ bytecount : Number of bytes in the message beyond this byte
* @ subcommand : Management Control Subcommand
* @ versionnumber : Management Control Version ( 0x10 )
*/
struct mei_mc_hdr {
u8 command ;
u8 bytecount ;
u8 subcommand ;
u8 versionnumber ;
} ;
/**
* struct mei_wdt_start_request watchdog start / ping
*
* @ hdr : Management Control Command Header
* @ timeout : timeout value
* @ reserved : reserved ( legacy )
*/
struct mei_wdt_start_request {
struct mei_mc_hdr hdr ;
u16 timeout ;
u8 reserved [ 17 ] ;
} __packed ;
2016-02-07 23:46:48 +03:00
/**
* struct mei_wdt_start_response watchdog start / ping response
*
* @ hdr : Management Control Command Header
* @ status : operation status
* @ wdstate : watchdog status bit mask
*/
struct mei_wdt_start_response {
struct mei_mc_hdr hdr ;
u8 status ;
u8 wdstate ;
} __packed ;
2016-01-08 01:49:22 +03:00
/**
* struct mei_wdt_stop_request - watchdog stop
*
* @ hdr : Management Control Command Header
*/
struct mei_wdt_stop_request {
struct mei_mc_hdr hdr ;
} __packed ;
/**
* mei_wdt_ping - send wd start / ping command
*
* @ wdt : mei watchdog device
*
* Return : 0 on success ,
* negative errno code on failure
*/
static int mei_wdt_ping ( struct mei_wdt * wdt )
{
struct mei_wdt_start_request req ;
const size_t req_len = sizeof ( req ) ;
int ret ;
memset ( & req , 0 , req_len ) ;
req . hdr . command = MEI_MANAGEMENT_CONTROL ;
req . hdr . bytecount = req_len - offsetof ( struct mei_mc_hdr , subcommand ) ;
req . hdr . subcommand = MEI_MC_START_WD_TIMER_REQ ;
req . hdr . versionnumber = MEI_MC_VERSION_NUMBER ;
req . timeout = wdt - > timeout ;
ret = mei_cldev_send ( wdt - > cldev , ( u8 * ) & req , req_len ) ;
if ( ret < 0 )
return ret ;
return 0 ;
}
/**
* mei_wdt_stop - send wd stop command
*
* @ wdt : mei watchdog device
*
* Return : 0 on success ,
* negative errno code on failure
*/
static int mei_wdt_stop ( struct mei_wdt * wdt )
{
struct mei_wdt_stop_request req ;
const size_t req_len = sizeof ( req ) ;
int ret ;
memset ( & req , 0 , req_len ) ;
req . hdr . command = MEI_MANAGEMENT_CONTROL ;
req . hdr . bytecount = req_len - offsetof ( struct mei_mc_hdr , subcommand ) ;
req . hdr . subcommand = MEI_MC_STOP_WD_TIMER_REQ ;
req . hdr . versionnumber = MEI_MC_VERSION_NUMBER ;
ret = mei_cldev_send ( wdt - > cldev , ( u8 * ) & req , req_len ) ;
if ( ret < 0 )
return ret ;
return 0 ;
}
/**
* mei_wdt_ops_start - wd start command from the watchdog core .
*
* @ wdd : watchdog device
*
* Return : 0 on success or - ENODEV ;
*/
static int mei_wdt_ops_start ( struct watchdog_device * wdd )
{
struct mei_wdt * wdt = watchdog_get_drvdata ( wdd ) ;
wdt - > state = MEI_WDT_START ;
wdd - > timeout = wdt - > timeout ;
return 0 ;
}
/**
* mei_wdt_ops_stop - wd stop command from the watchdog core .
*
* @ wdd : watchdog device
*
* Return : 0 if success , negative errno code for failure
*/
static int mei_wdt_ops_stop ( struct watchdog_device * wdd )
{
struct mei_wdt * wdt = watchdog_get_drvdata ( wdd ) ;
int ret ;
if ( wdt - > state ! = MEI_WDT_RUNNING )
return 0 ;
wdt - > state = MEI_WDT_STOPPING ;
ret = mei_wdt_stop ( wdt ) ;
if ( ret )
return ret ;
wdt - > state = MEI_WDT_IDLE ;
return 0 ;
}
/**
* mei_wdt_ops_ping - wd ping command from the watchdog core .
*
* @ wdd : watchdog device
*
* Return : 0 if success , negative errno code on failure
*/
static int mei_wdt_ops_ping ( struct watchdog_device * wdd )
{
struct mei_wdt * wdt = watchdog_get_drvdata ( wdd ) ;
int ret ;
if ( wdt - > state ! = MEI_WDT_START & & wdt - > state ! = MEI_WDT_RUNNING )
return 0 ;
2016-02-07 23:46:48 +03:00
if ( wdt - > resp_required )
init_completion ( & wdt - > response ) ;
wdt - > state = MEI_WDT_RUNNING ;
2016-01-08 01:49:22 +03:00
ret = mei_wdt_ping ( wdt ) ;
if ( ret )
return ret ;
2016-02-07 23:46:48 +03:00
if ( wdt - > resp_required )
ret = wait_for_completion_killable ( & wdt - > response ) ;
2016-01-08 01:49:22 +03:00
2016-02-07 23:46:48 +03:00
return ret ;
2016-01-08 01:49:22 +03:00
}
/**
* mei_wdt_ops_set_timeout - wd set timeout command from the watchdog core .
*
* @ wdd : watchdog device
* @ timeout : timeout value to set
*
* Return : 0 if success , negative errno code for failure
*/
static int mei_wdt_ops_set_timeout ( struct watchdog_device * wdd ,
unsigned int timeout )
{
struct mei_wdt * wdt = watchdog_get_drvdata ( wdd ) ;
/* valid value is already checked by the caller */
wdt - > timeout = timeout ;
wdd - > timeout = timeout ;
return 0 ;
}
static const struct watchdog_ops wd_ops = {
. owner = THIS_MODULE ,
. start = mei_wdt_ops_start ,
. stop = mei_wdt_ops_stop ,
. ping = mei_wdt_ops_ping ,
. set_timeout = mei_wdt_ops_set_timeout ,
} ;
/* not const as the firmware_version field need to be retrieved */
static struct watchdog_info wd_info = {
. identity = INTEL_AMT_WATCHDOG_ID ,
. options = WDIOF_KEEPALIVEPING |
WDIOF_SETTIMEOUT |
WDIOF_ALARMONLY ,
} ;
2016-02-07 23:46:48 +03:00
/**
* __mei_wdt_is_registered - check if wdt is registered
*
* @ wdt : mei watchdog device
*
* Return : true if the wdt is registered with the watchdog subsystem
* Locking : should be called under wdt - > reg_lock
*/
static inline bool __mei_wdt_is_registered ( struct mei_wdt * wdt )
{
return ! ! watchdog_get_drvdata ( & wdt - > wdd ) ;
}
2016-01-08 01:49:22 +03:00
/**
* mei_wdt_unregister - unregister from the watchdog subsystem
*
* @ wdt : mei watchdog device
*/
static void mei_wdt_unregister ( struct mei_wdt * wdt )
{
2016-02-07 23:46:48 +03:00
mutex_lock ( & wdt - > reg_lock ) ;
if ( __mei_wdt_is_registered ( wdt ) ) {
watchdog_unregister_device ( & wdt - > wdd ) ;
watchdog_set_drvdata ( & wdt - > wdd , NULL ) ;
memset ( & wdt - > wdd , 0 , sizeof ( wdt - > wdd ) ) ;
}
mutex_unlock ( & wdt - > reg_lock ) ;
2016-01-08 01:49:22 +03:00
}
/**
* mei_wdt_register - register with the watchdog subsystem
*
* @ wdt : mei watchdog device
*
* Return : 0 if success , negative errno code for failure
*/
static int mei_wdt_register ( struct mei_wdt * wdt )
{
struct device * dev ;
int ret ;
if ( ! wdt | | ! wdt - > cldev )
return - EINVAL ;
dev = & wdt - > cldev - > dev ;
2016-02-07 23:46:48 +03:00
mutex_lock ( & wdt - > reg_lock ) ;
if ( __mei_wdt_is_registered ( wdt ) ) {
ret = 0 ;
goto out ;
}
2016-01-08 01:49:22 +03:00
wdt - > wdd . info = & wd_info ;
wdt - > wdd . ops = & wd_ops ;
wdt - > wdd . parent = dev ;
wdt - > wdd . timeout = MEI_WDT_DEFAULT_TIMEOUT ;
wdt - > wdd . min_timeout = MEI_WDT_MIN_TIMEOUT ;
wdt - > wdd . max_timeout = MEI_WDT_MAX_TIMEOUT ;
watchdog_set_drvdata ( & wdt - > wdd , wdt ) ;
ret = watchdog_register_device ( & wdt - > wdd ) ;
if ( ret ) {
dev_err ( dev , " unable to register watchdog device = %d. \n " , ret ) ;
watchdog_set_drvdata ( & wdt - > wdd , NULL ) ;
}
2016-02-07 23:46:48 +03:00
wdt - > state = MEI_WDT_IDLE ;
out :
mutex_unlock ( & wdt - > reg_lock ) ;
2016-01-08 01:49:22 +03:00
return ret ;
}
2016-02-07 23:46:48 +03:00
static void mei_wdt_unregister_work ( struct work_struct * work )
{
struct mei_wdt * wdt = container_of ( work , struct mei_wdt , unregister ) ;
mei_wdt_unregister ( wdt ) ;
}
/**
* mei_wdt_event_rx - callback for data receive
*
* @ cldev : bus device
*/
static void mei_wdt_event_rx ( struct mei_cl_device * cldev )
{
struct mei_wdt * wdt = mei_cldev_get_drvdata ( cldev ) ;
struct mei_wdt_start_response res ;
const size_t res_len = sizeof ( res ) ;
int ret ;
ret = mei_cldev_recv ( wdt - > cldev , ( u8 * ) & res , res_len ) ;
if ( ret < 0 ) {
dev_err ( & cldev - > dev , " failure in recv %d \n " , ret ) ;
return ;
}
/* Empty response can be sent on stop */
if ( ret = = 0 )
return ;
if ( ret < sizeof ( struct mei_mc_hdr ) ) {
dev_err ( & cldev - > dev , " recv small data %d \n " , ret ) ;
return ;
}
if ( res . hdr . command ! = MEI_MANAGEMENT_CONTROL | |
res . hdr . versionnumber ! = MEI_MC_VERSION_NUMBER ) {
dev_err ( & cldev - > dev , " wrong command received \n " ) ;
return ;
}
if ( res . hdr . subcommand ! = MEI_MC_START_WD_TIMER_RES ) {
dev_warn ( & cldev - > dev , " unsupported command %d :%s[%d] \n " ,
res . hdr . subcommand ,
mei_wdt_state_str ( wdt - > state ) ,
wdt - > state ) ;
return ;
}
/* Run the unregistration in a worker as this can be
* run only after ping completion , otherwise the flow will
* deadlock on watchdog core mutex .
*/
if ( wdt - > state = = MEI_WDT_RUNNING ) {
if ( res . wdstate & MEI_WDT_WDSTATE_NOT_REQUIRED ) {
wdt - > state = MEI_WDT_NOT_REQUIRED ;
schedule_work ( & wdt - > unregister ) ;
}
goto out ;
}
if ( wdt - > state = = MEI_WDT_PROBE ) {
if ( res . wdstate & MEI_WDT_WDSTATE_NOT_REQUIRED ) {
wdt - > state = MEI_WDT_NOT_REQUIRED ;
} else {
/* stop the watchdog and register watchdog device */
mei_wdt_stop ( wdt ) ;
mei_wdt_register ( wdt ) ;
}
return ;
}
dev_warn ( & cldev - > dev , " not in correct state %s[%d] \n " ,
mei_wdt_state_str ( wdt - > state ) , wdt - > state ) ;
out :
if ( ! completion_done ( & wdt - > response ) )
complete ( & wdt - > response ) ;
}
2016-02-07 23:46:50 +03:00
/*
* mei_wdt_notify_event - callback for event notification
*
* @ cldev : bus device
*/
static void mei_wdt_notify_event ( struct mei_cl_device * cldev )
{
struct mei_wdt * wdt = mei_cldev_get_drvdata ( cldev ) ;
if ( wdt - > state ! = MEI_WDT_NOT_REQUIRED )
return ;
mei_wdt_register ( wdt ) ;
}
2016-02-07 23:46:48 +03:00
/**
* mei_wdt_event - callback for event receive
*
* @ cldev : bus device
* @ events : event mask
* @ context : callback context
*/
static void mei_wdt_event ( struct mei_cl_device * cldev ,
u32 events , void * context )
{
if ( events & BIT ( MEI_CL_EVENT_RX ) )
mei_wdt_event_rx ( cldev ) ;
2016-02-07 23:46:50 +03:00
if ( events & BIT ( MEI_CL_EVENT_NOTIF ) )
mei_wdt_notify_event ( cldev ) ;
2016-02-07 23:46:48 +03:00
}
2016-01-08 01:49:23 +03:00
# if IS_ENABLED(CONFIG_DEBUG_FS)
2016-02-07 23:46:49 +03:00
static ssize_t mei_dbgfs_read_activation ( struct file * file , char __user * ubuf ,
size_t cnt , loff_t * ppos )
{
struct mei_wdt * wdt = file - > private_data ;
const size_t bufsz = 32 ;
char buf [ 32 ] ;
ssize_t pos ;
mutex_lock ( & wdt - > reg_lock ) ;
pos = scnprintf ( buf , bufsz , " %s \n " ,
__mei_wdt_is_registered ( wdt ) ? " activated " : " deactivated " ) ;
mutex_unlock ( & wdt - > reg_lock ) ;
return simple_read_from_buffer ( ubuf , cnt , ppos , buf , pos ) ;
}
static const struct file_operations dbgfs_fops_activation = {
. open = simple_open ,
. read = mei_dbgfs_read_activation ,
. llseek = generic_file_llseek ,
} ;
2016-01-08 01:49:23 +03:00
static ssize_t mei_dbgfs_read_state ( struct file * file , char __user * ubuf ,
size_t cnt , loff_t * ppos )
{
struct mei_wdt * wdt = file - > private_data ;
const size_t bufsz = 32 ;
char buf [ bufsz ] ;
ssize_t pos ;
pos = scnprintf ( buf , bufsz , " state: %s \n " ,
mei_wdt_state_str ( wdt - > state ) ) ;
return simple_read_from_buffer ( ubuf , cnt , ppos , buf , pos ) ;
}
static const struct file_operations dbgfs_fops_state = {
. open = simple_open ,
. read = mei_dbgfs_read_state ,
. llseek = generic_file_llseek ,
} ;
static void dbgfs_unregister ( struct mei_wdt * wdt )
{
debugfs_remove_recursive ( wdt - > dbgfs_dir ) ;
wdt - > dbgfs_dir = NULL ;
}
static int dbgfs_register ( struct mei_wdt * wdt )
{
struct dentry * dir , * f ;
dir = debugfs_create_dir ( KBUILD_MODNAME , NULL ) ;
if ( ! dir )
return - ENOMEM ;
wdt - > dbgfs_dir = dir ;
f = debugfs_create_file ( " state " , S_IRUSR , dir , wdt , & dbgfs_fops_state ) ;
if ( ! f )
goto err ;
2016-02-07 23:46:49 +03:00
f = debugfs_create_file ( " activation " , S_IRUSR ,
dir , wdt , & dbgfs_fops_activation ) ;
if ( ! f )
goto err ;
2016-01-08 01:49:23 +03:00
return 0 ;
err :
dbgfs_unregister ( wdt ) ;
return - ENODEV ;
}
# else
static inline void dbgfs_unregister ( struct mei_wdt * wdt ) { }
static inline int dbgfs_register ( struct mei_wdt * wdt )
{
return 0 ;
}
# endif /* CONFIG_DEBUG_FS */
2016-01-08 01:49:22 +03:00
static int mei_wdt_probe ( struct mei_cl_device * cldev ,
const struct mei_cl_device_id * id )
{
struct mei_wdt * wdt ;
int ret ;
wdt = kzalloc ( sizeof ( struct mei_wdt ) , GFP_KERNEL ) ;
if ( ! wdt )
return - ENOMEM ;
wdt - > timeout = MEI_WDT_DEFAULT_TIMEOUT ;
2016-02-07 23:46:48 +03:00
wdt - > state = MEI_WDT_PROBE ;
2016-01-08 01:49:22 +03:00
wdt - > cldev = cldev ;
2016-02-07 23:46:48 +03:00
wdt - > resp_required = mei_cldev_ver ( cldev ) > 0x1 ;
mutex_init ( & wdt - > reg_lock ) ;
init_completion ( & wdt - > response ) ;
INIT_WORK ( & wdt - > unregister , mei_wdt_unregister_work ) ;
2016-01-08 01:49:22 +03:00
mei_cldev_set_drvdata ( cldev , wdt ) ;
ret = mei_cldev_enable ( cldev ) ;
if ( ret < 0 ) {
dev_err ( & cldev - > dev , " Could not enable cl device \n " ) ;
goto err_out ;
}
2016-02-07 23:46:50 +03:00
ret = mei_cldev_register_event_cb ( wdt - > cldev ,
BIT ( MEI_CL_EVENT_RX ) |
BIT ( MEI_CL_EVENT_NOTIF ) ,
2016-02-07 23:46:48 +03:00
mei_wdt_event , NULL ) ;
2016-02-07 23:46:50 +03:00
/* on legacy devices notification is not supported
* this doesn ' t fail the registration for RX event
*/
if ( ret & & ret ! = - EOPNOTSUPP ) {
2016-02-07 23:46:48 +03:00
dev_err ( & cldev - > dev , " Could not register event ret=%d \n " , ret ) ;
goto err_disable ;
}
2016-01-08 01:49:22 +03:00
wd_info . firmware_version = mei_cldev_ver ( cldev ) ;
2016-02-07 23:46:48 +03:00
if ( wdt - > resp_required )
ret = mei_wdt_ping ( wdt ) ;
else
ret = mei_wdt_register ( wdt ) ;
2016-01-08 01:49:22 +03:00
if ( ret )
goto err_disable ;
2016-01-08 01:49:23 +03:00
if ( dbgfs_register ( wdt ) )
dev_warn ( & cldev - > dev , " cannot register debugfs \n " ) ;
2016-01-08 01:49:22 +03:00
return 0 ;
err_disable :
mei_cldev_disable ( cldev ) ;
err_out :
kfree ( wdt ) ;
return ret ;
}
static int mei_wdt_remove ( struct mei_cl_device * cldev )
{
struct mei_wdt * wdt = mei_cldev_get_drvdata ( cldev ) ;
2016-02-07 23:46:48 +03:00
/* Free the caller in case of fw initiated or unexpected reset */
if ( ! completion_done ( & wdt - > response ) )
complete ( & wdt - > response ) ;
cancel_work_sync ( & wdt - > unregister ) ;
2016-01-08 01:49:22 +03:00
mei_wdt_unregister ( wdt ) ;
mei_cldev_disable ( cldev ) ;
2016-01-08 01:49:23 +03:00
dbgfs_unregister ( wdt ) ;
2016-01-08 01:49:22 +03:00
kfree ( wdt ) ;
return 0 ;
}
# define MEI_UUID_WD UUID_LE(0x05B79A6F, 0x4628, 0x4D7F, \
0x89 , 0x9D , 0xA9 , 0x15 , 0x14 , 0xCB , 0x32 , 0xAB )
static struct mei_cl_device_id mei_wdt_tbl [ ] = {
2016-02-07 23:46:48 +03:00
{ . uuid = MEI_UUID_WD , . version = MEI_CL_VERSION_ANY } ,
2016-01-08 01:49:22 +03:00
/* required last entry */
{ }
} ;
MODULE_DEVICE_TABLE ( mei , mei_wdt_tbl ) ;
static struct mei_cl_driver mei_wdt_driver = {
. id_table = mei_wdt_tbl ,
. name = KBUILD_MODNAME ,
. probe = mei_wdt_probe ,
. remove = mei_wdt_remove ,
} ;
static int __init mei_wdt_init ( void )
{
int ret ;
ret = mei_cldev_driver_register ( & mei_wdt_driver ) ;
if ( ret ) {
pr_err ( KBUILD_MODNAME " : module registration failed \n " ) ;
return ret ;
}
return 0 ;
}
static void __exit mei_wdt_exit ( void )
{
mei_cldev_driver_unregister ( & mei_wdt_driver ) ;
}
module_init ( mei_wdt_init ) ;
module_exit ( mei_wdt_exit ) ;
MODULE_AUTHOR ( " Intel Corporation " ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_DESCRIPTION ( " Device driver for Intel MEI iAMT watchdog " ) ;