2019-05-29 17:17:56 +03:00
// SPDX-License-Identifier: GPL-2.0-only
2015-03-11 16:39:39 +03:00
/*
* Copyright ( c ) 2015 - 2016 , Linaro Limited
*/
# define pr_fmt(fmt) "%s: " fmt, __func__
# include <linux/cdev.h>
2020-04-30 15:37:09 +03:00
# include <linux/cred.h>
2015-03-11 16:39:39 +03:00
# include <linux/fs.h>
# include <linux/idr.h>
# include <linux/module.h>
# include <linux/slab.h>
# include <linux/tee_drv.h>
# include <linux/uaccess.h>
2020-04-30 15:37:09 +03:00
# include <crypto/hash.h>
2020-11-13 08:20:21 +03:00
# include <crypto/sha1.h>
2015-03-11 16:39:39 +03:00
# include "tee_private.h"
# define TEE_NUM_DEVICES 32
# define TEE_IOCTL_PARAM_SIZE(x) (sizeof(struct tee_param) * (x))
2020-04-30 15:37:09 +03:00
# define TEE_UUID_NS_NAME_SIZE 128
/*
* TEE Client UUID name space identifier ( UUIDv4 )
*
* Value here is random UUID that is allocated as name space identifier for
* forming Client UUID ' s for TEE environment using UUIDv5 scheme .
*/
static const uuid_t tee_client_uuid_ns = UUID_INIT ( 0x58ac9ca0 , 0x2086 , 0x4683 ,
0xa1 , 0xb8 , 0xec , 0x4b ,
0xc0 , 0x8e , 0x01 , 0xb6 ) ;
2015-03-11 16:39:39 +03:00
/*
* Unprivileged devices in the lower half range and privileged devices in
* the upper half range .
*/
static DECLARE_BITMAP ( dev_mask , TEE_NUM_DEVICES ) ;
static DEFINE_SPINLOCK ( driver_lock ) ;
static struct class * tee_class ;
static dev_t tee_devt ;
2021-10-04 17:11:52 +03:00
struct tee_context * teedev_open ( struct tee_device * teedev )
2015-03-11 16:39:39 +03:00
{
int rc ;
struct tee_context * ctx ;
if ( ! tee_device_get ( teedev ) )
2018-07-09 09:15:49 +03:00
return ERR_PTR ( - EINVAL ) ;
2015-03-11 16:39:39 +03:00
ctx = kzalloc ( sizeof ( * ctx ) , GFP_KERNEL ) ;
if ( ! ctx ) {
rc = - ENOMEM ;
goto err ;
}
2017-11-29 15:48:37 +03:00
kref_init ( & ctx - > refcount ) ;
2015-03-11 16:39:39 +03:00
ctx - > teedev = teedev ;
rc = teedev - > desc - > ops - > open ( ctx ) ;
if ( rc )
goto err ;
2018-07-09 09:15:49 +03:00
return ctx ;
2015-03-11 16:39:39 +03:00
err :
kfree ( ctx ) ;
tee_device_put ( teedev ) ;
2018-07-09 09:15:49 +03:00
return ERR_PTR ( rc ) ;
2015-03-11 16:39:39 +03:00
}
2021-10-04 17:11:52 +03:00
EXPORT_SYMBOL_GPL ( teedev_open ) ;
2015-03-11 16:39:39 +03:00
2017-11-29 15:48:37 +03:00
void teedev_ctx_get ( struct tee_context * ctx )
2015-03-11 16:39:39 +03:00
{
2017-11-29 15:48:37 +03:00
if ( ctx - > releasing )
return ;
2015-03-11 16:39:39 +03:00
2017-11-29 15:48:37 +03:00
kref_get ( & ctx - > refcount ) ;
}
static void teedev_ctx_release ( struct kref * ref )
{
struct tee_context * ctx = container_of ( ref , struct tee_context ,
refcount ) ;
ctx - > releasing = true ;
2015-03-11 16:39:39 +03:00
ctx - > teedev - > desc - > ops - > release ( ctx ) ;
kfree ( ctx ) ;
2017-11-29 15:48:37 +03:00
}
void teedev_ctx_put ( struct tee_context * ctx )
{
if ( ctx - > releasing )
return ;
kref_put ( & ctx - > refcount , teedev_ctx_release ) ;
}
2021-10-04 17:11:52 +03:00
void teedev_close_context ( struct tee_context * ctx )
2017-11-29 15:48:37 +03:00
{
2021-06-15 23:23:50 +03:00
struct tee_device * teedev = ctx - > teedev ;
2017-11-29 15:48:37 +03:00
teedev_ctx_put ( ctx ) ;
2021-06-15 23:23:50 +03:00
tee_device_put ( teedev ) ;
2017-11-29 15:48:37 +03:00
}
2021-10-04 17:11:52 +03:00
EXPORT_SYMBOL_GPL ( teedev_close_context ) ;
2017-11-29 15:48:37 +03:00
2018-07-09 09:15:49 +03:00
static int tee_open ( struct inode * inode , struct file * filp )
{
struct tee_context * ctx ;
ctx = teedev_open ( container_of ( inode - > i_cdev , struct tee_device , cdev ) ) ;
if ( IS_ERR ( ctx ) )
return PTR_ERR ( ctx ) ;
2019-01-29 08:49:36 +03:00
/*
* Default user - space behaviour is to wait for tee - supplicant
* if not present for any requests in this context .
*/
ctx - > supp_nowait = false ;
2018-07-09 09:15:49 +03:00
filp - > private_data = ctx ;
return 0 ;
}
2017-11-29 15:48:37 +03:00
static int tee_release ( struct inode * inode , struct file * filp )
{
teedev_close_context ( filp - > private_data ) ;
2015-03-11 16:39:39 +03:00
return 0 ;
}
2020-04-30 15:37:09 +03:00
/**
* uuid_v5 ( ) - Calculate UUIDv5
* @ uuid : Resulting UUID
* @ ns : Name space ID for UUIDv5 function
* @ name : Name for UUIDv5 function
* @ size : Size of name
*
* UUIDv5 is specific in RFC 4122.
*
* This implements section ( for SHA - 1 ) :
* 4.3 . Algorithm for Creating a Name - Based UUID
*/
static int uuid_v5 ( uuid_t * uuid , const uuid_t * ns , const void * name ,
size_t size )
{
unsigned char hash [ SHA1_DIGEST_SIZE ] ;
struct crypto_shash * shash = NULL ;
struct shash_desc * desc = NULL ;
int rc ;
shash = crypto_alloc_shash ( " sha1 " , 0 , 0 ) ;
if ( IS_ERR ( shash ) ) {
rc = PTR_ERR ( shash ) ;
pr_err ( " shash(sha1) allocation failed \n " ) ;
return rc ;
}
desc = kzalloc ( sizeof ( * desc ) + crypto_shash_descsize ( shash ) ,
GFP_KERNEL ) ;
if ( ! desc ) {
rc = - ENOMEM ;
goto out_free_shash ;
}
desc - > tfm = shash ;
rc = crypto_shash_init ( desc ) ;
if ( rc < 0 )
goto out_free_desc ;
rc = crypto_shash_update ( desc , ( const u8 * ) ns , sizeof ( * ns ) ) ;
if ( rc < 0 )
goto out_free_desc ;
rc = crypto_shash_update ( desc , ( const u8 * ) name , size ) ;
if ( rc < 0 )
goto out_free_desc ;
rc = crypto_shash_final ( desc , hash ) ;
if ( rc < 0 )
goto out_free_desc ;
memcpy ( uuid - > b , hash , UUID_SIZE ) ;
/* Tag for version 5 */
uuid - > b [ 6 ] = ( hash [ 6 ] & 0x0F ) | 0x50 ;
uuid - > b [ 8 ] = ( hash [ 8 ] & 0x3F ) | 0x80 ;
out_free_desc :
kfree ( desc ) ;
out_free_shash :
crypto_free_shash ( shash ) ;
return rc ;
}
int tee_session_calc_client_uuid ( uuid_t * uuid , u32 connection_method ,
const u8 connection_data [ TEE_IOCTL_UUID_LEN ] )
{
gid_t ns_grp = ( gid_t ) - 1 ;
kgid_t grp = INVALID_GID ;
char * name = NULL ;
int name_len ;
int rc ;
2020-09-17 16:40:22 +03:00
if ( connection_method = = TEE_IOCTL_LOGIN_PUBLIC | |
connection_method = = TEE_IOCTL_LOGIN_REE_KERNEL ) {
2020-04-30 15:37:09 +03:00
/* Nil UUID to be passed to TEE environment */
uuid_copy ( uuid , & uuid_null ) ;
return 0 ;
}
/*
* In Linux environment client UUID is based on UUIDv5 .
*
* Determine client UUID with following semantics for ' name ' :
*
* For TEEC_LOGIN_USER :
* uid = < uid >
*
* For TEEC_LOGIN_GROUP :
* gid = < gid >
*
*/
name = kzalloc ( TEE_UUID_NS_NAME_SIZE , GFP_KERNEL ) ;
if ( ! name )
return - ENOMEM ;
switch ( connection_method ) {
case TEE_IOCTL_LOGIN_USER :
name_len = snprintf ( name , TEE_UUID_NS_NAME_SIZE , " uid=%x " ,
current_euid ( ) . val ) ;
if ( name_len > = TEE_UUID_NS_NAME_SIZE ) {
rc = - E2BIG ;
goto out_free_name ;
}
break ;
case TEE_IOCTL_LOGIN_GROUP :
memcpy ( & ns_grp , connection_data , sizeof ( gid_t ) ) ;
grp = make_kgid ( current_user_ns ( ) , ns_grp ) ;
if ( ! gid_valid ( grp ) | | ! in_egroup_p ( grp ) ) {
rc = - EPERM ;
goto out_free_name ;
}
name_len = snprintf ( name , TEE_UUID_NS_NAME_SIZE , " gid=%x " ,
grp . val ) ;
if ( name_len > = TEE_UUID_NS_NAME_SIZE ) {
rc = - E2BIG ;
goto out_free_name ;
}
break ;
default :
rc = - EINVAL ;
goto out_free_name ;
}
rc = uuid_v5 ( uuid , & tee_client_uuid_ns , name , name_len ) ;
out_free_name :
kfree ( name ) ;
return rc ;
}
EXPORT_SYMBOL_GPL ( tee_session_calc_client_uuid ) ;
2015-03-11 16:39:39 +03:00
static int tee_ioctl_version ( struct tee_context * ctx ,
struct tee_ioctl_version_data __user * uvers )
{
struct tee_ioctl_version_data vers ;
ctx - > teedev - > desc - > ops - > get_version ( ctx - > teedev , & vers ) ;
2017-02-16 11:07:02 +03:00
if ( ctx - > teedev - > desc - > flags & TEE_DESC_PRIVILEGED )
vers . gen_caps | = TEE_GEN_CAP_PRIVILEGED ;
2015-03-11 16:39:39 +03:00
if ( copy_to_user ( uvers , & vers , sizeof ( vers ) ) )
return - EFAULT ;
2017-02-16 11:07:02 +03:00
2015-03-11 16:39:39 +03:00
return 0 ;
}
static int tee_ioctl_shm_alloc ( struct tee_context * ctx ,
struct tee_ioctl_shm_alloc_data __user * udata )
{
long ret ;
struct tee_ioctl_shm_alloc_data data ;
struct tee_shm * shm ;
if ( copy_from_user ( & data , udata , sizeof ( data ) ) )
return - EFAULT ;
/* Currently no input flags are supported */
if ( data . flags )
return - EINVAL ;
shm = tee_shm_alloc ( ctx , data . size , TEE_SHM_MAPPED | TEE_SHM_DMA_BUF ) ;
if ( IS_ERR ( shm ) )
return PTR_ERR ( shm ) ;
data . id = shm - > id ;
data . flags = shm - > flags ;
data . size = shm - > size ;
if ( copy_to_user ( udata , & data , sizeof ( data ) ) )
ret = - EFAULT ;
else
ret = tee_shm_get_fd ( shm ) ;
/*
* When user space closes the file descriptor the shared memory
* should be freed or if tee_shm_get_fd ( ) failed then it will
* be freed immediately .
*/
tee_shm_put ( shm ) ;
return ret ;
}
2017-11-29 15:48:26 +03:00
static int
tee_ioctl_shm_register ( struct tee_context * ctx ,
struct tee_ioctl_shm_register_data __user * udata )
{
long ret ;
struct tee_ioctl_shm_register_data data ;
struct tee_shm * shm ;
if ( copy_from_user ( & data , udata , sizeof ( data ) ) )
return - EFAULT ;
/* Currently no input flags are supported */
if ( data . flags )
return - EINVAL ;
shm = tee_shm_register ( ctx , data . addr , data . length ,
TEE_SHM_DMA_BUF | TEE_SHM_USER_MAPPED ) ;
if ( IS_ERR ( shm ) )
return PTR_ERR ( shm ) ;
data . id = shm - > id ;
data . flags = shm - > flags ;
data . length = shm - > size ;
if ( copy_to_user ( udata , & data , sizeof ( data ) ) )
ret = - EFAULT ;
else
ret = tee_shm_get_fd ( shm ) ;
/*
* When user space closes the file descriptor the shared memory
* should be freed or if tee_shm_get_fd ( ) failed then it will
* be freed immediately .
*/
tee_shm_put ( shm ) ;
return ret ;
}
2015-03-11 16:39:39 +03:00
static int params_from_user ( struct tee_context * ctx , struct tee_param * params ,
size_t num_params ,
struct tee_ioctl_param __user * uparams )
{
size_t n ;
for ( n = 0 ; n < num_params ; n + + ) {
struct tee_shm * shm ;
struct tee_ioctl_param ip ;
if ( copy_from_user ( & ip , uparams + n , sizeof ( ip ) ) )
return - EFAULT ;
/* All unused attribute bits has to be zero */
2016-12-23 15:13:34 +03:00
if ( ip . attr & ~ TEE_IOCTL_PARAM_ATTR_MASK )
2015-03-11 16:39:39 +03:00
return - EINVAL ;
params [ n ] . attr = ip . attr ;
2016-12-23 15:13:34 +03:00
switch ( ip . attr & TEE_IOCTL_PARAM_ATTR_TYPE_MASK ) {
2015-03-11 16:39:39 +03:00
case TEE_IOCTL_PARAM_ATTR_TYPE_NONE :
case TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_OUTPUT :
break ;
case TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT :
case TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INOUT :
params [ n ] . u . value . a = ip . a ;
params [ n ] . u . value . b = ip . b ;
params [ n ] . u . value . c = ip . c ;
break ;
case TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INPUT :
case TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_OUTPUT :
case TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INOUT :
/*
2019-03-04 10:54:23 +03:00
* If a NULL pointer is passed to a TA in the TEE ,
* the ip . c IOCTL parameters is set to TEE_MEMREF_NULL
* indicating a NULL memory reference .
2015-03-11 16:39:39 +03:00
*/
2019-03-04 10:54:23 +03:00
if ( ip . c ! = TEE_MEMREF_NULL ) {
/*
* If we fail to get a pointer to a shared
* memory object ( and increase the ref count )
* from an identifier we return an error . All
* pointers that has been added in params have
* an increased ref count . It ' s the callers
* responibility to do tee_shm_put ( ) on all
* resolved pointers .
*/
shm = tee_shm_get_from_id ( ctx , ip . c ) ;
if ( IS_ERR ( shm ) )
return PTR_ERR ( shm ) ;
/*
* Ensure offset + size does not overflow
* offset and does not overflow the size of
* the referred shared memory object .
*/
if ( ( ip . a + ip . b ) < ip . a | |
( ip . a + ip . b ) > shm - > size ) {
tee_shm_put ( shm ) ;
return - EINVAL ;
}
} else if ( ctx - > cap_memref_null ) {
/* Pass NULL pointer to OP-TEE */
shm = NULL ;
} else {
2018-04-29 15:22:29 +03:00
return - EINVAL ;
}
2015-03-11 16:39:39 +03:00
params [ n ] . u . memref . shm_offs = ip . a ;
params [ n ] . u . memref . size = ip . b ;
params [ n ] . u . memref . shm = shm ;
break ;
default :
/* Unknown attribute */
return - EINVAL ;
}
}
return 0 ;
}
static int params_to_user ( struct tee_ioctl_param __user * uparams ,
size_t num_params , struct tee_param * params )
{
size_t n ;
for ( n = 0 ; n < num_params ; n + + ) {
struct tee_ioctl_param __user * up = uparams + n ;
struct tee_param * p = params + n ;
switch ( p - > attr ) {
case TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_OUTPUT :
case TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INOUT :
if ( put_user ( p - > u . value . a , & up - > a ) | |
put_user ( p - > u . value . b , & up - > b ) | |
put_user ( p - > u . value . c , & up - > c ) )
return - EFAULT ;
break ;
case TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_OUTPUT :
case TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INOUT :
if ( put_user ( ( u64 ) p - > u . memref . size , & up - > b ) )
return - EFAULT ;
2020-11-20 21:33:19 +03:00
break ;
2015-03-11 16:39:39 +03:00
default :
break ;
}
}
return 0 ;
}
static int tee_ioctl_open_session ( struct tee_context * ctx ,
struct tee_ioctl_buf_data __user * ubuf )
{
int rc ;
size_t n ;
struct tee_ioctl_buf_data buf ;
struct tee_ioctl_open_session_arg __user * uarg ;
struct tee_ioctl_open_session_arg arg ;
struct tee_ioctl_param __user * uparams = NULL ;
struct tee_param * params = NULL ;
bool have_session = false ;
if ( ! ctx - > teedev - > desc - > ops - > open_session )
return - EINVAL ;
if ( copy_from_user ( & buf , ubuf , sizeof ( buf ) ) )
return - EFAULT ;
if ( buf . buf_len > TEE_MAX_ARG_SIZE | |
buf . buf_len < sizeof ( struct tee_ioctl_open_session_arg ) )
return - EINVAL ;
uarg = u64_to_user_ptr ( buf . buf_ptr ) ;
if ( copy_from_user ( & arg , uarg , sizeof ( arg ) ) )
return - EFAULT ;
if ( sizeof ( arg ) + TEE_IOCTL_PARAM_SIZE ( arg . num_params ) ! = buf . buf_len )
return - EINVAL ;
if ( arg . num_params ) {
params = kcalloc ( arg . num_params , sizeof ( struct tee_param ) ,
GFP_KERNEL ) ;
if ( ! params )
return - ENOMEM ;
uparams = uarg - > params ;
rc = params_from_user ( ctx , params , arg . num_params , uparams ) ;
if ( rc )
goto out ;
}
2020-03-27 08:29:48 +03:00
if ( arg . clnt_login > = TEE_IOCTL_LOGIN_REE_KERNEL_MIN & &
arg . clnt_login < = TEE_IOCTL_LOGIN_REE_KERNEL_MAX ) {
pr_debug ( " login method not allowed for user-space client \n " ) ;
rc = - EPERM ;
goto out ;
}
2015-03-11 16:39:39 +03:00
rc = ctx - > teedev - > desc - > ops - > open_session ( ctx , & arg , params ) ;
if ( rc )
goto out ;
have_session = true ;
if ( put_user ( arg . session , & uarg - > session ) | |
put_user ( arg . ret , & uarg - > ret ) | |
put_user ( arg . ret_origin , & uarg - > ret_origin ) ) {
rc = - EFAULT ;
goto out ;
}
rc = params_to_user ( uparams , arg . num_params , params ) ;
out :
/*
* If we ' ve succeeded to open the session but failed to communicate
* it back to user space , close the session again to avoid leakage .
*/
if ( rc & & have_session & & ctx - > teedev - > desc - > ops - > close_session )
ctx - > teedev - > desc - > ops - > close_session ( ctx , arg . session ) ;
if ( params ) {
/* Decrease ref count for all valid shared memory pointers */
for ( n = 0 ; n < arg . num_params ; n + + )
2016-12-23 15:13:27 +03:00
if ( tee_param_is_memref ( params + n ) & &
2015-03-11 16:39:39 +03:00
params [ n ] . u . memref . shm )
tee_shm_put ( params [ n ] . u . memref . shm ) ;
kfree ( params ) ;
}
return rc ;
}
static int tee_ioctl_invoke ( struct tee_context * ctx ,
struct tee_ioctl_buf_data __user * ubuf )
{
int rc ;
size_t n ;
struct tee_ioctl_buf_data buf ;
struct tee_ioctl_invoke_arg __user * uarg ;
struct tee_ioctl_invoke_arg arg ;
struct tee_ioctl_param __user * uparams = NULL ;
struct tee_param * params = NULL ;
if ( ! ctx - > teedev - > desc - > ops - > invoke_func )
return - EINVAL ;
if ( copy_from_user ( & buf , ubuf , sizeof ( buf ) ) )
return - EFAULT ;
if ( buf . buf_len > TEE_MAX_ARG_SIZE | |
buf . buf_len < sizeof ( struct tee_ioctl_invoke_arg ) )
return - EINVAL ;
uarg = u64_to_user_ptr ( buf . buf_ptr ) ;
if ( copy_from_user ( & arg , uarg , sizeof ( arg ) ) )
return - EFAULT ;
if ( sizeof ( arg ) + TEE_IOCTL_PARAM_SIZE ( arg . num_params ) ! = buf . buf_len )
return - EINVAL ;
if ( arg . num_params ) {
params = kcalloc ( arg . num_params , sizeof ( struct tee_param ) ,
GFP_KERNEL ) ;
if ( ! params )
return - ENOMEM ;
uparams = uarg - > params ;
rc = params_from_user ( ctx , params , arg . num_params , uparams ) ;
if ( rc )
goto out ;
}
rc = ctx - > teedev - > desc - > ops - > invoke_func ( ctx , & arg , params ) ;
if ( rc )
goto out ;
if ( put_user ( arg . ret , & uarg - > ret ) | |
put_user ( arg . ret_origin , & uarg - > ret_origin ) ) {
rc = - EFAULT ;
goto out ;
}
rc = params_to_user ( uparams , arg . num_params , params ) ;
out :
if ( params ) {
/* Decrease ref count for all valid shared memory pointers */
for ( n = 0 ; n < arg . num_params ; n + + )
2016-12-23 15:13:27 +03:00
if ( tee_param_is_memref ( params + n ) & &
2015-03-11 16:39:39 +03:00
params [ n ] . u . memref . shm )
tee_shm_put ( params [ n ] . u . memref . shm ) ;
kfree ( params ) ;
}
return rc ;
}
static int tee_ioctl_cancel ( struct tee_context * ctx ,
struct tee_ioctl_cancel_arg __user * uarg )
{
struct tee_ioctl_cancel_arg arg ;
if ( ! ctx - > teedev - > desc - > ops - > cancel_req )
return - EINVAL ;
if ( copy_from_user ( & arg , uarg , sizeof ( arg ) ) )
return - EFAULT ;
return ctx - > teedev - > desc - > ops - > cancel_req ( ctx , arg . cancel_id ,
arg . session ) ;
}
static int
tee_ioctl_close_session ( struct tee_context * ctx ,
struct tee_ioctl_close_session_arg __user * uarg )
{
struct tee_ioctl_close_session_arg arg ;
if ( ! ctx - > teedev - > desc - > ops - > close_session )
return - EINVAL ;
if ( copy_from_user ( & arg , uarg , sizeof ( arg ) ) )
return - EFAULT ;
return ctx - > teedev - > desc - > ops - > close_session ( ctx , arg . session ) ;
}
static int params_to_supp ( struct tee_context * ctx ,
struct tee_ioctl_param __user * uparams ,
size_t num_params , struct tee_param * params )
{
size_t n ;
for ( n = 0 ; n < num_params ; n + + ) {
struct tee_ioctl_param ip ;
struct tee_param * p = params + n ;
2016-12-23 15:13:34 +03:00
ip . attr = p - > attr ;
switch ( p - > attr & TEE_IOCTL_PARAM_ATTR_TYPE_MASK ) {
2015-03-11 16:39:39 +03:00
case TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT :
case TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INOUT :
ip . a = p - > u . value . a ;
ip . b = p - > u . value . b ;
ip . c = p - > u . value . c ;
break ;
case TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INPUT :
case TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_OUTPUT :
case TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INOUT :
ip . b = p - > u . memref . size ;
if ( ! p - > u . memref . shm ) {
ip . a = 0 ;
ip . c = ( u64 ) - 1 ; /* invalid shm id */
break ;
}
ip . a = p - > u . memref . shm_offs ;
ip . c = p - > u . memref . shm - > id ;
break ;
default :
ip . a = 0 ;
ip . b = 0 ;
ip . c = 0 ;
break ;
}
if ( copy_to_user ( uparams + n , & ip , sizeof ( ip ) ) )
return - EFAULT ;
}
return 0 ;
}
static int tee_ioctl_supp_recv ( struct tee_context * ctx ,
struct tee_ioctl_buf_data __user * ubuf )
{
int rc ;
struct tee_ioctl_buf_data buf ;
struct tee_iocl_supp_recv_arg __user * uarg ;
struct tee_param * params ;
u32 num_params ;
u32 func ;
if ( ! ctx - > teedev - > desc - > ops - > supp_recv )
return - EINVAL ;
if ( copy_from_user ( & buf , ubuf , sizeof ( buf ) ) )
return - EFAULT ;
if ( buf . buf_len > TEE_MAX_ARG_SIZE | |
buf . buf_len < sizeof ( struct tee_iocl_supp_recv_arg ) )
return - EINVAL ;
uarg = u64_to_user_ptr ( buf . buf_ptr ) ;
if ( get_user ( num_params , & uarg - > num_params ) )
return - EFAULT ;
if ( sizeof ( * uarg ) + TEE_IOCTL_PARAM_SIZE ( num_params ) ! = buf . buf_len )
return - EINVAL ;
params = kcalloc ( num_params , sizeof ( struct tee_param ) , GFP_KERNEL ) ;
if ( ! params )
return - ENOMEM ;
2016-12-23 15:13:34 +03:00
rc = params_from_user ( ctx , params , num_params , uarg - > params ) ;
if ( rc )
goto out ;
2015-03-11 16:39:39 +03:00
rc = ctx - > teedev - > desc - > ops - > supp_recv ( ctx , & func , & num_params , params ) ;
if ( rc )
goto out ;
if ( put_user ( func , & uarg - > func ) | |
put_user ( num_params , & uarg - > num_params ) ) {
rc = - EFAULT ;
goto out ;
}
rc = params_to_supp ( ctx , uarg - > params , num_params , params ) ;
out :
kfree ( params ) ;
return rc ;
}
static int params_from_supp ( struct tee_param * params , size_t num_params ,
struct tee_ioctl_param __user * uparams )
{
size_t n ;
for ( n = 0 ; n < num_params ; n + + ) {
struct tee_param * p = params + n ;
struct tee_ioctl_param ip ;
if ( copy_from_user ( & ip , uparams + n , sizeof ( ip ) ) )
return - EFAULT ;
/* All unused attribute bits has to be zero */
2016-12-23 15:13:34 +03:00
if ( ip . attr & ~ TEE_IOCTL_PARAM_ATTR_MASK )
2015-03-11 16:39:39 +03:00
return - EINVAL ;
p - > attr = ip . attr ;
2016-12-23 15:13:34 +03:00
switch ( ip . attr & TEE_IOCTL_PARAM_ATTR_TYPE_MASK ) {
2015-03-11 16:39:39 +03:00
case TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_OUTPUT :
case TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INOUT :
/* Only out and in/out values can be updated */
p - > u . value . a = ip . a ;
p - > u . value . b = ip . b ;
p - > u . value . c = ip . c ;
break ;
case TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_OUTPUT :
case TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INOUT :
/*
* Only the size of the memref can be updated .
* Since we don ' t have access to the original
* parameters here , only store the supplied size .
* The driver will copy the updated size into the
* original parameters .
*/
p - > u . memref . shm = NULL ;
p - > u . memref . shm_offs = 0 ;
p - > u . memref . size = ip . b ;
break ;
default :
memset ( & p - > u , 0 , sizeof ( p - > u ) ) ;
break ;
}
}
return 0 ;
}
static int tee_ioctl_supp_send ( struct tee_context * ctx ,
struct tee_ioctl_buf_data __user * ubuf )
{
long rc ;
struct tee_ioctl_buf_data buf ;
struct tee_iocl_supp_send_arg __user * uarg ;
struct tee_param * params ;
u32 num_params ;
u32 ret ;
/* Not valid for this driver */
if ( ! ctx - > teedev - > desc - > ops - > supp_send )
return - EINVAL ;
if ( copy_from_user ( & buf , ubuf , sizeof ( buf ) ) )
return - EFAULT ;
if ( buf . buf_len > TEE_MAX_ARG_SIZE | |
buf . buf_len < sizeof ( struct tee_iocl_supp_send_arg ) )
return - EINVAL ;
uarg = u64_to_user_ptr ( buf . buf_ptr ) ;
if ( get_user ( ret , & uarg - > ret ) | |
get_user ( num_params , & uarg - > num_params ) )
return - EFAULT ;
if ( sizeof ( * uarg ) + TEE_IOCTL_PARAM_SIZE ( num_params ) > buf . buf_len )
return - EINVAL ;
params = kcalloc ( num_params , sizeof ( struct tee_param ) , GFP_KERNEL ) ;
if ( ! params )
return - ENOMEM ;
rc = params_from_supp ( params , num_params , uarg - > params ) ;
if ( rc )
goto out ;
rc = ctx - > teedev - > desc - > ops - > supp_send ( ctx , ret , num_params , params ) ;
out :
kfree ( params ) ;
return rc ;
}
static long tee_ioctl ( struct file * filp , unsigned int cmd , unsigned long arg )
{
struct tee_context * ctx = filp - > private_data ;
void __user * uarg = ( void __user * ) arg ;
switch ( cmd ) {
case TEE_IOC_VERSION :
return tee_ioctl_version ( ctx , uarg ) ;
case TEE_IOC_SHM_ALLOC :
return tee_ioctl_shm_alloc ( ctx , uarg ) ;
2017-11-29 15:48:26 +03:00
case TEE_IOC_SHM_REGISTER :
return tee_ioctl_shm_register ( ctx , uarg ) ;
2015-03-11 16:39:39 +03:00
case TEE_IOC_OPEN_SESSION :
return tee_ioctl_open_session ( ctx , uarg ) ;
case TEE_IOC_INVOKE :
return tee_ioctl_invoke ( ctx , uarg ) ;
case TEE_IOC_CANCEL :
return tee_ioctl_cancel ( ctx , uarg ) ;
case TEE_IOC_CLOSE_SESSION :
return tee_ioctl_close_session ( ctx , uarg ) ;
case TEE_IOC_SUPPL_RECV :
return tee_ioctl_supp_recv ( ctx , uarg ) ;
case TEE_IOC_SUPPL_SEND :
return tee_ioctl_supp_send ( ctx , uarg ) ;
default :
return - EINVAL ;
}
}
static const struct file_operations tee_fops = {
. owner = THIS_MODULE ,
. open = tee_open ,
. release = tee_release ,
. unlocked_ioctl = tee_ioctl ,
2018-09-11 22:59:08 +03:00
. compat_ioctl = compat_ptr_ioctl ,
2015-03-11 16:39:39 +03:00
} ;
static void tee_release_device ( struct device * dev )
{
struct tee_device * teedev = container_of ( dev , struct tee_device , dev ) ;
spin_lock ( & driver_lock ) ;
clear_bit ( teedev - > id , dev_mask ) ;
spin_unlock ( & driver_lock ) ;
mutex_destroy ( & teedev - > mutex ) ;
idr_destroy ( & teedev - > idr ) ;
kfree ( teedev ) ;
}
/**
* tee_device_alloc ( ) - Allocate a new struct tee_device instance
* @ teedesc : Descriptor for this driver
* @ dev : Parent device for this device
* @ pool : Shared memory pool , NULL if not used
* @ driver_data : Private driver data for this device
*
* Allocates a new struct tee_device instance . The device is
* removed by tee_device_unregister ( ) .
*
* @ returns a pointer to a ' struct tee_device ' or an ERR_PTR on failure
*/
struct tee_device * tee_device_alloc ( const struct tee_desc * teedesc ,
struct device * dev ,
struct tee_shm_pool * pool ,
void * driver_data )
{
struct tee_device * teedev ;
void * ret ;
2018-01-15 12:27:35 +03:00
int rc , max_id ;
2015-03-11 16:39:39 +03:00
int offs = 0 ;
if ( ! teedesc | | ! teedesc - > name | | ! teedesc - > ops | |
! teedesc - > ops - > get_version | | ! teedesc - > ops - > open | |
! teedesc - > ops - > release | | ! pool )
return ERR_PTR ( - EINVAL ) ;
teedev = kzalloc ( sizeof ( * teedev ) , GFP_KERNEL ) ;
if ( ! teedev ) {
ret = ERR_PTR ( - ENOMEM ) ;
goto err ;
}
2018-01-15 12:27:35 +03:00
max_id = TEE_NUM_DEVICES / 2 ;
if ( teedesc - > flags & TEE_DESC_PRIVILEGED ) {
2015-03-11 16:39:39 +03:00
offs = TEE_NUM_DEVICES / 2 ;
2018-01-15 12:27:35 +03:00
max_id = TEE_NUM_DEVICES ;
}
2015-03-11 16:39:39 +03:00
spin_lock ( & driver_lock ) ;
2018-01-15 12:27:35 +03:00
teedev - > id = find_next_zero_bit ( dev_mask , max_id , offs ) ;
if ( teedev - > id < max_id )
2015-03-11 16:39:39 +03:00
set_bit ( teedev - > id , dev_mask ) ;
spin_unlock ( & driver_lock ) ;
2018-01-15 12:27:35 +03:00
if ( teedev - > id > = max_id ) {
2015-03-11 16:39:39 +03:00
ret = ERR_PTR ( - ENOMEM ) ;
goto err ;
}
snprintf ( teedev - > name , sizeof ( teedev - > name ) , " tee%s%d " ,
teedesc - > flags & TEE_DESC_PRIVILEGED ? " priv " : " " ,
teedev - > id - offs ) ;
teedev - > dev . class = tee_class ;
teedev - > dev . release = tee_release_device ;
teedev - > dev . parent = dev ;
teedev - > dev . devt = MKDEV ( MAJOR ( tee_devt ) , teedev - > id ) ;
rc = dev_set_name ( & teedev - > dev , " %s " , teedev - > name ) ;
if ( rc ) {
ret = ERR_PTR ( rc ) ;
goto err_devt ;
}
cdev_init ( & teedev - > cdev , & tee_fops ) ;
teedev - > cdev . owner = teedesc - > owner ;
dev_set_drvdata ( & teedev - > dev , driver_data ) ;
device_initialize ( & teedev - > dev ) ;
/* 1 as tee_device_unregister() does one final tee_device_put() */
teedev - > num_users = 1 ;
init_completion ( & teedev - > c_no_users ) ;
mutex_init ( & teedev - > mutex ) ;
idr_init ( & teedev - > idr ) ;
teedev - > desc = teedesc ;
teedev - > pool = pool ;
return teedev ;
err_devt :
unregister_chrdev_region ( teedev - > dev . devt , 1 ) ;
err :
pr_err ( " could not register %s driver \n " ,
teedesc - > flags & TEE_DESC_PRIVILEGED ? " privileged " : " client " ) ;
if ( teedev & & teedev - > id < TEE_NUM_DEVICES ) {
spin_lock ( & driver_lock ) ;
clear_bit ( teedev - > id , dev_mask ) ;
spin_unlock ( & driver_lock ) ;
}
kfree ( teedev ) ;
return ret ;
}
EXPORT_SYMBOL_GPL ( tee_device_alloc ) ;
static ssize_t implementation_id_show ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
struct tee_device * teedev = container_of ( dev , struct tee_device , dev ) ;
struct tee_ioctl_version_data vers ;
teedev - > desc - > ops - > get_version ( teedev , & vers ) ;
return scnprintf ( buf , PAGE_SIZE , " %d \n " , vers . impl_id ) ;
}
static DEVICE_ATTR_RO ( implementation_id ) ;
static struct attribute * tee_dev_attrs [ ] = {
& dev_attr_implementation_id . attr ,
NULL
} ;
2020-09-01 13:33:35 +03:00
ATTRIBUTE_GROUPS ( tee_dev ) ;
2015-03-11 16:39:39 +03:00
/**
* tee_device_register ( ) - Registers a TEE device
* @ teedev : Device to register
*
* tee_device_unregister ( ) need to be called to remove the @ teedev if
* this function fails .
*
* @ returns < 0 on failure
*/
int tee_device_register ( struct tee_device * teedev )
{
int rc ;
if ( teedev - > flags & TEE_DEVICE_FLAG_REGISTERED ) {
dev_err ( & teedev - > dev , " attempt to register twice \n " ) ;
return - EINVAL ;
}
2020-09-01 13:33:35 +03:00
teedev - > dev . groups = tee_dev_groups ;
2015-03-11 16:39:39 +03:00
2020-09-01 13:33:34 +03:00
rc = cdev_device_add ( & teedev - > cdev , & teedev - > dev ) ;
2015-03-11 16:39:39 +03:00
if ( rc ) {
dev_err ( & teedev - > dev ,
2020-09-01 13:33:34 +03:00
" unable to cdev_device_add() %s, major %d, minor %d, err=%d \n " ,
2015-03-11 16:39:39 +03:00
teedev - > name , MAJOR ( teedev - > dev . devt ) ,
MINOR ( teedev - > dev . devt ) , rc ) ;
return rc ;
}
teedev - > flags | = TEE_DEVICE_FLAG_REGISTERED ;
return 0 ;
}
EXPORT_SYMBOL_GPL ( tee_device_register ) ;
void tee_device_put ( struct tee_device * teedev )
{
mutex_lock ( & teedev - > mutex ) ;
/* Shouldn't put in this state */
if ( ! WARN_ON ( ! teedev - > desc ) ) {
teedev - > num_users - - ;
if ( ! teedev - > num_users ) {
teedev - > desc = NULL ;
complete ( & teedev - > c_no_users ) ;
}
}
mutex_unlock ( & teedev - > mutex ) ;
}
bool tee_device_get ( struct tee_device * teedev )
{
mutex_lock ( & teedev - > mutex ) ;
if ( ! teedev - > desc ) {
mutex_unlock ( & teedev - > mutex ) ;
return false ;
}
teedev - > num_users + + ;
mutex_unlock ( & teedev - > mutex ) ;
return true ;
}
/**
* tee_device_unregister ( ) - Removes a TEE device
* @ teedev : Device to unregister
*
* This function should be called to remove the @ teedev even if
* tee_device_register ( ) hasn ' t been called yet . Does nothing if
* @ teedev is NULL .
*/
void tee_device_unregister ( struct tee_device * teedev )
{
if ( ! teedev )
return ;
2020-09-01 13:33:35 +03:00
if ( teedev - > flags & TEE_DEVICE_FLAG_REGISTERED )
2020-09-01 13:33:34 +03:00
cdev_device_del ( & teedev - > cdev , & teedev - > dev ) ;
2015-03-11 16:39:39 +03:00
tee_device_put ( teedev ) ;
wait_for_completion ( & teedev - > c_no_users ) ;
/*
* No need to take a mutex any longer now since teedev - > desc was
* set to NULL before teedev - > c_no_users was completed .
*/
teedev - > pool = NULL ;
put_device ( & teedev - > dev ) ;
}
EXPORT_SYMBOL_GPL ( tee_device_unregister ) ;
/**
* tee_get_drvdata ( ) - Return driver_data pointer
* @ teedev : Device containing the driver_data pointer
* @ returns the driver_data pointer supplied to tee_register ( ) .
*/
void * tee_get_drvdata ( struct tee_device * teedev )
{
return dev_get_drvdata ( & teedev - > dev ) ;
}
EXPORT_SYMBOL_GPL ( tee_get_drvdata ) ;
2018-07-09 09:15:49 +03:00
struct match_dev_data {
struct tee_ioctl_version_data * vers ;
const void * data ;
int ( * match ) ( struct tee_ioctl_version_data * , const void * ) ;
} ;
static int match_dev ( struct device * dev , const void * data )
{
const struct match_dev_data * match_data = data ;
struct tee_device * teedev = container_of ( dev , struct tee_device , dev ) ;
teedev - > desc - > ops - > get_version ( teedev , match_data - > vers ) ;
return match_data - > match ( match_data - > vers , match_data - > data ) ;
}
struct tee_context *
tee_client_open_context ( struct tee_context * start ,
int ( * match ) ( struct tee_ioctl_version_data * ,
const void * ) ,
const void * data , struct tee_ioctl_version_data * vers )
{
struct device * dev = NULL ;
struct device * put_dev = NULL ;
struct tee_context * ctx = NULL ;
struct tee_ioctl_version_data v ;
struct match_dev_data match_data = { vers ? vers : & v , data , match } ;
if ( start )
dev = & start - > teedev - > dev ;
do {
dev = class_find_device ( tee_class , dev , & match_data , match_dev ) ;
if ( ! dev ) {
ctx = ERR_PTR ( - ENOENT ) ;
break ;
}
put_device ( put_dev ) ;
put_dev = dev ;
ctx = teedev_open ( container_of ( dev , struct tee_device , dev ) ) ;
} while ( IS_ERR ( ctx ) & & PTR_ERR ( ctx ) ! = - ENOMEM ) ;
put_device ( put_dev ) ;
2019-01-29 08:49:36 +03:00
/*
* Default behaviour for in kernel client is to not wait for
* tee - supplicant if not present for any requests in this context .
* Also this flag could be configured again before call to
* tee_client_open_session ( ) if any in kernel client requires
* different behaviour .
*/
2019-02-20 09:02:27 +03:00
if ( ! IS_ERR ( ctx ) )
ctx - > supp_nowait = true ;
2018-07-09 09:15:49 +03:00
return ctx ;
}
EXPORT_SYMBOL_GPL ( tee_client_open_context ) ;
void tee_client_close_context ( struct tee_context * ctx )
{
teedev_close_context ( ctx ) ;
}
EXPORT_SYMBOL_GPL ( tee_client_close_context ) ;
void tee_client_get_version ( struct tee_context * ctx ,
struct tee_ioctl_version_data * vers )
{
ctx - > teedev - > desc - > ops - > get_version ( ctx - > teedev , vers ) ;
}
EXPORT_SYMBOL_GPL ( tee_client_get_version ) ;
int tee_client_open_session ( struct tee_context * ctx ,
struct tee_ioctl_open_session_arg * arg ,
struct tee_param * param )
{
if ( ! ctx - > teedev - > desc - > ops - > open_session )
return - EINVAL ;
return ctx - > teedev - > desc - > ops - > open_session ( ctx , arg , param ) ;
}
EXPORT_SYMBOL_GPL ( tee_client_open_session ) ;
int tee_client_close_session ( struct tee_context * ctx , u32 session )
{
if ( ! ctx - > teedev - > desc - > ops - > close_session )
return - EINVAL ;
return ctx - > teedev - > desc - > ops - > close_session ( ctx , session ) ;
}
EXPORT_SYMBOL_GPL ( tee_client_close_session ) ;
int tee_client_invoke_func ( struct tee_context * ctx ,
struct tee_ioctl_invoke_arg * arg ,
struct tee_param * param )
{
if ( ! ctx - > teedev - > desc - > ops - > invoke_func )
return - EINVAL ;
return ctx - > teedev - > desc - > ops - > invoke_func ( ctx , arg , param ) ;
}
EXPORT_SYMBOL_GPL ( tee_client_invoke_func ) ;
2019-01-24 20:32:31 +03:00
int tee_client_cancel_req ( struct tee_context * ctx ,
struct tee_ioctl_cancel_arg * arg )
{
if ( ! ctx - > teedev - > desc - > ops - > cancel_req )
return - EINVAL ;
return ctx - > teedev - > desc - > ops - > cancel_req ( ctx , arg - > cancel_id ,
arg - > session ) ;
}
2019-01-29 08:49:35 +03:00
static int tee_client_device_match ( struct device * dev ,
struct device_driver * drv )
{
const struct tee_client_device_id * id_table ;
struct tee_client_device * tee_device ;
id_table = to_tee_client_driver ( drv ) - > id_table ;
tee_device = to_tee_client_device ( dev ) ;
while ( ! uuid_is_null ( & id_table - > uuid ) ) {
if ( uuid_equal ( & tee_device - > id . uuid , & id_table - > uuid ) )
return 1 ;
id_table + + ;
}
return 0 ;
}
static int tee_client_device_uevent ( struct device * dev ,
struct kobj_uevent_env * env )
{
uuid_t * dev_id = & to_tee_client_device ( dev ) - > id . uuid ;
return add_uevent_var ( env , " MODALIAS=tee:%pUb " , dev_id ) ;
}
struct bus_type tee_bus_type = {
. name = " tee " ,
. match = tee_client_device_match ,
. uevent = tee_client_device_uevent ,
} ;
EXPORT_SYMBOL_GPL ( tee_bus_type ) ;
2015-03-11 16:39:39 +03:00
static int __init tee_init ( void )
{
int rc ;
tee_class = class_create ( THIS_MODULE , " tee " ) ;
if ( IS_ERR ( tee_class ) ) {
pr_err ( " couldn't create class \n " ) ;
return PTR_ERR ( tee_class ) ;
}
rc = alloc_chrdev_region ( & tee_devt , 0 , TEE_NUM_DEVICES , " tee " ) ;
if ( rc ) {
pr_err ( " failed to allocate char dev region \n " ) ;
2019-01-29 08:49:35 +03:00
goto out_unreg_class ;
}
rc = bus_register ( & tee_bus_type ) ;
if ( rc ) {
pr_err ( " failed to register tee bus \n " ) ;
goto out_unreg_chrdev ;
2015-03-11 16:39:39 +03:00
}
2019-01-29 08:49:35 +03:00
return 0 ;
out_unreg_chrdev :
unregister_chrdev_region ( tee_devt , TEE_NUM_DEVICES ) ;
out_unreg_class :
class_destroy ( tee_class ) ;
tee_class = NULL ;
2015-03-11 16:39:39 +03:00
return rc ;
}
static void __exit tee_exit ( void )
{
2019-01-29 08:49:35 +03:00
bus_unregister ( & tee_bus_type ) ;
unregister_chrdev_region ( tee_devt , TEE_NUM_DEVICES ) ;
2015-03-11 16:39:39 +03:00
class_destroy ( tee_class ) ;
tee_class = NULL ;
}
subsys_initcall ( tee_init ) ;
module_exit ( tee_exit ) ;
MODULE_AUTHOR ( " Linaro " ) ;
MODULE_DESCRIPTION ( " TEE Driver " ) ;
MODULE_VERSION ( " 1.0 " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;