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 QLIST_HEAD(spapr_tce_tables, sPAPRTCETable) spapr_tce_tables; 40 41 static 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 60 static 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 /* Check if we are in bound */ 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 104 static 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 113 static 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 /* Sanity check */ 121 VMSTATE_UINT32_EQUAL(liobn, sPAPRTCETable), 122 VMSTATE_UINT32_EQUAL(window_size, sPAPRTCETable), 123 124 /* IOMMU state */ 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 132 static MemoryRegionIOMMUOps spapr_iommu_ops = { 133 .translate = spapr_tce_translate_iommu, 134 }; 135 136 static 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 166 sPAPRTCETable *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 191 static 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 204 MemoryRegion *spapr_tce_get_iommu(sPAPRTCETable *tcet) 205 { 206 return &tcet->iommu; 207 } 208 209 void spapr_tce_set_bypass(sPAPRTCETable *tcet, bool bypass) 210 { 211 tcet->bypass = bypass; 212 } 213 214 static 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 224 static 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 247 static 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 /*%s*/ 262 " ioba 0x" TARGET_FMT_lx " TCE 0x" TARGET_FMT_lx "\n", 263 __func__, liobn, /*dev->qdev.id, */ioba, tce); 264 #endif 265 266 return H_PARAMETER; 267 } 268 269 int 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; /* window size is 32 bits */ 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 299 int 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 310 static 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 /* hcall-tce */ 320 spapr_register_hypercall(H_PUT_TCE, h_put_tce); 321 } 322 323 static 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 331 static void register_types(void) 332 { 333 type_register_static(&spapr_tce_table_info); 334 } 335 336 type_init(register_types); 337