Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+56
View File
@@ -0,0 +1,56 @@
# SPDX-License-Identifier: GPL-2.0-only
config CRYPTO_DEV_CCP_DD
tristate "Secure Processor device driver"
depends on CPU_SUP_AMD || ARM64
default m
help
Provides AMD Secure Processor device driver.
If you choose 'M' here, this module will be called ccp.
config CRYPTO_DEV_SP_CCP
bool "Cryptographic Coprocessor device"
default y
depends on CRYPTO_DEV_CCP_DD && DMADEVICES
select HW_RANDOM
select DMA_ENGINE
select CRYPTO_SHA1
select CRYPTO_SHA256
help
Provides the support for AMD Cryptographic Coprocessor (CCP) device
which can be used to offload encryption operations such as SHA, AES
and more.
config CRYPTO_DEV_CCP_CRYPTO
tristate "Encryption and hashing offload support"
default m
depends on CRYPTO_DEV_CCP_DD
depends on CRYPTO_DEV_SP_CCP
select CRYPTO_HASH
select CRYPTO_SKCIPHER
select CRYPTO_AUTHENC
select CRYPTO_RSA
select CRYPTO_LIB_AES
help
Support for using the cryptographic API with the AMD Cryptographic
Coprocessor. This module supports offload of SHA and AES algorithms.
If you choose 'M' here, this module will be called ccp_crypto.
config CRYPTO_DEV_SP_PSP
bool "Platform Security Processor (PSP) device"
default y
depends on CRYPTO_DEV_CCP_DD && X86_64 && AMD_IOMMU
select PCI_TSM if PCI
help
Provide support for the AMD Platform Security Processor (PSP).
The PSP is a dedicated processor that provides support for key
management commands in Secure Encrypted Virtualization (SEV) mode,
along with software-based Trusted Execution Environment (TEE) to
enable third-party trusted applications.
config CRYPTO_DEV_CCP_DEBUGFS
bool "Enable CCP Internals in DebugFS"
default n
depends on CRYPTO_DEV_SP_CCP
help
Expose CCP device information such as operation statistics, feature
information, and descriptor queue contents.
+31
View File
@@ -0,0 +1,31 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_CRYPTO_DEV_CCP_DD) += ccp.o
ccp-objs := sp-dev.o sp-platform.o
ccp-$(CONFIG_CRYPTO_DEV_SP_CCP) += ccp-dev.o \
ccp-ops.o \
ccp-dev-v3.o \
ccp-dev-v5.o \
ccp-dmaengine.o
ccp-$(CONFIG_CRYPTO_DEV_CCP_DEBUGFS) += ccp-debugfs.o
ccp-$(CONFIG_PCI) += sp-pci.o
ccp-$(CONFIG_CRYPTO_DEV_SP_PSP) += psp-dev.o \
sev-dev.o \
tee-dev.o \
platform-access.o \
dbc.o \
hsti.o \
sfs.o
ifeq ($(CONFIG_PCI_TSM),y)
ccp-$(CONFIG_CRYPTO_DEV_SP_PSP) += sev-dev-tsm.o sev-dev-tio.o
endif
obj-$(CONFIG_CRYPTO_DEV_CCP_CRYPTO) += ccp-crypto.o
ccp-crypto-objs := ccp-crypto-main.o \
ccp-crypto-aes.o \
ccp-crypto-aes-cmac.o \
ccp-crypto-aes-xts.o \
ccp-crypto-aes-galois.o \
ccp-crypto-des3.o \
ccp-crypto-rsa.o \
ccp-crypto-sha.o
@@ -0,0 +1,402 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* AMD Cryptographic Coprocessor (CCP) AES CMAC crypto API support
*
* Copyright (C) 2013,2018 Advanced Micro Devices, Inc.
*
* Author: Tom Lendacky <thomas.lendacky@amd.com>
*/
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/delay.h>
#include <linux/scatterlist.h>
#include <linux/crypto.h>
#include <crypto/algapi.h>
#include <crypto/aes.h>
#include <crypto/hash.h>
#include <crypto/internal/hash.h>
#include <crypto/scatterwalk.h>
#include "ccp-crypto.h"
static int ccp_aes_cmac_complete(struct crypto_async_request *async_req,
int ret)
{
struct ahash_request *req = ahash_request_cast(async_req);
struct crypto_ahash *tfm = crypto_ahash_reqtfm(req);
struct ccp_aes_cmac_req_ctx *rctx = ahash_request_ctx_dma(req);
unsigned int digest_size = crypto_ahash_digestsize(tfm);
if (ret)
goto e_free;
if (rctx->hash_rem) {
/* Save remaining data to buffer */
unsigned int offset = rctx->nbytes - rctx->hash_rem;
scatterwalk_map_and_copy(rctx->buf, rctx->src,
offset, rctx->hash_rem, 0);
rctx->buf_count = rctx->hash_rem;
} else {
rctx->buf_count = 0;
}
/* Update result area if supplied */
if (req->result && rctx->final)
memcpy(req->result, rctx->iv, digest_size);
e_free:
sg_free_table(&rctx->data_sg);
return ret;
}
static int ccp_do_cmac_update(struct ahash_request *req, unsigned int nbytes,
unsigned int final)
{
struct crypto_ahash *tfm = crypto_ahash_reqtfm(req);
struct ccp_ctx *ctx = crypto_ahash_ctx_dma(tfm);
struct ccp_aes_cmac_req_ctx *rctx = ahash_request_ctx_dma(req);
struct scatterlist *sg, *cmac_key_sg = NULL;
unsigned int block_size =
crypto_tfm_alg_blocksize(crypto_ahash_tfm(tfm));
unsigned int need_pad, sg_count;
gfp_t gfp;
u64 len;
int ret;
if (!ctx->u.aes.key_len)
return -EINVAL;
if (nbytes)
rctx->null_msg = 0;
len = (u64)rctx->buf_count + (u64)nbytes;
if (!final && (len <= block_size)) {
scatterwalk_map_and_copy(rctx->buf + rctx->buf_count, req->src,
0, nbytes, 0);
rctx->buf_count += nbytes;
return 0;
}
rctx->src = req->src;
rctx->nbytes = nbytes;
rctx->final = final;
rctx->hash_rem = final ? 0 : len & (block_size - 1);
rctx->hash_cnt = len - rctx->hash_rem;
if (!final && !rctx->hash_rem) {
/* CCP can't do zero length final, so keep some data around */
rctx->hash_cnt -= block_size;
rctx->hash_rem = block_size;
}
if (final && (rctx->null_msg || (len & (block_size - 1))))
need_pad = 1;
else
need_pad = 0;
sg_init_one(&rctx->iv_sg, rctx->iv, sizeof(rctx->iv));
/* Build the data scatterlist table - allocate enough entries for all
* possible data pieces (buffer, input data, padding)
*/
sg_count = (nbytes) ? sg_nents(req->src) + 2 : 2;
gfp = req->base.flags & CRYPTO_TFM_REQ_MAY_SLEEP ?
GFP_KERNEL : GFP_ATOMIC;
ret = sg_alloc_table(&rctx->data_sg, sg_count, gfp);
if (ret)
return ret;
sg = NULL;
if (rctx->buf_count) {
sg_init_one(&rctx->buf_sg, rctx->buf, rctx->buf_count);
sg = ccp_crypto_sg_table_add(&rctx->data_sg, &rctx->buf_sg);
if (!sg) {
ret = -EINVAL;
goto e_free;
}
}
if (nbytes) {
sg = ccp_crypto_sg_table_add(&rctx->data_sg, req->src);
if (!sg) {
ret = -EINVAL;
goto e_free;
}
}
if (need_pad) {
int pad_length = block_size - (len & (block_size - 1));
rctx->hash_cnt += pad_length;
memset(rctx->pad, 0, sizeof(rctx->pad));
rctx->pad[0] = 0x80;
sg_init_one(&rctx->pad_sg, rctx->pad, pad_length);
sg = ccp_crypto_sg_table_add(&rctx->data_sg, &rctx->pad_sg);
if (!sg) {
ret = -EINVAL;
goto e_free;
}
}
if (sg) {
sg_mark_end(sg);
sg = rctx->data_sg.sgl;
}
/* Initialize the K1/K2 scatterlist */
if (final)
cmac_key_sg = (need_pad) ? &ctx->u.aes.k2_sg
: &ctx->u.aes.k1_sg;
memset(&rctx->cmd, 0, sizeof(rctx->cmd));
INIT_LIST_HEAD(&rctx->cmd.entry);
rctx->cmd.engine = CCP_ENGINE_AES;
rctx->cmd.u.aes.type = ctx->u.aes.type;
rctx->cmd.u.aes.mode = ctx->u.aes.mode;
rctx->cmd.u.aes.action = CCP_AES_ACTION_ENCRYPT;
rctx->cmd.u.aes.key = &ctx->u.aes.key_sg;
rctx->cmd.u.aes.key_len = ctx->u.aes.key_len;
rctx->cmd.u.aes.iv = &rctx->iv_sg;
rctx->cmd.u.aes.iv_len = AES_BLOCK_SIZE;
rctx->cmd.u.aes.src = sg;
rctx->cmd.u.aes.src_len = rctx->hash_cnt;
rctx->cmd.u.aes.dst = NULL;
rctx->cmd.u.aes.cmac_key = cmac_key_sg;
rctx->cmd.u.aes.cmac_key_len = ctx->u.aes.kn_len;
rctx->cmd.u.aes.cmac_final = final;
ret = ccp_crypto_enqueue_request(&req->base, &rctx->cmd);
return ret;
e_free:
sg_free_table(&rctx->data_sg);
return ret;
}
static int ccp_aes_cmac_init(struct ahash_request *req)
{
struct ccp_aes_cmac_req_ctx *rctx = ahash_request_ctx_dma(req);
memset(rctx, 0, sizeof(*rctx));
rctx->null_msg = 1;
return 0;
}
static int ccp_aes_cmac_update(struct ahash_request *req)
{
return ccp_do_cmac_update(req, req->nbytes, 0);
}
static int ccp_aes_cmac_final(struct ahash_request *req)
{
return ccp_do_cmac_update(req, 0, 1);
}
static int ccp_aes_cmac_finup(struct ahash_request *req)
{
return ccp_do_cmac_update(req, req->nbytes, 1);
}
static int ccp_aes_cmac_digest(struct ahash_request *req)
{
int ret;
ret = ccp_aes_cmac_init(req);
if (ret)
return ret;
return ccp_aes_cmac_finup(req);
}
static int ccp_aes_cmac_export(struct ahash_request *req, void *out)
{
struct ccp_aes_cmac_req_ctx *rctx = ahash_request_ctx_dma(req);
struct ccp_aes_cmac_exp_ctx state;
/* Don't let anything leak to 'out' */
memset(&state, 0, sizeof(state));
state.null_msg = rctx->null_msg;
memcpy(state.iv, rctx->iv, sizeof(state.iv));
state.buf_count = rctx->buf_count;
memcpy(state.buf, rctx->buf, sizeof(state.buf));
/* 'out' may not be aligned so memcpy from local variable */
memcpy(out, &state, sizeof(state));
return 0;
}
static int ccp_aes_cmac_import(struct ahash_request *req, const void *in)
{
struct ccp_aes_cmac_req_ctx *rctx = ahash_request_ctx_dma(req);
struct ccp_aes_cmac_exp_ctx state;
/* 'in' may not be aligned so memcpy to local variable */
memcpy(&state, in, sizeof(state));
memset(rctx, 0, sizeof(*rctx));
rctx->null_msg = state.null_msg;
memcpy(rctx->iv, state.iv, sizeof(rctx->iv));
rctx->buf_count = state.buf_count;
memcpy(rctx->buf, state.buf, sizeof(rctx->buf));
return 0;
}
static int ccp_aes_cmac_setkey(struct crypto_ahash *tfm, const u8 *key,
unsigned int key_len)
{
struct ccp_ctx *ctx = crypto_ahash_ctx_dma(tfm);
struct ccp_crypto_ahash_alg *alg =
ccp_crypto_ahash_alg(crypto_ahash_tfm(tfm));
u64 k0_hi, k0_lo, k1_hi, k1_lo, k2_hi, k2_lo;
u64 rb_hi = 0x00, rb_lo = 0x87;
struct aes_enckey aes;
__be64 *gk;
int ret;
switch (key_len) {
case AES_KEYSIZE_128:
ctx->u.aes.type = CCP_AES_TYPE_128;
break;
case AES_KEYSIZE_192:
ctx->u.aes.type = CCP_AES_TYPE_192;
break;
case AES_KEYSIZE_256:
ctx->u.aes.type = CCP_AES_TYPE_256;
break;
default:
return -EINVAL;
}
ctx->u.aes.mode = alg->mode;
/* Set to zero until complete */
ctx->u.aes.key_len = 0;
/* Set the key for the AES cipher used to generate the keys */
ret = aes_prepareenckey(&aes, key, key_len);
if (ret)
return ret;
/* Encrypt a block of zeroes - use key area in context */
memset(ctx->u.aes.key, 0, sizeof(ctx->u.aes.key));
aes_encrypt(&aes, ctx->u.aes.key, ctx->u.aes.key);
memzero_explicit(&aes, sizeof(aes));
/* Generate K1 and K2 */
k0_hi = be64_to_cpu(*((__be64 *)ctx->u.aes.key));
k0_lo = be64_to_cpu(*((__be64 *)ctx->u.aes.key + 1));
k1_hi = (k0_hi << 1) | (k0_lo >> 63);
k1_lo = k0_lo << 1;
if (ctx->u.aes.key[0] & 0x80) {
k1_hi ^= rb_hi;
k1_lo ^= rb_lo;
}
gk = (__be64 *)ctx->u.aes.k1;
*gk = cpu_to_be64(k1_hi);
gk++;
*gk = cpu_to_be64(k1_lo);
k2_hi = (k1_hi << 1) | (k1_lo >> 63);
k2_lo = k1_lo << 1;
if (ctx->u.aes.k1[0] & 0x80) {
k2_hi ^= rb_hi;
k2_lo ^= rb_lo;
}
gk = (__be64 *)ctx->u.aes.k2;
*gk = cpu_to_be64(k2_hi);
gk++;
*gk = cpu_to_be64(k2_lo);
ctx->u.aes.kn_len = sizeof(ctx->u.aes.k1);
sg_init_one(&ctx->u.aes.k1_sg, ctx->u.aes.k1, sizeof(ctx->u.aes.k1));
sg_init_one(&ctx->u.aes.k2_sg, ctx->u.aes.k2, sizeof(ctx->u.aes.k2));
/* Save the supplied key */
memset(ctx->u.aes.key, 0, sizeof(ctx->u.aes.key));
memcpy(ctx->u.aes.key, key, key_len);
ctx->u.aes.key_len = key_len;
sg_init_one(&ctx->u.aes.key_sg, ctx->u.aes.key, key_len);
return ret;
}
static int ccp_aes_cmac_cra_init(struct crypto_tfm *tfm)
{
struct ccp_ctx *ctx = crypto_tfm_ctx_dma(tfm);
struct crypto_ahash *ahash = __crypto_ahash_cast(tfm);
ctx->complete = ccp_aes_cmac_complete;
ctx->u.aes.key_len = 0;
crypto_ahash_set_reqsize_dma(ahash,
sizeof(struct ccp_aes_cmac_req_ctx));
return 0;
}
int ccp_register_aes_cmac_algs(struct list_head *head)
{
struct ccp_crypto_ahash_alg *ccp_alg;
struct ahash_alg *alg;
struct hash_alg_common *halg;
struct crypto_alg *base;
int ret;
ccp_alg = kzalloc_obj(*ccp_alg);
if (!ccp_alg)
return -ENOMEM;
INIT_LIST_HEAD(&ccp_alg->entry);
ccp_alg->mode = CCP_AES_MODE_CMAC;
alg = &ccp_alg->alg;
alg->init = ccp_aes_cmac_init;
alg->update = ccp_aes_cmac_update;
alg->final = ccp_aes_cmac_final;
alg->finup = ccp_aes_cmac_finup;
alg->digest = ccp_aes_cmac_digest;
alg->export = ccp_aes_cmac_export;
alg->import = ccp_aes_cmac_import;
alg->setkey = ccp_aes_cmac_setkey;
halg = &alg->halg;
halg->digestsize = AES_BLOCK_SIZE;
halg->statesize = sizeof(struct ccp_aes_cmac_exp_ctx);
base = &halg->base;
snprintf(base->cra_name, CRYPTO_MAX_ALG_NAME, "cmac(aes)");
snprintf(base->cra_driver_name, CRYPTO_MAX_ALG_NAME, "cmac-aes-ccp");
base->cra_flags = CRYPTO_ALG_ASYNC |
CRYPTO_ALG_ALLOCATES_MEMORY |
CRYPTO_ALG_KERN_DRIVER_ONLY |
CRYPTO_ALG_NEED_FALLBACK;
base->cra_blocksize = AES_BLOCK_SIZE;
base->cra_ctxsize = sizeof(struct ccp_ctx) + crypto_dma_padding();
base->cra_priority = CCP_CRA_PRIORITY;
base->cra_init = ccp_aes_cmac_cra_init;
base->cra_module = THIS_MODULE;
ret = crypto_register_ahash(alg);
if (ret) {
pr_err("%s ahash algorithm registration error (%d)\n",
base->cra_name, ret);
kfree(ccp_alg);
return ret;
}
list_add(&ccp_alg->entry, head);
return 0;
}
@@ -0,0 +1,258 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* AMD Cryptographic Coprocessor (CCP) AES GCM crypto API support
*
* Copyright (C) 2016,2017 Advanced Micro Devices, Inc.
*
* Author: Gary R Hook <gary.hook@amd.com>
*/
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/delay.h>
#include <linux/scatterlist.h>
#include <linux/string.h>
#include <linux/crypto.h>
#include <crypto/internal/aead.h>
#include <crypto/algapi.h>
#include <crypto/aes.h>
#include <crypto/ctr.h>
#include <crypto/gcm.h>
#include <crypto/scatterwalk.h>
#include "ccp-crypto.h"
static int ccp_aes_gcm_complete(struct crypto_async_request *async_req, int ret)
{
return ret;
}
static int ccp_aes_gcm_setkey(struct crypto_aead *tfm, const u8 *key,
unsigned int key_len)
{
struct ccp_ctx *ctx = crypto_aead_ctx_dma(tfm);
switch (key_len) {
case AES_KEYSIZE_128:
ctx->u.aes.type = CCP_AES_TYPE_128;
break;
case AES_KEYSIZE_192:
ctx->u.aes.type = CCP_AES_TYPE_192;
break;
case AES_KEYSIZE_256:
ctx->u.aes.type = CCP_AES_TYPE_256;
break;
default:
return -EINVAL;
}
ctx->u.aes.mode = CCP_AES_MODE_GCM;
ctx->u.aes.key_len = key_len;
memcpy(ctx->u.aes.key, key, key_len);
sg_init_one(&ctx->u.aes.key_sg, ctx->u.aes.key, key_len);
return 0;
}
static int ccp_aes_gcm_setauthsize(struct crypto_aead *tfm,
unsigned int authsize)
{
switch (authsize) {
case 16:
case 15:
case 14:
case 13:
case 12:
case 8:
case 4:
break;
default:
return -EINVAL;
}
return 0;
}
static int ccp_aes_gcm_crypt(struct aead_request *req, bool encrypt)
{
struct crypto_aead *tfm = crypto_aead_reqtfm(req);
struct ccp_ctx *ctx = crypto_aead_ctx_dma(tfm);
struct ccp_aes_req_ctx *rctx = aead_request_ctx_dma(req);
struct scatterlist *iv_sg = NULL;
unsigned int iv_len = 0;
int i;
int ret = 0;
if (!ctx->u.aes.key_len)
return -EINVAL;
if (ctx->u.aes.mode != CCP_AES_MODE_GCM)
return -EINVAL;
if (!req->iv)
return -EINVAL;
/*
* 5 parts:
* plaintext/ciphertext input
* AAD
* key
* IV
* Destination+tag buffer
*/
/* Prepare the IV: 12 bytes + an integer (counter) */
memcpy(rctx->iv, req->iv, GCM_AES_IV_SIZE);
for (i = 0; i < 3; i++)
rctx->iv[i + GCM_AES_IV_SIZE] = 0;
rctx->iv[AES_BLOCK_SIZE - 1] = 1;
/* Set up a scatterlist for the IV */
iv_sg = &rctx->iv_sg;
iv_len = AES_BLOCK_SIZE;
sg_init_one(iv_sg, rctx->iv, iv_len);
/* The AAD + plaintext are concatenated in the src buffer */
memset(&rctx->cmd, 0, sizeof(rctx->cmd));
INIT_LIST_HEAD(&rctx->cmd.entry);
rctx->cmd.engine = CCP_ENGINE_AES;
rctx->cmd.u.aes.authsize = crypto_aead_authsize(tfm);
rctx->cmd.u.aes.type = ctx->u.aes.type;
rctx->cmd.u.aes.mode = ctx->u.aes.mode;
rctx->cmd.u.aes.action = encrypt;
rctx->cmd.u.aes.key = &ctx->u.aes.key_sg;
rctx->cmd.u.aes.key_len = ctx->u.aes.key_len;
rctx->cmd.u.aes.iv = iv_sg;
rctx->cmd.u.aes.iv_len = iv_len;
rctx->cmd.u.aes.src = req->src;
rctx->cmd.u.aes.src_len = req->cryptlen;
rctx->cmd.u.aes.aad_len = req->assoclen;
/* The cipher text + the tag are in the dst buffer */
rctx->cmd.u.aes.dst = req->dst;
ret = ccp_crypto_enqueue_request(&req->base, &rctx->cmd);
return ret;
}
static int ccp_aes_gcm_encrypt(struct aead_request *req)
{
return ccp_aes_gcm_crypt(req, CCP_AES_ACTION_ENCRYPT);
}
static int ccp_aes_gcm_decrypt(struct aead_request *req)
{
return ccp_aes_gcm_crypt(req, CCP_AES_ACTION_DECRYPT);
}
static int ccp_aes_gcm_cra_init(struct crypto_aead *tfm)
{
struct ccp_ctx *ctx = crypto_aead_ctx_dma(tfm);
ctx->complete = ccp_aes_gcm_complete;
ctx->u.aes.key_len = 0;
crypto_aead_set_reqsize_dma(tfm, sizeof(struct ccp_aes_req_ctx));
return 0;
}
static void ccp_aes_gcm_cra_exit(struct crypto_tfm *tfm)
{
}
static struct aead_alg ccp_aes_gcm_defaults = {
.setkey = ccp_aes_gcm_setkey,
.setauthsize = ccp_aes_gcm_setauthsize,
.encrypt = ccp_aes_gcm_encrypt,
.decrypt = ccp_aes_gcm_decrypt,
.init = ccp_aes_gcm_cra_init,
.ivsize = GCM_AES_IV_SIZE,
.maxauthsize = AES_BLOCK_SIZE,
.base = {
.cra_flags = CRYPTO_ALG_ASYNC |
CRYPTO_ALG_ALLOCATES_MEMORY |
CRYPTO_ALG_KERN_DRIVER_ONLY |
CRYPTO_ALG_NEED_FALLBACK,
.cra_blocksize = AES_BLOCK_SIZE,
.cra_ctxsize = sizeof(struct ccp_ctx) + CRYPTO_DMA_PADDING,
.cra_priority = CCP_CRA_PRIORITY,
.cra_exit = ccp_aes_gcm_cra_exit,
.cra_module = THIS_MODULE,
},
};
struct ccp_aes_aead_def {
enum ccp_aes_mode mode;
unsigned int version;
const char *name;
const char *driver_name;
unsigned int blocksize;
unsigned int ivsize;
struct aead_alg *alg_defaults;
};
static struct ccp_aes_aead_def aes_aead_algs[] = {
{
.mode = CCP_AES_MODE_GHASH,
.version = CCP_VERSION(5, 0),
.name = "gcm(aes)",
.driver_name = "gcm-aes-ccp",
.blocksize = 1,
.ivsize = AES_BLOCK_SIZE,
.alg_defaults = &ccp_aes_gcm_defaults,
},
};
static int ccp_register_aes_aead(struct list_head *head,
const struct ccp_aes_aead_def *def)
{
struct ccp_crypto_aead *ccp_aead;
struct aead_alg *alg;
int ret;
ccp_aead = kzalloc_obj(*ccp_aead);
if (!ccp_aead)
return -ENOMEM;
INIT_LIST_HEAD(&ccp_aead->entry);
ccp_aead->mode = def->mode;
/* Copy the defaults and override as necessary */
alg = &ccp_aead->alg;
*alg = *def->alg_defaults;
strscpy(alg->base.cra_name, def->name);
strscpy(alg->base.cra_driver_name, def->driver_name);
alg->base.cra_blocksize = def->blocksize;
ret = crypto_register_aead(alg);
if (ret) {
pr_err("%s aead algorithm registration error (%d)\n",
alg->base.cra_name, ret);
kfree(ccp_aead);
return ret;
}
list_add(&ccp_aead->entry, head);
return 0;
}
int ccp_register_aes_aeads(struct list_head *head)
{
int i, ret;
unsigned int ccpversion = ccp_version();
for (i = 0; i < ARRAY_SIZE(aes_aead_algs); i++) {
if (aes_aead_algs[i].version > ccpversion)
continue;
ret = ccp_register_aes_aead(head, &aes_aead_algs[i]);
if (ret)
return ret;
}
return 0;
}
@@ -0,0 +1,288 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* AMD Cryptographic Coprocessor (CCP) AES XTS crypto API support
*
* Copyright (C) 2013,2017 Advanced Micro Devices, Inc.
*
* Author: Gary R Hook <gary.hook@amd.com>
* Author: Tom Lendacky <thomas.lendacky@amd.com>
*/
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/delay.h>
#include <linux/scatterlist.h>
#include <linux/string.h>
#include <crypto/aes.h>
#include <crypto/xts.h>
#include <crypto/internal/skcipher.h>
#include <crypto/scatterwalk.h>
#include "ccp-crypto.h"
struct ccp_aes_xts_def {
const char *name;
const char *drv_name;
};
static const struct ccp_aes_xts_def aes_xts_algs[] = {
{
.name = "xts(aes)",
.drv_name = "xts-aes-ccp",
},
};
struct ccp_unit_size_map {
unsigned int size;
u32 value;
};
static struct ccp_unit_size_map xts_unit_sizes[] = {
{
.size = 16,
.value = CCP_XTS_AES_UNIT_SIZE_16,
},
{
.size = 512,
.value = CCP_XTS_AES_UNIT_SIZE_512,
},
{
.size = 1024,
.value = CCP_XTS_AES_UNIT_SIZE_1024,
},
{
.size = 2048,
.value = CCP_XTS_AES_UNIT_SIZE_2048,
},
{
.size = 4096,
.value = CCP_XTS_AES_UNIT_SIZE_4096,
},
};
static int ccp_aes_xts_complete(struct crypto_async_request *async_req, int ret)
{
struct skcipher_request *req = skcipher_request_cast(async_req);
struct ccp_aes_req_ctx *rctx = skcipher_request_ctx_dma(req);
if (ret)
return ret;
memcpy(req->iv, rctx->iv, AES_BLOCK_SIZE);
return 0;
}
static int ccp_aes_xts_setkey(struct crypto_skcipher *tfm, const u8 *key,
unsigned int key_len)
{
struct ccp_ctx *ctx = crypto_skcipher_ctx_dma(tfm);
unsigned int ccpversion = ccp_version();
int ret;
ret = xts_verify_key(tfm, key, key_len);
if (ret)
return ret;
/* Version 3 devices support 128-bit keys; version 5 devices can
* accommodate 128- and 256-bit keys.
*/
switch (key_len) {
case AES_KEYSIZE_128 * 2:
memcpy(ctx->u.aes.key, key, key_len);
break;
case AES_KEYSIZE_256 * 2:
if (ccpversion > CCP_VERSION(3, 0))
memcpy(ctx->u.aes.key, key, key_len);
break;
}
ctx->u.aes.key_len = key_len / 2;
sg_init_one(&ctx->u.aes.key_sg, ctx->u.aes.key, key_len);
return crypto_skcipher_setkey(ctx->u.aes.tfm_skcipher, key, key_len);
}
static int ccp_aes_xts_crypt(struct skcipher_request *req,
unsigned int encrypt)
{
struct crypto_skcipher *tfm = crypto_skcipher_reqtfm(req);
struct ccp_ctx *ctx = crypto_skcipher_ctx_dma(tfm);
struct ccp_aes_req_ctx *rctx = skcipher_request_ctx_dma(req);
unsigned int ccpversion = ccp_version();
unsigned int fallback = 0;
unsigned int unit;
u32 unit_size;
int ret;
if (!ctx->u.aes.key_len)
return -EINVAL;
if (!req->iv)
return -EINVAL;
/* Check conditions under which the CCP can fulfill a request. The
* device can handle input plaintext of a length that is a multiple
* of the unit_size, bug the crypto implementation only supports
* the unit_size being equal to the input length. This limits the
* number of scenarios we can handle.
*/
unit_size = CCP_XTS_AES_UNIT_SIZE__LAST;
for (unit = 0; unit < ARRAY_SIZE(xts_unit_sizes); unit++) {
if (req->cryptlen == xts_unit_sizes[unit].size) {
unit_size = unit;
break;
}
}
/* The CCP has restrictions on block sizes. Also, a version 3 device
* only supports AES-128 operations; version 5 CCPs support both
* AES-128 and -256 operations.
*/
if (unit_size == CCP_XTS_AES_UNIT_SIZE__LAST)
fallback = 1;
if ((ccpversion < CCP_VERSION(5, 0)) &&
(ctx->u.aes.key_len != AES_KEYSIZE_128))
fallback = 1;
if ((ctx->u.aes.key_len != AES_KEYSIZE_128) &&
(ctx->u.aes.key_len != AES_KEYSIZE_256))
fallback = 1;
if (fallback) {
/* Use the fallback to process the request for any
* unsupported unit sizes or key sizes
*/
skcipher_request_set_tfm(&rctx->fallback_req,
ctx->u.aes.tfm_skcipher);
skcipher_request_set_callback(&rctx->fallback_req,
req->base.flags,
req->base.complete,
req->base.data);
skcipher_request_set_crypt(&rctx->fallback_req, req->src,
req->dst, req->cryptlen, req->iv);
ret = encrypt ? crypto_skcipher_encrypt(&rctx->fallback_req) :
crypto_skcipher_decrypt(&rctx->fallback_req);
return ret;
}
memcpy(rctx->iv, req->iv, AES_BLOCK_SIZE);
sg_init_one(&rctx->iv_sg, rctx->iv, AES_BLOCK_SIZE);
memset(&rctx->cmd, 0, sizeof(rctx->cmd));
INIT_LIST_HEAD(&rctx->cmd.entry);
rctx->cmd.engine = CCP_ENGINE_XTS_AES_128;
rctx->cmd.u.xts.type = CCP_AES_TYPE_128;
rctx->cmd.u.xts.action = (encrypt) ? CCP_AES_ACTION_ENCRYPT
: CCP_AES_ACTION_DECRYPT;
rctx->cmd.u.xts.unit_size = unit_size;
rctx->cmd.u.xts.key = &ctx->u.aes.key_sg;
rctx->cmd.u.xts.key_len = ctx->u.aes.key_len;
rctx->cmd.u.xts.iv = &rctx->iv_sg;
rctx->cmd.u.xts.iv_len = AES_BLOCK_SIZE;
rctx->cmd.u.xts.src = req->src;
rctx->cmd.u.xts.src_len = req->cryptlen;
rctx->cmd.u.xts.dst = req->dst;
ret = ccp_crypto_enqueue_request(&req->base, &rctx->cmd);
return ret;
}
static int ccp_aes_xts_encrypt(struct skcipher_request *req)
{
return ccp_aes_xts_crypt(req, 1);
}
static int ccp_aes_xts_decrypt(struct skcipher_request *req)
{
return ccp_aes_xts_crypt(req, 0);
}
static int ccp_aes_xts_init_tfm(struct crypto_skcipher *tfm)
{
struct ccp_ctx *ctx = crypto_skcipher_ctx_dma(tfm);
struct crypto_skcipher *fallback_tfm;
ctx->complete = ccp_aes_xts_complete;
ctx->u.aes.key_len = 0;
fallback_tfm = crypto_alloc_skcipher("xts(aes)", 0,
CRYPTO_ALG_NEED_FALLBACK);
if (IS_ERR(fallback_tfm)) {
pr_warn("could not load fallback driver xts(aes)\n");
return PTR_ERR(fallback_tfm);
}
ctx->u.aes.tfm_skcipher = fallback_tfm;
crypto_skcipher_set_reqsize_dma(tfm,
sizeof(struct ccp_aes_req_ctx) +
crypto_skcipher_reqsize(fallback_tfm));
return 0;
}
static void ccp_aes_xts_exit_tfm(struct crypto_skcipher *tfm)
{
struct ccp_ctx *ctx = crypto_skcipher_ctx_dma(tfm);
crypto_free_skcipher(ctx->u.aes.tfm_skcipher);
}
static int ccp_register_aes_xts_alg(struct list_head *head,
const struct ccp_aes_xts_def *def)
{
struct ccp_crypto_skcipher_alg *ccp_alg;
struct skcipher_alg *alg;
int ret;
ccp_alg = kzalloc_obj(*ccp_alg);
if (!ccp_alg)
return -ENOMEM;
INIT_LIST_HEAD(&ccp_alg->entry);
alg = &ccp_alg->alg;
strscpy(alg->base.cra_name, def->name);
strscpy(alg->base.cra_driver_name, def->drv_name);
alg->base.cra_flags = CRYPTO_ALG_ASYNC |
CRYPTO_ALG_ALLOCATES_MEMORY |
CRYPTO_ALG_KERN_DRIVER_ONLY |
CRYPTO_ALG_NEED_FALLBACK;
alg->base.cra_blocksize = AES_BLOCK_SIZE;
alg->base.cra_ctxsize = sizeof(struct ccp_ctx) +
crypto_dma_padding();
alg->base.cra_priority = CCP_CRA_PRIORITY;
alg->base.cra_module = THIS_MODULE;
alg->setkey = ccp_aes_xts_setkey;
alg->encrypt = ccp_aes_xts_encrypt;
alg->decrypt = ccp_aes_xts_decrypt;
alg->min_keysize = AES_MIN_KEY_SIZE * 2;
alg->max_keysize = AES_MAX_KEY_SIZE * 2;
alg->ivsize = AES_BLOCK_SIZE;
alg->init = ccp_aes_xts_init_tfm;
alg->exit = ccp_aes_xts_exit_tfm;
ret = crypto_register_skcipher(alg);
if (ret) {
pr_err("%s skcipher algorithm registration error (%d)\n",
alg->base.cra_name, ret);
kfree(ccp_alg);
return ret;
}
list_add(&ccp_alg->entry, head);
return 0;
}
int ccp_register_aes_xts_algs(struct list_head *head)
{
int i, ret;
for (i = 0; i < ARRAY_SIZE(aes_xts_algs); i++) {
ret = ccp_register_aes_xts_alg(head, &aes_xts_algs[i]);
if (ret)
return ret;
}
return 0;
}
@@ -0,0 +1,343 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* AMD Cryptographic Coprocessor (CCP) AES crypto API support
*
* Copyright (C) 2013-2019 Advanced Micro Devices, Inc.
*
* Author: Tom Lendacky <thomas.lendacky@amd.com>
*/
#include <crypto/aes.h>
#include <crypto/ctr.h>
#include <crypto/internal/skcipher.h>
#include <linux/err.h>
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/scatterlist.h>
#include <linux/slab.h>
#include <linux/string.h>
#include "ccp-crypto.h"
static int ccp_aes_complete(struct crypto_async_request *async_req, int ret)
{
struct skcipher_request *req = skcipher_request_cast(async_req);
struct ccp_ctx *ctx = crypto_skcipher_ctx_dma(
crypto_skcipher_reqtfm(req));
struct ccp_aes_req_ctx *rctx = skcipher_request_ctx_dma(req);
if (ret)
return ret;
if (ctx->u.aes.mode != CCP_AES_MODE_ECB) {
size_t ivsize = crypto_skcipher_ivsize(crypto_skcipher_reqtfm(req));
memcpy(req->iv, rctx->iv, ivsize);
}
return 0;
}
static int ccp_aes_setkey(struct crypto_skcipher *tfm, const u8 *key,
unsigned int key_len)
{
struct ccp_crypto_skcipher_alg *alg = ccp_crypto_skcipher_alg(tfm);
struct ccp_ctx *ctx = crypto_skcipher_ctx_dma(tfm);
switch (key_len) {
case AES_KEYSIZE_128:
ctx->u.aes.type = CCP_AES_TYPE_128;
break;
case AES_KEYSIZE_192:
ctx->u.aes.type = CCP_AES_TYPE_192;
break;
case AES_KEYSIZE_256:
ctx->u.aes.type = CCP_AES_TYPE_256;
break;
default:
return -EINVAL;
}
ctx->u.aes.mode = alg->mode;
ctx->u.aes.key_len = key_len;
memcpy(ctx->u.aes.key, key, key_len);
sg_init_one(&ctx->u.aes.key_sg, ctx->u.aes.key, key_len);
return 0;
}
static int ccp_aes_crypt(struct skcipher_request *req, bool encrypt)
{
struct crypto_skcipher *tfm = crypto_skcipher_reqtfm(req);
struct ccp_ctx *ctx = crypto_skcipher_ctx_dma(tfm);
struct ccp_aes_req_ctx *rctx = skcipher_request_ctx_dma(req);
struct scatterlist *iv_sg = NULL;
unsigned int iv_len = 0;
if (!ctx->u.aes.key_len)
return -EINVAL;
if (((ctx->u.aes.mode == CCP_AES_MODE_ECB) ||
(ctx->u.aes.mode == CCP_AES_MODE_CBC)) &&
(req->cryptlen & (AES_BLOCK_SIZE - 1)))
return -EINVAL;
if (ctx->u.aes.mode != CCP_AES_MODE_ECB) {
if (!req->iv)
return -EINVAL;
memcpy(rctx->iv, req->iv, AES_BLOCK_SIZE);
iv_sg = &rctx->iv_sg;
iv_len = AES_BLOCK_SIZE;
sg_init_one(iv_sg, rctx->iv, iv_len);
}
memset(&rctx->cmd, 0, sizeof(rctx->cmd));
INIT_LIST_HEAD(&rctx->cmd.entry);
rctx->cmd.engine = CCP_ENGINE_AES;
rctx->cmd.u.aes.type = ctx->u.aes.type;
rctx->cmd.u.aes.mode = ctx->u.aes.mode;
rctx->cmd.u.aes.action =
(encrypt) ? CCP_AES_ACTION_ENCRYPT : CCP_AES_ACTION_DECRYPT;
rctx->cmd.u.aes.key = &ctx->u.aes.key_sg;
rctx->cmd.u.aes.key_len = ctx->u.aes.key_len;
rctx->cmd.u.aes.iv = iv_sg;
rctx->cmd.u.aes.iv_len = iv_len;
rctx->cmd.u.aes.src = req->src;
rctx->cmd.u.aes.src_len = req->cryptlen;
rctx->cmd.u.aes.dst = req->dst;
return ccp_crypto_enqueue_request(&req->base, &rctx->cmd);
}
static int ccp_aes_encrypt(struct skcipher_request *req)
{
return ccp_aes_crypt(req, true);
}
static int ccp_aes_decrypt(struct skcipher_request *req)
{
return ccp_aes_crypt(req, false);
}
static int ccp_aes_init_tfm(struct crypto_skcipher *tfm)
{
struct ccp_ctx *ctx = crypto_skcipher_ctx_dma(tfm);
ctx->complete = ccp_aes_complete;
ctx->u.aes.key_len = 0;
crypto_skcipher_set_reqsize(tfm, sizeof(struct ccp_aes_req_ctx));
return 0;
}
static int ccp_aes_rfc3686_complete(struct crypto_async_request *async_req,
int ret)
{
struct skcipher_request *req = skcipher_request_cast(async_req);
struct ccp_aes_req_ctx *rctx = skcipher_request_ctx_dma(req);
/* Restore the original pointer */
req->iv = rctx->rfc3686_info;
return ccp_aes_complete(async_req, ret);
}
static int ccp_aes_rfc3686_setkey(struct crypto_skcipher *tfm, const u8 *key,
unsigned int key_len)
{
struct ccp_ctx *ctx = crypto_skcipher_ctx_dma(tfm);
if (key_len < CTR_RFC3686_NONCE_SIZE)
return -EINVAL;
key_len -= CTR_RFC3686_NONCE_SIZE;
memcpy(ctx->u.aes.nonce, key + key_len, CTR_RFC3686_NONCE_SIZE);
return ccp_aes_setkey(tfm, key, key_len);
}
static int ccp_aes_rfc3686_crypt(struct skcipher_request *req, bool encrypt)
{
struct crypto_skcipher *tfm = crypto_skcipher_reqtfm(req);
struct ccp_ctx *ctx = crypto_skcipher_ctx_dma(tfm);
struct ccp_aes_req_ctx *rctx = skcipher_request_ctx_dma(req);
u8 *iv;
/* Initialize the CTR block */
iv = rctx->rfc3686_iv;
memcpy(iv, ctx->u.aes.nonce, CTR_RFC3686_NONCE_SIZE);
iv += CTR_RFC3686_NONCE_SIZE;
memcpy(iv, req->iv, CTR_RFC3686_IV_SIZE);
iv += CTR_RFC3686_IV_SIZE;
*(__be32 *)iv = cpu_to_be32(1);
/* Point to the new IV */
rctx->rfc3686_info = req->iv;
req->iv = rctx->rfc3686_iv;
return ccp_aes_crypt(req, encrypt);
}
static int ccp_aes_rfc3686_encrypt(struct skcipher_request *req)
{
return ccp_aes_rfc3686_crypt(req, true);
}
static int ccp_aes_rfc3686_decrypt(struct skcipher_request *req)
{
return ccp_aes_rfc3686_crypt(req, false);
}
static int ccp_aes_rfc3686_init_tfm(struct crypto_skcipher *tfm)
{
struct ccp_ctx *ctx = crypto_skcipher_ctx_dma(tfm);
ctx->complete = ccp_aes_rfc3686_complete;
ctx->u.aes.key_len = 0;
crypto_skcipher_set_reqsize_dma(tfm, sizeof(struct ccp_aes_req_ctx));
return 0;
}
static const struct skcipher_alg ccp_aes_defaults = {
.setkey = ccp_aes_setkey,
.encrypt = ccp_aes_encrypt,
.decrypt = ccp_aes_decrypt,
.min_keysize = AES_MIN_KEY_SIZE,
.max_keysize = AES_MAX_KEY_SIZE,
.init = ccp_aes_init_tfm,
.base.cra_flags = CRYPTO_ALG_ASYNC |
CRYPTO_ALG_ALLOCATES_MEMORY |
CRYPTO_ALG_KERN_DRIVER_ONLY |
CRYPTO_ALG_NEED_FALLBACK,
.base.cra_blocksize = AES_BLOCK_SIZE,
.base.cra_ctxsize = sizeof(struct ccp_ctx) + CRYPTO_DMA_PADDING,
.base.cra_priority = CCP_CRA_PRIORITY,
.base.cra_module = THIS_MODULE,
};
static const struct skcipher_alg ccp_aes_rfc3686_defaults = {
.setkey = ccp_aes_rfc3686_setkey,
.encrypt = ccp_aes_rfc3686_encrypt,
.decrypt = ccp_aes_rfc3686_decrypt,
.min_keysize = AES_MIN_KEY_SIZE + CTR_RFC3686_NONCE_SIZE,
.max_keysize = AES_MAX_KEY_SIZE + CTR_RFC3686_NONCE_SIZE,
.init = ccp_aes_rfc3686_init_tfm,
.base.cra_flags = CRYPTO_ALG_ASYNC |
CRYPTO_ALG_ALLOCATES_MEMORY |
CRYPTO_ALG_KERN_DRIVER_ONLY |
CRYPTO_ALG_NEED_FALLBACK,
.base.cra_blocksize = CTR_RFC3686_BLOCK_SIZE,
.base.cra_ctxsize = sizeof(struct ccp_ctx) + CRYPTO_DMA_PADDING,
.base.cra_priority = CCP_CRA_PRIORITY,
.base.cra_module = THIS_MODULE,
};
struct ccp_aes_def {
enum ccp_aes_mode mode;
unsigned int version;
const char *name;
const char *driver_name;
unsigned int blocksize;
unsigned int ivsize;
const struct skcipher_alg *alg_defaults;
};
static struct ccp_aes_def aes_algs[] = {
{
.mode = CCP_AES_MODE_ECB,
.version = CCP_VERSION(3, 0),
.name = "ecb(aes)",
.driver_name = "ecb-aes-ccp",
.blocksize = AES_BLOCK_SIZE,
.ivsize = 0,
.alg_defaults = &ccp_aes_defaults,
},
{
.mode = CCP_AES_MODE_CBC,
.version = CCP_VERSION(3, 0),
.name = "cbc(aes)",
.driver_name = "cbc-aes-ccp",
.blocksize = AES_BLOCK_SIZE,
.ivsize = AES_BLOCK_SIZE,
.alg_defaults = &ccp_aes_defaults,
},
{
.mode = CCP_AES_MODE_CTR,
.version = CCP_VERSION(3, 0),
.name = "ctr(aes)",
.driver_name = "ctr-aes-ccp",
.blocksize = 1,
.ivsize = AES_BLOCK_SIZE,
.alg_defaults = &ccp_aes_defaults,
},
{
.mode = CCP_AES_MODE_CTR,
.version = CCP_VERSION(3, 0),
.name = "rfc3686(ctr(aes))",
.driver_name = "rfc3686-ctr-aes-ccp",
.blocksize = 1,
.ivsize = CTR_RFC3686_IV_SIZE,
.alg_defaults = &ccp_aes_rfc3686_defaults,
},
};
static int ccp_register_aes_alg(struct list_head *head,
const struct ccp_aes_def *def)
{
struct ccp_crypto_skcipher_alg *ccp_alg;
struct skcipher_alg *alg;
int ret;
ccp_alg = kzalloc_obj(*ccp_alg);
if (!ccp_alg)
return -ENOMEM;
INIT_LIST_HEAD(&ccp_alg->entry);
ccp_alg->mode = def->mode;
/* Copy the defaults and override as necessary */
alg = &ccp_alg->alg;
*alg = *def->alg_defaults;
strscpy(alg->base.cra_name, def->name);
strscpy(alg->base.cra_driver_name, def->driver_name);
alg->base.cra_blocksize = def->blocksize;
alg->ivsize = def->ivsize;
ret = crypto_register_skcipher(alg);
if (ret) {
pr_err("%s skcipher algorithm registration error (%d)\n",
alg->base.cra_name, ret);
kfree(ccp_alg);
return ret;
}
list_add(&ccp_alg->entry, head);
return 0;
}
int ccp_register_aes_algs(struct list_head *head)
{
int i, ret;
unsigned int ccpversion = ccp_version();
for (i = 0; i < ARRAY_SIZE(aes_algs); i++) {
if (aes_algs[i].version > ccpversion)
continue;
ret = ccp_register_aes_alg(head, &aes_algs[i]);
if (ret)
return ret;
}
return 0;
}
@@ -0,0 +1,228 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* AMD Cryptographic Coprocessor (CCP) DES3 crypto API support
*
* Copyright (C) 2016,2017 Advanced Micro Devices, Inc.
*
* Author: Gary R Hook <ghook@amd.com>
*/
#include <crypto/internal/des.h>
#include <crypto/internal/skcipher.h>
#include <linux/err.h>
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/scatterlist.h>
#include <linux/slab.h>
#include <linux/string.h>
#include "ccp-crypto.h"
static int ccp_des3_complete(struct crypto_async_request *async_req, int ret)
{
struct skcipher_request *req = skcipher_request_cast(async_req);
struct ccp_ctx *ctx = crypto_skcipher_ctx_dma(
crypto_skcipher_reqtfm(req));
struct ccp_des3_req_ctx *rctx = skcipher_request_ctx_dma(req);
if (ret)
return ret;
if (ctx->u.des3.mode != CCP_DES3_MODE_ECB)
memcpy(req->iv, rctx->iv, DES3_EDE_BLOCK_SIZE);
return 0;
}
static int ccp_des3_setkey(struct crypto_skcipher *tfm, const u8 *key,
unsigned int key_len)
{
struct ccp_crypto_skcipher_alg *alg = ccp_crypto_skcipher_alg(tfm);
struct ccp_ctx *ctx = crypto_skcipher_ctx_dma(tfm);
int err;
err = verify_skcipher_des3_key(tfm, key);
if (err)
return err;
/* It's not clear that there is any support for a keysize of 112.
* If needed, the caller should make K1 == K3
*/
ctx->u.des3.type = CCP_DES3_TYPE_168;
ctx->u.des3.mode = alg->mode;
ctx->u.des3.key_len = key_len;
memcpy(ctx->u.des3.key, key, key_len);
sg_init_one(&ctx->u.des3.key_sg, ctx->u.des3.key, key_len);
return 0;
}
static int ccp_des3_crypt(struct skcipher_request *req, bool encrypt)
{
struct crypto_skcipher *tfm = crypto_skcipher_reqtfm(req);
struct ccp_ctx *ctx = crypto_skcipher_ctx_dma(tfm);
struct ccp_des3_req_ctx *rctx = skcipher_request_ctx_dma(req);
struct scatterlist *iv_sg = NULL;
unsigned int iv_len = 0;
if (!ctx->u.des3.key_len)
return -EINVAL;
if (((ctx->u.des3.mode == CCP_DES3_MODE_ECB) ||
(ctx->u.des3.mode == CCP_DES3_MODE_CBC)) &&
(req->cryptlen & (DES3_EDE_BLOCK_SIZE - 1)))
return -EINVAL;
if (ctx->u.des3.mode != CCP_DES3_MODE_ECB) {
if (!req->iv)
return -EINVAL;
memcpy(rctx->iv, req->iv, DES3_EDE_BLOCK_SIZE);
iv_sg = &rctx->iv_sg;
iv_len = DES3_EDE_BLOCK_SIZE;
sg_init_one(iv_sg, rctx->iv, iv_len);
}
memset(&rctx->cmd, 0, sizeof(rctx->cmd));
INIT_LIST_HEAD(&rctx->cmd.entry);
rctx->cmd.engine = CCP_ENGINE_DES3;
rctx->cmd.u.des3.type = ctx->u.des3.type;
rctx->cmd.u.des3.mode = ctx->u.des3.mode;
rctx->cmd.u.des3.action = (encrypt)
? CCP_DES3_ACTION_ENCRYPT
: CCP_DES3_ACTION_DECRYPT;
rctx->cmd.u.des3.key = &ctx->u.des3.key_sg;
rctx->cmd.u.des3.key_len = ctx->u.des3.key_len;
rctx->cmd.u.des3.iv = iv_sg;
rctx->cmd.u.des3.iv_len = iv_len;
rctx->cmd.u.des3.src = req->src;
rctx->cmd.u.des3.src_len = req->cryptlen;
rctx->cmd.u.des3.dst = req->dst;
return ccp_crypto_enqueue_request(&req->base, &rctx->cmd);
}
static int ccp_des3_encrypt(struct skcipher_request *req)
{
return ccp_des3_crypt(req, true);
}
static int ccp_des3_decrypt(struct skcipher_request *req)
{
return ccp_des3_crypt(req, false);
}
static int ccp_des3_init_tfm(struct crypto_skcipher *tfm)
{
struct ccp_ctx *ctx = crypto_skcipher_ctx_dma(tfm);
ctx->complete = ccp_des3_complete;
ctx->u.des3.key_len = 0;
crypto_skcipher_set_reqsize_dma(tfm, sizeof(struct ccp_des3_req_ctx));
return 0;
}
static const struct skcipher_alg ccp_des3_defaults = {
.setkey = ccp_des3_setkey,
.encrypt = ccp_des3_encrypt,
.decrypt = ccp_des3_decrypt,
.min_keysize = DES3_EDE_KEY_SIZE,
.max_keysize = DES3_EDE_KEY_SIZE,
.init = ccp_des3_init_tfm,
.base.cra_flags = CRYPTO_ALG_ASYNC |
CRYPTO_ALG_ALLOCATES_MEMORY |
CRYPTO_ALG_KERN_DRIVER_ONLY |
CRYPTO_ALG_NEED_FALLBACK,
.base.cra_blocksize = DES3_EDE_BLOCK_SIZE,
.base.cra_ctxsize = sizeof(struct ccp_ctx) + CRYPTO_DMA_PADDING,
.base.cra_priority = CCP_CRA_PRIORITY,
.base.cra_module = THIS_MODULE,
};
struct ccp_des3_def {
enum ccp_des3_mode mode;
unsigned int version;
const char *name;
const char *driver_name;
unsigned int blocksize;
unsigned int ivsize;
const struct skcipher_alg *alg_defaults;
};
static const struct ccp_des3_def des3_algs[] = {
{
.mode = CCP_DES3_MODE_ECB,
.version = CCP_VERSION(5, 0),
.name = "ecb(des3_ede)",
.driver_name = "ecb-des3-ccp",
.blocksize = DES3_EDE_BLOCK_SIZE,
.ivsize = 0,
.alg_defaults = &ccp_des3_defaults,
},
{
.mode = CCP_DES3_MODE_CBC,
.version = CCP_VERSION(5, 0),
.name = "cbc(des3_ede)",
.driver_name = "cbc-des3-ccp",
.blocksize = DES3_EDE_BLOCK_SIZE,
.ivsize = DES3_EDE_BLOCK_SIZE,
.alg_defaults = &ccp_des3_defaults,
},
};
static int ccp_register_des3_alg(struct list_head *head,
const struct ccp_des3_def *def)
{
struct ccp_crypto_skcipher_alg *ccp_alg;
struct skcipher_alg *alg;
int ret;
ccp_alg = kzalloc_obj(*ccp_alg);
if (!ccp_alg)
return -ENOMEM;
INIT_LIST_HEAD(&ccp_alg->entry);
ccp_alg->mode = def->mode;
/* Copy the defaults and override as necessary */
alg = &ccp_alg->alg;
*alg = *def->alg_defaults;
strscpy(alg->base.cra_name, def->name);
strscpy(alg->base.cra_driver_name, def->driver_name);
alg->base.cra_blocksize = def->blocksize;
alg->ivsize = def->ivsize;
ret = crypto_register_skcipher(alg);
if (ret) {
pr_err("%s skcipher algorithm registration error (%d)\n",
alg->base.cra_name, ret);
kfree(ccp_alg);
return ret;
}
list_add(&ccp_alg->entry, head);
return 0;
}
int ccp_register_des3_algs(struct list_head *head)
{
int i, ret;
unsigned int ccpversion = ccp_version();
for (i = 0; i < ARRAY_SIZE(des3_algs); i++) {
if (des3_algs[i].version > ccpversion)
continue;
ret = ccp_register_des3_alg(head, &des3_algs[i]);
if (ret)
return ret;
}
return 0;
}
@@ -0,0 +1,426 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* AMD Cryptographic Coprocessor (CCP) crypto API support
*
* Copyright (C) 2013,2017 Advanced Micro Devices, Inc.
*
* Author: Tom Lendacky <thomas.lendacky@amd.com>
*/
#include <crypto/internal/akcipher.h>
#include <crypto/internal/hash.h>
#include <crypto/internal/skcipher.h>
#include <linux/ccp.h>
#include <linux/err.h>
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/scatterlist.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include "ccp-crypto.h"
MODULE_AUTHOR("Tom Lendacky <thomas.lendacky@amd.com>");
MODULE_LICENSE("GPL");
MODULE_VERSION("1.0.0");
MODULE_DESCRIPTION("AMD Cryptographic Coprocessor crypto API support");
static unsigned int aes_disable;
module_param(aes_disable, uint, 0444);
MODULE_PARM_DESC(aes_disable, "Disable use of AES - any non-zero value");
static unsigned int sha_disable;
module_param(sha_disable, uint, 0444);
MODULE_PARM_DESC(sha_disable, "Disable use of SHA - any non-zero value");
static unsigned int des3_disable;
module_param(des3_disable, uint, 0444);
MODULE_PARM_DESC(des3_disable, "Disable use of 3DES - any non-zero value");
static unsigned int rsa_disable;
module_param(rsa_disable, uint, 0444);
MODULE_PARM_DESC(rsa_disable, "Disable use of RSA - any non-zero value");
/* List heads for the supported algorithms */
static LIST_HEAD(hash_algs);
static LIST_HEAD(skcipher_algs);
static LIST_HEAD(aead_algs);
static LIST_HEAD(akcipher_algs);
/* For any tfm, requests for that tfm must be returned on the order
* received. With multiple queues available, the CCP can process more
* than one cmd at a time. Therefore we must maintain a cmd list to insure
* the proper ordering of requests on a given tfm.
*/
struct ccp_crypto_queue {
struct list_head cmds;
struct list_head *backlog;
unsigned int cmd_count;
};
#define CCP_CRYPTO_MAX_QLEN 100
static struct ccp_crypto_queue req_queue;
static DEFINE_SPINLOCK(req_queue_lock);
struct ccp_crypto_cmd {
struct list_head entry;
struct ccp_cmd *cmd;
/* Save the crypto_tfm and crypto_async_request addresses
* separately to avoid any reference to a possibly invalid
* crypto_async_request structure after invoking the request
* callback
*/
struct crypto_async_request *req;
struct crypto_tfm *tfm;
/* Used for held command processing to determine state */
int ret;
};
static inline bool ccp_crypto_success(int err)
{
if (err && (err != -EINPROGRESS) && (err != -EBUSY))
return false;
return true;
}
static struct ccp_crypto_cmd *ccp_crypto_cmd_complete(
struct ccp_crypto_cmd *crypto_cmd, struct ccp_crypto_cmd **backlog)
{
struct ccp_crypto_cmd *held = NULL, *tmp;
unsigned long flags;
*backlog = NULL;
spin_lock_irqsave(&req_queue_lock, flags);
/* Held cmds will be after the current cmd in the queue so start
* searching for a cmd with a matching tfm for submission.
*/
tmp = crypto_cmd;
list_for_each_entry_continue(tmp, &req_queue.cmds, entry) {
if (crypto_cmd->tfm != tmp->tfm)
continue;
held = tmp;
break;
}
/* Process the backlog:
* Because cmds can be executed from any point in the cmd list
* special precautions have to be taken when handling the backlog.
*/
if (req_queue.backlog != &req_queue.cmds) {
/* Skip over this cmd if it is the next backlog cmd */
if (req_queue.backlog == &crypto_cmd->entry)
req_queue.backlog = crypto_cmd->entry.next;
*backlog = container_of(req_queue.backlog,
struct ccp_crypto_cmd, entry);
req_queue.backlog = req_queue.backlog->next;
/* Skip over this cmd if it is now the next backlog cmd */
if (req_queue.backlog == &crypto_cmd->entry)
req_queue.backlog = crypto_cmd->entry.next;
}
/* Remove the cmd entry from the list of cmds */
req_queue.cmd_count--;
list_del(&crypto_cmd->entry);
spin_unlock_irqrestore(&req_queue_lock, flags);
return held;
}
static void ccp_crypto_complete(void *data, int err)
{
struct ccp_crypto_cmd *crypto_cmd = data;
struct ccp_crypto_cmd *held, *next, *backlog;
struct crypto_async_request *req = crypto_cmd->req;
struct ccp_ctx *ctx = crypto_tfm_ctx_dma(req->tfm);
int ret;
if (err == -EINPROGRESS) {
/* Only propagate the -EINPROGRESS if necessary */
if (crypto_cmd->ret == -EBUSY) {
crypto_cmd->ret = -EINPROGRESS;
crypto_request_complete(req, -EINPROGRESS);
}
return;
}
/* Operation has completed - update the queue before invoking
* the completion callbacks and retrieve the next cmd (cmd with
* a matching tfm) that can be submitted to the CCP.
*/
held = ccp_crypto_cmd_complete(crypto_cmd, &backlog);
if (backlog) {
backlog->ret = -EINPROGRESS;
crypto_request_complete(backlog->req, -EINPROGRESS);
}
/* Transition the state from -EBUSY to -EINPROGRESS first */
if (crypto_cmd->ret == -EBUSY)
crypto_request_complete(req, -EINPROGRESS);
/* Completion callbacks */
ret = err;
if (ctx->complete)
ret = ctx->complete(req, ret);
crypto_request_complete(req, ret);
/* Submit the next cmd */
while (held) {
/* Since we have already queued the cmd, we must indicate that
* we can backlog so as not to "lose" this request.
*/
held->cmd->flags |= CCP_CMD_MAY_BACKLOG;
ret = ccp_enqueue_cmd(held->cmd);
if (ccp_crypto_success(ret))
break;
/* Error occurred, report it and get the next entry */
ctx = crypto_tfm_ctx_dma(held->req->tfm);
if (ctx->complete)
ret = ctx->complete(held->req, ret);
crypto_request_complete(held->req, ret);
next = ccp_crypto_cmd_complete(held, &backlog);
if (backlog) {
backlog->ret = -EINPROGRESS;
crypto_request_complete(backlog->req, -EINPROGRESS);
}
kfree(held);
held = next;
}
kfree(crypto_cmd);
}
static int ccp_crypto_enqueue_cmd(struct ccp_crypto_cmd *crypto_cmd)
{
struct ccp_crypto_cmd *active = NULL, *tmp;
unsigned long flags;
bool free_cmd = true;
int ret;
spin_lock_irqsave(&req_queue_lock, flags);
/* Check if the cmd can/should be queued */
if (req_queue.cmd_count >= CCP_CRYPTO_MAX_QLEN) {
if (!(crypto_cmd->cmd->flags & CCP_CMD_MAY_BACKLOG)) {
ret = -ENOSPC;
goto e_lock;
}
}
/* Look for an entry with the same tfm. If there is a cmd
* with the same tfm in the list then the current cmd cannot
* be submitted to the CCP yet.
*/
list_for_each_entry(tmp, &req_queue.cmds, entry) {
if (crypto_cmd->tfm != tmp->tfm)
continue;
active = tmp;
break;
}
ret = -EINPROGRESS;
if (!active) {
ret = ccp_enqueue_cmd(crypto_cmd->cmd);
if (!ccp_crypto_success(ret))
goto e_lock; /* Error, don't queue it */
}
if (req_queue.cmd_count >= CCP_CRYPTO_MAX_QLEN) {
ret = -EBUSY;
if (req_queue.backlog == &req_queue.cmds)
req_queue.backlog = &crypto_cmd->entry;
}
crypto_cmd->ret = ret;
req_queue.cmd_count++;
list_add_tail(&crypto_cmd->entry, &req_queue.cmds);
free_cmd = false;
e_lock:
spin_unlock_irqrestore(&req_queue_lock, flags);
if (free_cmd)
kfree(crypto_cmd);
return ret;
}
/**
* ccp_crypto_enqueue_request - queue an crypto async request for processing
* by the CCP
*
* @req: crypto_async_request struct to be processed
* @cmd: ccp_cmd struct to be sent to the CCP
*/
int ccp_crypto_enqueue_request(struct crypto_async_request *req,
struct ccp_cmd *cmd)
{
struct ccp_crypto_cmd *crypto_cmd;
gfp_t gfp;
gfp = req->flags & CRYPTO_TFM_REQ_MAY_SLEEP ? GFP_KERNEL : GFP_ATOMIC;
crypto_cmd = kzalloc_obj(*crypto_cmd, gfp);
if (!crypto_cmd)
return -ENOMEM;
/* The tfm pointer must be saved and not referenced from the
* crypto_async_request (req) pointer because it is used after
* completion callback for the request and the req pointer
* might not be valid anymore.
*/
crypto_cmd->cmd = cmd;
crypto_cmd->req = req;
crypto_cmd->tfm = req->tfm;
cmd->callback = ccp_crypto_complete;
cmd->data = crypto_cmd;
if (req->flags & CRYPTO_TFM_REQ_MAY_BACKLOG)
cmd->flags |= CCP_CMD_MAY_BACKLOG;
else
cmd->flags &= ~CCP_CMD_MAY_BACKLOG;
return ccp_crypto_enqueue_cmd(crypto_cmd);
}
struct scatterlist *ccp_crypto_sg_table_add(struct sg_table *table,
struct scatterlist *sg_add)
{
struct scatterlist *sg, *sg_last = NULL;
for (sg = table->sgl; sg; sg = sg_next(sg))
if (!sg_page(sg))
break;
if (WARN_ON(!sg))
return NULL;
for (; sg && sg_add; sg = sg_next(sg), sg_add = sg_next(sg_add)) {
sg_set_page(sg, sg_page(sg_add), sg_add->length,
sg_add->offset);
sg_last = sg;
}
if (WARN_ON(sg_add))
return NULL;
return sg_last;
}
static int ccp_register_algs(void)
{
int ret;
if (!aes_disable) {
ret = ccp_register_aes_algs(&skcipher_algs);
if (ret)
return ret;
ret = ccp_register_aes_cmac_algs(&hash_algs);
if (ret)
return ret;
ret = ccp_register_aes_xts_algs(&skcipher_algs);
if (ret)
return ret;
ret = ccp_register_aes_aeads(&aead_algs);
if (ret)
return ret;
}
if (!des3_disable) {
ret = ccp_register_des3_algs(&skcipher_algs);
if (ret)
return ret;
}
if (!sha_disable) {
ret = ccp_register_sha_algs(&hash_algs);
if (ret)
return ret;
}
if (!rsa_disable) {
ret = ccp_register_rsa_algs(&akcipher_algs);
if (ret)
return ret;
}
return 0;
}
static void ccp_unregister_algs(void)
{
struct ccp_crypto_ahash_alg *ahash_alg, *ahash_tmp;
struct ccp_crypto_skcipher_alg *ablk_alg, *ablk_tmp;
struct ccp_crypto_aead *aead_alg, *aead_tmp;
struct ccp_crypto_akcipher_alg *akc_alg, *akc_tmp;
list_for_each_entry_safe(ahash_alg, ahash_tmp, &hash_algs, entry) {
crypto_unregister_ahash(&ahash_alg->alg);
list_del(&ahash_alg->entry);
kfree(ahash_alg);
}
list_for_each_entry_safe(ablk_alg, ablk_tmp, &skcipher_algs, entry) {
crypto_unregister_skcipher(&ablk_alg->alg);
list_del(&ablk_alg->entry);
kfree(ablk_alg);
}
list_for_each_entry_safe(aead_alg, aead_tmp, &aead_algs, entry) {
crypto_unregister_aead(&aead_alg->alg);
list_del(&aead_alg->entry);
kfree(aead_alg);
}
list_for_each_entry_safe(akc_alg, akc_tmp, &akcipher_algs, entry) {
crypto_unregister_akcipher(&akc_alg->alg);
list_del(&akc_alg->entry);
kfree(akc_alg);
}
}
static int __init ccp_crypto_init(void)
{
int ret;
ret = ccp_present();
if (ret) {
pr_err("Cannot load: there are no available CCPs\n");
return ret;
}
INIT_LIST_HEAD(&req_queue.cmds);
req_queue.backlog = &req_queue.cmds;
req_queue.cmd_count = 0;
ret = ccp_register_algs();
if (ret)
ccp_unregister_algs();
return ret;
}
static void __exit ccp_crypto_exit(void)
{
ccp_unregister_algs();
}
module_init(ccp_crypto_init);
module_exit(ccp_crypto_exit);
@@ -0,0 +1,293 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* AMD Cryptographic Coprocessor (CCP) RSA crypto API support
*
* Copyright (C) 2017 Advanced Micro Devices, Inc.
*
* Author: Gary R Hook <gary.hook@amd.com>
*/
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/scatterlist.h>
#include <linux/string.h>
#include <linux/crypto.h>
#include <crypto/algapi.h>
#include <crypto/internal/rsa.h>
#include <crypto/internal/akcipher.h>
#include <crypto/akcipher.h>
#include <crypto/scatterwalk.h>
#include "ccp-crypto.h"
static inline struct akcipher_request *akcipher_request_cast(
struct crypto_async_request *req)
{
return container_of(req, struct akcipher_request, base);
}
static inline int ccp_copy_and_save_keypart(u8 **kpbuf, unsigned int *kplen,
const u8 *buf, size_t sz)
{
int nskip;
for (nskip = 0; nskip < sz; nskip++)
if (buf[nskip])
break;
*kplen = sz - nskip;
*kpbuf = kmemdup(buf + nskip, *kplen, GFP_KERNEL);
if (!*kpbuf)
return -ENOMEM;
return 0;
}
static int ccp_rsa_complete(struct crypto_async_request *async_req, int ret)
{
struct akcipher_request *req = akcipher_request_cast(async_req);
struct ccp_rsa_req_ctx *rctx = akcipher_request_ctx_dma(req);
if (ret)
return ret;
req->dst_len = rctx->cmd.u.rsa.key_size >> 3;
return 0;
}
static unsigned int ccp_rsa_maxsize(struct crypto_akcipher *tfm)
{
struct ccp_ctx *ctx = akcipher_tfm_ctx_dma(tfm);
return ctx->u.rsa.n_len;
}
static int ccp_rsa_crypt(struct akcipher_request *req, bool encrypt)
{
struct crypto_akcipher *tfm = crypto_akcipher_reqtfm(req);
struct ccp_ctx *ctx = akcipher_tfm_ctx_dma(tfm);
struct ccp_rsa_req_ctx *rctx = akcipher_request_ctx_dma(req);
int ret = 0;
memset(&rctx->cmd, 0, sizeof(rctx->cmd));
INIT_LIST_HEAD(&rctx->cmd.entry);
rctx->cmd.engine = CCP_ENGINE_RSA;
rctx->cmd.u.rsa.key_size = ctx->u.rsa.key_len; /* in bits */
if (encrypt) {
rctx->cmd.u.rsa.exp = &ctx->u.rsa.e_sg;
rctx->cmd.u.rsa.exp_len = ctx->u.rsa.e_len;
} else {
rctx->cmd.u.rsa.exp = &ctx->u.rsa.d_sg;
rctx->cmd.u.rsa.exp_len = ctx->u.rsa.d_len;
}
rctx->cmd.u.rsa.mod = &ctx->u.rsa.n_sg;
rctx->cmd.u.rsa.mod_len = ctx->u.rsa.n_len;
rctx->cmd.u.rsa.src = req->src;
rctx->cmd.u.rsa.src_len = req->src_len;
rctx->cmd.u.rsa.dst = req->dst;
ret = ccp_crypto_enqueue_request(&req->base, &rctx->cmd);
return ret;
}
static int ccp_rsa_encrypt(struct akcipher_request *req)
{
return ccp_rsa_crypt(req, true);
}
static int ccp_rsa_decrypt(struct akcipher_request *req)
{
return ccp_rsa_crypt(req, false);
}
static int ccp_check_key_length(unsigned int len)
{
/* In bits */
if (len < 8 || len > 4096)
return -EINVAL;
return 0;
}
static void ccp_rsa_free_key_bufs(struct ccp_ctx *ctx)
{
/* Clean up old key data */
kfree_sensitive(ctx->u.rsa.e_buf);
ctx->u.rsa.e_buf = NULL;
ctx->u.rsa.e_len = 0;
kfree_sensitive(ctx->u.rsa.n_buf);
ctx->u.rsa.n_buf = NULL;
ctx->u.rsa.n_len = 0;
kfree_sensitive(ctx->u.rsa.d_buf);
ctx->u.rsa.d_buf = NULL;
ctx->u.rsa.d_len = 0;
}
static int ccp_rsa_setkey(struct crypto_akcipher *tfm, const void *key,
unsigned int keylen, bool private)
{
struct ccp_ctx *ctx = akcipher_tfm_ctx_dma(tfm);
struct rsa_key raw_key;
int ret;
ccp_rsa_free_key_bufs(ctx);
memset(&raw_key, 0, sizeof(raw_key));
/* Code borrowed from crypto/rsa.c */
if (private)
ret = rsa_parse_priv_key(&raw_key, key, keylen);
else
ret = rsa_parse_pub_key(&raw_key, key, keylen);
if (ret)
goto n_key;
ret = ccp_copy_and_save_keypart(&ctx->u.rsa.n_buf, &ctx->u.rsa.n_len,
raw_key.n, raw_key.n_sz);
if (ret)
goto key_err;
sg_init_one(&ctx->u.rsa.n_sg, ctx->u.rsa.n_buf, ctx->u.rsa.n_len);
ctx->u.rsa.key_len = ctx->u.rsa.n_len << 3; /* convert to bits */
if (ccp_check_key_length(ctx->u.rsa.key_len)) {
ret = -EINVAL;
goto key_err;
}
ret = ccp_copy_and_save_keypart(&ctx->u.rsa.e_buf, &ctx->u.rsa.e_len,
raw_key.e, raw_key.e_sz);
if (ret)
goto key_err;
sg_init_one(&ctx->u.rsa.e_sg, ctx->u.rsa.e_buf, ctx->u.rsa.e_len);
if (private) {
ret = ccp_copy_and_save_keypart(&ctx->u.rsa.d_buf,
&ctx->u.rsa.d_len,
raw_key.d, raw_key.d_sz);
if (ret)
goto key_err;
sg_init_one(&ctx->u.rsa.d_sg,
ctx->u.rsa.d_buf, ctx->u.rsa.d_len);
}
return 0;
key_err:
ccp_rsa_free_key_bufs(ctx);
n_key:
return ret;
}
static int ccp_rsa_setprivkey(struct crypto_akcipher *tfm, const void *key,
unsigned int keylen)
{
return ccp_rsa_setkey(tfm, key, keylen, true);
}
static int ccp_rsa_setpubkey(struct crypto_akcipher *tfm, const void *key,
unsigned int keylen)
{
return ccp_rsa_setkey(tfm, key, keylen, false);
}
static int ccp_rsa_init_tfm(struct crypto_akcipher *tfm)
{
struct ccp_ctx *ctx = akcipher_tfm_ctx_dma(tfm);
akcipher_set_reqsize_dma(tfm, sizeof(struct ccp_rsa_req_ctx));
ctx->complete = ccp_rsa_complete;
return 0;
}
static void ccp_rsa_exit_tfm(struct crypto_akcipher *tfm)
{
struct ccp_ctx *ctx = akcipher_tfm_ctx_dma(tfm);
ccp_rsa_free_key_bufs(ctx);
}
static struct akcipher_alg ccp_rsa_defaults = {
.encrypt = ccp_rsa_encrypt,
.decrypt = ccp_rsa_decrypt,
.set_pub_key = ccp_rsa_setpubkey,
.set_priv_key = ccp_rsa_setprivkey,
.max_size = ccp_rsa_maxsize,
.init = ccp_rsa_init_tfm,
.exit = ccp_rsa_exit_tfm,
.base = {
.cra_name = "rsa",
.cra_driver_name = "rsa-ccp",
.cra_priority = CCP_CRA_PRIORITY,
.cra_module = THIS_MODULE,
.cra_ctxsize = 2 * sizeof(struct ccp_ctx) + CRYPTO_DMA_PADDING,
},
};
struct ccp_rsa_def {
unsigned int version;
const char *name;
const char *driver_name;
unsigned int reqsize;
struct akcipher_alg *alg_defaults;
};
static struct ccp_rsa_def rsa_algs[] = {
{
.version = CCP_VERSION(3, 0),
.name = "rsa",
.driver_name = "rsa-ccp",
.reqsize = sizeof(struct ccp_rsa_req_ctx),
.alg_defaults = &ccp_rsa_defaults,
}
};
static int ccp_register_rsa_alg(struct list_head *head,
const struct ccp_rsa_def *def)
{
struct ccp_crypto_akcipher_alg *ccp_alg;
struct akcipher_alg *alg;
int ret;
ccp_alg = kzalloc_obj(*ccp_alg);
if (!ccp_alg)
return -ENOMEM;
INIT_LIST_HEAD(&ccp_alg->entry);
alg = &ccp_alg->alg;
*alg = *def->alg_defaults;
strscpy(alg->base.cra_name, def->name);
strscpy(alg->base.cra_driver_name, def->driver_name);
ret = crypto_register_akcipher(alg);
if (ret) {
pr_err("%s akcipher algorithm registration error (%d)\n",
alg->base.cra_name, ret);
kfree(ccp_alg);
return ret;
}
list_add(&ccp_alg->entry, head);
return 0;
}
int ccp_register_rsa_algs(struct list_head *head)
{
int i, ret;
unsigned int ccpversion = ccp_version();
/* Register the RSA algorithm in standard mode
* This works for CCP v3 and later
*/
for (i = 0; i < ARRAY_SIZE(rsa_algs); i++) {
if (rsa_algs[i].version > ccpversion)
continue;
ret = ccp_register_rsa_alg(head, &rsa_algs[i]);
if (ret)
return ret;
}
return 0;
}
@@ -0,0 +1,529 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* AMD Cryptographic Coprocessor (CCP) SHA crypto API support
*
* Copyright (C) 2013,2018 Advanced Micro Devices, Inc.
*
* Author: Tom Lendacky <thomas.lendacky@amd.com>
* Author: Gary R Hook <gary.hook@amd.com>
*/
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/delay.h>
#include <linux/scatterlist.h>
#include <linux/crypto.h>
#include <crypto/algapi.h>
#include <crypto/hash.h>
#include <crypto/hmac.h>
#include <crypto/internal/hash.h>
#include <crypto/sha1.h>
#include <crypto/sha2.h>
#include <crypto/scatterwalk.h>
#include <linux/string.h>
#include "ccp-crypto.h"
static int ccp_sha_complete(struct crypto_async_request *async_req, int ret)
{
struct ahash_request *req = ahash_request_cast(async_req);
struct crypto_ahash *tfm = crypto_ahash_reqtfm(req);
struct ccp_sha_req_ctx *rctx = ahash_request_ctx_dma(req);
unsigned int digest_size = crypto_ahash_digestsize(tfm);
if (ret)
goto e_free;
if (rctx->hash_rem) {
/* Save remaining data to buffer */
unsigned int offset = rctx->nbytes - rctx->hash_rem;
scatterwalk_map_and_copy(rctx->buf, rctx->src,
offset, rctx->hash_rem, 0);
rctx->buf_count = rctx->hash_rem;
} else {
rctx->buf_count = 0;
}
/* Update result area if supplied */
if (req->result && rctx->final)
memcpy(req->result, rctx->ctx, digest_size);
e_free:
sg_free_table(&rctx->data_sg);
return ret;
}
static int ccp_do_sha_update(struct ahash_request *req, unsigned int nbytes,
unsigned int final)
{
struct crypto_ahash *tfm = crypto_ahash_reqtfm(req);
struct ccp_ctx *ctx = crypto_ahash_ctx_dma(tfm);
struct ccp_sha_req_ctx *rctx = ahash_request_ctx_dma(req);
struct scatterlist *sg;
unsigned int block_size =
crypto_tfm_alg_blocksize(crypto_ahash_tfm(tfm));
unsigned int sg_count;
gfp_t gfp;
u64 len;
int ret;
len = (u64)rctx->buf_count + (u64)nbytes;
if (!final && (len <= block_size)) {
scatterwalk_map_and_copy(rctx->buf + rctx->buf_count, req->src,
0, nbytes, 0);
rctx->buf_count += nbytes;
return 0;
}
rctx->src = req->src;
rctx->nbytes = nbytes;
rctx->final = final;
rctx->hash_rem = final ? 0 : len & (block_size - 1);
rctx->hash_cnt = len - rctx->hash_rem;
if (!final && !rctx->hash_rem) {
/* CCP can't do zero length final, so keep some data around */
rctx->hash_cnt -= block_size;
rctx->hash_rem = block_size;
}
/* Initialize the context scatterlist */
sg_init_one(&rctx->ctx_sg, rctx->ctx, sizeof(rctx->ctx));
sg = NULL;
if (rctx->buf_count && nbytes) {
/* Build the data scatterlist table - allocate enough entries
* for both data pieces (buffer and input data)
*/
gfp = req->base.flags & CRYPTO_TFM_REQ_MAY_SLEEP ?
GFP_KERNEL : GFP_ATOMIC;
sg_count = sg_nents(req->src) + 1;
ret = sg_alloc_table(&rctx->data_sg, sg_count, gfp);
if (ret)
return ret;
sg_init_one(&rctx->buf_sg, rctx->buf, rctx->buf_count);
sg = ccp_crypto_sg_table_add(&rctx->data_sg, &rctx->buf_sg);
if (!sg) {
ret = -EINVAL;
goto e_free;
}
sg = ccp_crypto_sg_table_add(&rctx->data_sg, req->src);
if (!sg) {
ret = -EINVAL;
goto e_free;
}
sg_mark_end(sg);
sg = rctx->data_sg.sgl;
} else if (rctx->buf_count) {
sg_init_one(&rctx->buf_sg, rctx->buf, rctx->buf_count);
sg = &rctx->buf_sg;
} else if (nbytes) {
sg = req->src;
}
rctx->msg_bits += (rctx->hash_cnt << 3); /* Total in bits */
memset(&rctx->cmd, 0, sizeof(rctx->cmd));
INIT_LIST_HEAD(&rctx->cmd.entry);
rctx->cmd.engine = CCP_ENGINE_SHA;
rctx->cmd.u.sha.type = rctx->type;
rctx->cmd.u.sha.ctx = &rctx->ctx_sg;
switch (rctx->type) {
case CCP_SHA_TYPE_1:
rctx->cmd.u.sha.ctx_len = SHA1_DIGEST_SIZE;
break;
case CCP_SHA_TYPE_224:
rctx->cmd.u.sha.ctx_len = SHA224_DIGEST_SIZE;
break;
case CCP_SHA_TYPE_256:
rctx->cmd.u.sha.ctx_len = SHA256_DIGEST_SIZE;
break;
case CCP_SHA_TYPE_384:
rctx->cmd.u.sha.ctx_len = SHA384_DIGEST_SIZE;
break;
case CCP_SHA_TYPE_512:
rctx->cmd.u.sha.ctx_len = SHA512_DIGEST_SIZE;
break;
default:
/* Should never get here */
break;
}
rctx->cmd.u.sha.src = sg;
rctx->cmd.u.sha.src_len = rctx->hash_cnt;
rctx->cmd.u.sha.opad = ctx->u.sha.key_len ?
&ctx->u.sha.opad_sg : NULL;
rctx->cmd.u.sha.opad_len = ctx->u.sha.key_len ?
ctx->u.sha.opad_count : 0;
rctx->cmd.u.sha.first = rctx->first;
rctx->cmd.u.sha.final = rctx->final;
rctx->cmd.u.sha.msg_bits = rctx->msg_bits;
rctx->first = 0;
ret = ccp_crypto_enqueue_request(&req->base, &rctx->cmd);
return ret;
e_free:
sg_free_table(&rctx->data_sg);
return ret;
}
static int ccp_sha_init(struct ahash_request *req)
{
struct crypto_ahash *tfm = crypto_ahash_reqtfm(req);
struct ccp_ctx *ctx = crypto_ahash_ctx_dma(tfm);
struct ccp_sha_req_ctx *rctx = ahash_request_ctx_dma(req);
struct ccp_crypto_ahash_alg *alg =
ccp_crypto_ahash_alg(crypto_ahash_tfm(tfm));
unsigned int block_size =
crypto_tfm_alg_blocksize(crypto_ahash_tfm(tfm));
memset(rctx, 0, sizeof(*rctx));
rctx->type = alg->type;
rctx->first = 1;
if (ctx->u.sha.key_len) {
/* Buffer the HMAC key for first update */
memcpy(rctx->buf, ctx->u.sha.ipad, block_size);
rctx->buf_count = block_size;
}
return 0;
}
static int ccp_sha_update(struct ahash_request *req)
{
return ccp_do_sha_update(req, req->nbytes, 0);
}
static int ccp_sha_final(struct ahash_request *req)
{
return ccp_do_sha_update(req, 0, 1);
}
static int ccp_sha_finup(struct ahash_request *req)
{
return ccp_do_sha_update(req, req->nbytes, 1);
}
static int ccp_sha_digest(struct ahash_request *req)
{
int ret;
ret = ccp_sha_init(req);
if (ret)
return ret;
return ccp_sha_finup(req);
}
static int ccp_sha_export(struct ahash_request *req, void *out)
{
struct ccp_sha_req_ctx *rctx = ahash_request_ctx_dma(req);
struct ccp_sha_exp_ctx state;
/* Don't let anything leak to 'out' */
memset(&state, 0, sizeof(state));
state.type = rctx->type;
state.msg_bits = rctx->msg_bits;
state.first = rctx->first;
memcpy(state.ctx, rctx->ctx, sizeof(state.ctx));
state.buf_count = rctx->buf_count;
memcpy(state.buf, rctx->buf, sizeof(state.buf));
/* 'out' may not be aligned so memcpy from local variable */
memcpy(out, &state, sizeof(state));
return 0;
}
static int ccp_sha_import(struct ahash_request *req, const void *in)
{
struct ccp_sha_req_ctx *rctx = ahash_request_ctx_dma(req);
struct ccp_sha_exp_ctx state;
/* 'in' may not be aligned so memcpy to local variable */
memcpy(&state, in, sizeof(state));
memset(rctx, 0, sizeof(*rctx));
rctx->type = state.type;
rctx->msg_bits = state.msg_bits;
rctx->first = state.first;
memcpy(rctx->ctx, state.ctx, sizeof(rctx->ctx));
rctx->buf_count = state.buf_count;
memcpy(rctx->buf, state.buf, sizeof(rctx->buf));
return 0;
}
static int ccp_sha_setkey(struct crypto_ahash *tfm, const u8 *key,
unsigned int key_len)
{
struct ccp_ctx *ctx = crypto_ahash_ctx_dma(tfm);
struct crypto_shash *shash = ctx->u.sha.hmac_tfm;
unsigned int block_size = crypto_shash_blocksize(shash);
unsigned int digest_size = crypto_shash_digestsize(shash);
int i, ret;
/* Set to zero until complete */
ctx->u.sha.key_len = 0;
/* Clear key area to provide zero padding for keys smaller
* than the block size
*/
memset(ctx->u.sha.key, 0, sizeof(ctx->u.sha.key));
if (key_len > block_size) {
/* Must hash the input key */
ret = crypto_shash_tfm_digest(shash, key, key_len,
ctx->u.sha.key);
if (ret)
return -EINVAL;
key_len = digest_size;
} else {
memcpy(ctx->u.sha.key, key, key_len);
}
for (i = 0; i < block_size; i++) {
ctx->u.sha.ipad[i] = ctx->u.sha.key[i] ^ HMAC_IPAD_VALUE;
ctx->u.sha.opad[i] = ctx->u.sha.key[i] ^ HMAC_OPAD_VALUE;
}
sg_init_one(&ctx->u.sha.opad_sg, ctx->u.sha.opad, block_size);
ctx->u.sha.opad_count = block_size;
ctx->u.sha.key_len = key_len;
return 0;
}
static int ccp_sha_cra_init(struct crypto_tfm *tfm)
{
struct crypto_ahash *ahash = __crypto_ahash_cast(tfm);
struct ccp_ctx *ctx = crypto_ahash_ctx_dma(ahash);
ctx->complete = ccp_sha_complete;
ctx->u.sha.key_len = 0;
crypto_ahash_set_reqsize_dma(ahash, sizeof(struct ccp_sha_req_ctx));
return 0;
}
static void ccp_sha_cra_exit(struct crypto_tfm *tfm)
{
}
static int ccp_hmac_sha_cra_init(struct crypto_tfm *tfm)
{
struct ccp_ctx *ctx = crypto_tfm_ctx_dma(tfm);
struct ccp_crypto_ahash_alg *alg = ccp_crypto_ahash_alg(tfm);
struct crypto_shash *hmac_tfm;
hmac_tfm = crypto_alloc_shash(alg->child_alg, 0, 0);
if (IS_ERR(hmac_tfm)) {
pr_warn("could not load driver %s need for HMAC support\n",
alg->child_alg);
return PTR_ERR(hmac_tfm);
}
ctx->u.sha.hmac_tfm = hmac_tfm;
return ccp_sha_cra_init(tfm);
}
static void ccp_hmac_sha_cra_exit(struct crypto_tfm *tfm)
{
struct ccp_ctx *ctx = crypto_tfm_ctx_dma(tfm);
if (ctx->u.sha.hmac_tfm)
crypto_free_shash(ctx->u.sha.hmac_tfm);
ccp_sha_cra_exit(tfm);
}
struct ccp_sha_def {
unsigned int version;
const char *name;
const char *drv_name;
enum ccp_sha_type type;
u32 digest_size;
u32 block_size;
};
static struct ccp_sha_def sha_algs[] = {
{
.version = CCP_VERSION(3, 0),
.name = "sha1",
.drv_name = "sha1-ccp",
.type = CCP_SHA_TYPE_1,
.digest_size = SHA1_DIGEST_SIZE,
.block_size = SHA1_BLOCK_SIZE,
},
{
.version = CCP_VERSION(3, 0),
.name = "sha224",
.drv_name = "sha224-ccp",
.type = CCP_SHA_TYPE_224,
.digest_size = SHA224_DIGEST_SIZE,
.block_size = SHA224_BLOCK_SIZE,
},
{
.version = CCP_VERSION(3, 0),
.name = "sha256",
.drv_name = "sha256-ccp",
.type = CCP_SHA_TYPE_256,
.digest_size = SHA256_DIGEST_SIZE,
.block_size = SHA256_BLOCK_SIZE,
},
{
.version = CCP_VERSION(5, 0),
.name = "sha384",
.drv_name = "sha384-ccp",
.type = CCP_SHA_TYPE_384,
.digest_size = SHA384_DIGEST_SIZE,
.block_size = SHA384_BLOCK_SIZE,
},
{
.version = CCP_VERSION(5, 0),
.name = "sha512",
.drv_name = "sha512-ccp",
.type = CCP_SHA_TYPE_512,
.digest_size = SHA512_DIGEST_SIZE,
.block_size = SHA512_BLOCK_SIZE,
},
};
static int ccp_register_hmac_alg(struct list_head *head,
const struct ccp_sha_def *def,
const struct ccp_crypto_ahash_alg *base_alg)
{
struct ccp_crypto_ahash_alg *ccp_alg;
struct ahash_alg *alg;
struct hash_alg_common *halg;
struct crypto_alg *base;
int ret;
ccp_alg = kzalloc_obj(*ccp_alg);
if (!ccp_alg)
return -ENOMEM;
/* Copy the base algorithm and only change what's necessary */
*ccp_alg = *base_alg;
INIT_LIST_HEAD(&ccp_alg->entry);
strscpy(ccp_alg->child_alg, def->name, CRYPTO_MAX_ALG_NAME);
alg = &ccp_alg->alg;
alg->setkey = ccp_sha_setkey;
halg = &alg->halg;
base = &halg->base;
snprintf(base->cra_name, CRYPTO_MAX_ALG_NAME, "hmac(%s)", def->name);
snprintf(base->cra_driver_name, CRYPTO_MAX_ALG_NAME, "hmac-%s",
def->drv_name);
base->cra_init = ccp_hmac_sha_cra_init;
base->cra_exit = ccp_hmac_sha_cra_exit;
ret = crypto_register_ahash(alg);
if (ret) {
pr_err("%s ahash algorithm registration error (%d)\n",
base->cra_name, ret);
kfree(ccp_alg);
return ret;
}
list_add(&ccp_alg->entry, head);
return ret;
}
static int ccp_register_sha_alg(struct list_head *head,
const struct ccp_sha_def *def)
{
struct ccp_crypto_ahash_alg *ccp_alg;
struct ahash_alg *alg;
struct hash_alg_common *halg;
struct crypto_alg *base;
int ret;
ccp_alg = kzalloc_obj(*ccp_alg);
if (!ccp_alg)
return -ENOMEM;
INIT_LIST_HEAD(&ccp_alg->entry);
ccp_alg->type = def->type;
alg = &ccp_alg->alg;
alg->init = ccp_sha_init;
alg->update = ccp_sha_update;
alg->final = ccp_sha_final;
alg->finup = ccp_sha_finup;
alg->digest = ccp_sha_digest;
alg->export = ccp_sha_export;
alg->import = ccp_sha_import;
halg = &alg->halg;
halg->digestsize = def->digest_size;
halg->statesize = sizeof(struct ccp_sha_exp_ctx);
base = &halg->base;
strscpy(base->cra_name, def->name);
strscpy(base->cra_driver_name, def->drv_name);
base->cra_flags = CRYPTO_ALG_ASYNC |
CRYPTO_ALG_ALLOCATES_MEMORY |
CRYPTO_ALG_KERN_DRIVER_ONLY |
CRYPTO_ALG_NEED_FALLBACK;
base->cra_blocksize = def->block_size;
base->cra_ctxsize = sizeof(struct ccp_ctx) + crypto_dma_padding();
base->cra_priority = CCP_CRA_PRIORITY;
base->cra_init = ccp_sha_cra_init;
base->cra_exit = ccp_sha_cra_exit;
base->cra_module = THIS_MODULE;
ret = crypto_register_ahash(alg);
if (ret) {
pr_err("%s ahash algorithm registration error (%d)\n",
base->cra_name, ret);
kfree(ccp_alg);
return ret;
}
list_add(&ccp_alg->entry, head);
ret = ccp_register_hmac_alg(head, def, ccp_alg);
return ret;
}
int ccp_register_sha_algs(struct list_head *head)
{
int i, ret;
unsigned int ccpversion = ccp_version();
for (i = 0; i < ARRAY_SIZE(sha_algs); i++) {
if (sha_algs[i].version > ccpversion)
continue;
ret = ccp_register_sha_alg(head, &sha_algs[i]);
if (ret)
return ret;
}
return 0;
}
+286
View File
@@ -0,0 +1,286 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* AMD Cryptographic Coprocessor (CCP) crypto API support
*
* Copyright (C) 2013,2017 Advanced Micro Devices, Inc.
*
* Author: Tom Lendacky <thomas.lendacky@amd.com>
*/
#ifndef __CCP_CRYPTO_H__
#define __CCP_CRYPTO_H__
#include <linux/list.h>
#include <linux/wait.h>
#include <linux/ccp.h>
#include <crypto/algapi.h>
#include <crypto/aes.h>
#include <crypto/internal/aead.h>
#include <crypto/aead.h>
#include <crypto/ctr.h>
#include <crypto/hash.h>
#include <crypto/sha1.h>
#include <crypto/sha2.h>
#include <crypto/akcipher.h>
#include <crypto/skcipher.h>
#include <crypto/internal/rsa.h>
/* We want the module name in front of our messages */
#undef pr_fmt
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#define CCP_LOG_LEVEL KERN_INFO
#define CCP_CRA_PRIORITY 300
struct ccp_crypto_skcipher_alg {
struct list_head entry;
u32 mode;
struct skcipher_alg alg;
};
struct ccp_crypto_aead {
struct list_head entry;
u32 mode;
struct aead_alg alg;
};
struct ccp_crypto_ahash_alg {
struct list_head entry;
const __be32 *init;
u32 type;
u32 mode;
/* Child algorithm used for HMAC, CMAC, etc */
char child_alg[CRYPTO_MAX_ALG_NAME];
struct ahash_alg alg;
};
struct ccp_crypto_akcipher_alg {
struct list_head entry;
struct akcipher_alg alg;
};
static inline struct ccp_crypto_skcipher_alg *
ccp_crypto_skcipher_alg(struct crypto_skcipher *tfm)
{
struct skcipher_alg *alg = crypto_skcipher_alg(tfm);
return container_of(alg, struct ccp_crypto_skcipher_alg, alg);
}
static inline struct ccp_crypto_ahash_alg *
ccp_crypto_ahash_alg(struct crypto_tfm *tfm)
{
struct crypto_alg *alg = tfm->__crt_alg;
struct ahash_alg *ahash_alg;
ahash_alg = container_of(alg, struct ahash_alg, halg.base);
return container_of(ahash_alg, struct ccp_crypto_ahash_alg, alg);
}
/***** AES related defines *****/
struct ccp_aes_ctx {
/* Fallback cipher for XTS with unsupported unit sizes */
struct crypto_skcipher *tfm_skcipher;
enum ccp_engine engine;
enum ccp_aes_type type;
enum ccp_aes_mode mode;
struct scatterlist key_sg;
unsigned int key_len;
u8 key[AES_MAX_KEY_SIZE * 2];
u8 nonce[CTR_RFC3686_NONCE_SIZE];
/* CMAC key structures */
struct scatterlist k1_sg;
struct scatterlist k2_sg;
unsigned int kn_len;
u8 k1[AES_BLOCK_SIZE];
u8 k2[AES_BLOCK_SIZE];
};
struct ccp_aes_req_ctx {
struct scatterlist iv_sg;
u8 iv[AES_BLOCK_SIZE];
struct scatterlist tag_sg;
u8 tag[AES_BLOCK_SIZE];
/* Fields used for RFC3686 requests */
u8 *rfc3686_info;
u8 rfc3686_iv[AES_BLOCK_SIZE];
struct ccp_cmd cmd;
struct skcipher_request fallback_req; // keep at the end
};
struct ccp_aes_cmac_req_ctx {
unsigned int null_msg;
unsigned int final;
struct scatterlist *src;
unsigned int nbytes;
u64 hash_cnt;
unsigned int hash_rem;
struct sg_table data_sg;
struct scatterlist iv_sg;
u8 iv[AES_BLOCK_SIZE];
struct scatterlist buf_sg;
unsigned int buf_count;
u8 buf[AES_BLOCK_SIZE];
struct scatterlist pad_sg;
unsigned int pad_count;
u8 pad[AES_BLOCK_SIZE];
struct ccp_cmd cmd;
};
struct ccp_aes_cmac_exp_ctx {
unsigned int null_msg;
u8 iv[AES_BLOCK_SIZE];
unsigned int buf_count;
u8 buf[AES_BLOCK_SIZE];
};
/***** 3DES related defines *****/
struct ccp_des3_ctx {
enum ccp_engine engine;
enum ccp_des3_type type;
enum ccp_des3_mode mode;
struct scatterlist key_sg;
unsigned int key_len;
u8 key[AES_MAX_KEY_SIZE];
};
struct ccp_des3_req_ctx {
struct scatterlist iv_sg;
u8 iv[AES_BLOCK_SIZE];
struct ccp_cmd cmd;
};
/* SHA-related defines
* These values must be large enough to accommodate any variant
*/
#define MAX_SHA_CONTEXT_SIZE SHA512_DIGEST_SIZE
#define MAX_SHA_BLOCK_SIZE SHA512_BLOCK_SIZE
struct ccp_sha_ctx {
struct scatterlist opad_sg;
unsigned int opad_count;
unsigned int key_len;
u8 key[MAX_SHA_BLOCK_SIZE];
u8 ipad[MAX_SHA_BLOCK_SIZE];
u8 opad[MAX_SHA_BLOCK_SIZE];
struct crypto_shash *hmac_tfm;
};
struct ccp_sha_req_ctx {
enum ccp_sha_type type;
u64 msg_bits;
unsigned int first;
unsigned int final;
struct scatterlist *src;
unsigned int nbytes;
u64 hash_cnt;
unsigned int hash_rem;
struct sg_table data_sg;
struct scatterlist ctx_sg;
u8 ctx[MAX_SHA_CONTEXT_SIZE];
struct scatterlist buf_sg;
unsigned int buf_count;
u8 buf[MAX_SHA_BLOCK_SIZE];
/* CCP driver command */
struct ccp_cmd cmd;
};
struct ccp_sha_exp_ctx {
enum ccp_sha_type type;
u64 msg_bits;
unsigned int first;
u8 ctx[MAX_SHA_CONTEXT_SIZE];
unsigned int buf_count;
u8 buf[MAX_SHA_BLOCK_SIZE];
};
/***** RSA related defines *****/
struct ccp_rsa_ctx {
unsigned int key_len; /* in bits */
struct scatterlist e_sg;
u8 *e_buf;
unsigned int e_len;
struct scatterlist n_sg;
u8 *n_buf;
unsigned int n_len;
struct scatterlist d_sg;
u8 *d_buf;
unsigned int d_len;
};
struct ccp_rsa_req_ctx {
struct ccp_cmd cmd;
};
#define CCP_RSA_MAXMOD (4 * 1024 / 8)
#define CCP5_RSA_MAXMOD (16 * 1024 / 8)
/***** Common Context Structure *****/
struct ccp_ctx {
int (*complete)(struct crypto_async_request *req, int ret);
union {
struct ccp_aes_ctx aes;
struct ccp_rsa_ctx rsa;
struct ccp_sha_ctx sha;
struct ccp_des3_ctx des3;
} u;
};
int ccp_crypto_enqueue_request(struct crypto_async_request *req,
struct ccp_cmd *cmd);
struct scatterlist *ccp_crypto_sg_table_add(struct sg_table *table,
struct scatterlist *sg_add);
int ccp_register_aes_algs(struct list_head *head);
int ccp_register_aes_cmac_algs(struct list_head *head);
int ccp_register_aes_xts_algs(struct list_head *head);
int ccp_register_aes_aeads(struct list_head *head);
int ccp_register_sha_algs(struct list_head *head);
int ccp_register_des3_algs(struct list_head *head);
int ccp_register_rsa_algs(struct list_head *head);
#endif
@@ -0,0 +1,326 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* AMD Cryptographic Coprocessor (CCP) driver
*
* Copyright (C) 2017 Advanced Micro Devices, Inc.
*
* Author: Gary R Hook <gary.hook@amd.com>
*/
#include <linux/debugfs.h>
#include <linux/ccp.h>
#include "ccp-dev.h"
/* DebugFS helpers */
#define OBUFP (obuf + oboff)
#define OBUFLEN 512
#define OBUFSPC (OBUFLEN - oboff)
#define OSCNPRINTF(fmt, ...) \
scnprintf(OBUFP, OBUFSPC, fmt, ## __VA_ARGS__)
#define BUFLEN 63
#define RI_VERSION_NUM 0x0000003F
#define RI_AES_PRESENT 0x00000040
#define RI_3DES_PRESENT 0x00000080
#define RI_SHA_PRESENT 0x00000100
#define RI_RSA_PRESENT 0x00000200
#define RI_ECC_PRESENT 0x00000400
#define RI_ZDE_PRESENT 0x00000800
#define RI_ZCE_PRESENT 0x00001000
#define RI_TRNG_PRESENT 0x00002000
#define RI_ELFC_PRESENT 0x00004000
#define RI_ELFC_SHIFT 14
#define RI_NUM_VQM 0x00078000
#define RI_NVQM_SHIFT 15
#define RI_NVQM(r) (((r) * RI_NUM_VQM) >> RI_NVQM_SHIFT)
#define RI_LSB_ENTRIES 0x0FF80000
#define RI_NLSB_SHIFT 19
#define RI_NLSB(r) (((r) * RI_LSB_ENTRIES) >> RI_NLSB_SHIFT)
static ssize_t ccp5_debugfs_info_read(struct file *filp, char __user *ubuf,
size_t count, loff_t *offp)
{
struct ccp_device *ccp = filp->private_data;
unsigned int oboff = 0;
unsigned int regval;
ssize_t ret;
char *obuf;
if (!ccp)
return 0;
obuf = kmalloc(OBUFLEN, GFP_KERNEL);
if (!obuf)
return -ENOMEM;
oboff += OSCNPRINTF("Device name: %s\n", ccp->name);
oboff += OSCNPRINTF(" RNG name: %s\n", ccp->rngname);
oboff += OSCNPRINTF(" # Queues: %d\n", ccp->cmd_q_count);
oboff += OSCNPRINTF(" # Cmds: %d\n", ccp->cmd_count);
regval = ioread32(ccp->io_regs + CMD5_PSP_CCP_VERSION);
oboff += OSCNPRINTF(" Version: %d\n", regval & RI_VERSION_NUM);
oboff += OSCNPRINTF(" Engines:");
if (regval & RI_AES_PRESENT)
oboff += OSCNPRINTF(" AES");
if (regval & RI_3DES_PRESENT)
oboff += OSCNPRINTF(" 3DES");
if (regval & RI_SHA_PRESENT)
oboff += OSCNPRINTF(" SHA");
if (regval & RI_RSA_PRESENT)
oboff += OSCNPRINTF(" RSA");
if (regval & RI_ECC_PRESENT)
oboff += OSCNPRINTF(" ECC");
if (regval & RI_ZDE_PRESENT)
oboff += OSCNPRINTF(" ZDE");
if (regval & RI_ZCE_PRESENT)
oboff += OSCNPRINTF(" ZCE");
if (regval & RI_TRNG_PRESENT)
oboff += OSCNPRINTF(" TRNG");
oboff += OSCNPRINTF("\n");
oboff += OSCNPRINTF(" Queues: %d\n",
(regval & RI_NUM_VQM) >> RI_NVQM_SHIFT);
oboff += OSCNPRINTF("LSB Entries: %d\n",
(regval & RI_LSB_ENTRIES) >> RI_NLSB_SHIFT);
ret = simple_read_from_buffer(ubuf, count, offp, obuf, oboff);
kfree(obuf);
return ret;
}
/* Return a formatted buffer containing the current
* statistics across all queues for a CCP.
*/
static ssize_t ccp5_debugfs_stats_read(struct file *filp, char __user *ubuf,
size_t count, loff_t *offp)
{
struct ccp_device *ccp = filp->private_data;
unsigned long total_xts_aes_ops = 0;
unsigned long total_3des_ops = 0;
unsigned long total_aes_ops = 0;
unsigned long total_sha_ops = 0;
unsigned long total_rsa_ops = 0;
unsigned long total_ecc_ops = 0;
unsigned long total_pt_ops = 0;
unsigned long total_ops = 0;
unsigned int oboff = 0;
ssize_t ret = 0;
unsigned int i;
char *obuf;
for (i = 0; i < ccp->cmd_q_count; i++) {
struct ccp_cmd_queue *cmd_q = &ccp->cmd_q[i];
total_ops += cmd_q->total_ops;
total_aes_ops += cmd_q->total_aes_ops;
total_xts_aes_ops += cmd_q->total_xts_aes_ops;
total_3des_ops += cmd_q->total_3des_ops;
total_sha_ops += cmd_q->total_sha_ops;
total_rsa_ops += cmd_q->total_rsa_ops;
total_pt_ops += cmd_q->total_pt_ops;
total_ecc_ops += cmd_q->total_ecc_ops;
}
obuf = kmalloc(OBUFLEN, GFP_KERNEL);
if (!obuf)
return -ENOMEM;
oboff += OSCNPRINTF("Total Interrupts Handled: %ld\n",
ccp->total_interrupts);
oboff += OSCNPRINTF(" Total Operations: %ld\n",
total_ops);
oboff += OSCNPRINTF(" AES: %ld\n",
total_aes_ops);
oboff += OSCNPRINTF(" XTS AES: %ld\n",
total_xts_aes_ops);
oboff += OSCNPRINTF(" SHA: %ld\n",
total_3des_ops);
oboff += OSCNPRINTF(" SHA: %ld\n",
total_sha_ops);
oboff += OSCNPRINTF(" RSA: %ld\n",
total_rsa_ops);
oboff += OSCNPRINTF(" Pass-Thru: %ld\n",
total_pt_ops);
oboff += OSCNPRINTF(" ECC: %ld\n",
total_ecc_ops);
ret = simple_read_from_buffer(ubuf, count, offp, obuf, oboff);
kfree(obuf);
return ret;
}
/* Reset the counters in a queue
*/
static void ccp5_debugfs_reset_queue_stats(struct ccp_cmd_queue *cmd_q)
{
cmd_q->total_ops = 0L;
cmd_q->total_aes_ops = 0L;
cmd_q->total_xts_aes_ops = 0L;
cmd_q->total_3des_ops = 0L;
cmd_q->total_sha_ops = 0L;
cmd_q->total_rsa_ops = 0L;
cmd_q->total_pt_ops = 0L;
cmd_q->total_ecc_ops = 0L;
}
/* A value was written to the stats variable, which
* should be used to reset the queue counters across
* that device.
*/
static ssize_t ccp5_debugfs_stats_write(struct file *filp,
const char __user *ubuf,
size_t count, loff_t *offp)
{
struct ccp_device *ccp = filp->private_data;
int i;
for (i = 0; i < ccp->cmd_q_count; i++)
ccp5_debugfs_reset_queue_stats(&ccp->cmd_q[i]);
ccp->total_interrupts = 0L;
return count;
}
/* Return a formatted buffer containing the current information
* for that queue
*/
static ssize_t ccp5_debugfs_queue_read(struct file *filp, char __user *ubuf,
size_t count, loff_t *offp)
{
struct ccp_cmd_queue *cmd_q = filp->private_data;
unsigned int oboff = 0;
unsigned int regval;
ssize_t ret;
char *obuf;
if (!cmd_q)
return 0;
obuf = kmalloc(OBUFLEN, GFP_KERNEL);
if (!obuf)
return -ENOMEM;
oboff += OSCNPRINTF(" Total Queue Operations: %ld\n",
cmd_q->total_ops);
oboff += OSCNPRINTF(" AES: %ld\n",
cmd_q->total_aes_ops);
oboff += OSCNPRINTF(" XTS AES: %ld\n",
cmd_q->total_xts_aes_ops);
oboff += OSCNPRINTF(" SHA: %ld\n",
cmd_q->total_3des_ops);
oboff += OSCNPRINTF(" SHA: %ld\n",
cmd_q->total_sha_ops);
oboff += OSCNPRINTF(" RSA: %ld\n",
cmd_q->total_rsa_ops);
oboff += OSCNPRINTF(" Pass-Thru: %ld\n",
cmd_q->total_pt_ops);
oboff += OSCNPRINTF(" ECC: %ld\n",
cmd_q->total_ecc_ops);
regval = ioread32(cmd_q->reg_int_enable);
oboff += OSCNPRINTF(" Enabled Interrupts:");
if (regval & INT_EMPTY_QUEUE)
oboff += OSCNPRINTF(" EMPTY");
if (regval & INT_QUEUE_STOPPED)
oboff += OSCNPRINTF(" STOPPED");
if (regval & INT_ERROR)
oboff += OSCNPRINTF(" ERROR");
if (regval & INT_COMPLETION)
oboff += OSCNPRINTF(" COMPLETION");
oboff += OSCNPRINTF("\n");
ret = simple_read_from_buffer(ubuf, count, offp, obuf, oboff);
kfree(obuf);
return ret;
}
/* A value was written to the stats variable for a
* queue. Reset the queue counters to this value.
*/
static ssize_t ccp5_debugfs_queue_write(struct file *filp,
const char __user *ubuf,
size_t count, loff_t *offp)
{
struct ccp_cmd_queue *cmd_q = filp->private_data;
ccp5_debugfs_reset_queue_stats(cmd_q);
return count;
}
static const struct file_operations ccp_debugfs_info_ops = {
.owner = THIS_MODULE,
.open = simple_open,
.read = ccp5_debugfs_info_read,
.write = NULL,
};
static const struct file_operations ccp_debugfs_queue_ops = {
.owner = THIS_MODULE,
.open = simple_open,
.read = ccp5_debugfs_queue_read,
.write = ccp5_debugfs_queue_write,
};
static const struct file_operations ccp_debugfs_stats_ops = {
.owner = THIS_MODULE,
.open = simple_open,
.read = ccp5_debugfs_stats_read,
.write = ccp5_debugfs_stats_write,
};
static struct dentry *ccp_debugfs_dir;
static DEFINE_MUTEX(ccp_debugfs_lock);
#define MAX_NAME_LEN 20
void ccp5_debugfs_setup(struct ccp_device *ccp)
{
struct ccp_cmd_queue *cmd_q;
char name[MAX_NAME_LEN + 1];
struct dentry *debugfs_q_instance;
int i;
if (!debugfs_initialized())
return;
mutex_lock(&ccp_debugfs_lock);
if (!ccp_debugfs_dir)
ccp_debugfs_dir = debugfs_create_dir(KBUILD_MODNAME, NULL);
mutex_unlock(&ccp_debugfs_lock);
ccp->debugfs_instance = debugfs_create_dir(ccp->name, ccp_debugfs_dir);
debugfs_create_file("info", 0400, ccp->debugfs_instance, ccp,
&ccp_debugfs_info_ops);
debugfs_create_file("stats", 0600, ccp->debugfs_instance, ccp,
&ccp_debugfs_stats_ops);
for (i = 0; i < ccp->cmd_q_count; i++) {
cmd_q = &ccp->cmd_q[i];
snprintf(name, MAX_NAME_LEN - 1, "q%d", cmd_q->id);
debugfs_q_instance =
debugfs_create_dir(name, ccp->debugfs_instance);
debugfs_create_file("stats", 0600, debugfs_q_instance, cmd_q,
&ccp_debugfs_queue_ops);
}
return;
}
void ccp5_debugfs_destroy(void)
{
mutex_lock(&ccp_debugfs_lock);
debugfs_remove_recursive(ccp_debugfs_dir);
ccp_debugfs_dir = NULL;
mutex_unlock(&ccp_debugfs_lock);
}
+597
View File
@@ -0,0 +1,597 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* AMD Cryptographic Coprocessor (CCP) driver
*
* Copyright (C) 2013,2017 Advanced Micro Devices, Inc.
*
* Author: Tom Lendacky <thomas.lendacky@amd.com>
* Author: Gary R Hook <gary.hook@amd.com>
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/kthread.h>
#include <linux/interrupt.h>
#include <linux/ccp.h>
#include "ccp-dev.h"
static u32 ccp_alloc_ksb(struct ccp_cmd_queue *cmd_q, unsigned int count)
{
int start;
struct ccp_device *ccp = cmd_q->ccp;
for (;;) {
mutex_lock(&ccp->sb_mutex);
start = (u32)bitmap_find_next_zero_area(ccp->sb,
ccp->sb_count,
ccp->sb_start,
count, 0);
if (start <= ccp->sb_count) {
bitmap_set(ccp->sb, start, count);
mutex_unlock(&ccp->sb_mutex);
break;
}
ccp->sb_avail = 0;
mutex_unlock(&ccp->sb_mutex);
/* Wait for KSB entries to become available */
if (wait_event_interruptible(ccp->sb_queue, ccp->sb_avail))
return 0;
}
return KSB_START + start;
}
static void ccp_free_ksb(struct ccp_cmd_queue *cmd_q, unsigned int start,
unsigned int count)
{
struct ccp_device *ccp = cmd_q->ccp;
if (!start)
return;
mutex_lock(&ccp->sb_mutex);
bitmap_clear(ccp->sb, start - KSB_START, count);
ccp->sb_avail = 1;
mutex_unlock(&ccp->sb_mutex);
wake_up_interruptible_all(&ccp->sb_queue);
}
static unsigned int ccp_get_free_slots(struct ccp_cmd_queue *cmd_q)
{
return CMD_Q_DEPTH(ioread32(cmd_q->reg_status));
}
static int ccp_do_cmd(struct ccp_op *op, u32 *cr, unsigned int cr_count)
{
struct ccp_cmd_queue *cmd_q = op->cmd_q;
struct ccp_device *ccp = cmd_q->ccp;
void __iomem *cr_addr;
u32 cr0, cmd;
unsigned int i;
int ret = 0;
/* We could read a status register to see how many free slots
* are actually available, but reading that register resets it
* and you could lose some error information.
*/
cmd_q->free_slots--;
cr0 = (cmd_q->id << REQ0_CMD_Q_SHIFT)
| (op->jobid << REQ0_JOBID_SHIFT)
| REQ0_WAIT_FOR_WRITE;
if (op->soc)
cr0 |= REQ0_STOP_ON_COMPLETE
| REQ0_INT_ON_COMPLETE;
if (op->ioc || !cmd_q->free_slots)
cr0 |= REQ0_INT_ON_COMPLETE;
/* Start at CMD_REQ1 */
cr_addr = ccp->io_regs + CMD_REQ0 + CMD_REQ_INCR;
mutex_lock(&ccp->req_mutex);
/* Write CMD_REQ1 through CMD_REQx first */
for (i = 0; i < cr_count; i++, cr_addr += CMD_REQ_INCR)
iowrite32(*(cr + i), cr_addr);
/* Tell the CCP to start */
wmb();
iowrite32(cr0, ccp->io_regs + CMD_REQ0);
mutex_unlock(&ccp->req_mutex);
if (cr0 & REQ0_INT_ON_COMPLETE) {
/* Wait for the job to complete */
ret = wait_event_interruptible(cmd_q->int_queue,
cmd_q->int_rcvd);
if (ret || cmd_q->cmd_error) {
/* On error delete all related jobs from the queue */
cmd = (cmd_q->id << DEL_Q_ID_SHIFT)
| op->jobid;
if (cmd_q->cmd_error)
ccp_log_error(cmd_q->ccp,
cmd_q->cmd_error);
iowrite32(cmd, ccp->io_regs + DEL_CMD_Q_JOB);
if (!ret)
ret = -EIO;
} else if (op->soc) {
/* Delete just head job from the queue on SoC */
cmd = DEL_Q_ACTIVE
| (cmd_q->id << DEL_Q_ID_SHIFT)
| op->jobid;
iowrite32(cmd, ccp->io_regs + DEL_CMD_Q_JOB);
}
cmd_q->free_slots = CMD_Q_DEPTH(cmd_q->q_status);
cmd_q->int_rcvd = 0;
}
return ret;
}
static int ccp_perform_aes(struct ccp_op *op)
{
u32 cr[6];
/* Fill out the register contents for REQ1 through REQ6 */
cr[0] = (CCP_ENGINE_AES << REQ1_ENGINE_SHIFT)
| (op->u.aes.type << REQ1_AES_TYPE_SHIFT)
| (op->u.aes.mode << REQ1_AES_MODE_SHIFT)
| (op->u.aes.action << REQ1_AES_ACTION_SHIFT)
| (op->sb_key << REQ1_KEY_KSB_SHIFT);
cr[1] = op->src.u.dma.length - 1;
cr[2] = ccp_addr_lo(&op->src.u.dma);
cr[3] = (op->sb_ctx << REQ4_KSB_SHIFT)
| (CCP_MEMTYPE_SYSTEM << REQ4_MEMTYPE_SHIFT)
| ccp_addr_hi(&op->src.u.dma);
cr[4] = ccp_addr_lo(&op->dst.u.dma);
cr[5] = (CCP_MEMTYPE_SYSTEM << REQ6_MEMTYPE_SHIFT)
| ccp_addr_hi(&op->dst.u.dma);
if (op->u.aes.mode == CCP_AES_MODE_CFB)
cr[0] |= ((0x7f) << REQ1_AES_CFB_SIZE_SHIFT);
if (op->eom)
cr[0] |= REQ1_EOM;
if (op->init)
cr[0] |= REQ1_INIT;
return ccp_do_cmd(op, cr, ARRAY_SIZE(cr));
}
static int ccp_perform_xts_aes(struct ccp_op *op)
{
u32 cr[6];
/* Fill out the register contents for REQ1 through REQ6 */
cr[0] = (CCP_ENGINE_XTS_AES_128 << REQ1_ENGINE_SHIFT)
| (op->u.xts.action << REQ1_AES_ACTION_SHIFT)
| (op->u.xts.unit_size << REQ1_XTS_AES_SIZE_SHIFT)
| (op->sb_key << REQ1_KEY_KSB_SHIFT);
cr[1] = op->src.u.dma.length - 1;
cr[2] = ccp_addr_lo(&op->src.u.dma);
cr[3] = (op->sb_ctx << REQ4_KSB_SHIFT)
| (CCP_MEMTYPE_SYSTEM << REQ4_MEMTYPE_SHIFT)
| ccp_addr_hi(&op->src.u.dma);
cr[4] = ccp_addr_lo(&op->dst.u.dma);
cr[5] = (CCP_MEMTYPE_SYSTEM << REQ6_MEMTYPE_SHIFT)
| ccp_addr_hi(&op->dst.u.dma);
if (op->eom)
cr[0] |= REQ1_EOM;
if (op->init)
cr[0] |= REQ1_INIT;
return ccp_do_cmd(op, cr, ARRAY_SIZE(cr));
}
static int ccp_perform_sha(struct ccp_op *op)
{
u32 cr[6];
/* Fill out the register contents for REQ1 through REQ6 */
cr[0] = (CCP_ENGINE_SHA << REQ1_ENGINE_SHIFT)
| (op->u.sha.type << REQ1_SHA_TYPE_SHIFT)
| REQ1_INIT;
cr[1] = op->src.u.dma.length - 1;
cr[2] = ccp_addr_lo(&op->src.u.dma);
cr[3] = (op->sb_ctx << REQ4_KSB_SHIFT)
| (CCP_MEMTYPE_SYSTEM << REQ4_MEMTYPE_SHIFT)
| ccp_addr_hi(&op->src.u.dma);
if (op->eom) {
cr[0] |= REQ1_EOM;
cr[4] = lower_32_bits(op->u.sha.msg_bits);
cr[5] = upper_32_bits(op->u.sha.msg_bits);
} else {
cr[4] = 0;
cr[5] = 0;
}
return ccp_do_cmd(op, cr, ARRAY_SIZE(cr));
}
static int ccp_perform_rsa(struct ccp_op *op)
{
u32 cr[6];
/* Fill out the register contents for REQ1 through REQ6 */
cr[0] = (CCP_ENGINE_RSA << REQ1_ENGINE_SHIFT)
| (op->u.rsa.mod_size << REQ1_RSA_MOD_SIZE_SHIFT)
| (op->sb_key << REQ1_KEY_KSB_SHIFT)
| REQ1_EOM;
cr[1] = op->u.rsa.input_len - 1;
cr[2] = ccp_addr_lo(&op->src.u.dma);
cr[3] = (op->sb_ctx << REQ4_KSB_SHIFT)
| (CCP_MEMTYPE_SYSTEM << REQ4_MEMTYPE_SHIFT)
| ccp_addr_hi(&op->src.u.dma);
cr[4] = ccp_addr_lo(&op->dst.u.dma);
cr[5] = (CCP_MEMTYPE_SYSTEM << REQ6_MEMTYPE_SHIFT)
| ccp_addr_hi(&op->dst.u.dma);
return ccp_do_cmd(op, cr, ARRAY_SIZE(cr));
}
static int ccp_perform_passthru(struct ccp_op *op)
{
u32 cr[6];
/* Fill out the register contents for REQ1 through REQ6 */
cr[0] = (CCP_ENGINE_PASSTHRU << REQ1_ENGINE_SHIFT)
| (op->u.passthru.bit_mod << REQ1_PT_BW_SHIFT)
| (op->u.passthru.byte_swap << REQ1_PT_BS_SHIFT);
if (op->src.type == CCP_MEMTYPE_SYSTEM)
cr[1] = op->src.u.dma.length - 1;
else
cr[1] = op->dst.u.dma.length - 1;
if (op->src.type == CCP_MEMTYPE_SYSTEM) {
cr[2] = ccp_addr_lo(&op->src.u.dma);
cr[3] = (CCP_MEMTYPE_SYSTEM << REQ4_MEMTYPE_SHIFT)
| ccp_addr_hi(&op->src.u.dma);
if (op->u.passthru.bit_mod != CCP_PASSTHRU_BITWISE_NOOP)
cr[3] |= (op->sb_key << REQ4_KSB_SHIFT);
} else {
cr[2] = op->src.u.sb * CCP_SB_BYTES;
cr[3] = (CCP_MEMTYPE_SB << REQ4_MEMTYPE_SHIFT);
}
if (op->dst.type == CCP_MEMTYPE_SYSTEM) {
cr[4] = ccp_addr_lo(&op->dst.u.dma);
cr[5] = (CCP_MEMTYPE_SYSTEM << REQ6_MEMTYPE_SHIFT)
| ccp_addr_hi(&op->dst.u.dma);
} else {
cr[4] = op->dst.u.sb * CCP_SB_BYTES;
cr[5] = (CCP_MEMTYPE_SB << REQ6_MEMTYPE_SHIFT);
}
if (op->eom)
cr[0] |= REQ1_EOM;
return ccp_do_cmd(op, cr, ARRAY_SIZE(cr));
}
static int ccp_perform_ecc(struct ccp_op *op)
{
u32 cr[6];
/* Fill out the register contents for REQ1 through REQ6 */
cr[0] = REQ1_ECC_AFFINE_CONVERT
| (CCP_ENGINE_ECC << REQ1_ENGINE_SHIFT)
| (op->u.ecc.function << REQ1_ECC_FUNCTION_SHIFT)
| REQ1_EOM;
cr[1] = op->src.u.dma.length - 1;
cr[2] = ccp_addr_lo(&op->src.u.dma);
cr[3] = (CCP_MEMTYPE_SYSTEM << REQ4_MEMTYPE_SHIFT)
| ccp_addr_hi(&op->src.u.dma);
cr[4] = ccp_addr_lo(&op->dst.u.dma);
cr[5] = (CCP_MEMTYPE_SYSTEM << REQ6_MEMTYPE_SHIFT)
| ccp_addr_hi(&op->dst.u.dma);
return ccp_do_cmd(op, cr, ARRAY_SIZE(cr));
}
static void ccp_disable_queue_interrupts(struct ccp_device *ccp)
{
iowrite32(0x00, ccp->io_regs + IRQ_MASK_REG);
}
static void ccp_enable_queue_interrupts(struct ccp_device *ccp)
{
iowrite32(ccp->qim, ccp->io_regs + IRQ_MASK_REG);
}
static void ccp_irq_bh(unsigned long data)
{
struct ccp_device *ccp = (struct ccp_device *)data;
struct ccp_cmd_queue *cmd_q;
u32 q_int, status;
unsigned int i;
status = ioread32(ccp->io_regs + IRQ_STATUS_REG);
for (i = 0; i < ccp->cmd_q_count; i++) {
cmd_q = &ccp->cmd_q[i];
q_int = status & (cmd_q->int_ok | cmd_q->int_err);
if (q_int) {
cmd_q->int_status = status;
cmd_q->q_status = ioread32(cmd_q->reg_status);
cmd_q->q_int_status = ioread32(cmd_q->reg_int_status);
/* On error, only save the first error value */
if ((q_int & cmd_q->int_err) && !cmd_q->cmd_error)
cmd_q->cmd_error = CMD_Q_ERROR(cmd_q->q_status);
cmd_q->int_rcvd = 1;
/* Acknowledge the interrupt and wake the kthread */
iowrite32(q_int, ccp->io_regs + IRQ_STATUS_REG);
wake_up_interruptible(&cmd_q->int_queue);
}
}
ccp_enable_queue_interrupts(ccp);
}
static irqreturn_t ccp_irq_handler(int irq, void *data)
{
struct ccp_device *ccp = (struct ccp_device *)data;
ccp_disable_queue_interrupts(ccp);
if (ccp->use_tasklet)
tasklet_schedule(&ccp->irq_tasklet);
else
ccp_irq_bh((unsigned long)ccp);
return IRQ_HANDLED;
}
static int ccp_init(struct ccp_device *ccp)
{
struct device *dev = ccp->dev;
struct ccp_cmd_queue *cmd_q;
struct dma_pool *dma_pool;
char dma_pool_name[MAX_DMAPOOL_NAME_LEN];
unsigned int qmr, i;
int ret;
/* Find available queues */
ccp->qim = 0;
qmr = ioread32(ccp->io_regs + Q_MASK_REG);
for (i = 0; (i < MAX_HW_QUEUES) && (ccp->cmd_q_count < ccp->max_q_count); i++) {
if (!(qmr & (1 << i)))
continue;
/* Allocate a dma pool for this queue */
snprintf(dma_pool_name, sizeof(dma_pool_name), "%s_q%d",
ccp->name, i);
dma_pool = dma_pool_create(dma_pool_name, dev,
CCP_DMAPOOL_MAX_SIZE,
CCP_DMAPOOL_ALIGN, 0);
if (!dma_pool) {
dev_err(dev, "unable to allocate dma pool\n");
ret = -ENOMEM;
goto e_pool;
}
cmd_q = &ccp->cmd_q[ccp->cmd_q_count];
ccp->cmd_q_count++;
cmd_q->ccp = ccp;
cmd_q->id = i;
cmd_q->dma_pool = dma_pool;
/* Reserve 2 KSB regions for the queue */
cmd_q->sb_key = KSB_START + ccp->sb_start++;
cmd_q->sb_ctx = KSB_START + ccp->sb_start++;
ccp->sb_count -= 2;
/* Preset some register values and masks that are queue
* number dependent
*/
cmd_q->reg_status = ccp->io_regs + CMD_Q_STATUS_BASE +
(CMD_Q_STATUS_INCR * i);
cmd_q->reg_int_status = ccp->io_regs + CMD_Q_INT_STATUS_BASE +
(CMD_Q_STATUS_INCR * i);
cmd_q->int_ok = 1 << (i * 2);
cmd_q->int_err = 1 << ((i * 2) + 1);
cmd_q->free_slots = ccp_get_free_slots(cmd_q);
init_waitqueue_head(&cmd_q->int_queue);
/* Build queue interrupt mask (two interrupts per queue) */
ccp->qim |= cmd_q->int_ok | cmd_q->int_err;
#ifdef CONFIG_ARM64
/* For arm64 set the recommended queue cache settings */
iowrite32(ccp->axcache, ccp->io_regs + CMD_Q_CACHE_BASE +
(CMD_Q_CACHE_INC * i));
#endif
dev_dbg(dev, "queue #%u available\n", i);
}
if (ccp->cmd_q_count == 0) {
dev_notice(dev, "no command queues available\n");
ret = -EIO;
goto e_pool;
}
dev_notice(dev, "%u command queues available\n", ccp->cmd_q_count);
/* Disable and clear interrupts until ready */
ccp_disable_queue_interrupts(ccp);
for (i = 0; i < ccp->cmd_q_count; i++) {
cmd_q = &ccp->cmd_q[i];
ioread32(cmd_q->reg_int_status);
ioread32(cmd_q->reg_status);
}
iowrite32(ccp->qim, ccp->io_regs + IRQ_STATUS_REG);
/* Request an irq */
ret = sp_request_ccp_irq(ccp->sp, ccp_irq_handler, ccp->name, ccp);
if (ret) {
dev_err(dev, "unable to allocate an IRQ\n");
goto e_pool;
}
/* Initialize the ISR tasklet? */
if (ccp->use_tasklet)
tasklet_init(&ccp->irq_tasklet, ccp_irq_bh,
(unsigned long)ccp);
dev_dbg(dev, "Starting threads...\n");
/* Create a kthread for each queue */
for (i = 0; i < ccp->cmd_q_count; i++) {
struct task_struct *kthread;
cmd_q = &ccp->cmd_q[i];
kthread = kthread_run(ccp_cmd_queue_thread, cmd_q,
"%s-q%u", ccp->name, cmd_q->id);
if (IS_ERR(kthread)) {
dev_err(dev, "error creating queue thread (%ld)\n",
PTR_ERR(kthread));
ret = PTR_ERR(kthread);
goto e_kthread;
}
cmd_q->kthread = kthread;
}
dev_dbg(dev, "Enabling interrupts...\n");
/* Enable interrupts */
ccp_enable_queue_interrupts(ccp);
dev_dbg(dev, "Registering device...\n");
ccp_add_device(ccp);
ret = ccp_register_rng(ccp);
if (ret)
goto e_kthread;
/* Register the DMA engine support */
ret = ccp_dmaengine_register(ccp);
if (ret)
goto e_hwrng;
return 0;
e_hwrng:
ccp_unregister_rng(ccp);
e_kthread:
for (i = 0; i < ccp->cmd_q_count; i++)
if (ccp->cmd_q[i].kthread)
kthread_stop(ccp->cmd_q[i].kthread);
sp_free_ccp_irq(ccp->sp, ccp);
e_pool:
for (i = 0; i < ccp->cmd_q_count; i++)
dma_pool_destroy(ccp->cmd_q[i].dma_pool);
return ret;
}
static void ccp_destroy(struct ccp_device *ccp)
{
struct ccp_cmd_queue *cmd_q;
struct ccp_cmd *cmd;
unsigned int i;
/* Unregister the DMA engine */
ccp_dmaengine_unregister(ccp);
/* Unregister the RNG */
ccp_unregister_rng(ccp);
/* Remove this device from the list of available units */
ccp_del_device(ccp);
/* Disable and clear interrupts */
ccp_disable_queue_interrupts(ccp);
for (i = 0; i < ccp->cmd_q_count; i++) {
cmd_q = &ccp->cmd_q[i];
ioread32(cmd_q->reg_int_status);
ioread32(cmd_q->reg_status);
}
iowrite32(ccp->qim, ccp->io_regs + IRQ_STATUS_REG);
/* Stop the queue kthreads */
for (i = 0; i < ccp->cmd_q_count; i++)
if (ccp->cmd_q[i].kthread)
kthread_stop(ccp->cmd_q[i].kthread);
sp_free_ccp_irq(ccp->sp, ccp);
for (i = 0; i < ccp->cmd_q_count; i++)
dma_pool_destroy(ccp->cmd_q[i].dma_pool);
/* Flush the cmd and backlog queue */
while (!list_empty(&ccp->cmd)) {
/* Invoke the callback directly with an error code */
cmd = list_first_entry(&ccp->cmd, struct ccp_cmd, entry);
list_del(&cmd->entry);
cmd->callback(cmd->data, -ENODEV);
}
while (!list_empty(&ccp->backlog)) {
/* Invoke the callback directly with an error code */
cmd = list_first_entry(&ccp->backlog, struct ccp_cmd, entry);
list_del(&cmd->entry);
cmd->callback(cmd->data, -ENODEV);
}
}
static const struct ccp_actions ccp3_actions = {
.aes = ccp_perform_aes,
.xts_aes = ccp_perform_xts_aes,
.des3 = NULL,
.sha = ccp_perform_sha,
.rsa = ccp_perform_rsa,
.passthru = ccp_perform_passthru,
.ecc = ccp_perform_ecc,
.sballoc = ccp_alloc_ksb,
.sbfree = ccp_free_ksb,
.init = ccp_init,
.destroy = ccp_destroy,
.get_free_slots = ccp_get_free_slots,
.irqhandler = ccp_irq_handler,
};
const struct ccp_vdata ccpv3_platform = {
.version = CCP_VERSION(3, 0),
.setup = NULL,
.perform = &ccp3_actions,
.offset = 0,
.rsamax = CCP_RSA_MAX_WIDTH,
};
const struct ccp_vdata ccpv3 = {
.version = CCP_VERSION(3, 0),
.setup = NULL,
.perform = &ccp3_actions,
.offset = 0x20000,
.rsamax = CCP_RSA_MAX_WIDTH,
};
File diff suppressed because it is too large Load Diff
+670
View File
@@ -0,0 +1,670 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* AMD Cryptographic Coprocessor (CCP) driver
*
* Copyright (C) 2013,2019 Advanced Micro Devices, Inc.
*
* Author: Tom Lendacky <thomas.lendacky@amd.com>
* Author: Gary R Hook <gary.hook@amd.com>
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/kthread.h>
#include <linux/sched.h>
#include <linux/interrupt.h>
#include <linux/spinlock.h>
#include <linux/spinlock_types.h>
#include <linux/types.h>
#include <linux/mutex.h>
#include <linux/delay.h>
#include <linux/hw_random.h>
#include <linux/cpu.h>
#include <linux/atomic.h>
#ifdef CONFIG_X86
#include <asm/cpu_device_id.h>
#endif
#include <linux/ccp.h>
#include "ccp-dev.h"
#define MAX_CCPS 32
/* Limit CCP use to a specifed number of queues per device */
static unsigned int nqueues;
module_param(nqueues, uint, 0444);
MODULE_PARM_DESC(nqueues, "Number of queues per CCP (minimum 1; default: all available)");
/* Limit the maximum number of configured CCPs */
static atomic_t dev_count = ATOMIC_INIT(0);
static unsigned int max_devs = MAX_CCPS;
module_param(max_devs, uint, 0444);
MODULE_PARM_DESC(max_devs, "Maximum number of CCPs to enable (default: all; 0 disables all CCPs)");
struct ccp_tasklet_data {
struct completion completion;
struct ccp_cmd *cmd;
};
/* Human-readable error strings */
#define CCP_MAX_ERROR_CODE 64
static char *ccp_error_codes[] = {
"",
"ILLEGAL_ENGINE",
"ILLEGAL_KEY_ID",
"ILLEGAL_FUNCTION_TYPE",
"ILLEGAL_FUNCTION_MODE",
"ILLEGAL_FUNCTION_ENCRYPT",
"ILLEGAL_FUNCTION_SIZE",
"Zlib_MISSING_INIT_EOM",
"ILLEGAL_FUNCTION_RSVD",
"ILLEGAL_BUFFER_LENGTH",
"VLSB_FAULT",
"ILLEGAL_MEM_ADDR",
"ILLEGAL_MEM_SEL",
"ILLEGAL_CONTEXT_ID",
"ILLEGAL_KEY_ADDR",
"0xF Reserved",
"Zlib_ILLEGAL_MULTI_QUEUE",
"Zlib_ILLEGAL_JOBID_CHANGE",
"CMD_TIMEOUT",
"IDMA0_AXI_SLVERR",
"IDMA0_AXI_DECERR",
"0x15 Reserved",
"IDMA1_AXI_SLAVE_FAULT",
"IDMA1_AIXI_DECERR",
"0x18 Reserved",
"ZLIBVHB_AXI_SLVERR",
"ZLIBVHB_AXI_DECERR",
"0x1B Reserved",
"ZLIB_UNEXPECTED_EOM",
"ZLIB_EXTRA_DATA",
"ZLIB_BTYPE",
"ZLIB_UNDEFINED_SYMBOL",
"ZLIB_UNDEFINED_DISTANCE_S",
"ZLIB_CODE_LENGTH_SYMBOL",
"ZLIB _VHB_ILLEGAL_FETCH",
"ZLIB_UNCOMPRESSED_LEN",
"ZLIB_LIMIT_REACHED",
"ZLIB_CHECKSUM_MISMATCH0",
"ODMA0_AXI_SLVERR",
"ODMA0_AXI_DECERR",
"0x28 Reserved",
"ODMA1_AXI_SLVERR",
"ODMA1_AXI_DECERR",
};
void ccp_log_error(struct ccp_device *d, unsigned int e)
{
if (WARN_ON(e >= CCP_MAX_ERROR_CODE))
return;
if (e < ARRAY_SIZE(ccp_error_codes))
dev_err(d->dev, "CCP error %d: %s\n", e, ccp_error_codes[e]);
else
dev_err(d->dev, "CCP error %d: Unknown Error\n", e);
}
/* List of CCPs, CCP count, read-write access lock, and access functions
*
* Lock structure: get ccp_unit_lock for reading whenever we need to
* examine the CCP list. While holding it for reading we can acquire
* the RR lock to update the round-robin next-CCP pointer. The unit lock
* must be acquired before the RR lock.
*
* If the unit-lock is acquired for writing, we have total control over
* the list, so there's no value in getting the RR lock.
*/
static DEFINE_RWLOCK(ccp_unit_lock);
static LIST_HEAD(ccp_units);
/* Round-robin counter */
static DEFINE_SPINLOCK(ccp_rr_lock);
static struct ccp_device *ccp_rr;
/**
* ccp_add_device - add a CCP device to the list
*
* @ccp: ccp_device struct pointer
*
* Put this CCP on the unit list, which makes it available
* for use.
*
* Returns zero if a CCP device is present, -ENODEV otherwise.
*/
void ccp_add_device(struct ccp_device *ccp)
{
unsigned long flags;
write_lock_irqsave(&ccp_unit_lock, flags);
list_add_tail(&ccp->entry, &ccp_units);
if (!ccp_rr)
/* We already have the list lock (we're first) so this
* pointer can't change on us. Set its initial value.
*/
ccp_rr = ccp;
write_unlock_irqrestore(&ccp_unit_lock, flags);
}
/**
* ccp_del_device - remove a CCP device from the list
*
* @ccp: ccp_device struct pointer
*
* Remove this unit from the list of devices. If the next device
* up for use is this one, adjust the pointer. If this is the last
* device, NULL the pointer.
*/
void ccp_del_device(struct ccp_device *ccp)
{
unsigned long flags;
write_lock_irqsave(&ccp_unit_lock, flags);
if (ccp_rr == ccp) {
/* ccp_unit_lock is read/write; any read access
* will be suspended while we make changes to the
* list and RR pointer.
*/
if (list_is_last(&ccp_rr->entry, &ccp_units))
ccp_rr = list_first_entry(&ccp_units, struct ccp_device,
entry);
else
ccp_rr = list_next_entry(ccp_rr, entry);
}
list_del(&ccp->entry);
if (list_empty(&ccp_units))
ccp_rr = NULL;
write_unlock_irqrestore(&ccp_unit_lock, flags);
}
int ccp_register_rng(struct ccp_device *ccp)
{
int ret = 0;
dev_dbg(ccp->dev, "Registering RNG...\n");
/* Register an RNG */
ccp->hwrng.name = ccp->rngname;
ccp->hwrng.read = ccp_trng_read;
ret = hwrng_register(&ccp->hwrng);
if (ret)
dev_err(ccp->dev, "error registering hwrng (%d)\n", ret);
return ret;
}
void ccp_unregister_rng(struct ccp_device *ccp)
{
if (ccp->hwrng.name)
hwrng_unregister(&ccp->hwrng);
}
static struct ccp_device *ccp_get_device(void)
{
unsigned long flags;
struct ccp_device *dp = NULL;
/* We round-robin through the unit list.
* The (ccp_rr) pointer refers to the next unit to use.
*/
read_lock_irqsave(&ccp_unit_lock, flags);
if (!list_empty(&ccp_units)) {
spin_lock(&ccp_rr_lock);
dp = ccp_rr;
if (list_is_last(&ccp_rr->entry, &ccp_units))
ccp_rr = list_first_entry(&ccp_units, struct ccp_device,
entry);
else
ccp_rr = list_next_entry(ccp_rr, entry);
spin_unlock(&ccp_rr_lock);
}
read_unlock_irqrestore(&ccp_unit_lock, flags);
return dp;
}
/**
* ccp_present - check if a CCP device is present
*
* Returns zero if a CCP device is present, -ENODEV otherwise.
*/
int ccp_present(void)
{
unsigned long flags;
int ret;
read_lock_irqsave(&ccp_unit_lock, flags);
ret = list_empty(&ccp_units);
read_unlock_irqrestore(&ccp_unit_lock, flags);
return ret ? -ENODEV : 0;
}
EXPORT_SYMBOL_GPL(ccp_present);
/**
* ccp_version - get the version of the CCP device
*
* Returns the version from the first unit on the list;
* otherwise a zero if no CCP device is present
*/
unsigned int ccp_version(void)
{
struct ccp_device *dp;
unsigned long flags;
int ret = 0;
read_lock_irqsave(&ccp_unit_lock, flags);
if (!list_empty(&ccp_units)) {
dp = list_first_entry(&ccp_units, struct ccp_device, entry);
ret = dp->vdata->version;
}
read_unlock_irqrestore(&ccp_unit_lock, flags);
return ret;
}
EXPORT_SYMBOL_GPL(ccp_version);
/**
* ccp_enqueue_cmd - queue an operation for processing by the CCP
*
* @cmd: ccp_cmd struct to be processed
*
* Queue a cmd to be processed by the CCP. If queueing the cmd
* would exceed the defined length of the cmd queue the cmd will
* only be queued if the CCP_CMD_MAY_BACKLOG flag is set and will
* result in a return code of -EBUSY.
*
* The callback routine specified in the ccp_cmd struct will be
* called to notify the caller of completion (if the cmd was not
* backlogged) or advancement out of the backlog. If the cmd has
* advanced out of the backlog the "err" value of the callback
* will be -EINPROGRESS. Any other "err" value during callback is
* the result of the operation.
*
* The cmd has been successfully queued if:
* the return code is -EINPROGRESS or
* the return code is -EBUSY and CCP_CMD_MAY_BACKLOG flag is set
*/
int ccp_enqueue_cmd(struct ccp_cmd *cmd)
{
struct ccp_device *ccp;
unsigned long flags;
unsigned int i;
int ret;
/* Some commands might need to be sent to a specific device */
ccp = cmd->ccp ? cmd->ccp : ccp_get_device();
if (!ccp)
return -ENODEV;
/* Caller must supply a callback routine */
if (!cmd->callback)
return -EINVAL;
cmd->ccp = ccp;
spin_lock_irqsave(&ccp->cmd_lock, flags);
i = ccp->cmd_q_count;
if (ccp->cmd_count >= MAX_CMD_QLEN) {
if (cmd->flags & CCP_CMD_MAY_BACKLOG) {
ret = -EBUSY;
list_add_tail(&cmd->entry, &ccp->backlog);
} else {
ret = -ENOSPC;
}
} else {
ret = -EINPROGRESS;
ccp->cmd_count++;
list_add_tail(&cmd->entry, &ccp->cmd);
/* Find an idle queue */
if (!ccp->suspending) {
for (i = 0; i < ccp->cmd_q_count; i++) {
if (ccp->cmd_q[i].active)
continue;
break;
}
}
}
spin_unlock_irqrestore(&ccp->cmd_lock, flags);
/* If we found an idle queue, wake it up */
if (i < ccp->cmd_q_count)
wake_up_process(ccp->cmd_q[i].kthread);
return ret;
}
EXPORT_SYMBOL_GPL(ccp_enqueue_cmd);
static void ccp_do_cmd_backlog(struct work_struct *work)
{
struct ccp_cmd *cmd = container_of(work, struct ccp_cmd, work);
struct ccp_device *ccp = cmd->ccp;
unsigned long flags;
unsigned int i;
cmd->callback(cmd->data, -EINPROGRESS);
spin_lock_irqsave(&ccp->cmd_lock, flags);
ccp->cmd_count++;
list_add_tail(&cmd->entry, &ccp->cmd);
/* Find an idle queue */
for (i = 0; i < ccp->cmd_q_count; i++) {
if (ccp->cmd_q[i].active)
continue;
break;
}
spin_unlock_irqrestore(&ccp->cmd_lock, flags);
/* If we found an idle queue, wake it up */
if (i < ccp->cmd_q_count)
wake_up_process(ccp->cmd_q[i].kthread);
}
static struct ccp_cmd *ccp_dequeue_cmd(struct ccp_cmd_queue *cmd_q)
{
struct ccp_device *ccp = cmd_q->ccp;
struct ccp_cmd *cmd = NULL;
struct ccp_cmd *backlog = NULL;
unsigned long flags;
spin_lock_irqsave(&ccp->cmd_lock, flags);
cmd_q->active = 0;
if (ccp->suspending) {
cmd_q->suspended = 1;
spin_unlock_irqrestore(&ccp->cmd_lock, flags);
wake_up_interruptible(&ccp->suspend_queue);
return NULL;
}
if (ccp->cmd_count) {
cmd_q->active = 1;
cmd = list_first_entry(&ccp->cmd, struct ccp_cmd, entry);
list_del(&cmd->entry);
ccp->cmd_count--;
}
if (!list_empty(&ccp->backlog)) {
backlog = list_first_entry(&ccp->backlog, struct ccp_cmd,
entry);
list_del(&backlog->entry);
}
spin_unlock_irqrestore(&ccp->cmd_lock, flags);
if (backlog) {
INIT_WORK(&backlog->work, ccp_do_cmd_backlog);
schedule_work(&backlog->work);
}
return cmd;
}
static void ccp_do_cmd_complete(unsigned long data)
{
struct ccp_tasklet_data *tdata = (struct ccp_tasklet_data *)data;
struct ccp_cmd *cmd = tdata->cmd;
cmd->callback(cmd->data, cmd->ret);
complete(&tdata->completion);
}
/**
* ccp_cmd_queue_thread - create a kernel thread to manage a CCP queue
*
* @data: thread-specific data
*/
int ccp_cmd_queue_thread(void *data)
{
struct ccp_cmd_queue *cmd_q = (struct ccp_cmd_queue *)data;
struct ccp_cmd *cmd;
struct ccp_tasklet_data tdata;
struct tasklet_struct tasklet;
tasklet_init(&tasklet, ccp_do_cmd_complete, (unsigned long)&tdata);
set_current_state(TASK_INTERRUPTIBLE);
while (!kthread_should_stop()) {
schedule();
set_current_state(TASK_INTERRUPTIBLE);
cmd = ccp_dequeue_cmd(cmd_q);
if (!cmd)
continue;
__set_current_state(TASK_RUNNING);
/* Execute the command */
cmd->ret = ccp_run_cmd(cmd_q, cmd);
/* Schedule the completion callback */
tdata.cmd = cmd;
init_completion(&tdata.completion);
tasklet_schedule(&tasklet);
wait_for_completion(&tdata.completion);
}
__set_current_state(TASK_RUNNING);
return 0;
}
/**
* ccp_alloc_struct - allocate and initialize the ccp_device struct
*
* @sp: sp_device struct of the CCP
*/
struct ccp_device *ccp_alloc_struct(struct sp_device *sp)
{
struct device *dev = sp->dev;
struct ccp_device *ccp;
ccp = devm_kzalloc(dev, sizeof(*ccp), GFP_KERNEL);
if (!ccp)
return NULL;
ccp->dev = dev;
ccp->sp = sp;
ccp->axcache = sp->axcache;
INIT_LIST_HEAD(&ccp->cmd);
INIT_LIST_HEAD(&ccp->backlog);
spin_lock_init(&ccp->cmd_lock);
mutex_init(&ccp->req_mutex);
mutex_init(&ccp->sb_mutex);
ccp->sb_count = KSB_COUNT;
ccp->sb_start = 0;
/* Initialize the wait queues */
init_waitqueue_head(&ccp->sb_queue);
init_waitqueue_head(&ccp->suspend_queue);
snprintf(ccp->name, MAX_CCP_NAME_LEN, "ccp-%u", sp->ord);
snprintf(ccp->rngname, MAX_CCP_NAME_LEN, "ccp-%u-rng", sp->ord);
return ccp;
}
int ccp_trng_read(struct hwrng *rng, void *data, size_t max, bool wait)
{
struct ccp_device *ccp = container_of(rng, struct ccp_device, hwrng);
u32 trng_value;
int len = min(sizeof(trng_value), max);
/* Locking is provided by the caller so we can update device
* hwrng-related fields safely
*/
trng_value = ioread32(ccp->io_regs + TRNG_OUT_REG);
if (!trng_value) {
/* Zero is returned if not data is available or if a
* bad-entropy error is present. Assume an error if
* we exceed TRNG_RETRIES reads of zero.
*/
if (ccp->hwrng_retries++ > TRNG_RETRIES)
return -EIO;
return 0;
}
/* Reset the counter and save the rng value */
ccp->hwrng_retries = 0;
memcpy(data, &trng_value, len);
return len;
}
bool ccp_queues_suspended(struct ccp_device *ccp)
{
unsigned int suspended = 0;
unsigned long flags;
unsigned int i;
spin_lock_irqsave(&ccp->cmd_lock, flags);
for (i = 0; i < ccp->cmd_q_count; i++)
if (ccp->cmd_q[i].suspended)
suspended++;
spin_unlock_irqrestore(&ccp->cmd_lock, flags);
return ccp->cmd_q_count == suspended;
}
void ccp_dev_suspend(struct sp_device *sp)
{
struct ccp_device *ccp = sp->ccp_data;
unsigned long flags;
unsigned int i;
/* If there's no device there's nothing to do */
if (!ccp)
return;
spin_lock_irqsave(&ccp->cmd_lock, flags);
ccp->suspending = 1;
/* Wake all the queue kthreads to prepare for suspend */
for (i = 0; i < ccp->cmd_q_count; i++)
wake_up_process(ccp->cmd_q[i].kthread);
spin_unlock_irqrestore(&ccp->cmd_lock, flags);
/* Wait for all queue kthreads to say they're done */
while (!ccp_queues_suspended(ccp))
wait_event_interruptible(ccp->suspend_queue,
ccp_queues_suspended(ccp));
}
void ccp_dev_resume(struct sp_device *sp)
{
struct ccp_device *ccp = sp->ccp_data;
unsigned long flags;
unsigned int i;
/* If there's no device there's nothing to do */
if (!ccp)
return;
spin_lock_irqsave(&ccp->cmd_lock, flags);
ccp->suspending = 0;
/* Wake up all the kthreads */
for (i = 0; i < ccp->cmd_q_count; i++) {
ccp->cmd_q[i].suspended = 0;
wake_up_process(ccp->cmd_q[i].kthread);
}
spin_unlock_irqrestore(&ccp->cmd_lock, flags);
}
int ccp_dev_init(struct sp_device *sp)
{
struct device *dev = sp->dev;
struct ccp_device *ccp;
int ret;
/*
* Check how many we have so far, and stop after reaching
* that number
*/
if (atomic_inc_return(&dev_count) > max_devs)
return 0; /* don't fail the load */
ret = -ENOMEM;
ccp = ccp_alloc_struct(sp);
if (!ccp)
goto e_err;
sp->ccp_data = ccp;
if (!nqueues || (nqueues > MAX_HW_QUEUES))
ccp->max_q_count = MAX_HW_QUEUES;
else
ccp->max_q_count = nqueues;
ccp->vdata = (struct ccp_vdata *)sp->dev_vdata->ccp_vdata;
if (!ccp->vdata || !ccp->vdata->version) {
ret = -ENODEV;
dev_err(dev, "missing driver data\n");
goto e_err;
}
ccp->use_tasklet = sp->use_tasklet;
ccp->io_regs = sp->io_map + ccp->vdata->offset;
if (ccp->vdata->setup)
ccp->vdata->setup(ccp);
ret = ccp->vdata->perform->init(ccp);
if (ret) {
/* A positive number means that the device cannot be initialized,
* but no additional message is required.
*/
if (ret > 0)
goto e_quiet;
/* An unexpected problem occurred, and should be reported in the log */
goto e_err;
}
dev_notice(dev, "ccp enabled\n");
return 0;
e_err:
dev_notice(dev, "ccp initialization failed\n");
e_quiet:
sp->ccp_data = NULL;
return ret;
}
void ccp_dev_destroy(struct sp_device *sp)
{
struct ccp_device *ccp = sp->ccp_data;
if (!ccp)
return;
ccp->vdata->perform->destroy(ccp);
}
+673
View File
@@ -0,0 +1,673 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* AMD Cryptographic Coprocessor (CCP) driver
*
* Copyright (C) 2013,2017 Advanced Micro Devices, Inc.
*
* Author: Tom Lendacky <thomas.lendacky@amd.com>
* Author: Gary R Hook <gary.hook@amd.com>
*/
#ifndef __CCP_DEV_H__
#define __CCP_DEV_H__
#include <linux/device.h>
#include <linux/spinlock.h>
#include <linux/mutex.h>
#include <linux/list.h>
#include <linux/wait.h>
#include <linux/dma-direction.h>
#include <linux/dmapool.h>
#include <linux/hw_random.h>
#include <linux/bitops.h>
#include <linux/interrupt.h>
#include <linux/irqreturn.h>
#include <linux/dmaengine.h>
#include "sp-dev.h"
#define MAX_CCP_NAME_LEN 16
#define MAX_DMAPOOL_NAME_LEN 32
#define MAX_HW_QUEUES 5
#define MAX_CMD_QLEN 100
#define TRNG_RETRIES 10
#define CACHE_NONE 0x00
#define CACHE_WB_NO_ALLOC 0xb7
/****** Register Mappings ******/
#define Q_MASK_REG 0x000
#define TRNG_OUT_REG 0x00c
#define IRQ_MASK_REG 0x040
#define IRQ_STATUS_REG 0x200
#define DEL_CMD_Q_JOB 0x124
#define DEL_Q_ACTIVE 0x00000200
#define DEL_Q_ID_SHIFT 6
#define CMD_REQ0 0x180
#define CMD_REQ_INCR 0x04
#define CMD_Q_STATUS_BASE 0x210
#define CMD_Q_INT_STATUS_BASE 0x214
#define CMD_Q_STATUS_INCR 0x20
#define CMD_Q_CACHE_BASE 0x228
#define CMD_Q_CACHE_INC 0x20
#define CMD_Q_ERROR(__qs) ((__qs) & 0x0000003f)
#define CMD_Q_DEPTH(__qs) (((__qs) >> 12) & 0x0000000f)
/* ------------------------ CCP Version 5 Specifics ------------------------ */
#define CMD5_QUEUE_MASK_OFFSET 0x00
#define CMD5_QUEUE_PRIO_OFFSET 0x04
#define CMD5_REQID_CONFIG_OFFSET 0x08
#define CMD5_CMD_TIMEOUT_OFFSET 0x10
#define LSB_PUBLIC_MASK_LO_OFFSET 0x18
#define LSB_PUBLIC_MASK_HI_OFFSET 0x1C
#define LSB_PRIVATE_MASK_LO_OFFSET 0x20
#define LSB_PRIVATE_MASK_HI_OFFSET 0x24
#define CMD5_PSP_CCP_VERSION 0x100
#define CMD5_Q_CONTROL_BASE 0x0000
#define CMD5_Q_TAIL_LO_BASE 0x0004
#define CMD5_Q_HEAD_LO_BASE 0x0008
#define CMD5_Q_INT_ENABLE_BASE 0x000C
#define CMD5_Q_INTERRUPT_STATUS_BASE 0x0010
#define CMD5_Q_STATUS_BASE 0x0100
#define CMD5_Q_INT_STATUS_BASE 0x0104
#define CMD5_Q_DMA_STATUS_BASE 0x0108
#define CMD5_Q_DMA_READ_STATUS_BASE 0x010C
#define CMD5_Q_DMA_WRITE_STATUS_BASE 0x0110
#define CMD5_Q_ABORT_BASE 0x0114
#define CMD5_Q_AX_CACHE_BASE 0x0118
#define CMD5_CONFIG_0_OFFSET 0x6000
#define CMD5_TRNG_CTL_OFFSET 0x6008
#define CMD5_AES_MASK_OFFSET 0x6010
#define CMD5_CLK_GATE_CTL_OFFSET 0x603C
/* Address offset between two virtual queue registers */
#define CMD5_Q_STATUS_INCR 0x1000
/* Bit masks */
#define CMD5_Q_RUN 0x1
#define CMD5_Q_HALT 0x2
#define CMD5_Q_MEM_LOCATION 0x4
#define CMD5_Q_SIZE 0x1F
#define CMD5_Q_SHIFT 3
#define COMMANDS_PER_QUEUE 16
#define QUEUE_SIZE_VAL ((ffs(COMMANDS_PER_QUEUE) - 2) & \
CMD5_Q_SIZE)
#define Q_PTR_MASK (2 << (QUEUE_SIZE_VAL + 5) - 1)
#define Q_DESC_SIZE sizeof(struct ccp5_desc)
#define Q_SIZE(n) (COMMANDS_PER_QUEUE*(n))
#define INT_COMPLETION 0x1
#define INT_ERROR 0x2
#define INT_QUEUE_STOPPED 0x4
#define INT_EMPTY_QUEUE 0x8
#define SUPPORTED_INTERRUPTS (INT_COMPLETION | INT_ERROR)
#define LSB_REGION_WIDTH 5
#define MAX_LSB_CNT 8
#define LSB_SIZE 16
#define LSB_ITEM_SIZE 32
#define PLSB_MAP_SIZE (LSB_SIZE)
#define SLSB_MAP_SIZE (MAX_LSB_CNT * LSB_SIZE)
#define LSB_ENTRY_NUMBER(LSB_ADDR) (LSB_ADDR / LSB_ITEM_SIZE)
/* ------------------------ CCP Version 3 Specifics ------------------------ */
#define REQ0_WAIT_FOR_WRITE 0x00000004
#define REQ0_INT_ON_COMPLETE 0x00000002
#define REQ0_STOP_ON_COMPLETE 0x00000001
#define REQ0_CMD_Q_SHIFT 9
#define REQ0_JOBID_SHIFT 3
/****** REQ1 Related Values ******/
#define REQ1_PROTECT_SHIFT 27
#define REQ1_ENGINE_SHIFT 23
#define REQ1_KEY_KSB_SHIFT 2
#define REQ1_EOM 0x00000002
#define REQ1_INIT 0x00000001
/* AES Related Values */
#define REQ1_AES_TYPE_SHIFT 21
#define REQ1_AES_MODE_SHIFT 18
#define REQ1_AES_ACTION_SHIFT 17
#define REQ1_AES_CFB_SIZE_SHIFT 10
/* XTS-AES Related Values */
#define REQ1_XTS_AES_SIZE_SHIFT 10
/* SHA Related Values */
#define REQ1_SHA_TYPE_SHIFT 21
/* RSA Related Values */
#define REQ1_RSA_MOD_SIZE_SHIFT 10
/* Pass-Through Related Values */
#define REQ1_PT_BW_SHIFT 12
#define REQ1_PT_BS_SHIFT 10
/* ECC Related Values */
#define REQ1_ECC_AFFINE_CONVERT 0x00200000
#define REQ1_ECC_FUNCTION_SHIFT 18
/****** REQ4 Related Values ******/
#define REQ4_KSB_SHIFT 18
#define REQ4_MEMTYPE_SHIFT 16
/****** REQ6 Related Values ******/
#define REQ6_MEMTYPE_SHIFT 16
/****** Key Storage Block ******/
#define KSB_START 77
#define KSB_END 127
#define KSB_COUNT (KSB_END - KSB_START + 1)
#define CCP_SB_BITS 256
#define CCP_JOBID_MASK 0x0000003f
/* ------------------------ General CCP Defines ------------------------ */
#define CCP_DMA_DFLT 0x0
#define CCP_DMA_PRIV 0x1
#define CCP_DMA_PUB 0x2
#define CCP_DMAPOOL_MAX_SIZE 64
#define CCP_DMAPOOL_ALIGN BIT(5)
#define CCP_REVERSE_BUF_SIZE 64
#define CCP_AES_KEY_SB_COUNT 1
#define CCP_AES_CTX_SB_COUNT 1
#define CCP_XTS_AES_KEY_SB_COUNT 1
#define CCP5_XTS_AES_KEY_SB_COUNT 2
#define CCP_XTS_AES_CTX_SB_COUNT 1
#define CCP_DES3_KEY_SB_COUNT 1
#define CCP_DES3_CTX_SB_COUNT 1
#define CCP_SHA_SB_COUNT 1
#define CCP_RSA_MAX_WIDTH 4096
#define CCP5_RSA_MAX_WIDTH 16384
#define CCP_PASSTHRU_BLOCKSIZE 256
#define CCP_PASSTHRU_MASKSIZE 32
#define CCP_PASSTHRU_SB_COUNT 1
#define CCP_ECC_MODULUS_BYTES 48 /* 384-bits */
#define CCP_ECC_MAX_OPERANDS 6
#define CCP_ECC_MAX_OUTPUTS 3
#define CCP_ECC_SRC_BUF_SIZE 448
#define CCP_ECC_DST_BUF_SIZE 192
#define CCP_ECC_OPERAND_SIZE 64
#define CCP_ECC_OUTPUT_SIZE 64
#define CCP_ECC_RESULT_OFFSET 60
#define CCP_ECC_RESULT_SUCCESS 0x0001
#define CCP_SB_BYTES 32
struct ccp_op;
struct ccp_device;
struct ccp_cmd;
struct ccp_fns;
struct ccp_dma_cmd {
struct list_head entry;
struct ccp_cmd ccp_cmd;
};
struct ccp_dma_desc {
struct list_head entry;
struct ccp_device *ccp;
struct list_head pending;
struct list_head active;
enum dma_status status;
struct dma_async_tx_descriptor tx_desc;
size_t len;
};
struct ccp_dma_chan {
struct ccp_device *ccp;
spinlock_t lock;
struct list_head created;
struct list_head pending;
struct list_head active;
struct list_head complete;
struct tasklet_struct cleanup_tasklet;
enum dma_status status;
struct dma_chan dma_chan;
};
struct ccp_cmd_queue {
struct ccp_device *ccp;
/* Queue identifier */
u32 id;
/* Queue dma pool */
struct dma_pool *dma_pool;
/* Queue base address (not neccessarily aligned)*/
struct ccp5_desc *qbase;
/* Aligned queue start address (per requirement) */
struct mutex q_mutex ____cacheline_aligned;
unsigned int qidx;
/* Version 5 has different requirements for queue memory */
unsigned int qsize;
dma_addr_t qbase_dma;
dma_addr_t qdma_tail;
/* Per-queue reserved storage block(s) */
u32 sb_key;
u32 sb_ctx;
/* Bitmap of LSBs that can be accessed by this queue */
DECLARE_BITMAP(lsbmask, MAX_LSB_CNT);
/* Private LSB that is assigned to this queue, or -1 if none.
* Bitmap for my private LSB, unused otherwise
*/
int lsb;
DECLARE_BITMAP(lsbmap, PLSB_MAP_SIZE);
/* Queue processing thread */
struct task_struct *kthread;
unsigned int active;
unsigned int suspended;
/* Number of free command slots available */
unsigned int free_slots;
/* Interrupt masks */
u32 int_ok;
u32 int_err;
/* Register addresses for queue */
void __iomem *reg_control;
void __iomem *reg_tail_lo;
void __iomem *reg_head_lo;
void __iomem *reg_int_enable;
void __iomem *reg_interrupt_status;
void __iomem *reg_status;
void __iomem *reg_int_status;
void __iomem *reg_dma_status;
void __iomem *reg_dma_read_status;
void __iomem *reg_dma_write_status;
u32 qcontrol; /* Cached control register */
/* Status values from job */
u32 int_status;
u32 q_status;
u32 q_int_status;
u32 cmd_error;
/* Interrupt wait queue */
wait_queue_head_t int_queue;
unsigned int int_rcvd;
/* Per-queue Statistics */
unsigned long total_ops;
unsigned long total_aes_ops;
unsigned long total_xts_aes_ops;
unsigned long total_3des_ops;
unsigned long total_sha_ops;
unsigned long total_rsa_ops;
unsigned long total_pt_ops;
unsigned long total_ecc_ops;
} ____cacheline_aligned;
struct ccp_device {
struct list_head entry;
struct ccp_vdata *vdata;
unsigned int ord;
char name[MAX_CCP_NAME_LEN];
char rngname[MAX_CCP_NAME_LEN];
struct device *dev;
struct sp_device *sp;
/* Bus specific device information
*/
void *dev_specific;
unsigned int qim;
unsigned int irq;
bool use_tasklet;
struct tasklet_struct irq_tasklet;
/* I/O area used for device communication. The register mapping
* starts at an offset into the mapped bar.
* The CMD_REQx registers and the Delete_Cmd_Queue_Job register
* need to be protected while a command queue thread is accessing
* them.
*/
struct mutex req_mutex ____cacheline_aligned;
void __iomem *io_regs;
/* Master lists that all cmds are queued on. Because there can be
* more than one CCP command queue that can process a cmd a separate
* backlog list is needed so that the backlog completion call
* completes before the cmd is available for execution.
*/
spinlock_t cmd_lock ____cacheline_aligned;
unsigned int cmd_count;
struct list_head cmd;
struct list_head backlog;
/* The command queues. These represent the queues available on the
* CCP that are available for processing cmds
*/
struct ccp_cmd_queue cmd_q[MAX_HW_QUEUES];
unsigned int cmd_q_count;
unsigned int max_q_count;
/* Support for the CCP True RNG
*/
struct hwrng hwrng;
unsigned int hwrng_retries;
/* Support for the CCP DMA capabilities
*/
struct dma_device dma_dev;
struct ccp_dma_chan *ccp_dma_chan;
struct kmem_cache *dma_cmd_cache;
struct kmem_cache *dma_desc_cache;
/* A counter used to generate job-ids for cmds submitted to the CCP
*/
atomic_t current_id ____cacheline_aligned;
/* The v3 CCP uses key storage blocks (SB) to maintain context for
* certain operations. To prevent multiple cmds from using the same
* SB range a command queue reserves an SB range for the duration of
* the cmd. Each queue, will however, reserve 2 SB blocks for
* operations that only require single SB entries (eg. AES context/iv
* and key) in order to avoid allocation contention. This will reserve
* at most 10 SB entries, leaving 40 SB entries available for dynamic
* allocation.
*
* The v5 CCP Local Storage Block (LSB) is broken up into 8
* memrory ranges, each of which can be enabled for access by one
* or more queues. Device initialization takes this into account,
* and attempts to assign one region for exclusive use by each
* available queue; the rest are then aggregated as "public" use.
* If there are fewer regions than queues, all regions are shared
* amongst all queues.
*/
struct mutex sb_mutex ____cacheline_aligned;
DECLARE_BITMAP(sb, KSB_COUNT);
wait_queue_head_t sb_queue;
unsigned int sb_avail;
unsigned int sb_count;
u32 sb_start;
/* Bitmap of shared LSBs, if any */
DECLARE_BITMAP(lsbmap, SLSB_MAP_SIZE);
/* Suspend support */
unsigned int suspending;
wait_queue_head_t suspend_queue;
/* DMA caching attribute support */
unsigned int axcache;
/* Device Statistics */
unsigned long total_interrupts;
/* DebugFS info */
struct dentry *debugfs_instance;
};
enum ccp_memtype {
CCP_MEMTYPE_SYSTEM = 0,
CCP_MEMTYPE_SB,
CCP_MEMTYPE_LOCAL,
CCP_MEMTYPE__LAST,
};
#define CCP_MEMTYPE_LSB CCP_MEMTYPE_KSB
struct ccp_dma_info {
dma_addr_t address;
unsigned int offset;
unsigned int length;
enum dma_data_direction dir;
} __packed __aligned(4);
struct ccp_dm_workarea {
struct device *dev;
struct dma_pool *dma_pool;
u8 *address;
struct ccp_dma_info dma;
unsigned int length;
};
struct ccp_sg_workarea {
struct scatterlist *sg;
int nents;
unsigned int sg_used;
struct scatterlist *dma_sg;
struct scatterlist *dma_sg_head;
struct device *dma_dev;
unsigned int dma_count;
enum dma_data_direction dma_dir;
u64 bytes_left;
};
struct ccp_data {
struct ccp_sg_workarea sg_wa;
struct ccp_dm_workarea dm_wa;
};
struct ccp_mem {
enum ccp_memtype type;
union {
struct ccp_dma_info dma;
u32 sb;
} u;
};
struct ccp_aes_op {
enum ccp_aes_type type;
enum ccp_aes_mode mode;
enum ccp_aes_action action;
unsigned int size;
};
struct ccp_xts_aes_op {
enum ccp_aes_type type;
enum ccp_aes_action action;
enum ccp_xts_aes_unit_size unit_size;
};
struct ccp_des3_op {
enum ccp_des3_type type;
enum ccp_des3_mode mode;
enum ccp_des3_action action;
};
struct ccp_sha_op {
enum ccp_sha_type type;
u64 msg_bits;
};
struct ccp_rsa_op {
u32 mod_size;
u32 input_len;
};
struct ccp_passthru_op {
enum ccp_passthru_bitwise bit_mod;
enum ccp_passthru_byteswap byte_swap;
};
struct ccp_ecc_op {
enum ccp_ecc_function function;
};
struct ccp_op {
struct ccp_cmd_queue *cmd_q;
u32 jobid;
u32 ioc;
u32 soc;
u32 sb_key;
u32 sb_ctx;
u32 init;
u32 eom;
struct ccp_mem src;
struct ccp_mem dst;
struct ccp_mem exp;
union {
struct ccp_aes_op aes;
struct ccp_xts_aes_op xts;
struct ccp_des3_op des3;
struct ccp_sha_op sha;
struct ccp_rsa_op rsa;
struct ccp_passthru_op passthru;
struct ccp_ecc_op ecc;
} u;
};
static inline u32 ccp_addr_lo(struct ccp_dma_info *info)
{
return lower_32_bits(info->address + info->offset);
}
static inline u32 ccp_addr_hi(struct ccp_dma_info *info)
{
return upper_32_bits(info->address + info->offset) & 0x0000ffff;
}
/**
* descriptor for version 5 CPP commands
* 8 32-bit words:
* word 0: function; engine; control bits
* word 1: length of source data
* word 2: low 32 bits of source pointer
* word 3: upper 16 bits of source pointer; source memory type
* word 4: low 32 bits of destination pointer
* word 5: upper 16 bits of destination pointer; destination memory type
* word 6: low 32 bits of key pointer
* word 7: upper 16 bits of key pointer; key memory type
*/
struct dword0 {
unsigned int soc:1;
unsigned int ioc:1;
unsigned int rsvd1:1;
unsigned int init:1;
unsigned int eom:1; /* AES/SHA only */
unsigned int function:15;
unsigned int engine:4;
unsigned int prot:1;
unsigned int rsvd2:7;
};
struct dword3 {
unsigned int src_hi:16;
unsigned int src_mem:2;
unsigned int lsb_cxt_id:8;
unsigned int rsvd1:5;
unsigned int fixed:1;
};
union dword4 {
u32 dst_lo; /* NON-SHA */
u32 sha_len_lo; /* SHA */
};
union dword5 {
struct {
unsigned int dst_hi:16;
unsigned int dst_mem:2;
unsigned int rsvd1:13;
unsigned int fixed:1;
} fields;
u32 sha_len_hi;
};
struct dword7 {
unsigned int key_hi:16;
unsigned int key_mem:2;
unsigned int rsvd1:14;
};
struct ccp5_desc {
struct dword0 dw0;
u32 length;
u32 src_lo;
struct dword3 dw3;
union dword4 dw4;
union dword5 dw5;
u32 key_lo;
struct dword7 dw7;
};
void ccp_add_device(struct ccp_device *ccp);
void ccp_del_device(struct ccp_device *ccp);
extern void ccp_log_error(struct ccp_device *, unsigned int);
struct ccp_device *ccp_alloc_struct(struct sp_device *sp);
bool ccp_queues_suspended(struct ccp_device *ccp);
int ccp_cmd_queue_thread(void *data);
int ccp_trng_read(struct hwrng *rng, void *data, size_t max, bool wait);
int ccp_run_cmd(struct ccp_cmd_queue *cmd_q, struct ccp_cmd *cmd);
int ccp_register_rng(struct ccp_device *ccp);
void ccp_unregister_rng(struct ccp_device *ccp);
int ccp_dmaengine_register(struct ccp_device *ccp);
void ccp_dmaengine_unregister(struct ccp_device *ccp);
void ccp5_debugfs_setup(struct ccp_device *ccp);
void ccp5_debugfs_destroy(void);
/* Structure for computation functions that are device-specific */
struct ccp_actions {
int (*aes)(struct ccp_op *);
int (*xts_aes)(struct ccp_op *);
int (*des3)(struct ccp_op *);
int (*sha)(struct ccp_op *);
int (*rsa)(struct ccp_op *);
int (*passthru)(struct ccp_op *);
int (*ecc)(struct ccp_op *);
u32 (*sballoc)(struct ccp_cmd_queue *, unsigned int);
void (*sbfree)(struct ccp_cmd_queue *, unsigned int, unsigned int);
unsigned int (*get_free_slots)(struct ccp_cmd_queue *);
int (*init)(struct ccp_device *);
void (*destroy)(struct ccp_device *);
irqreturn_t (*irqhandler)(int, void *);
};
extern const struct ccp_vdata ccpv3_platform;
extern const struct ccp_vdata ccpv3;
extern const struct ccp_vdata ccpv5a;
extern const struct ccp_vdata ccpv5b;
#endif
@@ -0,0 +1,791 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* AMD Cryptographic Coprocessor (CCP) driver
*
* Copyright (C) 2016,2019 Advanced Micro Devices, Inc.
*
* Author: Gary R Hook <gary.hook@amd.com>
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/dma-mapping.h>
#include <linux/dmaengine.h>
#include <linux/spinlock.h>
#include <linux/mutex.h>
#include <linux/ccp.h>
#include "ccp-dev.h"
#include "../../dma/dmaengine.h"
#define CCP_DMA_WIDTH(_mask) \
({ \
u64 mask = _mask + 1; \
(mask == 0) ? 64 : fls64(mask); \
})
/* The CCP as a DMA provider can be configured for public or private
* channels. Default is specified in the vdata for the device (PCI ID).
* This module parameter will override for all channels on all devices:
* dma_chan_attr = 0x2 to force all channels public
* = 0x1 to force all channels private
* = 0x0 to defer to the vdata setting
* = any other value: warning, revert to 0x0
*/
static unsigned int dma_chan_attr = CCP_DMA_DFLT;
module_param(dma_chan_attr, uint, 0444);
MODULE_PARM_DESC(dma_chan_attr, "Set DMA channel visibility: 0 (default) = device defaults, 1 = make private, 2 = make public");
static unsigned int dmaengine = 1;
module_param(dmaengine, uint, 0444);
MODULE_PARM_DESC(dmaengine, "Register services with the DMA subsystem (any non-zero value, default: 1)");
static unsigned int ccp_get_dma_chan_attr(struct ccp_device *ccp)
{
switch (dma_chan_attr) {
case CCP_DMA_DFLT:
return ccp->vdata->dma_chan_attr;
case CCP_DMA_PRIV:
return DMA_PRIVATE;
case CCP_DMA_PUB:
return 0;
default:
dev_info_once(ccp->dev, "Invalid value for dma_chan_attr: %d\n",
dma_chan_attr);
return ccp->vdata->dma_chan_attr;
}
}
static void ccp_free_cmd_resources(struct ccp_device *ccp,
struct list_head *list)
{
struct ccp_dma_cmd *cmd, *ctmp;
list_for_each_entry_safe(cmd, ctmp, list, entry) {
list_del(&cmd->entry);
kmem_cache_free(ccp->dma_cmd_cache, cmd);
}
}
static void ccp_free_desc_resources(struct ccp_device *ccp,
struct list_head *list)
{
struct ccp_dma_desc *desc, *dtmp;
list_for_each_entry_safe(desc, dtmp, list, entry) {
ccp_free_cmd_resources(ccp, &desc->active);
ccp_free_cmd_resources(ccp, &desc->pending);
list_del(&desc->entry);
kmem_cache_free(ccp->dma_desc_cache, desc);
}
}
static void ccp_free_chan_resources(struct dma_chan *dma_chan)
{
struct ccp_dma_chan *chan = container_of(dma_chan, struct ccp_dma_chan,
dma_chan);
unsigned long flags;
dev_dbg(chan->ccp->dev, "%s - chan=%p\n", __func__, chan);
spin_lock_irqsave(&chan->lock, flags);
ccp_free_desc_resources(chan->ccp, &chan->complete);
ccp_free_desc_resources(chan->ccp, &chan->active);
ccp_free_desc_resources(chan->ccp, &chan->pending);
ccp_free_desc_resources(chan->ccp, &chan->created);
spin_unlock_irqrestore(&chan->lock, flags);
}
static void ccp_cleanup_desc_resources(struct ccp_device *ccp,
struct list_head *list)
{
struct ccp_dma_desc *desc, *dtmp;
list_for_each_entry_safe_reverse(desc, dtmp, list, entry) {
if (!async_tx_test_ack(&desc->tx_desc))
continue;
dev_dbg(ccp->dev, "%s - desc=%p\n", __func__, desc);
ccp_free_cmd_resources(ccp, &desc->active);
ccp_free_cmd_resources(ccp, &desc->pending);
list_del(&desc->entry);
kmem_cache_free(ccp->dma_desc_cache, desc);
}
}
static void ccp_do_cleanup(unsigned long data)
{
struct ccp_dma_chan *chan = (struct ccp_dma_chan *)data;
unsigned long flags;
dev_dbg(chan->ccp->dev, "%s - chan=%s\n", __func__,
dma_chan_name(&chan->dma_chan));
spin_lock_irqsave(&chan->lock, flags);
ccp_cleanup_desc_resources(chan->ccp, &chan->complete);
spin_unlock_irqrestore(&chan->lock, flags);
}
static int ccp_issue_next_cmd(struct ccp_dma_desc *desc)
{
struct ccp_dma_cmd *cmd;
int ret;
cmd = list_first_entry(&desc->pending, struct ccp_dma_cmd, entry);
list_move(&cmd->entry, &desc->active);
dev_dbg(desc->ccp->dev, "%s - tx %d, cmd=%p\n", __func__,
desc->tx_desc.cookie, cmd);
ret = ccp_enqueue_cmd(&cmd->ccp_cmd);
if (!ret || (ret == -EINPROGRESS) || (ret == -EBUSY))
return 0;
dev_dbg(desc->ccp->dev, "%s - error: ret=%d, tx %d, cmd=%p\n", __func__,
ret, desc->tx_desc.cookie, cmd);
return ret;
}
static void ccp_free_active_cmd(struct ccp_dma_desc *desc)
{
struct ccp_dma_cmd *cmd;
cmd = list_first_entry_or_null(&desc->active, struct ccp_dma_cmd,
entry);
if (!cmd)
return;
dev_dbg(desc->ccp->dev, "%s - freeing tx %d cmd=%p\n",
__func__, desc->tx_desc.cookie, cmd);
list_del(&cmd->entry);
kmem_cache_free(desc->ccp->dma_cmd_cache, cmd);
}
static struct ccp_dma_desc *__ccp_next_dma_desc(struct ccp_dma_chan *chan,
struct ccp_dma_desc *desc)
{
/* Move current DMA descriptor to the complete list */
if (desc)
list_move(&desc->entry, &chan->complete);
/* Get the next DMA descriptor on the active list */
desc = list_first_entry_or_null(&chan->active, struct ccp_dma_desc,
entry);
return desc;
}
static struct ccp_dma_desc *ccp_handle_active_desc(struct ccp_dma_chan *chan,
struct ccp_dma_desc *desc)
{
struct dma_async_tx_descriptor *tx_desc;
unsigned long flags;
/* Loop over descriptors until one is found with commands */
do {
if (desc) {
/* Remove the DMA command from the list and free it */
ccp_free_active_cmd(desc);
if (!list_empty(&desc->pending)) {
/* No errors, keep going */
if (desc->status != DMA_ERROR)
return desc;
/* Error, free remaining commands and move on */
ccp_free_cmd_resources(desc->ccp,
&desc->pending);
}
tx_desc = &desc->tx_desc;
} else {
tx_desc = NULL;
}
spin_lock_irqsave(&chan->lock, flags);
if (desc) {
if (desc->status != DMA_ERROR)
desc->status = DMA_COMPLETE;
dev_dbg(desc->ccp->dev,
"%s - tx %d complete, status=%u\n", __func__,
desc->tx_desc.cookie, desc->status);
dma_cookie_complete(tx_desc);
dma_descriptor_unmap(tx_desc);
}
desc = __ccp_next_dma_desc(chan, desc);
spin_unlock_irqrestore(&chan->lock, flags);
if (tx_desc) {
dmaengine_desc_get_callback_invoke(tx_desc, NULL);
dma_run_dependencies(tx_desc);
}
} while (desc);
return NULL;
}
static struct ccp_dma_desc *__ccp_pending_to_active(struct ccp_dma_chan *chan)
{
struct ccp_dma_desc *desc;
if (list_empty(&chan->pending))
return NULL;
desc = list_empty(&chan->active)
? list_first_entry(&chan->pending, struct ccp_dma_desc, entry)
: NULL;
list_splice_tail_init(&chan->pending, &chan->active);
return desc;
}
static void ccp_cmd_callback(void *data, int err)
{
struct ccp_dma_desc *desc = data;
struct ccp_dma_chan *chan;
int ret;
if (err == -EINPROGRESS)
return;
chan = container_of(desc->tx_desc.chan, struct ccp_dma_chan,
dma_chan);
dev_dbg(chan->ccp->dev, "%s - tx %d callback, err=%d\n",
__func__, desc->tx_desc.cookie, err);
if (err)
desc->status = DMA_ERROR;
while (true) {
/* Check for DMA descriptor completion */
desc = ccp_handle_active_desc(chan, desc);
/* Don't submit cmd if no descriptor or DMA is paused */
if (!desc || (chan->status == DMA_PAUSED))
break;
ret = ccp_issue_next_cmd(desc);
if (!ret)
break;
desc->status = DMA_ERROR;
}
tasklet_schedule(&chan->cleanup_tasklet);
}
static dma_cookie_t ccp_tx_submit(struct dma_async_tx_descriptor *tx_desc)
{
struct ccp_dma_desc *desc = container_of(tx_desc, struct ccp_dma_desc,
tx_desc);
struct ccp_dma_chan *chan;
dma_cookie_t cookie;
unsigned long flags;
chan = container_of(tx_desc->chan, struct ccp_dma_chan, dma_chan);
spin_lock_irqsave(&chan->lock, flags);
cookie = dma_cookie_assign(tx_desc);
list_move_tail(&desc->entry, &chan->pending);
spin_unlock_irqrestore(&chan->lock, flags);
dev_dbg(chan->ccp->dev, "%s - added tx descriptor %d to pending list\n",
__func__, cookie);
return cookie;
}
static struct ccp_dma_cmd *ccp_alloc_dma_cmd(struct ccp_dma_chan *chan)
{
struct ccp_dma_cmd *cmd;
cmd = kmem_cache_alloc(chan->ccp->dma_cmd_cache, GFP_NOWAIT);
if (cmd)
memset(cmd, 0, sizeof(*cmd));
return cmd;
}
static struct ccp_dma_desc *ccp_alloc_dma_desc(struct ccp_dma_chan *chan,
unsigned long flags)
{
struct ccp_dma_desc *desc;
desc = kmem_cache_zalloc(chan->ccp->dma_desc_cache, GFP_NOWAIT);
if (!desc)
return NULL;
dma_async_tx_descriptor_init(&desc->tx_desc, &chan->dma_chan);
desc->tx_desc.flags = flags;
desc->tx_desc.tx_submit = ccp_tx_submit;
desc->ccp = chan->ccp;
INIT_LIST_HEAD(&desc->entry);
INIT_LIST_HEAD(&desc->pending);
INIT_LIST_HEAD(&desc->active);
desc->status = DMA_IN_PROGRESS;
return desc;
}
static struct ccp_dma_desc *ccp_create_desc(struct dma_chan *dma_chan,
struct scatterlist *dst_sg,
unsigned int dst_nents,
struct scatterlist *src_sg,
unsigned int src_nents,
unsigned long flags)
{
struct ccp_dma_chan *chan = container_of(dma_chan, struct ccp_dma_chan,
dma_chan);
struct ccp_device *ccp = chan->ccp;
struct ccp_dma_desc *desc;
struct ccp_dma_cmd *cmd;
struct ccp_cmd *ccp_cmd;
struct ccp_passthru_nomap_engine *ccp_pt;
unsigned int src_offset, src_len;
unsigned int dst_offset, dst_len;
unsigned int len;
unsigned long sflags;
size_t total_len;
if (!dst_sg || !src_sg)
return NULL;
if (!dst_nents || !src_nents)
return NULL;
desc = ccp_alloc_dma_desc(chan, flags);
if (!desc)
return NULL;
total_len = 0;
src_len = sg_dma_len(src_sg);
src_offset = 0;
dst_len = sg_dma_len(dst_sg);
dst_offset = 0;
while (true) {
if (!src_len) {
src_nents--;
if (!src_nents)
break;
src_sg = sg_next(src_sg);
if (!src_sg)
break;
src_len = sg_dma_len(src_sg);
src_offset = 0;
continue;
}
if (!dst_len) {
dst_nents--;
if (!dst_nents)
break;
dst_sg = sg_next(dst_sg);
if (!dst_sg)
break;
dst_len = sg_dma_len(dst_sg);
dst_offset = 0;
continue;
}
len = min(dst_len, src_len);
cmd = ccp_alloc_dma_cmd(chan);
if (!cmd)
goto err;
ccp_cmd = &cmd->ccp_cmd;
ccp_cmd->ccp = chan->ccp;
ccp_pt = &ccp_cmd->u.passthru_nomap;
ccp_cmd->flags = CCP_CMD_MAY_BACKLOG;
ccp_cmd->flags |= CCP_CMD_PASSTHRU_NO_DMA_MAP;
ccp_cmd->engine = CCP_ENGINE_PASSTHRU;
ccp_pt->bit_mod = CCP_PASSTHRU_BITWISE_NOOP;
ccp_pt->byte_swap = CCP_PASSTHRU_BYTESWAP_NOOP;
ccp_pt->src_dma = sg_dma_address(src_sg) + src_offset;
ccp_pt->dst_dma = sg_dma_address(dst_sg) + dst_offset;
ccp_pt->src_len = len;
ccp_pt->final = 1;
ccp_cmd->callback = ccp_cmd_callback;
ccp_cmd->data = desc;
list_add_tail(&cmd->entry, &desc->pending);
dev_dbg(ccp->dev,
"%s - cmd=%p, src=%pad, dst=%pad, len=%llu\n", __func__,
cmd, &ccp_pt->src_dma,
&ccp_pt->dst_dma, ccp_pt->src_len);
total_len += len;
src_len -= len;
src_offset += len;
dst_len -= len;
dst_offset += len;
}
desc->len = total_len;
if (list_empty(&desc->pending))
goto err;
dev_dbg(ccp->dev, "%s - desc=%p\n", __func__, desc);
spin_lock_irqsave(&chan->lock, sflags);
list_add_tail(&desc->entry, &chan->created);
spin_unlock_irqrestore(&chan->lock, sflags);
return desc;
err:
ccp_free_cmd_resources(ccp, &desc->pending);
kmem_cache_free(ccp->dma_desc_cache, desc);
return NULL;
}
static struct dma_async_tx_descriptor *ccp_prep_dma_memcpy(
struct dma_chan *dma_chan, dma_addr_t dst, dma_addr_t src, size_t len,
unsigned long flags)
{
struct ccp_dma_chan *chan = container_of(dma_chan, struct ccp_dma_chan,
dma_chan);
struct ccp_dma_desc *desc;
struct scatterlist dst_sg, src_sg;
dev_dbg(chan->ccp->dev,
"%s - src=%pad, dst=%pad, len=%zu, flags=%#lx\n",
__func__, &src, &dst, len, flags);
sg_init_table(&dst_sg, 1);
sg_dma_address(&dst_sg) = dst;
sg_dma_len(&dst_sg) = len;
sg_init_table(&src_sg, 1);
sg_dma_address(&src_sg) = src;
sg_dma_len(&src_sg) = len;
desc = ccp_create_desc(dma_chan, &dst_sg, 1, &src_sg, 1, flags);
if (!desc)
return NULL;
return &desc->tx_desc;
}
static struct dma_async_tx_descriptor *ccp_prep_dma_interrupt(
struct dma_chan *dma_chan, unsigned long flags)
{
struct ccp_dma_chan *chan = container_of(dma_chan, struct ccp_dma_chan,
dma_chan);
struct ccp_dma_desc *desc;
desc = ccp_alloc_dma_desc(chan, flags);
if (!desc)
return NULL;
return &desc->tx_desc;
}
static void ccp_issue_pending(struct dma_chan *dma_chan)
{
struct ccp_dma_chan *chan = container_of(dma_chan, struct ccp_dma_chan,
dma_chan);
struct ccp_dma_desc *desc;
unsigned long flags;
dev_dbg(chan->ccp->dev, "%s\n", __func__);
spin_lock_irqsave(&chan->lock, flags);
desc = __ccp_pending_to_active(chan);
spin_unlock_irqrestore(&chan->lock, flags);
/* If there was nothing active, start processing */
if (desc)
ccp_cmd_callback(desc, 0);
}
static enum dma_status ccp_tx_status(struct dma_chan *dma_chan,
dma_cookie_t cookie,
struct dma_tx_state *state)
{
struct ccp_dma_chan *chan = container_of(dma_chan, struct ccp_dma_chan,
dma_chan);
struct ccp_dma_desc *desc;
enum dma_status ret;
unsigned long flags;
if (chan->status == DMA_PAUSED) {
ret = DMA_PAUSED;
goto out;
}
ret = dma_cookie_status(dma_chan, cookie, state);
if (ret == DMA_COMPLETE) {
spin_lock_irqsave(&chan->lock, flags);
/* Get status from complete chain, if still there */
list_for_each_entry(desc, &chan->complete, entry) {
if (desc->tx_desc.cookie != cookie)
continue;
ret = desc->status;
break;
}
spin_unlock_irqrestore(&chan->lock, flags);
}
out:
dev_dbg(chan->ccp->dev, "%s - %u\n", __func__, ret);
return ret;
}
static int ccp_pause(struct dma_chan *dma_chan)
{
struct ccp_dma_chan *chan = container_of(dma_chan, struct ccp_dma_chan,
dma_chan);
chan->status = DMA_PAUSED;
/*TODO: Wait for active DMA to complete before returning? */
return 0;
}
static int ccp_resume(struct dma_chan *dma_chan)
{
struct ccp_dma_chan *chan = container_of(dma_chan, struct ccp_dma_chan,
dma_chan);
struct ccp_dma_desc *desc;
unsigned long flags;
spin_lock_irqsave(&chan->lock, flags);
desc = list_first_entry_or_null(&chan->active, struct ccp_dma_desc,
entry);
spin_unlock_irqrestore(&chan->lock, flags);
/* Indicate the channel is running again */
chan->status = DMA_IN_PROGRESS;
/* If there was something active, re-start */
if (desc)
ccp_cmd_callback(desc, 0);
return 0;
}
static int ccp_terminate_all(struct dma_chan *dma_chan)
{
struct ccp_dma_chan *chan = container_of(dma_chan, struct ccp_dma_chan,
dma_chan);
unsigned long flags;
dev_dbg(chan->ccp->dev, "%s\n", __func__);
/*TODO: Wait for active DMA to complete before continuing */
spin_lock_irqsave(&chan->lock, flags);
/*TODO: Purge the complete list? */
ccp_free_desc_resources(chan->ccp, &chan->active);
ccp_free_desc_resources(chan->ccp, &chan->pending);
ccp_free_desc_resources(chan->ccp, &chan->created);
spin_unlock_irqrestore(&chan->lock, flags);
return 0;
}
static void ccp_dma_release(struct ccp_device *ccp)
{
struct ccp_dma_chan *chan;
struct dma_chan *dma_chan;
unsigned int i;
for (i = 0; i < ccp->cmd_q_count; i++) {
chan = ccp->ccp_dma_chan + i;
dma_chan = &chan->dma_chan;
tasklet_kill(&chan->cleanup_tasklet);
list_del_rcu(&dma_chan->device_node);
}
}
static void ccp_dma_release_channels(struct ccp_device *ccp)
{
struct ccp_dma_chan *chan;
struct dma_chan *dma_chan;
unsigned int i;
for (i = 0; i < ccp->cmd_q_count; i++) {
chan = ccp->ccp_dma_chan + i;
dma_chan = &chan->dma_chan;
if (dma_chan->client_count)
dma_release_channel(dma_chan);
}
}
int ccp_dmaengine_register(struct ccp_device *ccp)
{
struct ccp_dma_chan *chan;
struct dma_device *dma_dev = &ccp->dma_dev;
struct dma_chan *dma_chan;
char *dma_cmd_cache_name;
char *dma_desc_cache_name;
unsigned int i;
int ret;
if (!dmaengine)
return 0;
ccp->ccp_dma_chan = devm_kcalloc(ccp->dev, ccp->cmd_q_count,
sizeof(*(ccp->ccp_dma_chan)),
GFP_KERNEL);
if (!ccp->ccp_dma_chan)
return -ENOMEM;
dma_cmd_cache_name = devm_kasprintf(ccp->dev, GFP_KERNEL,
"%s-dmaengine-cmd-cache",
ccp->name);
if (!dma_cmd_cache_name)
return -ENOMEM;
ccp->dma_cmd_cache = kmem_cache_create(dma_cmd_cache_name,
sizeof(struct ccp_dma_cmd),
sizeof(void *),
SLAB_HWCACHE_ALIGN, NULL);
if (!ccp->dma_cmd_cache)
return -ENOMEM;
dma_desc_cache_name = devm_kasprintf(ccp->dev, GFP_KERNEL,
"%s-dmaengine-desc-cache",
ccp->name);
if (!dma_desc_cache_name) {
ret = -ENOMEM;
goto err_cache;
}
ccp->dma_desc_cache = kmem_cache_create(dma_desc_cache_name,
sizeof(struct ccp_dma_desc),
sizeof(void *),
SLAB_HWCACHE_ALIGN, NULL);
if (!ccp->dma_desc_cache) {
ret = -ENOMEM;
goto err_cache;
}
dma_dev->dev = ccp->dev;
dma_dev->src_addr_widths = CCP_DMA_WIDTH(dma_get_mask(ccp->dev));
dma_dev->dst_addr_widths = CCP_DMA_WIDTH(dma_get_mask(ccp->dev));
dma_dev->directions = DMA_MEM_TO_MEM;
dma_dev->residue_granularity = DMA_RESIDUE_GRANULARITY_DESCRIPTOR;
dma_cap_set(DMA_MEMCPY, dma_dev->cap_mask);
dma_cap_set(DMA_INTERRUPT, dma_dev->cap_mask);
/* The DMA channels for this device can be set to public or private,
* and overridden by the module parameter dma_chan_attr.
* Default: according to the value in vdata (dma_chan_attr=0)
* dma_chan_attr=0x1: all channels private (override vdata)
* dma_chan_attr=0x2: all channels public (override vdata)
*/
if (ccp_get_dma_chan_attr(ccp) == DMA_PRIVATE)
dma_cap_set(DMA_PRIVATE, dma_dev->cap_mask);
INIT_LIST_HEAD(&dma_dev->channels);
for (i = 0; i < ccp->cmd_q_count; i++) {
chan = ccp->ccp_dma_chan + i;
dma_chan = &chan->dma_chan;
chan->ccp = ccp;
spin_lock_init(&chan->lock);
INIT_LIST_HEAD(&chan->created);
INIT_LIST_HEAD(&chan->pending);
INIT_LIST_HEAD(&chan->active);
INIT_LIST_HEAD(&chan->complete);
tasklet_init(&chan->cleanup_tasklet, ccp_do_cleanup,
(unsigned long)chan);
dma_chan->device = dma_dev;
dma_cookie_init(dma_chan);
list_add_tail(&dma_chan->device_node, &dma_dev->channels);
}
dma_dev->device_free_chan_resources = ccp_free_chan_resources;
dma_dev->device_prep_dma_memcpy = ccp_prep_dma_memcpy;
dma_dev->device_prep_dma_interrupt = ccp_prep_dma_interrupt;
dma_dev->device_issue_pending = ccp_issue_pending;
dma_dev->device_tx_status = ccp_tx_status;
dma_dev->device_pause = ccp_pause;
dma_dev->device_resume = ccp_resume;
dma_dev->device_terminate_all = ccp_terminate_all;
ret = dma_async_device_register(dma_dev);
if (ret)
goto err_reg;
return 0;
err_reg:
ccp_dma_release(ccp);
kmem_cache_destroy(ccp->dma_desc_cache);
err_cache:
kmem_cache_destroy(ccp->dma_cmd_cache);
return ret;
}
void ccp_dmaengine_unregister(struct ccp_device *ccp)
{
struct dma_device *dma_dev = &ccp->dma_dev;
if (!dmaengine)
return;
ccp_dma_release_channels(ccp);
dma_async_device_unregister(dma_dev);
ccp_dma_release(ccp);
kmem_cache_destroy(ccp->dma_desc_cache);
kmem_cache_destroy(ccp->dma_cmd_cache);
}
File diff suppressed because it is too large Load Diff
+259
View File
@@ -0,0 +1,259 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* AMD Secure Processor Dynamic Boost Control interface
*
* Copyright (C) 2023 Advanced Micro Devices, Inc.
*
* Author: Mario Limonciello <mario.limonciello@amd.com>
*/
#include <linux/mutex.h>
#include "dbc.h"
#define DBC_DEFAULT_TIMEOUT (10 * MSEC_PER_SEC)
struct error_map {
u32 psp;
int ret;
};
#define DBC_ERROR_ACCESS_DENIED 0x0001
#define DBC_ERROR_EXCESS_DATA 0x0004
#define DBC_ERROR_BAD_PARAMETERS 0x0006
#define DBC_ERROR_BAD_STATE 0x0007
#define DBC_ERROR_NOT_IMPLEMENTED 0x0009
#define DBC_ERROR_BUSY 0x000D
#define DBC_ERROR_MESSAGE_FAILURE 0x0307
#define DBC_ERROR_OVERFLOW 0x300F
#define DBC_ERROR_SIGNATURE_INVALID 0x3072
static struct error_map error_codes[] = {
{DBC_ERROR_ACCESS_DENIED, -EACCES},
{DBC_ERROR_EXCESS_DATA, -E2BIG},
{DBC_ERROR_BAD_PARAMETERS, -EINVAL},
{DBC_ERROR_BAD_STATE, -EAGAIN},
{DBC_ERROR_MESSAGE_FAILURE, -ENOENT},
{DBC_ERROR_NOT_IMPLEMENTED, -ENOENT},
{DBC_ERROR_BUSY, -EBUSY},
{DBC_ERROR_OVERFLOW, -ENFILE},
{DBC_ERROR_SIGNATURE_INVALID, -EPERM},
{0x0, 0x0},
};
static inline int send_dbc_cmd_thru_ext(struct psp_dbc_device *dbc_dev, int msg)
{
dbc_dev->mbox->ext_req.header.sub_cmd_id = msg;
return psp_extended_mailbox_cmd(dbc_dev->psp,
DBC_DEFAULT_TIMEOUT,
(struct psp_ext_request *)dbc_dev->mbox);
}
static inline int send_dbc_cmd_thru_pa(struct psp_dbc_device *dbc_dev, int msg)
{
return psp_send_platform_access_msg(msg,
(struct psp_request *)dbc_dev->mbox);
}
static int send_dbc_cmd(struct psp_dbc_device *dbc_dev, int msg)
{
int ret;
*dbc_dev->result = 0;
ret = dbc_dev->use_ext ? send_dbc_cmd_thru_ext(dbc_dev, msg) :
send_dbc_cmd_thru_pa(dbc_dev, msg);
if (ret == -EIO) {
int i;
dev_dbg(dbc_dev->dev,
"msg 0x%x failed with PSP error: 0x%x\n",
msg, *dbc_dev->result);
for (i = 0; error_codes[i].psp; i++) {
if (*dbc_dev->result == error_codes[i].psp)
return error_codes[i].ret;
}
}
return ret;
}
static int send_dbc_nonce(struct psp_dbc_device *dbc_dev)
{
int ret;
*dbc_dev->payload_size = dbc_dev->header_size + sizeof(struct dbc_user_nonce);
ret = send_dbc_cmd(dbc_dev, PSP_DYNAMIC_BOOST_GET_NONCE);
if (ret == -EAGAIN) {
dev_dbg(dbc_dev->dev, "retrying get nonce\n");
ret = send_dbc_cmd(dbc_dev, PSP_DYNAMIC_BOOST_GET_NONCE);
}
return ret;
}
static int send_dbc_parameter(struct psp_dbc_device *dbc_dev)
{
struct dbc_user_param *user_param = (struct dbc_user_param *)dbc_dev->payload;
switch (user_param->msg_index) {
case PARAM_SET_FMAX_CAP:
case PARAM_SET_PWR_CAP:
case PARAM_SET_GFX_MODE:
return send_dbc_cmd(dbc_dev, PSP_DYNAMIC_BOOST_SET_PARAMETER);
case PARAM_GET_FMAX_CAP:
case PARAM_GET_PWR_CAP:
case PARAM_GET_CURR_TEMP:
case PARAM_GET_FMAX_MAX:
case PARAM_GET_FMAX_MIN:
case PARAM_GET_SOC_PWR_MAX:
case PARAM_GET_SOC_PWR_MIN:
case PARAM_GET_SOC_PWR_CUR:
case PARAM_GET_GFX_MODE:
return send_dbc_cmd(dbc_dev, PSP_DYNAMIC_BOOST_GET_PARAMETER);
}
return -EINVAL;
}
void dbc_dev_destroy(struct psp_device *psp)
{
struct psp_dbc_device *dbc_dev = psp->dbc_data;
if (!dbc_dev)
return;
misc_deregister(&dbc_dev->char_dev);
mutex_destroy(&dbc_dev->ioctl_mutex);
psp->dbc_data = NULL;
}
static long dbc_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
struct psp_device *psp_master = psp_get_master_device();
void __user *argp = (void __user *)arg;
struct psp_dbc_device *dbc_dev;
int ret;
if (!psp_master || !psp_master->dbc_data)
return -ENODEV;
dbc_dev = psp_master->dbc_data;
guard(mutex)(&dbc_dev->ioctl_mutex);
switch (cmd) {
case DBCIOCNONCE:
if (copy_from_user(dbc_dev->payload, argp, sizeof(struct dbc_user_nonce)))
return -EFAULT;
ret = send_dbc_nonce(dbc_dev);
if (ret)
return ret;
if (copy_to_user(argp, dbc_dev->payload, sizeof(struct dbc_user_nonce)))
return -EFAULT;
break;
case DBCIOCUID:
if (copy_from_user(dbc_dev->payload, argp, sizeof(struct dbc_user_setuid)))
return -EFAULT;
*dbc_dev->payload_size = dbc_dev->header_size + sizeof(struct dbc_user_setuid);
ret = send_dbc_cmd(dbc_dev, PSP_DYNAMIC_BOOST_SET_UID);
if (ret)
return ret;
if (copy_to_user(argp, dbc_dev->payload, sizeof(struct dbc_user_setuid)))
return -EFAULT;
break;
case DBCIOCPARAM:
if (copy_from_user(dbc_dev->payload, argp, sizeof(struct dbc_user_param)))
return -EFAULT;
*dbc_dev->payload_size = dbc_dev->header_size + sizeof(struct dbc_user_param);
ret = send_dbc_parameter(dbc_dev);
if (ret)
return ret;
if (copy_to_user(argp, dbc_dev->payload, sizeof(struct dbc_user_param)))
return -EFAULT;
break;
default:
return -EINVAL;
}
return 0;
}
static const struct file_operations dbc_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = dbc_ioctl,
};
int dbc_dev_init(struct psp_device *psp)
{
struct device *dev = psp->dev;
struct psp_dbc_device *dbc_dev;
int ret;
dbc_dev = devm_kzalloc(dev, sizeof(*dbc_dev), GFP_KERNEL);
if (!dbc_dev)
return -ENOMEM;
BUILD_BUG_ON(sizeof(union dbc_buffer) > PAGE_SIZE);
dbc_dev->mbox = (void *)devm_get_free_pages(dev, GFP_KERNEL | __GFP_ZERO, 0);
if (!dbc_dev->mbox) {
ret = -ENOMEM;
goto cleanup_dev;
}
psp->dbc_data = dbc_dev;
dbc_dev->dev = dev;
dbc_dev->psp = psp;
if (psp->capability.dbc_thru_ext) {
dbc_dev->use_ext = true;
dbc_dev->payload_size = &dbc_dev->mbox->ext_req.header.payload_size;
dbc_dev->result = &dbc_dev->mbox->ext_req.header.status;
dbc_dev->payload = &dbc_dev->mbox->ext_req.buf;
dbc_dev->header_size = sizeof(struct psp_ext_req_buffer_hdr);
} else {
dbc_dev->payload_size = &dbc_dev->mbox->pa_req.header.payload_size;
dbc_dev->result = &dbc_dev->mbox->pa_req.header.status;
dbc_dev->payload = &dbc_dev->mbox->pa_req.buf;
dbc_dev->header_size = sizeof(struct psp_req_buffer_hdr);
}
ret = send_dbc_nonce(dbc_dev);
if (ret == -EACCES) {
dev_dbg(dbc_dev->dev,
"dynamic boost control was previously authenticated\n");
ret = 0;
}
dev_dbg(dbc_dev->dev, "dynamic boost control is %savailable\n",
ret ? "un" : "");
if (ret) {
ret = 0;
goto cleanup_mbox;
}
dbc_dev->char_dev.minor = MISC_DYNAMIC_MINOR;
dbc_dev->char_dev.name = "dbc";
dbc_dev->char_dev.fops = &dbc_fops;
dbc_dev->char_dev.mode = 0600;
ret = misc_register(&dbc_dev->char_dev);
if (ret)
goto cleanup_mbox;
mutex_init(&dbc_dev->ioctl_mutex);
return 0;
cleanup_mbox:
devm_free_pages(dev, (unsigned long)dbc_dev->mbox);
cleanup_dev:
psp->dbc_data = NULL;
devm_kfree(dev, dbc_dev);
return ret;
}
+47
View File
@@ -0,0 +1,47 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* AMD Platform Security Processor (PSP) Dynamic Boost Control support
*
* Copyright (C) 2023 Advanced Micro Devices, Inc.
*
* Author: Mario Limonciello <mario.limonciello@amd.com>
*/
#ifndef __DBC_H__
#define __DBC_H__
#include <uapi/linux/psp-dbc.h>
#include <linux/device.h>
#include <linux/miscdevice.h>
#include <linux/psp-platform-access.h>
#include "psp-dev.h"
struct psp_dbc_device {
struct device *dev;
struct psp_device *psp;
union dbc_buffer *mbox;
struct mutex ioctl_mutex;
struct miscdevice char_dev;
/* used to abstract communication path */
bool use_ext;
u32 header_size;
u32 *payload_size;
u32 *result;
void *payload;
};
union dbc_buffer {
struct psp_request pa_req;
struct psp_ext_request ext_req;
};
void dbc_dev_destroy(struct psp_device *psp);
int dbc_dev_init(struct psp_device *psp);
#endif /* __DBC_H */
+141
View File
@@ -0,0 +1,141 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* AMD Secure Processor device driver, security attributes
*
* Copyright (C) 2023-2024 Advanced Micro Devices, Inc.
*
* Author: Mario Limonciello <mario.limonciello@amd.com>
*/
#include <linux/device.h>
#include "psp-dev.h"
#include "hsti.h"
#define PSP_CAPABILITY_PSP_SECURITY_OFFSET 8
struct hsti_request {
struct psp_req_buffer_hdr header;
u32 hsti;
} __packed;
#define security_attribute_show(name) \
static ssize_t name##_show(struct device *d, struct device_attribute *attr, \
char *buf) \
{ \
struct sp_device *sp = dev_get_drvdata(d); \
struct psp_device *psp = sp->psp_data; \
return sysfs_emit(buf, "%d\n", psp->capability.name); \
}
security_attribute_show(fused_part)
static DEVICE_ATTR_RO(fused_part);
security_attribute_show(boot_integrity)
static DEVICE_ATTR_RO(boot_integrity);
security_attribute_show(debug_lock_on)
static DEVICE_ATTR_RO(debug_lock_on);
security_attribute_show(tsme_status)
static DEVICE_ATTR_RO(tsme_status);
security_attribute_show(anti_rollback_status)
static DEVICE_ATTR_RO(anti_rollback_status);
security_attribute_show(rpmc_production_enabled)
static DEVICE_ATTR_RO(rpmc_production_enabled);
security_attribute_show(rpmc_spirom_available)
static DEVICE_ATTR_RO(rpmc_spirom_available);
security_attribute_show(hsp_tpm_available)
static DEVICE_ATTR_RO(hsp_tpm_available);
security_attribute_show(rom_armor_enforced)
static DEVICE_ATTR_RO(rom_armor_enforced);
static struct attribute *psp_security_attrs[] = {
&dev_attr_fused_part.attr,
&dev_attr_boot_integrity.attr,
&dev_attr_debug_lock_on.attr,
&dev_attr_tsme_status.attr,
&dev_attr_anti_rollback_status.attr,
&dev_attr_rpmc_production_enabled.attr,
&dev_attr_rpmc_spirom_available.attr,
&dev_attr_hsp_tpm_available.attr,
&dev_attr_rom_armor_enforced.attr,
NULL
};
static umode_t psp_security_is_visible(struct kobject *kobj, struct attribute *attr, int idx)
{
struct device *dev = kobj_to_dev(kobj);
struct sp_device *sp = dev_get_drvdata(dev);
struct psp_device *psp = sp->psp_data;
if (psp && psp->capability.security_reporting)
return 0444;
return 0;
}
struct attribute_group psp_security_attr_group = {
.attrs = psp_security_attrs,
.is_visible = psp_security_is_visible,
};
static int psp_populate_hsti(struct psp_device *psp)
{
struct hsti_request *req;
int ret;
/* Are the security attributes already reported? */
if (psp->capability.security_reporting)
return 0;
/* Allocate command-response buffer */
req = kzalloc_obj(*req);
if (!req)
return -ENOMEM;
req->header.payload_size = sizeof(*req);
ret = psp_send_platform_access_msg(PSP_CMD_HSTI_QUERY, (struct psp_request *)req);
if (ret)
goto out;
if (req->header.status != 0) {
dev_dbg(psp->dev, "failed to populate HSTI state: %d\n", req->header.status);
ret = -EINVAL;
goto out;
}
psp->capability.security_reporting = 1;
psp->capability.raw |= req->hsti << PSP_CAPABILITY_PSP_SECURITY_OFFSET;
out:
kfree(req);
return ret;
}
int psp_init_hsti(struct psp_device *psp)
{
int ret;
if (PSP_FEATURE(psp, HSTI)) {
ret = psp_populate_hsti(psp);
if (ret)
return ret;
}
/*
* At this stage, if security information hasn't been populated by
* either the PSP or by the driver through the platform command,
* then there is nothing more to do.
*/
if (!psp->capability.security_reporting)
return 0;
if (psp->capability.tsme_status) {
if (cc_platform_has(CC_ATTR_HOST_MEM_ENCRYPT))
dev_notice(psp->dev, "psp: Both TSME and SME are active, SME is unnecessary when TSME is active.\n");
else
dev_notice(psp->dev, "psp: TSME enabled\n");
}
return 0;
}
+17
View File
@@ -0,0 +1,17 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* AMD Secure Processor device driver, security attributes
*
* Copyright (C) 2023-2024 Advanced Micro Devices, Inc.
*
* Author: Mario Limonciello <mario.limonciello@amd.com>
*/
#ifndef __HSTI_H
#define __HSTI_H
extern struct attribute_group psp_security_attr_group;
int psp_init_hsti(struct psp_device *psp);
#endif /* __HSTI_H */
@@ -0,0 +1,227 @@
// SPDX-License-Identifier: GPL-2.0
/*
* AMD Platform Security Processor (PSP) Platform Access interface
*
* Copyright (C) 2023 Advanced Micro Devices, Inc.
*
* Author: Mario Limonciello <mario.limonciello@amd.com>
*
* Some of this code is adapted from drivers/i2c/busses/i2c-designware-amdpsp.c
* developed by Jan Dabros <jsd@semihalf.com> and Copyright (C) 2022 Google Inc.
*
*/
#include <linux/bitfield.h>
#include <linux/errno.h>
#include <linux/iopoll.h>
#include <linux/mutex.h>
#include "platform-access.h"
#define PSP_CMD_TIMEOUT_US (500 * USEC_PER_MSEC)
#define DOORBELL_CMDRESP_STS GENMASK(7, 0)
/* Recovery field should be equal 0 to start sending commands */
static int check_recovery(u32 __iomem *cmd)
{
return FIELD_GET(PSP_CMDRESP_RECOVERY, ioread32(cmd));
}
static int wait_cmd(u32 __iomem *cmd)
{
u32 tmp, expected;
/* Expect mbox_cmd to be cleared and ready bit to be set by PSP */
expected = FIELD_PREP(PSP_CMDRESP_RESP, 1);
/*
* Check for readiness of PSP mailbox in a tight loop in order to
* process further as soon as command was consumed.
*/
return readl_poll_timeout(cmd, tmp, (tmp & expected), 0,
PSP_CMD_TIMEOUT_US);
}
int psp_check_platform_access_status(void)
{
struct psp_device *psp = psp_get_master_device();
if (!psp || !psp->platform_access_data)
return -ENODEV;
return 0;
}
EXPORT_SYMBOL(psp_check_platform_access_status);
int psp_send_platform_access_msg(enum psp_platform_access_msg msg,
struct psp_request *req)
{
struct psp_device *psp = psp_get_master_device();
u32 __iomem *cmd, *lo, *hi;
struct psp_platform_access_device *pa_dev;
phys_addr_t req_addr;
u32 cmd_reg;
int ret;
if (!psp || !psp->platform_access_data)
return -ENODEV;
pa_dev = psp->platform_access_data;
if (!pa_dev->vdata->cmdresp_reg || !pa_dev->vdata->cmdbuff_addr_lo_reg ||
!pa_dev->vdata->cmdbuff_addr_hi_reg)
return -ENODEV;
cmd = psp->io_regs + pa_dev->vdata->cmdresp_reg;
lo = psp->io_regs + pa_dev->vdata->cmdbuff_addr_lo_reg;
hi = psp->io_regs + pa_dev->vdata->cmdbuff_addr_hi_reg;
mutex_lock(&pa_dev->mailbox_mutex);
if (check_recovery(cmd)) {
dev_dbg(psp->dev, "platform mailbox is in recovery\n");
ret = -EBUSY;
goto unlock;
}
if (wait_cmd(cmd)) {
dev_dbg(psp->dev, "platform mailbox is not done processing command\n");
ret = -EBUSY;
goto unlock;
}
/*
* Fill mailbox with address of command-response buffer, which will be
* used for sending i2c requests as well as reading status returned by
* PSP. Use physical address of buffer, since PSP will map this region.
*/
req_addr = __psp_pa(req);
iowrite32(lower_32_bits(req_addr), lo);
iowrite32(upper_32_bits(req_addr), hi);
print_hex_dump_debug("->psp ", DUMP_PREFIX_OFFSET, 16, 2, req,
req->header.payload_size, false);
/* Write command register to trigger processing */
cmd_reg = FIELD_PREP(PSP_CMDRESP_CMD, msg);
iowrite32(cmd_reg, cmd);
if (wait_cmd(cmd)) {
ret = -ETIMEDOUT;
goto unlock;
}
/* Ensure it was triggered by this driver */
if (ioread32(lo) != lower_32_bits(req_addr) ||
ioread32(hi) != upper_32_bits(req_addr)) {
ret = -EBUSY;
goto unlock;
}
/*
* Read status from PSP. If status is non-zero, it indicates an error
* occurred during "processing" of the command.
* If status is zero, it indicates the command was "processed"
* successfully, but the result of the command is in the payload.
* Return both cases to the caller as -EIO to investigate.
*/
cmd_reg = ioread32(cmd);
if (FIELD_GET(PSP_CMDRESP_STS, cmd_reg))
req->header.status = FIELD_GET(PSP_CMDRESP_STS, cmd_reg);
if (req->header.status) {
ret = -EIO;
goto unlock;
}
print_hex_dump_debug("<-psp ", DUMP_PREFIX_OFFSET, 16, 2, req,
req->header.payload_size, false);
ret = 0;
unlock:
mutex_unlock(&pa_dev->mailbox_mutex);
return ret;
}
EXPORT_SYMBOL_GPL(psp_send_platform_access_msg);
int psp_ring_platform_doorbell(int msg, u32 *result)
{
struct psp_device *psp = psp_get_master_device();
struct psp_platform_access_device *pa_dev;
u32 __iomem *button, *cmd;
int ret, val;
if (!psp || !psp->platform_access_data)
return -ENODEV;
pa_dev = psp->platform_access_data;
button = psp->io_regs + pa_dev->vdata->doorbell_button_reg;
cmd = psp->io_regs + pa_dev->vdata->doorbell_cmd_reg;
mutex_lock(&pa_dev->doorbell_mutex);
if (wait_cmd(cmd)) {
dev_err(psp->dev, "doorbell command not done processing\n");
ret = -EBUSY;
goto unlock;
}
iowrite32(FIELD_PREP(DOORBELL_CMDRESP_STS, msg), cmd);
iowrite32(PSP_DRBL_RING, button);
if (wait_cmd(cmd)) {
ret = -ETIMEDOUT;
goto unlock;
}
val = FIELD_GET(DOORBELL_CMDRESP_STS, ioread32(cmd));
if (val) {
if (result)
*result = val;
ret = -EIO;
goto unlock;
}
ret = 0;
unlock:
mutex_unlock(&pa_dev->doorbell_mutex);
return ret;
}
EXPORT_SYMBOL_GPL(psp_ring_platform_doorbell);
void platform_access_dev_destroy(struct psp_device *psp)
{
struct psp_platform_access_device *pa_dev = psp->platform_access_data;
if (!pa_dev)
return;
mutex_destroy(&pa_dev->mailbox_mutex);
mutex_destroy(&pa_dev->doorbell_mutex);
psp->platform_access_data = NULL;
}
int platform_access_dev_init(struct psp_device *psp)
{
struct device *dev = psp->dev;
struct psp_platform_access_device *pa_dev;
pa_dev = devm_kzalloc(dev, sizeof(*pa_dev), GFP_KERNEL);
if (!pa_dev)
return -ENOMEM;
psp->platform_access_data = pa_dev;
pa_dev->psp = psp;
pa_dev->dev = dev;
pa_dev->vdata = (struct platform_access_vdata *)psp->vdata->platform_access;
mutex_init(&pa_dev->mailbox_mutex);
mutex_init(&pa_dev->doorbell_mutex);
dev_dbg(dev, "platform access enabled\n");
return 0;
}
@@ -0,0 +1,35 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* AMD Platform Security Processor (PSP) Platform Access interface
*
* Copyright (C) 2023 Advanced Micro Devices, Inc.
*
* Author: Mario Limonciello <mario.limonciello@amd.com>
*/
#ifndef __PSP_PLATFORM_ACCESS_H__
#define __PSP_PLATFORM_ACCESS_H__
#include <linux/device.h>
#include <linux/miscdevice.h>
#include <linux/mutex.h>
#include <linux/psp-platform-access.h>
#include "psp-dev.h"
struct psp_platform_access_device {
struct device *dev;
struct psp_device *psp;
struct platform_access_vdata *vdata;
struct mutex mailbox_mutex;
struct mutex doorbell_mutex;
void *platform_access_data;
};
void platform_access_dev_destroy(struct psp_device *psp);
int platform_access_dev_init(struct psp_device *psp);
#endif /* __PSP_PLATFORM_ACCESS_H__ */
+381
View File
@@ -0,0 +1,381 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* AMD Platform Security Processor (PSP) interface
*
* Copyright (C) 2016,2019 Advanced Micro Devices, Inc.
*
* Author: Brijesh Singh <brijesh.singh@amd.com>
*/
#include <linux/kernel.h>
#include <linux/irqreturn.h>
#include <linux/mutex.h>
#include <linux/bitfield.h>
#include <linux/delay.h>
#include "sp-dev.h"
#include "psp-dev.h"
#include "sev-dev.h"
#include "tee-dev.h"
#include "sfs.h"
#include "platform-access.h"
#include "dbc.h"
#include "hsti.h"
struct psp_device *psp_master;
#define PSP_C2PMSG_17_CMDRESP_CMD GENMASK(19, 16)
static int psp_mailbox_poll(const void __iomem *cmdresp_reg, unsigned int *cmdresp,
unsigned int timeout_msecs)
{
while (true) {
*cmdresp = ioread32(cmdresp_reg);
if (FIELD_GET(PSP_CMDRESP_RESP, *cmdresp))
return 0;
if (!timeout_msecs--)
break;
usleep_range(1000, 1100);
}
return -ETIMEDOUT;
}
int psp_mailbox_command(struct psp_device *psp, enum psp_cmd cmd, void *cmdbuff,
unsigned int timeout_msecs, unsigned int *cmdresp)
{
void __iomem *cmdresp_reg, *cmdbuff_lo_reg, *cmdbuff_hi_reg;
int ret;
if (!psp || !psp->vdata || !psp->vdata->cmdresp_reg ||
!psp->vdata->cmdbuff_addr_lo_reg || !psp->vdata->cmdbuff_addr_hi_reg)
return -ENODEV;
cmdresp_reg = psp->io_regs + psp->vdata->cmdresp_reg;
cmdbuff_lo_reg = psp->io_regs + psp->vdata->cmdbuff_addr_lo_reg;
cmdbuff_hi_reg = psp->io_regs + psp->vdata->cmdbuff_addr_hi_reg;
mutex_lock(&psp->mailbox_mutex);
/* Ensure mailbox is ready for a command */
ret = -EBUSY;
if (psp_mailbox_poll(cmdresp_reg, cmdresp, 0))
goto unlock;
if (cmdbuff) {
iowrite32(lower_32_bits(__psp_pa(cmdbuff)), cmdbuff_lo_reg);
iowrite32(upper_32_bits(__psp_pa(cmdbuff)), cmdbuff_hi_reg);
}
*cmdresp = FIELD_PREP(PSP_C2PMSG_17_CMDRESP_CMD, cmd);
iowrite32(*cmdresp, cmdresp_reg);
ret = psp_mailbox_poll(cmdresp_reg, cmdresp, timeout_msecs);
unlock:
mutex_unlock(&psp->mailbox_mutex);
return ret;
}
int psp_extended_mailbox_cmd(struct psp_device *psp, unsigned int timeout_msecs,
struct psp_ext_request *req)
{
unsigned int reg;
int ret;
print_hex_dump_debug("->psp ", DUMP_PREFIX_OFFSET, 16, 2, req,
req->header.payload_size, false);
ret = psp_mailbox_command(psp, PSP_CMD_TEE_EXTENDED_CMD, (void *)req,
timeout_msecs, &reg);
if (ret) {
return ret;
} else if (FIELD_GET(PSP_CMDRESP_STS, reg)) {
req->header.status = FIELD_GET(PSP_CMDRESP_STS, reg);
return -EIO;
}
print_hex_dump_debug("<-psp ", DUMP_PREFIX_OFFSET, 16, 2, req,
req->header.payload_size, false);
return 0;
}
static struct psp_device *psp_alloc_struct(struct sp_device *sp)
{
struct device *dev = sp->dev;
struct psp_device *psp;
psp = devm_kzalloc(dev, sizeof(*psp), GFP_KERNEL);
if (!psp)
return NULL;
psp->dev = dev;
psp->sp = sp;
snprintf(psp->name, sizeof(psp->name), "psp-%u", sp->ord);
return psp;
}
static irqreturn_t psp_irq_handler(int irq, void *data)
{
struct psp_device *psp = data;
unsigned int status;
/* Read the interrupt status: */
status = ioread32(psp->io_regs + psp->vdata->intsts_reg);
/* Clear the interrupt status by writing the same value we read. */
iowrite32(status, psp->io_regs + psp->vdata->intsts_reg);
/* invoke subdevice interrupt handlers */
if (status) {
if (psp->sev_irq_handler)
psp->sev_irq_handler(irq, psp->sev_irq_data, status);
}
return IRQ_HANDLED;
}
static unsigned int psp_get_capability(struct psp_device *psp)
{
unsigned int val = ioread32(psp->io_regs + psp->vdata->feature_reg);
/*
* Check for a access to the registers. If this read returns
* 0xffffffff, it's likely that the system is running a broken
* BIOS which disallows access to the device. Stop here and
* fail the PSP initialization (but not the load, as the CCP
* could get properly initialized).
*/
if (val == 0xffffffff) {
dev_notice(psp->dev, "psp: unable to access the device: you might be running a broken BIOS.\n");
return -ENODEV;
}
psp->capability.raw = val;
return 0;
}
static int psp_check_sev_support(struct psp_device *psp)
{
/* Check if device supports SEV feature */
if (!psp->capability.sev) {
dev_dbg(psp->dev, "psp does not support SEV\n");
return -ENODEV;
}
return 0;
}
static int psp_check_tee_support(struct psp_device *psp)
{
/* Check if device supports TEE feature */
if (!psp->capability.tee) {
dev_dbg(psp->dev, "psp does not support TEE\n");
return -ENODEV;
}
return 0;
}
static int psp_check_sfs_support(struct psp_device *psp)
{
/* Check if device supports SFS feature */
if (!psp->capability.sfs) {
dev_dbg(psp->dev, "psp does not support SFS\n");
return -ENODEV;
}
return 0;
}
static int psp_init(struct psp_device *psp)
{
int ret;
if (!psp_check_sev_support(psp)) {
ret = sev_dev_init(psp);
if (ret)
return ret;
}
if (!psp_check_tee_support(psp)) {
ret = tee_dev_init(psp);
if (ret)
return ret;
}
if (!psp_check_sfs_support(psp)) {
ret = sfs_dev_init(psp);
if (ret)
return ret;
}
if (psp->vdata->platform_access) {
ret = platform_access_dev_init(psp);
if (ret)
return ret;
}
/* dbc must come after platform access as it tests the feature */
if (PSP_FEATURE(psp, DBC) ||
psp->capability.dbc_thru_ext) {
ret = dbc_dev_init(psp);
if (ret)
return ret;
}
/* HSTI uses platform access on some systems. */
ret = psp_init_hsti(psp);
if (ret)
return ret;
return 0;
}
int psp_dev_init(struct sp_device *sp)
{
struct device *dev = sp->dev;
struct psp_device *psp;
int ret;
ret = -ENOMEM;
psp = psp_alloc_struct(sp);
if (!psp)
goto e_err;
sp->psp_data = psp;
psp->vdata = (struct psp_vdata *)sp->dev_vdata->psp_vdata;
if (!psp->vdata) {
ret = -ENODEV;
dev_err(dev, "missing driver data\n");
goto e_err;
}
psp->io_regs = sp->io_map;
mutex_init(&psp->mailbox_mutex);
ret = psp_get_capability(psp);
if (ret)
goto e_disable;
/* Disable and clear interrupts until ready */
iowrite32(0, psp->io_regs + psp->vdata->inten_reg);
iowrite32(-1, psp->io_regs + psp->vdata->intsts_reg);
/* Request an irq */
ret = sp_request_psp_irq(psp->sp, psp_irq_handler, psp->name, psp);
if (ret) {
dev_err(dev, "psp: unable to allocate an IRQ\n");
goto e_err;
}
/* master device must be set for platform access */
if (psp->sp->set_psp_master_device)
psp->sp->set_psp_master_device(psp->sp);
ret = psp_init(psp);
if (ret)
goto e_irq;
/* Enable interrupt */
iowrite32(-1, psp->io_regs + psp->vdata->inten_reg);
dev_notice(dev, "psp enabled\n");
return 0;
e_irq:
if (sp->clear_psp_master_device)
sp->clear_psp_master_device(sp);
sp_free_psp_irq(psp->sp, psp);
e_err:
sp->psp_data = NULL;
dev_notice(dev, "psp initialization failed\n");
return ret;
e_disable:
sp->psp_data = NULL;
return ret;
}
void psp_dev_destroy(struct sp_device *sp)
{
struct psp_device *psp = sp->psp_data;
if (!psp)
return;
dbc_dev_destroy(psp);
platform_access_dev_destroy(psp);
sfs_dev_destroy(psp);
tee_dev_destroy(psp);
sev_dev_destroy(psp);
sp_free_psp_irq(sp, psp);
if (sp->clear_psp_master_device)
sp->clear_psp_master_device(sp);
}
void psp_set_sev_irq_handler(struct psp_device *psp, psp_irq_handler_t handler,
void *data)
{
psp->sev_irq_data = data;
psp->sev_irq_handler = handler;
}
void psp_clear_sev_irq_handler(struct psp_device *psp)
{
psp_set_sev_irq_handler(psp, NULL, NULL);
}
struct psp_device *psp_get_master_device(void)
{
struct sp_device *sp = sp_get_psp_master_device();
return sp ? sp->psp_data : NULL;
}
int psp_restore(struct sp_device *sp)
{
struct psp_device *psp = sp->psp_data;
int ret = 0;
if (psp->tee_data)
ret = tee_restore(psp);
return ret;
}
void psp_pci_init(void)
{
psp_master = psp_get_master_device();
if (!psp_master)
return;
sev_pci_init();
}
void psp_pci_exit(void)
{
if (!psp_master)
return;
sev_pci_exit();
}
+137
View File
@@ -0,0 +1,137 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* AMD Platform Security Processor (PSP) interface driver
*
* Copyright (C) 2017-2019 Advanced Micro Devices, Inc.
*
* Author: Brijesh Singh <brijesh.singh@amd.com>
*/
#ifndef __PSP_DEV_H__
#define __PSP_DEV_H__
#include <linux/device.h>
#include <linux/list.h>
#include <linux/bits.h>
#include <linux/interrupt.h>
#include <linux/mutex.h>
#include <linux/psp.h>
#include <linux/psp-platform-access.h>
#include "sp-dev.h"
#define MAX_PSP_NAME_LEN 16
extern struct psp_device *psp_master;
typedef void (*psp_irq_handler_t)(int, void *, unsigned int);
union psp_cap_register {
unsigned int raw;
struct {
unsigned int sev :1,
tee :1,
dbc_thru_ext :1,
sfs :1,
rsvd1 :3,
security_reporting :1,
fused_part :1,
boot_integrity :1,
debug_lock_on :1,
rsvd3 :2,
tsme_status :1,
rsvd4 :1,
anti_rollback_status :1,
rpmc_production_enabled :1,
rpmc_spirom_available :1,
hsp_tpm_available :1,
rom_armor_enforced :1,
rsvd5 :12;
};
};
struct psp_device {
struct list_head entry;
struct psp_vdata *vdata;
char name[MAX_PSP_NAME_LEN];
struct device *dev;
struct sp_device *sp;
void __iomem *io_regs;
struct mutex mailbox_mutex;
psp_irq_handler_t sev_irq_handler;
void *sev_irq_data;
void *sev_data;
void *tee_data;
void *platform_access_data;
void *dbc_data;
void *sfs_data;
union psp_cap_register capability;
};
void psp_set_sev_irq_handler(struct psp_device *psp, psp_irq_handler_t handler,
void *data);
void psp_clear_sev_irq_handler(struct psp_device *psp);
struct psp_device *psp_get_master_device(void);
/**
* enum psp_cmd - PSP mailbox commands
* @PSP_CMD_TEE_RING_INIT: Initialize TEE ring buffer
* @PSP_CMD_TEE_RING_DESTROY: Destroy TEE ring buffer
* @PSP_CMD_TEE_EXTENDED_CMD: Extended command
* @PSP_CMD_MAX: Maximum command id
*/
enum psp_cmd {
PSP_CMD_TEE_RING_INIT = 1,
PSP_CMD_TEE_RING_DESTROY = 2,
PSP_CMD_TEE_EXTENDED_CMD = 14,
PSP_CMD_MAX = 15,
};
int psp_mailbox_command(struct psp_device *psp, enum psp_cmd cmd, void *cmdbuff,
unsigned int timeout_msecs, unsigned int *cmdresp);
/**
* struct psp_ext_req_buffer_hdr - Structure of the extended command header
* @payload_size: total payload size
* @sub_cmd_id: extended command ID
* @status: status of command execution (out)
*/
struct psp_ext_req_buffer_hdr {
u32 payload_size;
u32 sub_cmd_id;
u32 status;
} __packed;
struct psp_ext_request {
struct psp_ext_req_buffer_hdr header;
void *buf;
} __packed;
/**
* enum psp_sub_cmd - PSP mailbox sub commands
* @PSP_SUB_CMD_DBC_GET_NONCE: Get nonce from DBC
* @PSP_SUB_CMD_DBC_SET_UID: Set UID for DBC
* @PSP_SUB_CMD_DBC_GET_PARAMETER: Get parameter from DBC
* @PSP_SUB_CMD_DBC_SET_PARAMETER: Set parameter for DBC
* @PSP_SUB_CMD_SFS_GET_FW_VERS: Get firmware versions for ASP and other MP
* @PSP_SUB_CMD_SFS_UPDATE: Command to load, verify and execute SFS package
*/
enum psp_sub_cmd {
PSP_SUB_CMD_DBC_GET_NONCE = PSP_DYNAMIC_BOOST_GET_NONCE,
PSP_SUB_CMD_DBC_SET_UID = PSP_DYNAMIC_BOOST_SET_UID,
PSP_SUB_CMD_DBC_GET_PARAMETER = PSP_DYNAMIC_BOOST_GET_PARAMETER,
PSP_SUB_CMD_DBC_SET_PARAMETER = PSP_DYNAMIC_BOOST_SET_PARAMETER,
PSP_SUB_CMD_SFS_GET_FW_VERS = PSP_SFS_GET_FW_VERSIONS,
PSP_SUB_CMD_SFS_UPDATE = PSP_SFS_UPDATE,
};
int psp_extended_mailbox_cmd(struct psp_device *psp, unsigned int timeout_msecs,
struct psp_ext_request *req);
#endif /* __PSP_DEV_H */
@@ -0,0 +1,864 @@
// SPDX-License-Identifier: GPL-2.0-only
// Interface to PSP for CCP/SEV-TIO/SNP-VM
#include <linux/pci.h>
#include <linux/tsm.h>
#include <linux/psp.h>
#include <linux/vmalloc.h>
#include <linux/bitfield.h>
#include <linux/pci-doe.h>
#include <asm/sev-common.h>
#include <asm/sev.h>
#include <asm/page.h>
#include "sev-dev.h"
#include "sev-dev-tio.h"
#define to_tio_status(dev_data) \
(container_of((dev_data), struct tio_dsm, data)->sev->tio_status)
#define SLA_PAGE_TYPE_DATA 0
#define SLA_PAGE_TYPE_SCATTER 1
#define SLA_PAGE_SIZE_4K 0
#define SLA_PAGE_SIZE_2M 1
#define SLA_SZ(s) ((s).page_size == SLA_PAGE_SIZE_2M ? SZ_2M : SZ_4K)
#define SLA_SCATTER_LEN(s) (SLA_SZ(s) / sizeof(struct sla_addr_t))
#define SLA_EOL ((struct sla_addr_t) { .pfn = ((1UL << 40) - 1) })
#define SLA_NULL ((struct sla_addr_t) { 0 })
#define IS_SLA_NULL(s) ((s).sla == SLA_NULL.sla)
#define IS_SLA_EOL(s) ((s).sla == SLA_EOL.sla)
static phys_addr_t sla_to_pa(struct sla_addr_t sla)
{
u64 pfn = sla.pfn;
u64 pa = pfn << PAGE_SHIFT;
return pa;
}
static void *sla_to_va(struct sla_addr_t sla)
{
void *va = __va(__sme_clr(sla_to_pa(sla)));
return va;
}
#define sla_to_pfn(sla) (__pa(sla_to_va(sla)) >> PAGE_SHIFT)
#define sla_to_page(sla) virt_to_page(sla_to_va(sla))
static struct sla_addr_t make_sla(struct page *pg, bool stp)
{
u64 pa = __sme_set(page_to_phys(pg));
struct sla_addr_t ret = {
.pfn = pa >> PAGE_SHIFT,
.page_size = SLA_PAGE_SIZE_4K, /* Do not do SLA_PAGE_SIZE_2M ATM */
.page_type = stp ? SLA_PAGE_TYPE_SCATTER : SLA_PAGE_TYPE_DATA
};
return ret;
}
/* the BUFFER Structure */
#define SLA_BUFFER_FLAG_ENCRYPTION BIT(0)
/*
* struct sla_buffer_hdr - Scatter list address buffer header
*
* @capacity_sz: Total capacity of the buffer in bytes
* @payload_sz: Size of buffer payload in bytes, must be multiple of 32B
* @flags: Buffer flags (SLA_BUFFER_FLAG_ENCRYPTION: buffer is encrypted)
* @iv: Initialization vector used for encryption
* @authtag: Authentication tag for encrypted buffer
*/
struct sla_buffer_hdr {
u32 capacity_sz;
u32 payload_sz; /* The size of BUFFER_PAYLOAD in bytes. Must be multiple of 32B */
u32 flags;
u8 reserved1[4];
u8 iv[16]; /* IV used for the encryption of this buffer */
u8 authtag[16]; /* Authentication tag for this buffer */
u8 reserved2[16];
} __packed;
enum spdm_data_type_t {
DOBJ_DATA_TYPE_SPDM = 0x1,
DOBJ_DATA_TYPE_SECURE_SPDM = 0x2,
};
struct spdm_dobj_hdr_req {
struct spdm_dobj_hdr hdr; /* hdr.id == SPDM_DOBJ_ID_REQ */
u8 data_type; /* spdm_data_type_t */
u8 reserved2[5];
} __packed;
struct spdm_dobj_hdr_resp {
struct spdm_dobj_hdr hdr; /* hdr.id == SPDM_DOBJ_ID_RESP */
u8 data_type; /* spdm_data_type_t */
u8 reserved2[5];
} __packed;
/* Defined in sev-dev-tio.h so sev-dev-tsm.c can read types of blobs */
struct spdm_dobj_hdr_cert;
struct spdm_dobj_hdr_meas;
struct spdm_dobj_hdr_report;
/* Used in all SPDM-aware TIO commands */
struct spdm_ctrl {
struct sla_addr_t req;
struct sla_addr_t resp;
struct sla_addr_t scratch;
struct sla_addr_t output;
} __packed;
static size_t sla_dobj_id_to_size(u8 id)
{
size_t n;
BUILD_BUG_ON(sizeof(struct spdm_dobj_hdr_resp) != 0x10);
switch (id) {
case SPDM_DOBJ_ID_REQ:
n = sizeof(struct spdm_dobj_hdr_req);
break;
case SPDM_DOBJ_ID_RESP:
n = sizeof(struct spdm_dobj_hdr_resp);
break;
default:
WARN_ON(1);
n = 0;
break;
}
return n;
}
#define SPDM_DOBJ_HDR_SIZE(hdr) sla_dobj_id_to_size((hdr)->id)
#define SPDM_DOBJ_DATA(hdr) ((u8 *)(hdr) + SPDM_DOBJ_HDR_SIZE(hdr))
#define SPDM_DOBJ_LEN(hdr) ((hdr)->length - SPDM_DOBJ_HDR_SIZE(hdr))
#define sla_to_dobj_resp_hdr(buf) ((struct spdm_dobj_hdr_resp *) \
sla_to_dobj_hdr_check((buf), SPDM_DOBJ_ID_RESP))
#define sla_to_dobj_req_hdr(buf) ((struct spdm_dobj_hdr_req *) \
sla_to_dobj_hdr_check((buf), SPDM_DOBJ_ID_REQ))
static struct spdm_dobj_hdr *sla_to_dobj_hdr(struct sla_buffer_hdr *buf)
{
if (!buf)
return NULL;
return (struct spdm_dobj_hdr *) &buf[1];
}
static struct spdm_dobj_hdr *sla_to_dobj_hdr_check(struct sla_buffer_hdr *buf, u32 check_dobjid)
{
struct spdm_dobj_hdr *hdr = sla_to_dobj_hdr(buf);
if (WARN_ON_ONCE(!hdr))
return NULL;
if (hdr->id != check_dobjid) {
pr_err("! ERROR: expected %d, found %d\n", check_dobjid, hdr->id);
return NULL;
}
return hdr;
}
static void *sla_to_data(struct sla_buffer_hdr *buf, u32 dobjid)
{
struct spdm_dobj_hdr *hdr = sla_to_dobj_hdr(buf);
if (WARN_ON_ONCE(dobjid != SPDM_DOBJ_ID_REQ && dobjid != SPDM_DOBJ_ID_RESP))
return NULL;
if (!hdr)
return NULL;
return (u8 *) hdr + sla_dobj_id_to_size(dobjid);
}
/*
* struct sev_data_tio_status - SEV_CMD_TIO_STATUS command
*
* @length: Length of this command buffer in bytes
* @status_paddr: System physical address of the TIO_STATUS structure
*/
struct sev_data_tio_status {
u32 length;
u8 reserved[4];
u64 status_paddr;
} __packed;
/* TIO_INIT */
struct sev_data_tio_init {
u32 length;
u8 reserved[12];
} __packed;
/*
* struct sev_data_tio_dev_create - TIO_DEV_CREATE command
*
* @length: Length in bytes of this command buffer
* @dev_ctx_sla: Scatter list address pointing to a buffer to be used as a device context buffer
* @device_id: PCIe Routing Identifier of the device to connect to
* @root_port_id: PCIe Routing Identifier of the root port of the device
* @segment_id: PCIe Segment Identifier of the device to connect to
*/
struct sev_data_tio_dev_create {
u32 length;
u8 reserved1[4];
struct sla_addr_t dev_ctx_sla;
u16 device_id;
u16 root_port_id;
u8 segment_id;
u8 reserved2[11];
} __packed;
/*
* struct sev_data_tio_dev_connect - TIO_DEV_CONNECT command
*
* @length: Length in bytes of this command buffer
* @spdm_ctrl: SPDM control structure defined in Section 5.1
* @dev_ctx_sla: Scatter list address of the device context buffer
* @tc_mask: Bitmask of the traffic classes to initialize for SEV-TIO usage.
* Setting the kth bit of the TC_MASK to 1 indicates that the traffic
* class k will be initialized
* @cert_slot: Slot number of the certificate requested for constructing the SPDM session
* @ide_stream_id: IDE stream IDs to be associated with this device.
* Valid only if corresponding bit in TC_MASK is set
*/
struct sev_data_tio_dev_connect {
u32 length;
u8 reserved1[4];
struct spdm_ctrl spdm_ctrl;
u8 reserved2[8];
struct sla_addr_t dev_ctx_sla;
u8 tc_mask;
u8 cert_slot;
u8 reserved3[6];
u8 ide_stream_id[8];
u8 reserved4[8];
} __packed;
/*
* struct sev_data_tio_dev_disconnect - TIO_DEV_DISCONNECT command
*
* @length: Length in bytes of this command buffer
* @flags: Command flags (TIO_DEV_DISCONNECT_FLAG_FORCE: force disconnect)
* @spdm_ctrl: SPDM control structure defined in Section 5.1
* @dev_ctx_sla: Scatter list address of the device context buffer
*/
#define TIO_DEV_DISCONNECT_FLAG_FORCE BIT(0)
struct sev_data_tio_dev_disconnect {
u32 length;
u32 flags;
struct spdm_ctrl spdm_ctrl;
struct sla_addr_t dev_ctx_sla;
} __packed;
/*
* struct sev_data_tio_dev_meas - TIO_DEV_MEASUREMENTS command
*
* @length: Length in bytes of this command buffer
* @flags: Command flags (TIO_DEV_MEAS_FLAG_RAW_BITSTREAM: request raw measurements)
* @spdm_ctrl: SPDM control structure defined in Section 5.1
* @dev_ctx_sla: Scatter list address of the device context buffer
* @meas_nonce: Nonce for measurement freshness verification
*/
#define TIO_DEV_MEAS_FLAG_RAW_BITSTREAM BIT(0)
struct sev_data_tio_dev_meas {
u32 length;
u32 flags;
struct spdm_ctrl spdm_ctrl;
struct sla_addr_t dev_ctx_sla;
u8 meas_nonce[32];
} __packed;
/*
* struct sev_data_tio_dev_certs - TIO_DEV_CERTIFICATES command
*
* @length: Length in bytes of this command buffer
* @spdm_ctrl: SPDM control structure defined in Section 5.1
* @dev_ctx_sla: Scatter list address of the device context buffer
*/
struct sev_data_tio_dev_certs {
u32 length;
u8 reserved[4];
struct spdm_ctrl spdm_ctrl;
struct sla_addr_t dev_ctx_sla;
} __packed;
/*
* struct sev_data_tio_dev_reclaim - TIO_DEV_RECLAIM command
*
* @length: Length in bytes of this command buffer
* @dev_ctx_sla: Scatter list address of the device context buffer
*
* This command reclaims resources associated with a device context.
*/
struct sev_data_tio_dev_reclaim {
u32 length;
u8 reserved[4];
struct sla_addr_t dev_ctx_sla;
} __packed;
static struct sla_buffer_hdr *sla_buffer_map(struct sla_addr_t sla)
{
struct sla_buffer_hdr *buf;
BUILD_BUG_ON(sizeof(struct sla_buffer_hdr) != 0x40);
if (IS_SLA_NULL(sla))
return NULL;
if (sla.page_type == SLA_PAGE_TYPE_SCATTER) {
struct sla_addr_t *scatter = sla_to_va(sla);
unsigned int i, npages = 0;
for (i = 0; i < SLA_SCATTER_LEN(sla); ++i) {
if (WARN_ON_ONCE(SLA_SZ(scatter[i]) > SZ_4K))
return NULL;
if (WARN_ON_ONCE(scatter[i].page_type == SLA_PAGE_TYPE_SCATTER))
return NULL;
if (IS_SLA_EOL(scatter[i])) {
npages = i;
break;
}
}
if (WARN_ON_ONCE(!npages))
return NULL;
struct page **pp = kmalloc_objs(pp[0], npages);
if (!pp)
return NULL;
for (i = 0; i < npages; ++i)
pp[i] = sla_to_page(scatter[i]);
buf = vm_map_ram(pp, npages, 0);
kfree(pp);
} else {
struct page *pg = sla_to_page(sla);
buf = vm_map_ram(&pg, 1, 0);
}
return buf;
}
static void sla_buffer_unmap(struct sla_addr_t sla, struct sla_buffer_hdr *buf)
{
if (!buf)
return;
if (sla.page_type == SLA_PAGE_TYPE_SCATTER) {
struct sla_addr_t *scatter = sla_to_va(sla);
unsigned int i, npages = 0;
for (i = 0; i < SLA_SCATTER_LEN(sla); ++i) {
if (IS_SLA_EOL(scatter[i])) {
npages = i;
break;
}
}
if (!npages)
return;
vm_unmap_ram(buf, npages);
} else {
vm_unmap_ram(buf, 1);
}
}
static void dobj_response_init(struct sla_buffer_hdr *buf)
{
struct spdm_dobj_hdr *dobj = sla_to_dobj_hdr(buf);
dobj->id = SPDM_DOBJ_ID_RESP;
dobj->version.major = 0x1;
dobj->version.minor = 0;
dobj->length = 0;
buf->payload_sz = sla_dobj_id_to_size(dobj->id) + dobj->length;
}
static void sla_free(struct sla_addr_t sla, size_t len, bool firmware_state)
{
unsigned int npages = PAGE_ALIGN(len) >> PAGE_SHIFT;
struct sla_addr_t *scatter = NULL;
int ret = 0, i;
if (IS_SLA_NULL(sla))
return;
if (firmware_state) {
if (sla.page_type == SLA_PAGE_TYPE_SCATTER) {
scatter = sla_to_va(sla);
for (i = 0; i < npages; ++i) {
if (IS_SLA_EOL(scatter[i]))
break;
ret = snp_reclaim_pages(sla_to_pa(scatter[i]), 1, false);
if (ret)
break;
}
} else {
ret = snp_reclaim_pages(sla_to_pa(sla), 1, false);
}
}
if (WARN_ON(ret))
return;
if (scatter) {
for (i = 0; i < npages; ++i) {
if (IS_SLA_EOL(scatter[i]))
break;
free_page((unsigned long)sla_to_va(scatter[i]));
}
}
free_page((unsigned long)sla_to_va(sla));
}
static struct sla_addr_t sla_alloc(size_t len, bool firmware_state)
{
unsigned long i, npages = PAGE_ALIGN(len) >> PAGE_SHIFT;
struct sla_addr_t *scatter = NULL;
struct sla_addr_t ret = SLA_NULL;
struct sla_buffer_hdr *buf;
struct page *pg;
if (npages == 0)
return ret;
if (WARN_ON_ONCE(npages > ((PAGE_SIZE / sizeof(struct sla_addr_t)) + 1)))
return ret;
BUILD_BUG_ON(PAGE_SIZE < SZ_4K);
if (npages > 1) {
pg = alloc_page(GFP_KERNEL | __GFP_ZERO);
if (!pg)
return SLA_NULL;
ret = make_sla(pg, true);
scatter = page_to_virt(pg);
for (i = 0; i < npages; ++i) {
pg = alloc_page(GFP_KERNEL | __GFP_ZERO);
if (!pg)
goto no_reclaim_exit;
scatter[i] = make_sla(pg, false);
}
scatter[i] = SLA_EOL;
} else {
pg = alloc_page(GFP_KERNEL | __GFP_ZERO);
if (!pg)
return SLA_NULL;
ret = make_sla(pg, false);
}
buf = sla_buffer_map(ret);
if (!buf)
goto no_reclaim_exit;
buf->capacity_sz = (npages << PAGE_SHIFT);
sla_buffer_unmap(ret, buf);
if (firmware_state) {
if (scatter) {
for (i = 0; i < npages; ++i) {
if (rmp_make_private(sla_to_pfn(scatter[i]), 0,
PG_LEVEL_4K, 0, true))
goto free_exit;
}
} else {
if (rmp_make_private(sla_to_pfn(ret), 0, PG_LEVEL_4K, 0, true))
goto no_reclaim_exit;
}
}
return ret;
no_reclaim_exit:
firmware_state = false;
free_exit:
sla_free(ret, len, firmware_state);
return SLA_NULL;
}
/* Expands a buffer, only firmware owned buffers allowed for now */
static int sla_expand(struct sla_addr_t *sla, size_t *len)
{
struct sla_buffer_hdr *oldbuf = sla_buffer_map(*sla), *newbuf;
struct sla_addr_t oldsla = *sla, newsla;
size_t oldlen = *len, newlen;
if (!oldbuf)
return -EFAULT;
newlen = oldbuf->capacity_sz;
if (oldbuf->capacity_sz == oldlen) {
/* This buffer does not require expansion, must be another buffer */
sla_buffer_unmap(oldsla, oldbuf);
return 1;
}
pr_notice("Expanding BUFFER from %ld to %ld bytes\n", oldlen, newlen);
newsla = sla_alloc(newlen, true);
if (IS_SLA_NULL(newsla))
return -ENOMEM;
newbuf = sla_buffer_map(newsla);
if (!newbuf) {
sla_free(newsla, newlen, true);
return -EFAULT;
}
memcpy(newbuf, oldbuf, oldlen);
sla_buffer_unmap(newsla, newbuf);
sla_free(oldsla, oldlen, true);
*sla = newsla;
*len = newlen;
return 0;
}
static int sev_tio_do_cmd(int cmd, void *data, size_t data_len, int *psp_ret,
struct tsm_dsm_tio *dev_data)
{
int rc;
*psp_ret = 0;
rc = sev_do_cmd(cmd, data, psp_ret);
if (WARN_ON(!rc && *psp_ret == SEV_RET_SPDM_REQUEST))
return -EIO;
if (rc == 0 && *psp_ret == SEV_RET_EXPAND_BUFFER_LENGTH_REQUEST) {
int rc1, rc2;
rc1 = sla_expand(&dev_data->output, &dev_data->output_len);
if (rc1 < 0)
return rc1;
rc2 = sla_expand(&dev_data->scratch, &dev_data->scratch_len);
if (rc2 < 0)
return rc2;
if (!rc1 && !rc2)
/* Neither buffer requires expansion, this is wrong */
return -EFAULT;
*psp_ret = 0;
rc = sev_do_cmd(cmd, data, psp_ret);
}
if ((rc == 0 || rc == -EIO) && *psp_ret == SEV_RET_SPDM_REQUEST) {
struct spdm_dobj_hdr_resp *resp_hdr;
struct spdm_dobj_hdr_req *req_hdr;
struct sev_tio_status *tio_status = to_tio_status(dev_data);
size_t resp_len = tio_status->spdm_req_size_max -
(sla_dobj_id_to_size(SPDM_DOBJ_ID_RESP) + sizeof(struct sla_buffer_hdr));
if (!dev_data->cmd) {
if (WARN_ON_ONCE(!data_len || (data_len != *(u32 *) data)))
return -EINVAL;
if (WARN_ON(data_len > sizeof(dev_data->cmd_data)))
return -EFAULT;
memcpy(dev_data->cmd_data, data, data_len);
memset(&dev_data->cmd_data[data_len], 0xFF,
sizeof(dev_data->cmd_data) - data_len);
dev_data->cmd = cmd;
}
req_hdr = sla_to_dobj_req_hdr(dev_data->reqbuf);
resp_hdr = sla_to_dobj_resp_hdr(dev_data->respbuf);
switch (req_hdr->data_type) {
case DOBJ_DATA_TYPE_SPDM:
rc = PCI_DOE_FEATURE_CMA;
break;
case DOBJ_DATA_TYPE_SECURE_SPDM:
rc = PCI_DOE_FEATURE_SSESSION;
break;
default:
return -EINVAL;
}
resp_hdr->data_type = req_hdr->data_type;
dev_data->spdm.req_len = req_hdr->hdr.length -
sla_dobj_id_to_size(SPDM_DOBJ_ID_REQ);
dev_data->spdm.rsp_len = resp_len;
} else if (dev_data && dev_data->cmd) {
/* For either error or success just stop the bouncing */
memset(dev_data->cmd_data, 0, sizeof(dev_data->cmd_data));
dev_data->cmd = 0;
}
return rc;
}
int sev_tio_continue(struct tsm_dsm_tio *dev_data)
{
struct spdm_dobj_hdr_resp *resp_hdr;
int ret;
if (!dev_data || !dev_data->cmd)
return -EINVAL;
resp_hdr = sla_to_dobj_resp_hdr(dev_data->respbuf);
resp_hdr->hdr.length = ALIGN(sla_dobj_id_to_size(SPDM_DOBJ_ID_RESP) +
dev_data->spdm.rsp_len, 32);
dev_data->respbuf->payload_sz = resp_hdr->hdr.length;
ret = sev_tio_do_cmd(dev_data->cmd, dev_data->cmd_data, 0,
&dev_data->psp_ret, dev_data);
if (ret)
return ret;
if (dev_data->psp_ret != SEV_RET_SUCCESS)
return -EINVAL;
return 0;
}
static void spdm_ctrl_init(struct spdm_ctrl *ctrl, struct tsm_dsm_tio *dev_data)
{
ctrl->req = dev_data->req;
ctrl->resp = dev_data->resp;
ctrl->scratch = dev_data->scratch;
ctrl->output = dev_data->output;
}
static void spdm_ctrl_free(struct tsm_dsm_tio *dev_data)
{
struct sev_tio_status *tio_status = to_tio_status(dev_data);
size_t len = tio_status->spdm_req_size_max -
(sla_dobj_id_to_size(SPDM_DOBJ_ID_RESP) +
sizeof(struct sla_buffer_hdr));
struct tsm_spdm *spdm = &dev_data->spdm;
sla_buffer_unmap(dev_data->resp, dev_data->respbuf);
sla_buffer_unmap(dev_data->req, dev_data->reqbuf);
spdm->rsp = NULL;
spdm->req = NULL;
sla_free(dev_data->req, len, true);
sla_free(dev_data->resp, len, false);
sla_free(dev_data->scratch, tio_status->spdm_scratch_size_max, true);
dev_data->req.sla = 0;
dev_data->resp.sla = 0;
dev_data->scratch.sla = 0;
dev_data->respbuf = NULL;
dev_data->reqbuf = NULL;
sla_free(dev_data->output, tio_status->spdm_out_size_max, true);
}
static int spdm_ctrl_alloc(struct tsm_dsm_tio *dev_data)
{
struct sev_tio_status *tio_status = to_tio_status(dev_data);
struct tsm_spdm *spdm = &dev_data->spdm;
int ret;
dev_data->req = sla_alloc(tio_status->spdm_req_size_max, true);
dev_data->resp = sla_alloc(tio_status->spdm_req_size_max, false);
dev_data->scratch_len = tio_status->spdm_scratch_size_max;
dev_data->scratch = sla_alloc(dev_data->scratch_len, true);
dev_data->output_len = tio_status->spdm_out_size_max;
dev_data->output = sla_alloc(dev_data->output_len, true);
if (IS_SLA_NULL(dev_data->req) || IS_SLA_NULL(dev_data->resp) ||
IS_SLA_NULL(dev_data->scratch) || IS_SLA_NULL(dev_data->dev_ctx)) {
ret = -ENOMEM;
goto free_spdm_exit;
}
dev_data->reqbuf = sla_buffer_map(dev_data->req);
dev_data->respbuf = sla_buffer_map(dev_data->resp);
if (!dev_data->reqbuf || !dev_data->respbuf) {
ret = -EFAULT;
goto free_spdm_exit;
}
spdm->req = sla_to_data(dev_data->reqbuf, SPDM_DOBJ_ID_REQ);
spdm->rsp = sla_to_data(dev_data->respbuf, SPDM_DOBJ_ID_RESP);
if (!spdm->req || !spdm->rsp) {
ret = -EFAULT;
goto free_spdm_exit;
}
dobj_response_init(dev_data->respbuf);
return 0;
free_spdm_exit:
spdm_ctrl_free(dev_data);
return ret;
}
int sev_tio_init_locked(void *tio_status_page)
{
struct sev_tio_status *tio_status = tio_status_page;
struct sev_data_tio_status data_status = {
.length = sizeof(data_status),
};
int ret, psp_ret;
data_status.status_paddr = __psp_pa(tio_status_page);
ret = __sev_do_cmd_locked(SEV_CMD_TIO_STATUS, &data_status, &psp_ret);
if (ret)
return ret;
if (tio_status->length < offsetofend(struct sev_tio_status, tdictx_size) ||
tio_status->reserved)
return -EFAULT;
if (!tio_status->tio_en && !tio_status->tio_init_done)
return -ENOENT;
if (tio_status->tio_init_done)
return -EBUSY;
struct sev_data_tio_init ti = { .length = sizeof(ti) };
ret = __sev_do_cmd_locked(SEV_CMD_TIO_INIT, &ti, &psp_ret);
if (ret)
return ret;
ret = __sev_do_cmd_locked(SEV_CMD_TIO_STATUS, &data_status, &psp_ret);
if (ret)
return ret;
return 0;
}
int sev_tio_dev_create(struct tsm_dsm_tio *dev_data, u16 device_id,
u16 root_port_id, u8 segment_id)
{
struct sev_tio_status *tio_status = to_tio_status(dev_data);
struct sev_data_tio_dev_create create = {
.length = sizeof(create),
.device_id = device_id,
.root_port_id = root_port_id,
.segment_id = segment_id,
};
void *data_pg;
int ret;
dev_data->dev_ctx = sla_alloc(tio_status->devctx_size, true);
if (IS_SLA_NULL(dev_data->dev_ctx))
return -ENOMEM;
data_pg = snp_alloc_firmware_page(GFP_KERNEL_ACCOUNT);
if (!data_pg) {
ret = -ENOMEM;
goto free_ctx_exit;
}
create.dev_ctx_sla = dev_data->dev_ctx;
ret = sev_do_cmd(SEV_CMD_TIO_DEV_CREATE, &create, &dev_data->psp_ret);
if (ret)
goto free_data_pg_exit;
dev_data->data_pg = data_pg;
return 0;
free_data_pg_exit:
snp_free_firmware_page(data_pg);
free_ctx_exit:
sla_free(create.dev_ctx_sla, tio_status->devctx_size, true);
return ret;
}
int sev_tio_dev_reclaim(struct tsm_dsm_tio *dev_data)
{
struct sev_tio_status *tio_status = to_tio_status(dev_data);
struct sev_data_tio_dev_reclaim r = {
.length = sizeof(r),
.dev_ctx_sla = dev_data->dev_ctx,
};
int ret;
if (dev_data->data_pg) {
snp_free_firmware_page(dev_data->data_pg);
dev_data->data_pg = NULL;
}
if (IS_SLA_NULL(dev_data->dev_ctx))
return 0;
ret = sev_do_cmd(SEV_CMD_TIO_DEV_RECLAIM, &r, &dev_data->psp_ret);
sla_free(dev_data->dev_ctx, tio_status->devctx_size, true);
dev_data->dev_ctx = SLA_NULL;
spdm_ctrl_free(dev_data);
return ret;
}
int sev_tio_dev_connect(struct tsm_dsm_tio *dev_data, u8 tc_mask, u8 ids[8], u8 cert_slot)
{
struct sev_data_tio_dev_connect connect = {
.length = sizeof(connect),
.tc_mask = tc_mask,
.cert_slot = cert_slot,
.dev_ctx_sla = dev_data->dev_ctx,
.ide_stream_id = {
ids[0], ids[1], ids[2], ids[3],
ids[4], ids[5], ids[6], ids[7]
},
};
int ret;
if (WARN_ON(IS_SLA_NULL(dev_data->dev_ctx)))
return -EFAULT;
if (!(tc_mask & 1))
return -EINVAL;
ret = spdm_ctrl_alloc(dev_data);
if (ret)
return ret;
spdm_ctrl_init(&connect.spdm_ctrl, dev_data);
return sev_tio_do_cmd(SEV_CMD_TIO_DEV_CONNECT, &connect, sizeof(connect),
&dev_data->psp_ret, dev_data);
}
int sev_tio_dev_disconnect(struct tsm_dsm_tio *dev_data, bool force)
{
struct sev_data_tio_dev_disconnect dc = {
.length = sizeof(dc),
.dev_ctx_sla = dev_data->dev_ctx,
.flags = force ? TIO_DEV_DISCONNECT_FLAG_FORCE : 0,
};
if (WARN_ON_ONCE(IS_SLA_NULL(dev_data->dev_ctx)))
return -EFAULT;
spdm_ctrl_init(&dc.spdm_ctrl, dev_data);
return sev_tio_do_cmd(SEV_CMD_TIO_DEV_DISCONNECT, &dc, sizeof(dc),
&dev_data->psp_ret, dev_data);
}
int sev_tio_cmd_buffer_len(int cmd)
{
switch (cmd) {
case SEV_CMD_TIO_STATUS: return sizeof(struct sev_data_tio_status);
case SEV_CMD_TIO_INIT: return sizeof(struct sev_data_tio_init);
case SEV_CMD_TIO_DEV_CREATE: return sizeof(struct sev_data_tio_dev_create);
case SEV_CMD_TIO_DEV_RECLAIM: return sizeof(struct sev_data_tio_dev_reclaim);
case SEV_CMD_TIO_DEV_CONNECT: return sizeof(struct sev_data_tio_dev_connect);
case SEV_CMD_TIO_DEV_DISCONNECT: return sizeof(struct sev_data_tio_dev_disconnect);
default: return 0;
}
}
@@ -0,0 +1,123 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#ifndef __PSP_SEV_TIO_H__
#define __PSP_SEV_TIO_H__
#include <linux/pci-tsm.h>
#include <linux/pci-ide.h>
#include <linux/tsm.h>
#include <uapi/linux/psp-sev.h>
struct sla_addr_t {
union {
u64 sla;
struct {
u64 page_type :1,
page_size :1,
reserved1 :10,
pfn :40,
reserved2 :12;
};
};
} __packed;
#define SEV_TIO_MAX_COMMAND_LENGTH 128
/* SPDM control structure for DOE */
struct tsm_spdm {
unsigned long req_len;
void *req;
unsigned long rsp_len;
void *rsp;
};
/* Describes TIO device */
struct tsm_dsm_tio {
u8 cert_slot;
struct sla_addr_t dev_ctx;
struct sla_addr_t req;
struct sla_addr_t resp;
struct sla_addr_t scratch;
struct sla_addr_t output;
size_t output_len;
size_t scratch_len;
struct tsm_spdm spdm;
struct sla_buffer_hdr *reqbuf; /* vmap'ed @req for DOE */
struct sla_buffer_hdr *respbuf; /* vmap'ed @resp for DOE */
int cmd;
int psp_ret;
u8 cmd_data[SEV_TIO_MAX_COMMAND_LENGTH];
void *data_pg; /* Data page for DEV_STATUS/TDI_STATUS/TDI_INFO/ASID_FENCE */
#define TIO_IDE_MAX_TC 8
struct pci_ide *ide[TIO_IDE_MAX_TC];
};
/* Describes TSM structure for PF0 pointed by pci_dev->tsm */
struct tio_dsm {
struct pci_tsm_pf0 tsm;
struct tsm_dsm_tio data;
struct sev_device *sev;
};
/* Data object IDs */
#define SPDM_DOBJ_ID_NONE 0
#define SPDM_DOBJ_ID_REQ 1
#define SPDM_DOBJ_ID_RESP 2
struct spdm_dobj_hdr {
u32 id; /* Data object type identifier */
u32 length; /* Length of the data object, INCLUDING THIS HEADER */
struct { /* Version of the data object structure */
u8 minor;
u8 major;
} version;
} __packed;
/**
* struct sev_tio_status - TIO_STATUS command's info_paddr buffer
*
* @length: Length of this structure in bytes
* @tio_en: Indicates that SNP_INIT_EX initialized the RMP for SEV-TIO
* @tio_init_done: Indicates TIO_INIT has been invoked
* @spdm_req_size_min: Minimum SPDM request buffer size in bytes
* @spdm_req_size_max: Maximum SPDM request buffer size in bytes
* @spdm_scratch_size_min: Minimum SPDM scratch buffer size in bytes
* @spdm_scratch_size_max: Maximum SPDM scratch buffer size in bytes
* @spdm_out_size_min: Minimum SPDM output buffer size in bytes
* @spdm_out_size_max: Maximum for the SPDM output buffer size in bytes
* @spdm_rsp_size_min: Minimum SPDM response buffer size in bytes
* @spdm_rsp_size_max: Maximum SPDM response buffer size in bytes
* @devctx_size: Size of a device context buffer in bytes
* @tdictx_size: Size of a TDI context buffer in bytes
* @tio_crypto_alg: TIO crypto algorithms supported
*/
struct sev_tio_status {
u32 length;
u32 tio_en :1,
tio_init_done :1,
reserved :30;
u32 spdm_req_size_min;
u32 spdm_req_size_max;
u32 spdm_scratch_size_min;
u32 spdm_scratch_size_max;
u32 spdm_out_size_min;
u32 spdm_out_size_max;
u32 spdm_rsp_size_min;
u32 spdm_rsp_size_max;
u32 devctx_size;
u32 tdictx_size;
u32 tio_crypto_alg;
u8 reserved2[12];
} __packed;
int sev_tio_init_locked(void *tio_status_page);
int sev_tio_continue(struct tsm_dsm_tio *dev_data);
int sev_tio_dev_create(struct tsm_dsm_tio *dev_data, u16 device_id, u16 root_port_id,
u8 segment_id);
int sev_tio_dev_connect(struct tsm_dsm_tio *dev_data, u8 tc_mask, u8 ids[8], u8 cert_slot);
int sev_tio_dev_disconnect(struct tsm_dsm_tio *dev_data, bool force);
int sev_tio_dev_reclaim(struct tsm_dsm_tio *dev_data);
#endif /* __PSP_SEV_TIO_H__ */
@@ -0,0 +1,399 @@
// SPDX-License-Identifier: GPL-2.0-only
// Interface to CCP/SEV-TIO for generic PCIe TDISP module
#include <linux/pci.h>
#include <linux/device.h>
#include <linux/tsm.h>
#include <linux/iommu.h>
#include <linux/pci-doe.h>
#include <linux/bitfield.h>
#include <linux/module.h>
#include <asm/sev-common.h>
#include <asm/sev.h>
#include "psp-dev.h"
#include "sev-dev.h"
#include "sev-dev-tio.h"
MODULE_IMPORT_NS("PCI_IDE");
#define dev_to_sp(dev) ((struct sp_device *)dev_get_drvdata(dev))
#define dev_to_psp(dev) ((struct psp_device *)(dev_to_sp(dev)->psp_data))
#define dev_to_sev(dev) ((struct sev_device *)(dev_to_psp(dev)->sev_data))
#define tsm_dev_to_sev(tsmdev) dev_to_sev((tsmdev)->dev.parent)
#define pdev_to_tio_dsm(pdev) (container_of((pdev)->tsm, struct tio_dsm, tsm.base_tsm))
static int sev_tio_spdm_cmd(struct tio_dsm *dsm, int ret)
{
struct tsm_dsm_tio *dev_data = &dsm->data;
struct tsm_spdm *spdm = &dev_data->spdm;
/* Check the main command handler response before entering the loop */
if (ret == 0 && dev_data->psp_ret != SEV_RET_SUCCESS)
return -EINVAL;
if (ret <= 0)
return ret;
/* ret > 0 means "SPDM requested" */
while (ret == PCI_DOE_FEATURE_CMA || ret == PCI_DOE_FEATURE_SSESSION) {
ret = pci_doe(dsm->tsm.doe_mb, PCI_VENDOR_ID_PCI_SIG, ret,
spdm->req, spdm->req_len, spdm->rsp, spdm->rsp_len);
if (ret < 0)
break;
WARN_ON_ONCE(ret == 0); /* The response should never be empty */
spdm->rsp_len = ret;
ret = sev_tio_continue(dev_data);
}
return ret;
}
static int stream_enable(struct pci_ide *ide)
{
struct pci_dev *rp = pcie_find_root_port(ide->pdev);
int ret;
ret = pci_ide_stream_enable(ide->pdev, ide);
if (ret && ret != -ENXIO)
return ret;
ret = pci_ide_stream_enable(rp, ide);
if (ret && ret != -ENXIO)
pci_ide_stream_disable(ide->pdev, ide);
return ret;
}
static int streams_enable(struct pci_ide **ide)
{
int ret = 0;
for (int i = 0; i < TIO_IDE_MAX_TC; ++i) {
if (ide[i]) {
ret = stream_enable(ide[i]);
if (ret)
break;
}
}
return ret;
}
static void stream_disable(struct pci_ide *ide)
{
pci_ide_stream_disable(ide->pdev, ide);
pci_ide_stream_disable(pcie_find_root_port(ide->pdev), ide);
}
static void streams_disable(struct pci_ide **ide)
{
for (int i = 0; i < TIO_IDE_MAX_TC; ++i)
if (ide[i])
stream_disable(ide[i]);
}
static void stream_setup(struct pci_ide *ide)
{
struct pci_dev *rp = pcie_find_root_port(ide->pdev);
ide->partner[PCI_IDE_EP].rid_start = 0;
ide->partner[PCI_IDE_EP].rid_end = 0xffff;
ide->partner[PCI_IDE_RP].rid_start = 0;
ide->partner[PCI_IDE_RP].rid_end = 0xffff;
ide->pdev->ide_cfg = 0;
ide->pdev->ide_tee_limit = 1;
rp->ide_cfg = 1;
rp->ide_tee_limit = 0;
pci_warn(ide->pdev, "Forcing CFG/TEE for %s", pci_name(rp));
pci_ide_stream_setup(ide->pdev, ide);
pci_ide_stream_setup(rp, ide);
}
static u8 streams_setup(struct pci_ide **ide, u8 *ids)
{
bool def = false;
u8 tc_mask = 0;
int i;
for (i = 0; i < TIO_IDE_MAX_TC; ++i) {
if (!ide[i]) {
ids[i] = 0xFF;
continue;
}
tc_mask |= BIT(i);
ids[i] = ide[i]->stream_id;
if (!def) {
struct pci_ide_partner *settings;
settings = pci_ide_to_settings(ide[i]->pdev, ide[i]);
settings->default_stream = 1;
def = true;
}
stream_setup(ide[i]);
}
return tc_mask;
}
static int streams_register(struct pci_ide **ide)
{
int ret = 0, i;
for (i = 0; i < TIO_IDE_MAX_TC; ++i) {
if (ide[i]) {
ret = pci_ide_stream_register(ide[i]);
if (ret)
break;
}
}
return ret;
}
static void streams_unregister(struct pci_ide **ide)
{
for (int i = 0; i < TIO_IDE_MAX_TC; ++i)
if (ide[i])
pci_ide_stream_unregister(ide[i]);
}
static void stream_teardown(struct pci_ide *ide)
{
pci_ide_stream_teardown(ide->pdev, ide);
pci_ide_stream_teardown(pcie_find_root_port(ide->pdev), ide);
}
static void streams_teardown(struct pci_ide **ide)
{
for (int i = 0; i < TIO_IDE_MAX_TC; ++i) {
if (ide[i]) {
stream_teardown(ide[i]);
pci_ide_stream_free(ide[i]);
ide[i] = NULL;
}
}
}
static int stream_alloc(struct pci_dev *pdev, struct pci_ide **ide,
unsigned int tc)
{
struct pci_ide *ide1;
if (ide[tc]) {
pci_err(pdev, "Stream for class=%d already registered", tc);
return -EBUSY;
}
ide1 = pci_ide_stream_alloc(pdev);
if (!ide1)
return -EFAULT;
ide1->stream_id = ide1->host_bridge_stream;
ide[tc] = ide1;
return 0;
}
static struct pci_tsm *tio_pf0_probe(struct pci_dev *pdev, struct sev_device *sev)
{
struct tio_dsm *dsm __free(kfree) = kzalloc_obj(*dsm);
int rc;
if (!dsm)
return NULL;
rc = pci_tsm_pf0_constructor(pdev, &dsm->tsm, sev->tsmdev);
if (rc)
return NULL;
pci_dbg(pdev, "TSM enabled\n");
dsm->sev = sev;
return &no_free_ptr(dsm)->tsm.base_tsm;
}
static struct pci_tsm *dsm_probe(struct tsm_dev *tsmdev, struct pci_dev *pdev)
{
struct sev_device *sev = tsm_dev_to_sev(tsmdev);
if (is_pci_tsm_pf0(pdev))
return tio_pf0_probe(pdev, sev);
return NULL;
}
static void dsm_remove(struct pci_tsm *tsm)
{
struct pci_dev *pdev = tsm->pdev;
pci_dbg(pdev, "TSM disabled\n");
if (is_pci_tsm_pf0(pdev)) {
struct tio_dsm *dsm = container_of(tsm, struct tio_dsm, tsm.base_tsm);
pci_tsm_pf0_destructor(&dsm->tsm);
kfree(dsm);
}
}
static int dsm_create(struct tio_dsm *dsm)
{
struct pci_dev *pdev = dsm->tsm.base_tsm.pdev;
struct pci_dev *rootport;
u8 segment_id;
u16 device_id;
u16 root_port_id;
u32 lnkcap = 0;
if (!pdev->bus)
return -ENODEV;
segment_id = pci_domain_nr(pdev->bus);
rootport = pcie_find_root_port(pdev);
device_id = pci_dev_id(pdev);
if (pci_read_config_dword(rootport, pci_pcie_cap(rootport) + PCI_EXP_LNKCAP,
&lnkcap))
return -ENODEV;
root_port_id = FIELD_GET(PCI_EXP_LNKCAP_PN, lnkcap);
return sev_tio_dev_create(&dsm->data, device_id, root_port_id, segment_id);
}
static int dsm_connect(struct pci_dev *pdev)
{
struct tio_dsm *dsm = pdev_to_tio_dsm(pdev);
struct tsm_dsm_tio *dev_data = &dsm->data;
u8 ids[TIO_IDE_MAX_TC];
u8 tc_mask;
int ret;
if (pci_find_doe_mailbox(pdev, PCI_VENDOR_ID_PCI_SIG,
PCI_DOE_FEATURE_SSESSION) != dsm->tsm.doe_mb) {
pci_err(pdev, "CMA DOE MB must support SSESSION\n");
return -EFAULT;
}
ret = stream_alloc(pdev, dev_data->ide, 0);
if (ret)
return ret;
ret = dsm_create(dsm);
if (ret)
goto ide_free_exit;
tc_mask = streams_setup(dev_data->ide, ids);
ret = sev_tio_dev_connect(dev_data, tc_mask, ids, dev_data->cert_slot);
ret = sev_tio_spdm_cmd(dsm, ret);
if (ret)
goto free_exit;
streams_enable(dev_data->ide);
ret = streams_register(dev_data->ide);
if (ret)
goto free_exit;
return 0;
free_exit:
sev_tio_dev_reclaim(dev_data);
streams_disable(dev_data->ide);
ide_free_exit:
streams_teardown(dev_data->ide);
return ret;
}
static void dsm_disconnect(struct pci_dev *pdev)
{
bool force = SYSTEM_HALT <= system_state && system_state <= SYSTEM_RESTART;
struct tio_dsm *dsm = pdev_to_tio_dsm(pdev);
struct tsm_dsm_tio *dev_data = &dsm->data;
int ret;
ret = sev_tio_dev_disconnect(dev_data, force);
ret = sev_tio_spdm_cmd(dsm, ret);
if (ret && !force) {
ret = sev_tio_dev_disconnect(dev_data, true);
sev_tio_spdm_cmd(dsm, ret);
}
sev_tio_dev_reclaim(dev_data);
streams_disable(dev_data->ide);
streams_unregister(dev_data->ide);
streams_teardown(dev_data->ide);
}
static struct pci_tsm_ops sev_tsm_ops = {
.probe = dsm_probe,
.remove = dsm_remove,
.connect = dsm_connect,
.disconnect = dsm_disconnect,
};
void sev_tsm_init_locked(struct sev_device *sev, void *tio_status_page)
{
struct sev_tio_status *t = kzalloc_obj(*t);
struct tsm_dev *tsmdev;
int ret;
WARN_ON(sev->tio_status);
if (!t)
return;
ret = sev_tio_init_locked(tio_status_page);
if (ret) {
pr_warn("SEV-TIO STATUS failed with %d\n", ret);
goto error_exit;
}
tsmdev = tsm_register(sev->dev, &sev_tsm_ops);
if (IS_ERR(tsmdev))
goto error_exit;
memcpy(t, tio_status_page, sizeof(*t));
pr_notice("SEV-TIO status: EN=%d INIT_DONE=%d rq=%d..%d rs=%d..%d "
"scr=%d..%d out=%d..%d dev=%d tdi=%d algos=%x\n",
t->tio_en, t->tio_init_done,
t->spdm_req_size_min, t->spdm_req_size_max,
t->spdm_rsp_size_min, t->spdm_rsp_size_max,
t->spdm_scratch_size_min, t->spdm_scratch_size_max,
t->spdm_out_size_min, t->spdm_out_size_max,
t->devctx_size, t->tdictx_size,
t->tio_crypto_alg);
sev->tsmdev = tsmdev;
sev->tio_status = t;
return;
error_exit:
pr_err("Failed to enable SEV-TIO: ret=%d en=%d initdone=%d SEV=%d\n",
ret, t->tio_en, t->tio_init_done, boot_cpu_has(X86_FEATURE_SEV));
kfree(t);
}
void sev_tsm_uninit(struct sev_device *sev)
{
if (sev->tsmdev)
tsm_unregister(sev->tsmdev);
sev->tsmdev = NULL;
}
File diff suppressed because it is too large Load Diff
+86
View File
@@ -0,0 +1,86 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* AMD Platform Security Processor (PSP) interface driver
*
* Copyright (C) 2017-2019 Advanced Micro Devices, Inc.
*
* Author: Brijesh Singh <brijesh.singh@amd.com>
*/
#ifndef __SEV_DEV_H__
#define __SEV_DEV_H__
#include <linux/device.h>
#include <linux/spinlock.h>
#include <linux/mutex.h>
#include <linux/list.h>
#include <linux/wait.h>
#include <linux/dmapool.h>
#include <linux/hw_random.h>
#include <linux/bitops.h>
#include <linux/interrupt.h>
#include <linux/irqreturn.h>
#include <linux/dmaengine.h>
#include <linux/psp-sev.h>
#include <linux/miscdevice.h>
#include <linux/capability.h>
#define SEV_CMDRESP_CMD GENMASK(26, 16)
#define SEV_CMD_COMPLETE BIT(1)
#define SEV_CMDRESP_IOC BIT(0)
struct sev_misc_dev {
struct kref refcount;
struct miscdevice misc;
};
struct sev_tio_status;
struct sev_device {
struct device *dev;
struct psp_device *psp;
void __iomem *io_regs;
struct sev_vdata *vdata;
unsigned int int_rcvd;
wait_queue_head_t int_queue;
struct sev_misc_dev *misc;
u8 api_major;
u8 api_minor;
u8 build;
void *cmd_buf;
void *cmd_buf_backup;
bool cmd_buf_active;
bool cmd_buf_backup_active;
bool snp_initialized;
struct sev_user_data_status sev_plat_status;
struct sev_user_data_snp_status snp_plat_status;
struct snp_feature_info snp_feat_info_0;
struct tsm_dev *tsmdev;
struct sev_tio_status *tio_status;
};
int sev_dev_init(struct psp_device *psp);
void sev_dev_destroy(struct psp_device *psp);
int __sev_do_cmd_locked(int cmd, void *data, int *psp_ret);
void sev_pci_init(void);
void sev_pci_exit(void);
struct page *snp_alloc_hv_fixed_pages(unsigned int num_2mb_pages);
void snp_free_hv_fixed_pages(struct page *page);
void sev_tsm_init_locked(struct sev_device *sev, void *tio_status_page);
void sev_tsm_uninit(struct sev_device *sev);
int sev_tio_cmd_buffer_len(int cmd);
#endif /* __SEV_DEV_H */
+311
View File
@@ -0,0 +1,311 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* AMD Secure Processor Seamless Firmware Servicing support.
*
* Copyright (C) 2025 Advanced Micro Devices, Inc.
*
* Author: Ashish Kalra <ashish.kalra@amd.com>
*/
#include <linux/firmware.h>
#include "sfs.h"
#include "sev-dev.h"
#define SFS_DEFAULT_TIMEOUT (10 * MSEC_PER_SEC)
#define SFS_MAX_PAYLOAD_SIZE (2 * 1024 * 1024)
#define SFS_NUM_2MB_PAGES_CMDBUF (SFS_MAX_PAYLOAD_SIZE / PMD_SIZE)
#define SFS_NUM_PAGES_CMDBUF (SFS_MAX_PAYLOAD_SIZE / PAGE_SIZE)
static DEFINE_MUTEX(sfs_ioctl_mutex);
static struct sfs_misc_dev *misc_dev;
static int send_sfs_cmd(struct sfs_device *sfs_dev, int msg)
{
int ret;
sfs_dev->command_buf->hdr.status = 0;
sfs_dev->command_buf->hdr.sub_cmd_id = msg;
ret = psp_extended_mailbox_cmd(sfs_dev->psp,
SFS_DEFAULT_TIMEOUT,
(struct psp_ext_request *)sfs_dev->command_buf);
if (ret == -EIO) {
dev_dbg(sfs_dev->dev,
"msg 0x%x failed with PSP error: 0x%x, extended status: 0x%x\n",
msg, sfs_dev->command_buf->hdr.status,
*(u32 *)sfs_dev->command_buf->buf);
}
return ret;
}
static int send_sfs_get_fw_versions(struct sfs_device *sfs_dev)
{
/*
* SFS_GET_FW_VERSIONS command needs the output buffer to be
* initialized to 0xC7 in every byte.
*/
memset(sfs_dev->command_buf->sfs_buffer, 0xc7, PAGE_SIZE);
sfs_dev->command_buf->hdr.payload_size = 2 * PAGE_SIZE;
return send_sfs_cmd(sfs_dev, PSP_SFS_GET_FW_VERSIONS);
}
static int send_sfs_update_package(struct sfs_device *sfs_dev, const char *payload_name)
{
char payload_path[PAYLOAD_NAME_SIZE + sizeof("amd/")];
const struct firmware *firmware;
unsigned long package_size;
int ret;
/* Sanitize userspace provided payload name */
if (!strnchr(payload_name, PAYLOAD_NAME_SIZE, '\0'))
return -EINVAL;
snprintf(payload_path, sizeof(payload_path), "amd/%s", payload_name);
ret = firmware_request_nowarn(&firmware, payload_path, sfs_dev->dev);
if (ret < 0) {
dev_warn_ratelimited(sfs_dev->dev, "firmware request failed for %s (%d)\n",
payload_path, ret);
return -ENOENT;
}
/*
* SFS Update Package command's input buffer contains TEE_EXT_CMD_BUFFER
* followed by the Update Package and it should be 64KB aligned.
*/
package_size = ALIGN(firmware->size + PAGE_SIZE, 0x10000U);
/*
* SFS command buffer is a pre-allocated 2MB buffer, fail update package
* if SFS payload is larger than the pre-allocated command buffer.
*/
if (package_size > SFS_MAX_PAYLOAD_SIZE) {
dev_warn_ratelimited(sfs_dev->dev,
"SFS payload size %ld larger than maximum supported payload size of %u\n",
package_size, SFS_MAX_PAYLOAD_SIZE);
release_firmware(firmware);
return -E2BIG;
}
/*
* Copy firmware data to a HV_Fixed memory region.
*/
memcpy(sfs_dev->command_buf->sfs_buffer, firmware->data, firmware->size);
sfs_dev->command_buf->hdr.payload_size = package_size;
release_firmware(firmware);
return send_sfs_cmd(sfs_dev, PSP_SFS_UPDATE);
}
static long sfs_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
struct sfs_user_get_fw_versions __user *sfs_get_fw_versions;
struct sfs_user_update_package __user *sfs_update_package;
struct psp_device *psp_master = psp_get_master_device();
char payload_name[PAYLOAD_NAME_SIZE];
struct sfs_device *sfs_dev;
int ret = 0;
if (!psp_master || !psp_master->sfs_data)
return -ENODEV;
sfs_dev = psp_master->sfs_data;
guard(mutex)(&sfs_ioctl_mutex);
switch (cmd) {
case SFSIOCFWVERS:
dev_dbg(sfs_dev->dev, "in SFSIOCFWVERS\n");
sfs_get_fw_versions = (struct sfs_user_get_fw_versions __user *)arg;
ret = send_sfs_get_fw_versions(sfs_dev);
if (ret && ret != -EIO)
return ret;
/*
* Return SFS status and extended status back to userspace
* if PSP status indicated success or command error.
*/
if (copy_to_user(&sfs_get_fw_versions->blob, sfs_dev->command_buf->sfs_buffer,
PAGE_SIZE))
return -EFAULT;
if (copy_to_user(&sfs_get_fw_versions->sfs_status,
&sfs_dev->command_buf->hdr.status,
sizeof(sfs_get_fw_versions->sfs_status)))
return -EFAULT;
if (copy_to_user(&sfs_get_fw_versions->sfs_extended_status,
&sfs_dev->command_buf->buf,
sizeof(sfs_get_fw_versions->sfs_extended_status)))
return -EFAULT;
break;
case SFSIOCUPDATEPKG:
dev_dbg(sfs_dev->dev, "in SFSIOCUPDATEPKG\n");
sfs_update_package = (struct sfs_user_update_package __user *)arg;
if (copy_from_user(payload_name, sfs_update_package->payload_name,
PAYLOAD_NAME_SIZE))
return -EFAULT;
ret = send_sfs_update_package(sfs_dev, payload_name);
if (ret && ret != -EIO)
return ret;
/*
* Return SFS status and extended status back to userspace
* if PSP status indicated success or command error.
*/
if (copy_to_user(&sfs_update_package->sfs_status,
&sfs_dev->command_buf->hdr.status,
sizeof(sfs_update_package->sfs_status)))
return -EFAULT;
if (copy_to_user(&sfs_update_package->sfs_extended_status,
&sfs_dev->command_buf->buf,
sizeof(sfs_update_package->sfs_extended_status)))
return -EFAULT;
break;
default:
ret = -EINVAL;
}
return ret;
}
static const struct file_operations sfs_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = sfs_ioctl,
};
static void sfs_exit(struct kref *ref)
{
misc_deregister(&misc_dev->misc);
kfree(misc_dev);
misc_dev = NULL;
}
void sfs_dev_destroy(struct psp_device *psp)
{
struct sfs_device *sfs_dev = psp->sfs_data;
if (!sfs_dev)
return;
/*
* Change SFS command buffer back to the default "Write-Back" type.
*/
set_memory_wb((unsigned long)sfs_dev->command_buf, SFS_NUM_PAGES_CMDBUF);
snp_free_hv_fixed_pages(sfs_dev->page);
if (sfs_dev->misc)
kref_put(&misc_dev->refcount, sfs_exit);
psp->sfs_data = NULL;
}
/* Based on sev_misc_init() */
static int sfs_misc_init(struct sfs_device *sfs)
{
struct device *dev = sfs->dev;
int ret;
/*
* SFS feature support can be detected on multiple devices but the SFS
* FW commands must be issued on the master. During probe, we do not
* know the master hence we create /dev/sfs on the first device probe.
*/
if (!misc_dev) {
struct miscdevice *misc;
misc_dev = kzalloc_obj(*misc_dev);
if (!misc_dev)
return -ENOMEM;
misc = &misc_dev->misc;
misc->minor = MISC_DYNAMIC_MINOR;
misc->name = "sfs";
misc->fops = &sfs_fops;
misc->mode = 0600;
ret = misc_register(misc);
if (ret)
return ret;
kref_init(&misc_dev->refcount);
} else {
kref_get(&misc_dev->refcount);
}
sfs->misc = misc_dev;
dev_dbg(dev, "registered SFS device\n");
return 0;
}
int sfs_dev_init(struct psp_device *psp)
{
struct device *dev = psp->dev;
struct sfs_device *sfs_dev;
struct page *page;
int ret = -ENOMEM;
sfs_dev = devm_kzalloc(dev, sizeof(*sfs_dev), GFP_KERNEL);
if (!sfs_dev)
return -ENOMEM;
/*
* Pre-allocate 2MB command buffer for all SFS commands using
* SNP HV_Fixed page allocator which also transitions the
* SFS command buffer to HV_Fixed page state if SNP is enabled.
*/
page = snp_alloc_hv_fixed_pages(SFS_NUM_2MB_PAGES_CMDBUF);
if (!page) {
dev_dbg(dev, "Command Buffer HV-Fixed page allocation failed\n");
goto cleanup_dev;
}
sfs_dev->page = page;
sfs_dev->command_buf = page_address(page);
dev_dbg(dev, "Command buffer 0x%px to be marked as HV_Fixed\n", sfs_dev->command_buf);
/*
* SFS command buffer must be mapped as non-cacheable.
*/
ret = set_memory_uc((unsigned long)sfs_dev->command_buf, SFS_NUM_PAGES_CMDBUF);
if (ret) {
dev_dbg(dev, "Set memory uc failed\n");
goto cleanup_cmd_buf;
}
dev_dbg(dev, "Command buffer 0x%px marked uncacheable\n", sfs_dev->command_buf);
psp->sfs_data = sfs_dev;
sfs_dev->dev = dev;
sfs_dev->psp = psp;
ret = sfs_misc_init(sfs_dev);
if (ret)
goto cleanup_mem_attr;
dev_notice(sfs_dev->dev, "SFS support is available\n");
return 0;
cleanup_mem_attr:
set_memory_wb((unsigned long)sfs_dev->command_buf, SFS_NUM_PAGES_CMDBUF);
cleanup_cmd_buf:
snp_free_hv_fixed_pages(page);
cleanup_dev:
psp->sfs_data = NULL;
devm_kfree(dev, sfs_dev);
return ret;
}
+47
View File
@@ -0,0 +1,47 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* AMD Platform Security Processor (PSP) Seamless Firmware (SFS) Support.
*
* Copyright (C) 2025 Advanced Micro Devices, Inc.
*
* Author: Ashish Kalra <ashish.kalra@amd.com>
*/
#ifndef __SFS_H__
#define __SFS_H__
#include <uapi/linux/psp-sfs.h>
#include <linux/device.h>
#include <linux/miscdevice.h>
#include <linux/psp-sev.h>
#include <linux/psp-platform-access.h>
#include <linux/set_memory.h>
#include "psp-dev.h"
struct sfs_misc_dev {
struct kref refcount;
struct miscdevice misc;
};
struct sfs_command {
struct psp_ext_req_buffer_hdr hdr;
u8 buf[PAGE_SIZE - sizeof(struct psp_ext_req_buffer_hdr)];
u8 sfs_buffer[];
} __packed;
struct sfs_device {
struct device *dev;
struct psp_device *psp;
struct page *page;
struct sfs_command *command_buf;
struct sfs_misc_dev *misc;
};
void sfs_dev_destroy(struct psp_device *psp);
int sfs_dev_init(struct psp_device *psp);
#endif /* __SFS_H__ */
+325
View File
@@ -0,0 +1,325 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* AMD Secure Processor driver
*
* Copyright (C) 2017-2018 Advanced Micro Devices, Inc.
*
* Author: Tom Lendacky <thomas.lendacky@amd.com>
* Author: Gary R Hook <gary.hook@amd.com>
* Author: Brijesh Singh <brijesh.singh@amd.com>
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/kthread.h>
#include <linux/sched.h>
#include <linux/interrupt.h>
#include <linux/spinlock.h>
#include <linux/spinlock_types.h>
#include <linux/types.h>
#include <linux/ccp.h>
#include "sev-dev.h"
#include "ccp-dev.h"
#include "sp-dev.h"
MODULE_AUTHOR("Tom Lendacky <thomas.lendacky@amd.com>");
MODULE_AUTHOR("Gary R Hook <gary.hook@amd.com>");
MODULE_LICENSE("GPL");
MODULE_VERSION("1.1.0");
MODULE_DESCRIPTION("AMD Secure Processor driver");
/* List of SPs, SP count, read-write access lock, and access functions
*
* Lock structure: get sp_unit_lock for reading whenever we need to
* examine the SP list.
*/
static DEFINE_RWLOCK(sp_unit_lock);
static LIST_HEAD(sp_units);
/* Ever-increasing value to produce unique unit numbers */
static atomic_t sp_ordinal;
static void sp_add_device(struct sp_device *sp)
{
unsigned long flags;
write_lock_irqsave(&sp_unit_lock, flags);
list_add_tail(&sp->entry, &sp_units);
write_unlock_irqrestore(&sp_unit_lock, flags);
}
static void sp_del_device(struct sp_device *sp)
{
unsigned long flags;
write_lock_irqsave(&sp_unit_lock, flags);
list_del(&sp->entry);
write_unlock_irqrestore(&sp_unit_lock, flags);
}
static irqreturn_t sp_irq_handler(int irq, void *data)
{
struct sp_device *sp = data;
if (sp->ccp_irq_handler)
sp->ccp_irq_handler(irq, sp->ccp_irq_data);
if (sp->psp_irq_handler)
sp->psp_irq_handler(irq, sp->psp_irq_data);
return IRQ_HANDLED;
}
int sp_request_ccp_irq(struct sp_device *sp, irq_handler_t handler,
const char *name, void *data)
{
int ret;
if ((sp->psp_irq == sp->ccp_irq) && sp->dev_vdata->psp_vdata) {
/* Need a common routine to manage all interrupts */
sp->ccp_irq_data = data;
sp->ccp_irq_handler = handler;
if (!sp->irq_registered) {
ret = request_irq(sp->ccp_irq, sp_irq_handler, 0,
sp->name, sp);
if (ret)
return ret;
sp->irq_registered = true;
}
} else {
/* Each sub-device can manage it's own interrupt */
ret = request_irq(sp->ccp_irq, handler, 0, name, data);
if (ret)
return ret;
}
return 0;
}
int sp_request_psp_irq(struct sp_device *sp, irq_handler_t handler,
const char *name, void *data)
{
int ret;
if ((sp->psp_irq == sp->ccp_irq) && sp->dev_vdata->ccp_vdata) {
/* Need a common routine to manage all interrupts */
sp->psp_irq_data = data;
sp->psp_irq_handler = handler;
if (!sp->irq_registered) {
ret = request_irq(sp->psp_irq, sp_irq_handler, 0,
sp->name, sp);
if (ret)
return ret;
sp->irq_registered = true;
}
} else {
/* Each sub-device can manage it's own interrupt */
ret = request_irq(sp->psp_irq, handler, 0, name, data);
if (ret)
return ret;
}
return 0;
}
void sp_free_ccp_irq(struct sp_device *sp, void *data)
{
if ((sp->psp_irq == sp->ccp_irq) && sp->dev_vdata->psp_vdata) {
/* Using common routine to manage all interrupts */
if (!sp->psp_irq_handler) {
/* Nothing else using it, so free it */
free_irq(sp->ccp_irq, sp);
sp->irq_registered = false;
}
sp->ccp_irq_handler = NULL;
sp->ccp_irq_data = NULL;
} else {
/* Each sub-device can manage it's own interrupt */
free_irq(sp->ccp_irq, data);
}
}
void sp_free_psp_irq(struct sp_device *sp, void *data)
{
if ((sp->psp_irq == sp->ccp_irq) && sp->dev_vdata->ccp_vdata) {
/* Using common routine to manage all interrupts */
if (!sp->ccp_irq_handler) {
/* Nothing else using it, so free it */
free_irq(sp->psp_irq, sp);
sp->irq_registered = false;
}
sp->psp_irq_handler = NULL;
sp->psp_irq_data = NULL;
} else {
/* Each sub-device can manage it's own interrupt */
free_irq(sp->psp_irq, data);
}
}
/**
* sp_alloc_struct - allocate and initialize the sp_device struct
*
* @dev: device struct of the SP
*/
struct sp_device *sp_alloc_struct(struct device *dev)
{
struct sp_device *sp;
sp = devm_kzalloc(dev, sizeof(*sp), GFP_KERNEL);
if (!sp)
return NULL;
sp->dev = dev;
sp->ord = atomic_inc_return(&sp_ordinal);
snprintf(sp->name, SP_MAX_NAME_LEN, "sp-%u", sp->ord);
return sp;
}
int sp_init(struct sp_device *sp)
{
sp_add_device(sp);
if (sp->dev_vdata->ccp_vdata)
ccp_dev_init(sp);
if (sp->dev_vdata->psp_vdata)
psp_dev_init(sp);
return 0;
}
void sp_destroy(struct sp_device *sp)
{
if (sp->dev_vdata->ccp_vdata)
ccp_dev_destroy(sp);
if (sp->dev_vdata->psp_vdata)
psp_dev_destroy(sp);
sp_del_device(sp);
}
int sp_suspend(struct sp_device *sp)
{
if (sp->dev_vdata->ccp_vdata) {
ccp_dev_suspend(sp);
}
return 0;
}
int sp_resume(struct sp_device *sp)
{
if (sp->dev_vdata->ccp_vdata) {
ccp_dev_resume(sp);
}
return 0;
}
int sp_restore(struct sp_device *sp)
{
if (sp->psp_data) {
int ret = psp_restore(sp);
if (ret)
return ret;
}
return sp_resume(sp);
}
struct sp_device *sp_get_psp_master_device(void)
{
struct sp_device *i, *ret = NULL;
unsigned long flags;
write_lock_irqsave(&sp_unit_lock, flags);
if (list_empty(&sp_units))
goto unlock;
list_for_each_entry(i, &sp_units, entry) {
if (i->psp_data && i->get_psp_master_device) {
ret = i->get_psp_master_device();
break;
}
}
unlock:
write_unlock_irqrestore(&sp_unit_lock, flags);
return ret;
}
static int __init sp_mod_init(void)
{
#ifdef CONFIG_X86
static bool initialized;
int ret;
if (initialized)
return 0;
ret = sp_pci_init();
if (ret)
return ret;
#ifdef CONFIG_CRYPTO_DEV_SP_PSP
psp_pci_init();
#endif
initialized = true;
return 0;
#endif
#ifdef CONFIG_ARM64
int ret;
ret = sp_platform_init();
if (ret)
return ret;
return 0;
#endif
return -ENODEV;
}
#if IS_BUILTIN(CONFIG_KVM_AMD) && IS_ENABLED(CONFIG_KVM_AMD_SEV)
int __init sev_module_init(void)
{
return sp_mod_init();
}
#endif
static void __exit sp_mod_exit(void)
{
#ifdef CONFIG_X86
#ifdef CONFIG_CRYPTO_DEV_SP_PSP
psp_pci_exit();
#endif
sp_pci_exit();
#endif
#ifdef CONFIG_ARM64
sp_platform_exit();
#endif
}
module_init(sp_mod_init);
module_exit(sp_mod_exit);
+190
View File
@@ -0,0 +1,190 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* AMD Secure Processor driver
*
* Copyright (C) 2017-2019 Advanced Micro Devices, Inc.
*
* Author: Tom Lendacky <thomas.lendacky@amd.com>
* Author: Gary R Hook <gary.hook@amd.com>
* Author: Brijesh Singh <brijesh.singh@amd.com>
*/
#ifndef __SP_DEV_H__
#define __SP_DEV_H__
#include <linux/device.h>
#include <linux/spinlock.h>
#include <linux/mutex.h>
#include <linux/list.h>
#include <linux/wait.h>
#include <linux/dmapool.h>
#include <linux/hw_random.h>
#include <linux/bitops.h>
#include <linux/interrupt.h>
#include <linux/irqreturn.h>
#define SP_MAX_NAME_LEN 32
#define CACHE_NONE 0x00
#define CACHE_WB_NO_ALLOC 0xb7
#define PLATFORM_FEATURE_DBC 0x1
#define PLATFORM_FEATURE_HSTI 0x2
#define PSP_FEATURE(psp, feat) (psp->vdata && psp->vdata->platform_features & PLATFORM_FEATURE_##feat)
/* Structure to hold CCP device data */
struct ccp_device;
struct ccp_vdata {
const unsigned int version;
const unsigned int dma_chan_attr;
void (*setup)(struct ccp_device *);
const struct ccp_actions *perform;
const unsigned int offset;
const unsigned int rsamax;
};
struct sev_vdata {
const unsigned int cmdresp_reg;
const unsigned int cmdbuff_addr_lo_reg;
const unsigned int cmdbuff_addr_hi_reg;
};
struct tee_vdata {
const unsigned int cmdresp_reg;
const unsigned int cmdbuff_addr_lo_reg;
const unsigned int cmdbuff_addr_hi_reg;
const unsigned int ring_wptr_reg;
const unsigned int ring_rptr_reg;
const unsigned int info_reg;
};
struct platform_access_vdata {
const unsigned int cmdresp_reg;
const unsigned int cmdbuff_addr_lo_reg;
const unsigned int cmdbuff_addr_hi_reg;
const unsigned int doorbell_button_reg;
const unsigned int doorbell_cmd_reg;
};
struct psp_vdata {
const struct sev_vdata *sev;
const struct tee_vdata *tee;
const struct platform_access_vdata *platform_access;
const unsigned int cmdresp_reg;
const unsigned int cmdbuff_addr_lo_reg;
const unsigned int cmdbuff_addr_hi_reg;
const unsigned int feature_reg;
const unsigned int inten_reg;
const unsigned int intsts_reg;
const unsigned int bootloader_info_reg;
const unsigned int platform_features;
};
/* Structure to hold SP device data */
struct sp_dev_vdata {
const unsigned int bar;
const struct ccp_vdata *ccp_vdata;
const struct psp_vdata *psp_vdata;
};
struct sp_device {
struct list_head entry;
struct device *dev;
const struct sp_dev_vdata *dev_vdata;
unsigned int ord;
char name[SP_MAX_NAME_LEN];
/* Bus specific device information */
void *dev_specific;
/* I/O area used for device communication. */
void __iomem *io_map;
/* DMA caching attribute support */
unsigned int axcache;
/* get and set master device */
struct sp_device*(*get_psp_master_device)(void);
void (*set_psp_master_device)(struct sp_device *);
void (*clear_psp_master_device)(struct sp_device *);
bool irq_registered;
bool use_tasklet;
unsigned int ccp_irq;
irq_handler_t ccp_irq_handler;
void *ccp_irq_data;
unsigned int psp_irq;
irq_handler_t psp_irq_handler;
void *psp_irq_data;
void *ccp_data;
void *psp_data;
};
int sp_pci_init(void);
void sp_pci_exit(void);
int sp_platform_init(void);
void sp_platform_exit(void);
struct sp_device *sp_alloc_struct(struct device *dev);
int sp_init(struct sp_device *sp);
void sp_destroy(struct sp_device *sp);
int sp_suspend(struct sp_device *sp);
int sp_resume(struct sp_device *sp);
int sp_restore(struct sp_device *sp);
int sp_request_ccp_irq(struct sp_device *sp, irq_handler_t handler,
const char *name, void *data);
void sp_free_ccp_irq(struct sp_device *sp, void *data);
int sp_request_psp_irq(struct sp_device *sp, irq_handler_t handler,
const char *name, void *data);
void sp_free_psp_irq(struct sp_device *sp, void *data);
struct sp_device *sp_get_psp_master_device(void);
#ifdef CONFIG_CRYPTO_DEV_SP_CCP
int ccp_dev_init(struct sp_device *sp);
void ccp_dev_destroy(struct sp_device *sp);
void ccp_dev_suspend(struct sp_device *sp);
void ccp_dev_resume(struct sp_device *sp);
#else /* !CONFIG_CRYPTO_DEV_SP_CCP */
static inline int ccp_dev_init(struct sp_device *sp)
{
return 0;
}
static inline void ccp_dev_destroy(struct sp_device *sp) { }
static inline void ccp_dev_suspend(struct sp_device *sp) { }
static inline void ccp_dev_resume(struct sp_device *sp) { }
#endif /* CONFIG_CRYPTO_DEV_SP_CCP */
#ifdef CONFIG_CRYPTO_DEV_SP_PSP
int psp_dev_init(struct sp_device *sp);
void psp_pci_init(void);
void psp_dev_destroy(struct sp_device *sp);
void psp_pci_exit(void);
int psp_restore(struct sp_device *sp);
#else /* !CONFIG_CRYPTO_DEV_SP_PSP */
static inline int psp_dev_init(struct sp_device *sp) { return 0; }
static inline void psp_pci_init(void) { }
static inline void psp_dev_destroy(struct sp_device *sp) { }
static inline void psp_pci_exit(void) { }
static inline int psp_restore(struct sp_device *sp) { return 0; }
#endif /* CONFIG_CRYPTO_DEV_SP_PSP */
#endif
+600
View File
@@ -0,0 +1,600 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* AMD Secure Processor device driver
*
* Copyright (C) 2013,2019 Advanced Micro Devices, Inc.
*
* Author: Tom Lendacky <thomas.lendacky@amd.com>
* Author: Gary R Hook <gary.hook@amd.com>
*/
#include <linux/bitfield.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/pci.h>
#include <linux/pci_ids.h>
#include <linux/dma-mapping.h>
#include <linux/kthread.h>
#include <linux/sched.h>
#include <linux/interrupt.h>
#include <linux/spinlock.h>
#include <linux/delay.h>
#include <linux/ccp.h>
#include "ccp-dev.h"
#include "psp-dev.h"
#include "hsti.h"
/* used for version string AA.BB.CC.DD */
#define AA GENMASK(31, 24)
#define BB GENMASK(23, 16)
#define CC GENMASK(15, 8)
#define DD GENMASK(7, 0)
#define MSIX_VECTORS 2
struct sp_pci {
int msix_count;
struct msix_entry msix_entry[MSIX_VECTORS];
};
static struct sp_device *sp_dev_master;
#define version_attribute_show(name, _offset) \
static ssize_t name##_show(struct device *d, struct device_attribute *attr, \
char *buf) \
{ \
struct sp_device *sp = dev_get_drvdata(d); \
struct psp_device *psp = sp->psp_data; \
unsigned int val = ioread32(psp->io_regs + _offset); \
return sysfs_emit(buf, "%02lx.%02lx.%02lx.%02lx\n", \
FIELD_GET(AA, val), \
FIELD_GET(BB, val), \
FIELD_GET(CC, val), \
FIELD_GET(DD, val)); \
}
version_attribute_show(bootloader_version, psp->vdata->bootloader_info_reg)
static DEVICE_ATTR_RO(bootloader_version);
version_attribute_show(tee_version, psp->vdata->tee->info_reg)
static DEVICE_ATTR_RO(tee_version);
static struct attribute *psp_firmware_attrs[] = {
&dev_attr_bootloader_version.attr,
&dev_attr_tee_version.attr,
NULL,
};
static umode_t psp_firmware_is_visible(struct kobject *kobj, struct attribute *attr, int idx)
{
struct device *dev = kobj_to_dev(kobj);
struct sp_device *sp = dev_get_drvdata(dev);
struct psp_device *psp = sp->psp_data;
unsigned int val = 0xffffffff;
if (!psp)
return 0;
if (attr == &dev_attr_bootloader_version.attr &&
psp->vdata->bootloader_info_reg)
val = ioread32(psp->io_regs + psp->vdata->bootloader_info_reg);
if (attr == &dev_attr_tee_version.attr && psp->capability.tee &&
psp->vdata->tee->info_reg)
val = ioread32(psp->io_regs + psp->vdata->tee->info_reg);
/* If platform disallows accessing this register it will be all f's */
if (val != 0xffffffff)
return 0444;
return 0;
}
static struct attribute_group psp_firmware_attr_group = {
.attrs = psp_firmware_attrs,
.is_visible = psp_firmware_is_visible,
};
static const struct attribute_group *psp_groups[] = {
#ifdef CONFIG_CRYPTO_DEV_SP_PSP
&psp_security_attr_group,
#endif
&psp_firmware_attr_group,
NULL,
};
static int sp_get_msix_irqs(struct sp_device *sp)
{
struct sp_pci *sp_pci = sp->dev_specific;
struct device *dev = sp->dev;
struct pci_dev *pdev = to_pci_dev(dev);
int v, ret;
for (v = 0; v < ARRAY_SIZE(sp_pci->msix_entry); v++)
sp_pci->msix_entry[v].entry = v;
ret = pci_enable_msix_range(pdev, sp_pci->msix_entry, 1, v);
if (ret < 0)
return ret;
sp_pci->msix_count = ret;
sp->use_tasklet = true;
sp->psp_irq = sp_pci->msix_entry[0].vector;
sp->ccp_irq = (sp_pci->msix_count > 1) ? sp_pci->msix_entry[1].vector
: sp_pci->msix_entry[0].vector;
return 0;
}
static int sp_get_msi_irq(struct sp_device *sp)
{
struct device *dev = sp->dev;
struct pci_dev *pdev = to_pci_dev(dev);
int ret;
ret = pci_enable_msi(pdev);
if (ret)
return ret;
sp->ccp_irq = pdev->irq;
sp->psp_irq = pdev->irq;
return 0;
}
static int sp_get_irqs(struct sp_device *sp)
{
struct device *dev = sp->dev;
int ret;
ret = sp_get_msix_irqs(sp);
if (!ret)
return 0;
/* Couldn't get MSI-X vectors, try MSI */
dev_notice(dev, "could not enable MSI-X (%d), trying MSI\n", ret);
ret = sp_get_msi_irq(sp);
if (!ret)
return 0;
/* Couldn't get MSI interrupt */
dev_notice(dev, "could not enable MSI (%d)\n", ret);
return ret;
}
static void sp_free_irqs(struct sp_device *sp)
{
struct sp_pci *sp_pci = sp->dev_specific;
struct device *dev = sp->dev;
struct pci_dev *pdev = to_pci_dev(dev);
if (sp_pci->msix_count)
pci_disable_msix(pdev);
else if (sp->psp_irq)
pci_disable_msi(pdev);
sp->ccp_irq = 0;
sp->psp_irq = 0;
}
static bool sp_pci_is_master(struct sp_device *sp)
{
struct device *dev_cur, *dev_new;
struct pci_dev *pdev_cur, *pdev_new;
dev_new = sp->dev;
dev_cur = sp_dev_master->dev;
pdev_new = to_pci_dev(dev_new);
pdev_cur = to_pci_dev(dev_cur);
if (pci_domain_nr(pdev_new->bus) != pci_domain_nr(pdev_cur->bus))
return pci_domain_nr(pdev_new->bus) < pci_domain_nr(pdev_cur->bus);
if (pdev_new->bus->number != pdev_cur->bus->number)
return pdev_new->bus->number < pdev_cur->bus->number;
if (PCI_SLOT(pdev_new->devfn) != PCI_SLOT(pdev_cur->devfn))
return PCI_SLOT(pdev_new->devfn) < PCI_SLOT(pdev_cur->devfn);
if (PCI_FUNC(pdev_new->devfn) != PCI_FUNC(pdev_cur->devfn))
return PCI_FUNC(pdev_new->devfn) < PCI_FUNC(pdev_cur->devfn);
return false;
}
static void psp_set_master(struct sp_device *sp)
{
if (!sp_dev_master) {
sp_dev_master = sp;
return;
}
if (sp_pci_is_master(sp))
sp_dev_master = sp;
}
static struct sp_device *psp_get_master(void)
{
return sp_dev_master;
}
static void psp_clear_master(struct sp_device *sp)
{
if (sp == sp_dev_master) {
sp_dev_master = NULL;
dev_dbg(sp->dev, "Cleared sp_dev_master\n");
}
}
static int sp_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
struct sp_device *sp;
struct sp_pci *sp_pci;
struct device *dev = &pdev->dev;
void __iomem * const *iomap_table;
int bar_mask;
int ret;
ret = -ENOMEM;
sp = sp_alloc_struct(dev);
if (!sp)
goto e_err;
sp_pci = devm_kzalloc(dev, sizeof(*sp_pci), GFP_KERNEL);
if (!sp_pci)
goto e_err;
sp->dev_specific = sp_pci;
sp->dev_vdata = (struct sp_dev_vdata *)id->driver_data;
if (!sp->dev_vdata) {
ret = -ENODEV;
dev_err(dev, "missing driver data\n");
goto e_err;
}
ret = pcim_enable_device(pdev);
if (ret) {
dev_err(dev, "pcim_enable_device failed (%d)\n", ret);
goto e_err;
}
bar_mask = pci_select_bars(pdev, IORESOURCE_MEM);
ret = pcim_iomap_regions(pdev, bar_mask, "ccp");
if (ret) {
dev_err(dev, "pcim_iomap_regions failed (%d)\n", ret);
goto e_err;
}
iomap_table = pcim_iomap_table(pdev);
if (!iomap_table) {
dev_err(dev, "pcim_iomap_table failed\n");
ret = -ENOMEM;
goto e_err;
}
sp->io_map = iomap_table[sp->dev_vdata->bar];
if (!sp->io_map) {
dev_err(dev, "ioremap failed\n");
ret = -ENOMEM;
goto e_err;
}
ret = sp_get_irqs(sp);
if (ret)
goto e_err;
pci_set_master(pdev);
sp->set_psp_master_device = psp_set_master;
sp->get_psp_master_device = psp_get_master;
sp->clear_psp_master_device = psp_clear_master;
ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(48));
if (ret) {
ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32));
if (ret) {
dev_err(dev, "dma_set_mask_and_coherent failed (%d)\n",
ret);
goto free_irqs;
}
}
dev_set_drvdata(dev, sp);
ret = sp_init(sp);
if (ret)
goto free_irqs;
return 0;
free_irqs:
sp_free_irqs(sp);
e_err:
dev_notice(dev, "initialization failed\n");
return ret;
}
static void sp_pci_shutdown(struct pci_dev *pdev)
{
struct device *dev = &pdev->dev;
struct sp_device *sp = dev_get_drvdata(dev);
if (!sp)
return;
sp_destroy(sp);
}
static void sp_pci_remove(struct pci_dev *pdev)
{
struct device *dev = &pdev->dev;
struct sp_device *sp = dev_get_drvdata(dev);
if (!sp)
return;
sp_destroy(sp);
sp_free_irqs(sp);
}
static int __maybe_unused sp_pci_suspend(struct device *dev)
{
struct sp_device *sp = dev_get_drvdata(dev);
return sp_suspend(sp);
}
static int __maybe_unused sp_pci_resume(struct device *dev)
{
struct sp_device *sp = dev_get_drvdata(dev);
return sp_resume(sp);
}
static int __maybe_unused sp_pci_restore(struct device *dev)
{
struct sp_device *sp = dev_get_drvdata(dev);
return sp_restore(sp);
}
#ifdef CONFIG_CRYPTO_DEV_SP_PSP
static const struct sev_vdata sevv1 = {
.cmdresp_reg = 0x10580, /* C2PMSG_32 */
.cmdbuff_addr_lo_reg = 0x105e0, /* C2PMSG_56 */
.cmdbuff_addr_hi_reg = 0x105e4, /* C2PMSG_57 */
};
static const struct sev_vdata sevv2 = {
.cmdresp_reg = 0x10980, /* C2PMSG_32 */
.cmdbuff_addr_lo_reg = 0x109e0, /* C2PMSG_56 */
.cmdbuff_addr_hi_reg = 0x109e4, /* C2PMSG_57 */
};
static const struct tee_vdata teev1 = {
.ring_wptr_reg = 0x10550, /* C2PMSG_20 */
.ring_rptr_reg = 0x10554, /* C2PMSG_21 */
.info_reg = 0x109e8, /* C2PMSG_58 */
};
static const struct tee_vdata teev2 = {
.ring_wptr_reg = 0x10950, /* C2PMSG_20 */
.ring_rptr_reg = 0x10954, /* C2PMSG_21 */
.info_reg = 0x109e8, /* C2PMSG_58 */
};
static const struct platform_access_vdata pa_v1 = {
.cmdresp_reg = 0x10570, /* C2PMSG_28 */
.cmdbuff_addr_lo_reg = 0x10574, /* C2PMSG_29 */
.cmdbuff_addr_hi_reg = 0x10578, /* C2PMSG_30 */
.doorbell_button_reg = 0x10a24, /* C2PMSG_73 */
.doorbell_cmd_reg = 0x10a40, /* C2PMSG_80 */
};
static const struct platform_access_vdata pa_v2 = {
.doorbell_button_reg = 0x10a24, /* C2PMSG_73 */
.doorbell_cmd_reg = 0x10a40, /* C2PMSG_80 */
};
static const struct psp_vdata pspv1 = {
.sev = &sevv1,
.bootloader_info_reg = 0x105ec, /* C2PMSG_59 */
.feature_reg = 0x105fc, /* C2PMSG_63 */
.inten_reg = 0x10610, /* P2CMSG_INTEN */
.intsts_reg = 0x10614, /* P2CMSG_INTSTS */
};
static const struct psp_vdata pspv2 = {
.sev = &sevv2,
.platform_access = &pa_v1,
.bootloader_info_reg = 0x109ec, /* C2PMSG_59 */
.feature_reg = 0x109fc, /* C2PMSG_63 */
.inten_reg = 0x10690, /* P2CMSG_INTEN */
.intsts_reg = 0x10694, /* P2CMSG_INTSTS */
.platform_features = PLATFORM_FEATURE_HSTI,
};
static const struct psp_vdata pspv3 = {
.tee = &teev1,
.platform_access = &pa_v1,
.cmdresp_reg = 0x10544, /* C2PMSG_17 */
.cmdbuff_addr_lo_reg = 0x10548, /* C2PMSG_18 */
.cmdbuff_addr_hi_reg = 0x1054c, /* C2PMSG_19 */
.bootloader_info_reg = 0x109ec, /* C2PMSG_59 */
.feature_reg = 0x109fc, /* C2PMSG_63 */
.inten_reg = 0x10690, /* P2CMSG_INTEN */
.intsts_reg = 0x10694, /* P2CMSG_INTSTS */
.platform_features = PLATFORM_FEATURE_DBC |
PLATFORM_FEATURE_HSTI,
};
static const struct psp_vdata pspv4 = {
.sev = &sevv2,
.tee = &teev1,
.cmdresp_reg = 0x10544, /* C2PMSG_17 */
.cmdbuff_addr_lo_reg = 0x10548, /* C2PMSG_18 */
.cmdbuff_addr_hi_reg = 0x1054c, /* C2PMSG_19 */
.bootloader_info_reg = 0x109ec, /* C2PMSG_59 */
.feature_reg = 0x109fc, /* C2PMSG_63 */
.inten_reg = 0x10690, /* P2CMSG_INTEN */
.intsts_reg = 0x10694, /* P2CMSG_INTSTS */
};
static const struct psp_vdata pspv5 = {
.tee = &teev2,
.platform_access = &pa_v2,
.cmdresp_reg = 0x10944, /* C2PMSG_17 */
.cmdbuff_addr_lo_reg = 0x10948, /* C2PMSG_18 */
.cmdbuff_addr_hi_reg = 0x1094c, /* C2PMSG_19 */
.bootloader_info_reg = 0x109ec, /* C2PMSG_59 */
.feature_reg = 0x109fc, /* C2PMSG_63 */
.inten_reg = 0x10510, /* P2CMSG_INTEN */
.intsts_reg = 0x10514, /* P2CMSG_INTSTS */
};
static const struct psp_vdata pspv6 = {
.sev = &sevv2,
.tee = &teev2,
.cmdresp_reg = 0x10944, /* C2PMSG_17 */
.cmdbuff_addr_lo_reg = 0x10948, /* C2PMSG_18 */
.cmdbuff_addr_hi_reg = 0x1094c, /* C2PMSG_19 */
.bootloader_info_reg = 0x109ec, /* C2PMSG_59 */
.feature_reg = 0x109fc, /* C2PMSG_63 */
.inten_reg = 0x10510, /* P2CMSG_INTEN */
.intsts_reg = 0x10514, /* P2CMSG_INTSTS */
};
static const struct psp_vdata pspv7 = {
.tee = &teev2,
.cmdresp_reg = 0x10944, /* C2PMSG_17 */
.cmdbuff_addr_lo_reg = 0x10948, /* C2PMSG_18 */
.cmdbuff_addr_hi_reg = 0x1094c, /* C2PMSG_19 */
.bootloader_info_reg = 0x109ec, /* C2PMSG_59 */
.feature_reg = 0x109fc, /* C2PMSG_63 */
.inten_reg = 0x10510, /* P2CMSG_INTEN */
.intsts_reg = 0x10514, /* P2CMSG_INTSTS */
};
#endif
static const struct sp_dev_vdata dev_vdata[] = {
{ /* 0 */
.bar = 2,
#ifdef CONFIG_CRYPTO_DEV_SP_CCP
.ccp_vdata = &ccpv3,
#endif
},
{ /* 1 */
.bar = 2,
#ifdef CONFIG_CRYPTO_DEV_SP_CCP
.ccp_vdata = &ccpv5a,
#endif
#ifdef CONFIG_CRYPTO_DEV_SP_PSP
.psp_vdata = &pspv1,
#endif
},
{ /* 2 */
.bar = 2,
#ifdef CONFIG_CRYPTO_DEV_SP_CCP
.ccp_vdata = &ccpv5b,
#endif
},
{ /* 3 */
.bar = 2,
#ifdef CONFIG_CRYPTO_DEV_SP_CCP
.ccp_vdata = &ccpv5a,
#endif
#ifdef CONFIG_CRYPTO_DEV_SP_PSP
.psp_vdata = &pspv2,
#endif
},
{ /* 4 */
.bar = 2,
#ifdef CONFIG_CRYPTO_DEV_SP_CCP
.ccp_vdata = &ccpv5a,
#endif
#ifdef CONFIG_CRYPTO_DEV_SP_PSP
.psp_vdata = &pspv3,
#endif
},
{ /* 5 */
.bar = 2,
#ifdef CONFIG_CRYPTO_DEV_SP_PSP
.psp_vdata = &pspv4,
#endif
},
{ /* 6 */
.bar = 2,
#ifdef CONFIG_CRYPTO_DEV_SP_PSP
.psp_vdata = &pspv3,
#endif
},
{ /* 7 */
.bar = 2,
#ifdef CONFIG_CRYPTO_DEV_SP_PSP
.psp_vdata = &pspv5,
#endif
},
{ /* 8 */
.bar = 2,
#ifdef CONFIG_CRYPTO_DEV_SP_PSP
.psp_vdata = &pspv6,
#endif
},
{ /* 9 */
.bar = 2,
#ifdef CONFIG_CRYPTO_DEV_SP_PSP
.psp_vdata = &pspv7,
#endif
},
};
static const struct pci_device_id sp_pci_table[] = {
{ PCI_VDEVICE(AMD, 0x1537), .driver_data = (kernel_ulong_t)&dev_vdata[0] },
{ PCI_VDEVICE(AMD, 0x1456), .driver_data = (kernel_ulong_t)&dev_vdata[1] },
{ PCI_VDEVICE(AMD, 0x1468), .driver_data = (kernel_ulong_t)&dev_vdata[2] },
{ PCI_VDEVICE(AMD, 0x1486), .driver_data = (kernel_ulong_t)&dev_vdata[3] },
{ PCI_VDEVICE(AMD, 0x15DF), .driver_data = (kernel_ulong_t)&dev_vdata[4] },
{ PCI_VDEVICE(AMD, 0x14CA), .driver_data = (kernel_ulong_t)&dev_vdata[5] },
{ PCI_VDEVICE(AMD, 0x15C7), .driver_data = (kernel_ulong_t)&dev_vdata[6] },
{ PCI_VDEVICE(AMD, 0x1649), .driver_data = (kernel_ulong_t)&dev_vdata[6] },
{ PCI_VDEVICE(AMD, 0x1134), .driver_data = (kernel_ulong_t)&dev_vdata[7] },
{ PCI_VDEVICE(AMD, 0x17E0), .driver_data = (kernel_ulong_t)&dev_vdata[7] },
{ PCI_VDEVICE(AMD, 0x156E), .driver_data = (kernel_ulong_t)&dev_vdata[8] },
{ PCI_VDEVICE(AMD, 0x17D8), .driver_data = (kernel_ulong_t)&dev_vdata[8] },
{ PCI_VDEVICE(AMD, 0x115A), .driver_data = (kernel_ulong_t)&dev_vdata[9] },
/* Last entry must be zero */
{ }
};
MODULE_DEVICE_TABLE(pci, sp_pci_table);
static const struct dev_pm_ops sp_pci_pm_ops = {
.suspend = pm_sleep_ptr(sp_pci_suspend),
.resume = pm_sleep_ptr(sp_pci_resume),
.freeze = pm_sleep_ptr(sp_pci_suspend),
.thaw = pm_sleep_ptr(sp_pci_resume),
.poweroff = pm_sleep_ptr(sp_pci_suspend),
.restore_early = pm_sleep_ptr(sp_pci_restore),
};
static struct pci_driver sp_pci_driver = {
.name = "ccp",
.id_table = sp_pci_table,
.probe = sp_pci_probe,
.remove = sp_pci_remove,
.shutdown = sp_pci_shutdown,
.driver.pm = &sp_pci_pm_ops,
.dev_groups = psp_groups,
};
int sp_pci_init(void)
{
return pci_register_driver(&sp_pci_driver);
}
void sp_pci_exit(void)
{
pci_unregister_driver(&sp_pci_driver);
}
@@ -0,0 +1,217 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* AMD Secure Processor device driver
*
* Copyright (C) 2014,2018 Advanced Micro Devices, Inc.
*
* Author: Tom Lendacky <thomas.lendacky@amd.com>
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/ioport.h>
#include <linux/dma-mapping.h>
#include <linux/kthread.h>
#include <linux/sched.h>
#include <linux/interrupt.h>
#include <linux/spinlock.h>
#include <linux/delay.h>
#include <linux/ccp.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/acpi.h>
#include "ccp-dev.h"
struct sp_platform {
int coherent;
unsigned int irq_count;
};
static const struct sp_dev_vdata dev_vdata[] = {
{
.bar = 0,
#ifdef CONFIG_CRYPTO_DEV_SP_CCP
.ccp_vdata = &ccpv3_platform,
#endif
},
};
static const struct acpi_device_id sp_acpi_match[] = {
{ "AMDI0C00", (kernel_ulong_t)&dev_vdata[0] },
{ },
};
MODULE_DEVICE_TABLE(acpi, sp_acpi_match);
static const struct of_device_id sp_of_match[] = {
{ .compatible = "amd,ccp-seattle-v1a",
.data = (const void *)&dev_vdata[0] },
{ },
};
MODULE_DEVICE_TABLE(of, sp_of_match);
static const struct sp_dev_vdata *sp_get_acpi_version(struct platform_device *pdev)
{
const struct acpi_device_id *match;
match = acpi_match_device(sp_acpi_match, &pdev->dev);
if (match && match->driver_data)
return (const struct sp_dev_vdata *)match->driver_data;
return NULL;
}
static int sp_get_irqs(struct sp_device *sp)
{
struct sp_platform *sp_platform = sp->dev_specific;
struct device *dev = sp->dev;
struct platform_device *pdev = to_platform_device(dev);
int ret;
sp_platform->irq_count = platform_irq_count(pdev);
ret = platform_get_irq(pdev, 0);
if (ret < 0) {
dev_notice(dev, "unable to get IRQ (%d)\n", ret);
return ret;
}
sp->psp_irq = ret;
if (sp_platform->irq_count == 1) {
sp->ccp_irq = ret;
} else {
ret = platform_get_irq(pdev, 1);
if (ret < 0) {
dev_notice(dev, "unable to get IRQ (%d)\n", ret);
return ret;
}
sp->ccp_irq = ret;
}
return 0;
}
static int sp_platform_probe(struct platform_device *pdev)
{
struct sp_device *sp;
struct sp_platform *sp_platform;
struct device *dev = &pdev->dev;
enum dev_dma_attr attr;
int ret;
ret = -ENOMEM;
sp = sp_alloc_struct(dev);
if (!sp)
goto e_err;
sp_platform = devm_kzalloc(dev, sizeof(*sp_platform), GFP_KERNEL);
if (!sp_platform)
goto e_err;
sp->dev_specific = sp_platform;
sp->dev_vdata = pdev->dev.of_node ? of_device_get_match_data(&pdev->dev)
: sp_get_acpi_version(pdev);
if (!sp->dev_vdata) {
ret = -ENODEV;
dev_err(dev, "missing driver data\n");
goto e_err;
}
sp->io_map = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(sp->io_map)) {
ret = PTR_ERR(sp->io_map);
goto e_err;
}
attr = device_get_dma_attr(dev);
if (attr == DEV_DMA_NOT_SUPPORTED) {
dev_err(dev, "DMA is not supported");
goto e_err;
}
sp_platform->coherent = (attr == DEV_DMA_COHERENT);
if (sp_platform->coherent)
sp->axcache = CACHE_WB_NO_ALLOC;
else
sp->axcache = CACHE_NONE;
ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(48));
if (ret) {
dev_err(dev, "dma_set_mask_and_coherent failed (%d)\n", ret);
goto e_err;
}
ret = sp_get_irqs(sp);
if (ret)
goto e_err;
dev_set_drvdata(dev, sp);
ret = sp_init(sp);
if (ret)
goto e_err;
dev_notice(dev, "enabled\n");
return 0;
e_err:
dev_notice(dev, "initialization failed\n");
return ret;
}
static void sp_platform_remove(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct sp_device *sp = dev_get_drvdata(dev);
sp_destroy(sp);
dev_notice(dev, "disabled\n");
}
#ifdef CONFIG_PM
static int sp_platform_suspend(struct platform_device *pdev,
pm_message_t state)
{
struct device *dev = &pdev->dev;
struct sp_device *sp = dev_get_drvdata(dev);
return sp_suspend(sp);
}
static int sp_platform_resume(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct sp_device *sp = dev_get_drvdata(dev);
return sp_resume(sp);
}
#endif
static struct platform_driver sp_platform_driver = {
.driver = {
.name = "ccp",
.acpi_match_table = sp_acpi_match,
.of_match_table = sp_of_match,
},
.probe = sp_platform_probe,
.remove = sp_platform_remove,
#ifdef CONFIG_PM
.suspend = sp_platform_suspend,
.resume = sp_platform_resume,
#endif
};
int sp_platform_init(void)
{
return platform_driver_register(&sp_platform_driver);
}
void sp_platform_exit(void)
{
platform_driver_unregister(&sp_platform_driver);
}
+399
View File
@@ -0,0 +1,399 @@
// SPDX-License-Identifier: MIT
/*
* AMD Trusted Execution Environment (TEE) interface
*
* Author: Rijo Thomas <Rijo-john.Thomas@amd.com>
* Author: Devaraj Rangasamy <Devaraj.Rangasamy@amd.com>
*
* Copyright (C) 2019,2021 Advanced Micro Devices, Inc.
*/
#include <linux/bitfield.h>
#include <linux/types.h>
#include <linux/mutex.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/gfp.h>
#include <linux/psp.h>
#include <linux/psp-tee.h>
#include "psp-dev.h"
#include "tee-dev.h"
static bool psp_dead;
static int tee_alloc_ring(struct psp_tee_device *tee, int ring_size)
{
struct ring_buf_manager *rb_mgr = &tee->rb_mgr;
void *start_addr;
if (!ring_size)
return -EINVAL;
/* We need actual physical address instead of DMA address, since
* Trusted OS running on AMD Secure Processor will map this region
*/
start_addr = (void *)__get_free_pages(GFP_KERNEL, get_order(ring_size));
if (!start_addr)
return -ENOMEM;
memset(start_addr, 0x0, ring_size);
rb_mgr->ring_start = start_addr;
rb_mgr->ring_size = ring_size;
rb_mgr->ring_pa = __psp_pa(start_addr);
mutex_init(&rb_mgr->mutex);
return 0;
}
static void tee_free_ring(struct psp_tee_device *tee)
{
struct ring_buf_manager *rb_mgr = &tee->rb_mgr;
if (!rb_mgr->ring_start)
return;
free_pages((unsigned long)rb_mgr->ring_start,
get_order(rb_mgr->ring_size));
rb_mgr->ring_start = NULL;
rb_mgr->ring_size = 0;
rb_mgr->ring_pa = 0;
mutex_destroy(&rb_mgr->mutex);
}
static
struct tee_init_ring_cmd *tee_alloc_cmd_buffer(struct psp_tee_device *tee)
{
struct tee_init_ring_cmd *cmd;
cmd = kzalloc_obj(*cmd);
if (!cmd)
return NULL;
cmd->hi_addr = upper_32_bits(tee->rb_mgr.ring_pa);
cmd->low_addr = lower_32_bits(tee->rb_mgr.ring_pa);
cmd->size = tee->rb_mgr.ring_size;
dev_dbg(tee->dev, "tee: ring address: high = 0x%x low = 0x%x size = %u\n",
cmd->hi_addr, cmd->low_addr, cmd->size);
return cmd;
}
static inline void tee_free_cmd_buffer(struct tee_init_ring_cmd *cmd)
{
kfree(cmd);
}
static bool tee_send_destroy_cmd(struct psp_tee_device *tee)
{
unsigned int reg;
int ret;
ret = psp_mailbox_command(tee->psp, PSP_CMD_TEE_RING_DESTROY, NULL,
TEE_DEFAULT_CMD_TIMEOUT, &reg);
if (ret) {
dev_err(tee->dev, "tee: ring destroy command timed out, disabling TEE support\n");
psp_dead = true;
return false;
}
if (FIELD_GET(PSP_CMDRESP_STS, reg)) {
dev_err(tee->dev, "tee: ring destroy command failed (%#010lx)\n",
FIELD_GET(PSP_CMDRESP_STS, reg));
psp_dead = true;
return false;
}
return true;
}
static int tee_init_ring(struct psp_tee_device *tee)
{
int ring_size = MAX_RING_BUFFER_ENTRIES * sizeof(struct tee_ring_cmd);
struct tee_init_ring_cmd *cmd;
bool retry = false;
unsigned int reg;
int ret;
BUILD_BUG_ON(sizeof(struct tee_ring_cmd) != 1024);
ret = tee_alloc_ring(tee, ring_size);
if (ret) {
dev_err(tee->dev, "tee: ring allocation failed %d\n", ret);
return ret;
}
tee->rb_mgr.wptr = 0;
cmd = tee_alloc_cmd_buffer(tee);
if (!cmd) {
tee_free_ring(tee);
return -ENOMEM;
}
/* Send command buffer details to Trusted OS by writing to
* CPU-PSP message registers
*/
retry_init:
ret = psp_mailbox_command(tee->psp, PSP_CMD_TEE_RING_INIT, cmd,
TEE_DEFAULT_CMD_TIMEOUT, &reg);
if (ret) {
dev_err(tee->dev, "tee: ring init command timed out, disabling TEE support\n");
tee_free_ring(tee);
psp_dead = true;
goto free_buf;
}
if (FIELD_GET(PSP_CMDRESP_STS, reg)) {
/*
* During the hibernate resume sequence driver may have gotten loaded
* but the ring not properly destroyed. If the ring doesn't work, try
* to destroy and re-init once.
*/
if (!retry && FIELD_GET(PSP_CMDRESP_STS, reg) == PSP_TEE_STS_RING_BUSY) {
dev_info(tee->dev, "tee: ring init command failed with busy status, retrying\n");
if (tee_send_destroy_cmd(tee)) {
retry = true;
goto retry_init;
}
}
dev_err(tee->dev, "tee: ring init command failed (%#010lx)\n",
FIELD_GET(PSP_CMDRESP_STS, reg));
tee_free_ring(tee);
psp_dead = true;
ret = -EIO;
}
free_buf:
tee_free_cmd_buffer(cmd);
return ret;
}
static void tee_destroy_ring(struct psp_tee_device *tee)
{
if (!tee->rb_mgr.ring_start)
return;
if (psp_dead)
goto free_ring;
tee_send_destroy_cmd(tee);
free_ring:
tee_free_ring(tee);
}
int tee_dev_init(struct psp_device *psp)
{
struct device *dev = psp->dev;
struct psp_tee_device *tee;
int ret;
ret = -ENOMEM;
tee = devm_kzalloc(dev, sizeof(*tee), GFP_KERNEL);
if (!tee)
goto e_err;
psp->tee_data = tee;
tee->dev = dev;
tee->psp = psp;
tee->io_regs = psp->io_regs;
tee->vdata = (struct tee_vdata *)psp->vdata->tee;
if (!tee->vdata) {
ret = -ENODEV;
dev_err(dev, "tee: missing driver data\n");
goto e_err;
}
ret = tee_init_ring(tee);
if (ret) {
dev_err(dev, "tee: failed to init ring buffer\n");
goto e_err;
}
dev_notice(dev, "tee enabled\n");
return 0;
e_err:
psp->tee_data = NULL;
dev_notice(dev, "tee initialization failed\n");
return ret;
}
void tee_dev_destroy(struct psp_device *psp)
{
struct psp_tee_device *tee = psp->tee_data;
if (!tee)
return;
tee_destroy_ring(tee);
}
static int tee_submit_cmd(struct psp_tee_device *tee, enum tee_cmd_id cmd_id,
void *buf, size_t len, struct tee_ring_cmd **resp)
{
struct tee_ring_cmd *cmd;
int nloop = 1000, ret = 0;
u32 rptr;
*resp = NULL;
mutex_lock(&tee->rb_mgr.mutex);
/* Loop until empty entry found in ring buffer */
do {
/* Get pointer to ring buffer command entry */
cmd = (struct tee_ring_cmd *)
(tee->rb_mgr.ring_start + tee->rb_mgr.wptr);
rptr = ioread32(tee->io_regs + tee->vdata->ring_rptr_reg);
/* Check if ring buffer is full or command entry is waiting
* for response from TEE
*/
if (!(tee->rb_mgr.wptr + sizeof(struct tee_ring_cmd) == rptr ||
cmd->flag == CMD_WAITING_FOR_RESPONSE))
break;
dev_dbg(tee->dev, "tee: ring buffer full. rptr = %u wptr = %u\n",
rptr, tee->rb_mgr.wptr);
/* Wait if ring buffer is full or TEE is processing data */
mutex_unlock(&tee->rb_mgr.mutex);
schedule_timeout_interruptible(msecs_to_jiffies(10));
mutex_lock(&tee->rb_mgr.mutex);
} while (--nloop);
if (!nloop &&
(tee->rb_mgr.wptr + sizeof(struct tee_ring_cmd) == rptr ||
cmd->flag == CMD_WAITING_FOR_RESPONSE)) {
dev_err(tee->dev, "tee: ring buffer full. rptr = %u wptr = %u response flag %u\n",
rptr, tee->rb_mgr.wptr, cmd->flag);
ret = -EBUSY;
goto unlock;
}
/* Do not submit command if PSP got disabled while processing any
* command in another thread
*/
if (psp_dead) {
ret = -EBUSY;
goto unlock;
}
/* Write command data into ring buffer */
cmd->cmd_id = cmd_id;
cmd->cmd_state = TEE_CMD_STATE_INIT;
memset(&cmd->buf[0], 0, sizeof(cmd->buf));
memcpy(&cmd->buf[0], buf, len);
/* Indicate driver is waiting for response */
cmd->flag = CMD_WAITING_FOR_RESPONSE;
/* Update local copy of write pointer */
tee->rb_mgr.wptr += sizeof(struct tee_ring_cmd);
if (tee->rb_mgr.wptr >= tee->rb_mgr.ring_size)
tee->rb_mgr.wptr = 0;
/* Trigger interrupt to Trusted OS */
iowrite32(tee->rb_mgr.wptr, tee->io_regs + tee->vdata->ring_wptr_reg);
/* The response is provided by Trusted OS in same
* location as submitted data entry within ring buffer.
*/
*resp = cmd;
unlock:
mutex_unlock(&tee->rb_mgr.mutex);
return ret;
}
static int tee_wait_cmd_completion(struct psp_tee_device *tee,
struct tee_ring_cmd *resp,
unsigned int timeout)
{
/* ~1ms sleep per loop => nloop = timeout * 1000 */
int nloop = timeout * 1000;
while (--nloop) {
if (resp->cmd_state == TEE_CMD_STATE_COMPLETED)
return 0;
usleep_range(1000, 1100);
}
dev_err(tee->dev, "tee: command 0x%x timed out, disabling PSP\n",
resp->cmd_id);
psp_dead = true;
return -ETIMEDOUT;
}
int psp_tee_process_cmd(enum tee_cmd_id cmd_id, void *buf, size_t len,
u32 *status)
{
struct psp_device *psp = psp_get_master_device();
struct psp_tee_device *tee;
struct tee_ring_cmd *resp;
int ret;
if (!buf || !status || !len || len > sizeof(resp->buf))
return -EINVAL;
*status = 0;
if (!psp || !psp->tee_data)
return -ENODEV;
if (psp_dead)
return -EBUSY;
tee = psp->tee_data;
ret = tee_submit_cmd(tee, cmd_id, buf, len, &resp);
if (ret)
return ret;
ret = tee_wait_cmd_completion(tee, resp, TEE_DEFAULT_RING_TIMEOUT);
if (ret) {
resp->flag = CMD_RESPONSE_TIMEDOUT;
return ret;
}
memcpy(buf, &resp->buf[0], len);
*status = resp->status;
resp->flag = CMD_RESPONSE_COPIED;
return 0;
}
EXPORT_SYMBOL(psp_tee_process_cmd);
int psp_check_tee_status(void)
{
struct psp_device *psp = psp_get_master_device();
if (!psp || !psp->tee_data)
return -ENODEV;
return 0;
}
EXPORT_SYMBOL(psp_check_tee_status);
int tee_restore(struct psp_device *psp)
{
return tee_init_ring(psp->tee_data);
}
+116
View File
@@ -0,0 +1,116 @@
/* SPDX-License-Identifier: MIT */
/*
* Copyright (C) 2019,2021 Advanced Micro Devices, Inc.
*
* Author: Rijo Thomas <Rijo-john.Thomas@amd.com>
* Author: Devaraj Rangasamy <Devaraj.Rangasamy@amd.com>
*
*/
/* This file describes the TEE communication interface between host and AMD
* Secure Processor
*/
#ifndef __TEE_DEV_H__
#define __TEE_DEV_H__
#include <linux/device.h>
#include <linux/mutex.h>
#define TEE_DEFAULT_CMD_TIMEOUT (10 * MSEC_PER_SEC)
#define TEE_DEFAULT_RING_TIMEOUT 10
#define MAX_BUFFER_SIZE 988
/**
* struct tee_init_ring_cmd - Command to init TEE ring buffer
* @low_addr: bits [31:0] of the physical address of ring buffer
* @hi_addr: bits [63:32] of the physical address of ring buffer
* @size: size of ring buffer in bytes
*/
struct tee_init_ring_cmd {
u32 low_addr;
u32 hi_addr;
u32 size;
};
#define MAX_RING_BUFFER_ENTRIES 32
/**
* struct ring_buf_manager - Helper structure to manage ring buffer.
* @ring_start: starting address of ring buffer
* @ring_size: size of ring buffer in bytes
* @ring_pa: physical address of ring buffer
* @wptr: index to the last written entry in ring buffer
*/
struct ring_buf_manager {
struct mutex mutex; /* synchronizes access to ring buffer */
void *ring_start;
u32 ring_size;
phys_addr_t ring_pa;
u32 wptr;
};
struct psp_tee_device {
struct device *dev;
struct psp_device *psp;
void __iomem *io_regs;
struct tee_vdata *vdata;
struct ring_buf_manager rb_mgr;
};
/**
* enum tee_cmd_state - TEE command states for the ring buffer interface
* @TEE_CMD_STATE_INIT: initial state of command when sent from host
* @TEE_CMD_STATE_PROCESS: command being processed by TEE environment
* @TEE_CMD_STATE_COMPLETED: command processing completed
*/
enum tee_cmd_state {
TEE_CMD_STATE_INIT,
TEE_CMD_STATE_PROCESS,
TEE_CMD_STATE_COMPLETED,
};
/**
* enum cmd_resp_state - TEE command's response status maintained by driver
* @CMD_RESPONSE_INVALID: initial state when no command is written to ring
* @CMD_WAITING_FOR_RESPONSE: driver waiting for response from TEE
* @CMD_RESPONSE_TIMEDOUT: failed to get response from TEE
* @CMD_RESPONSE_COPIED: driver has copied response from TEE
*/
enum cmd_resp_state {
CMD_RESPONSE_INVALID,
CMD_WAITING_FOR_RESPONSE,
CMD_RESPONSE_TIMEDOUT,
CMD_RESPONSE_COPIED,
};
/**
* struct tee_ring_cmd - Structure of the command buffer in TEE ring
* @cmd_id: refers to &enum tee_cmd_id. Command id for the ring buffer
* interface
* @cmd_state: refers to &enum tee_cmd_state
* @status: status of TEE command execution
* @res0: reserved region
* @pdata: private data (currently unused)
* @res1: reserved region
* @buf: TEE command specific buffer
* @flag: refers to &enum cmd_resp_state
*/
struct tee_ring_cmd {
u32 cmd_id;
u32 cmd_state;
u32 status;
u32 res0[1];
u64 pdata;
u32 res1[2];
u8 buf[MAX_BUFFER_SIZE];
u32 flag;
/* Total size: 1024 bytes */
} __packed;
int tee_dev_init(struct psp_device *psp);
void tee_dev_destroy(struct psp_device *psp);
int tee_restore(struct psp_device *psp);
#endif /* __TEE_DEV_H__ */