spapr_iommu.c 7.3 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

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

#include <libfdt.h>

/* #define DEBUG_TCE */

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

struct sPAPRTCETable {
    uint32_t liobn;
    uint32_t window_size;
    sPAPRTCE *table;
43
    bool bypass;
44
    int fd;
45
    MemoryRegion iommu;
46 47 48 49 50 51 52 53 54 55
    QLIST_ENTRY(sPAPRTCETable) list;
};


QLIST_HEAD(spapr_tce_tables, sPAPRTCETable) spapr_tce_tables;

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

56 57 58 59 60 61
    if (liobn & 0xFFFFFFFF00000000ULL) {
        hcall_dprintf("Request for out-of-bounds LIOBN 0x" TARGET_FMT_lx "\n",
                      liobn);
        return NULL;
    }

62 63 64 65 66 67 68 69 70
    QLIST_FOREACH(tcet, &spapr_tce_tables, list) {
        if (tcet->liobn == liobn) {
            return tcet;
        }
    }

    return NULL;
}

71
static IOMMUTLBEntry spapr_tce_translate_iommu(MemoryRegion *iommu, hwaddr addr)
72
{
73
    sPAPRTCETable *tcet = container_of(iommu, sPAPRTCETable, iommu);
74 75 76 77 78 79 80
    uint64_t tce;

#ifdef DEBUG_TCE
    fprintf(stderr, "spapr_tce_translate liobn=0x%" PRIx32 " addr=0x"
            DMA_ADDR_FMT "\n", tcet->liobn, addr);
#endif

81
    if (tcet->bypass) {
82 83 84 85 86 87 88
        return (IOMMUTLBEntry) {
            .target_as = &address_space_memory,
            .iova = 0,
            .translated_addr = 0,
            .addr_mask = ~(hwaddr)0,
            .perm = IOMMU_RW,
        };
89 90
    }

91 92 93 94 95
    /* Check if we are in bound */
    if (addr >= tcet->window_size) {
#ifdef DEBUG_TCE
        fprintf(stderr, "spapr_tce_translate out of bounds\n");
#endif
96
        return (IOMMUTLBEntry) { .perm = IOMMU_NONE };
97 98 99 100
    }

    tce = tcet->table[addr >> SPAPR_TCE_PAGE_SHIFT].tce;

101 102 103 104 105 106 107 108 109 110 111 112 113 114
#ifdef DEBUG_TCE
    fprintf(stderr, " ->  *paddr=0x%llx, *len=0x%llx\n",
            (tce & ~SPAPR_TCE_PAGE_MASK), SPAPR_TCE_PAGE_MASK + 1);
#endif

    return (IOMMUTLBEntry) {
        .target_as = &address_space_memory,
        .iova = addr & ~SPAPR_TCE_PAGE_MASK,
        .translated_addr = tce & ~SPAPR_TCE_PAGE_MASK,
        .addr_mask = SPAPR_TCE_PAGE_MASK,
        .perm = tce,
    };
}

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

119
sPAPRTCETable *spapr_tce_new_table(uint32_t liobn, size_t window_size)
120 121 122
{
    sPAPRTCETable *tcet;

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

129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
    if (!window_size) {
        return NULL;
    }

    tcet = g_malloc0(sizeof(*tcet));
    tcet->liobn = liobn;
    tcet->window_size = window_size;

    if (kvm_enabled()) {
        tcet->table = kvmppc_create_spapr_tce(liobn,
                                              window_size,
                                              &tcet->fd);
    }

    if (!tcet->table) {
        size_t table_size = (window_size >> SPAPR_TCE_PAGE_SHIFT)
            * sizeof(sPAPRTCE);
        tcet->table = g_malloc0(table_size);
    }

#ifdef DEBUG_TCE
150 151
    fprintf(stderr, "spapr_iommu: New TCE table @ %p, liobn=0x%x, "
            "table @ %p, fd=%d\n", tcet, liobn, tcet->table, tcet->fd);
152 153
#endif

154
    memory_region_init_iommu(&tcet->iommu, NULL, &spapr_iommu_ops,
155 156
                             "iommu-spapr", UINT64_MAX);

157 158
    QLIST_INSERT_HEAD(&spapr_tce_tables, tcet, list);

159
    return tcet;
160 161
}

162
void spapr_tce_free(sPAPRTCETable *tcet)
163
{
164
    QLIST_REMOVE(tcet, list);
165

166 167 168 169
    if (!kvm_enabled() ||
        (kvmppc_remove_spapr_tce(tcet->table, tcet->fd,
                                 tcet->window_size) != 0)) {
        g_free(tcet->table);
170
    }
171 172

    g_free(tcet);
173 174
}

175 176 177 178 179
MemoryRegion *spapr_tce_get_iommu(sPAPRTCETable *tcet)
{
    return &tcet->iommu;
}

180 181
void spapr_tce_set_bypass(sPAPRTCETable *tcet, bool bypass)
{
182 183 184
    tcet->bypass = bypass;
}

185
void spapr_tce_reset(sPAPRTCETable *tcet)
186
{
187 188
    size_t table_size = (tcet->window_size >> SPAPR_TCE_PAGE_SHIFT)
        * sizeof(sPAPRTCE);
189

190 191
    tcet->bypass = false;
    memset(tcet->table, 0, table_size);
192 193
}

194 195 196 197
static target_ulong put_tce_emu(sPAPRTCETable *tcet, target_ulong ioba,
                                target_ulong tce)
{
    sPAPRTCE *tcep;
198
    IOMMUTLBEntry entry;
199 200

    if (ioba >= tcet->window_size) {
201
        hcall_dprintf("spapr_vio_put_tce on out-of-bounds IOBA 0x"
202 203 204 205 206 207 208
                      TARGET_FMT_lx "\n", ioba);
        return H_PARAMETER;
    }

    tcep = tcet->table + (ioba >> SPAPR_TCE_PAGE_SHIFT);
    tcep->tce = tce;

209 210 211 212 213 214 215
    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);

216 217
    return H_SUCCESS;
}
218

219
static target_ulong h_put_tce(PowerPCCPU *cpu, sPAPREnvironment *spapr,
220 221 222 223 224 225 226 227 228
                              target_ulong opcode, target_ulong *args)
{
    target_ulong liobn = args[0];
    target_ulong ioba = args[1];
    target_ulong tce = args[2];
    sPAPRTCETable *tcet = spapr_tce_find_by_liobn(liobn);

    ioba &= ~(SPAPR_TCE_PAGE_SIZE - 1);

229 230 231
    if (tcet) {
        return put_tce_emu(tcet, ioba, tce);
    }
232
#ifdef DEBUG_TCE
233
    fprintf(stderr, "%s on liobn=" TARGET_FMT_lx /*%s*/
234
            "  ioba 0x" TARGET_FMT_lx "  TCE 0x" TARGET_FMT_lx "\n",
235
            __func__, liobn, /*dev->qdev.id, */ioba, tce);
236 237
#endif

238
    return H_PARAMETER;
239 240 241 242 243 244 245 246 247 248 249
}

void spapr_iommu_init(void)
{
    QLIST_INIT(&spapr_tce_tables);

    /* hcall-tce */
    spapr_register_hypercall(H_PUT_TCE, h_put_tce);
}

int spapr_dma_dt(void *fdt, int node_off, const char *propname,
250
                 uint32_t liobn, uint64_t window, uint32_t size)
251
{
252 253 254 255 256 257 258 259 260 261 262 263 264
    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;
    }
265

266 267 268 269
    ret = fdt_setprop_cell(fdt, node_off, "ibm,#dma-size-cells", 2);
    if (ret < 0) {
        return ret;
    }
270

271 272 273
    ret = fdt_setprop(fdt, node_off, propname, dma_prop, sizeof(dma_prop));
    if (ret < 0) {
        return ret;
274 275 276 277
    }

    return 0;
}
278 279

int spapr_tcet_dma_dt(void *fdt, int node_off, const char *propname,
280
                      sPAPRTCETable *tcet)
281
{
282
    if (!tcet) {
283 284 285
        return 0;
    }

286 287
    return spapr_dma_dt(fdt, node_off, propname,
                        tcet->liobn, 0, tcet->window_size);
288
}