Skip to content

fw/nrf52: Use UICR to simulate OTP on nRF5 platforms. [FIRM-35] #177

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

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
67 changes: 67 additions & 0 deletions src/fw/drivers/nrf5/otp.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
#include "drivers/otp.h"

#include "system/passert.h"

#define NRF5_COMPATIBLE
#include <mcu.h>

#include <stdlib.h>
#include <stdint.h>
#include <inttypes.h>
#include <string.h>
#include <stdbool.h>

#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;
}
30 changes: 0 additions & 30 deletions src/fw/drivers/nrf5/stubs/otp.c

This file was deleted.

11 changes: 10 additions & 1 deletion src/fw/drivers/otp.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
Expand Down
3 changes: 1 addition & 2 deletions src/fw/drivers/wscript_build
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
24 changes: 16 additions & 8 deletions src/fw/mfg/mfg_serials.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,20 +27,30 @@
#include <hal/nrf_ficr.h>
#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
};
#else
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";
Expand All @@ -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];
Expand All @@ -63,7 +67,6 @@ const char* mfg_get_serial_number(void) {
}
}
return DUMMY_SERIAL;
#endif
}

const char* mfg_get_hw_version(void) {
Expand Down Expand Up @@ -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;
}
Expand Down
1 change: 1 addition & 0 deletions third_party/hal_nordic/wscript
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
Loading