altos: Add simple stats test to TRNG code
[fw/altos] / src / drivers / ao_trng_send.c
1 /*
2  * Copyright © 2015 Keith Packard <keithp@keithp.com>
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; version 2 of the License.
7  *
8  * This program is distributed in the hope that it will be useful, but
9  * WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11  * General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License along
14  * with this program; if not, write to the Free Software Foundation, Inc.,
15  * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
16  */
17
18 #include <ao.h>
19 #include <ao_adc_fast.h>
20 #include <ao_crc.h>
21 #include <ao_trng_send.h>
22 #include <ao_exti.h>
23 #include <ao_power.h>
24
25 static uint8_t          trng_running;
26 static AO_TICK_TYPE     trng_power_time;
27
28 /* Make sure there's at least 8 bits of variance in the samples */
29 #define MIN_VARIANCE            (128 * 128)
30
31 #define DECLARE_STATS   int32_t sum = 0, sum2 = 0
32
33 #define ADD_STATS(value) do {                   \
34                 sum += (value);                 \
35                 sum2 += (value) * (value);      \
36         } while(0)
37
38 #define GOOD_STATS(i)   (((sum2 - (sum * sum) / i) / i) >= MIN_VARIANCE)
39
40 #define TRNG_ENABLE_DELAY       AO_MS_TO_TICKS(100)
41
42 static int
43 ao_trng_send_raw(uint16_t *buf)
44 {
45         uint16_t        i;
46         uint16_t        t;
47         uint16_t        v;
48
49         DECLARE_STATS;
50
51         t = ao_adc_get(AO_USB_IN_SIZE>>1);      /* one 16-bit value per two output bytes */
52         for (i = 0; i < AO_USB_IN_SIZE / sizeof (uint16_t); i++) {
53                 v = ao_adc_ring[t];
54                 *buf++ = v;
55                 t = (t + 1) & (AO_ADC_RING_SIZE - 1);
56
57                 ADD_STATS(v);
58         }
59         return GOOD_STATS(AO_USB_IN_SIZE / sizeof (uint16_t));
60 }
61
62 static int
63 ao_trng_send_cooked(uint16_t *buf)
64 {
65         uint16_t        i;
66         uint16_t        t;
67         uint32_t        *rnd = (uint32_t *) ao_adc_ring;
68
69         DECLARE_STATS;
70
71         t = ao_adc_get(AO_USB_IN_SIZE) >> 1;            /* one 16-bit value per output byte */
72
73         for (i = 0; i < AO_USB_IN_SIZE / sizeof (uint16_t); i++) {
74                 uint32_t        v;
75                 uint16_t        v1, v2;
76
77                 /* Fetch two values in one operation */
78                 v = rnd[t];
79                 t = (t + 1) & ((AO_ADC_RING_SIZE >> 1) - 1);
80
81                 *buf++ = ao_crc_in_32_out_16(v);
82
83                 v1 = v;
84                 v2 = v >> 16;
85
86                 ADD_STATS(v1);
87                 ADD_STATS(v2);
88         }
89         return GOOD_STATS(2 * AO_USB_IN_SIZE / sizeof (uint16_t));
90 }
91
92 static inline int
93 ao_send_raw(void)
94 {
95         return !ao_gpio_get(AO_RAW_PORT, AO_RAW_BIT, AO_RAW_PIN);
96 }
97
98 static void
99 ao_trng_send(void)
100 {
101         static uint16_t *buffer[2];
102         int     usb_buf_id;
103         int     good_bits;
104         int     failed = 0;
105
106         if (!buffer[0]) {
107                 buffer[0] = ao_usb_alloc();
108                 buffer[1] = ao_usb_alloc();
109                 if (!buffer[0])
110                         return;
111         }
112
113         usb_buf_id = 0;
114
115 #ifdef AO_TRNG_ENABLE_PORT
116         ao_gpio_set(AO_TRNG_ENABLE_PORT, AO_TRNG_ENABLE_BIT, AO_TRNG_ENABLE_PIN, 1);
117 #endif
118         trng_power_time = ao_time();
119
120         ao_crc_reset();
121
122         for (;;) {
123                 if (!trng_running) {
124                         AO_TICK_TYPE    delay;
125
126                         delay = trng_power_time + TRNG_ENABLE_DELAY - ao_time();
127                         if (delay > TRNG_ENABLE_DELAY)
128                                 delay = TRNG_ENABLE_DELAY;
129
130                         /* Delay long enough for the HV power supply
131                          * to stabilize so that the first bits we read
132                          * aren't of poor quality
133                          */
134                         ao_delay(delay);
135                         trng_running = TRUE;
136                 }
137                 if (ao_send_raw()) {
138                         ao_led_on(AO_LED_TRNG_RAW);
139                         good_bits = ao_trng_send_raw(buffer[usb_buf_id]);
140                         ao_led_off(AO_LED_TRNG_RAW);
141                 } else {
142                         ao_led_on(AO_LED_TRNG_COOKED);
143                         good_bits = ao_trng_send_cooked(buffer[usb_buf_id]);
144                         ao_led_off(AO_LED_TRNG_COOKED);
145                 }
146                 ao_adc_ack(AO_USB_IN_SIZE);
147                 if (good_bits) {
148                         ao_usb_write(buffer[usb_buf_id], AO_USB_IN_SIZE);
149                         usb_buf_id = 1-usb_buf_id;
150                         failed = 0;
151                 } else {
152                         failed++;
153                         ao_delay(AO_MS_TO_TICKS(10));
154                         if (failed > 10) {
155                                 ao_usb_disable();
156                                 ao_panic(AO_PANIC_DMA);
157                         }
158                 }
159         }
160 }
161
162 static struct ao_task ao_trng_send_task;
163
164 #if AO_POWER_MANAGEMENT
165
166 static void ao_trng_suspend(void *arg)
167 {
168         (void) arg;
169 #ifdef AO_TRNG_ENABLE_PORT
170         ao_gpio_set(AO_TRNG_ENABLE_PORT, AO_TRNG_ENABLE_BIT, AO_TRNG_ENABLE_PIN, 0);
171 #endif
172         trng_running = FALSE;
173 }
174
175 static void ao_trng_resume(void *arg)
176 {
177         (void) arg;
178 #ifdef AO_TRNG_ENABLE_PORT
179         ao_gpio_set(AO_TRNG_ENABLE_PORT, AO_TRNG_ENABLE_BIT, AO_TRNG_ENABLE_PIN, 1);
180 #endif
181         trng_power_time = ao_time();
182 }
183
184 static struct ao_power ao_trng_power = {
185         .suspend = ao_trng_suspend,
186         .resume = ao_trng_resume
187 };
188
189 #endif
190
191 void
192 ao_trng_send_init(void)
193 {
194 #ifdef AO_TRNG_ENABLE_PORT
195         ao_enable_output(AO_TRNG_ENABLE_PORT, AO_TRNG_ENABLE_BIT, AO_TRNG_ENABLE_PIN, 0);
196         ao_power_register(&ao_trng_power);
197 #endif
198         ao_enable_input(AO_RAW_PORT, AO_RAW_BIT, AO_EXTI_MODE_PULL_UP);
199         ao_add_task(&ao_trng_send_task, ao_trng_send, "trng_send");
200 }