2017-02-03 12:55:33 -05:00
/*
* Copyright 2016 Broadcom
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License , version 2 , as
* published by the Free Software Foundation ( the " GPL " ) .
*
* 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 version 2 ( GPLv2 ) for more details .
*
* You should have received a copy of the GNU General Public License
* version 2 ( GPLv2 ) along with this source code .
*/
# include <linux/debugfs.h>
# include "cipher.h"
# include "util.h"
/* offset of SPU_OFIFO_CTRL register */
# define SPU_OFIFO_CTRL 0x40
# define SPU_FIFO_WATERMARK 0x1FF
/**
* spu_sg_at_offset ( ) - Find the scatterlist entry at a given distance from the
* start of a scatterlist .
* @ sg : [ in ] Start of a scatterlist
* @ skip : [ in ] Distance from the start of the scatterlist , in bytes
* @ sge : [ out ] Scatterlist entry at skip bytes from start
* @ sge_offset : [ out ] Number of bytes from start of sge buffer to get to
* requested distance .
*
* Return : 0 if entry found at requested distance
* < 0 otherwise
*/
int spu_sg_at_offset ( struct scatterlist * sg , unsigned int skip ,
struct scatterlist * * sge , unsigned int * sge_offset )
{
/* byte index from start of sg to the end of the previous entry */
unsigned int index = 0 ;
/* byte index from start of sg to the end of the current entry */
unsigned int next_index ;
next_index = sg - > length ;
while ( next_index < = skip ) {
sg = sg_next ( sg ) ;
index = next_index ;
if ( ! sg )
return - EINVAL ;
next_index + = sg - > length ;
}
* sge_offset = skip - index ;
* sge = sg ;
return 0 ;
}
/* Copy len bytes of sg data, starting at offset skip, to a dest buffer */
void sg_copy_part_to_buf ( struct scatterlist * src , u8 * dest ,
unsigned int len , unsigned int skip )
{
size_t copied ;
unsigned int nents = sg_nents ( src ) ;
copied = sg_pcopy_to_buffer ( src , nents , dest , len , skip ) ;
if ( copied ! = len ) {
flow_log ( " %s copied %u bytes of %u requested. " ,
__func__ , ( u32 ) copied , len ) ;
flow_log ( " sg with %u entries and skip %u \n " , nents , skip ) ;
}
}
/*
* Copy data into a scatterlist starting at a specified offset in the
* scatterlist . Specifically , copy len bytes of data in the buffer src
* into the scatterlist dest , starting skip bytes into the scatterlist .
*/
void sg_copy_part_from_buf ( struct scatterlist * dest , u8 * src ,
unsigned int len , unsigned int skip )
{
size_t copied ;
unsigned int nents = sg_nents ( dest ) ;
copied = sg_pcopy_from_buffer ( dest , nents , src , len , skip ) ;
if ( copied ! = len ) {
flow_log ( " %s copied %u bytes of %u requested. " ,
__func__ , ( u32 ) copied , len ) ;
flow_log ( " sg with %u entries and skip %u \n " , nents , skip ) ;
}
}
/**
* spu_sg_count ( ) - Determine number of elements in scatterlist to provide a
* specified number of bytes .
* @ sg_list : scatterlist to examine
* @ skip : index of starting point
* @ nbytes : consider elements of scatterlist until reaching this number of
* bytes
*
* Return : the number of sg entries contributing to nbytes of data
*/
int spu_sg_count ( struct scatterlist * sg_list , unsigned int skip , int nbytes )
{
struct scatterlist * sg ;
int sg_nents = 0 ;
unsigned int offset ;
if ( ! sg_list )
return 0 ;
if ( spu_sg_at_offset ( sg_list , skip , & sg , & offset ) < 0 )
return 0 ;
while ( sg & & ( nbytes > 0 ) ) {
sg_nents + + ;
nbytes - = ( sg - > length - offset ) ;
offset = 0 ;
sg = sg_next ( sg ) ;
}
return sg_nents ;
}
/**
* spu_msg_sg_add ( ) - Copy scatterlist entries from one sg to another , up to a
* given length .
* @ to_sg : scatterlist to copy to
* @ from_sg : scatterlist to copy from
* @ from_skip : number of bytes to skip in from_sg . Non - zero when previous
* request included part of the buffer in entry in from_sg .
* Assumes from_skip < from_sg - > length .
* @ from_nents number of entries in from_sg
* @ length number of bytes to copy . may reach this limit before exhausting
* from_sg .
*
* Copies the entries themselves , not the data in the entries . Assumes to_sg has
* enough entries . Does not limit the size of an individual buffer in to_sg .
*
* to_sg , from_sg , skip are all updated to end of copy
*
* Return : Number of bytes copied
*/
u32 spu_msg_sg_add ( struct scatterlist * * to_sg ,
struct scatterlist * * from_sg , u32 * from_skip ,
u8 from_nents , u32 length )
{
struct scatterlist * sg ; /* an entry in from_sg */
struct scatterlist * to = * to_sg ;
struct scatterlist * from = * from_sg ;
u32 skip = * from_skip ;
u32 offset ;
int i ;
u32 entry_len = 0 ;
u32 frag_len = 0 ; /* length of entry added to to_sg */
u32 copied = 0 ; /* number of bytes copied so far */
if ( length = = 0 )
return 0 ;
for_each_sg ( from , sg , from_nents , i ) {
/* number of bytes in this from entry not yet used */
entry_len = sg - > length - skip ;
frag_len = min ( entry_len , length - copied ) ;
offset = sg - > offset + skip ;
if ( frag_len )
sg_set_page ( to + + , sg_page ( sg ) , frag_len , offset ) ;
copied + = frag_len ;
if ( copied = = entry_len ) {
/* used up all of from entry */
skip = 0 ; /* start at beginning of next entry */
}
if ( copied = = length )
break ;
}
* to_sg = to ;
* from_sg = sg ;
if ( frag_len < entry_len )
* from_skip = skip + frag_len ;
else
* from_skip = 0 ;
return copied ;
}
void add_to_ctr ( u8 * ctr_pos , unsigned int increment )
{
__be64 * high_be = ( __be64 * ) ctr_pos ;
__be64 * low_be = high_be + 1 ;
u64 orig_low = __be64_to_cpu ( * low_be ) ;
u64 new_low = orig_low + ( u64 ) increment ;
* low_be = __cpu_to_be64 ( new_low ) ;
if ( new_low < orig_low )
/* there was a carry from the low 8 bytes */
* high_be = __cpu_to_be64 ( __be64_to_cpu ( * high_be ) + 1 ) ;
}
struct sdesc {
struct shash_desc shash ;
char ctx [ ] ;
} ;
/* do a synchronous decrypt operation */
int do_decrypt ( char * alg_name ,
void * key_ptr , unsigned int key_len ,
void * iv_ptr , void * src_ptr , void * dst_ptr ,
unsigned int block_len )
{
struct scatterlist sg_in [ 1 ] , sg_out [ 1 ] ;
struct crypto_blkcipher * tfm =
crypto_alloc_blkcipher ( alg_name , 0 , CRYPTO_ALG_ASYNC ) ;
struct blkcipher_desc desc = { . tfm = tfm , . flags = 0 } ;
int ret = 0 ;
void * iv ;
int ivsize ;
flow_log ( " %s() name:%s block_len:%u \n " , __func__ , alg_name , block_len ) ;
if ( IS_ERR ( tfm ) )
return PTR_ERR ( tfm ) ;
crypto_blkcipher_setkey ( ( void * ) tfm , key_ptr , key_len ) ;
sg_init_table ( sg_in , 1 ) ;
sg_set_buf ( sg_in , src_ptr , block_len ) ;
sg_init_table ( sg_out , 1 ) ;
sg_set_buf ( sg_out , dst_ptr , block_len ) ;
iv = crypto_blkcipher_crt ( tfm ) - > iv ;
ivsize = crypto_blkcipher_ivsize ( tfm ) ;
memcpy ( iv , iv_ptr , ivsize ) ;
ret = crypto_blkcipher_decrypt ( & desc , sg_out , sg_in , block_len ) ;
crypto_free_blkcipher ( tfm ) ;
if ( ret < 0 )
pr_err ( " aes_decrypt failed %d \n " , ret ) ;
return ret ;
}
/**
* do_shash ( ) - Do a synchronous hash operation in software
* @ name : The name of the hash algorithm
* @ result : Buffer where digest is to be written
* @ data1 : First part of data to hash . May be NULL .
* @ data1_len : Length of data1 , in bytes
* @ data2 : Second part of data to hash . May be NULL .
* @ data2_len : Length of data2 , in bytes
* @ key : Key ( if keyed hash )
* @ key_len : Length of key , in bytes ( or 0 if non - keyed hash )
*
* Note that the crypto API will not select this driver ' s own transform because
* this driver only registers asynchronous algos .
*
* Return : 0 if hash successfully stored in result
* < 0 otherwise
*/
int do_shash ( unsigned char * name , unsigned char * result ,
const u8 * data1 , unsigned int data1_len ,
const u8 * data2 , unsigned int data2_len ,
const u8 * key , unsigned int key_len )
{
int rc ;
unsigned int size ;
struct crypto_shash * hash ;
struct sdesc * sdesc ;
hash = crypto_alloc_shash ( name , 0 , 0 ) ;
if ( IS_ERR ( hash ) ) {
rc = PTR_ERR ( hash ) ;
2017-09-25 16:06:16 +05:30
pr_err ( " %s: Crypto %s allocation error %d \n " , __func__ , name , rc ) ;
2017-02-03 12:55:33 -05:00
return rc ;
}
size = sizeof ( struct shash_desc ) + crypto_shash_descsize ( hash ) ;
sdesc = kmalloc ( size , GFP_KERNEL ) ;
if ( ! sdesc ) {
rc = - ENOMEM ;
goto do_shash_err ;
}
sdesc - > shash . tfm = hash ;
sdesc - > shash . flags = 0x0 ;
if ( key_len > 0 ) {
rc = crypto_shash_setkey ( hash , key , key_len ) ;
if ( rc ) {
2017-09-25 16:06:16 +05:30
pr_err ( " %s: Could not setkey %s shash \n " , __func__ , name ) ;
2017-02-03 12:55:33 -05:00
goto do_shash_err ;
}
}
rc = crypto_shash_init ( & sdesc - > shash ) ;
if ( rc ) {
2017-09-25 16:06:16 +05:30
pr_err ( " %s: Could not init %s shash \n " , __func__ , name ) ;
2017-02-03 12:55:33 -05:00
goto do_shash_err ;
}
rc = crypto_shash_update ( & sdesc - > shash , data1 , data1_len ) ;
if ( rc ) {
2017-09-25 16:06:16 +05:30
pr_err ( " %s: Could not update1 \n " , __func__ ) ;
2017-02-03 12:55:33 -05:00
goto do_shash_err ;
}
if ( data2 & & data2_len ) {
rc = crypto_shash_update ( & sdesc - > shash , data2 , data2_len ) ;
if ( rc ) {
2017-09-25 16:06:16 +05:30
pr_err ( " %s: Could not update2 \n " , __func__ ) ;
2017-02-03 12:55:33 -05:00
goto do_shash_err ;
}
}
rc = crypto_shash_final ( & sdesc - > shash , result ) ;
if ( rc )
2017-09-25 16:06:16 +05:30
pr_err ( " %s: Could not generate %s hash \n " , __func__ , name ) ;
2017-02-03 12:55:33 -05:00
do_shash_err :
crypto_free_shash ( hash ) ;
kfree ( sdesc ) ;
return rc ;
}
/* Dump len bytes of a scatterlist starting at skip bytes into the sg */
void __dump_sg ( struct scatterlist * sg , unsigned int skip , unsigned int len )
{
u8 dbuf [ 16 ] ;
unsigned int idx = skip ;
unsigned int num_out = 0 ; /* number of bytes dumped so far */
unsigned int count ;
if ( packet_debug_logging ) {
while ( num_out < len ) {
count = ( len - num_out > 16 ) ? 16 : len - num_out ;
sg_copy_part_to_buf ( sg , dbuf , count , idx ) ;
num_out + = count ;
print_hex_dump ( KERN_ALERT , " sg: " , DUMP_PREFIX_NONE ,
4 , 1 , dbuf , count , false ) ;
idx + = 16 ;
}
}
if ( debug_logging_sleep )
msleep ( debug_logging_sleep ) ;
}
/* Returns the name for a given cipher alg/mode */
char * spu_alg_name ( enum spu_cipher_alg alg , enum spu_cipher_mode mode )
{
switch ( alg ) {
case CIPHER_ALG_RC4 :
return " rc4 " ;
case CIPHER_ALG_AES :
switch ( mode ) {
case CIPHER_MODE_CBC :
return " cbc(aes) " ;
case CIPHER_MODE_ECB :
return " ecb(aes) " ;
case CIPHER_MODE_OFB :
return " ofb(aes) " ;
case CIPHER_MODE_CFB :
return " cfb(aes) " ;
case CIPHER_MODE_CTR :
return " ctr(aes) " ;
case CIPHER_MODE_XTS :
return " xts(aes) " ;
case CIPHER_MODE_GCM :
return " gcm(aes) " ;
default :
return " aes " ;
}
break ;
case CIPHER_ALG_DES :
switch ( mode ) {
case CIPHER_MODE_CBC :
return " cbc(des) " ;
case CIPHER_MODE_ECB :
return " ecb(des) " ;
case CIPHER_MODE_CTR :
return " ctr(des) " ;
default :
return " des " ;
}
break ;
case CIPHER_ALG_3DES :
switch ( mode ) {
case CIPHER_MODE_CBC :
return " cbc(des3_ede) " ;
case CIPHER_MODE_ECB :
return " ecb(des3_ede) " ;
case CIPHER_MODE_CTR :
return " ctr(des3_ede) " ;
default :
return " 3des " ;
}
break ;
default :
return " other " ;
}
}
static ssize_t spu_debugfs_read ( struct file * filp , char __user * ubuf ,
size_t count , loff_t * offp )
{
struct device_private * ipriv ;
char * buf ;
ssize_t ret , out_offset , out_count ;
int i ;
u32 fifo_len ;
u32 spu_ofifo_ctrl ;
u32 alg ;
u32 mode ;
u32 op_cnt ;
out_count = 2048 ;
buf = kmalloc ( out_count , GFP_KERNEL ) ;
if ( ! buf )
return - ENOMEM ;
ipriv = filp - > private_data ;
out_offset = 0 ;
out_offset + = snprintf ( buf + out_offset , out_count - out_offset ,
" Number of SPUs.........%u \n " ,
ipriv - > spu . num_spu ) ;
out_offset + = snprintf ( buf + out_offset , out_count - out_offset ,
" Current sessions.......%u \n " ,
atomic_read ( & ipriv - > session_count ) ) ;
out_offset + = snprintf ( buf + out_offset , out_count - out_offset ,
" Session count..........%u \n " ,
atomic_read ( & ipriv - > stream_count ) ) ;
out_offset + = snprintf ( buf + out_offset , out_count - out_offset ,
" Cipher setkey..........%u \n " ,
atomic_read ( & ipriv - > setkey_cnt [ SPU_OP_CIPHER ] ) ) ;
out_offset + = snprintf ( buf + out_offset , out_count - out_offset ,
" Cipher Ops.............%u \n " ,
atomic_read ( & ipriv - > op_counts [ SPU_OP_CIPHER ] ) ) ;
for ( alg = 0 ; alg < CIPHER_ALG_LAST ; alg + + ) {
for ( mode = 0 ; mode < CIPHER_MODE_LAST ; mode + + ) {
op_cnt = atomic_read ( & ipriv - > cipher_cnt [ alg ] [ mode ] ) ;
if ( op_cnt ) {
out_offset + = snprintf ( buf + out_offset ,
out_count - out_offset ,
" %-13s%11u \n " ,
spu_alg_name ( alg , mode ) , op_cnt ) ;
}
}
}
out_offset + = snprintf ( buf + out_offset , out_count - out_offset ,
" Hash Ops...............%u \n " ,
atomic_read ( & ipriv - > op_counts [ SPU_OP_HASH ] ) ) ;
for ( alg = 0 ; alg < HASH_ALG_LAST ; alg + + ) {
op_cnt = atomic_read ( & ipriv - > hash_cnt [ alg ] ) ;
if ( op_cnt ) {
out_offset + = snprintf ( buf + out_offset ,
out_count - out_offset ,
" %-13s%11u \n " ,
hash_alg_name [ alg ] , op_cnt ) ;
}
}
out_offset + = snprintf ( buf + out_offset , out_count - out_offset ,
" HMAC setkey............%u \n " ,
atomic_read ( & ipriv - > setkey_cnt [ SPU_OP_HMAC ] ) ) ;
out_offset + = snprintf ( buf + out_offset , out_count - out_offset ,
" HMAC Ops...............%u \n " ,
atomic_read ( & ipriv - > op_counts [ SPU_OP_HMAC ] ) ) ;
for ( alg = 0 ; alg < HASH_ALG_LAST ; alg + + ) {
op_cnt = atomic_read ( & ipriv - > hmac_cnt [ alg ] ) ;
if ( op_cnt ) {
out_offset + = snprintf ( buf + out_offset ,
out_count - out_offset ,
" %-13s%11u \n " ,
hash_alg_name [ alg ] , op_cnt ) ;
}
}
out_offset + = snprintf ( buf + out_offset , out_count - out_offset ,
" AEAD setkey............%u \n " ,
atomic_read ( & ipriv - > setkey_cnt [ SPU_OP_AEAD ] ) ) ;
out_offset + = snprintf ( buf + out_offset , out_count - out_offset ,
" AEAD Ops...............%u \n " ,
atomic_read ( & ipriv - > op_counts [ SPU_OP_AEAD ] ) ) ;
for ( alg = 0 ; alg < AEAD_TYPE_LAST ; alg + + ) {
op_cnt = atomic_read ( & ipriv - > aead_cnt [ alg ] ) ;
if ( op_cnt ) {
out_offset + = snprintf ( buf + out_offset ,
out_count - out_offset ,
" %-13s%11u \n " ,
aead_alg_name [ alg ] , op_cnt ) ;
}
}
out_offset + = snprintf ( buf + out_offset , out_count - out_offset ,
" Bytes of req data......%llu \n " ,
( u64 ) atomic64_read ( & ipriv - > bytes_out ) ) ;
out_offset + = snprintf ( buf + out_offset , out_count - out_offset ,
" Bytes of resp data.....%llu \n " ,
( u64 ) atomic64_read ( & ipriv - > bytes_in ) ) ;
out_offset + = snprintf ( buf + out_offset , out_count - out_offset ,
" Mailbox full...........%u \n " ,
atomic_read ( & ipriv - > mb_no_spc ) ) ;
out_offset + = snprintf ( buf + out_offset , out_count - out_offset ,
" Mailbox send failures..%u \n " ,
atomic_read ( & ipriv - > mb_send_fail ) ) ;
out_offset + = snprintf ( buf + out_offset , out_count - out_offset ,
" Check ICV errors.......%u \n " ,
atomic_read ( & ipriv - > bad_icv ) ) ;
if ( ipriv - > spu . spu_type = = SPU_TYPE_SPUM )
for ( i = 0 ; i < ipriv - > spu . num_spu ; i + + ) {
spu_ofifo_ctrl = ioread32 ( ipriv - > spu . reg_vbase [ i ] +
SPU_OFIFO_CTRL ) ;
fifo_len = spu_ofifo_ctrl & SPU_FIFO_WATERMARK ;
out_offset + = snprintf ( buf + out_offset ,
out_count - out_offset ,
" SPU %d output FIFO high water.....%u \n " ,
i , fifo_len ) ;
}
if ( out_offset > out_count )
out_offset = out_count ;
ret = simple_read_from_buffer ( ubuf , count , offp , buf , out_offset ) ;
kfree ( buf ) ;
return ret ;
}
static const struct file_operations spu_debugfs_stats = {
. owner = THIS_MODULE ,
. open = simple_open ,
. read = spu_debugfs_read ,
} ;
/*
* Create the debug FS directories . If the top - level directory has not yet
* been created , create it now . Create a stats file in this directory for
* a SPU .
*/
void spu_setup_debugfs ( void )
{
if ( ! debugfs_initialized ( ) )
return ;
if ( ! iproc_priv . debugfs_dir )
iproc_priv . debugfs_dir = debugfs_create_dir ( KBUILD_MODNAME ,
NULL ) ;
if ( ! iproc_priv . debugfs_stats )
/* Create file with permissions S_IRUSR */
debugfs_create_file ( " stats " , 0400 , iproc_priv . debugfs_dir ,
& iproc_priv , & spu_debugfs_stats ) ;
}
void spu_free_debugfs ( void )
{
debugfs_remove_recursive ( iproc_priv . debugfs_dir ) ;
iproc_priv . debugfs_dir = NULL ;
}
/**
* format_value_ccm ( ) - Format a value into a buffer , using a specified number
* of bytes ( i . e . maybe writing value X into a 4 byte
* buffer , or maybe into a 12 byte buffer ) , as per the
* SPU CCM spec .
*
* @ val : value to write ( up to max of unsigned int )
* @ buf : ( pointer to ) buffer to write the value
* @ len : number of bytes to use ( 0 to 255 )
*
*/
void format_value_ccm ( unsigned int val , u8 * buf , u8 len )
{
int i ;
/* First clear full output buffer */
memset ( buf , 0 , len ) ;
/* Then, starting from right side, fill in with data */
for ( i = 0 ; i < len ; i + + ) {
buf [ len - i - 1 ] = ( val > > ( 8 * i ) ) & 0xff ;
if ( i > = 3 )
break ; /* Only handle up to 32 bits of 'val' */
}
}