| /* |
| * 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 <zephyr/sys/byteorder.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) __builtin_bswap32(_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 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; |
| 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); |
| } |
| |
| 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) { |
| sys_mem_swap(data, sizeof(uint32_t)); |
| |
| return (*(uint32_t *)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; |
| } |
| |
| ret = crypto_smartbond_cipher_key_load(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; |
| } |
| |
| ret = crypto_smartbond_cipher_set_mode(mode); |
| if (ret < 0) { |
| LOG_ERR("Unsupported cipher mode"); |
| 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 struct crypto_driver_api 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 |
| }; |
| |
| 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); |
| |
| 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"); \ |
| \ |
| static struct crypto_smartbond_data crypto_smartbond_data_##inst; \ |
| \ |
| DEVICE_DT_INST_DEFINE(0, \ |
| crypto_smartbond_init, NULL, \ |
| &crypto_smartbond_data_##inst, NULL, \ |
| POST_KERNEL, \ |
| CONFIG_CRYPTO_INIT_PRIORITY, \ |
| &crypto_smartbond_driver_api); |
| |
| DT_INST_FOREACH_STATUS_OKAY(SMARTBOND_CRYPTO_INIT) |