2015-02-02 14:26:25 +03:00
/*
* cros_ec_dev - expose the Chrome OS Embedded Controller to user - space
*
* Copyright ( C ) 2014 Google , Inc .
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 2 of the License , or
* ( at your option ) any later version .
*
* This program is distributed in the hope that 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 .
*
* You should have received a copy of the GNU General Public License
* along with this program . If not , see < http : //www.gnu.org/licenses/>.
*/
# include <linux/fs.h>
# include <linux/module.h>
# include <linux/platform_device.h>
2015-06-09 14:04:42 +03:00
# include <linux/slab.h>
2015-02-02 14:26:25 +03:00
# include <linux/uaccess.h>
# include "cros_ec_dev.h"
/* Device variables */
# define CROS_MAX_DEV 128
static int ec_major ;
2015-06-09 14:04:47 +03:00
static const struct attribute_group * cros_ec_groups [ ] = {
& cros_ec_attr_group ,
& cros_ec_lightbar_attr_group ,
2015-09-21 16:38:22 +03:00
& cros_ec_vbc_attr_group ,
2015-06-09 14:04:47 +03:00
NULL ,
} ;
static struct class cros_class = {
. owner = THIS_MODULE ,
. name = " chromeos " ,
. dev_groups = cros_ec_groups ,
} ;
2015-02-02 14:26:25 +03:00
/* Basic communication */
2015-06-09 14:04:47 +03:00
static int ec_get_version ( struct cros_ec_dev * ec , char * str , int maxlen )
2015-02-02 14:26:25 +03:00
{
struct ec_response_get_version * resp ;
static const char * const current_image_name [ ] = {
" unknown " , " read-only " , " read-write " , " invalid " ,
} ;
2015-06-09 14:04:42 +03:00
struct cros_ec_command * msg ;
2015-02-02 14:26:25 +03:00
int ret ;
2015-06-09 14:04:42 +03:00
msg = kmalloc ( sizeof ( * msg ) + sizeof ( * resp ) , GFP_KERNEL ) ;
if ( ! msg )
return - ENOMEM ;
msg - > version = 0 ;
2015-06-09 14:04:47 +03:00
msg - > command = EC_CMD_GET_VERSION + ec - > cmd_offset ;
2015-06-09 14:04:42 +03:00
msg - > insize = sizeof ( * resp ) ;
msg - > outsize = 0 ;
2015-06-09 14:04:47 +03:00
ret = cros_ec_cmd_xfer ( ec - > ec_dev , msg ) ;
2015-02-02 14:26:25 +03:00
if ( ret < 0 )
2015-06-09 14:04:42 +03:00
goto exit ;
2015-02-02 14:26:25 +03:00
2015-06-09 14:04:42 +03:00
if ( msg - > result ! = EC_RES_SUCCESS ) {
2015-02-02 14:26:25 +03:00
snprintf ( str , maxlen ,
" %s \n Unknown EC version: EC returned %d \n " ,
2015-06-09 14:04:42 +03:00
CROS_EC_DEV_VERSION , msg - > result ) ;
ret = - EINVAL ;
goto exit ;
2015-02-02 14:26:25 +03:00
}
2015-06-09 14:04:42 +03:00
resp = ( struct ec_response_get_version * ) msg - > data ;
2015-02-02 14:26:25 +03:00
if ( resp - > current_image > = ARRAY_SIZE ( current_image_name ) )
resp - > current_image = 3 ; /* invalid */
2015-03-03 15:22:32 +03:00
snprintf ( str , maxlen , " %s \n %s \n %s \n %s \n " , CROS_EC_DEV_VERSION ,
2015-02-02 14:26:25 +03:00
resp - > version_string_ro , resp - > version_string_rw ,
current_image_name [ resp - > current_image ] ) ;
2015-06-09 14:04:42 +03:00
ret = 0 ;
exit :
kfree ( msg ) ;
return ret ;
2015-02-02 14:26:25 +03:00
}
/* Device file ops */
static int ec_device_open ( struct inode * inode , struct file * filp )
{
2015-06-09 14:04:47 +03:00
struct cros_ec_dev * ec = container_of ( inode - > i_cdev ,
struct cros_ec_dev , cdev ) ;
filp - > private_data = ec ;
nonseekable_open ( inode , filp ) ;
2015-02-02 14:26:25 +03:00
return 0 ;
}
static int ec_device_release ( struct inode * inode , struct file * filp )
{
return 0 ;
}
static ssize_t ec_device_read ( struct file * filp , char __user * buffer ,
size_t length , loff_t * offset )
{
2015-06-09 14:04:47 +03:00
struct cros_ec_dev * ec = filp - > private_data ;
2015-02-02 14:26:25 +03:00
char msg [ sizeof ( struct ec_response_get_version ) +
sizeof ( CROS_EC_DEV_VERSION ) ] ;
size_t count ;
int ret ;
if ( * offset ! = 0 )
return 0 ;
ret = ec_get_version ( ec , msg , sizeof ( msg ) ) ;
if ( ret )
return ret ;
count = min ( length , strlen ( msg ) ) ;
if ( copy_to_user ( buffer , msg , count ) )
return - EFAULT ;
* offset = count ;
return count ;
}
/* Ioctls */
2015-06-09 14:04:47 +03:00
static long ec_device_ioctl_xcmd ( struct cros_ec_dev * ec , void __user * arg )
2015-02-02 14:26:25 +03:00
{
long ret ;
2015-06-09 14:04:42 +03:00
struct cros_ec_command u_cmd ;
struct cros_ec_command * s_cmd ;
2015-02-02 14:26:25 +03:00
2015-06-09 14:04:42 +03:00
if ( copy_from_user ( & u_cmd , arg , sizeof ( u_cmd ) ) )
2015-02-02 14:26:25 +03:00
return - EFAULT ;
2015-06-09 14:04:42 +03:00
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 ;
}
2015-06-09 14:04:47 +03:00
s_cmd - > command + = ec - > cmd_offset ;
ret = cros_ec_cmd_xfer ( ec - > ec_dev , s_cmd ) ;
2015-02-02 14:26:25 +03:00
/* Only copy data to userland if data was received. */
if ( ret < 0 )
2015-06-09 14:04:42 +03:00
goto exit ;
2015-02-02 14:26:25 +03:00
2015-06-09 14:04:42 +03:00
if ( copy_to_user ( arg , s_cmd , sizeof ( * s_cmd ) + u_cmd . insize ) )
ret = - EFAULT ;
exit :
kfree ( s_cmd ) ;
return ret ;
2015-02-02 14:26:25 +03:00
}
2015-06-09 14:04:47 +03:00
static long ec_device_ioctl_readmem ( struct cros_ec_dev * ec , void __user * arg )
2015-02-02 14:26:25 +03:00
{
2015-06-09 14:04:47 +03:00
struct cros_ec_device * ec_dev = ec - > ec_dev ;
2015-02-02 14:26:25 +03:00
struct cros_ec_readmem s_mem = { } ;
long num ;
/* Not every platform supports direct reads */
2015-06-09 14:04:47 +03:00
if ( ! ec_dev - > cmd_readmem )
2015-02-02 14:26:25 +03:00
return - ENOTTY ;
if ( copy_from_user ( & s_mem , arg , sizeof ( s_mem ) ) )
return - EFAULT ;
2015-06-09 14:04:47 +03:00
num = ec_dev - > cmd_readmem ( ec_dev , s_mem . offset , s_mem . bytes ,
s_mem . buffer ) ;
2015-02-02 14:26:25 +03:00
if ( num < = 0 )
return num ;
if ( copy_to_user ( ( void __user * ) arg , & s_mem , sizeof ( s_mem ) ) )
return - EFAULT ;
return 0 ;
}
static long ec_device_ioctl ( struct file * filp , unsigned int cmd ,
unsigned long arg )
{
2015-06-09 14:04:47 +03:00
struct cros_ec_dev * ec = filp - > private_data ;
2015-02-02 14:26:25 +03:00
if ( _IOC_TYPE ( cmd ) ! = CROS_EC_DEV_IOC )
return - ENOTTY ;
switch ( cmd ) {
case CROS_EC_DEV_IOCXCMD :
return ec_device_ioctl_xcmd ( ec , ( void __user * ) arg ) ;
case CROS_EC_DEV_IOCRDMEM :
return ec_device_ioctl_readmem ( ec , ( void __user * ) arg ) ;
}
return - ENOTTY ;
}
/* Module initialization */
static const struct file_operations fops = {
. open = ec_device_open ,
. release = ec_device_release ,
. read = ec_device_read ,
. unlocked_ioctl = ec_device_ioctl ,
} ;
2015-06-09 14:04:47 +03:00
static void __remove ( struct device * dev )
{
struct cros_ec_dev * ec = container_of ( dev , struct cros_ec_dev ,
class_dev ) ;
kfree ( ec ) ;
}
2015-02-02 14:26:25 +03:00
static int ec_device_probe ( struct platform_device * pdev )
{
2015-06-09 14:04:47 +03:00
int retval = - ENOMEM ;
struct device * dev = & pdev - > dev ;
struct cros_ec_platform * ec_platform = dev_get_platdata ( dev ) ;
dev_t devno = MKDEV ( ec_major , pdev - > id ) ;
struct cros_ec_dev * ec = kzalloc ( sizeof ( * ec ) , GFP_KERNEL ) ;
if ( ! ec )
return retval ;
2015-02-02 14:26:25 +03:00
2015-06-09 14:04:47 +03:00
dev_set_drvdata ( dev , ec ) ;
ec - > ec_dev = dev_get_drvdata ( dev - > parent ) ;
ec - > dev = dev ;
ec - > cmd_offset = ec_platform - > cmd_offset ;
device_initialize ( & ec - > class_dev ) ;
2015-02-02 14:26:25 +03:00
cdev_init ( & ec - > cdev , & fops ) ;
2015-06-09 14:04:47 +03:00
/*
* Add the character device
* Link cdev to the class device to be sure device is not used
* before unbinding it .
*/
ec - > cdev . kobj . parent = & ec - > class_dev . kobj ;
2015-02-02 14:26:25 +03:00
retval = cdev_add ( & ec - > cdev , devno , 1 ) ;
if ( retval ) {
2015-06-09 14:04:47 +03:00
dev_err ( dev , " : failed to add character device \n " ) ;
goto cdev_add_failed ;
2015-02-02 14:26:25 +03:00
}
2015-06-09 14:04:47 +03:00
/*
* Add the class device
* Link to the character device for creating the / dev entry
* in devtmpfs .
*/
ec - > class_dev . devt = ec - > cdev . dev ;
ec - > class_dev . class = & cros_class ;
ec - > class_dev . parent = dev ;
ec - > class_dev . release = __remove ;
retval = dev_set_name ( & ec - > class_dev , " %s " , ec_platform - > ec_name ) ;
if ( retval ) {
dev_err ( dev , " dev_set_name failed => %d \n " , retval ) ;
goto set_named_failed ;
2015-02-02 14:26:25 +03:00
}
2015-06-09 14:04:47 +03:00
retval = device_add ( & ec - > class_dev ) ;
if ( retval ) {
dev_err ( dev , " device_register failed => %d \n " , retval ) ;
goto dev_reg_failed ;
}
2015-02-02 14:26:27 +03:00
2015-02-02 14:26:25 +03:00
return 0 ;
2015-06-09 14:04:47 +03:00
dev_reg_failed :
set_named_failed :
dev_set_drvdata ( dev , NULL ) ;
cdev_del ( & ec - > cdev ) ;
cdev_add_failed :
kfree ( ec ) ;
return retval ;
2015-02-02 14:26:25 +03:00
}
static int ec_device_remove ( struct platform_device * pdev )
{
2015-06-09 14:04:47 +03:00
struct cros_ec_dev * ec = dev_get_drvdata ( & pdev - > dev ) ;
2015-02-02 14:26:25 +03:00
cdev_del ( & ec - > cdev ) ;
2015-06-09 14:04:47 +03:00
device_unregister ( & ec - > class_dev ) ;
2015-02-02 14:26:25 +03:00
return 0 ;
}
2015-06-22 09:27:20 +03:00
static const struct platform_device_id cros_ec_id [ ] = {
{ " cros-ec-ctl " , 0 } ,
{ /* sentinel */ } ,
} ;
MODULE_DEVICE_TABLE ( platform , cros_ec_id ) ;
2015-02-02 14:26:25 +03:00
static struct platform_driver cros_ec_dev_driver = {
. driver = {
. name = " cros-ec-ctl " ,
} ,
. probe = ec_device_probe ,
. remove = ec_device_remove ,
} ;
static int __init cros_ec_dev_init ( void )
{
int ret ;
dev_t dev = 0 ;
2015-06-09 14:04:47 +03:00
ret = class_register ( & cros_class ) ;
if ( ret ) {
2015-02-02 14:26:25 +03:00
pr_err ( CROS_EC_DEV_NAME " : failed to register device class \n " ) ;
2015-06-09 14:04:47 +03:00
return ret ;
2015-02-02 14:26:25 +03:00
}
/* Get a range of minor numbers (starting with 0) to work with */
ret = alloc_chrdev_region ( & dev , 0 , CROS_MAX_DEV , CROS_EC_DEV_NAME ) ;
if ( ret < 0 ) {
pr_err ( CROS_EC_DEV_NAME " : alloc_chrdev_region() failed \n " ) ;
goto failed_chrdevreg ;
}
ec_major = MAJOR ( dev ) ;
/* Register the driver */
ret = platform_driver_register ( & cros_ec_dev_driver ) ;
if ( ret < 0 ) {
pr_warn ( CROS_EC_DEV_NAME " : can't register driver: %d \n " , ret ) ;
goto failed_devreg ;
}
return 0 ;
failed_devreg :
unregister_chrdev_region ( MKDEV ( ec_major , 0 ) , CROS_MAX_DEV ) ;
failed_chrdevreg :
2015-06-09 14:04:47 +03:00
class_unregister ( & cros_class ) ;
2015-02-02 14:26:25 +03:00
return ret ;
}
static void __exit cros_ec_dev_exit ( void )
{
platform_driver_unregister ( & cros_ec_dev_driver ) ;
unregister_chrdev ( ec_major , CROS_EC_DEV_NAME ) ;
2015-06-09 14:04:47 +03:00
class_unregister ( & cros_class ) ;
2015-02-02 14:26:25 +03:00
}
module_init ( cros_ec_dev_init ) ;
module_exit ( cros_ec_dev_exit ) ;
MODULE_AUTHOR ( " Bill Richardson <wfrichar@chromium.org> " ) ;
MODULE_DESCRIPTION ( " Userspace interface to the Chrome OS Embedded Controller " ) ;
MODULE_VERSION ( " 1.0 " ) ;
MODULE_LICENSE ( " GPL " ) ;