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 /** 70 * qcom_mdt_read_metadata() - read header and metadata from mdt or mbn 71 * @fw: firmware of mdt header or mbn 72 * @data_len: length of the read metadata blob 73 * 74 * The mechanism that performs the authentication of the loading firmware 75 * expects an ELF header directly followed by the segment of hashes, with no 76 * padding inbetween. This function allocates a chunk of memory for this pair 77 * and copy the two pieces into the buffer. 78 * 79 * In the case of split firmware the hash is found directly following the ELF 80 * header, rather than at p_offset described by the second program header. 81 * 82 * The caller is responsible to free (kfree()) the returned pointer. 83 * 84 * Return: pointer to data, or ERR_PTR() 85 */ 86 void *qcom_mdt_read_metadata(const struct firmware *fw, size_t *data_len) 87 { 88 const struct elf32_phdr *phdrs; 89 const struct elf32_hdr *ehdr; 90 size_t hash_offset; 91 size_t hash_size; 92 size_t ehdr_size; 93 void *data; 94 95 ehdr = (struct elf32_hdr *)fw->data; 96 phdrs = (struct elf32_phdr *)(ehdr + 1); 97 98 if (ehdr->e_phnum < 2) 99 return ERR_PTR(-EINVAL); 100 101 if (phdrs[0].p_type == PT_LOAD || phdrs[1].p_type == PT_LOAD) 102 return ERR_PTR(-EINVAL); 103 104 if ((phdrs[1].p_flags & QCOM_MDT_TYPE_MASK) != QCOM_MDT_TYPE_HASH) 105 return ERR_PTR(-EINVAL); 106 107 ehdr_size = phdrs[0].p_filesz; 108 hash_size = phdrs[1].p_filesz; 109 110 data = kmalloc(ehdr_size + hash_size, GFP_KERNEL); 111 if (!data) 112 return ERR_PTR(-ENOMEM); 113 114 /* Is the header and hash already packed */ 115 if (ehdr_size + hash_size == fw->size) 116 hash_offset = phdrs[0].p_filesz; 117 else 118 hash_offset = phdrs[1].p_offset; 119 120 memcpy(data, fw->data, ehdr_size); 121 memcpy(data + ehdr_size, fw->data + hash_offset, hash_size); 122 123 *data_len = ehdr_size + hash_size; 124 125 return data; 126 } 127 EXPORT_SYMBOL_GPL(qcom_mdt_read_metadata); 128 129 static int __qcom_mdt_load(struct device *dev, const struct firmware *fw, 130 const char *firmware, int pas_id, void *mem_region, 131 phys_addr_t mem_phys, size_t mem_size, 132 phys_addr_t *reloc_base, bool pas_init) 133 { 134 const struct elf32_phdr *phdrs; 135 const struct elf32_phdr *phdr; 136 const struct elf32_hdr *ehdr; 137 const struct firmware *seg_fw; 138 phys_addr_t mem_reloc; 139 phys_addr_t min_addr = PHYS_ADDR_MAX; 140 phys_addr_t max_addr = 0; 141 size_t metadata_len; 142 size_t fw_name_len; 143 ssize_t offset; 144 void *metadata; 145 char *fw_name; 146 bool relocate = false; 147 void *ptr; 148 int ret = 0; 149 int i; 150 151 if (!fw || !mem_region || !mem_phys || !mem_size) 152 return -EINVAL; 153 154 ehdr = (struct elf32_hdr *)fw->data; 155 phdrs = (struct elf32_phdr *)(ehdr + 1); 156 157 fw_name_len = strlen(firmware); 158 if (fw_name_len <= 4) 159 return -EINVAL; 160 161 fw_name = kstrdup(firmware, GFP_KERNEL); 162 if (!fw_name) 163 return -ENOMEM; 164 165 if (pas_init) { 166 metadata = qcom_mdt_read_metadata(fw, &metadata_len); 167 if (IS_ERR(metadata)) { 168 ret = PTR_ERR(metadata); 169 goto out; 170 } 171 172 ret = qcom_scm_pas_init_image(pas_id, metadata, metadata_len); 173 174 kfree(metadata); 175 if (ret) { 176 dev_err(dev, "invalid firmware metadata\n"); 177 goto out; 178 } 179 } 180 181 for (i = 0; i < ehdr->e_phnum; i++) { 182 phdr = &phdrs[i]; 183 184 if (!mdt_phdr_valid(phdr)) 185 continue; 186 187 if (phdr->p_flags & QCOM_MDT_RELOCATABLE) 188 relocate = true; 189 190 if (phdr->p_paddr < min_addr) 191 min_addr = phdr->p_paddr; 192 193 if (phdr->p_paddr + phdr->p_memsz > max_addr) 194 max_addr = ALIGN(phdr->p_paddr + phdr->p_memsz, SZ_4K); 195 } 196 197 if (relocate) { 198 if (pas_init) { 199 ret = qcom_scm_pas_mem_setup(pas_id, mem_phys, 200 max_addr - min_addr); 201 if (ret) { 202 dev_err(dev, "unable to setup relocation\n"); 203 goto out; 204 } 205 } 206 207 /* 208 * The image is relocatable, so offset each segment based on 209 * the lowest segment address. 210 */ 211 mem_reloc = min_addr; 212 } else { 213 /* 214 * Image is not relocatable, so offset each segment based on 215 * the allocated physical chunk of memory. 216 */ 217 mem_reloc = mem_phys; 218 } 219 220 for (i = 0; i < ehdr->e_phnum; i++) { 221 phdr = &phdrs[i]; 222 223 if (!mdt_phdr_valid(phdr)) 224 continue; 225 226 offset = phdr->p_paddr - mem_reloc; 227 if (offset < 0 || offset + phdr->p_memsz > mem_size) { 228 dev_err(dev, "segment outside memory range\n"); 229 ret = -EINVAL; 230 break; 231 } 232 233 ptr = mem_region + offset; 234 235 if (phdr->p_filesz && phdr->p_offset < fw->size) { 236 /* Firmware is large enough to be non-split */ 237 if (phdr->p_offset + phdr->p_filesz > fw->size) { 238 dev_err(dev, 239 "failed to load segment %d from truncated file %s\n", 240 i, firmware); 241 ret = -EINVAL; 242 break; 243 } 244 245 memcpy(ptr, fw->data + phdr->p_offset, phdr->p_filesz); 246 } else if (phdr->p_filesz) { 247 /* Firmware not large enough, load split-out segments */ 248 sprintf(fw_name + fw_name_len - 3, "b%02d", i); 249 ret = request_firmware_into_buf(&seg_fw, fw_name, dev, 250 ptr, phdr->p_filesz); 251 if (ret) { 252 dev_err(dev, "failed to load %s\n", fw_name); 253 break; 254 } 255 256 release_firmware(seg_fw); 257 } 258 259 if (phdr->p_memsz > phdr->p_filesz) 260 memset(ptr + phdr->p_filesz, 0, phdr->p_memsz - phdr->p_filesz); 261 } 262 263 if (reloc_base) 264 *reloc_base = mem_reloc; 265 266 out: 267 kfree(fw_name); 268 269 return ret; 270 } 271 272 /** 273 * qcom_mdt_load() - load the firmware which header is loaded as fw 274 * @dev: device handle to associate resources with 275 * @fw: firmware object for the mdt file 276 * @firmware: name of the firmware, for construction of segment file names 277 * @pas_id: PAS identifier 278 * @mem_region: allocated memory region to load firmware into 279 * @mem_phys: physical address of allocated memory region 280 * @mem_size: size of the allocated memory region 281 * @reloc_base: adjusted physical address after relocation 282 * 283 * Returns 0 on success, negative errno otherwise. 284 */ 285 int qcom_mdt_load(struct device *dev, const struct firmware *fw, 286 const char *firmware, int pas_id, void *mem_region, 287 phys_addr_t mem_phys, size_t mem_size, 288 phys_addr_t *reloc_base) 289 { 290 return __qcom_mdt_load(dev, fw, firmware, pas_id, mem_region, mem_phys, 291 mem_size, reloc_base, true); 292 } 293 EXPORT_SYMBOL_GPL(qcom_mdt_load); 294 295 /** 296 * qcom_mdt_load_no_init() - load the firmware which header is loaded as fw 297 * @dev: device handle to associate resources with 298 * @fw: firmware object for the mdt file 299 * @firmware: name of the firmware, for construction of segment file names 300 * @pas_id: PAS identifier 301 * @mem_region: allocated memory region to load firmware into 302 * @mem_phys: physical address of allocated memory region 303 * @mem_size: size of the allocated memory region 304 * @reloc_base: adjusted physical address after relocation 305 * 306 * Returns 0 on success, negative errno otherwise. 307 */ 308 int qcom_mdt_load_no_init(struct device *dev, const struct firmware *fw, 309 const char *firmware, int pas_id, 310 void *mem_region, phys_addr_t mem_phys, 311 size_t mem_size, phys_addr_t *reloc_base) 312 { 313 return __qcom_mdt_load(dev, fw, firmware, pas_id, mem_region, mem_phys, 314 mem_size, reloc_base, false); 315 } 316 EXPORT_SYMBOL_GPL(qcom_mdt_load_no_init); 317 318 MODULE_DESCRIPTION("Firmware parser for Qualcomm MDT format"); 319 MODULE_LICENSE("GPL v2"); 320