summaryrefslogblamecommitdiff
path: root/drivers/crypto/bcm/cipher.c
blob: a353217a0d33e263ce34285cee88491f629ff325 (plain) (tree)
1
2
3
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
3217
3218
3219
3220
3221
3222
3223
3224
3225
3226
3227
3228
3229
3230
3231
3232
3233
3234
3235
3236
3237
3238
3239
3240
3241
3242
3243
3244
3245
3246
3247
3248
3249
3250
3251
3252
3253
3254
3255
3256
3257
3258
3259
3260
3261
3262
3263
3264
3265
3266
3267
3268
3269
3270
3271
3272
3273
3274
3275
3276
3277
3278
3279
3280
3281
3282
3283
3284
3285
3286
3287
3288
3289
3290
3291
3292
3293
3294
3295
3296
3297
3298
3299
3300
3301
3302
3303
3304
3305
3306
3307
3308
3309
3310
3311
3312
3313
3314
3315
3316
3317
3318
3319
3320
3321
3322
3323
3324
3325
3326
3327
3328
3329
3330
3331
3332
3333
3334
3335
3336
3337
3338
3339
3340
3341
3342
3343
3344
3345
3346
3347
3348
3349
3350
3351
3352
3353
3354
3355
3356
3357
3358
3359
3360
3361
3362
3363
3364
3365
3366
3367
3368
3369
3370
3371
3372
3373
3374
3375
3376
3377
3378
3379
3380
3381
3382
3383
3384
3385
3386
3387
3388
3389
3390
3391
3392
3393
3394
3395
3396
3397
3398
3399
3400
3401
3402
3403
3404
3405
3406
3407
3408
3409
3410
3411
3412
3413
3414
3415
3416
3417
3418
3419
3420
3421
3422
3423
3424
3425
3426
3427
3428
3429
3430
3431
3432
3433
3434
3435
3436
3437
3438
3439
3440
3441
3442
3443
3444
3445
3446
3447
3448
3449
3450
3451
3452
3453
3454
3455
3456
3457
3458
3459
3460
3461
3462
3463
3464
3465
3466
3467
3468
3469
3470
3471
3472
3473
3474
3475
3476
3477
3478
3479
3480
3481
3482
3483
3484
3485
3486
3487
3488
3489
3490
3491
3492
3493
3494
3495
3496
3497
3498
3499
3500
3501
3502
3503
3504
3505
3506
3507
3508
3509
3510
3511
3512
3513
3514
3515
3516
3517
3518
3519
3520
3521
3522
3523
3524
3525
3526
3527
3528
3529
3530
3531
3532
3533
3534
3535
3536
3537
3538
3539
3540
3541
3542
3543
3544
3545
3546
3547
3548
3549
3550
3551
3552
3553
3554
3555
3556
3557
3558
3559
3560
3561
3562
3563
3564
3565
3566
3567
3568
3569
3570
3571
3572
3573
3574
3815
3816
3817
3818
3819
3820
3821
3822
3823
3824
3825
3826
3827
3828
3829
3830
3831
3832
3833
3834
3835
3836
3837
3838
3839
3840
3841
3842
3843
3844
3845
3846
3847
3848
3849
3850
3851
3852
3853
3854
3855
3856
3857
3858
3859
3860
3861
3862
3863
3864
3865
3866
3867
3868
3869
3870
3871
3872
3873
3874
3875
3876
3877
3878
3879
3880
3881
3882
3883
3884
3885
3886
3887
3888
3889
3890
3891
3892
3893
3894
3895
3896
3897
3898
3899
3900
3901
3902
3903
3904
3905
3906
3907
3908
3909
3910
3911
3912
3913
3914
3915
3916
3917
3918
3919
3920
3921
3922
3923
3924
3925
3926
3927
3928
3929
3930
3931
3932
3933
3934
3935
3936
3937
3938
3939
3940
3941
3942
3943
3944
3945
3946
3947
3948
3949
3950
3951
3952
3953
3954
3955
3956
3957
3958
3959
3960
3961
3962
3963
3964
3965
3966
3967
3968
3969
3970
3971
3972
3973
3974
3975
3976
3977
3978
3979
3980
3981
3982
3983
3984
3985
3986
3987
3988
3989
3990
3991
3992
3993
3994
3995
3996
3997
3998
3999
4000
4001
4002
4003
4004
4005
4006
4007
4008
4009
4010
4011
4012
4013
4014
4015
4016
4017
4018
4019
4020
4021
4022
4023
4024
4025
4026
4027
4028
4029
4030
4031
4032
4033
4034
4035
4036
4037
4038
4039
4040
4041
4042
4043
4044
4045
4046
4047
4048
4049
4050
4051
4052
4053
4054
4055
4056
4057
4058
4059
4060
4061
4062
4063
4064
4065
4066
4067
4068
4069
4070
4071
4072
4073
4074
4075
4076
4077
4078
4079
4080
4081
4082
4083
4084
4085
4086
4087
4088
4089
4090
4091
4092
4093
4094
4095
4096
4097
4098
4099
4100
4101
4102
4103
4104
4105
4106
4107
4108
4109
4110
4111
4112
4113
4114
4115
4116
4117
4118
4119
4120
4121
4122
4123
4124
4125
4126
4127
4128
4129
4130
4131
4132
4133
4134
4135
4136
4137
4138
4139
4140
4141
4142
4143
4144
4145
4146
4147
4148
4149
4150
4151
4152
4153
4154
4155
4156
4157
4158
4159
4160
4161
4162
4163
4164
4165
4166
4167
4168
4169
4170
4171
4172
4173
4174
4175
4176
4177
4178
4179
4180
4181
4182
4183
4184
4185
4186
4187
4188
4189
4190
4191
4192
4193
4194
4195
4196
4197
4198
4199
4200
4201
4202
4203
4204
4205
4206
4207
4208
4209
4210
4211
4212
4213
4214
4215
4216
4217
4218
4219
4220
4221
4222
4223
4224
4225
4226
4227
4228
4229
4230
4231
4232
4233
4234
4235
4236
4237
4238
4239
4240
4241
4242
4243
4244
4245
4246
4247
                                        

                          






















                                  
                                
                        




                            














































                                                                               






                                                                              
                                                                             



















                                                                              
                                                  


   

                                                                              

















                                                                             
                                                    















































                                                                                           

                                                                            
















                                                                             
                                                    












































                                                                                





































                                                                                
   
                                                                              















                                                                                
                                                           


                                                         

                                                              

                                             


                                                                    







                                                                           




















































































































































                                                                                
                                                                           












                                                                  
                                                                           



                                                   

                                                                          
                           




                            
                                                                            


                                                                 
                                                             



                                                         
                                                                   
































































































































































































                                                                               
                






                                                                             
                                                    












                                                                               






















































































                                                                                
                                                                    








































































































                                                                             

                                                                          
                           
 













































































































































































































































































































































































































                                                                                                      













































                                                                               
                                                             


                                                    
                                                                    































                                                                                
                                                                   
























































































































































                                                                                

                                                                          
                           












































































































                                                                              
                







                                                             











                                                             

                                           




















                                                                             

                                                        




























                                                                               
                                                                   






                                                                       
                                                                       
 
                                                                
                                 
                                                             








                                                                            
                                         



















                                                                     

                                                                        





                                                  
                                        






                                        
                                                                    

                                          
                                                              
                
 
                                                   

                           
 
                                           


                 
                                                                         

                                               
                                                              
                
 
                                                    

                           
 
                                            


                 
                                                                    

                                          
                                                              















                                                           






                                                            
                                                                    

                                          
                                                              















                                                                   
                                                                         


                                                 
                                                              



                                             
                                                           









































                                                                             
                                                             






















                                                                          
                                                         
 
                                                                  
 
                                           

 
                                                         
 

                                                                  






                                                             
                




























































































                                                                                
                                                     












































                                                                              















































































































































































                                                                              















                                                                            
                

































































































































































                                                                                

                                                            





























































































































                                                                                
                                                                            















                                                                              












                                                                             








































































































































































































                                                                                

                                        




                                                                          

                                                             
                            
 

                                            

                            

                                          
 
                                                         

                                                                    
                                                            


                                            

                                                                             
 
                                                   

                             
                                                                              
                                       

                                                    



































                                                                             
                                                                            
                        
                                                                           














                                                                          

















































                                                                             
                        
                                                                           

















                                                                          


























































































































































































                                                                               
                                               












































                                                                
                                               





































































































































































































































































































































































                                                                                
                          
         







                                                                 










                                                  







                                                                










                                                 







                                                                










                                                 







                                                                










                                                 







                                                                  










                                                 







                                                                  










                                                 







                                                                  










                                                 







                                                                










                                                 







                                                                










                                                 







                                                                










                                                 







                                                                










                                                 







                                                                



















                                                                         
                                                                  
















































































































































































































































































































































































































































                                                                             
                                                              
 

                                                                 



                                       
                                                                             
 
                                                                         






































































                                                                                          




                                                          





















































































                                                                              






                                                                          



                              
                                 


                                           


                                                                  
                                                          





                                                                             


                 






                                                              





                                                        
                                                     









                                                      
                                                                        





















                                                                    
                                                                

                                             
                                                                






                                                              




                                                                                
 




                                           
 
                                               


                                                               
                                                                             

























                                                                             
                                                     


                                                                




                                            




                                                                      




























                                                               
 
                                                 






















                                                                       

                                                                     




























                                                                               

                                                                                 



























































                                                                          
                                                        




                                                                                
 

                                                         
                                                         


                               

                                                     
 

                                                                                
 


                                                                              
                                                                     



                                                 
         

                                                         



                 
                                                      


                                             
                
 

                                             





                                          
                               


                             




                                                
                                                                     








                                     










                                                                  
                                                       














                                                                           

                                                                                 
                                                                  
                                                                                  



































                                                                                
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright 2016 Broadcom
 */

#include <linux/err.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/scatterlist.h>
#include <linux/crypto.h>
#include <linux/kthread.h>
#include <linux/rtnetlink.h>
#include <linux/sched.h>
#include <linux/of_address.h>
#include <linux/of_device.h>
#include <linux/io.h>
#include <linux/bitops.h>

#include <crypto/algapi.h>
#include <crypto/aead.h>
#include <crypto/internal/aead.h>
#include <crypto/aes.h>
#include <crypto/internal/des.h>
#include <crypto/hmac.h>
#include <crypto/sha.h>
#include <crypto/md5.h>
#include <crypto/authenc.h>
#include <crypto/skcipher.h>
#include <crypto/hash.h>
#include <crypto/sha3.h>

#include "util.h"
#include "cipher.h"
#include "spu.h"
#include "spum.h"
#include "spu2.h"

/* ================= Device Structure ================== */

struct device_private iproc_priv;

/* ==================== Parameters ===================== */

int flow_debug_logging;
module_param(flow_debug_logging, int, 0644);
MODULE_PARM_DESC(flow_debug_logging, "Enable Flow Debug Logging");

int packet_debug_logging;
module_param(packet_debug_logging, int, 0644);
MODULE_PARM_DESC(packet_debug_logging, "Enable Packet Debug Logging");

int debug_logging_sleep;
module_param(debug_logging_sleep, int, 0644);
MODULE_PARM_DESC(debug_logging_sleep, "Packet Debug Logging Sleep");

/*
 * The value of these module parameters is used to set the priority for each
 * algo type when this driver registers algos with the kernel crypto API.
 * To use a priority other than the default, set the priority in the insmod or
 * modprobe. Changing the module priority after init time has no effect.
 *
 * The default priorities are chosen to be lower (less preferred) than ARMv8 CE
 * algos, but more preferred than generic software algos.
 */
static int cipher_pri = 150;
module_param(cipher_pri, int, 0644);
MODULE_PARM_DESC(cipher_pri, "Priority for cipher algos");

static int hash_pri = 100;
module_param(hash_pri, int, 0644);
MODULE_PARM_DESC(hash_pri, "Priority for hash algos");

static int aead_pri = 150;
module_param(aead_pri, int, 0644);
MODULE_PARM_DESC(aead_pri, "Priority for AEAD algos");

/* A type 3 BCM header, expected to precede the SPU header for SPU-M.
 * Bits 3 and 4 in the first byte encode the channel number (the dma ringset).
 * 0x60 - ring 0
 * 0x68 - ring 1
 * 0x70 - ring 2
 * 0x78 - ring 3
 */
static char BCMHEADER[] = { 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28 };
/*
 * Some SPU hw does not use BCM header on SPU messages. So BCM_HDR_LEN
 * is set dynamically after reading SPU type from device tree.
 */
#define BCM_HDR_LEN  iproc_priv.bcm_hdr_len

/* min and max time to sleep before retrying when mbox queue is full. usec */
#define MBOX_SLEEP_MIN  800
#define MBOX_SLEEP_MAX 1000

/**
 * select_channel() - Select a SPU channel to handle a crypto request. Selects
 * channel in round robin order.
 *
 * Return:  channel index
 */
static u8 select_channel(void)
{
	u8 chan_idx = atomic_inc_return(&iproc_priv.next_chan);

	return chan_idx % iproc_priv.spu.num_chan;
}

/**
 * spu_skcipher_rx_sg_create() - Build up the scatterlist of buffers used to
 * receive a SPU response message for an skcipher request. Includes buffers to
 * catch SPU message headers and the response data.
 * @mssg:	mailbox message containing the receive sg
 * @rctx:	crypto request context
 * @rx_frag_num: number of scatterlist elements required to hold the
 *		SPU response message
 * @chunksize:	Number of bytes of response data expected
 * @stat_pad_len: Number of bytes required to pad the STAT field to
 *		a 4-byte boundary
 *
 * The scatterlist that gets allocated here is freed in spu_chunk_cleanup()
 * when the request completes, whether the request is handled successfully or
 * there is an error.
 *
 * Returns:
 *   0 if successful
 *   < 0 if an error
 */
static int
spu_skcipher_rx_sg_create(struct brcm_message *mssg,
			    struct iproc_reqctx_s *rctx,
			    u8 rx_frag_num,
			    unsigned int chunksize, u32 stat_pad_len)
{
	struct spu_hw *spu = &iproc_priv.spu;
	struct scatterlist *sg;	/* used to build sgs in mbox message */
	struct iproc_ctx_s *ctx = rctx->ctx;
	u32 datalen;		/* Number of bytes of response data expected */

	mssg->spu.dst = kcalloc(rx_frag_num, sizeof(struct scatterlist),
				rctx->gfp);
	if (!mssg->spu.dst)
		return -ENOMEM;

	sg = mssg->spu.dst;
	sg_init_table(sg, rx_frag_num);
	/* Space for SPU message header */
	sg_set_buf(sg++, rctx->msg_buf.spu_resp_hdr, ctx->spu_resp_hdr_len);

	/* If XTS tweak in payload, add buffer to receive encrypted tweak */
	if ((ctx->cipher.mode == CIPHER_MODE_XTS) &&
	    spu->spu_xts_tweak_in_payload())
		sg_set_buf(sg++, rctx->msg_buf.c.supdt_tweak,
			   SPU_XTS_TWEAK_SIZE);

	/* Copy in each dst sg entry from request, up to chunksize */
	datalen = spu_msg_sg_add(&sg, &rctx->dst_sg, &rctx->dst_skip,
				 rctx->dst_nents, chunksize);
	if (datalen < chunksize) {
		pr_err("%s(): failed to copy dst sg to mbox msg. chunksize %u, datalen %u",
		       __func__, chunksize, datalen);
		return -EFAULT;
	}

	if (ctx->cipher.alg == CIPHER_ALG_RC4)
		/* Add buffer to catch 260-byte SUPDT field for RC4 */
		sg_set_buf(sg++, rctx->msg_buf.c.supdt_tweak, SPU_SUPDT_LEN);

	if (stat_pad_len)
		sg_set_buf(sg++, rctx->msg_buf.rx_stat_pad, stat_pad_len);

	memset(rctx->msg_buf.rx_stat, 0, SPU_RX_STATUS_LEN);
	sg_set_buf(sg, rctx->msg_buf.rx_stat, spu->spu_rx_status_len());

	return 0;
}

/**
 * spu_skcipher_tx_sg_create() - Build up the scatterlist of buffers used to
 * send a SPU request message for an skcipher request. Includes SPU message
 * headers and the request data.
 * @mssg:	mailbox message containing the transmit sg
 * @rctx:	crypto request context
 * @tx_frag_num: number of scatterlist elements required to construct the
 *		SPU request message
 * @chunksize:	Number of bytes of request data
 * @pad_len:	Number of pad bytes
 *
 * The scatterlist that gets allocated here is freed in spu_chunk_cleanup()
 * when the request completes, whether the request is handled successfully or
 * there is an error.
 *
 * Returns:
 *   0 if successful
 *   < 0 if an error
 */
static int
spu_skcipher_tx_sg_create(struct brcm_message *mssg,
			    struct iproc_reqctx_s *rctx,
			    u8 tx_frag_num, unsigned int chunksize, u32 pad_len)
{
	struct spu_hw *spu = &iproc_priv.spu;
	struct scatterlist *sg;	/* used to build sgs in mbox message */
	struct iproc_ctx_s *ctx = rctx->ctx;
	u32 datalen;		/* Number of bytes of response data expected */
	u32 stat_len;

	mssg->spu.src = kcalloc(tx_frag_num, sizeof(struct scatterlist),
				rctx->gfp);
	if (unlikely(!mssg->spu.src))
		return -ENOMEM;

	sg = mssg->spu.src;
	sg_init_table(sg, tx_frag_num);

	sg_set_buf(sg++, rctx->msg_buf.bcm_spu_req_hdr,
		   BCM_HDR_LEN + ctx->spu_req_hdr_len);

	/* if XTS tweak in payload, copy from IV (where crypto API puts it) */
	if ((ctx->cipher.mode == CIPHER_MODE_XTS) &&
	    spu->spu_xts_tweak_in_payload())
		sg_set_buf(sg++, rctx->msg_buf.iv_ctr, SPU_XTS_TWEAK_SIZE);

	/* Copy in each src sg entry from request, up to chunksize */
	datalen = spu_msg_sg_add(&sg, &rctx->src_sg, &rctx->src_skip,
				 rctx->src_nents, chunksize);
	if (unlikely(datalen < chunksize)) {
		pr_err("%s(): failed to copy src sg to mbox msg",
		       __func__);
		return -EFAULT;
	}

	if (pad_len)
		sg_set_buf(sg++, rctx->msg_buf.spu_req_pad, pad_len);

	stat_len = spu->spu_tx_status_len();
	if (stat_len) {
		memset(rctx->msg_buf.tx_stat, 0, stat_len);
		sg_set_buf(sg, rctx->msg_buf.tx_stat, stat_len);
	}
	return 0;
}

static int mailbox_send_message(struct brcm_message *mssg, u32 flags,
				u8 chan_idx)
{
	int err;
	int retry_cnt = 0;
	struct device *dev = &(iproc_priv.pdev->dev);

	err = mbox_send_message(iproc_priv.mbox[chan_idx], mssg);
	if (flags & CRYPTO_TFM_REQ_MAY_SLEEP) {
		while ((err == -ENOBUFS) && (retry_cnt < SPU_MB_RETRY_MAX)) {
			/*
			 * Mailbox queue is full. Since MAY_SLEEP is set, assume
			 * not in atomic context and we can wait and try again.
			 */
			retry_cnt++;
			usleep_range(MBOX_SLEEP_MIN, MBOX_SLEEP_MAX);
			err = mbox_send_message(iproc_priv.mbox[chan_idx],
						mssg);
			atomic_inc(&iproc_priv.mb_no_spc);
		}
	}
	if (err < 0) {
		atomic_inc(&iproc_priv.mb_send_fail);
		return err;
	}

	/* Check error returned by mailbox controller */
	err = mssg->error;
	if (unlikely(err < 0)) {
		dev_err(dev, "message error %d", err);
		/* Signal txdone for mailbox channel */
	}

	/* Signal txdone for mailbox channel */
	mbox_client_txdone(iproc_priv.mbox[chan_idx], err);
	return err;
}

/**
 * handle_skcipher_req() - Submit as much of a block cipher request as fits in
 * a single SPU request message, starting at the current position in the request
 * data.
 * @rctx:	Crypto request context
 *
 * This may be called on the crypto API thread, or, when a request is so large
 * it must be broken into multiple SPU messages, on the thread used to invoke
 * the response callback. When requests are broken into multiple SPU
 * messages, we assume subsequent messages depend on previous results, and
 * thus always wait for previous results before submitting the next message.
 * Because requests are submitted in lock step like this, there is no need
 * to synchronize access to request data structures.
 *
 * Return: -EINPROGRESS: request has been accepted and result will be returned
 *			 asynchronously
 *         Any other value indicates an error
 */
static int handle_skcipher_req(struct iproc_reqctx_s *rctx)
{
	struct spu_hw *spu = &iproc_priv.spu;
	struct crypto_async_request *areq = rctx->parent;
	struct skcipher_request *req =
	    container_of(areq, struct skcipher_request, base);
	struct iproc_ctx_s *ctx = rctx->ctx;
	struct spu_cipher_parms cipher_parms;
	int err;
	unsigned int chunksize;	/* Num bytes of request to submit */
	int remaining;	/* Bytes of request still to process */
	int chunk_start;	/* Beginning of data for current SPU msg */

	/* IV or ctr value to use in this SPU msg */
	u8 local_iv_ctr[MAX_IV_SIZE];
	u32 stat_pad_len;	/* num bytes to align status field */
	u32 pad_len;		/* total length of all padding */
	bool update_key = false;
	struct brcm_message *mssg;	/* mailbox message */

	/* number of entries in src and dst sg in mailbox message. */
	u8 rx_frag_num = 2;	/* response header and STATUS */
	u8 tx_frag_num = 1;	/* request header */

	flow_log("%s\n", __func__);

	cipher_parms.alg = ctx->cipher.alg;
	cipher_parms.mode = ctx->cipher.mode;
	cipher_parms.type = ctx->cipher_type;
	cipher_parms.key_len = ctx->enckeylen;
	cipher_parms.key_buf = ctx->enckey;
	cipher_parms.iv_buf = local_iv_ctr;
	cipher_parms.iv_len = rctx->iv_ctr_len;

	mssg = &rctx->mb_mssg;
	chunk_start = rctx->src_sent;
	remaining = rctx->total_todo - chunk_start;

	/* determine the chunk we are breaking off and update the indexes */
	if ((ctx->max_payload != SPU_MAX_PAYLOAD_INF) &&
	    (remaining > ctx->max_payload))
		chunksize = ctx->max_payload;
	else
		chunksize = remaining;

	rctx->src_sent += chunksize;
	rctx->total_sent = rctx->src_sent;

	/* Count number of sg entries to be included in this request */
	rctx->src_nents = spu_sg_count(rctx->src_sg, rctx->src_skip, chunksize);
	rctx->dst_nents = spu_sg_count(rctx->dst_sg, rctx->dst_skip, chunksize);

	if ((ctx->cipher.mode == CIPHER_MODE_CBC) &&
	    rctx->is_encrypt && chunk_start)
		/*
		 * Encrypting non-first first chunk. Copy last block of
		 * previous result to IV for this chunk.
		 */
		sg_copy_part_to_buf(req->dst, rctx->msg_buf.iv_ctr,
				    rctx->iv_ctr_len,
				    chunk_start - rctx->iv_ctr_len);

	if (rctx->iv_ctr_len) {
		/* get our local copy of the iv */
		__builtin_memcpy(local_iv_ctr, rctx->msg_buf.iv_ctr,
				 rctx->iv_ctr_len);

		/* generate the next IV if possible */
		if ((ctx->cipher.mode == CIPHER_MODE_CBC) &&
		    !rctx->is_encrypt) {
			/*
			 * CBC Decrypt: next IV is the last ciphertext block in
			 * this chunk
			 */
			sg_copy_part_to_buf(req->src, rctx->msg_buf.iv_ctr,
					    rctx->iv_ctr_len,
					    rctx->src_sent - rctx->iv_ctr_len);
		} else if (ctx->cipher.mode == CIPHER_MODE_CTR) {
			/*
			 * The SPU hardware increments the counter once for
			 * each AES block of 16 bytes. So update the counter
			 * for the next chunk, if there is one. Note that for
			 * this chunk, the counter has already been copied to
			 * local_iv_ctr. We can assume a block size of 16,
			 * because we only support CTR mode for AES, not for
			 * any other cipher alg.
			 */
			add_to_ctr(rctx->msg_buf.iv_ctr, chunksize >> 4);
		}
	}

	if (ctx->cipher.alg == CIPHER_ALG_RC4) {
		rx_frag_num++;
		if (chunk_start) {
			/*
			 * for non-first RC4 chunks, use SUPDT from previous
			 * response as key for this chunk.
			 */
			cipher_parms.key_buf = rctx->msg_buf.c.supdt_tweak;
			update_key = true;
			cipher_parms.type = CIPHER_TYPE_UPDT;
		} else if (!rctx->is_encrypt) {
			/*
			 * First RC4 chunk. For decrypt, key in pre-built msg
			 * header may have been changed if encrypt required
			 * multiple chunks. So revert the key to the
			 * ctx->enckey value.
			 */
			update_key = true;
			cipher_parms.type = CIPHER_TYPE_INIT;
		}
	}

	if (ctx->max_payload == SPU_MAX_PAYLOAD_INF)
		flow_log("max_payload infinite\n");
	else
		flow_log("max_payload %u\n", ctx->max_payload);

	flow_log("sent:%u start:%u remains:%u size:%u\n",
		 rctx->src_sent, chunk_start, remaining, chunksize);

	/* Copy SPU header template created at setkey time */
	memcpy(rctx->msg_buf.bcm_spu_req_hdr, ctx->bcm_spu_req_hdr,
	       sizeof(rctx->msg_buf.bcm_spu_req_hdr));

	/*
	 * Pass SUPDT field as key. Key field in finish() call is only used
	 * when update_key has been set above for RC4. Will be ignored in
	 * all other cases.
	 */
	spu->spu_cipher_req_finish(rctx->msg_buf.bcm_spu_req_hdr + BCM_HDR_LEN,
				   ctx->spu_req_hdr_len, !(rctx->is_encrypt),
				   &cipher_parms, update_key, chunksize);

	atomic64_add(chunksize, &iproc_priv.bytes_out);

	stat_pad_len = spu->spu_wordalign_padlen(chunksize);
	if (stat_pad_len)
		rx_frag_num++;
	pad_len = stat_pad_len;
	if (pad_len) {
		tx_frag_num++;
		spu->spu_request_pad(rctx->msg_buf.spu_req_pad, 0,
				     0, ctx->auth.alg, ctx->auth.mode,
				     rctx->total_sent, stat_pad_len);
	}

	spu->spu_dump_msg_hdr(rctx->msg_buf.bcm_spu_req_hdr + BCM_HDR_LEN,
			      ctx->spu_req_hdr_len);
	packet_log("payload:\n");
	dump_sg(rctx->src_sg, rctx->src_skip, chunksize);
	packet_dump("   pad: ", rctx->msg_buf.spu_req_pad, pad_len);

	/*
	 * Build mailbox message containing SPU request msg and rx buffers
	 * to catch response message
	 */
	memset(mssg, 0, sizeof(*mssg));
	mssg->type = BRCM_MESSAGE_SPU;
	mssg->ctx = rctx;	/* Will be returned in response */

	/* Create rx scatterlist to catch result */
	rx_frag_num += rctx->dst_nents;

	if ((ctx->cipher.mode == CIPHER_MODE_XTS) &&
	    spu->spu_xts_tweak_in_payload())
		rx_frag_num++;	/* extra sg to insert tweak */

	err = spu_skcipher_rx_sg_create(mssg, rctx, rx_frag_num, chunksize,
					  stat_pad_len);
	if (err)
		return err;

	/* Create tx scatterlist containing SPU request message */
	tx_frag_num += rctx->src_nents;
	if (spu->spu_tx_status_len())
		tx_frag_num++;

	if ((ctx->cipher.mode == CIPHER_MODE_XTS) &&
	    spu->spu_xts_tweak_in_payload())
		tx_frag_num++;	/* extra sg to insert tweak */

	err = spu_skcipher_tx_sg_create(mssg, rctx, tx_frag_num, chunksize,
					  pad_len);
	if (err)
		return err;

	err = mailbox_send_message(mssg, req->base.flags, rctx->chan_idx);
	if (unlikely(err < 0))
		return err;

	return -EINPROGRESS;
}

/**
 * handle_skcipher_resp() - Process a block cipher SPU response. Updates the
 * total received count for the request and updates global stats.
 * @rctx:	Crypto request context
 */
static void handle_skcipher_resp(struct iproc_reqctx_s *rctx)
{
	struct spu_hw *spu = &iproc_priv.spu;
#ifdef DEBUG
	struct crypto_async_request *areq = rctx->parent;
	struct skcipher_request *req = skcipher_request_cast(areq);
#endif
	struct iproc_ctx_s *ctx = rctx->ctx;
	u32 payload_len;

	/* See how much data was returned */
	payload_len = spu->spu_payload_length(rctx->msg_buf.spu_resp_hdr);

	/*
	 * In XTS mode, the first SPU_XTS_TWEAK_SIZE bytes may be the
	 * encrypted tweak ("i") value; we don't count those.
	 */
	if ((ctx->cipher.mode == CIPHER_MODE_XTS) &&
	    spu->spu_xts_tweak_in_payload() &&
	    (payload_len >= SPU_XTS_TWEAK_SIZE))
		payload_len -= SPU_XTS_TWEAK_SIZE;

	atomic64_add(payload_len, &iproc_priv.bytes_in);

	flow_log("%s() offset: %u, bd_len: %u BD:\n",
		 __func__, rctx->total_received, payload_len);

	dump_sg(req->dst, rctx->total_received, payload_len);
	if (ctx->cipher.alg == CIPHER_ALG_RC4)
		packet_dump("  supdt ", rctx->msg_buf.c.supdt_tweak,
			    SPU_SUPDT_LEN);

	rctx->total_received += payload_len;
	if (rctx->total_received == rctx->total_todo) {
		atomic_inc(&iproc_priv.op_counts[SPU_OP_CIPHER]);
		atomic_inc(
		   &iproc_priv.cipher_cnt[ctx->cipher.alg][ctx->cipher.mode]);
	}
}

/**
 * spu_ahash_rx_sg_create() - Build up the scatterlist of buffers used to
 * receive a SPU response message for an ahash request.
 * @mssg:	mailbox message containing the receive sg
 * @rctx:	crypto request context
 * @rx_frag_num: number of scatterlist elements required to hold the
 *		SPU response message
 * @digestsize: length of hash digest, in bytes
 * @stat_pad_len: Number of bytes required to pad the STAT field to
 *		a 4-byte boundary
 *
 * The scatterlist that gets allocated here is freed in spu_chunk_cleanup()
 * when the request completes, whether the request is handled successfully or
 * there is an error.
 *
 * Return:
 *   0 if successful
 *   < 0 if an error
 */
static int
spu_ahash_rx_sg_create(struct brcm_message *mssg,
		       struct iproc_reqctx_s *rctx,
		       u8 rx_frag_num, unsigned int digestsize,
		       u32 stat_pad_len)
{
	struct spu_hw *spu = &iproc_priv.spu;
	struct scatterlist *sg;	/* used to build sgs in mbox message */
	struct iproc_ctx_s *ctx = rctx->ctx;

	mssg->spu.dst = kcalloc(rx_frag_num, sizeof(struct scatterlist),
				rctx->gfp);
	if (!mssg->spu.dst)
		return -ENOMEM;

	sg = mssg->spu.dst;
	sg_init_table(sg, rx_frag_num);
	/* Space for SPU message header */
	sg_set_buf(sg++, rctx->msg_buf.spu_resp_hdr, ctx->spu_resp_hdr_len);

	/* Space for digest */
	sg_set_buf(sg++, rctx->msg_buf.digest, digestsize);

	if (stat_pad_len)
		sg_set_buf(sg++, rctx->msg_buf.rx_stat_pad, stat_pad_len);

	memset(rctx->msg_buf.rx_stat, 0, SPU_RX_STATUS_LEN);
	sg_set_buf(sg, rctx->msg_buf.rx_stat, spu->spu_rx_status_len());
	return 0;
}

/**
 * spu_ahash_tx_sg_create() -  Build up the scatterlist of buffers used to send
 * a SPU request message for an ahash request. Includes SPU message headers and
 * the request data.
 * @mssg:	mailbox message containing the transmit sg
 * @rctx:	crypto request context
 * @tx_frag_num: number of scatterlist elements required to construct the
 *		SPU request message
 * @spu_hdr_len: length in bytes of SPU message header
 * @hash_carry_len: Number of bytes of data carried over from previous req
 * @new_data_len: Number of bytes of new request data
 * @pad_len:	Number of pad bytes
 *
 * The scatterlist that gets allocated here is freed in spu_chunk_cleanup()
 * when the request completes, whether the request is handled successfully or
 * there is an error.
 *
 * Return:
 *   0 if successful
 *   < 0 if an error
 */
static int
spu_ahash_tx_sg_create(struct brcm_message *mssg,
		       struct iproc_reqctx_s *rctx,
		       u8 tx_frag_num,
		       u32 spu_hdr_len,
		       unsigned int hash_carry_len,
		       unsigned int new_data_len, u32 pad_len)
{
	struct spu_hw *spu = &iproc_priv.spu;
	struct scatterlist *sg;	/* used to build sgs in mbox message */
	u32 datalen;		/* Number of bytes of response data expected */
	u32 stat_len;

	mssg->spu.src = kcalloc(tx_frag_num, sizeof(struct scatterlist),
				rctx->gfp);
	if (!mssg->spu.src)
		return -ENOMEM;

	sg = mssg->spu.src;
	sg_init_table(sg, tx_frag_num);

	sg_set_buf(sg++, rctx->msg_buf.bcm_spu_req_hdr,
		   BCM_HDR_LEN + spu_hdr_len);

	if (hash_carry_len)
		sg_set_buf(sg++, rctx->hash_carry, hash_carry_len);

	if (new_data_len) {
		/* Copy in each src sg entry from request, up to chunksize */
		datalen = spu_msg_sg_add(&sg, &rctx->src_sg, &rctx->src_skip,
					 rctx->src_nents, new_data_len);
		if (datalen < new_data_len) {
			pr_err("%s(): failed to copy src sg to mbox msg",
			       __func__);
			return -EFAULT;
		}
	}

	if (pad_len)
		sg_set_buf(sg++, rctx->msg_buf.spu_req_pad, pad_len);

	stat_len = spu->spu_tx_status_len();
	if (stat_len) {
		memset(rctx->msg_buf.tx_stat, 0, stat_len);
		sg_set_buf(sg, rctx->msg_buf.tx_stat, stat_len);
	}

	return 0;
}

/**
 * handle_ahash_req() - Process an asynchronous hash request from the crypto
 * API.
 * @rctx:  Crypto request context
 *
 * Builds a SPU request message embedded in a mailbox message and submits the
 * mailbox message on a selected mailbox channel. The SPU request message is
 * constructed as a scatterlist, including entries from the crypto API's
 * src scatterlist to avoid copying the data to be hashed. This function is
 * called either on the thread from the crypto API, or, in the case that the
 * crypto API request is too large to fit in a single SPU request message,
 * on the thread that invokes the receive callback with a response message.
 * Because some operations require the response from one chunk before the next
 * chunk can be submitted, we always wait for the response for the previous
 * chunk before submitting the next chunk. Because requests are submitted in
 * lock step like this, there is no need to synchronize access to request data
 * structures.
 *
 * Return:
 *   -EINPROGRESS: request has been submitted to SPU and response will be
 *		   returned asynchronously
 *   -EAGAIN:      non-final request included a small amount of data, which for
 *		   efficiency we did not submit to the SPU, but instead stored
 *		   to be submitted to the SPU with the next part of the request
 *   other:        an error code
 */
static int handle_ahash_req(struct iproc_reqctx_s *rctx)
{
	struct spu_hw *spu = &iproc_priv.spu;
	struct crypto_async_request *areq = rctx->parent;
	struct ahash_request *req = ahash_request_cast(areq);
	struct crypto_ahash *ahash = crypto_ahash_reqtfm(req);
	struct crypto_tfm *tfm = crypto_ahash_tfm(ahash);
	unsigned int blocksize = crypto_tfm_alg_blocksize(tfm);
	struct iproc_ctx_s *ctx = rctx->ctx;

	/* number of bytes still to be hashed in this req */
	unsigned int nbytes_to_hash = 0;
	int err;
	unsigned int chunksize = 0;	/* length of hash carry + new data */
	/*
	 * length of new data, not from hash carry, to be submitted in
	 * this hw request
	 */
	unsigned int new_data_len;

	unsigned int __maybe_unused chunk_start = 0;
	u32 db_size;	 /* Length of data field, incl gcm and hash padding */
	int pad_len = 0; /* total pad len, including gcm, hash, stat padding */
	u32 data_pad_len = 0;	/* length of GCM/CCM padding */
	u32 stat_pad_len = 0;	/* length of padding to align STATUS word */
	struct brcm_message *mssg;	/* mailbox message */
	struct spu_request_opts req_opts;
	struct spu_cipher_parms cipher_parms;
	struct spu_hash_parms hash_parms;
	struct spu_aead_parms aead_parms;
	unsigned int local_nbuf;
	u32 spu_hdr_len;
	unsigned int digestsize;
	u16 rem = 0;

	/*
	 * number of entries in src and dst sg. Always includes SPU msg header.
	 * rx always includes a buffer to catch digest and STATUS.
	 */
	u8 rx_frag_num = 3;
	u8 tx_frag_num = 1;

	flow_log("total_todo %u, total_sent %u\n",
		 rctx->total_todo, rctx->total_sent);

	memset(&req_opts, 0, sizeof(req_opts));
	memset(&cipher_parms, 0, sizeof(cipher_parms));
	memset(&hash_parms, 0, sizeof(hash_parms));
	memset(&aead_parms, 0, sizeof(aead_parms));

	req_opts.bd_suppress = true;
	hash_parms.alg = ctx->auth.alg;
	hash_parms.mode = ctx->auth.mode;
	hash_parms.type = HASH_TYPE_NONE;
	hash_parms.key_buf = (u8 *)ctx->authkey;
	hash_parms.key_len = ctx->authkeylen;

	/*
	 * For hash algorithms below assignment looks bit odd but
	 * it's needed for AES-XCBC and AES-CMAC hash algorithms
	 * to differentiate between 128, 192, 256 bit key values.
	 * Based on the key values, hash algorithm is selected.
	 * For example for 128 bit key, hash algorithm is AES-128.
	 */
	cipher_parms.type = ctx->cipher_type;

	mssg = &rctx->mb_mssg;
	chunk_start = rctx->src_sent;

	/*
	 * Compute the amount remaining to hash. This may include data
	 * carried over from previous requests.
	 */
	nbytes_to_hash = rctx->total_todo - rctx->total_sent;
	chunksize = nbytes_to_hash;
	if ((ctx->max_payload != SPU_MAX_PAYLOAD_INF) &&
	    (chunksize > ctx->max_payload))
		chunksize = ctx->max_payload;

	/*
	 * If this is not a final request and the request data is not a multiple
	 * of a full block, then simply park the extra data and prefix it to the
	 * data for the next request.
	 */
	if (!rctx->is_final) {
		u8 *dest = rctx->hash_carry + rctx->hash_carry_len;
		u16 new_len;  /* len of data to add to hash carry */

		rem = chunksize % blocksize;   /* remainder */
		if (rem) {
			/* chunksize not a multiple of blocksize */
			chunksize -= rem;
			if (chunksize == 0) {
				/* Don't have a full block to submit to hw */
				new_len = rem - rctx->hash_carry_len;
				sg_copy_part_to_buf(req->src, dest, new_len,
						    rctx->src_sent);
				rctx->hash_carry_len = rem;
				flow_log("Exiting with hash carry len: %u\n",
					 rctx->hash_carry_len);
				packet_dump("  buf: ",
					    rctx->hash_carry,
					    rctx->hash_carry_len);
				return -EAGAIN;
			}
		}
	}

	/* if we have hash carry, then prefix it to the data in this request */
	local_nbuf = rctx->hash_carry_len;
	rctx->hash_carry_len = 0;
	if (local_nbuf)
		tx_frag_num++;
	new_data_len = chunksize - local_nbuf;

	/* Count number of sg entries to be used in this request */
	rctx->src_nents = spu_sg_count(rctx->src_sg, rctx->src_skip,
				       new_data_len);

	/* AES hashing keeps key size in type field, so need to copy it here */
	if (hash_parms.alg == HASH_ALG_AES)
		hash_parms.type = (enum hash_type)cipher_parms.type;
	else
		hash_parms.type = spu->spu_hash_type(rctx->total_sent);

	digestsize = spu->spu_digest_size(ctx->digestsize, ctx->auth.alg,
					  hash_parms.type);
	hash_parms.digestsize =	digestsize;

	/* update the indexes */
	rctx->total_sent += chunksize;
	/* if you sent a prebuf then that wasn't from this req->src */
	rctx->src_sent += new_data_len;

	if ((rctx->total_sent == rctx->total_todo) && rctx->is_final)
		hash_parms.pad_len = spu->spu_hash_pad_len(hash_parms.alg,
							   hash_parms.mode,
							   chunksize,
							   blocksize);

	/*
	 * If a non-first chunk, then include the digest returned from the
	 * previous chunk so that hw can add to it (except for AES types).
	 */
	if ((hash_parms.type == HASH_TYPE_UPDT) &&
	    (hash_parms.alg != HASH_ALG_AES)) {
		hash_parms.key_buf = rctx->incr_hash;
		hash_parms.key_len = digestsize;
	}

	atomic64_add(chunksize, &iproc_priv.bytes_out);

	flow_log("%s() final: %u nbuf: %u ",
		 __func__, rctx->is_final, local_nbuf);

	if (ctx->max_payload == SPU_MAX_PAYLOAD_INF)
		flow_log("max_payload infinite\n");
	else
		flow_log("max_payload %u\n", ctx->max_payload);

	flow_log("chunk_start: %u chunk_size: %u\n", chunk_start, chunksize);

	/* Prepend SPU header with type 3 BCM header */
	memcpy(rctx->msg_buf.bcm_spu_req_hdr, BCMHEADER, BCM_HDR_LEN);

	hash_parms.prebuf_len = local_nbuf;
	spu_hdr_len = spu->spu_create_request(rctx->msg_buf.bcm_spu_req_hdr +
					      BCM_HDR_LEN,
					      &req_opts, &cipher_parms,
					      &hash_parms, &aead_parms,
					      new_data_len);

	if (spu_hdr_len == 0) {
		pr_err("Failed to create SPU request header\n");
		return -EFAULT;
	}

	/*
	 * Determine total length of padding required. Put all padding in one
	 * buffer.
	 */
	data_pad_len = spu->spu_gcm_ccm_pad_len(ctx->cipher.mode, chunksize);
	db_size = spu_real_db_size(0, 0, local_nbuf, new_data_len,
				   0, 0, hash_parms.pad_len);
	if (spu->spu_tx_status_len())
		stat_pad_len = spu->spu_wordalign_padlen(db_size);
	if (stat_pad_len)
		rx_frag_num++;
	pad_len = hash_parms.pad_len + data_pad_len + stat_pad_len;
	if (pad_len) {
		tx_frag_num++;
		spu->spu_request_pad(rctx->msg_buf.spu_req_pad, data_pad_len,
				     hash_parms.pad_len, ctx->auth.alg,
				     ctx->auth.mode, rctx->total_sent,
				     stat_pad_len);
	}

	spu->spu_dump_msg_hdr(rctx->msg_buf.bcm_spu_req_hdr + BCM_HDR_LEN,
			      spu_hdr_len);
	packet_dump("    prebuf: ", rctx->hash_carry, local_nbuf);
	flow_log("Data:\n");
	dump_sg(rctx->src_sg, rctx->src_skip, new_data_len);
	packet_dump("   pad: ", rctx->msg_buf.spu_req_pad, pad_len);

	/*
	 * Build mailbox message containing SPU request msg and rx buffers
	 * to catch response message
	 */
	memset(mssg, 0, sizeof(*mssg));
	mssg->type = BRCM_MESSAGE_SPU;
	mssg->ctx = rctx;	/* Will be returned in response */

	/* Create rx scatterlist to catch result */
	err = spu_ahash_rx_sg_create(mssg, rctx, rx_frag_num, digestsize,
				     stat_pad_len);
	if (err)
		return err;

	/* Create tx scatterlist containing SPU request message */
	tx_frag_num += rctx->src_nents;
	if (spu->spu_tx_status_len())
		tx_frag_num++;
	err = spu_ahash_tx_sg_create(mssg, rctx, tx_frag_num, spu_hdr_len,
				     local_nbuf, new_data_len, pad_len);
	if (err)
		return err;

	err = mailbox_send_message(mssg, req->base.flags, rctx->chan_idx);
	if (unlikely(err < 0))
		return err;

	return -EINPROGRESS;
}

/**
 * spu_hmac_outer_hash() - Request synchonous software compute of the outer hash
 * for an HMAC request.
 * @req:  The HMAC request from the crypto API
 * @ctx:  The session context
 *
 * Return: 0 if synchronous hash operation successful
 *         -EINVAL if the hash algo is unrecognized
 *         any other value indicates an error
 */
static int spu_hmac_outer_hash(struct ahash_request *req,
			       struct iproc_ctx_s *ctx)
{
	struct crypto_ahash *ahash = crypto_ahash_reqtfm(req);
	unsigned int blocksize =
		crypto_tfm_alg_blocksize(crypto_ahash_tfm(ahash));
	int rc;

	switch (ctx->auth.alg) {
	case HASH_ALG_MD5:
		rc = do_shash("md5", req->result, ctx->opad, blocksize,
			      req->result, ctx->digestsize, NULL, 0);
		break;
	case HASH_ALG_SHA1:
		rc = do_shash("sha1", req->result, ctx->opad, blocksize,
			      req->result, ctx->digestsize, NULL, 0);
		break;
	case HASH_ALG_SHA224:
		rc = do_shash("sha224", req->result, ctx->opad, blocksize,
			      req->result, ctx->digestsize, NULL, 0);
		break;
	case HASH_ALG_SHA256:
		rc = do_shash("sha256", req->result, ctx->opad, blocksize,
			      req->result, ctx->digestsize, NULL, 0);
		break;
	case HASH_ALG_SHA384:
		rc = do_shash("sha384", req->result, ctx->opad, blocksize,
			      req->result, ctx->digestsize, NULL, 0);
		break;
	case HASH_ALG_SHA512:
		rc = do_shash("sha512", req->result, ctx->opad, blocksize,
			      req->result, ctx->digestsize, NULL, 0);
		break;
	default:
		pr_err("%s() Error : unknown hmac type\n", __func__);
		rc = -EINVAL;
	}
	return rc;
}

/**
 * ahash_req_done() - Process a hash result from the SPU hardware.
 * @rctx: Crypto request context
 *
 * Return: 0 if successful
 *         < 0 if an error
 */
static int ahash_req_done(struct iproc_reqctx_s *rctx)
{
	struct spu_hw *spu = &iproc_priv.spu;
	struct crypto_async_request *areq = rctx->parent;
	struct ahash_request *req = ahash_request_cast(areq);
	struct iproc_ctx_s *ctx = rctx->ctx;
	int err;

	memcpy(req->result, rctx->msg_buf.digest, ctx->digestsize);

	if (spu->spu_type == SPU_TYPE_SPUM) {
		/* byte swap the output from the UPDT function to network byte
		 * order
		 */
		if (ctx->auth.alg == HASH_ALG_MD5) {
			__swab32s((u32 *)req->result);
			__swab32s(((u32 *)req->result) + 1);
			__swab32s(((u32 *)req->result) + 2);
			__swab32s(((u32 *)req->result) + 3);
			__swab32s(((u32 *)req->result) + 4);
		}
	}

	flow_dump("  digest ", req->result, ctx->digestsize);

	/* if this an HMAC then do the outer hash */
	if (rctx->is_sw_hmac) {
		err = spu_hmac_outer_hash(req, ctx);
		if (err < 0)
			return err;
		flow_dump("  hmac: ", req->result, ctx->digestsize);
	}

	if (rctx->is_sw_hmac || ctx->auth.mode == HASH_MODE_HMAC) {
		atomic_inc(&iproc_priv.op_counts[SPU_OP_HMAC]);
		atomic_inc(&iproc_priv.hmac_cnt[ctx->auth.alg]);
	} else {
		atomic_inc(&iproc_priv.op_counts[SPU_OP_HASH]);
		atomic_inc(&iproc_priv.hash_cnt[ctx->auth.alg]);
	}

	return 0;
}

/**
 * handle_ahash_resp() - Process a SPU response message for a hash request.
 * Checks if the entire crypto API request has been processed, and if so,
 * invokes post processing on the result.
 * @rctx: Crypto request context
 */
static void handle_ahash_resp(struct iproc_reqctx_s *rctx)
{
	struct iproc_ctx_s *ctx = rctx->ctx;
#ifdef DEBUG
	struct crypto_async_request *areq = rctx->parent;
	struct ahash_request *req = ahash_request_cast(areq);
	struct crypto_ahash *ahash = crypto_ahash_reqtfm(req);
	unsigned int blocksize =
		crypto_tfm_alg_blocksize(crypto_ahash_tfm(ahash));
#endif
	/*
	 * Save hash to use as input to next op if incremental. Might be copying
	 * too much, but that's easier than figuring out actual digest size here
	 */
	memcpy(rctx->incr_hash, rctx->msg_buf.digest, MAX_DIGEST_SIZE);

	flow_log("%s() blocksize:%u digestsize:%u\n",
		 __func__, blocksize, ctx->digestsize);

	atomic64_add(ctx->digestsize, &iproc_priv.bytes_in);

	if (rctx->is_final && (rctx->total_sent == rctx->total_todo))
		ahash_req_done(rctx);
}

/**
 * spu_aead_rx_sg_create() - Build up the scatterlist of buffers used to receive
 * a SPU response message for an AEAD request. Includes buffers to catch SPU
 * message headers and the response data.
 * @mssg:	mailbox message containing the receive sg
 * @rctx:	crypto request context
 * @rx_frag_num: number of scatterlist elements required to hold the
 *		SPU response message
 * @assoc_len:	Length of associated data included in the crypto request
 * @ret_iv_len: Length of IV returned in response
 * @resp_len:	Number of bytes of response data expected to be written to
 *              dst buffer from crypto API
 * @digestsize: Length of hash digest, in bytes
 * @stat_pad_len: Number of bytes required to pad the STAT field to
 *		a 4-byte boundary
 *
 * The scatterlist that gets allocated here is freed in spu_chunk_cleanup()
 * when the request completes, whether the request is handled successfully or
 * there is an error.
 *
 * Returns:
 *   0 if successful
 *   < 0 if an error
 */
static int spu_aead_rx_sg_create(struct brcm_message *mssg,
				 struct aead_request *req,
				 struct iproc_reqctx_s *rctx,
				 u8 rx_frag_num,
				 unsigned int assoc_len,
				 u32 ret_iv_len, unsigned int resp_len,
				 unsigned int digestsize, u32 stat_pad_len)
{
	struct spu_hw *spu = &iproc_priv.spu;
	struct scatterlist *sg;	/* used to build sgs in mbox message */
	struct iproc_ctx_s *ctx = rctx->ctx;
	u32 datalen;		/* Number of bytes of response data expected */
	u32 assoc_buf_len;
	u8 data_padlen = 0;

	if (ctx->is_rfc4543) {
		/* RFC4543: only pad after data, not after AAD */
		data_padlen = spu->spu_gcm_ccm_pad_len(ctx->cipher.mode,
							  assoc_len + resp_len);
		assoc_buf_len = assoc_len;
	} else {
		data_padlen = spu->spu_gcm_ccm_pad_len(ctx->cipher.mode,
							  resp_len);
		assoc_buf_len = spu->spu_assoc_resp_len(ctx->cipher.mode,
						assoc_len, ret_iv_len,
						rctx->is_encrypt);
	}

	if (ctx->cipher.mode == CIPHER_MODE_CCM)
		/* ICV (after data) must be in the next 32-bit word for CCM */
		data_padlen += spu->spu_wordalign_padlen(assoc_buf_len +
							 resp_len +
							 data_padlen);

	if (data_padlen)
		/* have to catch gcm pad in separate buffer */
		rx_frag_num++;

	mssg->spu.dst = kcalloc(rx_frag_num, sizeof(struct scatterlist),
				rctx->gfp);
	if (!mssg->spu.dst)
		return -ENOMEM;

	sg = mssg->spu.dst;
	sg_init_table(sg, rx_frag_num);

	/* Space for SPU message header */
	sg_set_buf(sg++, rctx->msg_buf.spu_resp_hdr, ctx->spu_resp_hdr_len);

	if (assoc_buf_len) {
		/*
		 * Don't write directly to req->dst, because SPU may pad the
		 * assoc data in the response
		 */
		memset(rctx->msg_buf.a.resp_aad, 0, assoc_buf_len);
		sg_set_buf(sg++, rctx->msg_buf.a.resp_aad, assoc_buf_len);
	}

	if (resp_len) {
		/*
		 * Copy in each dst sg entry from request, up to chunksize.
		 * dst sg catches just the data. digest caught in separate buf.
		 */
		datalen = spu_msg_sg_add(&sg, &rctx->dst_sg, &rctx->dst_skip,
					 rctx->dst_nents, resp_len);
		if (datalen < (resp_len)) {
			pr_err("%s(): failed to copy dst sg to mbox msg. expected len %u, datalen %u",
			       __func__, resp_len, datalen);
			return -EFAULT;
		}
	}

	/* If GCM/CCM data is padded, catch padding in separate buffer */
	if (data_padlen) {
		memset(rctx->msg_buf.a.gcmpad, 0, data_padlen);
		sg_set_buf(sg++, rctx->msg_buf.a.gcmpad, data_padlen);
	}

	/* Always catch ICV in separate buffer */
	sg_set_buf(sg++, rctx->msg_buf.digest, digestsize);

	flow_log("stat_pad_len %u\n", stat_pad_len);
	if (stat_pad_len) {
		memset(rctx->msg_buf.rx_stat_pad, 0, stat_pad_len);
		sg_set_buf(sg++, rctx->msg_buf.rx_stat_pad, stat_pad_len);
	}

	memset(rctx->msg_buf.rx_stat, 0, SPU_RX_STATUS_LEN);
	sg_set_buf(sg, rctx->msg_buf.rx_stat, spu->spu_rx_status_len());

	return 0;
}

/**
 * spu_aead_tx_sg_create() - Build up the scatterlist of buffers used to send a
 * SPU request message for an AEAD request. Includes SPU message headers and the
 * request data.
 * @mssg:	mailbox message containing the transmit sg
 * @rctx:	crypto request context
 * @tx_frag_num: number of scatterlist elements required to construct the
 *		SPU request message
 * @spu_hdr_len: length of SPU message header in bytes
 * @assoc:	crypto API associated data scatterlist
 * @assoc_len:	length of associated data
 * @assoc_nents: number of scatterlist entries containing assoc data
 * @aead_iv_len: length of AEAD IV, if included
 * @chunksize:	Number of bytes of request data
 * @aad_pad_len: Number of bytes of padding at end of AAD. For GCM/CCM.
 * @pad_len:	Number of pad bytes
 * @incl_icv:	If true, write separate ICV buffer after data and
 *              any padding
 *
 * The scatterlist that gets allocated here is freed in spu_chunk_cleanup()
 * when the request completes, whether the request is handled successfully or
 * there is an error.
 *
 * Return:
 *   0 if successful
 *   < 0 if an error
 */
static int spu_aead_tx_sg_create(struct brcm_message *mssg,
				 struct iproc_reqctx_s *rctx,
				 u8 tx_frag_num,
				 u32 spu_hdr_len,
				 struct scatterlist *assoc,
				 unsigned int assoc_len,
				 int assoc_nents,
				 unsigned int aead_iv_len,
				 unsigned int chunksize,
				 u32 aad_pad_len, u32 pad_len, bool incl_icv)
{
	struct spu_hw *spu = &iproc_priv.spu;
	struct scatterlist *sg;	/* used to build sgs in mbox message */
	struct scatterlist *assoc_sg = assoc;
	struct iproc_ctx_s *ctx = rctx->ctx;
	u32 datalen;		/* Number of bytes of data to write */
	u32 written;		/* Number of bytes of data written */
	u32 assoc_offset = 0;
	u32 stat_len;

	mssg->spu.src = kcalloc(tx_frag_num, sizeof(struct scatterlist),
				rctx->gfp);
	if (!mssg->spu.src)
		return -ENOMEM;

	sg = mssg->spu.src;
	sg_init_table(sg, tx_frag_num);

	sg_set_buf(sg++, rctx->msg_buf.bcm_spu_req_hdr,
		   BCM_HDR_LEN + spu_hdr_len);

	if (assoc_len) {
		/* Copy in each associated data sg entry from request */
		written = spu_msg_sg_add(&sg, &assoc_sg, &assoc_offset,
					 assoc_nents, assoc_len);
		if (written < assoc_len) {
			pr_err("%s(): failed to copy assoc sg to mbox msg",
			       __func__);
			return -EFAULT;
		}
	}

	if (aead_iv_len)
		sg_set_buf(sg++, rctx->msg_buf.iv_ctr, aead_iv_len);

	if (aad_pad_len) {
		memset(rctx->msg_buf.a.req_aad_pad, 0, aad_pad_len);
		sg_set_buf(sg++, rctx->msg_buf.a.req_aad_pad, aad_pad_len);
	}

	datalen = chunksize;
	if ((chunksize > ctx->digestsize) && incl_icv)
		datalen -= ctx->digestsize;
	if (datalen) {
		/* For aead, a single msg should consume the entire src sg */
		written = spu_msg_sg_add(&sg, &rctx->src_sg, &rctx->src_skip,
					 rctx->src_nents, datalen);
		if (written < datalen) {
			pr_err("%s(): failed to copy src sg to mbox msg",
			       __func__);
			return -EFAULT;
		}
	}

	if (pad_len) {
		memset(rctx->msg_buf.spu_req_pad, 0, pad_len);
		sg_set_buf(sg++, rctx->msg_buf.spu_req_pad, pad_len);
	}

	if (incl_icv)
		sg_set_buf(sg++, rctx->msg_buf.digest, ctx->digestsize);

	stat_len = spu->spu_tx_status_len();
	if (stat_len) {
		memset(rctx->msg_buf.tx_stat, 0, stat_len);
		sg_set_buf(sg, rctx->msg_buf.tx_stat, stat_len);
	}
	return 0;
}

/**
 * handle_aead_req() - Submit a SPU request message for the next chunk of the
 * current AEAD request.
 * @rctx:  Crypto request context
 *
 * Unlike other operation types, we assume the length of the request fits in
 * a single SPU request message. aead_enqueue() makes sure this is true.
 * Comments for other op types regarding threads applies here as well.
 *
 * Unlike incremental hash ops, where the spu returns the entire hash for
 * truncated algs like sha-224, the SPU returns just the truncated hash in
 * response to aead requests. So digestsize is always ctx->digestsize here.
 *
 * Return: -EINPROGRESS: crypto request has been accepted and result will be
 *			 returned asynchronously
 *         Any other value indicates an error
 */
static int handle_aead_req(struct iproc_reqctx_s *rctx)
{
	struct spu_hw *spu = &iproc_priv.spu;
	struct crypto_async_request *areq = rctx->parent;
	struct aead_request *req = container_of(areq,
						struct aead_request, base);
	struct iproc_ctx_s *ctx = rctx->ctx;
	int err;
	unsigned int chunksize;
	unsigned int resp_len;
	u32 spu_hdr_len;
	u32 db_size;
	u32 stat_pad_len;
	u32 pad_len;
	struct brcm_message *mssg;	/* mailbox message */
	struct spu_request_opts req_opts;
	struct spu_cipher_parms cipher_parms;
	struct spu_hash_parms hash_parms;
	struct spu_aead_parms aead_parms;
	int assoc_nents = 0;
	bool incl_icv = false;
	unsigned int digestsize = ctx->digestsize;

	/* number of entries in src and dst sg. Always includes SPU msg header.
	 */
	u8 rx_frag_num = 2;	/* and STATUS */
	u8 tx_frag_num = 1;

	/* doing the whole thing at once */
	chunksize = rctx->total_todo;

	flow_log("%s: chunksize %u\n", __func__, chunksize);

	memset(&req_opts, 0, sizeof(req_opts));
	memset(&hash_parms, 0, sizeof(hash_parms));
	memset(&aead_parms, 0, sizeof(aead_parms));

	req_opts.is_inbound = !(rctx->is_encrypt);
	req_opts.auth_first = ctx->auth_first;
	req_opts.is_aead = true;
	req_opts.is_esp = ctx->is_esp;

	cipher_parms.alg = ctx->cipher.alg;
	cipher_parms.mode = ctx->cipher.mode;
	cipher_parms.type = ctx->cipher_type;
	cipher_parms.key_buf = ctx->enckey;
	cipher_parms.key_len = ctx->enckeylen;
	cipher_parms.iv_buf = rctx->msg_buf.iv_ctr;
	cipher_parms.iv_len = rctx->iv_ctr_len;

	hash_parms.alg = ctx->auth.alg;
	hash_parms.mode = ctx->auth.mode;
	hash_parms.type = HASH_TYPE_NONE;
	hash_parms.key_buf = (u8 *)ctx->authkey;
	hash_parms.key_len = ctx->authkeylen;
	hash_parms.digestsize = digestsize;

	if ((ctx->auth.alg == HASH_ALG_SHA224) &&
	    (ctx->authkeylen < SHA224_DIGEST_SIZE))
		hash_parms.key_len = SHA224_DIGEST_SIZE;

	aead_parms.assoc_size = req->assoclen;
	if (ctx->is_esp && !ctx->is_rfc4543) {
		/*
		 * 8-byte IV is included assoc data in request. SPU2
		 * expects AAD to include just SPI and seqno. So
		 * subtract off the IV len.
		 */
		aead_parms.assoc_size -= GCM_RFC4106_IV_SIZE;

		if (rctx->is_encrypt) {
			aead_parms.return_iv = true;
			aead_parms.ret_iv_len = GCM_RFC4106_IV_SIZE;
			aead_parms.ret_iv_off = GCM_ESP_SALT_SIZE;
		}
	} else {
		aead_parms.ret_iv_len = 0;
	}

	/*
	 * Count number of sg entries from the crypto API request that are to
	 * be included in this mailbox message. For dst sg, don't count space
	 * for digest. Digest gets caught in a separate buffer and copied back
	 * to dst sg when processing response.
	 */
	rctx->src_nents = spu_sg_count(rctx->src_sg, rctx->src_skip, chunksize);
	rctx->dst_nents = spu_sg_count(rctx->dst_sg, rctx->dst_skip, chunksize);
	if (aead_parms.assoc_size)
		assoc_nents = spu_sg_count(rctx->assoc, 0,
					   aead_parms.assoc_size);

	mssg = &rctx->mb_mssg;

	rctx->total_sent = chunksize;
	rctx->src_sent = chunksize;
	if (spu->spu_assoc_resp_len(ctx->cipher.mode,
				    aead_parms.assoc_size,
				    aead_parms.ret_iv_len,
				    rctx->is_encrypt))
		rx_frag_num++;

	aead_parms.iv_len = spu->spu_aead_ivlen(ctx->cipher.mode,
						rctx->iv_ctr_len);

	if (ctx->auth.alg == HASH_ALG_AES)
		hash_parms.type = (enum hash_type)ctx->cipher_type;

	/* General case AAD padding (CCM and RFC4543 special cases below) */
	aead_parms.aad_pad_len = spu->spu_gcm_ccm_pad_len(ctx->cipher.mode,
						 aead_parms.assoc_size);

	/* General case data padding (CCM decrypt special case below) */
	aead_parms.data_pad_len = spu->spu_gcm_ccm_pad_len(ctx->cipher.mode,
							   chunksize);

	if (ctx->cipher.mode == CIPHER_MODE_CCM) {
		/*
		 * for CCM, AAD len + 2 (rather than AAD len) needs to be
		 * 128-bit aligned
		 */
		aead_parms.aad_pad_len = spu->spu_gcm_ccm_pad_len(
					 ctx->cipher.mode,
					 aead_parms.assoc_size + 2);

		/*
		 * And when decrypting CCM, need to pad without including
		 * size of ICV which is tacked on to end of chunk
		 */
		if (!rctx->is_encrypt)
			aead_parms.data_pad_len =
				spu->spu_gcm_ccm_pad_len(ctx->cipher.mode,
							chunksize - digestsize);

		/* CCM also requires software to rewrite portions of IV: */
		spu->spu_ccm_update_iv(digestsize, &cipher_parms, req->assoclen,
				       chunksize, rctx->is_encrypt,
				       ctx->is_esp);
	}

	if (ctx->is_rfc4543) {
		/*
		 * RFC4543: data is included in AAD, so don't pad after AAD
		 * and pad data based on both AAD + data size
		 */
		aead_parms.aad_pad_len = 0;
		if (!rctx->is_encrypt)
			aead_parms.data_pad_len = spu->spu_gcm_ccm_pad_len(
					ctx->cipher.mode,
					aead_parms.assoc_size + chunksize -
					digestsize);
		else
			aead_parms.data_pad_len = spu->spu_gcm_ccm_pad_len(
					ctx->cipher.mode,
					aead_parms.assoc_size + chunksize);

		req_opts.is_rfc4543 = true;
	}

	if (spu_req_incl_icv(ctx->cipher.mode, rctx->is_encrypt)) {
		incl_icv = true;
		tx_frag_num++;
		/* Copy ICV from end of src scatterlist to digest buf */
		sg_copy_part_to_buf(req->src, rctx->msg_buf.digest, digestsize,
				    req->assoclen + rctx->total_sent -
				    digestsize);
	}

	atomic64_add(chunksize, &iproc_priv.bytes_out);

	flow_log("%s()-sent chunksize:%u\n", __func__, chunksize);

	/* Prepend SPU header with type 3 BCM header */
	memcpy(rctx->msg_buf.bcm_spu_req_hdr, BCMHEADER, BCM_HDR_LEN);

	spu_hdr_len = spu->spu_create_request(rctx->msg_buf.bcm_spu_req_hdr +
					      BCM_HDR_LEN, &req_opts,
					      &cipher_parms, &hash_parms,
					      &aead_parms, chunksize);

	/* Determine total length of padding. Put all padding in one buffer. */
	db_size = spu_real_db_size(aead_parms.assoc_size, aead_parms.iv_len, 0,
				   chunksize, aead_parms.aad_pad_len,
				   aead_parms.data_pad_len, 0);

	stat_pad_len = spu->spu_wordalign_padlen(db_size);

	if (stat_pad_len)
		rx_frag_num++;
	pad_len = aead_parms.data_pad_len + stat_pad_len;
	if (pad_len) {
		tx_frag_num++;
		spu->spu_request_pad(rctx->msg_buf.spu_req_pad,
				     aead_parms.data_pad_len, 0,
				     ctx->auth.alg, ctx->auth.mode,
				     rctx->total_sent, stat_pad_len);
	}

	spu->spu_dump_msg_hdr(rctx->msg_buf.bcm_spu_req_hdr + BCM_HDR_LEN,
			      spu_hdr_len);
	dump_sg(rctx->assoc, 0, aead_parms.assoc_size);
	packet_dump("    aead iv: ", rctx->msg_buf.iv_ctr, aead_parms.iv_len);
	packet_log("BD:\n");
	dump_sg(rctx->src_sg, rctx->src_skip, chunksize);
	packet_dump("   pad: ", rctx->msg_buf.spu_req_pad, pad_len);

	/*
	 * Build mailbox message containing SPU request msg and rx buffers
	 * to catch response message
	 */
	memset(mssg, 0, sizeof(*mssg));
	mssg->type = BRCM_MESSAGE_SPU;
	mssg->ctx = rctx;	/* Will be returned in response */

	/* Create rx scatterlist to catch result */
	rx_frag_num += rctx->dst_nents;
	resp_len = chunksize;

	/*
	 * Always catch ICV in separate buffer. Have to for GCM/CCM because of
	 * padding. Have to for SHA-224 and other truncated SHAs because SPU
	 * sends entire digest back.
	 */
	rx_frag_num++;

	if (((ctx->cipher.mode == CIPHER_MODE_GCM) ||
	     (ctx->cipher.mode == CIPHER_MODE_CCM)) && !rctx->is_encrypt) {
		/*
		 * Input is ciphertxt plus ICV, but ICV not incl
		 * in output.
		 */
		resp_len -= ctx->digestsize;
		if (resp_len == 0)
			/* no rx frags to catch output data */
			rx_frag_num -= rctx->dst_nents;
	}

	err = spu_aead_rx_sg_create(mssg, req, rctx, rx_frag_num,
				    aead_parms.assoc_size,
				    aead_parms.ret_iv_len, resp_len, digestsize,
				    stat_pad_len);
	if (err)
		return err;

	/* Create tx scatterlist containing SPU request message */
	tx_frag_num += rctx->src_nents;
	tx_frag_num += assoc_nents;
	if (aead_parms.aad_pad_len)
		tx_frag_num++;
	if (aead_parms.iv_len)
		tx_frag_num++;
	if (spu->spu_tx_status_len())
		tx_frag_num++;
	err = spu_aead_tx_sg_create(mssg, rctx, tx_frag_num, spu_hdr_len,
				    rctx->assoc, aead_parms.assoc_size,
				    assoc_nents, aead_parms.iv_len, chunksize,
				    aead_parms.aad_pad_len, pad_len, incl_icv);
	if (err)
		return err;

	err = mailbox_send_message(mssg, req->base.flags, rctx->chan_idx);
	if (unlikely(err < 0))
		return err;

	return -EINPROGRESS;
}

/**
 * handle_aead_resp() - Process a SPU response message for an AEAD request.
 * @rctx:  Crypto request context
 */
static void handle_aead_resp(struct iproc_reqctx_s *rctx)
{
	struct spu_hw *spu = &iproc_priv.spu;
	struct crypto_async_request *areq = rctx->parent;
	struct aead_request *req = container_of(areq,
						struct aead_request, base);
	struct iproc_ctx_s *ctx = rctx->ctx;
	u32 payload_len;
	unsigned int icv_offset;
	u32 result_len;

	/* See how much data was returned */
	payload_len = spu->spu_payload_length(rctx->msg_buf.spu_resp_hdr);
	flow_log("payload_len %u\n", payload_len);

	/* only count payload */
	atomic64_add(payload_len, &iproc_priv.bytes_in);

	if (req->assoclen)
		packet_dump("  assoc_data ", rctx->msg_buf.a.resp_aad,
			    req->assoclen);

	/*
	 * Copy the ICV back to the destination
	 * buffer. In decrypt case, SPU gives us back the digest, but crypto
	 * API doesn't expect ICV in dst buffer.
	 */
	result_len = req->cryptlen;
	if (rctx->is_encrypt) {
		icv_offset = req->assoclen + rctx->total_sent;
		packet_dump("  ICV: ", rctx->msg_buf.digest, ctx->digestsize);
		flow_log("copying ICV to dst sg at offset %u\n", icv_offset);
		sg_copy_part_from_buf(req->dst, rctx->msg_buf.digest,
				      ctx->digestsize, icv_offset);
		result_len += ctx->digestsize;
	}

	packet_log("response data:  ");
	dump_sg(req->dst, req->assoclen, result_len);

	atomic_inc(&iproc_priv.op_counts[SPU_OP_AEAD]);
	if (ctx->cipher.alg == CIPHER_ALG_AES) {
		if (ctx->cipher.mode == CIPHER_MODE_CCM)
			atomic_inc(&iproc_priv.aead_cnt[AES_CCM]);
		else if (ctx->cipher.mode == CIPHER_MODE_GCM)
			atomic_inc(&iproc_priv.aead_cnt[AES_GCM]);
		else
			atomic_inc(&iproc_priv.aead_cnt[AUTHENC]);
	} else {
		atomic_inc(&iproc_priv.aead_cnt[AUTHENC]);
	}
}

/**
 * spu_chunk_cleanup() - Do cleanup after processing one chunk of a request
 * @rctx:  request context
 *
 * Mailbox scatterlists are allocated for each chunk. So free them after
 * processing each chunk.
 */
static void spu_chunk_cleanup(struct iproc_reqctx_s *rctx)
{
	/* mailbox message used to tx request */
	struct brcm_message *mssg = &rctx->mb_mssg;

	kfree(mssg->spu.src);
	kfree(mssg->spu.dst);
	memset(mssg, 0, sizeof(struct brcm_message));
}

/**
 * finish_req() - Used to invoke the complete callback from the requester when
 * a request has been handled asynchronously.
 * @rctx:  Request context
 * @err:   Indicates whether the request was successful or not
 *
 * Ensures that cleanup has been done for request
 */
static void finish_req(struct iproc_reqctx_s *rctx, int err)
{
	struct crypto_async_request *areq = rctx->parent;

	flow_log("%s() err:%d\n\n", __func__, err);

	/* No harm done if already called */
	spu_chunk_cleanup(rctx);

	if (areq)
		areq->complete(areq, err);
}

/**
 * spu_rx_callback() - Callback from mailbox framework with a SPU response.
 * @cl:		mailbox client structure for SPU driver
 * @msg:	mailbox message containing SPU response
 */
static void spu_rx_callback(struct mbox_client *cl, void *msg)
{
	struct spu_hw *spu = &iproc_priv.spu;
	struct brcm_message *mssg = msg;
	struct iproc_reqctx_s *rctx;
	int err;

	rctx = mssg->ctx;
	if (unlikely(!rctx)) {
		/* This is fatal */
		pr_err("%s(): no request context", __func__);
		err = -EFAULT;
		goto cb_finish;
	}

	/* process the SPU status */
	err = spu->spu_status_process(rctx->msg_buf.rx_stat);
	if (err != 0) {
		if (err == SPU_INVALID_ICV)
			atomic_inc(&iproc_priv.bad_icv);
		err = -EBADMSG;
		goto cb_finish;
	}

	/* Process the SPU response message */
	switch (rctx->ctx->alg->type) {
	case CRYPTO_ALG_TYPE_SKCIPHER:
		handle_skcipher_resp(rctx);
		break;
	case CRYPTO_ALG_TYPE_AHASH:
		handle_ahash_resp(rctx);
		break;
	case CRYPTO_ALG_TYPE_AEAD:
		handle_aead_resp(rctx);
		break;
	default:
		err = -EINVAL;
		goto cb_finish;
	}

	/*
	 * If this response does not complete the request, then send the next
	 * request chunk.
	 */
	if (rctx->total_sent < rctx->total_todo) {
		/* Deallocate anything specific to previous chunk */
		spu_chunk_cleanup(rctx);

		switch (rctx->ctx->alg->type) {
		case CRYPTO_ALG_TYPE_SKCIPHER:
			err = handle_skcipher_req(rctx);
			break;
		case CRYPTO_ALG_TYPE_AHASH:
			err = handle_ahash_req(rctx);
			if (err == -EAGAIN)
				/*
				 * we saved data in hash carry, but tell crypto
				 * API we successfully completed request.
				 */
				err = 0;
			break;
		case CRYPTO_ALG_TYPE_AEAD:
			err = handle_aead_req(rctx);
			break;
		default:
			err = -EINVAL;
		}

		if (err == -EINPROGRESS)
			/* Successfully submitted request for next chunk */
			return;
	}

cb_finish:
	finish_req(rctx, err);
}

/* ==================== Kernel Cryptographic API ==================== */

/**
 * skcipher_enqueue() - Handle skcipher encrypt or decrypt request.
 * @req:	Crypto API request
 * @encrypt:	true if encrypting; false if decrypting
 *
 * Return: -EINPROGRESS if request accepted and result will be returned
 *			asynchronously
 *	   < 0 if an error
 */
static int skcipher_enqueue(struct skcipher_request *req, bool encrypt)
{
	struct iproc_reqctx_s *rctx = skcipher_request_ctx(req);
	struct iproc_ctx_s *ctx =
	    crypto_skcipher_ctx(crypto_skcipher_reqtfm(req));
	int err;

	flow_log("%s() enc:%u\n", __func__, encrypt);

	rctx->gfp = (req->base.flags & (CRYPTO_TFM_REQ_MAY_BACKLOG |
		       CRYPTO_TFM_REQ_MAY_SLEEP)) ? GFP_KERNEL : GFP_ATOMIC;
	rctx->parent = &req->base;
	rctx->is_encrypt = encrypt;
	rctx->bd_suppress = false;
	rctx->total_todo = req->cryptlen;
	rctx->src_sent = 0;
	rctx->total_sent = 0;
	rctx->total_received = 0;
	rctx->ctx = ctx;

	/* Initialize current position in src and dst scatterlists */
	rctx->src_sg = req->src;
	rctx->src_nents = 0;
	rctx->src_skip = 0;
	rctx->dst_sg = req->dst;
	rctx->dst_nents = 0;
	rctx->dst_skip = 0;

	if (ctx->cipher.mode == CIPHER_MODE_CBC ||
	    ctx->cipher.mode == CIPHER_MODE_CTR ||
	    ctx->cipher.mode == CIPHER_MODE_OFB ||
	    ctx->cipher.mode == CIPHER_MODE_XTS ||
	    ctx->cipher.mode == CIPHER_MODE_GCM ||
	    ctx->cipher.mode == CIPHER_MODE_CCM) {
		rctx->iv_ctr_len =
		    crypto_skcipher_ivsize(crypto_skcipher_reqtfm(req));
		memcpy(rctx->msg_buf.iv_ctr, req->iv, rctx->iv_ctr_len);
	} else {
		rctx->iv_ctr_len = 0;
	}

	/* Choose a SPU to process this request */
	rctx->chan_idx = select_channel();
	err = handle_skcipher_req(rctx);
	if (err != -EINPROGRESS)
		/* synchronous result */
		spu_chunk_cleanup(rctx);

	return err;
}

static int des_setkey(struct crypto_skcipher *cipher, const u8 *key,
		      unsigned int keylen)
{
	struct iproc_ctx_s *ctx = crypto_skcipher_ctx(cipher);
	int err;

	err = verify_skcipher_des_key(cipher, key);
	if (err)
		return err;

	ctx->cipher_type = CIPHER_TYPE_DES;
	return 0;
}

static int threedes_setkey(struct crypto_skcipher *cipher, const u8 *key,
			   unsigned int keylen)
{
	struct iproc_ctx_s *ctx = crypto_skcipher_ctx(cipher);
	int err;

	err = verify_skcipher_des3_key(cipher, key);
	if (err)
		return err;

	ctx->cipher_type = CIPHER_TYPE_3DES;
	return 0;
}

static int aes_setkey(struct crypto_skcipher *cipher, const u8 *key,
		      unsigned int keylen)
{
	struct iproc_ctx_s *ctx = crypto_skcipher_ctx(cipher);

	if (ctx->cipher.mode == CIPHER_MODE_XTS)
		/* XTS includes two keys of equal length */
		keylen = keylen / 2;

	switch (keylen) {
	case AES_KEYSIZE_128:
		ctx->cipher_type = CIPHER_TYPE_AES128;
		break;
	case AES_KEYSIZE_192:
		ctx->cipher_type = CIPHER_TYPE_AES192;
		break;
	case AES_KEYSIZE_256:
		ctx->cipher_type = CIPHER_TYPE_AES256;
		break;
	default:
		return -EINVAL;
	}
	WARN_ON((ctx->max_payload != SPU_MAX_PAYLOAD_INF) &&
		((ctx->max_payload % AES_BLOCK_SIZE) != 0));
	return 0;
}

static int rc4_setkey(struct crypto_skcipher *cipher, const u8 *key,
		      unsigned int keylen)
{
	struct iproc_ctx_s *ctx = crypto_skcipher_ctx(cipher);
	int i;

	ctx->enckeylen = ARC4_MAX_KEY_SIZE + ARC4_STATE_SIZE;

	ctx->enckey[0] = 0x00;	/* 0x00 */
	ctx->enckey[1] = 0x00;	/* i    */
	ctx->enckey[2] = 0x00;	/* 0x00 */
	ctx->enckey[3] = 0x00;	/* j    */
	for (i = 0; i < ARC4_MAX_KEY_SIZE; i++)
		ctx->enckey[i + ARC4_STATE_SIZE] = key[i % keylen];

	ctx->cipher_type = CIPHER_TYPE_INIT;

	return 0;
}

static int skcipher_setkey(struct crypto_skcipher *cipher, const u8 *key,
			     unsigned int keylen)
{
	struct spu_hw *spu = &iproc_priv.spu;
	struct iproc_ctx_s *ctx = crypto_skcipher_ctx(cipher);
	struct spu_cipher_parms cipher_parms;
	u32 alloc_len = 0;
	int err;

	flow_log("skcipher_setkey() keylen: %d\n", keylen);
	flow_dump("  key: ", key, keylen);

	switch (ctx->cipher.alg) {
	case CIPHER_ALG_DES:
		err = des_setkey(cipher, key, keylen);
		break;
	case CIPHER_ALG_3DES:
		err = threedes_setkey(cipher, key, keylen);
		break;
	case CIPHER_ALG_AES:
		err = aes_setkey(cipher, key, keylen);
		break;
	case CIPHER_ALG_RC4:
		err = rc4_setkey(cipher, key, keylen);
		break;
	default:
		pr_err("%s() Error: unknown cipher alg\n", __func__);
		err = -EINVAL;
	}
	if (err)
		return err;

	/* RC4 already populated ctx->enkey */
	if (ctx->cipher.alg != CIPHER_ALG_RC4) {
		memcpy(ctx->enckey, key, keylen);
		ctx->enckeylen = keylen;
	}
	/* SPU needs XTS keys in the reverse order the crypto API presents */
	if ((ctx->cipher.alg == CIPHER_ALG_AES) &&
	    (ctx->cipher.mode == CIPHER_MODE_XTS)) {
		unsigned int xts_keylen = keylen / 2;

		memcpy(ctx->enckey, key + xts_keylen, xts_keylen);
		memcpy(ctx->enckey + xts_keylen, key, xts_keylen);
	}

	if (spu->spu_type == SPU_TYPE_SPUM)
		alloc_len = BCM_HDR_LEN + SPU_HEADER_ALLOC_LEN;
	else if (spu->spu_type == SPU_TYPE_SPU2)
		alloc_len = BCM_HDR_LEN + SPU2_HEADER_ALLOC_LEN;
	memset(ctx->bcm_spu_req_hdr, 0, alloc_len);
	cipher_parms.iv_buf = NULL;
	cipher_parms.iv_len = crypto_skcipher_ivsize(cipher);
	flow_log("%s: iv_len %u\n", __func__, cipher_parms.iv_len);

	cipher_parms.alg = ctx->cipher.alg;
	cipher_parms.mode = ctx->cipher.mode;
	cipher_parms.type = ctx->cipher_type;
	cipher_parms.key_buf = ctx->enckey;
	cipher_parms.key_len = ctx->enckeylen;

	/* Prepend SPU request message with BCM header */
	memcpy(ctx->bcm_spu_req_hdr, BCMHEADER, BCM_HDR_LEN);
	ctx->spu_req_hdr_len =
	    spu->spu_cipher_req_init(ctx->bcm_spu_req_hdr + BCM_HDR_LEN,
				     &cipher_parms);

	ctx->spu_resp_hdr_len = spu->spu_response_hdr_len(ctx->authkeylen,
							  ctx->enckeylen,
							  false);

	atomic_inc(&iproc_priv.setkey_cnt[SPU_OP_CIPHER]);

	return 0;
}

static int skcipher_encrypt(struct skcipher_request *req)
{
	flow_log("skcipher_encrypt() nbytes:%u\n", req->cryptlen);

	return skcipher_enqueue(req, true);
}

static int skcipher_decrypt(struct skcipher_request *req)
{
	flow_log("skcipher_decrypt() nbytes:%u\n", req->cryptlen);
	return skcipher_enqueue(req, false);
}

static int ahash_enqueue(struct ahash_request *req)
{
	struct iproc_reqctx_s *rctx = ahash_request_ctx(req);
	struct crypto_ahash *tfm = crypto_ahash_reqtfm(req);
	struct iproc_ctx_s *ctx = crypto_ahash_ctx(tfm);
	int err;
	const char *alg_name;

	flow_log("ahash_enqueue() nbytes:%u\n", req->nbytes);

	rctx->gfp = (req->base.flags & (CRYPTO_TFM_REQ_MAY_BACKLOG |
		       CRYPTO_TFM_REQ_MAY_SLEEP)) ? GFP_KERNEL : GFP_ATOMIC;
	rctx->parent = &req->base;
	rctx->ctx = ctx;
	rctx->bd_suppress = true;
	memset(&rctx->mb_mssg, 0, sizeof(struct brcm_message));

	/* Initialize position in src scatterlist */
	rctx->src_sg = req->src;
	rctx->src_skip = 0;
	rctx->src_nents = 0;
	rctx->dst_sg = NULL;
	rctx->dst_skip = 0;
	rctx->dst_nents = 0;

	/* SPU2 hardware does not compute hash of zero length data */
	if ((rctx->is_final == 1) && (rctx->total_todo == 0) &&
	    (iproc_priv.spu.spu_type == SPU_TYPE_SPU2)) {
		alg_name = crypto_tfm_alg_name(crypto_ahash_tfm(tfm));
		flow_log("Doing %sfinal %s zero-len hash request in software\n",
			 rctx->is_final ? "" : "non-", alg_name);
		err = do_shash((unsigned char *)alg_name, req->result,
			       NULL, 0, NULL, 0, ctx->authkey,
			       ctx->authkeylen);
		if (err < 0)
			flow_log("Hash request failed with error %d\n", err);
		return err;
	}
	/* Choose a SPU to process this request */
	rctx->chan_idx = select_channel();

	err = handle_ahash_req(rctx);
	if (err != -EINPROGRESS)
		/* synchronous result */
		spu_chunk_cleanup(rctx);

	if (err == -EAGAIN)
		/*
		 * we saved data in hash carry, but tell crypto API
		 * we successfully completed request.
		 */
		err = 0;

	return err;
}

static int __ahash_init(struct ahash_request *req)
{
	struct spu_hw *spu = &iproc_priv.spu;
	struct iproc_reqctx_s *rctx = ahash_request_ctx(req);
	struct crypto_ahash *tfm = crypto_ahash_reqtfm(req);
	struct iproc_ctx_s *ctx = crypto_ahash_ctx(tfm);

	flow_log("%s()\n", __func__);

	/* Initialize the context */
	rctx->hash_carry_len = 0;
	rctx->is_final = 0;

	rctx->total_todo = 0;
	rctx->src_sent = 0;
	rctx->total_sent = 0;
	rctx->total_received = 0;

	ctx->digestsize = crypto_ahash_digestsize(tfm);
	/* If we add a hash whose digest is larger, catch it here. */
	WARN_ON(ctx->digestsize > MAX_DIGEST_SIZE);

	rctx->is_sw_hmac = false;

	ctx->spu_resp_hdr_len = spu->spu_response_hdr_len(ctx->authkeylen, 0,
							  true);

	return 0;
}

/**
 * spu_no_incr_hash() - Determine whether incremental hashing is supported.
 * @ctx:  Crypto session context
 *
 * SPU-2 does not support incremental hashing (we'll have to revisit and
 * condition based on chip revision or device tree entry if future versions do
 * support incremental hash)
 *
 * SPU-M also doesn't support incremental hashing of AES-XCBC
 *
 * Return: true if incremental hashing is not supported
 *         false otherwise
 */
static bool spu_no_incr_hash(struct iproc_ctx_s *ctx)
{
	struct spu_hw *spu = &iproc_priv.spu;

	if (spu->spu_type == SPU_TYPE_SPU2)
		return true;

	if ((ctx->auth.alg == HASH_ALG_AES) &&
	    (ctx->auth.mode == HASH_MODE_XCBC))
		return true;

	/* Otherwise, incremental hashing is supported */
	return false;
}

static int ahash_init(struct ahash_request *req)
{
	struct crypto_ahash *tfm = crypto_ahash_reqtfm(req);
	struct iproc_ctx_s *ctx = crypto_ahash_ctx(tfm);
	const char *alg_name;
	struct crypto_shash *hash;
	int ret;
	gfp_t gfp;

	if (spu_no_incr_hash(ctx)) {
		/*
		 * If we get an incremental hashing request and it's not
		 * supported by the hardware, we need to handle it in software
		 * by calling synchronous hash functions.
		 */
		alg_name = crypto_tfm_alg_name(crypto_ahash_tfm(tfm));
		hash = crypto_alloc_shash(alg_name, 0, 0);
		if (IS_ERR(hash)) {
			ret = PTR_ERR(hash);
			goto err;
		}

		gfp = (req->base.flags & (CRYPTO_TFM_REQ_MAY_BACKLOG |
		       CRYPTO_TFM_REQ_MAY_SLEEP)) ? GFP_KERNEL : GFP_ATOMIC;
		ctx->shash = kmalloc(sizeof(*ctx->shash) +
				     crypto_shash_descsize(hash), gfp);
		if (!ctx->shash) {
			ret = -ENOMEM;
			goto err_hash;
		}
		ctx->shash->tfm = hash;

		/* Set the key using data we already have from setkey */
		if (ctx->authkeylen > 0) {
			ret = crypto_shash_setkey(hash, ctx->authkey,
						  ctx->authkeylen);
			if (ret)
				goto err_shash;
		}

		/* Initialize hash w/ this key and other params */
		ret = crypto_shash_init(ctx->shash);
		if (ret)
			goto err_shash;
	} else {
		/* Otherwise call the internal function which uses SPU hw */
		ret = __ahash_init(req);
	}

	return ret;

err_shash:
	kfree(ctx->shash);
err_hash:
	crypto_free_shash(hash);
err:
	return ret;
}

static int __ahash_update(struct ahash_request *req)
{
	struct iproc_reqctx_s *rctx = ahash_request_ctx(req);

	flow_log("ahash_update() nbytes:%u\n", req->nbytes);

	if (!req->nbytes)
		return 0;
	rctx->total_todo += req->nbytes;
	rctx->src_sent = 0;

	return ahash_enqueue(req);
}

static int ahash_update(struct ahash_request *req)
{
	struct crypto_ahash *tfm = crypto_ahash_reqtfm(req);
	struct iproc_ctx_s *ctx = crypto_ahash_ctx(tfm);
	u8 *tmpbuf;
	int ret;
	int nents;
	gfp_t gfp;

	if (spu_no_incr_hash(ctx)) {
		/*
		 * If we get an incremental hashing request and it's not
		 * supported by the hardware, we need to handle it in software
		 * by calling synchronous hash functions.
		 */
		if (req->src)
			nents = sg_nents(req->src);
		else
			return -EINVAL;

		/* Copy data from req scatterlist to tmp buffer */
		gfp = (req->base.flags & (CRYPTO_TFM_REQ_MAY_BACKLOG |
		       CRYPTO_TFM_REQ_MAY_SLEEP)) ? GFP_KERNEL : GFP_ATOMIC;
		tmpbuf = kmalloc(req->nbytes, gfp);
		if (!tmpbuf)
			return -ENOMEM;

		if (sg_copy_to_buffer(req->src, nents, tmpbuf, req->nbytes) !=
				req->nbytes) {
			kfree(tmpbuf);
			return -EINVAL;
		}

		/* Call synchronous update */
		ret = crypto_shash_update(ctx->shash, tmpbuf, req->nbytes);
		kfree(tmpbuf);
	} else {
		/* Otherwise call the internal function which uses SPU hw */
		ret = __ahash_update(req);
	}

	return ret;
}

static int __ahash_final(struct ahash_request *req)
{
	struct iproc_reqctx_s *rctx = ahash_request_ctx(req);

	flow_log("ahash_final() nbytes:%u\n", req->nbytes);

	rctx->is_final = 1;

	return ahash_enqueue(req);
}

static int ahash_final(struct ahash_request *req)
{
	struct crypto_ahash *tfm = crypto_ahash_reqtfm(req);
	struct iproc_ctx_s *ctx = crypto_ahash_ctx(tfm);
	int ret;

	if (spu_no_incr_hash(ctx)) {
		/*
		 * If we get an incremental hashing request and it's not
		 * supported by the hardware, we need to handle it in software
		 * by calling synchronous hash functions.
		 */
		ret = crypto_shash_final(ctx->shash, req->result);

		/* Done with hash, can deallocate it now */
		crypto_free_shash(ctx->shash->tfm);
		kfree(ctx->shash);

	} else {
		/* Otherwise call the internal function which uses SPU hw */
		ret = __ahash_final(req);
	}

	return ret;
}

static int __ahash_finup(struct ahash_request *req)
{
	struct iproc_reqctx_s *rctx = ahash_request_ctx(req);

	flow_log("ahash_finup() nbytes:%u\n", req->nbytes);

	rctx->total_todo += req->nbytes;
	rctx->src_sent = 0;
	rctx->is_final = 1;

	return ahash_enqueue(req);
}

static int ahash_finup(struct ahash_request *req)
{
	struct crypto_ahash *tfm = crypto_ahash_reqtfm(req);
	struct iproc_ctx_s *ctx = crypto_ahash_ctx(tfm);
	u8 *tmpbuf;
	int ret;
	int nents;
	gfp_t gfp;

	if (spu_no_incr_hash(ctx)) {
		/*
		 * If we get an incremental hashing request and it's not
		 * supported by the hardware, we need to handle it in software
		 * by calling synchronous hash functions.
		 */
		if (req->src) {
			nents = sg_nents(req->src);
		} else {
			ret = -EINVAL;
			goto ahash_finup_exit;
		}

		/* Copy data from req scatterlist to tmp buffer */
		gfp = (req->base.flags & (CRYPTO_TFM_REQ_MAY_BACKLOG |
		       CRYPTO_TFM_REQ_MAY_SLEEP)) ? GFP_KERNEL : GFP_ATOMIC;
		tmpbuf = kmalloc(req->nbytes, gfp);
		if (!tmpbuf) {
			ret = -ENOMEM;
			goto ahash_finup_exit;
		}

		if (sg_copy_to_buffer(req->src, nents, tmpbuf, req->nbytes) !=
				req->nbytes) {
			ret = -EINVAL;
			goto ahash_finup_free;
		}

		/* Call synchronous update */
		ret = crypto_shash_finup(ctx->shash, tmpbuf, req->nbytes,
					 req->result);
	} else {
		/* Otherwise call the internal function which uses SPU hw */
		return __ahash_finup(req);
	}
ahash_finup_free:
	kfree(tmpbuf);

ahash_finup_exit:
	/* Done with hash, can deallocate it now */
	crypto_free_shash(ctx->shash->tfm);
	kfree(ctx->shash);
	return ret;
}

static int ahash_digest(struct ahash_request *req)
{
	int err;

	flow_log("ahash_digest() nbytes:%u\n", req->nbytes);

	/* whole thing at once */
	err = __ahash_init(req);
	if (!err)
		err = __ahash_finup(req);

	return err;
}

static int ahash_setkey(struct crypto_ahash *ahash, const u8 *key,
			unsigned int keylen)
{
	struct iproc_ctx_s *ctx = crypto_ahash_ctx(ahash);

	flow_log("%s() ahash:%p key:%p keylen:%u\n",
		 __func__, ahash, key, keylen);
	flow_dump("  key: ", key, keylen);

	if (ctx->auth.alg == HASH_ALG_AES) {
		switch (keylen) {
		case AES_KEYSIZE_128:
			ctx->cipher_type = CIPHER_TYPE_AES128;
			break;
		case AES_KEYSIZE_192:
			ctx->cipher_type = CIPHER_TYPE_AES192;
			break;
		case AES_KEYSIZE_256:
			ctx->cipher_type = CIPHER_TYPE_AES256;
			break;
		default:
			pr_err("%s() Error: Invalid key length\n", __func__);
			return -EINVAL;
		}
	} else {
		pr_err("%s() Error: unknown hash alg\n", __func__);
		return -EINVAL;
	}
	memcpy(ctx->authkey, key, keylen);
	ctx->authkeylen = keylen;

	return 0;
}

static int ahash_export(struct ahash_request *req, void *out)
{
	const struct iproc_reqctx_s *rctx = ahash_request_ctx(req);
	struct spu_hash_export_s *spu_exp = (struct spu_hash_export_s *)out;

	spu_exp->total_todo = rctx->total_todo;
	spu_exp->total_sent = rctx->total_sent;
	spu_exp->is_sw_hmac = rctx->is_sw_hmac;
	memcpy(spu_exp->hash_carry, rctx->hash_carry, sizeof(rctx->hash_carry));
	spu_exp->hash_carry_len = rctx->hash_carry_len;
	memcpy(spu_exp->incr_hash, rctx->incr_hash, sizeof(rctx->incr_hash));

	return 0;
}

static int ahash_import(struct ahash_request *req, const void *in)
{
	struct iproc_reqctx_s *rctx = ahash_request_ctx(req);
	struct spu_hash_export_s *spu_exp = (struct spu_hash_export_s *)in;

	rctx->total_todo = spu_exp->total_todo;
	rctx->total_sent = spu_exp->total_sent;
	rctx->is_sw_hmac = spu_exp->is_sw_hmac;
	memcpy(rctx->hash_carry, spu_exp->hash_carry, sizeof(rctx->hash_carry));
	rctx->hash_carry_len = spu_exp->hash_carry_len;
	memcpy(rctx->incr_hash, spu_exp->incr_hash, sizeof(rctx->incr_hash));

	return 0;
}

static int ahash_hmac_setkey(struct crypto_ahash *ahash, const u8 *key,
			     unsigned int keylen)
{
	struct iproc_ctx_s *ctx = crypto_ahash_ctx(ahash);
	unsigned int blocksize =
		crypto_tfm_alg_blocksize(crypto_ahash_tfm(ahash));
	unsigned int digestsize = crypto_ahash_digestsize(ahash);
	unsigned int index;
	int rc;

	flow_log("%s() ahash:%p key:%p keylen:%u blksz:%u digestsz:%u\n",
		 __func__, ahash, key, keylen, blocksize, digestsize);
	flow_dump("  key: ", key, keylen);

	if (keylen > blocksize) {
		switch (ctx->auth.alg) {
		case HASH_ALG_MD5:
			rc = do_shash("md5", ctx->authkey, key, keylen, NULL,
				      0, NULL, 0);
			break;
		case HASH_ALG_SHA1:
			rc = do_shash("sha1", ctx->authkey, key, keylen, NULL,
				      0, NULL, 0);
			break;
		case HASH_ALG_SHA224:
			rc = do_shash("sha224", ctx->authkey, key, keylen, NULL,
				      0, NULL, 0);
			break;
		case HASH_ALG_SHA256:
			rc = do_shash("sha256", ctx->authkey, key, keylen, NULL,
				      0, NULL, 0);
			break;
		case HASH_ALG_SHA384:
			rc = do_shash("sha384", ctx->authkey, key, keylen, NULL,
				      0, NULL, 0);
			break;
		case HASH_ALG_SHA512:
			rc = do_shash("sha512", ctx->authkey, key, keylen, NULL,
				      0, NULL, 0);
			break;
		case HASH_ALG_SHA3_224:
			rc = do_shash("sha3-224", ctx->authkey, key, keylen,
				      NULL, 0, NULL, 0);
			break;
		case HASH_ALG_SHA3_256:
			rc = do_shash("sha3-256", ctx->authkey, key, keylen,
				      NULL, 0, NULL, 0);
			break;
		case HASH_ALG_SHA3_384:
			rc = do_shash("sha3-384", ctx->authkey, key, keylen,
				      NULL, 0, NULL, 0);
			break;
		case HASH_ALG_SHA3_512:
			rc = do_shash("sha3-512", ctx->authkey, key, keylen,
				      NULL, 0, NULL, 0);
			break;
		default:
			pr_err("%s() Error: unknown hash alg\n", __func__);
			return -EINVAL;
		}
		if (rc < 0) {
			pr_err("%s() Error %d computing shash for %s\n",
			       __func__, rc, hash_alg_name[ctx->auth.alg]);
			return rc;
		}
		ctx->authkeylen = digestsize;

		flow_log("  keylen > digestsize... hashed\n");
		flow_dump("  newkey: ", ctx->authkey, ctx->authkeylen);
	} else {
		memcpy(ctx->authkey, key, keylen);
		ctx->authkeylen = keylen;
	}

	/*
	 * Full HMAC operation in SPUM is not verified,
	 * So keeping the generation of IPAD, OPAD and
	 * outer hashing in software.
	 */
	if (iproc_priv.spu.spu_type == SPU_TYPE_SPUM) {
		memcpy(ctx->ipad, ctx->authkey, ctx->authkeylen);
		memset(ctx->ipad + ctx->authkeylen, 0,
		       blocksize - ctx->authkeylen);
		ctx->authkeylen = 0;
		memcpy(ctx->opad, ctx->ipad, blocksize);

		for (index = 0; index < blocksize; index++) {
			ctx->ipad[index] ^= HMAC_IPAD_VALUE;
			ctx->opad[index] ^= HMAC_OPAD_VALUE;
		}

		flow_dump("  ipad: ", ctx->ipad, blocksize);
		flow_dump("  opad: ", ctx->opad, blocksize);
	}
	ctx->digestsize = digestsize;
	atomic_inc(&iproc_priv.setkey_cnt[SPU_OP_HMAC]);

	return 0;
}

static int ahash_hmac_init(struct ahash_request *req)
{
	struct iproc_reqctx_s *rctx = ahash_request_ctx(req);
	struct crypto_ahash *tfm = crypto_ahash_reqtfm(req);
	struct iproc_ctx_s *ctx = crypto_ahash_ctx(tfm);
	unsigned int blocksize =
			crypto_tfm_alg_blocksize(crypto_ahash_tfm(tfm));

	flow_log("ahash_hmac_init()\n");

	/* init the context as a hash */
	ahash_init(req);

	if (!spu_no_incr_hash(ctx)) {
		/* SPU-M can do incr hashing but needs sw for outer HMAC */
		rctx->is_sw_hmac = true;
		ctx->auth.mode = HASH_MODE_HASH;
		/* start with a prepended ipad */
		memcpy(rctx->hash_carry, ctx->ipad, blocksize);
		rctx->hash_carry_len = blocksize;
		rctx->total_todo += blocksize;
	}

	return 0;
}

static int ahash_hmac_update(struct ahash_request *req)
{
	flow_log("ahash_hmac_update() nbytes:%u\n", req->nbytes);

	if (!req->nbytes)
		return 0;

	return ahash_update(req);
}

static int ahash_hmac_final(struct ahash_request *req)
{
	flow_log("ahash_hmac_final() nbytes:%u\n", req->nbytes);

	return ahash_final(req);
}

static int ahash_hmac_finup(struct ahash_request *req)
{
	flow_log("ahash_hmac_finupl() nbytes:%u\n", req->nbytes);

	return ahash_finup(req);
}

static int ahash_hmac_digest(struct ahash_request *req)
{
	struct iproc_reqctx_s *rctx = ahash_request_ctx(req);
	struct crypto_ahash *tfm = crypto_ahash_reqtfm(req);
	struct iproc_ctx_s *ctx = crypto_ahash_ctx(tfm);
	unsigned int blocksize =
			crypto_tfm_alg_blocksize(crypto_ahash_tfm(tfm));

	flow_log("ahash_hmac_digest() nbytes:%u\n", req->nbytes);

	/* Perform initialization and then call finup */
	__ahash_init(req);

	if (iproc_priv.spu.spu_type == SPU_TYPE_SPU2) {
		/*
		 * SPU2 supports full HMAC implementation in the
		 * hardware, need not to generate IPAD, OPAD and
		 * outer hash in software.
		 * Only for hash key len > hash block size, SPU2
		 * expects to perform hashing on the key, shorten
		 * it to digest size and feed it as hash key.
		 */
		rctx->is_sw_hmac = false;
		ctx->auth.mode = HASH_MODE_HMAC;
	} else {
		rctx->is_sw_hmac = true;
		ctx->auth.mode = HASH_MODE_HASH;
		/* start with a prepended ipad */
		memcpy(rctx->hash_carry, ctx->ipad, blocksize);
		rctx->hash_carry_len = blocksize;
		rctx->total_todo += blocksize;
	}

	return __ahash_finup(req);
}

/* aead helpers */

static int aead_need_fallback(struct aead_request *req)
{
	struct iproc_reqctx_s *rctx = aead_request_ctx(req);
	struct spu_hw *spu = &iproc_priv.spu;
	struct crypto_aead *aead = crypto_aead_reqtfm(req);
	struct iproc_ctx_s *ctx = crypto_aead_ctx(aead);
	u32 payload_len;

	/*
	 * SPU hardware cannot handle the AES-GCM/CCM case where plaintext
	 * and AAD are both 0 bytes long. So use fallback in this case.
	 */
	if (((ctx->cipher.mode == CIPHER_MODE_GCM) ||
	     (ctx->cipher.mode == CIPHER_MODE_CCM)) &&
	    (req->assoclen == 0)) {
		if ((rctx->is_encrypt && (req->cryptlen == 0)) ||
		    (!rctx->is_encrypt && (req->cryptlen == ctx->digestsize))) {
			flow_log("AES GCM/CCM needs fallback for 0 len req\n");
			return 1;
		}
	}

	/* SPU-M hardware only supports CCM digest size of 8, 12, or 16 bytes */
	if ((ctx->cipher.mode == CIPHER_MODE_CCM) &&
	    (spu->spu_type == SPU_TYPE_SPUM) &&
	    (ctx->digestsize != 8) && (ctx->digestsize != 12) &&
	    (ctx->digestsize != 16)) {
		flow_log("%s() AES CCM needs fallback for digest size %d\n",
			 __func__, ctx->digestsize);
		return 1;
	}

	/*
	 * SPU-M on NSP has an issue where AES-CCM hash is not correct
	 * when AAD size is 0
	 */
	if ((ctx->cipher.mode == CIPHER_MODE_CCM) &&
	    (spu->spu_subtype == SPU_SUBTYPE_SPUM_NSP) &&
	    (req->assoclen == 0)) {
		flow_log("%s() AES_CCM needs fallback for 0 len AAD on NSP\n",
			 __func__);
		return 1;
	}

	/*
	 * RFC4106 and RFC4543 cannot handle the case where AAD is other than
	 * 16 or 20 bytes long. So use fallback in this case.
	 */
	if (ctx->cipher.mode == CIPHER_MODE_GCM &&
	    ctx->cipher.alg == CIPHER_ALG_AES &&
	    rctx->iv_ctr_len == GCM_RFC4106_IV_SIZE &&
	    req->assoclen != 16 && req->assoclen != 20) {
		flow_log("RFC4106/RFC4543 needs fallback for assoclen"
			 " other than 16 or 20 bytes\n");
		return 1;
	}

	payload_len = req->cryptlen;
	if (spu->spu_type == SPU_TYPE_SPUM)
		payload_len += req->assoclen;

	flow_log("%s() payload len: %u\n", __func__, payload_len);

	if (ctx->max_payload == SPU_MAX_PAYLOAD_INF)
		return 0;
	else
		return payload_len > ctx->max_payload;
}

static void aead_complete(struct crypto_async_request *areq, int err)
{
	struct aead_request *req =
	    container_of(areq, struct aead_request, base);
	struct iproc_reqctx_s *rctx = aead_request_ctx(req);
	struct crypto_aead *aead = crypto_aead_reqtfm(req);

	flow_log("%s() err:%d\n", __func__, err);

	areq->tfm = crypto_aead_tfm(aead);

	areq->complete = rctx->old_complete;
	areq->data = rctx->old_data;

	areq->complete(areq, err);
}

static int aead_do_fallback(struct aead_request *req, bool is_encrypt)
{
	struct crypto_aead *aead = crypto_aead_reqtfm(req);
	struct crypto_tfm *tfm = crypto_aead_tfm(aead);
	struct iproc_reqctx_s *rctx = aead_request_ctx(req);
	struct iproc_ctx_s *ctx = crypto_tfm_ctx(tfm);
	int err;
	u32 req_flags;

	flow_log("%s() enc:%u\n", __func__, is_encrypt);

	if (ctx->fallback_cipher) {
		/* Store the cipher tfm and then use the fallback tfm */
		rctx->old_tfm = tfm;
		aead_request_set_tfm(req, ctx->fallback_cipher);
		/*
		 * Save the callback and chain ourselves in, so we can restore
		 * the tfm
		 */
		rctx->old_complete = req->base.complete;
		rctx->old_data = req->base.data;
		req_flags = aead_request_flags(req);
		aead_request_set_callback(req, req_flags, aead_complete, req);
		err = is_encrypt ? crypto_aead_encrypt(req) :
		    crypto_aead_decrypt(req);

		if (err == 0) {
			/*
			 * fallback was synchronous (did not return
			 * -EINPROGRESS). So restore request state here.
			 */
			aead_request_set_callback(req, req_flags,
						  rctx->old_complete, req);
			req->base.data = rctx->old_data;
			aead_request_set_tfm(req, aead);
			flow_log("%s() fallback completed successfully\n\n",
				 __func__);
		}
	} else {
		err = -EINVAL;
	}

	return err;
}

static int aead_enqueue(struct aead_request *req, bool is_encrypt)
{
	struct iproc_reqctx_s *rctx = aead_request_ctx(req);
	struct crypto_aead *aead = crypto_aead_reqtfm(req);
	struct iproc_ctx_s *ctx = crypto_aead_ctx(aead);
	int err;

	flow_log("%s() enc:%u\n", __func__, is_encrypt);

	if (req->assoclen > MAX_ASSOC_SIZE) {
		pr_err
		    ("%s() Error: associated data too long. (%u > %u bytes)\n",
		     __func__, req->assoclen, MAX_ASSOC_SIZE);
		return -EINVAL;
	}

	rctx->gfp = (req->base.flags & (CRYPTO_TFM_REQ_MAY_BACKLOG |
		       CRYPTO_TFM_REQ_MAY_SLEEP)) ? GFP_KERNEL : GFP_ATOMIC;
	rctx->parent = &req->base;
	rctx->is_encrypt = is_encrypt;
	rctx->bd_suppress = false;
	rctx->total_todo = req->cryptlen;
	rctx->src_sent = 0;
	rctx->total_sent = 0;
	rctx->total_received = 0;
	rctx->is_sw_hmac = false;
	rctx->ctx = ctx;
	memset(&rctx->mb_mssg, 0, sizeof(struct brcm_message));

	/* assoc data is at start of src sg */
	rctx->assoc = req->src;

	/*
	 * Init current position in src scatterlist to be after assoc data.
	 * src_skip set to buffer offset where data begins. (Assoc data could
	 * end in the middle of a buffer.)
	 */
	if (spu_sg_at_offset(req->src, req->assoclen, &rctx->src_sg,
			     &rctx->src_skip) < 0) {
		pr_err("%s() Error: Unable to find start of src data\n",
		       __func__);
		return -EINVAL;
	}

	rctx->src_nents = 0;
	rctx->dst_nents = 0;
	if (req->dst == req->src) {
		rctx->dst_sg = rctx->src_sg;
		rctx->dst_skip = rctx->src_skip;
	} else {
		/*
		 * Expect req->dst to have room for assoc data followed by
		 * output data and ICV, if encrypt. So initialize dst_sg
		 * to point beyond assoc len offset.
		 */
		if (spu_sg_at_offset(req->dst, req->assoclen, &rctx->dst_sg,
				     &rctx->dst_skip) < 0) {
			pr_err("%s() Error: Unable to find start of dst data\n",
			       __func__);
			return -EINVAL;
		}
	}

	if (ctx->cipher.mode == CIPHER_MODE_CBC ||
	    ctx->cipher.mode == CIPHER_MODE_CTR ||
	    ctx->cipher.mode == CIPHER_MODE_OFB ||
	    ctx->cipher.mode == CIPHER_MODE_XTS ||
	    ctx->cipher.mode == CIPHER_MODE_GCM) {
		rctx->iv_ctr_len =
			ctx->salt_len +
			crypto_aead_ivsize(crypto_aead_reqtfm(req));
	} else if (ctx->cipher.mode == CIPHER_MODE_CCM) {
		rctx->iv_ctr_len = CCM_AES_IV_SIZE;
	} else {
		rctx->iv_ctr_len = 0;
	}

	rctx->hash_carry_len = 0;

	flow_log("  src sg: %p\n", req->src);
	flow_log("  rctx->src_sg: %p, src_skip %u\n",
		 rctx->src_sg, rctx->src_skip);
	flow_log("  assoc:  %p, assoclen %u\n", rctx->assoc, req->assoclen);
	flow_log("  dst sg: %p\n", req->dst);
	flow_log("  rctx->dst_sg: %p, dst_skip %u\n",
		 rctx->dst_sg, rctx->dst_skip);
	flow_log("  iv_ctr_len:%u\n", rctx->iv_ctr_len);
	flow_dump("  iv: ", req->iv, rctx->iv_ctr_len);
	flow_log("  authkeylen:%u\n", ctx->authkeylen);
	flow_log("  is_esp: %s\n", ctx->is_esp ? "yes" : "no");

	if (ctx->max_payload == SPU_MAX_PAYLOAD_INF)
		flow_log("  max_payload infinite");
	else
		flow_log("  max_payload: %u\n", ctx->max_payload);

	if (unlikely(aead_need_fallback(req)))
		return aead_do_fallback(req, is_encrypt);

	/*
	 * Do memory allocations for request after fallback check, because if we
	 * do fallback, we won't call finish_req() to dealloc.
	 */
	if (rctx->iv_ctr_len) {
		if (ctx->salt_len)
			memcpy(rctx->msg_buf.iv_ctr + ctx->salt_offset,
			       ctx->salt, ctx->salt_len);
		memcpy(rctx->msg_buf.iv_ctr + ctx->salt_offset + ctx->salt_len,
		       req->iv,
		       rctx->iv_ctr_len - ctx->salt_len - ctx->salt_offset);
	}

	rctx->chan_idx = select_channel();
	err = handle_aead_req(rctx);
	if (err != -EINPROGRESS)
		/* synchronous result */
		spu_chunk_cleanup(rctx);

	return err;
}

static int aead_authenc_setkey(struct crypto_aead *cipher,
			       const u8 *key, unsigned int keylen)
{
	struct spu_hw *spu = &iproc_priv.spu;
	struct iproc_ctx_s *ctx = crypto_aead_ctx(cipher);
	struct crypto_tfm *tfm = crypto_aead_tfm(cipher);
	struct crypto_authenc_keys keys;
	int ret;

	flow_log("%s() aead:%p key:%p keylen:%u\n", __func__, cipher, key,
		 keylen);
	flow_dump("  key: ", key, keylen);

	ret = crypto_authenc_extractkeys(&keys, key, keylen);
	if (ret)
		goto badkey;

	if (keys.enckeylen > MAX_KEY_SIZE ||
	    keys.authkeylen > MAX_KEY_SIZE)
		goto badkey;

	ctx->enckeylen = keys.enckeylen;
	ctx->authkeylen = keys.authkeylen;

	memcpy(ctx->enckey, keys.enckey, keys.enckeylen);
	/* May end up padding auth key. So make sure it's zeroed. */
	memset(ctx->authkey, 0, sizeof(ctx->authkey));
	memcpy(ctx->authkey, keys.authkey, keys.authkeylen);

	switch (ctx->alg->cipher_info.alg) {
	case CIPHER_ALG_DES:
		if (verify_aead_des_key(cipher, keys.enckey, keys.enckeylen))
			return -EINVAL;

		ctx->cipher_type = CIPHER_TYPE_DES;
		break;
	case CIPHER_ALG_3DES:
		if (verify_aead_des3_key(cipher, keys.enckey, keys.enckeylen))
			return -EINVAL;

		ctx->cipher_type = CIPHER_TYPE_3DES;
		break;
	case CIPHER_ALG_AES:
		switch (ctx->enckeylen) {
		case AES_KEYSIZE_128:
			ctx->cipher_type = CIPHER_TYPE_AES128;
			break;
		case AES_KEYSIZE_192:
			ctx->cipher_type = CIPHER_TYPE_AES192;
			break;
		case AES_KEYSIZE_256:
			ctx->cipher_type = CIPHER_TYPE_AES256;
			break;
		default:
			goto badkey;
		}
		break;
	case CIPHER_ALG_RC4:
		ctx->cipher_type = CIPHER_TYPE_INIT;
		break;
	default:
		pr_err("%s() Error: Unknown cipher alg\n", __func__);
		return -EINVAL;
	}

	flow_log("  enckeylen:%u authkeylen:%u\n", ctx->enckeylen,
		 ctx->authkeylen);
	flow_dump("  enc: ", ctx->enckey, ctx->enckeylen);
	flow_dump("  auth: ", ctx->authkey, ctx->authkeylen);

	/* setkey the fallback just in case we needto use it */
	if (ctx->fallback_cipher) {
		flow_log("  running fallback setkey()\n");

		ctx->fallback_cipher->base.crt_flags &= ~CRYPTO_TFM_REQ_MASK;
		ctx->fallback_cipher->base.crt_flags |=
		    tfm->crt_flags & CRYPTO_TFM_REQ_MASK;
		ret = crypto_aead_setkey(ctx->fallback_cipher, key, keylen);
		if (ret)
			flow_log("  fallback setkey() returned:%d\n", ret);
	}

	ctx->spu_resp_hdr_len = spu->spu_response_hdr_len(ctx->authkeylen,
							  ctx->enckeylen,
							  false);

	atomic_inc(&iproc_priv.setkey_cnt[SPU_OP_AEAD]);

	return ret;

badkey:
	ctx->enckeylen = 0;
	ctx->authkeylen = 0;
	ctx->digestsize = 0;

	return -EINVAL;
}

static int aead_gcm_ccm_setkey(struct crypto_aead *cipher,
			       const u8 *key, unsigned int keylen)
{
	struct spu_hw *spu = &iproc_priv.spu;
	struct iproc_ctx_s *ctx = crypto_aead_ctx(cipher);
	struct crypto_tfm *tfm = crypto_aead_tfm(cipher);

	int ret = 0;

	flow_log("%s() keylen:%u\n", __func__, keylen);
	flow_dump("  key: ", key, keylen);

	if (!ctx->is_esp)
		ctx->digestsize = keylen;

	ctx->enckeylen = keylen;
	ctx->authkeylen = 0;
	memcpy(ctx->enckey, key, ctx->enckeylen);

	switch (ctx->enckeylen) {
	case AES_KEYSIZE_128:
		ctx->cipher_type = CIPHER_TYPE_AES128;
		break;
	case AES_KEYSIZE_192:
		ctx->cipher_type = CIPHER_TYPE_AES192;
		break;
	case AES_KEYSIZE_256:
		ctx->cipher_type = CIPHER_TYPE_AES256;
		break;
	default:
		goto badkey;
	}

	flow_log("  enckeylen:%u authkeylen:%u\n", ctx->enckeylen,
		 ctx->authkeylen);
	flow_dump("  enc: ", ctx->enckey, ctx->enckeylen);
	flow_dump("  auth: ", ctx->authkey, ctx->authkeylen);

	/* setkey the fallback just in case we need to use it */
	if (ctx->fallback_cipher) {
		flow_log("  running fallback setkey()\n");

		ctx->fallback_cipher->base.crt_flags &= ~CRYPTO_TFM_REQ_MASK;
		ctx->fallback_cipher->base.crt_flags |=
		    tfm->crt_flags & CRYPTO_TFM_REQ_MASK;
		ret = crypto_aead_setkey(ctx->fallback_cipher, key,
					 keylen + ctx->salt_len);
		if (ret)
			flow_log("  fallback setkey() returned:%d\n", ret);
	}

	ctx->spu_resp_hdr_len = spu->spu_response_hdr_len(ctx->authkeylen,
							  ctx->enckeylen,
							  false);

	atomic_inc(&iproc_priv.setkey_cnt[SPU_OP_AEAD]);

	flow_log("  enckeylen:%u authkeylen:%u\n", ctx->enckeylen,
		 ctx->authkeylen);

	return ret;

badkey:
	ctx->enckeylen = 0;
	ctx->authkeylen = 0;
	ctx->digestsize = 0;

	return -EINVAL;
}

/**
 * aead_gcm_esp_setkey() - setkey() operation for ESP variant of GCM AES.
 * @cipher: AEAD structure
 * @key:    Key followed by 4 bytes of salt
 * @keylen: Length of key plus salt, in bytes
 *
 * Extracts salt from key and stores it to be prepended to IV on each request.
 * Digest is always 16 bytes
 *
 * Return: Value from generic gcm setkey.
 */
static int aead_gcm_esp_setkey(struct crypto_aead *cipher,
			       const u8 *key, unsigned int keylen)
{
	struct iproc_ctx_s *ctx = crypto_aead_ctx(cipher);

	flow_log("%s\n", __func__);
	ctx->salt_len = GCM_ESP_SALT_SIZE;
	ctx->salt_offset = GCM_ESP_SALT_OFFSET;
	memcpy(ctx->salt, key + keylen - GCM_ESP_SALT_SIZE, GCM_ESP_SALT_SIZE);
	keylen -= GCM_ESP_SALT_SIZE;
	ctx->digestsize = GCM_ESP_DIGESTSIZE;
	ctx->is_esp = true;
	flow_dump("salt: ", ctx->salt, GCM_ESP_SALT_SIZE);

	return aead_gcm_ccm_setkey(cipher, key, keylen);
}

/**
 * rfc4543_gcm_esp_setkey() - setkey operation for RFC4543 variant of GCM/GMAC.
 * cipher: AEAD structure
 * key:    Key followed by 4 bytes of salt
 * keylen: Length of key plus salt, in bytes
 *
 * Extracts salt from key and stores it to be prepended to IV on each request.
 * Digest is always 16 bytes
 *
 * Return: Value from generic gcm setkey.
 */
static int rfc4543_gcm_esp_setkey(struct crypto_aead *cipher,
				  const u8 *key, unsigned int keylen)
{
	struct iproc_ctx_s *ctx = crypto_aead_ctx(cipher);

	flow_log("%s\n", __func__);
	ctx->salt_len = GCM_ESP_SALT_SIZE;
	ctx->salt_offset = GCM_ESP_SALT_OFFSET;
	memcpy(ctx->salt, key + keylen - GCM_ESP_SALT_SIZE, GCM_ESP_SALT_SIZE);
	keylen -= GCM_ESP_SALT_SIZE;
	ctx->digestsize = GCM_ESP_DIGESTSIZE;
	ctx->is_esp = true;
	ctx->is_rfc4543 = true;
	flow_dump("salt: ", ctx->salt, GCM_ESP_SALT_SIZE);

	return aead_gcm_ccm_setkey(cipher, key, keylen);
}

/**
 * aead_ccm_esp_setkey() - setkey() operation for ESP variant of CCM AES.
 * @cipher: AEAD structure
 * @key:    Key followed by 4 bytes of salt
 * @keylen: Length of key plus salt, in bytes
 *
 * Extracts salt from key and stores it to be prepended to IV on each request.
 * Digest is always 16 bytes
 *
 * Return: Value from generic ccm setkey.
 */
static int aead_ccm_esp_setkey(struct crypto_aead *cipher,
			       const u8 *key, unsigned int keylen)
{
	struct iproc_ctx_s *ctx = crypto_aead_ctx(cipher);

	flow_log("%s\n", __func__);
	ctx->salt_len = CCM_ESP_SALT_SIZE;
	ctx->salt_offset = CCM_ESP_SALT_OFFSET;
	memcpy(ctx->salt, key + keylen - CCM_ESP_SALT_SIZE, CCM_ESP_SALT_SIZE);
	keylen -= CCM_ESP_SALT_SIZE;
	ctx->is_esp = true;
	flow_dump("salt: ", ctx->salt, CCM_ESP_SALT_SIZE);

	return aead_gcm_ccm_setkey(cipher, key, keylen);
}

static int aead_setauthsize(struct crypto_aead *cipher, unsigned int authsize)
{
	struct iproc_ctx_s *ctx = crypto_aead_ctx(cipher);
	int ret = 0;

	flow_log("%s() authkeylen:%u authsize:%u\n",
		 __func__, ctx->authkeylen, authsize);

	ctx->digestsize = authsize;

	/* setkey the fallback just in case we needto use it */
	if (ctx->fallback_cipher) {
		flow_log("  running fallback setauth()\n");

		ret = crypto_aead_setauthsize(ctx->fallback_cipher, authsize);
		if (ret)
			flow_log("  fallback setauth() returned:%d\n", ret);
	}

	return ret;
}

static int aead_encrypt(struct aead_request *req)
{
	flow_log("%s() cryptlen:%u %08x\n", __func__, req->cryptlen,
		 req->cryptlen);
	dump_sg(req->src, 0, req->cryptlen + req->assoclen);
	flow_log("  assoc_len:%u\n", req->assoclen);

	return aead_enqueue(req, true);
}

static int aead_decrypt(struct aead_request *req)
{
	flow_log("%s() cryptlen:%u\n", __func__, req->cryptlen);
	dump_sg(req->src, 0, req->cryptlen + req->assoclen);
	flow_log("  assoc_len:%u\n", req->assoclen);

	return aead_enqueue(req, false);
}

/* ==================== Supported Cipher Algorithms ==================== */

static struct iproc_alg_s driver_algs[] = {
	{
	 .type = CRYPTO_ALG_TYPE_AEAD,
	 .alg.aead = {
		 .base = {
			.cra_name = "gcm(aes)",
			.cra_driver_name = "gcm-aes-iproc",
			.cra_blocksize = AES_BLOCK_SIZE,
			.cra_flags = CRYPTO_ALG_NEED_FALLBACK
		 },
		 .setkey = aead_gcm_ccm_setkey,
		 .ivsize = GCM_AES_IV_SIZE,
		.maxauthsize = AES_BLOCK_SIZE,
	 },
	 .cipher_info = {
			 .alg = CIPHER_ALG_AES,
			 .mode = CIPHER_MODE_GCM,
			 },
	 .auth_info = {
		       .alg = HASH_ALG_AES,
		       .mode = HASH_MODE_GCM,
		       },
	 .auth_first = 0,
	 },
	{
	 .type = CRYPTO_ALG_TYPE_AEAD,
	 .alg.aead = {
		 .base = {
			.cra_name = "ccm(aes)",
			.cra_driver_name = "ccm-aes-iproc",
			.cra_blocksize = AES_BLOCK_SIZE,
			.cra_flags = CRYPTO_ALG_NEED_FALLBACK
		 },
		 .setkey = aead_gcm_ccm_setkey,
		 .ivsize = CCM_AES_IV_SIZE,
		.maxauthsize = AES_BLOCK_SIZE,
	 },
	 .cipher_info = {
			 .alg = CIPHER_ALG_AES,
			 .mode = CIPHER_MODE_CCM,
			 },
	 .auth_info = {
		       .alg = HASH_ALG_AES,
		       .mode = HASH_MODE_CCM,
		       },
	 .auth_first = 0,
	 },
	{
	 .type = CRYPTO_ALG_TYPE_AEAD,
	 .alg.aead = {
		 .base = {
			.cra_name = "rfc4106(gcm(aes))",
			.cra_driver_name = "gcm-aes-esp-iproc",
			.cra_blocksize = AES_BLOCK_SIZE,
			.cra_flags = CRYPTO_ALG_NEED_FALLBACK
		 },
		 .setkey = aead_gcm_esp_setkey,
		 .ivsize = GCM_RFC4106_IV_SIZE,
		 .maxauthsize = AES_BLOCK_SIZE,
	 },
	 .cipher_info = {
			 .alg = CIPHER_ALG_AES,
			 .mode = CIPHER_MODE_GCM,
			 },
	 .auth_info = {
		       .alg = HASH_ALG_AES,
		       .mode = HASH_MODE_GCM,
		       },
	 .auth_first = 0,
	 },
	{
	 .type = CRYPTO_ALG_TYPE_AEAD,
	 .alg.aead = {
		 .base = {
			.cra_name = "rfc4309(ccm(aes))",
			.cra_driver_name = "ccm-aes-esp-iproc",
			.cra_blocksize = AES_BLOCK_SIZE,
			.cra_flags = CRYPTO_ALG_NEED_FALLBACK
		 },
		 .setkey = aead_ccm_esp_setkey,
		 .ivsize = CCM_AES_IV_SIZE,
		 .maxauthsize = AES_BLOCK_SIZE,
	 },
	 .cipher_info = {
			 .alg = CIPHER_ALG_AES,
			 .mode = CIPHER_MODE_CCM,
			 },
	 .auth_info = {
		       .alg = HASH_ALG_AES,
		       .mode = HASH_MODE_CCM,
		       },
	 .auth_first = 0,
	 },
	{
	 .type = CRYPTO_ALG_TYPE_AEAD,
	 .alg.aead = {
		 .base = {
			.cra_name = "rfc4543(gcm(aes))",
			.cra_driver_name = "gmac-aes-esp-iproc",
			.cra_blocksize = AES_BLOCK_SIZE,
			.cra_flags = CRYPTO_ALG_NEED_FALLBACK
		 },
		 .setkey = rfc4543_gcm_esp_setkey,
		 .ivsize = GCM_RFC4106_IV_SIZE,
		 .maxauthsize = AES_BLOCK_SIZE,
	 },
	 .cipher_info = {
			 .alg = CIPHER_ALG_AES,
			 .mode = CIPHER_MODE_GCM,
			 },
	 .auth_info = {
		       .alg = HASH_ALG_AES,
		       .mode = HASH_MODE_GCM,
		       },
	 .auth_first = 0,
	 },
	{
	 .type = CRYPTO_ALG_TYPE_AEAD,
	 .alg.aead = {
		 .base = {
			.cra_name = "authenc(hmac(md5),cbc(aes))",
			.cra_driver_name = "authenc-hmac-md5-cbc-aes-iproc",
			.cra_blocksize = AES_BLOCK_SIZE,
			.cra_flags = CRYPTO_ALG_NEED_FALLBACK | CRYPTO_ALG_ASYNC
		 },
		 .setkey = aead_authenc_setkey,
		.ivsize = AES_BLOCK_SIZE,
		.maxauthsize = MD5_DIGEST_SIZE,
	 },
	 .cipher_info = {
			 .alg = CIPHER_ALG_AES,
			 .mode = CIPHER_MODE_CBC,
			 },
	 .auth_info = {
		       .alg = HASH_ALG_MD5,
		       .mode = HASH_MODE_HMAC,
		       },
	 .auth_first = 0,
	 },
	{
	 .type = CRYPTO_ALG_TYPE_AEAD,
	 .alg.aead = {
		 .base = {
			.cra_name = "authenc(hmac(sha1),cbc(aes))",
			.cra_driver_name = "authenc-hmac-sha1-cbc-aes-iproc",
			.cra_blocksize = AES_BLOCK_SIZE,
			.cra_flags = CRYPTO_ALG_NEED_FALLBACK | CRYPTO_ALG_ASYNC
		 },
		 .setkey = aead_authenc_setkey,
		 .ivsize = AES_BLOCK_SIZE,
		 .maxauthsize = SHA1_DIGEST_SIZE,
	 },
	 .cipher_info = {
			 .alg = CIPHER_ALG_AES,
			 .mode = CIPHER_MODE_CBC,
			 },
	 .auth_info = {
		       .alg = HASH_ALG_SHA1,
		       .mode = HASH_MODE_HMAC,
		       },
	 .auth_first = 0,
	 },
	{
	 .type = CRYPTO_ALG_TYPE_AEAD,
	 .alg.aead = {
		 .base = {
			.cra_name = "authenc(hmac(sha256),cbc(aes))",
			.cra_driver_name = "authenc-hmac-sha256-cbc-aes-iproc",
			.cra_blocksize = AES_BLOCK_SIZE,
			.cra_flags = CRYPTO_ALG_NEED_FALLBACK | CRYPTO_ALG_ASYNC
		 },
		 .setkey = aead_authenc_setkey,
		 .ivsize = AES_BLOCK_SIZE,
		 .maxauthsize = SHA256_DIGEST_SIZE,
	 },
	 .cipher_info = {
			 .alg = CIPHER_ALG_AES,
			 .mode = CIPHER_MODE_CBC,
			 },
	 .auth_info = {
		       .alg = HASH_ALG_SHA256,
		       .mode = HASH_MODE_HMAC,
		       },
	 .auth_first = 0,
	 },
	{
	 .type = CRYPTO_ALG_TYPE_AEAD,
	 .alg.aead = {
		 .base = {
			.cra_name = "authenc(hmac(md5),cbc(des))",
			.cra_driver_name = "authenc-hmac-md5-cbc-des-iproc",
			.cra_blocksize = DES_BLOCK_SIZE,
			.cra_flags = CRYPTO_ALG_NEED_FALLBACK | CRYPTO_ALG_ASYNC
		 },
		 .setkey = aead_authenc_setkey,
		 .ivsize = DES_BLOCK_SIZE,
		 .maxauthsize = MD5_DIGEST_SIZE,
	 },
	 .cipher_info = {
			 .alg = CIPHER_ALG_DES,
			 .mode = CIPHER_MODE_CBC,
			 },
	 .auth_info = {
		       .alg = HASH_ALG_MD5,
		       .mode = HASH_MODE_HMAC,
		       },
	 .auth_first = 0,
	 },
	{
	 .type = CRYPTO_ALG_TYPE_AEAD,
	 .alg.aead = {
		 .base = {
			.cra_name = "authenc(hmac(sha1),cbc(des))",
			.cra_driver_name = "authenc-hmac-sha1-cbc-des-iproc",
			.cra_blocksize = DES_BLOCK_SIZE,
			.cra_flags = CRYPTO_ALG_NEED_FALLBACK | CRYPTO_ALG_ASYNC
		 },
		 .setkey = aead_authenc_setkey,
		 .ivsize = DES_BLOCK_SIZE,
		 .maxauthsize = SHA1_DIGEST_SIZE,
	 },
	 .cipher_info = {
			 .alg = CIPHER_ALG_DES,
			 .mode = CIPHER_MODE_CBC,
			 },
	 .auth_info = {
		       .alg = HASH_ALG_SHA1,
		       .mode = HASH_MODE_HMAC,
		       },
	 .auth_first = 0,
	 },
	{
	 .type = CRYPTO_ALG_TYPE_AEAD,
	 .alg.aead = {
		 .base = {
			.cra_name = "authenc(hmac(sha224),cbc(des))",
			.cra_driver_name = "authenc-hmac-sha224-cbc-des-iproc",
			.cra_blocksize = DES_BLOCK_SIZE,
			.cra_flags = CRYPTO_ALG_NEED_FALLBACK | CRYPTO_ALG_ASYNC
		 },
		 .setkey = aead_authenc_setkey,
		 .ivsize = DES_BLOCK_SIZE,
		 .maxauthsize = SHA224_DIGEST_SIZE,
	 },
	 .cipher_info = {
			 .alg = CIPHER_ALG_DES,
			 .mode = CIPHER_MODE_CBC,
			 },
	 .auth_info = {
		       .alg = HASH_ALG_SHA224,
		       .mode = HASH_MODE_HMAC,
		       },
	 .auth_first = 0,
	 },
	{
	 .type = CRYPTO_ALG_TYPE_AEAD,
	 .alg.aead = {
		 .base = {
			.cra_name = "authenc(hmac(sha256),cbc(des))",
			.cra_driver_name = "authenc-hmac-sha256-cbc-des-iproc",
			.cra_blocksize = DES_BLOCK_SIZE,
			.cra_flags = CRYPTO_ALG_NEED_FALLBACK | CRYPTO_ALG_ASYNC
		 },
		 .setkey = aead_authenc_setkey,
		 .ivsize = DES_BLOCK_SIZE,
		 .maxauthsize = SHA256_DIGEST_SIZE,
	 },
	 .cipher_info = {
			 .alg = CIPHER_ALG_DES,
			 .mode = CIPHER_MODE_CBC,
			 },
	 .auth_info = {
		       .alg = HASH_ALG_SHA256,
		       .mode = HASH_MODE_HMAC,
		       },
	 .auth_first = 0,
	 },
	{
	 .type = CRYPTO_ALG_TYPE_AEAD,
	 .alg.aead = {
		 .base = {
			.cra_name = "authenc(hmac(sha384),cbc(des))",
			.cra_driver_name = "authenc-hmac-sha384-cbc-des-iproc",
			.cra_blocksize = DES_BLOCK_SIZE,
			.cra_flags = CRYPTO_ALG_NEED_FALLBACK | CRYPTO_ALG_ASYNC
		 },
		 .setkey = aead_authenc_setkey,
		 .ivsize = DES_BLOCK_SIZE,
		 .maxauthsize = SHA384_DIGEST_SIZE,
	 },
	 .cipher_info = {
			 .alg = CIPHER_ALG_DES,
			 .mode = CIPHER_MODE_CBC,
			 },
	 .auth_info = {
		       .alg = HASH_ALG_SHA384,
		       .mode = HASH_MODE_HMAC,
		       },
	 .auth_first = 0,
	 },
	{
	 .type = CRYPTO_ALG_TYPE_AEAD,
	 .alg.aead = {
		 .base = {
			.cra_name = "authenc(hmac(sha512),cbc(des))",
			.cra_driver_name = "authenc-hmac-sha512-cbc-des-iproc",
			.cra_blocksize = DES_BLOCK_SIZE,
			.cra_flags = CRYPTO_ALG_NEED_FALLBACK | CRYPTO_ALG_ASYNC
		 },
		 .setkey = aead_authenc_setkey,
		 .ivsize = DES_BLOCK_SIZE,
		 .maxauthsize = SHA512_DIGEST_SIZE,
	 },
	 .cipher_info = {
			 .alg = CIPHER_ALG_DES,
			 .mode = CIPHER_MODE_CBC,
			 },
	 .auth_info = {
		       .alg = HASH_ALG_SHA512,
		       .mode = HASH_MODE_HMAC,
		       },
	 .auth_first = 0,
	 },
	{
	 .type = CRYPTO_ALG_TYPE_AEAD,
	 .alg.aead = {
		 .base = {
			.cra_name = "authenc(hmac(md5),cbc(des3_ede))",
			.cra_driver_name = "authenc-hmac-md5-cbc-des3-iproc",
			.cra_blocksize = DES3_EDE_BLOCK_SIZE,
			.cra_flags = CRYPTO_ALG_NEED_FALLBACK | CRYPTO_ALG_ASYNC
		 },
		 .setkey = aead_authenc_setkey,
		 .ivsize = DES3_EDE_BLOCK_SIZE,
		 .maxauthsize = MD5_DIGEST_SIZE,
	 },
	 .cipher_info = {
			 .alg = CIPHER_ALG_3DES,
			 .mode = CIPHER_MODE_CBC,
			 },
	 .auth_info = {
		       .alg = HASH_ALG_MD5,
		       .mode = HASH_MODE_HMAC,
		       },
	 .auth_first = 0,
	 },
	{
	 .type = CRYPTO_ALG_TYPE_AEAD,
	 .alg.aead = {
		 .base = {
			.cra_name = "authenc(hmac(sha1),cbc(des3_ede))",
			.cra_driver_name = "authenc-hmac-sha1-cbc-des3-iproc",
			.cra_blocksize = DES3_EDE_BLOCK_SIZE,
			.cra_flags = CRYPTO_ALG_NEED_FALLBACK | CRYPTO_ALG_ASYNC
		 },
		 .setkey = aead_authenc_setkey,
		 .ivsize = DES3_EDE_BLOCK_SIZE,
		 .maxauthsize = SHA1_DIGEST_SIZE,
	 },
	 .cipher_info = {
			 .alg = CIPHER_ALG_3DES,
			 .mode = CIPHER_MODE_CBC,
			 },
	 .auth_info = {
		       .alg = HASH_ALG_SHA1,
		       .mode = HASH_MODE_HMAC,
		       },
	 .auth_first = 0,
	 },
	{
	 .type = CRYPTO_ALG_TYPE_AEAD,
	 .alg.aead = {
		 .base = {
			.cra_name = "authenc(hmac(sha224),cbc(des3_ede))",
			.cra_driver_name = "authenc-hmac-sha224-cbc-des3-iproc",
			.cra_blocksize = DES3_EDE_BLOCK_SIZE,
			.cra_flags = CRYPTO_ALG_NEED_FALLBACK | CRYPTO_ALG_ASYNC
		 },
		 .setkey = aead_authenc_setkey,
		 .ivsize = DES3_EDE_BLOCK_SIZE,
		 .maxauthsize = SHA224_DIGEST_SIZE,
	 },
	 .cipher_info = {
			 .alg = CIPHER_ALG_3DES,
			 .mode = CIPHER_MODE_CBC,
			 },
	 .auth_info = {
		       .alg = HASH_ALG_SHA224,
		       .mode = HASH_MODE_HMAC,
		       },
	 .auth_first = 0,
	 },
	{
	 .type = CRYPTO_ALG_TYPE_AEAD,
	 .alg.aead = {
		 .base = {
			.cra_name = "authenc(hmac(sha256),cbc(des3_ede))",
			.cra_driver_name = "authenc-hmac-sha256-cbc-des3-iproc",
			.cra_blocksize = DES3_EDE_BLOCK_SIZE,
			.cra_flags = CRYPTO_ALG_NEED_FALLBACK | CRYPTO_ALG_ASYNC
		 },
		 .setkey = aead_authenc_setkey,
		 .ivsize = DES3_EDE_BLOCK_SIZE,
		 .maxauthsize = SHA256_DIGEST_SIZE,
	 },
	 .cipher_info = {
			 .alg = CIPHER_ALG_3DES,
			 .mode = CIPHER_MODE_CBC,
			 },
	 .auth_info = {
		       .alg = HASH_ALG_SHA256,
		       .mode = HASH_MODE_HMAC,
		       },
	 .auth_first = 0,
	 },
	{
	 .type = CRYPTO_ALG_TYPE_AEAD,
	 .alg.aead = {
		 .base = {
			.cra_name = "authenc(hmac(sha384),cbc(des3_ede))",
			.cra_driver_name = "authenc-hmac-sha384-cbc-des3-iproc",
			.cra_blocksize = DES3_EDE_BLOCK_SIZE,
			.cra_flags = CRYPTO_ALG_NEED_FALLBACK | CRYPTO_ALG_ASYNC
		 },
		 .setkey = aead_authenc_setkey,
		 .ivsize = DES3_EDE_BLOCK_SIZE,
		 .maxauthsize = SHA384_DIGEST_SIZE,
	 },
	 .cipher_info = {
			 .alg = CIPHER_ALG_3DES,
			 .mode = CIPHER_MODE_CBC,
			 },
	 .auth_info = {
		       .alg = HASH_ALG_SHA384,
		       .mode = HASH_MODE_HMAC,
		       },
	 .auth_first = 0,
	 },
	{
	 .type = CRYPTO_ALG_TYPE_AEAD,
	 .alg.aead = {
		 .base = {
			.cra_name = "authenc(hmac(sha512),cbc(des3_ede))",
			.cra_driver_name = "authenc-hmac-sha512-cbc-des3-iproc",
			.cra_blocksize = DES3_EDE_BLOCK_SIZE,
			.cra_flags = CRYPTO_ALG_NEED_FALLBACK | CRYPTO_ALG_ASYNC
		 },
		 .setkey = aead_authenc_setkey,
		 .ivsize = DES3_EDE_BLOCK_SIZE,
		 .maxauthsize = SHA512_DIGEST_SIZE,
	 },
	 .cipher_info = {
			 .alg = CIPHER_ALG_3DES,
			 .mode = CIPHER_MODE_CBC,
			 },
	 .auth_info = {
		       .alg = HASH_ALG_SHA512,
		       .mode = HASH_MODE_HMAC,
		       },
	 .auth_first = 0,
	 },

/* SKCIPHER algorithms. */
	{
	 .type = CRYPTO_ALG_TYPE_SKCIPHER,
	 .alg.skcipher = {
			.base.cra_name = "ecb(arc4)",
			.base.cra_driver_name = "ecb-arc4-iproc",
			.base.cra_blocksize = ARC4_BLOCK_SIZE,
			.min_keysize = ARC4_MIN_KEY_SIZE,
			.max_keysize = ARC4_MAX_KEY_SIZE,
			.ivsize = 0,
			},
	 .cipher_info = {
			 .alg = CIPHER_ALG_RC4,
			 .mode = CIPHER_MODE_NONE,
			 },
	 .auth_info = {
		       .alg = HASH_ALG_NONE,
		       .mode = HASH_MODE_NONE,
		       },
	 },
	{
	 .type = CRYPTO_ALG_TYPE_SKCIPHER,
	 .alg.skcipher = {
			.base.cra_name = "ofb(des)",
			.base.cra_driver_name = "ofb-des-iproc",
			.base.cra_blocksize = DES_BLOCK_SIZE,
			.min_keysize = DES_KEY_SIZE,
			.max_keysize = DES_KEY_SIZE,
			.ivsize = DES_BLOCK_SIZE,
			},
	 .cipher_info = {
			 .alg = CIPHER_ALG_DES,
			 .mode = CIPHER_MODE_OFB,
			 },
	 .auth_info = {
		       .alg = HASH_ALG_NONE,
		       .mode = HASH_MODE_NONE,
		       },
	 },
	{
	 .type = CRYPTO_ALG_TYPE_SKCIPHER,
	 .alg.skcipher = {
			.base.cra_name = "cbc(des)",
			.base.cra_driver_name = "cbc-des-iproc",
			.base.cra_blocksize = DES_BLOCK_SIZE,
			.min_keysize = DES_KEY_SIZE,
			.max_keysize = DES_KEY_SIZE,
			.ivsize = DES_BLOCK_SIZE,
			},
	 .cipher_info = {
			 .alg = CIPHER_ALG_DES,
			 .mode = CIPHER_MODE_CBC,
			 },
	 .auth_info = {
		       .alg = HASH_ALG_NONE,
		       .mode = HASH_MODE_NONE,
		       },
	 },
	{
	 .type = CRYPTO_ALG_TYPE_SKCIPHER,
	 .alg.skcipher = {
			.base.cra_name = "ecb(des)",
			.base.cra_driver_name = "ecb-des-iproc",
			.base.cra_blocksize = DES_BLOCK_SIZE,
			.min_keysize = DES_KEY_SIZE,
			.max_keysize = DES_KEY_SIZE,
			.ivsize = 0,
			},
	 .cipher_info = {
			 .alg = CIPHER_ALG_DES,
			 .mode = CIPHER_MODE_ECB,
			 },
	 .auth_info = {
		       .alg = HASH_ALG_NONE,
		       .mode = HASH_MODE_NONE,
		       },
	 },
	{
	 .type = CRYPTO_ALG_TYPE_SKCIPHER,
	 .alg.skcipher = {
			.base.cra_name = "ofb(des3_ede)",
			.base.cra_driver_name = "ofb-des3-iproc",
			.base.cra_blocksize = DES3_EDE_BLOCK_SIZE,
			.min_keysize = DES3_EDE_KEY_SIZE,
			.max_keysize = DES3_EDE_KEY_SIZE,
			.ivsize = DES3_EDE_BLOCK_SIZE,
			},
	 .cipher_info = {
			 .alg = CIPHER_ALG_3DES,
			 .mode = CIPHER_MODE_OFB,
			 },
	 .auth_info = {
		       .alg = HASH_ALG_NONE,
		       .mode = HASH_MODE_NONE,
		       },
	 },
	{
	 .type = CRYPTO_ALG_TYPE_SKCIPHER,
	 .alg.skcipher = {
			.base.cra_name = "cbc(des3_ede)",
			.base.cra_driver_name = "cbc-des3-iproc",
			.base.cra_blocksize = DES3_EDE_BLOCK_SIZE,
			.min_keysize = DES3_EDE_KEY_SIZE,
			.max_keysize = DES3_EDE_KEY_SIZE,
			.ivsize = DES3_EDE_BLOCK_SIZE,
			},
	 .cipher_info = {
			 .alg = CIPHER_ALG_3DES,
			 .mode = CIPHER_MODE_CBC,
			 },
	 .auth_info = {
		       .alg = HASH_ALG_NONE,
		       .mode = HASH_MODE_NONE,
		       },
	 },
	{
	 .type = CRYPTO_ALG_TYPE_SKCIPHER,
	 .alg.skcipher = {
			.base.cra_name = "ecb(des3_ede)",
			.base.cra_driver_name = "ecb-des3-iproc",
			.base.cra_blocksize = DES3_EDE_BLOCK_SIZE,
			.min_keysize = DES3_EDE_KEY_SIZE,
			.max_keysize = DES3_EDE_KEY_SIZE,
			.ivsize = 0,
			},
	 .cipher_info = {
			 .alg = CIPHER_ALG_3DES,
			 .mode = CIPHER_MODE_ECB,
			 },
	 .auth_info = {
		       .alg = HASH_ALG_NONE,
		       .mode = HASH_MODE_NONE,
		       },
	 },
	{
	 .type = CRYPTO_ALG_TYPE_SKCIPHER,
	 .alg.skcipher = {
			.base.cra_name = "ofb(aes)",
			.base.cra_driver_name = "ofb-aes-iproc",
			.base.cra_blocksize = AES_BLOCK_SIZE,
			.min_keysize = AES_MIN_KEY_SIZE,
			.max_keysize = AES_MAX_KEY_SIZE,
			.ivsize = AES_BLOCK_SIZE,
			},
	 .cipher_info = {
			 .alg = CIPHER_ALG_AES,
			 .mode = CIPHER_MODE_OFB,
			 },
	 .auth_info = {
		       .alg = HASH_ALG_NONE,
		       .mode = HASH_MODE_NONE,
		       },
	 },
	{
	 .type = CRYPTO_ALG_TYPE_SKCIPHER,
	 .alg.skcipher = {
			.base.cra_name = "cbc(aes)",
			.base.cra_driver_name = "cbc-aes-iproc",
			.base.cra_blocksize = AES_BLOCK_SIZE,
			.min_keysize = AES_MIN_KEY_SIZE,
			.max_keysize = AES_MAX_KEY_SIZE,
			.ivsize = AES_BLOCK_SIZE,
			},
	 .cipher_info = {
			 .alg = CIPHER_ALG_AES,
			 .mode = CIPHER_MODE_CBC,
			 },
	 .auth_info = {
		       .alg = HASH_ALG_NONE,
		       .mode = HASH_MODE_NONE,
		       },
	 },
	{
	 .type = CRYPTO_ALG_TYPE_SKCIPHER,
	 .alg.skcipher = {
			.base.cra_name = "ecb(aes)",
			.base.cra_driver_name = "ecb-aes-iproc",
			.base.cra_blocksize = AES_BLOCK_SIZE,
			.min_keysize = AES_MIN_KEY_SIZE,
			.max_keysize = AES_MAX_KEY_SIZE,
			.ivsize = 0,
			},
	 .cipher_info = {
			 .alg = CIPHER_ALG_AES,
			 .mode = CIPHER_MODE_ECB,
			 },
	 .auth_info = {
		       .alg = HASH_ALG_NONE,
		       .mode = HASH_MODE_NONE,
		       },
	 },
	{
	 .type = CRYPTO_ALG_TYPE_SKCIPHER,
	 .alg.skcipher = {
			.base.cra_name = "ctr(aes)",
			.base.cra_driver_name = "ctr-aes-iproc",
			.base.cra_blocksize = AES_BLOCK_SIZE,
			.min_keysize = AES_MIN_KEY_SIZE,
			.max_keysize = AES_MAX_KEY_SIZE,
			.ivsize = AES_BLOCK_SIZE,
			},
	 .cipher_info = {
			 .alg = CIPHER_ALG_AES,
			 .mode = CIPHER_MODE_CTR,
			 },
	 .auth_info = {
		       .alg = HASH_ALG_NONE,
		       .mode = HASH_MODE_NONE,
		       },
	 },
{
	 .type = CRYPTO_ALG_TYPE_SKCIPHER,
	 .alg.skcipher = {
			.base.cra_name = "xts(aes)",
			.base.cra_driver_name = "xts-aes-iproc",
			.base.cra_blocksize = AES_BLOCK_SIZE,
			.min_keysize = 2 * AES_MIN_KEY_SIZE,
			.max_keysize = 2 * AES_MAX_KEY_SIZE,
			.ivsize = AES_BLOCK_SIZE,
			},
	 .cipher_info = {
			 .alg = CIPHER_ALG_AES,
			 .mode = CIPHER_MODE_XTS,
			 },
	 .auth_info = {
		       .alg = HASH_ALG_NONE,
		       .mode = HASH_MODE_NONE,
		       },
	 },

/* AHASH algorithms. */
	{
	 .type = CRYPTO_ALG_TYPE_AHASH,
	 .alg.hash = {
		      .halg.digestsize = MD5_DIGEST_SIZE,
		      .halg.base = {
				    .cra_name = "md5",
				    .cra_driver_name = "md5-iproc",
				    .cra_blocksize = MD5_BLOCK_WORDS * 4,
				    .cra_flags = CRYPTO_ALG_ASYNC,
				}
		      },
	 .cipher_info = {
			 .alg = CIPHER_ALG_NONE,
			 .mode = CIPHER_MODE_NONE,
			 },
	 .auth_info = {
		       .alg = HASH_ALG_MD5,
		       .mode = HASH_MODE_HASH,
		       },
	 },
	{
	 .type = CRYPTO_ALG_TYPE_AHASH,
	 .alg.hash = {
		      .halg.digestsize = MD5_DIGEST_SIZE,
		      .halg.base = {
				    .cra_name = "hmac(md5)",
				    .cra_driver_name = "hmac-md5-iproc",
				    .cra_blocksize = MD5_BLOCK_WORDS * 4,
				}
		      },
	 .cipher_info = {
			 .alg = CIPHER_ALG_NONE,
			 .mode = CIPHER_MODE_NONE,
			 },
	 .auth_info = {
		       .alg = HASH_ALG_MD5,
		       .mode = HASH_MODE_HMAC,
		       },
	 },
	{.type = CRYPTO_ALG_TYPE_AHASH,
	 .alg.hash = {
		      .halg.digestsize = SHA1_DIGEST_SIZE,
		      .halg.base = {
				    .cra_name = "sha1",
				    .cra_driver_name = "sha1-iproc",
				    .cra_blocksize = SHA1_BLOCK_SIZE,
				}
		      },
	 .cipher_info = {
			 .alg = CIPHER_ALG_NONE,
			 .mode = CIPHER_MODE_NONE,
			 },
	 .auth_info = {
		       .alg = HASH_ALG_SHA1,
		       .mode = HASH_MODE_HASH,
		       },
	 },
	{.type = CRYPTO_ALG_TYPE_AHASH,
	 .alg.hash = {
		      .halg.digestsize = SHA1_DIGEST_SIZE,
		      .halg.base = {
				    .cra_name = "hmac(sha1)",
				    .cra_driver_name = "hmac-sha1-iproc",
				    .cra_blocksize = SHA1_BLOCK_SIZE,
				}
		      },
	 .cipher_info = {
			 .alg = CIPHER_ALG_NONE,
			 .mode = CIPHER_MODE_NONE,
			 },
	 .auth_info = {
		       .alg = HASH_ALG_SHA1,
		       .mode = HASH_MODE_HMAC,
		       },
	 },
	{.type = CRYPTO_ALG_TYPE_AHASH,
	 .alg.hash = {
			.halg.digestsize = SHA224_DIGEST_SIZE,
			.halg.base = {
				    .cra_name = "sha224",
				    .cra_driver_name = "sha224-iproc",
				    .cra_blocksize = SHA224_BLOCK_SIZE,
			}
		      },
	 .cipher_info = {
			 .alg = CIPHER_ALG_NONE,
			 .mode = CIPHER_MODE_NONE,
			 },
	 .auth_info = {
		       .alg = HASH_ALG_SHA224,
		       .mode = HASH_MODE_HASH,
		       },
	 },
	{.type = CRYPTO_ALG_TYPE_AHASH,
	 .alg.hash = {
		      .halg.digestsize = SHA224_DIGEST_SIZE,
		      .halg.base = {
				    .cra_name = "hmac(sha224)",
				    .cra_driver_name = "hmac-sha224-iproc",
				    .cra_blocksize = SHA224_BLOCK_SIZE,
				}
		      },
	 .cipher_info = {
			 .alg = CIPHER_ALG_NONE,
			 .mode = CIPHER_MODE_NONE,
			 },
	 .auth_info = {
		       .alg = HASH_ALG_SHA224,
		       .mode = HASH_MODE_HMAC,
		       },
	 },
	{.type = CRYPTO_ALG_TYPE_AHASH,
	 .alg.hash = {
		      .halg.digestsize = SHA256_DIGEST_SIZE,
		      .halg.base = {
				    .cra_name = "sha256",
				    .cra_driver_name = "sha256-iproc",
				    .cra_blocksize = SHA256_BLOCK_SIZE,
				}
		      },
	 .cipher_info = {
			 .alg = CIPHER_ALG_NONE,
			 .mode = CIPHER_MODE_NONE,
			 },
	 .auth_info = {
		       .alg = HASH_ALG_SHA256,
		       .mode = HASH_MODE_HASH,
		       },
	 },
	{.type = CRYPTO_ALG_TYPE_AHASH,
	 .alg.hash = {
		      .halg.digestsize = SHA256_DIGEST_SIZE,
		      .halg.base = {
				    .cra_name = "hmac(sha256)",
				    .cra_driver_name = "hmac-sha256-iproc",
				    .cra_blocksize = SHA256_BLOCK_SIZE,
				}
		      },
	 .cipher_info = {
			 .alg = CIPHER_ALG_NONE,
			 .mode = CIPHER_MODE_NONE,
			 },
	 .auth_info = {
		       .alg = HASH_ALG_SHA256,
		       .mode = HASH_MODE_HMAC,
		       },
	 },
	{
	.type = CRYPTO_ALG_TYPE_AHASH,
	 .alg.hash = {
		      .halg.digestsize = SHA384_DIGEST_SIZE,
		      .halg.base = {
				    .cra_name = "sha384",
				    .cra_driver_name = "sha384-iproc",
				    .cra_blocksize = SHA384_BLOCK_SIZE,
				}
		      },
	 .cipher_info = {
			 .alg = CIPHER_ALG_NONE,
			 .mode = CIPHER_MODE_NONE,
			 },
	 .auth_info = {
		       .alg = HASH_ALG_SHA384,
		       .mode = HASH_MODE_HASH,
		       },
	 },
	{
	 .type = CRYPTO_ALG_TYPE_AHASH,
	 .alg.hash = {
		      .halg.digestsize = SHA384_DIGEST_SIZE,
		      .halg.base = {
				    .cra_name = "hmac(sha384)",
				    .cra_driver_name = "hmac-sha384-iproc",
				    .cra_blocksize = SHA384_BLOCK_SIZE,
				}
		      },
	 .cipher_info = {
			 .alg = CIPHER_ALG_NONE,
			 .mode = CIPHER_MODE_NONE,
			 },
	 .auth_info = {
		       .alg = HASH_ALG_SHA384,
		       .mode = HASH_MODE_HMAC,
		       },
	 },
	{
	 .type = CRYPTO_ALG_TYPE_AHASH,
	 .alg.hash = {
		      .halg.digestsize = SHA512_DIGEST_SIZE,
		      .halg.base = {
				    .cra_name = "sha512",
				    .cra_driver_name = "sha512-iproc",
				    .cra_blocksize = SHA512_BLOCK_SIZE,
				}
		      },
	 .cipher_info = {
			 .alg = CIPHER_ALG_NONE,
			 .mode = CIPHER_MODE_NONE,
			 },
	 .auth_info = {
		       .alg = HASH_ALG_SHA512,
		       .mode = HASH_MODE_HASH,
		       },
	 },
	{
	 .type = CRYPTO_ALG_TYPE_AHASH,
	 .alg.hash = {
		      .halg.digestsize = SHA512_DIGEST_SIZE,
		      .halg.base = {
				    .cra_name = "hmac(sha512)",
				    .cra_driver_name = "hmac-sha512-iproc",
				    .cra_blocksize = SHA512_BLOCK_SIZE,
				}
		      },
	 .cipher_info = {
			 .alg = CIPHER_ALG_NONE,
			 .mode = CIPHER_MODE_NONE,
			 },
	 .auth_info = {
		       .alg = HASH_ALG_SHA512,
		       .mode = HASH_MODE_HMAC,
		       },
	 },
	{
	 .type = CRYPTO_ALG_TYPE_AHASH,
	 .alg.hash = {
		      .halg.digestsize = SHA3_224_DIGEST_SIZE,
		      .halg.base = {
				    .cra_name = "sha3-224",
				    .cra_driver_name = "sha3-224-iproc",
				    .cra_blocksize = SHA3_224_BLOCK_SIZE,
				}
		      },
	 .cipher_info = {
			 .alg = CIPHER_ALG_NONE,
			 .mode = CIPHER_MODE_NONE,
			 },
	 .auth_info = {
		       .alg = HASH_ALG_SHA3_224,
		       .mode = HASH_MODE_HASH,
		       },
	 },
	{
	 .type = CRYPTO_ALG_TYPE_AHASH,
	 .alg.hash = {
		      .halg.digestsize = SHA3_224_DIGEST_SIZE,
		      .halg.base = {
				    .cra_name = "hmac(sha3-224)",
				    .cra_driver_name = "hmac-sha3-224-iproc",
				    .cra_blocksize = SHA3_224_BLOCK_SIZE,
				}
		      },
	 .cipher_info = {
			 .alg = CIPHER_ALG_NONE,
			 .mode = CIPHER_MODE_NONE,
			 },
	 .auth_info = {
		       .alg = HASH_ALG_SHA3_224,
		       .mode = HASH_MODE_HMAC
		       },
	 },
	{
	 .type = CRYPTO_ALG_TYPE_AHASH,
	 .alg.hash = {
		      .halg.digestsize = SHA3_256_DIGEST_SIZE,
		      .halg.base = {
				    .cra_name = "sha3-256",
				    .cra_driver_name = "sha3-256-iproc",
				    .cra_blocksize = SHA3_256_BLOCK_SIZE,
				}
		      },
	 .cipher_info = {
			 .alg = CIPHER_ALG_NONE,
			 .mode = CIPHER_MODE_NONE,
			 },
	 .auth_info = {
		       .alg = HASH_ALG_SHA3_256,
		       .mode = HASH_MODE_HASH,
		       },
	 },
	{
	 .type = CRYPTO_ALG_TYPE_AHASH,
	 .alg.hash = {
		      .halg.digestsize = SHA3_256_DIGEST_SIZE,
		      .halg.base = {
				    .cra_name = "hmac(sha3-256)",
				    .cra_driver_name = "hmac-sha3-256-iproc",
				    .cra_blocksize = SHA3_256_BLOCK_SIZE,
				}
		      },
	 .cipher_info = {
			 .alg = CIPHER_ALG_NONE,
			 .mode = CIPHER_MODE_NONE,
			 },
	 .auth_info = {
		       .alg = HASH_ALG_SHA3_256,
		       .mode = HASH_MODE_HMAC,
		       },
	 },
	{
	 .type = CRYPTO_ALG_TYPE_AHASH,
	 .alg.hash = {
		      .halg.digestsize = SHA3_384_DIGEST_SIZE,
		      .halg.base = {
				    .cra_name = "sha3-384",
				    .cra_driver_name = "sha3-384-iproc",
				    .cra_blocksize = SHA3_224_BLOCK_SIZE,
				}
		      },
	 .cipher_info = {
			 .alg = CIPHER_ALG_NONE,
			 .mode = CIPHER_MODE_NONE,
			 },
	 .auth_info = {
		       .alg = HASH_ALG_SHA3_384,
		       .mode = HASH_MODE_HASH,
		       },
	 },
	{
	 .type = CRYPTO_ALG_TYPE_AHASH,
	 .alg.hash = {
		      .halg.digestsize = SHA3_384_DIGEST_SIZE,
		      .halg.base = {
				    .cra_name = "hmac(sha3-384)",
				    .cra_driver_name = "hmac-sha3-384-iproc",
				    .cra_blocksize = SHA3_384_BLOCK_SIZE,
				}
		      },
	 .cipher_info = {
			 .alg = CIPHER_ALG_NONE,
			 .mode = CIPHER_MODE_NONE,
			 },
	 .auth_info = {
		       .alg = HASH_ALG_SHA3_384,
		       .mode = HASH_MODE_HMAC,
		       },
	 },
	{
	 .type = CRYPTO_ALG_TYPE_AHASH,
	 .alg.hash = {
		      .halg.digestsize = SHA3_512_DIGEST_SIZE,
		      .halg.base = {
				    .cra_name = "sha3-512",
				    .cra_driver_name = "sha3-512-iproc",
				    .cra_blocksize = SHA3_512_BLOCK_SIZE,
				}
		      },
	 .cipher_info = {
			 .alg = CIPHER_ALG_NONE,
			 .mode = CIPHER_MODE_NONE,
			 },
	 .auth_info = {
		       .alg = HASH_ALG_SHA3_512,
		       .mode = HASH_MODE_HASH,
		       },
	 },
	{
	 .type = CRYPTO_ALG_TYPE_AHASH,
	 .alg.hash = {
		      .halg.digestsize = SHA3_512_DIGEST_SIZE,
		      .halg.base = {
				    .cra_name = "hmac(sha3-512)",
				    .cra_driver_name = "hmac-sha3-512-iproc",
				    .cra_blocksize = SHA3_512_BLOCK_SIZE,
				}
		      },
	 .cipher_info = {
			 .alg = CIPHER_ALG_NONE,
			 .mode = CIPHER_MODE_NONE,
			 },
	 .auth_info = {
		       .alg = HASH_ALG_SHA3_512,
		       .mode = HASH_MODE_HMAC,
		       },
	 },
	{
	 .type = CRYPTO_ALG_TYPE_AHASH,
	 .alg.hash = {
		      .halg.digestsize = AES_BLOCK_SIZE,
		      .halg.base = {
				    .cra_name = "xcbc(aes)",
				    .cra_driver_name = "xcbc-aes-iproc",
				    .cra_blocksize = AES_BLOCK_SIZE,
				}
		      },
	 .cipher_info = {
			 .alg = CIPHER_ALG_NONE,
			 .mode = CIPHER_MODE_NONE,
			 },
	 .auth_info = {
		       .alg = HASH_ALG_AES,
		       .mode = HASH_MODE_XCBC,
		       },
	 },
	{
	 .type = CRYPTO_ALG_TYPE_AHASH,
	 .alg.hash = {
		      .halg.digestsize = AES_BLOCK_SIZE,
		      .halg.base = {
				    .cra_name = "cmac(aes)",
				    .cra_driver_name = "cmac-aes-iproc",
				    .cra_blocksize = AES_BLOCK_SIZE,
				}
		      },
	 .cipher_info = {
			 .alg = CIPHER_ALG_NONE,
			 .mode = CIPHER_MODE_NONE,
			 },
	 .auth_info = {
		       .alg = HASH_ALG_AES,
		       .mode = HASH_MODE_CMAC,
		       },
	 },
};

static int generic_cra_init(struct crypto_tfm *tfm,
			    struct iproc_alg_s *cipher_alg)
{
	struct spu_hw *spu = &iproc_priv.spu;
	struct iproc_ctx_s *ctx = crypto_tfm_ctx(tfm);
	unsigned int blocksize = crypto_tfm_alg_blocksize(tfm);

	flow_log("%s()\n", __func__);

	ctx->alg = cipher_alg;
	ctx->cipher = cipher_alg->cipher_info;
	ctx->auth = cipher_alg->auth_info;
	ctx->auth_first = cipher_alg->auth_first;
	ctx->max_payload = spu->spu_ctx_max_payload(ctx->cipher.alg,
						    ctx->cipher.mode,
						    blocksize);
	ctx->fallback_cipher = NULL;

	ctx->enckeylen = 0;
	ctx->authkeylen = 0;

	atomic_inc(&iproc_priv.stream_count);
	atomic_inc(&iproc_priv.session_count);

	return 0;
}

static int skcipher_init_tfm(struct crypto_skcipher *skcipher)
{
	struct crypto_tfm *tfm = crypto_skcipher_tfm(skcipher);
	struct skcipher_alg *alg = crypto_skcipher_alg(skcipher);
	struct iproc_alg_s *cipher_alg;

	flow_log("%s()\n", __func__);

	crypto_skcipher_set_reqsize(skcipher, sizeof(struct iproc_reqctx_s));

	cipher_alg = container_of(alg, struct iproc_alg_s, alg.skcipher);
	return generic_cra_init(tfm, cipher_alg);
}

static int ahash_cra_init(struct crypto_tfm *tfm)
{
	int err;
	struct crypto_alg *alg = tfm->__crt_alg;
	struct iproc_alg_s *cipher_alg;

	cipher_alg = container_of(__crypto_ahash_alg(alg), struct iproc_alg_s,
				  alg.hash);

	err = generic_cra_init(tfm, cipher_alg);
	flow_log("%s()\n", __func__);

	/*
	 * export state size has to be < 512 bytes. So don't include msg bufs
	 * in state size.
	 */
	crypto_ahash_set_reqsize(__crypto_ahash_cast(tfm),
				 sizeof(struct iproc_reqctx_s));

	return err;
}

static int aead_cra_init(struct crypto_aead *aead)
{
	struct crypto_tfm *tfm = crypto_aead_tfm(aead);
	struct iproc_ctx_s *ctx = crypto_tfm_ctx(tfm);
	struct crypto_alg *alg = tfm->__crt_alg;
	struct aead_alg *aalg = container_of(alg, struct aead_alg, base);
	struct iproc_alg_s *cipher_alg = container_of(aalg, struct iproc_alg_s,
						      alg.aead);

	int err = generic_cra_init(tfm, cipher_alg);

	flow_log("%s()\n", __func__);

	crypto_aead_set_reqsize(aead, sizeof(struct iproc_reqctx_s));
	ctx->is_esp = false;
	ctx->salt_len = 0;
	ctx->salt_offset = 0;

	/* random first IV */
	get_random_bytes(ctx->iv, MAX_IV_SIZE);
	flow_dump("  iv: ", ctx->iv, MAX_IV_SIZE);

	if (!err) {
		if (alg->cra_flags & CRYPTO_ALG_NEED_FALLBACK) {
			flow_log("%s() creating fallback cipher\n", __func__);

			ctx->fallback_cipher =
			    crypto_alloc_aead(alg->cra_name, 0,
					      CRYPTO_ALG_ASYNC |
					      CRYPTO_ALG_NEED_FALLBACK);
			if (IS_ERR(ctx->fallback_cipher)) {
				pr_err("%s() Error: failed to allocate fallback for %s\n",
				       __func__, alg->cra_name);
				return PTR_ERR(ctx->fallback_cipher);
			}
		}
	}

	return err;
}

static void generic_cra_exit(struct crypto_tfm *tfm)
{
	atomic_dec(&iproc_priv.session_count);
}

static void skcipher_exit_tfm(struct crypto_skcipher *tfm)
{
	generic_cra_exit(crypto_skcipher_tfm(tfm));
}

static void aead_cra_exit(struct crypto_aead *aead)
{
	struct crypto_tfm *tfm = crypto_aead_tfm(aead);
	struct iproc_ctx_s *ctx = crypto_tfm_ctx(tfm);

	generic_cra_exit(tfm);

	if (ctx->fallback_cipher) {
		crypto_free_aead(ctx->fallback_cipher);
		ctx->fallback_cipher = NULL;
	}
}

/**
 * spu_functions_register() - Specify hardware-specific SPU functions based on
 * SPU type read from device tree.
 * @dev:	device structure
 * @spu_type:	SPU hardware generation
 * @spu_subtype: SPU hardware version
 */
static void spu_functions_register(struct device *dev,
				   enum spu_spu_type spu_type,
				   enum spu_spu_subtype spu_subtype)
{
	struct spu_hw *spu = &iproc_priv.spu;

	if (spu_type == SPU_TYPE_SPUM) {
		dev_dbg(dev, "Registering SPUM functions");
		spu->spu_dump_msg_hdr = spum_dump_msg_hdr;
		spu->spu_payload_length = spum_payload_length;
		spu->spu_response_hdr_len = spum_response_hdr_len;
		spu->spu_hash_pad_len = spum_hash_pad_len;
		spu->spu_gcm_ccm_pad_len = spum_gcm_ccm_pad_len;
		spu->spu_assoc_resp_len = spum_assoc_resp_len;
		spu->spu_aead_ivlen = spum_aead_ivlen;
		spu->spu_hash_type = spum_hash_type;
		spu->spu_digest_size = spum_digest_size;
		spu->spu_create_request = spum_create_request;
		spu->spu_cipher_req_init = spum_cipher_req_init;
		spu->spu_cipher_req_finish = spum_cipher_req_finish;
		spu->spu_request_pad = spum_request_pad;
		spu->spu_tx_status_len = spum_tx_status_len;
		spu->spu_rx_status_len = spum_rx_status_len;
		spu->spu_status_process = spum_status_process;
		spu->spu_xts_tweak_in_payload = spum_xts_tweak_in_payload;
		spu->spu_ccm_update_iv = spum_ccm_update_iv;
		spu->spu_wordalign_padlen = spum_wordalign_padlen;
		if (spu_subtype == SPU_SUBTYPE_SPUM_NS2)
			spu->spu_ctx_max_payload = spum_ns2_ctx_max_payload;
		else
			spu->spu_ctx_max_payload = spum_nsp_ctx_max_payload;
	} else {
		dev_dbg(dev, "Registering SPU2 functions");
		spu->spu_dump_msg_hdr = spu2_dump_msg_hdr;
		spu->spu_ctx_max_payload = spu2_ctx_max_payload;
		spu->spu_payload_length = spu2_payload_length;
		spu->spu_response_hdr_len = spu2_response_hdr_len;
		spu->spu_hash_pad_len = spu2_hash_pad_len;
		spu->spu_gcm_ccm_pad_len = spu2_gcm_ccm_pad_len;
		spu->spu_assoc_resp_len = spu2_assoc_resp_len;
		spu->spu_aead_ivlen = spu2_aead_ivlen;
		spu->spu_hash_type = spu2_hash_type;
		spu->spu_digest_size = spu2_digest_size;
		spu->spu_create_request = spu2_create_request;
		spu->spu_cipher_req_init = spu2_cipher_req_init;
		spu->spu_cipher_req_finish = spu2_cipher_req_finish;
		spu->spu_request_pad = spu2_request_pad;
		spu->spu_tx_status_len = spu2_tx_status_len;
		spu->spu_rx_status_len = spu2_rx_status_len;
		spu->spu_status_process = spu2_status_process;
		spu->spu_xts_tweak_in_payload = spu2_xts_tweak_in_payload;
		spu->spu_ccm_update_iv = spu2_ccm_update_iv;
		spu->spu_wordalign_padlen = spu2_wordalign_padlen;
	}
}

/**
 * spu_mb_init() - Initialize mailbox client. Request ownership of a mailbox
 * channel for the SPU being probed.
 * @dev:  SPU driver device structure
 *
 * Return: 0 if successful
 *	   < 0 otherwise
 */
static int spu_mb_init(struct device *dev)
{
	struct mbox_client *mcl = &iproc_priv.mcl;
	int err, i;

	iproc_priv.mbox = devm_kcalloc(dev, iproc_priv.spu.num_chan,
				  sizeof(struct mbox_chan *), GFP_KERNEL);
	if (!iproc_priv.mbox)
		return -ENOMEM;

	mcl->dev = dev;
	mcl->tx_block = false;
	mcl->tx_tout = 0;
	mcl->knows_txdone = true;
	mcl->rx_callback = spu_rx_callback;
	mcl->tx_done = NULL;

	for (i = 0; i < iproc_priv.spu.num_chan; i++) {
		iproc_priv.mbox[i] = mbox_request_channel(mcl, i);
		if (IS_ERR(iproc_priv.mbox[i])) {
			err = PTR_ERR(iproc_priv.mbox[i]);
			dev_err(dev,
				"Mbox channel %d request failed with err %d",
				i, err);
			iproc_priv.mbox[i] = NULL;
			goto free_channels;
		}
	}

	return 0;
free_channels:
	for (i = 0; i < iproc_priv.spu.num_chan; i++) {
		if (iproc_priv.mbox[i])
			mbox_free_channel(iproc_priv.mbox[i]);
	}

	return err;
}

static void spu_mb_release(struct platform_device *pdev)
{
	int i;

	for (i = 0; i < iproc_priv.spu.num_chan; i++)
		mbox_free_channel(iproc_priv.mbox[i]);
}

static void spu_counters_init(void)
{
	int i;
	int j;

	atomic_set(&iproc_priv.session_count, 0);
	atomic_set(&iproc_priv.stream_count, 0);
	atomic_set(&iproc_priv.next_chan, (int)iproc_priv.spu.num_chan);
	atomic64_set(&iproc_priv.bytes_in, 0);
	atomic64_set(&iproc_priv.bytes_out, 0);
	for (i = 0; i < SPU_OP_NUM; i++) {
		atomic_set(&iproc_priv.op_counts[i], 0);
		atomic_set(&iproc_priv.setkey_cnt[i], 0);
	}
	for (i = 0; i < CIPHER_ALG_LAST; i++)
		for (j = 0; j < CIPHER_MODE_LAST; j++)
			atomic_set(&iproc_priv.cipher_cnt[i][j], 0);

	for (i = 0; i < HASH_ALG_LAST; i++) {
		atomic_set(&iproc_priv.hash_cnt[i], 0);
		atomic_set(&iproc_priv.hmac_cnt[i], 0);
	}
	for (i = 0; i < AEAD_TYPE_LAST; i++)
		atomic_set(&iproc_priv.aead_cnt[i], 0);

	atomic_set(&iproc_priv.mb_no_spc, 0);
	atomic_set(&iproc_priv.mb_send_fail, 0);
	atomic_set(&iproc_priv.bad_icv, 0);
}

static int spu_register_skcipher(struct iproc_alg_s *driver_alg)
{
	struct spu_hw *spu = &iproc_priv.spu;
	struct skcipher_alg *crypto = &driver_alg->alg.skcipher;
	int err;

	/* SPU2 does not support RC4 */
	if ((driver_alg->cipher_info.alg == CIPHER_ALG_RC4) &&
	    (spu->spu_type == SPU_TYPE_SPU2))
		return 0;

	crypto->base.cra_module = THIS_MODULE;
	crypto->base.cra_priority = cipher_pri;
	crypto->base.cra_alignmask = 0;
	crypto->base.cra_ctxsize = sizeof(struct iproc_ctx_s);
	crypto->base.cra_flags = CRYPTO_ALG_ASYNC | CRYPTO_ALG_KERN_DRIVER_ONLY;

	crypto->init = skcipher_init_tfm;
	crypto->exit = skcipher_exit_tfm;
	crypto->setkey = skcipher_setkey;
	crypto->encrypt = skcipher_encrypt;
	crypto->decrypt = skcipher_decrypt;

	err = crypto_register_skcipher(crypto);
	/* Mark alg as having been registered, if successful */
	if (err == 0)
		driver_alg->registered = true;
	pr_debug("  registered skcipher %s\n", crypto->base.cra_driver_name);
	return err;
}

static int spu_register_ahash(struct iproc_alg_s *driver_alg)
{
	struct spu_hw *spu = &iproc_priv.spu;
	struct ahash_alg *hash = &driver_alg->alg.hash;
	int err;

	/* AES-XCBC is the only AES hash type currently supported on SPU-M */
	if ((driver_alg->auth_info.alg == HASH_ALG_AES) &&
	    (driver_alg->auth_info.mode != HASH_MODE_XCBC) &&
	    (spu->spu_type == SPU_TYPE_SPUM))
		return 0;

	/* SHA3 algorithm variants are not registered for SPU-M or SPU2. */
	if ((driver_alg->auth_info.alg >= HASH_ALG_SHA3_224) &&
	    (spu->spu_subtype != SPU_SUBTYPE_SPU2_V2))
		return 0;

	hash->halg.base.cra_module = THIS_MODULE;
	hash->halg.base.cra_priority = hash_pri;
	hash->halg.base.cra_alignmask = 0;
	hash->halg.base.cra_ctxsize = sizeof(struct iproc_ctx_s);
	hash->halg.base.cra_init = ahash_cra_init;
	hash->halg.base.cra_exit = generic_cra_exit;
	hash->halg.base.cra_flags = CRYPTO_ALG_ASYNC;
	hash->halg.statesize = sizeof(struct spu_hash_export_s);

	if (driver_alg->auth_info.mode != HASH_MODE_HMAC) {
		hash->init = ahash_init;
		hash->update = ahash_update;
		hash->final = ahash_final;
		hash->finup = ahash_finup;
		hash->digest = ahash_digest;
		if ((driver_alg->auth_info.alg == HASH_ALG_AES) &&
		    ((driver_alg->auth_info.mode == HASH_MODE_XCBC) ||
		    (driver_alg->auth_info.mode == HASH_MODE_CMAC))) {
			hash->setkey = ahash_setkey;
		}
	} else {
		hash->setkey = ahash_hmac_setkey;
		hash->init = ahash_hmac_init;
		hash->update = ahash_hmac_update;
		hash->final = ahash_hmac_final;
		hash->finup = ahash_hmac_finup;
		hash->digest = ahash_hmac_digest;
	}
	hash->export = ahash_export;
	hash->import = ahash_import;

	err = crypto_register_ahash(hash);
	/* Mark alg as having been registered, if successful */
	if (err == 0)
		driver_alg->registered = true;
	pr_debug("  registered ahash %s\n",
		 hash->halg.base.cra_driver_name);
	return err;
}

static int spu_register_aead(struct iproc_alg_s *driver_alg)
{
	struct aead_alg *aead = &driver_alg->alg.aead;
	int err;

	aead->base.cra_module = THIS_MODULE;
	aead->base.cra_priority = aead_pri;
	aead->base.cra_alignmask = 0;
	aead->base.cra_ctxsize = sizeof(struct iproc_ctx_s);

	aead->base.cra_flags |= CRYPTO_ALG_ASYNC;
	/* setkey set in alg initialization */
	aead->setauthsize = aead_setauthsize;
	aead->encrypt = aead_encrypt;
	aead->decrypt = aead_decrypt;
	aead->init = aead_cra_init;
	aead->exit = aead_cra_exit;

	err = crypto_register_aead(aead);
	/* Mark alg as having been registered, if successful */
	if (err == 0)
		driver_alg->registered = true;
	pr_debug("  registered aead %s\n", aead->base.cra_driver_name);
	return err;
}

/* register crypto algorithms the device supports */
static int spu_algs_register(struct device *dev)
{
	int i, j;
	int err;

	for (i = 0; i < ARRAY_SIZE(driver_algs); i++) {
		switch (driver_algs[i].type) {
		case CRYPTO_ALG_TYPE_SKCIPHER:
			err = spu_register_skcipher(&driver_algs[i]);
			break;
		case CRYPTO_ALG_TYPE_AHASH:
			err = spu_register_ahash(&driver_algs[i]);
			break;
		case CRYPTO_ALG_TYPE_AEAD:
			err = spu_register_aead(&driver_algs[i]);
			break;
		default:
			dev_err(dev,
				"iproc-crypto: unknown alg type: %d",
				driver_algs[i].type);
			err = -EINVAL;
		}

		if (err) {
			dev_err(dev, "alg registration failed with error %d\n",
				err);
			goto err_algs;
		}
	}

	return 0;

err_algs:
	for (j = 0; j < i; j++) {
		/* Skip any algorithm not registered */
		if (!driver_algs[j].registered)
			continue;
		switch (driver_algs[j].type) {
		case CRYPTO_ALG_TYPE_SKCIPHER:
			crypto_unregister_skcipher(&driver_algs[j].alg.skcipher);
			driver_algs[j].registered = false;
			break;
		case CRYPTO_ALG_TYPE_AHASH:
			crypto_unregister_ahash(&driver_algs[j].alg.hash);
			driver_algs[j].registered = false;
			break;
		case CRYPTO_ALG_TYPE_AEAD:
			crypto_unregister_aead(&driver_algs[j].alg.aead);
			driver_algs[j].registered = false;
			break;
		}
	}
	return err;
}

/* ==================== Kernel Platform API ==================== */

static struct spu_type_subtype spum_ns2_types = {
	SPU_TYPE_SPUM, SPU_SUBTYPE_SPUM_NS2
};

static struct spu_type_subtype spum_nsp_types = {
	SPU_TYPE_SPUM, SPU_SUBTYPE_SPUM_NSP
};

static struct spu_type_subtype spu2_types = {
	SPU_TYPE_SPU2, SPU_SUBTYPE_SPU2_V1
};

static struct spu_type_subtype spu2_v2_types = {
	SPU_TYPE_SPU2, SPU_SUBTYPE_SPU2_V2
};

static const struct of_device_id bcm_spu_dt_ids[] = {
	{
		.compatible = "brcm,spum-crypto",
		.data = &spum_ns2_types,
	},
	{
		.compatible = "brcm,spum-nsp-crypto",
		.data = &spum_nsp_types,
	},
	{
		.compatible = "brcm,spu2-crypto",
		.data = &spu2_types,
	},
	{
		.compatible = "brcm,spu2-v2-crypto",
		.data = &spu2_v2_types,
	},
	{ /* sentinel */ }
};

MODULE_DEVICE_TABLE(of, bcm_spu_dt_ids);

static int spu_dt_read(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;
	struct spu_hw *spu = &iproc_priv.spu;
	struct resource *spu_ctrl_regs;
	const struct spu_type_subtype *matched_spu_type;
	struct device_node *dn = pdev->dev.of_node;
	int err, i;

	/* Count number of mailbox channels */
	spu->num_chan = of_count_phandle_with_args(dn, "mboxes", "#mbox-cells");

	matched_spu_type = of_device_get_match_data(dev);
	if (!matched_spu_type) {
		dev_err(dev, "Failed to match device\n");
		return -ENODEV;
	}

	spu->spu_type = matched_spu_type->type;
	spu->spu_subtype = matched_spu_type->subtype;

	for (i = 0; (i < MAX_SPUS) && ((spu_ctrl_regs =
		platform_get_resource(pdev, IORESOURCE_MEM, i)) != NULL); i++) {

		spu->reg_vbase[i] = devm_ioremap_resource(dev, spu_ctrl_regs);
		if (IS_ERR(spu->reg_vbase[i])) {
			err = PTR_ERR(spu->reg_vbase[i]);
			dev_err(dev, "Failed to map registers: %d\n",
				err);
			spu->reg_vbase[i] = NULL;
			return err;
		}
	}
	spu->num_spu = i;
	dev_dbg(dev, "Device has %d SPUs", spu->num_spu);

	return 0;
}

static int bcm_spu_probe(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;
	struct spu_hw *spu = &iproc_priv.spu;
	int err;

	iproc_priv.pdev  = pdev;
	platform_set_drvdata(iproc_priv.pdev,
			     &iproc_priv);

	err = spu_dt_read(pdev);
	if (err < 0)
		goto failure;

	err = spu_mb_init(dev);
	if (err < 0)
		goto failure;

	if (spu->spu_type == SPU_TYPE_SPUM)
		iproc_priv.bcm_hdr_len = 8;
	else if (spu->spu_type == SPU_TYPE_SPU2)
		iproc_priv.bcm_hdr_len = 0;

	spu_functions_register(dev, spu->spu_type, spu->spu_subtype);

	spu_counters_init();

	spu_setup_debugfs();

	err = spu_algs_register(dev);
	if (err < 0)
		goto fail_reg;

	return 0;

fail_reg:
	spu_free_debugfs();
failure:
	spu_mb_release(pdev);
	dev_err(dev, "%s failed with error %d.\n", __func__, err);

	return err;
}

static int bcm_spu_remove(struct platform_device *pdev)
{
	int i;
	struct device *dev = &pdev->dev;
	char *cdn;

	for (i = 0; i < ARRAY_SIZE(driver_algs); i++) {
		/*
		 * Not all algorithms were registered, depending on whether
		 * hardware is SPU or SPU2.  So here we make sure to skip
		 * those algorithms that were not previously registered.
		 */
		if (!driver_algs[i].registered)
			continue;

		switch (driver_algs[i].type) {
		case CRYPTO_ALG_TYPE_SKCIPHER:
			crypto_unregister_skcipher(&driver_algs[i].alg.skcipher);
			dev_dbg(dev, "  unregistered cipher %s\n",
				driver_algs[i].alg.skcipher.base.cra_driver_name);
			driver_algs[i].registered = false;
			break;
		case CRYPTO_ALG_TYPE_AHASH:
			crypto_unregister_ahash(&driver_algs[i].alg.hash);
			cdn = driver_algs[i].alg.hash.halg.base.cra_driver_name;
			dev_dbg(dev, "  unregistered hash %s\n", cdn);
			driver_algs[i].registered = false;
			break;
		case CRYPTO_ALG_TYPE_AEAD:
			crypto_unregister_aead(&driver_algs[i].alg.aead);
			dev_dbg(dev, "  unregistered aead %s\n",
				driver_algs[i].alg.aead.base.cra_driver_name);
			driver_algs[i].registered = false;
			break;
		}
	}
	spu_free_debugfs();
	spu_mb_release(pdev);
	return 0;
}

/* ===== Kernel Module API ===== */

static struct platform_driver bcm_spu_pdriver = {
	.driver = {
		   .name = "brcm-spu-crypto",
		   .of_match_table = of_match_ptr(bcm_spu_dt_ids),
		   },
	.probe = bcm_spu_probe,
	.remove = bcm_spu_remove,
};
module_platform_driver(bcm_spu_pdriver);

MODULE_AUTHOR("Rob Rice <rob.rice@broadcom.com>");
MODULE_DESCRIPTION("Broadcom symmetric crypto offload driver");
MODULE_LICENSE("GPL v2");