// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2016-2018, The Linux Foundation. All rights reserved. * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. */ #include #include #include #include #include #include #include "wcd-dsp-utils.h" static bool wdsp_is_valid_elf_hdr(const struct elf32_hdr *ehdr, size_t fw_size) { if (fw_size < sizeof(*ehdr)) { pr_err_ratelimited("%s: Firmware too small\n", __func__); goto elf_check_fail; } if (memcmp(ehdr->e_ident, ELFMAG, SELFMAG) != 0) { pr_err_ratelimited("%s: Not an ELF file\n", __func__); goto elf_check_fail; } if (ehdr->e_type != ET_EXEC && ehdr->e_type != ET_DYN) { pr_err_ratelimited("%s: Not an executable image\n", __func__); goto elf_check_fail; } if (ehdr->e_phnum == 0) { pr_err_ratelimited("%s: no segments to load\n", __func__); goto elf_check_fail; } if (sizeof(struct elf32_phdr) * ehdr->e_phnum + sizeof(struct elf32_hdr) > fw_size) { pr_err_ratelimited("%s: Too small MDT file\n", __func__); goto elf_check_fail; } return true; elf_check_fail: return false; } static int wdsp_add_segment_to_list(struct device *dev, const char *img_fname, const struct elf32_phdr *phdr, int phdr_idx, struct list_head *seg_list) { struct wdsp_img_segment *seg; int ret = 0; /* Do not load segments with zero size */ if (phdr->p_filesz == 0 || phdr->p_memsz == 0) goto done; seg = kzalloc(sizeof(*seg), GFP_KERNEL); if (!seg) { ret = -ENOMEM; goto done; } snprintf(seg->split_fname, sizeof(seg->split_fname), "%s.b%02d", img_fname, phdr_idx); ret = request_firmware(&seg->split_fw, seg->split_fname, dev); if (ret < 0) { dev_err_ratelimited(dev, "%s: firmware %s not found\n", __func__, seg->split_fname); goto bad_seg; } if (phdr->p_filesz != seg->split_fw->size) { dev_err_ratelimited(dev, "%s: %s size mismatch, phdr_size: 0x%x fw_size: 0x%zx", __func__, seg->split_fname, phdr->p_filesz, seg->split_fw->size); ret = -EINVAL; goto bad_elf; } seg->load_addr = phdr->p_paddr; seg->size = phdr->p_filesz; seg->data = (u8 *) seg->split_fw->data; list_add_tail(&seg->list, seg_list); done: return ret; bad_elf: release_firmware(seg->split_fw); bad_seg: kfree(seg); return ret; } /* * wdsp_flush_segment_list: Flush the list of segments * @seg_list: List of segments to be flushed * This API will traverse through the list of segments provided in * seg_list, release the firmware for each segment and delete the * segment from the list. */ void wdsp_flush_segment_list(struct list_head *seg_list) { struct wdsp_img_segment *seg, *next; list_for_each_entry_safe(seg, next, seg_list, list) { release_firmware(seg->split_fw); list_del(&seg->list); kfree(seg); } } EXPORT_SYMBOL(wdsp_flush_segment_list); /* * wdsp_get_segment_list: Get the list of requested segments * @dev: struct device pointer of caller * @img_fname: Image name for the mdt and split firmware files * @segment_type: Requested segment type, should be either * WDSP_ELF_FLAG_RE or WDSP_ELF_FLAG_WRITE * @seg_list: An initialized head for list of segmented to be returned * @entry_point: Pointer to return the entry point of the image * This API will parse the mdt file for img_fname and create * an struct wdsp_img_segment for each segment that matches segment_type * and add this structure to list pointed by seg_list */ int wdsp_get_segment_list(struct device *dev, const char *img_fname, unsigned int segment_type, struct list_head *seg_list, u32 *entry_point) { const struct firmware *fw; const struct elf32_hdr *ehdr; const struct elf32_phdr *phdr; const u8 *elf_ptr; char mdt_name[WDSP_IMG_NAME_LEN_MAX]; int ret, phdr_idx; bool segment_match; if (!dev) { ret = -EINVAL; pr_err_ratelimited("%s: Invalid device handle\n", __func__); goto done; } if (!img_fname || !seg_list || !entry_point) { ret = -EINVAL; dev_err_ratelimited(dev, "%s: Invalid input params\n", __func__); goto done; } if (segment_type != WDSP_ELF_FLAG_RE && segment_type != WDSP_ELF_FLAG_WRITE) { dev_err_ratelimited(dev, "%s: Invalid request for segment_type %d\n", __func__, segment_type); ret = -EINVAL; goto done; } snprintf(mdt_name, sizeof(mdt_name), "%s.mdt", img_fname); ret = request_firmware(&fw, mdt_name, dev); if (ret < 0) { dev_err_ratelimited(dev, "%s: firmware %s not found\n", __func__, mdt_name); goto done; } ehdr = (struct elf32_hdr *) fw->data; *entry_point = ehdr->e_entry; if (!wdsp_is_valid_elf_hdr(ehdr, fw->size)) { dev_err_ratelimited(dev, "%s: fw mdt %s is invalid\n", __func__, mdt_name); ret = -EINVAL; goto bad_elf; } elf_ptr = fw->data + sizeof(*ehdr); for (phdr_idx = 0; phdr_idx < ehdr->e_phnum; phdr_idx++) { phdr = (struct elf32_phdr *) elf_ptr; segment_match = false; switch (segment_type) { case WDSP_ELF_FLAG_RE: /* * Flag can be READ or EXECUTE or both but * WRITE flag should not be set. */ if ((phdr->p_flags & segment_type) && !(phdr->p_flags & WDSP_ELF_FLAG_WRITE)) segment_match = true; break; case WDSP_ELF_FLAG_WRITE: /* * If WRITE flag is set, other flags do not * matter. */ if (phdr->p_flags & segment_type) segment_match = true; break; } if (segment_match) { ret = wdsp_add_segment_to_list(dev, img_fname, phdr, phdr_idx, seg_list); if (ret < 0) { wdsp_flush_segment_list(seg_list); goto bad_elf; } } elf_ptr = elf_ptr + sizeof(*phdr); } bad_elf: release_firmware(fw); done: return ret; } EXPORT_SYMBOL(wdsp_get_segment_list);