diff options
Diffstat (limited to 'drivers/soc/qcom/mdt_loader.c')
-rw-r--r-- | drivers/soc/qcom/mdt_loader.c | 88 |
1 files changed, 85 insertions, 3 deletions
diff --git a/drivers/soc/qcom/mdt_loader.c b/drivers/soc/qcom/mdt_loader.c index 1c488024c698..1c970d6dd532 100644 --- a/drivers/soc/qcom/mdt_loader.c +++ b/drivers/soc/qcom/mdt_loader.c | |||
@@ -74,6 +74,66 @@ ssize_t qcom_mdt_get_size(const struct firmware *fw) | |||
74 | } | 74 | } |
75 | EXPORT_SYMBOL_GPL(qcom_mdt_get_size); | 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 | static int __qcom_mdt_load(struct device *dev, const struct firmware *fw, | 137 | static int __qcom_mdt_load(struct device *dev, const struct firmware *fw, |
78 | const char *firmware, int pas_id, void *mem_region, | 138 | const char *firmware, int pas_id, void *mem_region, |
79 | phys_addr_t mem_phys, size_t mem_size, | 139 | phys_addr_t mem_phys, size_t mem_size, |
@@ -86,12 +146,14 @@ static int __qcom_mdt_load(struct device *dev, const struct firmware *fw, | |||
86 | phys_addr_t mem_reloc; | 146 | phys_addr_t mem_reloc; |
87 | phys_addr_t min_addr = PHYS_ADDR_MAX; | 147 | phys_addr_t min_addr = PHYS_ADDR_MAX; |
88 | phys_addr_t max_addr = 0; | 148 | phys_addr_t max_addr = 0; |
149 | size_t metadata_len; | ||
89 | size_t fw_name_len; | 150 | size_t fw_name_len; |
90 | ssize_t offset; | 151 | ssize_t offset; |
152 | void *metadata; | ||
91 | char *fw_name; | 153 | char *fw_name; |
92 | bool relocate = false; | 154 | bool relocate = false; |
93 | void *ptr; | 155 | void *ptr; |
94 | int ret; | 156 | int ret = 0; |
95 | int i; | 157 | int i; |
96 | 158 | ||
97 | if (!fw || !mem_region || !mem_phys || !mem_size) | 159 | if (!fw || !mem_region || !mem_phys || !mem_size) |
@@ -109,7 +171,15 @@ static int __qcom_mdt_load(struct device *dev, const struct firmware *fw, | |||
109 | return -ENOMEM; | 171 | return -ENOMEM; |
110 | 172 | ||
111 | if (pas_init) { | 173 | if (pas_init) { |
112 | ret = qcom_scm_pas_init_image(pas_id, fw->data, fw->size); | 174 | metadata = qcom_mdt_read_metadata(fw, &metadata_len); |
175 | if (IS_ERR(metadata)) { | ||
176 | ret = PTR_ERR(metadata); | ||
177 | goto out; | ||
178 | } | ||
179 | |||
180 | ret = qcom_scm_pas_init_image(pas_id, metadata, metadata_len); | ||
181 | |||
182 | kfree(metadata); | ||
113 | if (ret) { | 183 | if (ret) { |
114 | dev_err(dev, "invalid firmware metadata\n"); | 184 | dev_err(dev, "invalid firmware metadata\n"); |
115 | goto out; | 185 | goto out; |
@@ -170,7 +240,19 @@ static int __qcom_mdt_load(struct device *dev, const struct firmware *fw, | |||
170 | 240 | ||
171 | ptr = mem_region + offset; | 241 | ptr = mem_region + offset; |
172 | 242 | ||
173 | if (phdr->p_filesz) { | 243 | if (phdr->p_filesz && phdr->p_offset < fw->size) { |
244 | /* Firmware is large enough to be non-split */ | ||
245 | if (phdr->p_offset + phdr->p_filesz > fw->size) { | ||
246 | dev_err(dev, | ||
247 | "failed to load segment %d from truncated file %s\n", | ||
248 | i, firmware); | ||
249 | ret = -EINVAL; | ||
250 | break; | ||
251 | } | ||
252 | |||
253 | memcpy(ptr, fw->data + phdr->p_offset, phdr->p_filesz); | ||
254 | } else if (phdr->p_filesz) { | ||
255 | /* Firmware not large enough, load split-out segments */ | ||
174 | sprintf(fw_name + fw_name_len - 3, "b%02d", i); | 256 | sprintf(fw_name + fw_name_len - 3, "b%02d", i); |
175 | ret = request_firmware_into_buf(&seg_fw, fw_name, dev, | 257 | ret = request_firmware_into_buf(&seg_fw, fw_name, dev, |
176 | ptr, phdr->p_filesz); | 258 | ptr, phdr->p_filesz); |