[PATCH v3 1/6] imx9: Add support for saving DDR training data to NVM

Marek Vasut marek.vasut at mailbox.org
Sat Apr 4 01:00:01 CEST 2026


On 4/2/26 11:40 AM, Simona Toaca (OSS) wrote:
> From: Simona Toaca <simona.toaca at nxp.com>
> 
> DDR training data can be saved to NVM and be available
> to OEI at boot time, which will trigger QuickBoot flow.
> 
> U-Boot only checks for data integrity (CRC32), while
> OEI is in charge of authentication when it tries to
> load the data from NVM.
> 
> On iMX95 A0/A1, 'authentication' is done via another
> CRC32. On the other boards, authentication is done by

On the other ... SoC revisions (not boards) , ... ?

[...]

> +++ b/arch/arm/include/asm/arch-imx9/ddr.h
> @@ -1,6 +1,6 @@
>   /* SPDX-License-Identifier: GPL-2.0+ */
>   /*
> - * Copyright 2022 NXP
> + * Copyright 2022-2026 NXP
>    */
>   
>   #ifndef __ASM_ARCH_IMX8M_DDR_H
> @@ -100,6 +100,52 @@ struct dram_timing_info {
>   
>   extern struct dram_timing_info dram_timing;
>   
> +/* Quick Boot related */
> +#define DDRPHY_QB_CSR_SIZE	5168
> +#define DDRPHY_QB_ACSM_SIZE	(4 * 1024)
> +#define DDRPHY_QB_MSB_SIZE	0x200
> +#define DDRPHY_QB_PSTATES	0
> +#define DDRPHY_QB_PST_SIZE	(DDRPHY_QB_PSTATES * 4 * 1024)
> +
> +/**
> + * This structure needs to be aligned with the one in OEI.
> + */
> +struct ddrphy_qb_state {
> +	u32 crc;		  /* Used for ensuring integrity in DRAM */
> +#define MAC_LENGTH              8 /* 256 bits, 32-bit aligned */

Please indent with tabs.

> +	u32 mac[MAC_LENGTH];	  /* For 95A0/1 use mac[0] to keep CRC32 value */


[...]

> +++ b/arch/arm/include/asm/mach-imx/qb.h
> @@ -0,0 +1,15 @@
> +/* SPDX-License-Identifier: GPL-2.0+ */
> +/*
> + * Copyright 2026 NXP
> + */
> +
> +#ifndef __IMX_QB_H__
> +#define __IMX_QB_H__
> +
> +#include <stdbool.h>
> +
> +bool qb_check(void);
> +int qb(int qb_dev, bool save);
> +void spl_qb_save(void);

Please call the functions something less opaque than "qb()" , imx_qb_*() 
would alrady be so much better.

[...]

> diff --git a/arch/arm/mach-imx/imx9/qb.c b/arch/arm/mach-imx/imx9/qb.c
> new file mode 100644
> index 00000000000..220545192d4
> --- /dev/null
> +++ b/arch/arm/mach-imx/imx9/qb.c
> @@ -0,0 +1,379 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/**
> + * Copyright 2024-2026 NXP
> + */
> +#include <dm/device-internal.h>
> +#include <dm/uclass.h>
> +#include <errno.h>
> +#include <imx_container.h>
> +#include <linux/bitfield.h>
> +#include <mmc.h>
> +#include <spi_flash.h>
> +#include <spl.h>
> +#include <stdlib.h>
> +#include <u-boot/crc.h>
> +
> +#include <asm/arch/ddr.h>
> +#include <asm/mach-imx/boot_mode.h>
> +#include <asm/mach-imx/sys_proto.h>
> +
> +#define QB_STATE_LOAD_SIZE    SZ_64K
> +
> +#define MMC_DEV		0
> +#define QSPI_DEV	1
> +
> +#define IMG_FLAGS_IMG_TYPE_MASK   0xF
> +#define IMG_FLAGS_IMG_TYPE(x)     FIELD_GET(IMG_FLAGS_IMG_TYPE_MASK, (x))
> +
> +#define IMG_TYPE_DDR_TDATA_DUMMY  0xD   /* dummy DDR training data image */
> +
> +bool qb_check(void)
> +{
> +	struct ddrphy_qb_state *qb_state;
> +	u32 size, crc;
> +
> +	/**
> +	 * Ensure CRC is not empty, the reason is that
> +	 * the data is invalidated after first save run
> +	 * or after it is overwritten.
> +	 */
> +	qb_state = (struct ddrphy_qb_state *)CONFIG_QB_SAVED_STATE_BASE;
> +	size = sizeof(struct ddrphy_qb_state) - sizeof(qb_state->crc);
> +	crc = crc32(0, (u8 *)qb_state->mac, size);
> +
> +	if (!qb_state->crc || crc != qb_state->crc)
> +		return false;
> +
> +	return true;
> +}
> +
> +static unsigned long qb_get_boot_device_offset(void *dev, int dev_type)
> +{
> +	unsigned long offset = 0;
> +	struct mmc *mmc;
> +
> +	switch (dev_type) {
> +	case MMC_DEV:
> +		mmc = dev;
> +
> +		if (IS_SD(mmc) || mmc->part_config == MMCPART_NOAVAILABLE) {
> +			offset = CONTAINER_HDR_MMCSD_OFFSET;

Multiple return in this case will make the function simpler:

return CONTAINER_HDR_MMCSD_OFFSET;

> +		} else {

No need for } else { anymore.

> +			u8 part = EXT_CSD_EXTRACT_BOOT_PART(mmc->part_config);
> +
> +			if (part == EMMC_BOOT_PART_BOOT1 || part == EMMC_BOOT_PART_BOOT2)
> +				offset = CONTAINER_HDR_EMMC_OFFSET;

return CONTAINER_HDR_EMMC_OFFSET;

> +			else
> +				offset = CONTAINER_HDR_MMCSD_OFFSET;

return CONTAINER_HDR_MMCSD_OFFSET;

> +		}
> +		break;
> +	case QSPI_DEV:
> +		offset = CONTAINER_HDR_QSPI_OFFSET;
> +		break;
> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +
> +	return offset;
> +}

[...]

> +static int get_dev_qbdata_offset(void *dev, int dev_type, unsigned long offset,
> +				 u32 *qbdata_offset)
> +{
> +	int ret = -EOPNOTSUPP;
> +	u16 ctnr_hdr_align = CONTAINER_HDR_ALIGNMENT;
> +	void *buf = kmalloc(ctnr_hdr_align, GFP_KERNEL);

Can you use buffer on stack ? u8 buf[CONTAINER_HDR_ALIGNMENT]; ? That 
would allow you to remove all the goto statements.

If not, use malloc() not kmalloc().

> +	if (!buf) {
> +		printf("kmalloc buffer failed\n");

if malloc fails, so will printing, so drop this print and return -ENOMEM 
only.

> +		return -ENOMEM;
> +	}
> +
> +	switch (dev_type) {
> +	case MMC_DEV:
> +		unsigned long count = 0;
> +		struct mmc *mmc = dev;
> +
> +		if (!(CONFIG_IS_ENABLED(DM_MMC) && CONFIG_IS_ENABLED(MMC_WRITE)))
> +			goto get_dev_qbdata_offset_exit;
> +
> +		count = blk_dread(mmc_get_blk_desc(mmc),
> +				  offset / mmc->read_bl_len,
> +				  ctnr_hdr_align / mmc->read_bl_len,
> +				  buf);
> +		if (count == 0) {
> +			printf("Read container image from MMC/SD failed\n");
> +			ret = -EIO;
> +			goto get_dev_qbdata_offset_exit;
> +		}
> +		break;
> +	case QSPI_DEV:
> +		if (!CONFIG_IS_ENABLED(SPI))
> +			goto get_dev_qbdata_offset_exit;
> +
> +		ret = spi_flash_read(dev, offset,
> +				     ctnr_hdr_align, buf);
> +		if (ret) {
> +			printf("Read container header from QSPI failed\n");
> +			ret = -EIO;
> +			goto get_dev_qbdata_offset_exit;
> +		}
> +		break;
> +	default:
> +		printf("Support for device %d not enabled\n", dev_type);
> +		goto get_dev_qbdata_offset_exit;
> +	}
> +
> +	ret = qb_parse_container(buf, qbdata_offset);
> +
> +get_dev_qbdata_offset_exit:
> +	free(buf);
> +
> +	return ret;
> +}

[...]

> +void spl_qb_save(void)
> +{
> +	int dev = spl_boot_device();
> +
> +	/* Save QB data on current boot device */
> +	if (qb(dev, true))

Please call this function something less opaque than "qb()" , pick 
something more descriptive.

> +		printf("QB save failed\n");
> +}
[...]


More information about the U-Boot mailing list