2015-07-17 17:39:41 +03:00
/*
* sun4i - ss - cipher . c - hardware cryptographic accelerator for Allwinner A20 SoC
*
* Copyright ( C ) 2013 - 2015 Corentin LABBE < clabbe . montjoie @ gmail . com >
*
* This file add support for AES cipher with 128 , 192 , 256 bits
* keysize in CBC and ECB mode .
* Add support also for DES and 3 DES in CBC and ECB mode .
*
* You could find the datasheet in Documentation / arm / sunxi / README
*
* 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 .
*/
# include "sun4i-ss.h"
static int sun4i_ss_opti_poll ( struct ablkcipher_request * areq )
{
struct crypto_ablkcipher * tfm = crypto_ablkcipher_reqtfm ( areq ) ;
struct sun4i_tfm_ctx * op = crypto_ablkcipher_ctx ( tfm ) ;
struct sun4i_ss_ctx * ss = op - > ss ;
unsigned int ivsize = crypto_ablkcipher_ivsize ( tfm ) ;
struct sun4i_cipher_req_ctx * ctx = ablkcipher_request_ctx ( areq ) ;
u32 mode = ctx - > mode ;
/* when activating SS, the default FIFO space is SS_RX_DEFAULT(32) */
u32 rx_cnt = SS_RX_DEFAULT ;
u32 tx_cnt = 0 ;
u32 spaces ;
u32 v ;
2016-08-10 12:45:29 +03:00
int err = 0 ;
unsigned int i ;
2015-07-17 17:39:41 +03:00
unsigned int ileft = areq - > nbytes ;
unsigned int oleft = areq - > nbytes ;
unsigned int todo ;
struct sg_mapping_iter mi , mo ;
unsigned int oi , oo ; /* offset for in and out */
2016-03-23 18:11:24 +03:00
unsigned long flags ;
2015-07-17 17:39:41 +03:00
if ( areq - > nbytes = = 0 )
return 0 ;
if ( ! areq - > info ) {
dev_err_ratelimited ( ss - > dev , " ERROR: Empty IV \n " ) ;
return - EINVAL ;
}
if ( ! areq - > src | | ! areq - > dst ) {
dev_err_ratelimited ( ss - > dev , " ERROR: Some SGs are NULL \n " ) ;
return - EINVAL ;
}
2016-03-23 18:11:24 +03:00
spin_lock_irqsave ( & ss - > slock , flags ) ;
2015-07-17 17:39:41 +03:00
for ( i = 0 ; i < op - > keylen ; i + = 4 )
writel ( * ( op - > key + i / 4 ) , ss - > base + SS_KEY0 + i ) ;
if ( areq - > info ) {
for ( i = 0 ; i < 4 & & i < ivsize / 4 ; i + + ) {
v = * ( u32 * ) ( areq - > info + i * 4 ) ;
writel ( v , ss - > base + SS_IV0 + i * 4 ) ;
}
}
writel ( mode , ss - > base + SS_CTL ) ;
sg_miter_start ( & mi , areq - > src , sg_nents ( areq - > src ) ,
SG_MITER_FROM_SG | SG_MITER_ATOMIC ) ;
sg_miter_start ( & mo , areq - > dst , sg_nents ( areq - > dst ) ,
SG_MITER_TO_SG | SG_MITER_ATOMIC ) ;
sg_miter_next ( & mi ) ;
sg_miter_next ( & mo ) ;
if ( ! mi . addr | | ! mo . addr ) {
dev_err_ratelimited ( ss - > dev , " ERROR: sg_miter return null \n " ) ;
err = - EINVAL ;
goto release_ss ;
}
ileft = areq - > nbytes / 4 ;
oleft = areq - > nbytes / 4 ;
oi = 0 ;
oo = 0 ;
do {
todo = min3 ( rx_cnt , ileft , ( mi . length - oi ) / 4 ) ;
if ( todo > 0 ) {
ileft - = todo ;
writesl ( ss - > base + SS_RXFIFO , mi . addr + oi , todo ) ;
oi + = todo * 4 ;
}
if ( oi = = mi . length ) {
sg_miter_next ( & mi ) ;
oi = 0 ;
}
spaces = readl ( ss - > base + SS_FCSR ) ;
rx_cnt = SS_RXFIFO_SPACES ( spaces ) ;
tx_cnt = SS_TXFIFO_SPACES ( spaces ) ;
todo = min3 ( tx_cnt , oleft , ( mo . length - oo ) / 4 ) ;
if ( todo > 0 ) {
oleft - = todo ;
readsl ( ss - > base + SS_TXFIFO , mo . addr + oo , todo ) ;
oo + = todo * 4 ;
}
if ( oo = = mo . length ) {
sg_miter_next ( & mo ) ;
oo = 0 ;
}
2015-09-09 15:27:07 +03:00
} while ( oleft > 0 ) ;
2015-07-17 17:39:41 +03:00
if ( areq - > info ) {
for ( i = 0 ; i < 4 & & i < ivsize / 4 ; i + + ) {
v = readl ( ss - > base + SS_IV0 + i * 4 ) ;
* ( u32 * ) ( areq - > info + i * 4 ) = v ;
}
}
release_ss :
sg_miter_stop ( & mi ) ;
sg_miter_stop ( & mo ) ;
writel ( 0 , ss - > base + SS_CTL ) ;
2016-03-23 18:11:24 +03:00
spin_unlock_irqrestore ( & ss - > slock , flags ) ;
2015-07-17 17:39:41 +03:00
return err ;
}
/* Generic function that support SG with size not multiple of 4 */
static int sun4i_ss_cipher_poll ( struct ablkcipher_request * areq )
{
struct crypto_ablkcipher * tfm = crypto_ablkcipher_reqtfm ( areq ) ;
struct sun4i_tfm_ctx * op = crypto_ablkcipher_ctx ( tfm ) ;
struct sun4i_ss_ctx * ss = op - > ss ;
int no_chunk = 1 ;
struct scatterlist * in_sg = areq - > src ;
struct scatterlist * out_sg = areq - > dst ;
unsigned int ivsize = crypto_ablkcipher_ivsize ( tfm ) ;
struct sun4i_cipher_req_ctx * ctx = ablkcipher_request_ctx ( areq ) ;
u32 mode = ctx - > mode ;
/* when activating SS, the default FIFO space is SS_RX_DEFAULT(32) */
u32 rx_cnt = SS_RX_DEFAULT ;
u32 tx_cnt = 0 ;
u32 v ;
u32 spaces ;
2016-08-10 12:45:29 +03:00
int err = 0 ;
unsigned int i ;
2015-07-17 17:39:41 +03:00
unsigned int ileft = areq - > nbytes ;
unsigned int oleft = areq - > nbytes ;
unsigned int todo ;
struct sg_mapping_iter mi , mo ;
unsigned int oi , oo ; /* offset for in and out */
char buf [ 4 * SS_RX_MAX ] ; /* buffer for linearize SG src */
char bufo [ 4 * SS_TX_MAX ] ; /* buffer for linearize SG dst */
unsigned int ob = 0 ; /* offset in buf */
unsigned int obo = 0 ; /* offset in bufo*/
unsigned int obl = 0 ; /* length of data in bufo */
2016-03-23 18:11:24 +03:00
unsigned long flags ;
2015-07-17 17:39:41 +03:00
if ( areq - > nbytes = = 0 )
return 0 ;
if ( ! areq - > info ) {
dev_err_ratelimited ( ss - > dev , " ERROR: Empty IV \n " ) ;
return - EINVAL ;
}
if ( ! areq - > src | | ! areq - > dst ) {
dev_err_ratelimited ( ss - > dev , " ERROR: Some SGs are NULL \n " ) ;
return - EINVAL ;
}
/*
* if we have only SGs with size multiple of 4 ,
* we can use the SS optimized function
*/
while ( in_sg & & no_chunk = = 1 ) {
if ( ( in_sg - > length % 4 ) ! = 0 )
no_chunk = 0 ;
in_sg = sg_next ( in_sg ) ;
}
while ( out_sg & & no_chunk = = 1 ) {
if ( ( out_sg - > length % 4 ) ! = 0 )
no_chunk = 0 ;
out_sg = sg_next ( out_sg ) ;
}
if ( no_chunk = = 1 )
return sun4i_ss_opti_poll ( areq ) ;
2016-03-23 18:11:24 +03:00
spin_lock_irqsave ( & ss - > slock , flags ) ;
2015-07-17 17:39:41 +03:00
for ( i = 0 ; i < op - > keylen ; i + = 4 )
writel ( * ( op - > key + i / 4 ) , ss - > base + SS_KEY0 + i ) ;
if ( areq - > info ) {
for ( i = 0 ; i < 4 & & i < ivsize / 4 ; i + + ) {
v = * ( u32 * ) ( areq - > info + i * 4 ) ;
writel ( v , ss - > base + SS_IV0 + i * 4 ) ;
}
}
writel ( mode , ss - > base + SS_CTL ) ;
sg_miter_start ( & mi , areq - > src , sg_nents ( areq - > src ) ,
SG_MITER_FROM_SG | SG_MITER_ATOMIC ) ;
sg_miter_start ( & mo , areq - > dst , sg_nents ( areq - > dst ) ,
SG_MITER_TO_SG | SG_MITER_ATOMIC ) ;
sg_miter_next ( & mi ) ;
sg_miter_next ( & mo ) ;
if ( ! mi . addr | | ! mo . addr ) {
dev_err_ratelimited ( ss - > dev , " ERROR: sg_miter return null \n " ) ;
err = - EINVAL ;
goto release_ss ;
}
ileft = areq - > nbytes ;
oleft = areq - > nbytes ;
oi = 0 ;
oo = 0 ;
while ( oleft > 0 ) {
if ( ileft > 0 ) {
/*
* todo is the number of consecutive 4 byte word that we
* can read from current SG
*/
todo = min3 ( rx_cnt , ileft / 4 , ( mi . length - oi ) / 4 ) ;
if ( todo > 0 & & ob = = 0 ) {
writesl ( ss - > base + SS_RXFIFO , mi . addr + oi ,
todo ) ;
ileft - = todo * 4 ;
oi + = todo * 4 ;
} else {
/*
* not enough consecutive bytes , so we need to
* linearize in buf . todo is in bytes
* After that copy , if we have a multiple of 4
* we need to be able to write all buf in one
* pass , so it is why we min ( ) with rx_cnt
*/
todo = min3 ( rx_cnt * 4 - ob , ileft ,
mi . length - oi ) ;
memcpy ( buf + ob , mi . addr + oi , todo ) ;
ileft - = todo ;
oi + = todo ;
ob + = todo ;
if ( ob % 4 = = 0 ) {
writesl ( ss - > base + SS_RXFIFO , buf ,
ob / 4 ) ;
ob = 0 ;
}
}
if ( oi = = mi . length ) {
sg_miter_next ( & mi ) ;
oi = 0 ;
}
}
spaces = readl ( ss - > base + SS_FCSR ) ;
rx_cnt = SS_RXFIFO_SPACES ( spaces ) ;
tx_cnt = SS_TXFIFO_SPACES ( spaces ) ;
2016-01-26 16:44:50 +03:00
dev_dbg ( ss - > dev , " %x %u/%u %u/%u cnt=%u %u/%u %u/%u cnt=%u %u \n " ,
2015-07-17 17:39:41 +03:00
mode ,
oi , mi . length , ileft , areq - > nbytes , rx_cnt ,
2016-01-26 16:44:50 +03:00
oo , mo . length , oleft , areq - > nbytes , tx_cnt , ob ) ;
2015-07-17 17:39:41 +03:00
if ( tx_cnt = = 0 )
continue ;
/* todo in 4bytes word */
todo = min3 ( tx_cnt , oleft / 4 , ( mo . length - oo ) / 4 ) ;
if ( todo > 0 ) {
readsl ( ss - > base + SS_TXFIFO , mo . addr + oo , todo ) ;
oleft - = todo * 4 ;
oo + = todo * 4 ;
if ( oo = = mo . length ) {
sg_miter_next ( & mo ) ;
oo = 0 ;
}
} else {
/*
* read obl bytes in bufo , we read at maximum for
* emptying the device
*/
readsl ( ss - > base + SS_TXFIFO , bufo , tx_cnt ) ;
obl = tx_cnt * 4 ;
obo = 0 ;
do {
/*
* how many bytes we can copy ?
* no more than remaining SG size
* no more than remaining buffer
* no need to test against oleft
*/
todo = min ( mo . length - oo , obl - obo ) ;
memcpy ( mo . addr + oo , bufo + obo , todo ) ;
oleft - = todo ;
obo + = todo ;
oo + = todo ;
if ( oo = = mo . length ) {
sg_miter_next ( & mo ) ;
oo = 0 ;
}
} while ( obo < obl ) ;
/* bufo must be fully used here */
}
}
if ( areq - > info ) {
for ( i = 0 ; i < 4 & & i < ivsize / 4 ; i + + ) {
v = readl ( ss - > base + SS_IV0 + i * 4 ) ;
* ( u32 * ) ( areq - > info + i * 4 ) = v ;
}
}
release_ss :
sg_miter_stop ( & mi ) ;
sg_miter_stop ( & mo ) ;
writel ( 0 , ss - > base + SS_CTL ) ;
2016-03-23 18:11:24 +03:00
spin_unlock_irqrestore ( & ss - > slock , flags ) ;
2015-07-17 17:39:41 +03:00
return err ;
}
/* CBC AES */
int sun4i_ss_cbc_aes_encrypt ( struct ablkcipher_request * areq )
{
struct crypto_ablkcipher * tfm = crypto_ablkcipher_reqtfm ( areq ) ;
struct sun4i_tfm_ctx * op = crypto_ablkcipher_ctx ( tfm ) ;
struct sun4i_cipher_req_ctx * rctx = ablkcipher_request_ctx ( areq ) ;
rctx - > mode = SS_OP_AES | SS_CBC | SS_ENABLED | SS_ENCRYPTION |
op - > keymode ;
return sun4i_ss_cipher_poll ( areq ) ;
}
int sun4i_ss_cbc_aes_decrypt ( struct ablkcipher_request * areq )
{
struct crypto_ablkcipher * tfm = crypto_ablkcipher_reqtfm ( areq ) ;
struct sun4i_tfm_ctx * op = crypto_ablkcipher_ctx ( tfm ) ;
struct sun4i_cipher_req_ctx * rctx = ablkcipher_request_ctx ( areq ) ;
rctx - > mode = SS_OP_AES | SS_CBC | SS_ENABLED | SS_DECRYPTION |
op - > keymode ;
return sun4i_ss_cipher_poll ( areq ) ;
}
/* ECB AES */
int sun4i_ss_ecb_aes_encrypt ( struct ablkcipher_request * areq )
{
struct crypto_ablkcipher * tfm = crypto_ablkcipher_reqtfm ( areq ) ;
struct sun4i_tfm_ctx * op = crypto_ablkcipher_ctx ( tfm ) ;
struct sun4i_cipher_req_ctx * rctx = ablkcipher_request_ctx ( areq ) ;
rctx - > mode = SS_OP_AES | SS_ECB | SS_ENABLED | SS_ENCRYPTION |
op - > keymode ;
return sun4i_ss_cipher_poll ( areq ) ;
}
int sun4i_ss_ecb_aes_decrypt ( struct ablkcipher_request * areq )
{
struct crypto_ablkcipher * tfm = crypto_ablkcipher_reqtfm ( areq ) ;
struct sun4i_tfm_ctx * op = crypto_ablkcipher_ctx ( tfm ) ;
struct sun4i_cipher_req_ctx * rctx = ablkcipher_request_ctx ( areq ) ;
rctx - > mode = SS_OP_AES | SS_ECB | SS_ENABLED | SS_DECRYPTION |
op - > keymode ;
return sun4i_ss_cipher_poll ( areq ) ;
}
/* CBC DES */
int sun4i_ss_cbc_des_encrypt ( struct ablkcipher_request * areq )
{
struct crypto_ablkcipher * tfm = crypto_ablkcipher_reqtfm ( areq ) ;
struct sun4i_tfm_ctx * op = crypto_ablkcipher_ctx ( tfm ) ;
struct sun4i_cipher_req_ctx * rctx = ablkcipher_request_ctx ( areq ) ;
rctx - > mode = SS_OP_DES | SS_CBC | SS_ENABLED | SS_ENCRYPTION |
op - > keymode ;
return sun4i_ss_cipher_poll ( areq ) ;
}
int sun4i_ss_cbc_des_decrypt ( struct ablkcipher_request * areq )
{
struct crypto_ablkcipher * tfm = crypto_ablkcipher_reqtfm ( areq ) ;
struct sun4i_tfm_ctx * op = crypto_ablkcipher_ctx ( tfm ) ;
struct sun4i_cipher_req_ctx * rctx = ablkcipher_request_ctx ( areq ) ;
rctx - > mode = SS_OP_DES | SS_CBC | SS_ENABLED | SS_DECRYPTION |
op - > keymode ;
return sun4i_ss_cipher_poll ( areq ) ;
}
/* ECB DES */
int sun4i_ss_ecb_des_encrypt ( struct ablkcipher_request * areq )
{
struct crypto_ablkcipher * tfm = crypto_ablkcipher_reqtfm ( areq ) ;
struct sun4i_tfm_ctx * op = crypto_ablkcipher_ctx ( tfm ) ;
struct sun4i_cipher_req_ctx * rctx = ablkcipher_request_ctx ( areq ) ;
rctx - > mode = SS_OP_DES | SS_ECB | SS_ENABLED | SS_ENCRYPTION |
op - > keymode ;
return sun4i_ss_cipher_poll ( areq ) ;
}
int sun4i_ss_ecb_des_decrypt ( struct ablkcipher_request * areq )
{
struct crypto_ablkcipher * tfm = crypto_ablkcipher_reqtfm ( areq ) ;
struct sun4i_tfm_ctx * op = crypto_ablkcipher_ctx ( tfm ) ;
struct sun4i_cipher_req_ctx * rctx = ablkcipher_request_ctx ( areq ) ;
rctx - > mode = SS_OP_DES | SS_ECB | SS_ENABLED | SS_DECRYPTION |
op - > keymode ;
return sun4i_ss_cipher_poll ( areq ) ;
}
/* CBC 3DES */
int sun4i_ss_cbc_des3_encrypt ( struct ablkcipher_request * areq )
{
struct crypto_ablkcipher * tfm = crypto_ablkcipher_reqtfm ( areq ) ;
struct sun4i_tfm_ctx * op = crypto_ablkcipher_ctx ( tfm ) ;
struct sun4i_cipher_req_ctx * rctx = ablkcipher_request_ctx ( areq ) ;
rctx - > mode = SS_OP_3DES | SS_CBC | SS_ENABLED | SS_ENCRYPTION |
op - > keymode ;
return sun4i_ss_cipher_poll ( areq ) ;
}
int sun4i_ss_cbc_des3_decrypt ( struct ablkcipher_request * areq )
{
struct crypto_ablkcipher * tfm = crypto_ablkcipher_reqtfm ( areq ) ;
struct sun4i_tfm_ctx * op = crypto_ablkcipher_ctx ( tfm ) ;
struct sun4i_cipher_req_ctx * rctx = ablkcipher_request_ctx ( areq ) ;
rctx - > mode = SS_OP_3DES | SS_CBC | SS_ENABLED | SS_DECRYPTION |
op - > keymode ;
return sun4i_ss_cipher_poll ( areq ) ;
}
/* ECB 3DES */
int sun4i_ss_ecb_des3_encrypt ( struct ablkcipher_request * areq )
{
struct crypto_ablkcipher * tfm = crypto_ablkcipher_reqtfm ( areq ) ;
struct sun4i_tfm_ctx * op = crypto_ablkcipher_ctx ( tfm ) ;
struct sun4i_cipher_req_ctx * rctx = ablkcipher_request_ctx ( areq ) ;
rctx - > mode = SS_OP_3DES | SS_ECB | SS_ENABLED | SS_ENCRYPTION |
op - > keymode ;
return sun4i_ss_cipher_poll ( areq ) ;
}
int sun4i_ss_ecb_des3_decrypt ( struct ablkcipher_request * areq )
{
struct crypto_ablkcipher * tfm = crypto_ablkcipher_reqtfm ( areq ) ;
struct sun4i_tfm_ctx * op = crypto_ablkcipher_ctx ( tfm ) ;
struct sun4i_cipher_req_ctx * rctx = ablkcipher_request_ctx ( areq ) ;
rctx - > mode = SS_OP_3DES | SS_ECB | SS_ENABLED | SS_DECRYPTION |
op - > keymode ;
return sun4i_ss_cipher_poll ( areq ) ;
}
int sun4i_ss_cipher_init ( struct crypto_tfm * tfm )
{
struct sun4i_tfm_ctx * op = crypto_tfm_ctx ( tfm ) ;
struct crypto_alg * alg = tfm - > __crt_alg ;
struct sun4i_ss_alg_template * algt ;
memset ( op , 0 , sizeof ( struct sun4i_tfm_ctx ) ) ;
algt = container_of ( alg , struct sun4i_ss_alg_template , alg . crypto ) ;
op - > ss = algt - > ss ;
tfm - > crt_ablkcipher . reqsize = sizeof ( struct sun4i_cipher_req_ctx ) ;
return 0 ;
}
/* check and set the AES key, prepare the mode to be used */
int sun4i_ss_aes_setkey ( struct crypto_ablkcipher * tfm , const u8 * key ,
unsigned int keylen )
{
struct sun4i_tfm_ctx * op = crypto_ablkcipher_ctx ( tfm ) ;
struct sun4i_ss_ctx * ss = op - > ss ;
switch ( keylen ) {
case 128 / 8 :
op - > keymode = SS_AES_128BITS ;
break ;
case 192 / 8 :
op - > keymode = SS_AES_192BITS ;
break ;
case 256 / 8 :
op - > keymode = SS_AES_256BITS ;
break ;
default :
dev_err ( ss - > dev , " ERROR: Invalid keylen %u \n " , keylen ) ;
crypto_ablkcipher_set_flags ( tfm , CRYPTO_TFM_RES_BAD_KEY_LEN ) ;
return - EINVAL ;
}
op - > keylen = keylen ;
memcpy ( op - > key , key , keylen ) ;
return 0 ;
}
/* check and set the DES key, prepare the mode to be used */
int sun4i_ss_des_setkey ( struct crypto_ablkcipher * tfm , const u8 * key ,
unsigned int keylen )
{
struct sun4i_tfm_ctx * op = crypto_ablkcipher_ctx ( tfm ) ;
struct sun4i_ss_ctx * ss = op - > ss ;
u32 flags ;
u32 tmp [ DES_EXPKEY_WORDS ] ;
int ret ;
if ( unlikely ( keylen ! = DES_KEY_SIZE ) ) {
dev_err ( ss - > dev , " Invalid keylen %u \n " , keylen ) ;
crypto_ablkcipher_set_flags ( tfm , CRYPTO_TFM_RES_BAD_KEY_LEN ) ;
return - EINVAL ;
}
flags = crypto_ablkcipher_get_flags ( tfm ) ;
ret = des_ekey ( tmp , key ) ;
if ( unlikely ( ret = = 0 ) & & ( flags & CRYPTO_TFM_REQ_WEAK_KEY ) ) {
crypto_ablkcipher_set_flags ( tfm , CRYPTO_TFM_RES_WEAK_KEY ) ;
dev_dbg ( ss - > dev , " Weak key %u \n " , keylen ) ;
return - EINVAL ;
}
op - > keylen = keylen ;
memcpy ( op - > key , key , keylen ) ;
return 0 ;
}
/* check and set the 3DES key, prepare the mode to be used */
int sun4i_ss_des3_setkey ( struct crypto_ablkcipher * tfm , const u8 * key ,
unsigned int keylen )
{
struct sun4i_tfm_ctx * op = crypto_ablkcipher_ctx ( tfm ) ;
struct sun4i_ss_ctx * ss = op - > ss ;
if ( unlikely ( keylen ! = 3 * DES_KEY_SIZE ) ) {
dev_err ( ss - > dev , " Invalid keylen %u \n " , keylen ) ;
crypto_ablkcipher_set_flags ( tfm , CRYPTO_TFM_RES_BAD_KEY_LEN ) ;
return - EINVAL ;
}
op - > keylen = keylen ;
memcpy ( op - > key , key , keylen ) ;
return 0 ;
}