
replace all dev(pr)_err/info logs that could potentially flood kernel logs with ratelimit functions dev_err_ratelimited and dev_info_ratelimited Change-Id: I32dc6002dead1a07622978c4de63d541c01982fd Signed-off-by: Shazmaan Ali <quic_shazmaan@quicinc.com>
226 righe
5.6 KiB
C
226 righe
5.6 KiB
C
// 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 <linux/device.h>
|
|
#include <linux/err.h>
|
|
#include <linux/firmware.h>
|
|
#include <linux/elf.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/list.h>
|
|
#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);
|