blob: 76a04f3f2762bc37ba294bcd5c6cd27b873524e4 [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 5000000
#define CHIP_VERSION_CHIP_REV_PAYLOAD_OFFSET 4
#define CHIP_VERSION_DEV_REV_PAYLOAD_OFFSET 6
#define CHUNK_SIZE_C0 2040
#define SPI_READY_TIMEOUT_MS_C0 200
#ifdef C0_WRITE_STATS
#include <linux/ktime.h>
#endif
#define SPI_SH_READY_CMD_BIT_MASK_C0 \
(SPI_SH_READY_CMD_BIT_MASK >> 4 | SPI_SH_READY_CMD_BIT_MASK)
enum C0_CMD {
ROM_CMD_C0_SEC_LOAD_ICV_IMG_TO_RRAM = 0x0,
ROM_CMD_C0_SEC_LOAD_OEM_IMG_TO_RRAM = 0x1,
ROM_CMD_C0_GET_CHIP_VER = 0x2,
ROM_CMD_C0_GET_SOC_INFO = 0x3,
ROM_CMD_C0_ERASE_DBG_CERT = 0x4,
ROM_CMD_C0_USE_DIRECT_RRAM_WR = 0X5,
ROM_CMD_C0_USE_INDIRECT_RRAM_WR = 0x6,
ROM_CMD_C0_WRITE_DBG_CERT = 0x7,
ROM_CMD_C0_SEC_IMAGE_DATA = 0x12,
ROM_CMD_C0_CERT_DATA = 0x13,
ROM_CMD_C0_DEBUG_CERT_SIZE = 0x14,
};
enum C0_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_c0_flash_fw(struct qmrom_handle *handle,
const struct firmware *fw);
static int qmrom_c0_flash_debug_cert(struct qmrom_handle *handle,
struct firmware *dbg_cert);
static int qmrom_c0_erase_debug_cert(struct qmrom_handle *handle);
#define qmrom_pre_read_c0(h) \
({ \
int rc; \
qmrom_spi_wait_for_ready_line((h)->ss_rdy_handle, \
SPI_READY_TIMEOUT_MS_C0); \
rc = qmrom_pre_read((h)); \
rc; \
})
#define qmrom_read_c0(h) \
({ \
int rc; \
qmrom_spi_wait_for_ready_line((h)->ss_rdy_handle, \
SPI_READY_TIMEOUT_MS_C0); \
rc = qmrom_read((h)); \
rc; \
})
#define qmrom_write_cmd_c0(h, cmd) \
({ \
int rc; \
qmrom_spi_wait_for_ready_line((h)->ss_rdy_handle, \
SPI_READY_TIMEOUT_MS_C0); \
rc = qmrom_write_cmd((h), (cmd)); \
rc; \
})
#define qmrom_write_cmd32_c0(h, cmd) \
({ \
int rc; \
qmrom_spi_wait_for_ready_line((h)->ss_rdy_handle, \
SPI_READY_TIMEOUT_MS_C0); \
rc = qmrom_write_cmd32((h), (cmd)); \
rc; \
})
#define qmrom_write_size_cmd_c0(h, cmd, ds, d) \
({ \
int rc; \
qmrom_spi_wait_for_ready_line((h)->ss_rdy_handle, \
SPI_READY_TIMEOUT_MS_C0); \
rc = qmrom_write_size_cmd((h), (cmd), (ds), (d)); \
rc; \
})
#define qmrom_write_size_cmd32_c0(h, cmd, ds, d) \
({ \
int rc; \
qmrom_spi_wait_for_ready_line((h)->ss_rdy_handle, \
SPI_READY_TIMEOUT_MS_C0); \
rc = qmrom_write_size_cmd32((h), (cmd), (ds), (d)); \
rc; \
})
static void qmrom_c0_poll_soc(struct qmrom_handle *handle)
{
int retries = handle->comms_retries;
memset(handle->hstc, 0, sizeof(struct stc));
handle->sstc->raw_flags = 0;
do {
int rc = qmrom_spi_wait_for_ready_line(handle->ss_rdy_handle,
SPI_READY_TIMEOUT_MS_C0);
if (rc) {
LOG_ERR("%s qmrom_spi_wait_for_ready_line failed\n",
__func__);
continue;
}
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_c0_wait_ready(struct qmrom_handle *handle)
{
int retries = handle->comms_retries;
qmrom_c0_poll_soc(handle);
/* handle->sstc has been updated */
while (retries-- &&
handle->sstc->raw_flags != SPI_SH_READY_CMD_BIT_MASK_C0) {
if (handle->sstc->soc_flags.out_waiting) {
qmrom_pre_read_c0(handle);
qmrom_read_c0(handle);
} else if (handle->sstc->soc_flags.out_active) {
return qmrom_read_c0(handle);
} else
qmrom_c0_poll_soc(handle);
}
return handle->sstc->raw_flags == SPI_SH_READY_CMD_BIT_MASK_C0 ?
0 :
SPI_ERR_WAIT_READY_TIMEOUT;
}
static int qmrom_c0_poll_cmd_resp(struct qmrom_handle *handle)
{
int retries = handle->comms_retries;
qmrom_c0_poll_soc(handle);
do {
if (handle->sstc->soc_flags.out_waiting) {
qmrom_pre_read_c0(handle);
return qmrom_read_c0(handle);
} else
qmrom_c0_poll_soc(handle);
} while (retries--);
if (retries <= 0)
LOG_ERR("%s failed after %d replies\n", __func__,
handle->comms_retries);
return retries > 0 ? 0 : -1;
}
int qmrom_c0_probe_device(struct qmrom_handle *handle)
{
int rc, i;
uint8_t *soc_lcs_uuid;
handle->is_be = false;
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_c0_wait_ready(handle);
if (rc) {
LOG_INFO("%s: maybe not a C0 device\n", __func__);
return rc;
}
rc = qmrom_write_cmd32_c0(handle, ROM_CMD_C0_GET_CHIP_VER);
if (rc)
return rc;
rc = qmrom_c0_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_C0) {
LOG_ERR("%s: wrong chip revision %#x\n", __func__,
handle->chip_rev);
handle->chip_rev = -1;
return -1;
}
rc = qmrom_c0_wait_ready(handle);
if (rc) {
LOG_ERR("%s: hmm something went wrong!!!\n", __func__);
return rc;
}
rc = qmrom_write_cmd32_c0(handle, ROM_CMD_C0_GET_SOC_INFO);
if (rc)
return rc;
rc = qmrom_c0_poll_cmd_resp(handle);
if (rc)
return rc;
/* skip the first 4 bytes */
soc_lcs_uuid = &(handle->sstc->payload[4]);
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;
memcpy(&handle->lcs_state, soc_lcs_uuid, sizeof(uint32_t));
soc_lcs_uuid += 4;
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_c0_flash_fw;
handle->rom_ops.flash_debug_cert = qmrom_c0_flash_debug_cert;
handle->rom_ops.erase_debug_cert = qmrom_c0_erase_debug_cert;
return 0;
}
#ifdef C0_WRITE_STATS
static uint64_t total_bytes, total_time_ns;
static uint32_t max_write_time_ns, min_write_time_ns = ~0;
static void update_write_max_chunk_stats(ktime_t start_time)
{
uint64_t elapsed_time_ns;
total_bytes += CHUNK_SIZE_C0;
elapsed_time_ns = ktime_to_ns(ktime_sub(ktime_get(), start_time));
total_time_ns += elapsed_time_ns;
if (elapsed_time_ns > max_write_time_ns)
max_write_time_ns = elapsed_time_ns;
if (elapsed_time_ns < min_write_time_ns)
min_write_time_ns = elapsed_time_ns;
}
static void dump_stats(void)
{
uint32_t nb_chunks = total_bytes / CHUNK_SIZE_C0;
LOG_WARN(
"C0 flashing time stats: %llu bytes over %llu us (chunk size %u, write timings: mean %u us, min %u us, max %u us)\n",
total_bytes, total_time_ns / 1000, CHUNK_SIZE_C0,
(uint32_t)((total_time_ns / nb_chunks) / 1000),
min_write_time_ns / 1000, max_write_time_ns / 1000);
}
#endif
static int qmrom_c0_flash_data(struct qmrom_handle *handle, struct firmware *fw,
uint8_t cmd, uint8_t resp, bool skip_last_check)
{
int rc, sent = 0;
const char *bin_data = (const char *)fw->data;
#ifdef C0_WRITE_STATS
ktime_t start_time;
#endif
while (sent < fw->size) {
uint32_t tx_bytes = fw->size - sent;
if (tx_bytes > CHUNK_SIZE_C0)
tx_bytes = CHUNK_SIZE_C0;
LOG_DBG("%s: sending command %#x with %" PRIu32 " bytes\n",
__func__, cmd, tx_bytes);
#ifdef C0_WRITE_STATS
start_time = ktime_get();
#endif
rc = qmrom_write_size_cmd32_c0(handle, cmd, tx_bytes, bin_data);
if (rc)
return rc;
sent += tx_bytes;
bin_data += tx_bytes;
if (skip_last_check && sent == fw->size) {
LOG_INFO("%s: flashing done, quitting now\n", __func__);
break;
}
qmrom_c0_poll_soc(handle);
#ifdef C0_WRITE_STATS
if (tx_bytes == CHUNK_SIZE_C0)
update_write_max_chunk_stats(start_time);
#endif
qmrom_pre_read_c0(handle);
qmrom_read_c0(handle);
if (handle->sstc->payload[0] != resp) {
LOG_ERR("%s: wrong data result (%#x vs %#x)!!!\n",
__func__, handle->sstc->payload[0] & 0xff,
resp);
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;
}
}
qmrom_msleep(SPI_READY_TIMEOUT_MS_C0);
return 0;
}
static int qmrom_c0_flash_fw(struct qmrom_handle *handle,
const struct firmware *fw)
{
int rc = 0;
struct unstitched_firmware all_fws = { 0 };
uint8_t flash_cmd = handle->lcs_state == CC_BSV_SECURE_LCS ?
ROM_CMD_C0_SEC_LOAD_OEM_IMG_TO_RRAM :
ROM_CMD_C0_SEC_LOAD_ICV_IMG_TO_RRAM;
LOG_INFO("Unstitching the fw %p->data %p\n", (void *)fw,
(void *)fw->data);
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;
}
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__);
/* Set RRAM write mode */
rc = qmrom_c0_wait_ready(handle);
if (rc)
goto end;
rc = qmrom_write_cmd32_c0(handle, ROM_CMD_C0_USE_INDIRECT_RRAM_WR);
if (rc)
goto end;
qmrom_c0_poll_soc(handle);
qmrom_pre_read_c0(handle);
qmrom_read_c0(handle);
qmrom_c0_poll_soc(handle);
LOG_DBG("%s: sending flash_cmd %u command\n", __func__, flash_cmd);
rc = qmrom_write_cmd32_c0(handle, flash_cmd);
if (rc)
goto end;
qmrom_c0_poll_cmd_resp(handle);
if (handle->sstc->payload[0] != WAITING_FOR_FIRST_KEY_CERT) {
LOG_ERR("%s: Waiting for WAITING_FOR_FIRST_KEY_CERT(%#x) but got %#x\n",
__func__, WAITING_FOR_FIRST_KEY_CERT,
handle->sstc->payload[0]);
goto end;
}
qmrom_c0_poll_soc(handle);
rc = qmrom_c0_flash_data(handle, all_fws.key1_crt, ROM_CMD_C0_CERT_DATA,
WAITING_FOR_SECOND_KEY_CERT, false);
if (rc)
goto end;
rc = qmrom_c0_flash_data(handle, all_fws.key2_crt, ROM_CMD_C0_CERT_DATA,
WAITING_FOR_CONTENT_CERT, false);
if (rc)
goto end;
rc = qmrom_c0_flash_data(handle, all_fws.fw_crt, ROM_CMD_C0_CERT_DATA,
WAITING_FOR_SEC_FILE_DATA, false);
if (rc)
goto end;
rc = qmrom_c0_flash_data(handle, all_fws.fw_img,
ROM_CMD_C0_SEC_IMAGE_DATA,
WAITING_FOR_SEC_FILE_DATA, true);
#ifdef C0_WRITE_STATS
dump_stats();
#endif
end:
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_c0_flash_debug_cert(struct qmrom_handle *handle,
struct firmware *dbg_cert)
{
int rc;
LOG_DBG("%s: starting...\n", __func__);
rc = qmrom_c0_wait_ready(handle);
if (rc)
return rc;
LOG_DBG("%s: sending ROM_CMD_C0_USE_DIRECT_RRAM_WR command\n",
__func__);
rc = qmrom_write_cmd32_c0(handle, ROM_CMD_C0_USE_INDIRECT_RRAM_WR);
if (rc)
return rc;
qmrom_c0_poll_soc(handle);
qmrom_pre_read_c0(handle);
qmrom_read_c0(handle);
qmrom_c0_poll_soc(handle);
LOG_DBG("%s: sending ROM_CMD_C0_WRITE_DBG_CERT command\n", __func__);
rc = qmrom_write_cmd32_c0(handle, ROM_CMD_C0_WRITE_DBG_CERT);
if (rc)
return rc;
qmrom_c0_poll_cmd_resp(handle);
if (handle->sstc->payload[0] != WAITING_TO_DEBUG_CERTIFICATE_SIZE) {
LOG_ERR("%s: Waiting for WAITING_TO_DEBUG_CERTIFICATE_SIZE(%#x) but got %#x\n",
__func__, WAITING_TO_DEBUG_CERTIFICATE_SIZE,
handle->sstc->payload[0]);
return rc;
}
LOG_DBG("%s: sending ROM_CMD_C0_DEBUG_CERT_SIZE command\n", __func__);
rc = qmrom_write_size_cmd32_c0(handle, ROM_CMD_C0_DEBUG_CERT_SIZE,
sizeof(uint32_t),
(const char *)&dbg_cert->size);
qmrom_c0_poll_cmd_resp(handle);
if (handle->sstc->payload[0] != WAITING_FOR_DEBUG_CERT_DATA) {
LOG_ERR("%s: Waiting for WAITING_FOR_DEBUG_CERT_DATA(%#x) but got %#x\n",
__func__, WAITING_FOR_DEBUG_CERT_DATA,
handle->sstc->payload[0]);
return rc;
}
rc = qmrom_c0_flash_data(handle, dbg_cert, ROM_CMD_C0_CERT_DATA,
WAITING_FOR_DEBUG_CERT_DATA, true);
return 0;
}
static int qmrom_c0_erase_debug_cert(struct qmrom_handle *handle)
{
int rc;
LOG_DBG("%s: starting...\n", __func__);
rc = qmrom_c0_wait_ready(handle);
if (rc)
return rc;
LOG_DBG("%s: sending ROM_CMD_C0_ERASE_DBG_CERT command\n", __func__);
rc = qmrom_write_cmd32_c0(handle, ROM_CMD_C0_ERASE_DBG_CERT);
if (rc)
return rc;
qmrom_msleep(SPI_READY_TIMEOUT_MS_C0);
return 0;
}