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