patch-2.3.16 linux/arch/sparc64/kernel/pci_iommu.c

Next file: linux/arch/sparc64/kernel/pci_psycho.c
Previous file: linux/arch/sparc64/kernel/pci_impl.h
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.3.15/linux/arch/sparc64/kernel/pci_iommu.c linux/arch/sparc64/kernel/pci_iommu.c
@@ -0,0 +1,510 @@
+/* $Id: pci_iommu.c,v 1.1 1999/08/30 10:00:47 davem Exp $
+ * pci_iommu.c: UltraSparc PCI controller IOM/STC support.
+ *
+ * Copyright (C) 1999 David S. Miller (davem@redhat.com)
+ */
+
+#include <asm/pbm.h>
+#include <asm/iommu.h>
+#include <asm/scatterlist.h>
+
+#define PCI_STC_CTXMATCH_ADDR(STC, CTX)	\
+	((STC)->strbuf_ctxmatch_base + ((CTX) << 3))
+
+/* Accessing IOMMU and Streaming Buffer registers.
+ * REG parameter is a physical address.  All registers
+ * are 64-bits in size.
+ */
+#define pci_iommu_read(__reg) \
+({	u64 __ret; \
+	__asm__ __volatile__("ldxa [%1] %2, %0" \
+			     : "=r" (__ret) \
+			     : "r" (__reg), "i" (ASI_PHYS_BYPASS_EC_E) \
+			     : "memory"); \
+	__ret; \
+})
+#define pci_iommu_write(__reg, __val) \
+	__asm__ __volatile__("stxa %0, [%1] %2" \
+			     : /* no outputs */ \
+			     : "r" (__val), "r" (__reg), \
+			       "i" (ASI_PHYS_BYPASS_EC_E))
+
+/* Find a range of iommu mappings of size NPAGES in page
+ * table PGT.  Return pointer to first iopte.
+ */
+static iopte_t *iommu_find_range(unsigned long npages, iopte_t *pgt, int pgt_size)
+{
+	int i;
+
+	pgt_size -= npages;
+	for (i = 0; i < pgt_size; i++) {
+		if (!iopte_val(pgt[i]) & IOPTE_VALID) {
+			int scan;
+
+			for (scan = 1; scan < npages; scan++) {
+				if (iopte_val(pgt[i + scan]) & IOPTE_VALID) {
+					i += scan;
+					goto do_next;
+				}
+			}
+			return &pgt[i];
+		}
+	do_next:
+	}
+	return NULL;
+}
+
+#define IOPTE_CONSISTANT(CTX, PADDR) \
+	(IOPTE_VALID | IOPTE_CACHE | IOPTE_WRITE | \
+	 (((CTX) << 47) & IOPTE_CONTEXT) | \
+	 ((PADDR) & IOPTE_PAGE))
+
+#define IOPTE_STREAMING(CTX, PADDR) \
+	(IOPTE_CONSISTANT(CTX, PADDR) | IOPTE_STBUF)
+
+#define IOPTE_INVALID	0UL
+
+/* Map kernel buffer at ADDR of size SZ using consistant mode
+ * DMA for PCI device PDEV.  Return 32-bit PCI DMA address.
+ */
+u32 pci_map_consistant(struct pci_dev *pdev, void *addr, int sz)
+{
+	struct pcidev_cookie *pcp = pdev->sysdata;
+	struct pci_iommu *iommu = &pcp->pbm->parent->iommu;
+	iopte_t *base;
+	unsigned long flags, npages, oaddr;
+	u32 ret;
+
+	spin_lock_irqsave(&iommu->lock, flags);
+	oaddr = (unsigned long)addr;
+	npages = PAGE_ALIGN(oaddr + sz) - (oaddr & PAGE_MASK);
+	npages >>= PAGE_SHIFT;
+	base = iommu_find_range(npages,
+				iommu->page_table, iommu->page_table_sz);
+	ret = 0;
+	if (base != NULL) {
+		unsigned long i, base_paddr, ctx;
+
+		ret = (iommu->page_table_map_base +
+		       ((base - iommu->page_table) << PAGE_SHIFT));
+		ret |= (oaddr & ~PAGE_MASK);
+		base_paddr = __pa(oaddr & PAGE_MASK);
+		ctx = 0;
+		if (iommu->iommu_has_ctx_flush)
+			ctx = iommu->iommu_cur_ctx++;
+		for (i = 0; i < npages; i++, base++, base_paddr += PAGE_SIZE)
+			iopte_val(*base) = IOPTE_CONSISTANT(ctx, base_paddr);
+	}
+	spin_unlock_irqrestore(&iommu->lock, flags);
+
+	return ret;
+}
+
+/* Unmap a consistant DMA translation. */
+void pci_unmap_consistant(struct pci_dev *pdev, u32 bus_addr, int sz)
+{
+	struct pcidev_cookie *pcp = pdev->sysdata;
+	struct pci_iommu *iommu = &pcp->pbm->parent->iommu;
+	iopte_t *base;
+	unsigned long flags, npages, i, ctx;
+
+	spin_lock_irqsave(&iommu->lock, flags);
+	npages = PAGE_ALIGN(bus_addr + sz) - (bus_addr & PAGE_MASK);
+	npages >>= PAGE_SHIFT;
+	base = iommu->page_table +
+		((bus_addr - iommu->page_table_map_base) >> PAGE_SHIFT);
+
+	/* Data for consistant mappings cannot enter the streaming
+	 * buffers, so we only need to update the TSB and flush
+	 * those entries from the IOMMU's TLB.
+	 */
+
+	/* Step 1: Clear out the TSB entries.  Save away
+	 *         the context if necessary.
+	 */
+	ctx = 0;
+	if (iommu->iommu_has_ctx_flush)
+		ctx = (iopte_val(*base) & IOPTE_CONTEXT) >> 47UL;
+	for (i = 0; i < npages; i++, base++)
+		iopte_val(*base) = IOPTE_INVALID;
+
+	/* Step 2: Flush from IOMMU TLB. */
+	if (iommu->iommu_has_ctx_flush) {
+		pci_iommu_write(iommu->iommu_ctxflush, ctx);
+	} else {
+		bus_addr &= PAGE_MASK;
+		for (i = 0; i < npages; i++, bus_addr += PAGE_SIZE)
+			pci_iommu_write(iommu->iommu_flush, bus_addr);
+	}
+
+	/* Step 3: Ensure completion of previous PIO writes. */
+	(void) pci_iommu_read(iommu->write_complete_reg);
+
+	spin_unlock_irqrestore(&iommu->lock, flags);
+}
+
+/* Map a single buffer at PTR of SZ bytes for PCI DMA
+ * in streaming mode.
+ */
+u32 pci_map_single(struct pci_dev *pdev, void *ptr, int sz)
+{
+	struct pcidev_cookie *pcp = pdev->sysdata;
+	struct pci_iommu *iommu = &pcp->pbm->parent->iommu;
+	iopte_t *base;
+	unsigned long flags, npages, oaddr;
+	u32 ret;
+
+	spin_lock_irqsave(&iommu->lock, flags);
+	oaddr = (unsigned long)ptr;
+	npages = PAGE_ALIGN(oaddr + sz) - (oaddr & PAGE_MASK);
+	npages >>= PAGE_SHIFT;
+	base = iommu_find_range(npages,
+				iommu->page_table, iommu->page_table_sz);
+	ret = 0;
+	if (base != NULL) {
+		unsigned long i, base_paddr, ctx;
+
+		ret = (iommu->page_table_map_base +
+		       ((base - iommu->page_table) << PAGE_SHIFT));
+		ret |= (oaddr & ~PAGE_MASK);
+		base_paddr = __pa(oaddr & PAGE_MASK);
+		ctx = 0;
+		if (iommu->iommu_has_ctx_flush)
+			ctx = iommu->iommu_cur_ctx++;
+		for (i = 0; i < npages; i++, base++, base_paddr += PAGE_SIZE)
+			iopte_val(*base) = IOPTE_STREAMING(ctx, base_paddr);
+	}
+	spin_unlock_irqrestore(&iommu->lock, flags);
+
+	return ret;
+}
+
+/* Unmap a single streaming mode DMA translation. */
+void pci_unmap_single(struct pci_dev *pdev, u32 bus_addr, int sz)
+{
+	struct pcidev_cookie *pcp = pdev->sysdata;
+	struct pci_iommu *iommu = &pcp->pbm->parent->iommu;
+	struct pci_strbuf *strbuf = &pcp->pbm->stc;
+	iopte_t *base;
+	unsigned long flags, npages, i, ctx;
+
+	spin_lock_irqsave(&iommu->lock, flags);
+	npages = PAGE_ALIGN(bus_addr + sz) - (bus_addr & PAGE_MASK);
+	npages >>= PAGE_SHIFT;
+	base = iommu->page_table +
+		((bus_addr - iommu->page_table_map_base) >> PAGE_SHIFT);
+	bus_addr &= PAGE_MASK;
+
+	/* Step 1: Record the context, if any. */
+	ctx = 0;
+	if (iommu->iommu_has_ctx_flush)
+		ctx = (iopte_val(*base) & IOPTE_CONTEXT) >> 47UL;
+
+	/* Step 2: Kick data out of streaming buffers if necessary. */
+	if (strbuf->strbuf_enabled) {
+		u32 vaddr = bus_addr;
+
+		PCI_STC_FLUSHFLAG_INIT(strbuf);
+		if (strbuf->strbuf_has_ctx_flush &&
+		    iommu->iommu_has_ctx_flush) {
+			unsigned long matchreg, flushreg;
+
+			flushreg = strbuf->strbuf_ctxflush;
+			matchreg = PCI_STC_CTXMATCH_ADDR(strbuf, ctx);
+			do {
+				pci_iommu_write(flushreg, ctx);
+			} while(((long)pci_iommu_read(matchreg)) < 0L);
+		} else {
+			for (i = 0; i < npages; i++, vaddr += PAGE_SIZE)
+				pci_iommu_write(strbuf->strbuf_pflush, vaddr);
+		}
+
+		pci_iommu_write(strbuf->strbuf_fsync, strbuf->strbuf_flushflag_pa);
+		(void) pci_iommu_read(iommu->write_complete_reg);
+		while (!PCI_STC_FLUSHFLAG_SET(strbuf))
+			membar("#LoadLoad");
+	}
+
+	/* Step 3: Clear out TSB entries. */
+	for (i = 0; i < npages; i++, base++)
+		iopte_val(*base) = IOPTE_INVALID;
+
+	/* Step 4: Flush the IOMMU TLB. */
+	if (iommu->iommu_has_ctx_flush) {
+		pci_iommu_write(iommu->iommu_ctxflush, ctx);
+	} else {
+		for (i = 0; i < npages; i++, bus_addr += PAGE_SIZE)
+			pci_iommu_write(iommu->iommu_flush, bus_addr);
+	}
+
+	/* Step 5: Ensure completion of previous PIO writes. */
+	(void) pci_iommu_read(iommu->write_complete_reg);
+
+	spin_unlock_irqrestore(&iommu->lock, flags);
+}
+
+/* Map a set of buffers described by SGLIST with NELEMS array
+ * elements in streaming mode for PCI DMA.
+ */
+void pci_map_sg(struct pci_dev *pdev, struct scatterlist *sglist, int nelems)
+{
+	struct pcidev_cookie *pcp = pdev->sysdata;
+	struct pci_iommu *iommu = &pcp->pbm->parent->iommu;
+	unsigned long flags, ctx, i;
+
+	spin_lock_irqsave(&iommu->lock, flags);
+
+	/* Step 1: Choose a context if necessary. */
+	ctx = 0;
+	if (iommu->iommu_has_ctx_flush)
+		ctx = iommu->iommu_cur_ctx++;
+
+	/* Step 2: Create the mappings. */
+	for (i = 0; i < nelems; i++) {
+		unsigned long oaddr, npages;
+		iopte_t *base;
+
+		oaddr = (unsigned long)sglist[i].address;
+		npages = PAGE_ALIGN(oaddr + sglist[i].length) - (oaddr & PAGE_MASK);
+		npages >>= PAGE_SHIFT;
+		base = iommu_find_range(npages,
+					iommu->page_table, iommu->page_table_sz);
+		if (base != NULL) {
+			unsigned long j, base_paddr;
+			u32 dvma_addr;
+
+			dvma_addr = (iommu->page_table_map_base +
+				     ((base - iommu->page_table) << PAGE_SHIFT));
+			dvma_addr |= (oaddr & ~PAGE_MASK);
+			sglist[i].dvma_address = dvma_addr;
+			sglist[i].dvma_length = sglist[i].length;
+			base_paddr = __pa(oaddr & PAGE_MASK);
+			for (j = 0; j < npages; j++, base++, base_paddr += PAGE_SIZE)
+				iopte_val(*base) = IOPTE_STREAMING(ctx, base_paddr);
+		} else {
+			sglist[i].dvma_address = 0;
+			sglist[i].dvma_length = 0;
+		}
+	}
+
+	spin_unlock_irqrestore(&iommu->lock, flags);
+}
+
+/* Unmap a set of streaming mode DMA translations. */
+void pci_unmap_sg(struct pci_dev *pdev, struct scatterlist *sglist, int nelems)
+{
+	struct pcidev_cookie *pcp = pdev->sysdata;
+	struct pci_iommu *iommu = &pcp->pbm->parent->iommu;
+	struct pci_strbuf *strbuf = &pcp->pbm->stc;
+	unsigned long flags, ctx, i;
+
+	spin_lock_irqsave(&iommu->lock, flags);
+
+	/* Step 1: Record the context, if any. */
+	ctx = 0;
+	if (iommu->iommu_has_ctx_flush) {
+		iopte_t *iopte;
+
+		iopte = iommu->page_table +
+			((sglist[0].dvma_address - iommu->page_table_map_base) >> PAGE_SHIFT);
+		ctx = (iopte_val(*iopte) & IOPTE_CONTEXT) >> 47UL;
+	}
+
+	/* Step 2: Kick data out of streaming buffers if necessary. */
+	if (strbuf->strbuf_enabled) {
+		PCI_STC_FLUSHFLAG_INIT(strbuf);
+		if (strbuf->strbuf_has_ctx_flush &&
+		    iommu->iommu_has_ctx_flush) {
+			unsigned long matchreg, flushreg;
+
+			flushreg = strbuf->strbuf_ctxflush;
+			matchreg = PCI_STC_CTXMATCH_ADDR(strbuf, ctx);
+			do {
+				pci_iommu_write(flushreg, ctx);
+			} while(((long)pci_iommu_read(matchreg)) < 0L);
+		} else {
+			for (i = 0; i < nelems; i++) {
+				unsigned long j, npages;
+				u32 vaddr;
+
+				j = sglist[i].dvma_length;
+				if (!j)
+					break;
+				vaddr = sglist[i].dvma_address;
+				npages = PAGE_ALIGN(vaddr + j) - (vaddr & PAGE_MASK);
+				npages >>= PAGE_SHIFT;
+				vaddr &= PAGE_MASK;
+				for (j = 0; j < npages; j++, vaddr += PAGE_SIZE)
+					pci_iommu_write(strbuf->strbuf_pflush, vaddr);
+			}
+
+			pci_iommu_write(strbuf->strbuf_fsync, strbuf->strbuf_flushflag_pa);
+			(void) pci_iommu_read(iommu->write_complete_reg);
+			while (!PCI_STC_FLUSHFLAG_SET(strbuf))
+				membar("#LoadLoad");
+		}
+	}
+
+	/* Step 3: Clear out TSB entries. */
+	for (i = 0; i < nelems; i++) {
+		unsigned long j, npages;
+		iopte_t *base;
+		u32 vaddr;
+
+		j = sglist[i].dvma_length;
+		if (!j)
+			break;
+		vaddr = sglist[i].dvma_address;
+		npages = PAGE_ALIGN(vaddr + j) - (vaddr & PAGE_MASK);
+		npages >>= PAGE_SHIFT;
+		base = iommu->page_table +
+			((vaddr - iommu->page_table_map_base) >> PAGE_SHIFT);
+		for (j = 0; j < npages; j++, base++)
+			iopte_val(*base) = IOPTE_INVALID;
+	}
+
+	/* Step 4: Flush the IOMMU TLB. */
+	if (iommu->iommu_has_ctx_flush) {
+		pci_iommu_write(iommu->iommu_ctxflush, ctx);
+	} else {
+		for (i = 0; i < nelems; i++) {
+			unsigned long j, npages;
+			u32 vaddr;
+
+			j = sglist[i].dvma_length;
+			if (!j)
+				break;
+			vaddr = sglist[i].dvma_address;
+			npages = PAGE_ALIGN(vaddr + j) - (vaddr & PAGE_MASK);
+			npages >>= PAGE_SHIFT;
+			for (j = 0; j < npages; j++, vaddr += PAGE_SIZE)
+				pci_iommu_write(iommu->iommu_flush, vaddr);
+		}
+	}
+
+	/* Step 5: Ensure completion of previous PIO writes. */
+	(void) pci_iommu_read(iommu->write_complete_reg);
+
+	spin_unlock_irqrestore(&iommu->lock, flags);
+}
+
+/* Make physical memory consistant for a single
+ * streaming mode DMA translation after a transfer.
+ */
+void pci_dma_sync_single(struct pci_dev *pdev, u32 bus_addr, int sz)
+{
+	struct pcidev_cookie *pcp = pdev->sysdata;
+	struct pci_iommu *iommu = &pcp->pbm->parent->iommu;
+	struct pci_strbuf *strbuf = &pcp->pbm->stc;
+	unsigned long flags, ctx, npages;
+
+	if (!strbuf->strbuf_enabled)
+		return;
+
+	spin_lock_irqsave(&iommu->lock, flags);
+
+	npages = PAGE_ALIGN(bus_addr + sz) - (bus_addr & PAGE_MASK);
+	npages >>= PAGE_SHIFT;
+	bus_addr &= PAGE_MASK;
+
+	/* Step 1: Record the context, if any. */
+	ctx = 0;
+	if (iommu->iommu_has_ctx_flush &&
+	    strbuf->strbuf_has_ctx_flush) {
+		iopte_t *iopte;
+
+		iopte = iommu->page_table +
+			((bus_addr - iommu->page_table_map_base)>>PAGE_SHIFT);
+		ctx = (iopte_val(*iopte) & IOPTE_CONTEXT) >> 47UL;
+	}
+
+	/* Step 2: Kick data out of streaming buffers. */
+	PCI_STC_FLUSHFLAG_INIT(strbuf);
+	if (iommu->iommu_has_ctx_flush &&
+	    strbuf->strbuf_has_ctx_flush) {
+		unsigned long matchreg, flushreg;
+
+		flushreg = strbuf->strbuf_ctxflush;
+		matchreg = PCI_STC_CTXMATCH_ADDR(strbuf, ctx);
+		do {
+			pci_iommu_write(flushreg, ctx);
+		} while(((long)pci_iommu_read(matchreg)) < 0L);
+	} else {
+		unsigned long i;
+
+		for (i = 0; i < npages; i++, bus_addr += PAGE_SIZE)
+			pci_iommu_write(strbuf->strbuf_pflush, bus_addr);
+	}
+
+	/* Step 3: Perform flush synchronization sequence. */
+	pci_iommu_write(strbuf->strbuf_fsync, strbuf->strbuf_flushflag_pa);
+	(void) pci_iommu_read(iommu->write_complete_reg);
+	while (!PCI_STC_FLUSHFLAG_SET(strbuf))
+		membar("#LoadLoad");
+
+	spin_unlock_irqrestore(&iommu->lock, flags);
+}
+
+/* Make physical memory consistant for a set of streaming
+ * mode DMA translations after a transfer.
+ */
+void pci_dma_sync_sg(struct pci_dev *pdev, struct scatterlist *sglist, int nelems)
+{
+	struct pcidev_cookie *pcp = pdev->sysdata;
+	struct pci_iommu *iommu = &pcp->pbm->parent->iommu;
+	struct pci_strbuf *strbuf = &pcp->pbm->stc;
+	unsigned long flags, ctx;
+
+	if (!strbuf->strbuf_enabled)
+		return;
+
+	spin_lock_irqsave(&iommu->lock, flags);
+
+	/* Step 1: Record the context, if any. */
+	ctx = 0;
+	if (iommu->iommu_has_ctx_flush &&
+	    strbuf->strbuf_has_ctx_flush) {
+		iopte_t *iopte;
+
+		iopte = iommu->page_table +
+			((sglist[0].dvma_address - iommu->page_table_map_base) >> PAGE_SHIFT);
+		ctx = (iopte_val(*iopte) & IOPTE_CONTEXT) >> 47UL;
+	}
+
+	/* Step 2: Kick data out of streaming buffers. */
+	PCI_STC_FLUSHFLAG_INIT(strbuf);
+	if (iommu->iommu_has_ctx_flush &&
+	    strbuf->strbuf_has_ctx_flush) {
+		unsigned long matchreg, flushreg;
+
+		flushreg = strbuf->strbuf_ctxflush;
+		matchreg = PCI_STC_CTXMATCH_ADDR(strbuf, ctx);
+		do {
+			pci_iommu_write(flushreg, ctx);
+		} while (((long)pci_iommu_read(matchreg)) < 0L);
+	} else {
+		unsigned long i;
+
+		for(i = 0; i < nelems; i++) {
+			unsigned long bus_addr, npages, j;
+
+			j = sglist[i].dvma_length;
+			if (!j)
+				break;
+			bus_addr = sglist[i].dvma_address;
+			npages = PAGE_ALIGN(bus_addr + j) - (bus_addr & PAGE_MASK);
+			npages >>= PAGE_SHIFT;
+			bus_addr &= PAGE_MASK;
+			for(j = 0; i < npages; i++, bus_addr += PAGE_SIZE)
+				pci_iommu_write(strbuf->strbuf_pflush, bus_addr);
+		}
+	}
+
+	/* Step 3: Perform flush synchronization sequence. */
+	pci_iommu_write(strbuf->strbuf_fsync, strbuf->strbuf_flushflag_pa);
+	(void) pci_iommu_read(iommu->write_complete_reg);
+	while (!PCI_STC_FLUSHFLAG_SET(strbuf))
+		membar("#LoadLoad");
+
+	spin_unlock_irqrestore(&iommu->lock, flags);
+}

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)