blob: 0f9912f05c1977c2e1eded8a711036e449c328f9 [file] [log] [blame]
// SPDX-License-Identifier: BSD-2-Clause
/*
* Copyright 2019 Broadcom.
*/
#include <crc32.h>
#include <drivers/bcm/bnxt.h>
#include <mm/core_mmu.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
/*
* These macros are the offsets where images reside on sec mem
*/
#define BNXT_BUFFER_SEC_MEM 0x8ae00000
#define BNXT_FW_SEC_MEM_SRC BNXT_BUFFER_SEC_MEM
#define BNXT_FW_SEC_MEM_CFG (BNXT_BUFFER_SEC_MEM + 0x100000)
#define TEMP_MEM (BNXT_BUFFER_SEC_MEM + 0x180000)
#define BNXT_CRASH_SEC_MEM 0x8b000000
#define BNXT_CRASH_LEN 0x2000000
#define BNXT_CONFIG_NS3_DEST 0x03a00000
#define BNXT_BSPD_CFG_OFFSET 0x51b0
#define BNXT_CONFIG_NS3_BSPD_DEST (BNXT_CONFIG_NS3_DEST + \
BNXT_BSPD_CFG_OFFSET)
#define BNXT_BSPD_CFG_SIZE 0x200
#define BNXT_CRASH_DUMP_INFO_NS3_BASE 0x3a5ff00
#define IS_ALIGNED(addr, algn) (!((addr) & ((algn) - 1)))
#define SZ_1K 0x400
#define BUFFER_PADDING SZ_1K
#define INC_SRC_ADDR 1
#define EOF -1
#define BCM_BNXT_FASTBOOT_MASK 0x3u
#define BCM_BNXT_FASTBOOT_TYPE_1 1
#define ADDR_IS_4BYTE_ALIGNED(addr) IS_ALIGNED(addr, 4)
#define SECTION_IS_LOADABLE(section_ptr) \
((section_ptr)->flags_src_offset & SECTION_FLAGS_IS_LOADABLE)
#define SECTION_IS_ZIPPED(section_ptr) \
((section_ptr)->flags_src_offset & SECTION_FLAGS_IS_ZIPPED)
#define SECTION_IS_TOBE_COPIED(section_ptr) \
((section_ptr)->flags_src_offset & \
(SECTION_FLAGS_IS_EXEC_INSTR | SECTION_FLAGS_IS_DATA))
#define SECTION_IS_TOBE_ZEROED(section_ptr) \
((section_ptr)->flags_src_offset & SECTION_FLAGS_IS_BSS)
#define SECTION_IS_4BYTE_ALIGNED(section_ptr) \
ADDR_IS_4BYTE_ALIGNED((section_ptr)->dest_addr)
#define SECTION_SRC_OFFSET(section_ptr) \
((section_ptr)->flags_src_offset & SECTION_SRC_OFFFSET_MASK)
/* -------------------------------------------------------------------------- */
/* Section header for each image block */
struct ape_section_hdr_s {
/* Destination address that this section is to be copied to */
uint32_t dest_addr;
/*
* bit[0:23] source offset address that this image copy from
* bit[24:31] flags
*/
uint32_t flags_src_offset;
#define SECTION_FLAGS_MASK 0xff000000
/* Session is compressed (zipped) */
#define SECTION_FLAGS_IS_ZIPPED 0x01000000
/* Session contains CRC */
#define SECTION_FLAGS_IS_CRC 0x02000000
/* Session contains executable code (e.g. .text) */
#define SECTION_FLAGS_IS_EXEC_INSTR 0x04000000
/* Session contains initialized data (e.g. .data) */
#define SECTION_FLAGS_IS_DATA 0x08000000
/* Session contains zero initialized data (e.g. .bss) */
#define SECTION_FLAGS_IS_BSS 0x10000000
/* Loadable section mask */
#define SECTION_FLAGS_IS_LOADABLE (SECTION_FLAGS_IS_EXEC_INSTR | \
SECTION_FLAGS_IS_DATA | \
SECTION_FLAGS_IS_BSS)
#define SECTION_SRC_OFFFSET_MASK 0x00ffffff
/* Original image length, dword (4byte) length */
uint32_t org_data_len;
/* Compressed image length (if FlAGS_IS_ZIPPED is set) */
uint32_t zip_data_len;
/*
* checksum value for this image block, if FLAGS_IS_CRC then
* this is CRC checksum; otherwise it is a simple summation
*/
uint32_t checksum;
};
struct version_s {
uint8_t version[16]; /* Null-terminated file version string */
};
struct ver_ext_offset_s {
uint8_t version[12]; /* Null-terminated file version string */
uint32_t ext_hdr_offset;
};
union version_and_offset_u {
struct version_s version1;
struct ver_ext_offset_s version2;
};
struct ape_bin_hdr_s {
/* APE binary header signature; expects APE_BIN_HDR_SIGNATURE */
uint32_t signature;
#define APE_BIN_HDR_SIGNATURE 0x1a4d4342 /* "BCM"+0x1a */
/* Reserved for ChiMP's use */
uint8_t flags;
uint8_t code_type;
uint8_t device;
uint8_t media;
union version_and_offset_u ver;
uint8_t build;
uint8_t revision;
uint8_t minor_ver;
uint8_t major_ver;
uint32_t entry_address;
uint8_t reserved;
uint8_t header_dword_size;
uint8_t num_total_sections;
uint8_t num_loadable_sections;
uint32_t checksum;
} __packed __aligned(1);
#define APE_BIN_HDR_SIZE sizeof(struct ape_bin_hdr_s)
#define APE_SECTION_HDR_SIZE sizeof(struct ape_section_hdr_s)
/* MAX number of image sections that will be accepted */
#define APE_IMG_MAX_SECTIONS 16
#define APE_IMG_LOAD_DEBUG 0
/* -------------------------------------------------------------------------- */
struct ape_mem_region_s {
uint32_t c_base; /* ChiMP's view of address */
uint32_t h_base; /* Host's view of address */
uint32_t size; /* Size in bytes */
};
/* Memory map into various scratchpad memories */
static struct ape_mem_region_s ape_mem_regions[] = {
/* CHIMP scratchpad */
{0x00100000, 0x03100000, 1024 * SZ_1K},
/* APE scratchpad */
{0x61000000, 0x03300000, 1152 * SZ_1K},
/* BONO scratchpad */
{0x61600000, 0x03a00000, 512 * SZ_1K},
/* KONG scratchpad */
{0x61400000, 0x03800000, 512 * SZ_1K},
/* Keep this last!! */
{0, 0, 0}
};
/* Nitro crash address configuration related macros */
#define BNXT_CRASH_INFO_SIGNATURE 0x20524444
#define BNXT_CRASH_INFO_VALID 0x1
#define MAX_CRASH_ADDR_ITEM 8
struct nitro_crash_addr_item {
uint32_t info;
uint32_t size;
uint32_t addr_hi;
uint32_t addr_lo;
};
struct nitro_crash_addr_info {
/* CRC of the struct content, starting at next field. */
uint32_t crc;
uint32_t signature;
uint32_t version;
struct nitro_crash_addr_item table[MAX_CRASH_ADDR_ITEM];
};
static inline void memcpy32_helper(uintptr_t src,
uintptr_t dst,
uint32_t entries,
int inc_src_addr)
{
uint32_t copied_entries = 0;
while (entries) {
copied_entries = bnxt_write32_multiple(dst, src, entries,
inc_src_addr);
if (copied_entries < entries) {
dst += copied_entries * sizeof(uint32_t);
src += (inc_src_addr) ?
(copied_entries * sizeof(uint32_t)) : 0;
entries -= copied_entries;
} else {
entries = 0;
}
}
}
static uint32_t ape_host_view_addr_get(uint32_t bnxt_view_addr, uint32_t size)
{
struct ape_mem_region_s *region = ape_mem_regions;
uint32_t addr = 0;
for (; region->size != 0; region++) {
if (bnxt_view_addr < region->c_base)
continue;
if (bnxt_view_addr >= (region->c_base + region->size))
continue;
if (size > (region->c_base + region->size - bnxt_view_addr)) {
EMSG("ERROR: 0x%x + 0x%x spans memory boundary",
bnxt_view_addr, size);
break;
}
addr = bnxt_view_addr - region->c_base;
addr += region->h_base;
break;
}
return addr;
}
static uint32_t ape_hdr_crc_calc(const struct ape_bin_hdr_s *hdr)
{
uint32_t crc = 0;
uint32_t dummy = 0;
/* Compute the CRC up to, but not including, the checksum field */
crc = CRC32(CRC32_INIT_VAL,
(const char *)hdr,
(uintptr_t)(&hdr->checksum) - (uintptr_t)hdr);
/* Compute the CRC with the checksum field zeroed out */
crc = CRC32(~crc, (const char *)&dummy, sizeof(uint32_t));
/*
* Compute the remainder part of the image header, i.e., the
* section headers
*/
crc = CRC32(~crc,
(const char *)((uintptr_t)hdr + APE_BIN_HDR_SIZE),
hdr->num_total_sections * APE_SECTION_HDR_SIZE);
return crc;
}
static int ape_bin_hdr_valid(const struct ape_bin_hdr_s *hdr)
{
uint32_t checksum = 0;
if (!hdr) {
EMSG("ERROR: no APE image header");
return BNXT_FAILURE;
}
if (hdr->signature != APE_BIN_HDR_SIGNATURE) {
EMSG("ERROR: bad APE image signature");
return BNXT_FAILURE;
}
if (hdr->num_total_sections > APE_IMG_MAX_SECTIONS) {
EMSG("ERROR: too many sections in APE image");
return BNXT_FAILURE;
}
checksum = ape_hdr_crc_calc(hdr);
if (hdr->checksum != checksum) {
EMSG("ERROR: bad APE header checksum (exp: %x, act: %x)",
hdr->checksum, checksum);
return BNXT_FAILURE;
}
return BNXT_SUCCESS;
}
static int get_char(uint8_t *inbuf, size_t *inbuf_idx, size_t inbuf_size)
{
int c = 0;
if (*inbuf_idx >= inbuf_size)
return EOF;
c = inbuf[*inbuf_idx];
*inbuf_idx += 1;
return c;
}
static void put_char(uint8_t *outbuf,
size_t *outbuf_idx,
size_t outbuf_size,
uint8_t ch)
{
if (*outbuf_idx >= outbuf_size)
return;
outbuf[*outbuf_idx] = ch;
*outbuf_idx += 1;
}
static size_t ape_section_uncompress(uint8_t *inbuf,
size_t inbuf_size,
uint8_t *outbuf,
size_t outbuf_size)
{
int i = 0, j = 0, k = 0, r = 0, c = 0;
uint32_t flags = 0;
size_t exp_size = 0, codesize = 0;
size_t inbuf_idx = 0, outbuf_idx = 0;
#define CODE_8U_MASK 0xff00u /* 8 code units count mask (8 bits) */
#define CODE_END_MASK 0x100u /* End of code units mask */
#define CODE_IS_UNENCODED_MASK 1 /* Unencoded code unit mask */
#define CODE_POS_MASK 0xe0u /* Encoded unit position mask and */
#define CODE_POS_SHIFT 3 /* Bit shift */
#define CODE_LEN_MASK 0x1fu /* Encoded unit length mask */
#define NS 2048 /* Size of ring buffer */
#define F 34 /* Upper limit for match_length */
#define THRESHOLD 2 /* Encode string into position and
* length, if match_length is
* greater than this.
*/
/*
* Ring buffer of size NS, with an extra F-1 bytes to facilitate
* string comparisons.
*/
uint8_t text_buf[NS + F - 1];
inbuf_idx = 0;
outbuf_idx = 0;
for (i = 0; i < NS - F; i++)
text_buf[i] = ' ';
r = NS - F;
for (;;) {
if (((flags >>= 1) & CODE_END_MASK) == 0) {
c = get_char(inbuf, &inbuf_idx, inbuf_size);
if (c == EOF)
break;
++exp_size;
if (exp_size > inbuf_size)
break;
/* Use higher byte cleverly to count to eight */
flags = c | CODE_8U_MASK;
}
if (flags & CODE_IS_UNENCODED_MASK) {
/* Not encoded; simply copy the unit */
c = get_char(inbuf, &inbuf_idx, inbuf_size);
if (c == EOF)
break;
++exp_size;
if (exp_size > inbuf_size)
break;
put_char(outbuf, &outbuf_idx, outbuf_size, c);
text_buf[r++] = c;
r &= (NS - 1);
++codesize;
} else {
/* Encoded; get the position and length & duplicate */
i = get_char(inbuf, &inbuf_idx, inbuf_size);
if (i == EOF)
break;
++exp_size;
if (exp_size > inbuf_size)
break;
j = get_char(inbuf, &inbuf_idx, inbuf_size);
if (j == EOF)
break;
++exp_size;
if (exp_size > inbuf_size)
break;
i |= ((j & CODE_POS_MASK) << CODE_POS_SHIFT);
j = ((j & CODE_LEN_MASK) + THRESHOLD);
for (k = 0; k <= j; k++) {
c = text_buf[((i + k) & (NS - 1))];
put_char(outbuf, &outbuf_idx, outbuf_size, c);
text_buf[r++] = c;
r &= (NS - 1);
++codesize;
}
}
}
return codesize;
}
static int ape_section_copy(struct ape_bin_hdr_s *bin_hdr,
struct ape_section_hdr_s *section)
{
uintptr_t src = 0;
uintptr_t dst = 0;
uint32_t checksum = 0;
uint32_t i = 0;
size_t size = 0;
uint8_t *section_data = NULL;
size_t work_buff_size = 0;
void *work_buff = NULL;
int rc = BNXT_FAILURE;
if (SECTION_IS_ZIPPED(section)) {
work_buff_size = section->org_data_len + BUFFER_PADDING;
work_buff = (void *)phys_to_virt(TEMP_MEM, MEM_AREA_RAM_SEC);
if (!work_buff) {
EMSG("ERROR: buffer allocation");
return BNXT_FAILURE;
}
section_data = (uint8_t *)((uintptr_t)bin_hdr +
SECTION_SRC_OFFSET(section));
size = ape_section_uncompress(section_data,
section->zip_data_len,
work_buff,
work_buff_size);
if (size >= work_buff_size) {
EMSG("ERROR: section uncompress");
goto ape_section_copy_exit;
}
if (size < section->org_data_len) {
EMSG("ERROR: decompressed data size mismatch ");
EMSG("(exp: %d, act: %ld)",
section->org_data_len, size);
goto ape_section_copy_exit;
}
src = (uintptr_t)work_buff;
} else {
src = (uintptr_t)bin_hdr + SECTION_SRC_OFFSET(section);
}
size = section->org_data_len;
if (section->flags_src_offset & SECTION_FLAGS_IS_CRC) {
checksum = CRC32(CRC32_INIT_VAL, (const char *)src, size);
} else {
checksum = 0;
for (i = 0; i < size / sizeof(uint32_t); i++)
checksum += ((uint32_t *)src)[i];
}
if (checksum != section->checksum) {
EMSG("ERROR: checksum mismatch (exp: %x, act: %x)",
section->checksum, checksum);
goto ape_section_copy_exit;
}
dst = ape_host_view_addr_get(section->dest_addr, size);
if (dst == 0) {
EMSG("ERROR: ChiMP-to-host address conversion of %x",
section->dest_addr);
goto ape_section_copy_exit;
}
/* Copy the section */
size = size / sizeof(uint32_t);
memcpy32_helper(src, dst, size, INC_SRC_ADDR);
rc = BNXT_SUCCESS;
ape_section_copy_exit:
return rc;
}
static int ape_section_zero(struct ape_section_hdr_s *section)
{
uint32_t dst = 0;
uint32_t size = section->org_data_len;
uint32_t zero = 0;
if (section->org_data_len == 0)
return BNXT_SUCCESS;
/* Convert ChiMP's view of the address in the image to the host view */
dst = ape_host_view_addr_get(section->dest_addr, size);
if (dst == 0) {
EMSG("ERROR: ChiMP-to-host address conversion of %x",
section->dest_addr);
return BNXT_FAILURE;
}
/*
* Zero the section; we simply copy zeros and do not increment the
* source buffer address.
*/
size = size / sizeof(uint32_t);
memcpy32_helper((uintptr_t)&zero, dst, size, !INC_SRC_ADDR);
return BNXT_SUCCESS;
}
static int bnxt_load(vaddr_t img_buffer)
{
struct ape_bin_hdr_s *bin_hdr = NULL;
struct ape_section_hdr_s *section = NULL;
int sidx = 0;
int rc = BNXT_SUCCESS;
bin_hdr = (struct ape_bin_hdr_s *)img_buffer;
section = (struct ape_section_hdr_s *)(img_buffer +
APE_BIN_HDR_SIZE);
if (ape_bin_hdr_valid(bin_hdr) != BNXT_SUCCESS)
return BNXT_FAILURE;
for (sidx = 0; sidx < bin_hdr->num_total_sections; sidx++, section++) {
if (!SECTION_IS_LOADABLE(section))
continue;
if (!ADDR_IS_4BYTE_ALIGNED(section->dest_addr)) {
EMSG("ERROR: unaligned section dest address 0x%x",
section->dest_addr);
rc = BNXT_FAILURE;
break;
}
if (!ADDR_IS_4BYTE_ALIGNED(SECTION_SRC_OFFSET(section))) {
EMSG("ERROR: unaligned section src offset (0x%x)",
SECTION_SRC_OFFSET(section));
rc = BNXT_FAILURE;
break;
}
if (section->org_data_len % sizeof(uint32_t)) {
EMSG("ERROR: section size (%d) not divisible by 4",
section->org_data_len);
rc = BNXT_FAILURE;
break;
}
if (SECTION_IS_TOBE_COPIED(section)) {
rc = ape_section_copy(bin_hdr, section);
if (rc != BNXT_SUCCESS)
break;
} else if (SECTION_IS_TOBE_ZEROED(section)) {
rc = ape_section_zero(section);
if (rc != BNXT_SUCCESS)
break;
}
}
/* Set up boot mode and take BNXT out of reset */
if (rc == BNXT_SUCCESS) {
bnxt_fastboot((bin_hdr->entry_address &
~BCM_BNXT_FASTBOOT_MASK) |
BCM_BNXT_FASTBOOT_TYPE_1);
}
return rc;
}
static TEE_Result bnxt_crash_config(uintptr_t info_dst,
uint32_t crash_area_start,
uint32_t crash_len)
{
struct nitro_crash_addr_item *item = NULL;
uintptr_t dst = 0;
struct nitro_crash_addr_info *info = NULL;
uintptr_t src = 0;
uint32_t crc = 0;
size_t size = 0;
/*
* First we write into local memory to calculate CRC before
* updating into Nitro memory
*/
info = malloc(sizeof(struct nitro_crash_addr_info));
if (!info) {
EMSG("ERROR: buffer allocation");
return TEE_ERROR_OUT_OF_MEMORY;
}
memset(info, 0, sizeof(struct nitro_crash_addr_info));
info->signature = BNXT_CRASH_INFO_SIGNATURE;
info->version = 0x01000000 | MAX_CRASH_ADDR_ITEM;
/* As of now only one item is filled */
item = &info->table[0];
item->info = 0;
item->size = crash_len | BNXT_CRASH_INFO_VALID;
item->addr_hi = 0;
item->addr_lo = crash_area_start;
/* Checksum calculation */
crc = CRC32(CRC32_INIT_VAL,
(const char *)info + sizeof(uint32_t),
sizeof(struct nitro_crash_addr_info) - sizeof(uint32_t));
info->crc = crc;
/* First we write the contents and then set valid bit */
item->size &= ~BNXT_CRASH_INFO_VALID;
size = sizeof(struct nitro_crash_addr_info) / sizeof(uint32_t);
dst = info_dst;
src = (uintptr_t)info;
memcpy32_helper(src, dst, size, INC_SRC_ADDR);
/* Set the valid bit */
item->size |= BNXT_CRASH_INFO_VALID;
dst = info_dst + offsetof(struct nitro_crash_addr_info, table) +
offsetof(struct nitro_crash_addr_item, size);
bnxt_write32_multiple(dst, (uintptr_t)&item->size, 1, 1);
free(info);
return TEE_SUCCESS;
}
TEE_Result bnxt_load_fw(int chip_type)
{
uint32_t size = 0;
uintptr_t dst = 0;
uintptr_t src = 0;
struct bnxt_images_info bnxt_src_image_info;
vaddr_t sec_mem_dest = (vaddr_t)phys_to_virt(BNXT_BUFFER_SEC_MEM,
MEM_AREA_RAM_SEC);
memset(&bnxt_src_image_info, 0, sizeof(struct bnxt_images_info));
if (get_bnxt_images_info(&bnxt_src_image_info,
chip_type, sec_mem_dest) != BNXT_SUCCESS)
return TEE_ERROR_ITEM_NOT_FOUND;
bnxt_handshake_clear();
bnxt_kong_halt();
bnxt_chimp_halt();
/* Copy the configs */
src = (uintptr_t)bnxt_src_image_info.bnxt_cfg_vaddr;
dst = (uintptr_t)BNXT_CONFIG_NS3_DEST;
size = bnxt_src_image_info.bnxt_cfg_len;
size = size / sizeof(uint32_t);
memcpy32_helper(src, dst, size, INC_SRC_ADDR);
/* Copy bspd config */
src = (uintptr_t)bnxt_src_image_info.bnxt_bspd_cfg_vaddr;
size = bnxt_src_image_info.bnxt_bspd_cfg_len;
dst = (uintptr_t)BNXT_CONFIG_NS3_BSPD_DEST;
size = size / sizeof(uint32_t);
memcpy32_helper(src, dst, size, INC_SRC_ADDR);
/* Fill the bnxt crash dump info */
bnxt_crash_config((uintptr_t)BNXT_CRASH_DUMP_INFO_NS3_BASE,
BNXT_CRASH_SEC_MEM,
BNXT_CRASH_LEN);
/* Load bnxt firmware and fastboot */
bnxt_load(bnxt_src_image_info.bnxt_fw_vaddr);
return TEE_SUCCESS;
}
TEE_Result bnxt_copy_crash_dump(uint8_t *d, uint32_t offset, uint32_t len)
{
void *s = NULL;
if (offset + len > BNXT_CRASH_LEN)
return TEE_ERROR_BAD_PARAMETERS;
s = phys_to_virt(BNXT_CRASH_SEC_MEM + offset, MEM_AREA_RAM_SEC);
cache_op_inner(DCACHE_AREA_INVALIDATE, s, len);
memcpy(d, s, len);
return TEE_SUCCESS;
}