Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8a244590 authored by Muli Ben-Yehuda's avatar Muli Ben-Yehuda Committed by Linus Torvalds
Browse files

x86_64: introduce CalIOC2 support



CalIOC2 is a PCI-e implementation of the Calgary logic. Most of the
programming details are the same, but some differ, e.g., TCE cache
flush. This patch introduces CalIOC2 support - detection and various
support routines. It's not expected to work yet (but will with
follow-on patches).

Signed-off-by: default avatarMuli Ben-Yehuda <muli@il.ibm.com>
Signed-off-by: default avatarAndrew Morton <akpm@linux-foundation.org>
Signed-off-by: default avatarAndi Kleen <ak@suse.de>
Signed-off-by: default avatarLinus Torvalds <torvalds@linux-foundation.org>
parent 35b6dfa0
Loading
Loading
Loading
Loading
+156 −40
Original line number Diff line number Diff line
@@ -50,8 +50,7 @@ int use_calgary __read_mostly = 0;
#endif /* CONFIG_CALGARY_DEFAULT_ENABLED */

#define PCI_DEVICE_ID_IBM_CALGARY 0x02a1
#define PCI_VENDOR_DEVICE_ID_CALGARY \
	(PCI_VENDOR_ID_IBM | PCI_DEVICE_ID_IBM_CALGARY << 16)
#define PCI_DEVICE_ID_IBM_CALIOC2 0x0308

/* we need these for register space address calculation */
#define START_ADDRESS           0xfe000000
@@ -193,6 +192,7 @@ static inline unsigned long verify_bit_range(unsigned long* bitmap,
{
	return ~0UL;
}

#endif /* CONFIG_IOMMU_DEBUG */

static inline unsigned int num_dma_pages(unsigned long dma, unsigned int dmalen)
@@ -346,9 +346,20 @@ static void iommu_free(struct iommu_table *tbl, dma_addr_t dma_addr,

static inline struct iommu_table *find_iommu_table(struct device *dev)
{
	struct pci_dev *pdev;
	struct pci_bus *pbus;
	struct iommu_table *tbl;

	tbl = to_pci_dev(dev)->bus->self->sysdata;
	pdev = to_pci_dev(dev);

	/* is the device behind a bridge? */
	if (unlikely(pdev->bus->parent))
		pbus = pdev->bus->parent;
	else
		pbus = pdev->bus;

	tbl = pbus->self->sysdata;
	BUG_ON(pdev->bus->parent && (tbl->it_busno != pdev->bus->parent->number));

	return tbl;
}
@@ -565,6 +576,21 @@ static inline void __iomem* calgary_reg(void __iomem *bar, unsigned long offset)
	return (void __iomem*)target;
}

static inline int is_calioc2(unsigned short device)
{
	return (device == PCI_DEVICE_ID_IBM_CALIOC2);
}

static inline int is_calgary(unsigned short device)
{
	return (device == PCI_DEVICE_ID_IBM_CALGARY);
}

static inline int is_cal_pci_dev(unsigned short device)
{
	return (is_calgary(device) || is_calioc2(device));
}

static void calgary_tce_cache_blast(struct iommu_table *tbl)
{
	u64 val;
@@ -685,8 +711,14 @@ static void __init calgary_reserve_regions(struct pci_dev *dev)
	iommu_range_reserve(tbl, bad_dma_address, EMERGENCY_PAGES);

	/* avoid the BIOS/VGA first 640KB-1MB region */
	/* for CalIOC2 - avoid the entire first 2MB */
	if (is_calgary(dev->device)) {
		start = (640 * 1024);
		npages = ((1024 - 640) * 1024) >> PAGE_SHIFT;
	} else { /* calioc2 */
		start = 0;
		npages = (2 * 1024 * 1024) >> PAGE_SHIFT;
	}
	iommu_range_reserve(tbl, start, npages);

	/* reserve the two PCI peripheral memory regions in IO space */
@@ -721,15 +753,15 @@ static int __init calgary_setup_tar(struct pci_dev *dev, void __iomem *bbar)

	/* zero out all TAR bits under sw control */
	val64 &= ~TAR_SW_BITS;

	tbl = dev->sysdata;
	table_phys = (u64)__pa(tbl->it_base);

	val64 |= table_phys;

	BUG_ON(specified_table_size > TCE_TABLE_SIZE_8M);
	val64 |= (u64) specified_table_size;

	tbl->tar_val = cpu_to_be64(val64);

	writeq(tbl->tar_val, target);
	readq(target); /* flush */

@@ -760,6 +792,43 @@ static void __init calgary_free_bus(struct pci_dev *dev)
	bus_info[dev->bus->number].tce_space = NULL;
}

static void calgary_dump_error_regs(struct iommu_table *tbl)
{
	void __iomem *bbar = tbl->bbar;
	u32 csr, csmr, plssr, mck;
	void __iomem *target;
	unsigned long phboff = phb_offset(tbl->it_busno);
	unsigned long erroff;
	u32 errregs[7];
	int i;

	/* dump CSR */
	target = calgary_reg(bbar, phboff | PHB_CSR_OFFSET);
	csr = be32_to_cpu(readl(target));
	/* dump PLSSR */
	target = calgary_reg(bbar, phboff | PHB_PLSSR_OFFSET);
	plssr = be32_to_cpu(readl(target));
	/* dump CSMR */
	target = calgary_reg(bbar, phboff | 0x290);
	csmr = be32_to_cpu(readl(target));
	/* dump mck */
	target = calgary_reg(bbar, phboff | 0x800);
	mck = be32_to_cpu(readl(target));

	printk(KERN_EMERG "Calgary: 0x%08x@CSR 0x%08x@PLSSR 0x%08x@CSMR "
	       "0x%08x@MCK\n", csr, plssr, csmr, mck);

	/* dump rest of error regs */
	printk(KERN_EMERG "Calgary: ");
	for (i = 0; i < ARRAY_SIZE(errregs); i++) {
		erroff = (0x810 + (i * 0x10)); /* err regs are at 0x810 - 0x870 */
		target = calgary_reg(bbar, phboff | erroff);
		errregs[i] = be32_to_cpu(readl(target));
		printk("0x%08x@0x%lx ", errregs[i], erroff);
	}
	printk("\n");
}

static void calgary_watchdog(unsigned long data)
{
	struct pci_dev *dev = (struct pci_dev *)data;
@@ -773,8 +842,11 @@ static void calgary_watchdog(unsigned long data)

	/* If no error, the agent ID in the CSR is not valid */
	if (val32 & CSR_AGENT_MASK) {
		printk(KERN_EMERG "calgary_watchdog: DMA error on PHB %#x, "
				  "CSR = %#x\n", dev->bus->number, val32);
		printk(KERN_EMERG "Calgary: DMA error on PHB %#x\n",
		       dev->bus->number);
		calgary_dump_error_regs(tbl);

		/* reset error */
		writel(0, target);

		/* Disable bus that caused the error */
@@ -853,7 +925,9 @@ static void __init calgary_enable_translation(struct pci_dev *dev)
	val32 = be32_to_cpu(readl(target));
	val32 |= PHB_TCE_ENABLE | PHB_DAC_DISABLE | PHB_MCSR_ENABLE;

	printk(KERN_INFO "Calgary: enabling translation on PHB %#x\n", busnum);
	printk(KERN_INFO "Calgary: enabling translation on %s PHB %#x\n",
	       (dev->device == PCI_DEVICE_ID_IBM_CALGARY) ?
	       "Calgary" : "CalIOC2", busnum);
	printk(KERN_INFO "Calgary: errant DMAs will now be prevented on this "
	       "bus.\n");

@@ -894,6 +968,11 @@ static void __init calgary_init_one_nontraslated(struct pci_dev *dev)
{
	pci_dev_get(dev);
	dev->sysdata = NULL;

	/* is the device behind a bridge? */
	if (dev->bus->parent)
		dev->bus->parent->self = dev;
	else
		dev->bus->self = dev;
}

@@ -911,6 +990,13 @@ static int __init calgary_init_one(struct pci_dev *dev)
		goto done;

	pci_dev_get(dev);

	if (dev->bus->parent) {
		if (dev->bus->parent->self)
			printk(KERN_WARNING "Calgary: IEEEE, dev %p has "
			       "bus->parent->self!\n", dev);
		dev->bus->parent->self = dev;
	} else
		dev->bus->self = dev;

	tbl = dev->sysdata;
@@ -951,12 +1037,19 @@ static int __init calgary_locate_bbars(void)
			target = calgary_reg(bbar, offset);

			val = be32_to_cpu(readl(target));

			start_bus = (u8)((val & 0x00FF0000) >> 16);
			end_bus = (u8)((val & 0x0000FF00) >> 8);

			if (end_bus) {
				for (bus = start_bus; bus <= end_bus; bus++) {
					bus_info[bus].bbar = bbar;
					bus_info[bus].phbid = phb;
				}
			} else {
				bus_info[start_bus].bbar = bbar;
				bus_info[start_bus].phbid = phb;
			}
		}
	}

@@ -975,24 +1068,27 @@ static int __init calgary_init(void)
{
	int ret;
	struct pci_dev *dev = NULL;
	void* tce_space;

	ret = calgary_locate_bbars();
	if (ret)
		return ret;

	do {
		dev = pci_get_device(PCI_VENDOR_ID_IBM,
				     PCI_DEVICE_ID_IBM_CALGARY,
				     dev);
		dev = pci_get_device(PCI_VENDOR_ID_IBM, PCI_ANY_ID, dev);
		if (!dev)
			break;
		if (!is_cal_pci_dev(dev->device))
			continue;
		if (!translate_phb(dev)) {
			calgary_init_one_nontraslated(dev);
			continue;
		}
		if (!bus_info[dev->bus->number].tce_space && !translate_empty_slots)
		tce_space = bus_info[dev->bus->number].tce_space;
		if (!tce_space && !translate_empty_slots) {
			printk("Calg: %p failed tce_space check\n", dev);
			continue;

		}
		ret = calgary_init_one(dev);
		if (ret)
			goto error;
@@ -1003,10 +1099,11 @@ static int __init calgary_init(void)
error:
	do {
		dev = pci_get_device_reverse(PCI_VENDOR_ID_IBM,
					      PCI_DEVICE_ID_IBM_CALGARY,
					      dev);
					     PCI_ANY_ID, dev);
		if (!dev)
			break;
		if (!is_cal_pci_dev(dev->device))
			continue;
		if (!translate_phb(dev)) {
			pci_dev_put(dev);
			continue;
@@ -1084,9 +1181,29 @@ static int __init build_detail_arrays(void)
	return 0;
}

void __init detect_calgary(void)
static int __init calgary_bus_has_devices(int bus, unsigned short pci_dev)
{
	int dev;
	u32 val;

	if (pci_dev == PCI_DEVICE_ID_IBM_CALIOC2) {
		/*
		 * FIXME: properly scan for devices accross the
		 * PCI-to-PCI bridge on every CalIOC2 port.
		 */
		return 1;
	}

	for (dev = 1; dev < 8; dev++) {
		val = read_pci_config(bus, dev, 0, 0);
		if (val != 0xffffffff)
			break;
	}
	return (val != 0xffffffff);
}

void __init detect_calgary(void)
{
	int bus;
	void *tbl;
	int calgary_found = 0;
@@ -1143,29 +1260,28 @@ void __init detect_calgary(void)
	specified_table_size = determine_tce_table_size(end_pfn * PAGE_SIZE);

	for (bus = 0; bus < MAX_PHB_BUS_NUM; bus++) {
		int dev;
		struct calgary_bus_info *info = &bus_info[bus];
		unsigned short pci_device;
		u32 val;

		val = read_pci_config(bus, 0, 0, 0);
		pci_device = (val & 0xFFFF0000) >> 16;

		if (read_pci_config(bus, 0, 0, 0) != PCI_VENDOR_DEVICE_ID_CALGARY)
		if (!is_cal_pci_dev(pci_device))
			continue;

		if (info->translation_disabled)
			continue;

		/*
		 * Scan the slots of the PCI bus to see if there is a device present.
		 * The parent bus will be the zero-ith device, so start at 1.
		 */
		for (dev = 1; dev < 8; dev++) {
			val = read_pci_config(bus, dev, 0, 0);
			if (val != 0xffffffff || translate_empty_slots) {
		if (calgary_bus_has_devices(bus, pci_device) ||
		    translate_empty_slots) {
			tbl = alloc_tce_table();
			if (!tbl)
				goto cleanup;
			info->tce_space = tbl;
			calgary_found = 1;
				break;
			}
			printk("Calg: allocated tce_table %p for bus 0x%x\n",
			       info->tce_space, bus);
		}
	}