2011-05-15 14:43:44 +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:44 +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 .
*
*/
2013-03-27 18:58:30 +04:00
# include <linux/export.h>
2011-05-15 14:43:44 +04:00
# include <linux/sched.h>
# include <linux/wait.h>
# include <linux/delay.h>
2012-12-25 21:06:03 +04:00
# include <linux/mei.h>
2011-05-15 14:43:44 +04:00
# include "mei_dev.h"
2013-03-11 20:27:03 +04:00
# include "hbm.h"
2013-01-09 01:07:14 +04:00
# include "client.h"
2011-05-15 14:43:44 +04:00
2012-08-07 01:03:56 +04: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 23:01:36 +04:00
MEI_DEV_STATE ( RESETTING ) ;
2012-08-07 01:03:56 +04:00
MEI_DEV_STATE ( DISABLED ) ;
MEI_DEV_STATE ( POWER_DOWN ) ;
MEI_DEV_STATE ( POWER_UP ) ;
default :
2013-05-21 18:13:12 +04:00
return " unknown " ;
2012-08-07 01:03:56 +04:00
}
# undef MEI_DEV_STATE
}
2014-09-29 17:31:33 +04: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 18:01:38 +03: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 14:43:44 +04:00
2014-01-08 22:19:21 +04:00
/**
2014-09-29 17:31:49 +04:00
* mei_cancel_work - Cancel mei background jobs
2014-01-08 22:19:21 +04:00
*
* @ dev : the device structure
*/
2013-11-11 15:26:06 +04:00
void mei_cancel_work ( struct mei_device * dev )
{
2014-01-08 22:19:21 +04:00
cancel_work_sync ( & dev - > reset_work ) ;
2016-02-08 00:35:41 +03:00
cancel_work_sync ( & dev - > bus_rescan_work ) ;
2013-11-11 15:26:06 +04:00
2016-09-25 13:25:31 +03:00
cancel_delayed_work_sync ( & dev - > timer_work ) ;
2013-11-11 15:26:06 +04:00
}
EXPORT_SYMBOL_GPL ( mei_cancel_work ) ;
2011-05-15 14:43:44 +04:00
/**
* mei_reset - resets host and fw .
*
* @ dev : the device structure
2014-09-29 17:31:50 +04:00
*
* Return : 0 on success or < 0 if the reset hasn ' t succeeded
2011-05-15 14:43:44 +04:00
*/
2014-01-12 02:36:09 +04:00
int mei_reset ( struct mei_device * dev )
2011-05-15 14:43:44 +04:00
{
2014-01-12 02:36:09 +04:00
enum mei_dev_state state = dev - > dev_state ;
bool interrupts_enabled ;
2013-06-23 11:42:49 +04:00
int ret ;
2011-05-15 14:43:44 +04:00
2014-01-12 02:36:09 +04:00
if ( state ! = MEI_DEV_INITIALIZING & &
state ! = MEI_DEV_DISABLED & &
state ! = MEI_DEV_POWER_DOWN & &
2014-03-31 18:59:23 +04:00
state ! = MEI_DEV_POWER_UP ) {
2014-11-19 18:01:38 +03:00
char fw_sts_str [ MEI_FW_STATUS_STR_SZ ] ;
2014-09-29 17:31:37 +04:00
2014-11-19 18:01:38 +03: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 18:59:23 +04:00
}
2013-10-21 23:05:43 +04:00
2016-12-04 16:22:58 +03:00
mei_clear_interrupts ( dev ) ;
2014-01-08 22:19:22 +04: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 02:36:09 +04:00
/* enter reset flow */
interrupts_enabled = state ! = MEI_DEV_POWER_DOWN ;
dev - > dev_state = MEI_DEV_RESETTING ;
2011-05-15 14:43:44 +04:00
2014-01-12 02:36:10 +04:00
dev - > reset_count + + ;
if ( dev - > reset_count > MEI_MAX_CONSEC_RESET ) {
2014-09-29 17:31:42 +04:00
dev_err ( dev - > dev , " reset: reached maximal consecutive resets: disabling the device \n " ) ;
2014-01-12 02:36:10 +04:00
dev - > dev_state = MEI_DEV_DISABLED ;
return - ENODEV ;
}
2014-01-12 02:36:09 +04:00
ret = mei_hw_reset ( dev , interrupts_enabled ) ;
/* fall through and remove the sw state even if hw reset has failed */
2011-05-15 14:43:44 +04:00
2014-01-12 02:36:09 +04:00
/* no need to clean up software state in case of power up */
2017-03-20 16:04:03 +03:00
if ( state ! = MEI_DEV_INITIALIZING & & state ! = MEI_DEV_POWER_UP )
2013-02-06 16:06:44 +04:00
mei_cl_all_disconnect ( dev ) ;
2014-05-07 17:51:28 +04:00
mei_hbm_reset ( dev ) ;
2013-09-02 14:29:47 +04:00
2011-05-15 14:43:44 +04:00
dev - > rd_msg_hdr = 0 ;
2014-01-12 02:36:09 +04:00
if ( ret ) {
2014-09-29 17:31:42 +04:00
dev_err ( dev - > dev , " hw_reset failed ret = %d \n " , ret ) ;
2014-01-12 02:36:09 +04:00
return ret ;
}
if ( state = = MEI_DEV_POWER_DOWN ) {
2014-09-29 17:31:42 +04:00
dev_dbg ( dev - > dev , " powering down: end of reset \n " ) ;
2014-01-12 02:36:09 +04:00
dev - > dev_state = MEI_DEV_DISABLED ;
return 0 ;
2013-03-11 20:27:03 +04:00
}
2013-06-23 23:49:04 +04:00
ret = mei_hw_start ( dev ) ;
if ( ret ) {
2014-09-29 17:31:42 +04:00
dev_err ( dev - > dev , " hw_start failed ret = %d \n " , ret ) ;
2014-01-12 02:36:09 +04:00
return ret ;
2013-06-23 23:49:04 +04:00
}
2013-03-11 20:27:03 +04:00
2014-09-29 17:31:42 +04:00
dev_dbg ( dev - > dev , " link is established start sending messages. \n " ) ;
2013-03-11 20:27:03 +04:00
dev - > dev_state = MEI_DEV_INIT_CLIENTS ;
2014-01-08 22:19:21 +04:00
ret = mei_hbm_start_req ( dev ) ;
if ( ret ) {
2014-09-29 17:31:42 +04:00
dev_err ( dev - > dev , " hbm_start failed ret = %d \n " , ret ) ;
2014-01-15 01:10:10 +04:00
dev - > dev_state = MEI_DEV_RESETTING ;
2014-01-12 02:36:09 +04:00
return ret ;
2014-01-08 22:19:21 +04:00
}
2014-01-12 02:36:09 +04:00
return 0 ;
2011-05-15 14:43:44 +04:00
}
2013-03-27 18:58:30 +04:00
EXPORT_SYMBOL_GPL ( mei_reset ) ;
2011-05-15 14:43:44 +04:00
2014-01-12 02:36:09 +04:00
/**
* mei_start - initializes host and fw to start work .
*
* @ dev : the device structure
*
2014-09-29 17:31:49 +04:00
* Return : 0 on success , < 0 on failure .
2014-01-12 02:36:09 +04:00
*/
int mei_start ( struct mei_device * dev )
{
2014-01-15 01:10:10 +04:00
int ret ;
2014-09-29 17:31:37 +04:00
2014-01-12 02:36:09 +04:00
mutex_lock ( & dev - > device_lock ) ;
/* acknowledge interrupt and stop interrupts */
mei_clear_interrupts ( dev ) ;
mei_hw_config ( dev ) ;
2014-09-29 17:31:42 +04:00
dev_dbg ( dev - > dev , " reset in start the mei device. \n " ) ;
2014-01-12 02:36:09 +04:00
2014-01-12 02:36:10 +04:00
dev - > reset_count = 0 ;
2014-01-15 01:10:10 +04:00
do {
dev - > dev_state = MEI_DEV_INITIALIZING ;
ret = mei_reset ( dev ) ;
if ( ret = = - ENODEV | | dev - > dev_state = = MEI_DEV_DISABLED ) {
2014-09-29 17:31:42 +04:00
dev_err ( dev - > dev , " reset failed ret = %d " , ret ) ;
2014-01-15 01:10:10 +04:00
goto err ;
}
} while ( ret ) ;
2014-01-12 02:36:09 +04:00
if ( mei_hbm_start_wait ( dev ) ) {
2014-09-29 17:31:42 +04:00
dev_err ( dev - > dev , " HBM haven't started " ) ;
2014-01-12 02:36:09 +04:00
goto err ;
}
if ( ! mei_host_is_ready ( dev ) ) {
2014-09-29 17:31:42 +04:00
dev_err ( dev - > dev , " host is not ready. \n " ) ;
2014-01-12 02:36:09 +04:00
goto err ;
}
if ( ! mei_hw_is_ready ( dev ) ) {
2014-09-29 17:31:42 +04:00
dev_err ( dev - > dev , " ME is not ready. \n " ) ;
2014-01-12 02:36:09 +04:00
goto err ;
}
if ( ! mei_hbm_version_is_supported ( dev ) ) {
2014-09-29 17:31:42 +04:00
dev_dbg ( dev - > dev , " MEI start failed. \n " ) ;
2014-01-12 02:36:09 +04:00
goto err ;
}
2014-09-29 17:31:42 +04:00
dev_dbg ( dev - > dev , " link layer has been established. \n " ) ;
2014-01-12 02:36:09 +04:00
mutex_unlock ( & dev - > device_lock ) ;
return 0 ;
err :
2014-09-29 17:31:42 +04:00
dev_err ( dev - > dev , " link layer initialization failed. \n " ) ;
2014-01-12 02:36:09 +04: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 17:31:49 +04:00
* Return : 0 on success or - ENODEV if the restart hasn ' t succeeded
2014-01-12 02:36:09 +04:00
*/
int mei_restart ( struct mei_device * dev )
{
int err ;
mutex_lock ( & dev - > device_lock ) ;
dev - > dev_state = MEI_DEV_POWER_UP ;
2014-01-12 02:36:10 +04:00
dev - > reset_count = 0 ;
2014-01-12 02:36:09 +04:00
err = mei_reset ( dev ) ;
mutex_unlock ( & dev - > device_lock ) ;
2014-01-15 01:10:10 +04:00
if ( err = = - ENODEV | | dev - > dev_state = = MEI_DEV_DISABLED ) {
2014-09-29 17:31:42 +04:00
dev_err ( dev - > dev , " device disabled = %d \n " , err ) ;
2014-01-12 02:36:09 +04:00
return - ENODEV ;
2014-01-15 01:10:10 +04:00
}
/* try to start again */
if ( err )
schedule_work ( & dev - > reset_work ) ;
2014-01-12 02:36:09 +04:00
return 0 ;
}
EXPORT_SYMBOL_GPL ( mei_restart ) ;
2014-01-08 22:19:21 +04:00
static void mei_reset_work ( struct work_struct * work )
{
struct mei_device * dev =
container_of ( work , struct mei_device , reset_work ) ;
2014-01-15 01:10:10 +04:00
int ret ;
2014-01-08 22:19:21 +04:00
2017-03-05 22:40:41 +03:00
mei_clear_interrupts ( dev ) ;
mei_synchronize_irq ( dev ) ;
2014-01-08 22:19:21 +04:00
mutex_lock ( & dev - > device_lock ) ;
2014-01-15 01:10:10 +04:00
ret = mei_reset ( dev ) ;
2014-01-08 22:19:21 +04:00
mutex_unlock ( & dev - > device_lock ) ;
2014-01-12 02:36:09 +04:00
2014-01-15 01:10:10 +04:00
if ( dev - > dev_state = = MEI_DEV_DISABLED ) {
2014-09-29 17:31:42 +04:00
dev_err ( dev - > dev , " device disabled = %d \n " , ret ) ;
2014-01-15 01:10:10 +04:00
return ;
}
/* retry reset in case of failure */
if ( ret )
schedule_work ( & dev - > reset_work ) ;
2014-01-08 22:19:21 +04:00
}
2013-03-10 15:56:08 +04:00
void mei_stop ( struct mei_device * dev )
{
2014-09-29 17:31:42 +04:00
dev_dbg ( dev - > dev , " stopping the device. \n " ) ;
2013-03-10 15:56:08 +04:00
2017-12-12 14:27:06 +03:00
mutex_lock ( & dev - > device_lock ) ;
dev - > dev_state = MEI_DEV_POWER_DOWN ;
mutex_unlock ( & dev - > device_lock ) ;
2015-07-23 15:08:47 +03:00
mei_cl_bus_remove_devices ( dev ) ;
2013-03-10 15:56:08 +04:00
2015-10-13 15:02:37 +03:00
mei_cancel_work ( dev ) ;
2017-03-05 22:40:41 +03:00
mei_clear_interrupts ( dev ) ;
mei_synchronize_irq ( dev ) ;
2013-11-11 15:26:06 +04:00
mutex_lock ( & dev - > device_lock ) ;
2013-03-10 15:56:08 +04:00
2014-01-12 02:36:09 +04:00
mei_reset ( dev ) ;
2015-02-10 11:36:36 +03:00
/* move device to disabled state unconditionally */
dev - > dev_state = MEI_DEV_DISABLED ;
2013-03-10 15:56:08 +04:00
mutex_unlock ( & dev - > device_lock ) ;
}
2013-03-27 18:58:30 +04:00
EXPORT_SYMBOL_GPL ( mei_stop ) ;
2011-05-15 14:43:44 +04:00
2014-03-19 00:52:01 +04:00
/**
* mei_write_is_idle - check if the write queues are idle
*
* @ dev : the device structure
*
2014-09-29 17:31:49 +04:00
* Return : true of there is no pending write
2014-03-19 00:52:01 +04:00
*/
bool mei_write_is_idle ( struct mei_device * dev )
{
bool idle = ( dev - > dev_state = = MEI_DEV_ENABLED & &
2017-01-27 17:32:45 +03:00
list_empty ( & dev - > ctrl_wr_list ) & &
list_empty ( & dev - > write_list ) & &
list_empty ( & dev - > write_waiting_list ) ) ;
2012-11-18 17:13:20 +04:00
2015-05-07 15:54:00 +03:00
dev_dbg ( dev - > dev , " write pg: is idle[%d] state=%s ctrl=%01d write=%01d wwait=%01d \n " ,
2014-03-19 00:52:01 +04:00
idle ,
mei_dev_state_str ( dev - > dev_state ) ,
2017-01-27 17:32:45 +03:00
list_empty ( & dev - > ctrl_wr_list ) ,
list_empty ( & dev - > write_list ) ,
list_empty ( & dev - > write_waiting_list ) ) ;
2014-03-19 00:52:01 +04:00
return idle ;
}
EXPORT_SYMBOL_GPL ( mei_write_is_idle ) ;
2011-05-15 14:43:44 +04:00
2014-09-29 17:31:41 +04: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 22:19:21 +04:00
{
/* setup our list array */
INIT_LIST_HEAD ( & dev - > file_list ) ;
INIT_LIST_HEAD ( & dev - > device_list ) ;
2014-08-21 15:29:13 +04:00
INIT_LIST_HEAD ( & dev - > me_clients ) ;
2014-01-08 22:19:21 +04:00
mutex_init ( & dev - > device_lock ) ;
2015-02-10 11:39:31 +03:00
init_rwsem ( & dev - > me_clients_rwsem ) ;
2015-07-23 15:08:42 +03:00
mutex_init ( & dev - > cl_bus_lock ) ;
2014-01-08 22:19:21 +04:00
init_waitqueue_head ( & dev - > wait_hw_ready ) ;
2014-03-19 00:51:55 +04:00
init_waitqueue_head ( & dev - > wait_pg ) ;
2014-08-21 15:29:19 +04:00
init_waitqueue_head ( & dev - > wait_hbm_start ) ;
2014-01-08 22:19:21 +04:00
dev - > dev_state = MEI_DEV_INITIALIZING ;
2014-01-12 02:36:10 +04:00
dev - > reset_count = 0 ;
2014-01-08 22:19:21 +04:00
2017-01-27 17:32:45 +03:00
INIT_LIST_HEAD ( & dev - > write_list ) ;
INIT_LIST_HEAD ( & dev - > write_waiting_list ) ;
INIT_LIST_HEAD ( & dev - > ctrl_wr_list ) ;
INIT_LIST_HEAD ( & dev - > ctrl_rd_list ) ;
2018-02-25 21:07:05 +03:00
dev - > tx_queue_limit = MEI_TX_QUEUE_LIMIT_DEFAULT ;
2014-01-08 22:19:21 +04:00
INIT_DELAYED_WORK ( & dev - > timer_work , mei_timer ) ;
INIT_WORK ( & dev - > reset_work , mei_reset_work ) ;
2016-02-08 00:35:41 +03:00
INIT_WORK ( & dev - > bus_rescan_work , mei_cl_bus_rescan_work ) ;
2014-01-08 22:19:21 +04:00
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-19 00:51:59 +04:00
dev - > pg_event = MEI_PG_EVENT_IDLE ;
2014-09-29 17:31:41 +04:00
dev - > ops = hw_ops ;
dev - > dev = device ;
2014-01-08 22:19:21 +04:00
}
EXPORT_SYMBOL_GPL ( mei_device_init ) ;