blob: 58ca662d0b697dc8043cf3b0282d69ff026433f5 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0 OR Apache-2.0
/*
* Copyright 2022 Qorvo US, Inc.
*
*/
#include <qmrom_utils.h>
#include <qmrom_log.h>
#include <qmrom_spi.h>
#include <qmrom.h>
#include <spi_rom_protocol.h>
int qmrom_a0_probe_device(struct qmrom_handle *handle);
int qmrom_b0_probe_device(struct qmrom_handle *handle);
int qmrom_c0_probe_device(struct qmrom_handle *handle);
static void qmrom_free_stcs(struct qmrom_handle *h)
{
if (h->hstc)
qmrom_free(h->hstc);
if (h->sstc)
qmrom_free(h->sstc);
}
#ifdef CHECK_STCS
void check_stcs(const char *func, int line, struct qmrom_handle *h)
{
uint32_t *buff = (uint32_t *)h->hstc;
if (buff[MAX_STC_FRAME_LEN / sizeof(uint32_t)] != 0xfeeddeef) {
LOG_ERR("%s:%d - hstc %pK corrupted\n", func, line,
(void *)h->hstc);
} else {
LOG_ERR("%s:%d - hstc %pK safe\n", func, line, (void *)h->hstc);
}
buff = (uint32_t *)h->sstc;
if (buff[MAX_STC_FRAME_LEN / sizeof(uint32_t)] != 0xfeeddeef) {
LOG_ERR("%s:%d - sstc %pK corrupted\n", func, line,
(void *)h->sstc);
} else {
LOG_ERR("%s:%d - sstc %pK safe\n", func, line, (void *)h->sstc);
}
}
#endif
static int qmrom_allocate_stcs(struct qmrom_handle *h)
{
int rc = 0;
uint8_t *tx_buf = NULL, *rx_buf = NULL;
qmrom_alloc(tx_buf, MAX_STC_FRAME_LEN + sizeof(uint32_t));
if (tx_buf == NULL) {
rc = -ENOMEM;
goto out;
}
qmrom_alloc(rx_buf, MAX_STC_FRAME_LEN + sizeof(uint32_t));
if (rx_buf == NULL) {
qmrom_free(tx_buf);
rc = -ENOMEM;
goto out;
}
#ifdef CHECK_STCS
((uint32_t *)tx_buf)[MAX_STC_FRAME_LEN / sizeof(uint32_t)] = 0xfeeddeef;
((uint32_t *)rx_buf)[MAX_STC_FRAME_LEN / sizeof(uint32_t)] = 0xfeeddeef;
#endif
h->hstc = (struct stc *)tx_buf;
h->sstc = (struct stc *)rx_buf;
return rc;
out:
qmrom_free_stcs(h);
return rc;
}
int qmrom_pre_read(struct qmrom_handle *handle)
{
handle->hstc->all = 0;
handle->hstc->host_flags.pre_read = 1;
handle->hstc->ul = 1;
handle->hstc->len = 0;
handle->hstc->payload[0] = 0;
return qmrom_spi_transfer(handle->spi_handle, (char *)handle->sstc,
(const char *)handle->hstc,
sizeof(struct stc) + handle->hstc->len);
}
int qmrom_read(struct qmrom_handle *handle)
{
size_t rd_size = handle->sstc->len;
if (rd_size > MAX_STC_FRAME_LEN)
return SPI_ERR_INVALID_STC_LEN;
LOG_DBG("%s: reading %zu bytes...\n", __func__, rd_size);
memset(handle->hstc, 0, sizeof(struct stc) + rd_size);
handle->hstc->host_flags.read = 1;
handle->hstc->ul = 1;
handle->hstc->len = handle->sstc->len;
return qmrom_spi_transfer(handle->spi_handle, (char *)handle->sstc,
(const char *)handle->hstc,
sizeof(struct stc) + rd_size);
}
int qmrom_write_cmd(struct qmrom_handle *handle, uint8_t cmd)
{
handle->hstc->all = 0;
handle->hstc->host_flags.write = 1;
handle->hstc->ul = 1;
handle->hstc->len = 1;
handle->hstc->payload[0] = cmd;
return qmrom_spi_transfer(handle->spi_handle, (char *)handle->sstc,
(const char *)handle->hstc,
sizeof(struct stc) + handle->hstc->len);
}
int qmrom_write_cmd32(struct qmrom_handle *handle, uint32_t cmd)
{
handle->hstc->all = 0;
handle->hstc->host_flags.write = 1;
handle->hstc->ul = 1;
handle->hstc->len = sizeof(cmd);
memcpy(handle->hstc->payload, &cmd, sizeof(cmd));
return qmrom_spi_transfer(handle->spi_handle, (char *)handle->sstc,
(const char *)handle->hstc,
sizeof(struct stc) + handle->hstc->len);
}
int qmrom_write_size_cmd(struct qmrom_handle *handle, uint8_t cmd,
uint16_t data_size, const char *data)
{
handle->hstc->all = 0;
handle->hstc->host_flags.write = 1;
handle->hstc->ul = 1;
handle->hstc->len = data_size + 1;
handle->hstc->payload[0] = cmd;
memcpy(&handle->hstc->payload[1], data, data_size);
return qmrom_spi_transfer(handle->spi_handle, (char *)handle->sstc,
(const char *)handle->hstc,
sizeof(struct stc) + handle->hstc->len);
}
int qmrom_write_size_cmd32(struct qmrom_handle *handle, uint32_t cmd,
uint16_t data_size, const char *data)
{
handle->hstc->all = 0;
handle->hstc->host_flags.write = 1;
handle->hstc->ul = 1;
handle->hstc->len = data_size + sizeof(cmd);
memcpy(handle->hstc->payload, &cmd, sizeof(cmd));
memcpy(&handle->hstc->payload[sizeof(cmd)], data, data_size);
return qmrom_spi_transfer(handle->spi_handle, (char *)handle->sstc,
(const char *)handle->hstc,
sizeof(struct stc) + handle->hstc->len);
}
/*
* Unfortunately, A0, B0 and C0 have different
* APIs to get the chip version...
*
*/
int qmrom_probe_device(struct qmrom_handle *handle)
{
int rc;
/* Test B0 first */
rc = qmrom_b0_probe_device(handle);
if (!rc)
return rc;
/* Test C0 next */
rc = qmrom_c0_probe_device(handle);
if (!rc)
return rc;
/* Finally try A0 */
rc = qmrom_a0_probe_device(handle);
if (!rc)
return rc;
/* None matched!!! */
return -1;
}
struct qmrom_handle *qmrom_init(void *spi_handle, void *reset_handle,
void *ss_rdy_handle, int comms_retries,
reset_device_fn reset)
{
struct qmrom_handle *handle;
int rc;
qmrom_alloc(handle, sizeof(struct qmrom_handle));
if (!handle) {
LOG_ERR("%s: Couldn't allocate %zu bytes...\n", __func__,
sizeof(struct qmrom_handle));
return NULL;
}
rc = qmrom_allocate_stcs(handle);
if (rc) {
LOG_ERR("%s: Couldn't allocate stcs...\n", __func__);
qmrom_free(handle);
return NULL;
}
handle->spi_handle = spi_handle;
handle->reset_handle = reset_handle;
handle->ss_rdy_handle = ss_rdy_handle;
handle->comms_retries = comms_retries;
handle->chip_rev = CHIP_REVISION_UNKNOWN;
handle->device_version = -1;
handle->lcs_state = -1;
handle->dev_ops.reset = reset;
rc = qmrom_probe_device(handle);
if (rc) {
LOG_ERR("%s: qmrom_probe_device returned %d!\n", __func__, rc);
qmrom_free_stcs(handle);
qmrom_free(handle);
return NULL;
}
check_stcs(__func__, __LINE__, handle);
return handle;
}
void qmrom_deinit(struct qmrom_handle *handle)
{
LOG_DBG("Deinitializing %pK\n", (void *)handle);
qmrom_free_stcs(handle);
qmrom_free(handle);
}
int qmrom_flash_dbg_cert(struct qmrom_handle *handle, struct firmware *dbg_cert)
{
if (!handle->rom_ops.flash_debug_cert) {
LOG_ERR("%s: flash debug certificate not support on this device\n",
__func__);
return -EINVAL;
}
return handle->rom_ops.flash_debug_cert(handle, dbg_cert);
}
int qmrom_erase_dbg_cert(struct qmrom_handle *handle)
{
if (!handle->rom_ops.erase_debug_cert) {
LOG_ERR("%s: erase debug certificate not support on this device\n",
__func__);
return -EINVAL;
}
return handle->rom_ops.erase_debug_cert(handle);
}
int qmrom_flash_fw(struct qmrom_handle *handle, const struct firmware *fw)
{
return handle->rom_ops.flash_fw(handle, fw);
}
int qmrom_flash_unstitched_fw(struct qmrom_handle *handle,
const struct unstitched_firmware *fw)
{
return handle->rom_ops.flash_unstitched_fw(handle, fw);
}
int qmrom_unstitch_fw(const struct firmware *fw,
struct unstitched_firmware *unstitched_fw,
enum chip_revision_e revision)
{
uint32_t tot_len = 0;
uint32_t fw_img_sz = 0;
uint32_t fw_crt_sz = 0;
uint32_t key1_crt_sz = 0;
uint32_t key2_crt_sz = 0;
uint8_t *p_key1;
uint8_t *p_key2;
uint8_t *p_crt;
uint8_t *p_fw;
int ret = 0;
if (revision == CHIP_REVISION_A0) {
LOG_ERR("%s: A0, no unstitching!!!\n", __func__);
return -EINVAL;
}
if (fw->size < 2 * sizeof(key1_crt_sz)) {
LOG_ERR("%s: Not enough data (%zu) to unstitch\n", __func__,
fw->size);
return -EINVAL;
}
LOG_INFO("%s: Unstitching %zu bytes\n", __func__, fw->size);
/* key1 */
key1_crt_sz = *(uint32_t *)&fw->data[tot_len];
if (tot_len + key1_crt_sz + sizeof(key1_crt_sz) > fw->size) {
LOG_ERR("%s: Invalid or corrupted stitched file at offset \
%" PRIu32 " (key1)\n",
__func__, tot_len);
ret = -EINVAL;
goto out;
}
tot_len += sizeof(key1_crt_sz);
p_key1 = (uint8_t *)&fw->data[tot_len];
tot_len += key1_crt_sz;
/* key2 */
key2_crt_sz = *(uint32_t *)&fw->data[tot_len];
if (tot_len + key2_crt_sz + sizeof(key2_crt_sz) > fw->size) {
LOG_ERR("%s: Invalid or corrupted stitched file at offset \
%" PRIu32 " (key2)\n",
__func__, tot_len);
ret = -EINVAL;
goto out;
}
tot_len += sizeof(key2_crt_sz);
p_key2 = (uint8_t *)&fw->data[tot_len];
tot_len += key2_crt_sz;
/* cert */
fw_crt_sz = *(uint32_t *)&fw->data[tot_len];
if (tot_len + fw_crt_sz + sizeof(fw_crt_sz) > fw->size) {
LOG_ERR("%s: Invalid or corrupted stitched file at offset \
%" PRIu32 " (content cert)\n",
__func__, tot_len);
ret = -EINVAL;
goto out;
}
tot_len += sizeof(fw_crt_sz);
p_crt = (uint8_t *)&fw->data[tot_len];
tot_len += fw_crt_sz;
/* fw */
fw_img_sz = *(uint32_t *)&fw->data[tot_len];
if (tot_len + fw_img_sz + sizeof(fw_img_sz) != fw->size) {
LOG_ERR("%s: Invalid or corrupted stitched file at offset \
%" PRIu32 " (firmnware)\n",
__func__, tot_len);
ret = -EINVAL;
goto out;
}
tot_len += sizeof(fw_img_sz);
p_fw = (uint8_t *)&fw->data[tot_len];
qmrom_alloc(unstitched_fw->fw_img, fw_img_sz + sizeof(struct firmware));
if (unstitched_fw->fw_img == NULL) {
ret = -ENOMEM;
goto out;
}
qmrom_alloc(unstitched_fw->fw_crt, fw_crt_sz + sizeof(struct firmware));
if (unstitched_fw->fw_crt == NULL) {
ret = -ENOMEM;
goto err;
}
qmrom_alloc(unstitched_fw->key1_crt,
key1_crt_sz + sizeof(struct firmware));
if (unstitched_fw->key1_crt == NULL) {
ret = -ENOMEM;
goto err;
}
qmrom_alloc(unstitched_fw->key2_crt,
key2_crt_sz + sizeof(struct firmware));
if (unstitched_fw->key2_crt == NULL) {
ret = -ENOMEM;
goto err;
}
unstitched_fw->key1_crt->data =
(const uint8_t *)(unstitched_fw->key1_crt + 1);
unstitched_fw->key2_crt->data =
(const uint8_t *)(unstitched_fw->key2_crt + 1);
unstitched_fw->fw_crt->data =
(const uint8_t *)(unstitched_fw->fw_crt + 1);
unstitched_fw->fw_img->data =
(const uint8_t *)(unstitched_fw->fw_img + 1);
unstitched_fw->key1_crt->size = key1_crt_sz;
unstitched_fw->key2_crt->size = key2_crt_sz;
unstitched_fw->fw_crt->size = fw_crt_sz;
unstitched_fw->fw_img->size = fw_img_sz;
memcpy((void *)unstitched_fw->key1_crt->data, p_key1, key1_crt_sz);
memcpy((void *)unstitched_fw->key2_crt->data, p_key2, key2_crt_sz);
memcpy((void *)unstitched_fw->fw_crt->data, p_crt, fw_crt_sz);
memcpy((void *)unstitched_fw->fw_img->data, p_fw, fw_img_sz);
return 0;
err:
if (unstitched_fw->fw_img)
qmrom_free(unstitched_fw->fw_img);
if (unstitched_fw->fw_crt)
qmrom_free(unstitched_fw->fw_crt);
if (unstitched_fw->key1_crt)
qmrom_free(unstitched_fw->key1_crt);
if (unstitched_fw->key2_crt)
qmrom_free(unstitched_fw->key2_crt);
out:
return ret;
}
int qmrom_reboot_bootloader(struct qmrom_handle *handle)
{
int rc;
rc = qmrom_spi_set_cs_level(handle->spi_handle, 0);
if (rc) {
LOG_ERR("%s: spi_set_cs_level(0) failed with %d\n", __func__,
rc);
return rc;
}
qmrom_msleep(SPI_RST_LOW_DELAY_MS);
handle->dev_ops.reset(handle->reset_handle);
qmrom_msleep(SPI_RST_LOW_DELAY_MS);
rc = qmrom_spi_set_cs_level(handle->spi_handle, 1);
if (rc) {
LOG_ERR("%s: spi_set_cs_level(1) failed with %d\n", __func__,
rc);
return rc;
}
qmrom_msleep(SPI_RST_LOW_DELAY_MS);
return 0;
}