1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * Qualcomm Peripheral Image Loader 4 * 5 * Copyright (C) 2016 Linaro Ltd 6 * Copyright (C) 2015 Sony Mobile Communications Inc 7 * Copyright (c) 2012-2013, The Linux Foundation. All rights reserved. 8 */ 9 10 #include <linux/device.h> 11 #include <linux/elf.h> 12 #include <linux/firmware.h> 13 #include <linux/kernel.h> 14 #include <linux/module.h> 15 #include <linux/qcom_scm.h> 16 #include <linux/sizes.h> 17 #include <linux/slab.h> 18 #include <linux/soc/qcom/mdt_loader.h> 19 20 static bool mdt_phdr_valid(const struct elf32_phdr *phdr) 21 { 22 if (phdr->p_type != PT_LOAD) 23 return false; 24 25 if ((phdr->p_flags & QCOM_MDT_TYPE_MASK) == QCOM_MDT_TYPE_HASH) 26 return false; 27 28 if (!phdr->p_memsz) 29 return false; 30 31 return true; 32 } 33 34 /** 35 * qcom_mdt_get_size() - acquire size of the memory region needed to load mdt 36 * @fw: firmware object for the mdt file 37 * 38 * Returns size of the loaded firmware blob, or -EINVAL on failure. 39 */ 40 ssize_t qcom_mdt_get_size(const struct firmware *fw) 41 { 42 const struct elf32_phdr *phdrs; 43 const struct elf32_phdr *phdr; 44 const struct elf32_hdr *ehdr; 45 phys_addr_t min_addr = PHYS_ADDR_MAX; 46 phys_addr_t max_addr = 0; 47 int i; 48 49 ehdr = (struct elf32_hdr *)fw->data; 50 phdrs = (struct elf32_phdr *)(ehdr + 1); 51 52 for (i = 0; i < ehdr->e_phnum; i++) { 53 phdr = &phdrs[i]; 54 55 if (!mdt_phdr_valid(phdr)) 56 continue; 57 58 if (phdr->p_paddr < min_addr) 59 min_addr = phdr->p_paddr; 60 61 if (phdr->p_paddr + phdr->p_memsz > max_addr) 62 max_addr = ALIGN(phdr->p_paddr + phdr->p_memsz, SZ_4K); 63 } 64 65 return min_addr < max_addr ? max_addr - min_addr : -EINVAL; 66 } 67 EXPORT_SYMBOL_GPL(qcom_mdt_get_size); 68 69 static int __qcom_mdt_load(struct device *dev, const struct firmware *fw, 70 const char *firmware, int pas_id, void *mem_region, 71 phys_addr_t mem_phys, size_t mem_size, 72 phys_addr_t *reloc_base, bool pas_init) 73 { 74 const struct elf32_phdr *phdrs; 75 const struct elf32_phdr *phdr; 76 const struct elf32_hdr *ehdr; 77 const struct firmware *seg_fw; 78 phys_addr_t mem_reloc; 79 phys_addr_t min_addr = PHYS_ADDR_MAX; 80 phys_addr_t max_addr = 0; 81 size_t fw_name_len; 82 ssize_t offset; 83 char *fw_name; 84 bool relocate = false; 85 void *ptr; 86 int ret; 87 int i; 88 89 if (!fw || !mem_region || !mem_phys || !mem_size) 90 return -EINVAL; 91 92 ehdr = (struct elf32_hdr *)fw->data; 93 phdrs = (struct elf32_phdr *)(ehdr + 1); 94 95 fw_name_len = strlen(firmware); 96 if (fw_name_len <= 4) 97 return -EINVAL; 98 99 fw_name = kstrdup(firmware, GFP_KERNEL); 100 if (!fw_name) 101 return -ENOMEM; 102 103 if (pas_init) { 104 ret = qcom_scm_pas_init_image(pas_id, fw->data, fw->size); 105 if (ret) { 106 dev_err(dev, "invalid firmware metadata\n"); 107 goto out; 108 } 109 } 110 111 for (i = 0; i < ehdr->e_phnum; i++) { 112 phdr = &phdrs[i]; 113 114 if (!mdt_phdr_valid(phdr)) 115 continue; 116 117 if (phdr->p_flags & QCOM_MDT_RELOCATABLE) 118 relocate = true; 119 120 if (phdr->p_paddr < min_addr) 121 min_addr = phdr->p_paddr; 122 123 if (phdr->p_paddr + phdr->p_memsz > max_addr) 124 max_addr = ALIGN(phdr->p_paddr + phdr->p_memsz, SZ_4K); 125 } 126 127 if (relocate) { 128 if (pas_init) { 129 ret = qcom_scm_pas_mem_setup(pas_id, mem_phys, 130 max_addr - min_addr); 131 if (ret) { 132 dev_err(dev, "unable to setup relocation\n"); 133 goto out; 134 } 135 } 136 137 /* 138 * The image is relocatable, so offset each segment based on 139 * the lowest segment address. 140 */ 141 mem_reloc = min_addr; 142 } else { 143 /* 144 * Image is not relocatable, so offset each segment based on 145 * the allocated physical chunk of memory. 146 */ 147 mem_reloc = mem_phys; 148 } 149 150 for (i = 0; i < ehdr->e_phnum; i++) { 151 phdr = &phdrs[i]; 152 153 if (!mdt_phdr_valid(phdr)) 154 continue; 155 156 offset = phdr->p_paddr - mem_reloc; 157 if (offset < 0 || offset + phdr->p_memsz > mem_size) { 158 dev_err(dev, "segment outside memory range\n"); 159 ret = -EINVAL; 160 break; 161 } 162 163 ptr = mem_region + offset; 164 165 if (phdr->p_filesz) { 166 sprintf(fw_name + fw_name_len - 3, "b%02d", i); 167 ret = request_firmware_into_buf(&seg_fw, fw_name, dev, 168 ptr, phdr->p_filesz); 169 if (ret) { 170 dev_err(dev, "failed to load %s\n", fw_name); 171 break; 172 } 173 174 release_firmware(seg_fw); 175 } 176 177 if (phdr->p_memsz > phdr->p_filesz) 178 memset(ptr + phdr->p_filesz, 0, phdr->p_memsz - phdr->p_filesz); 179 } 180 181 if (reloc_base) 182 *reloc_base = mem_reloc; 183 184 out: 185 kfree(fw_name); 186 187 return ret; 188 } 189 190 /** 191 * qcom_mdt_load() - load the firmware which header is loaded as fw 192 * @dev: device handle to associate resources with 193 * @fw: firmware object for the mdt file 194 * @firmware: name of the firmware, for construction of segment file names 195 * @pas_id: PAS identifier 196 * @mem_region: allocated memory region to load firmware into 197 * @mem_phys: physical address of allocated memory region 198 * @mem_size: size of the allocated memory region 199 * @reloc_base: adjusted physical address after relocation 200 * 201 * Returns 0 on success, negative errno otherwise. 202 */ 203 int qcom_mdt_load(struct device *dev, const struct firmware *fw, 204 const char *firmware, int pas_id, void *mem_region, 205 phys_addr_t mem_phys, size_t mem_size, 206 phys_addr_t *reloc_base) 207 { 208 return __qcom_mdt_load(dev, fw, firmware, pas_id, mem_region, mem_phys, 209 mem_size, reloc_base, true); 210 } 211 EXPORT_SYMBOL_GPL(qcom_mdt_load); 212 213 /** 214 * qcom_mdt_load_no_init() - load the firmware which header is loaded as fw 215 * @dev: device handle to associate resources with 216 * @fw: firmware object for the mdt file 217 * @firmware: name of the firmware, for construction of segment file names 218 * @pas_id: PAS identifier 219 * @mem_region: allocated memory region to load firmware into 220 * @mem_phys: physical address of allocated memory region 221 * @mem_size: size of the allocated memory region 222 * @reloc_base: adjusted physical address after relocation 223 * 224 * Returns 0 on success, negative errno otherwise. 225 */ 226 int qcom_mdt_load_no_init(struct device *dev, const struct firmware *fw, 227 const char *firmware, int pas_id, 228 void *mem_region, phys_addr_t mem_phys, 229 size_t mem_size, phys_addr_t *reloc_base) 230 { 231 return __qcom_mdt_load(dev, fw, firmware, pas_id, mem_region, mem_phys, 232 mem_size, reloc_base, false); 233 } 234 EXPORT_SYMBOL_GPL(qcom_mdt_load_no_init); 235 236 MODULE_DESCRIPTION("Firmware parser for Qualcomm MDT format"); 237 MODULE_LICENSE("GPL v2"); 238