Skip to content

[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

Merged
merged 5 commits into from
Apr 6, 2017
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions targets/TARGET_Silicon_Labs/TARGET_EFM32/common/objects.h
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,12 @@ struct flash_s {
};
#endif

#if DEVICE_TRNG
struct trng_s {
TRNG_TypeDef *instance;
};
#endif

#ifdef __cplusplus
}
#endif
Expand Down
373 changes: 373 additions & 0 deletions targets/TARGET_Silicon_Labs/TARGET_EFM32/trng/sl_trng.c
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)
Copy link
Contributor

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?

Copy link
Contributor Author

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.

#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 )
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where is this API being called?
If I understand this API correct, you are now supplying an API for setting a constant output. am I right?
@yanesca I would like to hear your input on this

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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.
Should I get rid of it instead?

Copy link
Contributor

Choose a reason for hiding this comment

The 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

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I'll remove both test functions.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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 )

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function changes the device state. A name like check_status does not convey this. How about sl_trng_retrieve_alarms?

{
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 )

Choose a reason for hiding this comment

The 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 )
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where is this API being called?
The entropy check should be wither done in you trng_get_bytes function, to verify that the collected noise has good entropy, or later from the entropy pool (TBD)

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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?

Copy link
Contributor

Choose a reason for hiding this comment

The 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)
Copy link
Contributor

Choose a reason for hiding this comment

The 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 SL_TRNG_IGNORE_ALL_ALARMS and SL_TRNG_IGNORE_NOISE_ALARMS)

/* Check status for current data in FIFO
* and handle any error conditions. */
ret = sl_trng_check_status( device );
Copy link
Contributor

Choose a reason for hiding this comment

The 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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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.
The other statuses are irrecoverable in the sense that they point to a larger issue, most likely someone tampering with the TRNG ring oscillators.

Copy link
Contributor

@RonEld RonEld Apr 6, 2017

Choose a reason for hiding this comment

The 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

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this be changed to continue? Do you want to continue collecting?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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 */
Loading