vserver 1.9.3
[linux-2.6.git] / arch / ppc64 / kernel / pSeries_iommu.c
index 9174917..abbbe28 100644 (file)
@@ -42,6 +42,7 @@
 #include <asm/pci-bridge.h>
 #include <asm/machdep.h>
 #include <asm/abs_addr.h>
+#include <asm/plpar_wrappers.h>
 #include "pci.h"
 
 
@@ -88,9 +89,153 @@ static void tce_free_pSeries(struct iommu_table *tbl, long index, long npages)
 }
 
 
+static void tce_build_pSeriesLP(struct iommu_table *tbl, long tcenum,
+                               long npages, unsigned long uaddr,
+                               enum dma_data_direction direction)
+{
+       u64 rc;
+       union tce_entry tce;
+
+       tce.te_word = 0;
+       tce.te_rpn = (virt_to_abs(uaddr)) >> PAGE_SHIFT;
+       tce.te_rdwr = 1;
+       if (direction != DMA_TO_DEVICE)
+               tce.te_pciwr = 1;
+
+       while (npages--) {
+               rc = plpar_tce_put((u64)tbl->it_index, 
+                                  (u64)tcenum << 12, 
+                                  tce.te_word );
+               
+               if (rc && printk_ratelimit()) {
+                       printk("tce_build_pSeriesLP: plpar_tce_put failed. rc=%ld\n", rc);
+                       printk("\tindex   = 0x%lx\n", (u64)tbl->it_index);
+                       printk("\ttcenum  = 0x%lx\n", (u64)tcenum);
+                       printk("\ttce val = 0x%lx\n", tce.te_word );
+                       show_stack(current, (unsigned long *)__get_SP());
+               }
+                       
+               tcenum++;
+               tce.te_rpn++;
+       }
+}
+
+DEFINE_PER_CPU(void *, tce_page) = NULL;
+
+static void tce_buildmulti_pSeriesLP(struct iommu_table *tbl, long tcenum,
+                                    long npages, unsigned long uaddr,
+                                    enum dma_data_direction direction)
+{
+       u64 rc;
+       union tce_entry tce, *tcep;
+       long l, limit;
+
+       if (npages == 1)
+               return tce_build_pSeriesLP(tbl, tcenum, npages, uaddr,
+                                          direction);
+
+       tcep = __get_cpu_var(tce_page);
+
+       /* This is safe to do since interrupts are off when we're called
+        * from iommu_alloc{,_sg}()
+        */
+       if (!tcep) {
+               tcep = (void *)__get_free_page(GFP_ATOMIC);
+               /* If allocation fails, fall back to the loop implementation */
+               if (!tcep)
+                       return tce_build_pSeriesLP(tbl, tcenum, npages,
+                                                  uaddr, direction);
+               __get_cpu_var(tce_page) = tcep;
+       }
+
+       tce.te_word = 0;
+       tce.te_rpn = (virt_to_abs(uaddr)) >> PAGE_SHIFT;
+       tce.te_rdwr = 1;
+       if (direction != DMA_TO_DEVICE)
+               tce.te_pciwr = 1;
+
+       /* We can map max one pageful of TCEs at a time */
+       do {
+               /*
+                * Set up the page with TCE data, looping through and setting
+                * the values.
+                */
+               limit = min_t(long, npages, PAGE_SIZE/sizeof(union tce_entry));
+
+               for (l = 0; l < limit; l++) {
+                       tcep[l] = tce;
+                       tce.te_rpn++;
+               }
+
+               rc = plpar_tce_put_indirect((u64)tbl->it_index,
+                                           (u64)tcenum << 12,
+                                           (u64)virt_to_abs(tcep),
+                                           limit);
+
+               npages -= limit;
+               tcenum += limit;
+       } while (npages > 0 && !rc);
+
+       if (rc && printk_ratelimit()) {
+               printk("tce_buildmulti_pSeriesLP: plpar_tce_put failed. rc=%ld\n", rc);
+               printk("\tindex   = 0x%lx\n", (u64)tbl->it_index);
+               printk("\tnpages  = 0x%lx\n", (u64)npages);
+               printk("\ttce[0] val = 0x%lx\n", tcep[0].te_word);
+               show_stack(current, (unsigned long *)__get_SP());
+       }
+}
+
+static void tce_free_pSeriesLP(struct iommu_table *tbl, long tcenum, long npages)
+{
+       u64 rc;
+       union tce_entry tce;
+
+       tce.te_word = 0;
+
+       while (npages--) {
+               rc = plpar_tce_put((u64)tbl->it_index,
+                                  (u64)tcenum << 12,
+                                  tce.te_word);
+
+               if (rc && printk_ratelimit()) {
+                       printk("tce_free_pSeriesLP: plpar_tce_put failed. rc=%ld\n", rc);
+                       printk("\tindex   = 0x%lx\n", (u64)tbl->it_index);
+                       printk("\ttcenum  = 0x%lx\n", (u64)tcenum);
+                       printk("\ttce val = 0x%lx\n", tce.te_word );
+                       show_stack(current, (unsigned long *)__get_SP());
+               }
+
+               tcenum++;
+       }
+}
+
+
+static void tce_freemulti_pSeriesLP(struct iommu_table *tbl, long tcenum, long npages)
+{
+       u64 rc;
+       union tce_entry tce;
+
+       tce.te_word = 0;
+
+       rc = plpar_tce_stuff((u64)tbl->it_index,
+                          (u64)tcenum << 12,
+                          tce.te_word,
+                          npages);
+
+       if (rc && printk_ratelimit()) {
+               printk("tce_freemulti_pSeriesLP: plpar_tce_stuff failed\n");
+               printk("\trc      = %ld\n", rc);
+               printk("\tindex   = 0x%lx\n", (u64)tbl->it_index);
+               printk("\tnpages  = 0x%lx\n", (u64)npages);
+               printk("\ttce val = 0x%lx\n", tce.te_word );
+               show_stack(current, (unsigned long *)__get_SP());
+       }
+}
+
+
 static void iommu_buses_init(void)
 {
-       struct pci_controller* phb;
+       struct pci_controller *phb, *tmp;
        struct device_node *dn, *first_dn;
        int num_slots, num_slots_ilog2;
        int first_phb = 1;
@@ -106,10 +251,10 @@ static void iommu_buses_init(void)
        else
                tcetable_ilog2 = 22;
 
-       /* XXX Should we be using pci_root_buses instead?  -ojn 
+       /* XXX Should we be using pci_root_buses instead?  -ojn
         */
 
-       for (phb=hose_head; phb; phb=phb->next) {
+       list_for_each_entry_safe(phb, tmp, &hose_list, list_node) {
                first_dn = ((struct device_node *)phb->arch_data)->child;
 
                /* Carve 2GB into the largest dma_window_size possible */
@@ -166,24 +311,25 @@ static void iommu_table_setparms(struct pci_controller *phb,
                                 struct device_node *dn,
                                 struct iommu_table *tbl) 
 {
-       phandle node;
-       unsigned long i;
-       struct of_tce_table *oft;
-
-       node = ((struct device_node *)(phb->arch_data))->node;
+       struct device_node *node;
+       unsigned long *basep;
+       unsigned int *sizep;
 
-       oft = NULL;
+       node = (struct device_node *)phb->arch_data;
 
-       for (i=0; of_tce_table[i].node; i++)
-               if(of_tce_table[i].node == node) {
-                       oft = &of_tce_table[i];
-                       break;
-               }
-
-       if (!oft)
-               panic("PCI_DMA: iommu_table_setparms: Can't find phb named '%s' in of_tce_table\n", dn->full_name);
-
-       memset((void *)oft->base, 0, oft->size);
+       if (get_property(node, "linux,has-tce-table", NULL) == NULL) {
+               printk(KERN_ERR "PCI_DMA: iommu_table_setparms: %s has no tce table !\n",
+                     dn->full_name);
+               return;
+       }
+       basep = (unsigned long *)get_property(node, "linux,tce-base", NULL);
+       sizep = (unsigned int *)get_property(node, "linux,tce-size", NULL);
+       if (basep == NULL || sizep == NULL) {
+               printk(KERN_ERR "PCI_DMA: iommu_table_setparms: %s has missing tce"
+                      " entries !\n", dn->full_name);
+               return;
+       }
+       memset((void *)(*basep), 0, *sizep);
 
        tbl->it_busno = phb->bus->number;
        
@@ -207,10 +353,11 @@ static void iommu_table_setparms(struct pci_controller *phb,
        if (phb->dma_window_base_cur > (1 << 19))
                panic("PCI_DMA: Unexpected number of IOAs under this PHB.\n"); 
        
-       tbl->it_base = oft->base;
+       tbl->it_base = *basep;
        tbl->it_index = 0;
        tbl->it_entrysize = sizeof(union tce_entry);
        tbl->it_blocksize = 16;
+       tbl->it_type = TCE_PCI;
 }
 
 /*
@@ -246,6 +393,7 @@ static void iommu_table_setparms_lpar(struct pci_controller *phb,
        tbl->it_index  = dma_window[0];
        tbl->it_entrysize = sizeof(union tce_entry);
        tbl->it_blocksize  = 16;
+       tbl->it_type = TCE_PCI;
 }
 
 
@@ -293,8 +441,16 @@ void iommu_setup_pSeries(void)
 /* These are called very early. */
 void tce_init_pSeries(void)
 {
-       ppc_md.tce_build = tce_build_pSeries;
-       ppc_md.tce_free  = tce_free_pSeries;
+       if (!(systemcfg->platform & PLATFORM_LPAR)) {
+               ppc_md.tce_build = tce_build_pSeries;
+               ppc_md.tce_free  = tce_free_pSeries;
+       } else if (cur_cpu_spec->firmware_features & FW_FEATURE_MULTITCE) {
+               ppc_md.tce_build = tce_buildmulti_pSeriesLP;
+               ppc_md.tce_free  = tce_freemulti_pSeriesLP;
+       } else {
+               ppc_md.tce_build = tce_build_pSeriesLP;
+               ppc_md.tce_free  = tce_free_pSeriesLP;
+       }
 
        pci_iommu_init();
 }