-
Notifications
You must be signed in to change notification settings - Fork 3k
[Silicon Labs] Add TRNG support #4051
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
285f82f
88c996e
f8be297
d2020f5
4675d05
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,373 @@ | ||
/* | ||
* True Random Number Generator (TRNG) driver for Silicon Labs devices | ||
* | ||
* Copyright (C) 2016, Silicon Labs, http://www.silabs.com | ||
* SPDX-License-Identifier: Apache-2.0 | ||
* | ||
* Licensed 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 "sl_trng.h" | ||
|
||
#if defined(TRNG_PRESENT) | ||
#include "em_cmu.h" | ||
#include "em_common.h" | ||
#include <string.h> | ||
|
||
#define FIFO_LEVEL_RETRY (1000) | ||
#define TEST_WORDS_MIN (257) | ||
|
||
#define TEST_VECTOR_CONDITIONING_KEY_SIZE (4) | ||
static const uint32_t | ||
test_vector_conditioning_key[TEST_VECTOR_CONDITIONING_KEY_SIZE] = | ||
{0x16157E2B, 0xA6D2AE28, 0x8815F7AB, 0x3C4FCF09}; | ||
|
||
#define TEST_VECTOR_CONDITIONING_INPUT_SIZE (16) | ||
static const uint32_t | ||
test_vector_conditioning_input[TEST_VECTOR_CONDITIONING_INPUT_SIZE] = | ||
{0xE1BCC06B, 0x9199452A, 0x1A7434E1, 0x25199E7F, | ||
0x578A2DAE, 0x9CAC031E, 0xAC6FB79E, 0x518EAF45, | ||
0x461CC830, 0x11E45CA3, 0x19C1FBE5, 0xEF520A1A, | ||
0x45249FF6, 0x179B4FDF, 0x7B412BAD, 0x10376CE6}; | ||
|
||
#define TEST_VECTOR_CONDITIONING_OUTPUT_SIZE (4) | ||
static const uint32_t | ||
test_vector_conditioning_output[TEST_VECTOR_CONDITIONING_OUTPUT_SIZE] = | ||
{0xA1CAF13F, 0x09AC1F68, 0x30CA0E12, 0xA7E18675}; | ||
|
||
#define TRNG_STARTUP_TEST_WAIT_RETRY (10000) | ||
|
||
typedef struct { | ||
TRNG_TypeDef *instance; | ||
CMU_Clock_TypeDef clock; | ||
} sl_trng_device_t; | ||
|
||
static const sl_trng_device_t sl_trng_devices[TRNG_COUNT] = | ||
{ | ||
#if defined(TRNG0) | ||
{ | ||
TRNG0, | ||
cmuClock_TRNG0 | ||
}, | ||
#endif | ||
}; | ||
|
||
static CMU_Clock_TypeDef sl_trng_get_clock( TRNG_TypeDef *device ) | ||
{ | ||
for(int i = 0; i < TRNG_COUNT; i++) { | ||
if(sl_trng_devices[i].instance == device) { | ||
return sl_trng_devices[i].clock; | ||
} | ||
} | ||
return cmuClock_TRNG0; | ||
} | ||
|
||
void sl_trng_init( TRNG_TypeDef *device ) | ||
{ | ||
int i; | ||
|
||
/* Enable the TRNG's clock. */ | ||
CMU_ClockEnable( sl_trng_get_clock(device), true ); | ||
|
||
device->CONTROL = | ||
TRNG_CONTROL_ENABLE | | ||
TRNG_CONTROL_REPCOUNTIEN | | ||
TRNG_CONTROL_APT64IEN | | ||
TRNG_CONTROL_APT4096IEN | | ||
TRNG_CONTROL_PREIEN | | ||
TRNG_CONTROL_ALMIEN; | ||
|
||
/* Apply software reset */ | ||
sl_trng_soft_reset(device); | ||
|
||
/* Wait for TRNG to complete startup tests and start filling the FIFO. */ | ||
for (i=0; (device->FIFOLEVEL == 0) && (i<TRNG_STARTUP_TEST_WAIT_RETRY); i++); | ||
EFM_ASSERT(i<TRNG_STARTUP_TEST_WAIT_RETRY); | ||
} | ||
|
||
void sl_trng_free( TRNG_TypeDef *device ) | ||
{ | ||
/* Disable TRNG. */ | ||
device->CONTROL = 0; | ||
|
||
/* Disable the TRNG clock. */ | ||
CMU_ClockEnable( sl_trng_get_clock(device), false ); | ||
} | ||
|
||
void sl_trng_soft_reset( TRNG_TypeDef *device ) | ||
{ | ||
uint32_t ctrl = device->CONTROL; | ||
|
||
ctrl |= TRNG_CONTROL_SOFTRESET; | ||
|
||
device->CONTROL = ctrl; | ||
|
||
ctrl &= ~TRNG_CONTROL_SOFTRESET; | ||
device->CONTROL = ctrl; | ||
} | ||
|
||
static inline | ||
void sl_trng_write_test_data( TRNG_TypeDef *device, uint32_t data ) | ||
{ | ||
/* Wait for TESTDATA register to become ready for next word. */ | ||
while (device->STATUS & TRNG_STATUS_TESTDATABUSY); | ||
device->TESTDATA = data; | ||
} | ||
|
||
static void sl_trng_clear_fifo( TRNG_TypeDef *device ) | ||
{ | ||
volatile uint32_t val32; | ||
|
||
/* Empty FIFO */ | ||
while ( device->FIFOLEVEL ) | ||
{ | ||
val32 = device->FIFO; | ||
(void)val32; | ||
} | ||
} | ||
|
||
int sl_trng_set_key( TRNG_TypeDef *device, const unsigned char *key ) | ||
{ | ||
uint32_t *_key = (uint32_t*) key; | ||
|
||
sl_trng_clear_fifo(device); | ||
|
||
/* Program key in KEY registers of the TRNG. */ | ||
device->KEY0 = *_key++; | ||
device->KEY1 = *_key++; | ||
device->KEY2 = *_key++; | ||
device->KEY3 = *_key++; | ||
|
||
return 0; | ||
} | ||
|
||
int sl_trng_check_conditioning( TRNG_TypeDef *device ) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. where is this API being called? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @RonEld No, this is a self-test function. It should never be used in regular operation, which is why it is never being called from the driver. We use it for internal test purposes, and since this driver was copied verbatim, the test function ended up here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In my opinion, internal test functions should not be added, in addition that they also might be exploited for other reasons There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Okay, I'll remove both test functions. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @RonEld removed now. |
||
{ | ||
uint32_t val32; | ||
int i, ret=0; | ||
uint32_t ctrl = device->CONTROL; | ||
|
||
/* Setup control register */ | ||
device->CONTROL = TRNG_CONTROL_ENABLE | TRNG_CONTROL_TESTEN | | ||
TRNG_CONTROL_BYPNIST | TRNG_CONTROL_BYPAIS31; | ||
|
||
/* Apply software reset */ | ||
sl_trng_soft_reset(device); | ||
|
||
/* Write test vector to the key register. */ | ||
sl_trng_set_key(device, | ||
(const unsigned char*)test_vector_conditioning_key); | ||
|
||
/* Write test vector to the TESTDATA register */ | ||
for (i=0; i<TEST_VECTOR_CONDITIONING_INPUT_SIZE; i++) | ||
{ | ||
sl_trng_write_test_data(device, | ||
test_vector_conditioning_input[i]); | ||
} | ||
|
||
for (i=0; i<TEST_VECTOR_CONDITIONING_OUTPUT_SIZE; i++) | ||
{ | ||
/* Wait for data to become available in the FIFO. */ | ||
while ( 0 == device->FIFOLEVEL ); | ||
/* Read output from the conditioning function */ | ||
val32 = device->FIFO; | ||
/* Compare with expected test vector. */ | ||
if (val32 != test_vector_conditioning_output[i]) | ||
{ | ||
/* | ||
mbedtls_printf("Conditioning test failed. " | ||
"Test output word %d 0x%lx. Expected 0x%lx\n", | ||
i, val32, test_vector_conditioning_output[i]); | ||
*/ | ||
ret = SL_TRNG_ERR_CONDITIONING_TEST_FAILED; | ||
} | ||
} | ||
|
||
/* Restore initial value of control register */ | ||
device->CONTROL = ctrl; | ||
|
||
return ret; | ||
} | ||
|
||
static int sl_trng_check_status( TRNG_TypeDef *device ) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This function changes the device state. A name like |
||
{ | ||
uint32_t status = device->STATUS; | ||
|
||
if ( (status & (TRNG_STATUS_PREIF | ||
| TRNG_STATUS_REPCOUNTIF | ||
| TRNG_STATUS_APT64IF | ||
| TRNG_STATUS_APT4096IF | ||
| TRNG_STATUS_ALMIF)) == 0 ) | ||
{ | ||
/* No errors */ | ||
return 0; | ||
} | ||
|
||
if ( status & TRNG_STATUS_PREIF ) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the preliminary noise alarm is raised, other alarms are ignored. Is this desirable? Or is the point moot because the alarms are mutually exclusive? |
||
{ | ||
/* On a preliminary noise alarm we clear the FIFO and clear | ||
* the alarm. The preliminary noise alarm is not critical. */ | ||
status &= ~TRNG_STATUS_PREIF; | ||
device->STATUS = status; | ||
sl_trng_clear_fifo(device); | ||
return SL_TRNG_ERR_PRELIMINARY_NOISE_ALARM; | ||
} | ||
else | ||
{ | ||
/* Clear alarm conditions by doing a TRNG soft reset. */ | ||
sl_trng_soft_reset( device ); | ||
if ( status & TRNG_STATUS_REPCOUNTIF ) | ||
{ | ||
return SL_TRNG_ERR_REPETITION_COUNT_TEST_FAILED; | ||
} | ||
if ( status & TRNG_STATUS_APT64IF ) | ||
{ | ||
return SL_TRNG_ERR_ADAPTIVE_PROPORTION_TEST_64_FAILED; | ||
} | ||
if ( status & TRNG_STATUS_APT4096IF ) | ||
{ | ||
return SL_TRNG_ERR_ADAPTIVE_PROPORTION_TEST_4096_FAILED; | ||
} | ||
if ( status & TRNG_STATUS_ALMIF ) | ||
{ | ||
return SL_TRNG_ERR_NOISE_ALARM; | ||
} | ||
} | ||
|
||
return 0; | ||
} | ||
|
||
int sl_trng_check_entropy( TRNG_TypeDef *device ) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. where is this API being called? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @RonEld This is a self-test function (see my other comment), and is only used during TRNG bringup. The TRNG peripheral has built-in entropy checks (called 'Noise Alarms') which are being checked in the polling function. Should I get rid of this function for the driver that we ship with mbed, or can I leave it as-is? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. see previous comment, but I think this function has less impact than previous test funciton |
||
{ | ||
volatile uint32_t val32; | ||
int i, ret = 0; | ||
uint32_t ctrl = device->CONTROL; | ||
|
||
/* Setup control register */ | ||
device->CONTROL = | ||
TRNG_CONTROL_ENABLE | | ||
TRNG_CONTROL_REPCOUNTIEN | | ||
TRNG_CONTROL_APT64IEN | | ||
TRNG_CONTROL_APT4096IEN | | ||
TRNG_CONTROL_PREIEN | | ||
TRNG_CONTROL_ALMIEN; | ||
|
||
/* Apply software reset */ | ||
sl_trng_soft_reset(device); | ||
|
||
/* Check FIFO level is non-zero . */ | ||
for (i=0; i<FIFO_LEVEL_RETRY; i++) | ||
{ | ||
if ( device->FIFOLEVEL ) | ||
{ | ||
break; | ||
} | ||
} | ||
/* Check for no data within timeout (max retry count) */ | ||
if (i>=FIFO_LEVEL_RETRY) | ||
{ | ||
ret = SL_TRNG_ERR_NO_DATA; | ||
} | ||
else | ||
{ | ||
/* Read at least 4097x2 bits (~257 x 32 bits) in order for the longest | ||
test to complete (adaptive proportion test of 4096 samples). */ | ||
for (i=0; i<TEST_WORDS_MIN; i++) | ||
{ | ||
val32 = device->FIFO; | ||
(void)val32; | ||
} | ||
|
||
/* Check in status register for errors. */ | ||
ret = sl_trng_check_status( device ); | ||
} | ||
|
||
/* Restore initial value of control register */ | ||
device->CONTROL = ctrl; | ||
|
||
return ret; | ||
} | ||
|
||
static void sl_trng_read_chunk( TRNG_TypeDef *device, | ||
unsigned char *output, | ||
size_t len ) | ||
{ | ||
uint32_t * out32 = (uint32_t *) output; | ||
uint32_t tmp; | ||
|
||
/* Read known good available data. */ | ||
while ( len >= 4) | ||
{ | ||
*out32++ = device->FIFO; | ||
len -= 4; | ||
} | ||
|
||
/* Handle the case where len is not a multiple of 4. */ | ||
if ( len < 4 ) | ||
{ | ||
tmp = device->FIFO; | ||
memcpy((uint8_t *)out32, (const uint8_t *) &tmp, len); | ||
} | ||
} | ||
|
||
int sl_trng_poll( TRNG_TypeDef *device, | ||
unsigned char *output, | ||
size_t len, | ||
size_t *olen ) | ||
{ | ||
size_t output_len = 0; | ||
size_t count = 0; | ||
size_t available; | ||
int ret = 0; | ||
|
||
while (len > 0) | ||
{ | ||
available = device->FIFOLEVEL * 4; | ||
if (available == 0) | ||
{ | ||
break; | ||
} | ||
|
||
#if !defined(SL_TRNG_IGNORE_ALL_ALARMS) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could somebody please help me understand why we need these alarm suppression options? (I mean |
||
/* Check status for current data in FIFO | ||
* and handle any error conditions. */ | ||
ret = sl_trng_check_status( device ); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is the check status an irrecoverable failure? Perhaps wait for an interrupt until check_status will succeed? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well, on a noise alarm you really should throw away the generated data and reset the TRNG (which is already happening in check_status). Thus, the polling 'failed'. IMO, this should bubble up to the user, who can then decide whether or not to retry. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you point me to the location where you poll on fail? It seems to me that on failure, you stop collecting and return an error to the user There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Changed now. Upon noise alarm, we throw away the collected entropy and continue collecting. For the other alarms (which are more severe, see my previous comment), we throw away the collected entropy, and return an error. |
||
#if defined(SL_TRNG_IGNORE_NOISE_ALARMS) | ||
/* Ignore noise alarms by returning 0 (OK) if they occur and | ||
* keeping the already generated random data. */ | ||
if ( (ret == SL_TRNG_ERR_PRELIMINARY_NOISE_ALARM) || | ||
(ret == SL_TRNG_ERR_NOISE_ALARM) ) | ||
{ | ||
ret = 0; | ||
break; | ||
} | ||
#endif | ||
/* Alarm has been signaled so we throw the generated data away. */ | ||
if (ret != 0) | ||
{ | ||
output_len = 0; | ||
break; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should this be changed to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See my other comment. |
||
} | ||
#endif | ||
|
||
count = SL_MIN(len, available); | ||
sl_trng_read_chunk(device, output, count); | ||
output += count; | ||
output_len += count; | ||
len -= count; | ||
} | ||
|
||
*olen = output_len; | ||
return ret; | ||
} | ||
|
||
#endif /* TRNG_PRESENT */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why not use the existing DEVICE_TRNG define?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TRNG_PRESENT comes from our own device headers, and is the authority for knowing whether a device has TRNG or not. DEVICE_TRNG is an mbed define to use the mbed TRNG HAL. Considering sl_trng.c is a Silicon Labs file, and the mbed HAL doesn't directly interface with it (that'd be trng_api.c), we are using Silicon Labs defines.