2019-09-02 11:53:02 +02:00
// SPDX-License-Identifier: GPL-2.0
/*
* Miscellaneous character driver for ChromeOS Embedded Controller
*
* Copyright 2014 Google , Inc .
* Copyright 2019 Google LLC
*
* This file is a rework and part of the code is ported from
* drivers / mfd / cros_ec_dev . c that was originally written by
* Bill Richardson .
*/
# include <linux/init.h>
# include <linux/device.h>
# include <linux/fs.h>
# include <linux/miscdevice.h>
# include <linux/module.h>
2019-09-02 18:08:48 +02:00
# include <linux/notifier.h>
2019-09-02 11:53:02 +02:00
# include <linux/platform_data/cros_ec_chardev.h>
2019-09-02 11:53:05 +02:00
# include <linux/platform_data/cros_ec_commands.h>
# include <linux/platform_data/cros_ec_proto.h>
2019-09-02 11:53:02 +02:00
# include <linux/platform_device.h>
2019-09-02 18:08:48 +02:00
# include <linux/poll.h>
2019-09-02 11:53:02 +02:00
# include <linux/slab.h>
# include <linux/types.h>
# include <linux/uaccess.h>
# define DRV_NAME "cros-ec-chardev"
2019-09-02 18:08:48 +02:00
/* Arbitrary bounded size for the event queue */
# define CROS_MAX_EVENT_LEN PAGE_SIZE
2019-09-02 11:53:02 +02:00
struct chardev_data {
struct cros_ec_dev * ec_dev ;
struct miscdevice misc ;
} ;
2019-09-02 18:08:48 +02:00
struct chardev_priv {
struct cros_ec_dev * ec_dev ;
struct notifier_block notifier ;
wait_queue_head_t wait_event ;
unsigned long event_mask ;
struct list_head events ;
size_t event_len ;
} ;
struct ec_event {
struct list_head node ;
size_t size ;
u8 event_type ;
2020-03-20 18:15:42 -05:00
u8 data [ ] ;
2019-09-02 18:08:48 +02:00
} ;
2019-09-02 11:53:02 +02:00
static int ec_get_version ( struct cros_ec_dev * ec , char * str , int maxlen )
{
static const char * const current_image_name [ ] = {
" unknown " , " read-only " , " read-write " , " invalid " ,
} ;
struct ec_response_get_version * resp ;
struct cros_ec_command * msg ;
int ret ;
msg = kzalloc ( sizeof ( * msg ) + sizeof ( * resp ) , GFP_KERNEL ) ;
if ( ! msg )
return - ENOMEM ;
msg - > command = EC_CMD_GET_VERSION + ec - > cmd_offset ;
msg - > insize = sizeof ( * resp ) ;
ret = cros_ec_cmd_xfer_status ( ec - > ec_dev , msg ) ;
if ( ret < 0 ) {
snprintf ( str , maxlen ,
" Unknown EC version, returned error: %d \n " ,
msg - > result ) ;
goto exit ;
}
resp = ( struct ec_response_get_version * ) msg - > data ;
if ( resp - > current_image > = ARRAY_SIZE ( current_image_name ) )
resp - > current_image = 3 ; /* invalid */
snprintf ( str , maxlen , " %s \n %s \n %s \n %s \n " , CROS_EC_DEV_VERSION ,
resp - > version_string_ro , resp - > version_string_rw ,
current_image_name [ resp - > current_image ] ) ;
ret = 0 ;
exit :
kfree ( msg ) ;
return ret ;
}
2019-09-02 18:08:48 +02:00
static int cros_ec_chardev_mkbp_event ( struct notifier_block * nb ,
unsigned long queued_during_suspend ,
void * _notify )
{
struct chardev_priv * priv = container_of ( nb , struct chardev_priv ,
notifier ) ;
struct cros_ec_device * ec_dev = priv - > ec_dev - > ec_dev ;
struct ec_event * event ;
unsigned long event_bit = 1 < < ec_dev - > event_data . event_type ;
int total_size = sizeof ( * event ) + ec_dev - > event_size ;
if ( ! ( event_bit & priv - > event_mask ) | |
( priv - > event_len + total_size ) > CROS_MAX_EVENT_LEN )
return NOTIFY_DONE ;
event = kzalloc ( total_size , GFP_KERNEL ) ;
if ( ! event )
return NOTIFY_DONE ;
event - > size = ec_dev - > event_size ;
event - > event_type = ec_dev - > event_data . event_type ;
memcpy ( event - > data , & ec_dev - > event_data . data , ec_dev - > event_size ) ;
spin_lock ( & priv - > wait_event . lock ) ;
list_add_tail ( & event - > node , & priv - > events ) ;
priv - > event_len + = total_size ;
wake_up_locked ( & priv - > wait_event ) ;
spin_unlock ( & priv - > wait_event . lock ) ;
return NOTIFY_OK ;
}
static struct ec_event * cros_ec_chardev_fetch_event ( struct chardev_priv * priv ,
bool fetch , bool block )
{
struct ec_event * event ;
int err ;
spin_lock ( & priv - > wait_event . lock ) ;
if ( ! block & & list_empty ( & priv - > events ) ) {
event = ERR_PTR ( - EWOULDBLOCK ) ;
goto out ;
}
if ( ! fetch ) {
event = NULL ;
goto out ;
}
err = wait_event_interruptible_locked ( priv - > wait_event ,
! list_empty ( & priv - > events ) ) ;
if ( err ) {
event = ERR_PTR ( err ) ;
goto out ;
}
event = list_first_entry ( & priv - > events , struct ec_event , node ) ;
list_del ( & event - > node ) ;
priv - > event_len - = sizeof ( * event ) + event - > size ;
out :
spin_unlock ( & priv - > wait_event . lock ) ;
return event ;
}
2019-09-02 11:53:02 +02:00
/*
* Device file ops
*/
static int cros_ec_chardev_open ( struct inode * inode , struct file * filp )
{
struct miscdevice * mdev = filp - > private_data ;
struct cros_ec_dev * ec_dev = dev_get_drvdata ( mdev - > parent ) ;
2019-09-02 18:08:48 +02:00
struct chardev_priv * priv ;
int ret ;
priv = kzalloc ( sizeof ( * priv ) , GFP_KERNEL ) ;
if ( ! priv )
return - ENOMEM ;
2019-09-02 11:53:02 +02:00
2019-09-02 18:08:48 +02:00
priv - > ec_dev = ec_dev ;
filp - > private_data = priv ;
INIT_LIST_HEAD ( & priv - > events ) ;
init_waitqueue_head ( & priv - > wait_event ) ;
2019-09-02 11:53:02 +02:00
nonseekable_open ( inode , filp ) ;
2019-09-02 18:08:48 +02:00
priv - > notifier . notifier_call = cros_ec_chardev_mkbp_event ;
ret = blocking_notifier_chain_register ( & ec_dev - > ec_dev - > event_notifier ,
& priv - > notifier ) ;
if ( ret ) {
dev_err ( ec_dev - > dev , " failed to register event notifier \n " ) ;
kfree ( priv ) ;
}
return ret ;
}
static __poll_t cros_ec_chardev_poll ( struct file * filp , poll_table * wait )
{
struct chardev_priv * priv = filp - > private_data ;
poll_wait ( filp , & priv - > wait_event , wait ) ;
if ( list_empty ( & priv - > events ) )
return 0 ;
return EPOLLIN | EPOLLRDNORM ;
2019-09-02 11:53:02 +02:00
}
static ssize_t cros_ec_chardev_read ( struct file * filp , char __user * buffer ,
size_t length , loff_t * offset )
{
char msg [ sizeof ( struct ec_response_get_version ) +
sizeof ( CROS_EC_DEV_VERSION ) ] ;
2019-09-02 18:08:48 +02:00
struct chardev_priv * priv = filp - > private_data ;
struct cros_ec_dev * ec_dev = priv - > ec_dev ;
2019-09-02 11:53:02 +02:00
size_t count ;
int ret ;
2019-09-02 18:08:48 +02:00
if ( priv - > event_mask ) { /* queued MKBP event */
struct ec_event * event ;
event = cros_ec_chardev_fetch_event ( priv , length ! = 0 ,
! ( filp - > f_flags & O_NONBLOCK ) ) ;
if ( IS_ERR ( event ) )
return PTR_ERR ( event ) ;
/*
* length = = 0 is special - no IO is done but we check
* for error conditions .
*/
if ( length = = 0 )
return 0 ;
/* The event is 1 byte of type plus the payload */
count = min ( length , event - > size + 1 ) ;
ret = copy_to_user ( buffer , & event - > event_type , count ) ;
kfree ( event ) ;
if ( ret ) /* the copy failed */
return - EFAULT ;
* offset = count ;
return count ;
}
/*
* Legacy behavior if no event mask is defined
*/
2019-09-02 11:53:02 +02:00
if ( * offset ! = 0 )
return 0 ;
2019-09-02 18:08:48 +02:00
ret = ec_get_version ( ec_dev , msg , sizeof ( msg ) ) ;
2019-09-02 11:53:02 +02:00
if ( ret )
return ret ;
count = min ( length , strlen ( msg ) ) ;
if ( copy_to_user ( buffer , msg , count ) )
return - EFAULT ;
* offset = count ;
return count ;
}
2019-09-02 18:08:48 +02:00
static int cros_ec_chardev_release ( struct inode * inode , struct file * filp )
{
struct chardev_priv * priv = filp - > private_data ;
struct cros_ec_dev * ec_dev = priv - > ec_dev ;
struct ec_event * event , * e ;
blocking_notifier_chain_unregister ( & ec_dev - > ec_dev - > event_notifier ,
& priv - > notifier ) ;
list_for_each_entry_safe ( event , e , & priv - > events , node ) {
list_del ( & event - > node ) ;
kfree ( event ) ;
}
kfree ( priv ) ;
return 0 ;
}
2019-09-02 11:53:02 +02:00
/*
* Ioctls
*/
static long cros_ec_chardev_ioctl_xcmd ( struct cros_ec_dev * ec , void __user * arg )
{
struct cros_ec_command * s_cmd ;
struct cros_ec_command u_cmd ;
long ret ;
if ( copy_from_user ( & u_cmd , arg , sizeof ( u_cmd ) ) )
return - EFAULT ;
if ( u_cmd . outsize > EC_MAX_MSG_BYTES | |
u_cmd . insize > EC_MAX_MSG_BYTES )
return - EINVAL ;
s_cmd = kmalloc ( sizeof ( * s_cmd ) + max ( u_cmd . outsize , u_cmd . insize ) ,
GFP_KERNEL ) ;
if ( ! s_cmd )
return - ENOMEM ;
if ( copy_from_user ( s_cmd , arg , sizeof ( * s_cmd ) + u_cmd . outsize ) ) {
ret = - EFAULT ;
goto exit ;
}
if ( u_cmd . outsize ! = s_cmd - > outsize | |
u_cmd . insize ! = s_cmd - > insize ) {
ret = - EINVAL ;
goto exit ;
}
s_cmd - > command + = ec - > cmd_offset ;
2020-02-20 16:58:55 +01:00
ret = cros_ec_cmd_xfer_status ( ec - > ec_dev , s_cmd ) ;
2019-09-02 11:53:02 +02:00
/* Only copy data to userland if data was received. */
if ( ret < 0 )
goto exit ;
if ( copy_to_user ( arg , s_cmd , sizeof ( * s_cmd ) + s_cmd - > insize ) )
ret = - EFAULT ;
exit :
kfree ( s_cmd ) ;
return ret ;
}
static long cros_ec_chardev_ioctl_readmem ( struct cros_ec_dev * ec ,
void __user * arg )
{
struct cros_ec_device * ec_dev = ec - > ec_dev ;
struct cros_ec_readmem s_mem = { } ;
long num ;
/* Not every platform supports direct reads */
if ( ! ec_dev - > cmd_readmem )
return - ENOTTY ;
if ( copy_from_user ( & s_mem , arg , sizeof ( s_mem ) ) )
return - EFAULT ;
num = ec_dev - > cmd_readmem ( ec_dev , s_mem . offset , s_mem . bytes ,
s_mem . buffer ) ;
if ( num < = 0 )
return num ;
if ( copy_to_user ( ( void __user * ) arg , & s_mem , sizeof ( s_mem ) ) )
return - EFAULT ;
return num ;
}
static long cros_ec_chardev_ioctl ( struct file * filp , unsigned int cmd ,
unsigned long arg )
{
2019-09-02 18:08:48 +02:00
struct chardev_priv * priv = filp - > private_data ;
struct cros_ec_dev * ec = priv - > ec_dev ;
2019-09-02 11:53:02 +02:00
if ( _IOC_TYPE ( cmd ) ! = CROS_EC_DEV_IOC )
return - ENOTTY ;
switch ( cmd ) {
case CROS_EC_DEV_IOCXCMD :
return cros_ec_chardev_ioctl_xcmd ( ec , ( void __user * ) arg ) ;
case CROS_EC_DEV_IOCRDMEM :
return cros_ec_chardev_ioctl_readmem ( ec , ( void __user * ) arg ) ;
2019-09-02 18:08:48 +02:00
case CROS_EC_DEV_IOCEVENTMASK :
priv - > event_mask = arg ;
return 0 ;
2019-09-02 11:53:02 +02:00
}
return - ENOTTY ;
}
static const struct file_operations chardev_fops = {
. open = cros_ec_chardev_open ,
2019-09-02 18:08:48 +02:00
. poll = cros_ec_chardev_poll ,
2019-09-02 11:53:02 +02:00
. read = cros_ec_chardev_read ,
2019-09-02 18:08:48 +02:00
. release = cros_ec_chardev_release ,
2019-09-02 11:53:02 +02:00
. unlocked_ioctl = cros_ec_chardev_ioctl ,
# ifdef CONFIG_COMPAT
. compat_ioctl = cros_ec_chardev_ioctl ,
# endif
} ;
static int cros_ec_chardev_probe ( struct platform_device * pdev )
{
struct cros_ec_dev * ec_dev = dev_get_drvdata ( pdev - > dev . parent ) ;
struct cros_ec_platform * ec_platform = dev_get_platdata ( ec_dev - > dev ) ;
struct chardev_data * data ;
/* Create a char device: we want to create it anew */
data = devm_kzalloc ( & pdev - > dev , sizeof ( * data ) , GFP_KERNEL ) ;
if ( ! data )
return - ENOMEM ;
data - > ec_dev = ec_dev ;
data - > misc . minor = MISC_DYNAMIC_MINOR ;
data - > misc . fops = & chardev_fops ;
data - > misc . name = ec_platform - > ec_name ;
data - > misc . parent = pdev - > dev . parent ;
dev_set_drvdata ( & pdev - > dev , data ) ;
return misc_register ( & data - > misc ) ;
}
static int cros_ec_chardev_remove ( struct platform_device * pdev )
{
struct chardev_data * data = dev_get_drvdata ( & pdev - > dev ) ;
misc_deregister ( & data - > misc ) ;
return 0 ;
}
static struct platform_driver cros_ec_chardev_driver = {
. driver = {
. name = DRV_NAME ,
} ,
. probe = cros_ec_chardev_probe ,
. remove = cros_ec_chardev_remove ,
} ;
module_platform_driver ( cros_ec_chardev_driver ) ;
MODULE_ALIAS ( " platform: " DRV_NAME ) ;
MODULE_AUTHOR ( " Enric Balletbo i Serra <enric.balletbo@collabora.com> " ) ;
MODULE_DESCRIPTION ( " ChromeOS EC Miscellaneous Character Driver " ) ;
MODULE_LICENSE ( " GPL " ) ;