spapr_iommu.c 11.5 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
/*
 * QEMU sPAPR IOMMU (TCE) code
 *
 * Copyright (c) 2010 David Gibson, IBM Corporation <dwg@au1.ibm.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, see <http://www.gnu.org/licenses/>.
 */
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
#include "trace.h"
26

P
Paolo Bonzini 已提交
27
#include "hw/ppc/spapr.h"
28 29 30 31 32 33 34 35 36 37

#include <libfdt.h>

enum sPAPRTCEAccess {
    SPAPR_TCE_FAULT = 0,
    SPAPR_TCE_RO = 1,
    SPAPR_TCE_WO = 2,
    SPAPR_TCE_RW = 3,
};

38
static QLIST_HEAD(spapr_tce_tables, sPAPRTCETable) spapr_tce_tables;
39 40 41 42 43

static sPAPRTCETable *spapr_tce_find_by_liobn(uint32_t liobn)
{
    sPAPRTCETable *tcet;

44 45 46 47 48 49
    if (liobn & 0xFFFFFFFF00000000ULL) {
        hcall_dprintf("Request for out-of-bounds LIOBN 0x" TARGET_FMT_lx "\n",
                      liobn);
        return NULL;
    }

50 51 52 53 54 55 56 57 58
    QLIST_FOREACH(tcet, &spapr_tce_tables, list) {
        if (tcet->liobn == liobn) {
            return tcet;
        }
    }

    return NULL;
}

59
static IOMMUTLBEntry spapr_tce_translate_iommu(MemoryRegion *iommu, hwaddr addr)
60
{
61
    sPAPRTCETable *tcet = container_of(iommu, sPAPRTCETable, iommu);
62
    uint64_t tce;
63 64 65 66 67 68 69
    IOMMUTLBEntry ret = {
        .target_as = &address_space_memory,
        .iova = 0,
        .translated_addr = 0,
        .addr_mask = ~(hwaddr)0,
        .perm = IOMMU_NONE,
    };
70

71
    if (tcet->bypass) {
72 73 74 75 76 77 78 79
        ret.perm = IOMMU_RW;
    } else if (addr < tcet->window_size) {
        /* Check if we are in bound */
        tce = tcet->table[addr >> SPAPR_TCE_PAGE_SHIFT];
        ret.iova = addr & ~SPAPR_TCE_PAGE_MASK;
        ret.translated_addr = tce & ~SPAPR_TCE_PAGE_MASK;
        ret.addr_mask = SPAPR_TCE_PAGE_MASK;
        ret.perm = tce;
80
    }
81 82
    trace_spapr_iommu_xlate(tcet->liobn, addr, ret.iova, ret.perm,
                            ret.addr_mask);
83

84
    return ret;
85 86
}

87 88 89 90 91 92 93 94 95 96 97 98 99 100
static int spapr_tce_table_pre_load(void *opaque)
{
    sPAPRTCETable *tcet = SPAPR_TCE_TABLE(opaque);

    tcet->nb_table = tcet->window_size >> SPAPR_TCE_PAGE_SHIFT;

    return 0;
}

static const VMStateDescription vmstate_spapr_tce_table = {
    .name = "spapr_iommu",
    .version_id = 1,
    .minimum_version_id = 1,
    .pre_load = spapr_tce_table_pre_load,
101
    .fields = (VMStateField[]) {
102 103 104 105 106 107 108 109 110 111 112 113
        /* Sanity check */
        VMSTATE_UINT32_EQUAL(liobn, sPAPRTCETable),
        VMSTATE_UINT32_EQUAL(window_size, sPAPRTCETable),

        /* IOMMU state */
        VMSTATE_BOOL(bypass, sPAPRTCETable),
        VMSTATE_VARRAY_UINT32(table, sPAPRTCETable, nb_table, 0, vmstate_info_uint64, uint64_t),

        VMSTATE_END_OF_LIST()
    },
};

114 115 116
static MemoryRegionIOMMUOps spapr_iommu_ops = {
    .translate = spapr_tce_translate_iommu,
};
117

118
static int spapr_tce_table_realize(DeviceState *dev)
119
{
120
    sPAPRTCETable *tcet = SPAPR_TCE_TABLE(dev);
121 122

    if (kvm_enabled()) {
123 124
        tcet->table = kvmppc_create_spapr_tce(tcet->liobn,
                                              tcet->window_size,
125 126 127 128
                                              &tcet->fd);
    }

    if (!tcet->table) {
129 130
        size_t table_size = (tcet->window_size >> SPAPR_TCE_PAGE_SHIFT)
            * sizeof(uint64_t);
131 132
        tcet->table = g_malloc0(table_size);
    }
133
    tcet->nb_table = tcet->window_size >> SPAPR_TCE_PAGE_SHIFT;
134

135
    trace_spapr_iommu_new_table(tcet->liobn, tcet, tcet->table, tcet->fd);
136

137
    memory_region_init_iommu(&tcet->iommu, OBJECT(dev), &spapr_iommu_ops,
138
                             "iommu-spapr", ram_size);
139

140 141
    QLIST_INSERT_HEAD(&spapr_tce_tables, tcet, list);

142 143 144
    vmstate_register(DEVICE(tcet), tcet->liobn, &vmstate_spapr_tce_table,
                     tcet);

145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167
    return 0;
}

sPAPRTCETable *spapr_tce_new_table(DeviceState *owner, uint32_t liobn, size_t window_size)
{
    sPAPRTCETable *tcet;

    if (spapr_tce_find_by_liobn(liobn)) {
        fprintf(stderr, "Attempted to create TCE table with duplicate"
                " LIOBN 0x%x\n", liobn);
        return NULL;
    }

    if (!window_size) {
        return NULL;
    }

    tcet = SPAPR_TCE_TABLE(object_new(TYPE_SPAPR_TCE_TABLE));
    tcet->liobn = liobn;
    tcet->window_size = window_size;

    object_property_add_child(OBJECT(owner), "tce-table", OBJECT(tcet), NULL);

168
    object_property_set_bool(OBJECT(tcet), true, "realized", NULL);
169

170
    return tcet;
171 172
}

173
static void spapr_tce_table_finalize(Object *obj)
174
{
175 176
    sPAPRTCETable *tcet = SPAPR_TCE_TABLE(obj);

177
    QLIST_REMOVE(tcet, list);
178

179 180 181 182
    if (!kvm_enabled() ||
        (kvmppc_remove_spapr_tce(tcet->table, tcet->fd,
                                 tcet->window_size) != 0)) {
        g_free(tcet->table);
183 184 185
    }
}

186 187 188 189 190
MemoryRegion *spapr_tce_get_iommu(sPAPRTCETable *tcet)
{
    return &tcet->iommu;
}

191 192
void spapr_tce_set_bypass(sPAPRTCETable *tcet, bool bypass)
{
193 194 195
    tcet->bypass = bypass;
}

196
static void spapr_tce_reset(DeviceState *dev)
197
{
198
    sPAPRTCETable *tcet = SPAPR_TCE_TABLE(dev);
199
    size_t table_size = (tcet->window_size >> SPAPR_TCE_PAGE_SHIFT)
200
        * sizeof(uint64_t);
201

202 203
    tcet->bypass = false;
    memset(tcet->table, 0, table_size);
204 205
}

206 207 208
static target_ulong put_tce_emu(sPAPRTCETable *tcet, target_ulong ioba,
                                target_ulong tce)
{
209
    IOMMUTLBEntry entry;
210 211

    if (ioba >= tcet->window_size) {
212
        hcall_dprintf("spapr_vio_put_tce on out-of-bounds IOBA 0x"
213 214 215 216
                      TARGET_FMT_lx "\n", ioba);
        return H_PARAMETER;
    }

217
    tcet->table[ioba >> SPAPR_TCE_PAGE_SHIFT] = tce;
218

219 220 221 222 223 224 225
    entry.target_as = &address_space_memory,
    entry.iova = ioba & ~SPAPR_TCE_PAGE_MASK;
    entry.translated_addr = tce & ~SPAPR_TCE_PAGE_MASK;
    entry.addr_mask = SPAPR_TCE_PAGE_MASK;
    entry.perm = tce;
    memory_region_notify_iommu(&tcet->iommu, entry);

226 227
    return H_SUCCESS;
}
228

229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304
static target_ulong h_put_tce_indirect(PowerPCCPU *cpu,
                                       sPAPREnvironment *spapr,
                                       target_ulong opcode, target_ulong *args)
{
    int i;
    target_ulong liobn = args[0];
    target_ulong ioba = args[1];
    target_ulong ioba1 = ioba;
    target_ulong tce_list = args[2];
    target_ulong npages = args[3];
    target_ulong ret = H_PARAMETER;
    sPAPRTCETable *tcet = spapr_tce_find_by_liobn(liobn);
    CPUState *cs = CPU(cpu);

    if (!tcet) {
        return H_PARAMETER;
    }

    if (npages > 512) {
        return H_PARAMETER;
    }

    ioba &= ~SPAPR_TCE_PAGE_MASK;
    tce_list &= ~SPAPR_TCE_PAGE_MASK;

    for (i = 0; i < npages; ++i, ioba += SPAPR_TCE_PAGE_SIZE) {
        target_ulong tce = ldq_phys(cs->as, tce_list +
                                    i * sizeof(target_ulong));
        ret = put_tce_emu(tcet, ioba, tce);
        if (ret) {
            break;
        }
    }

    /* Trace last successful or the first problematic entry */
    i = i ? (i - 1) : 0;
    trace_spapr_iommu_indirect(liobn, ioba1, tce_list, i,
                               ldq_phys(cs->as,
                               tce_list + i * sizeof(target_ulong)),
                               ret);

    return ret;
}

static target_ulong h_stuff_tce(PowerPCCPU *cpu, sPAPREnvironment *spapr,
                              target_ulong opcode, target_ulong *args)
{
    int i;
    target_ulong liobn = args[0];
    target_ulong ioba = args[1];
    target_ulong tce_value = args[2];
    target_ulong npages = args[3];
    target_ulong ret = H_PARAMETER;
    sPAPRTCETable *tcet = spapr_tce_find_by_liobn(liobn);

    if (!tcet) {
        return H_PARAMETER;
    }

    if (npages > tcet->nb_table) {
        return H_PARAMETER;
    }

    ioba &= ~SPAPR_TCE_PAGE_MASK;

    for (i = 0; i < npages; ++i, ioba += SPAPR_TCE_PAGE_SIZE) {
        ret = put_tce_emu(tcet, ioba, tce_value);
        if (ret) {
            break;
        }
    }
    trace_spapr_iommu_stuff(liobn, ioba, tce_value, npages, ret);

    return ret;
}

305
static target_ulong h_put_tce(PowerPCCPU *cpu, sPAPREnvironment *spapr,
306 307 308 309 310
                              target_ulong opcode, target_ulong *args)
{
    target_ulong liobn = args[0];
    target_ulong ioba = args[1];
    target_ulong tce = args[2];
311
    target_ulong ret = H_PARAMETER;
312 313 314 315
    sPAPRTCETable *tcet = spapr_tce_find_by_liobn(liobn);

    ioba &= ~(SPAPR_TCE_PAGE_SIZE - 1);

316
    if (tcet) {
317
        ret = put_tce_emu(tcet, ioba, tce);
318
    }
319
    trace_spapr_iommu_put(liobn, ioba, tce, ret);
320

321
    return ret;
322 323
}

324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359
static target_ulong get_tce_emu(sPAPRTCETable *tcet, target_ulong ioba,
                                target_ulong *tce)
{
    if (ioba >= tcet->window_size) {
        hcall_dprintf("spapr_iommu_get_tce on out-of-bounds IOBA 0x"
                      TARGET_FMT_lx "\n", ioba);
        return H_PARAMETER;
    }

    *tce = tcet->table[ioba >> SPAPR_TCE_PAGE_SHIFT];

    return H_SUCCESS;
}

static target_ulong h_get_tce(PowerPCCPU *cpu, sPAPREnvironment *spapr,
                              target_ulong opcode, target_ulong *args)
{
    target_ulong liobn = args[0];
    target_ulong ioba = args[1];
    target_ulong tce = 0;
    target_ulong ret = H_PARAMETER;
    sPAPRTCETable *tcet = spapr_tce_find_by_liobn(liobn);

    ioba &= ~(SPAPR_TCE_PAGE_SIZE - 1);

    if (tcet) {
        ret = get_tce_emu(tcet, ioba, &tce);
        if (!ret) {
            args[0] = tce;
        }
    }
    trace_spapr_iommu_get(liobn, ioba, ret, tce);

    return ret;
}

360
int spapr_dma_dt(void *fdt, int node_off, const char *propname,
361
                 uint32_t liobn, uint64_t window, uint32_t size)
362
{
363 364 365 366 367 368 369 370 371 372 373 374 375
    uint32_t dma_prop[5];
    int ret;

    dma_prop[0] = cpu_to_be32(liobn);
    dma_prop[1] = cpu_to_be32(window >> 32);
    dma_prop[2] = cpu_to_be32(window & 0xFFFFFFFF);
    dma_prop[3] = 0; /* window size is 32 bits */
    dma_prop[4] = cpu_to_be32(size);

    ret = fdt_setprop_cell(fdt, node_off, "ibm,#dma-address-cells", 2);
    if (ret < 0) {
        return ret;
    }
376

377 378 379 380
    ret = fdt_setprop_cell(fdt, node_off, "ibm,#dma-size-cells", 2);
    if (ret < 0) {
        return ret;
    }
381

382 383 384
    ret = fdt_setprop(fdt, node_off, propname, dma_prop, sizeof(dma_prop));
    if (ret < 0) {
        return ret;
385 386 387 388
    }

    return 0;
}
389 390

int spapr_tcet_dma_dt(void *fdt, int node_off, const char *propname,
391
                      sPAPRTCETable *tcet)
392
{
393
    if (!tcet) {
394 395 396
        return 0;
    }

397 398
    return spapr_dma_dt(fdt, node_off, propname,
                        tcet->liobn, 0, tcet->window_size);
399
}
400 401 402 403 404 405 406 407 408 409 410

static void spapr_tce_table_class_init(ObjectClass *klass, void *data)
{
    DeviceClass *dc = DEVICE_CLASS(klass);
    dc->init = spapr_tce_table_realize;
    dc->reset = spapr_tce_reset;

    QLIST_INIT(&spapr_tce_tables);

    /* hcall-tce */
    spapr_register_hypercall(H_PUT_TCE, h_put_tce);
411
    spapr_register_hypercall(H_GET_TCE, h_get_tce);
412 413
    spapr_register_hypercall(H_PUT_TCE_INDIRECT, h_put_tce_indirect);
    spapr_register_hypercall(H_STUFF_TCE, h_stuff_tce);
414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429
}

static TypeInfo spapr_tce_table_info = {
    .name = TYPE_SPAPR_TCE_TABLE,
    .parent = TYPE_DEVICE,
    .instance_size = sizeof(sPAPRTCETable),
    .class_init = spapr_tce_table_class_init,
    .instance_finalize = spapr_tce_table_finalize,
};

static void register_types(void)
{
    type_register_static(&spapr_tce_table_info);
}

type_init(register_types);