Subject: PATCH: pci_auto bridge support

From: "Bradley D. LaRonde" <[email-masked]>
Date: Fri, 26 Oct 2001 21:07:46 -0400

2001-10-26  Bradley D. LaRonde <[email-masked]>

* PCI bridge support.  See change log entry below.

--- arch/mips/kernel/pci_auto.c	2001/08/18 06:21:53	1.1
+++ arch/mips/kernel/pci_auto.c	2001/10/27 01:01:21
@@ -4,6 +4,7 @@
  * Author: Matt Porter <[email-masked]>
  *
  * Copyright 2000, 2001 MontaVista Software Inc.
+ * Copyright 2001 Bradley D. LaRonde <[email-masked]>
  *
  * This program is free software; you can redistribute  it and/or modify it
  * under  the terms of  the GNU General  Public License as published by the
@@ -19,6 +20,15 @@
  * . change most int to u32.
  *
  * Further modified to include it as mips generic code, [email-masked].
+ *
+ * 2001-10-26  Bradley D. LaRonde <[email-masked]>
+ * - Add a top_bus argument to the "early config" functions so that
+ *   they can set a fake parent bus pointer to convince the underlying
+ *   pci ops to use type 1 configuration for sub busses.
+ * - Set bridge base and limit registers correctly.
+ * - Align io and memory base properly before and after bridge setup.
+ * - Don't fall through to pci_setup_bars for bridge.
+ * - Reformat the debug output to look more like lspci's output.
  */
 
 #include <linux/kernel.h>
@@ -34,14 +44,47 @@
 #else
 #define	DBG(x...)	
 #endif
+
+/*
+ * These functions are used early on before PCI scanning is done
+ * and all of the pci_dev and pci_bus structures have been created.
+ */
+static struct pci_dev *fake_pci_dev(struct pci_channel *hose,
+	int top_bus, int busnr, int devfn)
+{
+	static struct pci_dev dev;
+	static struct pci_bus bus;
+
+	dev.bus = &bus;
+	dev.sysdata = hose;
+	dev.devfn = devfn;
+	bus.number = busnr;
+	bus.ops = hose->pci_ops;
+
+	if(busnr != top_bus)
+		/* Fake a parent bus structure. */
+		bus.parent = &bus;
+	else
+		bus.parent = NULL;
+
+	return &dev;
+}
+
+#define EARLY_PCI_OP(rw, size, type)					\
+int early_##rw##_config_##size(struct pci_channel *hose,		\
+	int top_bus, int bus, int devfn, int offset, type value)	\
+{									\
+	return pci_##rw##_config_##size(				\
+		fake_pci_dev(hose, top_bus, bus, devfn),		\
+		offset, value);						\
+}
 
-/* These are used for config access before all the PCI probing has been done. */
-int early_read_config_byte(struct pci_channel *hose, int bus, int dev_fn, int where, u8 *val);
-int early_read_config_word(struct pci_channel *hose, int bus, int dev_fn, int where, u16 *val);
-int early_read_config_dword(struct pci_channel *hose, int bus, int dev_fn, int where, u32 *val);
-int early_write_config_byte(struct pci_channel *hose, int bus, int dev_fn, int where, u8 val);
-int early_write_config_word(struct pci_channel *hose, int bus, int dev_fn, int where, u16 val);
-int early_write_config_dword(struct pci_channel *hose, int bus, int dev_fn, int where, u32 val);
+EARLY_PCI_OP(read, byte, u8 *)
+EARLY_PCI_OP(read, word, u16 *)
+EARLY_PCI_OP(read, dword, u32 *)
+EARLY_PCI_OP(write, byte, u8)
+EARLY_PCI_OP(write, word, u16)
+EARLY_PCI_OP(write, dword, u32)
 
 static u32 pciauto_lower_iospc;
 static u32 pciauto_upper_iospc;
@@ -51,6 +94,7 @@
 
 void __init 
 pciauto_setup_bars(struct pci_channel *hose,
+		   int top_bus,
 		   int current_bus,
 		   int pci_devfn)
 {
@@ -60,17 +104,14 @@
 	u32 * lower_limit;
 	int found_mem64 = 0;
 
-	DBG("PCI Autoconfig: Found Bus %d, Device %d, Function %d\n",
-	    current_bus, PCI_SLOT(pci_devfn), PCI_FUNC(pci_devfn) );
-
 	for (bar = PCI_BASE_ADDRESS_0; bar <= PCI_BASE_ADDRESS_5; bar+=4) {
 		/* Tickle the BAR and get the response */
-		early_write_config_dword(hose,
+		early_write_config_dword(hose, top_bus,
 					 current_bus,
 					 pci_devfn,
 					 bar,
 					 0xffffffff);
-		early_read_config_dword(hose,
+		early_read_config_dword(hose, top_bus,
 					current_bus,
 					pci_devfn,
 					bar,
@@ -80,12 +121,20 @@
 		if (!bar_response)
 			continue;
 
+		/*
+		 * Workaround for a BAR that doesn't use its upper word,
+		 * like the ALi 1535D+ PCI DC-97 Controller Modem (M5457).
+		 * bdl <[email-masked]>
+		 */
+		if (!(bar_response & 0xffff0000))
+			bar_response |= 0xffff0000;
+
 		/* Check the BAR type and set our address mask */
 		if (bar_response & PCI_BASE_ADDRESS_SPACE) {
 			addr_mask = PCI_BASE_ADDRESS_IO_MASK;
 			upper_limit = &pciauto_upper_iospc;
 			lower_limit = &pciauto_lower_iospc;
-			DBG("PCI Autoconfig: BAR %d, I/O, ", bar_nr);
+			DBG("        I/O");
 		} else {
 			if ((bar_response & PCI_BASE_ADDRESS_MEM_TYPE_MASK) ==
 			    PCI_BASE_ADDRESS_MEM_TYPE_64)
@@ -94,7 +143,7 @@
 			addr_mask = PCI_BASE_ADDRESS_MEM_MASK;		
 			upper_limit = &pciauto_upper_memspc;
 			lower_limit = &pciauto_lower_memspc;
-			DBG("PCI Autoconfig: BAR %d, Mem, ", bar_nr);
+			DBG("        Mem");
 		}
 
 		/* Calculate requested size */
@@ -104,7 +153,7 @@
 		bar_value = ((*lower_limit - 1) & ~(bar_size - 1)) + bar_size;
 
 		/* Write it out and update our limit */
-		early_write_config_dword(hose, current_bus, pci_devfn,
+		early_write_config_dword(hose, top_bus, current_bus, pci_devfn,
 					 bar, bar_value);
 
 		*lower_limit = bar_value + bar_size;
@@ -116,97 +165,99 @@
 		 */ 
 		if (found_mem64) {
 			bar += 4;
-			early_write_config_dword(hose,
+			early_write_config_dword(hose, top_bus,
 						 current_bus,
 						 pci_devfn,
 						 bar,
 						 0x00000000);
 		}
 
-		bar_nr++;
+		DBG(" at 0x%.8x [size=0x%x]\n", bar_value, bar_size);
 
-		DBG("size=0x%x, address=0x%x\n",
-		    bar_size, bar_value);
+		bar_nr++;
 	}
 
 }
 
 void __init
 pciauto_prescan_setup_bridge(struct pci_channel *hose,
+			     int top_bus,
 			     int current_bus,
 			     int pci_devfn,
 			     int sub_bus)
 {
-	int cmdstat;
-
 	/* Configure bus number registers */
-	early_write_config_byte(hose, current_bus, pci_devfn,
+	early_write_config_byte(hose, top_bus, current_bus, pci_devfn,
 	                        PCI_PRIMARY_BUS, current_bus);
-	early_write_config_byte(hose, current_bus, pci_devfn,
+	early_write_config_byte(hose, top_bus, current_bus, pci_devfn,
 				PCI_SECONDARY_BUS, sub_bus + 1);
-	early_write_config_byte(hose, current_bus, pci_devfn,
+	early_write_config_byte(hose, top_bus, current_bus, pci_devfn,
 				PCI_SUBORDINATE_BUS, 0xff);
-
-	/* Round memory allocator to 1MB boundary */
-	pciauto_upper_memspc &= ~(0x100000 - 1);
 
-	/* Round I/O allocator to 4KB boundary */
-	pciauto_upper_iospc &= ~(0x1000 - 1);
+	/* Align memory and I/O to 1MB and 4KB boundaries. */
+	pciauto_lower_memspc = (pciauto_lower_memspc + (0x100000 - 1))
+		& ~(0x100000 - 1);
+	pciauto_lower_iospc = (pciauto_lower_iospc + (0x1000 - 1))
+		& ~(0x1000 - 1);
+
+	/* Set base (lower limit) of address range behind bridge. */
+	early_write_config_word(hose, top_bus, current_bus, pci_devfn,
+		PCI_MEMORY_BASE, pciauto_lower_memspc >> 16);
+	early_write_config_byte(hose, top_bus, current_bus, pci_devfn,
+		PCI_IO_BASE, (pciauto_lower_iospc & 0x0000f000) >> 8);
+	early_write_config_word(hose, top_bus, current_bus, pci_devfn,
+		PCI_IO_BASE_UPPER16, pciauto_lower_iospc >> 16);
 
-	/* Set up memory and I/O filter limits, assume 32-bit I/O space */
-	early_write_config_word(hose, current_bus, pci_devfn, PCI_MEMORY_LIMIT,
-				((pciauto_upper_memspc - 1) & 0xfff00000) >> 16);
-	early_write_config_byte(hose, current_bus, pci_devfn, PCI_IO_LIMIT,
-				((pciauto_upper_iospc - 1) & 0x0000f000) >> 8);
-	early_write_config_word(hose, current_bus, pci_devfn,
-				PCI_IO_LIMIT_UPPER16,
-				((pciauto_upper_iospc - 1) & 0xffff0000) >> 16);
-			
 	/* We don't support prefetchable memory for now, so disable */
-	early_write_config_word(hose, current_bus, pci_devfn,
-				PCI_PREF_MEMORY_BASE, 0x1000);
-	early_write_config_word(hose, current_bus, pci_devfn,
-				PCI_PREF_MEMORY_LIMIT, 0x1000);
-
-	/* Enable memory and I/O accesses, enable bus master */
-	early_read_config_dword(hose, current_bus, pci_devfn, PCI_COMMAND,
-				&cmdstat);
-	early_write_config_dword(hose, current_bus, pci_devfn, PCI_COMMAND,
-				 cmdstat | PCI_COMMAND_IO | PCI_COMMAND_MEMORY |
-				 PCI_COMMAND_MASTER);
+	early_write_config_word(hose, top_bus, current_bus, pci_devfn,
+				PCI_PREF_MEMORY_BASE, 0);
+	early_write_config_word(hose, top_bus, current_bus, pci_devfn,
+				PCI_PREF_MEMORY_LIMIT, 0);
 }
 
 void __init
 pciauto_postscan_setup_bridge(struct pci_channel *hose,
+			      int top_bus,
 			      int current_bus,
 			      int pci_devfn,
 			      int sub_bus)
 {
+	u32 temp;
+
 	/* Configure bus number registers */
-	early_write_config_byte(hose, current_bus, pci_devfn,
+	early_write_config_byte(hose, top_bus, current_bus, pci_devfn,
 				PCI_SUBORDINATE_BUS, sub_bus);
 
-	/* Round memory allocator to 1MB boundary */
-	pciauto_upper_memspc &= ~(0x100000 - 1);
-	early_write_config_word(hose, current_bus, pci_devfn, PCI_MEMORY_BASE,
-				pciauto_upper_memspc >> 16);
-
-	/* Round I/O allocator to 4KB boundary */
-	pciauto_upper_iospc &= ~(0x1000 - 1);
-	early_write_config_byte(hose, current_bus, pci_devfn, PCI_IO_BASE,
-				(pciauto_upper_iospc & 0x0000f000) >> 8);
-	early_write_config_word(hose, current_bus, pci_devfn,
-				PCI_IO_BASE_UPPER16, pciauto_upper_iospc >> 16);
+	/* Set upper limit of address range behind bridge. */
+	early_write_config_word(hose, top_bus, current_bus, pci_devfn,
+		PCI_MEMORY_LIMIT, pciauto_lower_memspc >> 16);
+	early_write_config_byte(hose, top_bus, current_bus, pci_devfn,
+		PCI_IO_LIMIT, (pciauto_lower_iospc & 0x0000f000) >> 8);
+	early_write_config_word(hose, top_bus, current_bus, pci_devfn,
+		PCI_IO_LIMIT_UPPER16, pciauto_lower_iospc >> 16);
+
+	/* Align memory and I/O to 1MB and 4KB boundaries. */
+	pciauto_lower_memspc = (pciauto_lower_memspc + (0x100000 - 1))
+		& ~(0x100000 - 1);
+	pciauto_lower_iospc = (pciauto_lower_iospc + (0x1000 - 1))
+		& ~(0x1000 - 1);
+
+	/* Enable memory and I/O accesses, enable bus master */
+	early_read_config_dword(hose, top_bus, current_bus, pci_devfn,
+		PCI_COMMAND, &temp);
+	early_write_config_dword(hose, top_bus, current_bus, pci_devfn,
+		PCI_COMMAND, temp | PCI_COMMAND_IO | PCI_COMMAND_MEMORY
+		| PCI_COMMAND_MASTER);
 }
 
 #define      PCIAUTO_IDE_MODE_MASK           0x05
 
 int __init
-pciauto_bus_scan(struct pci_channel *hose, int current_bus)
+pciauto_bus_scan(struct pci_channel *hose, int top_bus, int current_bus)
 {
 	int sub_bus;
 	u32 pci_devfn, pci_class, cmdstat, found_multi=0;
-	unsigned short vid;
+	unsigned short vid, did;
 	unsigned char header_type;
 	int devfn_start = 0;
 	int devfn_stop = 0xff;
@@ -223,54 +274,70 @@
 		if (PCI_FUNC(pci_devfn) && !found_multi)
 			continue;
 
-		early_read_config_byte(hose, current_bus, pci_devfn,
+		early_read_config_word(hose, top_bus, current_bus, pci_devfn,
+				       PCI_VENDOR_ID, &vid);
+
+		if (vid == 0xffff) continue;
+
+		early_read_config_byte(hose, top_bus, current_bus, pci_devfn,
 				       PCI_HEADER_TYPE, &header_type);
 
 		if (!PCI_FUNC(pci_devfn))
 			found_multi = header_type & 0x80;
 
-		early_read_config_word(hose, current_bus, pci_devfn,
-				       PCI_VENDOR_ID, &vid);
+		early_read_config_word(hose, top_bus, current_bus, pci_devfn,
+				       PCI_DEVICE_ID, &did);
 
-		if (vid == 0xffff) continue;
-
-		early_read_config_dword(hose, current_bus, pci_devfn,
+		early_read_config_dword(hose, top_bus, current_bus, pci_devfn,
 					PCI_CLASS_REVISION, &pci_class);
+
+		DBG("%.2x:%.2x.%x Class %.4x: %.4x:%.4x",
+			current_bus, PCI_SLOT(pci_devfn), PCI_FUNC(pci_devfn),
+			pci_class >> 16, vid, did);
+		if (pci_class & 0xff)
+			DBG(" (rev %.2x)", pci_class & 0xff);
+		DBG("\n");
+
 		if ((pci_class >> 16) == PCI_CLASS_BRIDGE_PCI) {
-			DBG("PCI Autoconfig: Found P2P bridge, device %d\n", PCI_SLOT(pci_devfn));
-			pciauto_prescan_setup_bridge(hose, current_bus,
+			DBG("        Bridge: primary=%.2x, secondary=%.2x\n",
+				current_bus, sub_bus + 1);
+			pciauto_prescan_setup_bridge(hose, top_bus, current_bus,
 						     pci_devfn, sub_bus);
-			sub_bus = pciauto_bus_scan(hose, sub_bus+1);
-			pciauto_postscan_setup_bridge(hose, current_bus,
+			DBG("Scanning sub bus %.2x, I/O 0x%.8x, Mem 0x%.8x\n",
+				sub_bus + 1,
+				pciauto_lower_iospc, pciauto_lower_memspc);
+			sub_bus = pciauto_bus_scan(hose, top_bus, sub_bus+1);
+			DBG("Back to bus %.2x\n", current_bus);
+			pciauto_postscan_setup_bridge(hose, top_bus, current_bus,
 						      pci_devfn, sub_bus);
-
+			continue;
 		} else if ((pci_class >> 16) == PCI_CLASS_STORAGE_IDE) {
 
 			unsigned char prg_iface;
 
-			early_read_config_byte(hose, current_bus, pci_devfn,
-					       PCI_CLASS_PROG, &prg_iface);
+			early_read_config_byte(hose, top_bus, current_bus,
+				pci_devfn, PCI_CLASS_PROG, &prg_iface);
 			if (!(prg_iface & PCIAUTO_IDE_MODE_MASK)) {
-				DBG("PCI Autoconfig: Skipping legacy mode IDE controller\n");
+				DBG("Skipping legacy mode IDE controller\n");
 				continue;
 			}
 		}
 
-		/*
+ 		/*
 		 * Found a peripheral, enable some standard
 		 * settings
 		 */
-		early_read_config_dword(hose, current_bus, pci_devfn,
+		early_read_config_dword(hose, top_bus, current_bus, pci_devfn,
 					PCI_COMMAND, &cmdstat);
-		early_write_config_dword(hose, current_bus, pci_devfn,
+		early_write_config_dword(hose, top_bus, current_bus, pci_devfn,
 					 PCI_COMMAND, cmdstat | PCI_COMMAND_IO |
 					 PCI_COMMAND_MEMORY |
 					 PCI_COMMAND_MASTER);
-		early_write_config_byte(hose, current_bus, pci_devfn,
+		early_write_config_byte(hose, top_bus, current_bus, pci_devfn,
 					PCI_LATENCY_TIMER, 0x80);
 
 		/* Allocate PCI I/O and/or memory space */
-		pciauto_setup_bars(hose, current_bus, pci_devfn);
+		pciauto_setup_bars(hose, top_bus, current_bus, pci_devfn);
 	}
 	return sub_bus;
 }
@@ -283,41 +350,9 @@
 	pciauto_upper_iospc = hose->io_resource->end + 1;
 	pciauto_lower_memspc = hose->mem_resource->start;
 	pciauto_upper_memspc = hose->mem_resource->end + 1;
+	DBG("Autoconfig PCI channel 0x%p\n", hose);
+	DBG("Scanning bus %.2x, I/O 0x%.8x, Mem 0x%.8x\n",
+		busno, pciauto_lower_iospc, pciauto_lower_memspc);
 
-	return pciauto_bus_scan(hose, busno);
+	return pciauto_bus_scan(hose, busno, busno);
 }
-
-
-/*
- * These functions are used early on before PCI scanning is done
- * and all of the pci_dev and pci_bus structures have been created.
- */
-static struct pci_dev *fake_pci_dev(struct pci_channel *hose, int busnr,
-                                    int devfn)
-{
-	static struct pci_dev dev;
-	static struct pci_bus bus;
-
-	dev.bus = &bus;
-	dev.sysdata = hose;
-	dev.devfn = devfn;
-	bus.number = busnr;
-	bus.ops = hose->pci_ops;
-
-	return &dev;
-}
-
-#define EARLY_PCI_OP(rw, size, type)					\
-int early_##rw##_config_##size(struct pci_channel *hose, int bus,	\
-                               int devfn, int offset, type value)	\
-{									\
-	return pci_##rw##_config_##size(fake_pci_dev(hose, bus, devfn),	\
-	                                offset, value);			\
-}
-
-EARLY_PCI_OP(read, byte, u8 *)
-EARLY_PCI_OP(read, word, u16 *)
-EARLY_PCI_OP(read, dword, u32 *)
-EARLY_PCI_OP(write, byte, u8)
-EARLY_PCI_OP(write, word, u16)
-EARLY_PCI_OP(write, dword, u32)

---

From: "Bradley D. LaRonde" <[email-masked]>
Date: Mon, 29 Oct 2001 14:30:06 -0500

I considered that, but since only this small chuck of run-once surrogate
bios autoconfig code needs to know, I figured better keep it separate.

Regards,
Brad

----- Original Message -----
From: "Jun Sun" <[email-masked]>
To: "Bradley D. LaRonde" <[email-masked]>
Cc: <[email-masked]>; <[email-masked]>
Sent: Monday, October 29, 2001 2:24 PM
Subject: Re: PATCH: pci_auto bridge support

>
> Brad,
>
> Have you considered embedding "topbus" argument into hose structure?  That
> sounds like potentially better solution.
>
>
>
> "Bradley D. LaRonde" wrote:
> >

---

From: Jun Sun <[email-masked]>
Date: Mon, 29 Oct 2001 11:24:02 -0800

Brad,

---

From: Jun Sun <[email-masked]>
Date: Mon, 29 Oct 2001 14:38:50 -0800

> bios autoconfig code needs to know, I figured better keep it separate.

I would vote to put it inside the hose structure:

. It makes a workaround look like a real fix. :-)

. In other implementations of pci_auto, hose is the private sys data of a pci
dev. Having a bus number inside is very useful (e.g., pci_ops can tell whether
it is type0 of type1 configuration based on the bus number rather than a shaky
NULL parent bus pointer).  In the future, all pci_auto should be combined into
the pci driver.  So that is probably the right direction to go.

I think hose may evolve to be the data structure that represents the topology
of PCI buses.  It should have more uses in the future (e.g., the standard IRQ
routing across PCI-PCI bridges).

Jun

---

From: "Bradley D. LaRonde" <[email-masked]>
Date: Mon, 29 Oct 2001 19:00:43 -0500

----- Original Message -----
From: "Jun Sun" <[email-masked]>
To: "Bradley D. LaRonde" <[email-masked]>
Cc: <[email-masked]>; <[email-masked]>
Sent: Monday, October 29, 2001 5:38 PM
Subject: [Linux-mips-kernel]Re: PATCH: pci_auto bridge support

> routing across PCI-PCI bridges).

Isn't the bus topology already adequately represented in the pci_dev and
pci_channel structures?

I look at the pci autoconfig stuff as a bios replacement.  The fact that we
can use some of the same structures and functions to help us implement it is
a bonus, but not a mandate to mess with the existing model.

Isn't Linux already handling PCI-PCI bridges and multiple PCI channles fine
already, or has our autoconfig code exposed some existing non-arch-specific
weakness?

Regards,
Brad

---

From: Jun Sun <[email-masked]>
Date: Mon, 29 Oct 2001 16:19:46 -0800

> Isn't the bus topology already adequately represented in the pci_dev and

Not really.  For example, we don't know which bus is the sub-bus of which,
directly, and how their address space translate into each other.  Those data
structures are needed when we start to support dynamically mapped and/or
arbitrarily mapped PCI memory spaces.

> a bonus, but not a mandate to mess with the existing model.

That is a right point.  I might be too far ahead of myself. :-)
 
> already, or has our autoconfig code exposed some existing non-arch-specific

I think on PCs, P2P still largely depends on BIOS.  (Correct me if I am
wrong).

There are some post-scan_bus() mechanism to setup P2P bridges.  I have not
looked into it closely. 

Jun