This commit changes the way random numbers are available with MySensors. The AVR platform uses a 32 Bit random seed build from a floating analog pin. The way of the generation of this seed is FIPS 140-2 compliant. For support MCU with a fast way to generate random data, the hwGetentropy() command is now available to allow rapid generation of random data. The ESP8266 platform uses the internal random source for randomness for hwGetentropy() and Arduino's random() function. The NRF5 platform initializes the pseudo random number generator from internal hardware number generator. The internal AES-ECB unit generates random data after filling with HWRNG data by AES-CBC. On Linux getrandom() is implemented via syscall(). signerAtsha204SoftGetNonce speed: AVR = ~14000us (sha204Soft) NRF51 = ~152us (AES-CBC), 1739us (old sha204Soft) NRF52 = ~31us (AES-CBC), 183us (old sha204Soft) ESP8266 = ~20us (RANDOM_REG32), 380us (old sha204Soft) All Nonce results tested for FIPS 140-2 compliance successfully.
331 lines
9.2 KiB
C++
331 lines
9.2 KiB
C++
/**
|
|
* The MySensors Arduino library handles the wireless radio link and protocol
|
|
* between your home built sensors/actuators and HA controller of choice.
|
|
* The sensors forms a self healing radio network with optional repeaters. Each
|
|
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
|
|
* network topology allowing messages to be routed to nodes.
|
|
*
|
|
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
|
|
* Copyright (C) 2013-2017 Sensnology AB
|
|
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
|
|
*
|
|
* Documentation: http://www.mysensors.org
|
|
* Support Forum: http://forum.mysensors.org
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* version 2 as published by the Free Software Foundation.
|
|
*/
|
|
|
|
#include "MyHwAVR.h"
|
|
|
|
bool hwInit(void)
|
|
{
|
|
#if !defined(MY_DISABLED_SERIAL)
|
|
MY_SERIALDEVICE.begin(MY_BAUD_RATE);
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
#define WDTO_SLEEP_FOREVER (0xFFu)
|
|
#define INVALID_INTERRUPT_NUM (0xFFu)
|
|
|
|
volatile uint8_t _wokeUpByInterrupt =
|
|
INVALID_INTERRUPT_NUM; // Interrupt number that woke the mcu.
|
|
volatile uint8_t _wakeUp1Interrupt =
|
|
INVALID_INTERRUPT_NUM; // Interrupt number for wakeUp1-callback.
|
|
volatile uint8_t _wakeUp2Interrupt =
|
|
INVALID_INTERRUPT_NUM; // Interrupt number for wakeUp2-callback.
|
|
|
|
void wakeUp1()
|
|
{
|
|
// Disable sleep. When an interrupt occurs after attachInterrupt,
|
|
// but before sleeping the CPU would not wake up.
|
|
// Ref: http://playground.arduino.cc/Learning/ArduinoSleepCode
|
|
sleep_disable();
|
|
detachInterrupt(_wakeUp1Interrupt);
|
|
// First interrupt occurred will be reported only
|
|
if (INVALID_INTERRUPT_NUM == _wokeUpByInterrupt) {
|
|
_wokeUpByInterrupt = _wakeUp1Interrupt;
|
|
}
|
|
}
|
|
void wakeUp2()
|
|
{
|
|
sleep_disable();
|
|
detachInterrupt(_wakeUp2Interrupt);
|
|
// First interrupt occurred will be reported only
|
|
if (INVALID_INTERRUPT_NUM == _wokeUpByInterrupt) {
|
|
_wokeUpByInterrupt = _wakeUp2Interrupt;
|
|
}
|
|
}
|
|
|
|
inline bool interruptWakeUp()
|
|
{
|
|
return _wokeUpByInterrupt != INVALID_INTERRUPT_NUM;
|
|
}
|
|
|
|
void clearPendingInterrupt(const uint8_t interrupt)
|
|
{
|
|
EIFR = _BV(interrupt);
|
|
}
|
|
|
|
// Watchdog Timer interrupt service routine. This routine is required
|
|
// to allow automatic WDIF and WDIE bit clearance in hardware.
|
|
ISR (WDT_vect)
|
|
{
|
|
}
|
|
|
|
void hwPowerDown(const uint8_t wdto)
|
|
{
|
|
// Let serial prints finish (debug, log etc)
|
|
#ifndef MY_DISABLED_SERIAL
|
|
MY_SERIALDEVICE.flush();
|
|
#endif
|
|
|
|
// disable ADC for power saving
|
|
ADCSRA &= ~(1 << ADEN);
|
|
// save WDT settings
|
|
uint8_t WDTsave = WDTCSR;
|
|
if (wdto != WDTO_SLEEP_FOREVER) {
|
|
wdt_enable(wdto);
|
|
// enable WDT interrupt before system reset
|
|
WDTCSR |= (1 << WDCE) | (1 << WDIE);
|
|
} else {
|
|
// if sleeping forever, disable WDT
|
|
wdt_disable();
|
|
}
|
|
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
|
|
cli();
|
|
sleep_enable();
|
|
#if defined __AVR_ATmega328P__
|
|
sleep_bod_disable();
|
|
#endif
|
|
// Enable interrupts & sleep until WDT or ext. interrupt
|
|
sei();
|
|
// Directly sleep CPU, to prevent race conditions!
|
|
// Ref: chapter 7.7 of ATMega328P datasheet
|
|
sleep_cpu();
|
|
sleep_disable();
|
|
// restore previous WDT settings
|
|
cli();
|
|
wdt_reset();
|
|
// enable WDT changes
|
|
WDTCSR |= (1 << WDCE) | (1 << WDE);
|
|
// restore saved WDT settings
|
|
WDTCSR = WDTsave;
|
|
sei();
|
|
// enable ADC
|
|
ADCSRA |= (1 << ADEN);
|
|
}
|
|
|
|
void hwInternalSleep(unsigned long ms)
|
|
{
|
|
// Sleeping with watchdog only supports multiples of 16ms.
|
|
// Round up to next multiple of 16ms, to assure we sleep at least the
|
|
// requested amount of time. Sleep of 0ms will not sleep at all!
|
|
ms += 15u;
|
|
|
|
while (!interruptWakeUp() && ms >= 16) {
|
|
for (uint8_t period = 9u; ; --period) {
|
|
const uint16_t comparatorMS = 1 << (period + 4);
|
|
if ( ms >= comparatorMS) {
|
|
hwPowerDown(period); // 8192ms => 9, 16ms => 0
|
|
ms -= comparatorMS;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int8_t hwSleep(unsigned long ms)
|
|
{
|
|
hwInternalSleep(ms);
|
|
return MY_WAKE_UP_BY_TIMER;
|
|
}
|
|
|
|
int8_t hwSleep(uint8_t interrupt, uint8_t mode, unsigned long ms)
|
|
{
|
|
return hwSleep(interrupt,mode,INVALID_INTERRUPT_NUM,0u,ms);
|
|
}
|
|
|
|
int8_t hwSleep(uint8_t interrupt1, uint8_t mode1, uint8_t interrupt2, uint8_t mode2,
|
|
unsigned long ms)
|
|
{
|
|
// ATMega328P supports following modes to wake from sleep: LOW, CHANGE, RISING, FALLING
|
|
// Datasheet states only LOW can be used with INT0/1 to wake from sleep, which is incorrect.
|
|
// Ref: http://gammon.com.au/interrupts
|
|
|
|
// Disable interrupts until going to sleep, otherwise interrupts occurring between attachInterrupt()
|
|
// and sleep might cause the ATMega to not wakeup from sleep as interrupt has already be handled!
|
|
cli();
|
|
// attach interrupts
|
|
_wakeUp1Interrupt = interrupt1;
|
|
_wakeUp2Interrupt = interrupt2;
|
|
|
|
// Attach external interrupt handlers, and clear any pending interrupt flag
|
|
// to prevent waking immediately again.
|
|
// Ref: https://forum.arduino.cc/index.php?topic=59217.0
|
|
if (interrupt1 != INVALID_INTERRUPT_NUM) {
|
|
clearPendingInterrupt(interrupt1);
|
|
attachInterrupt(interrupt1, wakeUp1, mode1);
|
|
}
|
|
if (interrupt2 != INVALID_INTERRUPT_NUM) {
|
|
clearPendingInterrupt(interrupt2);
|
|
attachInterrupt(interrupt2, wakeUp2, mode2);
|
|
}
|
|
|
|
if (ms > 0u) {
|
|
// sleep for defined time
|
|
hwInternalSleep(ms);
|
|
} else {
|
|
// sleep until ext interrupt triggered
|
|
hwPowerDown(WDTO_SLEEP_FOREVER);
|
|
}
|
|
|
|
// Assure any interrupts attached, will get detached when they did not occur.
|
|
if (interrupt1 != INVALID_INTERRUPT_NUM) {
|
|
detachInterrupt(interrupt1);
|
|
}
|
|
if (interrupt2 != INVALID_INTERRUPT_NUM) {
|
|
detachInterrupt(interrupt2);
|
|
}
|
|
|
|
// Return what woke the mcu.
|
|
// Default: no interrupt triggered, timer wake up
|
|
int8_t ret = MY_WAKE_UP_BY_TIMER;
|
|
if (interruptWakeUp()) {
|
|
ret = static_cast<int8_t>(_wokeUpByInterrupt);
|
|
}
|
|
// Clear woke-up-by-interrupt flag, so next sleeps won't return immediately.
|
|
_wokeUpByInterrupt = INVALID_INTERRUPT_NUM;
|
|
|
|
return ret;
|
|
}
|
|
|
|
inline void hwRandomNumberInit()
|
|
{
|
|
// This function initializes the random number generator with a seed
|
|
// of 32 bits. This method is good enough to earn FIPS 140-2 conform
|
|
// random data. This should reach to generate 32 Bit for randomSeed().
|
|
unsigned long seed = 0;
|
|
unsigned long start = millis();
|
|
unsigned long timeout = start + 20;
|
|
|
|
// Trigger floating effect of an unconnected pin
|
|
pinMode(MY_SIGNING_SOFT_RANDOMSEED_PIN, INPUT_PULLUP);
|
|
pinMode(MY_SIGNING_SOFT_RANDOMSEED_PIN, INPUT);
|
|
delay(10);
|
|
|
|
// Generate 32 bits of datas
|
|
for (uint8_t i=0; i<32; i++) {
|
|
int pinValue = analogRead(MY_SIGNING_SOFT_RANDOMSEED_PIN);
|
|
// Wait until the analog value has changed
|
|
while ((pinValue == analogRead(MY_SIGNING_SOFT_RANDOMSEED_PIN)) && (timeout>=millis())) {
|
|
seed ^= (millis() << i);
|
|
// Check of data generation is slow
|
|
if (timeout<=millis()) {
|
|
// Trigger pin again
|
|
pinMode(MY_SIGNING_SOFT_RANDOMSEED_PIN, INPUT_PULLUP);
|
|
pinMode(MY_SIGNING_SOFT_RANDOMSEED_PIN, INPUT);
|
|
// Pause a short while
|
|
delay(seed % 10);
|
|
timeout = millis()+20;
|
|
}
|
|
}
|
|
}
|
|
|
|
randomSeed(seed);
|
|
}
|
|
|
|
bool hwUniqueID(unique_id_t* uniqueID)
|
|
{
|
|
// not implemented yet
|
|
(void)uniqueID;
|
|
return false;
|
|
}
|
|
|
|
uint16_t hwCPUVoltage()
|
|
{
|
|
// Measure Vcc against 1.1V Vref
|
|
#if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
|
|
ADMUX = (_BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1));
|
|
#elif defined (__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__)
|
|
ADMUX = (_BV(MUX5) | _BV(MUX0));
|
|
#elif defined (__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__)
|
|
ADMUX = (_BV(MUX3) | _BV(MUX2));
|
|
#else
|
|
ADMUX = (_BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1));
|
|
#endif
|
|
// Vref settle
|
|
delay(70);
|
|
// Do conversion
|
|
ADCSRA |= _BV(ADSC);
|
|
while (bit_is_set(ADCSRA,ADSC)) {};
|
|
// return Vcc in mV
|
|
return (1125300UL) / ADC;
|
|
}
|
|
|
|
uint16_t hwCPUFrequency()
|
|
{
|
|
cli();
|
|
// setup timer1
|
|
TIFR1 = 0xFF;
|
|
TCNT1 = 0;
|
|
TCCR1A = 0;
|
|
TCCR1C = 0;
|
|
// save WDT settings
|
|
uint8_t WDTsave = WDTCSR;
|
|
wdt_enable(WDTO_500MS);
|
|
// enable WDT interrupt mode => first timeout WDIF, 2nd timeout reset
|
|
WDTCSR |= (1 << WDIE);
|
|
wdt_reset();
|
|
// start timer1 with 1024 prescaling
|
|
TCCR1B = _BV(CS12) | _BV(CS10);
|
|
// wait until wdt interrupt
|
|
while (bit_is_clear(WDTCSR,WDIF)) {};
|
|
// stop timer
|
|
TCCR1B = 0;
|
|
// restore WDT settings
|
|
wdt_reset();
|
|
WDTCSR |= (1 << WDCE) | (1 << WDE);
|
|
WDTCSR = WDTsave;
|
|
sei();
|
|
// return frequency in 1/10MHz (accuracy +- 10%)
|
|
return TCNT1 * 2048UL / 100000UL;
|
|
}
|
|
|
|
uint16_t hwFreeMem()
|
|
{
|
|
extern int __heap_start, *__brkval;
|
|
int v;
|
|
return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval);
|
|
}
|
|
|
|
void hwDebugPrint(const char *fmt, ... )
|
|
{
|
|
char fmtBuffer[MY_SERIAL_OUTPUT_SIZE];
|
|
#ifdef MY_GATEWAY_FEATURE
|
|
// prepend debug message to be handled correctly by controller (C_INTERNAL, I_LOG_MESSAGE)
|
|
snprintf_P(fmtBuffer, sizeof(fmtBuffer), PSTR("0;255;%d;0;%d;%lu "),
|
|
C_INTERNAL, I_LOG_MESSAGE, hwMillis());
|
|
MY_SERIALDEVICE.print(fmtBuffer);
|
|
#else
|
|
// prepend timestamp
|
|
MY_SERIALDEVICE.print(hwMillis());
|
|
MY_SERIALDEVICE.print(F(" "));
|
|
#endif
|
|
va_list args;
|
|
va_start (args, fmt );
|
|
#ifdef MY_GATEWAY_FEATURE
|
|
// Truncate message if this is gateway node
|
|
vsnprintf_P(fmtBuffer, sizeof(fmtBuffer), fmt, args);
|
|
fmtBuffer[sizeof(fmtBuffer) - 2] = '\n';
|
|
fmtBuffer[sizeof(fmtBuffer) - 1] = '\0';
|
|
#else
|
|
vsnprintf_P(fmtBuffer, sizeof(fmtBuffer), fmt, args);
|
|
#endif
|
|
va_end (args);
|
|
MY_SERIALDEVICE.print(fmtBuffer);
|
|
MY_SERIALDEVICE.flush();
|
|
}
|