linux/arch/powerpc/platforms/iseries/iommu.c
<<
>>
Prefs
   1/*
   2 * Copyright (C) 2001 Mike Corrigan & Dave Engebretsen, IBM Corporation
   3 *
   4 * Rewrite, cleanup:
   5 *
   6 * Copyright (C) 2004 Olof Johansson <olof@lixom.net>, IBM Corporation
   7 * Copyright (C) 2006 Olof Johansson <olof@lixom.net>
   8 *
   9 * Dynamic DMA mapping support, iSeries-specific parts.
  10 *
  11 *
  12 * This program is free software; you can redistribute it and/or modify
  13 * it under the terms of the GNU General Public License as published by
  14 * the Free Software Foundation; either version 2 of the License, or
  15 * (at your option) any later version.
  16 *
  17 * This program is distributed in the hope that it will be useful,
  18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  20 * GNU General Public License for more details.
  21 *
  22 * You should have received a copy of the GNU General Public License
  23 * along with this program; if not, write to the Free Software
  24 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
  25 */
  26
  27#include <linux/types.h>
  28#include <linux/dma-mapping.h>
  29#include <linux/list.h>
  30#include <linux/pci.h>
  31#include <linux/export.h>
  32#include <linux/slab.h>
  33
  34#include <asm/iommu.h>
  35#include <asm/vio.h>
  36#include <asm/tce.h>
  37#include <asm/machdep.h>
  38#include <asm/abs_addr.h>
  39#include <asm/prom.h>
  40#include <asm/pci-bridge.h>
  41#include <asm/iseries/hv_call_xm.h>
  42#include <asm/iseries/hv_call_event.h>
  43#include <asm/iseries/iommu.h>
  44
  45static int tce_build_iSeries(struct iommu_table *tbl, long index, long npages,
  46                unsigned long uaddr, enum dma_data_direction direction,
  47                struct dma_attrs *attrs)
  48{
  49        u64 rc;
  50        u64 tce, rpn;
  51
  52        while (npages--) {
  53                rpn = virt_to_abs(uaddr) >> TCE_SHIFT;
  54                tce = (rpn & TCE_RPN_MASK) << TCE_RPN_SHIFT;
  55
  56                if (tbl->it_type == TCE_VB) {
  57                        /* Virtual Bus */
  58                        tce |= TCE_VALID|TCE_ALLIO;
  59                        if (direction != DMA_TO_DEVICE)
  60                                tce |= TCE_VB_WRITE;
  61                } else {
  62                        /* PCI Bus */
  63                        tce |= TCE_PCI_READ; /* Read allowed */
  64                        if (direction != DMA_TO_DEVICE)
  65                                tce |= TCE_PCI_WRITE;
  66                }
  67
  68                rc = HvCallXm_setTce((u64)tbl->it_index, (u64)index, tce);
  69                if (rc)
  70                        panic("PCI_DMA: HvCallXm_setTce failed, Rc: 0x%llx\n",
  71                                        rc);
  72                index++;
  73                uaddr += TCE_PAGE_SIZE;
  74        }
  75        return 0;
  76}
  77
  78static void tce_free_iSeries(struct iommu_table *tbl, long index, long npages)
  79{
  80        u64 rc;
  81
  82        while (npages--) {
  83                rc = HvCallXm_setTce((u64)tbl->it_index, (u64)index, 0);
  84                if (rc)
  85                        panic("PCI_DMA: HvCallXm_setTce failed, Rc: 0x%llx\n",
  86                                        rc);
  87                index++;
  88        }
  89}
  90
  91/*
  92 * Structure passed to HvCallXm_getTceTableParms
  93 */
  94struct iommu_table_cb {
  95        unsigned long   itc_busno;      /* Bus number for this tce table */
  96        unsigned long   itc_start;      /* Will be NULL for secondary */
  97        unsigned long   itc_totalsize;  /* Size (in pages) of whole table */
  98        unsigned long   itc_offset;     /* Index into real tce table of the
  99                                           start of our section */
 100        unsigned long   itc_size;       /* Size (in pages) of our section */
 101        unsigned long   itc_index;      /* Index of this tce table */
 102        unsigned short  itc_maxtables;  /* Max num of tables for partition */
 103        unsigned char   itc_virtbus;    /* Flag to indicate virtual bus */
 104        unsigned char   itc_slotno;     /* IOA Tce Slot Index */
 105        unsigned char   itc_rsvd[4];
 106};
 107
 108/*
 109 * Call Hv with the architected data structure to get TCE table info.
 110 * info. Put the returned data into the Linux representation of the
 111 * TCE table data.
 112 * The Hardware Tce table comes in three flavors.
 113 * 1. TCE table shared between Buses.
 114 * 2. TCE table per Bus.
 115 * 3. TCE Table per IOA.
 116 */
 117void iommu_table_getparms_iSeries(unsigned long busno,
 118                                  unsigned char slotno,
 119                                  unsigned char virtbus,
 120                                  struct iommu_table* tbl)
 121{
 122        struct iommu_table_cb *parms;
 123
 124        parms = kzalloc(sizeof(*parms), GFP_KERNEL);
 125        if (parms == NULL)
 126                panic("PCI_DMA: TCE Table Allocation failed.");
 127
 128        parms->itc_busno = busno;
 129        parms->itc_slotno = slotno;
 130        parms->itc_virtbus = virtbus;
 131
 132        HvCallXm_getTceTableParms(iseries_hv_addr(parms));
 133
 134        if (parms->itc_size == 0)
 135                panic("PCI_DMA: parms->size is zero, parms is 0x%p", parms);
 136
 137        /* itc_size is in pages worth of table, it_size is in # of entries */
 138        tbl->it_size = (parms->itc_size * TCE_PAGE_SIZE) / TCE_ENTRY_SIZE;
 139        tbl->it_busno = parms->itc_busno;
 140        tbl->it_offset = parms->itc_offset;
 141        tbl->it_index = parms->itc_index;
 142        tbl->it_blocksize = 1;
 143        tbl->it_type = virtbus ? TCE_VB : TCE_PCI;
 144
 145        kfree(parms);
 146}
 147
 148
 149#ifdef CONFIG_PCI
 150/*
 151 * This function compares the known tables to find an iommu_table
 152 * that has already been built for hardware TCEs.
 153 */
 154static struct iommu_table *iommu_table_find(struct iommu_table * tbl)
 155{
 156        struct device_node *node;
 157
 158        for (node = NULL; (node = of_find_all_nodes(node)); ) {
 159                struct pci_dn *pdn = PCI_DN(node);
 160                struct iommu_table *it;
 161
 162                if (pdn == NULL)
 163                        continue;
 164                it = pdn->iommu_table;
 165                if ((it != NULL) &&
 166                    (it->it_type == TCE_PCI) &&
 167                    (it->it_offset == tbl->it_offset) &&
 168                    (it->it_index == tbl->it_index) &&
 169                    (it->it_size == tbl->it_size)) {
 170                        of_node_put(node);
 171                        return it;
 172                }
 173        }
 174        return NULL;
 175}
 176
 177
 178static void pci_dma_dev_setup_iseries(struct pci_dev *pdev)
 179{
 180        struct iommu_table *tbl;
 181        struct device_node *dn = pci_device_to_OF_node(pdev);
 182        struct pci_dn *pdn = PCI_DN(dn);
 183        const u32 *lsn = of_get_property(dn, "linux,logical-slot-number", NULL);
 184
 185        BUG_ON(lsn == NULL);
 186
 187        tbl = kzalloc(sizeof(struct iommu_table), GFP_KERNEL);
 188
 189        iommu_table_getparms_iSeries(pdn->busno, *lsn, 0, tbl);
 190
 191        /* Look for existing tce table */
 192        pdn->iommu_table = iommu_table_find(tbl);
 193        if (pdn->iommu_table == NULL)
 194                pdn->iommu_table = iommu_init_table(tbl, -1);
 195        else
 196                kfree(tbl);
 197        set_iommu_table_base(&pdev->dev, pdn->iommu_table);
 198}
 199#else
 200#define pci_dma_dev_setup_iseries       NULL
 201#endif
 202
 203static struct iommu_table veth_iommu_table;
 204static struct iommu_table vio_iommu_table;
 205
 206void *iseries_hv_alloc(size_t size, dma_addr_t *dma_handle, gfp_t flag)
 207{
 208        return iommu_alloc_coherent(NULL, &vio_iommu_table, size, dma_handle,
 209                                DMA_BIT_MASK(32), flag, -1);
 210}
 211EXPORT_SYMBOL_GPL(iseries_hv_alloc);
 212
 213void iseries_hv_free(size_t size, void *vaddr, dma_addr_t dma_handle)
 214{
 215        iommu_free_coherent(&vio_iommu_table, size, vaddr, dma_handle);
 216}
 217EXPORT_SYMBOL_GPL(iseries_hv_free);
 218
 219dma_addr_t iseries_hv_map(void *vaddr, size_t size,
 220                        enum dma_data_direction direction)
 221{
 222        return iommu_map_page(NULL, &vio_iommu_table, virt_to_page(vaddr),
 223                              (unsigned long)vaddr % PAGE_SIZE, size,
 224                              DMA_BIT_MASK(32), direction, NULL);
 225}
 226
 227void iseries_hv_unmap(dma_addr_t dma_handle, size_t size,
 228                        enum dma_data_direction direction)
 229{
 230        iommu_unmap_page(&vio_iommu_table, dma_handle, size, direction, NULL);
 231}
 232
 233void __init iommu_vio_init(void)
 234{
 235        iommu_table_getparms_iSeries(255, 0, 0xff, &veth_iommu_table);
 236        veth_iommu_table.it_size /= 2;
 237        vio_iommu_table = veth_iommu_table;
 238        vio_iommu_table.it_offset += veth_iommu_table.it_size;
 239
 240        if (!iommu_init_table(&veth_iommu_table, -1))
 241                printk("Virtual Bus VETH TCE table failed.\n");
 242        if (!iommu_init_table(&vio_iommu_table, -1))
 243                printk("Virtual Bus VIO TCE table failed.\n");
 244}
 245
 246struct iommu_table *vio_build_iommu_table_iseries(struct vio_dev *dev)
 247{
 248        if (strcmp(dev->type, "network") == 0)
 249                return &veth_iommu_table;
 250        return &vio_iommu_table;
 251}
 252
 253void iommu_init_early_iSeries(void)
 254{
 255        ppc_md.tce_build = tce_build_iSeries;
 256        ppc_md.tce_free  = tce_free_iSeries;
 257
 258        ppc_md.pci_dma_dev_setup = pci_dma_dev_setup_iseries;
 259        set_pci_dma_ops(&dma_iommu_ops);
 260}
 261