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 all 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
274 changes: 274 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,274 @@
/*
* 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 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;
}

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;
}

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 chunk_len = 0;
size_t available;
int ret = 0;

while (output_len < len)
{
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;
continue;

Choose a reason for hiding this comment

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

Like yanesca I wonder why there's a compilation option to ignore alarms. Assuming that the mode SL_TRNG_IGNORE_NOISE_ALARMS is useful, I don't understand the logic here. If there is a physical condition that causes the alarm to keep being raised, wouldn't this cause a tight loop?

}
#else
/* Noise alarms trigger a FIFO clearing, and we need to throw
* away the collected entropy. */
if ( (ret == SL_TRNG_ERR_PRELIMINARY_NOISE_ALARM) ||
(ret == SL_TRNG_ERR_NOISE_ALARM) )
{
ret = 0;
output_len = 0;
continue;
}
#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

chunk_len = SL_MIN(len - output_len, available);
sl_trng_read_chunk(device, output + output_len, chunk_len);
output_len += chunk_len;
}

*olen = output_len;
return ret;
}

#endif /* TRNG_PRESENT */
Loading