wcd-dsp-utils.c 5.6 KB

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