aboutsummaryrefslogtreecommitdiff
path: root/src/cc20rng/main.c
blob: 88b5144d5fc70ab85bc8fcf0814675f616f9e268 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
/*
 * Copyright (c) 2014, 2015, 2016 NORDUnet A/S
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or
 * without modification, are permitted provided that the following
 * conditions are met:
 *
 *    1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *    2. Redistributions in binary form must reproduce the above
 *       copyright notice, this list of conditions and the following
 *       disclaimer in the documentation and/or other materials provided
 *       with the distribution.
 *    3. Neither the name of the NORDUnet nor the names of its
 *       contributors may be used to endorse or promote products derived
 *       from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
#include <string.h>

#include "cc20_prng.h"
#include "main.h"
#include "stm_init.h"

#define UART_RANDOM_BYTES_PER_CHUNK 8
#define RESEED_BLOCKS CHACHA20_MAX_BLOCK_COUNTER

extern DMA_HandleTypeDef hdma_tim;

static UART_HandleTypeDef *huart;
static __IO ITStatus UartReady = RESET;

static union {
  uint8_t rnd[257]; /* 256 bytes + 1 for use in the POST */
  uint32_t rnd32[64];
} buf;

/* First DMA value (DMA_counters[0]) is unreliable, leftover in DMA FIFO
 * perhaps? */
#define FIRST_DMA_IDX_USED 3

/*
 * Number of counters used to produce 8 bits of entropy is:
 *   8 * 4 - four flanks are used to produce two (hopefully) uncorrelated bits
 * (a and b)
 *     * 2 - von Neumann will on average discard 1/2 of the bits 'a' and 'b'
 */
#define DMA_COUNTERS_NUM                                                       \
  ((UART_RANDOM_BYTES_PER_CHUNK * 8 * 4 * 2) + FIRST_DMA_IDX_USED + 1)
struct DMA_params {
  volatile uint32_t buf0[DMA_COUNTERS_NUM];
  volatile uint32_t buf1[DMA_COUNTERS_NUM];
  volatile uint32_t write_buf;
};

static struct DMA_params DMA = {
    {}, {}, 0,
};

/* The main work horse functions */
static void get_entropy32(uint32_t num_bytes, uint32_t buf_idx);
/* Various support functions */
static inline uint32_t get_one_bit(void) __attribute__((__always_inline__));
static volatile uint32_t *restart_DMA(void);
static inline volatile uint32_t *get_DMA_read_buf(void);
static inline uint32_t safe_get_counter(volatile uint32_t *dmabuf,
                                        const uint32_t dmabuf_idx);
static void check_uart_rx(UART_HandleTypeDef *this);
static void cc_reseed(struct cc20_state *cc);

void Error_Handler(void);

int main() {
  uint32_t i, timeout, block_counter = 0;
  struct cc20_state cc, out;
  HAL_StatusTypeDef res;

  /* Initialize buffers */
  memset(buf.rnd, 0, sizeof(buf.rnd));
  for (i = 0; i < DMA_COUNTERS_NUM; i++) {
    DMA.buf0[i] = 0xffff0000 + i;
    DMA.buf1[i] = 0xffff0100 + i;
  }

  stm_init((uint32_t *)&DMA.buf0, DMA_COUNTERS_NUM);

  if (!chacha20_prng_self_test()) {
    Error_Handler();
  }

  /* Ensure there is actual Timer IC counters in both DMA buffers. */
  restart_DMA();
  restart_DMA();

  huart = &huart1;

  /* Toggle GREEN LED to show we've initialized */
  for (i = 0; i < 10; i++) {
    HAL_GPIO_TogglePin(LED_PORT, LED_GREEN);
    HAL_Delay(125);
  }

  /* Generate initial block of random data directly into buf */
  cc_reseed(&cc);
  block_counter = RESEED_BLOCKS;
  chacha20_prng_block(&cc, block_counter--, (struct cc20_state *)buf.rnd32);

  /* Main loop */
  while (1) {
    if (!(block_counter % 1000)) {
      HAL_GPIO_TogglePin(LED_PORT, LED_YELLOW);
    }

    if (!block_counter) {
      cc_reseed(&cc);
      block_counter = RESEED_BLOCKS;
    }

    /* Send buf on UART (non blocking interrupt driven send). */
    UartReady = RESET;
    res = HAL_UART_Transmit_IT(huart, &buf.rnd[0], CHACHA20_BLOCK_SIZE);

    /* Generate next block while this block is being transmitted */
    chacha20_prng_block(&cc, block_counter--, &out);
    /* Copying using a loop is faster than memcpy on STM32 */
    for (i = 0; i < CHACHA20_NUM_WORDS; i++) {
      buf.rnd32[i] = out.i[i];
    }

    if (res == HAL_OK) {
      timeout = 0xffff;
      while (UartReady != SET && timeout) {
        timeout--;
      }
    }

    if (UartReady != SET) {
      /* Failed to send, turn on RED LED for one second */
      HAL_GPIO_WritePin(LED_PORT, LED_RED, GPIO_PIN_SET);
      HAL_Delay(1000);
      HAL_GPIO_WritePin(LED_PORT, LED_RED, GPIO_PIN_RESET);
    }

    /* Check for UART change request */
    check_uart_rx(&huart1);
    check_uart_rx(&huart2);
  }
}

/**
 * @brief  Reseed chacha20 state with hardware generated entropy.
 * @param  cc: ChaCha20 state
 * @retval None
 */
static void cc_reseed(struct cc20_state *cc) {
  HAL_GPIO_WritePin(LED_PORT, LED_BLUE, GPIO_PIN_SET);

  get_entropy32(CHACHA20_BLOCK_SIZE / 4, 0);
  restart_DMA();
  chacha20_prng_reseed(cc, (uint32_t *)&buf);

  HAL_GPIO_WritePin(LED_PORT, LED_BLUE, GPIO_PIN_RESET);
}

/**
 * @brief  Collect `count' times 32 bits of entropy.
 * @param  count: Number of 32 bit words to collect.
 * @param  start: Start index value into buf.rnd32.
 * @retval None
 */
static inline void get_entropy32(uint32_t count, const uint32_t start) {
  uint32_t i, bits, buf_idx;

  buf_idx = start;

  do {
    bits = 0;
    /* Get 32 bits of entropy. */
    for (i = 32; i; i--) {
      bits <<= 1;
      bits += get_one_bit();
    }

    /* Store the 32 bits in output buffer */
    buf.rnd32[buf_idx++] = bits;
  } while (--count);
}

/**
 * @brief  Return one bit of entropy.
 * @param  None
 * @retval One bit, in the LSB of an uint32_t since this is a 32 bit MCU.
 */
static inline uint32_t get_one_bit() {
  register uint32_t a, b, temp;
  /* Start at end of buffer so restart_DMA() is called. */
  static uint32_t dmabuf_idx = DMA_COUNTERS_NUM - 1;
  volatile uint32_t *dmabuf;

  dmabuf = get_DMA_read_buf();

  do {
    if (dmabuf_idx > DMA_COUNTERS_NUM - 1 - 4) {
      /* If there are less than four counters available in the dmabuf,
       * we need to get a fresh DMA buffer first.
       */
      dmabuf = restart_DMA();
      dmabuf_idx = FIRST_DMA_IDX_USED;
    }

    /* Get one bit from two subsequent counter values */
    a = safe_get_counter(dmabuf, dmabuf_idx++) & 1;
    temp = safe_get_counter(dmabuf, dmabuf_idx++) & 1;
    a ^= temp;

    /* Get another bit from two other counter values. Getting
     * two bits from two unrelated [1] pairs of counters is
     * supposed to help against phase correlations between the
     * frequency of the noise and the MCU sampling rate.
     *
     * [1] This is how it is done in the ARRGH board, although
     *     since this is a faster MCU and DMA is used the two
     *     pairs are more likely to still be related since they
     *     are more likely to be directly subsequent diode
     *     breakdowns. Have to evaluate if this is enough.
     */
    b = safe_get_counter(dmabuf, dmabuf_idx++) & 1;
    temp = safe_get_counter(dmabuf, dmabuf_idx++) & 1;
    b ^= temp;

    /* Do von Neumann extraction of a and b to eliminate bias
     * (only eliminates bias if a and b are uncorrelated)
     */
  } while (a == b);

  return a;
}

/**
 * @brief  Return a pointer to the DMA.buf NOT currently being written to
 * @param  None
 * @retval Pointer to buffer currently being read from.
 */
static inline volatile uint32_t *get_DMA_read_buf(void) {
  return DMA.write_buf ? DMA.buf0 : DMA.buf1;
}

/**
 * @brief  Return a pointer to the DMA.buf currently being written to
 * @param  None
 * @retval Pointer to buffer currently being written to.
 */
static inline volatile uint32_t *get_DMA_write_buf(void) {
  return DMA.write_buf ? DMA.buf1 : DMA.buf0;
}

/**
 * @brief  Initiate DMA collection of another buffer of Timer IC values.
 * @param  None
 * @retval Pointer to buffer full of Timer IC values ready to be consumed.
 */
static volatile uint32_t *restart_DMA(void) {
  /* Wait for transfer complete flag to become SET. Trying to change the
   * M0AR register while the DMA is running is a no-no.
   */
  while (__HAL_DMA_GET_FLAG(&hdma_tim,
                            __HAL_DMA_GET_TC_FLAG_INDEX(&hdma_tim)) == RESET)
    ;

  /* Switch buffer being written to */
  DMA.write_buf ^= 1;
  hdma_tim.Instance->M0AR = (uint32_t)get_DMA_write_buf();

  /* Start at 0 to help manual inspection */
  TIM2->CNT = 0;

  /* Clear the transfer complete flag before re-enabling DMA */
  __HAL_DMA_CLEAR_FLAG(&hdma_tim, __HAL_DMA_GET_TC_FLAG_INDEX(&hdma_tim));
  __HAL_DMA_ENABLE(&hdma_tim);

  return get_DMA_read_buf();
}

/**
 * @brief  Get one counter value, guaranteed to not have been used before.
 * @param  dmabuf:     Pointer to the current DMA read buffer.
 * @param  dmabuf_idx: Word index into `dmabuf'.
 * @retval One Timer IC counter value.
 */
static inline uint32_t safe_get_counter(volatile uint32_t *dmabuf,
                                        const uint32_t dmabuf_idx) {
  register uint32_t a;
  /* Prevent re-use of values. DMA stored values are <= 0xffff. */
  do {
    a = dmabuf[dmabuf_idx];
  } while (a > 0xffff);
  dmabuf[dmabuf_idx] = 0xffff0000;
  return a;
}

/* UART transmit complete callback */
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *UH) {
  if ((UH->Instance == USART1 && huart->Instance == USART1) ||
      (UH->Instance == USART2 && huart->Instance == USART2)) {
    /* Signal UART transmit complete to the code in the main loop. */
    UartReady = SET;
  }
}

/*
 * If a newline is received on UART1 or UART2, redirect output to that UART.
 */
static void check_uart_rx(UART_HandleTypeDef *this) {
  uint8_t rx = 0;
  if (HAL_UART_Receive(this, &rx, 1, 0) == HAL_OK) {
    if (rx == '\n') {
      huart = this;
      /* Signal UART transmit complete to the code in the main loop. */
      UartReady = SET;
    }
  }
}

/**
 * @brief  This function is executed in case of error occurrence.
 * @param  None
 * @retval None
 */
void Error_Handler(void) {
  /* Turn on RED LED and then loop indefinitely */
  HAL_GPIO_WritePin(LED_PORT, LED_RED, GPIO_PIN_SET);
  while (1)
    ;
}