2011-05-15 13:43:44 +03:00
/*
*
* Intel Management Engine Interface ( Intel MEI ) Linux driver
2012-02-09 19:25:53 +02:00
* Copyright ( c ) 2003 - 2012 , Intel Corporation .
2011-05-15 13:43:44 +03: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 .
*
*/
2013-03-27 16:58:30 +02:00
# include <linux/export.h>
2011-05-15 13:43:44 +03:00
# include <linux/sched.h>
# include <linux/wait.h>
# include <linux/delay.h>
2012-12-25 19:06:03 +02:00
# include <linux/mei.h>
2011-05-15 13:43:44 +03:00
# include "mei_dev.h"
2013-03-11 18:27:03 +02:00
# include "hbm.h"
2013-01-08 23:07:14 +02:00
# include "client.h"
2011-05-15 13:43:44 +03:00
2012-08-07 00:03:56 +03:00
const char * mei_dev_state_str ( int state )
{
# define MEI_DEV_STATE(state) case MEI_DEV_##state: return #state
switch ( state ) {
MEI_DEV_STATE ( INITIALIZING ) ;
MEI_DEV_STATE ( INIT_CLIENTS ) ;
MEI_DEV_STATE ( ENABLED ) ;
2013-04-19 22:01:36 +03:00
MEI_DEV_STATE ( RESETTING ) ;
2012-08-07 00:03:56 +03:00
MEI_DEV_STATE ( DISABLED ) ;
MEI_DEV_STATE ( POWER_DOWN ) ;
MEI_DEV_STATE ( POWER_UP ) ;
default :
2013-05-21 23:13:12 +09:00
return " unknown " ;
2012-08-07 00:03:56 +03:00
}
# undef MEI_DEV_STATE
}
2014-09-29 16:31:33 +03:00
const char * mei_pg_state_str ( enum mei_pg_state state )
{
# define MEI_PG_STATE(state) case MEI_PG_##state: return #state
switch ( state ) {
MEI_PG_STATE ( OFF ) ;
MEI_PG_STATE ( ON ) ;
default :
return " unknown " ;
}
# undef MEI_PG_STATE
}
2014-11-19 17:01:38 +02:00
/**
* mei_fw_status2str - convert fw status registers to printable string
*
* @ fw_status : firmware status
* @ buf : string buffer at minimal size MEI_FW_STATUS_STR_SZ
* @ len : buffer len must be > = MEI_FW_STATUS_STR_SZ
*
* Return : number of bytes written or - EINVAL if buffer is to small
*/
ssize_t mei_fw_status2str ( struct mei_fw_status * fw_status ,
char * buf , size_t len )
{
ssize_t cnt = 0 ;
int i ;
buf [ 0 ] = ' \0 ' ;
if ( len < MEI_FW_STATUS_STR_SZ )
return - EINVAL ;
for ( i = 0 ; i < fw_status - > count ; i + + )
cnt + = scnprintf ( buf + cnt , len - cnt , " %08X " ,
fw_status - > status [ i ] ) ;
/* drop last space */
buf [ cnt ] = ' \0 ' ;
return cnt ;
}
EXPORT_SYMBOL_GPL ( mei_fw_status2str ) ;
2011-05-15 13:43:44 +03:00
2014-01-08 20:19:21 +02:00
/**
2014-09-29 16:31:49 +03:00
* mei_cancel_work - Cancel mei background jobs
2014-01-08 20:19:21 +02:00
*
* @ dev : the device structure
*/
2013-11-11 13:26:06 +02:00
void mei_cancel_work ( struct mei_device * dev )
{
cancel_work_sync ( & dev - > init_work ) ;
2014-01-08 20:19:21 +02:00
cancel_work_sync ( & dev - > reset_work ) ;
2013-11-11 13:26:06 +02:00
cancel_delayed_work ( & dev - > timer_work ) ;
}
EXPORT_SYMBOL_GPL ( mei_cancel_work ) ;
2011-05-15 13:43:44 +03:00
/**
* mei_reset - resets host and fw .
*
* @ dev : the device structure
2014-09-29 16:31:50 +03:00
*
* Return : 0 on success or < 0 if the reset hasn ' t succeeded
2011-05-15 13:43:44 +03:00
*/
2014-01-12 00:36:09 +02:00
int mei_reset ( struct mei_device * dev )
2011-05-15 13:43:44 +03:00
{
2014-01-12 00:36:09 +02:00
enum mei_dev_state state = dev - > dev_state ;
bool interrupts_enabled ;
2013-06-23 10:42:49 +03:00
int ret ;
2011-05-15 13:43:44 +03:00
2014-01-12 00:36:09 +02:00
if ( state ! = MEI_DEV_INITIALIZING & &
state ! = MEI_DEV_DISABLED & &
state ! = MEI_DEV_POWER_DOWN & &
2014-03-31 17:59:23 +03:00
state ! = MEI_DEV_POWER_UP ) {
2014-11-19 17:01:38 +02:00
char fw_sts_str [ MEI_FW_STATUS_STR_SZ ] ;
2014-09-29 16:31:37 +03:00
2014-11-19 17:01:38 +02:00
mei_fw_status_str ( dev , fw_sts_str , MEI_FW_STATUS_STR_SZ ) ;
dev_warn ( dev - > dev , " unexpected reset: dev_state = %s fw status = %s \n " ,
mei_dev_state_str ( state ) , fw_sts_str ) ;
2014-03-31 17:59:23 +03:00
}
2013-10-21 22:05:43 +03:00
2014-01-08 20:19:22 +02:00
/* we're already in reset, cancel the init timer
* if the reset was called due the hbm protocol error
* we need to call it before hw start
* so the hbm watchdog won ' t kick in
*/
mei_hbm_idle ( dev ) ;
2014-01-12 00:36:09 +02:00
/* enter reset flow */
interrupts_enabled = state ! = MEI_DEV_POWER_DOWN ;
dev - > dev_state = MEI_DEV_RESETTING ;
2011-05-15 13:43:44 +03:00
2014-01-12 00:36:10 +02:00
dev - > reset_count + + ;
if ( dev - > reset_count > MEI_MAX_CONSEC_RESET ) {
2014-09-29 16:31:42 +03:00
dev_err ( dev - > dev , " reset: reached maximal consecutive resets: disabling the device \n " ) ;
2014-01-12 00:36:10 +02:00
dev - > dev_state = MEI_DEV_DISABLED ;
return - ENODEV ;
}
2014-01-12 00:36:09 +02:00
ret = mei_hw_reset ( dev , interrupts_enabled ) ;
/* fall through and remove the sw state even if hw reset has failed */
2011-05-15 13:43:44 +03:00
2014-01-12 00:36:09 +02:00
/* no need to clean up software state in case of power up */
if ( state ! = MEI_DEV_INITIALIZING & &
state ! = MEI_DEV_POWER_UP ) {
2011-05-15 13:43:44 +03:00
2013-07-25 20:15:53 +03:00
/* remove all waiting requests */
mei_cl_all_write_clear ( dev ) ;
2013-02-06 14:06:44 +02:00
mei_cl_all_disconnect ( dev ) ;
2014-01-12 00:36:09 +02:00
/* wake up all readers and writers so they can be interrupted */
2013-07-25 20:15:53 +03:00
mei_cl_all_wakeup ( dev ) ;
2011-05-15 13:43:44 +03:00
/* remove entry if already in list */
2014-09-29 16:31:42 +03:00
dev_dbg ( dev - > dev , " remove iamthif and wd from the file list. \n " ) ;
2013-01-08 23:07:14 +02:00
mei_cl_unlink ( & dev - > wd_cl ) ;
mei_cl_unlink ( & dev - > iamthif_cl ) ;
2012-11-01 21:17:15 +02:00
mei_amthif_reset_params ( dev ) ;
2011-05-15 13:43:44 +03:00
}
2014-05-07 16:51:28 +03:00
mei_hbm_reset ( dev ) ;
2013-09-02 13:29:47 +03:00
2011-05-15 13:43:44 +03:00
dev - > rd_msg_hdr = 0 ;
2011-05-25 17:28:22 +03:00
dev - > wd_pending = false ;
2011-05-15 13:43:44 +03:00
2014-01-12 00:36:09 +02:00
if ( ret ) {
2014-09-29 16:31:42 +03:00
dev_err ( dev - > dev , " hw_reset failed ret = %d \n " , ret ) ;
2014-01-12 00:36:09 +02:00
return ret ;
}
if ( state = = MEI_DEV_POWER_DOWN ) {
2014-09-29 16:31:42 +03:00
dev_dbg ( dev - > dev , " powering down: end of reset \n " ) ;
2014-01-12 00:36:09 +02:00
dev - > dev_state = MEI_DEV_DISABLED ;
return 0 ;
2013-03-11 18:27:03 +02:00
}
2013-06-23 22:49:04 +03:00
ret = mei_hw_start ( dev ) ;
if ( ret ) {
2014-09-29 16:31:42 +03:00
dev_err ( dev - > dev , " hw_start failed ret = %d \n " , ret ) ;
2014-01-12 00:36:09 +02:00
return ret ;
2013-06-23 22:49:04 +03:00
}
2013-03-11 18:27:03 +02:00
2014-09-29 16:31:42 +03:00
dev_dbg ( dev - > dev , " link is established start sending messages. \n " ) ;
2013-03-11 18:27:03 +02:00
dev - > dev_state = MEI_DEV_INIT_CLIENTS ;
2014-01-08 20:19:21 +02:00
ret = mei_hbm_start_req ( dev ) ;
if ( ret ) {
2014-09-29 16:31:42 +03:00
dev_err ( dev - > dev , " hbm_start failed ret = %d \n " , ret ) ;
2014-01-14 23:10:10 +02:00
dev - > dev_state = MEI_DEV_RESETTING ;
2014-01-12 00:36:09 +02:00
return ret ;
2014-01-08 20:19:21 +02:00
}
2014-01-12 00:36:09 +02:00
return 0 ;
2011-05-15 13:43:44 +03:00
}
2013-03-27 16:58:30 +02:00
EXPORT_SYMBOL_GPL ( mei_reset ) ;
2011-05-15 13:43:44 +03:00
2014-01-12 00:36:09 +02:00
/**
* mei_start - initializes host and fw to start work .
*
* @ dev : the device structure
*
2014-09-29 16:31:49 +03:00
* Return : 0 on success , < 0 on failure .
2014-01-12 00:36:09 +02:00
*/
int mei_start ( struct mei_device * dev )
{
2014-01-14 23:10:10 +02:00
int ret ;
2014-09-29 16:31:37 +03:00
2014-01-12 00:36:09 +02:00
mutex_lock ( & dev - > device_lock ) ;
/* acknowledge interrupt and stop interrupts */
mei_clear_interrupts ( dev ) ;
mei_hw_config ( dev ) ;
2014-09-29 16:31:42 +03:00
dev_dbg ( dev - > dev , " reset in start the mei device. \n " ) ;
2014-01-12 00:36:09 +02:00
2014-01-12 00:36:10 +02:00
dev - > reset_count = 0 ;
2014-01-14 23:10:10 +02:00
do {
dev - > dev_state = MEI_DEV_INITIALIZING ;
ret = mei_reset ( dev ) ;
if ( ret = = - ENODEV | | dev - > dev_state = = MEI_DEV_DISABLED ) {
2014-09-29 16:31:42 +03:00
dev_err ( dev - > dev , " reset failed ret = %d " , ret ) ;
2014-01-14 23:10:10 +02:00
goto err ;
}
} while ( ret ) ;
2014-01-12 00:36:09 +02:00
2014-01-14 23:10:10 +02:00
/* we cannot start the device w/o hbm start message completed */
2014-01-12 00:36:09 +02:00
if ( dev - > dev_state = = MEI_DEV_DISABLED ) {
2014-09-29 16:31:42 +03:00
dev_err ( dev - > dev , " reset failed " ) ;
2014-01-12 00:36:09 +02:00
goto err ;
}
if ( mei_hbm_start_wait ( dev ) ) {
2014-09-29 16:31:42 +03:00
dev_err ( dev - > dev , " HBM haven't started " ) ;
2014-01-12 00:36:09 +02:00
goto err ;
}
if ( ! mei_host_is_ready ( dev ) ) {
2014-09-29 16:31:42 +03:00
dev_err ( dev - > dev , " host is not ready. \n " ) ;
2014-01-12 00:36:09 +02:00
goto err ;
}
if ( ! mei_hw_is_ready ( dev ) ) {
2014-09-29 16:31:42 +03:00
dev_err ( dev - > dev , " ME is not ready. \n " ) ;
2014-01-12 00:36:09 +02:00
goto err ;
}
if ( ! mei_hbm_version_is_supported ( dev ) ) {
2014-09-29 16:31:42 +03:00
dev_dbg ( dev - > dev , " MEI start failed. \n " ) ;
2014-01-12 00:36:09 +02:00
goto err ;
}
2014-09-29 16:31:42 +03:00
dev_dbg ( dev - > dev , " link layer has been established. \n " ) ;
2014-01-12 00:36:09 +02:00
mutex_unlock ( & dev - > device_lock ) ;
return 0 ;
err :
2014-09-29 16:31:42 +03:00
dev_err ( dev - > dev , " link layer initialization failed. \n " ) ;
2014-01-12 00:36:09 +02:00
dev - > dev_state = MEI_DEV_DISABLED ;
mutex_unlock ( & dev - > device_lock ) ;
return - ENODEV ;
}
EXPORT_SYMBOL_GPL ( mei_start ) ;
/**
* mei_restart - restart device after suspend
*
* @ dev : the device structure
*
2014-09-29 16:31:49 +03:00
* Return : 0 on success or - ENODEV if the restart hasn ' t succeeded
2014-01-12 00:36:09 +02:00
*/
int mei_restart ( struct mei_device * dev )
{
int err ;
mutex_lock ( & dev - > device_lock ) ;
mei_clear_interrupts ( dev ) ;
dev - > dev_state = MEI_DEV_POWER_UP ;
2014-01-12 00:36:10 +02:00
dev - > reset_count = 0 ;
2014-01-12 00:36:09 +02:00
err = mei_reset ( dev ) ;
mutex_unlock ( & dev - > device_lock ) ;
2014-01-14 23:10:10 +02:00
if ( err = = - ENODEV | | dev - > dev_state = = MEI_DEV_DISABLED ) {
2014-09-29 16:31:42 +03:00
dev_err ( dev - > dev , " device disabled = %d \n " , err ) ;
2014-01-12 00:36:09 +02:00
return - ENODEV ;
2014-01-14 23:10:10 +02:00
}
/* try to start again */
if ( err )
schedule_work ( & dev - > reset_work ) ;
2014-01-12 00:36:09 +02:00
return 0 ;
}
EXPORT_SYMBOL_GPL ( mei_restart ) ;
2014-01-08 20:19:21 +02:00
static void mei_reset_work ( struct work_struct * work )
{
struct mei_device * dev =
container_of ( work , struct mei_device , reset_work ) ;
2014-01-14 23:10:10 +02:00
int ret ;
2014-01-08 20:19:21 +02:00
mutex_lock ( & dev - > device_lock ) ;
2014-01-14 23:10:10 +02:00
ret = mei_reset ( dev ) ;
2014-01-08 20:19:21 +02:00
mutex_unlock ( & dev - > device_lock ) ;
2014-01-12 00:36:09 +02:00
2014-01-14 23:10:10 +02:00
if ( dev - > dev_state = = MEI_DEV_DISABLED ) {
2014-09-29 16:31:42 +03:00
dev_err ( dev - > dev , " device disabled = %d \n " , ret ) ;
2014-01-14 23:10:10 +02:00
return ;
}
/* retry reset in case of failure */
if ( ret )
schedule_work ( & dev - > reset_work ) ;
2014-01-08 20:19:21 +02:00
}
2013-03-10 13:56:08 +02:00
void mei_stop ( struct mei_device * dev )
{
2014-09-29 16:31:42 +03:00
dev_dbg ( dev - > dev , " stopping the device. \n " ) ;
2013-03-10 13:56:08 +02:00
2013-11-11 13:26:06 +02:00
mei_cancel_work ( dev ) ;
2013-06-10 10:10:25 +03:00
2013-11-11 13:26:06 +02:00
mei_nfc_host_exit ( dev ) ;
2013-03-10 13:56:08 +02:00
2014-02-17 15:13:19 +02:00
mei_cl_bus_remove_devices ( dev ) ;
2013-11-11 13:26:06 +02:00
mutex_lock ( & dev - > device_lock ) ;
2013-03-10 13:56:08 +02:00
mei_wd_stop ( dev ) ;
dev - > dev_state = MEI_DEV_POWER_DOWN ;
2014-01-12 00:36:09 +02:00
mei_reset ( dev ) ;
2013-03-10 13:56:08 +02:00
mutex_unlock ( & dev - > device_lock ) ;
2013-03-27 16:58:26 +02:00
mei_watchdog_unregister ( dev ) ;
2013-03-10 13:56:08 +02:00
}
2013-03-27 16:58:30 +02:00
EXPORT_SYMBOL_GPL ( mei_stop ) ;
2011-05-15 13:43:44 +03:00
2014-03-18 22:52:01 +02:00
/**
* mei_write_is_idle - check if the write queues are idle
*
* @ dev : the device structure
*
2014-09-29 16:31:49 +03:00
* Return : true of there is no pending write
2014-03-18 22:52:01 +02:00
*/
bool mei_write_is_idle ( struct mei_device * dev )
{
bool idle = ( dev - > dev_state = = MEI_DEV_ENABLED & &
list_empty ( & dev - > ctrl_wr_list . list ) & &
list_empty ( & dev - > write_list . list ) ) ;
2012-11-18 15:13:20 +02:00
2014-09-29 16:31:42 +03:00
dev_dbg ( dev - > dev , " write pg: is idle[%d] state=%s ctrl=%d write=%d \n " ,
2014-03-18 22:52:01 +02:00
idle ,
mei_dev_state_str ( dev - > dev_state ) ,
list_empty ( & dev - > ctrl_wr_list . list ) ,
list_empty ( & dev - > write_list . list ) ) ;
return idle ;
}
EXPORT_SYMBOL_GPL ( mei_write_is_idle ) ;
2011-05-15 13:43:44 +03:00
2014-09-29 16:31:41 +03:00
/**
* mei_device_init - - initialize mei_device structure
*
* @ dev : the mei device
* @ device : the device structure
* @ hw_ops : hw operations
*/
void mei_device_init ( struct mei_device * dev ,
struct device * device ,
const struct mei_hw_ops * hw_ops )
2014-01-08 20:19:21 +02:00
{
/* setup our list array */
INIT_LIST_HEAD ( & dev - > file_list ) ;
INIT_LIST_HEAD ( & dev - > device_list ) ;
2014-08-21 14:29:13 +03:00
INIT_LIST_HEAD ( & dev - > me_clients ) ;
2014-01-08 20:19:21 +02:00
mutex_init ( & dev - > device_lock ) ;
2015-02-10 10:39:31 +02:00
init_rwsem ( & dev - > me_clients_rwsem ) ;
2014-01-08 20:19:21 +02:00
init_waitqueue_head ( & dev - > wait_hw_ready ) ;
2014-03-18 22:51:55 +02:00
init_waitqueue_head ( & dev - > wait_pg ) ;
2014-08-21 14:29:19 +03:00
init_waitqueue_head ( & dev - > wait_hbm_start ) ;
2014-01-08 20:19:21 +02:00
init_waitqueue_head ( & dev - > wait_stop_wd ) ;
dev - > dev_state = MEI_DEV_INITIALIZING ;
2014-01-12 00:36:10 +02:00
dev - > reset_count = 0 ;
2014-01-08 20:19:21 +02:00
mei_io_list_init ( & dev - > read_list ) ;
mei_io_list_init ( & dev - > write_list ) ;
mei_io_list_init ( & dev - > write_waiting_list ) ;
mei_io_list_init ( & dev - > ctrl_wr_list ) ;
mei_io_list_init ( & dev - > ctrl_rd_list ) ;
INIT_DELAYED_WORK ( & dev - > timer_work , mei_timer ) ;
INIT_WORK ( & dev - > init_work , mei_host_client_init ) ;
INIT_WORK ( & dev - > reset_work , mei_reset_work ) ;
INIT_LIST_HEAD ( & dev - > wd_cl . link ) ;
INIT_LIST_HEAD ( & dev - > iamthif_cl . link ) ;
mei_io_list_init ( & dev - > amthif_cmd_list ) ;
mei_io_list_init ( & dev - > amthif_rd_complete_list ) ;
bitmap_zero ( dev - > host_clients_map , MEI_CLIENTS_MAX ) ;
dev - > open_handle_count = 0 ;
/*
* Reserving the first client ID
* 0 : Reserved for MEI Bus Message communications
*/
bitmap_set ( dev - > host_clients_map , 0 , 1 ) ;
2014-03-18 22:51:59 +02:00
dev - > pg_event = MEI_PG_EVENT_IDLE ;
2014-09-29 16:31:41 +03:00
dev - > ops = hw_ops ;
dev - > dev = device ;
2014-01-08 20:19:21 +02:00
}
EXPORT_SYMBOL_GPL ( mei_device_init ) ;