2013-03-27 17:29:53 +02:00
/*
* Intel Management Engine Interface ( Intel MEI ) Linux driver
* Copyright ( c ) 2012 - 2013 , 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/device.h>
# include <linux/kernel.h>
2013-03-27 17:29:55 +02:00
# include <linux/sched.h>
2013-03-27 17:29:53 +02:00
# include <linux/init.h>
# include <linux/errno.h>
# include <linux/slab.h>
# include <linux/mutex.h>
# include <linux/interrupt.h>
# include <linux/mei_cl_bus.h>
# include "mei_dev.h"
2013-03-27 17:29:55 +02:00
# include "client.h"
2013-03-27 17:29:53 +02:00
# define to_mei_cl_driver(d) container_of(d, struct mei_cl_driver, driver)
# define to_mei_cl_device(d) container_of(d, struct mei_cl_device, dev)
static int mei_cl_device_match ( struct device * dev , struct device_driver * drv )
{
struct mei_cl_device * device = to_mei_cl_device ( dev ) ;
struct mei_cl_driver * driver = to_mei_cl_driver ( drv ) ;
const struct mei_cl_device_id * id ;
if ( ! device )
return 0 ;
if ( ! driver | | ! driver - > id_table )
return 0 ;
id = driver - > id_table ;
while ( id - > name [ 0 ] ) {
2013-07-24 16:22:59 +03:00
if ( ! strncmp ( dev_name ( dev ) , id - > name , sizeof ( id - > name ) ) )
2013-03-27 17:29:53 +02:00
return 1 ;
id + + ;
}
return 0 ;
}
static int mei_cl_device_probe ( struct device * dev )
{
struct mei_cl_device * device = to_mei_cl_device ( dev ) ;
struct mei_cl_driver * driver ;
struct mei_cl_device_id id ;
if ( ! device )
return 0 ;
driver = to_mei_cl_driver ( dev - > driver ) ;
if ( ! driver | | ! driver - > probe )
return - ENODEV ;
dev_dbg ( dev , " Device probe \n " ) ;
2014-08-25 16:46:53 +03:00
strlcpy ( id . name , dev_name ( dev ) , sizeof ( id . name ) ) ;
2013-03-27 17:29:53 +02:00
return driver - > probe ( device , & id ) ;
}
static int mei_cl_device_remove ( struct device * dev )
{
struct mei_cl_device * device = to_mei_cl_device ( dev ) ;
struct mei_cl_driver * driver ;
if ( ! device | | ! dev - > driver )
return 0 ;
2013-03-27 17:29:55 +02:00
if ( device - > event_cb ) {
device - > event_cb = NULL ;
cancel_work_sync ( & device - > event_work ) ;
}
2013-03-27 17:29:53 +02:00
driver = to_mei_cl_driver ( dev - > driver ) ;
if ( ! driver - > remove ) {
dev - > driver = NULL ;
return 0 ;
}
return driver - > remove ( device ) ;
}
static ssize_t modalias_show ( struct device * dev , struct device_attribute * a ,
char * buf )
{
int len ;
len = snprintf ( buf , PAGE_SIZE , " mei:%s \n " , dev_name ( dev ) ) ;
return ( len > = PAGE_SIZE ) ? ( PAGE_SIZE - 1 ) : len ;
}
2013-08-23 14:24:39 -07:00
static DEVICE_ATTR_RO ( modalias ) ;
2013-03-27 17:29:53 +02:00
2013-08-23 14:24:39 -07:00
static struct attribute * mei_cl_dev_attrs [ ] = {
& dev_attr_modalias . attr ,
NULL ,
2013-03-27 17:29:53 +02:00
} ;
2013-08-23 14:24:39 -07:00
ATTRIBUTE_GROUPS ( mei_cl_dev ) ;
2013-03-27 17:29:53 +02:00
static int mei_cl_uevent ( struct device * dev , struct kobj_uevent_env * env )
{
if ( add_uevent_var ( env , " MODALIAS=mei:%s " , dev_name ( dev ) ) )
return - ENOMEM ;
return 0 ;
}
static struct bus_type mei_cl_bus_type = {
. name = " mei " ,
2013-08-23 14:24:39 -07:00
. dev_groups = mei_cl_dev_groups ,
2013-03-27 17:29:53 +02:00
. match = mei_cl_device_match ,
. probe = mei_cl_device_probe ,
. remove = mei_cl_device_remove ,
. uevent = mei_cl_uevent ,
} ;
static void mei_cl_dev_release ( struct device * dev )
{
kfree ( to_mei_cl_device ( dev ) ) ;
}
static struct device_type mei_cl_device_type = {
. release = mei_cl_dev_release ,
} ;
2013-03-27 17:29:56 +02:00
static struct mei_cl * mei_bus_find_mei_cl_by_uuid ( struct mei_device * dev ,
uuid_le uuid )
{
2014-02-17 15:13:25 +02:00
struct mei_cl * cl ;
2013-03-27 17:29:56 +02:00
2014-02-17 15:13:25 +02:00
list_for_each_entry ( cl , & dev - > device_list , device_link ) {
2014-08-21 14:29:15 +03:00
if ( ! uuid_le_cmp ( uuid , cl - > cl_uuid ) )
2013-03-27 17:29:56 +02:00
return cl ;
}
return NULL ;
}
struct mei_cl_device * mei_cl_add_device ( struct mei_device * dev ,
2013-04-09 01:51:38 +03:00
uuid_le uuid , char * name ,
struct mei_cl_ops * ops )
2013-03-27 17:29:53 +02:00
{
struct mei_cl_device * device ;
2013-03-27 17:29:56 +02:00
struct mei_cl * cl ;
2013-03-27 17:29:53 +02:00
int status ;
2013-03-27 17:29:56 +02:00
cl = mei_bus_find_mei_cl_by_uuid ( dev , uuid ) ;
if ( cl = = NULL )
return NULL ;
2013-03-27 17:29:53 +02:00
device = kzalloc ( sizeof ( struct mei_cl_device ) , GFP_KERNEL ) ;
if ( ! device )
return NULL ;
2013-03-27 17:29:56 +02:00
device - > cl = cl ;
2013-04-09 01:51:38 +03:00
device - > ops = ops ;
2013-03-27 17:29:56 +02:00
2014-09-29 16:31:42 +03:00
device - > dev . parent = dev - > dev ;
2013-03-27 17:29:53 +02:00
device - > dev . bus = & mei_cl_bus_type ;
device - > dev . type = & mei_cl_device_type ;
dev_set_name ( & device - > dev , " %s " , name ) ;
status = device_register ( & device - > dev ) ;
2013-03-27 17:29:56 +02:00
if ( status ) {
2014-09-29 16:31:42 +03:00
dev_err ( dev - > dev , " Failed to register MEI device \n " ) ;
2013-03-27 17:29:56 +02:00
kfree ( device ) ;
return NULL ;
}
cl - > device = device ;
2013-03-27 17:29:53 +02:00
dev_dbg ( & device - > dev , " client %s registered \n " , name ) ;
return device ;
}
EXPORT_SYMBOL_GPL ( mei_cl_add_device ) ;
void mei_cl_remove_device ( struct mei_cl_device * device )
{
device_unregister ( & device - > dev ) ;
}
EXPORT_SYMBOL_GPL ( mei_cl_remove_device ) ;
2013-03-27 17:29:54 +02:00
int __mei_cl_driver_register ( struct mei_cl_driver * driver , struct module * owner )
{
int err ;
driver - > driver . name = driver - > name ;
driver - > driver . owner = owner ;
driver - > driver . bus = & mei_cl_bus_type ;
err = driver_register ( & driver - > driver ) ;
if ( err )
return err ;
pr_debug ( " mei: driver [%s] registered \n " , driver - > driver . name ) ;
return 0 ;
}
EXPORT_SYMBOL_GPL ( __mei_cl_driver_register ) ;
void mei_cl_driver_unregister ( struct mei_cl_driver * driver )
{
driver_unregister ( & driver - > driver ) ;
pr_debug ( " mei: driver [%s] unregistered \n " , driver - > driver . name ) ;
}
EXPORT_SYMBOL_GPL ( mei_cl_driver_unregister ) ;
2013-03-27 17:29:55 +02:00
2013-03-27 17:29:58 +02:00
static int ___mei_cl_send ( struct mei_cl * cl , u8 * buf , size_t length ,
bool blocking )
2013-03-27 17:29:55 +02:00
{
struct mei_device * dev ;
2014-08-24 12:08:55 +03:00
struct mei_me_client * me_cl ;
2013-03-27 17:29:55 +02:00
struct mei_cl_cb * cb ;
2013-04-08 21:56:37 +03:00
int rets ;
2013-03-27 17:29:55 +02:00
if ( WARN_ON ( ! cl | | ! cl - > dev ) )
return - ENODEV ;
2013-04-08 21:56:37 +03:00
dev = cl - > dev ;
2013-03-27 17:29:55 +02:00
if ( cl - > state ! = MEI_FILE_CONNECTED )
return - ENODEV ;
2013-04-08 21:56:37 +03:00
/* Check if we have an ME client device */
2014-08-21 14:29:15 +03:00
me_cl = mei_me_cl_by_uuid_id ( dev , & cl - > cl_uuid , cl - > me_client_id ) ;
2014-08-24 12:08:55 +03:00
if ( ! me_cl )
return - ENOTTY ;
2013-04-08 21:56:37 +03:00
2014-08-24 12:08:55 +03:00
if ( length > me_cl - > props . max_msg_length )
2014-03-31 17:59:24 +03:00
return - EFBIG ;
2013-04-08 21:56:37 +03:00
2013-03-27 17:29:55 +02:00
cb = mei_io_cb_init ( cl , NULL ) ;
if ( ! cb )
return - ENOMEM ;
2013-04-08 21:56:37 +03:00
rets = mei_io_cb_alloc_req_buf ( cb , length ) ;
if ( rets < 0 ) {
2013-03-27 17:29:55 +02:00
mei_io_cb_free ( cb ) ;
2013-04-08 21:56:37 +03:00
return rets ;
2013-03-27 17:29:55 +02:00
}
memcpy ( cb - > request_buffer . data , buf , length ) ;
mutex_lock ( & dev - > device_lock ) ;
2013-04-08 21:56:37 +03:00
rets = mei_cl_write ( cl , cb , blocking ) ;
2013-03-27 17:29:55 +02:00
mutex_unlock ( & dev - > device_lock ) ;
2013-04-08 21:56:37 +03:00
if ( rets < 0 )
mei_io_cb_free ( cb ) ;
2013-03-27 17:29:55 +02:00
2013-04-08 21:56:37 +03:00
return rets ;
2013-03-27 17:29:55 +02:00
}
int __mei_cl_recv ( struct mei_cl * cl , u8 * buf , size_t length )
{
struct mei_device * dev ;
struct mei_cl_cb * cb ;
size_t r_length ;
int err ;
if ( WARN_ON ( ! cl | | ! cl - > dev ) )
return - ENODEV ;
dev = cl - > dev ;
mutex_lock ( & dev - > device_lock ) ;
if ( ! cl - > read_cb ) {
2013-04-19 22:01:35 +03:00
err = mei_cl_read_start ( cl , length ) ;
2013-03-27 17:29:55 +02:00
if ( err < 0 ) {
mutex_unlock ( & dev - > device_lock ) ;
return err ;
}
}
if ( cl - > reading_state ! = MEI_READ_COMPLETE & &
! waitqueue_active ( & cl - > rx_wait ) ) {
2013-09-02 13:29:46 +03:00
2013-03-27 17:29:55 +02:00
mutex_unlock ( & dev - > device_lock ) ;
if ( wait_event_interruptible ( cl - > rx_wait ,
2013-09-02 13:29:46 +03:00
cl - > reading_state = = MEI_READ_COMPLETE | |
mei_cl_is_transitioning ( cl ) ) ) {
2013-03-27 17:29:55 +02:00
if ( signal_pending ( current ) )
return - EINTR ;
return - ERESTARTSYS ;
}
mutex_lock ( & dev - > device_lock ) ;
}
cb = cl - > read_cb ;
if ( cl - > reading_state ! = MEI_READ_COMPLETE ) {
r_length = 0 ;
goto out ;
}
r_length = min_t ( size_t , length , cb - > buf_idx ) ;
memcpy ( buf , cb - > response_buffer . data , r_length ) ;
mei_io_cb_free ( cb ) ;
cl - > reading_state = MEI_IDLE ;
cl - > read_cb = NULL ;
out :
mutex_unlock ( & dev - > device_lock ) ;
return r_length ;
}
2013-03-27 17:29:58 +02:00
inline int __mei_cl_async_send ( struct mei_cl * cl , u8 * buf , size_t length )
{
return ___mei_cl_send ( cl , buf , length , 0 ) ;
}
inline int __mei_cl_send ( struct mei_cl * cl , u8 * buf , size_t length )
{
return ___mei_cl_send ( cl , buf , length , 1 ) ;
}
2013-03-27 17:29:55 +02:00
int mei_cl_send ( struct mei_cl_device * device , u8 * buf , size_t length )
{
2013-03-27 17:29:56 +02:00
struct mei_cl * cl = device - > cl ;
2013-03-27 17:29:55 +02:00
2013-03-27 17:29:56 +02:00
if ( cl = = NULL )
return - ENODEV ;
2013-03-27 17:29:55 +02:00
if ( device - > ops & & device - > ops - > send )
return device - > ops - > send ( device , buf , length ) ;
return __mei_cl_send ( cl , buf , length ) ;
}
EXPORT_SYMBOL_GPL ( mei_cl_send ) ;
int mei_cl_recv ( struct mei_cl_device * device , u8 * buf , size_t length )
{
2013-03-27 17:29:56 +02:00
struct mei_cl * cl = device - > cl ;
2013-03-27 17:29:55 +02:00
2013-03-27 17:29:56 +02:00
if ( cl = = NULL )
return - ENODEV ;
2013-03-27 17:29:55 +02:00
if ( device - > ops & & device - > ops - > recv )
return device - > ops - > recv ( device , buf , length ) ;
return __mei_cl_recv ( cl , buf , length ) ;
}
EXPORT_SYMBOL_GPL ( mei_cl_recv ) ;
static void mei_bus_event_work ( struct work_struct * work )
{
struct mei_cl_device * device ;
device = container_of ( work , struct mei_cl_device , event_work ) ;
if ( device - > event_cb )
device - > event_cb ( device , device - > events , device - > event_context ) ;
device - > events = 0 ;
/* Prepare for the next read */
2013-04-19 22:01:35 +03:00
mei_cl_read_start ( device - > cl , 0 ) ;
2013-03-27 17:29:55 +02:00
}
int mei_cl_register_event_cb ( struct mei_cl_device * device ,
mei_cl_event_cb_t event_cb , void * context )
{
if ( device - > event_cb )
return - EALREADY ;
device - > events = 0 ;
device - > event_cb = event_cb ;
device - > event_context = context ;
INIT_WORK ( & device - > event_work , mei_bus_event_work ) ;
2013-04-19 22:01:35 +03:00
mei_cl_read_start ( device - > cl , 0 ) ;
2013-03-27 17:29:55 +02:00
return 0 ;
}
EXPORT_SYMBOL_GPL ( mei_cl_register_event_cb ) ;
2013-03-27 17:29:57 +02:00
2013-03-27 17:29:59 +02:00
void * mei_cl_get_drvdata ( const struct mei_cl_device * device )
{
return dev_get_drvdata ( & device - > dev ) ;
}
EXPORT_SYMBOL_GPL ( mei_cl_get_drvdata ) ;
void mei_cl_set_drvdata ( struct mei_cl_device * device , void * data )
{
dev_set_drvdata ( & device - > dev , data ) ;
}
EXPORT_SYMBOL_GPL ( mei_cl_set_drvdata ) ;
2013-04-09 01:51:38 +03:00
int mei_cl_enable_device ( struct mei_cl_device * device )
{
int err ;
struct mei_device * dev ;
struct mei_cl * cl = device - > cl ;
if ( cl = = NULL )
return - ENODEV ;
dev = cl - > dev ;
mutex_lock ( & dev - > device_lock ) ;
err = mei_cl_connect ( cl , NULL ) ;
if ( err < 0 ) {
mutex_unlock ( & dev - > device_lock ) ;
2014-09-29 16:31:42 +03:00
dev_err ( dev - > dev , " Could not connect to the ME client " ) ;
2013-04-09 01:51:38 +03:00
return err ;
}
mutex_unlock ( & dev - > device_lock ) ;
if ( device - > event_cb & & ! cl - > read_cb )
2013-04-19 22:01:35 +03:00
mei_cl_read_start ( device - > cl , 0 ) ;
2013-04-09 01:51:38 +03:00
if ( ! device - > ops | | ! device - > ops - > enable )
return 0 ;
return device - > ops - > enable ( device ) ;
}
EXPORT_SYMBOL_GPL ( mei_cl_enable_device ) ;
int mei_cl_disable_device ( struct mei_cl_device * device )
{
int err ;
struct mei_device * dev ;
struct mei_cl * cl = device - > cl ;
if ( cl = = NULL )
return - ENODEV ;
dev = cl - > dev ;
mutex_lock ( & dev - > device_lock ) ;
if ( cl - > state ! = MEI_FILE_CONNECTED ) {
mutex_unlock ( & dev - > device_lock ) ;
2014-09-29 16:31:42 +03:00
dev_err ( dev - > dev , " Already disconnected " ) ;
2013-04-09 01:51:38 +03:00
return 0 ;
}
cl - > state = MEI_FILE_DISCONNECTING ;
err = mei_cl_disconnect ( cl ) ;
if ( err < 0 ) {
mutex_unlock ( & dev - > device_lock ) ;
2014-09-29 16:31:42 +03:00
dev_err ( dev - > dev ,
2013-04-09 01:51:38 +03:00
" Could not disconnect from the ME client " ) ;
return err ;
}
/* Flush queues and remove any pending read */
mei_cl_flush_queues ( cl ) ;
if ( cl - > read_cb ) {
struct mei_cl_cb * cb = NULL ;
cb = mei_cl_find_read_cb ( cl ) ;
/* Remove entry from read list */
if ( cb )
list_del ( & cb - > list ) ;
cb = cl - > read_cb ;
cl - > read_cb = NULL ;
if ( cb ) {
mei_io_cb_free ( cb ) ;
cb = NULL ;
}
}
2013-05-21 18:52:09 +02:00
device - > event_cb = NULL ;
2013-04-09 01:51:38 +03:00
mutex_unlock ( & dev - > device_lock ) ;
if ( ! device - > ops | | ! device - > ops - > disable )
return 0 ;
return device - > ops - > disable ( device ) ;
}
EXPORT_SYMBOL_GPL ( mei_cl_disable_device ) ;
2013-03-27 17:29:57 +02:00
void mei_cl_bus_rx_event ( struct mei_cl * cl )
{
struct mei_cl_device * device = cl - > device ;
if ( ! device | | ! device - > event_cb )
return ;
set_bit ( MEI_CL_EVENT_RX , & device - > events ) ;
schedule_work ( & device - > event_work ) ;
}
2014-02-17 15:13:19 +02:00
void mei_cl_bus_remove_devices ( struct mei_device * dev )
{
struct mei_cl * cl , * next ;
mutex_lock ( & dev - > device_lock ) ;
list_for_each_entry_safe ( cl , next , & dev - > device_list , device_link ) {
if ( cl - > device )
mei_cl_remove_device ( cl - > device ) ;
list_del ( & cl - > device_link ) ;
mei_cl_unlink ( cl ) ;
kfree ( cl ) ;
}
mutex_unlock ( & dev - > device_lock ) ;
}
2013-03-27 17:29:57 +02:00
int __init mei_cl_bus_init ( void )
{
return bus_register ( & mei_cl_bus_type ) ;
}
void __exit mei_cl_bus_exit ( void )
{
bus_unregister ( & mei_cl_bus_type ) ;
}