blob: c157ab1c32ee912599e1f6840592ee0a3e50ad9e [file] [log] [blame]
Meng Wang43bbb872018-12-10 12:32:05 +08001// SPDX-License-Identifier: GPL-2.0-only
Asish Bhattacharya8e2277f2017-07-20 18:31:55 +05302/*
Xiaoyu Ye90fa02a2018-01-09 12:27:29 -08003 * Copyright (c) 2016-2018, The Linux Foundation. All rights reserved.
Asish Bhattacharya8e2277f2017-07-20 18:31:55 +05304 */
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
14static 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
45elf_check_fail:
46 return false;
47}
48
49static 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 Ye90fa02a2018-01-09 12:27:29 -080077 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 Bhattacharya8e2277f2017-07-20 18:31:55 +053086 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);
91done:
92 return ret;
Xiaoyu Ye90fa02a2018-01-09 12:27:29 -080093bad_elf:
94 release_firmware(seg->split_fw);
Asish Bhattacharya8e2277f2017-07-20 18:31:55 +053095bad_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 */
107void 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}
117EXPORT_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 */
131int 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
219bad_elf:
220 release_firmware(fw);
221done:
222 return ret;
223}
224EXPORT_SYMBOL(wdsp_get_segment_list);