| /* SPDX-License-Identifier: GPL-2.0-or-later */ | |
| /* | |
| * SHA-3 optimized using the CP Assist for Cryptographic Functions (CPACF) | |
| * | |
| * Copyright 2025 Google LLC | |
| */ | |
| #include <asm/cpacf.h> | |
| #include <linux/cpufeature.h> | |
| static __ro_after_init DEFINE_STATIC_KEY_FALSE(have_sha3); | |
| static __ro_after_init DEFINE_STATIC_KEY_FALSE(have_sha3_init_optim); | |
| static void sha3_absorb_blocks(struct sha3_state *state, const u8 *data, | |
| size_t nblocks, size_t block_size) | |
| { | |
| if (static_branch_likely(&have_sha3)) { | |
| /* | |
| * Note that KIMD assumes little-endian order of the state | |
| * words. sha3_state already uses that order, though, so | |
| * there's no need for a byteswap. | |
| */ | |
| switch (block_size) { | |
| case SHA3_224_BLOCK_SIZE: | |
| cpacf_kimd(CPACF_KIMD_SHA3_224, state, | |
| data, nblocks * block_size); | |
| return; | |
| case SHA3_256_BLOCK_SIZE: | |
| /* | |
| * This case handles both SHA3-256 and SHAKE256, since | |
| * they have the same block size. | |
| */ | |
| cpacf_kimd(CPACF_KIMD_SHA3_256, state, | |
| data, nblocks * block_size); | |
| return; | |
| case SHA3_384_BLOCK_SIZE: | |
| cpacf_kimd(CPACF_KIMD_SHA3_384, state, | |
| data, nblocks * block_size); | |
| return; | |
| case SHA3_512_BLOCK_SIZE: | |
| cpacf_kimd(CPACF_KIMD_SHA3_512, state, | |
| data, nblocks * block_size); | |
| return; | |
| } | |
| } | |
| sha3_absorb_blocks_generic(state, data, nblocks, block_size); | |
| } | |
| static void sha3_keccakf(struct sha3_state *state) | |
| { | |
| if (static_branch_likely(&have_sha3)) { | |
| /* | |
| * Passing zeroes into any of CPACF_KIMD_SHA3_* gives the plain | |
| * Keccak-f permutation, which is what we want here. Use | |
| * SHA3-512 since it has the smallest block size. | |
| */ | |
| static const u8 zeroes[SHA3_512_BLOCK_SIZE]; | |
| cpacf_kimd(CPACF_KIMD_SHA3_512, state, zeroes, sizeof(zeroes)); | |
| } else { | |
| sha3_keccakf_generic(state); | |
| } | |
| } | |
| static inline bool s390_sha3(int func, const u8 *in, size_t in_len, | |
| u8 *out, size_t out_len) | |
| { | |
| struct sha3_state state; | |
| if (!static_branch_likely(&have_sha3)) | |
| return false; | |
| if (static_branch_likely(&have_sha3_init_optim)) | |
| func |= CPACF_KLMD_NIP | CPACF_KLMD_DUFOP; | |
| else | |
| memset(&state, 0, sizeof(state)); | |
| cpacf_klmd(func, &state, in, in_len); | |
| if (static_branch_likely(&have_sha3_init_optim)) | |
| kmsan_unpoison_memory(&state, out_len); | |
| memcpy(out, &state, out_len); | |
| memzero_explicit(&state, sizeof(state)); | |
| return true; | |
| } | |
| #define sha3_224_arch sha3_224_arch | |
| static bool sha3_224_arch(const u8 *in, size_t in_len, | |
| u8 out[SHA3_224_DIGEST_SIZE]) | |
| { | |
| return s390_sha3(CPACF_KLMD_SHA3_224, in, in_len, | |
| out, SHA3_224_DIGEST_SIZE); | |
| } | |
| #define sha3_256_arch sha3_256_arch | |
| static bool sha3_256_arch(const u8 *in, size_t in_len, | |
| u8 out[SHA3_256_DIGEST_SIZE]) | |
| { | |
| return s390_sha3(CPACF_KLMD_SHA3_256, in, in_len, | |
| out, SHA3_256_DIGEST_SIZE); | |
| } | |
| #define sha3_384_arch sha3_384_arch | |
| static bool sha3_384_arch(const u8 *in, size_t in_len, | |
| u8 out[SHA3_384_DIGEST_SIZE]) | |
| { | |
| return s390_sha3(CPACF_KLMD_SHA3_384, in, in_len, | |
| out, SHA3_384_DIGEST_SIZE); | |
| } | |
| #define sha3_512_arch sha3_512_arch | |
| static bool sha3_512_arch(const u8 *in, size_t in_len, | |
| u8 out[SHA3_512_DIGEST_SIZE]) | |
| { | |
| return s390_sha3(CPACF_KLMD_SHA3_512, in, in_len, | |
| out, SHA3_512_DIGEST_SIZE); | |
| } | |
| #define sha3_mod_init_arch sha3_mod_init_arch | |
| static void sha3_mod_init_arch(void) | |
| { | |
| int num_present = 0; | |
| int num_possible = 0; | |
| if (!cpu_have_feature(S390_CPU_FEATURE_MSA)) | |
| return; | |
| /* | |
| * Since all the SHA-3 functions are in Message-Security-Assist | |
| * Extension 6, just treat them as all or nothing. This way we need | |
| * only one static_key. | |
| */ | |
| #define QUERY(opcode, func) \ | |
| ({ num_present += !!cpacf_query_func(opcode, func); num_possible++; }) | |
| QUERY(CPACF_KIMD, CPACF_KIMD_SHA3_224); | |
| QUERY(CPACF_KIMD, CPACF_KIMD_SHA3_256); | |
| QUERY(CPACF_KIMD, CPACF_KIMD_SHA3_384); | |
| QUERY(CPACF_KIMD, CPACF_KIMD_SHA3_512); | |
| QUERY(CPACF_KLMD, CPACF_KLMD_SHA3_224); | |
| QUERY(CPACF_KLMD, CPACF_KLMD_SHA3_256); | |
| QUERY(CPACF_KLMD, CPACF_KLMD_SHA3_384); | |
| QUERY(CPACF_KLMD, CPACF_KLMD_SHA3_512); | |
| #undef QUERY | |
| if (num_present == num_possible) { | |
| static_branch_enable(&have_sha3); | |
| if (test_facility(86)) | |
| static_branch_enable(&have_sha3_init_optim); | |
| } else if (num_present != 0) { | |
| pr_warn("Unsupported combination of SHA-3 facilities\n"); | |
| } | |
| } |