#include <asm/pci-bridge.h>
#include <asm/machdep.h>
#include <asm/abs_addr.h>
+#include <asm/plpar_wrappers.h>
#include "pci.h"
}
+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;
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 */
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;
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;
}
/*
tbl->it_index = dma_window[0];
tbl->it_entrysize = sizeof(union tce_entry);
tbl->it_blocksize = 16;
+ tbl->it_type = TCE_PCI;
}
/* 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();
}