2016-03-04 12:26:38 +01:00
# include <linux/kernel.h>
# include <linux/fs.h>
# include <linux/semaphore.h>
# include <linux/slab.h>
# include <linux/uaccess.h>
# include <asm/rtas.h>
# include "cxl.h"
# include "hcalls.h"
# define DOWNLOAD_IMAGE 1
# define VALIDATE_IMAGE 2
struct ai_header {
u16 version ;
u8 reserved0 [ 6 ] ;
u16 vendor ;
u16 device ;
u16 subsystem_vendor ;
u16 subsystem ;
u64 image_offset ;
u64 image_length ;
u8 reserved1 [ 96 ] ;
} ;
static struct semaphore sem ;
2016-04-18 15:03:50 +10:00
static unsigned long * buffer [ CXL_AI_MAX_ENTRIES ] ;
static struct sg_list * le ;
2016-03-04 12:26:38 +01:00
static u64 continue_token ;
static unsigned int transfer ;
struct update_props_workarea {
__be32 phandle ;
__be32 state ;
__be64 reserved ;
__be32 nprops ;
} __packed ;
struct update_nodes_workarea {
__be32 state ;
__be64 unit_address ;
__be32 reserved ;
} __packed ;
# define DEVICE_SCOPE 3
# define NODE_ACTION_MASK 0xff000000
# define NODE_COUNT_MASK 0x00ffffff
# define OPCODE_DELETE 0x01000000
# define OPCODE_UPDATE 0x02000000
# define OPCODE_ADD 0x03000000
static int rcall ( int token , char * buf , s32 scope )
{
int rc ;
spin_lock ( & rtas_data_buf_lock ) ;
memcpy ( rtas_data_buf , buf , RTAS_DATA_BUF_SIZE ) ;
rc = rtas_call ( token , 2 , 1 , NULL , rtas_data_buf , scope ) ;
memcpy ( buf , rtas_data_buf , RTAS_DATA_BUF_SIZE ) ;
spin_unlock ( & rtas_data_buf_lock ) ;
return rc ;
}
static int update_property ( struct device_node * dn , const char * name ,
u32 vd , char * value )
{
struct property * new_prop ;
u32 * val ;
int rc ;
new_prop = kzalloc ( sizeof ( * new_prop ) , GFP_KERNEL ) ;
if ( ! new_prop )
return - ENOMEM ;
new_prop - > name = kstrdup ( name , GFP_KERNEL ) ;
if ( ! new_prop - > name ) {
kfree ( new_prop ) ;
return - ENOMEM ;
}
new_prop - > length = vd ;
new_prop - > value = kzalloc ( new_prop - > length , GFP_KERNEL ) ;
if ( ! new_prop - > value ) {
kfree ( new_prop - > name ) ;
kfree ( new_prop ) ;
return - ENOMEM ;
}
memcpy ( new_prop - > value , value , vd ) ;
val = ( u32 * ) new_prop - > value ;
rc = cxl_update_properties ( dn , new_prop ) ;
pr_devel ( " %s: update property (%s, length: %i, value: %#x) \n " ,
dn - > name , name , vd , be32_to_cpu ( * val ) ) ;
if ( rc ) {
kfree ( new_prop - > name ) ;
kfree ( new_prop - > value ) ;
kfree ( new_prop ) ;
}
return rc ;
}
static int update_node ( __be32 phandle , s32 scope )
{
struct update_props_workarea * upwa ;
struct device_node * dn ;
int i , rc , ret ;
char * prop_data ;
char * buf ;
int token ;
u32 nprops ;
u32 vd ;
token = rtas_token ( " ibm,update-properties " ) ;
if ( token = = RTAS_UNKNOWN_SERVICE )
return - EINVAL ;
buf = kzalloc ( RTAS_DATA_BUF_SIZE , GFP_KERNEL ) ;
if ( ! buf )
return - ENOMEM ;
dn = of_find_node_by_phandle ( be32_to_cpu ( phandle ) ) ;
if ( ! dn ) {
kfree ( buf ) ;
return - ENOENT ;
}
upwa = ( struct update_props_workarea * ) & buf [ 0 ] ;
upwa - > phandle = phandle ;
do {
rc = rcall ( token , buf , scope ) ;
if ( rc < 0 )
break ;
prop_data = buf + sizeof ( * upwa ) ;
nprops = be32_to_cpu ( upwa - > nprops ) ;
if ( * prop_data = = 0 ) {
prop_data + + ;
vd = be32_to_cpu ( * ( __be32 * ) prop_data ) ;
prop_data + = vd + sizeof ( vd ) ;
nprops - - ;
}
for ( i = 0 ; i < nprops ; i + + ) {
char * prop_name ;
prop_name = prop_data ;
prop_data + = strlen ( prop_name ) + 1 ;
vd = be32_to_cpu ( * ( __be32 * ) prop_data ) ;
prop_data + = sizeof ( vd ) ;
if ( ( vd ! = 0x00000000 ) & & ( vd ! = 0x80000000 ) ) {
ret = update_property ( dn , prop_name , vd ,
prop_data ) ;
if ( ret )
pr_err ( " cxl: Could not update property %s - %i \n " ,
prop_name , ret ) ;
prop_data + = vd ;
}
}
} while ( rc = = 1 ) ;
of_node_put ( dn ) ;
kfree ( buf ) ;
return rc ;
}
static int update_devicetree ( struct cxl * adapter , s32 scope )
{
struct update_nodes_workarea * unwa ;
u32 action , node_count ;
int token , rc , i ;
__be32 * data , drc_index , phandle ;
char * buf ;
token = rtas_token ( " ibm,update-nodes " ) ;
if ( token = = RTAS_UNKNOWN_SERVICE )
return - EINVAL ;
buf = kzalloc ( RTAS_DATA_BUF_SIZE , GFP_KERNEL ) ;
if ( ! buf )
return - ENOMEM ;
unwa = ( struct update_nodes_workarea * ) & buf [ 0 ] ;
unwa - > unit_address = cpu_to_be64 ( adapter - > guest - > handle ) ;
do {
rc = rcall ( token , buf , scope ) ;
if ( rc & & rc ! = 1 )
break ;
data = ( __be32 * ) buf + 4 ;
while ( be32_to_cpu ( * data ) & NODE_ACTION_MASK ) {
action = be32_to_cpu ( * data ) & NODE_ACTION_MASK ;
node_count = be32_to_cpu ( * data ) & NODE_COUNT_MASK ;
pr_devel ( " device reconfiguration - action: %#x, nodes: %#x \n " ,
action , node_count ) ;
data + + ;
for ( i = 0 ; i < node_count ; i + + ) {
phandle = * data + + ;
switch ( action ) {
case OPCODE_DELETE :
/* nothing to do */
break ;
case OPCODE_UPDATE :
update_node ( phandle , scope ) ;
break ;
case OPCODE_ADD :
/* nothing to do, just move pointer */
drc_index = * data + + ;
break ;
}
}
}
} while ( rc = = 1 ) ;
kfree ( buf ) ;
return 0 ;
}
static int handle_image ( struct cxl * adapter , int operation ,
long ( * fct ) ( u64 , u64 , u64 , u64 * ) ,
struct cxl_adapter_image * ai )
{
size_t mod , s_copy , len_chunk = 0 ;
struct ai_header * header = NULL ;
unsigned int entries = 0 , i ;
void * dest , * from ;
int rc = 0 , need_header ;
/* base adapter image header */
need_header = ( ai - > flags & CXL_AI_NEED_HEADER ) ;
if ( need_header ) {
header = kzalloc ( sizeof ( struct ai_header ) , GFP_KERNEL ) ;
if ( ! header )
return - ENOMEM ;
header - > version = cpu_to_be16 ( 1 ) ;
header - > vendor = cpu_to_be16 ( adapter - > guest - > vendor ) ;
header - > device = cpu_to_be16 ( adapter - > guest - > device ) ;
header - > subsystem_vendor = cpu_to_be16 ( adapter - > guest - > subsystem_vendor ) ;
header - > subsystem = cpu_to_be16 ( adapter - > guest - > subsystem ) ;
header - > image_offset = cpu_to_be64 ( CXL_AI_HEADER_SIZE ) ;
header - > image_length = cpu_to_be64 ( ai - > len_image ) ;
}
/* number of entries in the list */
len_chunk = ai - > len_data ;
if ( need_header )
len_chunk + = CXL_AI_HEADER_SIZE ;
entries = len_chunk / CXL_AI_BUFFER_SIZE ;
mod = len_chunk % CXL_AI_BUFFER_SIZE ;
if ( mod )
entries + + ;
if ( entries > CXL_AI_MAX_ENTRIES ) {
rc = - EINVAL ;
goto err ;
}
/* < -- MAX_CHUNK_SIZE = 4096 * 256 = 1048576 bytes -->
* chunk 0 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* | header | data |
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* chunk 1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* | data |
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* . . . .
* chunk n - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* | data |
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
from = ( void * ) ai - > data ;
for ( i = 0 ; i < entries ; i + + ) {
dest = buffer [ i ] ;
s_copy = CXL_AI_BUFFER_SIZE ;
if ( ( need_header ) & & ( i = = 0 ) ) {
/* add adapter image header */
memcpy ( buffer [ i ] , header , sizeof ( struct ai_header ) ) ;
s_copy = CXL_AI_BUFFER_SIZE - CXL_AI_HEADER_SIZE ;
dest + = CXL_AI_HEADER_SIZE ; /* image offset */
}
if ( ( i = = ( entries - 1 ) ) & & mod )
s_copy = mod ;
/* copy data */
if ( copy_from_user ( dest , from , s_copy ) )
goto err ;
/* fill in the list */
le [ i ] . phys_addr = cpu_to_be64 ( virt_to_phys ( buffer [ i ] ) ) ;
le [ i ] . len = cpu_to_be64 ( CXL_AI_BUFFER_SIZE ) ;
if ( ( i = = ( entries - 1 ) ) & & mod )
le [ i ] . len = cpu_to_be64 ( mod ) ;
from + = s_copy ;
}
pr_devel ( " %s (op: %i, need header: %i, entries: %i, token: %#llx) \n " ,
__func__ , operation , need_header , entries , continue_token ) ;
/*
* download / validate the adapter image to the coherent
* platform facility
*/
rc = fct ( adapter - > guest - > handle , virt_to_phys ( le ) , entries ,
& continue_token ) ;
if ( rc = = 0 ) /* success of download/validation operation */
continue_token = 0 ;
err :
kfree ( header ) ;
return rc ;
}
static int transfer_image ( struct cxl * adapter , int operation ,
struct cxl_adapter_image * ai )
{
int rc = 0 ;
int afu ;
switch ( operation ) {
case DOWNLOAD_IMAGE :
rc = handle_image ( adapter , operation ,
& cxl_h_download_adapter_image , ai ) ;
if ( rc < 0 ) {
pr_devel ( " resetting adapter \n " ) ;
cxl_h_reset_adapter ( adapter - > guest - > handle ) ;
}
return rc ;
case VALIDATE_IMAGE :
rc = handle_image ( adapter , operation ,
& cxl_h_validate_adapter_image , ai ) ;
if ( rc < 0 ) {
pr_devel ( " resetting adapter \n " ) ;
cxl_h_reset_adapter ( adapter - > guest - > handle ) ;
return rc ;
}
if ( rc = = 0 ) {
2016-11-23 22:44:47 +09:00
pr_devel ( " remove current afu \n " ) ;
2016-03-04 12:26:38 +01:00
for ( afu = 0 ; afu < adapter - > slices ; afu + + )
cxl_guest_remove_afu ( adapter - > afu [ afu ] ) ;
pr_devel ( " resetting adapter \n " ) ;
cxl_h_reset_adapter ( adapter - > guest - > handle ) ;
/* The entire image has now been
* downloaded and the validation has
* been successfully performed .
* After that , the partition should call
* ibm , update - nodes and
* ibm , update - properties to receive the
* current configuration
*/
rc = update_devicetree ( adapter , DEVICE_SCOPE ) ;
transfer = 1 ;
}
return rc ;
}
return - EINVAL ;
}
static long ioctl_transfer_image ( struct cxl * adapter , int operation ,
struct cxl_adapter_image __user * uai )
{
struct cxl_adapter_image ai ;
pr_devel ( " %s \n " , __func__ ) ;
if ( copy_from_user ( & ai , uai , sizeof ( struct cxl_adapter_image ) ) )
return - EFAULT ;
/*
* Make sure reserved fields and bits are set to 0
*/
if ( ai . reserved1 | | ai . reserved2 | | ai . reserved3 | | ai . reserved4 | |
( ai . flags & ~ CXL_AI_ALL ) )
return - EINVAL ;
return transfer_image ( adapter , operation , & ai ) ;
}
static int device_open ( struct inode * inode , struct file * file )
{
int adapter_num = CXL_DEVT_ADAPTER ( inode - > i_rdev ) ;
struct cxl * adapter ;
int rc = 0 , i ;
pr_devel ( " in %s \n " , __func__ ) ;
BUG_ON ( sizeof ( struct ai_header ) ! = CXL_AI_HEADER_SIZE ) ;
/* Allows one process to open the device by using a semaphore */
if ( down_interruptible ( & sem ) ! = 0 )
return - EPERM ;
2017-05-05 08:34:58 +03:00
if ( ! ( adapter = get_cxl_adapter ( adapter_num ) ) ) {
rc = - ENODEV ;
goto err_unlock ;
}
2016-03-04 12:26:38 +01:00
file - > private_data = adapter ;
continue_token = 0 ;
transfer = 0 ;
for ( i = 0 ; i < CXL_AI_MAX_ENTRIES ; i + + )
buffer [ i ] = NULL ;
/* aligned buffer containing list entries which describes up to
* 1 megabyte of data ( 256 entries of 4096 bytes each )
* Logical real address of buffer 0 - Buffer 0 length in bytes
* Logical real address of buffer 1 - Buffer 1 length in bytes
* Logical real address of buffer 2 - Buffer 2 length in bytes
* . . . .
* . . . .
* Logical real address of buffer N - Buffer N length in bytes
*/
le = ( struct sg_list * ) get_zeroed_page ( GFP_KERNEL ) ;
if ( ! le ) {
rc = - ENOMEM ;
goto err ;
}
for ( i = 0 ; i < CXL_AI_MAX_ENTRIES ; i + + ) {
buffer [ i ] = ( unsigned long * ) get_zeroed_page ( GFP_KERNEL ) ;
if ( ! buffer [ i ] ) {
rc = - ENOMEM ;
goto err1 ;
}
}
return 0 ;
err1 :
for ( i = 0 ; i < CXL_AI_MAX_ENTRIES ; i + + ) {
if ( buffer [ i ] )
free_page ( ( unsigned long ) buffer [ i ] ) ;
}
if ( le )
free_page ( ( unsigned long ) le ) ;
err :
put_device ( & adapter - > dev ) ;
2017-05-05 08:34:58 +03:00
err_unlock :
up ( & sem ) ;
2016-03-04 12:26:38 +01:00
return rc ;
}
static long device_ioctl ( struct file * file , unsigned int cmd , unsigned long arg )
{
struct cxl * adapter = file - > private_data ;
pr_devel ( " in %s \n " , __func__ ) ;
if ( cmd = = CXL_IOCTL_DOWNLOAD_IMAGE )
return ioctl_transfer_image ( adapter ,
DOWNLOAD_IMAGE ,
( struct cxl_adapter_image __user * ) arg ) ;
else if ( cmd = = CXL_IOCTL_VALIDATE_IMAGE )
return ioctl_transfer_image ( adapter ,
VALIDATE_IMAGE ,
( struct cxl_adapter_image __user * ) arg ) ;
else
return - EINVAL ;
}
static long device_compat_ioctl ( struct file * file , unsigned int cmd ,
unsigned long arg )
{
return device_ioctl ( file , cmd , arg ) ;
}
static int device_close ( struct inode * inode , struct file * file )
{
struct cxl * adapter = file - > private_data ;
int i ;
pr_devel ( " in %s \n " , __func__ ) ;
for ( i = 0 ; i < CXL_AI_MAX_ENTRIES ; i + + ) {
if ( buffer [ i ] )
free_page ( ( unsigned long ) buffer [ i ] ) ;
}
if ( le )
free_page ( ( unsigned long ) le ) ;
up ( & sem ) ;
put_device ( & adapter - > dev ) ;
continue_token = 0 ;
/* reload the module */
if ( transfer )
cxl_guest_reload_module ( adapter ) ;
else {
pr_devel ( " resetting adapter \n " ) ;
cxl_h_reset_adapter ( adapter - > guest - > handle ) ;
}
transfer = 0 ;
return 0 ;
}
static const struct file_operations fops = {
. owner = THIS_MODULE ,
. open = device_open ,
. unlocked_ioctl = device_ioctl ,
. compat_ioctl = device_compat_ioctl ,
. release = device_close ,
} ;
void cxl_guest_remove_chardev ( struct cxl * adapter )
{
cdev_del ( & adapter - > guest - > cdev ) ;
}
int cxl_guest_add_chardev ( struct cxl * adapter )
{
dev_t devt ;
int rc ;
devt = MKDEV ( MAJOR ( cxl_get_dev ( ) ) , CXL_CARD_MINOR ( adapter ) ) ;
cdev_init ( & adapter - > guest - > cdev , & fops ) ;
if ( ( rc = cdev_add ( & adapter - > guest - > cdev , devt , 1 ) ) ) {
dev_err ( & adapter - > dev ,
" Unable to add chardev on adapter (card%i): %i \n " ,
adapter - > adapter_num , rc ) ;
goto err ;
}
adapter - > dev . devt = devt ;
sema_init ( & sem , 1 ) ;
err :
return rc ;
}