Subject: kern/32938: Remapping of PCMCIA IO addresses by the controller
To: None <kern-bug-people@netbsd.org, gnats-admin@netbsd.org,>
From: None <sverre@viewmark.com>
List: netbsd-bugs
Date: 02/26/2006 21:00:00
>Number:         32938
>Category:       kern
>Synopsis:       PCMCIA cards with fixed IO addresses are not working.
>Confidential:   no
>Severity:       serious
>Priority:       high
>Responsible:    kern-bug-people
>State:          open
>Class:          sw-bug
>Submitter-Id:   net
>Arrival-Date:   Sun Feb 26 21:00:00 +0000 2006
>Originator:     Sverre Froyen
>Release:        NetBSD 3.99.15 (2006-02-21)
>Organization:
Viewmark
>Environment:
System: NetBSD abbor.fesk.com 3.99.15 NetBSD 3.99.15 (ABBOR) #40: Sun Feb 26 13:01:08 MST 2006 toor@abbor.fesk.com:/usr/src/sys/arch/i386/compile/obj.i386/ABBOR i386
Architecture: i386
Machine: i386
>Description:
Some PCMCIA cards do not provide IO addresses that can be assigned using
the card's IOBASE registers.  (In fact, the PCMCIA standard appears to not
even require IOBASE registers for single function cards.)  Such cards do
not work under NetBSD (at least when "rbus" is defined) because the kernel
will try to access the card using an address assigned by the rbus system
(usually an address above 0x4000).

The attached patch modifies the code for cbb (in sys/dev/pci/pccbb.c) to
remap the rbus address to the card's IO address.  The patch also modifies
the pcmcia code (in sys/dev/pcmcia/pcmcia.c) to set the IOBASE registers
to correct values when remapping is being done.

I note that because the ExCA standard does not require the ability to
remap IO windows, not all PCMCIA controllers can be made to work with
these cards.

Comments on the patch:

Passing the IO window offset from pccbb_pcmcia_io_alloc to
pccbb_pcmcia_io_map to pccbb_pcmcia_do_io_map was painful.  The
It is done first by using the new opaque ihandle member in the
pcmcia_io_handle structure.  Then, in order to make it accessible
to pccbb_pcmcia_do_io_map (which is being called from two separate places),
I added an offset to the pcic_handle.io structure (in analogy to
the offset in the pcic_handle.mem structure).  I also added the offset to
the cbb_pcic_handle.io but I am not certain that that was necessary.

The use of variable pf->pf_mfc_iobase to modify the IOBASE register handling
is a bit of a kludge and does not work when a function has multiple IO
spaces.  I added a test for this.

>How-To-Repeat:
Insert any number of PCMCIA modem cards and attempt to use them.
>Fix:
Apply the attached patch.

Index: src/sys/dev/pcmcia/pcmcia.c
===================================================================
RCS file: /cvsroot/src/sys/dev/pcmcia/pcmcia.c,v
retrieving revision 1.78
diff -u -r1.78 pcmcia.c
--- src/sys/dev/pcmcia/pcmcia.c	11 Dec 2005 12:23:23 -0000	1.78
+++ src/sys/dev/pcmcia/pcmcia.c	26 Feb 2006 20:14:46 -0000
@@ -548,6 +548,9 @@
 		}
 	}
 
+	DPRINTF(("%s: pf_mfc_iobase=0x%lx pf_mfc_iomax=0x%lx\n",
+		 sc->dev.dv_xname, pf->pf_mfc_iobase, pf->pf_mfc_iomax));
+
 	if (pcmcia_mfc(sc) || 1) {
 		pcmcia_ccr_write(pf, PCMCIA_CCR_IOBASE0,
 				 (pf->pf_mfc_iobase >>  0) & 0xff);
@@ -780,6 +783,8 @@
 	int error = 0;
 	int n, m;
 
+	pf->pf_mfc_iobase = 0;
+
 	for (n = 0; n < cfe->num_iospace; n++) {
 		bus_addr_t start = cfe->iospace[n].start;
 		bus_size_t length = cfe->iospace[n].length;
@@ -800,6 +805,18 @@
 		    &cfe->iospace[n].handle);
 		if (error)
 			break;
+		/*
+		 * Set pf_mfc_iobase equal to start. This flags the case
+		 * where the card specifies a fixed IO base address and the
+		 * controller remaps that address.
+		 * XXX Need to rethink this in the case of multiple IO
+		 * spaces per function. Fail for now.
+		 */
+		if (pf->pf_mfc_iobase != 0) {
+			DPRINTF(("pcmcia_config_alloc: cannot (yet) remap multiple IO spaces\n"));
+			break;
+		}
+		pf->pf_mfc_iobase = start;
 	}
 	if (n < cfe->num_iospace) {
 		for (m = 0; m < n; m++)
Index: src/sys/dev/pci/pccbb.c
===================================================================
RCS file: /cvsroot/src/sys/dev/pci/pccbb.c,v
retrieving revision 1.127
diff -u -r1.127 pccbb.c
--- src/sys/dev/pci/pccbb.c	18 Dec 2005 11:04:00 -0000	1.127
+++ src/sys/dev/pci/pccbb.c	26 Feb 2006 20:14:48 -0000
@@ -2073,6 +2073,7 @@
 	bus_space_tag_t iot;
 	bus_space_handle_t ioh;
 	bus_addr_t mask;
+	long *remap_offset;
 #if rbus
 	rbus_tag_t rb;
 #endif
@@ -2141,12 +2142,20 @@
 		    (u_long) ioaddr, (u_long) size));
 	}
 #endif
+	remap_offset = (long *) malloc(sizeof(long), M_DEVBUF, M_WAITOK);
+	if (! remap_offset)
+		return 1;
+	*remap_offset = 0;
+#if rbus
+	*remap_offset = start - ioaddr;
+#endif
 
 	pcihp->iot = iot;
 	pcihp->ioh = ioh;
 	pcihp->addr = ioaddr;
 	pcihp->size = size;
 	pcihp->flags = flags;
+	pcihp->ihandle = (void *) remap_offset;
 
 	return 0;
 }
@@ -2170,6 +2179,9 @@
 	bus_space_handle_t ioh = pcihp->ioh;
 	bus_size_t size = pcihp->size;
 
+	if (pcihp->ihandle)
+		free(pcihp->ihandle, M_DEVBUF);
+
 #if rbus
 	struct pccbb_softc *sc =
 	    (struct pccbb_softc *)((struct pcic_handle *)pch)->ph_parent;
@@ -2209,6 +2221,7 @@
 	struct pcic_handle *ph = (struct pcic_handle *)pch;
 	bus_addr_t ioaddr = pcihp->addr + offset;
 	int i, win;
+	long *remap_offset = (long *) pcihp->ihandle;
 #if defined CBB_DEBUG
 	static const char *width_names[] = { "dynamic", "io8", "io16" };
 #endif
@@ -2253,6 +2266,7 @@
 	ph->io[win].addr = ioaddr;
 	ph->io[win].size = size;
 	ph->io[win].width = width;
+	ph->io[win].offset = *remap_offset;
 
 	/* actual dirty register-value changing in the function below. */
 	pccbb_pcmcia_do_io_map(ph, win);
@@ -2282,6 +2296,8 @@
 #define PCIC_SIA_START_HIGH 1
 #define PCIC_SIA_STOP_LOW 2
 #define PCIC_SIA_STOP_HIGH 3
+#define PCIC_SIA_OFFSET_LOW 0x2e
+#define PCIC_SIA_OFFSET_HIGH 0x2f
 
 	int regbase_win = 0x8 + win * 0x04;
 	u_int8_t ioctl, enable;
@@ -2300,6 +2316,11 @@
 	Pcic_write(ph, regbase_win + PCIC_SIA_STOP_HIGH,
 	    ((ph->io[win].addr + ph->io[win].size - 1) >> 8) & 0xff);
 
+	Pcic_write(ph, regbase_win + PCIC_SIA_OFFSET_LOW,
+	    ph->io[win].offset & 0xff);
+	Pcic_write(ph, regbase_win + PCIC_SIA_OFFSET_HIGH,
+	    (ph->io[win].offset >> 8) & 0xff);
+
 	ioctl = Pcic_read(ph, PCIC_IOCTL);
 	enable = Pcic_read(ph, PCIC_ADDRWIN_ENABLE);
 	switch (win) {
@@ -2333,6 +2354,7 @@
 		printf
 		    (" start %02x %02x, stop %02x %02x, ioctl %02x enable %02x\n",
 		    start_low, start_high, stop_low, stop_high, ioctl, enable);
+		printf("pccbb_pcmcia_do_io_map: PCIC_SIA_OFFSET = %lx\n", ph->io[win].offset);
 	}
 #endif
 }
Index: src/sys/dev/pci/pccbbvar.h
===================================================================
RCS file: /cvsroot/src/sys/dev/pci/pccbbvar.h,v
retrieving revision 1.25
diff -u -r1.25 pccbbvar.h
--- src/sys/dev/pci/pccbbvar.h	11 Dec 2005 12:22:50 -0000	1.25
+++ src/sys/dev/pci/pccbbvar.h	26 Feb 2006 20:14:48 -0000
@@ -85,6 +85,7 @@
 	struct {
 		bus_addr_t addr;
 		bus_size_t size;
+		long offset;
 		int width;
 	} io[PCIC_IO_WINS];
 	int ih_irq;
Index: src/sys/dev/ic/i82365var.h
===================================================================
RCS file: /cvsroot/src/sys/dev/ic/i82365var.h,v
retrieving revision 1.26
diff -u -r1.26 i82365var.h
--- src/sys/dev/ic/i82365var.h	16 Feb 2006 20:17:16 -0000	1.26
+++ src/sys/dev/ic/i82365var.h	26 Feb 2006 20:14:49 -0000
@@ -73,6 +73,7 @@
 	struct {
 		bus_addr_t	addr;
 		bus_size_t	size;
+		long		offset;
 		int		width;
 	} io[PCIC_IO_WINS];
 	int	ih_irq;