/*
* Copyright 2015-2016 Freescale Semiconductor, Inc.
* Copyright 2017 NXP
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
/*
* @file
* Contains all the functions to handle parsing and loading of PE firmware
* files.
*/
#include
#include "pfe_mod.h"
#include "pfe_firmware.h"
#include "pfe/pfe.h"
static struct elf32_shdr *get_elf_section_header(const struct firmware *fw,
const char *section)
{
struct elf32_hdr *elf_hdr = (struct elf32_hdr *)fw->data;
struct elf32_shdr *shdr;
struct elf32_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 = (struct 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 = (struct 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;
}
pr_err("%s: didn't find section %s\n", __func__, section);
return NULL;
}
#if defined(CFG_DIAGS)
static int pfe_get_diags_info(const struct firmware *fw, struct pfe_diags_info
*diags_info)
{
struct 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 = 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;*/
static char *version;
struct 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));
pr_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)))) {
pr_info(
"WARNING: PFE firmware binaries from incompatible version\n");
}
}
} else {
/*
* version cannot be verified, a potential issue that should
* be reported
*/
pr_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 success, a negative value on error
*
*/
int pfe_load_elf(int pe_mask, const struct firmware *fw, struct pfe *pfe)
{
struct elf32_hdr *elf_hdr = (struct elf32_hdr *)fw->data;
Elf32_Half sections = be16_to_cpu(elf_hdr->e_shnum);
struct elf32_shdr *shdr = (struct elf32_shdr *)(fw->data +
be32_to_cpu(elf_hdr->e_shoff));
int id, section;
int rc;
pr_info("%s\n", __func__);
/* Some sanity checks */
if (strncmp(&elf_hdr->e_ident[EI_MAG0], ELFMAG, SELFMAG)) {
pr_err("%s: incorrect elf magic number\n", __func__);
return -EINVAL;
}
if (elf_hdr->e_ident[EI_CLASS] != ELFCLASS32) {
pr_err("%s: incorrect elf class(%x)\n", __func__,
elf_hdr->e_ident[EI_CLASS]);
return -EINVAL;
}
if (elf_hdr->e_ident[EI_DATA] != ELFDATA2MSB) {
pr_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) {
pr_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,
pfe->dev);
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 success, a negative value on error
*
*/
int pfe_firmware_init(struct pfe *pfe)
{
const struct firmware *class_fw, *tmu_fw;
int rc = 0;
#if !defined(CONFIG_FSL_PPFE_UTIL_DISABLED)
const char *util_fw_name;
const struct firmware *util_fw;
#endif
pr_info("%s\n", __func__);
if (request_firmware(&class_fw, CLASS_FIRMWARE_FILENAME, pfe->dev)) {
pr_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)) {
pr_err("%s: request firmware %s failed\n", __func__,
TMU_FIRMWARE_FILENAME);
rc = -ETIMEDOUT;
goto err1;
}
#if !defined(CONFIG_FSL_PPFE_UTIL_DISABLED)
util_fw_name = UTIL_FIRMWARE_FILENAME;
if (request_firmware(&util_fw, util_fw_name, pfe->dev)) {
pr_err("%s: request firmware %s failed\n", __func__,
util_fw_name);
rc = -ETIMEDOUT;
goto err2;
}
#endif
rc = pfe_load_elf(CLASS_MASK, class_fw, pfe);
if (rc < 0) {
pr_err("%s: class firmware load failed\n", __func__);
goto err3;
}
#if defined(CFG_DIAGS)
rc = pfe_get_diags_info(class_fw, &pfe->diags.class_diags_info);
if (rc < 0) {
pr_warn(
"PFE diags won't be available for class PEs\n");
rc = 0;
}
#endif
rc = pfe_load_elf(TMU_MASK, tmu_fw, pfe);
if (rc < 0) {
pr_err("%s: tmu firmware load failed\n", __func__);
goto err3;
}
#if !defined(CONFIG_FSL_PPFE_UTIL_DISABLED)
rc = pfe_load_elf(UTIL_MASK, util_fw, pfe);
if (rc < 0) {
pr_err("%s: util firmware load failed\n", __func__);
goto err3;
}
#if defined(CFG_DIAGS)
rc = pfe_get_diags_info(util_fw, &pfe->diags.util_diags_info);
if (rc < 0) {
pr_warn(
"PFE diags won't be available for util PE\n");
rc = 0;
}
#endif
util_enable();
#endif
tmu_enable(0xf);
class_enable();
err3:
#if !defined(CONFIG_FSL_PPFE_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)
{
pr_info("%s\n", __func__);
if (pe_reset_all(&pfe->ctrl) != 0)
pr_err("Error: Failed to stop PEs, PFE reload may not work correctly\n");
class_disable();
tmu_disable(0xf);
#if !defined(CONFIG_FSL_PPFE_UTIL_DISABLED)
util_disable();
#endif
}