cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

mdt_loader.c (11221B)


      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
     20static 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
     34static ssize_t mdt_load_split_segment(void *ptr, const struct elf32_phdr *phdrs,
     35				      unsigned int segment, const char *fw_name,
     36				      struct device *dev)
     37{
     38	const struct elf32_phdr *phdr = &phdrs[segment];
     39	const struct firmware *seg_fw;
     40	char *seg_name;
     41	ssize_t ret;
     42
     43	if (strlen(fw_name) < 4)
     44		return -EINVAL;
     45
     46	seg_name = kstrdup(fw_name, GFP_KERNEL);
     47	if (!seg_name)
     48		return -ENOMEM;
     49
     50	sprintf(seg_name + strlen(fw_name) - 3, "b%02d", segment);
     51	ret = request_firmware_into_buf(&seg_fw, seg_name, dev,
     52					ptr, phdr->p_filesz);
     53	if (ret) {
     54		dev_err(dev, "error %zd loading %s\n", ret, seg_name);
     55		kfree(seg_name);
     56		return ret;
     57	}
     58
     59	if (seg_fw->size != phdr->p_filesz) {
     60		dev_err(dev,
     61			"failed to load segment %d from truncated file %s\n",
     62			segment, seg_name);
     63		ret = -EINVAL;
     64	}
     65
     66	release_firmware(seg_fw);
     67	kfree(seg_name);
     68
     69	return ret;
     70}
     71
     72/**
     73 * qcom_mdt_get_size() - acquire size of the memory region needed to load mdt
     74 * @fw:		firmware object for the mdt file
     75 *
     76 * Returns size of the loaded firmware blob, or -EINVAL on failure.
     77 */
     78ssize_t qcom_mdt_get_size(const struct firmware *fw)
     79{
     80	const struct elf32_phdr *phdrs;
     81	const struct elf32_phdr *phdr;
     82	const struct elf32_hdr *ehdr;
     83	phys_addr_t min_addr = PHYS_ADDR_MAX;
     84	phys_addr_t max_addr = 0;
     85	int i;
     86
     87	ehdr = (struct elf32_hdr *)fw->data;
     88	phdrs = (struct elf32_phdr *)(ehdr + 1);
     89
     90	for (i = 0; i < ehdr->e_phnum; i++) {
     91		phdr = &phdrs[i];
     92
     93		if (!mdt_phdr_valid(phdr))
     94			continue;
     95
     96		if (phdr->p_paddr < min_addr)
     97			min_addr = phdr->p_paddr;
     98
     99		if (phdr->p_paddr + phdr->p_memsz > max_addr)
    100			max_addr = ALIGN(phdr->p_paddr + phdr->p_memsz, SZ_4K);
    101	}
    102
    103	return min_addr < max_addr ? max_addr - min_addr : -EINVAL;
    104}
    105EXPORT_SYMBOL_GPL(qcom_mdt_get_size);
    106
    107/**
    108 * qcom_mdt_read_metadata() - read header and metadata from mdt or mbn
    109 * @fw:		firmware of mdt header or mbn
    110 * @data_len:	length of the read metadata blob
    111 *
    112 * The mechanism that performs the authentication of the loading firmware
    113 * expects an ELF header directly followed by the segment of hashes, with no
    114 * padding inbetween. This function allocates a chunk of memory for this pair
    115 * and copy the two pieces into the buffer.
    116 *
    117 * In the case of split firmware the hash is found directly following the ELF
    118 * header, rather than at p_offset described by the second program header.
    119 *
    120 * The caller is responsible to free (kfree()) the returned pointer.
    121 *
    122 * Return: pointer to data, or ERR_PTR()
    123 */
    124void *qcom_mdt_read_metadata(const struct firmware *fw, size_t *data_len,
    125			     const char *fw_name, struct device *dev)
    126{
    127	const struct elf32_phdr *phdrs;
    128	const struct elf32_hdr *ehdr;
    129	unsigned int hash_segment = 0;
    130	size_t hash_offset;
    131	size_t hash_size;
    132	size_t ehdr_size;
    133	unsigned int i;
    134	ssize_t ret;
    135	void *data;
    136
    137	ehdr = (struct elf32_hdr *)fw->data;
    138	phdrs = (struct elf32_phdr *)(ehdr + 1);
    139
    140	if (ehdr->e_phnum < 2)
    141		return ERR_PTR(-EINVAL);
    142
    143	if (phdrs[0].p_type == PT_LOAD)
    144		return ERR_PTR(-EINVAL);
    145
    146	for (i = 1; i < ehdr->e_phnum; i++) {
    147		if ((phdrs[i].p_flags & QCOM_MDT_TYPE_MASK) == QCOM_MDT_TYPE_HASH) {
    148			hash_segment = i;
    149			break;
    150		}
    151	}
    152
    153	if (!hash_segment) {
    154		dev_err(dev, "no hash segment found in %s\n", fw_name);
    155		return ERR_PTR(-EINVAL);
    156	}
    157
    158	ehdr_size = phdrs[0].p_filesz;
    159	hash_size = phdrs[hash_segment].p_filesz;
    160
    161	data = kmalloc(ehdr_size + hash_size, GFP_KERNEL);
    162	if (!data)
    163		return ERR_PTR(-ENOMEM);
    164
    165	/* Copy ELF header */
    166	memcpy(data, fw->data, ehdr_size);
    167
    168	if (ehdr_size + hash_size == fw->size) {
    169		/* Firmware is split and hash is packed following the ELF header */
    170		hash_offset = phdrs[0].p_filesz;
    171		memcpy(data + ehdr_size, fw->data + hash_offset, hash_size);
    172	} else if (phdrs[hash_segment].p_offset + hash_size <= fw->size) {
    173		/* Hash is in its own segment, but within the loaded file */
    174		hash_offset = phdrs[hash_segment].p_offset;
    175		memcpy(data + ehdr_size, fw->data + hash_offset, hash_size);
    176	} else {
    177		/* Hash is in its own segment, beyond the loaded file */
    178		ret = mdt_load_split_segment(data + ehdr_size, phdrs, hash_segment, fw_name, dev);
    179		if (ret) {
    180			kfree(data);
    181			return ERR_PTR(ret);
    182		}
    183	}
    184
    185	*data_len = ehdr_size + hash_size;
    186
    187	return data;
    188}
    189EXPORT_SYMBOL_GPL(qcom_mdt_read_metadata);
    190
    191/**
    192 * qcom_mdt_pas_init() - initialize PAS region for firmware loading
    193 * @dev:	device handle to associate resources with
    194 * @fw:		firmware object for the mdt file
    195 * @firmware:	name of the firmware, for construction of segment file names
    196 * @pas_id:	PAS identifier
    197 * @mem_phys:	physical address of allocated memory region
    198 * @ctx:	PAS metadata context, to be released by caller
    199 *
    200 * Returns 0 on success, negative errno otherwise.
    201 */
    202int qcom_mdt_pas_init(struct device *dev, const struct firmware *fw,
    203		      const char *fw_name, int pas_id, phys_addr_t mem_phys,
    204		      struct qcom_scm_pas_metadata *ctx)
    205{
    206	const struct elf32_phdr *phdrs;
    207	const struct elf32_phdr *phdr;
    208	const struct elf32_hdr *ehdr;
    209	phys_addr_t min_addr = PHYS_ADDR_MAX;
    210	phys_addr_t max_addr = 0;
    211	size_t metadata_len;
    212	void *metadata;
    213	int ret;
    214	int i;
    215
    216	ehdr = (struct elf32_hdr *)fw->data;
    217	phdrs = (struct elf32_phdr *)(ehdr + 1);
    218
    219	for (i = 0; i < ehdr->e_phnum; i++) {
    220		phdr = &phdrs[i];
    221
    222		if (!mdt_phdr_valid(phdr))
    223			continue;
    224
    225		if (phdr->p_paddr < min_addr)
    226			min_addr = phdr->p_paddr;
    227
    228		if (phdr->p_paddr + phdr->p_memsz > max_addr)
    229			max_addr = ALIGN(phdr->p_paddr + phdr->p_memsz, SZ_4K);
    230	}
    231
    232	metadata = qcom_mdt_read_metadata(fw, &metadata_len, fw_name, dev);
    233	if (IS_ERR(metadata)) {
    234		ret = PTR_ERR(metadata);
    235		dev_err(dev, "error %d reading firmware %s metadata\n", ret, fw_name);
    236		goto out;
    237	}
    238
    239	ret = qcom_scm_pas_init_image(pas_id, metadata, metadata_len, ctx);
    240	kfree(metadata);
    241	if (ret) {
    242		/* Invalid firmware metadata */
    243		dev_err(dev, "error %d initializing firmware %s\n", ret, fw_name);
    244		goto out;
    245	}
    246
    247	ret = qcom_scm_pas_mem_setup(pas_id, mem_phys, max_addr - min_addr);
    248	if (ret) {
    249		/* Unable to set up relocation */
    250		dev_err(dev, "error %d setting up firmware %s\n", ret, fw_name);
    251		goto out;
    252	}
    253
    254out:
    255	return ret;
    256}
    257EXPORT_SYMBOL_GPL(qcom_mdt_pas_init);
    258
    259static int __qcom_mdt_load(struct device *dev, const struct firmware *fw,
    260			   const char *fw_name, int pas_id, void *mem_region,
    261			   phys_addr_t mem_phys, size_t mem_size,
    262			   phys_addr_t *reloc_base, bool pas_init)
    263{
    264	const struct elf32_phdr *phdrs;
    265	const struct elf32_phdr *phdr;
    266	const struct elf32_hdr *ehdr;
    267	phys_addr_t mem_reloc;
    268	phys_addr_t min_addr = PHYS_ADDR_MAX;
    269	ssize_t offset;
    270	bool relocate = false;
    271	void *ptr;
    272	int ret = 0;
    273	int i;
    274
    275	if (!fw || !mem_region || !mem_phys || !mem_size)
    276		return -EINVAL;
    277
    278	ehdr = (struct elf32_hdr *)fw->data;
    279	phdrs = (struct elf32_phdr *)(ehdr + 1);
    280
    281	for (i = 0; i < ehdr->e_phnum; i++) {
    282		phdr = &phdrs[i];
    283
    284		if (!mdt_phdr_valid(phdr))
    285			continue;
    286
    287		if (phdr->p_flags & QCOM_MDT_RELOCATABLE)
    288			relocate = true;
    289
    290		if (phdr->p_paddr < min_addr)
    291			min_addr = phdr->p_paddr;
    292	}
    293
    294	if (relocate) {
    295		/*
    296		 * The image is relocatable, so offset each segment based on
    297		 * the lowest segment address.
    298		 */
    299		mem_reloc = min_addr;
    300	} else {
    301		/*
    302		 * Image is not relocatable, so offset each segment based on
    303		 * the allocated physical chunk of memory.
    304		 */
    305		mem_reloc = mem_phys;
    306	}
    307
    308	for (i = 0; i < ehdr->e_phnum; i++) {
    309		phdr = &phdrs[i];
    310
    311		if (!mdt_phdr_valid(phdr))
    312			continue;
    313
    314		offset = phdr->p_paddr - mem_reloc;
    315		if (offset < 0 || offset + phdr->p_memsz > mem_size) {
    316			dev_err(dev, "segment outside memory range\n");
    317			ret = -EINVAL;
    318			break;
    319		}
    320
    321		if (phdr->p_filesz > phdr->p_memsz) {
    322			dev_err(dev,
    323				"refusing to load segment %d with p_filesz > p_memsz\n",
    324				i);
    325			ret = -EINVAL;
    326			break;
    327		}
    328
    329		ptr = mem_region + offset;
    330
    331		if (phdr->p_filesz && phdr->p_offset < fw->size &&
    332		    phdr->p_offset + phdr->p_filesz <= fw->size) {
    333			/* Firmware is large enough to be non-split */
    334			if (phdr->p_offset + phdr->p_filesz > fw->size) {
    335				dev_err(dev, "file %s segment %d would be truncated\n",
    336					fw_name, i);
    337				ret = -EINVAL;
    338				break;
    339			}
    340
    341			memcpy(ptr, fw->data + phdr->p_offset, phdr->p_filesz);
    342		} else if (phdr->p_filesz) {
    343			/* Firmware not large enough, load split-out segments */
    344			ret = mdt_load_split_segment(ptr, phdrs, i, fw_name, dev);
    345			if (ret)
    346				break;
    347		}
    348
    349		if (phdr->p_memsz > phdr->p_filesz)
    350			memset(ptr + phdr->p_filesz, 0, phdr->p_memsz - phdr->p_filesz);
    351	}
    352
    353	if (reloc_base)
    354		*reloc_base = mem_reloc;
    355
    356	return ret;
    357}
    358
    359/**
    360 * qcom_mdt_load() - load the firmware which header is loaded as fw
    361 * @dev:	device handle to associate resources with
    362 * @fw:		firmware object for the mdt file
    363 * @firmware:	name of the firmware, for construction of segment file names
    364 * @pas_id:	PAS identifier
    365 * @mem_region:	allocated memory region to load firmware into
    366 * @mem_phys:	physical address of allocated memory region
    367 * @mem_size:	size of the allocated memory region
    368 * @reloc_base:	adjusted physical address after relocation
    369 *
    370 * Returns 0 on success, negative errno otherwise.
    371 */
    372int qcom_mdt_load(struct device *dev, const struct firmware *fw,
    373		  const char *firmware, int pas_id, void *mem_region,
    374		  phys_addr_t mem_phys, size_t mem_size,
    375		  phys_addr_t *reloc_base)
    376{
    377	int ret;
    378
    379	ret = qcom_mdt_pas_init(dev, fw, firmware, pas_id, mem_phys, NULL);
    380	if (ret)
    381		return ret;
    382
    383	return __qcom_mdt_load(dev, fw, firmware, pas_id, mem_region, mem_phys,
    384			       mem_size, reloc_base, true);
    385}
    386EXPORT_SYMBOL_GPL(qcom_mdt_load);
    387
    388/**
    389 * qcom_mdt_load_no_init() - load the firmware which header is loaded as fw
    390 * @dev:	device handle to associate resources with
    391 * @fw:		firmware object for the mdt file
    392 * @firmware:	name of the firmware, for construction of segment file names
    393 * @pas_id:	PAS identifier
    394 * @mem_region:	allocated memory region to load firmware into
    395 * @mem_phys:	physical address of allocated memory region
    396 * @mem_size:	size of the allocated memory region
    397 * @reloc_base:	adjusted physical address after relocation
    398 *
    399 * Returns 0 on success, negative errno otherwise.
    400 */
    401int qcom_mdt_load_no_init(struct device *dev, const struct firmware *fw,
    402			  const char *firmware, int pas_id,
    403			  void *mem_region, phys_addr_t mem_phys,
    404			  size_t mem_size, phys_addr_t *reloc_base)
    405{
    406	return __qcom_mdt_load(dev, fw, firmware, pas_id, mem_region, mem_phys,
    407			       mem_size, reloc_base, false);
    408}
    409EXPORT_SYMBOL_GPL(qcom_mdt_load_no_init);
    410
    411MODULE_DESCRIPTION("Firmware parser for Qualcomm MDT format");
    412MODULE_LICENSE("GPL v2");