xref: /openbmc/qemu/hw/ppc/spapr_iommu.c (revision e00387d58243d4ae24ac68008a2aea76313ab997)
1 /*
2  * QEMU sPAPR IOMMU (TCE) code
3  *
4  * Copyright (c) 2010 David Gibson, IBM Corporation <dwg@au1.ibm.com>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, see <http://www.gnu.org/licenses/>.
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 /* #define DEBUG_TCE */
31 
32 enum sPAPRTCEAccess {
33     SPAPR_TCE_FAULT = 0,
34     SPAPR_TCE_RO = 1,
35     SPAPR_TCE_WO = 2,
36     SPAPR_TCE_RW = 3,
37 };
38 
39 struct sPAPRTCETable {
40     /* temporary until everyone has its own AddressSpace */
41     DMAContext dma;
42     AddressSpace as;
43 
44     uint32_t liobn;
45     uint32_t window_size;
46     sPAPRTCE *table;
47     bool bypass;
48     int fd;
49     MemoryRegion iommu;
50     QLIST_ENTRY(sPAPRTCETable) list;
51 };
52 
53 
54 QLIST_HEAD(spapr_tce_tables, sPAPRTCETable) spapr_tce_tables;
55 
56 static sPAPRTCETable *spapr_tce_find_by_liobn(uint32_t liobn)
57 {
58     sPAPRTCETable *tcet;
59 
60     if (liobn & 0xFFFFFFFF00000000ULL) {
61         hcall_dprintf("Request for out-of-bounds LIOBN 0x" TARGET_FMT_lx "\n",
62                       liobn);
63         return NULL;
64     }
65 
66     QLIST_FOREACH(tcet, &spapr_tce_tables, list) {
67         if (tcet->liobn == liobn) {
68             return tcet;
69         }
70     }
71 
72     return NULL;
73 }
74 
75 static IOMMUTLBEntry spapr_tce_translate_iommu(MemoryRegion *iommu, hwaddr addr)
76 {
77     sPAPRTCETable *tcet = container_of(iommu, sPAPRTCETable, iommu);
78     uint64_t tce;
79 
80 #ifdef DEBUG_TCE
81     fprintf(stderr, "spapr_tce_translate liobn=0x%" PRIx32 " addr=0x"
82             DMA_ADDR_FMT "\n", tcet->liobn, addr);
83 #endif
84 
85     if (tcet->bypass) {
86         return (IOMMUTLBEntry) {
87             .target_as = &address_space_memory,
88             .iova = 0,
89             .translated_addr = 0,
90             .addr_mask = ~(hwaddr)0,
91             .perm = IOMMU_RW,
92         };
93     }
94 
95     /* Check if we are in bound */
96     if (addr >= tcet->window_size) {
97 #ifdef DEBUG_TCE
98         fprintf(stderr, "spapr_tce_translate out of bounds\n");
99 #endif
100         return (IOMMUTLBEntry) { .perm = IOMMU_NONE };
101     }
102 
103     tce = tcet->table[addr >> SPAPR_TCE_PAGE_SHIFT].tce;
104 
105 #ifdef DEBUG_TCE
106     fprintf(stderr, " ->  *paddr=0x%llx, *len=0x%llx\n",
107             (tce & ~SPAPR_TCE_PAGE_MASK), SPAPR_TCE_PAGE_MASK + 1);
108 #endif
109 
110     return (IOMMUTLBEntry) {
111         .target_as = &address_space_memory,
112         .iova = addr & ~SPAPR_TCE_PAGE_MASK,
113         .translated_addr = tce & ~SPAPR_TCE_PAGE_MASK,
114         .addr_mask = SPAPR_TCE_PAGE_MASK,
115         .perm = tce,
116     };
117 }
118 
119 static MemoryRegionIOMMUOps spapr_iommu_ops = {
120     .translate = spapr_tce_translate_iommu,
121 };
122 
123 sPAPRTCETable *spapr_tce_new_table(uint32_t liobn, size_t window_size)
124 {
125     sPAPRTCETable *tcet;
126 
127     if (spapr_tce_find_by_liobn(liobn)) {
128         fprintf(stderr, "Attempted to create TCE table with duplicate"
129                 " LIOBN 0x%x\n", liobn);
130         return NULL;
131     }
132 
133     if (!window_size) {
134         return NULL;
135     }
136 
137     tcet = g_malloc0(sizeof(*tcet));
138     tcet->liobn = liobn;
139     tcet->window_size = window_size;
140 
141     if (kvm_enabled()) {
142         tcet->table = kvmppc_create_spapr_tce(liobn,
143                                               window_size,
144                                               &tcet->fd);
145     }
146 
147     if (!tcet->table) {
148         size_t table_size = (window_size >> SPAPR_TCE_PAGE_SHIFT)
149             * sizeof(sPAPRTCE);
150         tcet->table = g_malloc0(table_size);
151     }
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, &spapr_iommu_ops,
159                              "iommu-spapr", UINT64_MAX);
160     address_space_init(&tcet->as, &tcet->iommu);
161     dma_context_init(&tcet->dma, &tcet->as);
162 
163     QLIST_INSERT_HEAD(&spapr_tce_tables, tcet, list);
164 
165     return tcet;
166 }
167 
168 void spapr_tce_free(sPAPRTCETable *tcet)
169 {
170     QLIST_REMOVE(tcet, list);
171 
172     if (!kvm_enabled() ||
173         (kvmppc_remove_spapr_tce(tcet->table, tcet->fd,
174                                  tcet->window_size) != 0)) {
175         g_free(tcet->table);
176     }
177 
178     g_free(tcet);
179 }
180 
181 DMAContext *spapr_tce_get_dma(sPAPRTCETable *tcet)
182 {
183     return &tcet->dma;
184 }
185 
186 MemoryRegion *spapr_tce_get_iommu(sPAPRTCETable *tcet)
187 {
188     return &tcet->iommu;
189 }
190 
191 void spapr_tce_set_bypass(sPAPRTCETable *tcet, bool bypass)
192 {
193     tcet->bypass = bypass;
194 }
195 
196 void spapr_tce_reset(sPAPRTCETable *tcet)
197 {
198     size_t table_size = (tcet->window_size >> SPAPR_TCE_PAGE_SHIFT)
199         * sizeof(sPAPRTCE);
200 
201     tcet->bypass = false;
202     memset(tcet->table, 0, table_size);
203 }
204 
205 static target_ulong put_tce_emu(sPAPRTCETable *tcet, target_ulong ioba,
206                                 target_ulong tce)
207 {
208     sPAPRTCE *tcep;
209     IOMMUTLBEntry entry;
210 
211     if (ioba >= tcet->window_size) {
212         hcall_dprintf("spapr_vio_put_tce on out-of-bounds IOBA 0x"
213                       TARGET_FMT_lx "\n", ioba);
214         return H_PARAMETER;
215     }
216 
217     tcep = tcet->table + (ioba >> SPAPR_TCE_PAGE_SHIFT);
218     tcep->tce = tce;
219 
220     entry.target_as = &address_space_memory,
221     entry.iova = ioba & ~SPAPR_TCE_PAGE_MASK;
222     entry.translated_addr = tce & ~SPAPR_TCE_PAGE_MASK;
223     entry.addr_mask = SPAPR_TCE_PAGE_MASK;
224     entry.perm = tce;
225     memory_region_notify_iommu(&tcet->iommu, entry);
226 
227     return H_SUCCESS;
228 }
229 
230 static target_ulong h_put_tce(PowerPCCPU *cpu, sPAPREnvironment *spapr,
231                               target_ulong opcode, target_ulong *args)
232 {
233     target_ulong liobn = args[0];
234     target_ulong ioba = args[1];
235     target_ulong tce = args[2];
236     sPAPRTCETable *tcet = spapr_tce_find_by_liobn(liobn);
237 
238     ioba &= ~(SPAPR_TCE_PAGE_SIZE - 1);
239 
240     if (tcet) {
241         return put_tce_emu(tcet, ioba, tce);
242     }
243 #ifdef DEBUG_TCE
244     fprintf(stderr, "%s on liobn=" TARGET_FMT_lx /*%s*/
245             "  ioba 0x" TARGET_FMT_lx "  TCE 0x" TARGET_FMT_lx "\n",
246             __func__, liobn, /*dev->qdev.id, */ioba, tce);
247 #endif
248 
249     return H_PARAMETER;
250 }
251 
252 void spapr_iommu_init(void)
253 {
254     QLIST_INIT(&spapr_tce_tables);
255 
256     /* hcall-tce */
257     spapr_register_hypercall(H_PUT_TCE, h_put_tce);
258 }
259 
260 int spapr_dma_dt(void *fdt, int node_off, const char *propname,
261                  uint32_t liobn, uint64_t window, uint32_t size)
262 {
263     uint32_t dma_prop[5];
264     int ret;
265 
266     dma_prop[0] = cpu_to_be32(liobn);
267     dma_prop[1] = cpu_to_be32(window >> 32);
268     dma_prop[2] = cpu_to_be32(window & 0xFFFFFFFF);
269     dma_prop[3] = 0; /* window size is 32 bits */
270     dma_prop[4] = cpu_to_be32(size);
271 
272     ret = fdt_setprop_cell(fdt, node_off, "ibm,#dma-address-cells", 2);
273     if (ret < 0) {
274         return ret;
275     }
276 
277     ret = fdt_setprop_cell(fdt, node_off, "ibm,#dma-size-cells", 2);
278     if (ret < 0) {
279         return ret;
280     }
281 
282     ret = fdt_setprop(fdt, node_off, propname, dma_prop, sizeof(dma_prop));
283     if (ret < 0) {
284         return ret;
285     }
286 
287     return 0;
288 }
289 
290 int spapr_tcet_dma_dt(void *fdt, int node_off, const char *propname,
291                       sPAPRTCETable *tcet)
292 {
293     if (!tcet) {
294         return 0;
295     }
296 
297     return spapr_dma_dt(fdt, node_off, propname,
298                         tcet->liobn, 0, tcet->window_size);
299 }
300