From 8d6f8d748c448ff8703a6a5ba6726ce637a5081c Mon Sep 17 00:00:00 2001 From: Nick Quarton <139178705+nquarton@users.noreply.github.com> Date: Tue, 10 Sep 2024 09:43:14 -0700 Subject: [PATCH] Libcaliptra piecewise FW load (#1651) * Libcaliptra: Adding function headers for piecewise FW LOAD * Libcaliptra: Adding piecewise FW load for loading FW chunks at a time --- libcaliptra/examples/generic/test.c | 99 +++++++++++++ libcaliptra/inc/caliptra_api.h | 25 +++- libcaliptra/inc/caliptra_enums.h | 13 +- libcaliptra/src/caliptra_api.c | 222 +++++++++++++++++++++------- 4 files changed, 299 insertions(+), 60 deletions(-) diff --git a/libcaliptra/examples/generic/test.c b/libcaliptra/examples/generic/test.c index e8ada9c0af..7f7daeeffa 100644 --- a/libcaliptra/examples/generic/test.c +++ b/libcaliptra/examples/generic/test.c @@ -668,6 +668,104 @@ int rom_test_devid_csr(const test_info* info) return failure; } +// Issue FW load commands repeatedly +// Coverage for piecewise FW load and runtime FW updates +int upload_fw_piecewise(const test_info* info) +{ + int failure = 0; + int status = boot_to_ready_for_fw(info, false); + + if (status){ + printf("Failed to boot to ready for FW: 0x%x\n", status); + failure = 1; + } + + // Some "random" size to split up the FW load into chunks + // These represent the first two chunks, the third chunk is the remainder of the image + // Sizes of 0 are ignored (meaning one fewer chunk is sent) + uint32_t chunk_sizes[][2] = { + {0x4, 0}, + {0x1000, 0}, + {0x1234, 0}, + {0xe924, 0}, + {0x8, 0x2000}, + {0x2340, 0x4}, + {0x388, 0x1844}, + }; + + // Load FW in a loop, using the offsets above as points to split chunks + for (int i = 0; i < CALIPTRA_ARRAY_SIZE(chunk_sizes); i++) { + // Start the FW load + uint32_t total_fw_size = info->image_bundle.len; + status = caliptra_upload_fw_start_req(total_fw_size); + + if (status) + { + printf("FW Load %d Start Failed: 0x%x\n", i, status); + dump_caliptra_error_codes(); + failure = 1; + } else { + printf("FW Load %d Start: OK\n", i); + } + + // Ensure other commands report busy during this process + struct caliptra_fips_version_resp version_resp; + status = caliptra_fips_version(&version_resp, false); + if (status != MBX_BUSY) { + printf("Command during piecewise FW load should report MBX_BUSY. Result was: 0x%x\n", status); + failure = 1; + } + + uint32_t sent_bytes = 0; + uint8_t chunk_count = 0; + // Upload each of up to 3 chunks + // The size of the first two chunks comes from the table above + // The final chunk is the remainder + // Some chunks may be skipped if their size is 0 in the table + for (int j = 0; j < 3; j++){ + uint32_t chunk_size; + if (j == 2) { + // Final chunk + chunk_size = total_fw_size - sent_bytes; + } else { + chunk_size = chunk_sizes[i][j]; + } + + if (chunk_size != 0){ + // Set up the caliptra_buffer for the chunk and send it + struct caliptra_buffer fw_chunk = {.data = info->image_bundle.data + sent_bytes, .len = chunk_size}; + status = caliptra_upload_fw_send_data(&fw_chunk); + + if (status) + { + printf("FW Load %d Send Data chunk %d (%d bytes) Failed: 0x%x\n", i, chunk_count, status, chunk_size); + dump_caliptra_error_codes(); + failure = 1; + } else { + printf("FW Load %d Send Data chunk %d (%d bytes): OK\n", i, chunk_count, chunk_size); + } + + // Track what has been sent + sent_bytes += chunk_size; + chunk_count++; + } + } + + // Finish the FW load + status = caliptra_upload_fw_end_req(false); + + if (status) + { + printf("FW Load %d End Failed: 0x%x\n", i, status); + dump_caliptra_error_codes(); + failure = 1; + } else { + printf("FW Load %d End: OK\n", i); + } + } + + return failure; +} // Test infrastructure @@ -699,6 +797,7 @@ int run_tests(const test_info* info) run_test(rom_test_all_commands, info, "Test all ROM commands"); run_test(rt_test_all_commands, info, "Test all Runtime commmands"); run_test(rom_test_devid_csr, info, "Test IDEV CSR GEN"); + run_test(upload_fw_piecewise, info, "Test Piecewise FW Load"); if (global_test_result) { printf("\t\tlibcaliptra test failures reported\n"); diff --git a/libcaliptra/inc/caliptra_api.h b/libcaliptra/inc/caliptra_api.h index 630de2a8b2..a5fbeefd68 100644 --- a/libcaliptra/inc/caliptra_api.h +++ b/libcaliptra/inc/caliptra_api.h @@ -99,8 +99,27 @@ int caliptra_mailbox_execute(uint32_t cmd, const struct caliptra_buffer *mbox_tx // For full command details, please refer to the Caliptra Runtime Readme file at runtime\README.md // Upload Caliptra Firmware +// Requires entire FW as fw_buffer +// For loading chunks of data at a time, use start/send/end functions below int caliptra_upload_fw(const struct caliptra_buffer *fw_buffer, bool async); +// If the SoC cannot buffer the entire FW, the following 3 functions can be used to write chunks at a time +// Upload Caliptra Firmware Start Request +// Begin a FW_LOAD command to caliptra. Total FW size is needed at the start per mailbox protocol +int caliptra_upload_fw_start_req(uint32_t fw_size_in_bytes); + +// Upload Caliptra Firmware Send Data +// Load a chunk of the FW data to Caliptra +// Intended to be called multiple times +// MUST follow caliptra_upload_fw_start_req and precede caliptra_upload_fw_end_request +// Size MUST be dword aligned for any chunks except the final chunk +int caliptra_upload_fw_send_data(const struct caliptra_buffer *fw_buffer); + +// Upload Caliptra Firmware End Request +// End the FW_LOAD request after sending all the FW data +// Waits for Caliptra completion and response if async is false +int caliptra_upload_fw_end_req(bool async); + // Get IDEV cert int caliptra_get_idev_cert(struct caliptra_get_idev_cert_req *req, struct caliptra_get_idev_cert_resp *resp, bool async); @@ -176,12 +195,12 @@ int caliptra_capabilities(struct caliptra_capabilities_resp *resp, bool async); // Query if IDevID CSR is ready. bool caliptra_is_idevid_csr_ready(); -int caliptra_retrieve_idevid_csr(struct caliptra_buffer* caliptra_idevid_csr); +int caliptra_retrieve_idevid_csr(struct caliptra_buffer* caliptra_idevid_csr); void caliptra_req_idev_csr_start(); -// Clear IDEV CSR request. -void caliptra_req_idev_csr_complete(); +// Clear IDEV CSR request. +void caliptra_req_idev_csr_complete(); #ifdef __cplusplus } diff --git a/libcaliptra/inc/caliptra_enums.h b/libcaliptra/inc/caliptra_enums.h index e60bf8535e..7351234cdc 100644 --- a/libcaliptra/inc/caliptra_enums.h +++ b/libcaliptra/inc/caliptra_enums.h @@ -11,11 +11,12 @@ */ enum libcaliptra_error { NO_ERROR = 0, - // General + // General API INVALID_PARAMS = 0x100, API_INTERNAL_ERROR = 0x101, REG_ACCESS_ERROR = 0x102, PAUSER_LOCKED = 0x103, + FW_LOAD_NOT_IN_PROGRESS = 0x104, // Fuse NOT_READY_FOR_FUSES = 0x200, STILL_READY_FOR_FUSES = 0x201, @@ -34,6 +35,16 @@ enum libcaliptra_error { IDEV_CSR_NOT_READY = 0x400, }; +/** + * fw_load_piecewise_state + * + * Tracks state for piecewise FW loading to enforce correct flow + */ +enum fw_load_piecewise_state { + FW_LOAD_PIECEWISE_IDLE = 0, + FW_LOAD_PIECEWISE_IN_PROGRESS = 1, +}; + /** * device_lifecycle * diff --git a/libcaliptra/src/caliptra_api.c b/libcaliptra/src/caliptra_api.c index 3cd539df8c..5bac11a39e 100644 --- a/libcaliptra/src/caliptra_api.c +++ b/libcaliptra/src/caliptra_api.c @@ -25,7 +25,8 @@ // All globals should use CALIPTRA_API_GLOBAL_SECTION_ATTRIBUTE // Globals should be uninitialized to maximize environment compatibility -static struct caliptra_buffer g_mbox_pending_rx_buffer CALIPTRA_API_GLOBAL_SECTION_ATTRIBUTE; +static struct caliptra_buffer g_caliptra_mbox_pending_rx_buffer CALIPTRA_API_GLOBAL_SECTION_ATTRIBUTE; +static uint8_t g_caliptra_fw_load_piecewise_in_progress CALIPTRA_API_GLOBAL_SECTION_ATTRIBUTE; #define CREATE_PARCEL(name, op, req, resp) \ struct parcel name = { \ @@ -391,19 +392,11 @@ static int caliptra_mailbox_write_fifo(const struct caliptra_buffer *buffer) return INVALID_PARAMS; } - if (buffer->len > CALIPTRA_MAILBOX_MAX_SIZE) - { - return INVALID_PARAMS; - } - - // Write DLEN to transition to the next state. - caliptra_mbox_write_dlen(buffer->len); + // TODO: Should we enforce we don't exceed the previously written mbox_write_dlen value? if (buffer->len == 0) { // We can return early, there is no payload. - // dlen needs to be written to transition the state machine, - // even if it is zero. return 0; } @@ -485,34 +478,6 @@ static int caliptra_mailbox_read_fifo(struct caliptra_buffer *buffer, uint32_t * return 0; } -/** - * caliptra_mailbox_send - * - * HELPER - Send the message to caliptra - * - * @param[in] cmd Caliptra command opcode - * @param[in] mbox_tx_buffer Transmit buffer - * - * @return 0 for success, non-zero for failure (see enum libcaliptra_error) - */ -int caliptra_mailbox_send(uint32_t cmd, const struct caliptra_buffer *mbox_tx_buffer) -{ - // If mbox already locked return - if (caliptra_mbox_is_lock()) - { - return MBX_BUSY; - } - - // Write Cmd and Tx Buffer - caliptra_mbox_write_cmd(cmd); - caliptra_mailbox_write_fifo(mbox_tx_buffer); - - // Set Execute bit - caliptra_mbox_write_execute(true); - - return 0; -}; - /** * caliptra_check_status_get_response * @@ -596,34 +561,80 @@ static inline int check_command_response(const uint8_t *buffer, const size_t res } /** - * caliptra_mailbox_execute + * caliptra_mailbox_send_start * - * Send the command with caliptra_mailbox_send. If async is false, wait for completion and call caliptra_complete to get result + * HELPER - Send the message to caliptra * - * @param[in] cmd 32 bit command identifier to be sent to caliptra - * @param[in] mbox_tx_buffer caliptra_buffer struct containing the pointer and length of the send buffer - * @param[out] mbox_rx_buffer caliptra_buffer struct containing the pointer and length of the receive buffer - * @param[in] async If true, return after sending command. If false, wait for command to complete and handle response + * @param[in] cmd Caliptra command opcode + * @param[in] data_size Number of bytes to be sent in the request (does not include command) * * @return 0 for success, non-zero for failure (see enum libcaliptra_error) */ -int caliptra_mailbox_execute(uint32_t cmd, const struct caliptra_buffer *mbox_tx_buffer, struct caliptra_buffer *mbox_rx_buffer, bool async) +int caliptra_mailbox_send_start(uint32_t cmd, uint32_t data_size) { - int status = caliptra_mailbox_send(cmd, mbox_tx_buffer); - if (status) { - return status; + if (data_size > CALIPTRA_MAILBOX_MAX_SIZE) + { + return INVALID_PARAMS; + } + + // Get mailbox lock, return error if already locked + if (caliptra_mbox_is_lock()) + { + return MBX_BUSY; } + // Write Cmd + caliptra_mbox_write_cmd(cmd); + + // Write DLEN to transition to the next state (needed even if it is zero) + caliptra_mbox_write_dlen(data_size); + + return 0; +}; + +/** + * caliptra_mailbox_send_data + * + * HELPER - Send the data portion of the message to caliptra + * Can be called multiple times + * + * @param[in] mbox_tx_buffer Transmit buffer + * + * @return 0 for success, non-zero for failure (see enum libcaliptra_error) + */ +int caliptra_mailbox_send_data(const struct caliptra_buffer *mbox_tx_buffer) +{ + // Write Tx Buffer + return caliptra_mailbox_write_fifo(mbox_tx_buffer); +}; + +/** + * caliptra_mailbox_send_complete + * + * HELPER - Set execute to indicate Calipta should now process the message + * Set the rx_buffer for the pending message if applicable + * Wait for the result if async is true + * + * @param[out] mbox_rx_buffer caliptra_buffer struct containing the pointer and length of the receive buffer + * @param[in] async If true, return after sending command. If false, wait for command to complete and handle response + * + * @return 0 for success, non-zero for failure (see enum libcaliptra_error) + */ +int caliptra_mailbox_send_complete(struct caliptra_buffer *mbox_rx_buffer, bool async) +{ // Store buffer info or init to zero if (mbox_rx_buffer != NULL) { - g_mbox_pending_rx_buffer = *mbox_rx_buffer; + g_caliptra_mbox_pending_rx_buffer = *mbox_rx_buffer; } else { - g_mbox_pending_rx_buffer = (struct caliptra_buffer){NULL, 0}; + g_caliptra_mbox_pending_rx_buffer = (struct caliptra_buffer){NULL, 0}; } + // Set Execute bit + caliptra_mbox_write_execute(true); + // Stop here if this is async (user will poll and complete) if (async) { - return status; + return 0; } // Wait indefinitely for completion @@ -632,12 +643,41 @@ int caliptra_mailbox_execute(uint32_t cmd, const struct caliptra_buffer *mbox_tx } return caliptra_complete(); +}; + +/** + * caliptra_mailbox_execute + * Send the command. If async is false, wait for completion and call caliptra_complete to get result + * + * @param[in] cmd 32 bit command identifier to be sent to caliptra + * @param[in] mbox_tx_buffer caliptra_buffer struct containing the pointer and length of the send buffer + * @param[out] mbox_rx_buffer caliptra_buffer struct containing the pointer and length of the receive buffer + * @param[in] async If true, return after sending command. If false, wait for command to complete and handle response + * + * @return 0 for success, non-zero for failure (see enum libcaliptra_error) + */ +int caliptra_mailbox_execute(uint32_t cmd, const struct caliptra_buffer *mbox_tx_buffer, struct caliptra_buffer *mbox_rx_buffer, bool async) +{ + // Mailbox send start + int status = caliptra_mailbox_send_start(cmd, mbox_tx_buffer->len); + if (status) { + return status; + } + + // Mailbox send data + status = caliptra_mailbox_send_data(mbox_tx_buffer); + if (status) { + return status; + } + + // Mailbox send complete + return caliptra_mailbox_send_complete(mbox_rx_buffer, async); } /** * pack_and_execute_command * - * HELPER - Create the caliptra buffer structs and call caliptra_mailbox_execute + * HELPER - Create the caliptra buffer structs and call caliptra_mailbox_send * * @param[in] parcel struct with tx and rx buffers for the transactions * @param[in] async If true, return after sending command. If false, wait for command to complete and handle response @@ -712,8 +752,8 @@ int caliptra_complete() // Store the buffer locally and clear the global var // The global should never be set when we don't have the mbx HW lock // (HW lock protects this from race conditions) - struct caliptra_buffer rx_buffer = g_mbox_pending_rx_buffer; - g_mbox_pending_rx_buffer = (struct caliptra_buffer){NULL, 0}; + struct caliptra_buffer rx_buffer = g_caliptra_mbox_pending_rx_buffer; + g_caliptra_mbox_pending_rx_buffer = (struct caliptra_buffer){NULL, 0}; // Complete the transaction and read back a response if applicable uint32_t bytes_read = 0; @@ -732,14 +772,84 @@ int caliptra_complete() return 0; } +/** + * caliptra_upload_fw_start_req + * + * Upload Caliptra Firmware Start Request. Begin a FW_LOAD command to caliptra + * + * @param[in] fw_size_in_bytes Total size of the FW to be sent in bytes + * + * @return 0 for success, non-zero for failure (see enum libcaliptra_error) + */ +int caliptra_upload_fw_start_req(uint32_t fw_size_in_bytes) +{ + // Mailbox send start + int status = caliptra_mailbox_send_start(OP_CALIPTRA_FW_LOAD, fw_size_in_bytes); + if (status) { + return status; + } + + // Cannot assume initialization value of globals + // If HW lock was open, we can be sure nothing was pending + // Otherwise, caliptra_mailbox_send_start will fail and we won't execute this + g_caliptra_fw_load_piecewise_in_progress = 0x1; + + return status; +} + +/** + * caliptra_upload_fw_send_data + * + * Load a chunk of the FW data to Caliptra. Intended to be called multiple times + * Must follow caliptra_upload_fw_start_req and precede caliptra_upload_fw_end_req + * + * @param[in] fw_buffer Buffer containing Caliptra firmware + * + * @return 0 for success, non-zero for failure (see enum libcaliptra_error) + */ +int caliptra_upload_fw_send_data(const struct caliptra_buffer *fw_buffer) +{ + // Make sure we are in the middle of a FW load + if (g_caliptra_fw_load_piecewise_in_progress != FW_LOAD_PIECEWISE_IN_PROGRESS) { + return FW_LOAD_NOT_IN_PROGRESS; + } + + // Mailbox send data + return caliptra_mailbox_send_data(fw_buffer); +} +/** + * caliptra_upload_fw_end_req + * + * End the FW_LOAD request after sending all the FW data + * + * @param[in] async If true, return after sending command. If false, wait for command to complete and handle response + * + * @return 0 for success, non-zero for failure (see enum libcaliptra_error) + */ +int caliptra_upload_fw_end_req(bool async) +{ + // Make sure we are in the middle of a FW load + if (g_caliptra_fw_load_piecewise_in_progress != FW_LOAD_PIECEWISE_IN_PROGRESS) { + return FW_LOAD_NOT_IN_PROGRESS; + } + + // Mailbox send complete + int status = caliptra_mailbox_send_complete(NULL, async); + + g_caliptra_fw_load_piecewise_in_progress = FW_LOAD_PIECEWISE_IDLE; + + return status; +} + /** * caliptra_upload_fw * - * Upload firmware to the Caliptra device + * Upload firmware to the Caliptra device. Requires entire FW as fw_buffer * * @param[in] fw_buffer Buffer containing Caliptra firmware + * @param[in] async If true, return after sending command. If false, wait for command to complete and handle response * - * @return See caliptra_mailbox, mb_resultx_execute for possible results. + * @return 0 for success, non-zero for failure (see enum libcaliptra_error) */ int caliptra_upload_fw(const struct caliptra_buffer *fw_buffer, bool async) {