You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1009 lines
27 KiB
1009 lines
27 KiB
/* |
|
* Copyright (c) 2023 Renesas Electronics Corporation |
|
* |
|
* SPDX-License-Identifier: Apache-2.0 |
|
*/ |
|
|
|
#include <zephyr/kernel.h> |
|
#include <zephyr/device.h> |
|
#include <zephyr/crypto/crypto.h> |
|
#include <zephyr/irq.h> |
|
#include <DA1469xAB.h> |
|
#include <da1469x_config.h> |
|
#include <da1469x_otp.h> |
|
#include <system_DA1469x.h> |
|
#include <da1469x_pd.h> |
|
#include <zephyr/sys/byteorder.h> |
|
#include <zephyr/pm/device.h> |
|
#include <zephyr/pm/policy.h> |
|
#include <zephyr/logging/log.h> |
|
|
|
LOG_MODULE_REGISTER(crypto_smartbond_crypto, CONFIG_CRYPTO_LOG_LEVEL); |
|
|
|
#define DT_DRV_COMPAT renesas_smartbond_crypto |
|
|
|
#define SMARTBOND_IRQN DT_INST_IRQN(0) |
|
#define SMARTBOND_IRQ_PRIO DT_INST_IRQ(0, priority) |
|
|
|
#if defined(CONFIG_CRYPTO_ASYNC) |
|
#define CRYPTO_HW_CAPS (CAP_RAW_KEY | CAP_SEPARATE_IO_BUFS | CAP_ASYNC_OPS | CAP_NO_IV_PREFIX) |
|
#else |
|
#define CRYPTO_HW_CAPS (CAP_RAW_KEY | CAP_SEPARATE_IO_BUFS | CAP_SYNC_OPS | CAP_NO_IV_PREFIX) |
|
#endif |
|
|
|
#define SWAP32(_w) __REV(_w) |
|
|
|
#define CRYPTO_CTRL_REG_SET(_field, _val) \ |
|
AES_HASH->CRYPTO_CTRL_REG = \ |
|
(AES_HASH->CRYPTO_CTRL_REG & ~AES_HASH_CRYPTO_CTRL_REG_ ## _field ## _Msk) | \ |
|
((_val) << AES_HASH_CRYPTO_CTRL_REG_ ## _field ## _Pos) |
|
|
|
#define CRYPTO_CTRL_REG_GET(_field) \ |
|
((AES_HASH->CRYPTO_CTRL_REG & AES_HASH_CRYPTO_CTRL_REG_ ## _field ## _Msk) >> \ |
|
AES_HASH_CRYPTO_CTRL_REG_ ## _field ## _Pos) |
|
|
|
|
|
struct crypto_smartbond_data { |
|
/* |
|
* Semaphore to provide mutual exlusion when a crypto session is requested. |
|
*/ |
|
struct k_sem session_sem; |
|
|
|
/* |
|
* Semaphore to provide mutual exlusion when a cryptographic task is requested. |
|
* (a session should be requested at this point). |
|
*/ |
|
struct k_sem device_sem; |
|
#if defined(CONFIG_CRYPTO_ASYNC) |
|
/* |
|
* User-defined callbacks to be called upon completion of asynchronous |
|
* cryptographic operations. Note that the AES and HASH modes can work |
|
* complementary to each other. |
|
*/ |
|
union { |
|
cipher_completion_cb cipher_user_cb; |
|
hash_completion_cb hash_user_cb; |
|
}; |
|
|
|
/* |
|
* Packet context should be stored during a session so that can be rertieved |
|
* from within the crypto engine ISR context. |
|
*/ |
|
union { |
|
struct cipher_pkt *cipher_pkt; |
|
struct hash_pkt *hash_pkt; |
|
}; |
|
#else |
|
/* |
|
* Semaphore used to block for as long as a synchronous cryptographic operation |
|
* is in progress. |
|
*/ |
|
struct k_sem sync_sem; |
|
#endif |
|
}; |
|
|
|
/* |
|
* Status flag to indicate if the crypto engine resources have been granted. Note that the |
|
* device integrates a single crypto engine instance. |
|
*/ |
|
static bool in_use; |
|
|
|
static void crypto_smartbond_set_status(bool enable); |
|
|
|
static void smartbond_crypto_isr(const void *arg) |
|
{ |
|
struct crypto_smartbond_data *data = ((const struct device *)arg)->data; |
|
uint32_t status = AES_HASH->CRYPTO_STATUS_REG; |
|
|
|
if (status & AES_HASH_CRYPTO_STATUS_REG_CRYPTO_IRQ_ST_Msk) { |
|
/* Clear interrupt source. Otherwise the handler will be fire constantly! */ |
|
AES_HASH->CRYPTO_CLRIRQ_REG = 0x1; |
|
|
|
#if defined(CONFIG_CRYPTO_ASYNC) |
|
/* Define the slected crypto mode (AES/HASH). */ |
|
if (AES_HASH->CRYPTO_CTRL_REG & AES_HASH_CRYPTO_CTRL_REG_CRYPTO_HASH_SEL_Msk) { |
|
if (data->hash_user_cb) { |
|
data->hash_user_cb(data->hash_pkt, status); |
|
} |
|
} else { |
|
if (data->cipher_user_cb) { |
|
data->cipher_user_cb(data->cipher_pkt, status); |
|
} |
|
} |
|
#else |
|
/* Designate the requested cryptographic tasks is finished. */ |
|
k_sem_give(&data->sync_sem); |
|
#endif |
|
} |
|
} |
|
|
|
static inline void crypto_smartbond_pm_policy_state_lock_get(const struct device *dev) |
|
{ |
|
/* |
|
* Prevent the SoC from entering the normal sleep state as PDC does not support |
|
* waking up the application core following AES/HASH events. |
|
*/ |
|
pm_policy_state_lock_get(PM_STATE_STANDBY, PM_ALL_SUBSTATES); |
|
} |
|
|
|
static inline void crypto_smartbond_pm_policy_state_lock_put(const struct device *dev) |
|
{ |
|
/* Allow the SoC to enter the normal sleep state once AES/HASH operations are done. */ |
|
pm_policy_state_lock_put(PM_STATE_STANDBY, PM_ALL_SUBSTATES); |
|
} |
|
|
|
static bool crypto_smartbond_lock_session(const struct device *dev) |
|
{ |
|
bool lock = false; |
|
struct crypto_smartbond_data *data = dev->data; |
|
|
|
k_sem_take(&data->session_sem, K_FOREVER); |
|
|
|
if (!in_use) { |
|
in_use = true; |
|
/* Prevent sleep as long as a cryptographic session is in place */ |
|
da1469x_pd_acquire(MCU_PD_DOMAIN_SYS); |
|
crypto_smartbond_pm_policy_state_lock_get(dev); |
|
crypto_smartbond_set_status(true); |
|
lock = true; |
|
} |
|
|
|
k_sem_give(&data->session_sem); |
|
|
|
return lock; |
|
} |
|
|
|
static void crypto_smartbond_unlock_session(const struct device *dev) |
|
{ |
|
struct crypto_smartbond_data *data = dev->data; |
|
|
|
k_sem_take(&data->session_sem, K_FOREVER); |
|
|
|
if (in_use) { |
|
in_use = false; |
|
crypto_smartbond_set_status(false); |
|
crypto_smartbond_pm_policy_state_lock_put(dev); |
|
da1469x_pd_release_nowait(MCU_PD_DOMAIN_SYS); |
|
} |
|
|
|
k_sem_give(&data->session_sem); |
|
} |
|
|
|
/* |
|
* Input vector should comply with the following restrictions: |
|
* |
|
* mode | CRYPTO_MORE_IN = true | CRYPTO_MORE_IN = false |
|
* ------------| -----------------------| ---------------------- |
|
* ECB | multiple of 16 (bytes) | multiple of 16 (bytes) |
|
* CBC | multiple of 16 | no restrictions |
|
* CTR | multiple of 16 | no restrictions |
|
* MD5 | multiple of 8 | no restrictions |
|
* SHA_1 | multiple of 8 | no restrictions |
|
* SHA_256_224 | multiple of 8 | no restrictions |
|
* SHA_256 | multiple of 8 | no restrictions |
|
* SHA_384 | multiple of 8 | no restrictions |
|
* SHA_512 | multiple of 8 | no restrictions |
|
* SHA_512_224 | multiple of 8 | no restrictions |
|
* SHA_512_256 | multiple of 8 | no restrictions |
|
*/ |
|
static int crypto_smartbond_check_in_restrictions(uint16_t in_len) |
|
{ |
|
#define CRYPTO_ALG_MD_ECB_MAGIC_0 0x00 |
|
#define CRYPTO_ALG_MD_ECB_MAGIC_1 0x01 |
|
|
|
bool not_last_in_block = !!(AES_HASH->CRYPTO_CTRL_REG & |
|
AES_HASH_CRYPTO_CTRL_REG_CRYPTO_MORE_IN_Msk); |
|
|
|
/* Define the slected crypto mode (AES/HASH). */ |
|
if (AES_HASH->CRYPTO_CTRL_REG & AES_HASH_CRYPTO_CTRL_REG_CRYPTO_HASH_SEL_Msk) { |
|
if (not_last_in_block && (in_len & 0x7)) { |
|
return -EINVAL; |
|
} |
|
} else { |
|
if (in_len & 0xF) { |
|
if (not_last_in_block) { |
|
return -EINVAL; |
|
} |
|
|
|
uint32_t crypto_mode = CRYPTO_CTRL_REG_GET(CRYPTO_ALG_MD); |
|
|
|
/* Check if AES mode is ECB */ |
|
if (crypto_mode == CRYPTO_ALG_MD_ECB_MAGIC_0 || |
|
crypto_mode == CRYPTO_ALG_MD_ECB_MAGIC_1) { |
|
return -EINVAL; |
|
} |
|
} |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
/* |
|
* The driver model does not define the max. output length. As such, the max supported length |
|
* per mode is applied. |
|
*/ |
|
static int crypto_smartbond_hash_set_out_len(void) |
|
{ |
|
uint32_t hash_algo = (AES_HASH->CRYPTO_CTRL_REG & AES_HASH_CRYPTO_CTRL_REG_CRYPTO_ALG_Msk); |
|
|
|
if (AES_HASH->CRYPTO_CTRL_REG & AES_HASH_CRYPTO_CTRL_REG_CRYPTO_ALG_MD_Msk) { |
|
/* 64-bit HASH operations */ |
|
switch (hash_algo) { |
|
case 0x0: |
|
/* SHA-384: 0..47 --> 1..48 bytes */ |
|
CRYPTO_CTRL_REG_SET(CRYPTO_HASH_OUT_LEN, 47); |
|
break; |
|
case 0x1: |
|
/* SHA-512: 0..63 --> 1..64 bytes */ |
|
CRYPTO_CTRL_REG_SET(CRYPTO_HASH_OUT_LEN, 63); |
|
break; |
|
case 0x2: |
|
/* SHA-512/224: 0..27 --> 1..28 bytes */ |
|
CRYPTO_CTRL_REG_SET(CRYPTO_HASH_OUT_LEN, 27); |
|
break; |
|
case 0x3: |
|
/* SHA-512/256: 0..31 --> 1..32 bytes */ |
|
CRYPTO_CTRL_REG_SET(CRYPTO_HASH_OUT_LEN, 31); |
|
break; |
|
default: |
|
break; |
|
} |
|
} else { |
|
/* 32-bit HASH operations */ |
|
switch (hash_algo) { |
|
case 0x0: |
|
/* MD5: 0..15 --> 1..16 bytes */ |
|
CRYPTO_CTRL_REG_SET(CRYPTO_HASH_OUT_LEN, 15); |
|
break; |
|
case 0x1: |
|
/* SHA-1: 0..19 --> 1..20 bytes */ |
|
CRYPTO_CTRL_REG_SET(CRYPTO_HASH_OUT_LEN, 19); |
|
break; |
|
case 0x2: |
|
/* SHA-256/224: 0..27 --> 1..28 bytes */ |
|
CRYPTO_CTRL_REG_SET(CRYPTO_HASH_OUT_LEN, 27); |
|
break; |
|
case 0x3: |
|
/* SHA-256: 0..31 --> 1..32 bytes */ |
|
CRYPTO_CTRL_REG_SET(CRYPTO_HASH_OUT_LEN, 31); |
|
break; |
|
default: |
|
break; |
|
} |
|
} |
|
|
|
/* Return the OUT size applied. */ |
|
return CRYPTO_CTRL_REG_GET(CRYPTO_HASH_OUT_LEN) + 1; |
|
} |
|
|
|
static uint32_t crypto_smartbond_swap_word(uint8_t *data) |
|
{ |
|
/* Check word boundaries of given address and if possible accellerate swapping */ |
|
if ((uint32_t)data & 0x3) { |
|
return SWAP32(sys_get_le32(data)); |
|
} else { |
|
return SWAP32(*(uint32_t *)data); |
|
} |
|
} |
|
|
|
static int crypto_smartbond_cipher_key_load(uint8_t *key, uint16_t key_len) |
|
{ |
|
if (key == NULL) { |
|
return -EIO; |
|
} |
|
|
|
AES_HASH->CRYPTO_CTRL_REG &= ~(AES_HASH_CRYPTO_CTRL_REG_CRYPTO_AES_KEY_SZ_Msk); |
|
|
|
if (key_len == 32) { |
|
AES_HASH->CRYPTO_CTRL_REG |= |
|
(0x2 << AES_HASH_CRYPTO_CTRL_REG_CRYPTO_AES_KEY_SZ_Pos); |
|
} else if (key_len == 24) { |
|
AES_HASH->CRYPTO_CTRL_REG |= |
|
(0x1 << AES_HASH_CRYPTO_CTRL_REG_CRYPTO_AES_KEY_SZ_Pos); |
|
} else if (key_len == 16) { |
|
/* Nothing to do */ |
|
} else { |
|
return -EINVAL; |
|
} |
|
|
|
/* Key expansion is performed by the crypto engine */ |
|
AES_HASH->CRYPTO_CTRL_REG |= AES_HASH_CRYPTO_CTRL_REG_CRYPTO_AES_KEXP_Msk; |
|
|
|
/* Check whether the cipher key is located in OTP (user keys segment) */ |
|
if (IS_ADDRESS_USER_DATA_KEYS_SEGMENT((uint32_t)key)) { |
|
|
|
/* User keys segmnet can be accessed if not locked (stick bits are not set) */ |
|
if (CRG_TOP->SECURE_BOOT_REG & CRG_TOP_SECURE_BOOT_REG_PROT_AES_KEY_READ_Msk) { |
|
return -EIO; |
|
} |
|
|
|
uint32_t cell_offset = da1469x_otp_address_to_cell_offset((uint32_t)key); |
|
|
|
da1469x_otp_read(cell_offset, |
|
(void *)&AES_HASH->CRYPTO_KEYS_START, (uint32_t)key_len); |
|
} else { |
|
volatile uint32_t *kmem_ptr = &AES_HASH->CRYPTO_KEYS_START; |
|
|
|
do { |
|
*(kmem_ptr++) = crypto_smartbond_swap_word(key); |
|
key += 4; |
|
key_len -= 4; |
|
} while (key_len); |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static int crypto_smartbond_cipher_set_mode(enum cipher_mode mode) |
|
{ |
|
/* Select AES mode */ |
|
AES_HASH->CRYPTO_CTRL_REG &= ~(AES_HASH_CRYPTO_CTRL_REG_CRYPTO_ALG_MD_Msk | |
|
AES_HASH_CRYPTO_CTRL_REG_CRYPTO_ALG_Msk | |
|
AES_HASH_CRYPTO_CTRL_REG_CRYPTO_HASH_SEL_Msk); |
|
switch (mode) { |
|
case CRYPTO_CIPHER_MODE_ECB: |
|
/* Already done; CRYPTO_ALG_MD = 0x0 or 0x1 defines ECB. */ |
|
break; |
|
case CRYPTO_CIPHER_MODE_CTR: |
|
AES_HASH->CRYPTO_CTRL_REG |= (0x2 << AES_HASH_CRYPTO_CTRL_REG_CRYPTO_ALG_MD_Pos); |
|
break; |
|
case CRYPTO_CIPHER_MODE_CBC: |
|
AES_HASH->CRYPTO_CTRL_REG |= (0x3 << AES_HASH_CRYPTO_CTRL_REG_CRYPTO_ALG_MD_Pos); |
|
break; |
|
default: |
|
return -EINVAL; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static int crypto_smartbond_hash_set_algo(enum hash_algo algo) |
|
{ |
|
/* Select HASH mode and reset to 32-bit mode */ |
|
AES_HASH->CRYPTO_CTRL_REG = |
|
(AES_HASH->CRYPTO_CTRL_REG & ~(AES_HASH_CRYPTO_CTRL_REG_CRYPTO_ALG_Msk | |
|
AES_HASH_CRYPTO_CTRL_REG_CRYPTO_ALG_MD_Msk)) | |
|
AES_HASH_CRYPTO_CTRL_REG_CRYPTO_HASH_SEL_Msk; |
|
|
|
switch (algo) { |
|
case CRYPTO_HASH_ALGO_SHA224: |
|
/* CRYPTO_ALG_MD = 0x0 defines 32-bit operations */ |
|
AES_HASH->CRYPTO_CTRL_REG |= (0x2 << AES_HASH_CRYPTO_CTRL_REG_CRYPTO_ALG_Pos); |
|
break; |
|
case CRYPTO_HASH_ALGO_SHA256: |
|
/* CRYPTO_ALG_MD = 0x0 defines 32-bit operations */ |
|
AES_HASH->CRYPTO_CTRL_REG |= (0x3 << AES_HASH_CRYPTO_CTRL_REG_CRYPTO_ALG_Pos); |
|
break; |
|
case CRYPTO_HASH_ALGO_SHA384: |
|
/* CRYPTO_ALG_MD = 0x1 defines 64-bit operations */ |
|
AES_HASH->CRYPTO_CLRIRQ_REG |= AES_HASH_CRYPTO_CTRL_REG_CRYPTO_ALG_MD_Msk; |
|
break; |
|
case CRYPTO_HASH_ALGO_SHA512: |
|
/* CRYPTO_ALG_MD = 0x1 defines 64-bit operations */ |
|
AES_HASH->CRYPTO_CTRL_REG |= (AES_HASH_CRYPTO_CTRL_REG_CRYPTO_ALG_MD_Msk | |
|
(0x1 << AES_HASH_CRYPTO_CTRL_REG_CRYPTO_ALG_Pos)); |
|
break; |
|
default: |
|
return -EINVAL; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static int crypto_smartbond_set_in_out_buf(uint8_t *in_buf, uint8_t *out_buf, int len) |
|
{ |
|
if (in_buf == NULL) { |
|
return -EIO; |
|
} |
|
|
|
/* |
|
* Input data can reside in any address space. Cryto DMA can only access physical addresses |
|
* (not remapped). |
|
*/ |
|
uint32_t phy_addr = black_orca_phy_addr((uint32_t)in_buf); |
|
|
|
if (IS_QSPIF_CACHED_ADDRESS(phy_addr)) { |
|
/* |
|
* To achiebe max. perfomance, peripherals should not access the Flash memory |
|
* through the instruction cache controller (avoid cache misses). |
|
*/ |
|
phy_addr += (MCU_QSPIF_M_BASE - MCU_QSPIF_M_CACHED_BASE); |
|
} else if (IS_OTP_ADDRESS(phy_addr)) { |
|
/* Peripherals should access the OTP memory through its peripheral address space. */ |
|
phy_addr += (MCU_OTP_M_P_BASE - MCU_OTP_M_BASE); |
|
} |
|
|
|
AES_HASH->CRYPTO_FETCH_ADDR_REG = phy_addr; |
|
|
|
/* |
|
* OUT buffer can be NULL in case of fregmented data processing. CRYPTO_DEST_ADDR and |
|
* CRYPTO_FETCH_ADDR are being updated as calculations prceed and OUT data are written |
|
* into memory. |
|
*/ |
|
if (out_buf) { |
|
uint32_t remap_adr0 = CRG_TOP->SYS_CTRL_REG & CRG_TOP_SYS_CTRL_REG_REMAP_ADR0_Msk; |
|
|
|
/* |
|
* OUT data can only be written in SYSRAM, non-cached remapped SYSRAM and |
|
* cached non-remapped SYSRAM. |
|
*/ |
|
if (IS_SYSRAM_ADDRESS(out_buf) || |
|
(IS_REMAPPED_ADDRESS(out_buf) && remap_adr0 == 3)) { |
|
AES_HASH->CRYPTO_DEST_ADDR_REG = black_orca_phy_addr((uint32_t)out_buf); |
|
} else { |
|
return -EIO; |
|
} |
|
} |
|
|
|
AES_HASH->CRYPTO_LEN_REG = len; |
|
|
|
return 0; |
|
} |
|
|
|
static inline void crypto_smartbond_cipher_store_dep_data(uint32_t *words, uint32_t len_words) |
|
{ |
|
volatile uint32_t *mreg3 = &AES_HASH->CRYPTO_MREG3_REG; |
|
|
|
for (int i = 0; i < len_words; i++) { |
|
*(mreg3--) = crypto_smartbond_swap_word((uint8_t *)(words++)); |
|
} |
|
} |
|
|
|
static int crypto_smartbond_cipher_set_mreg(uint8_t *mreg, uint32_t len_words) |
|
{ |
|
if (mreg == NULL || len_words == 0 || len_words > 4) { |
|
return -EINVAL; |
|
} |
|
|
|
AES_HASH->CRYPTO_MREG0_REG = 0; |
|
AES_HASH->CRYPTO_MREG1_REG = 0; |
|
AES_HASH->CRYPTO_MREG2_REG = 0; |
|
AES_HASH->CRYPTO_MREG3_REG = 0; |
|
|
|
crypto_smartbond_cipher_store_dep_data((uint32_t *)mreg, len_words); |
|
|
|
return 0; |
|
} |
|
|
|
static void crypto_smartbond_set_status(bool enable) |
|
{ |
|
unsigned int key; |
|
|
|
key = irq_lock(); |
|
|
|
if (enable) { |
|
CRG_TOP->CLK_AMBA_REG |= (CRG_TOP_CLK_AMBA_REG_AES_CLK_ENABLE_Msk); |
|
|
|
AES_HASH->CRYPTO_CLRIRQ_REG = 0x1; |
|
AES_HASH->CRYPTO_CTRL_REG |= (AES_HASH_CRYPTO_CTRL_REG_CRYPTO_IRQ_EN_Msk); |
|
|
|
irq_enable(SMARTBOND_IRQN); |
|
} else { |
|
AES_HASH->CRYPTO_CTRL_REG &= ~(AES_HASH_CRYPTO_CTRL_REG_CRYPTO_IRQ_EN_Msk); |
|
AES_HASH->CRYPTO_CLRIRQ_REG = 0x1; |
|
|
|
irq_disable(SMARTBOND_IRQN); |
|
|
|
CRG_TOP->CLK_AMBA_REG &= ~(CRG_TOP_CLK_AMBA_REG_AES_CLK_ENABLE_Msk); |
|
} |
|
|
|
irq_unlock(key); |
|
} |
|
|
|
static int crypto_smartbond_query_hw_caps(const struct device *dev) |
|
{ |
|
return CRYPTO_HW_CAPS; |
|
} |
|
|
|
static int crypto_smartbond_cipher_ecb_handler(struct cipher_ctx *ctx, struct cipher_pkt *pkt) |
|
{ |
|
int ret; |
|
struct crypto_smartbond_data *data = ctx->device->data; |
|
|
|
if ((AES_HASH->CRYPTO_STATUS_REG & AES_HASH_CRYPTO_STATUS_REG_CRYPTO_INACTIVE_Msk) == 0) { |
|
LOG_ERR("Crypto engine is already employed"); |
|
return -EINVAL; |
|
} |
|
|
|
if (pkt->out_buf_max < pkt->in_len) { |
|
LOG_ERR("OUT buffer cannot be less that IN buffer"); |
|
return -EINVAL; |
|
} |
|
|
|
if (pkt->in_buf == NULL || pkt->out_buf == NULL) { |
|
LOG_ERR("Missing IN or OUT buffer declaration"); |
|
return -EIO; |
|
} |
|
|
|
if (pkt->in_len > 16) { |
|
LOG_ERR("For security reasons, do not operate on more than 16 bytes"); |
|
return -EINVAL; |
|
} |
|
|
|
k_sem_take(&data->device_sem, K_FOREVER); |
|
|
|
ret = crypto_smartbond_check_in_restrictions(pkt->in_len); |
|
if (ret < 0) { |
|
LOG_ERR("Unsupported IN buffer size"); |
|
k_sem_give(&data->device_sem); |
|
return ret; |
|
} |
|
|
|
ret = crypto_smartbond_set_in_out_buf(pkt->in_buf, pkt->out_buf, pkt->in_len); |
|
if (ret < 0) { |
|
LOG_ERR("Unsupported IN or OUT buffer location"); |
|
k_sem_give(&data->device_sem); |
|
return ret; |
|
} |
|
|
|
#if defined(CONFIG_CRYPTO_ASYNC) |
|
data->cipher_pkt = pkt; |
|
#endif |
|
|
|
/* Start crypto processing */ |
|
AES_HASH->CRYPTO_START_REG = 1; |
|
|
|
#if !defined(CONFIG_CRYPTO_ASYNC) |
|
/* Wait for crypto to finish its task */ |
|
k_sem_take(&data->sync_sem, K_FOREVER); |
|
#endif |
|
|
|
/* Report that number of bytes operated upon. */ |
|
pkt->out_len = pkt->in_len; |
|
|
|
k_sem_give(&data->device_sem); |
|
|
|
return 0; |
|
} |
|
|
|
static int |
|
crypto_smartbond_cipher_cbc_handler(struct cipher_ctx *ctx, struct cipher_pkt *pkt, uint8_t *iv) |
|
{ |
|
int ret; |
|
int offset = 0; |
|
struct crypto_smartbond_data *data = ctx->device->data; |
|
bool is_op_encryption = |
|
!!(AES_HASH->CRYPTO_CTRL_REG & AES_HASH_CRYPTO_CTRL_REG_CRYPTO_ENCDEC_Msk); |
|
|
|
if ((AES_HASH->CRYPTO_STATUS_REG & AES_HASH_CRYPTO_STATUS_REG_CRYPTO_INACTIVE_Msk) == 0) { |
|
LOG_ERR("Crypto engine is already employed"); |
|
return -EINVAL; |
|
} |
|
|
|
if ((is_op_encryption && pkt->out_buf_max < (pkt->in_len + 16)) || |
|
pkt->out_buf_max < (pkt->in_len - 16)) { |
|
LOG_ERR("Invalid OUT buffer size"); |
|
return -EINVAL; |
|
} |
|
|
|
if (pkt->in_buf == NULL || pkt->out_buf == NULL) { |
|
LOG_ERR("Missing IN or OUT buffer declaration"); |
|
return -EIO; |
|
} |
|
|
|
if ((ctx->flags & CAP_NO_IV_PREFIX) == 0) { |
|
offset = 16; |
|
if (is_op_encryption) { |
|
/* Prefix IV to ciphertet unless CAP_NO_IV_PREFIX is set. */ |
|
memcpy(pkt->out_buf, iv, offset); |
|
} |
|
} |
|
|
|
k_sem_take(&data->device_sem, K_FOREVER); |
|
|
|
ret = crypto_smartbond_check_in_restrictions(pkt->in_len); |
|
if (ret < 0) { |
|
LOG_ERR("Unsupported IN buffer size"); |
|
k_sem_give(&data->device_sem); |
|
return ret; |
|
} |
|
|
|
ret = crypto_smartbond_cipher_set_mreg(iv, 4); |
|
if (ret < 0) { |
|
LOG_ERR("Missing Initialization Vector (IV)"); |
|
k_sem_give(&data->device_sem); |
|
return ret; |
|
} |
|
|
|
if (is_op_encryption) { |
|
ret = crypto_smartbond_set_in_out_buf(pkt->in_buf, |
|
pkt->out_buf + offset, pkt->in_len); |
|
} else { |
|
ret = crypto_smartbond_set_in_out_buf(pkt->in_buf + offset, |
|
pkt->out_buf, pkt->in_len - offset); |
|
} |
|
|
|
if (ret < 0) { |
|
LOG_ERR("Unsupported IN or OUT buffer location"); |
|
k_sem_give(&data->device_sem); |
|
return ret; |
|
} |
|
|
|
#if defined(CONFIG_CRYPTO_ASYNC) |
|
data->cipher_pkt = pkt; |
|
#endif |
|
|
|
/* Start crypto processing */ |
|
AES_HASH->CRYPTO_START_REG = 1; |
|
|
|
#if !defined(CONFIG_CRYPTO_ASYNC) |
|
/* Wait for crypto to finish its task */ |
|
k_sem_take(&data->sync_sem, K_FOREVER); |
|
#endif |
|
|
|
/* Report that number of bytes operated upon. */ |
|
if (is_op_encryption) { |
|
pkt->out_len = pkt->in_len + offset; |
|
} else { |
|
pkt->out_len = pkt->in_len - offset; |
|
} |
|
|
|
k_sem_give(&data->device_sem); |
|
|
|
return 0; |
|
} |
|
|
|
static int crypto_smartbond_cipher_ctr_handler(struct cipher_ctx *ctx, |
|
struct cipher_pkt *pkt, uint8_t *ic) |
|
{ |
|
int ret; |
|
/* ivlen + ctrlen = keylen, ctrl_len is expressed in bits */ |
|
uint32_t iv_len = ctx->keylen - (ctx->mode_params.ctr_info.ctr_len >> 3); |
|
struct crypto_smartbond_data *data = ctx->device->data; |
|
|
|
if ((AES_HASH->CRYPTO_STATUS_REG & AES_HASH_CRYPTO_STATUS_REG_CRYPTO_INACTIVE_Msk) == 0) { |
|
LOG_ERR("Crypto engine is already employed"); |
|
return -EINVAL; |
|
} |
|
|
|
if (pkt->out_buf_max < pkt->in_len) { |
|
LOG_ERR("OUT buffer cannot be less that IN buffer"); |
|
return -EINVAL; |
|
} |
|
|
|
if (pkt->in_buf == NULL || pkt->out_buf == NULL) { |
|
LOG_ERR("Missing IN or OUT buffer declaration"); |
|
return -EIO; |
|
} |
|
|
|
k_sem_take(&data->device_sem, K_FOREVER); |
|
|
|
ret = crypto_smartbond_check_in_restrictions(pkt->in_len); |
|
if (ret < 0) { |
|
LOG_ERR("Unsupported IN buffer size"); |
|
k_sem_give(&data->device_sem); |
|
return ret; |
|
} |
|
|
|
ret = crypto_smartbond_cipher_set_mreg(ic, iv_len >> 2); |
|
if (ret < 0) { |
|
LOG_ERR("Missing Initialization Counter (IC)"); |
|
k_sem_give(&data->device_sem); |
|
return ret; |
|
} |
|
|
|
ret = crypto_smartbond_set_in_out_buf(pkt->in_buf, pkt->out_buf, pkt->in_len); |
|
if (ret < 0) { |
|
LOG_ERR("Unsupported IN or OUT buffer location"); |
|
k_sem_give(&data->device_sem); |
|
return ret; |
|
} |
|
|
|
#if defined(CONFIG_CRYPTO_ASYNC) |
|
data->cipher_pkt = pkt; |
|
#endif |
|
|
|
/* Start crypto processing */ |
|
AES_HASH->CRYPTO_START_REG = 1; |
|
|
|
#if !defined(CONFIG_CRYPTO_ASYNC) |
|
/* Wait for crypto to finish its task */ |
|
k_sem_take(&data->sync_sem, K_FOREVER); |
|
#endif |
|
|
|
/* Report that number of bytes operated upon. */ |
|
pkt->out_len = pkt->in_len; |
|
|
|
k_sem_give(&data->device_sem); |
|
|
|
return 0; |
|
} |
|
|
|
static int crypto_smartbond_hash_handler(struct hash_ctx *ctx, struct hash_pkt *pkt, bool finish) |
|
{ |
|
int ret; |
|
struct crypto_smartbond_data *data = ctx->device->data; |
|
/* |
|
* In case of framgemented data processing crypto status should be visible as busy for |
|
* as long as the last block is to be processed. |
|
*/ |
|
bool is_multipart_started = |
|
(AES_HASH->CRYPTO_STATUS_REG & AES_HASH_CRYPTO_STATUS_REG_CRYPTO_WAIT_FOR_IN_Msk) && |
|
!(AES_HASH->CRYPTO_STATUS_REG & AES_HASH_CRYPTO_STATUS_REG_CRYPTO_INACTIVE_Msk); |
|
|
|
if (pkt->in_buf == NULL || (pkt->out_buf == NULL)) { |
|
LOG_ERR("Missing IN or OUT buffer declaration"); |
|
return -EIO; |
|
} |
|
|
|
k_sem_take(&data->device_sem, K_FOREVER); |
|
|
|
/* Check if this is the last block to process or more blocks will follow */ |
|
if (finish) { |
|
AES_HASH->CRYPTO_CTRL_REG &= ~(AES_HASH_CRYPTO_CTRL_REG_CRYPTO_MORE_IN_Msk); |
|
} else { |
|
AES_HASH->CRYPTO_CTRL_REG |= AES_HASH_CRYPTO_CTRL_REG_CRYPTO_MORE_IN_Msk; |
|
} |
|
|
|
/* CRYPTO_MORE_IN should be updated prior to checking for IN restrictions! */ |
|
ret = crypto_smartbond_check_in_restrictions(pkt->in_len); |
|
if (ret < 0) { |
|
LOG_ERR("Unsupported IN buffer size"); |
|
k_sem_give(&data->device_sem); |
|
return ret; |
|
} |
|
|
|
if (!is_multipart_started) { |
|
ret = crypto_smartbond_hash_set_out_len(); |
|
if (ret < 0) { |
|
LOG_ERR("Invalid OUT buffer size"); |
|
k_sem_give(&data->device_sem); |
|
return ret; |
|
} |
|
} |
|
|
|
if (!is_multipart_started) { |
|
ret = crypto_smartbond_set_in_out_buf(pkt->in_buf, pkt->out_buf, pkt->in_len); |
|
} else { |
|
/* Destination buffer is being updated as fragmented input is being processed. */ |
|
ret = crypto_smartbond_set_in_out_buf(pkt->in_buf, NULL, pkt->in_len); |
|
} |
|
|
|
if (ret < 0) { |
|
LOG_ERR("Unsupported IN or OUT buffer location"); |
|
k_sem_give(&data->device_sem); |
|
return ret; |
|
} |
|
|
|
#if defined(CONFIG_CRYPTO_ASYNC) |
|
data->hash_pkt = pkt; |
|
#endif |
|
|
|
/* Start hash processing */ |
|
AES_HASH->CRYPTO_START_REG = 1; |
|
|
|
#if !defined(CONFIG_CRYPTO_ASYNC) |
|
k_sem_take(&data->sync_sem, K_FOREVER); |
|
#endif |
|
|
|
k_sem_give(&data->device_sem); |
|
|
|
return 0; |
|
} |
|
|
|
static int |
|
crypto_smartbond_cipher_begin_session(const struct device *dev, struct cipher_ctx *ctx, |
|
enum cipher_algo algo, enum cipher_mode mode, enum cipher_op op_type) |
|
{ |
|
int ret; |
|
|
|
if (ctx->flags & ~(CRYPTO_HW_CAPS)) { |
|
LOG_ERR("Unsupported flag"); |
|
return -EINVAL; |
|
} |
|
|
|
if (algo != CRYPTO_CIPHER_ALGO_AES) { |
|
LOG_ERR("Unsupported cipher algo"); |
|
return -EINVAL; |
|
} |
|
|
|
if (!crypto_smartbond_lock_session(dev)) { |
|
LOG_ERR("No free session for now"); |
|
return -ENOSPC; |
|
} |
|
|
|
/* First check if the requested cryptographic algo is supported */ |
|
ret = crypto_smartbond_cipher_set_mode(mode); |
|
if (ret < 0) { |
|
LOG_ERR("Unsupported cipher mode"); |
|
crypto_smartbond_unlock_session(dev); |
|
return ret; |
|
} |
|
|
|
ret = crypto_smartbond_cipher_key_load((uint8_t *)ctx->key.bit_stream, ctx->keylen); |
|
if (ret < 0) { |
|
LOG_ERR("Invalid key length or key cannot be accessed"); |
|
crypto_smartbond_unlock_session(dev); |
|
return ret; |
|
} |
|
|
|
if (op_type == CRYPTO_CIPHER_OP_ENCRYPT) { |
|
AES_HASH->CRYPTO_CTRL_REG |= AES_HASH_CRYPTO_CTRL_REG_CRYPTO_ENCDEC_Msk; |
|
} else { |
|
AES_HASH->CRYPTO_CTRL_REG &= ~(AES_HASH_CRYPTO_CTRL_REG_CRYPTO_ENCDEC_Msk); |
|
} |
|
|
|
/* IN buffer fragmentation is not supported by the driver model */ |
|
AES_HASH->CRYPTO_CTRL_REG &= ~(AES_HASH_CRYPTO_CTRL_REG_CRYPTO_MORE_IN_Msk); |
|
|
|
switch (mode) { |
|
case CRYPTO_CIPHER_MODE_ECB: |
|
ctx->ops.block_crypt_hndlr = crypto_smartbond_cipher_ecb_handler; |
|
break; |
|
case CRYPTO_CIPHER_MODE_CBC: |
|
ctx->ops.cbc_crypt_hndlr = crypto_smartbond_cipher_cbc_handler; |
|
break; |
|
case CRYPTO_CIPHER_MODE_CTR: |
|
ctx->ops.ctr_crypt_hndlr = crypto_smartbond_cipher_ctr_handler; |
|
break; |
|
default: |
|
break; |
|
} |
|
|
|
ctx->drv_sessn_state = NULL; |
|
|
|
return 0; |
|
} |
|
|
|
static int crypto_smartbond_cipher_free_session(const struct device *dev, struct cipher_ctx *ctx) |
|
{ |
|
ARG_UNUSED(ctx); |
|
crypto_smartbond_unlock_session(dev); |
|
|
|
return 0; |
|
} |
|
|
|
#if defined(CONFIG_CRYPTO_ASYNC) |
|
static int |
|
crypto_smartbond_cipher_set_async_callback(const struct device *dev, cipher_completion_cb cb) |
|
{ |
|
struct crypto_smartbond_data *data = dev->data; |
|
|
|
data->cipher_user_cb = cb; |
|
|
|
return 0; |
|
} |
|
#endif |
|
|
|
static int |
|
crypto_smartbond_hash_begin_session(const struct device *dev, |
|
struct hash_ctx *ctx, enum hash_algo algo) |
|
{ |
|
int ret; |
|
|
|
if (ctx->flags & ~(CRYPTO_HW_CAPS)) { |
|
LOG_ERR("Unsupported flag"); |
|
return -EINVAL; |
|
} |
|
|
|
if (!crypto_smartbond_lock_session(dev)) { |
|
LOG_ERR("No free session for now"); |
|
return -ENOSPC; |
|
} |
|
|
|
/* |
|
* Crypto should be disabled only if not used in other sessions. In case of failure, |
|
* developer should next free the current session. |
|
*/ |
|
crypto_smartbond_set_status(true); |
|
|
|
ret = crypto_smartbond_hash_set_algo(algo); |
|
if (ret < 0) { |
|
LOG_ERR("Unsupported HASH algo"); |
|
crypto_smartbond_unlock_session(dev); |
|
return ret; |
|
} |
|
|
|
ctx->hash_hndlr = crypto_smartbond_hash_handler; |
|
|
|
ctx->drv_sessn_state = NULL; |
|
|
|
return 0; |
|
} |
|
|
|
static int crypto_smartbond_hash_free_session(const struct device *dev, struct hash_ctx *ctx) |
|
{ |
|
ARG_UNUSED(ctx); |
|
crypto_smartbond_unlock_session(dev); |
|
|
|
return 0; |
|
} |
|
|
|
#if defined(CONFIG_CRYPTO_ASYNC) |
|
static int |
|
crypto_smartbond_hash_set_async_callback(const struct device *dev, hash_completion_cb cb) |
|
{ |
|
struct crypto_smartbond_data *data = dev->data; |
|
|
|
data->hash_user_cb = cb; |
|
|
|
return 0; |
|
} |
|
#endif |
|
|
|
static DEVICE_API(crypto, crypto_smartbond_driver_api) = { |
|
.cipher_begin_session = crypto_smartbond_cipher_begin_session, |
|
.cipher_free_session = crypto_smartbond_cipher_free_session, |
|
#if defined(CONFIG_CRYPTO_ASYNC) |
|
.cipher_async_callback_set = crypto_smartbond_cipher_set_async_callback, |
|
#endif |
|
.hash_begin_session = crypto_smartbond_hash_begin_session, |
|
.hash_free_session = crypto_smartbond_hash_free_session, |
|
#if defined(CONFIG_CRYPTO_ASYNC) |
|
.hash_async_callback_set = crypto_smartbond_hash_set_async_callback, |
|
#endif |
|
.query_hw_caps = crypto_smartbond_query_hw_caps |
|
}; |
|
|
|
#if defined(CONFIG_PM_DEVICE) |
|
static int crypto_smartbond_pm_action(const struct device *dev, |
|
enum pm_device_action action) |
|
{ |
|
int ret = 0; |
|
|
|
switch (action) { |
|
case PM_DEVICE_ACTION_SUSPEND: |
|
/* |
|
* No need to perform any actions here as the AES/HASH controller |
|
* should already be turned off. |
|
*/ |
|
break; |
|
case PM_DEVICE_ACTION_RESUME: |
|
/* |
|
* No need to perform any actions here as the AES/HASH controller |
|
* will be initialized upon acquiring a cryptographic session. |
|
*/ |
|
break; |
|
default: |
|
return -ENOTSUP; |
|
} |
|
|
|
return ret; |
|
} |
|
#endif |
|
|
|
static int crypto_smartbond_init(const struct device *dev) |
|
{ |
|
struct crypto_smartbond_data *data = dev->data; |
|
|
|
/* Semaphore used during sessions (begin/free) */ |
|
k_sem_init(&data->session_sem, 1, 1); |
|
|
|
/* Semaphore used to employ the crypto device */ |
|
k_sem_init(&data->device_sem, 1, 1); |
|
|
|
#if !defined(CONFIG_CRYPTO_ASYNC) |
|
/* Sempahore used when sync operations are enabled */ |
|
k_sem_init(&data->sync_sem, 0, 1); |
|
#endif |
|
|
|
IRQ_CONNECT(SMARTBOND_IRQN, SMARTBOND_IRQ_PRIO, smartbond_crypto_isr, |
|
DEVICE_DT_INST_GET(0), 0); |
|
|
|
/* Controller should be initialized once a crypyographic session is requested */ |
|
crypto_smartbond_set_status(false); |
|
|
|
return 0; |
|
} |
|
|
|
/* |
|
* There is only one instance integrated on the SoC. Just in case that assumption becomes invalid |
|
* in the future, we use a BUILD_ASSERT(). |
|
*/ |
|
#define SMARTBOND_CRYPTO_INIT(inst) \ |
|
BUILD_ASSERT((inst) == 0, \ |
|
"multiple instances are not supported"); \ |
|
\ |
|
PM_DEVICE_DT_INST_DEFINE(inst, crypto_smartbond_pm_action); \ |
|
\ |
|
static struct crypto_smartbond_data crypto_smartbond_data_##inst; \ |
|
\ |
|
DEVICE_DT_INST_DEFINE(0, \ |
|
crypto_smartbond_init, \ |
|
PM_DEVICE_DT_INST_GET(inst), \ |
|
&crypto_smartbond_data_##inst, NULL, \ |
|
POST_KERNEL, \ |
|
CONFIG_CRYPTO_INIT_PRIORITY, \ |
|
&crypto_smartbond_driver_api); |
|
|
|
DT_INST_FOREACH_STATUS_OKAY(SMARTBOND_CRYPTO_INIT)
|
|
|