2011-05-15 14:43:47 +04:00
/*
*
* Intel Management Engine Interface ( Intel MEI ) Linux driver
2012-02-09 21:25:53 +04:00
* Copyright ( c ) 2003 - 2012 , Intel Corporation .
2011-05-15 14:43:47 +04:00
*
* 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/kernel.h>
# include <linux/module.h>
# include <linux/moduleparam.h>
# include <linux/device.h>
# include <linux/pci.h>
# include <linux/sched.h>
2011-09-07 10:03:09 +04:00
# include <linux/watchdog.h>
2011-05-15 14:43:47 +04:00
2012-12-25 21:06:03 +04:00
# include <linux/mei.h>
2011-05-15 14:43:47 +04:00
# include "mei_dev.h"
2013-01-09 01:07:12 +04:00
# include "hbm.h"
2013-01-09 01:07:17 +04:00
# include "hw-me.h"
2013-01-09 01:07:14 +04:00
# include "client.h"
2011-05-15 14:43:47 +04:00
static const u8 mei_start_wd_params [ ] = { 0x02 , 0x12 , 0x13 , 0x10 } ;
static const u8 mei_stop_wd_params [ ] = { 0x02 , 0x02 , 0x14 , 0x10 } ;
2011-12-22 20:50:50 +04:00
/*
* AMT Watchdog Device
*/
# define INTEL_AMT_WATCHDOG_ID "INTCAMT"
2011-05-15 14:43:47 +04:00
/* UUIDs for AMT F/W clients */
const uuid_le mei_wd_guid = UUID_LE ( 0x05B79A6F , 0x4628 , 0x4D7F , 0x89 ,
0x9D , 0xA9 , 0x15 , 0x14 , 0xCB ,
0x32 , 0xAB ) ;
2012-03-19 19:58:43 +04:00
static void mei_wd_set_start_timeout ( struct mei_device * dev , u16 timeout )
2011-05-15 14:43:47 +04:00
{
2012-04-04 00:34:59 +04:00
dev_dbg ( & dev - > pdev - > dev , " wd: set timeout=%d. \n " , timeout ) ;
2012-08-16 20:39:42 +04:00
memcpy ( dev - > wd_data , mei_start_wd_params , MEI_WD_HDR_SIZE ) ;
memcpy ( dev - > wd_data + MEI_WD_HDR_SIZE , & timeout , sizeof ( u16 ) ) ;
2011-05-15 14:43:47 +04:00
}
/**
2012-07-04 20:24:51 +04:00
* mei_wd_host_init - connect to the watchdog client
2011-05-15 14:43:47 +04:00
*
* @ dev : the device structure
2013-04-04 20:05:05 +04:00
*
2012-04-04 00:34:58 +04:00
* returns - ENENT if wd client cannot be found
* - EIO if write has failed
2012-07-04 20:24:51 +04:00
* 0 on success
2011-05-15 14:43:47 +04:00
*/
2012-04-04 00:34:58 +04:00
int mei_wd_host_init ( struct mei_device * dev )
2011-05-15 14:43:47 +04:00
{
2013-01-09 01:07:22 +04:00
struct mei_cl * cl = & dev - > wd_cl ;
int i ;
int ret ;
mei_cl_init ( cl , dev ) ;
2011-05-15 14:43:47 +04:00
2012-08-16 20:39:42 +04:00
dev - > wd_timeout = MEI_WD_DEFAULT_TIMEOUT ;
2012-08-16 20:39:43 +04:00
dev - > wd_state = MEI_WD_IDLE ;
2011-05-15 14:43:47 +04:00
2013-01-09 01:07:22 +04:00
/* check for valid client id */
i = mei_me_cl_by_uuid ( dev , & mei_wd_guid ) ;
if ( i < 0 ) {
2012-04-04 00:34:58 +04:00
dev_info ( & dev - > pdev - > dev , " wd: failed to find the client \n " ) ;
return - ENOENT ;
}
2013-01-09 01:07:22 +04:00
cl - > me_client_id = dev - > me_clients [ i ] . client_id ;
ret = mei_cl_link ( cl , MEI_WD_HOST_CLIENT_ID ) ;
if ( ret < 0 ) {
dev_info ( & dev - > pdev - > dev , " wd: failed link client \n " ) ;
return - ENOENT ;
}
cl - > state = MEI_FILE_CONNECTING ;
if ( mei_hbm_cl_connect_req ( dev , cl ) ) {
2012-04-04 00:34:58 +04:00
dev_err ( & dev - > pdev - > dev , " wd: failed to connect to the client \n " ) ;
2013-01-09 01:07:22 +04:00
cl - > state = MEI_FILE_DISCONNECTED ;
cl - > host_client_id = 0 ;
2012-04-04 00:34:58 +04:00
return - EIO ;
2011-05-15 14:43:47 +04:00
}
2013-01-09 01:07:22 +04:00
cl - > timer_count = MEI_CONNECT_TIMEOUT ;
2011-09-07 10:03:07 +04:00
2012-04-04 00:34:58 +04:00
return 0 ;
2011-05-15 14:43:47 +04:00
}
/**
* mei_wd_send - sends watch dog message to fw .
*
* @ dev : the device structure
*
* returns 0 if success ,
* - EIO when message send fails
* - EINVAL when invalid message is to be sent
*/
int mei_wd_send ( struct mei_device * dev )
{
2012-12-25 21:06:10 +04:00
struct mei_msg_hdr hdr ;
2011-05-15 14:43:47 +04:00
2012-12-25 21:06:10 +04:00
hdr . host_addr = dev - > wd_cl . host_client_id ;
hdr . me_addr = dev - > wd_cl . me_client_id ;
hdr . msg_complete = 1 ;
hdr . reserved = 0 ;
2011-05-15 14:43:47 +04:00
2012-08-16 20:39:42 +04:00
if ( ! memcmp ( dev - > wd_data , mei_start_wd_params , MEI_WD_HDR_SIZE ) )
2012-12-25 21:06:10 +04:00
hdr . length = MEI_WD_START_MSG_SIZE ;
2012-08-16 20:39:42 +04:00
else if ( ! memcmp ( dev - > wd_data , mei_stop_wd_params , MEI_WD_HDR_SIZE ) )
2012-12-25 21:06:10 +04:00
hdr . length = MEI_WD_STOP_MSG_SIZE ;
2011-05-15 14:43:47 +04:00
else
return - EINVAL ;
2012-12-25 21:06:10 +04:00
return mei_write_message ( dev , & hdr , dev - > wd_data ) ;
2011-05-15 14:43:47 +04:00
}
2011-09-07 10:03:17 +04:00
/**
* mei_wd_stop - sends watchdog stop message to fw .
*
* @ dev : the device structure
* @ preserve : indicate if to keep the timeout value
*
* returns 0 if success ,
* - EIO when message send fails
* - EINVAL when invalid message is to be sent
*/
2012-08-16 20:39:43 +04:00
int mei_wd_stop ( struct mei_device * dev )
2011-05-15 14:43:47 +04:00
{
int ret ;
2012-08-16 20:39:43 +04:00
if ( dev - > wd_cl . state ! = MEI_FILE_CONNECTED | |
dev - > wd_state ! = MEI_WD_RUNNING )
2011-05-15 14:43:47 +04:00
return 0 ;
2012-08-16 20:39:42 +04:00
memcpy ( dev - > wd_data , mei_stop_wd_params , MEI_WD_STOP_MSG_SIZE ) ;
2012-08-16 20:39:43 +04:00
dev - > wd_state = MEI_WD_STOPPING ;
2011-05-15 14:43:47 +04:00
2013-01-09 01:07:14 +04:00
ret = mei_cl_flow_ctrl_creds ( & dev - > wd_cl ) ;
2011-05-15 14:43:47 +04:00
if ( ret < 0 )
goto out ;
2013-02-06 16:06:43 +04:00
if ( ret & & dev - > hbuf_is_ready ) {
2011-05-15 14:43:47 +04:00
ret = 0 ;
2013-02-06 16:06:43 +04:00
dev - > hbuf_is_ready = false ;
2011-05-15 14:43:47 +04:00
if ( ! mei_wd_send ( dev ) ) {
2013-01-09 01:07:14 +04:00
ret = mei_cl_flow_ctrl_reduce ( & dev - > wd_cl ) ;
2011-05-15 14:43:47 +04:00
if ( ret )
goto out ;
} else {
2012-04-04 00:34:59 +04:00
dev_err ( & dev - > pdev - > dev , " wd: send stop failed \n " ) ;
2011-05-15 14:43:47 +04:00
}
2011-05-25 18:28:22 +04:00
dev - > wd_pending = false ;
2011-05-15 14:43:47 +04:00
} else {
2011-05-25 18:28:22 +04:00
dev - > wd_pending = true ;
2011-05-15 14:43:47 +04:00
}
2012-08-16 20:39:43 +04:00
2011-05-15 14:43:47 +04:00
mutex_unlock ( & dev - > device_lock ) ;
ret = wait_event_interruptible_timeout ( dev - > wait_stop_wd ,
2012-08-16 20:39:43 +04:00
dev - > wd_state = = MEI_WD_IDLE ,
msecs_to_jiffies ( MEI_WD_STOP_TIMEOUT ) ) ;
2011-05-15 14:43:47 +04:00
mutex_lock ( & dev - > device_lock ) ;
2012-08-16 20:39:43 +04:00
if ( dev - > wd_state = = MEI_WD_IDLE ) {
2012-04-04 00:34:59 +04:00
dev_dbg ( & dev - > pdev - > dev , " wd: stop completed ret=%d. \n " , ret ) ;
2011-06-13 17:39:31 +04:00
ret = 0 ;
} else {
if ( ! ret )
ret = - ETIMEDOUT ;
dev_warn ( & dev - > pdev - > dev ,
2012-04-04 00:34:59 +04:00
" wd: stop failed to complete ret=%d. \n " , ret ) ;
2011-06-13 17:39:31 +04:00
}
2011-05-15 14:43:47 +04:00
out :
return ret ;
}
2011-09-07 10:03:10 +04:00
/*
* mei_wd_ops_start - wd start command from the watchdog core .
*
* @ wd_dev - watchdog device struct
*
* returns 0 if success , negative errno code for failure
*/
static int mei_wd_ops_start ( struct watchdog_device * wd_dev )
{
int err = - ENODEV ;
struct mei_device * dev ;
2012-08-16 20:39:44 +04:00
dev = watchdog_get_drvdata ( wd_dev ) ;
2011-09-07 10:03:10 +04:00
if ( ! dev )
return - ENODEV ;
mutex_lock ( & dev - > device_lock ) ;
2012-08-07 01:03:56 +04:00
if ( dev - > dev_state ! = MEI_DEV_ENABLED ) {
2012-04-04 00:34:59 +04:00
dev_dbg ( & dev - > pdev - > dev ,
2012-08-07 01:03:56 +04:00
" wd: dev_state != MEI_DEV_ENABLED dev_state = %s \n " ,
mei_dev_state_str ( dev - > dev_state ) ) ;
2011-09-07 10:03:10 +04:00
goto end_unlock ;
}
if ( dev - > wd_cl . state ! = MEI_FILE_CONNECTED ) {
2012-04-04 00:34:59 +04:00
dev_dbg ( & dev - > pdev - > dev ,
" MEI Driver is not connected to Watchdog Client \n " ) ;
2011-09-07 10:03:10 +04:00
goto end_unlock ;
}
2011-09-07 10:03:12 +04:00
mei_wd_set_start_timeout ( dev , dev - > wd_timeout ) ;
2011-09-07 10:03:10 +04:00
err = 0 ;
end_unlock :
mutex_unlock ( & dev - > device_lock ) ;
return err ;
}
/*
* mei_wd_ops_stop - wd stop command from the watchdog core .
*
* @ wd_dev - watchdog device struct
*
* returns 0 if success , negative errno code for failure
*/
static int mei_wd_ops_stop ( struct watchdog_device * wd_dev )
{
struct mei_device * dev ;
2012-08-16 20:39:44 +04:00
dev = watchdog_get_drvdata ( wd_dev ) ;
2011-09-07 10:03:10 +04:00
if ( ! dev )
return - ENODEV ;
mutex_lock ( & dev - > device_lock ) ;
2012-08-16 20:39:43 +04:00
mei_wd_stop ( dev ) ;
2011-09-07 10:03:10 +04:00
mutex_unlock ( & dev - > device_lock ) ;
return 0 ;
}
2011-09-07 10:03:11 +04:00
/*
* mei_wd_ops_ping - wd ping command from the watchdog core .
*
* @ wd_dev - watchdog device struct
*
* returns 0 if success , negative errno code for failure
*/
static int mei_wd_ops_ping ( struct watchdog_device * wd_dev )
{
int ret = 0 ;
struct mei_device * dev ;
2012-08-16 20:39:44 +04:00
dev = watchdog_get_drvdata ( wd_dev ) ;
2011-09-07 10:03:11 +04:00
if ( ! dev )
return - ENODEV ;
mutex_lock ( & dev - > device_lock ) ;
if ( dev - > wd_cl . state ! = MEI_FILE_CONNECTED ) {
2012-04-04 00:34:59 +04:00
dev_err ( & dev - > pdev - > dev , " wd: not connected. \n " ) ;
2011-09-07 10:03:11 +04:00
ret = - ENODEV ;
goto end ;
}
2012-08-16 20:39:43 +04:00
dev - > wd_state = MEI_WD_RUNNING ;
2011-09-07 10:03:11 +04:00
/* Check if we can send the ping to HW*/
2013-02-06 16:06:43 +04:00
if ( dev - > hbuf_is_ready & & mei_cl_flow_ctrl_creds ( & dev - > wd_cl ) > 0 ) {
2011-09-07 10:03:11 +04:00
2013-02-06 16:06:43 +04:00
dev - > hbuf_is_ready = false ;
2012-04-04 00:34:59 +04:00
dev_dbg ( & dev - > pdev - > dev , " wd: sending ping \n " ) ;
2011-09-07 10:03:11 +04:00
if ( mei_wd_send ( dev ) ) {
2012-04-04 00:34:59 +04:00
dev_err ( & dev - > pdev - > dev , " wd: send failed. \n " ) ;
2011-09-07 10:03:11 +04:00
ret = - EIO ;
goto end ;
}
2013-01-09 01:07:14 +04:00
if ( mei_cl_flow_ctrl_reduce ( & dev - > wd_cl ) ) {
2012-04-04 00:34:59 +04:00
dev_err ( & dev - > pdev - > dev ,
2013-01-09 01:07:14 +04:00
" wd: mei_cl_flow_ctrl_reduce() failed. \n " ) ;
2011-09-07 10:03:11 +04:00
ret = - EIO ;
goto end ;
}
} else {
dev - > wd_pending = true ;
}
end :
mutex_unlock ( & dev - > device_lock ) ;
return ret ;
}
2011-09-07 10:03:12 +04:00
/*
* mei_wd_ops_set_timeout - wd set timeout command from the watchdog core .
*
* @ wd_dev - watchdog device struct
* @ timeout - timeout value to set
*
* returns 0 if success , negative errno code for failure
*/
2013-04-14 00:41:25 +04:00
static int mei_wd_ops_set_timeout ( struct watchdog_device * wd_dev ,
unsigned int timeout )
2011-09-07 10:03:12 +04:00
{
struct mei_device * dev ;
2012-08-16 20:39:44 +04:00
dev = watchdog_get_drvdata ( wd_dev ) ;
2011-09-07 10:03:12 +04:00
if ( ! dev )
return - ENODEV ;
/* Check Timeout value */
2012-08-16 20:39:42 +04:00
if ( timeout < MEI_WD_MIN_TIMEOUT | | timeout > MEI_WD_MAX_TIMEOUT )
2011-09-07 10:03:12 +04:00
return - EINVAL ;
mutex_lock ( & dev - > device_lock ) ;
dev - > wd_timeout = timeout ;
2012-02-29 23:20:58 +04:00
wd_dev - > timeout = timeout ;
2011-09-07 10:03:12 +04:00
mei_wd_set_start_timeout ( dev , dev - > wd_timeout ) ;
mutex_unlock ( & dev - > device_lock ) ;
return 0 ;
}
2011-09-07 10:03:10 +04:00
/*
* Watchdog Device structs
*/
2011-10-23 20:30:39 +04:00
static const struct watchdog_ops wd_ops = {
2011-09-07 10:03:10 +04:00
. owner = THIS_MODULE ,
. start = mei_wd_ops_start ,
. stop = mei_wd_ops_stop ,
2011-09-07 10:03:11 +04:00
. ping = mei_wd_ops_ping ,
2011-09-07 10:03:12 +04:00
. set_timeout = mei_wd_ops_set_timeout ,
2011-09-07 10:03:10 +04:00
} ;
2011-10-23 20:30:39 +04:00
static const struct watchdog_info wd_info = {
2011-09-07 10:03:10 +04:00
. identity = INTEL_AMT_WATCHDOG_ID ,
2012-08-16 20:39:41 +04:00
. options = WDIOF_KEEPALIVEPING |
WDIOF_SETTIMEOUT |
WDIOF_ALARMONLY ,
2011-09-07 10:03:10 +04:00
} ;
2012-04-02 21:32:38 +04:00
static struct watchdog_device amt_wd_dev = {
2011-09-07 10:03:10 +04:00
. info = & wd_info ,
. ops = & wd_ops ,
2012-08-16 20:39:42 +04:00
. timeout = MEI_WD_DEFAULT_TIMEOUT ,
. min_timeout = MEI_WD_MIN_TIMEOUT ,
. max_timeout = MEI_WD_MAX_TIMEOUT ,
2011-09-07 10:03:10 +04:00
} ;
2012-08-16 20:39:44 +04:00
void mei_watchdog_register ( struct mei_device * dev )
2011-12-22 20:50:50 +04:00
{
if ( watchdog_register_device ( & amt_wd_dev ) ) {
2012-04-04 00:34:59 +04:00
dev_err ( & dev - > pdev - > dev ,
" wd: unable to register watchdog device. \n " ) ;
2012-08-16 20:39:44 +04:00
return ;
2011-12-22 20:50:50 +04:00
}
2012-08-16 20:39:44 +04:00
dev_dbg ( & dev - > pdev - > dev ,
" wd: successfully register watchdog interface. \n " ) ;
watchdog_set_drvdata ( & amt_wd_dev , dev ) ;
2011-12-22 20:50:50 +04:00
}
void mei_watchdog_unregister ( struct mei_device * dev )
{
2012-12-16 15:23:17 +04:00
if ( watchdog_get_drvdata ( & amt_wd_dev ) = = NULL )
2012-08-16 20:39:44 +04:00
return ;
watchdog_set_drvdata ( & amt_wd_dev , NULL ) ;
watchdog_unregister_device ( & amt_wd_dev ) ;
2011-12-22 20:50:50 +04:00
}