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