Merge tag 'u-boot-imx-next-20240524' of https://gitlab.denx.de/u-boot/custodians/u-boot-imx into next
CI: https://source.denx.de/u-boot/custodians/u-boot-imx/-/pipelines/20834 - Allow signing i.MX8M flash.bin via binman, which is a much more elegant solution that using scripts. - Improve i.MX8M HAB documentation. - Increase PHY auto-negotiation timeout to 20s on MX8Menlo - Add bmode support for the MX53 Menlo board. - Update Update iMX8MM Menlo board configuration
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -73,6 +73,8 @@ fit-dtb.blob*
|
||||
/capsule.*.efi-capsule
|
||||
/capsule*.map
|
||||
/keep-syms-lto.*
|
||||
/*imx8mimage*
|
||||
/*imx8mcst*
|
||||
|
||||
#
|
||||
# Generated include files
|
||||
|
@@ -306,6 +306,7 @@ F: arch/arm/include/asm/mach-imx/
|
||||
F: board/freescale/*mx*/
|
||||
F: board/freescale/common/
|
||||
F: common/spl/spl_imx_container.c
|
||||
F: doc/imx/
|
||||
F: drivers/serial/serial_mxc.c
|
||||
F: include/imx_container.h
|
||||
|
||||
|
2
Makefile
2
Makefile
@@ -2213,7 +2213,7 @@ MRPROPER_DIRS += include/config include/generated spl tpl vpl \
|
||||
# Remove include/asm symlink created by U-Boot before v2014.01
|
||||
MRPROPER_FILES += .config .config.old include/autoconf.mk* include/config.h \
|
||||
ctags etags tags TAGS cscope* GPATH GTAGS GRTAGS GSYMS \
|
||||
drivers/video/fonts/*.S include/asm
|
||||
drivers/video/fonts/*.S include/asm *imx8mimage* *imx8mcst*
|
||||
|
||||
# clean - Delete most, but leave enough to build external modules
|
||||
#
|
||||
|
@@ -54,7 +54,15 @@
|
||||
};
|
||||
#endif
|
||||
|
||||
nxp-imx8mimage {
|
||||
#ifdef CONFIG_IMX_HAB
|
||||
nxp-imx8mcst@0 {
|
||||
filename = "u-boot-spl-mkimage.signed.bin";
|
||||
nxp,loader-address = <CONFIG_SPL_TEXT_BASE>;
|
||||
nxp,unlock;
|
||||
args; /* Needed by mkimage etype superclass */
|
||||
#endif
|
||||
|
||||
binman_imx_spl: nxp-imx8mimage {
|
||||
filename = "u-boot-spl-mkimage.bin";
|
||||
nxp,boot-from = "sd";
|
||||
nxp,rom-version = <1>;
|
||||
@@ -97,8 +105,22 @@
|
||||
};
|
||||
};
|
||||
};
|
||||
#ifdef CONFIG_IMX_HAB
|
||||
};
|
||||
|
||||
fit {
|
||||
nxp-imx8mcst@1 {
|
||||
filename = "u-boot-fit.signed.bin";
|
||||
nxp,loader-address = <CONFIG_SPL_LOAD_FIT_ADDRESS>;
|
||||
#ifdef CONFIG_FSPI_CONF_HEADER
|
||||
offset = <0x58C00>;
|
||||
#else
|
||||
offset = <0x57c00>;
|
||||
#endif
|
||||
|
||||
args; /* Needed by mkimage etype superclass */
|
||||
#endif
|
||||
|
||||
binman_imx_fit: fit {
|
||||
description = "Configuration to load ATF before U-Boot";
|
||||
#ifndef CONFIG_IMX_HAB
|
||||
fit,external-offset = <CONFIG_FIT_EXTERNAL_OFFSET>;
|
||||
@@ -174,6 +196,9 @@
|
||||
};
|
||||
};
|
||||
};
|
||||
#ifdef CONFIG_IMX_HAB
|
||||
};
|
||||
#endif
|
||||
};
|
||||
};
|
||||
|
||||
|
@@ -35,12 +35,8 @@
|
||||
bootph-pre-ram;
|
||||
};
|
||||
|
||||
&binman {
|
||||
section {
|
||||
fit {
|
||||
&binman_imx_fit {
|
||||
offset = <0x5fc00>;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
&gpio1 {
|
||||
|
@@ -103,7 +103,15 @@
|
||||
};
|
||||
#endif
|
||||
|
||||
nxp-imx8mimage {
|
||||
#ifdef CONFIG_IMX_HAB
|
||||
nxp-imx8mcst@0 {
|
||||
filename = "u-boot-spl-mkimage.signed.bin";
|
||||
nxp,loader-address = <CONFIG_SPL_TEXT_BASE>;
|
||||
nxp,unlock;
|
||||
args; /* Needed by mkimage etype superclass */
|
||||
#endif
|
||||
|
||||
binman_imx_spl: nxp-imx8mimage {
|
||||
filename = "u-boot-spl-mkimage.bin";
|
||||
nxp,boot-from = "sd";
|
||||
nxp,rom-version = <2>;
|
||||
@@ -169,7 +177,21 @@
|
||||
};
|
||||
};
|
||||
|
||||
fit {
|
||||
#ifdef CONFIG_IMX_HAB
|
||||
};
|
||||
|
||||
nxp-imx8mcst@1 {
|
||||
filename = "u-boot-fit.signed.bin";
|
||||
nxp,loader-address = <CONFIG_SPL_LOAD_FIT_ADDRESS>;
|
||||
#ifdef CONFIG_FSPI_CONF_HEADER
|
||||
offset = <0x59000>;
|
||||
#else
|
||||
offset = <0x58000>;
|
||||
#endif
|
||||
args; /* Needed by mkimage etype superclass */
|
||||
#endif
|
||||
|
||||
binman_imx_fit: fit {
|
||||
description = "Configuration to load ATF before U-Boot";
|
||||
#ifndef CONFIG_IMX_HAB
|
||||
fit,external-offset = <CONFIG_FIT_EXTERNAL_OFFSET>;
|
||||
@@ -245,5 +267,8 @@
|
||||
};
|
||||
};
|
||||
};
|
||||
#ifdef CONFIG_IMX_HAB
|
||||
};
|
||||
#endif
|
||||
};
|
||||
};
|
||||
|
@@ -135,9 +135,7 @@
|
||||
bootph-pre-ram;
|
||||
};
|
||||
|
||||
&binman {
|
||||
section {
|
||||
fit {
|
||||
&binman_imx_fit {
|
||||
images {
|
||||
fdt-dto-imx8mp-dhcom-som-overlay-eth1xfast {
|
||||
description = "imx8mp-dhcom-som-overlay-eth1xfast";
|
||||
@@ -202,6 +200,4 @@
|
||||
"fdt-dto-imx8mp-dhcom-pdk3-overlay-rev100";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
@@ -135,9 +135,7 @@
|
||||
assigned-clock-parents = <&clk IMX8MP_SYS_PLL1_400M>;
|
||||
};
|
||||
|
||||
&binman {
|
||||
section {
|
||||
fit {
|
||||
&binman_imx_fit {
|
||||
images {
|
||||
fip {
|
||||
description = "Trusted Firmware FIP";
|
||||
@@ -151,8 +149,6 @@
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
/* This cannot work since it refers to a template node
|
||||
|
@@ -86,7 +86,15 @@
|
||||
section {
|
||||
pad-byte = <0x00>;
|
||||
|
||||
nxp-imx8mimage {
|
||||
#ifdef CONFIG_IMX_HAB
|
||||
nxp-imx8mcst@0 {
|
||||
filename = "u-boot-spl-mkimage.signed.bin";
|
||||
nxp,loader-address = <CONFIG_SPL_TEXT_BASE>;
|
||||
nxp,unlock;
|
||||
args; /* Needed by mkimage etype superclass */
|
||||
#endif
|
||||
|
||||
binman_imx_spl: nxp-imx8mimage {
|
||||
filename = "u-boot-spl-mkimage.bin";
|
||||
nxp,boot-from = "sd";
|
||||
nxp,rom-version = <2>;
|
||||
@@ -128,8 +136,17 @@
|
||||
};
|
||||
};
|
||||
};
|
||||
#ifdef CONFIG_IMX_HAB
|
||||
};
|
||||
|
||||
fit {
|
||||
nxp-imx8mcst@1 {
|
||||
filename = "u-boot-fit.signed.bin";
|
||||
nxp,loader-address = <CONFIG_SPL_LOAD_FIT_ADDRESS>;
|
||||
offset = <0x58000>;
|
||||
args; /* Needed by mkimage etype superclass */
|
||||
#endif
|
||||
|
||||
binman_imx_fit: fit {
|
||||
description = "Configuration to load ATF before U-Boot";
|
||||
#ifndef CONFIG_IMX_HAB
|
||||
fit,external-offset = <CONFIG_FIT_EXTERNAL_OFFSET>;
|
||||
@@ -191,5 +208,8 @@
|
||||
};
|
||||
};
|
||||
};
|
||||
#ifdef CONFIG_IMX_HAB
|
||||
};
|
||||
#endif
|
||||
};
|
||||
};
|
||||
|
@@ -10,14 +10,10 @@
|
||||
bootph-pre-ram;
|
||||
};
|
||||
|
||||
&binman {
|
||||
section {
|
||||
nxp-imx8mimage {
|
||||
&binman_imx_spl {
|
||||
section {
|
||||
signed-hdmi-imx8m {
|
||||
filename = "signed_dp_imx8m.bin";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
@@ -38,7 +38,15 @@
|
||||
section {
|
||||
pad-byte = <0x00>;
|
||||
|
||||
nxp-imx8mimage {
|
||||
#ifdef CONFIG_IMX_HAB
|
||||
nxp-imx8mcst@0 {
|
||||
filename = "u-boot-spl-mkimage.signed.bin";
|
||||
nxp,loader-address = <CONFIG_SPL_TEXT_BASE>;
|
||||
nxp,unlock;
|
||||
args; /* Needed by mkimage etype superclass */
|
||||
#endif
|
||||
|
||||
binman_imx_spl: nxp-imx8mimage {
|
||||
filename = "u-boot-spl-mkimage.bin";
|
||||
nxp,boot-from = "sd";
|
||||
nxp,rom-version = <1>;
|
||||
@@ -86,8 +94,17 @@
|
||||
};
|
||||
};
|
||||
};
|
||||
#ifdef CONFIG_IMX_HAB
|
||||
};
|
||||
|
||||
fit {
|
||||
nxp-imx8mcst@1 {
|
||||
filename = "u-boot-fit.signed.bin";
|
||||
nxp,loader-address = <CONFIG_SPL_LOAD_FIT_ADDRESS>;
|
||||
offset = <0x58000>;
|
||||
args; /* Needed by mkimage etype superclass */
|
||||
#endif
|
||||
|
||||
binman_imx_fit: fit {
|
||||
description = "Configuration to load ATF before U-Boot";
|
||||
#ifndef CONFIG_IMX_HAB
|
||||
fit,external-offset = <CONFIG_FIT_EXTERNAL_OFFSET>;
|
||||
@@ -149,5 +166,8 @@
|
||||
};
|
||||
};
|
||||
};
|
||||
#ifdef CONFIG_IMX_HAB
|
||||
};
|
||||
#endif
|
||||
};
|
||||
};
|
||||
|
@@ -25,15 +25,19 @@ CONFIG_SPL_BSS_MAX_SIZE=0x2000
|
||||
CONFIG_SPL=y
|
||||
CONFIG_SYS_BOOTCOUNT_SINGLEWORD=y
|
||||
CONFIG_ENV_OFFSET_REDUND=0xFFFFDE00
|
||||
CONFIG_IMX_BOOTAUX=y
|
||||
CONFIG_SYS_LOAD_ADDR=0x40480000
|
||||
CONFIG_SYS_MEMTEST_START=0x40000000
|
||||
CONFIG_SYS_MEMTEST_END=0x80000000
|
||||
CONFIG_FIT=y
|
||||
CONFIG_FIT_EXTERNAL_OFFSET=0x3000
|
||||
CONFIG_FIT_VERBOSE=y
|
||||
CONFIG_SPL_LOAD_FIT=y
|
||||
CONFIG_DISTRO_DEFAULTS=y
|
||||
CONFIG_BOOTDELAY=1
|
||||
CONFIG_OF_SYSTEM_SETUP=y
|
||||
CONFIG_BOOTCOMMAND="mmc partconf 0 distro_bootpart && load ${devtype} ${devnum}:${distro_bootpart} ${loadaddr} boot/fitImage && source ${loadaddr}:bootscr-boot.cmd ; reset"
|
||||
CONFIG_USE_PREBOOT=y
|
||||
CONFIG_DEFAULT_FDT_FILE="imx8mm-mx8menlo.dtb"
|
||||
CONFIG_SYS_CBSIZE=2048
|
||||
CONFIG_SYS_PBSIZE=2081
|
||||
@@ -57,19 +61,26 @@ CONFIG_SYS_PROMPT="Verdin iMX8MM # "
|
||||
# CONFIG_BOOTM_NETBSD is not set
|
||||
CONFIG_CMD_ASKENV=y
|
||||
# CONFIG_CMD_EXPORTENV is not set
|
||||
# CONFIG_CMD_CRC32 is not set
|
||||
CONFIG_CRC32_VERIFY=y
|
||||
CONFIG_CMD_MD5SUM=y
|
||||
CONFIG_MD5SUM_VERIFY=y
|
||||
CONFIG_CMD_MEMTEST=y
|
||||
CONFIG_CMD_CLK=y
|
||||
CONFIG_CMD_FUSE=y
|
||||
CONFIG_CMD_GPIO=y
|
||||
CONFIG_CMD_I2C=y
|
||||
CONFIG_CMD_MMC=y
|
||||
CONFIG_CMD_READ=y
|
||||
CONFIG_CMD_USB=y
|
||||
CONFIG_CMD_USB_SDP=y
|
||||
CONFIG_CMD_USB_MASS_STORAGE=y
|
||||
CONFIG_CMD_CAT=y
|
||||
CONFIG_CMD_XXD=y
|
||||
CONFIG_CMD_BOOTCOUNT=y
|
||||
CONFIG_CMD_CACHE=y
|
||||
CONFIG_CMD_TIME=y
|
||||
CONFIG_CMD_UUID=y
|
||||
CONFIG_CMD_PMIC=y
|
||||
CONFIG_CMD_REGULATOR=y
|
||||
CONFIG_CMD_BTRFS=y
|
||||
CONFIG_CMD_EXT4_WRITE=y
|
||||
@@ -84,8 +95,9 @@ CONFIG_SYS_RELOC_GD_ENV_ADDR=y
|
||||
CONFIG_SYS_MMC_ENV_PART=1
|
||||
CONFIG_ENV_VARS_UBOOT_RUNTIME_CONFIG=y
|
||||
CONFIG_USE_ETHPRIME=y
|
||||
CONFIG_ETHPRIME="FEC"
|
||||
CONFIG_ETHPRIME="eth0"
|
||||
CONFIG_VERSION_VARIABLE=y
|
||||
CONFIG_NET_RANDOM_ETHADDR=y
|
||||
CONFIG_IP_DEFRAG=y
|
||||
CONFIG_TFTP_BLOCKSIZE=4096
|
||||
CONFIG_SPL_DM=y
|
||||
@@ -96,16 +108,26 @@ CONFIG_CLK_COMPOSITE_CCF=y
|
||||
CONFIG_SPL_CLK_IMX8MM=y
|
||||
CONFIG_CLK_IMX8MM=y
|
||||
CONFIG_GPIO_HOG=y
|
||||
CONFIG_SPL_GPIO_HOG=y
|
||||
CONFIG_MXC_GPIO=y
|
||||
CONFIG_DM_I2C=y
|
||||
CONFIG_MISC=y
|
||||
CONFIG_I2C_EEPROM=y
|
||||
CONFIG_SUPPORT_EMMC_BOOT=y
|
||||
CONFIG_MMC_IO_VOLTAGE=y
|
||||
CONFIG_SPL_MMC_IO_VOLTAGE=y
|
||||
CONFIG_MMC_UHS_SUPPORT=y
|
||||
CONFIG_SPL_MMC_UHS_SUPPORT=y
|
||||
CONFIG_MMC_HS400_ES_SUPPORT=y
|
||||
CONFIG_MMC_HS400_SUPPORT=y
|
||||
CONFIG_SPL_MMC_HS400_SUPPORT=y
|
||||
CONFIG_FSL_USDHC=y
|
||||
CONFIG_PHYLIB=y
|
||||
CONFIG_PHY_ADDR_ENABLE=y
|
||||
CONFIG_PHY_MICREL=y
|
||||
CONFIG_PHY_MICREL_KSZ90X1=y
|
||||
CONFIG_PHY_FIXED=y
|
||||
CONFIG_DM_MDIO=y
|
||||
CONFIG_FEC_MXC=y
|
||||
CONFIG_MII=y
|
||||
CONFIG_SPL_PHY=y
|
||||
@@ -128,6 +150,7 @@ CONFIG_SPL_SYSRESET=y
|
||||
CONFIG_SYSRESET_PSCI=y
|
||||
CONFIG_SYSRESET_WATCHDOG=y
|
||||
CONFIG_DM_THERMAL=y
|
||||
CONFIG_IMX_TMU=y
|
||||
CONFIG_USB=y
|
||||
CONFIG_SPL_USB_HOST=y
|
||||
CONFIG_USB_EHCI_HCD=y
|
||||
@@ -143,3 +166,4 @@ CONFIG_SDP_LOADADDR=0x40400000
|
||||
CONFIG_USB_GADGET_DOWNLOAD=y
|
||||
CONFIG_SPL_USB_SDP_SUPPORT=y
|
||||
CONFIG_IMX_WATCHDOG=y
|
||||
CONFIG_HEXDUMP=y
|
||||
|
@@ -22,6 +22,7 @@ CONFIG_SPL=y
|
||||
CONFIG_SYS_BOOTCOUNT_SINGLEWORD=y
|
||||
CONFIG_ENV_OFFSET_REDUND=0x180000
|
||||
CONFIG_SYS_LOAD_ADDR=0x70800000
|
||||
CONFIG_CMD_BMODE=y
|
||||
CONFIG_FIT=y
|
||||
CONFIG_BOOTDELAY=1
|
||||
CONFIG_OF_BOARD_SETUP=y
|
||||
@@ -71,7 +72,7 @@ CONFIG_ENV_RANGE=0x80000
|
||||
CONFIG_SYS_REDUNDAND_ENVIRONMENT=y
|
||||
CONFIG_SYS_RELOC_GD_ENV_ADDR=y
|
||||
CONFIG_USE_BOOTFILE=y
|
||||
CONFIG_BOOTFILE="boot/fitImage"
|
||||
CONFIG_BOOTFILE="fitImage"
|
||||
CONFIG_USE_ETHPRIME=y
|
||||
CONFIG_ETHPRIME="FEC0"
|
||||
CONFIG_USE_HOSTNAME=y
|
||||
|
@@ -1,92 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# 0) Generate keys
|
||||
#
|
||||
# WARNING: ECDSA keys are only supported by HAB 4.5 and newer (i.e. i.MX8M Plus)
|
||||
#
|
||||
# cd /path/to/cst-3.3.1/keys/
|
||||
# ./hab4_pki_tree.sh -existing-ca n -use-ecc n -kl 4096 -duration 10 -num-srk 4 -srk-ca y
|
||||
# cd /path/to/cst-3.3.1/crts/
|
||||
# ../linux64/bin/srktool -h 4 -t SRK_1_2_3_4_table.bin -e SRK_1_2_3_4_fuse.bin -d sha256 -c ./SRK1_sha256_4096_65537_v3_ca_crt.pem,./SRK2_sha256_4096_65537_v3_ca_crt.pem,./SRK3_sha256_4096_65537_v3_ca_crt.pem,./SRK4_sha256_4096_65537_v3_ca_crt.pem -f 1
|
||||
|
||||
# 1) Build U-Boot (e.g. for i.MX8MM)
|
||||
#
|
||||
# cp -Lv /path/to/arm-trusted-firmware/build/imx8mm/release/bl31.bin .
|
||||
# cp -Lv /path/to/firmware-imx-8.14/firmware/ddr/synopsys/ddr3* .
|
||||
# make -j imx8mm_board_defconfig
|
||||
# make -j`nproc` flash.bin
|
||||
|
||||
# 2) Sign SPL and DRAM blobs
|
||||
|
||||
cp doc/imx/habv4/csf_examples/mx8m/csf_spl.txt csf_spl.tmp
|
||||
cp doc/imx/habv4/csf_examples/mx8m/csf_fit.txt csf_fit.tmp
|
||||
|
||||
# update File Paths from env vars
|
||||
if ! [ -r $CSF_KEY ]; then
|
||||
echo "Error: \$CSF_KEY not found"
|
||||
exit 1
|
||||
fi
|
||||
if ! [ -r $IMG_KEY ]; then
|
||||
echo "Error: \$IMG_KEY not found"
|
||||
exit 1
|
||||
fi
|
||||
if ! [ -r $SRK_TABLE ]; then
|
||||
echo "Error: \$SRK_TABLE not found"
|
||||
exit 1
|
||||
fi
|
||||
sed -i "s:\$CSF_KEY:$CSF_KEY:" csf_spl.tmp
|
||||
sed -i "s:\$IMG_KEY:$IMG_KEY:" csf_spl.tmp
|
||||
sed -i "s:\$SRK_TABLE:$SRK_TABLE:" csf_spl.tmp
|
||||
sed -i "s:\$CSF_KEY:$CSF_KEY:" csf_fit.tmp
|
||||
sed -i "s:\$IMG_KEY:$IMG_KEY:" csf_fit.tmp
|
||||
sed -i "s:\$SRK_TABLE:$SRK_TABLE:" csf_fit.tmp
|
||||
|
||||
# update SPL Blocks
|
||||
spl_block_base=$(printf "0x%x" $(( $(sed -n "/CONFIG_SPL_TEXT_BASE=/ s@.*=@@p" .config) - 0x40)) )
|
||||
spl_block_size=$(printf "0x%x" $(stat -tc %s u-boot-spl-ddr.bin))
|
||||
sed -i "/Blocks = / s@.*@ Blocks = $spl_block_base 0x0 $spl_block_size \"flash.bin\"@" csf_spl.tmp
|
||||
|
||||
# Generate CSF blob
|
||||
cst -i csf_spl.tmp -o csf_spl.bin
|
||||
|
||||
# Patch CSF blob into flash.bin
|
||||
spl_csf_offset=$(xxd -s 24 -l 4 -e flash.bin | cut -d " " -f 2 | sed "s@^@0x@")
|
||||
spl_bin_offset=$(xxd -s 4 -l 4 -e flash.bin | cut -d " " -f 2 | sed "s@^@0x@")
|
||||
spl_dd_offset=$((${spl_csf_offset} - ${spl_bin_offset} + 0x40))
|
||||
dd if=csf_spl.bin of=flash.bin bs=1 seek=${spl_dd_offset} conv=notrunc
|
||||
|
||||
# 3) Sign u-boot.itb
|
||||
|
||||
# fitImage
|
||||
fit_block_base=$(printf "0x%x" $(sed -n "/CONFIG_SPL_LOAD_FIT_ADDRESS=/ s@.*=@@p" .config) )
|
||||
fit_block_offset=$(printf "0x%s" $(fdtget -t x u-boot.dtb /binman/imx-boot/uboot offset))
|
||||
fit_block_size=$(printf "0x%x" $(( ( ( $(stat -tc %s u-boot.itb) + 0x1000 - 0x1 ) & ~(0x1000 - 0x1)) + 0x20 )) )
|
||||
sed -i "/Blocks = / s@.*@ Blocks = $fit_block_base $fit_block_offset $fit_block_size \"flash.bin\"@" csf_fit.tmp
|
||||
|
||||
# IVT
|
||||
ivt_ptr_base=$(printf "%08x" ${fit_block_base} | sed "s@\(..\)\(..\)\(..\)\(..\)@0x\4\3\2\1@")
|
||||
ivt_block_base=$(printf "%08x" $(( ${fit_block_base} + ${fit_block_size} - 0x20 )) | sed "s@\(..\)\(..\)\(..\)\(..\)@0x\4\3\2\1@")
|
||||
csf_block_base=$(printf "%08x" $(( ${fit_block_base} + ${fit_block_size} )) | sed "s@\(..\)\(..\)\(..\)\(..\)@0x\4\3\2\1@")
|
||||
ivt_block_offset=$((${fit_block_offset} + ${fit_block_size} - 0x20))
|
||||
csf_block_offset=$((${ivt_block_offset} + 0x20))
|
||||
|
||||
echo "0xd1002041 ${ivt_block_base} 0x00000000 0x00000000 0x00000000 ${ivt_block_base} ${csf_block_base} 0x00000000" | xxd -r -p > ivt.bin
|
||||
dd if=ivt.bin of=flash.bin bs=1 seek=${ivt_block_offset} conv=notrunc
|
||||
|
||||
# Generate CSF blob
|
||||
cst -i csf_fit.tmp -o csf_fit.bin
|
||||
|
||||
# When loading flash.bin via USB, we must ensure that the file being
|
||||
# served is as large as the target expects (see
|
||||
# board_spl_fit_size_align()), otherwise the target will hang in
|
||||
# rom_api_download_image() waiting for the remaining bytes.
|
||||
#
|
||||
# Note that in order for dd to actually extend the file, one must not
|
||||
# pass conv=notrunc here. With a non-zero seek= argument, dd is
|
||||
# documented to preserve the contents of the file seeked past; in
|
||||
# particular, dd does not open the file with O_TRUNC.
|
||||
CSF_SIZE=$(sed -n "/CONFIG_CSF_SIZE=/ s@.*=@@p" .config)
|
||||
dd if=/dev/null of=csf_fit.bin bs=1 seek=$((CSF_SIZE - 0x20)) count=0
|
||||
|
||||
# Patch CSF blob into flash.bin
|
||||
dd if=csf_fit.bin of=flash.bin bs=1 seek=${csf_block_offset} conv=notrunc
|
@@ -1,30 +0,0 @@
|
||||
[Header]
|
||||
Version = 4.3
|
||||
Hash Algorithm = sha256
|
||||
Engine = CAAM
|
||||
Engine Configuration = 0
|
||||
Certificate Format = X509
|
||||
Signature Format = CMS
|
||||
|
||||
[Install SRK]
|
||||
# SRK_TABLE is full path to SRK_1_2_3_4_table.bin
|
||||
File = "$SRK_TABLE"
|
||||
Source index = 0
|
||||
|
||||
[Install CSFK]
|
||||
# CSF_KEY is full path to CSF1_1_sha256_4096_65537_v3_usr_crt.pem
|
||||
File = "$CSF_KEY"
|
||||
|
||||
[Authenticate CSF]
|
||||
|
||||
[Install Key]
|
||||
Verification index = 0
|
||||
Target Index = 2
|
||||
# IMG_KEY is full path to IMG1_1_sha256_4096_65537_v3_usr_crt.pem
|
||||
File = "$IMG_KEY"
|
||||
|
||||
[Authenticate Data]
|
||||
Verification index = 2
|
||||
# FIXME:
|
||||
# Line 1 -- fitImage
|
||||
Blocks = CONFIG_SPL_LOAD_FIT_ADDRESS 0x57c00 0xffff "flash.bin"
|
@@ -1,33 +0,0 @@
|
||||
[Header]
|
||||
Version = 4.3
|
||||
Hash Algorithm = sha256
|
||||
Engine = CAAM
|
||||
Engine Configuration = 0
|
||||
Certificate Format = X509
|
||||
Signature Format = CMS
|
||||
|
||||
[Install SRK]
|
||||
# SRK_TABLE is full path to SRK_1_2_3_4_table.bin
|
||||
File = "$SRK_TABLE"
|
||||
Source index = 0
|
||||
|
||||
[Install CSFK]
|
||||
# CSF_KEY is full path to CSF1_1_sha256_4096_65537_v3_usr_crt.pem
|
||||
File = "$CSF_KEY"
|
||||
|
||||
[Authenticate CSF]
|
||||
|
||||
[Unlock]
|
||||
Engine = CAAM
|
||||
Features = MID
|
||||
|
||||
[Install Key]
|
||||
Verification index = 0
|
||||
Target Index = 2
|
||||
# IMG_KEY is full path to IMG1_1_sha256_4096_65537_v3_usr_crt.pem
|
||||
File = "$IMG_KEY"
|
||||
|
||||
[Authenticate Data]
|
||||
Verification index = 2
|
||||
# FIXME: Adjust start (first column) and size (third column) here
|
||||
Blocks = 0x7e0fc0 0x0 0x306f0 "flash.bin"
|
@@ -121,6 +121,9 @@ build configuration:
|
||||
- Defconfig:
|
||||
|
||||
CONFIG_IMX_HAB=y
|
||||
CONFIG_FSL_CAAM=y
|
||||
CONFIG_ARCH_MISC_INIT=y
|
||||
CONFIG_SPL_CRYPTO=y
|
||||
|
||||
- Kconfig:
|
||||
|
||||
@@ -131,91 +134,59 @@ build configuration:
|
||||
|
||||
The CSF contains all the commands that the HAB executes during the secure
|
||||
boot. These commands instruct the HAB code on which memory areas of the image
|
||||
to authenticate, which keys to install, use and etc.
|
||||
to authenticate, which keys to install, use and etc. The CSF is generated
|
||||
using the CST Code Signing Tool based on input configuration file. This tool
|
||||
input configuration file is generated using binman, and the tool is invoked
|
||||
from binman as well.
|
||||
|
||||
CSF examples are available under doc/imx/habv4/csf_examples/ directory.
|
||||
The SPL and fitImage sections of the generated image are signed separately.
|
||||
The signing is activated by wrapping SPL and fitImage sections into nxp-imx8mcst
|
||||
etype, which is done automatically in arch/arm/dts/imx8m{m,n,p,q}-u-boot.dtsi
|
||||
in case CONFIG_IMX_HAB Kconfig symbol is enabled.
|
||||
|
||||
CSF "Blocks" line for csf_spl.txt can be generated as follows:
|
||||
Per default the HAB keys and certificates need to be located in the build
|
||||
directory, this means creating a symbolic link or copying the following files
|
||||
from the HAB keys directory flat (e.g. removing the `keys` and `cert`
|
||||
subdirectory) into the u-boot build directory for the CST Code Signing Tool to
|
||||
locate them:
|
||||
|
||||
```
|
||||
spl_block_base=$(printf "0x%x" $(( $(sed -n "/CONFIG_SPL_TEXT_BASE=/ s@.*=@@p" .config) - 0x40)) )
|
||||
spl_block_size=$(printf "0x%x" $(stat -tc %s u-boot-spl-ddr.bin))
|
||||
sed -i "/Blocks = / s@.*@ Blocks = $spl_block_base 0x0 $spl_block_size \"flash.bin\"@" csf_spl.txt
|
||||
```
|
||||
- `crts/SRK_1_2_3_4_table.bin`
|
||||
- `crts/CSF1_1_sha256_4096_65537_v3_usr_crt.pem`
|
||||
- `keys/CSF1_1_sha256_4096_65537_v3_usr_key.pem`
|
||||
- `crts/IMG1_1_sha256_4096_65537_v3_usr_crt.pem`
|
||||
- `keys/IMG1_1_sha256_4096_65537_v3_usr_key.pem`
|
||||
- `keys/key_pass.txt`
|
||||
|
||||
The resulting line looks as follows:
|
||||
```
|
||||
Blocks = 0x7e0fc0 0x0 0x306f0 "flash.bin"
|
||||
```
|
||||
The paths to the SRK table and the certificates can be modified via changes to
|
||||
the nxp_imx8mcst device tree node(s), however the other files are required by
|
||||
the CST tools as well, and will be searched for in relation to them.
|
||||
|
||||
The columns mean:
|
||||
- CONFIG_SPL_TEXT_BASE - 0x40 -- Start address of signed data, in DRAM
|
||||
- 0x0 -- Start address of signed data, in "flash.bin"
|
||||
- 0x306f0 -- Length of signed data, in "flash.bin"
|
||||
- Filename -- "flash.bin"
|
||||
Build of flash.bin target then produces a signed flash.bin automatically.
|
||||
|
||||
To generate signature for the SPL part of flash.bin container, use CST:
|
||||
```
|
||||
cst -i csf_spl.tmp -o csf_spl.bin
|
||||
```
|
||||
The nxp-imx8mcst etype is configurable using either DT properties or environment
|
||||
variables. The following DT properties and environment variables are supported.
|
||||
Note that environment variables override DT properties.
|
||||
|
||||
The newly generated CST blob has to be patched into existing flash.bin
|
||||
container. Conveniently, flash.bin IVT contains physical address of the
|
||||
CSF blob. Remember, the SPL part of flash.bin container is loaded by the
|
||||
BootROM at CONFIG_SPL_TEXT_BASE - 0x40 , so the offset of CSF blob in
|
||||
the fitImage can be calculated and inserted into the flash.bin in the
|
||||
correct location as follows:
|
||||
```
|
||||
# offset = IVT_HEADER[6 = CSF address] - CONFIG_SPL_TEXT_BASE - 0x40
|
||||
spl_csf_offset=$(xxd -s 24 -l 4 -e flash.bin | cut -d " " -f 2 | sed "s@^@0x@")
|
||||
spl_bin_offset=$(xxd -s 4 -l 4 -e flash.bin | cut -d " " -f 2 | sed "s@^@0x@")
|
||||
spl_dd_offset=$((${spl_csf_offset} - ${spl_bin_offset} + 0x40))
|
||||
dd if=csf_spl.bin of=flash.bin bs=1 seek=${spl_dd_offset} conv=notrunc
|
||||
```
|
||||
+--------------------+-----------+------------------------------------------------------------------+
|
||||
| DT property | Variable | Description |
|
||||
+====================+===========+==================================================================+
|
||||
| nxp,loader-address | | SPL base address |
|
||||
+--------------------+-----------+------------------------------------------------------------------+
|
||||
| nxp,srk-table | SRK_TABLE | full path to SRK_1_2_3_4_table.bin |
|
||||
+--------------------+-----------+------------------------------------------------------------------+
|
||||
| nxp,csf-crt | CSF_KEY | full path to the CSF Key CSF1_1_sha256_4096_65537_v3_usr_crt.pem |
|
||||
+--------------------+-----------+------------------------------------------------------------------+
|
||||
| nxp,img-crt | IMG_KEY | full path to the IMG Key IMG1_1_sha256_4096_65537_v3_usr_crt.pem |
|
||||
+--------------------+-----------+------------------------------------------------------------------+
|
||||
|
||||
CSF "Blocks" line for csf_fit.txt can be generated as follows:
|
||||
```
|
||||
# fitImage
|
||||
fit_block_base=$(printf "0x%x" $(sed -n "/CONFIG_SPL_LOAD_FIT_ADDRESS=/ s@.*=@@p" .config) )
|
||||
fit_block_offset=$(printf "0x%s" $(fdtget -t x u-boot.dtb /binman/imx-boot/uboot offset))
|
||||
fit_block_size=$(printf "0x%x" $(( ( ( $(stat -tc %s u-boot.itb) + 0x1000 - 0x1 ) & ~(0x1000 - 0x1)) + 0x20 )) )
|
||||
sed -i "/Blocks = / s@.*@ Blocks = $fit_block_base $fit_block_offset $fit_block_size \"flash.bin\"@" csf_fit.tmp
|
||||
```
|
||||
|
||||
The fitImage part of flash.bin requires separate IVT. Generate the IVT and
|
||||
patch it into the correct aligned location of flash.bin as follows:
|
||||
```
|
||||
# IVT
|
||||
ivt_ptr_base=$(printf "%08x" ${fit_block_base} | sed "s@\(..\)\(..\)\(..\)\(..\)@0x\4\3\2\1@")
|
||||
ivt_block_base=$(printf "%08x" $(( ${fit_block_base} + ${fit_block_size} - 0x20 )) | sed "s@\(..\)\(..\)\(..\)\(..\)@0x\4\3\2\1@")
|
||||
csf_block_base=$(printf "%08x" $(( ${fit_block_base} + ${fit_block_size} )) | sed "s@\(..\)\(..\)\(..\)\(..\)@0x\4\3\2\1@")
|
||||
ivt_block_offset=$((${fit_block_offset} + ${fit_block_size} - 0x20))
|
||||
csf_block_offset=$((${ivt_block_offset} + 0x20))
|
||||
|
||||
echo "0xd1002041 ${ivt_block_base} 0x00000000 0x00000000 0x00000000 ${ivt_block_base} ${csf_block_base} 0x00000000" | xxd -r -p > ivt.bin
|
||||
dd if=ivt.bin of=flash.bin bs=1 seek=${ivt_block_offset} conv=notrunc
|
||||
```
|
||||
|
||||
To generate CSF signature for the fitImage part of flash.bin container, use CST:
|
||||
```
|
||||
cst -i csf_fit.tmp -o csf_fit.bin
|
||||
```
|
||||
|
||||
Finally, patch the CSF signature into the fitImage right past the IVT:
|
||||
```
|
||||
dd if=csf_fit.bin of=flash.bin bs=1 seek=${csf_block_offset} conv=notrunc
|
||||
```
|
||||
|
||||
The entire script is available in doc/imx/habv4/csf_examples/mx8m/csf.sh
|
||||
and can be used as follows to modify flash.bin to be signed
|
||||
(adjust paths as needed):
|
||||
Environment variables can be set as follows to point the build process
|
||||
to external key material:
|
||||
```
|
||||
export CST_DIR=/usr/src/cst-3.3.1/
|
||||
export CSF_KEY=$CST_DIR/crts/CSF1_1_sha256_4096_65537_v3_usr_crt.pem
|
||||
export IMG_KEY=$CST_DIR/crts/IMG1_1_sha256_4096_65537_v3_usr_crt.pem
|
||||
export SRK_TABLE=$CST_DIR/crts/SRK_1_2_3_4_table.bin
|
||||
export PATH=$CST_DIR/linux64/bin:$PATH
|
||||
/bin/sh doc/imx/habv4/csf_examples/mx8m/csf.sh
|
||||
make flash.bin
|
||||
```
|
||||
|
||||
1.4 Closing the device
|
||||
|
@@ -8,6 +8,9 @@
|
||||
|
||||
#include <configs/verdin-imx8mm.h>
|
||||
|
||||
/* PHY needs a longer autoneg timeout */
|
||||
#define PHY_ANEG_TIMEOUT 20000
|
||||
|
||||
/* Custom initial environment variables */
|
||||
#undef CFG_EXTRA_ENV_SETTINGS
|
||||
#define CFG_EXTRA_ENV_SETTINGS \
|
||||
|
@@ -119,7 +119,7 @@
|
||||
"addargs=run addcons addmisc addmtd\0" \
|
||||
"mmcload=" \
|
||||
"mmc rescan || reset ; load mmc ${mmcdev}:${mmcpart} " \
|
||||
"${kernel_addr_r} ${bootfile} || reset\0" \
|
||||
"${kernel_addr_r} boot/${bootfile} || reset\0" \
|
||||
"miscargs=nohlt panic=1\0" \
|
||||
"mmcargs=setenv bootargs root=/dev/mmcblk0p${mmcpart} rw " \
|
||||
"rootwait\0" \
|
||||
|
48
tools/binman/btool/cst.py
Normal file
48
tools/binman/btool/cst.py
Normal file
@@ -0,0 +1,48 @@
|
||||
# SPDX-License-Identifier: GPL-2.0+
|
||||
# Copyright 2024 Marek Vasut <marex@denx.de>
|
||||
#
|
||||
"""Bintool implementation for cst"""
|
||||
|
||||
import re
|
||||
|
||||
from binman import bintool
|
||||
|
||||
class Bintoolcst(bintool.Bintool):
|
||||
"""Image generation for U-Boot
|
||||
|
||||
This bintool supports running `cst` with some basic parameters as
|
||||
needed by binman.
|
||||
"""
|
||||
def __init__(self, name):
|
||||
super().__init__(name, 'Sign NXP i.MX image')
|
||||
|
||||
# pylint: disable=R0913
|
||||
def run(self, output_fname=None):
|
||||
"""Run cst
|
||||
|
||||
Args:
|
||||
output_fname: Output filename to write to
|
||||
"""
|
||||
args = []
|
||||
if output_fname:
|
||||
args += ['-o', output_fname]
|
||||
return self.run_cmd(*args)
|
||||
|
||||
def fetch(self, method):
|
||||
"""Fetch handler for cst
|
||||
|
||||
This installs cst using the apt utility.
|
||||
|
||||
Args:
|
||||
method (FETCH_...): Method to use
|
||||
|
||||
Returns:
|
||||
True if the file was fetched and now installed, None if a method
|
||||
other than FETCH_BIN was requested
|
||||
|
||||
Raises:
|
||||
Valuerror: Fetching could not be completed
|
||||
"""
|
||||
if method != bintool.FETCH_BIN:
|
||||
return None
|
||||
return self.apt_install('imx-code-signing-tool')
|
164
tools/binman/etype/nxp_imx8mcst.py
Normal file
164
tools/binman/etype/nxp_imx8mcst.py
Normal file
@@ -0,0 +1,164 @@
|
||||
# SPDX-License-Identifier: GPL-2.0+
|
||||
# Copyright 2023-2024 Marek Vasut <marex@denx.de>
|
||||
# Written with much help from Simon Glass <sjg@chromium.org>
|
||||
#
|
||||
# Entry-type module for generating the i.MX8M code signing tool
|
||||
# input configuration file and invocation of cst on generated
|
||||
# input configuration file and input data to be signed.
|
||||
#
|
||||
|
||||
import configparser
|
||||
import os
|
||||
import struct
|
||||
|
||||
from collections import OrderedDict
|
||||
|
||||
from binman.entry import Entry
|
||||
from binman.etype.mkimage import Entry_mkimage
|
||||
from binman.etype.section import Entry_section
|
||||
from binman import elf
|
||||
from dtoc import fdt_util
|
||||
from u_boot_pylib import tools
|
||||
|
||||
MAGIC_NXP_IMX_IVT = 0x412000d1
|
||||
MAGIC_FITIMAGE = 0xedfe0dd0
|
||||
|
||||
csf_config_template = """
|
||||
[Header]
|
||||
Version = 4.3
|
||||
Hash Algorithm = sha256
|
||||
Engine = CAAM
|
||||
Engine Configuration = 0
|
||||
Certificate Format = X509
|
||||
Signature Format = CMS
|
||||
|
||||
[Install SRK]
|
||||
File = "SRK_1_2_3_4_table.bin"
|
||||
Source index = 0
|
||||
|
||||
[Install CSFK]
|
||||
File = "CSF1_1_sha256_4096_65537_v3_usr_crt.pem"
|
||||
|
||||
[Authenticate CSF]
|
||||
|
||||
[Unlock]
|
||||
Engine = CAAM
|
||||
Features = MID
|
||||
|
||||
[Install Key]
|
||||
Verification index = 0
|
||||
Target Index = 2
|
||||
File = "IMG1_1_sha256_4096_65537_v3_usr_crt.pem"
|
||||
|
||||
[Authenticate Data]
|
||||
Verification index = 2
|
||||
Blocks = 0x1234 0x78 0xabcd "data.bin"
|
||||
"""
|
||||
|
||||
class Entry_nxp_imx8mcst(Entry_mkimage):
|
||||
"""NXP i.MX8M CST .cfg file generator and cst invoker
|
||||
|
||||
Properties / Entry arguments:
|
||||
- nxp,loader-address - loader address (SPL text base)
|
||||
"""
|
||||
|
||||
def __init__(self, section, etype, node):
|
||||
super().__init__(section, etype, node)
|
||||
self.required_props = ['nxp,loader-address']
|
||||
|
||||
def ReadNode(self):
|
||||
super().ReadNode()
|
||||
self.loader_address = fdt_util.GetInt(self._node, 'nxp,loader-address')
|
||||
self.srk_table = os.getenv('SRK_TABLE', fdt_util.GetString(self._node, 'nxp,srk-table', 'SRK_1_2_3_4_table.bin'))
|
||||
self.csf_crt = os.getenv('CSF_KEY', fdt_util.GetString(self._node, 'nxp,csf-crt', 'CSF1_1_sha256_4096_65537_v3_usr_crt.pem'))
|
||||
self.img_crt = os.getenv('IMG_KEY', fdt_util.GetString(self._node, 'nxp,img-crt', 'IMG1_1_sha256_4096_65537_v3_usr_crt.pem'))
|
||||
self.unlock = fdt_util.GetBool(self._node, 'nxp,unlock')
|
||||
self.ReadEntries()
|
||||
|
||||
def BuildSectionData(self, required):
|
||||
data, input_fname, uniq = self.collect_contents_to_file(
|
||||
self._entries.values(), 'input')
|
||||
|
||||
# Parse the input data and figure out what it is that is being signed.
|
||||
# - If it is mkimage'd imx8mimage, then extract to be signed data size
|
||||
# from imx8mimage header, and calculate CSF blob offset right past
|
||||
# the SPL from this information.
|
||||
# - If it is fitImage, then pad the image to 4k, add generated IVT and
|
||||
# sign the whole payload, then append CSF blob at the end right past
|
||||
# the IVT.
|
||||
signtype = struct.unpack('<I', data[:4])[0]
|
||||
signbase = self.loader_address
|
||||
signsize = 0
|
||||
if signtype == MAGIC_NXP_IMX_IVT: # SPL/imx8mimage
|
||||
# Sign the payload including imx8mimage header
|
||||
# (extra 0x40 bytes before the payload)
|
||||
signbase -= 0x40
|
||||
signsize = struct.unpack('<I', data[24:28])[0] - signbase
|
||||
# Remove mkimage generated padding from the end of data
|
||||
data = data[:signsize]
|
||||
elif signtype == MAGIC_FITIMAGE: # fitImage
|
||||
# Align fitImage to 4k
|
||||
signsize = tools.align(len(data), 0x1000)
|
||||
data += tools.get_bytes(0, signsize - len(data))
|
||||
# Add generated IVT
|
||||
data += struct.pack('<I', MAGIC_NXP_IMX_IVT)
|
||||
data += struct.pack('<I', signbase + signsize) # IVT base
|
||||
data += struct.pack('<I', 0)
|
||||
data += struct.pack('<I', 0)
|
||||
data += struct.pack('<I', 0)
|
||||
data += struct.pack('<I', signbase + signsize) # IVT base
|
||||
data += struct.pack('<I', signbase + signsize + 0x20) # CSF base
|
||||
data += struct.pack('<I', 0)
|
||||
else:
|
||||
# Unknown section type, pass input data through.
|
||||
return data
|
||||
|
||||
# Write out customized data to be signed
|
||||
output_dname = tools.get_output_filename(f'nxp.cst-input-data.{uniq}')
|
||||
tools.write_file(output_dname, data)
|
||||
|
||||
# Generate CST configuration file used to sign payload
|
||||
cfg_fname = tools.get_output_filename('nxp.csf-config-txt.%s' % uniq)
|
||||
config = configparser.ConfigParser()
|
||||
# Do not make key names lowercase
|
||||
config.optionxform = str
|
||||
# Load configuration template and modify keys of interest
|
||||
config.read_string(csf_config_template)
|
||||
config['Install SRK']['File'] = '"' + self.srk_table + '"'
|
||||
config['Install CSFK']['File'] = '"' + self.csf_crt + '"'
|
||||
config['Install Key']['File'] = '"' + self.img_crt + '"'
|
||||
config['Authenticate Data']['Blocks'] = hex(signbase) + ' 0 ' + hex(len(data)) + ' "' + str(output_dname) + '"'
|
||||
if not self.unlock:
|
||||
config.remove_section('Unlock')
|
||||
with open(cfg_fname, 'w') as cfgf:
|
||||
config.write(cfgf)
|
||||
|
||||
output_fname = tools.get_output_filename(f'nxp.csf-output-blob.{uniq}')
|
||||
args = ['-i', cfg_fname, '-o', output_fname]
|
||||
if self.cst.run_cmd(*args) is not None:
|
||||
outdata = tools.read_file(output_fname)
|
||||
return data + outdata
|
||||
else:
|
||||
# Bintool is missing; just use the input data as the output
|
||||
self.record_missing_bintool(self.cst)
|
||||
return data
|
||||
|
||||
def SetImagePos(self, image_pos):
|
||||
# Customized SoC specific SetImagePos which skips the mkimage etype
|
||||
# implementation and removes the 0x48 offset introduced there. That
|
||||
# offset is only used for uImage/fitImage, which is not the case in
|
||||
# here.
|
||||
upto = 0x00
|
||||
for entry in super().GetEntries().values():
|
||||
entry.SetOffsetSize(upto, None)
|
||||
|
||||
# Give up if any entries lack a size
|
||||
if entry.size is None:
|
||||
return
|
||||
upto += entry.size
|
||||
|
||||
Entry_section.SetImagePos(self, image_pos)
|
||||
|
||||
def AddBintools(self, btools):
|
||||
super().AddBintools(btools)
|
||||
self.cst = self.AddBintool(btools, 'cst')
|
Reference in New Issue
Block a user