| // SPDX-License-Identifier: GPL-2.0-or-later | 
 | /* | 
 |  * x64 SIMD accelerated ChaCha and XChaCha stream ciphers, | 
 |  * including ChaCha20 (RFC7539) | 
 |  * | 
 |  * Copyright (C) 2015 Martin Willi | 
 |  */ | 
 |  | 
 | #include <crypto/algapi.h> | 
 | #include <crypto/internal/chacha.h> | 
 | #include <crypto/internal/simd.h> | 
 | #include <crypto/internal/skcipher.h> | 
 | #include <linux/kernel.h> | 
 | #include <linux/module.h> | 
 | #include <linux/sizes.h> | 
 | #include <asm/simd.h> | 
 |  | 
 | asmlinkage void chacha_block_xor_ssse3(u32 *state, u8 *dst, const u8 *src, | 
 | 				       unsigned int len, int nrounds); | 
 | asmlinkage void chacha_4block_xor_ssse3(u32 *state, u8 *dst, const u8 *src, | 
 | 					unsigned int len, int nrounds); | 
 | asmlinkage void hchacha_block_ssse3(const u32 *state, u32 *out, int nrounds); | 
 |  | 
 | asmlinkage void chacha_2block_xor_avx2(u32 *state, u8 *dst, const u8 *src, | 
 | 				       unsigned int len, int nrounds); | 
 | asmlinkage void chacha_4block_xor_avx2(u32 *state, u8 *dst, const u8 *src, | 
 | 				       unsigned int len, int nrounds); | 
 | asmlinkage void chacha_8block_xor_avx2(u32 *state, u8 *dst, const u8 *src, | 
 | 				       unsigned int len, int nrounds); | 
 |  | 
 | asmlinkage void chacha_2block_xor_avx512vl(u32 *state, u8 *dst, const u8 *src, | 
 | 					   unsigned int len, int nrounds); | 
 | asmlinkage void chacha_4block_xor_avx512vl(u32 *state, u8 *dst, const u8 *src, | 
 | 					   unsigned int len, int nrounds); | 
 | asmlinkage void chacha_8block_xor_avx512vl(u32 *state, u8 *dst, const u8 *src, | 
 | 					   unsigned int len, int nrounds); | 
 |  | 
 | static __ro_after_init DEFINE_STATIC_KEY_FALSE(chacha_use_simd); | 
 | static __ro_after_init DEFINE_STATIC_KEY_FALSE(chacha_use_avx2); | 
 | static __ro_after_init DEFINE_STATIC_KEY_FALSE(chacha_use_avx512vl); | 
 |  | 
 | static unsigned int chacha_advance(unsigned int len, unsigned int maxblocks) | 
 | { | 
 | 	len = min(len, maxblocks * CHACHA_BLOCK_SIZE); | 
 | 	return round_up(len, CHACHA_BLOCK_SIZE) / CHACHA_BLOCK_SIZE; | 
 | } | 
 |  | 
 | static void chacha_dosimd(u32 *state, u8 *dst, const u8 *src, | 
 | 			  unsigned int bytes, int nrounds) | 
 | { | 
 | 	if (IS_ENABLED(CONFIG_AS_AVX512) && | 
 | 	    static_branch_likely(&chacha_use_avx512vl)) { | 
 | 		while (bytes >= CHACHA_BLOCK_SIZE * 8) { | 
 | 			chacha_8block_xor_avx512vl(state, dst, src, bytes, | 
 | 						   nrounds); | 
 | 			bytes -= CHACHA_BLOCK_SIZE * 8; | 
 | 			src += CHACHA_BLOCK_SIZE * 8; | 
 | 			dst += CHACHA_BLOCK_SIZE * 8; | 
 | 			state[12] += 8; | 
 | 		} | 
 | 		if (bytes > CHACHA_BLOCK_SIZE * 4) { | 
 | 			chacha_8block_xor_avx512vl(state, dst, src, bytes, | 
 | 						   nrounds); | 
 | 			state[12] += chacha_advance(bytes, 8); | 
 | 			return; | 
 | 		} | 
 | 		if (bytes > CHACHA_BLOCK_SIZE * 2) { | 
 | 			chacha_4block_xor_avx512vl(state, dst, src, bytes, | 
 | 						   nrounds); | 
 | 			state[12] += chacha_advance(bytes, 4); | 
 | 			return; | 
 | 		} | 
 | 		if (bytes) { | 
 | 			chacha_2block_xor_avx512vl(state, dst, src, bytes, | 
 | 						   nrounds); | 
 | 			state[12] += chacha_advance(bytes, 2); | 
 | 			return; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	if (static_branch_likely(&chacha_use_avx2)) { | 
 | 		while (bytes >= CHACHA_BLOCK_SIZE * 8) { | 
 | 			chacha_8block_xor_avx2(state, dst, src, bytes, nrounds); | 
 | 			bytes -= CHACHA_BLOCK_SIZE * 8; | 
 | 			src += CHACHA_BLOCK_SIZE * 8; | 
 | 			dst += CHACHA_BLOCK_SIZE * 8; | 
 | 			state[12] += 8; | 
 | 		} | 
 | 		if (bytes > CHACHA_BLOCK_SIZE * 4) { | 
 | 			chacha_8block_xor_avx2(state, dst, src, bytes, nrounds); | 
 | 			state[12] += chacha_advance(bytes, 8); | 
 | 			return; | 
 | 		} | 
 | 		if (bytes > CHACHA_BLOCK_SIZE * 2) { | 
 | 			chacha_4block_xor_avx2(state, dst, src, bytes, nrounds); | 
 | 			state[12] += chacha_advance(bytes, 4); | 
 | 			return; | 
 | 		} | 
 | 		if (bytes > CHACHA_BLOCK_SIZE) { | 
 | 			chacha_2block_xor_avx2(state, dst, src, bytes, nrounds); | 
 | 			state[12] += chacha_advance(bytes, 2); | 
 | 			return; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	while (bytes >= CHACHA_BLOCK_SIZE * 4) { | 
 | 		chacha_4block_xor_ssse3(state, dst, src, bytes, nrounds); | 
 | 		bytes -= CHACHA_BLOCK_SIZE * 4; | 
 | 		src += CHACHA_BLOCK_SIZE * 4; | 
 | 		dst += CHACHA_BLOCK_SIZE * 4; | 
 | 		state[12] += 4; | 
 | 	} | 
 | 	if (bytes > CHACHA_BLOCK_SIZE) { | 
 | 		chacha_4block_xor_ssse3(state, dst, src, bytes, nrounds); | 
 | 		state[12] += chacha_advance(bytes, 4); | 
 | 		return; | 
 | 	} | 
 | 	if (bytes) { | 
 | 		chacha_block_xor_ssse3(state, dst, src, bytes, nrounds); | 
 | 		state[12]++; | 
 | 	} | 
 | } | 
 |  | 
 | void hchacha_block_arch(const u32 *state, u32 *stream, int nrounds) | 
 | { | 
 | 	if (!static_branch_likely(&chacha_use_simd) || !crypto_simd_usable()) { | 
 | 		hchacha_block_generic(state, stream, nrounds); | 
 | 	} else { | 
 | 		kernel_fpu_begin(); | 
 | 		hchacha_block_ssse3(state, stream, nrounds); | 
 | 		kernel_fpu_end(); | 
 | 	} | 
 | } | 
 | EXPORT_SYMBOL(hchacha_block_arch); | 
 |  | 
 | void chacha_init_arch(u32 *state, const u32 *key, const u8 *iv) | 
 | { | 
 | 	chacha_init_generic(state, key, iv); | 
 | } | 
 | EXPORT_SYMBOL(chacha_init_arch); | 
 |  | 
 | void chacha_crypt_arch(u32 *state, u8 *dst, const u8 *src, unsigned int bytes, | 
 | 		       int nrounds) | 
 | { | 
 | 	if (!static_branch_likely(&chacha_use_simd) || !crypto_simd_usable() || | 
 | 	    bytes <= CHACHA_BLOCK_SIZE) | 
 | 		return chacha_crypt_generic(state, dst, src, bytes, nrounds); | 
 |  | 
 | 	do { | 
 | 		unsigned int todo = min_t(unsigned int, bytes, SZ_4K); | 
 |  | 
 | 		kernel_fpu_begin(); | 
 | 		chacha_dosimd(state, dst, src, todo, nrounds); | 
 | 		kernel_fpu_end(); | 
 |  | 
 | 		bytes -= todo; | 
 | 		src += todo; | 
 | 		dst += todo; | 
 | 	} while (bytes); | 
 | } | 
 | EXPORT_SYMBOL(chacha_crypt_arch); | 
 |  | 
 | static int chacha_simd_stream_xor(struct skcipher_request *req, | 
 | 				  const struct chacha_ctx *ctx, const u8 *iv) | 
 | { | 
 | 	u32 state[CHACHA_STATE_WORDS] __aligned(8); | 
 | 	struct skcipher_walk walk; | 
 | 	int err; | 
 |  | 
 | 	err = skcipher_walk_virt(&walk, req, false); | 
 |  | 
 | 	chacha_init_generic(state, ctx->key, iv); | 
 |  | 
 | 	while (walk.nbytes > 0) { | 
 | 		unsigned int nbytes = walk.nbytes; | 
 |  | 
 | 		if (nbytes < walk.total) | 
 | 			nbytes = round_down(nbytes, walk.stride); | 
 |  | 
 | 		if (!static_branch_likely(&chacha_use_simd) || | 
 | 		    !crypto_simd_usable()) { | 
 | 			chacha_crypt_generic(state, walk.dst.virt.addr, | 
 | 					     walk.src.virt.addr, nbytes, | 
 | 					     ctx->nrounds); | 
 | 		} else { | 
 | 			kernel_fpu_begin(); | 
 | 			chacha_dosimd(state, walk.dst.virt.addr, | 
 | 				      walk.src.virt.addr, nbytes, | 
 | 				      ctx->nrounds); | 
 | 			kernel_fpu_end(); | 
 | 		} | 
 | 		err = skcipher_walk_done(&walk, walk.nbytes - nbytes); | 
 | 	} | 
 |  | 
 | 	return err; | 
 | } | 
 |  | 
 | static int chacha_simd(struct skcipher_request *req) | 
 | { | 
 | 	struct crypto_skcipher *tfm = crypto_skcipher_reqtfm(req); | 
 | 	struct chacha_ctx *ctx = crypto_skcipher_ctx(tfm); | 
 |  | 
 | 	return chacha_simd_stream_xor(req, ctx, req->iv); | 
 | } | 
 |  | 
 | static int xchacha_simd(struct skcipher_request *req) | 
 | { | 
 | 	struct crypto_skcipher *tfm = crypto_skcipher_reqtfm(req); | 
 | 	struct chacha_ctx *ctx = crypto_skcipher_ctx(tfm); | 
 | 	u32 state[CHACHA_STATE_WORDS] __aligned(8); | 
 | 	struct chacha_ctx subctx; | 
 | 	u8 real_iv[16]; | 
 |  | 
 | 	chacha_init_generic(state, ctx->key, req->iv); | 
 |  | 
 | 	if (req->cryptlen > CHACHA_BLOCK_SIZE && crypto_simd_usable()) { | 
 | 		kernel_fpu_begin(); | 
 | 		hchacha_block_ssse3(state, subctx.key, ctx->nrounds); | 
 | 		kernel_fpu_end(); | 
 | 	} else { | 
 | 		hchacha_block_generic(state, subctx.key, ctx->nrounds); | 
 | 	} | 
 | 	subctx.nrounds = ctx->nrounds; | 
 |  | 
 | 	memcpy(&real_iv[0], req->iv + 24, 8); | 
 | 	memcpy(&real_iv[8], req->iv + 16, 8); | 
 | 	return chacha_simd_stream_xor(req, &subctx, real_iv); | 
 | } | 
 |  | 
 | static struct skcipher_alg algs[] = { | 
 | 	{ | 
 | 		.base.cra_name		= "chacha20", | 
 | 		.base.cra_driver_name	= "chacha20-simd", | 
 | 		.base.cra_priority	= 300, | 
 | 		.base.cra_blocksize	= 1, | 
 | 		.base.cra_ctxsize	= sizeof(struct chacha_ctx), | 
 | 		.base.cra_module	= THIS_MODULE, | 
 |  | 
 | 		.min_keysize		= CHACHA_KEY_SIZE, | 
 | 		.max_keysize		= CHACHA_KEY_SIZE, | 
 | 		.ivsize			= CHACHA_IV_SIZE, | 
 | 		.chunksize		= CHACHA_BLOCK_SIZE, | 
 | 		.setkey			= chacha20_setkey, | 
 | 		.encrypt		= chacha_simd, | 
 | 		.decrypt		= chacha_simd, | 
 | 	}, { | 
 | 		.base.cra_name		= "xchacha20", | 
 | 		.base.cra_driver_name	= "xchacha20-simd", | 
 | 		.base.cra_priority	= 300, | 
 | 		.base.cra_blocksize	= 1, | 
 | 		.base.cra_ctxsize	= sizeof(struct chacha_ctx), | 
 | 		.base.cra_module	= THIS_MODULE, | 
 |  | 
 | 		.min_keysize		= CHACHA_KEY_SIZE, | 
 | 		.max_keysize		= CHACHA_KEY_SIZE, | 
 | 		.ivsize			= XCHACHA_IV_SIZE, | 
 | 		.chunksize		= CHACHA_BLOCK_SIZE, | 
 | 		.setkey			= chacha20_setkey, | 
 | 		.encrypt		= xchacha_simd, | 
 | 		.decrypt		= xchacha_simd, | 
 | 	}, { | 
 | 		.base.cra_name		= "xchacha12", | 
 | 		.base.cra_driver_name	= "xchacha12-simd", | 
 | 		.base.cra_priority	= 300, | 
 | 		.base.cra_blocksize	= 1, | 
 | 		.base.cra_ctxsize	= sizeof(struct chacha_ctx), | 
 | 		.base.cra_module	= THIS_MODULE, | 
 |  | 
 | 		.min_keysize		= CHACHA_KEY_SIZE, | 
 | 		.max_keysize		= CHACHA_KEY_SIZE, | 
 | 		.ivsize			= XCHACHA_IV_SIZE, | 
 | 		.chunksize		= CHACHA_BLOCK_SIZE, | 
 | 		.setkey			= chacha12_setkey, | 
 | 		.encrypt		= xchacha_simd, | 
 | 		.decrypt		= xchacha_simd, | 
 | 	}, | 
 | }; | 
 |  | 
 | static int __init chacha_simd_mod_init(void) | 
 | { | 
 | 	if (!boot_cpu_has(X86_FEATURE_SSSE3)) | 
 | 		return 0; | 
 |  | 
 | 	static_branch_enable(&chacha_use_simd); | 
 |  | 
 | 	if (boot_cpu_has(X86_FEATURE_AVX) && | 
 | 	    boot_cpu_has(X86_FEATURE_AVX2) && | 
 | 	    cpu_has_xfeatures(XFEATURE_MASK_SSE | XFEATURE_MASK_YMM, NULL)) { | 
 | 		static_branch_enable(&chacha_use_avx2); | 
 |  | 
 | 		if (IS_ENABLED(CONFIG_AS_AVX512) && | 
 | 		    boot_cpu_has(X86_FEATURE_AVX512VL) && | 
 | 		    boot_cpu_has(X86_FEATURE_AVX512BW)) /* kmovq */ | 
 | 			static_branch_enable(&chacha_use_avx512vl); | 
 | 	} | 
 | 	return IS_REACHABLE(CONFIG_CRYPTO_SKCIPHER) ? | 
 | 		crypto_register_skciphers(algs, ARRAY_SIZE(algs)) : 0; | 
 | } | 
 |  | 
 | static void __exit chacha_simd_mod_fini(void) | 
 | { | 
 | 	if (IS_REACHABLE(CONFIG_CRYPTO_SKCIPHER) && boot_cpu_has(X86_FEATURE_SSSE3)) | 
 | 		crypto_unregister_skciphers(algs, ARRAY_SIZE(algs)); | 
 | } | 
 |  | 
 | module_init(chacha_simd_mod_init); | 
 | module_exit(chacha_simd_mod_fini); | 
 |  | 
 | MODULE_LICENSE("GPL"); | 
 | MODULE_AUTHOR("Martin Willi <martin@strongswan.org>"); | 
 | MODULE_DESCRIPTION("ChaCha and XChaCha stream ciphers (x64 SIMD accelerated)"); | 
 | MODULE_ALIAS_CRYPTO("chacha20"); | 
 | MODULE_ALIAS_CRYPTO("chacha20-simd"); | 
 | MODULE_ALIAS_CRYPTO("xchacha20"); | 
 | MODULE_ALIAS_CRYPTO("xchacha20-simd"); | 
 | MODULE_ALIAS_CRYPTO("xchacha12"); | 
 | MODULE_ALIAS_CRYPTO("xchacha12-simd"); |