diff --git a/src/fw/drivers/nrf5/otp.c b/src/fw/drivers/nrf5/otp.c new file mode 100644 index 000000000..4b7c332be --- /dev/null +++ b/src/fw/drivers/nrf5/otp.c @@ -0,0 +1,67 @@ +#include "drivers/otp.h" + +#include "system/passert.h" + +#define NRF5_COMPATIBLE +#include + +#include +#include +#include +#include +#include + +#include "nrfx_nvmc.h" + +// nRF5's OTP is based on UICR, which is not *really* OTP ... it can always +// be erased with, oh, say, ERASEUICR. And it is in fact probable that it +// will get erased with developers doing a `nrf52_recover`. But it *is* a +// region of flash that is distinct from SPI flash, which could *also* get +// erased, I suppose. +// +// On the other hand, a more pressing problem is that nRF52 only has 128 +// bytes of UICR. That one is more annoying -- that's only 4 slots worth! +// But the good news, I suppose, is that since the OTP is not actually OTP, +// there is no need to fear -- if you screw it up, you can just reset it and +// try again and program the value you actually wanted. + +#define OTP_SLOT_SIZE_BYTES 32 + +char * otp_get_slot(const uint8_t index) { + PBL_ASSERTN(index < NUM_OTP_SLOTS); + return (char *)(NRF_UICR->CUSTOMER) + index * OTP_SLOT_SIZE_BYTES; +} + +bool otp_is_locked(const uint8_t index) { + char *slot = otp_get_slot(index); + for (int i = 0; i < OTP_SLOT_SIZE_BYTES; i++) { + if (slot[i] != 0xFF) { + return true; + } + } + return false; +} + +OtpWriteResult otp_write_slot(const uint8_t index, const char *value) { + // This may interrupt Bluetooth timing, but we don't care, because we + // should always be in mfg land for this. + if (otp_is_locked(index)) { + return OtpWriteFailAlreadyWritten; + } + + // nrfx_nvmc_words_write needs word-aligned data to write. + uint32_t otp_data[OTP_SLOT_SIZE_BYTES / 4]; + memcpy(otp_data, value, OTP_SLOT_SIZE_BYTES); + + nrfx_nvmc_words_write((uintptr_t)(NRF_UICR->CUSTOMER) + index * OTP_SLOT_SIZE_BYTES, otp_data, OTP_SLOT_SIZE_BYTES / 4); + while (!nrfx_nvmc_write_done_check()) + ; + + // According to the spec, UICR changes aren't reflected until the system + // is reset. In practice, they seem to be, though. + if (memcmp(otp_get_slot(index), value, OTP_SLOT_SIZE_BYTES)) { + return OtpWriteFailCorrupt; + } + + return OtpWriteSuccess; +} diff --git a/src/fw/drivers/nrf5/stubs/otp.c b/src/fw/drivers/nrf5/stubs/otp.c deleted file mode 100644 index 9944cf98a..000000000 --- a/src/fw/drivers/nrf5/stubs/otp.c +++ /dev/null @@ -1,30 +0,0 @@ -#include "drivers/otp.h" - -#include "system/passert.h" - -#define NRF5_COMPATIBLE -#include - -#include -#include -#include -#include -#include - -static char slot[32]; - -char * otp_get_slot(const uint8_t index) { - return slot; -} - -uint8_t * otp_get_lock(const uint8_t index) { - return (uint8_t *)slot; -} - -bool otp_is_locked(const uint8_t index) { - return false; -} - -OtpWriteResult otp_write_slot(const uint8_t index, const char *value) { - return OtpWriteFailAlreadyWritten; -} diff --git a/src/fw/drivers/otp.h b/src/fw/drivers/otp.h index f2de51d62..ae066802b 100644 --- a/src/fw/drivers/otp.h +++ b/src/fw/drivers/otp.h @@ -35,7 +35,7 @@ enum { NUM_OTP_SLOTS = 16, }; -#elif PLATFORM_SILK || PLATFORM_ASTERIX || PLATFORM_CALCULUS || PLATFORM_ROBERT +#elif PLATFORM_SILK || PLATFORM_CALCULUS || PLATFORM_ROBERT enum { OTP_HWVER1 = 0, OTP_HWVER2 = 1, @@ -55,6 +55,15 @@ enum { NUM_OTP_SLOTS = 16, }; +#elif PLATFORM_ASTERIX +enum { + OTP_HWVER = 0, + OTP_SERIAL = 1, + OTP_PCBA_SERIAL = 2, + + NUM_OTP_SLOTS = 4, +}; + #else #error "OTP Slots not set for platform" #endif diff --git a/src/fw/drivers/wscript_build b/src/fw/drivers/wscript_build index 3a1d6230a..4c1c3869f 100644 --- a/src/fw/drivers/wscript_build +++ b/src/fw/drivers/wscript_build @@ -1017,11 +1017,10 @@ if mcu_family in ('STM32F2', 'STM32F4', 'STM32F7'): ], ) elif mcu_family == 'NRF52840': - # FIXME: provide working implementation bld.objects( name='driver_otp', source=[ - 'nrf5/stubs/otp.c', + 'nrf5/otp.c', ], use=[ 'fw_includes', diff --git a/src/fw/mfg/mfg_serials.c b/src/fw/mfg/mfg_serials.c index 6e0887110..885b61b92 100644 --- a/src/fw/mfg/mfg_serials.c +++ b/src/fw/mfg/mfg_serials.c @@ -27,13 +27,21 @@ #include #endif +#if PLATFORM_ASTERIX + +static const uint8_t OTP_SERIAL_SLOT_INDICES[] = {OTP_SERIAL}; +static const uint8_t OTP_PCBA_SLOT_INDICES[] = {OTP_PCBA_SERIAL}; +static const uint8_t OTP_HWVER_SLOT_INDICES[] = {OTP_HWVER}; + +#else + static const uint8_t OTP_SERIAL_SLOT_INDICES[] = { OTP_SERIAL1, OTP_SERIAL2, OTP_SERIAL3, OTP_SERIAL4, OTP_SERIAL5 }; static const uint8_t OTP_PCBA_SLOT_INDICES[] = { OTP_PCBA_SERIAL1, OTP_PCBA_SERIAL2, OTP_PCBA_SERIAL3 }; -#if PLATFORM_SILK || PLATFORM_ASTERIX || PLATFORM_CALCULUS || PLATFORM_ROBERT +#if PLATFORM_SILK || PLATFORM_CALCULUS || PLATFORM_ROBERT static const uint8_t OTP_HWVER_SLOT_INDICES[] = { OTP_HWVER1, OTP_HWVER2, OTP_HWVER3, OTP_HWVER4, OTP_HWVER5 }; @@ -41,6 +49,8 @@ static const uint8_t OTP_HWVER_SLOT_INDICES[] = { static const uint8_t OTP_HWVER_SLOT_INDICES[] = {OTP_HWVER1}; #endif +#endif + static const char DUMMY_SERIAL[MFG_SERIAL_NUMBER_SIZE + 1] = "XXXXXXXXXXXX"; // FIXME: shouldn't the dummy HWVER be 9 X's? static const char DUMMY_HWVER[MFG_HW_VERSION_SIZE + 1] = "XXXXXXXX"; @@ -49,12 +59,6 @@ static const char DUMMY_PCBA_SERIAL[MFG_PCBA_SERIAL_NUMBER_SIZE + 1] = "XXXXXXXX static void mfg_print_feedback(const MfgSerialsResult result, const uint8_t index, const char *value, const char *name); const char* mfg_get_serial_number(void) { -#if MICRO_FAMILY_NRF5 - // HACK: we don't have OTP storage on Asterix yet, so we make one up here using FICR.DEVICEID - static char nrf5_serial[MFG_SERIAL_NUMBER_SIZE + 1] = "_NRFXXXXXXXX"; - snprintf(nrf5_serial, sizeof(nrf5_serial), "_NRF%08lx", nrf_ficr_deviceid_get(NRF_FICR, 0)); - return nrf5_serial; -#else // Trying from "most recent" slot to "least recent": for (int i = ARRAY_LENGTH(OTP_SERIAL_SLOT_INDICES) - 1; i >= 0; --i) { const uint8_t index = OTP_SERIAL_SLOT_INDICES[i]; @@ -63,7 +67,6 @@ const char* mfg_get_serial_number(void) { } } return DUMMY_SERIAL; -#endif } const char* mfg_get_hw_version(void) { @@ -240,9 +243,14 @@ static void prv_get_not_so_unique_serial(char *serial_number) { #endif static bool prv_get_more_unique_serial(char *serial_number) { +#if MICRO_FAMILY_NRF5 + // This is actually quite a bit more unique on nRF5. + snprintf(serial_number + 2, MFG_SERIAL_NUMBER_SIZE - 2, "n%08lX", nrf_ficr_deviceid_get(NRF_FICR, 0)); +#else for (int i = 2; i < MFG_SERIAL_NUMBER_SIZE; i += 2) { sniprintf(&serial_number[i], 3 /* 2 hex digits + zero terminator */, "%02X", rand()); } +#endif serial_number[MFG_SERIAL_NUMBER_SIZE] = 0; return true; } diff --git a/third_party/hal_nordic/wscript b/third_party/hal_nordic/wscript index e1d7a3795..7ac9b5e6c 100644 --- a/third_party/hal_nordic/wscript +++ b/third_party/hal_nordic/wscript @@ -4,6 +4,7 @@ def configure(conf): if conf.is_asterix(): conf.env.append_unique('DEFINES', 'NRF_CONFIG_NFCT_PINS_AS_GPIOS') conf.env.append_unique('DEFINES', 'NRFX_GPIOTE_ENABLED=1') + conf.env.append_unique('DEFINES', 'NRFX_NVMC_ENABLED=1') conf.env.append_unique('DEFINES', 'NRFX_PPI_ENABLED=1') conf.env.append_unique('DEFINES', 'NRFX_PWM_ENABLED=1') conf.env.append_unique('DEFINES', 'NRFX_PWM0_ENABLED=1')