mirror of https://github.com/wolfSSL/wolfBoot.git
456 lines
15 KiB
C
456 lines
15 KiB
C
/* psoc_c3.c
|
|
*
|
|
* HAL for Infineon PSOC Control C3 (CAT1B, Cortex-M33).
|
|
*
|
|
* Copyright (C) 2026 wolfSSL Inc.
|
|
*
|
|
* This file is part of wolfBoot.
|
|
*
|
|
* wolfBoot is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* wolfBoot is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
|
|
*/
|
|
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
|
|
#include "image.h"
|
|
#include "hal.h"
|
|
#include "printf.h"
|
|
#include "hal/psoc_c3.h"
|
|
#include <target.h>
|
|
|
|
/* Console SCB and its pin muxing. Defaults match the PSOC Control C3
|
|
* evaluation kit, where the on-board debug probe's virtual COM port lands on
|
|
* SCB3 P6.2/P6.3. */
|
|
#ifndef PSOC_C3_UART_SCB
|
|
#define PSOC_C3_UART_SCB 3
|
|
#endif
|
|
#define PSOC_C3_UART_SCB_BASE PSOC_C3_SCB_BASE(PSOC_C3_UART_SCB)
|
|
#ifndef PSOC_C3_UART_PORT
|
|
#define PSOC_C3_UART_PORT 6
|
|
#endif
|
|
#ifndef PSOC_C3_UART_RX_PIN
|
|
#define PSOC_C3_UART_RX_PIN 2
|
|
#endif
|
|
#ifndef PSOC_C3_UART_TX_PIN
|
|
#define PSOC_C3_UART_TX_PIN 3
|
|
#endif
|
|
#ifndef PSOC_C3_UART_HSIOM_SEL
|
|
#define PSOC_C3_UART_HSIOM_SEL 16
|
|
#endif
|
|
|
|
/* Peripheral clock routing for the console SCB. The index is NOT the one the
|
|
* PSC3M5 headers give for SCB3: on a C3M6 the console is driven through
|
|
* group 4 index 6, confirmed by perturbing the divider on a running part.
|
|
* Treat these as per-part values and override them for another variant. */
|
|
#ifndef PSOC_C3_UART_PCLK_GR
|
|
#define PSOC_C3_UART_PCLK_GR 4
|
|
#endif
|
|
#ifndef PSOC_C3_UART_PCLK_IDX
|
|
#define PSOC_C3_UART_PCLK_IDX 6
|
|
#endif
|
|
#ifndef PSOC_C3_UART_PCLK_DIV
|
|
#define PSOC_C3_UART_PCLK_DIV 1
|
|
#endif
|
|
|
|
/* Frequency feeding the peripheral clock group. wolfBoot does not touch the
|
|
* clock tree, so this is the power-on IHO rate. */
|
|
#ifndef PSOC_C3_PCLK_HZ
|
|
#define PSOC_C3_PCLK_HZ 48000000UL
|
|
#endif
|
|
#ifndef PSOC_C3_UART_BAUD
|
|
#define PSOC_C3_UART_BAUD 115200UL
|
|
#endif
|
|
#define PSOC_C3_UART_OVS 8UL
|
|
|
|
/* The BootROM refresh bookkeeping is indexed by absolute 128 KB sector, so this
|
|
* must cover the whole device rather than just the partitions. Eight entries
|
|
* span 1 MB, comfortably more than any part in the family. */
|
|
#ifndef PSOC_C3_FLASH_SECTORS
|
|
#define PSOC_C3_FLASH_SECTORS 8
|
|
#endif
|
|
|
|
/* BootROM flash API: a table of function pointers at a fixed address. Only the
|
|
* three blocking row operations are used; the 29 preceding entries are skipped
|
|
* by the reserved field. The pointers are range-checked before first use, so a
|
|
* table layout change fails loudly rather than branching into hyperspace. */
|
|
#define PSOC_C3_ROM_BASE (0x10800000UL)
|
|
#define PSOC_C3_ROM_SIZE (0x10000UL)
|
|
#define PSOC_C3_ROM_FUNC_ADDR (0x1080FF6CUL)
|
|
#define PSOC_C3_ROM_SKIP 29
|
|
|
|
#define CYBOOT_FLASH_SUCCESS (0x0D50B002UL)
|
|
/* flags == 0 selects a blocking operation and lets the ROM compute the row's
|
|
* column-33 metadata itself. */
|
|
#define CYBOOT_FLAGS_BLOCKING (0UL)
|
|
|
|
typedef struct {
|
|
uint32_t min_count;
|
|
uint32_t max_count;
|
|
uint32_t min_page_addr;
|
|
uint32_t scratch_row_idx;
|
|
} cyboot_flash_refresh_t;
|
|
|
|
typedef struct {
|
|
uint32_t flags;
|
|
uint32_t hv_params_addr;
|
|
cyboot_flash_refresh_t *refresh;
|
|
void (*callback_pre_irq)(void *ctx);
|
|
void (*callback_post_irq)(void *ctx);
|
|
void (*callback_complete)(void *ctx);
|
|
uint32_t callback_param;
|
|
uint32_t state;
|
|
uint32_t flash_addr;
|
|
uint32_t data_addr;
|
|
uint32_t reserved[2];
|
|
} cyboot_flash_context_t;
|
|
|
|
typedef uint32_t (*cyboot_flash_erase_row_t)(uint32_t addr,
|
|
cyboot_flash_context_t *ctx);
|
|
typedef uint32_t (*cyboot_flash_row_op_t)(uint32_t addr, uint32_t *data,
|
|
cyboot_flash_context_t *ctx);
|
|
|
|
typedef struct {
|
|
uint32_t reserved[PSOC_C3_ROM_SKIP];
|
|
cyboot_flash_erase_row_t erase_row;
|
|
cyboot_flash_row_op_t program_row;
|
|
cyboot_flash_row_op_t write_row;
|
|
} psoc_c3_rom_flash_t;
|
|
|
|
#define PSOC_C3_ROM ((const psoc_c3_rom_flash_t *)PSOC_C3_ROM_FUNC_ADDR)
|
|
|
|
/* The ROM write path updates the refresh bookkeeping through ctx->refresh even
|
|
* when the refresh feature is unused, and it writes one entry per sector. A
|
|
* NULL pointer or an undersized array here corrupts whatever follows it. */
|
|
static cyboot_flash_refresh_t flash_refresh[PSOC_C3_FLASH_SECTORS];
|
|
static cyboot_flash_context_t flash_ctx;
|
|
/* Row staging buffer: too large for the stack under -Wstack-usage. */
|
|
static uint32_t flash_row[PSOC_C3_FLASH_ROW_WORDS];
|
|
static int flash_rom_checked;
|
|
static int flash_rom_ok;
|
|
static int peri_init_done;
|
|
|
|
/* Group 0 is enabled out of reset; the rest are not, and a slave in a gated
|
|
* group bus-faults on first access. Release the slave reset before enabling,
|
|
* the order the vendor startup uses. Slaves that do not exist read back as
|
|
* zero, so writing all ones stays correct whatever a variant populates. */
|
|
void psoc_c3_peri_init(void)
|
|
{
|
|
int g;
|
|
|
|
if (peri_init_done)
|
|
return;
|
|
peri_init_done = 1;
|
|
|
|
for (g = 1; g < PERI_GR_COUNT; g++) {
|
|
PERI_GR_SL_CTL2(g) = 0;
|
|
PERI_GR_SL_CTL(g) = 0xFFFFFFFFUL;
|
|
}
|
|
DSB();
|
|
}
|
|
|
|
/* A pin is unusable until its bit is cleared here: while it is marked
|
|
* non-secure the port's HSIOM and GPIO registers discard secure writes and
|
|
* read back zero. */
|
|
void psoc_c3_pin_setup(int port, int pin, uint32_t sel, uint32_t drive)
|
|
{
|
|
uint32_t v;
|
|
|
|
HSIOM_SECURE_PRT_NSMASK(port) &= ~(1UL << pin);
|
|
|
|
v = HSIOM_PORT_SEL(port, pin);
|
|
v &= ~(0xFFUL << HSIOM_SEL_SHIFT(pin));
|
|
v |= (sel << HSIOM_SEL_SHIFT(pin));
|
|
HSIOM_PORT_SEL(port, pin) = v;
|
|
|
|
v = GPIO_PRT_CFG(port);
|
|
v &= ~(GPIO_CFG_DM_MASK << ((uint32_t)pin * 4U));
|
|
v |= (drive << ((uint32_t)pin * 4U));
|
|
GPIO_PRT_CFG(port) = v;
|
|
}
|
|
|
|
static int RAMFUNCTION rom_ptr_valid(void *fn)
|
|
{
|
|
uint32_t a = (uint32_t)fn;
|
|
return ((a >= PSOC_C3_ROM_BASE) &&
|
|
(a < (PSOC_C3_ROM_BASE + PSOC_C3_ROM_SIZE)) &&
|
|
((a & 1U) != 0U));
|
|
}
|
|
|
|
/* Validated on first use, not from hal_init(): the test application links this
|
|
* HAL without __WOLFBOOT and never calls hal_init(), so gating flash on
|
|
* hal_init() state made every application write fail silently -- which is the
|
|
* path wolfBoot_update_trigger() takes. */
|
|
static int RAMFUNCTION flash_rom_ready(void)
|
|
{
|
|
if (flash_rom_checked == 0) {
|
|
flash_rom_checked = 1;
|
|
flash_rom_ok = (rom_ptr_valid((void *)PSOC_C3_ROM->erase_row) &&
|
|
rom_ptr_valid((void *)PSOC_C3_ROM->program_row) &&
|
|
rom_ptr_valid((void *)PSOC_C3_ROM->write_row));
|
|
}
|
|
return flash_rom_ok;
|
|
}
|
|
|
|
void RAMFUNCTION hal_flash_unlock(void)
|
|
{
|
|
}
|
|
|
|
void RAMFUNCTION hal_flash_lock(void)
|
|
{
|
|
}
|
|
|
|
/* A len that is not a whole number of rows erases the row the final byte falls
|
|
* in, rounding up. wolfBoot only ever passes sector multiples. */
|
|
int RAMFUNCTION hal_flash_erase(uint32_t address, int len)
|
|
{
|
|
uint32_t status;
|
|
|
|
if ((len <= 0) || (flash_rom_ready() == 0))
|
|
return -1;
|
|
if ((address % PSOC_C3_FLASH_ROW_SIZE) != 0)
|
|
return -1;
|
|
|
|
while (len > 0) {
|
|
flash_ctx.flags = CYBOOT_FLAGS_BLOCKING;
|
|
flash_ctx.refresh = flash_refresh;
|
|
status = PSOC_C3_ROM->erase_row(PSOC_C3_SBUS_ALIAS(address),
|
|
&flash_ctx);
|
|
if (status != CYBOOT_FLASH_SUCCESS)
|
|
return -1;
|
|
address += PSOC_C3_FLASH_ROW_SIZE;
|
|
len -= (int)PSOC_C3_FLASH_ROW_SIZE;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int RAMFUNCTION hal_flash_write(uint32_t address, const uint8_t *data, int len)
|
|
{
|
|
uint32_t row_start;
|
|
uint32_t offset;
|
|
uint32_t chunk;
|
|
uint32_t status;
|
|
|
|
if ((data == NULL) || (len <= 0) || (flash_rom_ready() == 0))
|
|
return -1;
|
|
|
|
while (len > 0) {
|
|
row_start = address & ~(PSOC_C3_FLASH_ROW_SIZE - 1U);
|
|
offset = address - row_start;
|
|
chunk = PSOC_C3_FLASH_ROW_SIZE - offset;
|
|
if (chunk > (uint32_t)len)
|
|
chunk = (uint32_t)len;
|
|
|
|
/* write_row erases before programming, so carry over the rest of the
|
|
* row. */
|
|
memcpy(flash_row, (const void *)row_start, PSOC_C3_FLASH_ROW_SIZE);
|
|
memcpy((uint8_t *)flash_row + offset, data, chunk);
|
|
/* The ROM fills the column-33 trailer itself for a blocking write;
|
|
* clear it so no bytes of the previous row are carried into this one. */
|
|
memset((uint8_t *)flash_row + PSOC_C3_FLASH_ROW_SIZE, 0,
|
|
PSOC_C3_FLASH_COL33_SZ);
|
|
|
|
flash_ctx.flags = CYBOOT_FLAGS_BLOCKING;
|
|
flash_ctx.refresh = flash_refresh;
|
|
status = PSOC_C3_ROM->write_row(PSOC_C3_SBUS_ALIAS(row_start),
|
|
flash_row, &flash_ctx);
|
|
if (status != CYBOOT_FLASH_SUCCESS)
|
|
return -1;
|
|
|
|
address += chunk;
|
|
data += chunk;
|
|
len -= (int)chunk;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Program a peripheral clock destination and start its divider.
|
|
*
|
|
* The divider's EN bit is status, not control: only DIV_CMD starts it. Touching
|
|
* an SCB before its divider runs bus-faults, so callers must not proceed on a
|
|
* failure here. Divider types are not uniform across groups, and a destination
|
|
* programmed with a type its group does not provide silently produces no clock.
|
|
*/
|
|
int psoc_c3_pclk_setup(uint32_t gr, uint32_t idx, uint32_t div, uint32_t type,
|
|
uint32_t pclk_hz, uint32_t target_hz)
|
|
{
|
|
uint32_t timeout = PSOC_C3_PERI_TIMEOUT;
|
|
uint32_t int_div;
|
|
uint32_t frac_div;
|
|
|
|
if (target_hz == 0U)
|
|
return -1;
|
|
|
|
if (type == PERI_PCLK_DIV_TYPE_16_5) {
|
|
psoc_c3_div16_5(pclk_hz, target_hz, &int_div, &frac_div);
|
|
PERI_PCLK_DIV_16_5_CTL(gr, div) =
|
|
(int_div << PERI_PCLK_DIV_16_5_INT_Pos) |
|
|
(frac_div << PERI_PCLK_DIV_16_5_FRAC_Pos);
|
|
}
|
|
else {
|
|
int_div = psoc_c3_div16(pclk_hz, target_hz);
|
|
PERI_PCLK_DIV_16_CTL(gr, div) =
|
|
((int_div - 1U) << PERI_PCLK_DIV_INT16_Pos);
|
|
}
|
|
|
|
PERI_PCLK_CLOCK_CTL(gr, idx) = (type << PERI_PCLK_CLOCK_CTL_TYPE_Pos) | div;
|
|
PERI_PCLK_DIV_CMD(gr) = PERI_PCLK_DIV_CMD_ENABLE |
|
|
PERI_PCLK_DIV_CMD_PA_NONE |
|
|
(type << PERI_PCLK_DIV_CMD_TYPE_Pos) | div;
|
|
|
|
while (timeout > 0) {
|
|
if (type == PERI_PCLK_DIV_TYPE_16_5) {
|
|
if ((PERI_PCLK_DIV_16_5_CTL(gr, div) & PERI_PCLK_DIV_16_5_EN) != 0)
|
|
return 0;
|
|
}
|
|
else if ((PERI_PCLK_DIV_16_CTL(gr, div) & PERI_PCLK_DIV_EN) != 0) {
|
|
return 0;
|
|
}
|
|
timeout--;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
#if defined(DEBUG_UART) || !defined(__WOLFBOOT)
|
|
|
|
static void uart_pins_setup(void)
|
|
{
|
|
psoc_c3_pin_setup(PSOC_C3_UART_PORT, PSOC_C3_UART_RX_PIN,
|
|
PSOC_C3_UART_HSIOM_SEL, GPIO_CFG_DM_HIGHZ);
|
|
psoc_c3_pin_setup(PSOC_C3_UART_PORT, PSOC_C3_UART_TX_PIN,
|
|
PSOC_C3_UART_HSIOM_SEL, GPIO_CFG_DM_STRONG);
|
|
}
|
|
|
|
/* Set once the console SCB has a running clock; every register access is
|
|
* gated on it. */
|
|
static int uart_ready;
|
|
|
|
static int uart_clock_setup(void)
|
|
{
|
|
return psoc_c3_pclk_setup(PSOC_C3_UART_PCLK_GR, PSOC_C3_UART_PCLK_IDX,
|
|
PSOC_C3_UART_PCLK_DIV, PERI_PCLK_DIV_TYPE_16_5, PSOC_C3_PCLK_HZ,
|
|
PSOC_C3_UART_BAUD * PSOC_C3_UART_OVS);
|
|
}
|
|
|
|
void uart_init(void)
|
|
{
|
|
psoc_c3_peri_init();
|
|
uart_pins_setup();
|
|
/* An SCB without a running divider bus-faults on first access, so a
|
|
* failed clock leaves the console silent rather than taking the fault.
|
|
* uart_write() checks the same flag, since the caller cannot. */
|
|
uart_ready = 0;
|
|
if (uart_clock_setup() != 0)
|
|
return;
|
|
|
|
SCB_CTRL(PSOC_C3_UART_SCB_BASE) = 0;
|
|
SCB_UART_CTRL(PSOC_C3_UART_SCB_BASE) = 0; /* standard UART */
|
|
SCB_UART_TX_CTRL(PSOC_C3_UART_SCB_BASE) = 1; /* one stop bit */
|
|
SCB_UART_RX_CTRL(PSOC_C3_UART_SCB_BASE) = 1;
|
|
SCB_TX_CTRL(PSOC_C3_UART_SCB_BASE) = SCB_DATA_WIDTH(8);
|
|
SCB_RX_CTRL(PSOC_C3_UART_SCB_BASE) = SCB_DATA_WIDTH(8);
|
|
SCB_TX_FIFO_CTRL(PSOC_C3_UART_SCB_BASE) = 0x3F;
|
|
SCB_RX_FIFO_CTRL(PSOC_C3_UART_SCB_BASE) = 0x3F;
|
|
SCB_CTRL(PSOC_C3_UART_SCB_BASE) = SCB_CTRL_ENABLED | SCB_CTRL_MODE_UART |
|
|
((PSOC_C3_UART_OVS - 1UL) & SCB_CTRL_OVS_MASK);
|
|
uart_ready = 1;
|
|
}
|
|
|
|
void uart_write(const char *buf, unsigned int sz)
|
|
{
|
|
unsigned int i;
|
|
uint32_t timeout;
|
|
|
|
if (!uart_ready)
|
|
return;
|
|
for (i = 0; i < sz; i++) {
|
|
/* USED is the low field; bits 16 and up are the FIFO pointers. */
|
|
timeout = PSOC_C3_PERI_TIMEOUT;
|
|
while (((SCB_TX_FIFO_STATUS(PSOC_C3_UART_SCB_BASE) &
|
|
SCB_TX_FIFO_USED_Msk) >= SCB_TX_FIFO_SAFE_DEPTH) &&
|
|
(timeout > 0))
|
|
timeout--;
|
|
if (timeout == 0)
|
|
return; /* console is stuck; drop the rest rather than hang */
|
|
SCB_TX_FIFO_WR(PSOC_C3_UART_SCB_BASE) = (uint32_t)(uint8_t)buf[i];
|
|
}
|
|
}
|
|
|
|
#endif /* DEBUG_UART || !__WOLFBOOT */
|
|
|
|
#ifdef __WOLFBOOT
|
|
|
|
#ifdef WOLFBOOT_TPM
|
|
/* Where the TPM's interface-select strap lands. On the evaluation kit it
|
|
* arrives on mikroBUS INT, which is P7.7. */
|
|
#ifndef PSOC_C3_TPM_SEL_PORT
|
|
#define PSOC_C3_TPM_SEL_PORT 7
|
|
#endif
|
|
#ifndef PSOC_C3_TPM_SEL_PIN
|
|
#define PSOC_C3_TPM_SEL_PIN 7
|
|
#endif
|
|
|
|
/* The TPM latches its bus from the CONFIG/PIRQ# strap while its reset is low:
|
|
* high selects SPI, low selects I2C. On the evaluation kit that strap is
|
|
* mikroBUS INT (P7.7), and the carrier's reset comes from XRES through an RC,
|
|
* so the TPM leaves reset about a millisecond after the CPU and this lands
|
|
* inside the window. The level is held afterwards because PIRQ# shares the
|
|
* pin and this port polls rather than using the interrupt. */
|
|
static void tpm_iface_select(void)
|
|
{
|
|
psoc_c3_pin_setup(PSOC_C3_TPM_SEL_PORT, PSOC_C3_TPM_SEL_PIN, 0,
|
|
GPIO_CFG_DM_STRONG);
|
|
#ifdef WOLFBOOT_TPM_I2C
|
|
GPIO_PRT_OUT(PSOC_C3_TPM_SEL_PORT) &= ~(1UL << PSOC_C3_TPM_SEL_PIN);
|
|
#else
|
|
GPIO_PRT_OUT(PSOC_C3_TPM_SEL_PORT) |= (1UL << PSOC_C3_TPM_SEL_PIN);
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
void hal_init(void)
|
|
{
|
|
psoc_c3_peri_init();
|
|
#ifdef WOLFBOOT_TPM
|
|
tpm_iface_select();
|
|
#endif
|
|
#ifdef DEBUG_UART
|
|
uart_init();
|
|
uart_write("wolfBoot HAL Init\n", sizeof("wolfBoot HAL Init\n") - 1);
|
|
if (flash_rom_ready() == 0) {
|
|
uart_write("BootROM flash table invalid\n",
|
|
sizeof("BootROM flash table invalid\n") - 1);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
void hal_prepare_boot(void)
|
|
{
|
|
#ifdef DEBUG_UART
|
|
uint32_t timeout = PSOC_C3_PERI_TIMEOUT;
|
|
|
|
if (!uart_ready)
|
|
return;
|
|
/* Drain the shift register before the application touches the SCB. A
|
|
* stuck console must not hold up the handoff. */
|
|
while (((SCB_TX_FIFO_STATUS(PSOC_C3_UART_SCB_BASE) &
|
|
(SCB_TX_FIFO_USED_Msk | SCB_TX_FIFO_SR_VALID_Msk)) != 0U) &&
|
|
(timeout > 0))
|
|
timeout--;
|
|
#endif
|
|
}
|
|
|
|
#endif /* __WOLFBOOT */
|