262 lines
7.5 KiB
C
Executable file
262 lines
7.5 KiB
C
Executable file
/****************************************************************************
|
|
*
|
|
* Copyright (c) 2014 - 2021 Samsung Electronics Co., Ltd. All rights reserved
|
|
*
|
|
****************************************************************************/
|
|
|
|
#include <linux/kernel.h>
|
|
#include <scsc/scsc_logring.h>
|
|
#include <linux/slab.h>
|
|
#include <scsc/scsc_logring.h>
|
|
#include "fwhdr_if.h"
|
|
#include "bhdr.h"
|
|
|
|
/** Definition of tags */
|
|
enum bhdr_tag {
|
|
/**
|
|
* The total length of the entire BHDR this tag is contained in, including the size of all
|
|
* tag, length and value fields.
|
|
*
|
|
* This special tag must be present as first item for validation/integrity check purposes,
|
|
* and to be able to determine the overall length of the BHDR.
|
|
*
|
|
* Value length: 4 byte
|
|
* Value encoding: uint32_t
|
|
*/
|
|
BHDR_TAG_TOTAL_LENGTH = 0x52444842, /* "BHDR" */
|
|
|
|
/**
|
|
* Offset of location to load the Bluetooth firmware binary image. The offset is relative to
|
|
* begining of the entire memory region that is shared between the AP and WLBT.
|
|
*
|
|
* Value length: 4 byte
|
|
* Value encoding: uint32_t
|
|
*/
|
|
BHDR_TAG_FW_OFFSET = 1,
|
|
|
|
/**
|
|
* Runtime size of firmware
|
|
*
|
|
* This accommodates not only the Bluetooth firmware binary image itself, but also any
|
|
* statically allocated memory (such as zero initialised static variables) immediately
|
|
* following it that is used by the firmware at runtime after it has booted.
|
|
*
|
|
* Value length: 4 byte
|
|
* Value encoding: uint32_t
|
|
*/
|
|
BHDR_TAG_FW_RUNTIME_SIZE = 2,
|
|
|
|
/**
|
|
* Firmware build identifier string
|
|
*
|
|
* Value length: N byte
|
|
* Value encoding: uint8_t[N]
|
|
*/
|
|
BHDR_TAG_FW_BUILD_ID = 3,
|
|
|
|
/**
|
|
* Offset of location of system error record. The offset is relative to the beginning of the
|
|
* entire memory region that is shared between the AP and WLBT.
|
|
*
|
|
* Value length: 4 byte
|
|
* Value encoding: uint32_t
|
|
*/
|
|
BHDR_TAG_SYSTEM_ERROR_RECORD_OFFSET = 4,
|
|
};
|
|
|
|
/**
|
|
* Structure describing the tag and length of each item in the BHDR.
|
|
* The value of the item follows immediately after.
|
|
*/
|
|
struct bhdr_tag_length {
|
|
uint32_t tag; /* One of bhdr_tag */
|
|
uint32_t length; /* Length of value in bytes */
|
|
};
|
|
|
|
/* bthdr */
|
|
/**
|
|
* Maximum length of build identifier including 0 termination.
|
|
*
|
|
* The length of the value of BHDR_TAG_FW_BUILD_ID is fixed to this value instead of the possibly
|
|
* varying length of the build identifier string, so that the length of the firmware header doesn't
|
|
* change for different build identifier strings in an otherwise identical firmware build.
|
|
*/
|
|
#define FIRMWARE_BUILD_ID_MAX_LENGTH 128
|
|
|
|
struct bt_firmware_header {
|
|
/* BHDR_TAG_TOTAL_LENGTH */
|
|
struct bhdr_tag_length total_length_tl;
|
|
uint32_t total_length_val;
|
|
|
|
/* BHDR_TAG_FW_OFFSET */
|
|
struct bhdr_tag_length fw_offset_tl;
|
|
uint32_t fw_offset_val;
|
|
|
|
/* BHDR_TAG_FW_RUNTIME_SIZE */
|
|
struct bhdr_tag_length fw_runtime_size_tl;
|
|
uint32_t fw_runtime_size_val;
|
|
|
|
/* BHDR_TAG_FW_BUILD_ID */
|
|
struct bhdr_tag_length fw_build_id_tl;
|
|
const char fw_build_id_val[FIRMWARE_BUILD_ID_MAX_LENGTH];
|
|
};
|
|
|
|
const void *bhdr_lookup_tag(const void *blob, enum bhdr_tag tag, uint32_t *length);
|
|
|
|
struct bhdr {
|
|
struct fwhdr_if fw_if;
|
|
uint32_t bt_fw_offset_val;
|
|
uint32_t bt_fw_runtime_size_val;
|
|
uint32_t bt_panic_record_offset;
|
|
char *fw_dram_addr;
|
|
uint32_t fw_size;
|
|
};
|
|
|
|
#define bhdr_from_fwhdr_if(FWHDR_IF_PTR) container_of(FWHDR_IF_PTR, struct bhdr, fw_if)
|
|
|
|
/** Return the next item after a given item and decrement the remaining length */
|
|
static const struct bhdr_tag_length *bhdr_next_item(const struct bhdr_tag_length *item, uint32_t *bhdr_length)
|
|
{
|
|
uint32_t skip_length = sizeof(*item) + item->length;
|
|
|
|
if (skip_length < *bhdr_length) {
|
|
*bhdr_length -= skip_length;
|
|
return (const struct bhdr_tag_length *)((uintptr_t)item + skip_length);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/** Return the value of an item */
|
|
static const void *bhdr_item_value(const struct bhdr_tag_length *item)
|
|
{
|
|
SCSC_TAG_DBG4(FW_LOAD, "bhdr_lookup_tag %p\n", item);
|
|
SCSC_TAG_DBG4(FW_LOAD, "bhdr_lookup_tag current tag %u lenght %u\n", item->tag, item->length);
|
|
return (const void *)((uintptr_t)item + sizeof(*item));
|
|
}
|
|
|
|
const void *bhdr_lookup_tag(const void *blob, enum bhdr_tag tag, uint32_t *length)
|
|
{
|
|
const struct bhdr_tag_length *bhdr = (const struct bhdr_tag_length *)((char *)blob + 8);
|
|
const struct bhdr_tag_length *item = bhdr;
|
|
uint32_t bhdr_length;
|
|
|
|
if (bhdr->tag != BHDR_TAG_TOTAL_LENGTH) {
|
|
SCSC_TAG_WARNING(FW_LOAD, "Unexpected first tag in bhdr: %u \n", item->tag);
|
|
return NULL;
|
|
}
|
|
|
|
/* First item is BHCD_TAG_TOTAL_LENGTH which contains overall length of the BHCD */
|
|
bhdr_length = *(const uint32_t *)bhdr_item_value(bhdr);
|
|
|
|
SCSC_TAG_DBG4(FW_LOAD, "bhdr_lookup_tag %u\n", tag);
|
|
|
|
while (item != NULL) {
|
|
if (item->tag == tag) {
|
|
if (length != NULL) {
|
|
*length = item->length;
|
|
}
|
|
SCSC_TAG_DBG4(FW_LOAD, "bhdr_lookup_tag %u found with size %u\n", item->tag, item->length);
|
|
return bhdr_item_value(item);
|
|
}
|
|
item = bhdr_next_item(item, &bhdr_length);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static int bhdr_copy_fw(struct fwhdr_if *interface, char *fw_data, size_t fw_size, void *dram_addr)
|
|
{
|
|
struct bhdr *bhdr = bhdr_from_fwhdr_if(interface);
|
|
|
|
bhdr->fw_dram_addr = (char *)dram_addr;
|
|
bhdr->fw_size = fw_size;
|
|
|
|
memcpy((void *)((uintptr_t)dram_addr + bhdr->bt_fw_offset_val), fw_data, fw_size);
|
|
|
|
/* Bit of 'paranoia' here, but make sure FW is copied over and visible
|
|
* for all CPUs*/
|
|
smp_mb();
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int bhdr_init(struct fwhdr_if *interface, char *fw_data, size_t fw_len, bool skip_header)
|
|
{
|
|
const void *bt_fw;
|
|
const void *bt_res;
|
|
struct bhdr *bhdr = bhdr_from_fwhdr_if(interface);
|
|
|
|
bt_fw = (const void *)((uintptr_t)fw_data);
|
|
bt_res = bhdr_lookup_tag(bt_fw, BHDR_TAG_FW_OFFSET, NULL);
|
|
if (!bt_res)
|
|
return -EIO;
|
|
bhdr->bt_fw_offset_val = *(uint32_t *)bt_res;
|
|
bt_res = bhdr_lookup_tag(bt_fw, BHDR_TAG_FW_RUNTIME_SIZE, NULL);
|
|
if (!bt_res)
|
|
return -EIO;
|
|
bhdr->bt_fw_runtime_size_val = *(uint32_t *)bt_res;
|
|
|
|
bt_res = bhdr_lookup_tag(bt_fw, BHDR_TAG_SYSTEM_ERROR_RECORD_OFFSET, NULL);
|
|
if (!bt_res)
|
|
return -EIO;
|
|
bhdr->bt_panic_record_offset = *(uint32_t *)bt_res;
|
|
|
|
SCSC_TAG_DBG4(FW_LOAD, "BT fw_len 0x%x runtime_size 0x%x offset 0x%x panic_record_val 0x%x from start of Shared DRAM\n", fw_len,
|
|
bhdr->bt_fw_runtime_size_val, bhdr->bt_fw_offset_val, bhdr->bt_panic_record_offset);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static u32 bhdr_get_fw_rt_len(struct fwhdr_if *interface)
|
|
{
|
|
struct bhdr *bhdr = bhdr_from_fwhdr_if(interface);
|
|
|
|
return bhdr->bt_fw_runtime_size_val;
|
|
}
|
|
|
|
static u32 bhdr_get_fw_offset(struct fwhdr_if *interface)
|
|
{
|
|
struct bhdr *bhdr = bhdr_from_fwhdr_if(interface);
|
|
|
|
return bhdr->bt_fw_offset_val;
|
|
}
|
|
|
|
static u32 bhdr_get_panic_record_offset(struct fwhdr_if *interface, enum scsc_mif_abs_target target)
|
|
{
|
|
struct bhdr *bhdr = bhdr_from_fwhdr_if(interface);
|
|
|
|
return bhdr->bt_panic_record_offset;
|
|
}
|
|
|
|
/* Implementation creation */
|
|
struct fwhdr_if *bhdr_create(void)
|
|
{
|
|
struct fwhdr_if *fw_if;
|
|
struct bhdr *bhdr = kzalloc(sizeof(struct bhdr), GFP_KERNEL);
|
|
|
|
if (!bhdr)
|
|
return NULL;
|
|
|
|
fw_if = &bhdr->fw_if;
|
|
fw_if->init = bhdr_init;
|
|
fw_if->get_panic_record_offset = bhdr_get_panic_record_offset;
|
|
fw_if->get_fw_rt_len = bhdr_get_fw_rt_len;
|
|
fw_if->get_fw_offset = bhdr_get_fw_offset;
|
|
fw_if->copy_fw = bhdr_copy_fw;
|
|
|
|
return fw_if;
|
|
}
|
|
|
|
/* Implementation destroy */
|
|
void bhdr_destroy(struct fwhdr_if *interface)
|
|
{
|
|
struct bhdr *bhdr = bhdr_from_fwhdr_if(interface);
|
|
struct fwhdr_if *fw_if;
|
|
|
|
if (!bhdr)
|
|
return;
|
|
|
|
fw_if = &bhdr->fw_if;
|
|
kfree(bhdr);
|
|
}
|