Skip to content

Commit

Permalink
hw/misc: implement AES-DMA working mode for ESP32-C3
Browse files Browse the repository at this point in the history
  • Loading branch information
o-marshmallow committed Jun 27, 2023
1 parent 9b36b5d commit a00a687
Show file tree
Hide file tree
Showing 4 changed files with 265 additions and 27 deletions.
249 changes: 233 additions & 16 deletions hw/misc/esp32c3_aes.c
Original file line number Diff line number Diff line change
Expand Up @@ -10,30 +10,209 @@
#include "qemu/osdep.h"
#include "hw/sysbus.h"
#include "hw/misc/esp32c3_aes.h"
#include "crypto/aes.h"
#include "qemu/error-report.h"
#include <gcrypt.h>
#include <endian.h>

This comment has been minimized.

Copy link
@lcgamboa

lcgamboa Jul 2, 2023

@o-marshmallow

the endian.h header it is not portable, it is not possible to compile for Windows OS using MinGW.

This comment has been minimized.

Copy link
@o-marshmallow

o-marshmallow Jul 3, 2023

Author Collaborator

@lcgamboa Thanks for reporting this issue, I overlooked it. I will create an internal issue and fix it

#include "hw/irq.h"
#include "crypto/aes.h"

#define AES_WARNING 0
#define AES_DEBUG 0


static void esp32c3_aes_dma_exit(ESP32C3AesState *s)
{
/* DMA is not supported yet */
(void) s;
s->state_reg = ESP32C3_AES_IDLE;
}

static void* esp32c3_aes_get_buffer(uint32_t size)
{
/* Instead of reallocating a buffer every time, keep a watermark and a single buffer */
static void* buffer = NULL;
static uint32_t buf_size = 0;

static void esp32c3_aes_start(ESP32C3AesState *s)
if (buf_size < size) {
buffer = g_realloc(buffer, size);
buf_size = size;
}

return buffer;
}


/**
* @brief Interpret data in IV memory as a counter and add a block count to its value.
* Used in CTR block mode.
*/
static void esp32c3_aes_ctr_add_counter(ESP32C3AesState *s, uint32_t blocks)
{
AES_KEY aes_key;
/* Check the length of this counter in bits. In both cases, it is stored in BIG-ENDIAN */
if (FIELD_EX32(s->inc_sel_reg, AES_INC_SEL_REG, AES_INC_SEL) == 1) {
/* 128-bit mode, no native 128 integer type, use two 64-bit types */
uint64_t* low_ptr = (uint64_t*) (s->iv_mem + sizeof(uint64_t));
uint64_t* high_ptr = (uint64_t*) s->iv_mem;
const uint64_t original = be64toh(*low_ptr);
uint64_t value = original + blocks;
*low_ptr = htobe64(value);
/* If the value overflowed, we have to update the upper part too */
if (original > value) {
value = be64toh(*high_ptr) + 1;
*high_ptr = htobe64(value);
}
} else {
/* 32-bit mode */
uint32_t* counter_ptr = (uint32_t*) &s->iv_mem[ESP32C3_AES_IV_REG_CNT - sizeof(uint32_t)];
const uint32_t value = be32toh(*counter_ptr) + blocks;
*counter_ptr = htobe32(value);
}
}


/* DMA mode is not supported yet! */
if (FIELD_EX32(s->dma_enable_reg , AES_DMA_ENA_REG, AES_DMA_ENA) != 0) {
error_report("[AES] DMA-AES is not supported yet\n");
static void esp32c3_aes_dma_start(ESP32C3AesState *s)
{
gcry_cipher_hd_t ghandle;
uint32_t gdma_out_idx;
uint32_t gdma_in_idx;

const enum gcry_cipher_modes cipher_map[ESP32C3_AES_CIPHER_COUNT] = {
[ESP32C3_AES_ECB_CIPHER] = GCRY_CIPHER_MODE_ECB,
[ESP32C3_AES_CBC_CIPHER] = GCRY_CIPHER_MODE_CBC,
[ESP32C3_AES_OFB_CIPHER] = GCRY_CIPHER_MODE_OFB,
[ESP32C3_AES_CTR_CIPHER] = GCRY_CIPHER_MODE_CTR,
[ESP32C3_AES_CFB8_CIPHER] = GCRY_CIPHER_MODE_CFB8,
[ESP32C3_AES_CFB128_CIPHER] = GCRY_CIPHER_MODE_CFB,
};

/* Get the block Cipher mode */
const uint32_t cipher_mode = FIELD_EX32(s->block_mode_reg , AES_BLK_MODE_REG, AES_BLOCK_MODE);

/* Check whether we have to encrypt or decrypt */
const uint32_t mode = FIELD_EX32(s->mode_reg , AES_MODE_REG, AES_MODE);
const bool encrypt = (mode == ESP32C3_AES_MODE_128_ENC) || (mode == ESP32C3_AES_MODE_256_ENC);
const bool decrypt = (mode == ESP32C3_AES_MODE_128_DEC) || (mode == ESP32C3_AES_MODE_256_DEC);

/* Get the length, in bits of the key */
const int length = (mode == ESP32C3_AES_MODE_128_ENC || mode == ESP32C3_AES_MODE_128_DEC) ? 128 : 256;
const int algo = length == 128 ? GCRY_CIPHER_AES128 : GCRY_CIPHER_AES256;

if (cipher_mode >= ESP32C3_AES_CIPHER_COUNT) {
error_report("[AES] Invalid or unsupported Cipher block mode!");
return;
} else if (!decrypt && !encrypt) {
error_report("[AES] Invalid mode!");
return;
}

gcry_error_t err = gcry_cipher_open(&ghandle, algo, cipher_map[cipher_mode], 0);
if (err) {
error_report("[AES] error 0x%x when opening cipher", err);
return;
}

/* Cast the keys and data to byte array.
* This can only work as-is if the host computer is has a little-endian CPU. */
const uint8_t* key = (uint8_t*) &s->key;
uint8_t* iv_mem = (uint8_t*) &s->iv_mem;

/* Set the algorithm key */
err = gcry_cipher_setkey(ghandle, key, length / 8);
if (err) {
error_report("[AES] error 0x%x setting key", err);
goto close_exit;
}

/* `iv_mem` field represents the Initialization Vector for CBC/OFB/CFB operations
* But it represents the Initial Counter Block for CTR operation.
* It shall be ignored for ECB block operation. */
if (cipher_mode == ESP32C3_AES_CTR_CIPHER) {
err = gcry_cipher_setctr(ghandle, iv_mem, ESP32C3_AES_IV_REG_CNT);
} else if (cipher_mode != ESP32C3_AES_ECB_CIPHER) {
err = gcry_cipher_setiv(ghandle, iv_mem, ESP32C3_AES_IV_REG_CNT);
}

if (err) {
error_report("[AES] error 0x%x setting IV memory", err);
goto close_exit;
}

/* Get the GDMA input channel index for AES peripheral */
assert(s->gdma != NULL);

if ( !esp32c3_gdma_get_channel_periph(s->gdma, GDMA_AES, ESP32C3_GDMA_OUT_IDX, &gdma_out_idx) ||
!esp32c3_gdma_get_channel_periph(s->gdma, GDMA_AES, ESP32C3_GDMA_IN_IDX, &gdma_in_idx) ) {
warn_report("[AES] GDMA requested but no properly configured channel found");
goto close_exit;
}

/* Block number represents the number of 128-bit (16-byte) blocks to encrypt.
* If block_num_reg is 100, we have to encrypt 100*128/8 = 1600 bytes */
uint32_t buf_size = s->block_num_reg * 16;
uint8_t* buffer = esp32c3_aes_get_buffer(buf_size);

if ( !esp32c3_gdma_read_channel(s->gdma, gdma_out_idx, buffer, buf_size) ) {
warn_report("[AES] Error reading from GDMA buffer");
goto close_exit;
}

/* Reading was successful, process the buffer (encrypt/decrypt) and write back to the GDMA OUT buffer */
if (encrypt) {
err = gcry_cipher_encrypt(ghandle, buffer, buf_size, NULL, 0);

if (cipher_mode != ESP32C3_AES_CTR_CIPHER) {
/* On the real hardware, IV memory is used in-place for encrypting data, so copy the last encrypted block to IV memory */
memcpy(iv_mem, buffer + buf_size - 16, ESP32C3_AES_IV_REG_CNT);
}
} else {
/* Store the last block of plaintext, needed for OFB */
const uint8_t* buffer_last_block = buffer + buf_size - ESP32C3_AES_IV_REG_CNT;
uint8_t plaintext[ESP32C3_AES_IV_REG_CNT];
memcpy(plaintext, buffer_last_block, ESP32C3_AES_IV_REG_CNT);

/* The IV memory is initalized with the encrypted data, so do the copy now */
if (cipher_mode != ESP32C3_AES_OFB_CIPHER && cipher_mode != ESP32C3_AES_CTR_CIPHER) {
memcpy(iv_mem, buffer + buf_size - 16, ESP32C3_AES_IV_REG_CNT);
}

err = gcry_cipher_decrypt(ghandle, buffer, buf_size, NULL, 0);

/* For OFB, it is done after the decryption. Moreover, the hardware XOR the original plaintext with the output
* and stores the result in IV memory. */
if (cipher_mode == ESP32C3_AES_OFB_CIPHER) {
for (int i = 0; i < ESP32C3_AES_IV_REG_CNT; i++) {
iv_mem[i] = buffer_last_block[i] ^ plaintext[i];
}
}
}

if (cipher_mode == ESP32C3_AES_CTR_CIPHER) {
esp32c3_aes_ctr_add_counter(s, s->block_num_reg);
}

if (err) {
error_report("[AES] error processing memory");
goto close_exit;
}

if ( !esp32c3_gdma_write_channel(s->gdma, gdma_in_idx, buffer, buf_size) ) {
warn_report("[AES] Error writing to GDMA buffer");
goto close_exit;
}

s->state_reg = ESP32C3_AES_DONE;

if (s->int_ena_reg) {
qemu_irq_raise(s->irq);
}

close_exit:
gcry_cipher_close(ghandle);
}


static void esp32c3_aes_start(ESP32C3AesState *s)
{
AES_KEY aes_key;

/* Check whether we have to encrypt or decrypt */
const uint32_t mode = FIELD_EX32(s->mode_reg , AES_MODE_REG, AES_MODE);
const bool encrypt = (mode == ESP32C3_AES_MODE_128_ENC) || (mode == ESP32C3_AES_MODE_256_ENC);
Expand Down Expand Up @@ -68,6 +247,10 @@ static uint64_t esp32c3_aes_read(void *opaque, hwaddr addr, unsigned int size)
ESP32C3AesState *s = ESP32C3_AES(opaque);
uint64_t r = 0;

/* At the moment, make the assumption that we always write a 32-bit word, except for IV memory */
assert((addr >= A_AES_IV_MEM_0_REG && addr <= A_AES_IV_MEM_15_REG) ||
size == sizeof(uint32_t));

switch (addr) {
case A_AES_KEY_0_REG ... A_AES_KEY_7_REG:
r = s->key[(addr - A_AES_KEY_0_REG) / sizeof(uint32_t)];
Expand All @@ -82,7 +265,13 @@ static uint64_t esp32c3_aes_read(void *opaque, hwaddr addr, unsigned int size)
break;

case A_AES_IV_MEM_0_REG ... A_AES_IV_MEM_15_REG:
r = s->iv_mem[(addr - A_AES_IV_MEM_0_REG) / sizeof(uint32_t)];
/* Use r as the offset */
r = addr - A_AES_IV_MEM_0_REG;
if (size == sizeof(uint32_t)) {
r = *((uint32_t*) (s->iv_mem + r));
} else if (size == sizeof(uint8_t)) {
r = s->iv_mem[r];
}
break;

case A_AES_STATE_REG:
Expand Down Expand Up @@ -116,13 +305,13 @@ static uint64_t esp32c3_aes_read(void *opaque, hwaddr addr, unsigned int size)
default:
#if AES_WARNING
/* Other registers are not supported yet */
warn_report("[AES] Unsupported read to %08lx\n", addr);
warn_report("[AES] Unsupported read to %08lx", addr);
#endif
break;
}

#if AES_DEBUG
info_report("[AES] Reading from %08lx (%08lx)\n", addr, r);
info_report("[AES] Reading from %08lx (%08lx)", addr, r);
#endif


Expand All @@ -134,6 +323,11 @@ static void esp32c3_aes_write(void *opaque, hwaddr addr,
uint64_t value, unsigned int size)
{
ESP32C3AesState *s = ESP32C3_AES(opaque);
uint32_t offset = 0;

/* At the moment, make the assumption that we always write a 32-bit word, except for IV memory */
assert((addr >= A_AES_IV_MEM_0_REG && addr <= A_AES_IV_MEM_15_REG) ||
size == sizeof(uint32_t));

switch (addr) {
case A_AES_KEY_0_REG ... A_AES_KEY_7_REG:
Expand All @@ -145,7 +339,12 @@ static void esp32c3_aes_write(void *opaque, hwaddr addr,
break;

case A_AES_IV_MEM_0_REG ... A_AES_IV_MEM_15_REG:
s->iv_mem[(addr - A_AES_IV_MEM_0_REG) / sizeof(uint32_t)] = value;
offset = addr - A_AES_IV_MEM_0_REG;
if (size == sizeof(uint32_t)) {
*((uint32_t*) (s->iv_mem + offset)) = value;
} else if (size == sizeof(uint8_t)) {
s->iv_mem[offset] = value & 0xff;
}
break;

case A_AES_MODE_REG:
Expand All @@ -154,7 +353,12 @@ static void esp32c3_aes_write(void *opaque, hwaddr addr,

case A_AES_TRIGGER_REG:
if (FIELD_EX32(value, AES_TRIGGER_REG, AES_TRIGGER)) {
esp32c3_aes_start(s);
/* DMA mode is different than "regular" mode */
if (FIELD_EX32(s->dma_enable_reg , AES_DMA_ENA_REG, AES_DMA_ENA) != 0) {
esp32c3_aes_dma_start(s);
} else {
esp32c3_aes_start(s);
}
}
break;

Expand All @@ -176,7 +380,7 @@ static void esp32c3_aes_write(void *opaque, hwaddr addr,

case A_AES_INT_CLR_REG:
if (FIELD_EX32(value, AES_INT_CLR_REG, AES_INT_CLR)) {
s->int_st = 0;
qemu_irq_lower(s->irq);
}
break;

Expand All @@ -191,13 +395,13 @@ static void esp32c3_aes_write(void *opaque, hwaddr addr,
default:
#if AES_WARNING
/* Other registers are not supported yet */
warn_report("[AES] Unsupported write to %08lx (%08lx)\n", addr, value);
warn_report("[AES] Unsupported write to %08lx (%08lx)", addr, value);
#endif
break;
}

#if AES_DEBUG
info_report("[AES] Writing to %08lx (%08lx)\n", addr, value);
info_report("[AES] Writing to %08lx (%08lx)", addr, value);
#endif

}
Expand All @@ -224,6 +428,16 @@ static void esp32c3_aes_reset(DeviceState *dev)
s->int_ena_reg = 0;
}

static void esp32c3_aes_realize(DeviceState *dev, Error **errp)
{
ESP32C3AesState *s = ESP32C3_AES(dev);

/* Make sure GDMA was set of issue an error */
if (s->gdma == NULL) {
error_report("[AES] GDMA controller must be set!");
}
}

static void esp32c3_aes_init(Object *obj)
{
ESP32C3AesState *s = ESP32C3_AES(obj);
Expand All @@ -232,12 +446,15 @@ static void esp32c3_aes_init(Object *obj)
memory_region_init_io(&s->iomem, obj, &esp32c3_aes_ops, s,
TYPE_ESP32C3_AES, ESP32C3_AES_REGS_SIZE);
sysbus_init_mmio(sbd, &s->iomem);

sysbus_init_irq(sbd, &s->irq);
}

static void esp32_aes_class_init(ObjectClass *klass, void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);

dc->realize = esp32c3_aes_realize;
dc->reset = esp32c3_aes_reset;
}

Expand Down
6 changes: 4 additions & 2 deletions hw/misc/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -140,14 +140,16 @@ softmmu_ss.add(when: 'CONFIG_RISCV_ESP32C3', if_true: files(
'esp32c3_cache.c',
'esp32c3_sha.c',
'esp32c3_jtag.c',
'esp32c3_rtc_cntl.c',
'esp32c3_aes.c'
'esp32c3_rtc_cntl.c'
))

if gcrypt.found()
softmmu_ss.add(when: [gcrypt, 'CONFIG_XTENSA_ESP32'], if_true: files(
'esp32_rsa.c',
))
softmmu_ss.add(when: [gcrypt, 'CONFIG_RISCV_ESP32C3'], if_true: files(
'esp32c3_aes.c',
))
endif

softmmu_ss.add(when: 'CONFIG_GRLIB', if_true: files('grlib_ahb_apb_pnp.c'))
Expand Down
Loading

0 comments on commit a00a687

Please sign in to comment.