18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Cell Broadband Engine OProfile Support
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * (C) Copyright IBM Corporation 2006
68c2ecf20Sopenharmony_ci *
78c2ecf20Sopenharmony_ci * Author: Maynard Johnson <maynardj@us.ibm.com>
88c2ecf20Sopenharmony_ci */
98c2ecf20Sopenharmony_ci
108c2ecf20Sopenharmony_ci/* The code in this source file is responsible for generating
118c2ecf20Sopenharmony_ci * vma-to-fileOffset maps for both overlay and non-overlay SPU
128c2ecf20Sopenharmony_ci * applications.
138c2ecf20Sopenharmony_ci */
148c2ecf20Sopenharmony_ci
158c2ecf20Sopenharmony_ci#include <linux/mm.h>
168c2ecf20Sopenharmony_ci#include <linux/string.h>
178c2ecf20Sopenharmony_ci#include <linux/uaccess.h>
188c2ecf20Sopenharmony_ci#include <linux/elf.h>
198c2ecf20Sopenharmony_ci#include <linux/slab.h>
208c2ecf20Sopenharmony_ci#include "pr_util.h"
218c2ecf20Sopenharmony_ci
228c2ecf20Sopenharmony_ci
238c2ecf20Sopenharmony_civoid vma_map_free(struct vma_to_fileoffset_map *map)
248c2ecf20Sopenharmony_ci{
258c2ecf20Sopenharmony_ci	while (map) {
268c2ecf20Sopenharmony_ci		struct vma_to_fileoffset_map *next = map->next;
278c2ecf20Sopenharmony_ci		kfree(map);
288c2ecf20Sopenharmony_ci		map = next;
298c2ecf20Sopenharmony_ci	}
308c2ecf20Sopenharmony_ci}
318c2ecf20Sopenharmony_ci
328c2ecf20Sopenharmony_ciunsigned int
338c2ecf20Sopenharmony_civma_map_lookup(struct vma_to_fileoffset_map *map, unsigned int vma,
348c2ecf20Sopenharmony_ci	       const struct spu *aSpu, int *grd_val)
358c2ecf20Sopenharmony_ci{
368c2ecf20Sopenharmony_ci	/*
378c2ecf20Sopenharmony_ci	 * Default the offset to the physical address + a flag value.
388c2ecf20Sopenharmony_ci	 * Addresses of dynamically generated code can't be found in the vma
398c2ecf20Sopenharmony_ci	 * map.  For those addresses the flagged value will be sent on to
408c2ecf20Sopenharmony_ci	 * the user space tools so they can be reported rather than just
418c2ecf20Sopenharmony_ci	 * thrown away.
428c2ecf20Sopenharmony_ci	 */
438c2ecf20Sopenharmony_ci	u32 offset = 0x10000000 + vma;
448c2ecf20Sopenharmony_ci	u32 ovly_grd;
458c2ecf20Sopenharmony_ci
468c2ecf20Sopenharmony_ci	for (; map; map = map->next) {
478c2ecf20Sopenharmony_ci		if (vma < map->vma || vma >= map->vma + map->size)
488c2ecf20Sopenharmony_ci			continue;
498c2ecf20Sopenharmony_ci
508c2ecf20Sopenharmony_ci		if (map->guard_ptr) {
518c2ecf20Sopenharmony_ci			ovly_grd = *(u32 *)(aSpu->local_store + map->guard_ptr);
528c2ecf20Sopenharmony_ci			if (ovly_grd != map->guard_val)
538c2ecf20Sopenharmony_ci				continue;
548c2ecf20Sopenharmony_ci			*grd_val = ovly_grd;
558c2ecf20Sopenharmony_ci		}
568c2ecf20Sopenharmony_ci		offset = vma - map->vma + map->offset;
578c2ecf20Sopenharmony_ci		break;
588c2ecf20Sopenharmony_ci	}
598c2ecf20Sopenharmony_ci
608c2ecf20Sopenharmony_ci	return offset;
618c2ecf20Sopenharmony_ci}
628c2ecf20Sopenharmony_ci
638c2ecf20Sopenharmony_cistatic struct vma_to_fileoffset_map *
648c2ecf20Sopenharmony_civma_map_add(struct vma_to_fileoffset_map *map, unsigned int vma,
658c2ecf20Sopenharmony_ci	    unsigned int size, unsigned int offset, unsigned int guard_ptr,
668c2ecf20Sopenharmony_ci	    unsigned int guard_val)
678c2ecf20Sopenharmony_ci{
688c2ecf20Sopenharmony_ci	struct vma_to_fileoffset_map *new = kzalloc(sizeof(*new), GFP_KERNEL);
698c2ecf20Sopenharmony_ci
708c2ecf20Sopenharmony_ci	if (!new) {
718c2ecf20Sopenharmony_ci		printk(KERN_ERR "SPU_PROF: %s, line %d: malloc failed\n",
728c2ecf20Sopenharmony_ci		       __func__, __LINE__);
738c2ecf20Sopenharmony_ci		vma_map_free(map);
748c2ecf20Sopenharmony_ci		return NULL;
758c2ecf20Sopenharmony_ci	}
768c2ecf20Sopenharmony_ci
778c2ecf20Sopenharmony_ci	new->next = map;
788c2ecf20Sopenharmony_ci	new->vma = vma;
798c2ecf20Sopenharmony_ci	new->size = size;
808c2ecf20Sopenharmony_ci	new->offset = offset;
818c2ecf20Sopenharmony_ci	new->guard_ptr = guard_ptr;
828c2ecf20Sopenharmony_ci	new->guard_val = guard_val;
838c2ecf20Sopenharmony_ci
848c2ecf20Sopenharmony_ci	return new;
858c2ecf20Sopenharmony_ci}
868c2ecf20Sopenharmony_ci
878c2ecf20Sopenharmony_ci
888c2ecf20Sopenharmony_ci/* Parse SPE ELF header and generate a list of vma_maps.
898c2ecf20Sopenharmony_ci * A pointer to the first vma_map in the generated list
908c2ecf20Sopenharmony_ci * of vma_maps is returned.  */
918c2ecf20Sopenharmony_cistruct vma_to_fileoffset_map *create_vma_map(const struct spu *aSpu,
928c2ecf20Sopenharmony_ci					     unsigned long __spu_elf_start)
938c2ecf20Sopenharmony_ci{
948c2ecf20Sopenharmony_ci	static const unsigned char expected[EI_PAD] = {
958c2ecf20Sopenharmony_ci		[EI_MAG0] = ELFMAG0,
968c2ecf20Sopenharmony_ci		[EI_MAG1] = ELFMAG1,
978c2ecf20Sopenharmony_ci		[EI_MAG2] = ELFMAG2,
988c2ecf20Sopenharmony_ci		[EI_MAG3] = ELFMAG3,
998c2ecf20Sopenharmony_ci		[EI_CLASS] = ELFCLASS32,
1008c2ecf20Sopenharmony_ci		[EI_DATA] = ELFDATA2MSB,
1018c2ecf20Sopenharmony_ci		[EI_VERSION] = EV_CURRENT,
1028c2ecf20Sopenharmony_ci		[EI_OSABI] = ELFOSABI_NONE
1038c2ecf20Sopenharmony_ci	};
1048c2ecf20Sopenharmony_ci
1058c2ecf20Sopenharmony_ci	int grd_val;
1068c2ecf20Sopenharmony_ci	struct vma_to_fileoffset_map *map = NULL;
1078c2ecf20Sopenharmony_ci	void __user *spu_elf_start = (void __user *)__spu_elf_start;
1088c2ecf20Sopenharmony_ci	struct spu_overlay_info ovly;
1098c2ecf20Sopenharmony_ci	unsigned int overlay_tbl_offset = -1;
1108c2ecf20Sopenharmony_ci	Elf32_Phdr __user *phdr_start;
1118c2ecf20Sopenharmony_ci	Elf32_Shdr __user *shdr_start;
1128c2ecf20Sopenharmony_ci	Elf32_Ehdr ehdr;
1138c2ecf20Sopenharmony_ci	Elf32_Phdr phdr;
1148c2ecf20Sopenharmony_ci	Elf32_Shdr shdr, shdr_str;
1158c2ecf20Sopenharmony_ci	Elf32_Sym sym;
1168c2ecf20Sopenharmony_ci	int i, j;
1178c2ecf20Sopenharmony_ci	char name[32];
1188c2ecf20Sopenharmony_ci
1198c2ecf20Sopenharmony_ci	unsigned int ovly_table_sym = 0;
1208c2ecf20Sopenharmony_ci	unsigned int ovly_buf_table_sym = 0;
1218c2ecf20Sopenharmony_ci	unsigned int ovly_table_end_sym = 0;
1228c2ecf20Sopenharmony_ci	unsigned int ovly_buf_table_end_sym = 0;
1238c2ecf20Sopenharmony_ci	struct spu_overlay_info __user *ovly_table;
1248c2ecf20Sopenharmony_ci	unsigned int n_ovlys;
1258c2ecf20Sopenharmony_ci
1268c2ecf20Sopenharmony_ci	/* Get and validate ELF header.	 */
1278c2ecf20Sopenharmony_ci
1288c2ecf20Sopenharmony_ci	if (copy_from_user(&ehdr, spu_elf_start, sizeof (ehdr)))
1298c2ecf20Sopenharmony_ci		goto fail;
1308c2ecf20Sopenharmony_ci
1318c2ecf20Sopenharmony_ci	if (memcmp(ehdr.e_ident, expected, EI_PAD) != 0) {
1328c2ecf20Sopenharmony_ci		printk(KERN_ERR "SPU_PROF: "
1338c2ecf20Sopenharmony_ci		       "%s, line %d: Unexpected e_ident parsing SPU ELF\n",
1348c2ecf20Sopenharmony_ci		       __func__, __LINE__);
1358c2ecf20Sopenharmony_ci		goto fail;
1368c2ecf20Sopenharmony_ci	}
1378c2ecf20Sopenharmony_ci	if (ehdr.e_machine != EM_SPU) {
1388c2ecf20Sopenharmony_ci		printk(KERN_ERR "SPU_PROF: "
1398c2ecf20Sopenharmony_ci		       "%s, line %d: Unexpected e_machine parsing SPU ELF\n",
1408c2ecf20Sopenharmony_ci		       __func__,  __LINE__);
1418c2ecf20Sopenharmony_ci		goto fail;
1428c2ecf20Sopenharmony_ci	}
1438c2ecf20Sopenharmony_ci	if (ehdr.e_type != ET_EXEC) {
1448c2ecf20Sopenharmony_ci		printk(KERN_ERR "SPU_PROF: "
1458c2ecf20Sopenharmony_ci		       "%s, line %d: Unexpected e_type parsing SPU ELF\n",
1468c2ecf20Sopenharmony_ci		       __func__, __LINE__);
1478c2ecf20Sopenharmony_ci		goto fail;
1488c2ecf20Sopenharmony_ci	}
1498c2ecf20Sopenharmony_ci	phdr_start = spu_elf_start + ehdr.e_phoff;
1508c2ecf20Sopenharmony_ci	shdr_start = spu_elf_start + ehdr.e_shoff;
1518c2ecf20Sopenharmony_ci
1528c2ecf20Sopenharmony_ci	/* Traverse program headers.  */
1538c2ecf20Sopenharmony_ci	for (i = 0; i < ehdr.e_phnum; i++) {
1548c2ecf20Sopenharmony_ci		if (copy_from_user(&phdr, phdr_start + i, sizeof(phdr)))
1558c2ecf20Sopenharmony_ci			goto fail;
1568c2ecf20Sopenharmony_ci
1578c2ecf20Sopenharmony_ci		if (phdr.p_type != PT_LOAD)
1588c2ecf20Sopenharmony_ci			continue;
1598c2ecf20Sopenharmony_ci		if (phdr.p_flags & (1 << 27))
1608c2ecf20Sopenharmony_ci			continue;
1618c2ecf20Sopenharmony_ci
1628c2ecf20Sopenharmony_ci		map = vma_map_add(map, phdr.p_vaddr, phdr.p_memsz,
1638c2ecf20Sopenharmony_ci				  phdr.p_offset, 0, 0);
1648c2ecf20Sopenharmony_ci		if (!map)
1658c2ecf20Sopenharmony_ci			goto fail;
1668c2ecf20Sopenharmony_ci	}
1678c2ecf20Sopenharmony_ci
1688c2ecf20Sopenharmony_ci	pr_debug("SPU_PROF: Created non-overlay maps\n");
1698c2ecf20Sopenharmony_ci	/* Traverse section table and search for overlay-related symbols.  */
1708c2ecf20Sopenharmony_ci	for (i = 0; i < ehdr.e_shnum; i++) {
1718c2ecf20Sopenharmony_ci		if (copy_from_user(&shdr, shdr_start + i, sizeof(shdr)))
1728c2ecf20Sopenharmony_ci			goto fail;
1738c2ecf20Sopenharmony_ci
1748c2ecf20Sopenharmony_ci		if (shdr.sh_type != SHT_SYMTAB)
1758c2ecf20Sopenharmony_ci			continue;
1768c2ecf20Sopenharmony_ci		if (shdr.sh_entsize != sizeof (sym))
1778c2ecf20Sopenharmony_ci			continue;
1788c2ecf20Sopenharmony_ci
1798c2ecf20Sopenharmony_ci		if (copy_from_user(&shdr_str,
1808c2ecf20Sopenharmony_ci				   shdr_start + shdr.sh_link,
1818c2ecf20Sopenharmony_ci				   sizeof(shdr)))
1828c2ecf20Sopenharmony_ci			goto fail;
1838c2ecf20Sopenharmony_ci
1848c2ecf20Sopenharmony_ci		if (shdr_str.sh_type != SHT_STRTAB)
1858c2ecf20Sopenharmony_ci			goto fail;
1868c2ecf20Sopenharmony_ci
1878c2ecf20Sopenharmony_ci		for (j = 0; j < shdr.sh_size / sizeof (sym); j++) {
1888c2ecf20Sopenharmony_ci			if (copy_from_user(&sym, spu_elf_start +
1898c2ecf20Sopenharmony_ci						 shdr.sh_offset +
1908c2ecf20Sopenharmony_ci						 j * sizeof (sym),
1918c2ecf20Sopenharmony_ci					   sizeof (sym)))
1928c2ecf20Sopenharmony_ci				goto fail;
1938c2ecf20Sopenharmony_ci
1948c2ecf20Sopenharmony_ci			if (copy_from_user(name,
1958c2ecf20Sopenharmony_ci					   spu_elf_start + shdr_str.sh_offset +
1968c2ecf20Sopenharmony_ci					   sym.st_name,
1978c2ecf20Sopenharmony_ci					   20))
1988c2ecf20Sopenharmony_ci				goto fail;
1998c2ecf20Sopenharmony_ci
2008c2ecf20Sopenharmony_ci			if (memcmp(name, "_ovly_table", 12) == 0)
2018c2ecf20Sopenharmony_ci				ovly_table_sym = sym.st_value;
2028c2ecf20Sopenharmony_ci			if (memcmp(name, "_ovly_buf_table", 16) == 0)
2038c2ecf20Sopenharmony_ci				ovly_buf_table_sym = sym.st_value;
2048c2ecf20Sopenharmony_ci			if (memcmp(name, "_ovly_table_end", 16) == 0)
2058c2ecf20Sopenharmony_ci				ovly_table_end_sym = sym.st_value;
2068c2ecf20Sopenharmony_ci			if (memcmp(name, "_ovly_buf_table_end", 20) == 0)
2078c2ecf20Sopenharmony_ci				ovly_buf_table_end_sym = sym.st_value;
2088c2ecf20Sopenharmony_ci		}
2098c2ecf20Sopenharmony_ci	}
2108c2ecf20Sopenharmony_ci
2118c2ecf20Sopenharmony_ci	/* If we don't have overlays, we're done.  */
2128c2ecf20Sopenharmony_ci	if (ovly_table_sym == 0 || ovly_buf_table_sym == 0
2138c2ecf20Sopenharmony_ci	    || ovly_table_end_sym == 0 || ovly_buf_table_end_sym == 0) {
2148c2ecf20Sopenharmony_ci		pr_debug("SPU_PROF: No overlay table found\n");
2158c2ecf20Sopenharmony_ci		goto out;
2168c2ecf20Sopenharmony_ci	} else {
2178c2ecf20Sopenharmony_ci		pr_debug("SPU_PROF: Overlay table found\n");
2188c2ecf20Sopenharmony_ci	}
2198c2ecf20Sopenharmony_ci
2208c2ecf20Sopenharmony_ci	/* The _ovly_table symbol represents a table with one entry
2218c2ecf20Sopenharmony_ci	 * per overlay section.	 The _ovly_buf_table symbol represents
2228c2ecf20Sopenharmony_ci	 * a table with one entry per overlay region.
2238c2ecf20Sopenharmony_ci	 * The struct spu_overlay_info gives the structure of the _ovly_table
2248c2ecf20Sopenharmony_ci	 * entries.  The structure of _ovly_table_buf is simply one
2258c2ecf20Sopenharmony_ci	 * u32 word per entry.
2268c2ecf20Sopenharmony_ci	 */
2278c2ecf20Sopenharmony_ci	overlay_tbl_offset = vma_map_lookup(map, ovly_table_sym,
2288c2ecf20Sopenharmony_ci					    aSpu, &grd_val);
2298c2ecf20Sopenharmony_ci	if (overlay_tbl_offset > 0x10000000) {
2308c2ecf20Sopenharmony_ci		printk(KERN_ERR "SPU_PROF: "
2318c2ecf20Sopenharmony_ci		       "%s, line %d: Error finding SPU overlay table\n",
2328c2ecf20Sopenharmony_ci		       __func__, __LINE__);
2338c2ecf20Sopenharmony_ci		goto fail;
2348c2ecf20Sopenharmony_ci	}
2358c2ecf20Sopenharmony_ci	ovly_table = spu_elf_start + overlay_tbl_offset;
2368c2ecf20Sopenharmony_ci
2378c2ecf20Sopenharmony_ci	n_ovlys = (ovly_table_end_sym -
2388c2ecf20Sopenharmony_ci		   ovly_table_sym) / sizeof (ovly);
2398c2ecf20Sopenharmony_ci
2408c2ecf20Sopenharmony_ci	/* Traverse overlay table.  */
2418c2ecf20Sopenharmony_ci	for (i = 0; i < n_ovlys; i++) {
2428c2ecf20Sopenharmony_ci		if (copy_from_user(&ovly, ovly_table + i, sizeof (ovly)))
2438c2ecf20Sopenharmony_ci			goto fail;
2448c2ecf20Sopenharmony_ci
2458c2ecf20Sopenharmony_ci		/* The ovly.vma/size/offset arguments are analogous to the same
2468c2ecf20Sopenharmony_ci		 * arguments used above for non-overlay maps.  The final two
2478c2ecf20Sopenharmony_ci		 * args are referred to as the guard pointer and the guard
2488c2ecf20Sopenharmony_ci		 * value.
2498c2ecf20Sopenharmony_ci		 * The guard pointer is an entry in the _ovly_buf_table,
2508c2ecf20Sopenharmony_ci		 * computed using ovly.buf as the index into the table.	 Since
2518c2ecf20Sopenharmony_ci		 * ovly.buf values begin at '1' to reference the first (or 0th)
2528c2ecf20Sopenharmony_ci		 * entry in the _ovly_buf_table, the computation subtracts 1
2538c2ecf20Sopenharmony_ci		 * from ovly.buf.
2548c2ecf20Sopenharmony_ci		 * The guard value is stored in the _ovly_buf_table entry and
2558c2ecf20Sopenharmony_ci		 * is an index (starting at 1) back to the _ovly_table entry
2568c2ecf20Sopenharmony_ci		 * that is pointing at this _ovly_buf_table entry.  So, for
2578c2ecf20Sopenharmony_ci		 * example, for an overlay scenario with one overlay segment
2588c2ecf20Sopenharmony_ci		 * and two overlay sections:
2598c2ecf20Sopenharmony_ci		 *	- Section 1 points to the first entry of the
2608c2ecf20Sopenharmony_ci		 *	  _ovly_buf_table, which contains a guard value
2618c2ecf20Sopenharmony_ci		 *	  of '1', referencing the first (index=0) entry of
2628c2ecf20Sopenharmony_ci		 *	  _ovly_table.
2638c2ecf20Sopenharmony_ci		 *	- Section 2 points to the second entry of the
2648c2ecf20Sopenharmony_ci		 *	  _ovly_buf_table, which contains a guard value
2658c2ecf20Sopenharmony_ci		 *	  of '2', referencing the second (index=1) entry of
2668c2ecf20Sopenharmony_ci		 *	  _ovly_table.
2678c2ecf20Sopenharmony_ci		 */
2688c2ecf20Sopenharmony_ci		map = vma_map_add(map, ovly.vma, ovly.size, ovly.offset,
2698c2ecf20Sopenharmony_ci				  ovly_buf_table_sym + (ovly.buf-1) * 4, i+1);
2708c2ecf20Sopenharmony_ci		if (!map)
2718c2ecf20Sopenharmony_ci			goto fail;
2728c2ecf20Sopenharmony_ci	}
2738c2ecf20Sopenharmony_ci	goto out;
2748c2ecf20Sopenharmony_ci
2758c2ecf20Sopenharmony_ci fail:
2768c2ecf20Sopenharmony_ci	map = NULL;
2778c2ecf20Sopenharmony_ci out:
2788c2ecf20Sopenharmony_ci	return map;
2798c2ecf20Sopenharmony_ci}
280