Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-only
0002 /*
0003  * Qualcomm Peripheral Image Loader
0004  *
0005  * Copyright (C) 2016 Linaro Ltd
0006  * Copyright (C) 2015 Sony Mobile Communications Inc
0007  * Copyright (c) 2012-2013, The Linux Foundation. All rights reserved.
0008  */
0009 
0010 #include <linux/device.h>
0011 #include <linux/elf.h>
0012 #include <linux/firmware.h>
0013 #include <linux/kernel.h>
0014 #include <linux/module.h>
0015 #include <linux/qcom_scm.h>
0016 #include <linux/sizes.h>
0017 #include <linux/slab.h>
0018 #include <linux/soc/qcom/mdt_loader.h>
0019 
0020 static bool mdt_phdr_valid(const struct elf32_phdr *phdr)
0021 {
0022     if (phdr->p_type != PT_LOAD)
0023         return false;
0024 
0025     if ((phdr->p_flags & QCOM_MDT_TYPE_MASK) == QCOM_MDT_TYPE_HASH)
0026         return false;
0027 
0028     if (!phdr->p_memsz)
0029         return false;
0030 
0031     return true;
0032 }
0033 
0034 static ssize_t mdt_load_split_segment(void *ptr, const struct elf32_phdr *phdrs,
0035                       unsigned int segment, const char *fw_name,
0036                       struct device *dev)
0037 {
0038     const struct elf32_phdr *phdr = &phdrs[segment];
0039     const struct firmware *seg_fw;
0040     char *seg_name;
0041     ssize_t ret;
0042 
0043     if (strlen(fw_name) < 4)
0044         return -EINVAL;
0045 
0046     seg_name = kstrdup(fw_name, GFP_KERNEL);
0047     if (!seg_name)
0048         return -ENOMEM;
0049 
0050     sprintf(seg_name + strlen(fw_name) - 3, "b%02d", segment);
0051     ret = request_firmware_into_buf(&seg_fw, seg_name, dev,
0052                     ptr, phdr->p_filesz);
0053     if (ret) {
0054         dev_err(dev, "error %zd loading %s\n", ret, seg_name);
0055         kfree(seg_name);
0056         return ret;
0057     }
0058 
0059     if (seg_fw->size != phdr->p_filesz) {
0060         dev_err(dev,
0061             "failed to load segment %d from truncated file %s\n",
0062             segment, seg_name);
0063         ret = -EINVAL;
0064     }
0065 
0066     release_firmware(seg_fw);
0067     kfree(seg_name);
0068 
0069     return ret;
0070 }
0071 
0072 /**
0073  * qcom_mdt_get_size() - acquire size of the memory region needed to load mdt
0074  * @fw:     firmware object for the mdt file
0075  *
0076  * Returns size of the loaded firmware blob, or -EINVAL on failure.
0077  */
0078 ssize_t qcom_mdt_get_size(const struct firmware *fw)
0079 {
0080     const struct elf32_phdr *phdrs;
0081     const struct elf32_phdr *phdr;
0082     const struct elf32_hdr *ehdr;
0083     phys_addr_t min_addr = PHYS_ADDR_MAX;
0084     phys_addr_t max_addr = 0;
0085     int i;
0086 
0087     ehdr = (struct elf32_hdr *)fw->data;
0088     phdrs = (struct elf32_phdr *)(ehdr + 1);
0089 
0090     for (i = 0; i < ehdr->e_phnum; i++) {
0091         phdr = &phdrs[i];
0092 
0093         if (!mdt_phdr_valid(phdr))
0094             continue;
0095 
0096         if (phdr->p_paddr < min_addr)
0097             min_addr = phdr->p_paddr;
0098 
0099         if (phdr->p_paddr + phdr->p_memsz > max_addr)
0100             max_addr = ALIGN(phdr->p_paddr + phdr->p_memsz, SZ_4K);
0101     }
0102 
0103     return min_addr < max_addr ? max_addr - min_addr : -EINVAL;
0104 }
0105 EXPORT_SYMBOL_GPL(qcom_mdt_get_size);
0106 
0107 /**
0108  * qcom_mdt_read_metadata() - read header and metadata from mdt or mbn
0109  * @fw:     firmware of mdt header or mbn
0110  * @data_len:   length of the read metadata blob
0111  * @fw_name:    name of the firmware, for construction of segment file names
0112  * @dev:    device handle to associate resources with
0113  *
0114  * The mechanism that performs the authentication of the loading firmware
0115  * expects an ELF header directly followed by the segment of hashes, with no
0116  * padding inbetween. This function allocates a chunk of memory for this pair
0117  * and copy the two pieces into the buffer.
0118  *
0119  * In the case of split firmware the hash is found directly following the ELF
0120  * header, rather than at p_offset described by the second program header.
0121  *
0122  * The caller is responsible to free (kfree()) the returned pointer.
0123  *
0124  * Return: pointer to data, or ERR_PTR()
0125  */
0126 void *qcom_mdt_read_metadata(const struct firmware *fw, size_t *data_len,
0127                  const char *fw_name, struct device *dev)
0128 {
0129     const struct elf32_phdr *phdrs;
0130     const struct elf32_hdr *ehdr;
0131     unsigned int hash_segment = 0;
0132     size_t hash_offset;
0133     size_t hash_size;
0134     size_t ehdr_size;
0135     unsigned int i;
0136     ssize_t ret;
0137     void *data;
0138 
0139     ehdr = (struct elf32_hdr *)fw->data;
0140     phdrs = (struct elf32_phdr *)(ehdr + 1);
0141 
0142     if (ehdr->e_phnum < 2)
0143         return ERR_PTR(-EINVAL);
0144 
0145     if (phdrs[0].p_type == PT_LOAD)
0146         return ERR_PTR(-EINVAL);
0147 
0148     for (i = 1; i < ehdr->e_phnum; i++) {
0149         if ((phdrs[i].p_flags & QCOM_MDT_TYPE_MASK) == QCOM_MDT_TYPE_HASH) {
0150             hash_segment = i;
0151             break;
0152         }
0153     }
0154 
0155     if (!hash_segment) {
0156         dev_err(dev, "no hash segment found in %s\n", fw_name);
0157         return ERR_PTR(-EINVAL);
0158     }
0159 
0160     ehdr_size = phdrs[0].p_filesz;
0161     hash_size = phdrs[hash_segment].p_filesz;
0162 
0163     data = kmalloc(ehdr_size + hash_size, GFP_KERNEL);
0164     if (!data)
0165         return ERR_PTR(-ENOMEM);
0166 
0167     /* Copy ELF header */
0168     memcpy(data, fw->data, ehdr_size);
0169 
0170     if (ehdr_size + hash_size == fw->size) {
0171         /* Firmware is split and hash is packed following the ELF header */
0172         hash_offset = phdrs[0].p_filesz;
0173         memcpy(data + ehdr_size, fw->data + hash_offset, hash_size);
0174     } else if (phdrs[hash_segment].p_offset + hash_size <= fw->size) {
0175         /* Hash is in its own segment, but within the loaded file */
0176         hash_offset = phdrs[hash_segment].p_offset;
0177         memcpy(data + ehdr_size, fw->data + hash_offset, hash_size);
0178     } else {
0179         /* Hash is in its own segment, beyond the loaded file */
0180         ret = mdt_load_split_segment(data + ehdr_size, phdrs, hash_segment, fw_name, dev);
0181         if (ret) {
0182             kfree(data);
0183             return ERR_PTR(ret);
0184         }
0185     }
0186 
0187     *data_len = ehdr_size + hash_size;
0188 
0189     return data;
0190 }
0191 EXPORT_SYMBOL_GPL(qcom_mdt_read_metadata);
0192 
0193 /**
0194  * qcom_mdt_pas_init() - initialize PAS region for firmware loading
0195  * @dev:    device handle to associate resources with
0196  * @fw:     firmware object for the mdt file
0197  * @fw_name:    name of the firmware, for construction of segment file names
0198  * @pas_id: PAS identifier
0199  * @mem_phys:   physical address of allocated memory region
0200  * @ctx:    PAS metadata context, to be released by caller
0201  *
0202  * Returns 0 on success, negative errno otherwise.
0203  */
0204 int qcom_mdt_pas_init(struct device *dev, const struct firmware *fw,
0205               const char *fw_name, int pas_id, phys_addr_t mem_phys,
0206               struct qcom_scm_pas_metadata *ctx)
0207 {
0208     const struct elf32_phdr *phdrs;
0209     const struct elf32_phdr *phdr;
0210     const struct elf32_hdr *ehdr;
0211     phys_addr_t min_addr = PHYS_ADDR_MAX;
0212     phys_addr_t max_addr = 0;
0213     size_t metadata_len;
0214     void *metadata;
0215     int ret;
0216     int i;
0217 
0218     ehdr = (struct elf32_hdr *)fw->data;
0219     phdrs = (struct elf32_phdr *)(ehdr + 1);
0220 
0221     for (i = 0; i < ehdr->e_phnum; i++) {
0222         phdr = &phdrs[i];
0223 
0224         if (!mdt_phdr_valid(phdr))
0225             continue;
0226 
0227         if (phdr->p_paddr < min_addr)
0228             min_addr = phdr->p_paddr;
0229 
0230         if (phdr->p_paddr + phdr->p_memsz > max_addr)
0231             max_addr = ALIGN(phdr->p_paddr + phdr->p_memsz, SZ_4K);
0232     }
0233 
0234     metadata = qcom_mdt_read_metadata(fw, &metadata_len, fw_name, dev);
0235     if (IS_ERR(metadata)) {
0236         ret = PTR_ERR(metadata);
0237         dev_err(dev, "error %d reading firmware %s metadata\n", ret, fw_name);
0238         goto out;
0239     }
0240 
0241     ret = qcom_scm_pas_init_image(pas_id, metadata, metadata_len, ctx);
0242     kfree(metadata);
0243     if (ret) {
0244         /* Invalid firmware metadata */
0245         dev_err(dev, "error %d initializing firmware %s\n", ret, fw_name);
0246         goto out;
0247     }
0248 
0249     ret = qcom_scm_pas_mem_setup(pas_id, mem_phys, max_addr - min_addr);
0250     if (ret) {
0251         /* Unable to set up relocation */
0252         dev_err(dev, "error %d setting up firmware %s\n", ret, fw_name);
0253         goto out;
0254     }
0255 
0256 out:
0257     return ret;
0258 }
0259 EXPORT_SYMBOL_GPL(qcom_mdt_pas_init);
0260 
0261 static int __qcom_mdt_load(struct device *dev, const struct firmware *fw,
0262                const char *fw_name, int pas_id, void *mem_region,
0263                phys_addr_t mem_phys, size_t mem_size,
0264                phys_addr_t *reloc_base, bool pas_init)
0265 {
0266     const struct elf32_phdr *phdrs;
0267     const struct elf32_phdr *phdr;
0268     const struct elf32_hdr *ehdr;
0269     phys_addr_t mem_reloc;
0270     phys_addr_t min_addr = PHYS_ADDR_MAX;
0271     ssize_t offset;
0272     bool relocate = false;
0273     void *ptr;
0274     int ret = 0;
0275     int i;
0276 
0277     if (!fw || !mem_region || !mem_phys || !mem_size)
0278         return -EINVAL;
0279 
0280     ehdr = (struct elf32_hdr *)fw->data;
0281     phdrs = (struct elf32_phdr *)(ehdr + 1);
0282 
0283     for (i = 0; i < ehdr->e_phnum; i++) {
0284         phdr = &phdrs[i];
0285 
0286         if (!mdt_phdr_valid(phdr))
0287             continue;
0288 
0289         if (phdr->p_flags & QCOM_MDT_RELOCATABLE)
0290             relocate = true;
0291 
0292         if (phdr->p_paddr < min_addr)
0293             min_addr = phdr->p_paddr;
0294     }
0295 
0296     if (relocate) {
0297         /*
0298          * The image is relocatable, so offset each segment based on
0299          * the lowest segment address.
0300          */
0301         mem_reloc = min_addr;
0302     } else {
0303         /*
0304          * Image is not relocatable, so offset each segment based on
0305          * the allocated physical chunk of memory.
0306          */
0307         mem_reloc = mem_phys;
0308     }
0309 
0310     for (i = 0; i < ehdr->e_phnum; i++) {
0311         phdr = &phdrs[i];
0312 
0313         if (!mdt_phdr_valid(phdr))
0314             continue;
0315 
0316         offset = phdr->p_paddr - mem_reloc;
0317         if (offset < 0 || offset + phdr->p_memsz > mem_size) {
0318             dev_err(dev, "segment outside memory range\n");
0319             ret = -EINVAL;
0320             break;
0321         }
0322 
0323         if (phdr->p_filesz > phdr->p_memsz) {
0324             dev_err(dev,
0325                 "refusing to load segment %d with p_filesz > p_memsz\n",
0326                 i);
0327             ret = -EINVAL;
0328             break;
0329         }
0330 
0331         ptr = mem_region + offset;
0332 
0333         if (phdr->p_filesz && phdr->p_offset < fw->size &&
0334             phdr->p_offset + phdr->p_filesz <= fw->size) {
0335             /* Firmware is large enough to be non-split */
0336             if (phdr->p_offset + phdr->p_filesz > fw->size) {
0337                 dev_err(dev, "file %s segment %d would be truncated\n",
0338                     fw_name, i);
0339                 ret = -EINVAL;
0340                 break;
0341             }
0342 
0343             memcpy(ptr, fw->data + phdr->p_offset, phdr->p_filesz);
0344         } else if (phdr->p_filesz) {
0345             /* Firmware not large enough, load split-out segments */
0346             ret = mdt_load_split_segment(ptr, phdrs, i, fw_name, dev);
0347             if (ret)
0348                 break;
0349         }
0350 
0351         if (phdr->p_memsz > phdr->p_filesz)
0352             memset(ptr + phdr->p_filesz, 0, phdr->p_memsz - phdr->p_filesz);
0353     }
0354 
0355     if (reloc_base)
0356         *reloc_base = mem_reloc;
0357 
0358     return ret;
0359 }
0360 
0361 /**
0362  * qcom_mdt_load() - load the firmware which header is loaded as fw
0363  * @dev:    device handle to associate resources with
0364  * @fw:     firmware object for the mdt file
0365  * @firmware:   name of the firmware, for construction of segment file names
0366  * @pas_id: PAS identifier
0367  * @mem_region: allocated memory region to load firmware into
0368  * @mem_phys:   physical address of allocated memory region
0369  * @mem_size:   size of the allocated memory region
0370  * @reloc_base: adjusted physical address after relocation
0371  *
0372  * Returns 0 on success, negative errno otherwise.
0373  */
0374 int qcom_mdt_load(struct device *dev, const struct firmware *fw,
0375           const char *firmware, int pas_id, void *mem_region,
0376           phys_addr_t mem_phys, size_t mem_size,
0377           phys_addr_t *reloc_base)
0378 {
0379     int ret;
0380 
0381     ret = qcom_mdt_pas_init(dev, fw, firmware, pas_id, mem_phys, NULL);
0382     if (ret)
0383         return ret;
0384 
0385     return __qcom_mdt_load(dev, fw, firmware, pas_id, mem_region, mem_phys,
0386                    mem_size, reloc_base, true);
0387 }
0388 EXPORT_SYMBOL_GPL(qcom_mdt_load);
0389 
0390 /**
0391  * qcom_mdt_load_no_init() - load the firmware which header is loaded as fw
0392  * @dev:    device handle to associate resources with
0393  * @fw:     firmware object for the mdt file
0394  * @firmware:   name of the firmware, for construction of segment file names
0395  * @pas_id: PAS identifier
0396  * @mem_region: allocated memory region to load firmware into
0397  * @mem_phys:   physical address of allocated memory region
0398  * @mem_size:   size of the allocated memory region
0399  * @reloc_base: adjusted physical address after relocation
0400  *
0401  * Returns 0 on success, negative errno otherwise.
0402  */
0403 int qcom_mdt_load_no_init(struct device *dev, const struct firmware *fw,
0404               const char *firmware, int pas_id,
0405               void *mem_region, phys_addr_t mem_phys,
0406               size_t mem_size, phys_addr_t *reloc_base)
0407 {
0408     return __qcom_mdt_load(dev, fw, firmware, pas_id, mem_region, mem_phys,
0409                    mem_size, reloc_base, false);
0410 }
0411 EXPORT_SYMBOL_GPL(qcom_mdt_load_no_init);
0412 
0413 MODULE_DESCRIPTION("Firmware parser for Qualcomm MDT format");
0414 MODULE_LICENSE("GPL v2");