/*
|
* Licensed to the Apache Software Foundation (ASF) under one
|
* or more contributor license agreements. See the NOTICE file
|
* distributed with this work for additional information
|
* regarding copyright ownership. The ASF licenses this file
|
* to you under the Apache License, Version 2.0 (the
|
* "License"); you may not use this file except in compliance
|
* with the License. You may obtain a copy of the License at
|
*
|
* http://www.apache.org/licenses/LICENSE-2.0
|
*
|
* Unless required by applicable law or agreed to in writing,
|
* software distributed under the License is distributed on an
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
* KIND, either express or implied. See the License for the
|
* specific language governing permissions and limitations
|
* under the License.
|
*/
|
|
#include <assert.h>
|
#include <stdint.h>
|
#include "mcu/mcu.h"
|
#include "nimble/ble.h"
|
#include "controller/ble_hw.h"
|
#include "CMAC.h"
|
#include "cmac_driver/cmac_shared.h"
|
#include "tinycrypt/aes.h"
|
|
static struct tc_aes_key_sched_struct g_ctx;
|
|
int
|
ble_hw_rng_init(ble_rng_isr_cb_t cb, int bias)
|
{
|
cmac_rand_set_isr_cb(cb);
|
return 0;
|
}
|
|
int
|
ble_hw_rng_start(void)
|
{
|
/* Chime the M33 in case we need random numbers generated */
|
cmac_rand_start();
|
CMAC->CM_EV_SET_REG = CMAC_CM_EV_SET_REG_EV1C_CMAC2SYS_IRQ_SET_Msk;
|
return 0;
|
}
|
|
int
|
ble_hw_rng_stop(void)
|
{
|
cmac_rand_stop();
|
return 0;
|
}
|
|
#define BLE_HW_RESOLV_LIST_SIZE (MYNEWT_VAL(BLE_LL_RESOLV_LIST_SIZE))
|
|
struct ble_hw_resolv_irk {
|
uint32_t key[4];
|
};
|
|
struct ble_hw_resolv_list {
|
uint8_t count;
|
struct ble_hw_resolv_irk irk[BLE_HW_RESOLV_LIST_SIZE];
|
};
|
|
struct ble_hw_resolv_proc {
|
uint32_t hash;
|
uint8_t f_configured;
|
uint8_t f_active;
|
uint8_t f_match;
|
uint8_t f_done;
|
struct ble_hw_resolv_irk *irk;
|
struct ble_hw_resolv_irk *irk_end;
|
uint32_t crypto_prand_in[4];
|
uint32_t crypto_e_out[4];
|
};
|
|
static struct ble_hw_resolv_list g_ble_hw_resolv_list;
|
static struct ble_hw_resolv_proc g_ble_hw_resolv_proc;
|
|
int
|
ble_hw_get_public_addr(ble_addr_t *addr)
|
{
|
return -1;
|
}
|
|
int
|
ble_hw_get_static_addr(ble_addr_t *addr)
|
{
|
return -1;
|
}
|
|
void
|
ble_hw_whitelist_clear(void)
|
{
|
}
|
|
int
|
ble_hw_whitelist_add(const uint8_t *addr, uint8_t addr_type)
|
{
|
return 0;
|
}
|
|
void
|
ble_hw_whitelist_rmv(const uint8_t *addr, uint8_t addr_type)
|
{
|
}
|
|
uint8_t
|
ble_hw_whitelist_size(void)
|
{
|
return 0;
|
}
|
|
void
|
ble_hw_whitelist_enable(void)
|
{
|
}
|
|
|
void
|
ble_hw_whitelist_disable(void)
|
{
|
}
|
|
int
|
ble_hw_whitelist_match(void)
|
{
|
return 0;
|
}
|
|
int
|
ble_hw_encrypt_block(struct ble_encryption_block *ecb)
|
{
|
uint32_t in_addr;
|
uint32_t out_addr;
|
|
/*
|
* The following code bears some explanation. This function is called by
|
* the LL task to encrypt blocks and calculate session keys. Address
|
* resolution also calls this function. Furthermore, during connections,
|
* the M0 crypto accelerator is used but this function is not called when
|
* using it. During the entire connection event, the M0 crypto block cannot
|
* be used as the crypto state (some of it) needs to remain un-changed.
|
* Note that this is also true when address resolution is enabled: the
|
* HW crypto block is set up and cannot be modified.
|
*
|
* Rather than attempt to share the M0 crypto block between the various
|
* controller features which require it, we decided to use software to
|
* perform the encryption task for anything being done at the link-layer
|
* (outside of an ISR). If this function is called inside an ISR, and it
|
* is when resolving addresses, the crypto accelerator is not being used
|
* by a connection event. Thus, we check to see if we are inside of an ISR.
|
* If so, we use the M0 crypto block. If outside of an ISR, we use the M33
|
*/
|
if (!os_arch_in_isr()) {
|
tc_aes128_set_encrypt_key(&g_ctx, ecb->key);
|
tc_aes_encrypt(ecb->cipher_text, ecb->plain_text, &g_ctx);
|
return 0;
|
}
|
|
/* Need to retain state of in/out pointers */
|
in_addr = CMAC->CM_CRYPTO_IN_ADR2_REG;
|
out_addr = CMAC->CM_CRYPTO_OUT_ADR_REG;
|
|
while (CMAC->CM_CRYPTO_STAT_REG & CMAC_CM_CRYPTO_STAT_REG_CM_CRYPTO_BUSY_Msk);
|
|
/* RECB, memory in/out, encryption */
|
CMAC->CM_CRYPTO_CTRL_REG = CMAC_CM_CRYPTO_CTRL_REG_CM_CRYPTO_ECB_ENC_EN_Msk |
|
CMAC_CM_CRYPTO_CTRL_REG_CM_CRYPTO_IN_SEL_Msk |
|
CMAC_CM_CRYPTO_CTRL_REG_CM_CRYPTO_OUT_SEL_Msk |
|
CMAC_CM_CRYPTO_CTRL_REG_CM_CRYPTO_ENC_DECN_Msk;
|
|
CMAC->CM_CRYPTO_KEY_31_0_REG = get_le32(&ecb->key[0]);
|
CMAC->CM_CRYPTO_KEY_63_32_REG = get_le32(&ecb->key[4]);
|
CMAC->CM_CRYPTO_KEY_95_64_REG = get_le32(&ecb->key[8]);
|
CMAC->CM_CRYPTO_KEY_127_96_REG = get_le32(&ecb->key[12]);
|
CMAC->CM_CRYPTO_IN_ADR2_REG = (uint32_t)ecb->plain_text;
|
CMAC->CM_CRYPTO_OUT_ADR_REG = (uint32_t)ecb->cipher_text;
|
|
CMAC->CM_EXC_STAT_REG = CMAC_CM_EXC_STAT_REG_EXC_CRYPTO_Msk;
|
CMAC->CM_EV_SET_REG = CMAC_CM_EV_SET_REG_EV_CRYPTO_START_Msk;
|
while (!(CMAC->CM_EXC_STAT_REG & CMAC_CM_EXC_STAT_REG_EXC_CRYPTO_Msk));
|
CMAC->CM_EXC_STAT_REG = CMAC_CM_EXC_STAT_REG_EXC_CRYPTO_Msk;
|
|
CMAC->CM_CRYPTO_IN_ADR2_REG = in_addr;
|
CMAC->CM_CRYPTO_OUT_ADR_REG = out_addr;
|
|
return 0;
|
}
|
|
void
|
ble_hw_resolv_list_clear(void)
|
{
|
g_ble_hw_resolv_list.count = 0;
|
}
|
|
int
|
ble_hw_resolv_list_add(uint8_t *irk)
|
{
|
struct ble_hw_resolv_irk *e;
|
|
if (g_ble_hw_resolv_list.count == BLE_HW_RESOLV_LIST_SIZE) {
|
return BLE_ERR_MEM_CAPACITY;
|
}
|
|
e = &g_ble_hw_resolv_list.irk[g_ble_hw_resolv_list.count];
|
/* Prepare key here so we do not need to do it during resolving */
|
e->key[0] = get_le32(&irk[0]);
|
e->key[1] = get_le32(&irk[4]);
|
e->key[2] = get_le32(&irk[8]);
|
e->key[3] = get_le32(&irk[12]);
|
|
g_ble_hw_resolv_list.count++;
|
|
return BLE_ERR_SUCCESS;
|
}
|
|
void
|
ble_hw_resolv_list_rmv(int index)
|
{
|
struct ble_hw_resolv_irk *e;
|
|
if (index < g_ble_hw_resolv_list.count) {
|
g_ble_hw_resolv_list.count--;
|
|
e = &g_ble_hw_resolv_list.irk[index];
|
memmove(e, e + 1, (g_ble_hw_resolv_list.count - index) * sizeof(e->key));
|
}
|
}
|
|
uint8_t
|
ble_hw_resolv_list_size(void)
|
{
|
return BLE_HW_RESOLV_LIST_SIZE;
|
}
|
|
int
|
ble_hw_resolv_list_match(void)
|
{
|
return g_ble_hw_resolv_proc.f_match ?
|
g_ble_hw_resolv_proc.irk - g_ble_hw_resolv_list.irk : -1;
|
}
|
|
static void
|
ble_hw_resolv_proc_next(void)
|
{
|
void *src = &g_ble_hw_resolv_proc.irk->key;
|
|
if (g_ble_hw_resolv_proc.irk == g_ble_hw_resolv_proc.irk_end) {
|
g_ble_hw_resolv_proc.f_done = 1;
|
g_ble_hw_resolv_proc.f_active = 0;
|
} else {
|
__asm__ volatile (".syntax unified \n"
|
" ldm %[ptr]!, {r1, r2, r3, r4} \n"
|
" ldr %[ptr], =%[reg] \n"
|
" stm %[ptr]!, {r1, r2, r3, r4} \n"
|
: [ptr] "+l" (src)
|
: [reg] "i" (&CMAC->CM_CRYPTO_KEY_31_0_REG)
|
: "r1", "r2", "r3", "r4", "memory");
|
|
CMAC->CM_EV_SET_REG = CMAC_CM_EV_SET_REG_EV_CRYPTO_START_Msk;
|
}
|
}
|
|
void
|
ble_hw_resolv_proc_enable(void)
|
{
|
assert(!g_ble_hw_resolv_proc.f_active);
|
|
CMAC->CM_CRYPTO_CTRL_REG = CMAC_CM_CRYPTO_CTRL_REG_CM_CRYPTO_SW_REQ_ABORT_Msk;
|
|
CMAC->CM_CRYPTO_CTRL_REG = CMAC_CM_CRYPTO_CTRL_REG_CM_CRYPTO_ECB_ENC_EN_Msk |
|
CMAC_CM_CRYPTO_CTRL_REG_CM_CRYPTO_IN_SEL_Msk |
|
CMAC_CM_CRYPTO_CTRL_REG_CM_CRYPTO_OUT_SEL_Msk |
|
CMAC_CM_CRYPTO_CTRL_REG_CM_CRYPTO_ENC_DECN_Msk;
|
|
CMAC->CM_CRYPTO_IN_ADR2_REG = (uint32_t)g_ble_hw_resolv_proc.crypto_prand_in;
|
CMAC->CM_CRYPTO_OUT_ADR_REG = (uint32_t)g_ble_hw_resolv_proc.crypto_e_out;
|
|
g_ble_hw_resolv_proc.irk = g_ble_hw_resolv_list.irk;
|
g_ble_hw_resolv_proc.irk_end = g_ble_hw_resolv_list.irk +
|
g_ble_hw_resolv_list.count;
|
g_ble_hw_resolv_proc.f_configured = 1;
|
g_ble_hw_resolv_proc.f_active = 0;
|
|
/*
|
* It would be better to enable IRQ in ble_hw_resolv_proc_start, but this
|
* would introduce a bit of latency when starting resolving procedure and
|
* we need to save every us possible there in order to be able to resolve
|
* RPA on time.
|
*/
|
NVIC_ClearPendingIRQ(CRYPTO_IRQn);
|
NVIC_EnableIRQ(CRYPTO_IRQn);
|
}
|
|
void
|
ble_hw_resolv_proc_disable(void)
|
{
|
g_ble_hw_resolv_proc.f_configured = 0;
|
g_ble_hw_resolv_proc.f_active = 0;
|
g_ble_hw_resolv_proc.f_match = 0;
|
g_ble_hw_resolv_proc.f_done = 1;
|
|
NVIC_DisableIRQ(CRYPTO_IRQn);
|
}
|
|
void
|
ble_hw_resolv_proc_reset(const uint8_t *addr)
|
{
|
g_ble_hw_resolv_proc.f_match = 0;
|
}
|
|
void
|
ble_hw_resolv_proc_start(const uint8_t *addr)
|
{
|
assert(g_ble_hw_resolv_proc.f_configured);
|
|
/* crypto_prand_in is already zeroed so prand is properly padded */
|
g_ble_hw_resolv_proc.crypto_prand_in[3] = get_be24(&addr[3]) << 8;
|
g_ble_hw_resolv_proc.hash = get_be24(&addr[0]);
|
|
g_ble_hw_resolv_proc.f_match = 0;
|
g_ble_hw_resolv_proc.f_done = 0;
|
g_ble_hw_resolv_proc.f_active = 1;
|
|
ble_hw_resolv_proc_next();
|
}
|
|
void
|
CRYPTO_IRQHandler(void)
|
{
|
uint32_t hash;
|
|
CMAC->CM_EXC_STAT_REG = CMAC_CM_EXC_STAT_REG_EXC_CRYPTO_Msk;
|
|
hash = g_ble_hw_resolv_proc.crypto_e_out[3] >> 8;
|
if (g_ble_hw_resolv_proc.hash == hash) {
|
g_ble_hw_resolv_proc.f_active = 0;
|
g_ble_hw_resolv_proc.f_match = 1;
|
g_ble_hw_resolv_proc.f_done = 1;
|
} else {
|
g_ble_hw_resolv_proc.irk++;
|
ble_hw_resolv_proc_next();
|
}
|
}
|