hw/misc: Implement a generic SHA driver for newer Espressif targets
This commit is contained in:
committed by
Ivan Grokhotkov
parent
3418debeda
commit
24970c0d2c
@@ -0,0 +1,383 @@
|
||||
/*
|
||||
* ESP SHA accelerator
|
||||
*
|
||||
* Copyright (c) 2025 Espressif Systems (Shanghai) Co. Ltd.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 or
|
||||
* (at your option) any later version.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "qemu/log.h"
|
||||
#include "qemu/error-report.h"
|
||||
#include "qapi/error.h"
|
||||
#include "hw/hw.h"
|
||||
#include "hw/sysbus.h"
|
||||
#include "hw/registerfields.h"
|
||||
#include "hw/dma/esp_gdma.h"
|
||||
#include "hw/misc/esp_sha.h"
|
||||
#include "hw/irq.h"
|
||||
|
||||
#define SHA_WARNING 0
|
||||
#define SHA_DEBUG 0
|
||||
|
||||
static ESPHashAlg esp_sha_algs[] = {
|
||||
[ESP_SHA_1_MODE] = {
|
||||
.init = (hash_init) sha1_init,
|
||||
.compress = (hash_compress) sha1_compress,
|
||||
.len = sizeof(struct sha1_state)
|
||||
},
|
||||
[ESP_SHA_224_MODE] = {
|
||||
.init = (hash_init) sha224_init,
|
||||
.compress = (hash_compress) sha224_compress,
|
||||
.len = SHA224_HASH_SIZE
|
||||
},
|
||||
[ESP_SHA_256_MODE] = {
|
||||
.init = (hash_init) sha256_init,
|
||||
.compress = (hash_compress) sha256_compress,
|
||||
.len = sizeof(struct sha256_state)
|
||||
},
|
||||
[ESP_SHA_384_MODE] = {
|
||||
.init = (hash_init) sha384_init,
|
||||
.compress = (hash_compress) sha512_compress,
|
||||
.len = SHA384_HASH_SIZE
|
||||
},
|
||||
[ESP_SHA_512_MODE] = {
|
||||
.init = (hash_init) sha512_init,
|
||||
.compress = (hash_compress) sha512_compress,
|
||||
.len = sizeof(struct sha512_state)
|
||||
},
|
||||
[ESP_SHA_512_224_MODE] = {
|
||||
.init = (hash_init) sha512_224_init,
|
||||
.compress = (hash_compress) sha512_compress,
|
||||
.len = sizeof(struct sha512_state)
|
||||
},
|
||||
[ESP_SHA_512_256_MODE] = {
|
||||
.init = (hash_init) sha512_256_init,
|
||||
.compress = (hash_compress) sha512_compress,
|
||||
.len = sizeof(struct sha512_state)
|
||||
},
|
||||
[ESP_SHA_512_t_MODE] = {
|
||||
.init = (hash_init) sha512_t_init,
|
||||
.init_message = (hash_init_message) sha512_t_init_message,
|
||||
.compress = (hash_compress) sha512_compress,
|
||||
.len = sizeof(struct sha512_state)
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
static void esp_sha_write_digest(ESPShaMode mode, uint32_t* hash, ESPHashContext* context, size_t len)
|
||||
{
|
||||
if (mode < ESP_SHA_384_MODE) {
|
||||
memcpy(context, hash, len);
|
||||
} else {
|
||||
for (int i = 0; i < 8; i++) {
|
||||
context->sha512.state[i] = ((uint64_t)hash[i * 2] << 32) | hash[1 + (i * 2)];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void esp_sha_read_digest(ESPShaMode mode, uint32_t* hash, ESPHashContext* context, size_t len)
|
||||
{
|
||||
if (mode < ESP_SHA_384_MODE) {
|
||||
memcpy(hash, context, len);
|
||||
} else {
|
||||
for (int i = 0; i < 8; i++) {
|
||||
hash[i * 2] = (uint32_t)(context->sha512.state[i] >> 32);
|
||||
hash[1 + (i * 2)] = (uint32_t)(context->sha512.state[i] & 0xffffffff);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void esp_sha_continue_hash(ESPShaState *s, uint32_t mode, uint32_t *message, uint32_t *hash)
|
||||
{
|
||||
assert(mode <= ESP_SHA_512_t_MODE);
|
||||
ESPHashAlg alg = esp_sha_algs[mode];
|
||||
|
||||
alg.compress(&s->context, (uint8_t*) message);
|
||||
|
||||
esp_sha_read_digest(mode, hash, &s->context, alg.len);
|
||||
}
|
||||
|
||||
|
||||
static void esp_sha_continue_dma(ESPShaState *s)
|
||||
{
|
||||
assert(s->mode <= ESP_SHA_512_t_MODE);
|
||||
ESPHashAlg alg = esp_sha_algs[s->mode];
|
||||
uint32_t gdma_out_idx = 0;
|
||||
|
||||
assert(alg.compress);
|
||||
|
||||
size_t blk_len = (s->mode < ESP_SHA_384_MODE) ? 64 : 128;
|
||||
|
||||
/* Number of blocks to process, each block is blk_len bytes big */
|
||||
const uint32_t blocks = s->block;
|
||||
const uint32_t buf_size = blocks * blk_len;
|
||||
|
||||
/* Get the GDMA channel connected to SHA module.
|
||||
* Specify ESP_GDMA_OUT_IDX since the data are going OUT of GDMA but IN our current component. */
|
||||
if ( !esp_gdma_get_channel_periph(s->gdma, GDMA_SHA, ESP_GDMA_OUT_IDX, &gdma_out_idx) )
|
||||
{
|
||||
warn_report("[SHA] GDMA requested but no properly configured channel found");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Allocate the buffer that will contain the data and get teh actual data */
|
||||
uint8_t *buffer = g_malloc(blocks * blk_len);
|
||||
if (buffer == NULL)
|
||||
{
|
||||
error_report("[SHA] No more memory in host!");
|
||||
return;
|
||||
}
|
||||
if ( !esp_gdma_read_channel(s->gdma, gdma_out_idx, buffer, buf_size) ) {
|
||||
warn_report("[SHA] Error reading from GDMA buffer");
|
||||
g_free(buffer);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Perform the actual SHA operation on the whole buffer */
|
||||
for (uint32_t i = 0; i < blocks; i++)
|
||||
{
|
||||
alg.compress(&s->context, buffer + i * blk_len);
|
||||
}
|
||||
|
||||
esp_sha_read_digest(s->mode, s->hash, &s->context, alg.len);
|
||||
|
||||
g_free(buffer);
|
||||
|
||||
/* Trigger an interrupt if enabled! */
|
||||
if (s->int_ena) {
|
||||
qemu_irq_raise(s->irq);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void esp_sha_start(ESPShaState *s, ESPShaOperation op, uint32_t mode, uint32_t *message, uint32_t *hash)
|
||||
{
|
||||
ESPHashAlg alg = esp_sha_algs[mode];
|
||||
assert(alg.init && alg.compress);
|
||||
|
||||
if ((op & SHA_OP_TYPE_MASK) == OP_START) {
|
||||
alg.init(&s->context);
|
||||
if (s->mode == ESP_SHA_512_t_MODE) {
|
||||
alg.init_message(message, ESP_SHA_MAX_MESSAGE_WORDS, s->t, s->t_len);
|
||||
}
|
||||
} else {
|
||||
/* Continue operation: initialize the context from the current hash.
|
||||
* We don't have any accessor to do it so ... do it the "dirty" way */
|
||||
esp_sha_write_digest(mode, hash, &s->context, alg.len);
|
||||
}
|
||||
|
||||
if ((op & SHA_OP_DMA_MASK) == SHA_OP_DMA_MASK) {
|
||||
esp_sha_continue_dma(s);
|
||||
} else {
|
||||
esp_sha_continue_hash(s, mode, message, hash);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static uint64_t esp_sha_read(void *opaque, hwaddr addr, unsigned int size)
|
||||
{
|
||||
ESPShaClass *class = ESP_SHA_GET_CLASS(opaque);
|
||||
ESPShaState *s = ESP_SHA(opaque);
|
||||
hwaddr index = 0;
|
||||
|
||||
uint64_t r = 0;
|
||||
switch (addr) {
|
||||
case A_SHA_MODE:
|
||||
r = s->mode;
|
||||
break;
|
||||
case A_SHA_BUSY:
|
||||
/* SHA driver is never busy as calculation happens synchronously */
|
||||
r = 0;
|
||||
break;
|
||||
case A_SHA_DATE:
|
||||
/* Hardcode the version control register for now */
|
||||
r = class->date;
|
||||
break;
|
||||
case A_SHA_H_MEM ... A_SHA_M_MEM - 1:
|
||||
index = (addr - A_SHA_H_MEM) / sizeof(uint32_t);
|
||||
r = bswap32(s->hash[index]);
|
||||
break;
|
||||
case A_SHA_M_MEM ... ESP_SHA_REGS_SIZE - 1:
|
||||
index = (addr - A_SHA_M_MEM) / sizeof(uint32_t);
|
||||
r = s->message[index];
|
||||
break;
|
||||
case A_SHA_DMA_BLOCK_NUM:
|
||||
r = s->block;
|
||||
break;
|
||||
case A_SHA_IRQ_ENA:
|
||||
r = s->int_ena ? 1 : 0;
|
||||
break;
|
||||
default:
|
||||
#if SHA_WARNING
|
||||
warn_report("[ESP] SHA DMA and IRQ unsupported for now, ignoring...\n");
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
|
||||
#if SHA_DEBUG
|
||||
info_report("[ESP] SHA reading %08lx (%08lx)", addr, r);
|
||||
#endif
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
|
||||
static void esp_sha_write(void *opaque, hwaddr addr,
|
||||
uint64_t value, unsigned int size)
|
||||
{
|
||||
ESPShaClass *class = ESP_SHA_GET_CLASS(opaque);
|
||||
ESPShaState *s = ESP_SHA(opaque);
|
||||
hwaddr index = 0;
|
||||
|
||||
#if SHA_DEBUG
|
||||
info_report("[ESP] SHA writing %08lx (%08lx)", addr, value);
|
||||
#endif
|
||||
|
||||
switch (addr) {
|
||||
case A_SHA_MODE:
|
||||
/* Make sure the value is always between 0 and 7 as the real hardware doesn't
|
||||
* accept a value of 8. Choose SHA-1 by default in that case. */
|
||||
s->mode = (value & 0b111) % 8;
|
||||
break;
|
||||
|
||||
case A_SHA_T_STRING:
|
||||
s->t = bswap32((uint32_t) value);
|
||||
break;
|
||||
|
||||
case A_SHA_T_LENGTH:
|
||||
s->t_len = bswap32(FIELD_EX32(value, SHA_T_LENGTH, T_LENGTH));
|
||||
break;
|
||||
|
||||
case A_SHA_START:
|
||||
if (FIELD_EX32(value, SHA_START, START)) {
|
||||
class->sha_start(s, OP_START, s->mode, s->message, s->hash);
|
||||
}
|
||||
break;
|
||||
|
||||
case A_SHA_CONTINUE:
|
||||
if (FIELD_EX32(value, SHA_CONTINUE, CONTINUE)) {
|
||||
class->sha_start(s, OP_CONTINUE, s->mode, s->message, s->hash);
|
||||
}
|
||||
break;
|
||||
|
||||
case A_SHA_H_MEM ... A_SHA_M_MEM - 1:
|
||||
/* Only support word aligned access for the moment */
|
||||
if (size != sizeof(uint32_t)) {
|
||||
error_report("[SHA] Only 32-bit word access supported at the moment");
|
||||
}
|
||||
index = (addr - A_SHA_H_MEM) / sizeof(uint32_t);
|
||||
s->hash[index] = bswap32((uint32_t) value);
|
||||
break;
|
||||
|
||||
case A_SHA_M_MEM ... ESP_SHA_REGS_SIZE - 1:
|
||||
index = (addr - A_SHA_M_MEM) / sizeof(uint32_t);
|
||||
s->message[index] = (uint32_t) value;
|
||||
break;
|
||||
|
||||
case A_SHA_DMA_BLOCK_NUM:
|
||||
s->block = FIELD_EX32(value, SHA_DMA_BLOCK_NUM, DMA_BLOCK_NUM);
|
||||
break;
|
||||
|
||||
case A_SHA_DMA_START:
|
||||
if (FIELD_EX32(value, SHA_DMA_START, DMA_START)) {
|
||||
class->sha_start(s, OP_DMA_START, s->mode, s->message, s->hash);
|
||||
}
|
||||
break;
|
||||
|
||||
case A_SHA_DMA_CONTINUE:
|
||||
if (FIELD_EX32(value, SHA_DMA_CONTINUE, DMA_CONTINUE)) {
|
||||
class->sha_start(s, OP_DMA_CONTINUE, s->mode, s->message, s->hash);
|
||||
}
|
||||
break;
|
||||
|
||||
case A_SHA_CLEAR_IRQ:
|
||||
qemu_irq_lower(s->irq);
|
||||
break;
|
||||
|
||||
case A_SHA_IRQ_ENA:
|
||||
s->int_ena = FIELD_EX32(value, SHA_IRQ_ENA, INTERRUPT_ENA) != 0;
|
||||
break;
|
||||
|
||||
default:
|
||||
#if SHA_WARNING
|
||||
/* Unsupported for now, do nothing */
|
||||
warn_report("[SHA] Unsupported write to %08lx\n", addr);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static const MemoryRegionOps esp_sha_ops = {
|
||||
.read = esp_sha_read,
|
||||
.write = esp_sha_write,
|
||||
.endianness = DEVICE_LITTLE_ENDIAN,
|
||||
};
|
||||
|
||||
|
||||
static void esp_sha_reset(DeviceState *dev)
|
||||
{
|
||||
ESPShaState *s = ESP_SHA(dev);
|
||||
memset(s->hash, 0, 8 * sizeof(uint32_t));
|
||||
memset(s->message, 0, ESP_SHA_MAX_MESSAGE_WORDS * sizeof(uint32_t));
|
||||
|
||||
s->block = 0;
|
||||
s->int_ena = 0;
|
||||
qemu_irq_lower(s->irq);
|
||||
}
|
||||
|
||||
|
||||
static void esp_sha_realize(DeviceState *dev, Error **errp)
|
||||
{
|
||||
ESPShaState *s = ESP_SHA(dev);
|
||||
|
||||
/* Make sure GDMA was set of issue an error */
|
||||
if (s->gdma == NULL) {
|
||||
error_report("[SHA] GDMA controller must be set!");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void esp_sha_init(Object *obj)
|
||||
{
|
||||
ESPShaState *s = ESP_SHA(obj);
|
||||
SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
|
||||
|
||||
memory_region_init_io(&s->iomem, obj, &esp_sha_ops, s,
|
||||
TYPE_ESP_SHA, ESP_SHA_REGS_SIZE);
|
||||
sysbus_init_mmio(sbd, &s->iomem);
|
||||
|
||||
sysbus_init_irq(sbd, &s->irq);
|
||||
}
|
||||
|
||||
static void esp_sha_class_init(ObjectClass *klass, void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
ESPShaClass* esp_sha = ESP_SHA_CLASS(klass);
|
||||
|
||||
dc->realize = esp_sha_realize;
|
||||
dc->legacy_reset = esp_sha_reset;
|
||||
|
||||
esp_sha->sha_start = esp_sha_start;
|
||||
}
|
||||
|
||||
static const TypeInfo esp_sha_info = {
|
||||
.name = TYPE_ESP_SHA,
|
||||
.parent = TYPE_SYS_BUS_DEVICE,
|
||||
.instance_size = sizeof(ESPShaState),
|
||||
.instance_init = esp_sha_init,
|
||||
.class_init = esp_sha_class_init,
|
||||
.class_size = sizeof(ESPShaClass),
|
||||
.abstract = true
|
||||
};
|
||||
|
||||
static void esp_sha_register_types(void)
|
||||
{
|
||||
type_register_static(&esp_sha_info);
|
||||
}
|
||||
|
||||
type_init(esp_sha_register_types)
|
||||
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
* ESP SHA emulation
|
||||
*
|
||||
* Copyright (c) 2025 Espressif Systems (Shanghai) Co. Ltd.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 or
|
||||
* (at your option) any later version.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "hw/hw.h"
|
||||
#include "hw/sysbus.h"
|
||||
#include "hw/registerfields.h"
|
||||
#include "crypto/sha512_t_i.h"
|
||||
#include "crypto/sha512_256_i.h"
|
||||
#include "crypto/sha512_224_i.h"
|
||||
#include "crypto/sha512_i.h"
|
||||
#include "crypto/sha384_i.h"
|
||||
#include "crypto/sha256_i.h"
|
||||
#include "crypto/sha224_i.h"
|
||||
#include "crypto/sha1_i.h"
|
||||
|
||||
#include "hw/dma/esp_gdma.h"
|
||||
|
||||
#define TYPE_ESP_SHA "misc.esp.sha"
|
||||
#define ESP_SHA(obj) OBJECT_CHECK(ESPShaState, (obj), TYPE_ESP_SHA)
|
||||
|
||||
#define ESP_SHA_GET_CLASS(obj) OBJECT_GET_CLASS(ESPShaClass, obj, TYPE_ESP_SHA)
|
||||
#define ESP_SHA_CLASS(klass) OBJECT_CLASS_CHECK(ESPShaClass, klass, TYPE_ESP_SHA)
|
||||
|
||||
#define ESP_SHA_REGS_SIZE (0x100)
|
||||
|
||||
|
||||
/**
|
||||
* @brief Size of the message array, in bytes
|
||||
*/
|
||||
#define ESP_SHA_MAX_MESSAGE_SIZE 128
|
||||
#define ESP_SHA_MAX_MESSAGE_WORDS (ESP_SHA_MAX_MESSAGE_SIZE / sizeof(uint32_t))
|
||||
|
||||
|
||||
/**
|
||||
* @brief Mode configuration for the SHA_MODE register.
|
||||
*/
|
||||
typedef enum {
|
||||
ESP_SHA_1_MODE = 0,
|
||||
ESP_SHA_224_MODE = 1,
|
||||
ESP_SHA_256_MODE = 2,
|
||||
ESP_SHA_384_MODE = 3,
|
||||
ESP_SHA_512_MODE = 4,
|
||||
ESP_SHA_512_224_MODE = 5,
|
||||
ESP_SHA_512_256_MODE = 6,
|
||||
ESP_SHA_512_t_MODE = 7,
|
||||
} ESPShaMode;
|
||||
|
||||
|
||||
#define SHA_OP_TYPE_MASK (1 << 0)
|
||||
#define SHA_OP_DMA_MASK (1 << 1)
|
||||
|
||||
typedef enum {
|
||||
OP_START = 0,
|
||||
OP_CONTINUE = 1,
|
||||
OP_DMA_START = SHA_OP_DMA_MASK | OP_START,
|
||||
OP_DMA_CONTINUE = SHA_OP_DMA_MASK | OP_CONTINUE,
|
||||
} ESPShaOperation;
|
||||
|
||||
typedef union {
|
||||
struct sha512_state sha512;
|
||||
struct sha256_state sha256;
|
||||
struct sha1_state sha1;
|
||||
} ESPHashContext;
|
||||
|
||||
|
||||
typedef void (*hash_init)(void *);
|
||||
typedef void (*hash_init_message)(uint32_t *, size_t, uint32_t, uint32_t);
|
||||
typedef void (*hash_compress)(void *, const uint8_t*);
|
||||
|
||||
typedef struct {
|
||||
hash_init init;
|
||||
hash_init_message init_message;
|
||||
/* For all types of hash, the message to "compress" must be 64-byte long (16 words of 32 bits) */
|
||||
hash_compress compress;
|
||||
/* Length of the context in bytes */
|
||||
size_t len;
|
||||
} ESPHashAlg;
|
||||
|
||||
|
||||
typedef struct ESPShaState {
|
||||
SysBusDevice parent_obj;
|
||||
MemoryRegion iomem;
|
||||
|
||||
/* SHA mode selected by the application */
|
||||
ESPShaMode mode;
|
||||
/* Context for the hash calculation */
|
||||
ESPHashContext context;
|
||||
|
||||
/* Resulted hash value */
|
||||
uint32_t hash[16];
|
||||
/* User data value */
|
||||
uint32_t message[ESP_SHA_MAX_MESSAGE_WORDS];
|
||||
|
||||
uint32_t t;
|
||||
uint32_t t_len;
|
||||
|
||||
/* DMA related */
|
||||
/* Number of block to process in DMA mode */
|
||||
uint32_t block;
|
||||
bool int_ena;
|
||||
qemu_irq irq;
|
||||
|
||||
/* Public: must be set before realizing instance*/
|
||||
ESPGdmaState *gdma;
|
||||
} ESPShaState;
|
||||
|
||||
typedef struct ESPShaClass {
|
||||
SysBusDeviceClass parent_class;
|
||||
|
||||
/* Target specific registers */
|
||||
uint32_t message_len;
|
||||
/* Version register */
|
||||
uint32_t date;
|
||||
|
||||
/* Virtual methods*/
|
||||
void (*sha_start)(ESPShaState *s, ESPShaOperation op, uint32_t mode, uint32_t *message, uint32_t *hash);
|
||||
} ESPShaClass;
|
||||
|
||||
|
||||
REG32(SHA_MODE, 0x000)
|
||||
FIELD(SHA_MODE, MODE, 0, 3)
|
||||
|
||||
REG32(SHA_T_STRING, 0x004)
|
||||
|
||||
REG32(SHA_T_LENGTH, 0x008)
|
||||
FIELD(SHA_T_LENGTH, T_LENGTH, 0, 7)
|
||||
|
||||
REG32(SHA_DMA_BLOCK_NUM, 0x00C)
|
||||
FIELD(SHA_DMA_BLOCK_NUM, DMA_BLOCK_NUM, 0, 6)
|
||||
|
||||
REG32(SHA_START, 0x010)
|
||||
FIELD(SHA_START, START, 0, 1)
|
||||
|
||||
REG32(SHA_CONTINUE, 0x014)
|
||||
FIELD(SHA_CONTINUE, CONTINUE, 0, 1)
|
||||
|
||||
REG32(SHA_BUSY, 0x018)
|
||||
FIELD(SHA_BUSY, BUSY_STATE, 0, 1)
|
||||
|
||||
REG32(SHA_DMA_START, 0x01C)
|
||||
FIELD(SHA_DMA_START, DMA_START, 0, 1)
|
||||
|
||||
REG32(SHA_DMA_CONTINUE, 0x020)
|
||||
FIELD(SHA_DMA_CONTINUE, DMA_CONTINUE, 0, 1)
|
||||
|
||||
REG32(SHA_CLEAR_IRQ, 0x024)
|
||||
FIELD(SHA_CLEAR_IRQ, CLEAR_INTERRUPT, 0, 1)
|
||||
|
||||
REG32(SHA_IRQ_ENA, 0x028)
|
||||
FIELD(SHA_IRQ_ENA, INTERRUPT_ENA, 0, 1)
|
||||
|
||||
REG32(SHA_DATE, 0x02C)
|
||||
FIELD(SHA_DATE, DATE, 0, 30)
|
||||
|
||||
REG32(SHA_H_MEM, 0x040)
|
||||
|
||||
REG32(SHA_M_MEM, 0x080)
|
||||
Reference in New Issue
Block a user