1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19#include "hw/hw.h"
20#include "sysemu/kvm.h"
21#include "hw/qdev.h"
22#include "kvm_ppc.h"
23#include "sysemu/dma.h"
24#include "exec/address-spaces.h"
25
26#include "hw/ppc/spapr.h"
27
28#include <libfdt.h>
29
30
31
32enum sPAPRTCEAccess {
33 SPAPR_TCE_FAULT = 0,
34 SPAPR_TCE_RO = 1,
35 SPAPR_TCE_WO = 2,
36 SPAPR_TCE_RW = 3,
37};
38
39QLIST_HEAD(spapr_tce_tables, sPAPRTCETable) spapr_tce_tables;
40
41static sPAPRTCETable *spapr_tce_find_by_liobn(uint32_t liobn)
42{
43 sPAPRTCETable *tcet;
44
45 if (liobn & 0xFFFFFFFF00000000ULL) {
46 hcall_dprintf("Request for out-of-bounds LIOBN 0x" TARGET_FMT_lx "\n",
47 liobn);
48 return NULL;
49 }
50
51 QLIST_FOREACH(tcet, &spapr_tce_tables, list) {
52 if (tcet->liobn == liobn) {
53 return tcet;
54 }
55 }
56
57 return NULL;
58}
59
60static IOMMUTLBEntry spapr_tce_translate_iommu(MemoryRegion *iommu, hwaddr addr)
61{
62 sPAPRTCETable *tcet = container_of(iommu, sPAPRTCETable, iommu);
63 uint64_t tce;
64
65#ifdef DEBUG_TCE
66 fprintf(stderr, "spapr_tce_translate liobn=0x%" PRIx32 " addr=0x"
67 DMA_ADDR_FMT "\n", tcet->liobn, addr);
68#endif
69
70 if (tcet->bypass) {
71 return (IOMMUTLBEntry) {
72 .target_as = &address_space_memory,
73 .iova = 0,
74 .translated_addr = 0,
75 .addr_mask = ~(hwaddr)0,
76 .perm = IOMMU_RW,
77 };
78 }
79
80
81 if (addr >= tcet->window_size) {
82#ifdef DEBUG_TCE
83 fprintf(stderr, "spapr_tce_translate out of bounds\n");
84#endif
85 return (IOMMUTLBEntry) { .perm = IOMMU_NONE };
86 }
87
88 tce = tcet->table[addr >> SPAPR_TCE_PAGE_SHIFT];
89
90#ifdef DEBUG_TCE
91 fprintf(stderr, " -> *paddr=0x%llx, *len=0x%llx\n",
92 (tce & ~SPAPR_TCE_PAGE_MASK), SPAPR_TCE_PAGE_MASK + 1);
93#endif
94
95 return (IOMMUTLBEntry) {
96 .target_as = &address_space_memory,
97 .iova = addr & ~SPAPR_TCE_PAGE_MASK,
98 .translated_addr = tce & ~SPAPR_TCE_PAGE_MASK,
99 .addr_mask = SPAPR_TCE_PAGE_MASK,
100 .perm = tce,
101 };
102}
103
104static int spapr_tce_table_pre_load(void *opaque)
105{
106 sPAPRTCETable *tcet = SPAPR_TCE_TABLE(opaque);
107
108 tcet->nb_table = tcet->window_size >> SPAPR_TCE_PAGE_SHIFT;
109
110 return 0;
111}
112
113static const VMStateDescription vmstate_spapr_tce_table = {
114 .name = "spapr_iommu",
115 .version_id = 1,
116 .minimum_version_id = 1,
117 .minimum_version_id_old = 1,
118 .pre_load = spapr_tce_table_pre_load,
119 .fields = (VMStateField []) {
120
121 VMSTATE_UINT32_EQUAL(liobn, sPAPRTCETable),
122 VMSTATE_UINT32_EQUAL(window_size, sPAPRTCETable),
123
124
125 VMSTATE_BOOL(bypass, sPAPRTCETable),
126 VMSTATE_VARRAY_UINT32(table, sPAPRTCETable, nb_table, 0, vmstate_info_uint64, uint64_t),
127
128 VMSTATE_END_OF_LIST()
129 },
130};
131
132static MemoryRegionIOMMUOps spapr_iommu_ops = {
133 .translate = spapr_tce_translate_iommu,
134};
135
136static int spapr_tce_table_realize(DeviceState *dev)
137{
138 sPAPRTCETable *tcet = SPAPR_TCE_TABLE(dev);
139
140 if (kvm_enabled()) {
141 tcet->table = kvmppc_create_spapr_tce(tcet->liobn,
142 tcet->window_size,
143 &tcet->fd);
144 }
145
146 if (!tcet->table) {
147 size_t table_size = (tcet->window_size >> SPAPR_TCE_PAGE_SHIFT)
148 * sizeof(uint64_t);
149 tcet->table = g_malloc0(table_size);
150 }
151 tcet->nb_table = tcet->window_size >> SPAPR_TCE_PAGE_SHIFT;
152
153#ifdef DEBUG_TCE
154 fprintf(stderr, "spapr_iommu: New TCE table @ %p, liobn=0x%x, "
155 "table @ %p, fd=%d\n", tcet, liobn, tcet->table, tcet->fd);
156#endif
157
158 memory_region_init_iommu(&tcet->iommu, OBJECT(dev), &spapr_iommu_ops,
159 "iommu-spapr", UINT64_MAX);
160
161 QLIST_INSERT_HEAD(&spapr_tce_tables, tcet, list);
162
163 return 0;
164}
165
166sPAPRTCETable *spapr_tce_new_table(DeviceState *owner, uint32_t liobn, size_t window_size)
167{
168 sPAPRTCETable *tcet;
169
170 if (spapr_tce_find_by_liobn(liobn)) {
171 fprintf(stderr, "Attempted to create TCE table with duplicate"
172 " LIOBN 0x%x\n", liobn);
173 return NULL;
174 }
175
176 if (!window_size) {
177 return NULL;
178 }
179
180 tcet = SPAPR_TCE_TABLE(object_new(TYPE_SPAPR_TCE_TABLE));
181 tcet->liobn = liobn;
182 tcet->window_size = window_size;
183
184 object_property_add_child(OBJECT(owner), "tce-table", OBJECT(tcet), NULL);
185
186 qdev_init_nofail(DEVICE(tcet));
187
188 return tcet;
189}
190
191static void spapr_tce_table_finalize(Object *obj)
192{
193 sPAPRTCETable *tcet = SPAPR_TCE_TABLE(obj);
194
195 QLIST_REMOVE(tcet, list);
196
197 if (!kvm_enabled() ||
198 (kvmppc_remove_spapr_tce(tcet->table, tcet->fd,
199 tcet->window_size) != 0)) {
200 g_free(tcet->table);
201 }
202}
203
204MemoryRegion *spapr_tce_get_iommu(sPAPRTCETable *tcet)
205{
206 return &tcet->iommu;
207}
208
209void spapr_tce_set_bypass(sPAPRTCETable *tcet, bool bypass)
210{
211 tcet->bypass = bypass;
212}
213
214static void spapr_tce_reset(DeviceState *dev)
215{
216 sPAPRTCETable *tcet = SPAPR_TCE_TABLE(dev);
217 size_t table_size = (tcet->window_size >> SPAPR_TCE_PAGE_SHIFT)
218 * sizeof(uint64_t);
219
220 tcet->bypass = false;
221 memset(tcet->table, 0, table_size);
222}
223
224static target_ulong put_tce_emu(sPAPRTCETable *tcet, target_ulong ioba,
225 target_ulong tce)
226{
227 IOMMUTLBEntry entry;
228
229 if (ioba >= tcet->window_size) {
230 hcall_dprintf("spapr_vio_put_tce on out-of-bounds IOBA 0x"
231 TARGET_FMT_lx "\n", ioba);
232 return H_PARAMETER;
233 }
234
235 tcet->table[ioba >> SPAPR_TCE_PAGE_SHIFT] = tce;
236
237 entry.target_as = &address_space_memory,
238 entry.iova = ioba & ~SPAPR_TCE_PAGE_MASK;
239 entry.translated_addr = tce & ~SPAPR_TCE_PAGE_MASK;
240 entry.addr_mask = SPAPR_TCE_PAGE_MASK;
241 entry.perm = tce;
242 memory_region_notify_iommu(&tcet->iommu, entry);
243
244 return H_SUCCESS;
245}
246
247static target_ulong h_put_tce(PowerPCCPU *cpu, sPAPREnvironment *spapr,
248 target_ulong opcode, target_ulong *args)
249{
250 target_ulong liobn = args[0];
251 target_ulong ioba = args[1];
252 target_ulong tce = args[2];
253 sPAPRTCETable *tcet = spapr_tce_find_by_liobn(liobn);
254
255 ioba &= ~(SPAPR_TCE_PAGE_SIZE - 1);
256
257 if (tcet) {
258 return put_tce_emu(tcet, ioba, tce);
259 }
260#ifdef DEBUG_TCE
261 fprintf(stderr, "%s on liobn=" TARGET_FMT_lx
262 " ioba 0x" TARGET_FMT_lx " TCE 0x" TARGET_FMT_lx "\n",
263 __func__, liobn, ioba, tce);
264#endif
265
266 return H_PARAMETER;
267}
268
269int spapr_dma_dt(void *fdt, int node_off, const char *propname,
270 uint32_t liobn, uint64_t window, uint32_t size)
271{
272 uint32_t dma_prop[5];
273 int ret;
274
275 dma_prop[0] = cpu_to_be32(liobn);
276 dma_prop[1] = cpu_to_be32(window >> 32);
277 dma_prop[2] = cpu_to_be32(window & 0xFFFFFFFF);
278 dma_prop[3] = 0;
279 dma_prop[4] = cpu_to_be32(size);
280
281 ret = fdt_setprop_cell(fdt, node_off, "ibm,#dma-address-cells", 2);
282 if (ret < 0) {
283 return ret;
284 }
285
286 ret = fdt_setprop_cell(fdt, node_off, "ibm,#dma-size-cells", 2);
287 if (ret < 0) {
288 return ret;
289 }
290
291 ret = fdt_setprop(fdt, node_off, propname, dma_prop, sizeof(dma_prop));
292 if (ret < 0) {
293 return ret;
294 }
295
296 return 0;
297}
298
299int spapr_tcet_dma_dt(void *fdt, int node_off, const char *propname,
300 sPAPRTCETable *tcet)
301{
302 if (!tcet) {
303 return 0;
304 }
305
306 return spapr_dma_dt(fdt, node_off, propname,
307 tcet->liobn, 0, tcet->window_size);
308}
309
310static void spapr_tce_table_class_init(ObjectClass *klass, void *data)
311{
312 DeviceClass *dc = DEVICE_CLASS(klass);
313 dc->vmsd = &vmstate_spapr_tce_table;
314 dc->init = spapr_tce_table_realize;
315 dc->reset = spapr_tce_reset;
316
317 QLIST_INIT(&spapr_tce_tables);
318
319
320 spapr_register_hypercall(H_PUT_TCE, h_put_tce);
321}
322
323static TypeInfo spapr_tce_table_info = {
324 .name = TYPE_SPAPR_TCE_TABLE,
325 .parent = TYPE_DEVICE,
326 .instance_size = sizeof(sPAPRTCETable),
327 .class_init = spapr_tce_table_class_init,
328 .instance_finalize = spapr_tce_table_finalize,
329};
330
331static void register_types(void)
332{
333 type_register_static(&spapr_tce_table_info);
334}
335
336type_init(register_types);
337