Meng Wang | 43bbb87 | 2018-12-10 12:32:05 +0800 | [diff] [blame^] | 1 | // SPDX-License-Identifier: GPL-2.0-only |
Asish Bhattacharya | 8e2277f | 2017-07-20 18:31:55 +0530 | [diff] [blame] | 2 | /* |
Xiaoyu Ye | 90fa02a | 2018-01-09 12:27:29 -0800 | [diff] [blame] | 3 | * Copyright (c) 2016-2018, The Linux Foundation. All rights reserved. |
Asish Bhattacharya | 8e2277f | 2017-07-20 18:31:55 +0530 | [diff] [blame] | 4 | */ |
| 5 | |
| 6 | #include <linux/device.h> |
| 7 | #include <linux/err.h> |
| 8 | #include <linux/firmware.h> |
| 9 | #include <linux/elf.h> |
| 10 | #include <linux/slab.h> |
| 11 | #include <linux/list.h> |
| 12 | #include "wcd-dsp-utils.h" |
| 13 | |
| 14 | static bool wdsp_is_valid_elf_hdr(const struct elf32_hdr *ehdr, |
| 15 | size_t fw_size) |
| 16 | { |
| 17 | if (fw_size < sizeof(*ehdr)) { |
| 18 | pr_err("%s: Firmware too small\n", __func__); |
| 19 | goto elf_check_fail; |
| 20 | } |
| 21 | |
| 22 | if (memcmp(ehdr->e_ident, ELFMAG, SELFMAG) != 0) { |
| 23 | pr_err("%s: Not an ELF file\n", __func__); |
| 24 | goto elf_check_fail; |
| 25 | } |
| 26 | |
| 27 | if (ehdr->e_type != ET_EXEC && ehdr->e_type != ET_DYN) { |
| 28 | pr_err("%s: Not an executable image\n", __func__); |
| 29 | goto elf_check_fail; |
| 30 | } |
| 31 | |
| 32 | if (ehdr->e_phnum == 0) { |
| 33 | pr_err("%s: no segments to load\n", __func__); |
| 34 | goto elf_check_fail; |
| 35 | } |
| 36 | |
| 37 | if (sizeof(struct elf32_phdr) * ehdr->e_phnum + |
| 38 | sizeof(struct elf32_hdr) > fw_size) { |
| 39 | pr_err("%s: Too small MDT file\n", __func__); |
| 40 | goto elf_check_fail; |
| 41 | } |
| 42 | |
| 43 | return true; |
| 44 | |
| 45 | elf_check_fail: |
| 46 | return false; |
| 47 | } |
| 48 | |
| 49 | static int wdsp_add_segment_to_list(struct device *dev, |
| 50 | const char *img_fname, |
| 51 | const struct elf32_phdr *phdr, |
| 52 | int phdr_idx, |
| 53 | struct list_head *seg_list) |
| 54 | { |
| 55 | struct wdsp_img_segment *seg; |
| 56 | int ret = 0; |
| 57 | |
| 58 | /* Do not load segments with zero size */ |
| 59 | if (phdr->p_filesz == 0 || phdr->p_memsz == 0) |
| 60 | goto done; |
| 61 | |
| 62 | seg = kzalloc(sizeof(*seg), GFP_KERNEL); |
| 63 | if (!seg) { |
| 64 | ret = -ENOMEM; |
| 65 | goto done; |
| 66 | } |
| 67 | |
| 68 | snprintf(seg->split_fname, sizeof(seg->split_fname), |
| 69 | "%s.b%02d", img_fname, phdr_idx); |
| 70 | ret = request_firmware(&seg->split_fw, seg->split_fname, dev); |
| 71 | if (ret < 0) { |
| 72 | dev_err(dev, "%s: firmware %s not found\n", |
| 73 | __func__, seg->split_fname); |
| 74 | goto bad_seg; |
| 75 | } |
| 76 | |
Xiaoyu Ye | 90fa02a | 2018-01-09 12:27:29 -0800 | [diff] [blame] | 77 | if (phdr->p_filesz != seg->split_fw->size) { |
| 78 | dev_err(dev, |
| 79 | "%s: %s size mismatch, phdr_size: 0x%x fw_size: 0x%zx", |
| 80 | __func__, seg->split_fname, phdr->p_filesz, |
| 81 | seg->split_fw->size); |
| 82 | ret = -EINVAL; |
| 83 | goto bad_elf; |
| 84 | } |
| 85 | |
Asish Bhattacharya | 8e2277f | 2017-07-20 18:31:55 +0530 | [diff] [blame] | 86 | seg->load_addr = phdr->p_paddr; |
| 87 | seg->size = phdr->p_filesz; |
| 88 | seg->data = (u8 *) seg->split_fw->data; |
| 89 | |
| 90 | list_add_tail(&seg->list, seg_list); |
| 91 | done: |
| 92 | return ret; |
Xiaoyu Ye | 90fa02a | 2018-01-09 12:27:29 -0800 | [diff] [blame] | 93 | bad_elf: |
| 94 | release_firmware(seg->split_fw); |
Asish Bhattacharya | 8e2277f | 2017-07-20 18:31:55 +0530 | [diff] [blame] | 95 | bad_seg: |
| 96 | kfree(seg); |
| 97 | return ret; |
| 98 | } |
| 99 | |
| 100 | /* |
| 101 | * wdsp_flush_segment_list: Flush the list of segments |
| 102 | * @seg_list: List of segments to be flushed |
| 103 | * This API will traverse through the list of segments provided in |
| 104 | * seg_list, release the firmware for each segment and delete the |
| 105 | * segment from the list. |
| 106 | */ |
| 107 | void wdsp_flush_segment_list(struct list_head *seg_list) |
| 108 | { |
| 109 | struct wdsp_img_segment *seg, *next; |
| 110 | |
| 111 | list_for_each_entry_safe(seg, next, seg_list, list) { |
| 112 | release_firmware(seg->split_fw); |
| 113 | list_del(&seg->list); |
| 114 | kfree(seg); |
| 115 | } |
| 116 | } |
| 117 | EXPORT_SYMBOL(wdsp_flush_segment_list); |
| 118 | |
| 119 | /* |
| 120 | * wdsp_get_segment_list: Get the list of requested segments |
| 121 | * @dev: struct device pointer of caller |
| 122 | * @img_fname: Image name for the mdt and split firmware files |
| 123 | * @segment_type: Requested segment type, should be either |
| 124 | * WDSP_ELF_FLAG_RE or WDSP_ELF_FLAG_WRITE |
| 125 | * @seg_list: An initialized head for list of segmented to be returned |
| 126 | * @entry_point: Pointer to return the entry point of the image |
| 127 | * This API will parse the mdt file for img_fname and create |
| 128 | * an struct wdsp_img_segment for each segment that matches segment_type |
| 129 | * and add this structure to list pointed by seg_list |
| 130 | */ |
| 131 | int wdsp_get_segment_list(struct device *dev, |
| 132 | const char *img_fname, |
| 133 | unsigned int segment_type, |
| 134 | struct list_head *seg_list, |
| 135 | u32 *entry_point) |
| 136 | { |
| 137 | const struct firmware *fw; |
| 138 | const struct elf32_hdr *ehdr; |
| 139 | const struct elf32_phdr *phdr; |
| 140 | const u8 *elf_ptr; |
| 141 | char mdt_name[WDSP_IMG_NAME_LEN_MAX]; |
| 142 | int ret, phdr_idx; |
| 143 | bool segment_match; |
| 144 | |
| 145 | if (!dev) { |
| 146 | ret = -EINVAL; |
| 147 | pr_err("%s: Invalid device handle\n", __func__); |
| 148 | goto done; |
| 149 | } |
| 150 | |
| 151 | if (!img_fname || !seg_list || !entry_point) { |
| 152 | ret = -EINVAL; |
| 153 | dev_err(dev, "%s: Invalid input params\n", |
| 154 | __func__); |
| 155 | goto done; |
| 156 | } |
| 157 | |
| 158 | if (segment_type != WDSP_ELF_FLAG_RE && |
| 159 | segment_type != WDSP_ELF_FLAG_WRITE) { |
| 160 | dev_err(dev, "%s: Invalid request for segment_type %d\n", |
| 161 | __func__, segment_type); |
| 162 | ret = -EINVAL; |
| 163 | goto done; |
| 164 | } |
| 165 | |
| 166 | snprintf(mdt_name, sizeof(mdt_name), "%s.mdt", img_fname); |
| 167 | ret = request_firmware(&fw, mdt_name, dev); |
| 168 | if (ret < 0) { |
| 169 | dev_err(dev, "%s: firmware %s not found\n", |
| 170 | __func__, mdt_name); |
| 171 | goto done; |
| 172 | } |
| 173 | |
| 174 | ehdr = (struct elf32_hdr *) fw->data; |
| 175 | *entry_point = ehdr->e_entry; |
| 176 | if (!wdsp_is_valid_elf_hdr(ehdr, fw->size)) { |
| 177 | dev_err(dev, "%s: fw mdt %s is invalid\n", |
| 178 | __func__, mdt_name); |
| 179 | ret = -EINVAL; |
| 180 | goto bad_elf; |
| 181 | } |
| 182 | |
| 183 | elf_ptr = fw->data + sizeof(*ehdr); |
| 184 | for (phdr_idx = 0; phdr_idx < ehdr->e_phnum; phdr_idx++) { |
| 185 | phdr = (struct elf32_phdr *) elf_ptr; |
| 186 | segment_match = false; |
| 187 | |
| 188 | switch (segment_type) { |
| 189 | case WDSP_ELF_FLAG_RE: |
| 190 | /* |
| 191 | * Flag can be READ or EXECUTE or both but |
| 192 | * WRITE flag should not be set. |
| 193 | */ |
| 194 | if ((phdr->p_flags & segment_type) && |
| 195 | !(phdr->p_flags & WDSP_ELF_FLAG_WRITE)) |
| 196 | segment_match = true; |
| 197 | break; |
| 198 | case WDSP_ELF_FLAG_WRITE: |
| 199 | /* |
| 200 | * If WRITE flag is set, other flags do not |
| 201 | * matter. |
| 202 | */ |
| 203 | if (phdr->p_flags & segment_type) |
| 204 | segment_match = true; |
| 205 | break; |
| 206 | } |
| 207 | |
| 208 | if (segment_match) { |
| 209 | ret = wdsp_add_segment_to_list(dev, img_fname, phdr, |
| 210 | phdr_idx, seg_list); |
| 211 | if (ret < 0) { |
| 212 | wdsp_flush_segment_list(seg_list); |
| 213 | goto bad_elf; |
| 214 | } |
| 215 | } |
| 216 | elf_ptr = elf_ptr + sizeof(*phdr); |
| 217 | } |
| 218 | |
| 219 | bad_elf: |
| 220 | release_firmware(fw); |
| 221 | done: |
| 222 | return ret; |
| 223 | } |
| 224 | EXPORT_SYMBOL(wdsp_get_segment_list); |