diff --git a/cmds/Makefile b/cmds/Makefile index 12277978..b06af2e5 100644 --- a/cmds/Makefile +++ b/cmds/Makefile @@ -7,7 +7,7 @@ # PLO_ALLCOMMANDS = alias app bankswitch bitstream blob bootcm4 bootrom bridge call console \ - copy devices dump echo erase go help jffs2 kernel kernelimg lspci map mem mpu otp phfs \ + copy devices dump echo erase go help jffs2 kernel kernelimg lspci map mem memcrypt mpu otp phfs \ ptable reboot script stop test-dev test-ddr wait watchdog vbe PLO_COMMANDS ?= $(PLO_ALLCOMMANDS) diff --git a/cmds/devices.c b/cmds/devices.c index e42be7e8..78b9ffd8 100644 --- a/cmds/devices.c +++ b/cmds/devices.c @@ -31,7 +31,7 @@ static void cmd_devsInfo(void) static const char *devClassName(unsigned int major) { static const char *className[] = { - "uart", "usb", "storage", "tty", "ram", "nand-data", "nand-meta", "nand-raw", "pipe" + "uart", "usb", "storage", "tty", "ram", "nand-data", "nand-meta", "nand-raw", "pipe", "encrypted" }; return (major < (sizeof(className) / sizeof(className[0]))) ? diff --git a/cmds/memcrypt.c b/cmds/memcrypt.c new file mode 100644 index 00000000..70127c81 --- /dev/null +++ b/cmds/memcrypt.c @@ -0,0 +1,290 @@ +/* + * Phoenix-RTOS + * + * Operating system loader + * + * encrypt external memory on STM32N6 + * + * Copyright 2025 Phoenix Systems + * Author: Krzysztof Radzewicz + * + * This file is part of Phoenix-RTOS. + * + * %LICENSE% + */ + +#include "cmd.h" + +#include +#include +#include + +#include +#include + + +#define MEMCRYPT_ENCR "-encr" +#define MEMCRYPT_ENCR_LEN 5 +#define MEMCRYPT_KEY "-key" +#define MEMCRYPT_KEY_LEN 4 +#define MEMCRPYT_AES256 "aes256:" +#define MEMCRYPT_AES256_LEN 7 +#define MEMCRPYT_AES128 "aes128:" +#define MEMCRPYT_AES128_LEN 7 +#define MEMCRPYT_NOEKEON "noekeon:" +#define MEMCRPYT_NOEKEON_LEN 8 +#define MEMCRYPT_KEY_EXPLICIT "x:" +#define MEMCRYPT_KEY_EXPLICIT_LEN 2 +#define MEMCRYPT_KEY_RANDOM "r" +#define MEMCRYPT_KEY_RANDOM_LEN 1 +#define MEMCRYPT_KEY_OTP "p:" +#define MEMCRYPT_KEY_OTP_LEN 2 + +#define MEMCYPT_MAX_KEYSIZE 256 + + +static void cmd_memcryptInfo(void) +{ + lib_printf("Configure external memory encryption. Use <-h> to see usage"); +} + + +static void cmd_memcryptUsage(const char *name) +{ + lib_printf( + "Usage: %s d saddr eaddr -encr algo:mode -key [r|p|x]:[addr:key]\n" + "where:\n" + "\td - device controller number (major.minor) \n" + "\tsaddr - start memory device address to be encrypted\n" + "\teaddr - end memory device address to be encrypted (inclusive)\n" + "\talgo - used cipher: aes128, aes256, noekeon\n" + "\tmode - used mode of operation: 1, 2, ...\n" + "\tr - generate random key\n" + "\tp - use key stored in OTP memory. Requires addr - first OTP word containing the key\n" + "\tx - provide key explicitly. Requires key - value in hexadecimal\n", + name); +} + + +static int cmd_memcryptGetUserKey(const char *string, size_t maxSize, u8 *out) +{ + size_t size; + u32 t, i; + const char *p; + if ((string[0] == '0') && (string[1] == 'x')) { + string += 2; + } + size = hal_strlen(string); + if ((size <= 0) || ((size * 2) > maxSize)) { + return -EINVAL; + } + p = string + size - 1; + i = 0; + while (p >= string) { + if (!lib_isdigit(*p) && !lib_isalpha(*p)) { + return -EINVAL; + } + t = *p - '0'; + if (t > 9) { + t = (*p | 0x20) - 'a' + 10; + } + if (t >= 16) { + return -EINVAL; + } + + if (i % 2 == 0) { + out[i / 2] = 0; + } + out[i / 2] += t << ((i % 2) * 4); + + p--; + i++; + } + + return EOK; +} + + +static int cmd_memcryptGetOtpKey(int fuse, u8 *out, size_t keysize) +{ + int ret; + u32 val = 0; + size_t i = 0; + if ((keysize % 4) != 0) { + return -EINVAL; + } + while (i < keysize) { + ret = otp_read(fuse, &val); + if (ret < 0) { + return ret; + } + *((u32 *)out) = val; + i += 4; + } + return EOK; +} + + +static int cmd_memcrypt(int argc, char *argv[]) +{ + int ret, major, minor, fuse; + addr_t start, end; + u32 cipher, mode, keysize; + static u8 keyBuffer[MEMCYPT_MAX_KEYSIZE] = {}; + xspi_memcrypt_args_t args = {}; + + lib_printf("\n"); + + /* Memcrypt only checks if device number is correct and parses string arguments. + * Argument validity is checked by the device. */ + + if (argc != 8) { + cmd_memcryptUsage(argv[0]); + return CMD_EXIT_FAILURE; + } + + major = lib_strtoul(argv[1], &argv[1], 0); + if (*argv[1] != '.') { + log_error("\nInvalid device.\n"); + cmd_memcryptUsage(argv[0]); + return CMD_EXIT_FAILURE; + } + minor = lib_strtoul(argv[1] + 1, &argv[1], 0); + if (*argv[1] != '\0') { + log_error("\nInvalid device.\n"); + cmd_memcryptUsage(argv[0]); + return CMD_EXIT_FAILURE; + } + + ret = devs_check(major, minor); + if (ret < 0) { + log_error("\nInvalid device: (%d,%d)\n", major, minor); + return -EINVAL; + } + /* Add check if device is DEV_ENCR_STORAGE */ + if (major != DEV_CRYP_STORAGE) { + log_error("\nInvalid device class: %d\n", major); + return -EINVAL; + } + + /* start end */ + start = lib_strtoul(argv[2], &argv[2], 0); + if (*argv[2] != '\0') { + log_error("\nInvalid start address.\n"); + cmd_memcryptUsage(argv[0]); + return CMD_EXIT_FAILURE; + } + end = lib_strtoul(argv[3], &argv[3], 0); + if (*argv[3] != '\0') { + log_error("\nInvalid end address.\n"); + cmd_memcryptUsage(argv[0]); + return CMD_EXIT_FAILURE; + } + + /* -encr cipher:mode */ + if (hal_strncmp(MEMCRYPT_ENCR, argv[4], MEMCRYPT_ENCR_LEN) != 0) { + log_error("\nMissing argument 3: %s.\n", MEMCRYPT_ENCR); + cmd_memcryptUsage(argv[0]); + return CMD_EXIT_FAILURE; + } + if (hal_strncmp(MEMCRPYT_AES128, argv[5], MEMCRPYT_AES128_LEN) == 0) { + cipher = MCE_CIPHER_AES128; + keysize = 16; + mode = lib_strtoul(argv[5] + MEMCRPYT_AES128_LEN, &argv[5], 0); + if (*argv[5] != 0) { + log_error("\nInvalid encryption mode.\n"); + cmd_memcryptUsage(argv[0]); + return CMD_EXIT_FAILURE; + } + } + else if (hal_strncmp(MEMCRPYT_AES256, argv[5], MEMCRYPT_AES256_LEN) == 0) { + cipher = MCE_CIPHER_AES256; + keysize = 32; + mode = lib_strtoul(argv[5] + MEMCRYPT_AES256_LEN, &argv[5], 0); + if (*argv[5] != 0) { + log_error("\nInvalid encryption mode.\n"); + cmd_memcryptUsage(argv[0]); + return CMD_EXIT_FAILURE; + } + } + else if (hal_strncmp(MEMCRPYT_NOEKEON, argv[5], MEMCRPYT_NOEKEON_LEN) == 0) { + cipher = MCE_CIPHER_NOEKEON; + keysize = 16; + mode = lib_strtoul(argv[5] + MEMCRPYT_NOEKEON_LEN, &argv[5], 0); + if (*argv[5] != 0) { + log_error("\nInvalid encryption mode.\n"); + cmd_memcryptUsage(argv[0]); + return CMD_EXIT_FAILURE; + } + } + else { + log_error("\nUnknown cipher: %s\n", argv[5]); + cmd_memcryptUsage(argv[0]); + return CMD_EXIT_FAILURE; + } + + /* -key k:val */ + if (hal_strncmp(MEMCRYPT_KEY, argv[6], MEMCRYPT_KEY_LEN) != 0) { + log_error("\nMissing argument 5: %s.\n", MEMCRYPT_KEY); + cmd_memcryptUsage(argv[0]); + return CMD_EXIT_FAILURE; + } + if (hal_strncmp(MEMCRYPT_KEY_EXPLICIT, argv[7], MEMCRYPT_KEY_EXPLICIT_LEN) == 0) { + ret = cmd_memcryptGetUserKey(argv[7] + MEMCRYPT_KEY_EXPLICIT_LEN, keysize, keyBuffer); + if (ret < 0) { + hal_memset(keyBuffer, 0, MEMCYPT_MAX_KEYSIZE); + log_error("\nInvalid key format.\n"); + cmd_memcryptUsage(argv[0]); + return CMD_EXIT_FAILURE; + } + } + else if (hal_strncmp(MEMCRYPT_KEY_OTP, argv[7], MEMCRYPT_KEY_OTP_LEN) == 0) { + fuse = lib_strtol(argv[7] + MEMCRYPT_KEY_OTP_LEN, &argv[7], 0); + if (*argv[7] != '\0') { + log_error("\nInvalid fuse format.\n"); + cmd_memcryptUsage(argv[0]); + return CMD_EXIT_FAILURE; + } + ret = cmd_memcryptGetOtpKey(fuse, keyBuffer, keysize); + if (ret < 0) { + hal_memset(keyBuffer, 0, MEMCYPT_MAX_KEYSIZE); + log_error("\nInvalid OTP fuse: %d\n", fuse); + cmd_memcryptUsage(argv[0]); + return CMD_EXIT_FAILURE; + } + } + else if (hal_strncmp(MEMCRYPT_KEY_RANDOM, argv[7], MEMCRYPT_KEY_RANDOM_LEN) == 0) { + ret = _stm32_rngRead(keyBuffer, keysize); + if (ret < 0) { + hal_memset(keyBuffer, 0, MEMCYPT_MAX_KEYSIZE); + log_error("\nFailed to generate random key\n"); + return CMD_EXIT_FAILURE; + } + } + else { + log_error("\nUnknown key option: %s\n", argv[7]); + cmd_memcryptUsage(argv[0]); + return CMD_EXIT_FAILURE; + } + + args.cipher = cipher; + args.mode = mode; + args.start = start; + args.end = end; + args.key = keyBuffer; + ret = devs_control(major, minor, DEV_CONTROL_MEMCRYPT, (void *)&args); + if (ret < 0) { + hal_memset(keyBuffer, 0, MEMCYPT_MAX_KEYSIZE); + log_error("\nXSPI driver error: %d\n", ret); + cmd_memcryptUsage(argv[0]); + return CMD_EXIT_FAILURE; + } + + hal_memset(keyBuffer, 0, MEMCYPT_MAX_KEYSIZE); + return CMD_EXIT_SUCCESS; +} + + +static const cmd_t memcrypt_cmd __attribute__((section("commands"), used)) = { + .name = "memcrypt", .run = cmd_memcrypt, .info = cmd_memcryptInfo +}; diff --git a/cmds/otp.c b/cmds/otp.c index 660d05b3..0ced0b93 100644 --- a/cmds/otp.c +++ b/cmds/otp.c @@ -15,7 +15,11 @@ #include "cmd.h" +#if defined(__CPU_STM32N6) +#include +#else #include +#endif #include diff --git a/devices/devs.c b/devices/devs.c index 0710c4ec..118f601f 100644 --- a/devices/devs.c +++ b/devices/devs.c @@ -17,7 +17,7 @@ #include -#define SIZE_MAJOR 9 +#define SIZE_MAJOR 10 #define SIZE_MINOR 16 struct { diff --git a/devices/devs.h b/devices/devs.h index 216ab4b4..0c5f22af 100644 --- a/devices/devs.h +++ b/devices/devs.h @@ -18,15 +18,16 @@ #include -#define DEV_UART 0 -#define DEV_USB 1 -#define DEV_STORAGE 2 -#define DEV_TTY 3 -#define DEV_RAM 4 -#define DEV_NAND_DATA 5 -#define DEV_NAND_META 6 -#define DEV_NAND_RAW 7 -#define DEV_PIPE 8 +#define DEV_UART 0 +#define DEV_USB 1 +#define DEV_STORAGE 2 +#define DEV_TTY 3 +#define DEV_RAM 4 +#define DEV_NAND_DATA 5 +#define DEV_NAND_META 6 +#define DEV_NAND_RAW 7 +#define DEV_PIPE 8 +#define DEV_CRYP_STORAGE 9 #define DEVS_ITER_STOP ((const dev_t *)-1) @@ -34,6 +35,7 @@ #define DEV_CONTROL_GETBAUD 2 #define DEV_CONTROL_GETPROP_TOTALSZ 3 #define DEV_CONTROL_GETPROP_BLOCKSZ 4 +#define DEV_CONTROL_MEMCRYPT 5 /* clang-format off */ enum { dev_isMappable = 0, dev_isNotMappable }; diff --git a/devices/flash-stm32xspi/Makefile b/devices/flash-stm32xspi/Makefile index 8d910a22..35ea835a 100644 --- a/devices/flash-stm32xspi/Makefile +++ b/devices/flash-stm32xspi/Makefile @@ -6,4 +6,4 @@ # %LICENSE% # -OBJS += $(addprefix $(PREFIX_O)devices/flash-stm32xspi/, xspi_common.o xspi_hyperbus.o xspi_regcom.o flash_params.o) +OBJS += $(addprefix $(PREFIX_O)devices/flash-stm32xspi/, xspi_common.o xspi_hyperbus.o xspi_regcom.o flash_params.o mce.o) diff --git a/devices/flash-stm32xspi/mce.c b/devices/flash-stm32xspi/mce.c new file mode 100644 index 00000000..068ba4e0 --- /dev/null +++ b/devices/flash-stm32xspi/mce.c @@ -0,0 +1,329 @@ +/* + * Phoenix-RTOS + * + * Operating system loader + * + * STM32 Memory Cipher Engine driver + * + * Copyright 2025 Phoenix Systems + * Author: Krzysztof Radzewicz + * + * This file is part of Phoenix-RTOS. + * + * %LICENSE% + */ + + +#include "mce.h" + + +static mce_common_t common = {}; + +const static mce_setup_t setup = { + .base = { MCE1_BASE, MCE2_BASE, MCE3_BASE, MCE4_BASE }, + .region = { + { MCE1_REG_START, MCE1_REG_END }, + { MCE2_REG_START, MCE2_REG_END }, + { MCE3_REG_START, MCE3_REG_END }, + { MCE4_REG_START, MCE4_REG_END }, + }, + .dev = { dev_mce1, dev_mce2, dev_mce3, dev_mce4 } +}; + + +/* reg - lowest key register + * keysize - in bytes (16/32) */ +static int mce_storeKey(mce_t per, u32 reg, u8 *key, u32 keysize) +{ + u32 *key32 = (u32 *)key; + /* Assume correct arguments */ + for (u32 i = 0; i * 4 < keysize; i++) { + *(setup.base[per] + (reg + i)) = *(key32++); + } + return EOK; +} + + +static void mce_waitBusy(mce_t per, u32 reg, u32 flag) +{ + while ((*(setup.base[per] + reg) & flag) == 0) { + ; + } +} + + +/* Configure MCE keysize and master/fast mastery key sources. + * Key bytes in little endian. + * Keys must have correct length, according to cipher. + * For restrictions see: RM0486 ch. 51 */ +static int mce_configurePerInternal(mce_t per, u32 cipher, u8 *mk, u8 *fmk) +{ + u32 keysize, t; + /* Select Cipher before writing key */ + if (common.cipher[per] == 0) { + t = *(setup.base[per] + mce_cr) & ~MCE_CR_CIPHERSEL_MASK; + t |= (cipher << MCE_CR_CIPHERSEL_OFF); + *(setup.base[per] + mce_cr) = t; + hal_cpuDataMemoryBarrier(); + common.cipher[per] = cipher; + } + + /* For now only write keys if no valid key present */ + keysize = (cipher == MCE_CIPHER_AES256) ? (32) : (16); + if ((mk != NULL) && ((*(setup.base[per] + mce_sr) & MCE_SR_MKVALID) == 0)) { + mce_storeKey(per, mce_mkeyr1, mk, keysize); + mce_waitBusy(per, mce_sr, MCE_SR_MKVALID); + } + if ((fmk != NULL) && ((*(setup.base[per] + mce_sr) & MCE_SR_FMKVALID) == 0)) { + mce_storeKey(per, mce_fmkeyr1, fmk, keysize); + mce_waitBusy(per, mce_sr, MCE_SR_FMKVALID); + } + + return EOK; +} + + +/* Configure and enable MCE cipher context z=1,2. + * Cannot reconfigure context already in use. + * If stream mode used, must provide 8 byte nonce value (little endian). Set to NULL otherwise. */ +static int mce_configureCipherContextInternal(mce_t per, u8 ctxid, u16 version, u32 mode, u8 *nonce, u8 *ctxkey) +{ + volatile u32 *cccfgr; + u32 cckeyr0; + u32 ccnr0; + u32 t; + if (ctxid != 1 && ctxid != 2) { + return -EINVAL; + } + if (mode == MCE_MODE_STREAM && nonce == NULL) { + return -EINVAL; + } + else if (mode != MCE_MODE_STREAM && nonce != NULL) { + return -EINVAL; + } + + /* For now configure context only if it's not yet enabled */ + if (((*(setup.base[per] + MCE_CCCFGR(ctxid)) & MCE_CCCFGR_CCEN) != 0)) { + return EOK; + } + + cccfgr = setup.base[per] + MCE_CCCFGR(ctxid); + t = *cccfgr & ~((0xffff << MCE_CCCFGR_VERSION_OFF) | (MCE_MODE_MASK << MCE_CCCFGR_MODE_OFF)); + t |= (mode << MCE_CCCFGR_MODE_OFF); + if (mode == MCE_MODE_STREAM) { + ccnr0 = (ctxid == 1) ? (mce_cc1nr0) : (mce_cc2nr0); + mce_storeKey(per, ccnr0, nonce, 8); + t |= (version << MCE_CCCFGR_VERSION_OFF); + } + *cccfgr = t; + hal_cpuDataMemoryBarrier(); + + if (ctxkey != NULL) { + cckeyr0 = (ctxid == 1) ? (mce_cc1keyr0) : (mce_cc2keyr0); + mce_storeKey(per, cckeyr0, ctxkey, 16); + /* TODO: consider computing the expected checksum and compare it */ + mce_waitBusy(per, MCE_CCCFGR(ctxid), MCE_CCCFGR_CRC_MASK); /* Wait until key check sum computed */ + /* NOTE: This could be a bug waiting to happen. I think we can't just wait for the checksum to not be zero? But there is no other way to know the key is usable now. */ + hal_cpuDataMemoryBarrier(); + } + + *cccfgr |= MCE_CCCFGR_CCEN; + mce_waitBusy(per, MCE_CCCFGR(ctxid), MCE_CCCFGR_CCEN); + + return EOK; +} + + +/* Configure and enable a memory region to be encrypted/decrypted, by MCE. + * Cannot reconfigure a region already in use. + * Relevant keys/cipher context needs to be configured first. + * Set ctxid to 0 if using master-key of fast-master-key. */ +static int mce_configureRegionInternal(mce_t per, mce_reg_t reg, u32 mode, u32 ctxid, addr_t first, addr_t last) +{ + volatile u32 *cccfgr; + u32 t; + if (per < 0 || per >= mce_count) { + return -EINVAL; + } + if (reg < 0 || reg >= mce_regcount) { + return -EINVAL; + } + if (mode != MCE_MODE_STREAM && mode != MCE_MODE_NBLOCK && mode != MCE_MODE_FBLOCK) { + return -EINVAL; + } + if (ctxid != 0 && ctxid != 1 && ctxid != 2) { + return -EINVAL; + } + if (mode == MCE_MODE_STREAM) { + if (ctxid == 0) { + return -EINVAL; + } + } + if (ctxid != 0) { + cccfgr = setup.base[per] + MCE_CCCFGR(ctxid); + if (((*cccfgr >> MCE_CCCFGR_MODE_OFF) & MCE_MODE_MASK) != mode) { + return -EINVAL; + } + } + if (last < first) { + return -EINVAL; + } + if (first < setup.region[per].base || last > setup.region[per].last) { + return -EINVAL; + } + if ((first % MCE_REG_BOUNDARY != 0) || ((last + 1) % MCE_REG_BOUNDARY != 0)) { + return -EINVAL; + } + + t = *(setup.base[per] + MCE_REGCR(reg)) & ~(MCE_REGCR_CTXID_MASK | MCE_REGCR_ENC_MASK); + t |= (mode << MCE_REGCR_ENC_OFF) | (ctxid << MCE_REGCR_CTXID_OFF); + *(setup.base[per] + MCE_REGCR(reg)) = t; + hal_cpuDataMemoryBarrier(); + + *(setup.base[per] + MCE_SADDR(reg)) = first; + *(setup.base[per] + MCE_EADDR(reg)) = last; + hal_cpuDataMemoryBarrier(); + + + *(setup.base[per] + MCE_REGCR(reg)) |= MCE_REGCR_BREN; + + return EOK; +}; + + +/* Lock some part of MCE configuration. Recommended to use global lock after mce regions are configured. */ +static int mce_configureLockInternal(mce_t per, mce_lock_t lock) +{ + if ((lock & mce_ctx1keylock) != 0) { + *(setup.base[per] + mce_cc1cfgr) |= MCE_CCCFGR_KEYLOCK; + } + if ((lock & mce_ctx2keylock) != 0) { + *(setup.base[per] + mce_cc2cfgr) |= MCE_CCCFGR_KEYLOCK; + } + if ((lock & mce_ctx1lock) != 0) { + *(setup.base[per] + mce_cc1cfgr) |= MCE_CCCFGR_CCLOCK; + } + if ((lock & mce_ctx2lock) != 0) { + *(setup.base[per] + mce_cc2cfgr) |= MCE_CCCFGR_CCLOCK; + } + if ((lock & mce_masterlock) != 0) { + *(setup.base[per] + mce_cr) |= MCE_CR_MKLOCK; + } + if ((lock & mce_globallock) != 0) { + *(setup.base[per] + mce_cr) |= MCE_CR_GLOCK; + } + return EOK; +} + + +/* start - refers to device memory + * end - same as above, exclusive */ +static int mce_validateParamsInternal(mce_t per, mce_reg_t reg, addr_t start, addr_t end, u32 cipher, u32 mode, u8 *key) +{ + if ((per < 0) || (per >= mce_count)) { + return -EINVAL; + } + if (reg < 0 || reg >= mce_regcount) { + return -EINVAL; + } + if (cipher != MCE_CIPHER_AES128 && cipher != MCE_CIPHER_AES256 && cipher != MCE_CIPHER_NOEKEON) { + return -EINVAL; + } + if (mode != MCE_MODE_STREAM && mode != MCE_MODE_NBLOCK && mode != MCE_MODE_FBLOCK) { + return -EINVAL; + } + + /* Check cipher/mode validity */ + if (mode == MCE_MODE_STREAM) { + if (cipher == MCE_CIPHER_AES256) { + return -EINVAL; + } + } + if ((common.cipher[per] != 0) && (common.cipher[per] != cipher)) { + return -EINVAL; + } + + + /* Check size granularity. We assume that controller made sure the region is correct otherwise */ + if (((start % MCE_REG_BOUNDARY) != 0) || ((end % MCE_REG_BOUNDARY) != 0)) { + return -EINVAL; + } + + return EOK; +} + + +/* MCE public interface functions */ +/* Note: For now we only allow a single region per xspi memory, so we don't have to worry about reusing previous keys. */ +int mce_configureRegion(mce_t per, mce_reg_t reg, addr_t start, addr_t end, u32 cipher, u32 mode, u8 *key) +{ + int ret; + u8 *mk = NULL, *fmk = NULL; + u32 ctxid = 0; + u8 nonce[8]; + u16 version; + /* Check basic parameters */ + ret = mce_validateParamsInternal(per, reg, start, end, cipher, mode, key); + if (ret < 0) { + return ret; + } + + /* The RM says that MCE clocks are automatically managed by the device, but that only applies to resetting clocks. + * We still need to turn them on. */ + if (common.initialized[per] == 0) { + _stm32_rccSetDevClock(setup.dev[per], 1); + common.initialized[per] = 1; + } + + if (cipher == MCE_CIPHER_AES256) { + if (mode == MCE_MODE_NBLOCK) { + mk = key; + } + else if (mode == MCE_MODE_FBLOCK) { + fmk = key; + } + } + ret = mce_configurePerInternal(per, cipher, mk, fmk); + if (ret < 0) { + return ret; + } + + if ((cipher == MCE_CIPHER_AES128) || (cipher == MCE_CIPHER_NOEKEON)) { + if (common.contextCount[per] < 2) { + ctxid = ++common.contextCount[per]; + } + else { + ctxid = common.contextCount[per]; + } + if (mode == MCE_MODE_STREAM) { + ret = _stm32_rngRead((u8 *)&version, 2); + if (ret < 0) { + return ret; + } + ret = _stm32_rngRead(nonce, 8); + if (ret < 0) { + return ret; + } + /* Note: Consider writing zeros to nonce and version as they will remain in memory after function returns. */ + ret = mce_configureCipherContextInternal(per, ctxid, version, mode, nonce, key); + } + else { + /* Configure context without version and nonce */ + ret = mce_configureCipherContextInternal(per, ctxid, 0, mode, NULL, key); + } + + if (ret < 0) { + return ret; + } + } + + ret = mce_configureRegionInternal(per, reg, mode, ctxid, setup.region[per].base + start, setup.region[per].base + end - 1); + if (ret < 0) { + return ret; + } + + /* No further reconfiguration required. Lock registers */ + mce_configureLockInternal(per, mce_globallock); + + return EOK; +} diff --git a/devices/flash-stm32xspi/mce.h b/devices/flash-stm32xspi/mce.h new file mode 100644 index 00000000..4eef50b1 --- /dev/null +++ b/devices/flash-stm32xspi/mce.h @@ -0,0 +1,187 @@ +/* + * Phoenix-RTOS + * + * Operating system loader + * + * STM32 MCE driver + * + * Copyright 2025 Phoenix Systems + * Author: Krzysztof Radzewicz + * + * This file is part of Phoenix-RTOS. + * + * %LICENSE% + */ + + +#ifndef _MCE_H_ +#define _MCE_H_ + +#include +#include + +#include +#include + +#define MCE1_BASE ((void *)0x5802b800) +#define MCE2_BASE ((void *)0x5802bc00) +#define MCE3_BASE ((void *)0x5802c000) +#define MCE4_BASE ((void *)0x5802e000) + +#define MCE_MODE_STREAM 1UL +#define MCE_MODE_NBLOCK 2UL +#define MCE_MODE_FBLOCK 3UL +#define MCE_MODE_MASK 3UL + +#define MCE_CIPHER_AES128 1UL +#define MCE_CIPHER_NOEKEON 2UL +#define MCE_CIPHER_AES256 3UL + +#define MCE_CR_CIPHERSEL_MASK (3UL << 4) +#define MCE_CR_CIPHERSEL_OFF 4UL +#define MCE_CR_MKLOCK (1UL << 1) +#define MCE_CR_GLOCK (1UL << 0) + +#define MCE_SR_MKVALID (1UL << 0) +#define MCE_SR_FMKVALID (1UL << 2) +#define MCE_SR_ENCDIS (1UL << 4) + +#define MCE_IASR_IAEF (1UL << 1) + +#define MCE_REGCR_BREN (1UL << 0) +#define MCE_REGCR_CTXID_OFF 9UL +#define MCE_REGCR_CTXID_MASK (3UL << 9) +#define MCE_REGCR_ENC_OFF 14UL +#define MCE_REGCR_ENC_MASK (3UL << 14) + +#define MCE_REGCR(reg_id) (mce_regcr1 + 4 * (reg_id)) +#define MCE_SADDR(reg_id) (mce_saddr1 + 4 * (reg_id)) +#define MCE_EADDR(reg_id) (mce_eaddr1 + 4 * (reg_id)) + +#define MCE_CCCFGR_VERSION_OFF 16UL +#define MCE_CCCFGR_CRC_MASK (0xffUL << 8) +#define MCE_CCCFGR_MODE_OFF 4UL +#define MCE_CCCFGR_KEYLOCK (1UL << 2) +#define MCE_CCCFGR_CCLOCK (1UL << 1) +#define MCE_CCCFGR_CCEN (1UL << 0) +#define MCE_CCCFGR(ctxid) (((ctxid) == 1) ? (mce_cc1cfgr) : (mce_cc2cfgr)) + + +#define MCE1_REG_START 0x90000000 +#define MCE1_REG_END 0x9fffffff +#define MCE2_REG_START 0x70000000 +#define MCE2_REG_END 0x7fffffff +#define MCE3_REG_START 0x80000000 +#define MCE3_REG_END 0x8fffffff +#define MCE4_REG_START 0x60000000 +#define MCE4_REG_END 0x6fffffff + +#define MCE_REG_BOUNDARY 4 * 1024 + +/* MCE peripherals */ +typedef enum { + mce1, + mce2, + mce3, + mce4, + mce_count, +} mce_t; + +/* MCE regions */ +typedef enum { + mce_r1, + mce_r2, + mce_r3, + mce_r4, + mce_regcount, +} mce_reg_t; + +/* MCE lock types */ +typedef enum { + mce_globallock = (1 << 0), + mce_masterlock = (1 << 1), + mce_ctx1lock = (1 << 2), + mce_ctx2lock = (1 << 3), + mce_ctx1keylock = (1 << 4), + mce_ctx2keylock = (1 << 5), + mce_lockcount, +} mce_lock_t; + +enum { + mce_cr, + mce_sr, + mce_iasr, + mce_iacr, + mce_iaier, + mce_iaddr = 0x9, + mce_regcr1 = 0x10, + mce_saddr1, + mce_eaddr1, + mce_regcr2 = 0x14, + mce_saddr2, + mce_eaddr2, + mce_regcr3 = 0x18, + mce_saddr3, + mce_eaddr3, + mce_regcr4 = 0x1c, + mce_saddr4, + mce_eaddr4, + mce_mkeyr1 = 0x80, + mce_mkeyr2, + mce_mkeyr3, + mce_mkeyr4, + mce_mkeyr5, + mce_mkeyr6, + mce_mkeyr7, + mce_mkeyr8, + mce_fmkeyr1 = 0x88, + mce_fmkeyr2, + mce_fmkeyr3, + mce_fmkeyr4, + mce_fmkeyr5, + mce_fmkeyr6, + mce_fmkeyr7, + mce_fmkeyr8, + mce_cc1cfgr = 0x90, + mce_cc1nr0, + mce_cc1nr1, + mce_cc1keyr0, + mce_cc1keyr1, + mce_cc1keyr2, + mce_cc1keyr3, + mce_cc2cfgr, + mce_cc2nr0, + mce_cc2nr1, + mce_cc2keyr0, + mce_cc2keyr1, + mce_cc2keyr2, + mce_cc2keyr3, +}; + +typedef struct mce_common_t { + u8 initialized[mce_count]; + u8 cipher[mce_count]; + u8 contextCount[mce_count]; +} mce_common_t; + +typedef struct mce_regAddr_t { + u32 base, last; +} mce_regAddr_t; + +typedef struct mce_setup_t { + volatile u32 *base[mce_count]; + mce_regAddr_t region[mce_count]; + unsigned int dev[mce_count]; +} mce_setup_t; + + +/* Public MCE interface */ + + +int mce_configureRegion(mce_t per, mce_reg_t reg, addr_t start, addr_t end, u32 cipher, u32 mode, u8 *key); + + +void mce_disable(mce_t per, mce_reg_t reg); + + +#endif diff --git a/devices/flash-stm32xspi/xspi_common.c b/devices/flash-stm32xspi/xspi_common.c index 4a8b6d2d..ffc3b457 100644 --- a/devices/flash-stm32xspi/xspi_common.c +++ b/devices/flash-stm32xspi/xspi_common.c @@ -63,6 +63,7 @@ const xspi_ctrlParams_t xspi_ctrlParams[XSPI_N_CONTROLLERS] = { .spiPort = XSPIM_PORT2, .chipSelect = XSPI_CHIPSELECT_NCS1, .isHyperbus = XSPI2_IS_HYPERBUS, + .mceDev = mce2, }, { .start = XSPI1_REG_BASE, @@ -78,6 +79,7 @@ const xspi_ctrlParams_t xspi_ctrlParams[XSPI_N_CONTROLLERS] = { .spiPort = XSPIM_PORT1, .chipSelect = XSPI_CHIPSELECT_NCS2, .isHyperbus = XSPI1_IS_HYPERBUS, + .mceDev = mce1, }, }; @@ -142,12 +144,20 @@ static const struct { }; +/* Note: Consider moving this to xspi_CtrlParams[minor] ? */ u32 xspi_memSize[XSPI_N_CONTROLLERS]; static struct { int xspimDone; -} xspi_common; + int xspiDone[XSPI_N_CONTROLLERS]; + u8 regions[XSPI_N_CONTROLLERS]; /* Allocated region count */ + xspi_memregion_t region[XSPI_N_CONTROLLERS][XSPI_MCE_REGIONS]; +} xspi_common = { + .xspiDone = {}, + .regions = {}, + .region = {}, +}; /* Function waits between 1 and 2 ms */ @@ -169,8 +179,49 @@ static int xspidrv_isValidAddress(unsigned int minor, u32 off, size_t size) return 0; } - if ((off < fsize) && ((off + size) <= fsize)) { - return 1; + if (!((off < fsize) && ((off + size) <= fsize))) { + return 0; + } + + for (int i = 0; i < XSPI_MCE_REGIONS; i++) { + if (xspi_common.region[minor][i].encrypt == 0) { + continue; + } + /* Range overlaps */ + if ((xspi_common.region[minor][i].start <= off) && (end >= xspi_common.region[minor][i].start)) { + return 0; + } + else if ((off < xspi_common.region[minor][i].end) && (end > xspi_common.region[minor][i].start)) { + return 0; + } + } + + return 1; +} + + +static int xspidrv_cryp_isValidAddress(unsigned int minor, u32 off, size_t size) +{ + size_t fsize = xspi_memSize[minor]; + size_t end = off + size; + if (end < off) { + /* Integer overflow has occurred */ + return 0; + } + + /* Note: shouldn't this be ((off + size) < fsize) ? */ + if (!((off < fsize) && ((off + size) <= fsize))) { + return 0; + } + + for (int i = 0; i < mce_regcount; i++) { + if (xspi_common.region[minor][i].encrypt == 0) { + continue; + } + /* Range inside region */ + if ((xspi_common.region[minor][i].start >= off) && (end <= xspi_common.region[minor][i].end)) { + return 1; + } } return 0; @@ -331,6 +382,21 @@ static int xspidrv_sync(unsigned int minor) } +static int xspidrv_cryp_sync(unsigned int minor) +{ + if (xspidrv_isValidMinor(minor) == 0) { + return -EINVAL; + } + + if (xspi_ctrlParams[minor].isHyperbus != 0) { + return xspi_hb_sync(minor); + } + else { + return xspi_regcom_sync(minor); + } +} + + static ssize_t xspidrv_read(unsigned int minor, addr_t offs, void *buff, size_t len, time_t timeout) { if ((xspidrv_isValidMinor(minor) == 0) || (xspidrv_isValidAddress(minor, offs, len) == 0)) { @@ -346,6 +412,21 @@ static ssize_t xspidrv_read(unsigned int minor, addr_t offs, void *buff, size_t } +static ssize_t xspidrv_cryp_read(unsigned int minor, addr_t offs, void *buff, size_t len, time_t timeout) +{ + if ((xspidrv_isValidMinor(minor) == 0) || (xspidrv_cryp_isValidAddress(minor, offs, len) == 0)) { + return -EINVAL; + } + + if (xspi_ctrlParams[minor].isHyperbus != 0) { + return xspi_hb_read(minor, offs, buff, len, timeout); + } + else { + return xspi_regcom_read(minor, offs, buff, len, timeout); + } +} + + static ssize_t xspidrv_write(unsigned int minor, addr_t offs, const void *buff, size_t len) { if ((xspidrv_isValidMinor(minor) == 0) || (xspidrv_isValidAddress(minor, offs, len) == 0)) { @@ -361,6 +442,21 @@ static ssize_t xspidrv_write(unsigned int minor, addr_t offs, const void *buff, } +static ssize_t xspidrv_cryp_write(unsigned int minor, addr_t offs, const void *buff, size_t len) +{ + if ((xspidrv_isValidMinor(minor) == 0) || (xspidrv_cryp_isValidAddress(minor, offs, len) == 0)) { + return -EINVAL; + } + + if (xspi_ctrlParams[minor].isHyperbus != 0) { + return xspi_hb_write(minor, offs, buff, len); + } + else { + return xspi_regcom_write_mapped(minor, offs, buff, len); + } +} + + static ssize_t xspidrv_erase(unsigned int minor, addr_t offs, size_t len, unsigned int flags) { if (xspidrv_isValidMinor(minor) == 0) { @@ -380,6 +476,25 @@ static ssize_t xspidrv_erase(unsigned int minor, addr_t offs, size_t len, unsign } +static ssize_t xspidrv_cryp_erase(unsigned int minor, addr_t offs, size_t len, unsigned int flags) +{ + if (xspidrv_isValidMinor(minor) == 0) { + return -EINVAL; + } + + if ((len != (size_t)-1) && (xspidrv_cryp_isValidAddress(minor, offs, len) == 0)) { + return -EINVAL; + } + + if (xspi_ctrlParams[minor].isHyperbus != 0) { + return xspi_hb_erase(minor, offs, len, flags); + } + else { + return xspi_regcom_erase(minor, offs, len, flags); + } +} + + static int xspidrv_map(unsigned int minor, addr_t addr, size_t sz, int mode, addr_t memaddr, size_t memsz, int memmode, addr_t *a) { size_t ctrlSz; @@ -413,6 +528,89 @@ static int xspidrv_map(unsigned int minor, addr_t addr, size_t sz, int mode, add } +static int xspidrv_cryp_map(unsigned int minor, addr_t addr, size_t sz, int mode, addr_t memaddr, size_t memsz, int memmode, addr_t *a) +{ + size_t ctrlSz; + addr_t fStart; + + if (xspidrv_isValidMinor(minor) == 0) { + return -EINVAL; + } + + fStart = (addr_t)xspi_ctrlParams[minor].start; + ctrlSz = xspi_ctrlParams[minor].size; + *a = fStart; + + /* Check if region is located on device */ + if (xspidrv_cryp_isValidAddress(minor, addr, sz) == 0) { + return -EINVAL; + } + + /* Check if flash is mappable to map region */ + if (fStart <= memaddr && (fStart + ctrlSz) >= (memaddr + memsz)) { + return dev_isMappable; + } + + /* Device mode cannot be higher than map mode to copy data */ + if ((mode & memmode) != mode) { + return -EINVAL; + } + + /* Data can be copied from device to map */ + return dev_isNotMappable; +} + + +static int xspidrv_cryp_control(unsigned int minor, int cmd, void *args) +{ + const xspi_ctrlParams_t *p; + const xspi_memcrypt_args_t *mce_args; + mce_reg_t reg; + int ret; + u32 size; + + ret = xspidrv_isValidMinor(minor); + if (ret < 0) { + return -EINVAL; + } + p = &xspi_ctrlParams[minor]; + if (cmd != DEV_CONTROL_MEMCRYPT) { + return -EINVAL; + } + if (args == NULL) { + return -EINVAL; + } + mce_args = (const xspi_memcrypt_args_t *)args; + if (xspi_common.regions[minor] >= XSPI_MCE_REGIONS) { + return -EINVAL; + } + if (mce_args->start >= mce_args->end) { + return -EINVAL; + } + size = mce_args->end - mce_args->start; + /* Check if the region is currently non encrypted memory. Note: Is it a bad idea depend on a xspidrv_ function? */ + ret = xspidrv_isValidAddress(minor, mce_args->start, size); + if (ret < 0) { + return -EINVAL; + } + + reg = xspi_common.regions[minor]; + + ret = mce_configureRegion(p->mceDev, reg, mce_args->start, mce_args->end, mce_args->cipher, mce_args->mode, mce_args->key); + if (ret < 0) { + return -EINVAL; + } + + /* Remember that region is configured */ + xspi_common.region[minor][reg].start = mce_args->start; + xspi_common.region[minor][reg].end = mce_args->end; + xspi_common.region[minor][reg].encrypt = 1; + xspi_common.regions[minor]++; + + return EOK; +} + + static int xspidrv_init(unsigned int minor) { const xspi_ctrlParams_t *p; @@ -425,6 +623,10 @@ static int xspidrv_init(unsigned int minor) xspi_common.xspimDone = 1; } + if (xspi_common.xspiDone[minor] != 0) { + return EOK; + } + p = &xspi_ctrlParams[minor]; _stm32_rccSetIPClk(p->clksel.sel, p->clksel.val); @@ -468,6 +670,8 @@ static int xspidrv_init(unsigned int minor) *(p->ctrl + xspi_dcr3) = 0; *(p->ctrl + xspi_dcr4) = 0; + xspi_common.xspiDone[minor] = 1; + if (p->isHyperbus != 0) { return xspi_hb_init(minor); } @@ -487,7 +691,7 @@ static int xspidrv_done(unsigned int minor) } -__attribute__((constructor)) static void xspidrv_reg(void) +__attribute__((constructor)) static void xspi_common_reg(void) { static const dev_ops_t opsStorageSTM32_XSPI = { .sync = xspidrv_sync, @@ -506,4 +710,22 @@ __attribute__((constructor)) static void xspidrv_reg(void) }; devs_register(DEV_STORAGE, XSPI_N_CONTROLLERS, &devStorageSTM32_XSPI); + + static const dev_ops_t opsCrypStorageSTM32_XSPI = { + .sync = xspidrv_cryp_sync, + .map = xspidrv_cryp_map, + .control = xspidrv_cryp_control, + .read = xspidrv_cryp_read, + .write = xspidrv_cryp_write, + .erase = xspidrv_cryp_erase, + }; + + static const dev_t devCrypStorageSTM32_XSPI = { + .name = "storage-cryp-stm32xspi", + .init = xspidrv_init, + .done = xspidrv_done, + .ops = &opsCrypStorageSTM32_XSPI, + }; + + devs_register(DEV_CRYP_STORAGE, XSPI_N_CONTROLLERS, &devCrypStorageSTM32_XSPI); } diff --git a/devices/flash-stm32xspi/xspi_common.h b/devices/flash-stm32xspi/xspi_common.h index a4a29aa1..28685de3 100644 --- a/devices/flash-stm32xspi/xspi_common.h +++ b/devices/flash-stm32xspi/xspi_common.h @@ -23,6 +23,7 @@ #include #include +#include "mce.h" /* It's not practical to automatically determine if a given XSPI bus uses HyperBus protocol @@ -54,6 +55,7 @@ #define XSPI_FIFO_SIZE 64 /* Size of hardware FIFO */ #define XSPI_N_CONTROLLERS 2 +#define XSPI_MCE_REGIONS 1 /* Hardware supports up to 4 */ #define XSPI_SR_BUSY (1UL << 5) /* Controller busy */ #define XSPI_SR_TOF (1UL << 4) /* Timeout */ @@ -132,6 +134,22 @@ typedef struct { } xspi_pin_t; +typedef struct { + addr_t start; + addr_t end; /* Exclusive */ + int encrypt; +} xspi_memregion_t; /* Encrypted memory region */ + + +typedef struct { + addr_t start; /* Address refers to the memory device, not cpu. */ + addr_t end; /* Same as above. Exclusive */ + u32 cipher; + u32 mode; + u8 *key; +} xspi_memcrypt_args_t; + + typedef struct { void *start; u32 size; @@ -149,6 +167,7 @@ typedef struct { u8 spiPort; u8 chipSelect; u8 isHyperbus; + u8 mceDev; } xspi_ctrlParams_t; @@ -191,6 +210,9 @@ ssize_t xspi_regcom_read(unsigned int minor, addr_t offs, void *buff, size_t len ssize_t xspi_regcom_write(unsigned int minor, addr_t offs, const void *buff, size_t len); +ssize_t xspi_regcom_write_mapped(unsigned int minor, addr_t offs, const void *buff, size_t len); + + ssize_t xspi_regcom_erase(unsigned int minor, addr_t offs, size_t len, unsigned int flags); diff --git a/devices/flash-stm32xspi/xspi_hyperbus.c b/devices/flash-stm32xspi/xspi_hyperbus.c index 3608b8d5..6c27ca8b 100644 --- a/devices/flash-stm32xspi/xspi_hyperbus.c +++ b/devices/flash-stm32xspi/xspi_hyperbus.c @@ -38,6 +38,8 @@ #define HYPERBUS_TYPE_RAM2 0x1UL /* HYPERRAMâ„¢ 2.0 */ #define HYPERBUS_TYPE_RAM3 0x9UL /* HYPERRAMâ„¢ 3.0 a.k.a. HYPERRAMâ„¢ extended-IO */ +#define TEST_LEN 4 * 32 * 1024 + /* XSPI registers needed for configuring a HyperBus transaction */ typedef struct { @@ -122,7 +124,7 @@ static struct hb_memParams { } hb_memParams[XSPI_N_CONTROLLERS]; -static int psramdrv_changeXspiMode(unsigned int minor, psram_xspiMode_t mode) +static int xspi_hb_changeXspiMode(unsigned int minor, psram_xspiMode_t mode) { const psram_xspiSetup_t *newSetup = &psram_xspiModes[mode]; const psram_xspiSetup_t *toClear = &psram_toClear; @@ -159,7 +161,7 @@ static int xspi_hb_transactionInternal(unsigned int minor, const psram_xspiMode_ const xspi_ctrlParams_t *p = &xspi_ctrlParams[minor]; u8 isRead = ((mode == xspi_memRead) || (mode == xspi_regRead)) ? 1 : 0; - psramdrv_changeXspiMode(minor, mode); + xspi_hb_changeXspiMode(minor, mode); *(p->ctrl + xspi_dlr) = size - 1; hal_cpuDataMemoryBarrier(); *(p->ctrl + xspi_ar) = sysaddr * ((hb_memParams[minor].is16Bit != 0) ? 4 : 2); @@ -232,7 +234,7 @@ static int xspi_hb_psramInit(unsigned int minor) } /* Set device to memory mapped mode */ - psramdrv_changeXspiMode(minor, xspi_memMapped); + xspi_hb_changeXspiMode(minor, xspi_memMapped); return EOK; } diff --git a/devices/flash-stm32xspi/xspi_regcom.c b/devices/flash-stm32xspi/xspi_regcom.c index c6db3348..4153eb9c 100644 --- a/devices/flash-stm32xspi/xspi_regcom.c +++ b/devices/flash-stm32xspi/xspi_regcom.c @@ -15,6 +15,7 @@ #include "xspi_common.h" #include "flash_params.h" +#include typedef struct { u32 ccr; @@ -274,6 +275,8 @@ static u32 flashdrv_changeCtrlMode(unsigned int minor, u32 new_mode) hal_cpuDataMemoryBarrier(); *(p->ctrl + xspi_cr) = v | (1 << 1); /* Abort operation in progress */ xspi_waitBusy(minor); + *(p->ctrl + xspi_cr) |= (1 << 26); /* Disable AXI prefetch */ + hal_cpuDataMemoryBarrier(); } v &= ~XSPI_CR_MODE_MASK; @@ -285,6 +288,12 @@ static u32 flashdrv_changeCtrlMode(unsigned int minor, u32 new_mode) *(p->ctrl + xspi_ccr) = mp->read.ccr; *(p->ctrl + xspi_tcr) = mp->read.tcr; *(p->ctrl + xspi_ir) = mp->read.ir; + /* todo: add write registers */ + *(p->ctrl + xspi_wccr) = mp->write.ccr; + *(p->ctrl + xspi_wtcr) = mp->write.tcr; + *(p->ctrl + xspi_wir) = mp->write.ir; + + *(p->ctrl + xspi_cr) &= ~(3 << 25); /* Enable prefetch */ hal_cpuDataMemoryBarrier(); } @@ -710,6 +719,40 @@ static ssize_t flashdrv_write_internal(unsigned int minor, addr_t offs, const vo } +ssize_t xspi_regcom_write_mapped(unsigned int minor, addr_t offs, const void *buff, size_t len) +{ + u32 remaining, i; + const xspi_ctrlParams_t *p = &xspi_ctrlParams[minor]; + struct flash_memParams *mp = &memParams[minor]; + // Each transacton must be exactly 32 bytes + if (len % 32 != 0 || len < 32) { + return (ssize_t)-1; + } + + remaining = len; + i = 0; + while (remaining > 0) { + flashdrv_writeEnable(minor, 1); + flashdrv_changeCtrlMode(minor, XSPI_CR_MODE_MEMORY); + + hal_cpuDataSyncBarrier(); + hal_memcpy(p->start + offs + i, buff + i, 32); + hal_cleanDCache(); + hal_cpuDataSyncBarrier(); + + flashdrv_waitForWriteCompletion(minor, mp->status, 10000); + flashdrv_changeCtrlMode(minor, XSPI_CR_MODE_IWRITE); + flashdrv_writeEnable(minor, 1); + flashdrv_changeCtrlMode(minor, XSPI_CR_MODE_MEMORY); + + remaining -= 32; + i += 32; + } + + return (ssize_t)len; +} + + ssize_t xspi_regcom_write(unsigned int minor, addr_t offs, const void *buff, size_t len) { ssize_t ret; diff --git a/hal/armv8m/stm32/n6/Makefile b/hal/armv8m/stm32/n6/Makefile index 796b1688..3330686d 100644 --- a/hal/armv8m/stm32/n6/Makefile +++ b/hal/armv8m/stm32/n6/Makefile @@ -13,8 +13,9 @@ CFLAGS := $(filter-out -mfloat-abi% , $(CFLAGS)) CFLAGS += -mfloat-abi=soft PLO_COMMANDS ?= alias app blob call console copy devices dump echo erase go help kernel kernelimg \ - map mem phfs reboot script stop wait test-dev + map mem memcrypt otp phfs reboot script stop wait test-dev + PLO_ALLDEVICES := pipe-rtt ram-storage uart-stm32 flash-stm32xspi -OBJS += $(addprefix $(PREFIX_O)hal/$(TARGET_SUFF)/stm32/n6/, _init.o stm32n6.o timer.o console.o) +OBJS += $(addprefix $(PREFIX_O)hal/$(TARGET_SUFF)/stm32/n6/, _init.o stm32n6.o timer.o console.o otp.o) diff --git a/hal/armv8m/stm32/n6/otp.c b/hal/armv8m/stm32/n6/otp.c new file mode 100644 index 00000000..6ce28c78 --- /dev/null +++ b/hal/armv8m/stm32/n6/otp.c @@ -0,0 +1,173 @@ +/* + * Phoenix-RTOS + * + * Operating system loader + * + * BSEC OTP control STM32N6 series + * + * Copyright 2025 Phoenix Systems + * Author: Krzysztof Radzewicz + * + * This file is part of Phoenix-RTOS. + * + * %LICENSE% + */ + +#include +#include +#include "stm32n6_regs.h" + + +#define BSEC_BASE ((void *)0x56009000u) +#define FUSE_MIN 0u +#define FUSE_MID_MIN 128u +#define FUSE_UPPER_MIN 256u +#define FUSE_MAX 375u + +#define OTPSR_BUSY (1u) +#define OTPSR_INIT_DONE (1u << 1) +#define OTPSR_HIDEUP (1u << 2) +#define OTPSR_OTPNVIR (1u << 4) +#define OTPSR_OTPERR (1u << 5) +#define OTPSR_OTPSEC (1u << 6) +#define OTPSR_PROGFAIL (1u << 16) +#define OTPSR_DISTURB (1u << 17) +#define OTPSR_DEDF (1u << 18) +#define OTPSR_SECF (1u << 19) +#define OTPSR_PPLF (1u << 20) +#define OTPSR_PPLMF (1u << 21) +#define OTPSR_AMEF (1u << 22) + +#define OTPCR_ADDR (0x1FF) +#define OTPCR_PROG (1u << 13) +#define OTPCR_PPLOCK (1u << 14) + + +static struct { + volatile u32 *bsec_base; +} otp_common; + + +static void otp_waitBusy(void) +{ + /* Wait until not busy */ + while (*(otp_common.bsec_base + bsec_otpsr) & OTPSR_BUSY) { + ; + } +} + + +static int otp_checkError(void) +{ + u32 t = *(otp_common.bsec_base + bsec_otpsr); + if ((t & OTPSR_OTPERR) == 0) { + return EOK; + } + return t & 0x7F0077; +} + + +int otp_checkFuseValid(int fuse) +{ + u32 fuseMax = FUSE_MAX; + if (*(otp_common.bsec_base + bsec_otpsr) & OTPSR_HIDEUP) { + fuseMax = FUSE_UPPER_MIN - 1; + } + if ((fuse >= FUSE_MIN) && (fuse <= fuseMax)) { + return EOK; + } + return -ERANGE; +} + + +int otp_read(int fuse, u32 *val) +{ + unsigned int t; + + int res = otp_checkFuseValid(fuse); + if (res != EOK) { + return res; + } + otp_waitBusy(); + + /* Set fuse address */ + t = *(otp_common.bsec_base + bsec_otpcr) & ~(OTPCR_ADDR | OTPCR_PROG | OTPCR_PPLOCK); + *(otp_common.bsec_base + bsec_otpcr) = t | fuse; + + otp_waitBusy(); + + res = otp_checkError(); + if (res != EOK) { + return res; + } + + /* Read the reloaded fuse */ + *val = *(otp_common.bsec_base + bsec_fvr0 + fuse); + + return EOK; +} + + +int otp_write(int fuse, u32 val) +{ + unsigned int t, lockFuse = 0; + + int res = otp_checkFuseValid(fuse); + if (res != EOK) { + return res; + } + + otp_waitBusy(); + + /* Set the word to program */ + *(otp_common.bsec_base + bsec_wdr) = val; + + if (fuse >= FUSE_MID_MIN) { + lockFuse = ~0u; + } + + /* Program the word using cr register. Fuse word is locked if it's mid or upper */ + t = *(otp_common.bsec_base + bsec_otpcr) & ~(OTPCR_ADDR | OTPCR_PROG | OTPCR_PPLOCK); + *(otp_common.bsec_base + bsec_otpcr) |= fuse | OTPCR_PROG | (lockFuse & OTPCR_PPLOCK); + + otp_waitBusy(); + + t = *(otp_common.bsec_base + bsec_otpsr); + if (t & OTPSR_PROGFAIL) { + return -EAGAIN; + } + if (t & OTPSR_PPLF) { + return -EPERM; + } + if (t & OTPSR_PPLMF) { + return -EINVAL; + } + + /* Reload the fuse word */ + t = *(otp_common.bsec_base + bsec_otpcr) & ~(OTPCR_ADDR | OTPCR_PROG | OTPCR_PPLOCK); + *(otp_common.bsec_base + bsec_otpcr) = t | fuse; + + otp_waitBusy(); + + if (*(otp_common.bsec_base + bsec_otpsr) & OTPSR_OTPERR) { + return -EAGAIN; + } + /* Compare the loaded word to val*/ + if (*(otp_common.bsec_base + bsec_fvr0 + fuse) != val) { + return -EAGAIN; + } + + return EOK; +} + + +void otp_init(void) +{ + u32 t; + otp_common.bsec_base = BSEC_BASE; + + /* Wait until not busy and BSEC initialized */ + while (((t = *(otp_common.bsec_base + bsec_otpsr)) & OTPSR_BUSY) || ((t & OTPSR_INIT_DONE) == 0)) { + ; + } +} diff --git a/hal/armv8m/stm32/n6/otp.h b/hal/armv8m/stm32/n6/otp.h new file mode 100644 index 00000000..a6af0b18 --- /dev/null +++ b/hal/armv8m/stm32/n6/otp.h @@ -0,0 +1,34 @@ +/* + * Phoenix-RTOS + * + * Operating system loader + * + * BSEC OTP control STM32N6 series + * + * Copyright 2020-2022 2025 Phoenix Systems + * Author: Aleksander Kaminski, Gerard Swiderski, Krzysztof Radzewicz + * + * This file is part of Phoenix-RTOS. + * + * %LICENSE% + */ + +#include <../types.h> + +#ifndef _OTP_H_ +#define _OTP_H_ + + +int otp_checkFuseValid(int fuse); + + +int otp_read(int fuse, u32 *val); + + +int otp_write(int fuse, u32 val); + + +void otp_init(void); + + +#endif /* _OTP_H_ */ diff --git a/hal/armv8m/stm32/n6/stm32n6.c b/hal/armv8m/stm32/n6/stm32n6.c index 0377839d..41bf047e 100644 --- a/hal/armv8m/stm32/n6/stm32n6.c +++ b/hal/armv8m/stm32/n6/stm32n6.c @@ -17,6 +17,7 @@ #include #include "stm32n6.h" #include "stm32n6_regs.h" +#include "otp.h" #ifndef USE_HSE_CLOCK_SOURCE @@ -45,6 +46,7 @@ #define RAMCFG_BASE ((void *)0x52023000) #define ICB_BASE ((void *)0xe000e000) #define BSEC_BASE ((void *)0x56009000) +#define RNG_BASE ((void *)0x54020000) static struct { volatile u32 *rcc; @@ -56,6 +58,7 @@ static struct { volatile u32 *iwdg; volatile u32 *ramcfg; volatile u32 *bsec; + volatile u32 *rng; u32 cpuclk; u32 perclk; @@ -915,6 +918,128 @@ int _stm32_gpioGetPort(unsigned int d, u16 *val) } +/* RNG */ + + +static int _stm32_rngInit(void) +{ + u32 v; + + /* Enable clock */ + _stm32_rccSetDevClock(dev_rng, 1); + + /* Disable RNG */ + *(stm32_common.rng + rng_cr) &= ~(1u << 2); + hal_cpuDataMemoryBarrier(); + + v = *(stm32_common.rng + rng_cr); + v |= (1u << 30); /* Conditioning soft reset */ + v &= ~(1u << 7); /* Enable auto reset if seed error detected */ + v &= ~(1u << 5); /* Enable clock error detection */ + /* AN4230 5.1.3 NIST compliant RNG configuration */ + v &= ~0x03FFFF00; + v |= 0x00F00D00; + *(stm32_common.rng + rng_cr) = v; /* Apply new configuration, hold in reset */ + /* AN4230 5.1.3 NIST compliant RNG configuration */ + *(stm32_common.rng + rng_htcr) = 0xAAC7; + *(stm32_common.rng + rng_nscr) = 0x0003FFFF; /* Activate all noise sources */ + *(stm32_common.rng + rng_cr) &= ~(1u << 30); /* Release from reset */ + while ((*(stm32_common.rng + rng_cr) & (1u << 30)) != 0) { + /* Wait for peripheral to become ready (2 AHB cycles + 2 RNG clock cycles) */ + } + + /* Disable interrupt */ + *(stm32_common.rng + rng_cr) &= ~(1u << 3); + + return 0; +} + + +static void _stm32_rngEnable(void) +{ + *(stm32_common.rng + rng_cr) |= 1u << 2; +} + + +static void _stm32_rngDisable(void) +{ + /* Disable RNG */ + *(stm32_common.rng + rng_cr) &= ~(1u << 2); +} + + +static int _stm32_rngReadDR(u32 *val) +{ + int ret = -EAGAIN; + u32 sr = *(stm32_common.rng + rng_sr); + + if ((sr & 1u) != 0) { + *val = *(stm32_common.rng + rng_dr); + + /* Zero check is recommended in Reference Manual due to rare race condition */ + ret = (*val != 0) ? 0 : -EIO; + } + + /* Has error been detected? */ + if ((sr & (3u << 5)) != 0) { + /* Mark data as faulty, but check cause of interrupts */ + if ((sr & (3u << 1)) == 0) { + /* Situation has been recovered from, try again */ + ret = -EAGAIN; + } + else { + /* Request IP reinit only for SE */ + ret = (sr & (1 << 6)) ? -EIO : -EAGAIN; + } + + /* Clear flags */ + *(stm32_common.rng + rng_sr) &= ~(sr & (3u << 5)); + hal_cpuDataMemoryBarrier(); + } + + return ret; +} + + +ssize_t _stm32_rngRead(u8 *val, size_t len) +{ + size_t pos = 0, chunk; + int retry = 0; + + _stm32_rngEnable(); + + while (pos < len) { + u32 t; + int err = _stm32_rngReadDR(&t); + if (err < 0) { + if (err != -EAGAIN) { + /* Reinitialize IP */ + _stm32_rngDisable(); + _stm32_rngEnable(); + } + + ++retry; + if (retry < 1000) { + /* Retry */ + continue; + } + + _stm32_rngDisable(); + return -EIO; + } + + retry = 0; + chunk = ((len - pos) > sizeof(t)) ? sizeof(t) : len - pos; + hal_memcpy(val + pos, &t, chunk); + pos += chunk; + } + + _stm32_rngDisable(); + + return (ssize_t)len; +} + + /* Watchdog */ @@ -997,6 +1122,7 @@ void _stm32_init(void) stm32_common.gpio[14] = GPIOO_BASE; stm32_common.gpio[15] = GPIOP_BASE; stm32_common.gpio[16] = GPIOQ_BASE; + stm32_common.rng = RNG_BASE; /* Store reset flags and then clear them */ stm32_common.resetFlags = (*(stm32_common.rcc + rcc_rsr) >> 21); @@ -1055,6 +1181,11 @@ void _stm32_init(void) hal_cpuDataMemoryBarrier(); + otp_init(); + _stm32_rngInit(); + + hal_cpuDataMemoryBarrier(); + #if defined(WATCHDOG) /* Init watchdog */ /* Enable write access to IWDG */ diff --git a/hal/armv8m/stm32/n6/stm32n6.h b/hal/armv8m/stm32/n6/stm32n6.h index 8a8129c4..f289054c 100644 --- a/hal/armv8m/stm32/n6/stm32n6.h +++ b/hal/armv8m/stm32/n6/stm32n6.h @@ -18,6 +18,14 @@ #include "../types.h" +#ifndef EIO +#define EIO 5 +#endif + +#ifndef EAGAIN +#define EAGAIN 11 +#endif + /* Device clocks */ enum { dev_aclkn = 0x0, @@ -488,6 +496,8 @@ extern int _stm32_gpioGet(unsigned int d, u8 pin, u8 *val); extern int _stm32_gpioGetPort(unsigned int d, u16 *val); +extern ssize_t _stm32_rngRead(u8 *val, size_t len); + /* Range = 0 - VOS low, 1 - VOS high */ extern void _stm32_pwrSetCPUVolt(u8 range); diff --git a/hal/armv8m/stm32/n6/stm32n6_regs.h b/hal/armv8m/stm32/n6/stm32n6_regs.h index 4b299468..ffe9ed03 100644 --- a/hal/armv8m/stm32/n6/stm32n6_regs.h +++ b/hal/armv8m/stm32/n6/stm32n6_regs.h @@ -462,4 +462,13 @@ enum bsec_regs { bsec_wrcr, }; + +enum rng_regs { + rng_cr, + rng_sr, + rng_dr, + rng_nscr, + rng_htcr, +}; + #endif /* _STM32N6_REGS_H_ */