patch-2.1.53 linux/arch/sparc64/kernel/psycho.c

Next file: linux/arch/sparc64/kernel/rtrap.S
Previous file: linux/arch/sparc64/kernel/process.c
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.1.52/linux/arch/sparc64/kernel/psycho.c linux/arch/sparc64/kernel/psycho.c
@@ -1,4 +1,4 @@
-/* $Id: psycho.c,v 1.5 1997/08/15 06:44:18 davem Exp $
+/* $Id: psycho.c,v 1.22 1997/08/31 03:51:40 davem Exp $
  * psycho.c: Ultra/AX U2P PCI controller support.
  *
  * Copyright (C) 1997 David S. Miller (davem@caipfs.rutgers.edu)
@@ -62,6 +62,33 @@
 		~(sizeof(unsigned long) - 1));
 }
 
+static unsigned long psycho_iommu_init(struct linux_psycho *psycho,
+				       unsigned long memory_start)
+{
+	unsigned long tsbbase = PAGE_ALIGN(memory_start);
+	unsigned long control, i;
+	unsigned long *iopte;
+
+	memory_start = (tsbbase + ((32 * 1024) * 8));
+	iopte = (unsigned long *)tsbbase;
+
+	for(i = 0; i < (65536 / 2); i++) {
+		*iopte = (IOPTE_VALID | IOPTE_64K |
+			  IOPTE_CACHE | IOPTE_WRITE);
+		*iopte |= (i << 16);
+		iopte++;
+	}
+
+	psycho->psycho_regs->iommu_tsbbase = __pa(tsbbase);
+
+	control = psycho->psycho_regs->iommu_control;
+	control &= ~(IOMMU_CTRL_TSBSZ);
+	control |= (IOMMU_TSBSZ_32K | IOMMU_CTRL_TBWSZ | IOMMU_CTRL_ENAB);
+	psycho->psycho_regs->iommu_control = control;
+
+	return memory_start;
+}
+
 extern void prom_pbm_ranges_init(int node, struct linux_pbm_info *pbm);
 
 unsigned long pcibios_init(unsigned long memory_start, unsigned long memory_end)
@@ -71,7 +98,6 @@
 	u32 portid;
 	int node;
 
-	/* prom_printf("PSYCHO: Probing for controllers.\n"); */
 	printk("PSYCHO: Probing for controllers.\n");
 
 	memory_start = long_align(memory_start);
@@ -126,10 +152,13 @@
 			prom_halt();
 		}
 
-		prom_printf("PSYCHO: Found controller, main regs at %p\n",
-			    psycho->psycho_regs);
 		printk("PSYCHO: Found controller, main regs at %p\n",
 		       psycho->psycho_regs);
+#if 0
+		printk("PSYCHO: Interrupt retry [%016lx]\n",
+		       psycho->psycho_regs->irq_retry);
+#endif
+		psycho->psycho_regs->irq_retry = 0xff;
 
 		/* Now map in PCI config space for entire PSYCHO. */
 		psycho->pci_config_space =
@@ -142,34 +171,17 @@
 			prom_halt();
 		}
 
-		/* Finally map in I/O space for both PBM's.  This is essentially
-		 * backwards compatability for non-conformant PCI cards which
-		 * do not map themselves into the PCI memory space.
-		 */
-		psycho->pbm_B.pbm_IO = __va(pr_regs[2].phys_addr + 0x02000000UL);
-		psycho->pbm_A.pbm_IO = __va(pr_regs[2].phys_addr + 0x02010000UL);
-
-		/* Now record MEM space for both PBM's.
-		 *
-		 * XXX Eddie, these can be reversed if BOOT_BUS pin is clear, is
-		 * XXX there some way to find out what value of BOOT_BUS pin is?
-		 */
-		psycho->pbm_B.pbm_mem = __va(pr_regs[2].phys_addr + 0x180000000UL);
-		psycho->pbm_A.pbm_mem = __va(pr_regs[2].phys_addr + 0x100000000UL);
-
 		/* Report some more info. */
-		prom_printf("PSYCHO: PCI config space at %p\n",
-			    psycho->pci_config_space);
-		prom_printf("PSYCHO: PBM A I/O space at %p, PBM B I/O at %p\n",
-			    psycho->pbm_A.pbm_IO, psycho->pbm_B.pbm_IO);
-		prom_printf("PSYCHO: PBM A MEM at %p, PBM B MEM at %p\n");
-
 		printk("PSYCHO: PCI config space at %p\n", psycho->pci_config_space);
-		printk("PSYCHO: PBM A I/O space at %p, PBM B I/O at %p\n",
-		       psycho->pbm_A.pbm_IO, psycho->pbm_B.pbm_IO);
+
+		memory_start = psycho_iommu_init(psycho, memory_start);
 
 		is_pbm_a = ((pr_regs[0].phys_addr & 0x6000) == 0x2000);
 
+		/* Enable arbitration for all PCI slots. */
+		psycho->psycho_regs->pci_a_control |= 0x3f;
+		psycho->psycho_regs->pci_b_control |= 0x3f;
+
 	other_pbm:
 		if(is_pbm_a)
 			pbm = &psycho->pbm_A;
@@ -177,6 +189,8 @@
 			pbm = &psycho->pbm_B;
 
 		pbm->parent = psycho;
+		pbm->IO_assignments = NULL;
+		pbm->MEM_assignments = NULL;
 		pbm->prom_node = node;
 
 		prom_getstring(node, "name", namebuf, sizeof(namebuf));
@@ -256,6 +270,97 @@
 	return PCIBIOS_DEVICE_NOT_FOUND;
 }
 
+static inline struct pci_vma *pci_find_vma(struct linux_pbm_info *pbm,
+					   unsigned long start,
+					   int io)
+{
+	struct pci_vma *vp = (io ? pbm->IO_assignments : pbm->MEM_assignments);
+
+	while(vp) {
+		if(vp->end > start)
+			break;
+		vp = vp->next;
+	}
+	return vp;
+}
+
+static inline void pci_add_vma(struct linux_pbm_info *pbm, struct pci_vma *new, int io)
+{
+	struct pci_vma *vp = (io ? pbm->IO_assignments : pbm->MEM_assignments);
+
+	if(!vp) {
+		new->next = NULL;
+		if(io)
+			pbm->IO_assignments = new;
+		else
+			pbm->MEM_assignments = new;
+	} else {
+		struct pci_vma *prev = NULL;
+
+		while(vp && (vp->end < new->end)) {
+			prev = vp;
+			vp = vp->next;
+		}
+		new->next = vp;
+		if(!prev) {
+			if(io)
+				pbm->IO_assignments = new;
+			else
+				pbm->MEM_assignments = new;
+		} else {
+			prev->next = new;
+		}
+
+		/* Check for programming errors. */
+		if(vp &&
+		   ((vp->start >= new->start && vp->start < new->end) ||
+		    ((vp->end - 1) >= new->start && (vp->end - 1) < new->end))) {
+			prom_printf("pci_add_vma: Wheee, overlapping %s PCI vma's\n",
+				    io ? "IO" : "MEM");
+			prom_printf("pci_add_vma: vp[%016lx:%016lx] "
+				    "new[%016lx:%016lx]\n",
+				    vp->start, vp->end,
+				    new->start, new->end);
+		}
+	}
+}
+
+static unsigned long *pci_alloc_arena = NULL;
+
+static inline void pci_init_alloc_init(unsigned long *mstart)
+{
+	pci_alloc_arena = mstart;
+}
+
+static inline void pci_init_alloc_fini(void)
+{
+	pci_alloc_arena = NULL;
+}
+
+static void *pci_init_alloc(int size)
+{
+	unsigned long start = long_align(*pci_alloc_arena);
+	void *mp = (void *)start;
+
+	if(!pci_alloc_arena) {
+		prom_printf("pci_init_alloc: pci_vma arena not init'd\n");
+		prom_halt();
+	}
+	start += size;
+	*pci_alloc_arena = start;
+	return mp;
+}
+
+static inline struct pci_vma *pci_vma_alloc(void)
+{
+	return pci_init_alloc(sizeof(struct pci_vma));
+}
+
+static inline struct pcidev_cookie *pci_devcookie_alloc(void)
+{
+	return pci_init_alloc(sizeof(struct pcidev_cookie));
+}
+
 static void pbm_probe(struct linux_pbm_info *pbm, unsigned long *mstart)
 {
 	struct pci_bus *pbus = &pbm->pci_bus;
@@ -270,6 +375,51 @@
 	pbus->subordinate = pci_scan_bus(pbus, mstart);
 }
 
+static int pdev_to_pnode_sibtraverse(struct linux_pbm_info *pbm,
+				     struct pci_dev *pdev,
+				     int node)
+{
+	struct linux_prom_pci_registers pregs[PROMREG_MAX];
+	int err;
+
+	while(node) {
+		int child;
+
+		child = prom_getchild(node);
+		if(child != 0 && child != -1) {
+			int res;
+
+			res = pdev_to_pnode_sibtraverse(pbm, pdev, child);
+			if(res != 0 && res != -1)
+				return res;
+		}
+		err = prom_getproperty(node, "reg", (char *)&pregs[0], sizeof(pregs));
+		if(err != 0 && err != -1) {
+			u32 devfn = (pregs[0].phys_hi >> 8) & 0xff;
+
+			if(devfn == pdev->devfn)
+				return node; /* Match */
+		}
+
+		node = prom_getsibling(node);
+	}
+	return 0;
+}
+
+static void pdev_cookie_fillin(struct linux_pbm_info *pbm, struct pci_dev *pdev)
+{
+	struct pcidev_cookie *pcp;
+	int node = prom_getchild(pbm->prom_node);
+
+	node = pdev_to_pnode_sibtraverse(pbm, pdev, node);
+	if(node == 0)
+		node = -1;
+	pcp = pci_devcookie_alloc();
+	pcp->pbm = pbm;
+	pcp->prom_node = node;
+	pdev->sysdata = pcp;
+}
+
 static void fill_in_pbm_cookies(struct linux_pbm_info *pbm)
 {
 	struct pci_bus *pbtmp, *pbus = &pbm->pci_bus;
@@ -280,42 +430,638 @@
 
 	for( ; pbus; pbus = pbus->children)
 		for(pdev = pbus->devices; pdev; pdev = pdev->sibling)
-			pdev->sysdata = pbm;
+			pdev_cookie_fillin(pbm, pdev);
 }
 
-static void fixup_pci_dev(struct pci_dev *pdev,
-			  struct pci_bus *pbus,
-			  struct linux_pbm_info *pbm)
+/* #define RECORD_ASSIGNMENTS_DEBUG */
+
+/* Walk PROM device tree under PBM, looking for 'assigned-address'
+ * properties, and recording them in pci_vma's linked in via
+ * PBM->assignments.
+ */
+static int gimme_ebus_assignments(int node, struct linux_prom_pci_registers *aregs)
 {
-	struct linux_prom_pci_registers pregs[PROMREG_MAX];
-	int node;
-#if 0
-	int nregs, busno = pbus->number;
-#endif
+	struct linux_prom_ebus_ranges erng[PROMREG_MAX];
+	int err, iter;
+
+	err = prom_getproperty(node, "ranges", (char *)&erng[0], sizeof(erng));
+	if(err == 0 || err == -1) {
+		prom_printf("EBUS: fatal error, no range property.\n");
+		prom_halt();
+	}
+	err = (err / sizeof(struct linux_prom_ebus_ranges));
+	for(iter = 0; iter < err; iter++) {
+		struct linux_prom_ebus_ranges *ep = &erng[iter];
+		struct linux_prom_pci_registers *ap = &aregs[iter];
+
+		ap->phys_hi = ep->parent_phys_hi;
+		ap->phys_mid = ep->parent_phys_mid;
+		ap->phys_lo = ep->parent_phys_lo;
+	}
+	return err;
+}
+
+static void assignment_process(struct linux_pbm_info *pbm, int node)
+{
+	struct linux_prom_pci_registers aregs[PROMREG_MAX];
+	char pname[256];
+	int err, iter, numa;
 
-	node = prom_getchild(pbm->prom_node);
+	err = prom_getproperty(node, "name", (char *)&pname[0], sizeof(pname));
+	if(strncmp(pname, "ebus", 4) == 0) {
+		numa = gimme_ebus_assignments(node, &aregs[0]);
+	} else {
+		err = prom_getproperty(node, "assigned-addresses",
+				       (char *)&aregs[0], sizeof(aregs));
+
+		/* No assignments, nothing to do. */
+		if(err == 0 || err == -1)
+			return;
+
+		numa = (err / sizeof(struct linux_prom_pci_ranges));
+	}
+
+	for(iter = 0; iter < numa; iter++) {
+		struct linux_prom_pci_registers *ap = &aregs[iter];
+		struct pci_vma *vp;
+		int space, breg, io;
+
+		space = (ap->phys_hi >> 24) & 3;
+		if(space != 1 && space != 2)
+			continue;
+		io = (space == 1);
+
+		breg = (ap->phys_hi & 0xff);
+		if(breg == PCI_ROM_ADDRESS)
+			continue;
+
+		vp = pci_vma_alloc();
+
+		/* XXX Means we don't support > 32-bit range of
+		 * XXX PCI MEM space, PSYCHO/PBM does not support it
+		 * XXX either due to it's layout so...
+		 */
+		vp->start = ap->phys_lo;
+		vp->end = vp->start + ap->size_lo;
+		vp->base_reg = breg;
+
+		/* Sanity */
+		if(io && (vp->end & ~(0xffff))) {
+			prom_printf("assignment_process: Out of range PCI I/O "
+				    "[%08lx:%08lx]\n", vp->start, vp->end);
+			prom_halt();
+		}
+
+		pci_add_vma(pbm, vp, io);
+	}
+}
+
+static void assignment_walk_siblings(struct linux_pbm_info *pbm, int node)
+{
 	while(node) {
-		u32 devfn;
-		int err, nregs;
+		int child = prom_getchild(node);
+		if(child)
+			assignment_walk_siblings(pbm, child);
 
-		err = prom_getproperty(node, "reg", (char *)&pregs[0], sizeof(pregs));
-		if(err == 0 || err == -1) {
-			prom_printf("fixup_pci_dev: No PCI device reg property?!?!\n");
+		assignment_process(pbm, node);
+
+		node = prom_getsibling(node);
+	}
+}
+
+static void record_assignments(struct linux_pbm_info *pbm)
+{
+	assignment_walk_siblings(pbm, prom_getchild(pbm->prom_node));
+}
+
+/* #define FIXUP_REGS_DEBUG */
+
+static void fixup_regs(struct pci_dev *pdev,
+		       struct linux_pbm_info *pbm,
+		       struct linux_prom_pci_registers *pregs,
+		       int nregs,
+		       struct linux_prom_pci_registers *assigned,
+		       int numaa)
+{
+	int preg, rng;
+	int IO_seen = 0;
+	int MEM_seen = 0;
+
+	for(preg = 0; preg < nregs; preg++) {
+		struct linux_prom_pci_registers *ap = NULL;
+		int bustype = (pregs[preg].phys_hi >> 24) & 0x3;
+		int bsreg, brindex;
+		u64 pci_addr;
+
+		if(bustype == 0) {
+			/* Config space cookie, nothing to do. */
+			if(preg != 0)
+				prom_printf("fixup_doit: strange, config space not 0\n");
+			continue;
+		} else if(bustype == 3) {
+			/* XXX add support for this... */
+			prom_printf("fixup_doit: Warning, ignoring 64-bit PCI "
+				    "memory space, tell DaveM.\n");
+			continue;
+		}
+		bsreg = (pregs[preg].phys_hi & 0xff);
+
+		/* We can safely ignore these. */
+		if(bsreg == PCI_ROM_ADDRESS)
+			continue;
+
+		/* Sanity */
+		if((bsreg < PCI_BASE_ADDRESS_0) ||
+		   (bsreg > (PCI_BASE_ADDRESS_5 + 4)) ||
+		   (bsreg & 3)) {
+			prom_printf("fixup_doit: Warning, ignoring bogus basereg [%x]\n",
+				    bsreg);
+			continue;
+		}
+
+		brindex = (bsreg - PCI_BASE_ADDRESS_0) >> 2;
+		if(numaa) {
+			int r;
+
+			for(r = 0; r < numaa; r++) {
+				int abreg;
+
+				abreg = (assigned[r].phys_hi & 0xff);
+				if(abreg == bsreg) {
+					ap = &assigned[r];
+					break;
+				}
+			}
+		}
+
+		/* Now construct UPA physical address. */
+		pci_addr  = (((u64)pregs[preg].phys_mid) << 32UL);
+		pci_addr |= (((u64)pregs[preg].phys_lo));
+
+		if(ap) {
+			pci_addr += ((u64)ap->phys_lo);
+			pci_addr += (((u64)ap->phys_mid) << 32UL);
+		}
+
+		/* Final step, apply PBM range. */
+		for(rng = 0; rng < pbm->num_pbm_ranges; rng++) {
+			struct linux_prom_pci_ranges *rp = &pbm->pbm_ranges[rng];
+			int space = (rp->child_phys_hi >> 24) & 3;
+
+			if(space == bustype) {
+				pci_addr += ((u64)rp->parent_phys_lo);
+				pci_addr += (((u64)rp->parent_phys_hi) << 32UL);
+				break;
+			}
+		}
+		if(rng == pbm->num_pbm_ranges) {
+			/* AIEEE */
+			prom_printf("fixup_doit: YIEEE, cannot find PBM ranges\n");
+		}
+		pdev->base_address[brindex] = (unsigned long)__va(pci_addr);
+
+		/* Preserve I/O space bit. */
+		if(bustype == 0x1) {
+			pdev->base_address[brindex] |= 1;
+			IO_seen = 1;
+		} else {
+			MEM_seen = 1;
+		}
+	}
+
+	/* Now handle assignments PROM did not take care of. */
+	if(nregs) {
+		int breg;
+
+		for(breg = PCI_BASE_ADDRESS_0; breg <= PCI_BASE_ADDRESS_5; breg += 4) {
+			unsigned int rtmp, ridx = ((breg - PCI_BASE_ADDRESS_0) >> 2);
+			unsigned int base = (unsigned int)pdev->base_address[ridx];
+			struct pci_vma *vp;
+			u64 pci_addr;
+			int io;
+
+			if(pdev->base_address[ridx] > PAGE_OFFSET)
+				continue;
+
+			io = (base & PCI_BASE_ADDRESS_SPACE)==PCI_BASE_ADDRESS_SPACE_IO;
+			base &= ~((io ?
+				   PCI_BASE_ADDRESS_IO_MASK :
+				   PCI_BASE_ADDRESS_MEM_MASK));
+			vp = pci_find_vma(pbm, base, io);
+			if(!vp || vp->start > base) {
+				unsigned int size, new_base;
+
+				pcibios_read_config_dword(pdev->bus->number,
+							  pdev->devfn,
+							  breg, &rtmp);
+				pcibios_write_config_dword(pdev->bus->number,
+							   pdev->devfn,
+							   breg, 0xffffffff);
+				pcibios_read_config_dword(pdev->bus->number,
+							  pdev->devfn,
+							  breg, &size);
+				if(io)
+					size &= ~1;
+				size = (~(size) + 1);
+				if(!size)
+					continue;
+
+				new_base = 0;
+				for(vp=pci_find_vma(pbm,new_base,io); ; vp=vp->next) {
+					if(!vp || new_base + size <= vp->start)
+						break;
+					new_base = (vp->end + (size - 1)) & ~(size-1);
+				}
+				if(vp && (new_base + size > vp->start)) {
+					prom_printf("PCI: Impossible full %s space.\n",
+						    (io ? "IO" : "MEM"));
+					prom_halt();
+				}
+				vp = pci_vma_alloc();
+				vp->start = new_base;
+				vp->end = vp->start + size;
+				vp->base_reg = breg;
+
+				/* Sanity */
+				if(io && vp->end & ~(0xffff)) {
+					prom_printf("PCI: Out of range PCI I/O "
+						    "[%08lx:%08lx] during fixup\n",
+						    vp->start, vp->end);
+					prom_halt();
+				}
+				pci_add_vma(pbm, vp, io);
+
+				rtmp = new_base;
+				if(io)
+					rtmp |= (rtmp & PCI_BASE_ADDRESS_IO_MASK);
+				else
+					rtmp |= (rtmp & PCI_BASE_ADDRESS_MEM_MASK);
+				pcibios_write_config_dword(pdev->bus->number,
+							   pdev->devfn,
+							   breg, rtmp);
+
+				/* Apply PBM ranges and update pci_dev. */
+				pci_addr = new_base;
+				for(rng = 0; rng < pbm->num_pbm_ranges; rng++) {
+					struct linux_prom_pci_ranges *rp;
+					int rspace;
+
+					rp = &pbm->pbm_ranges[rng];
+					rspace = (rp->child_phys_hi >> 24) & 3;
+					if(io && rspace != 1)
+						continue;
+					else if(!io && rspace != 2)
+						continue;
+					pci_addr += ((u64)rp->parent_phys_lo);
+					pci_addr += (((u64)rp->parent_phys_hi)<<32UL);
+					break;
+				}
+				if(rng == pbm->num_pbm_ranges) {
+					/* AIEEE */
+					prom_printf("fixup_doit: YIEEE, cannot find "
+						    "PBM ranges\n");
+				}
+				pdev->base_address[ridx] = (unsigned long)__va(pci_addr);
+
+				/* Preserve I/O space bit. */
+				if(io) {
+					pdev->base_address[ridx] |= 1;
+					IO_seen = 1;
+				} else {
+					MEM_seen = 1;
+				}
+			}
+		}
+	}
+	if(IO_seen || MEM_seen) {
+		unsigned int l;
+
+		pcibios_read_config_dword(pdev->bus->number,
+					  pdev->devfn,
+					  PCI_COMMAND, &l);
+#ifdef FIXUP_REGS_DEBUG
+		prom_printf("[");
+#endif
+		if(IO_seen) {
+#ifdef FIXUP_REGS_DEBUG
+			prom_printf("IO ");
+#endif
+			l |= PCI_COMMAND_IO;
+		}
+		if(MEM_seen) {
+#ifdef FIXUP_REGS_DEBUG
+			prom_printf("MEM");
+#endif
+			l |= PCI_COMMAND_MEMORY;
+		}
+#ifdef FIXUP_REGS_DEBUG
+		prom_printf("]");
+#endif
+		pcibios_write_config_dword(pdev->bus->number,
+					   pdev->devfn,
+					   PCI_COMMAND, l);
+	}
+
+#ifdef FIXUP_REGS_DEBUG
+	prom_printf("REG_FIXUP[%s]: ", pci_strdev(pdev->vendor, pdev->device));
+	for(preg = 0; preg < 6; preg++) {
+		if(pdev->base_address[preg] != 0)
+			prom_printf("%d[%016lx] ", preg, pdev->base_address[preg]);
+	}
+	prom_printf("\n");
+#endif
+}
+
+#define imap_offset(__member) \
+	((unsigned long)(&(((struct psycho_regs *)0)->__member)))
+
+static unsigned long psycho_pcislot_imap_offset(unsigned long ino)
+{
+	unsigned int bus, slot;
+
+	bus = (ino & 0x10) >> 4;
+	slot = (ino & 0x0c) >> 2;
+
+	if(bus == 0) {
+		/* Perform a sanity check, we might as well.
+		 * PBM A only has 2 PCI slots.
+		 */
+		if(slot > 1) {
+			prom_printf("pcislot_imap: Bogus slot on PBM A (%ld)\n", slot);
 			prom_halt();
 		}
-		nregs = (err / sizeof(struct linux_prom_pci_registers));
+		if(slot == 0)
+			return imap_offset(imap_a_slot0);
+		else
+			return imap_offset(imap_a_slot1);
+	} else {
+		switch(slot) {
+		case 0:
+			return imap_offset(imap_b_slot0);
+		case 1:
+			return imap_offset(imap_b_slot1);
+		case 2:
+			return imap_offset(imap_b_slot2);
+		case 3:
+			return imap_offset(imap_b_slot3);
+		default:
+			prom_printf("pcislot_imap: IMPOSSIBLE [%d:%d]\n",
+				    bus, slot);
+			prom_halt();
+			return 0; /* Make gcc happy */
+		};
+	}
+}
 
-		devfn = (pregs[0].phys_hi >> 8) & 0xff;
-		if(devfn == pdev->devfn) {
+/* Exported for EBUS probing layer. */
+unsigned int psycho_irq_build(unsigned int full_ino)
+{
+	unsigned long imap_off, ign, ino;
 
-			return;
+	ign = (full_ino & PSYCHO_IMAP_IGN) >> 6;
+	ino = (full_ino & PSYCHO_IMAP_INO);
+
+	/* Compute IMAP register offset, generic IRQ layer figures out
+	 * the ICLR register address as this is simple given the 32-bit
+	 * irq number and IMAP register address.
+	 */
+	if((ino & 0x20) == 0)
+		imap_off = psycho_pcislot_imap_offset(ino);
+	else {
+		switch(ino) {
+		case 0x20:
+			/* Onboard SCSI. */
+			imap_off = imap_offset(imap_scsi);
+			break;
+
+		case 0x21:
+			/* Onboard Ethernet (ie. CheerIO/HME) */
+			imap_off = imap_offset(imap_eth);
+			break;
+
+		case 0x22:
+			/* Onboard Parallel Port */
+			imap_off = imap_offset(imap_bpp);
+			break;
+
+		case 0x23:
+			/* Audio Record */
+			imap_off = imap_offset(imap_au_rec);
+			break;
+
+		case 0x24:
+			/* Audio Play */
+			imap_off = imap_offset(imap_au_play);
+			break;
+
+		case 0x25:
+			/* Power Fail */
+			imap_off = imap_offset(imap_pfail);
+			break;
+
+		case 0x26:
+			/* Onboard KBD/MOUSE/SERIAL */
+			imap_off = imap_offset(imap_kms);
+			break;
+
+		case 0x27:
+			/* Floppy (ie. fdthree) */
+			imap_off = imap_offset(imap_flpy);
+			break;
+
+		case 0x28:
+			/* Spare HW INT */
+			imap_off = imap_offset(imap_shw);
+			break;
+
+		case 0x29:
+			/* Onboard Keyboard (only) */
+			imap_off = imap_offset(imap_kbd);
+			break;
+
+		case 0x2a:
+			/* Onboard Mouse (only) */
+			imap_off = imap_offset(imap_ms);
+			break;
+
+		case 0x2b:
+			/* Onboard Serial (only) */
+			imap_off = imap_offset(imap_ser);
+			break;
+
+		case 0x32:
+			/* Power Management */
+			imap_off = imap_offset(imap_pmgmt);
+			break;
+
+		default:
+			/* We don't expect anything else.  The other possible
+			 * values are not found in PCI device nodes, and are
+			 * so hardware specific that they should use DCOOKIE's
+			 * anyways.
+			 */
+			prom_printf("psycho_irq_build: Wacky INO [%x]\n", ino);
+			prom_halt();
+		};
+	}
+	imap_off -= imap_offset(imap_a_slot0);
+
+	return pci_irq_encode(imap_off, 0 /* XXX */, ign, ino);
+}
+
+/* #define FIXUP_IRQ_DEBUG */
+
+static void fixup_irq(struct pci_dev *pdev,
+		      struct linux_pbm_info *pbm,
+		      int node)
+{
+	unsigned int prom_irq, portid = pbm->parent->upa_portid;
+	unsigned char pci_irq_line = pdev->irq;
+	int err;
+
+#ifdef FIXUP_IRQ_DEBUG
+	printk("fixup_irq[%s:%s]: ",
+	       pci_strvendor(pdev->vendor),
+	       pci_strdev(pdev->vendor, pdev->device));
+#endif
+	err = prom_getproperty(node, "interrupts", (void *)&prom_irq, sizeof(prom_irq));
+	if(err == 0 || err == -1) {
+		prom_printf("fixup_irq: No interrupts property for dev[%s:%s]\n",
+			    pci_strvendor(pdev->vendor),
+			    pci_strdev(pdev->vendor, pdev->device));
+		prom_halt();
+	}
+
+	/* See if fully specified already (ie. for onboard devices like hme) */
+	if(((prom_irq & PSYCHO_IMAP_IGN) >> 6) == pbm->parent->upa_portid) {
+		pdev->irq = psycho_irq_build(prom_irq);
+#ifdef FIXUP_IRQ_DEBUG
+		printk("fully specified prom_irq[%x] pdev->irq[%x]",
+		       prom_irq, pdev->irq);
+#endif
+	} else {
+		unsigned int bus, slot, line;
+
+		bus = (pbm == &pbm->parent->pbm_B) ? (1 << 4) : 0;
+		line = (pci_irq_line) & 3;
+
+		/* Slot determination is only slightly complex.  Handle
+		 * the easy case first.
+		 */
+		if(pdev->bus->number == pbm->pci_first_busno) {
+			if(pbm == &pbm->parent->pbm_A)
+				slot = (pdev->devfn >> 3) - 1;
+			else
+				slot = ((pdev->devfn >> 3) >> 1) - 1;
+		} else {
+			/* Underneath a bridge, use slot number of parent
+			 * bridge.
+			 */
+			slot = (pdev->bus->self->devfn >> 3) - 1;
+
+			/* Use low slot number bits of child as IRQ line. */
+			line = ((pdev->devfn >> 3) & 3);
 		}
+		slot = (slot << 2);
 
-		node = prom_getsibling(node);
+		pdev->irq = psycho_irq_build((((portid << 6) & PSYCHO_IMAP_IGN) |
+					     (bus | slot | line)));
+#ifdef FIXUP_IRQ_DEBUG
+		do {
+			unsigned char iline, ipin;
+
+			(void)pcibios_read_config_byte(pdev->bus->number,
+						       pdev->devfn,
+						       PCI_INTERRUPT_PIN,
+						       &ipin);
+			(void)pcibios_read_config_byte(pdev->bus->number,
+						       pdev->devfn,
+						       PCI_INTERRUPT_LINE,
+						       &iline);
+			printk("FIXED portid[%x] bus[%x] slot[%x] line[%x] irq[%x] "
+			       "iline[%x] ipin[%x] prom_irq[%x]",
+			       portid, bus>>4, slot>>2, line, pdev->irq,
+			       iline, ipin, prom_irq);
+		} while(0);
+#endif
 	}
+#ifdef FIXUP_IRQ_DEBUG
+	printk("\n");
+#endif
+}
 
-	prom_printf("fixup_pci_dev: Cannot find prom node for PCI device\n");
-	prom_halt();
+static void fixup_doit(struct pci_dev *pdev,
+		       struct linux_pbm_info *pbm,
+		       struct linux_prom_pci_registers *pregs,
+		       int nregs,
+		       int node)
+{
+	struct linux_prom_pci_registers assigned[PROMREG_MAX];
+	int numaa, err;
+
+	/* Get assigned addresses, if any. */
+	err = prom_getproperty(node, "assigned-addresses",
+			       (char *)&assigned[0], sizeof(assigned));
+	if(err == 0 || err == -1)
+		numaa = 0;
+	else
+		numaa = (err / sizeof(struct linux_prom_pci_registers));
+
+	/* First, scan and fixup base registers. */
+	fixup_regs(pdev, pbm, pregs, nregs, &assigned[0], numaa);
+
+	/* Next, fixup interrupt numbers. */
+	fixup_irq(pdev, pbm, node);
+}
+
+static void fixup_pci_dev(struct pci_dev *pdev,
+			  struct pci_bus *pbus,
+			  struct linux_pbm_info *pbm)
+{
+	struct linux_prom_pci_registers pregs[PROMREG_MAX];
+	struct pcidev_cookie *pcp = pdev->sysdata;
+	int node, nregs, err;
+
+	/* If this is a PCI bridge, we must program it. */
+	if(pdev->class >> 8 == PCI_CLASS_BRIDGE_PCI) {
+		unsigned short cmd;
+
+		/* First, enable bus mastering. */
+		pcibios_read_config_word(pdev->bus->number,
+					 pdev->devfn,
+					 PCI_COMMAND, &cmd);
+		cmd |= PCI_COMMAND_MASTER;
+		pcibios_write_config_word(pdev->bus->number,
+					  pdev->devfn,
+					  PCI_COMMAND, cmd);
+
+		/* Now, set cache line size to 64-bytes. */
+		pcibios_write_config_byte(pdev->bus->number,
+					  pdev->devfn,
+					  PCI_CACHE_LINE_SIZE, 64);
+	}
+
+	/* Ignore if this is one of the PBM's, EBUS, or a
+	 * sub-bridge underneath the PBM.  We only need to fixup
+	 * true devices.
+	 */
+	if((pdev->class >> 8 == PCI_CLASS_BRIDGE_PCI) ||
+	   (pdev->class >> 8 == PCI_CLASS_BRIDGE_HOST) ||
+	   (pdev->class >> 8 == PCI_CLASS_BRIDGE_OTHER) ||
+	   (pcp == NULL))
+		return;
+
+	node = pcp->prom_node;
+
+	err = prom_getproperty(node, "reg", (char *)&pregs[0], sizeof(pregs));
+	if(err == 0 || err == -1) {
+		prom_printf("Cannot find REG for pci_dev\n");
+		prom_halt();
+	}
+
+	nregs = (err / sizeof(pregs[0]));
+
+	fixup_doit(pdev, pbm, &pregs[0], nregs, node);
 }
 
 static void fixup_pci_bus(struct pci_bus *pbus, struct linux_pbm_info *pbm)
@@ -344,16 +1090,13 @@
  */
 static void psycho_final_fixup(struct linux_psycho *psycho)
 {
-	/* First, walk all PCI devices found.  For each device, and
-	 * PCI bridge which is not one of the PSYCHO PBM's, fill in the
-	 * sysdata with a pointer to the PBM.
-	 */
-	fill_in_pbm_cookies(&psycho->pbm_A);
-	fill_in_pbm_cookies(&psycho->pbm_B);
-
 	/* Second, fixup base address registers and IRQ lines... */
 	fixup_addr_irq(&psycho->pbm_A);
 	fixup_addr_irq(&psycho->pbm_B);
+
+#if 0
+	prom_halt();
+#endif
 }
 
 unsigned long pcibios_fixup(unsigned long memory_start, unsigned long memory_end)
@@ -380,9 +1123,25 @@
 	/* Probe busses under PBM B. */
 	pbm_probe(&psycho->pbm_B, &memory_start);
 
+	pci_init_alloc_init(&memory_start);
+
+	/* Walk all PCI devices found.  For each device, and
+	 * PCI bridge which is not one of the PSYCHO PBM's, fill in the
+	 * sysdata with a pointer to the PBM (for pci_bus's) or
+	 * a pci_dev cookie (PBM+PROM_NODE, for pci_dev's).
+	 */
+	fill_in_pbm_cookies(&psycho->pbm_A);
+	fill_in_pbm_cookies(&psycho->pbm_B);
+
+	/* See what OBP has taken care of already. */
+	record_assignments(&psycho->pbm_A);
+	record_assignments(&psycho->pbm_B);
 
+	/* Now, fix it all up. */
 	psycho_final_fixup(psycho);
 
+	pci_init_alloc_fini();
+
 	return ebus_init(memory_start, memory_end);
 }
 
@@ -464,8 +1223,6 @@
 	if(out_of_range(bus, device_fn))
 		return PCIBIOS_SUCCESSFUL;
 
-	/* XXX Check no-probe-list conflicts here. XXX */
-
 	pci_poke_in_progress = 1;
 	pci_poke_faulted = 0;
 	__asm__ __volatile__("membar #Sync\n\t"
@@ -485,9 +1242,9 @@
 			*value = (word >> 16) & 0xffff;
 			break;
 		default:
-			prom_printf("pcibios_read_config_word: misaligned "
-				    "reg [%x]\n", where);
-			prom_halt();
+			printk("pcibios_read_config_word: misaligned "
+			       "reg [%x]\n", where);
+			break;
 		};
 	}
 	return PCIBIOS_SUCCESSFUL;
@@ -504,8 +1261,6 @@
 	if(out_of_range(bus, device_fn))
 		return PCIBIOS_SUCCESSFUL;
 
-	/* XXX Check no-probe-list conflicts here. XXX */
-
 	pci_poke_in_progress = 1;
 	pci_poke_faulted = 0;
 	__asm__ __volatile__("membar #Sync\n\t"
@@ -529,8 +1284,6 @@
 	if(out_of_range(bus, device_fn))
 		return PCIBIOS_SUCCESSFUL;
 
-	/* XXX Check no-probe-list conflicts here. XXX */
-
 	pci_poke_in_progress = 1;
 
 	/* Endianness doesn't matter but we have to get the memory
@@ -554,8 +1307,6 @@
 	if(out_of_range(bus, device_fn))
 		return PCIBIOS_SUCCESSFUL;
 
-	/* XXX Check no-probe-list conflicts here. XXX */
-
 	pci_poke_in_progress = 1;
 	__asm__ __volatile__("membar #Sync\n\t"
 			     "stha %0, [%1] %2\n\t"
@@ -573,8 +1324,6 @@
 
 	if(out_of_range(bus, device_fn))
 		return PCIBIOS_SUCCESSFUL;
-
-	/* XXX Check no-probe-list conflicts here. XXX */
 
 	pci_poke_in_progress = 1;
 	__asm__ __volatile__("membar #Sync\n\t"

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen, slshen@lbl.gov