blob: 2f2146ea02aff007a5e064895fbc968d31b1a014 [file] [log] [blame]
/** @file
* Contains all the functions to handle parsing and loading of PE firmware files.
*/
#include <linux/firmware.h>
#include "pfe_mod.h"
#include "pfe_firmware.h"
#include "pfe/pfe.h"
static Elf32_Shdr * get_elf_section_header(const struct firmware *fw, const char *section)
{
Elf32_Ehdr *elf_hdr = (Elf32_Ehdr *)fw->data;
Elf32_Shdr *shdr, *shdr_shstr;
Elf32_Off e_shoff = be32_to_cpu(elf_hdr->e_shoff);
Elf32_Half e_shentsize = be16_to_cpu(elf_hdr->e_shentsize);
Elf32_Half e_shnum = be16_to_cpu(elf_hdr->e_shnum);
Elf32_Half e_shstrndx = be16_to_cpu(elf_hdr->e_shstrndx);
Elf32_Off shstr_offset;
Elf32_Word sh_name;
const char *name;
int i;
/* Section header strings */
shdr_shstr = (Elf32_Shdr *)(fw->data + e_shoff + e_shstrndx * e_shentsize);
shstr_offset = be32_to_cpu(shdr_shstr->sh_offset);
for (i = 0; i < e_shnum; i++) {
shdr = (Elf32_Shdr *)(fw->data + e_shoff + i * e_shentsize);
sh_name = be32_to_cpu(shdr->sh_name);
name = (const char *)(fw->data + shstr_offset + sh_name);
if (!strcmp(name, section))
return shdr;
}
printk(KERN_ERR "%s: didn't find section %s\n", __func__, section);
return NULL;
}
static unsigned long get_elf_section(const struct firmware *fw, const char *section)
{
Elf32_Shdr *shdr = get_elf_section_header(fw, section);
if (shdr)
return be32_to_cpu(shdr->sh_addr);
else
return -1;
}
#if defined(FPP_DIAGNOSTICS)
static int pfe_get_diags_info(const struct firmware *fw, struct pfe_diags_info *diags_info)
{
Elf32_Shdr *shdr;
unsigned long offset, size;
shdr = get_elf_section_header(fw, ".pfe_diags_str");
if (shdr)
{
offset = be32_to_cpu(shdr->sh_offset);
size = be32_to_cpu(shdr->sh_size);
diags_info->diags_str_base = be32_to_cpu(shdr->sh_addr);
diags_info->diags_str_size = size;
diags_info->diags_str_array = pfe_kmalloc(size, GFP_KERNEL);
memcpy(diags_info->diags_str_array, fw->data+offset, size);
return 0;
} else
{
return -1;
}
}
#endif
static void pfe_check_version_info(const struct firmware *fw)
{
static char *version = NULL;
Elf32_Shdr *shdr = get_elf_section_header(fw, ".version");
if (shdr)
{
if(!version)
{
/* this is the first fw we load, use its version string as reference (whatever it is) */
version = (char *)(fw->data + be32_to_cpu(shdr->sh_offset));
printk(KERN_INFO "PFE binary version: %s\n", version);
}
else
{
/* already have loaded at least one firmware, check sequence can start now */
if(strcmp(version, (char *)(fw->data + be32_to_cpu(shdr->sh_offset))))
{
printk(KERN_INFO "WARNING: PFE firmware binaries from incompatible version\n");
}
}
}
else
{
/* version cannot be verified, a potential issue that should be reported */
printk(KERN_INFO "WARNING: PFE firmware binaries from incompatible version\n");
}
}
/** PFE elf firmware loader.
* Loads an elf firmware image into a list of PE's (specified using a bitmask)
*
* @param pe_mask Mask of PE id's to load firmware to
* @param fw Pointer to the firmware image
*
* @return 0 on sucess, a negative value on error
*
*/
int pfe_load_elf(int pe_mask, const struct firmware *fw)
{
Elf32_Ehdr *elf_hdr = (Elf32_Ehdr *)fw->data;
Elf32_Half sections = be16_to_cpu(elf_hdr->e_shnum);
Elf32_Shdr *shdr = (Elf32_Shdr *) (fw->data + be32_to_cpu(elf_hdr->e_shoff));
int id, section;
int rc;
printk(KERN_INFO "%s\n", __func__);
/* Some sanity checks */
if (strncmp(&elf_hdr->e_ident[EI_MAG0], ELFMAG, SELFMAG))
{
printk(KERN_ERR "%s: incorrect elf magic number\n", __func__);
return -EINVAL;
}
if (elf_hdr->e_ident[EI_CLASS] != ELFCLASS32)
{
printk(KERN_ERR "%s: incorrect elf class(%x)\n", __func__, elf_hdr->e_ident[EI_CLASS]);
return -EINVAL;
}
if (elf_hdr->e_ident[EI_DATA] != ELFDATA2MSB)
{
printk(KERN_ERR "%s: incorrect elf data(%x)\n", __func__, elf_hdr->e_ident[EI_DATA]);
return -EINVAL;
}
if (be16_to_cpu(elf_hdr->e_type) != ET_EXEC)
{
printk(KERN_ERR "%s: incorrect elf file type(%x)\n", __func__, be16_to_cpu(elf_hdr->e_type));
return -EINVAL;
}
for (section = 0; section < sections; section++, shdr++)
{
if (!(be32_to_cpu(shdr->sh_flags) & (SHF_WRITE | SHF_ALLOC | SHF_EXECINSTR)))
continue;
for (id = 0; id < MAX_PE; id++)
if (pe_mask & (1 << id))
{
rc = pe_load_elf_section(id, fw->data, shdr);
if (rc < 0)
goto err;
}
}
pfe_check_version_info(fw);
return 0;
err:
return rc;
}
/** PFE firmware initialization.
* Loads different firmware files from filesystem.
* Initializes PE IMEM/DMEM and UTIL-PE DDR
* Initializes control path symbol addresses (by looking them up in the elf firmware files
* Takes PE's out of reset
*
* @return 0 on sucess, a negative value on error
*
*/
int pfe_firmware_init(struct pfe *pfe)
{
const struct firmware *class_fw, *tmu_fw, *util_fw;
int rc = 0;
#if !defined(CONFIG_UTIL_DISABLED)
const char* util_fw_name;
#endif
printk(KERN_INFO "%s\n", __func__);
if (request_firmware(&class_fw, CLASS_FIRMWARE_FILENAME, pfe->dev)) {
printk(KERN_ERR "%s: request firmware %s failed\n", __func__, CLASS_FIRMWARE_FILENAME);
rc = -ETIMEDOUT;
goto err0;
}
if (request_firmware(&tmu_fw, TMU_FIRMWARE_FILENAME, pfe->dev)) {
printk(KERN_ERR "%s: request firmware %s failed\n", __func__, TMU_FIRMWARE_FILENAME);
rc = -ETIMEDOUT;
goto err1;
}
#if !defined(CONFIG_UTIL_DISABLED)
util_fw_name = (system_rev == 0) ? UTIL_REVA0_FIRMWARE_FILENAME : UTIL_FIRMWARE_FILENAME;
if (request_firmware(&util_fw, util_fw_name, pfe->dev)) {
printk(KERN_ERR "%s: request firmware %s failed\n", __func__, util_fw_name);
rc = -ETIMEDOUT;
goto err2;
}
#endif
rc = pfe_load_elf(CLASS_MASK, class_fw);
if (rc < 0) {
printk(KERN_ERR "%s: class firmware load failed\n", __func__);
goto err3;
}
pfe->ctrl.class_dmem_sh = get_elf_section(class_fw, ".dmem_sh");
pfe->ctrl.class_pe_lmem_sh = get_elf_section(class_fw, ".pe_lmem_sh");
#if defined(FPP_DIAGNOSTICS)
rc = pfe_get_diags_info(class_fw, &pfe->diags.class_diags_info);
if (rc < 0) {
printk (KERN_WARNING "PFE diags won't be available for class PEs\n");
rc = 0;
}
#endif
printk(KERN_INFO "%s: class firmware loaded %#lx %#lx\n", __func__, pfe->ctrl.class_dmem_sh, pfe->ctrl.class_pe_lmem_sh);
rc = pfe_load_elf(TMU_MASK, tmu_fw);
if (rc < 0) {
printk(KERN_ERR "%s: tmu firmware load failed\n", __func__);
goto err3;
}
pfe->ctrl.tmu_dmem_sh = get_elf_section(tmu_fw, ".dmem_sh");
printk(KERN_INFO "%s: tmu firmware loaded %#lx\n", __func__, pfe->ctrl.tmu_dmem_sh);
#if !defined(CONFIG_UTIL_DISABLED)
rc = pfe_load_elf(UTIL_MASK, util_fw);
if (rc < 0) {
printk(KERN_ERR "%s: util firmware load failed\n", __func__);
goto err3;
}
pfe->ctrl.util_dmem_sh = get_elf_section(util_fw, ".dmem_sh");
pfe->ctrl.util_ddr_sh = get_elf_section(util_fw, ".ddr_sh");
#if defined(FPP_DIAGNOSTICS)
rc = pfe_get_diags_info(util_fw, &pfe->diags.util_diags_info);
if (rc < 0) {
printk(KERN_WARNING "PFE diags won't be available for util PE\n");
rc = 0;
}
#endif
printk(KERN_INFO "%s: util firmware loaded %#lx\n", __func__, pfe->ctrl.util_dmem_sh);
util_enable();
#endif
tmu_enable(0xf);
class_enable();
err3:
#if !defined(CONFIG_UTIL_DISABLED)
release_firmware(util_fw);
err2:
#endif
release_firmware(tmu_fw);
err1:
release_firmware(class_fw);
err0:
return rc;
}
/** PFE firmware cleanup
* Puts PE's in reset
*
*
*/
void pfe_firmware_exit(struct pfe *pfe)
{
printk(KERN_INFO "%s\n", __func__);
class_disable();
tmu_disable(0xf);
#if !defined(CONFIG_UTIL_DISABLED)
util_disable();
#endif
}