Direct ATF to Linux from QSPI boot flow not working
I have a board with an Agilex SoC device. Trying to debug the Altera TSE IP operation with the appropriate Linux drivers. However, the SD card reader is having hardware problems, so I would like to implement a full QSPI boot flow, following this tutorial. I've followed all the steps with a couple of small differences (additional packages in rootfs, different hardware design and kernel configs for TSE and other IP).
I also enabled debug build and added some additional logging statements to ATF, resulting in the following:
INFO: DDR: DRAM calibration success. INFO: Scrubbing ECC INFO: mailbox_poll_response_v3: SDM err code: 0x2ff INFO: QSPI ref clock: 400000000 NOTICE: QSPI boot INFO: Initializing Qspi INFO: QSPI Capacity: 10000000 INFO: Flash size: 268435456 Bytes INFO: mailbox_poll_response_v3: SDM err code: 0xc WARNING: ROS: Not booted in RSU mode NOTICE: BL2: v2.12.0(debug):QPDS25.1_REL_GSRD_PR-dirty NOTICE: BL2: Built : 10:43:27, May 6 2025 INFO: BL2: Doing platform setup INFO: BL2: Loading image id 3 INFO: Loading image id=3 at address 0x1000 INFO: Image id=3 loaded: 0x1000 - 0x1201d INFO: BL2: Loading image id 5 INFO: Loading image id=5 at address 0x2000000 INFO: Image id=5 loaded: 0x2000000 - 0x4c4b200 INFO: BL2: Loading image id 27 INFO: Loading image id=27 at address 0x10000000 INFO: Image id=27 loaded: 0x10000000 - 0x1000438a INFO: loop 1 count: 0, IMAGE ID: 3, Attr:0x18 INFO: loop 2 count: 0, IMAGE ID: 3, Attr:0x18 INFO: loop 2 count: 1, IMAGE ID: 5, Attr:0x9 INFO: loop 2 count: 2, IMAGE ID: 27, Attr:0x1 ASSERT: common/desc_image_load.c:175 BACKTRACE: START: assert 0: EL3: 0xffe00b2c 1: EL3: 0xffe07a50 2: EL3: 0xffe010b4 3: EL3: 0xffe00990 4: EL3: 0xffe00a68 5: EL3: 0xffe000fc BACKTRACE: END: assert ... restarts and repeats the same ...
the two relevant pieces of ATF source code are in arm-trusted-firmware/plat/intel/soc/common/bl2_plat_mem_params_desc.c:
#include <common/bl_common.h> #include <common/desc_image_load.h> #include <platform_def.h> #include <plat/common/platform.h> /******************************************************************************* * Following descriptor provides BL image/ep information that gets used * by BL2 to load the images and also subset of this information is * passed to next BL image. The image loading sequence is managed by * populating the images in required loading order. The image execution * sequence is managed by populating the `next_handoff_image_id` with * the next executable image id. ******************************************************************************/ static bl_mem_params_node_t bl2_mem_params_descs[] = { #ifdef SCP_BL2_BASE /* Fill SCP_BL2 related information if it exists */ { .image_id = SCP_BL2_IMAGE_ID, SET_STATIC_PARAM_HEAD(ep_info, PARAM_IMAGE_BINARY, VERSION_2, entry_point_info_t, SECURE | NON_EXECUTABLE), SET_STATIC_PARAM_HEAD(image_info, PARAM_IMAGE_BINARY, VERSION_2, image_info_t, 0), .image_info.image_base = SCP_BL2_BASE, .image_info.image_max_size = SCP_BL2_SIZE, .next_handoff_image_id = INVALID_IMAGE_ID, }, #endif /* SCP_BL2_BASE */ #ifdef EL3_PAYLOAD_BASE /* Fill EL3 payload related information (BL31 is EL3 payload)*/ { .image_id = BL31_IMAGE_ID, SET_STATIC_PARAM_HEAD(ep_info, PARAM_EP, VERSION_2, entry_point_info_t, SECURE | EXECUTABLE | EP_FIRST_EXE), .ep_info.pc = EL3_PAYLOAD_BASE, .ep_info.spsr = SPSR_64(MODE_EL3, MODE_SP_ELX, DISABLE_ALL_EXCEPTIONS), SET_STATIC_PARAM_HEAD(image_info, PARAM_EP, VERSION_2, image_info_t, IMAGE_ATTRIB_PLAT_SETUP | IMAGE_ATTRIB_SKIP_LOADING), .next_handoff_image_id = INVALID_IMAGE_ID, }, #else /* EL3_PAYLOAD_BASE */ /* Fill BL31 related information */ { .image_id = BL31_IMAGE_ID, SET_STATIC_PARAM_HEAD(ep_info, PARAM_EP, VERSION_2, entry_point_info_t, SECURE | EXECUTABLE | EP_FIRST_EXE), .ep_info.pc = BL31_BASE, .ep_info.spsr = SPSR_64(MODE_EL3, MODE_SP_ELX, DISABLE_ALL_EXCEPTIONS), SET_STATIC_PARAM_HEAD(image_info, PARAM_EP, VERSION_2, image_info_t, IMAGE_ATTRIB_PLAT_SETUP), .image_info.image_base = BL31_BASE, .image_info.image_max_size = BL31_LIMIT - BL31_BASE, .next_handoff_image_id = BL33_IMAGE_ID, }, #endif /* EL3_PAYLOAD_BASE */ { .image_id = BL33_IMAGE_ID, SET_STATIC_PARAM_HEAD(ep_info, PARAM_EP, VERSION_2, entry_point_info_t, NON_SECURE | EXECUTABLE), .ep_info.pc = PLAT_NS_IMAGE_OFFSET, SET_STATIC_PARAM_HEAD(image_info, PARAM_EP, VERSION_2, image_info_t, 0), .image_info.image_base = PLAT_NS_IMAGE_OFFSET, .image_info.image_max_size = 0x0 + 0x40000000 - PLAT_NS_IMAGE_OFFSET, # if ARM_LINUX_KERNEL_AS_BL33 != 0 .next_handoff_image_id = NT_FW_CONFIG_ID, }, { .image_id = NT_FW_CONFIG_ID, SET_STATIC_PARAM_HEAD(ep_info, PARAM_IMAGE_BINARY, VERSION_2, entry_point_info_t, NON_SECURE | NON_EXECUTABLE), SET_STATIC_PARAM_HEAD(image_info, PARAM_IMAGE_BINARY, VERSION_2, image_info_t, 0), .image_info.image_base = ARM_PRELOADED_DTB_BASE, .image_info.image_max_size = 0x0 + 0x40000000 - ARM_PRELOADED_DTB_BASE, .next_handoff_image_id = INVALID_IMAGE_ID, }, #else .next_handoff_image_id = INVALID_IMAGE_ID, }, # endif }; REGISTER_BL_IMAGE_DESCS(bl2_mem_params_descs)
where based on build options specified in the tutorial (ARM_LINUX_KERNEL_AS_BL33=1), the firmware image/entrypoint descriptor list has three items corresponding to bl31, the kernel image, and the kernel .dtb file. The `EXECUTABLE` attributes and `next_handoff_image_id` are as expected, corresponding to the FIP image file created from bl31, image, and .dtb files in the referenced tutorial.
The second relevant piece of ATF source code is in arm-trusted-firmware/common/desc_image_load.c:
/******************************************************************************* * This function creates the list of executable images, by populating and * linking each `bl_params_node_t` type node, using the internal array of * image descriptor provided by bl_mem_params_desc_ptr. It also populates * and returns `bl_params_t` type structure that contains head of the list * of executable images. ******************************************************************************/ bl_params_t *get_next_bl_params_from_mem_params_desc(void) { unsigned int count; unsigned int img_id = 0U; unsigned int link_index = 0U; bl_params_node_t *bl_current_exec_node = NULL; bl_params_node_t *bl_last_exec_node = NULL; bl_mem_params_node_t *desc_ptr; /* If there is no image to start with, return NULL */ if (bl_mem_params_desc_num == 0U) return NULL; /* Get the list HEAD */ for (count = 0U; count < bl_mem_params_desc_num; count++) { desc_ptr = &bl_mem_params_desc_ptr[count]; INFO("loop 1 count: %u, IMAGE ID: %u, Attr:0x%x", count, desc_ptr->image_id, desc_ptr->ep_info.h.attr); if ((EP_GET_EXE(desc_ptr->ep_info.h.attr) == EXECUTABLE) && (EP_GET_FIRST_EXE(desc_ptr->ep_info.h.attr) == EP_FIRST_EXE)) { next_bl_params.head = &desc_ptr->params_node_mem; link_index = count; break; } } /* Make sure we have a HEAD node */ assert(next_bl_params.head != NULL); /* Populate the HEAD information */ SET_PARAM_HEAD(&next_bl_params, PARAM_BL_PARAMS, VERSION_2, 0U); /* * Go through the image descriptor array and create the list. * This bounded loop is to make sure that we are not looping forever. */ for (count = 0U; count < bl_mem_params_desc_num; count++) { desc_ptr = &bl_mem_params_desc_ptr[link_index]; INFO("loop 2 count: %u, IMAGE ID: %u, Attr:0x%x\n", count, desc_ptr->image_id, desc_ptr->ep_info.h.attr); /* Make sure the image is executable */ assert(EP_GET_EXE(desc_ptr->ep_info.h.attr) == EXECUTABLE); /* Get the memory for current node */ bl_current_exec_node = &desc_ptr->params_node_mem; /* Populate the image information */ bl_current_exec_node->image_id = desc_ptr->image_id; bl_current_exec_node->image_info = &desc_ptr->image_info; bl_current_exec_node->ep_info = &desc_ptr->ep_info; if (bl_last_exec_node != NULL) { /* Assert if loop detected */ assert(bl_last_exec_node->next_params_info == NULL); /* Link the previous node to the current one */ bl_last_exec_node->next_params_info = bl_current_exec_node; } /* Update the last node */ bl_last_exec_node = bl_current_exec_node; /* If no next hand-off image then break out */ img_id = desc_ptr->next_handoff_image_id; if (img_id == INVALID_IMAGE_ID) break; /* Get the index for the next hand-off image */ link_index = get_bl_params_node_index(img_id); assert((link_index > 0U) && (link_index < bl_mem_params_desc_num)); } /* Invalid image is expected to terminate the loop */ assert(img_id == INVALID_IMAGE_ID); return &next_bl_params; }
The logs show bl2 failing at
assert(EP_GET_EXE(desc_ptr->ep_info.h.attr) == EXECUTABLE);
on the third trip through the loop, where `desc_ptr` points to the nt-fw-config image corresponding to the dtb partition in the FIP file. The second for-loop seems like it should break on the last loop trip where the next image in the list has
next_handoff_image_id == INVALID_IMAGE_ID
but the assertion fails before this loop can break because the nt-fw-config file is not executable.
Not really sure where to go from this point. I'm using the same tag for the arm-trusted-firmware repo as the tutorial suggests. There are changes between my configuration and the tutorial as I mentioned, mostly involving the hardware design, but the ATF build was exactly the same. It seems like ATF firmware isn't really capable of booting Linux given the configuration provided in the tutorial, unless there's some other build option I'm missing.