mirror of https://gitlab.com/qemu-project/qemu
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
582 lines
15 KiB
C
582 lines
15 KiB
C
/*
|
|
* QEMU Apple AES device emulation
|
|
*
|
|
* Copyright © 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
*
|
|
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
|
* See the COPYING file in the top-level directory.
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later
|
|
*/
|
|
|
|
#include "qemu/osdep.h"
|
|
#include "trace.h"
|
|
#include "crypto/hash.h"
|
|
#include "crypto/aes.h"
|
|
#include "crypto/cipher.h"
|
|
#include "hw/irq.h"
|
|
#include "hw/sysbus.h"
|
|
#include "hw/vmapple/vmapple.h"
|
|
#include "migration/vmstate.h"
|
|
#include "qemu/cutils.h"
|
|
#include "qemu/log.h"
|
|
#include "qemu/module.h"
|
|
#include "system/dma.h"
|
|
|
|
OBJECT_DECLARE_SIMPLE_TYPE(AESState, APPLE_AES)
|
|
|
|
#define MAX_FIFO_SIZE 9
|
|
|
|
#define CMD_KEY 0x1
|
|
#define CMD_KEY_CONTEXT_SHIFT 27
|
|
#define CMD_KEY_CONTEXT_MASK (0x1 << CMD_KEY_CONTEXT_SHIFT)
|
|
#define CMD_KEY_SELECT_MAX_IDX 0x7
|
|
#define CMD_KEY_SELECT_SHIFT 24
|
|
#define CMD_KEY_SELECT_MASK (CMD_KEY_SELECT_MAX_IDX << CMD_KEY_SELECT_SHIFT)
|
|
#define CMD_KEY_KEY_LEN_NUM 4u
|
|
#define CMD_KEY_KEY_LEN_SHIFT 22
|
|
#define CMD_KEY_KEY_LEN_MASK ((CMD_KEY_KEY_LEN_NUM - 1u) << CMD_KEY_KEY_LEN_SHIFT)
|
|
#define CMD_KEY_ENCRYPT_SHIFT 20
|
|
#define CMD_KEY_ENCRYPT_MASK (0x1 << CMD_KEY_ENCRYPT_SHIFT)
|
|
#define CMD_KEY_BLOCK_MODE_SHIFT 16
|
|
#define CMD_KEY_BLOCK_MODE_MASK (0x3 << CMD_KEY_BLOCK_MODE_SHIFT)
|
|
#define CMD_IV 0x2
|
|
#define CMD_IV_CONTEXT_SHIFT 26
|
|
#define CMD_IV_CONTEXT_MASK (0x3 << CMD_KEY_CONTEXT_SHIFT)
|
|
#define CMD_DSB 0x3
|
|
#define CMD_SKG 0x4
|
|
#define CMD_DATA 0x5
|
|
#define CMD_DATA_KEY_CTX_SHIFT 27
|
|
#define CMD_DATA_KEY_CTX_MASK (0x1 << CMD_DATA_KEY_CTX_SHIFT)
|
|
#define CMD_DATA_IV_CTX_SHIFT 25
|
|
#define CMD_DATA_IV_CTX_MASK (0x3 << CMD_DATA_IV_CTX_SHIFT)
|
|
#define CMD_DATA_LEN_MASK 0xffffff
|
|
#define CMD_STORE_IV 0x6
|
|
#define CMD_STORE_IV_ADDR_MASK 0xffffff
|
|
#define CMD_WRITE_REG 0x7
|
|
#define CMD_FLAG 0x8
|
|
#define CMD_FLAG_STOP_MASK BIT(26)
|
|
#define CMD_FLAG_RAISE_IRQ_MASK BIT(27)
|
|
#define CMD_FLAG_INFO_MASK 0xff
|
|
#define CMD_MAX 0x10
|
|
|
|
#define CMD_SHIFT 28
|
|
|
|
#define REG_STATUS 0xc
|
|
#define REG_STATUS_DMA_READ_RUNNING BIT(0)
|
|
#define REG_STATUS_DMA_READ_PENDING BIT(1)
|
|
#define REG_STATUS_DMA_WRITE_RUNNING BIT(2)
|
|
#define REG_STATUS_DMA_WRITE_PENDING BIT(3)
|
|
#define REG_STATUS_BUSY BIT(4)
|
|
#define REG_STATUS_EXECUTING BIT(5)
|
|
#define REG_STATUS_READY BIT(6)
|
|
#define REG_STATUS_TEXT_DPA_SEEDED BIT(7)
|
|
#define REG_STATUS_UNWRAP_DPA_SEEDED BIT(8)
|
|
|
|
#define REG_IRQ_STATUS 0x18
|
|
#define REG_IRQ_STATUS_INVALID_CMD BIT(2)
|
|
#define REG_IRQ_STATUS_FLAG BIT(5)
|
|
#define REG_IRQ_ENABLE 0x1c
|
|
#define REG_WATERMARK 0x20
|
|
#define REG_Q_STATUS 0x24
|
|
#define REG_FLAG_INFO 0x30
|
|
#define REG_FIFO 0x200
|
|
|
|
static const uint32_t key_lens[CMD_KEY_KEY_LEN_NUM] = {
|
|
[0] = 16,
|
|
[1] = 24,
|
|
[2] = 32,
|
|
[3] = 64,
|
|
};
|
|
|
|
typedef struct Key {
|
|
uint32_t key_len;
|
|
uint8_t key[32];
|
|
} Key;
|
|
|
|
typedef struct IV {
|
|
uint32_t iv[4];
|
|
} IV;
|
|
|
|
static Key builtin_keys[CMD_KEY_SELECT_MAX_IDX + 1] = {
|
|
[1] = {
|
|
.key_len = 32,
|
|
.key = { 0x1 },
|
|
},
|
|
[2] = {
|
|
.key_len = 32,
|
|
.key = { 0x2 },
|
|
},
|
|
[3] = {
|
|
.key_len = 32,
|
|
.key = { 0x3 },
|
|
}
|
|
};
|
|
|
|
struct AESState {
|
|
SysBusDevice parent_obj;
|
|
|
|
qemu_irq irq;
|
|
MemoryRegion iomem1;
|
|
MemoryRegion iomem2;
|
|
AddressSpace *as;
|
|
|
|
uint32_t status;
|
|
uint32_t q_status;
|
|
uint32_t irq_status;
|
|
uint32_t irq_enable;
|
|
uint32_t watermark;
|
|
uint32_t flag_info;
|
|
uint32_t fifo[MAX_FIFO_SIZE];
|
|
uint32_t fifo_idx;
|
|
Key key[2];
|
|
IV iv[4];
|
|
bool is_encrypt;
|
|
QCryptoCipherMode block_mode;
|
|
};
|
|
|
|
static void aes_update_irq(AESState *s)
|
|
{
|
|
qemu_set_irq(s->irq, !!(s->irq_status & s->irq_enable));
|
|
}
|
|
|
|
static uint64_t aes1_read(void *opaque, hwaddr offset, unsigned size)
|
|
{
|
|
AESState *s = opaque;
|
|
uint64_t res = 0;
|
|
|
|
switch (offset) {
|
|
case REG_STATUS:
|
|
res = s->status;
|
|
break;
|
|
case REG_IRQ_STATUS:
|
|
res = s->irq_status;
|
|
break;
|
|
case REG_IRQ_ENABLE:
|
|
res = s->irq_enable;
|
|
break;
|
|
case REG_WATERMARK:
|
|
res = s->watermark;
|
|
break;
|
|
case REG_Q_STATUS:
|
|
res = s->q_status;
|
|
break;
|
|
case REG_FLAG_INFO:
|
|
res = s->flag_info;
|
|
break;
|
|
|
|
default:
|
|
qemu_log_mask(LOG_UNIMP, "%s: Unknown AES MMIO offset %" PRIx64 "\n",
|
|
__func__, offset);
|
|
break;
|
|
}
|
|
|
|
trace_aes_read(offset, res);
|
|
|
|
return res;
|
|
}
|
|
|
|
static void fifo_append(AESState *s, uint64_t val)
|
|
{
|
|
if (s->fifo_idx == MAX_FIFO_SIZE) {
|
|
/* Exceeded the FIFO. Bail out */
|
|
return;
|
|
}
|
|
|
|
s->fifo[s->fifo_idx++] = val;
|
|
}
|
|
|
|
static bool has_payload(AESState *s, uint32_t elems)
|
|
{
|
|
return s->fifo_idx >= elems + 1;
|
|
}
|
|
|
|
static bool cmd_key(AESState *s)
|
|
{
|
|
uint32_t cmd = s->fifo[0];
|
|
uint32_t key_select = (cmd & CMD_KEY_SELECT_MASK) >> CMD_KEY_SELECT_SHIFT;
|
|
uint32_t ctxt = (cmd & CMD_KEY_CONTEXT_MASK) >> CMD_KEY_CONTEXT_SHIFT;
|
|
uint32_t key_len;
|
|
|
|
switch ((cmd & CMD_KEY_BLOCK_MODE_MASK) >> CMD_KEY_BLOCK_MODE_SHIFT) {
|
|
case 0:
|
|
s->block_mode = QCRYPTO_CIPHER_MODE_ECB;
|
|
break;
|
|
case 1:
|
|
s->block_mode = QCRYPTO_CIPHER_MODE_CBC;
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
s->is_encrypt = cmd & CMD_KEY_ENCRYPT_MASK;
|
|
key_len = key_lens[(cmd & CMD_KEY_KEY_LEN_MASK) >> CMD_KEY_KEY_LEN_SHIFT];
|
|
|
|
if (key_select) {
|
|
trace_aes_cmd_key_select_builtin(ctxt, key_select,
|
|
s->is_encrypt ? "en" : "de",
|
|
QCryptoCipherMode_str(s->block_mode));
|
|
s->key[ctxt] = builtin_keys[key_select];
|
|
} else {
|
|
trace_aes_cmd_key_select_new(ctxt, key_len,
|
|
s->is_encrypt ? "en" : "de",
|
|
QCryptoCipherMode_str(s->block_mode));
|
|
if (key_len > sizeof(s->key[ctxt].key)) {
|
|
return false;
|
|
}
|
|
if (!has_payload(s, key_len / sizeof(uint32_t))) {
|
|
/* wait for payload */
|
|
qemu_log_mask(LOG_GUEST_ERROR, "%s: No payload\n", __func__);
|
|
return false;
|
|
}
|
|
memcpy(&s->key[ctxt].key, &s->fifo[1], key_len);
|
|
s->key[ctxt].key_len = key_len;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool cmd_iv(AESState *s)
|
|
{
|
|
uint32_t cmd = s->fifo[0];
|
|
uint32_t ctxt = (cmd & CMD_IV_CONTEXT_MASK) >> CMD_IV_CONTEXT_SHIFT;
|
|
|
|
if (!has_payload(s, 4)) {
|
|
/* wait for payload */
|
|
return false;
|
|
}
|
|
memcpy(&s->iv[ctxt].iv, &s->fifo[1], sizeof(s->iv[ctxt].iv));
|
|
trace_aes_cmd_iv(ctxt, s->fifo[1], s->fifo[2], s->fifo[3], s->fifo[4]);
|
|
|
|
return true;
|
|
}
|
|
|
|
static void dump_data(const char *desc, const void *p, size_t len)
|
|
{
|
|
static const size_t MAX_LEN = 0x1000;
|
|
char hex[MAX_LEN * 2 + 1] = "";
|
|
|
|
if (len > MAX_LEN) {
|
|
return;
|
|
}
|
|
|
|
qemu_hexdump_to_buffer(hex, sizeof(hex), p, len);
|
|
trace_aes_dump_data(desc, hex);
|
|
}
|
|
|
|
static bool cmd_data(AESState *s)
|
|
{
|
|
uint32_t cmd = s->fifo[0];
|
|
uint32_t ctxt_iv = 0;
|
|
uint32_t ctxt_key = (cmd & CMD_DATA_KEY_CTX_MASK) >> CMD_DATA_KEY_CTX_SHIFT;
|
|
uint32_t len = cmd & CMD_DATA_LEN_MASK;
|
|
uint64_t src_addr = s->fifo[2];
|
|
uint64_t dst_addr = s->fifo[3];
|
|
QCryptoCipherAlgo alg;
|
|
g_autoptr(QCryptoCipher) cipher = NULL;
|
|
g_autoptr(GByteArray) src = NULL;
|
|
g_autoptr(GByteArray) dst = NULL;
|
|
MemTxResult r;
|
|
|
|
src_addr |= ((uint64_t)s->fifo[1] << 16) & 0xffff00000000ULL;
|
|
dst_addr |= ((uint64_t)s->fifo[1] << 32) & 0xffff00000000ULL;
|
|
|
|
trace_aes_cmd_data(ctxt_key, ctxt_iv, src_addr, dst_addr, len);
|
|
|
|
if (!has_payload(s, 3)) {
|
|
/* wait for payload */
|
|
qemu_log_mask(LOG_GUEST_ERROR, "%s: No payload\n", __func__);
|
|
return false;
|
|
}
|
|
|
|
if (ctxt_key >= ARRAY_SIZE(s->key) ||
|
|
ctxt_iv >= ARRAY_SIZE(s->iv)) {
|
|
qemu_log_mask(LOG_GUEST_ERROR, "%s: Invalid key or iv\n", __func__);
|
|
return false;
|
|
}
|
|
|
|
src = g_byte_array_sized_new(len);
|
|
g_byte_array_set_size(src, len);
|
|
dst = g_byte_array_sized_new(len);
|
|
g_byte_array_set_size(dst, len);
|
|
|
|
r = dma_memory_read(s->as, src_addr, src->data, len, MEMTXATTRS_UNSPECIFIED);
|
|
if (r != MEMTX_OK) {
|
|
qemu_log_mask(LOG_GUEST_ERROR, "%s: DMA read of %"PRIu32" bytes "
|
|
"from 0x%"PRIx64" failed. (r=%d)\n",
|
|
__func__, len, src_addr, r);
|
|
return false;
|
|
}
|
|
|
|
dump_data("cmd_data(): src_data=", src->data, len);
|
|
|
|
switch (s->key[ctxt_key].key_len) {
|
|
case 128 / 8:
|
|
alg = QCRYPTO_CIPHER_ALGO_AES_128;
|
|
break;
|
|
case 192 / 8:
|
|
alg = QCRYPTO_CIPHER_ALGO_AES_192;
|
|
break;
|
|
case 256 / 8:
|
|
alg = QCRYPTO_CIPHER_ALGO_AES_256;
|
|
break;
|
|
default:
|
|
qemu_log_mask(LOG_GUEST_ERROR, "%s: Invalid key length\n", __func__);
|
|
return false;
|
|
}
|
|
cipher = qcrypto_cipher_new(alg, s->block_mode,
|
|
s->key[ctxt_key].key,
|
|
s->key[ctxt_key].key_len, NULL);
|
|
if (!cipher) {
|
|
qemu_log_mask(LOG_GUEST_ERROR, "%s: Failed to create cipher object\n",
|
|
__func__);
|
|
return false;
|
|
}
|
|
if (s->block_mode != QCRYPTO_CIPHER_MODE_ECB) {
|
|
if (qcrypto_cipher_setiv(cipher, (void *)s->iv[ctxt_iv].iv,
|
|
sizeof(s->iv[ctxt_iv].iv), NULL) != 0) {
|
|
qemu_log_mask(LOG_GUEST_ERROR, "%s: Failed to set IV\n", __func__);
|
|
return false;
|
|
}
|
|
}
|
|
if (s->is_encrypt) {
|
|
if (qcrypto_cipher_encrypt(cipher, src->data, dst->data, len, NULL) != 0) {
|
|
qemu_log_mask(LOG_GUEST_ERROR, "%s: Encryption failed\n", __func__);
|
|
return false;
|
|
}
|
|
} else {
|
|
if (qcrypto_cipher_decrypt(cipher, src->data, dst->data, len, NULL) != 0) {
|
|
qemu_log_mask(LOG_GUEST_ERROR, "%s: Decryption failed\n", __func__);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
dump_data("cmd_data(): dst_data=", dst->data, len);
|
|
r = dma_memory_write(s->as, dst_addr, dst->data, len, MEMTXATTRS_UNSPECIFIED);
|
|
if (r != MEMTX_OK) {
|
|
qemu_log_mask(LOG_GUEST_ERROR, "%s: DMA write of %"PRIu32" bytes "
|
|
"to 0x%"PRIx64" failed. (r=%d)\n",
|
|
__func__, len, src_addr, r);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool cmd_store_iv(AESState *s)
|
|
{
|
|
uint32_t cmd = s->fifo[0];
|
|
uint32_t ctxt = (cmd & CMD_IV_CONTEXT_MASK) >> CMD_IV_CONTEXT_SHIFT;
|
|
uint64_t addr = s->fifo[1];
|
|
MemTxResult dma_result;
|
|
|
|
if (!has_payload(s, 1)) {
|
|
qemu_log_mask(LOG_GUEST_ERROR, "%s: No payload\n", __func__);
|
|
return false;
|
|
}
|
|
|
|
if (ctxt >= ARRAY_SIZE(s->iv)) {
|
|
qemu_log_mask(LOG_GUEST_ERROR,
|
|
"%s: Invalid context. ctxt = %u, allowed: 0..%zu\n",
|
|
__func__, ctxt, ARRAY_SIZE(s->iv) - 1);
|
|
return false;
|
|
}
|
|
|
|
addr |= ((uint64_t)cmd << 32) & 0xff00000000ULL;
|
|
dma_result = dma_memory_write(&address_space_memory, addr,
|
|
&s->iv[ctxt].iv, sizeof(s->iv[ctxt].iv),
|
|
MEMTXATTRS_UNSPECIFIED);
|
|
|
|
trace_aes_cmd_store_iv(ctxt, addr, s->iv[ctxt].iv[0], s->iv[ctxt].iv[1],
|
|
s->iv[ctxt].iv[2], s->iv[ctxt].iv[3]);
|
|
|
|
return dma_result == MEMTX_OK;
|
|
}
|
|
|
|
static bool cmd_flag(AESState *s)
|
|
{
|
|
uint32_t cmd = s->fifo[0];
|
|
uint32_t raise_irq = cmd & CMD_FLAG_RAISE_IRQ_MASK;
|
|
|
|
/* We always process data when it's coming in, so fire an IRQ immediately */
|
|
if (raise_irq) {
|
|
s->irq_status |= REG_IRQ_STATUS_FLAG;
|
|
}
|
|
|
|
s->flag_info = cmd & CMD_FLAG_INFO_MASK;
|
|
|
|
trace_aes_cmd_flag(!!raise_irq, s->flag_info);
|
|
|
|
return true;
|
|
}
|
|
|
|
static void fifo_process(AESState *s)
|
|
{
|
|
uint32_t cmd = s->fifo[0] >> CMD_SHIFT;
|
|
bool success = false;
|
|
|
|
if (!s->fifo_idx) {
|
|
return;
|
|
}
|
|
|
|
switch (cmd) {
|
|
case CMD_KEY:
|
|
success = cmd_key(s);
|
|
break;
|
|
case CMD_IV:
|
|
success = cmd_iv(s);
|
|
break;
|
|
case CMD_DATA:
|
|
success = cmd_data(s);
|
|
break;
|
|
case CMD_STORE_IV:
|
|
success = cmd_store_iv(s);
|
|
break;
|
|
case CMD_FLAG:
|
|
success = cmd_flag(s);
|
|
break;
|
|
default:
|
|
s->irq_status |= REG_IRQ_STATUS_INVALID_CMD;
|
|
break;
|
|
}
|
|
|
|
if (success) {
|
|
s->fifo_idx = 0;
|
|
}
|
|
|
|
trace_aes_fifo_process(cmd, success);
|
|
}
|
|
|
|
static void aes1_write(void *opaque, hwaddr offset, uint64_t val, unsigned size)
|
|
{
|
|
AESState *s = opaque;
|
|
|
|
trace_aes_write(offset, val);
|
|
|
|
switch (offset) {
|
|
case REG_IRQ_STATUS:
|
|
s->irq_status &= ~val;
|
|
break;
|
|
case REG_IRQ_ENABLE:
|
|
s->irq_enable = val;
|
|
break;
|
|
case REG_FIFO:
|
|
fifo_append(s, val);
|
|
fifo_process(s);
|
|
break;
|
|
default:
|
|
qemu_log_mask(LOG_UNIMP,
|
|
"%s: Unknown AES MMIO offset %"PRIx64", data %"PRIx64"\n",
|
|
__func__, offset, val);
|
|
return;
|
|
}
|
|
|
|
aes_update_irq(s);
|
|
}
|
|
|
|
static const MemoryRegionOps aes1_ops = {
|
|
.read = aes1_read,
|
|
.write = aes1_write,
|
|
.endianness = DEVICE_NATIVE_ENDIAN,
|
|
.valid = {
|
|
.min_access_size = 4,
|
|
.max_access_size = 8,
|
|
},
|
|
.impl = {
|
|
.min_access_size = 4,
|
|
.max_access_size = 4,
|
|
},
|
|
};
|
|
|
|
static uint64_t aes2_read(void *opaque, hwaddr offset, unsigned size)
|
|
{
|
|
uint64_t res = 0;
|
|
|
|
switch (offset) {
|
|
case 0:
|
|
res = 0;
|
|
break;
|
|
default:
|
|
qemu_log_mask(LOG_UNIMP,
|
|
"%s: Unknown AES MMIO 2 offset %"PRIx64"\n",
|
|
__func__, offset);
|
|
break;
|
|
}
|
|
|
|
trace_aes_2_read(offset, res);
|
|
|
|
return res;
|
|
}
|
|
|
|
static void aes2_write(void *opaque, hwaddr offset, uint64_t val, unsigned size)
|
|
{
|
|
trace_aes_2_write(offset, val);
|
|
|
|
switch (offset) {
|
|
default:
|
|
qemu_log_mask(LOG_UNIMP,
|
|
"%s: Unknown AES MMIO 2 offset %"PRIx64", data %"PRIx64"\n",
|
|
__func__, offset, val);
|
|
return;
|
|
}
|
|
}
|
|
|
|
static const MemoryRegionOps aes2_ops = {
|
|
.read = aes2_read,
|
|
.write = aes2_write,
|
|
.endianness = DEVICE_NATIVE_ENDIAN,
|
|
.valid = {
|
|
.min_access_size = 4,
|
|
.max_access_size = 8,
|
|
},
|
|
.impl = {
|
|
.min_access_size = 4,
|
|
.max_access_size = 4,
|
|
},
|
|
};
|
|
|
|
static void aes_reset(Object *obj, ResetType type)
|
|
{
|
|
AESState *s = APPLE_AES(obj);
|
|
|
|
s->status = 0x3f80;
|
|
s->q_status = 2;
|
|
s->irq_status = 0;
|
|
s->irq_enable = 0;
|
|
s->watermark = 0;
|
|
}
|
|
|
|
static void aes_init(Object *obj)
|
|
{
|
|
AESState *s = APPLE_AES(obj);
|
|
|
|
memory_region_init_io(&s->iomem1, obj, &aes1_ops, s, TYPE_APPLE_AES, 0x4000);
|
|
memory_region_init_io(&s->iomem2, obj, &aes2_ops, s, TYPE_APPLE_AES, 0x4000);
|
|
sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->iomem1);
|
|
sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->iomem2);
|
|
sysbus_init_irq(SYS_BUS_DEVICE(s), &s->irq);
|
|
s->as = &address_space_memory;
|
|
}
|
|
|
|
static void aes_class_init(ObjectClass *klass, void *data)
|
|
{
|
|
ResettableClass *rc = RESETTABLE_CLASS(klass);
|
|
|
|
rc->phases.hold = aes_reset;
|
|
}
|
|
|
|
static const TypeInfo aes_info = {
|
|
.name = TYPE_APPLE_AES,
|
|
.parent = TYPE_SYS_BUS_DEVICE,
|
|
.instance_size = sizeof(AESState),
|
|
.class_init = aes_class_init,
|
|
.instance_init = aes_init,
|
|
};
|
|
|
|
static void aes_register_types(void)
|
|
{
|
|
type_register_static(&aes_info);
|
|
}
|
|
|
|
type_init(aes_register_types)
|