| |
| |
| /** @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 |
| } |