Subject: Hooking MCA DMA to bus_dma framework
To: None <port-i386@netbsd.org>
From: Jaromir Dolecek <jdolecek@netbsd.org>
List: port-i386
Date: 11/17/2001 00:00:38
--ELM713407792-10865-0_
Content-Transfer-Encoding: 7bit
Content-Type: text/plain; charset=US-ASCII

Hi,
I've implemented the (hopefull) better way to deal with DMA on MCA
bus. I moved the MCA DMA controller setup related code to MD location
from edc(4), and exposed the functionality via bus_dma + mca_dmamap_create()
to associate DRQ with the dmamap.
The primary reason was to make the code usable also to other drivers
besides edc, but this was also to deal with buffer bouncing on >16MB RAM
machines properly.

Since MCA shares the same 16MB DMA limit as ISA, I've used mainly
the _isa_dmamap_* routines and just hooked the necessary MCA bits.
I'm appending the changes as a diff to current i386/mca/mca_machdep.c.

I'm not quite sure about two things in the code:
* is it 'right' to abuse the bus_dmamap_t the way I do it in
  _mca_dmamap_* ? (most imminent in _mca_dmamap_create())
* is it okay to kick the MCA DMA controller off _mca_dmamap_sync()?

The latter one has good side effect in that the drivers would _need_
to have appropriate sync calls even on i386, thus hopefully less
work would be required to make the drivers working on other archs
such as RS/6000. The former one is mainly because I did not find
any better way to cleanly hook to the _isa_dmamap_* routines.

Your comments would be greatly appreciated,

Jaromir
-- 
Jaromir Dolecek <jdolecek@NetBSD.org> http://www.NetBSD.org/Ports/i386/ps2.html
-=  Those who would give up liberty for a little temporary safety deserve  =-
-=  neither liberty nor safety, and will lose both.  -- Benjamin Franklin  =-

--ELM713407792-10865-0_
Content-Transfer-Encoding: 7bit
Content-Type: text/plain; charset=ISO-8859-2
Content-Disposition: attachment; filename=mcadma.diff

Index: mca_machdep.c
===================================================================
RCS file: /cvsroot/syssrc/sys/arch/i386/mca/mca_machdep.c,v
retrieving revision 1.10
diff -u -p -r1.10 mca_machdep.c
--- mca_machdep.c	2001/11/15 07:03:33	1.10
+++ mca_machdep.c	2001/11/16 22:37:12
@@ -46,10 +46,12 @@ __KERNEL_RCSID(0, "$NetBSD: mca_machdep.
 
 #include <sys/types.h>
 #include <sys/param.h>
-#include <sys/time.h>
-#include <sys/systm.h>
 #include <sys/device.h>
+#include <sys/malloc.h>
+#include <sys/systm.h>
 #include <sys/syslog.h>
+#include <sys/time.h>
+#include <sys/kernel.h>
 
 #include <machine/bioscall.h>
 #include <machine/psl.h>
@@ -84,33 +86,85 @@ struct bios_config {
 	u_int8_t	pad[10];
 } __attribute__ ((packed));
 
+/* Used as MCA DMA context, passed up as a fake bus_dmamap_t */
+struct i386_mca_dmactx {
+	bus_dmamap_t dm;
+	int drq;
+};
+
+/* ISA DMA stuff - see i386/isa/isa_machdep.c */
+int	_isa_bus_dmamap_create __P((bus_dma_tag_t, bus_size_t, int,
+	    bus_size_t, bus_size_t, int, bus_dmamap_t *));
+void	_isa_bus_dmamap_destroy __P((bus_dma_tag_t, bus_dmamap_t));
+int	_isa_bus_dmamap_load __P((bus_dma_tag_t, bus_dmamap_t, void *,
+	    bus_size_t, struct proc *, int));
+void	_isa_bus_dmamap_unload __P((bus_dma_tag_t, bus_dmamap_t));
+void	_isa_bus_dmamap_sync __P((bus_dma_tag_t, bus_dmamap_t,
+	    bus_addr_t, bus_size_t, int));
+
+int	_isa_bus_dmamem_alloc __P((bus_dma_tag_t, bus_size_t, bus_size_t,
+	    bus_size_t, bus_dma_segment_t *, int, int *, int));
+
+static int	_mca_bus_dmamap_create __P((bus_dma_tag_t, bus_size_t, int,
+		    bus_size_t, bus_size_t, int, bus_dmamap_t *));
+static void	_mca_bus_dmamap_destroy __P((bus_dma_tag_t, bus_dmamap_t));
+static int	_mca_bus_dmamap_load __P((bus_dma_tag_t, bus_dmamap_t, void *,
+		    bus_size_t, struct proc *, int));
+static void	_mca_bus_dmamap_unload __P((bus_dma_tag_t, bus_dmamap_t));
+static void	_mca_bus_dmamap_sync __P((bus_dma_tag_t, bus_dmamap_t,
+		    bus_addr_t, bus_size_t, int));
+static int	_mca_bus_dmamap_load_mbuf __P((bus_dma_tag_t, bus_dmamap_t,
+		    struct mbuf *, int));
+static int	_mca_bus_dmamap_load_uio __P((bus_dma_tag_t, bus_dmamap_t,
+		    struct uio *, int));
+static int	_mca_bus_dmamap_load_raw __P((bus_dma_tag_t, bus_dmamap_t,
+		    bus_dma_segment_t *, int, bus_size_t, int));
 
+/*
+ * For now, we use MCA DMA to 0-16M always. Some IBM PS/2 have 32bit MCA bus,
+ * but majority of them have 24bit only.
+ */
+#define	MCA_DMA_BOUNCE_THRESHOLD	(16 * 1024 * 1024)
+
 struct i386_bus_dma_tag mca_bus_dma_tag = {
-	NULL,			/* _cookie */
-	_bus_dmamap_create,
-	_bus_dmamap_destroy,
-	_bus_dmamap_load,
-	_bus_dmamap_load_mbuf,
-	_bus_dmamap_load_uio,
-	_bus_dmamap_load_raw,
-	_bus_dmamap_unload,
-	NULL,			/* _dmamap_sync */
-	_bus_dmamem_alloc,
+	MCA_DMA_BOUNCE_THRESHOLD,		/* _bounce_thresh */
+	_mca_bus_dmamap_create,
+	_mca_bus_dmamap_destroy,
+	_mca_bus_dmamap_load,
+	_mca_bus_dmamap_load_mbuf,
+	_mca_bus_dmamap_load_uio,
+	_mca_bus_dmamap_load_raw,
+	_mca_bus_dmamap_unload,
+	_mca_bus_dmamap_sync,
+	_isa_bus_dmamem_alloc,
 	_bus_dmamem_free,
 	_bus_dmamem_map,
 	_bus_dmamem_unmap,
 	_bus_dmamem_mmap,
 };
 
-/* setup by mca_busprobe() */
-int MCA_system = 0;	/* Updated in mca_busprobe() if appropriate. */
+/* Updated in mca_busprobe() if appropriate. */
+int MCA_system = 0;
 
+/* Used to kick MCA DMA controller */
+#define DMA_EXTCMD	0x18
+#define DMA_EXEC	0x1A
+static bus_space_handle_t dmaiot, dmaextcmdh, dmaexech;
+
+/*
+ * Map the MCA DMA controller registers.
+ */
 void
 mca_attach_hook(parent, self, mba)
 	struct device *parent, *self;
 	struct mcabus_attach_args *mba;
 {
-	/* Nothing */
+	dmaiot = mba->mba_iot;
+
+	if (bus_space_map(dmaiot, DMA_EXTCMD, 1, 0, &dmaextcmdh)
+	    || bus_space_map(dmaiot, DMA_EXEC, 1, 0, &dmaexech))
+		panic("%s: couldn't map DMA registers",
+			mba->mba_busname);
 }
 
 /*
@@ -208,11 +262,9 @@ mca_nmi()
 	outb(MCA_MB_SETUP_REG, 0xff);
 
 	/* find if an MCA slot has the CHCK bit asserted (low) in POS 5 */
-	for(slot=0; slot<MCA_MAX_SLOTS; slot++)
-	{
+	for(slot=0; slot<MCA_MAX_SLOTS; slot++) {
 		outb(MCA_ADAP_SETUP_REG, slot | MCA_ADAP_SET);
-		if ((inb(MCA_POS_REG(5)) & MCA_POS5_CHCK) == 0)
-		{
+		if ((inb(MCA_POS_REG(5)) & MCA_POS5_CHCK) == 0) {
 			mcanmi = 1;
 			/* find if CHCK status is available in POS 6/7 */
 			if((inb(MCA_POS_REG(5)) & MCA_POS5_CHCK_STAT) == 0)
@@ -226,12 +278,10 @@ mca_nmi()
 	outb(MCA_ADAP_SETUP_REG, 0);
 
    out:
-#if NISA > 0
 	if (!mcanmi) {
 		/* no CHCK bits asserted, assume ISA NMI */
 		return (isa_nmi());
 	} else
-#endif
 		return(0);
 }
 
@@ -307,4 +357,241 @@ void
 mca_disk_unbusy(void)
 {
 	outb(PORT_DISKLED, inb(PORT_DISKLED) & ~DISKLED_ON);
+}
+
+/*
+ * -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+ * MCA DMA specific stuff. We use ISA routines for bulk of the work,
+ * since MCA shares much of the charasteristics with it. We just hook
+ * the DRQ initialization and kick MCA DMA controller appropriately.
+ * -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+ */
+
+/*
+ * Create a MCA DMA map.
+ */
+static int
+_mca_bus_dmamap_create(t, size, nsegments, maxsegsz, boundary, flags, dmamp)
+	bus_dma_tag_t t;
+	bus_size_t size;
+	int nsegments;
+	bus_size_t maxsegsz;
+	bus_size_t boundary;
+	int flags;
+	bus_dmamap_t *dmamp;
+{
+	struct i386_mca_dmactx *ctx;
+	int error;
+
+	MALLOC(ctx, struct i386_mca_dmactx *, sizeof(*ctx), M_DEVBUF,
+		cold ? M_NOWAIT : M_WAITOK);
+	if (ctx == NULL)
+		return (ENOMEM);
+
+	if ((error = _isa_bus_dmamap_create(t, size, nsegments, maxsegsz,
+			boundary, flags, &ctx->dm))) {
+		FREE(ctx, M_DEVBUF);
+		return (error);
+	}
+
+	ctx->drq = 0xdeadbeef;
+
+	*dmamp = (bus_dmamap_t) ctx;
+
+	return (0);
+}
+	
+/*
+ * Destroy a MCA DMA map.
+ */
+static void
+_mca_bus_dmamap_destroy(t, map)
+	bus_dma_tag_t t;
+	bus_dmamap_t map;
+{
+	struct i386_mca_dmactx *ctx = (struct i386_mca_dmactx *)map;
+
+	_isa_bus_dmamap_destroy(t, ctx->dm);
+
+	FREE(ctx, M_DEVBUF);
+}
+
+/*
+ * Load a MCA DMA map with a linear buffer.
+ */
+static int
+_mca_bus_dmamap_load(t, map, buf, buflen, p, flags)
+	bus_dma_tag_t t;
+	bus_dmamap_t map; 
+	void *buf;
+	bus_size_t buflen;
+	struct proc *p;
+	int flags;
+{
+	struct i386_mca_dmactx *ctx = (struct i386_mca_dmactx *)map;
+
+	return _isa_bus_dmamap_load(t, ctx->dm, buf, buflen, p, flags);
+}
+
+/*
+ * Like _mca_bus_dmamap_load(), but for mbufs.
+ */
+static int
+_mca_bus_dmamap_load_mbuf(t, map, m0, flags)  
+	bus_dma_tag_t t;
+	bus_dmamap_t map;
+	struct mbuf *m0;
+	int flags;
+{
+
+	panic("_mca_bus_dmamap_load_mbuf: not implemented");
+}
+
+/*
+ * Like _mca_bus_dmamap_load(), but for uios.
+ */
+static int
+_mca_bus_dmamap_load_uio(t, map, uio, flags)
+	bus_dma_tag_t t;
+	bus_dmamap_t map;
+	struct uio *uio;
+	int flags;
+{
+
+	panic("_mca_bus_dmamap_load_uio: not implemented");
+}
+
+/*
+ * Like _mca_bus_dmamap_load(), but for raw memory allocated with
+ * bus_dmamem_alloc().
+ */
+static int
+_mca_bus_dmamap_load_raw(t, map, segs, nsegs, size, flags)
+	bus_dma_tag_t t;
+	bus_dmamap_t map;
+	bus_dma_segment_t *segs;
+	int nsegs;
+	bus_size_t size;
+	int flags;
+{
+
+	panic("_mca_bus_dmamap_load_raw: not implemented");
+}
+
+/*
+ * Unload a MCA DMA map.
+ */
+static void
+_mca_bus_dmamap_unload(t, map)
+	bus_dma_tag_t t;
+	bus_dmamap_t map;
+{
+	struct i386_mca_dmactx *ctx = (struct i386_mca_dmactx *)map;
+
+	return _isa_bus_dmamap_unload(t, ctx->dm);
+}
+
+/*
+ * Synchronize a MCA DMA map.
+ */
+static void
+_mca_bus_dmamap_sync(t, _map, offset, len, ops)
+	bus_dma_tag_t t;
+	bus_dmamap_t _map;
+	bus_addr_t offset;
+	bus_size_t len;
+	int ops;
+{
+	struct i386_mca_dmactx *ctx = (struct i386_mca_dmactx *) _map;
+	bus_addr_t phys;
+	bus_size_t cnt;
+	int isread;
+
+	_isa_bus_dmamap_sync(t, ctx->dm, offset, len, ops);
+
+	/*
+	 * Don't do anything if not PRE* operation, allow only
+	 * one of PREREAD and PREWRITE.
+	 */
+	if (ops != BUS_DMASYNC_PREREAD && ops != BUS_DMASYNC_PREWRITE)
+		return;
+
+#ifdef DEBUG
+	if (ctx->drq == 0xdeadbeef) {
+		panic("_mca_bus_dmamap_sync: DRQ not set");
+		/* NOTREACHED */
+	}
+#endif
+
+	isread = (ops & BUS_DMASYNC_PREREAD);
+	phys = ctx->dm->dm_segs[0].ds_addr;
+	cnt = ctx->dm->dm_segs[0].ds_len;
+
+	/*
+	 * Initialize the MCA DMA controller appropriately. The exact
+	 * sequence to setup the controller is taken from Minix.
+	 */
+
+	/* Disable access to dma channel       */
+	bus_space_write_1(dmaiot, dmaextcmdh, 0, 0x90 + ctx->drq);
+
+	/* Clear the address byte pointer      */
+	bus_space_write_1(dmaiot, dmaextcmdh, 0, 0x20 + ctx->drq);
+	/* address bits 0..7   */
+	bus_space_write_1(dmaiot, dmaexech, 0, (phys >> 0) & 0xff);
+	/* address bits 8..15  */
+	bus_space_write_1(dmaiot, dmaexech, 0, (phys >> 8) & 0xff);
+	/* address bits 16..23  */
+	bus_space_write_1(dmaiot, dmaexech, 0, (phys >> 16) & 0xff);
+
+	/* Clear the count byte pointer        */
+	bus_space_write_1(dmaiot, dmaextcmdh, 0, 0x40 + ctx->drq);
+	/* count bits 0..7     */
+	bus_space_write_1(dmaiot, dmaexech, 0, ((cnt - 1) >> 0) & 0xff);        
+	/* count bits 8..15    */
+	bus_space_write_1(dmaiot, dmaexech, 0, ((cnt - 1) >> 8) & 0xff);        
+
+	/* Set the transfer mode               */
+	bus_space_write_1(dmaiot, dmaextcmdh, 0, 0x70 + ctx->drq);
+	bus_space_write_1(dmaiot, dmaexech, 0, (isread) ? 0x4C : 0x44);
+
+	/* Enable access to dma channel        */
+	bus_space_write_1(dmaiot, dmaextcmdh, 0, 0xA0 + ctx->drq);
+}
+
+/*
+ * Allocate a dma map, and set up drq.
+ */
+int
+mca_dmamap_create(t, size, nsegments, maxsegsz, boundary, flags, dmamp, drq)
+	bus_dma_tag_t t;
+	bus_size_t size;
+	int nsegments;
+	bus_size_t maxsegsz;
+	bus_size_t boundary;
+	int flags;
+	bus_dmamap_t *dmamp;
+	int drq;
+{
+	int error;
+	struct i386_mca_dmactx *ctx;
+
+#ifdef DEBUG
+	/* Sanity check */
+	if (drq < 0 || drq >= 16) {
+		printf("mcadma_create: invalid DMA Arbitration Level %d\n",
+			drq);
+		return (EINVAL);
+	}
+#endif
+
+	if ((error = bus_dmamap_create(t, size, nsegments,
+	    maxsegsz, boundary, flags, dmamp)) != 0)
+		return (error);
+
+	/* Set DRQ appropriately */
+	ctx = (struct i386_mca_dmactx *) *dmamp;
+	ctx->drq = drq;
+
+	return (0);
 }

--ELM713407792-10865-0_--