qemu/hw/spapr_iommu.c
<<
>>
Prefs
   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.h"
  20#include "sysemu/kvm.h"
  21#include "qdev.h"
  22#include "kvm_ppc.h"
  23#include "sysemu/dma.h"
  24#include "exec/address-spaces.h"
  25
  26#include "hw/spapr.h"
  27
  28#include <libfdt.h>
  29
  30/* #define DEBUG_TCE */
  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
  39typedef struct sPAPRTCETable sPAPRTCETable;
  40
  41struct sPAPRTCETable {
  42    DMAContext dma;
  43    uint32_t liobn;
  44    uint32_t window_size;
  45    sPAPRTCE *table;
  46    bool bypass;
  47    int fd;
  48    QLIST_ENTRY(sPAPRTCETable) list;
  49};
  50
  51
  52QLIST_HEAD(spapr_tce_tables, sPAPRTCETable) spapr_tce_tables;
  53
  54static sPAPRTCETable *spapr_tce_find_by_liobn(uint32_t liobn)
  55{
  56    sPAPRTCETable *tcet;
  57
  58    QLIST_FOREACH(tcet, &spapr_tce_tables, list) {
  59        if (tcet->liobn == liobn) {
  60            return tcet;
  61        }
  62    }
  63
  64    return NULL;
  65}
  66
  67static int spapr_tce_translate(DMAContext *dma,
  68                               dma_addr_t addr,
  69                               hwaddr *paddr,
  70                               hwaddr *len,
  71                               DMADirection dir)
  72{
  73    sPAPRTCETable *tcet = DO_UPCAST(sPAPRTCETable, dma, dma);
  74    enum sPAPRTCEAccess access = (dir == DMA_DIRECTION_FROM_DEVICE)
  75        ? SPAPR_TCE_WO : SPAPR_TCE_RO;
  76    uint64_t tce;
  77
  78#ifdef DEBUG_TCE
  79    fprintf(stderr, "spapr_tce_translate liobn=0x%" PRIx32 " addr=0x"
  80            DMA_ADDR_FMT "\n", tcet->liobn, addr);
  81#endif
  82
  83    if (tcet->bypass) {
  84        *paddr = addr;
  85        *len = (hwaddr)-1;
  86        return 0;
  87    }
  88
  89    /* Check if we are in bound */
  90    if (addr >= tcet->window_size) {
  91#ifdef DEBUG_TCE
  92        fprintf(stderr, "spapr_tce_translate out of bounds\n");
  93#endif
  94        return -EFAULT;
  95    }
  96
  97    tce = tcet->table[addr >> SPAPR_TCE_PAGE_SHIFT].tce;
  98
  99    /* Check TCE */
 100    if (!(tce & access)) {
 101        return -EPERM;
 102    }
 103
 104    /* How much til end of page ? */
 105    *len = ((~addr) & SPAPR_TCE_PAGE_MASK) + 1;
 106
 107    /* Translate */
 108    *paddr = (tce & ~SPAPR_TCE_PAGE_MASK) |
 109        (addr & SPAPR_TCE_PAGE_MASK);
 110
 111#ifdef DEBUG_TCE
 112    fprintf(stderr, " ->  *paddr=0x" TARGET_FMT_plx ", *len=0x"
 113            TARGET_FMT_plx "\n", *paddr, *len);
 114#endif
 115
 116    return 0;
 117}
 118
 119DMAContext *spapr_tce_new_dma_context(uint32_t liobn, size_t window_size)
 120{
 121    sPAPRTCETable *tcet;
 122
 123    if (spapr_tce_find_by_liobn(liobn)) {
 124        fprintf(stderr, "Attempted to create TCE table with duplicate"
 125                " LIOBN 0x%x\n", liobn);
 126        return NULL;
 127    }
 128
 129    if (!window_size) {
 130        return NULL;
 131    }
 132
 133    tcet = g_malloc0(sizeof(*tcet));
 134    dma_context_init(&tcet->dma, &address_space_memory, spapr_tce_translate, NULL, NULL);
 135
 136    tcet->liobn = liobn;
 137    tcet->window_size = window_size;
 138
 139    if (kvm_enabled()) {
 140        tcet->table = kvmppc_create_spapr_tce(liobn,
 141                                              window_size,
 142                                              &tcet->fd);
 143    }
 144
 145    if (!tcet->table) {
 146        size_t table_size = (window_size >> SPAPR_TCE_PAGE_SHIFT)
 147            * sizeof(sPAPRTCE);
 148        tcet->table = g_malloc0(table_size);
 149    }
 150
 151#ifdef DEBUG_TCE
 152    fprintf(stderr, "spapr_iommu: New TCE table, liobn=0x%x, context @ %p, "
 153            "table @ %p, fd=%d\n", liobn, &tcet->dma, tcet->table, tcet->fd);
 154#endif
 155
 156    QLIST_INSERT_HEAD(&spapr_tce_tables, tcet, list);
 157
 158    return &tcet->dma;
 159}
 160
 161void spapr_tce_free(DMAContext *dma)
 162{
 163
 164    if (dma) {
 165        sPAPRTCETable *tcet = DO_UPCAST(sPAPRTCETable, dma, dma);
 166
 167        QLIST_REMOVE(tcet, list);
 168
 169        if (!kvm_enabled() ||
 170            (kvmppc_remove_spapr_tce(tcet->table, tcet->fd,
 171                                     tcet->window_size) != 0)) {
 172            g_free(tcet->table);
 173        }
 174
 175        g_free(tcet);
 176    }
 177}
 178
 179void spapr_tce_set_bypass(DMAContext *dma, bool bypass)
 180{
 181    sPAPRTCETable *tcet = DO_UPCAST(sPAPRTCETable, dma, dma);
 182
 183    tcet->bypass = bypass;
 184}
 185
 186void spapr_tce_reset(DMAContext *dma)
 187{
 188    sPAPRTCETable *tcet = DO_UPCAST(sPAPRTCETable, dma, dma);
 189    size_t table_size = (tcet->window_size >> SPAPR_TCE_PAGE_SHIFT)
 190        * sizeof(sPAPRTCE);
 191
 192    tcet->bypass = false;
 193    memset(tcet->table, 0, table_size);
 194}
 195
 196static target_ulong put_tce_emu(sPAPRTCETable *tcet, target_ulong ioba,
 197                                target_ulong tce)
 198{
 199    sPAPRTCE *tcep;
 200
 201    if (ioba >= tcet->window_size) {
 202        hcall_dprintf("spapr_vio_put_tce on out-of-boards IOBA 0x"
 203                      TARGET_FMT_lx "\n", ioba);
 204        return H_PARAMETER;
 205    }
 206
 207    tcep = tcet->table + (ioba >> SPAPR_TCE_PAGE_SHIFT);
 208    tcep->tce = tce;
 209
 210    return H_SUCCESS;
 211}
 212
 213static target_ulong h_put_tce(PowerPCCPU *cpu, sPAPREnvironment *spapr,
 214                              target_ulong opcode, target_ulong *args)
 215{
 216    target_ulong liobn = args[0];
 217    target_ulong ioba = args[1];
 218    target_ulong tce = args[2];
 219    sPAPRTCETable *tcet = spapr_tce_find_by_liobn(liobn);
 220
 221    if (liobn & 0xFFFFFFFF00000000ULL) {
 222        hcall_dprintf("spapr_vio_put_tce on out-of-boundsw LIOBN "
 223                      TARGET_FMT_lx "\n", liobn);
 224        return H_PARAMETER;
 225    }
 226
 227    ioba &= ~(SPAPR_TCE_PAGE_SIZE - 1);
 228
 229    if (tcet) {
 230        return put_tce_emu(tcet, ioba, tce);
 231    }
 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#endif
 237
 238    return H_PARAMETER;
 239}
 240
 241void spapr_iommu_init(void)
 242{
 243    QLIST_INIT(&spapr_tce_tables);
 244
 245    /* hcall-tce */
 246    spapr_register_hypercall(H_PUT_TCE, h_put_tce);
 247}
 248
 249int spapr_dma_dt(void *fdt, int node_off, const char *propname,
 250                 uint32_t liobn, uint64_t window, uint32_t size)
 251{
 252    uint32_t dma_prop[5];
 253    int ret;
 254
 255    dma_prop[0] = cpu_to_be32(liobn);
 256    dma_prop[1] = cpu_to_be32(window >> 32);
 257    dma_prop[2] = cpu_to_be32(window & 0xFFFFFFFF);
 258    dma_prop[3] = 0; /* window size is 32 bits */
 259    dma_prop[4] = cpu_to_be32(size);
 260
 261    ret = fdt_setprop_cell(fdt, node_off, "ibm,#dma-address-cells", 2);
 262    if (ret < 0) {
 263        return ret;
 264    }
 265
 266    ret = fdt_setprop_cell(fdt, node_off, "ibm,#dma-size-cells", 2);
 267    if (ret < 0) {
 268        return ret;
 269    }
 270
 271    ret = fdt_setprop(fdt, node_off, propname, dma_prop, sizeof(dma_prop));
 272    if (ret < 0) {
 273        return ret;
 274    }
 275
 276    return 0;
 277}
 278
 279int spapr_tcet_dma_dt(void *fdt, int node_off, const char *propname,
 280                      DMAContext *iommu)
 281{
 282    if (!iommu) {
 283        return 0;
 284    }
 285
 286    if (iommu->translate == spapr_tce_translate) {
 287        sPAPRTCETable *tcet = DO_UPCAST(sPAPRTCETable, dma, iommu);
 288        return spapr_dma_dt(fdt, node_off, propname,
 289                tcet->liobn, 0, tcet->window_size);
 290    }
 291
 292    return -1;
 293}
 294