Linux Audio

Check our new training course

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