1 /*
2  * Copyright (c) 2015, Xilinx Inc. and Contributors. All rights reserved.
3  *
4  * SPDX-License-Identifier: BSD-3-Clause
5  */
6 
7 /*
8  * @file	linux/shmem.c
9  * @brief	Linux libmetal shared memory handling.
10  */
11 
12 #include <metal/shmem.h>
13 #include <metal/sys.h>
14 #include <metal/utilities.h>
15 
16 struct metal_shmem {
17 	struct metal_io_region	io;
18 	metal_phys_addr_t	*phys;
19 };
20 
metal_shmem_io_close(struct metal_io_region * io)21 static void metal_shmem_io_close(struct metal_io_region *io)
22 {
23 	metal_unmap(io->virt, io->size);
24 	free((void *)io->physmap);
25 }
26 
27 static const struct metal_io_ops metal_shmem_io_ops = {
28 	NULL, NULL, NULL, NULL, NULL, metal_shmem_io_close, NULL, NULL
29 };
30 
metal_virt2phys(void * addr,unsigned long * phys)31 static int metal_virt2phys(void *addr, unsigned long *phys)
32 {
33 	off_t offset;
34 	uint64_t entry;
35 	int error;
36 
37 	if (_metal.pagemap_fd < 0)
38 		return -EINVAL;
39 
40 	offset = ((uintptr_t)addr >> _metal.page_shift) * sizeof(entry);
41 	error = pread(_metal.pagemap_fd, &entry, sizeof(entry), offset);
42 	if (error < 0) {
43 		metal_log(METAL_LOG_ERROR, "failed pagemap pread (offset %llx) - %s\n",
44 			  (unsigned long long)offset, strerror(errno));
45 		return -errno;
46 	}
47 
48 	/* Check page present and not swapped. */
49 	if ((entry >> 62) != 2) {
50 		metal_log(METAL_LOG_ERROR, "pagemap page not present, %llx -> %llx\n",
51 			  (unsigned long long)offset, (unsigned long long)entry);
52 		return -ENOENT;
53 	}
54 
55 	*phys = (entry & ((1ULL << 54) - 1)) << _metal.page_shift;
56 	return 0;
57 }
58 
metal_shmem_try_map(struct metal_page_size * ps,int fd,size_t size,struct metal_io_region ** result)59 static int metal_shmem_try_map(struct metal_page_size *ps, int fd, size_t size,
60 			       struct metal_io_region **result)
61 {
62 	size_t pages, page, phys_size;
63 	struct metal_io_region *io;
64 	metal_phys_addr_t *phys;
65 	uint8_t *virt;
66 	void *mem;
67 	int error;
68 
69 	size = metal_align_up(size, ps->page_size);
70 	pages = size / ps->page_size;
71 
72 	error = metal_map(fd, 0, size, 1, ps->mmap_flags, &mem);
73 	if (error) {
74 		metal_log(METAL_LOG_WARNING,
75 			  "failed to mmap shmem %ld,0x%x - %s\n",
76 			  size, ps->mmap_flags, strerror(-error));
77 		return error;
78 	}
79 
80 	error = mlock(mem, size);
81 	if (error) {
82 		metal_log(METAL_LOG_WARNING, "failed to mlock shmem - %s\n",
83 			  strerror(errno));
84 	}
85 
86 	phys_size = sizeof(*phys) * pages;
87 	phys = malloc(phys_size);
88 	if (!phys) {
89 		metal_unmap(mem, size);
90 		return -ENOMEM;
91 	}
92 
93 	io = malloc(sizeof(*io));
94 	if (!io) {
95 		free(phys);
96 		metal_unmap(mem, size);
97 		return -ENOMEM;
98 	}
99 
100 	if (_metal.pagemap_fd < 0) {
101 		phys[0] = 0;
102 		metal_log(METAL_LOG_WARNING,
103 		"shmem - failed to get va2pa mapping. use offset as pa.\n");
104 		metal_io_init(io, mem, phys, size, -1, 0, &metal_shmem_io_ops);
105 	} else {
106 		for (virt = mem, page = 0; page < pages; page++) {
107 			size_t offset = page * ps->page_size;
108 
109 			error = metal_virt2phys(virt + offset, &phys[page]);
110 			if (error < 0)
111 				phys[page] = METAL_BAD_OFFSET;
112 		}
113 		metal_io_init(io, mem, phys, size, ps->page_shift, 0,
114 			&metal_shmem_io_ops);
115 	}
116 	*result = io;
117 
118 	return 0;
119 }
120 
metal_shmem_open(const char * name,size_t size,struct metal_io_region ** result)121 int metal_shmem_open(const char *name, size_t size,
122 		     struct metal_io_region **result)
123 {
124 	struct metal_page_size *ps;
125 	int fd, error;
126 
127 	error = metal_shmem_open_generic(name, size, result);
128 	if (!error)
129 		return error;
130 
131 	error = metal_open(name, 1);
132 	if (error < 0) {
133 		metal_log(METAL_LOG_ERROR, "Failed to open shmem file :%s\n", name);
134 		return error;
135 	}
136 	fd = error;
137 
138 	/* Iterate through page sizes in decreasing order. */
139 	metal_for_each_page_size_down(ps) {
140 		if (ps->page_size > 2 * size)
141 			continue;
142 		error = metal_shmem_try_map(ps, fd, size, result);
143 		if (!error)
144 			break;
145 	}
146 
147 	close(fd);
148 	return error;
149 }
150