Subject: support for AST-style multi-port serial cards with nice config
To: None <current-users@sun-lamp.cs.berkeley.edu>
From: Roland McGrath <roland@frob.com>
List: current-users
Date: 03/21/1994 18:42:00
Inspired by Bill Sommerfeld's hacks to support the multi-port serial
cards (really, his code was better documentation than the poor
translation that came with my hardware, so I could finally figure out
what to do), I hacked this further to support them with nice
configuration.

For example, here is the configuration I am using:

     master	ast0	at isa? port 0x1a0 tty irq 5 vector astintr
     device	com2	at ast0 flags 2 slave 0
     device	com3	at ast0 flags 2 slave 1
     device	com4	at ast0 flags 2 slave 2
     device	com5	at ast0 flags 2 slave 3

This says that com2 uses ports 0x1a0-0x1a7, com3 uses ports
0x1a8-0x1af, etc.; com2 uses the low-order bit in the interrupt
demultiplexing byte, com3 uses the 2 bit, com4 uses the 4 bit, and
com5 uses the 8 bit.  

You can also put `port' specifiers on the individual device lines
(according to its documentation, my card supports some modes where all
the ports are not all at consecutive io addresses--I haven't used
them).  You must still put in the slave ID, so it knows which
interrupt bit to check.  For each device that has no io port
specified, it will use BASE+(8*SLAVE), where BASE is the io port given
for the master device and SLAVE is the slave ID number.

An unrelated kludge you can omit, `flags 2' sets CLOCAL for those ports.

Here comes the code.  Firstly you want this hack to config(8).  You
can do fine without it, you just have to use "drive" in place of
"slave" in the configuration, which sounds a little silly for a serial port.

diff -c /usr/src/usr.sbin/config/mkioconf.c /usr/src/usr.sbin/config/my-mkioconf.c
*** /usr/src/usr.sbin/config/mkioconf.c Sat Mar 12 06:10:41 1994
--- /usr/src/usr.sbin/config/my-mkioconf.c      Sat Mar 19 04:09:04 1994
***************
*** 730,736 ****
        fprintf(fp, " %5s, %2d, C 0x%05x, %5d, %5s,  %2d, 0x%04x, %3d,",
              sirq(dp->d_irq), dp->d_drq, dp->d_maddr, dp->d_msize,
              shandler(dp), dp->d_unit, dp->d_flags,
!             eq(mp->d_name, "isa") ? 0 : dp->d_drive);
        if (eq(mp->d_name, "isa"))
          fprintf(fp, "  NULL,");
        else
--- 730,737 ----
        fprintf(fp, " %5s, %2d, C 0x%05x, %5d, %5s,  %2d, 0x%04x, %3d,",
              sirq(dp->d_irq), dp->d_drq, dp->d_maddr, dp->d_msize,
              shandler(dp), dp->d_unit, dp->d_flags,
!             eq(mp->d_name, "isa") ? 0 :
!             dp->d_drive == UNKNOWN ? dp->d_slave : dp->d_drive);
        if (eq(mp->d_name, "isa"))
          fprintf(fp, "  NULL,");
        else


This tells config about the new `ast' device.

diff -c /sys/arch/i386/conf/files.i386.\~1\~ /sys/arch/i386/conf/files.i386
*** /sys/arch/i386/conf/files.i386.~1~  Sat Mar 12 06:04:48 1994
--- /sys/arch/i386/conf/files.i386      Mon Mar 21 16:34:45 1994
***************
*** 23,28 ****
--- 23,29 ----
  arch/i386/isa/bt742a.c                optional bt device-driver requires isa scsi not aha
  arch/i386/isa/clock.c         optional isa
  arch/i386/isa/com.c           optional com device-driver requires isa
+ arch/i386/isa/ast.c           optional ast device-driver requires com isa
  arch/i386/isa/cy.c            optional cy device-driver requires isa
  arch/i386/isa/elink.c         optional ep or ie
  arch/i386/isa/fd.c            optional fd device-driver requires isa


These are the necessary changes to com.c.  The ability to set CLOCAL
with `flags 2' is in here as well, and not really related to the
multiport support (but we all need it, so what the hell).

diff -c /sys/arch/i386/isa/com.c.\~1\~ /sys/arch/i386/isa/com.c
*** /sys/arch/i386/isa/com.c.~1~        Fri Mar 18 05:56:36 1994
--- /sys/arch/i386/isa/com.c    Mon Mar 21 17:54:39 1994
***************
*** 40,45 ****
--- 40,46 ----
   * uses National Semiconductor NS16450/NS16550AF UART
   */
  #include "com.h"
+ #include "ast.h"
  
  #include <sys/param.h>
  #include <sys/systm.h>
***************
*** 69,75 ****
        u_short sc_iobase;
        u_char sc_hwflags;
  #define       COM_HW_MULTI    0x01
- #define       COM_HW_CONFIGBITS       COM_HW_MULTI
  #define       COM_HW_FIFO     0x02
  #define       COM_HW_CONSOLE  0x40
        u_char sc_swflags;
--- 70,75 ----
***************
*** 77,82 ****
--- 77,83 ----
  #define       COM_SW_CLOCAL   0x02
  #define       COM_SW_CRTSCTS  0x04
  #define       COM_SW_MDMBUF   0x08
+ #define       COM_SW_CONFIGBITS       COM_SW_CLOCAL
        u_char sc_msr, sc_mcr;
  } com_softc[NCOM];
  /* XXXX should be in com_softc, but not ready for that yet */
***************
*** 163,168 ****
--- 164,182 ----
        struct com_softc *sc = &com_softc[isa_dev->id_unit];
        u_short iobase = isa_dev->id_iobase;
  
+       if (isa_dev->id_parent) {
+               if (iobase == 0) {
+                       /* For multiport cards, the iobase may be left
+                          unspecified (zero) for slave ports.  In
+                          that case we calculate it from the master
+                          (parent) iobase setting and the slave port
+                          number (physid).  */
+                       iobase = isa_dev->id_iobase
+                         = isa_dev->id_parent->id_iobase +
+                           (8 * isa_dev->id_physid);
+                     }
+       }
+ 
        /* XXX HACK */
        sprintf(sc->sc_dev.dv_xname, "%s%d", comdriver.name, isa_dev->id_unit);
        sc->sc_dev.dv_unit = isa_dev->id_unit;
***************
*** 186,195 ****
                delay(1000);
  
        sc->sc_iobase = iobase;
!       sc->sc_hwflags = isa_dev->id_flags & COM_HW_CONFIGBITS;
!       sc->sc_swflags = 0;
  
!       printf("%s: ", sc->sc_dev.dv_xname);
  
        /* look for a NS 16550AF UART with FIFOs */
        outb(iobase + com_fifo,
--- 200,221 ----
                delay(1000);
  
        sc->sc_iobase = iobase;
!       sc->sc_hwflags = 0;
!       sc->sc_swflags = isa_dev->id_flags & COM_SW_CONFIGBITS;
  
!       printf("%s", sc->sc_dev.dv_xname);
! #if NAST > 0
!       if (isa_dev->id_parent) {
!               printf(" at 0x%x %s%d slave %d",
!                      isa_dev->id_iobase,
!                      isa_dev->id_parent->id_driver->name,
!                      isa_dev->id_parent->id_unit,
!                      isa_dev->id_physid);
!               astslave(isa_dev, unit);
!               sc->sc_hwflags |= COM_HW_MULTI;
!       }
! #endif
!       printf(": ");
  
        /* look for a NS 16550AF UART with FIFOs */
        outb(iobase + com_fifo,
***************
*** 295,302 ****
                (void) inb(iobase + com_lsr);
                (void) inb(iobase + com_data);
                /* you turn me on, baby */
!               outb(iobase + com_mcr,
!                   sc->sc_mcr = MCR_DTR | MCR_RTS | MCR_IENABLE);
                outb(iobase + com_ier,
                    IER_ERXRDY | IER_ETXRDY | IER_ERLS | IER_EMSC);
  
--- 321,330 ----
                (void) inb(iobase + com_lsr);
                (void) inb(iobase + com_data);
                /* you turn me on, baby */
!               sc->sc_mcr = MCR_DTR | MCR_RTS;
!               if (!(sc->sc_hwflags & COM_HW_MULTI))
!                 sc->sc_mcr |= MCR_IENABLE;
!               outb(iobase + com_mcr, sc->sc_mcr);
                outb(iobase + com_ier,
                    IER_ERXRDY | IER_ETXRDY | IER_ERLS | IER_EMSC);


And, finally, here is sys/arch/i386/isa/ast.c:

/* Multi-port serial card interrupt demuxing support.
   Roland McGrath 3/20/94 */

#include "ast.h"

#include <sys/types.h>

#include <machine/pio.h>
#include <i386/isa/isa_device.h>

int astprobe __P((struct isa_device *));
int astattach __P((struct isa_device *));

struct	isa_driver astdriver = {
	astprobe, astattach, "ast"
};

struct astunit
{
	u_short iobase;
	int alive;		/* Mask of slave units attached. */
	int slaveunits[8];	/* com device unit numbers.  */
} astunits[NAST];

int
astprobe(struct isa_device *isa_dev)
{
	/* Do the normal com probe for the first UART and assume
	   its presence means there is a multiport board there.  */
	return comprobe1(isa_dev->id_iobase);
}

int
astattach(struct isa_device *isa_dev)
{
	int unit = isa_dev->id_unit;
  	u_short iobase = isa_dev->id_iobase;
	unsigned int x;
  
	astunits[unit].iobase = iobase;

	outb (iobase | 0x1f, 0x80);
	/* XXX Is this needed?  Ted's driver for Linux does it.  */
	x = inb (iobase | 0x1f);
	/* My guess is this bitmask tells you how many ports are there.
	   I only have a 4-port board to try (returns 0xf). --roland */
	printf ("ast%d: 0x%x\n", unit, x);
}

void
astslave(struct isa_device *slave, int comunit)
{
	struct astunit *a = &astunits[slave->id_parent->id_unit];

	a->slaveunits[slave->id_physid] = comunit;
	a->alive |= 1 << slave->id_physid;
}

int
astintr(int unit)
{
	struct astunit *a = &astunits[unit];
	u_short iobase = a->iobase;
	int alive = a->alive;
	int bits;

	do {
		bits = inb (iobase | 0x1f) & alive;
#define TRY(I)	((bits & (1 << (I))) ? 0 : comintr (a->slaveunits[I]))
		TRY (0), TRY (1), TRY (2), TRY (3);
		TRY (4), TRY (5), TRY (6), TRY (7);
#undef TRY
 	} while (bits != alive);

	return 1;
}


Enjoy,
Roland

------------------------------------------------------------------------------