Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

soc: qcom: mdt_loader: Support loading non-split images

In some software releases the firmware images are not split up with each
loadable segment in it's own file. Check the size of the loaded firmware
to see if it still contains each segment to be loaded, before falling
back to the split-out segments.

Acked-by: Andy Gross <agross@kernel.org>
Reviewed-by: Jeffrey Hugo <jeffrey.l.hugo@gmail.com>
Signed-off-by: Bjorn Andersson <bjorn.andersson@linaro.org>

+87 -3
+85 -3
drivers/soc/qcom/mdt_loader.c
··· 74 74 } 75 75 EXPORT_SYMBOL_GPL(qcom_mdt_get_size); 76 76 77 + /** 78 + * qcom_mdt_read_metadata() - read header and metadata from mdt or mbn 79 + * @fw: firmware of mdt header or mbn 80 + * @data_len: length of the read metadata blob 81 + * 82 + * The mechanism that performs the authentication of the loading firmware 83 + * expects an ELF header directly followed by the segment of hashes, with no 84 + * padding inbetween. This function allocates a chunk of memory for this pair 85 + * and copy the two pieces into the buffer. 86 + * 87 + * In the case of split firmware the hash is found directly following the ELF 88 + * header, rather than at p_offset described by the second program header. 89 + * 90 + * The caller is responsible to free (kfree()) the returned pointer. 91 + * 92 + * Return: pointer to data, or ERR_PTR() 93 + */ 94 + void *qcom_mdt_read_metadata(const struct firmware *fw, size_t *data_len) 95 + { 96 + const struct elf32_phdr *phdrs; 97 + const struct elf32_hdr *ehdr; 98 + size_t hash_offset; 99 + size_t hash_size; 100 + size_t ehdr_size; 101 + void *data; 102 + 103 + ehdr = (struct elf32_hdr *)fw->data; 104 + phdrs = (struct elf32_phdr *)(ehdr + 1); 105 + 106 + if (ehdr->e_phnum < 2) 107 + return ERR_PTR(-EINVAL); 108 + 109 + if (phdrs[0].p_type == PT_LOAD || phdrs[1].p_type == PT_LOAD) 110 + return ERR_PTR(-EINVAL); 111 + 112 + if ((phdrs[1].p_flags & QCOM_MDT_TYPE_MASK) != QCOM_MDT_TYPE_HASH) 113 + return ERR_PTR(-EINVAL); 114 + 115 + ehdr_size = phdrs[0].p_filesz; 116 + hash_size = phdrs[1].p_filesz; 117 + 118 + data = kmalloc(ehdr_size + hash_size, GFP_KERNEL); 119 + if (!data) 120 + return ERR_PTR(-ENOMEM); 121 + 122 + /* Is the header and hash already packed */ 123 + if (ehdr_size + hash_size == fw->size) 124 + hash_offset = phdrs[0].p_filesz; 125 + else 126 + hash_offset = phdrs[1].p_offset; 127 + 128 + memcpy(data, fw->data, ehdr_size); 129 + memcpy(data + ehdr_size, fw->data + hash_offset, hash_size); 130 + 131 + *data_len = ehdr_size + hash_size; 132 + 133 + return data; 134 + } 135 + EXPORT_SYMBOL_GPL(qcom_mdt_read_metadata); 136 + 77 137 static int __qcom_mdt_load(struct device *dev, const struct firmware *fw, 78 138 const char *firmware, int pas_id, void *mem_region, 79 139 phys_addr_t mem_phys, size_t mem_size, ··· 146 86 phys_addr_t mem_reloc; 147 87 phys_addr_t min_addr = PHYS_ADDR_MAX; 148 88 phys_addr_t max_addr = 0; 89 + size_t metadata_len; 149 90 size_t fw_name_len; 150 91 ssize_t offset; 92 + void *metadata; 151 93 char *fw_name; 152 94 bool relocate = false; 153 95 void *ptr; 154 - int ret; 96 + int ret = 0; 155 97 int i; 156 98 157 99 if (!fw || !mem_region || !mem_phys || !mem_size) ··· 171 109 return -ENOMEM; 172 110 173 111 if (pas_init) { 174 - ret = qcom_scm_pas_init_image(pas_id, fw->data, fw->size); 112 + metadata = qcom_mdt_read_metadata(fw, &metadata_len); 113 + if (IS_ERR(metadata)) { 114 + ret = PTR_ERR(metadata); 115 + goto out; 116 + } 117 + 118 + ret = qcom_scm_pas_init_image(pas_id, metadata, metadata_len); 119 + 120 + kfree(metadata); 175 121 if (ret) { 176 122 dev_err(dev, "invalid firmware metadata\n"); 177 123 goto out; ··· 240 170 241 171 ptr = mem_region + offset; 242 172 243 - if (phdr->p_filesz) { 173 + if (phdr->p_filesz && phdr->p_offset < fw->size) { 174 + /* Firmware is large enough to be non-split */ 175 + if (phdr->p_offset + phdr->p_filesz > fw->size) { 176 + dev_err(dev, 177 + "failed to load segment %d from truncated file %s\n", 178 + i, firmware); 179 + ret = -EINVAL; 180 + break; 181 + } 182 + 183 + memcpy(ptr, fw->data + phdr->p_offset, phdr->p_filesz); 184 + } else if (phdr->p_filesz) { 185 + /* Firmware not large enough, load split-out segments */ 244 186 sprintf(fw_name + fw_name_len - 3, "b%02d", i); 245 187 ret = request_firmware_into_buf(&seg_fw, fw_name, dev, 246 188 ptr, phdr->p_filesz);
+2
include/linux/soc/qcom/mdt_loader.h
··· 21 21 const char *fw_name, int pas_id, void *mem_region, 22 22 phys_addr_t mem_phys, size_t mem_size, 23 23 phys_addr_t *reloc_base); 24 + void *qcom_mdt_read_metadata(const struct firmware *fw, size_t *data_len); 25 + 24 26 #endif