Files
zephyr/drivers/crypto/crypto_mchp_sha_g1.c
Tony Han 6d52c87443 drivers: crypto: hash: add driver for Microchip SHA
Add driver for Microchip Secure Hash Algorithm (SHA), update Kconfig
and make files.

Signed-off-by: Tony Han <tony.han@microchip.com>
2025-12-03 09:11:40 -05:00

333 lines
8.4 KiB
C

/*
* Copyright (C) 2025 Microchip Technology Inc. and its subsidiaries
*
* SPDX-License-Identifier: Apache-2.0
*
*/
#define DT_DRV_COMPAT microchip_sha_g1_crypto
#define LOG_LEVEL CONFIG_CRYPTO_LOG_LEVEL
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(cypto_mchp_sha_g1);
#include <zephyr/crypto/crypto.h>
#include <zephyr/device.h>
#include <zephyr/drivers/clock_control/mchp_sam_pmc.h>
#include <zephyr/init.h>
#include <zephyr/kernel.h>
#include <zephyr/sys/atomic.h>
#include <zephyr/sys/util.h>
#define MCHP_SHA_CAPS_SUPPORT (CAP_INPLACE_OPS | CAP_SEPARATE_IO_BUFS | CAP_SYNC_OPS)
#define FIRST_WORD_4_PADDING_EMPTY_MSG 0x000000080
struct crypto_mchp_sha_cfg {
sha_registers_t *const regs;
const struct sam_clk_cfg clock_cfg;
};
struct crypto_mchp_sha_data {
struct k_sem device_sem;
};
struct crypto_mchp_sha_algo_cfg {
enum hash_algo algo;
uint32_t sha_mr_algo;
uint8_t dgst_len;
uint8_t block_size;
};
struct crypto_mchp_sha_session {
struct crypto_mchp_sha_algo_cfg const *algo_cfg;
bool in_use;
};
static const struct crypto_mchp_sha_algo_cfg mchp_sha_algo_cfgs[4] = {
{CRYPTO_HASH_ALGO_SHA224, SHA_MR_ALGO_SHA224, 224 / BITS_PER_BYTE, 64},
{CRYPTO_HASH_ALGO_SHA256, SHA_MR_ALGO_SHA256, 256 / BITS_PER_BYTE, 64},
{CRYPTO_HASH_ALGO_SHA384, SHA_MR_ALGO_SHA384, 384 / BITS_PER_BYTE, 128},
{CRYPTO_HASH_ALGO_SHA512, SHA_MR_ALGO_SHA512, 512 / BITS_PER_BYTE, 128},
};
static struct crypto_mchp_sha_session mchp_sha_sessions[2];
static struct k_sem mchp_sha_session_sem;
static inline void mchp_sha_set_input(sha_registers_t *const regs, const unsigned char *data,
int len)
{
const uint32_t *data_u32 = (const uint32_t *)data;
uint32_t i = 0, v;
while (len >= sizeof(uint32_t)) {
v = data_u32[i];
if (i < ARRAY_SIZE(regs->SHA_IDATAR)) {
regs->SHA_IDATAR[i] = v;
} else {
regs->SHA_IODATAR[i - ARRAY_SIZE(regs->SHA_IDATAR)] = v;
}
i++;
len -= sizeof(uint32_t);
}
if (len > 0) {
v = 0;
memcpy(&v, &data[i * sizeof(uint32_t)], len);
if (i < ARRAY_SIZE(regs->SHA_IDATAR)) {
regs->SHA_IDATAR[i] = v;
} else {
regs->SHA_IODATAR[i - ARRAY_SIZE(regs->SHA_IDATAR)] = v;
}
}
}
static inline void mchp_sha_get_output(sha_registers_t *const regs, unsigned char *data, int len)
{
uint32_t *data_u32 = (uint32_t *)data;
for (uint32_t i = 0; i < MIN(len / sizeof(uint32_t), ARRAY_SIZE(regs->SHA_IODATAR)); i++) {
data_u32[i] = regs->SHA_IODATAR[i];
}
}
static int mchp_sha_wait_data_rdy(sha_registers_t *const regs)
{
uint32_t timeout = 1;
while ((regs->SHA_ISR & SHA_ISR_DATRDY_Msk) == 0) {
if (timeout-- == 0) {
LOG_ERR("MCHP SHA wait data ready timeout");
return -ETIMEDOUT;
}
k_busy_wait(5);
}
return 0;
}
static int mchp_sha_process(sha_registers_t *const regs,
struct crypto_mchp_sha_algo_cfg const *algo_cfg,
const unsigned char *data, unsigned int len, unsigned char *digest)
{
int block_size = algo_cfg->block_size;
unsigned int processed = 0;
unsigned int cnt;
int ret;
regs->SHA_CR = SHA_CR_SWRST_Msk;
regs->SHA_MR = algo_cfg->sha_mr_algo | SHA_MR_SMOD_AUTO_START | SHA_MR_PROCDLY_LONGEST;
regs->SHA_CR = SHA_CR_FIRST_Msk;
regs->SHA_MSR = len;
regs->SHA_BCR = len;
if (len == 0) {
/* For the empty message, automatic padding is not quired in this driver
* (SHA_MSR.MSGSIZE and SHA_BCR.BYTCNT are configured to 0). The block to
* be processed are the padded part (a one bit, then '1' followed by zeor
* bits) and then the message length.
*/
regs->SHA_IDATAR[0] = FIRST_WORD_4_PADDING_EMPTY_MSG;
for (int i = 1; i < block_size / sizeof(uint32_t); i++) {
regs->SHA_IDATAR[i] = 0;
}
goto out;
}
while (len - processed != 0) {
cnt = MIN(block_size, len - processed);
/* Write the block to be processed in the Input Data Registers */
mchp_sha_set_input(regs, &data[processed], cnt);
ret = mchp_sha_wait_data_rdy(regs);
if (ret < 0) {
return ret;
}
processed += cnt;
}
out:
ret = mchp_sha_wait_data_rdy(regs);
if (ret < 0) {
return ret;
}
mchp_sha_get_output(regs, digest, algo_cfg->dgst_len);
return ret;
}
static struct crypto_mchp_sha_session *crypto_mchp_sha_get_unused_session(void)
{
struct crypto_mchp_sha_session *session = NULL;
k_sem_take(&mchp_sha_session_sem, K_FOREVER);
for (int i = 0; i < ARRAY_SIZE(mchp_sha_sessions); i++) {
if (!mchp_sha_sessions[i].in_use) {
session = &mchp_sha_sessions[i];
break;
}
}
k_sem_give(&mchp_sha_session_sem);
return session;
}
static int mchp_sha_handler(struct hash_ctx *ctx, struct hash_pkt *pkt, bool finish)
{
const struct device *dev = ctx->device;
const struct crypto_mchp_sha_cfg *cfg = dev->config;
struct crypto_mchp_sha_data *data = dev->data;
struct crypto_mchp_sha_session *session = ctx->drv_sessn_state;
bool inplace_ops = (ctx->flags & CAP_INPLACE_OPS) == CAP_INPLACE_OPS;
int ret;
if (!pkt || !pkt->in_buf || (!pkt->out_buf && !inplace_ops)) {
LOG_ERR("Invalid packet buffers");
return -EINVAL;
}
if (!finish) {
LOG_ERR("Multipart shaing not supported yet");
return -ENOTSUP;
}
k_sem_take(&data->device_sem, K_FOREVER);
if (session->algo_cfg == NULL) {
k_sem_give(&data->device_sem);
LOG_ERR("Unsupported algorithm");
return -ENOTSUP;
}
if (inplace_ops && pkt->in_len < session->algo_cfg->dgst_len) {
k_sem_give(&data->device_sem);
LOG_ERR("Insufficient in_buf for digest");
return -EINVAL;
}
ret = mchp_sha_process(cfg->regs, session->algo_cfg, pkt->in_buf, pkt->in_len,
inplace_ops ? (uint8_t *)(uintptr_t)pkt->in_buf : pkt->out_buf);
k_sem_give(&data->device_sem);
return ret;
}
static int mchp_sha_begin_session(const struct device *dev, struct hash_ctx *ctx,
enum hash_algo algo)
{
struct crypto_mchp_sha_algo_cfg const *algo_cfg = NULL;
struct crypto_mchp_sha_session *session;
if (ctx->flags & ~(MCHP_SHA_CAPS_SUPPORT)) {
LOG_ERR("Unsupported flag");
return -ENOTSUP;
}
for (int i = 0; i < ARRAY_SIZE(mchp_sha_algo_cfgs); i++) {
if (algo == mchp_sha_algo_cfgs[i].algo) {
algo_cfg = &mchp_sha_algo_cfgs[i];
break;
}
}
if (algo_cfg == NULL) {
LOG_ERR("Unsupported hash algorithm: %d", algo);
return -ENOTSUP;
}
session = crypto_mchp_sha_get_unused_session();
if (session == NULL) {
LOG_ERR("No free session for now");
return -ENOSPC;
}
session->algo_cfg = algo_cfg;
ctx->device = dev;
ctx->drv_sessn_state = session;
ctx->hash_hndlr = mchp_sha_handler;
ctx->started = false;
LOG_DBG("Session started: algo=%d", algo);
return 0;
}
static int mchp_sha_free_session(const struct device *dev, struct hash_ctx *ctx)
{
if (!ctx || !ctx->device || !ctx->drv_sessn_state) {
LOG_ERR("Tried to free a invalid context or session");
return -EINVAL;
}
if (ctx->device != dev) {
LOG_ERR("The context or session tried to free is not related to the device");
return -EINVAL;
}
k_sem_take(&mchp_sha_session_sem, K_FOREVER);
memset(ctx->drv_sessn_state, 0, sizeof(struct crypto_mchp_sha_session));
k_sem_give(&mchp_sha_session_sem);
ctx->device = NULL;
ctx->drv_sessn_state = NULL;
LOG_DBG("Session freed");
return 0;
}
static int mchp_sha_query_caps(const struct device *dev)
{
ARG_UNUSED(dev);
return MCHP_SHA_CAPS_SUPPORT;
}
static int crypto_mchp_sha_init(const struct device *dev)
{
const struct device *const pmc = DEVICE_DT_GET(DT_NODELABEL(pmc));
const struct crypto_mchp_sha_cfg *cfg = dev->config;
struct crypto_mchp_sha_data *data = dev->data;
if (!device_is_ready(pmc)) {
LOG_ERR("Power Management Controller device not ready");
return -ENODEV;
}
if (clock_control_on(pmc, (clock_control_subsys_t)(uintptr_t)&(cfg->clock_cfg)) != 0) {
LOG_ERR("Clock op failed\n");
return -EIO;
}
k_sem_init(&data->device_sem, 1, 1);
k_sem_init(&mchp_sha_session_sem, 1, 1);
return 0;
}
static DEVICE_API(crypto, mchp_sha_api) = {
.hash_begin_session = mchp_sha_begin_session,
.hash_free_session = mchp_sha_free_session,
.query_hw_caps = mchp_sha_query_caps,
};
#define CRYPTO_MCHP_SHA_INIT(n) \
static const struct crypto_mchp_sha_cfg mchp_sha##n##_cfg = { \
.regs = (sha_registers_t *)DT_INST_REG_ADDR(n), \
.clock_cfg = SAM_DT_INST_CLOCK_PMC_CFG(n), \
}; \
\
static struct crypto_mchp_sha_data mchp_sha##n##_data; \
\
DEVICE_DT_INST_DEFINE(n, \
crypto_mchp_sha_init, \
NULL, \
&mchp_sha##n##_data, \
&mchp_sha##n##_cfg, \
POST_KERNEL, \
CONFIG_CRYPTO_INIT_PRIORITY, \
&mchp_sha_api);
DT_INST_FOREACH_STATUS_OKAY(CRYPTO_MCHP_SHA_INIT)