// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright IBM Corp. 2025
*
* s390 specific HMAC support for protected keys.
*/
#define pr_fmt(fmt) "phmac_s390: " fmt
#include <asm/cpacf.h>
#include <asm/pkey.h>
#include <crypto/engine.h>
#include <crypto/hash.h>
#include <crypto/internal/hash.h>
#include <crypto/sha2.h>
#include <linux/atomic.h>
#include <linux/cpufeature.h>
#include <linux/delay.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/spinlock.h>
static struct crypto_engine *phmac_crypto_engine;
#define MAX_QLEN 10
static bool pkey_clrkey_allowed;
module_param_named(clrkey, pkey_clrkey_allowed, bool, 0444);
MODULE_PARM_DESC(clrkey, "Allow clear key material (default N)");
/*
* A simple hash walk helper
*/
struct hash_walk_helper {
struct crypto_hash_walk walk;
const u8 *walkaddr;
int walkbytes;
};
/*
* Prepare hash walk helper.
* Set up the base hash walk, fill walkaddr and walkbytes.
* Returns 0 on success or negative value on error.
*/
static inline int hwh_prepare(struct ahash_request *req,
struct hash_walk_helper *hwh)
{
hwh->walkbytes = crypto_hash_walk_first(req, &hwh->walk);
if (hwh->walkbytes < 0)
return hwh->walkbytes;
hwh->walkaddr = hwh->walk.data;
return 0;
}
/*
* Advance hash walk helper by n bytes.
* Progress the walkbytes and walkaddr fields by n bytes.
* If walkbytes is then 0, pull next hunk from hash walk
* and update walkbytes and walkaddr.
* If n is negative, unmap hash walk and return error.
* Returns 0 on success or negative value on error.
*/
static inline int hwh_advance(struct hash_walk_helper *hwh, int n)
{
if (n < 0)
return crypto_hash_walk_done(&hwh->walk, n);
hwh->walkbytes -= n;
hwh->walkaddr += n;
if (hwh->walkbytes > 0)
return 0;
hwh->walkbytes = crypto_hash_walk_done(&hwh->walk, 0);
if (hwh->walkbytes < 0)
return hwh->walkbytes;
hwh->walkaddr = hwh->walk.data;
return 0;
}
/*
* KMAC param block layout for sha2 function codes:
* The layout of the param block for the KMAC instruction depends on the
* blocksize of the used hashing sha2-algorithm function codes. The param block
* contains the hash chaining value (cv), the input message bit-length (imbl)
* and the hmac-secret (key). To prevent code duplication, the sizes of all
* these are calculated based on the blocksize.
*
* param-block:
* +-------+
* | cv |
* +-------+
* | imbl |
* +-------+
* | key |
* +-------+
*
* sizes:
* part | sh2-alg | calculation | size | type
* -----+---------+-------------+------+--------
* cv | 224/256 | blocksize/2 | 32 | u64[8]
* | 384/512 | | 64 | u128[8]
* imbl | 224/256 | blocksize/8 | 8 | u64
* | 384/512 | | 16 | u128
* key | 224/256 | blocksize | 96 | u8[96]
* | 384/512 | | 160 | u8[160]
*/
#define MAX_DIGEST_SIZE SHA512_DIGEST_SIZE
#define MAX_IMBL_SIZE sizeof(u128)
#define MAX_BLOCK_SIZE SHA512_BLOCK_SIZE
#define SHA2_CV_SIZE(bs) ((bs) >> 1)
#define SHA2_IMBL_SIZE(bs) ((bs) >> 3)
#define SHA2_IMBL_OFFSET(bs) (SHA2_CV_SIZE(bs))
#define SHA2_KEY_OFFSET(bs) (SHA2_CV_SIZE(bs) + SHA2_IMBL_SIZE(bs))
#define PHMAC_MAX_KEYSIZE 256
#define PHMAC_SHA256_PK_SIZE (SHA256_BLOCK_SIZE + 32)
#define PHMAC_SHA512_PK_SIZE (SHA512_BLOCK_SIZE + 32)
#define PHMAC_MAX_PK_SIZE PHMAC_SHA512_PK_SIZE
/* phmac protected key struct */
struct phmac_protkey {
u32 type;
u32 len;
u8 protkey[PHMAC_MAX_PK_SIZE];
};
#define PK_STATE_NO_KEY 0
#define PK_STATE_CONVERT_IN_PROGRESS 1
#define PK_STATE_VALID 2
/* phmac tfm context */
struct phmac_tfm_ctx {
/* source key material used to derive a protected key from */
u8 keybuf[PHMAC_MAX_KEYSIZE];
unsigned int keylen;
/* cpacf function code to use with this protected key type */
long fc;
/* nr of requests enqueued via crypto engine which use this tfm ctx */
atomic_t via_engine_ctr;
/* spinlock to atomic read/update all the following fields */
spinlock_t pk_lock;
/* see PK_STATE* defines above, < 0 holds convert failure rc */
int pk_state;
/* if state is valid, pk holds the protected key */
struct phmac_protkey pk;
};
union kmac_gr0 {
unsigned long reg;
struct {
unsigned long : 48;
unsigned long ikp : 1;
unsigned long iimp : 1;
unsigned long ccup : 1;
unsigned long : 6;
unsigned long fc : 7;
};
};
struct kmac_sha2_ctx {
u8 param[MAX_DIGEST_SIZE + MAX_IMBL_SIZE + PHMAC_MAX_PK_SIZE];
union kmac_gr0 gr0;
u8 buf[MAX_BLOCK_SIZE];
u64 buflen[2];
};
enum async_op {
OP_NOP = 0,
OP_UPDATE,
OP_FINAL,
OP_FINUP,
};
/* phmac request context */
struct phmac_req_ctx {
struct hash_walk_helper hwh;
struct kmac_sha2_ctx kmac_ctx;
enum async_op