blob: 9b23cffa5fb27e85e1e6f831b41c8a9519fce419 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0 OR Apache-2.0
/*
* Copyright 2022 Qorvo US, Inc.
*
*/
#include <qmrom.h>
#include <qmrom_spi.h>
#include <qmrom_log.h>
#include <qmrom_utils.h>
#include <spi_rom_protocol.h>
#define DEFAULT_SPI_CLOCKRATE 3000000
#define CHIP_VERSION_CHIP_REV_PAYLOAD_OFFSET 1
#define CHIP_VERSION_DEV_REV_PAYLOAD_OFFSET 3
#define CHUNK_SIZE_B0 1008
enum B0_CMD {
ROM_CMD_B0_SEC_LOAD_ICV_IMG_TO_RRAM = 0x0,
ROM_CMD_B0_SEC_LOAD_OEM_IMG_TO_RRAM = 0x1,
ROM_CMD_B0_GET_CHIP_VER = 0x2,
ROM_CMD_B0_GET_SOC_INFO = 0x3,
ROM_CMD_B0_ERASE_DBG_CERT = 0x4,
ROM_CMD_B0_WRITE_DBG_CERT = 0x5,
ROM_CMD_B0_SEC_IMAGE_DATA = 0xf,
ROM_CMD_B0_CERT_DATA = 0x10,
ROM_CMD_B0_DEBUG_CERT_SIZE = 0x11,
};
enum B0_RESP {
READY_FOR_CS_LOW_CMD = 0x00,
WRONG_CS_LOW_CMD = 0x01,
WAITING_FOR_NS_RRAM_FILE_SIZE = 0x02,
WAITING_FOR_NS_SRAM_FILE_SIZE = 0x03,
WAITING_FOR_NS_RRAM_FILE_DATA = 0x04,
WAITING_FOR_NS_SRAM_FILE_DATA = 0x05,
WAITING_FOR_SEC_FILE_DATA = 0x06,
ERR_NS_SRAM_OR_RRAM_SIZE_CMD = 0x07,
ERR_SEC_RRAM_SIZE_CMD = 0x08,
ERR_WAITING_FOR_NS_IMAGE_DATA_CMD = 0x09,
ERR_WAITING_FOR_SEC_IMAGE_DATA_CMD = 0x0A,
ERR_IMAGE_SIZE_IS_ZERO = 0x0B,
/* Got more data than expected size */
ERR_IMAGE_SIZE_TOO_BIG = 0x0C,
/* Image must divide in 16 without remainder */
ERR_IMAGE_IS_NOT_16BYTES_MUL = 0x0D,
ERR_GOT_DATA_MORE_THAN_ALLOWED = 0x0E,
/* Remainder is allowed only for last packet */
ERR_RRAM_DATA_REMAINDER_NOT_ALLOWED = 0x0F,
ERR_WAITING_FOR_CERT_DATA_CMD = 0x10,
WAITING_FOR_FIRST_KEY_CERT = 0x11,
WAITING_FOR_SECOND_KEY_CERT = 0x12,
WAITING_FOR_CONTENT_CERT = 0x13,
WAITING_FOR_DEBUG_CERT_DATA = 0x14,
ERR_FIRST_KEY_CERT_OR_FW_VER = 0x15,
ERR_SECOND_KEY_CERT = 0x16,
ERR_CONTENT_CERT_DOWNLOAD_ADDR = 0x17,
/* If the content certificate contains to much images */
ERR_TOO_MANY_IMAGES_IN_CONTENT_CERT = 0x18,
ERR_ADDRESS_NOT_DIVIDED_BY_8 = 0x19,
ERR_IMAGE_BOUNDARIES = 0x1A,
/* Expected ICV type and got OEM */
ERR_CERT_TYPE = 0x1B,
ERR_PRODUCT_ID = 0x1C,
ERR_RRAM_RANGE_OR_WRITE = 0x1D,
WAITING_TO_DEBUG_CERTIFICATE_SIZE = 0x1E,
ERR_DEBUG_CERT_SIZE = 0x1F,
};
static int qmrom_b0_flash_fw(struct qmrom_handle *handle,
const struct firmware *fw);
static int qmrom_b0_flash_debug_cert(struct qmrom_handle *handle,
struct firmware *dbg_cert);
static int qmrom_b0_erase_debug_cert(struct qmrom_handle *handle);
static int
qmrom_b0_flash_unstitched_fw(struct qmrom_handle *handle,
const struct unstitched_firmware *all_fws);
static void qmrom_b0_poll_soc(struct qmrom_handle *handle)
{
int retries = handle->comms_retries;
memset(handle->hstc, 0, sizeof(struct stc));
qmrom_msleep(SPI_READY_TIMEOUT_MS);
do {
qmrom_spi_transfer(handle->spi_handle, (char *)handle->sstc,
(const char *)handle->hstc,
sizeof(struct stc) + handle->hstc->len);
} while (retries-- && handle->sstc->raw_flags == 0);
}
static int qmrom_b0_wait_ready(struct qmrom_handle *handle)
{
int retries = handle->comms_retries;
int rc;
qmrom_b0_poll_soc(handle);
/* handle->sstc has been updated */
while (retries-- &&
handle->sstc->raw_flags != SPI_SH_READY_CMD_BIT_MASK) {
if (handle->sstc->soc_flags.out_waiting) {
qmrom_pre_read(handle);
} else if (handle->sstc->soc_flags.out_active) {
rc = qmrom_read(handle);
if (rc)
return rc;
} else {
/* error? */
qmrom_b0_poll_soc(handle);
}
}
return handle->sstc->raw_flags == SPI_SH_READY_CMD_BIT_MASK ?
0 :
SPI_ERR_WAIT_READY_TIMEOUT;
}
static int qmrom_b0_poll_cmd_resp(struct qmrom_handle *handle)
{
int retries = handle->comms_retries;
qmrom_b0_poll_soc(handle);
do {
if (handle->sstc->soc_flags.out_waiting) {
qmrom_pre_read(handle);
if (handle->sstc->len > 0xff) {
/* likely the wrong endianness, A0? */
return -1;
}
qmrom_read(handle);
break;
} else
qmrom_b0_poll_soc(handle);
} while (retries--);
return retries > 0 ? 0 : -1;
}
int qmrom_b0_probe_device(struct qmrom_handle *handle)
{
int rc, i;
uint8_t *soc_lcs_uuid;
handle->is_be = false;
check_stcs(__func__, __LINE__, handle);
qmrom_spi_set_freq(DEFAULT_SPI_CLOCKRATE);
rc = qmrom_reboot_bootloader(handle);
if (rc) {
LOG_ERR("%s: cannot reset the device...\n", __func__);
return rc;
}
rc = qmrom_b0_wait_ready(handle);
if (rc) {
LOG_INFO("%s: maybe not a B0 device\n", __func__);
return rc;
}
rc = qmrom_write_cmd(handle, ROM_CMD_B0_GET_CHIP_VER);
if (rc)
return rc;
rc = qmrom_b0_poll_cmd_resp(handle);
if (rc)
return rc;
handle->chip_rev =
SSTC2UINT16(handle, CHIP_VERSION_CHIP_REV_PAYLOAD_OFFSET) &
0xFF;
handle->device_version = bswap_16(
SSTC2UINT16(handle, CHIP_VERSION_DEV_REV_PAYLOAD_OFFSET));
if (handle->chip_rev != CHIP_REVISION_B0) {
LOG_ERR("%s: wrong chip revision 0x%x\n", __func__,
handle->chip_rev);
handle->chip_rev = -1;
return -1;
}
rc = qmrom_b0_wait_ready(handle);
if (rc) {
LOG_ERR("%s: hmm something went wrong!!!\n", __func__);
return rc;
}
rc = qmrom_write_cmd(handle, ROM_CMD_B0_GET_SOC_INFO);
if (rc)
return rc;
rc = qmrom_b0_poll_cmd_resp(handle);
if (rc)
return rc;
/* skip the first byte */
soc_lcs_uuid = &(handle->sstc->payload[1]);
for (i = 0; i < ROM_SOC_ID_LEN; i++)
handle->soc_id[i] = soc_lcs_uuid[ROM_SOC_ID_LEN - i - 1];
soc_lcs_uuid += ROM_SOC_ID_LEN;
handle->lcs_state = soc_lcs_uuid[0];
soc_lcs_uuid += 1;
for (i = 0; i < ROM_UUID_LEN; i++)
handle->uuid[i] = soc_lcs_uuid[ROM_UUID_LEN - i - 1];
/* Set rom ops */
handle->rom_ops.flash_fw = qmrom_b0_flash_fw;
handle->rom_ops.flash_unstitched_fw = qmrom_b0_flash_unstitched_fw;
handle->rom_ops.flash_debug_cert = qmrom_b0_flash_debug_cert;
handle->rom_ops.erase_debug_cert = qmrom_b0_erase_debug_cert;
check_stcs(__func__, __LINE__, handle);
return 0;
}
static int qmrom_b0_flash_data(struct qmrom_handle *handle, struct firmware *fw,
uint8_t cmd, uint8_t exp)
{
int rc, sent = 0;
const char *bin_data = (const char *)fw->data;
check_stcs(__func__, __LINE__, handle);
while (sent < fw->size) {
uint32_t tx_bytes = fw->size - sent;
if (tx_bytes > CHUNK_SIZE_B0)
tx_bytes = CHUNK_SIZE_B0;
LOG_DBG("%s: poll soc...\n", __func__);
check_stcs(__func__, __LINE__, handle);
qmrom_b0_poll_soc(handle);
qmrom_pre_read(handle);
qmrom_read(handle);
if (handle->sstc->payload[0] != exp) {
LOG_ERR("%s: wrong data expected (%#x vs %#x)!!!\n",
__func__, handle->sstc->payload[0] & 0xff, exp);
if (handle->sstc->payload[0] ==
ERR_FIRST_KEY_CERT_OR_FW_VER)
return PEG_ERR_FIRST_KEY_CERT_OR_FW_VER;
else
return SPI_PROTO_WRONG_RESP;
}
LOG_DBG("%s: sending %d command with %" PRIu32 " bytes\n",
__func__, cmd, tx_bytes);
rc = qmrom_write_size_cmd(handle, cmd, tx_bytes, bin_data);
if (rc)
return rc;
sent += tx_bytes;
bin_data += tx_bytes;
check_stcs(__func__, __LINE__, handle);
}
return 0;
}
static int qmrom_b0_flash_fw(struct qmrom_handle *handle,
const struct firmware *fw)
{
int rc = 0;
struct unstitched_firmware all_fws = { 0 };
rc = qmrom_unstitch_fw(fw, &all_fws, handle->chip_rev);
if (rc) {
LOG_ERR("%s: Unstitched fw flashing not supported yet\n",
__func__);
return rc;
}
rc = qmrom_b0_flash_unstitched_fw(handle, &all_fws);
return rc;
}
static int
qmrom_b0_flash_unstitched_fw(struct qmrom_handle *handle,
const struct unstitched_firmware *all_fws)
{
int rc = 0;
uint8_t flash_cmd = handle->lcs_state == CC_BSV_SECURE_LCS ?
ROM_CMD_B0_SEC_LOAD_OEM_IMG_TO_RRAM :
ROM_CMD_B0_SEC_LOAD_ICV_IMG_TO_RRAM;
if (all_fws->key1_crt->data[HBK_LOC] == HBK_2E_ICV &&
handle->lcs_state != CC_BSV_CHIP_MANUFACTURE_LCS) {
LOG_ERR("%s: Trying to flash an ICV fw on a non ICV platform\n",
__func__);
rc = -EINVAL;
goto end;
}
if (all_fws->key1_crt->data[HBK_LOC] == HBK_2E_OEM &&
handle->lcs_state != CC_BSV_SECURE_LCS) {
LOG_ERR("%s: Trying to flash an OEM fw on a non OEM platform\n",
__func__);
rc = -EINVAL;
goto end;
}
LOG_DBG("%s: starting...\n", __func__);
check_stcs(__func__, __LINE__, handle);
rc = qmrom_b0_wait_ready(handle);
if (rc)
goto end;
check_stcs(__func__, __LINE__, handle);
LOG_DBG("%s: sending flash_cmd %u command\n", __func__, flash_cmd);
rc = qmrom_write_cmd(handle, flash_cmd);
if (rc)
goto end;
check_stcs(__func__, __LINE__, handle);
rc = qmrom_b0_flash_data(handle, all_fws->key1_crt,
ROM_CMD_B0_CERT_DATA,
WAITING_FOR_FIRST_KEY_CERT);
if (rc)
goto end;
check_stcs(__func__, __LINE__, handle);
rc = qmrom_b0_flash_data(handle, all_fws->key2_crt,
ROM_CMD_B0_CERT_DATA,
WAITING_FOR_SECOND_KEY_CERT);
if (rc)
goto end;
check_stcs(__func__, __LINE__, handle);
rc = qmrom_b0_flash_data(handle, all_fws->fw_crt, ROM_CMD_B0_CERT_DATA,
WAITING_FOR_CONTENT_CERT);
if (rc)
goto end;
check_stcs(__func__, __LINE__, handle);
rc = qmrom_b0_flash_data(handle, all_fws->fw_img,
ROM_CMD_B0_SEC_IMAGE_DATA,
WAITING_FOR_SEC_FILE_DATA);
if (!rc)
qmrom_msleep(SPI_READY_TIMEOUT_MS);
end:
check_stcs(__func__, __LINE__, handle);
qmrom_free(all_fws->fw_img);
qmrom_free(all_fws->fw_crt);
qmrom_free(all_fws->key1_crt);
qmrom_free(all_fws->key2_crt);
return rc;
}
static int qmrom_b0_flash_debug_cert(struct qmrom_handle *handle,
struct firmware *dbg_cert)
{
int rc;
LOG_DBG("%s: starting...\n", __func__);
check_stcs(__func__, __LINE__, handle);
rc = qmrom_b0_wait_ready(handle);
if (rc)
return rc;
check_stcs(__func__, __LINE__, handle);
LOG_DBG("%s: sending ROM_CMD_B0_WRITE_DBG_CERT command\n", __func__);
rc = qmrom_write_cmd(handle, ROM_CMD_B0_WRITE_DBG_CERT);
if (rc)
return rc;
check_stcs(__func__, __LINE__, handle);
LOG_DBG("%s: poll soc...\n", __func__);
qmrom_b0_poll_soc(handle);
qmrom_pre_read(handle);
qmrom_read(handle);
check_stcs(__func__, __LINE__, handle);
LOG_DBG("%s: sending ROM_CMD_B0_DEBUG_CERT_SIZE command\n", __func__);
rc = qmrom_write_size_cmd(handle, ROM_CMD_B0_DEBUG_CERT_SIZE,
sizeof(uint32_t),
(const char *)&dbg_cert->size);
if (handle->sstc->payload[0] != WAITING_TO_DEBUG_CERTIFICATE_SIZE) {
LOG_ERR("%s: wrong debug cert size result (0x%x vs 0x%x)!!!\n",
__func__, handle->sstc->payload[0] & 0xff,
WAITING_TO_DEBUG_CERTIFICATE_SIZE);
return SPI_PROTO_WRONG_RESP;
}
if (rc)
return rc;
rc = qmrom_b0_flash_data(handle, dbg_cert, ROM_CMD_B0_CERT_DATA,
WAITING_FOR_DEBUG_CERT_DATA);
check_stcs(__func__, __LINE__, handle);
return 0;
}
static int qmrom_b0_erase_debug_cert(struct qmrom_handle *handle)
{
int rc;
LOG_INFO("%s: starting...\n", __func__);
check_stcs(__func__, __LINE__, handle);
rc = qmrom_b0_wait_ready(handle);
if (!rc)
return rc;
LOG_DBG("%s: sending ROM_CMD_B0_ERASE_DBG_CERT command\n", __func__);
rc = qmrom_write_cmd(handle, ROM_CMD_B0_ERASE_DBG_CERT);
if (rc)
return rc;
qmrom_msleep(SPI_READY_TIMEOUT_MS);
check_stcs(__func__, __LINE__, handle);
return 0;
}