*_Design Specification of SAB8253X ASLX Driver for Linux_*

 


  The effort to design and implement the ASLX SAB8253X Driver for Aurora
  <http://www.auroratech.com/> hardware with the functionality described
  in *_Functional Specification of SAB8253X ASLX Driver for Linux
  <http://www.telfordtools.com/sab8253x/sab8253xfs.html>_* requires
  solutions to seven separate problems:

 

   1. creating a development environment for maintaining and extending
      the driver,
   2. integrating the driver into the kernel sources,
   3. creating a file structure of the driver that aids understanding,
   4. crafting a reasonable and easy to use user interface,
   5. developing simple tools and example programs for the driver, and
   6. designing the driver data structures and
   7. designing the driver program logic.

 


  _Development Environment_

 


      There are several possible approaches to creating a development
      environment.  The development environments for many drivers seem
      to have consisted simply of a single machine, and the developer
      used only /printk()/ in the driver code to debug.  I used such an
      environment to develop a driver for an IOP480 based intelligent
      adapter card.

 

For a driver that provides the functionality described in the Functional
Specification, a more sophisticated development and debugging
environment is useful.  (One could even occasionally wish for an ICE,
but that level of resources was not available to me.)

 

The development environment consisted of two 686 class machines, on
which the Linux operating system was installed.  One machine ran at 800
Mhz, the other at 1Ghz.  It probably would have been worthwhile to have
dual processor machine, and one was added to the development environment
later.  The 800 Mhz machine hosted the remote gdb application.  It ran
Redhat Linux 7.0, but because the machine served only as an NFS and
remote gdb host, the details of the Linux distribution on this machine
are not particularly important.

 

The target machine on which the driver was developed and debugged hosted
Suse Linux 7.1 and was later upgraded to Suse Linux 7.3.  Suse Linux
seemed to provide the most complete Linux distribution with the least
hassle in installation.  (As the Suse distribution comes on 7 standard
CDs or 1 DVD, there is a lot of value in having a DVD drive in the
target machine [and the gdb host if it runs a Suse distribution].)

 

The target and remote gdb machines are connected by a 100 Mbps Ethernet
network and by a serial crossover cable between the Com1 (ttyS0) ports. 

 

When I started developing the driver, I obtained the Linux 2.4.3 sources
from The Linux Kernel Archives <http://www.kernel.org/>.  Later as later
distributions became stable, I switched to the Linux 2.4.6
distribution.  The sources were installed first in
/home/martillo/kernel/linux-2.4.3 and then in
/home/martillo/kernel/linux-2.4.6 on the remote gdb host machine, which
was named frolix.

 

The sources were imported into CVS on frolix, and the core directory
into CVS  was added manually because the cvs import function ignores
it.  I consider it safer to maintain the CVS repository on the remote
gdb host machine instead of the target machine because the target
machine is likely to crash frequently, and its file system may be put
into bad states.

 

I executed the following commands on the remote gdb host machine.

 

*ln ^Ös /home/martillo/kernel/linux-2.4.3/include/asm-i386
/home/martillo/kernel/linux-2.4.3/include/asm*

* *

or

* *

*ln ^Ös /home/martillo/kernel/linux-2.4.6/include/asm-i386
/home/martillo/kernel/linux-2.4.6/include/asm*

* *

*ln ^Ös / /frolix *

 

I edited the /etc/exports file to contain the following.

 

/               ylith(rw)

/               fireball(rw)

/               bohun(rw)

/               indefatigable(rw)

 

Ylith is the original 1 Ghz target machine.  Fireball is a 400 Mhz
compact PCI target machine.  Indefatigable is a dual 1 Ghz target
machine.  Bohun is a Solaris target machine used for another project.

 

On the frolix, I started NFS with the following commands (contained in a
shell script).

 

*/etc/rc.d/init.d/nfs start*

*exportfs -va*

 

If it had been a Suse Linux machine, I would have used the yast2 control
center to start NFS service.

 

On ylith, I created an empty directory /frolix and executed the
following command.

 

*mount frolix:/ /frolix*

* *

At this point both the remote gdb host (frolix) and the target
development and debugging machine can refer to the same files by the
same paths.  I could have guaranteed the same paths to the source files
on both machines if I had simply used /home/martillo as my home
directory on frolix and exported /home from frolix to all the other
machines so that there would only be one /home directory for all the
machines in my network.  I was lazy about the network configuration.

 

After making sure that the user martillo had read write access
permissions to all the kernel sources, I built the kernel on the target
machine from within xemacs with the following sequence of commands.

 

From a shell window:

*xemacs ^Öe shell&*

 

Inside xemacs:

 

*cd /frolix/home/martillo/kernel/linux-2.4.3*

 

or

 

*cd /frolix/home/martillo/kernel/linux-2.4.6*

 

Then make sure that linux-2.4.x/include/asm-i386 is symbolically linked to
linux-2.4.x/include/asm.

 

Execute the following emacs command.

 


          M-x compile

 

This command prompts for targets.

 

In the development environment the most useful target string was usually
/clean xconfig dep bzImage modules./ The target /xconfig /brings up a
configuration window.  In the basic development environment, it was
generally worthwhile to add SCSI CD ROM, SCSI legacy support, an
Ethernet driver and DOS file system support

 

The target/ dep/ creates the dependencies (note that if the kernel tree
is ever removed, the .depend and .hdepend files must be regenerated). 
The /bzImage /target builds the kernel.  The /modules/ target generates
all the modules to be dynamically installed in the kernel via the
*insmod* command.

 

After building the kernel, installing the modules in the /lib tree
requires the execution (as root) of

 

make modules_install

 

The command *make install* *INSTALL_PATH=/boot* will install the
compressed kernel image as vmlinuz along with other files in the /boot
partition.  I preferred to use a shell script with commands like the
following.

 

cp /frolix/home/martillo/kernel/linux-2.4.6/arch/i386/boot/bzImage
/boot/vmlinuz_246

cp /frolix/home/martillo/kernel/linux-2.4.6/System.map
/boot/System.map-2.4.6

cp /frolix/home/martillo/kernel/linux-2.4.6/.config /boot/vmlinuz_246.config

cp /frolix/home/martillo/kernel/linux-2.4.6/include/linux/autoconf.h
/boot/vmlinuz_246.autoconf.h

cp /frolix/home/martillo/kernel/linux-2.4.6/include/linux/version.h
/boot/vmlinuz_246.version.h

 

When the kernel comes from a linux-2.4.3 tree, the obvious substitutions
of 3 for 6 are required.

 

Once all the modules and the kernel image are installed, the next step
in giving the system the ability to boot with the new linux-2.4.3 or
linux-2.4.6 kernel image is the modification of the lilo.conf file.

 

I added the following directives to the lilo.conf file.

 

  image  = /boot/vmlinuz_243

  label  = linux_2.4.3

  root   = /dev/hde7

  optional

 

  image  = /boot/vmlinuz_246

  label  = linux_2.4.6

  root   = /dev/hde7

  optional

 

In this case /dev/hde7 corresponds to the /boot partition, and the
options linux_2.4.3 and linux_2.4.6 will be added to the boot menu once
the *lilo* command has been executed.  In other system setups the disk
partition that corresponds to /boot might have a different name like
/dev/hda7.

 

Once the new kernel successfully boots, the next step to creating a
driver development and debugging environment is patching the kernel for
remote gdb debugging.

 

The necessary patch can be obtained from kgdb: Source level debugging of
linux kernel <http://kgdb.sourceforge.net/downloads.html>.

 

Now the kernel can be built again with the extra step of configuring for
remote gdb support in the kernel configuration menu. 

 

The following directives should be added to lilo.conf.

 

  image  = /boot/vmlinuz_243

  label  = debug243

  append = "gdb gdbttyS=0 gdbbaud=115200"

  root   = /dev/hde7

  optional

 

  image  = /boot/vmlinuz_246

  label  = debug246

  append = "gdb gdbttyS=0 gdbbaud=115200"

  root   = /dev/hde7

  optional

 


      Then lilo can be executed.  On reboot the boot menu will include
      options for debug243 and debug246.

 

To test the patch, select one of the debug options.  Then, on the remote
gdb host machine execute the following command.

 

stty 115200 < /dev/ttyS0

 

Start up an *xemacs* process.  Execute the following commands within
*xemacs.*

 


          M-x shell

 

Then within the shell window execute the following command.

 

cd /frolix/home/martillo/kernel/linux-2.4./X/

/ /

Then invoke the remote debugger.

 


          M-x gdb

 

Reply to the file prompt with *vmlinux.*

* *

In the gdb window, execute the following command.

 

target remote /dev/ttyS0

 

The gdb window should break in gdbstub.c which will be displayed in the
gdb source window.

 

At this point, all the basic gdb remote debugging capabilities are ready
to use.

 

To access the hardware breakpoint capability of the i386 processor, the
following commands can be loaded directly or from a file with the
*script* command.

 

#Hardware breakpoints in gdb

#

#Using ia-32 hardware breakpoints.

#

#4 hardware breakpoints are available in ia-32 processors. These breakpoints

#do not need code modification. They are set using debug registers.

#

#Each hardware breakpoint can be of one of the

#three types: execution, write, access.

#1. An Execution breakpoint is triggered when code at the breakpoint
address is

#executed.

#2. A write breakpoint ( aka watchpoints ) is triggered when memory location

#at the breakpoint address is written.

#3. An access breakpoint is triggered when memory location at the breakpoint

#address is either read or written.

#

#As hardware breakpoints are available in limited number, use software

#breakpoints ( br command in gdb ) instead of execution hardware
breakpoints.

#

#Length of an access or a write breakpoint defines length of the datatype to

#be watched. Length is 1 for char, 2 short , 3 int.

#

#For placing execution, write and access breakpoints, use commands

#hwebrk, hwwbrk, hwabrk

#To remove a breakpoint use hwrmbrk command.

#

#These commands take following types of arguments. For arguments associated

#with each command, use help command.

#1. breakpointno: 0 to 3

#2. length: 1 to 3

#3. address: Memory location in hex ( without 0x ) e.g c015e9bc

#

#Use the command exinfo to find which hardware breakpoint occured.

 

 

#hwebrk breakpointno address

define hwebrk

        maintenance packet Y$arg0,0,0,$arg1

end

document hwebrk

        hwebrk breakpointno address

        Places a hardware execution breakpoint

end

 

#hwwbrk breakpointno length address

define hwwbrk

        maintenance packet Y$arg0,1,$arg1,$arg2

end

document hwwbrk

        hwwbrk breakpointno length address

        Places a hardware write breakpoint

end

 

#hwabrk breakpointno length address

define hwabrk

        maintenance packet Y$arg0,1,$arg1,$arg2

end

document hwabrk

        hwabrk breakpointno length address

        Places a hardware access breakpoint

end

 

#hwrmbrk breakpointno

define hwrmbrk

        maintenance packet y$arg0

end

document hwrmbrk

        hwrmbrk breakpointno

        Removes a hardware breakpoint

end

 

#exinfo

define exinfo

        maintenance packet qE

end

document exinfo

        exinfo

        Gives information about a breakpoint.

end

 

Once the above macros are define, the developer can set hardware
breakpoints.

 

The next step to creating a useful development and debugging environment
is to provide a shell script to for remote debugging of dynamically
loaded modules.  The following shell script (called *loadmodule.sh*)
creates a gdb script called *load/ModuleName/* in
/frolix/home/martillo/kernel/linux-2.4.6 when it is invoked (as root)
with the following command.

loadmodule.sh modulename

 

In order to decrease the probability of confusion, I usually make a link
in kernel root directory,  /frolix/home/martillo/kernel/linux-2.4.6, to
the location of the module to be debugged in the kernel tree.  The above
command is invoked on the target machine (ylith) in the root directory. 
On the remote debug machine, in the gdb command window, whose working
directory should be the kernel root directory,
/frolix/home/martillo/kernel/linux-2.4.6, the command, *script
load/ModuleName/*, is invoked.  Once the script is executed the symbols
for the module are available for remote symbolic debugging.

 

#!/bin/sh

# This script loads a module on a target machine and generates a gdb script.

# source generated gdb script to load the module file at appropriate
addresses

# in gdb.

#

# Usage:

# Loading the module on target machine and generating gdb script)

#       [foo]$ loadmodule.sh <modulename>

#

# Loading the module file into gdb

#       (gdb) source <gdbscriptpath>

#

# Modify following variables according to your setup.

#       TESTMACHINE - Name of the target machine

#       GDBSCRIPTS - The directory where a gdb script will be generated

#

# Author: Amit S. Kale (akale@veritas.com).

#

# If you run into problems, please check files pointed to by following

# variables.

#       ERRFILE - /tmp/<modulename>.errs contains stderr output of insmod

#       MAPFILE - /tmp/<modulename>.map contains stdout output of insmod

#       GDBSCRIPT - $GDBSCRIPTS/load<modulename> gdb script.

 

TESTMACHINE=ylith

GDBSCRIPTS=/frolix/home/martillo/kernel/linux-2.4.6

 

if [ $# -lt 1 ] ; then {

        echo Usage: $0 modulefile

        exit

} ; fi

 

MODULEFILE=$1

MODULEFILEBASENAME=`basename $1`

 

if [ $MODULEFILE = $MODULEFILEBASENAME ] ; then {

        MODULEFILE=`pwd`/$MODULEFILE

} fi

 

ERRFILE=/tmp/$MODULEFILEBASENAME.errs

MAPFILE=/tmp/$MODULEFILEBASENAME.map

GDBSCRIPT=$GDBSCRIPTS/load$MODULEFILEBASENAME

 

function findaddr() {

        local ADDR=0x$(echo "$SEGMENTS" | \

                grep "$1" | sed 's/^[^ ]*[ ]*[^ ]*[ ]*//' | \

                sed 's/[ ]*[^ ]*$//')

        echo $ADDR

}

 

function checkerrs() {

        if [ "`cat $ERRFILE`" != "" ] ; then {

                cat $ERRFILE

        } fi

}

 

#load the module

#echo Copying $MODULEFILE to $TESTMACHINE

#*rcp $MODULEFILE root@${TESTMACHINE}:

 

echo Loading module $MODULEFILE

#rsh -l root $TESTMACHINE  /sbin/insmod -m ./`basename $MODULEFILE` \

#       > $MAPFILE 2> $ERRFILE &

/sbin/insmod -m ./`basename $MODULEFILE` $2 . .  > $MAPFILE 2> $ERRFILE &

sleep 5

checkerrs

 

NUMLINES=`grep -n '^$' $MAPFILE | sed -e 's/:.*//g'`

SEGMENTS=`head -n $NUMLINES $MAPFILE | tail -n $(eval expr $NUMLINES - 1)`

TEXTADDR=$(findaddr "\\.text[^.]")

LOADSTRING="add-symbol-file $MODULEFILE $TEXTADDR"

SEGADDRS=`echo "$SEGMENTS" | awk '//{

        if ($1 != ".text" && $1 != ".this" &&

            $1 != ".kstrtab" && $1 != ".kmodtab") {

                print " -s " $1 " 0x" $3 " "

        }

}'`

LOADSTRING="$LOADSTRING $SEGADDRS"

echo Generating script $GDBSCRIPT

echo $LOADSTRING > $GDBSCRIPT

 

With the addition of the above shell script, the driver development and
debugging environment is almost complete.  Other useful tools for
developing and debugging this type of serial driver would include a
Wanalyzer (I used an Interview 7700 and an HP 4952A in developing this
driver), a breakout box that displays interface signal states and  (for
developing the serial Ethernet-like network driver) several WAN LAN VLAN
routers as described in *Packet Switching Software and Platforms
<http://members.aol.com/Telford001/vrouter2g.html>*, *Routing in a
Bridged Network <http://members.aol.com/Telford001/routetti2.html>, **A
WAN SUBSYSTEM for a High Performance Packet Switch
<http://members.aol.com/Keleustes/syncdob.html>* and *A New High
Performance Architecture for Routers, Bridges and LAN Switches (Software
Defined Internetworking)
<http://members.aol.com/Ishtar7713/private/sdi4.html>.*

 


  _Integration into the Kernel Sources_


   

The driver has its own directory, {kernel root
directory}/drivers/net/wan/8253x, in the 2.4.* kernel source tree.

 

To facilitate the automatic build of the 8253x driver, the following
standard kernel files were modified.

 

1.                  {kernel root directory}/drivers/net/wan/Config.in to
which the line

 

tristate '  Aurora Technology, Inc. synchronous asynchronous PCI cards
V2' CONFIG_ATI_XX20


 

was added,

2.                  {kernel root directory}/drivers/net/wan/Makefile to
which the following lines were added,

 

subdir-$(CONFIG_ATI_XX20) += 8253x

 

ifeq ($(CONFIG_ATI_XX20),y)

  obj-y += 8253x/ASLX.o

endif

 

When the driver is built as a dynamically loaded module, the following
macro commands in the file 8253xini.c puts the module entry points in
the special module entry point segment.

 

module_init(auraXX20_probe);

module_exit(auraXX20_cleanup);

 

The sources are provided to the users in a patch file, tentatively named
8253x.patch <http://www.telfordtools.com/sab8253x/8253x.patch>.

 

To install it the user sets his directory to the top level of the kernel
sources and executes the following command.

 

patch ^Öp1 < /{directory-patch}//8253x.patch


   


  _File Structure of the ASLX Driver Source Code_

 

The following files are present in the driver directory.

 

8253x.h

8253xdbg.c

8253xmac.c

8253xsyn.c

PciRegs.h

crc32.h

sp502.h

8253xcfg.c

8253xini.c

8253xnet.c

8253xtty.c

Reg9050.h

crc32dcl.h

ring.h

8253xctl.h

8253xioc.h

8253xplx.c

8253xint.c

crc32.c

endian.h

Makefile

Amcc5920.c

8253xmcs.h

8253xmcs.c

8253xchr.c

8253xutl.c

 

 

 

The source code is divided functionally among the files of the ASLX driver.

 

8253xcfg.c is the source for a user application that configures 8253x
control registers to provide clocking.  8253xmac.c is the source for a
user application that sets a pseudo-MAC address for the network driver.

 

8253xini.c contains the initialization/probe logic.

 

8253xint.c contains the common interrupt logic.

 

8253xtty.c contains the asynchronous TTY logic.

 

8253xsyn.c contains the synchronous TTY logic.

 

8253xnet.c contains the network driver logic.

 

8253xchr.c contains the character driver logic.


 

8253xdbg.c contains some debugging functions.

 

8253xutl.c contains most of the functions that are common among the
different driver functional subunits.

 

8253xplx.c contains some functions specific to the PLX9050 (a PCI bridge
chip) and specifically to reading and reprogramming the associated
serial EEPROM.

 

amcc5920.c contains some functions specific to the AMCC5920 (a PCI
bridge chip) and specifically to reading and reprogramming the
associated serial EEPROM.

 

8253xmcs.c contains functions specific to programming the multichannel
server (mostly G-LINK related logic, programming the sp502 driver chip
and reading or programming the serial EEPROM associated with the
interface cards contained within the MCS unit).

 

crc32.c contains logic to append a CRC32 to a pseudo MAC frame that is
generated by the network driver.

 

8253x.h contains symbols, structures and macros that relate mostly to
the 8253x chips and ports.

 

8253xctl.h contains symbols, structures and macros that relate mostly to
the adapter cards.

 

8253xmcs.h contains symbols and structures that relate mostly to the
multichannel server.  A lot of this file relates to G-LINK.

 

sp502.h contains symbols and structures that relate to the programming
of the hardware interface line drivers of the 3500 adapter cards of the
multichannel server.

 

8253xioc.h contains symbols and structures that relate to private ioctls.

 

PciRegs.h contains symbols and structures that relate to PCI
configuration space.

 

Reg9050.h contains symbols and structures that relate to the PLX9050 PCI
interface chip and its serial eprom

 

crc32.h, crc32dcl.h and .endian.h contain symbols, structures and macros
that relate to generating a correct CRC32.

 

ring.h contains symbols and structures that relate to the network driver
frame transmission ring and frame reception.

 

The Makefile is a standard Linux kernel Makefile whose structure is
dictated by the current Linux build formalism.

 


  _Using the ASLX Driver _


   


  The ASLX driver is designed to be a ^Óplug-and-play^Ô driver as far as
  possible.  If it is built as a dynamically loadable module, the user
  (or relevant system configuration file) invokes /insmod /to load the
  ASLX.o file. 

 

The following parameters can be set on the /insmod/ command line.

 

MODULE_PARM(xx20_minorstart, "i");/*when statically linked autodected
otherwise 128 by default*/

MODULE_PARM(sab8253xc_major, "i");/*major dev for character device, by
default dynamic */

MODULE_PARM(auraXX20n_debug, "i");/*turns on debugging messages, default
off*/

MODULE_PARM(auraXX20n_name, "s"); /*base network driver name = 8253x000*/

MODULE_PARM(sab8253xn_listsize, "i"); /*transmit ring size default 32*/

MODULE_PARM(sab8253xc_name, "s");/*registered name for char driver =
sab8253xc*/

MODULE_PARM(sab8253x_default_sp502_mode, "i");

 


  The asynchronous TTY functionality can immediately be used without
  extra configuration.  [Note that immediate use of the WMS3500 products
  is possible because the default value of sab8253x_default_sp502_mode
  is SP502_RS232_MODE (== 1).  If a different default mode is needed, it
  can be set as options in the /etc/modules.conf file.  OFF = 0.

RS232 = 1, RS422 = 2, RS485 = 3, RS449 = 4, EIA530 = 5 and V.35 = 6, as
defined in 8253xioc.h.]

 

The MAKETERMS script below parses the /proc/tty/driver/auraserial file
to make the asynchronous TTY device files in the /dev directory.

 

TTYDEV=$1

MDEVS=`cat /proc/tty/driver/auraserial | gawk '{print $1}' | sed -e
'/[a-zA-Z]/d' | sed -e 's/://'`

 

for i in $MDEVS

do

        TTYNAME=/dev/ttyS${TTYDEV}

        mknod $TTYNAME c 4 $i

        TTYDEV=$((${TTYDEV}+1))

done

 

The MAKEPROTO script below provides a prototype to modify the
/etc/inittab file so that an agetty process can be spawned on every
other /dev/ttyS* at 9600 bps

 

TTYDEV=$1

MDEVS=`cat /proc/tty/driver/auraserial | gawk '{print $1}' | sed -e
'/[a-zA-Z]/d' | sed -e 's/://'`

LEADCHAR=""

 

for i in $MDEVS

do

        NAME=S${TTYDEV}

        TTYNAME=ttyS${TTYDEV}

        echo ${LEADCHAR}${NAME}:35:respawn:/sbin/agetty 9600 ${TTYNAME}

        TTYDEV=$((${TTYDEV}+1))

        if [ -z "$LEADCHAR" ]

        then

                LEADCHAR="#"

        else

                LEADCHAR=""

        fi

done

 


  If loopback cables are connected between successive TTY ports on each
  Aurora adapter card or unit, the command

 

cu ^Öl /dev/ttyS{n} ^Ös 9600

 

would connect to the login that was spawned on /dev/ttyS{n-1}.

 

The script MAKESTERMS (viz below) creates synchronous TTY dev files for
all the Aurora serial ports.

 

TTYDEV=$1

MDEVS=`cat /proc/tty/driver/auraserial | gawk '{print $1}' | sed -e
'/[a-zA-Z]/d' | sed -e 's/://'`

 

for i in $MDEVS

do

        TTYNAME=/dev/sttyS${TTYDEV}

        mknod $TTYNAME c 5 $i

        TTYDEV=$((${TTYDEV}+1))

done

 

The MAKESPROTO script below creates a prototype with which to modify the
/etc/inittab file to spawn an agetty process on every other
/dev/sttyS{N} device.

 

TTYDEV=$1

MDEVS=`cat /proc/tty/driver/auraserial | gawk '{print $1}' | sed -e
'/[a-zA-Z]/d' | sed -e 's/://'`

LEADCHAR=""

 

for i in $MDEVS

do

        NAME=sS${TTYDEV}

        TTYNAME=sttyS${TTYDEV}

        echo ${LEADCHAR}${NAME}:35:respawn:/sbin/agetty 9600 ${TTYNAME}

        TTYDEV=$((${TTYDEV}+1))

        if [ -z "$LEADCHAR" ]

        then

                LEADCHAR="#"

        else

                LEADCHAR=""

        fi

done

 


  The simplest way to use these terminals with the agetty process that
  comes with the Linux distribution is to leave externally clocked (the
  default) the terminals on which agetty has been spawned.

 

The loopback cable can be connected to a port on which agetty is not
being run.  The clockside of the cable is connected to this port.  The
user can run the MAKECLOCKING script below.

 

echo 8253xcfg $1 -n 64 158 56 4 192 140 15

8253xcfg $1 -n 64 158 56 4 192 140 15

 

The numbers on the 8253xcfg command line are new (decimal) values for
the channel control, mode and baud rate registers.  The file 8253xioc.h
and sab8253x manuals from Siemens/Infineon can assist in explaining the
reasoning behind these values. The 8253xcfg sets the mode, channel
control and baudrate generator registers of the port specified by
/dev/sttyS{N-1} which is the argument $1 of this script file.  8253xcfg
is a simple example program that is included with the driver sources. 
It is described in the next section of this document.

 

At this point, the user could execute the following command to connect
synchronously to the peer synchronous TTY port.

 

cu ^Öl /dev/sttyS{n} ^Ös 9600

 

To turn off internal clocking use the following command.

 

8253xcfg /dev/sttyS? ^Ön 64 152 0 4 0 140 15

 

To use an ASLX network device the following commands would be used.

 

*MAKECLOCKING /dev/sttyS*/{N} [if the interface is to provide clock]/

*stty */{speed} /*< /dev/sttyS*/{N} [if the interface is to provide clock]/

 

To set the MAC address, which defaults to 00:00:00:00:00:00 and which
consequently must be changed, use the following command.

 

*ifconfig 8253x*/{mdev} /*hw ether*/ {mac address} [as root]/

 

[Note that the 8253x{mdev} interface must not be running when the above
command is executed.]

 

To set the IP address, use the command.

/ /

*ifconfig 8253x*/{mdev} {ipadress} [as root]/

 

[Note that the two ifconfig commands can be combined on one line.  If
they are executed separately the MAC address command must be executed
before the IP address command.]

 

After the completion of the above commands, assuming there is an active
network peer that uses the same serial Ethernet frame structure, it
should be possible to ping or telnet to the peer networking device.

 

{mdev} is the minor device number (in decimal, 3 digits including
leading 0s required) associated with /dev/sttyS{N}.//

* *

To disable the network interface use the following command.

 

*ifconfig 8253x*/{mdev} /*down*/ [as root]/

 

If there is a need to disable clocking on a serial port, the
MAKENONCLOCKING shell script is invoked with the TTY device as an
argument as follows.

 

MAKENONCLOCKING /dev/ttyS{N}

 

The shell script contains the following commands.

 

echo 8253xcfg $1 -n 64 152 0 4 192 140 255

8253xcfg $1 -n 64 152 0 4 192 140 255

 

The numbers on the 8253xcfg command line are new (decimal) values for
the channel control, mode and baud rate registers.  The file 8253xioc.h
and sab8253x manuals from Siemens/Infineon can assist in explaining the
reasoning behind these values. The 8253xcfg sets the mode, channel
control and baudrate generator registers of the port specified by
/dev/sttyS{N-1} which is the argument $1 of this script file.  8253xcfg
is a simple example program that is included with the driver sources. 
It is described in the next section of this document.


         


  _Simple Tools and Example Programs_

 

The tools and example programs supplied with the SAB8253X ASLX driver
are the following.

1.      eprom9050

2.      8253xcfg

3.      8253xspeed

4.      8253xpeer

5.      8253xmode


    eprom9050

 

This program performs the bit-banging necessary to read and to program
the serial eprom of the PLX9050. 

 

To access the serial eprom on an adapter card the program opens up a TTY
device on   the adapter card, whose serial eprom is to be modified. 
This TTY device can either be synchronous, asynchronous or callout.  The
TTY device is passed as an argument when the program is invoked.

 

The program uses the ATIS_IOCGSEP9050 IOCTL to get the current serial
eprom values and the ATIS_IOCSSEP9050 IOCTL to set the new serial eprom
values.

 

Here is the source code of the program.

 

/*

 * Copyright (C) 2001 By Joachim Martillo, Telford Tools, Inc.

 *

 * 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 Free Software Foundation; either version

 * 2 of the License, or (at your option) any later version.

 *

 **/

 

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

#include <stdio.h>

#include <stdlib.h>

#include "8253xioc.h"

#include "Reg9050.h"

 

 

                                /* This application shows how to load the */

                                /* channel control, mode and rx frame
length */

                                /* check registers via an ioctl.*/

 

int main(int argc, char **argv)

{

  int fd;

  unsigned short oldeeprom[EPROM9050_SIZE], neweeprom[EPROM9050_SIZE];

  char buffer[200];

  int count;

  int value;

  unsigned short *pointer;

  unsigned short *pointerold;

  int noprompt = 0;

  int epromindex;

 

  if(argc < 2)

    {

      fprintf(stderr, "Syntax: %s {portname} [-n] {prom values}.\n", *argv);

      exit(-1);

    }

  fd = open(argv[1], O_RDWR);

  if(fd < 0)

    {

      perror("open failed.");

      exit(-2);

    }

 

  if((argc > 2) && !strcmp("-n", argv[2]))

    {

      noprompt = 1;

    }

 

                                /* get the current values */

  if(ioctl(fd, ATIS_IOCGSEP9050, &oldeeprom) < 0)

    {

      perror("ioctl failed.");

      exit(-3);

    }

                                /* set up the existing values as defaults */

  memcpy(neweeprom, oldeeprom, sizeof(oldeeprom));

                                /* gather all new values from the
command line */

                                /* or via tty input.*/

  for(count = (2+noprompt), pointer = neweeprom; count < argc; ++count,
++pointer)

    {

      *pointer = atoi(argv[count]);

    }

  pointer = neweeprom;

  pointerold = oldeeprom;

  for(epromindex = 0; epromindex < EPROM9050_SIZE; ++epromindex)

    {

      fprintf(stderr, "LOCATION %i [%4.4x/%4.4x]: ", epromindex,
*pointerold, *pointer);

 

      if(!noprompt)

        {

          if(count = read(0, buffer, 150), count <= 0)

            {

              exit(0);

            }

          buffer[count] = '\0';

          if(buffer[0] != '\n')

            {

              sscanf(buffer, "%x", &value);

              *pointer = (unsigned short) value;

            }

        }

      else

        {

          fprintf(stderr, "\n");

        }

      ++pointerold;

      ++pointer;

    }

                                /* This ioctl does the actual register
load. */

  if(ioctl(fd, ATIS_IOCSSEP9050, neweeprom) < 0)

    {

      perror("ioctl failed.");

      exit(-3);

    }

 

  fflush(stdout);

}

 

With the above program it is possible to change PCI vendor and device ID
values of the adapter card.  At that point the card would no longer be
visible to the driver.  To correct the vendor and device IDs use the
*lspci* Linux command to find out what new values are and recompile the
driver code after modifying the symbols that correspond to the correct
vendor and device Ids of the card to the new values.  (I should make the
vendor and device IDs module parameters that can be set from the
*insmod* command line.)  The eprom9050 can be invoked to write the
device and vendor IDs to the correct values.  Of course, then the card
will no longer be visible to the new version of the driver, and the
original version of the driver must be used to communicate with this card.

 


    8253xcfg

 

The 8253xcfg command provides access to images of the channel control,
mode and baud rate generator registers of the serial port that is
specified by the minor device number (port number = minor device number
^Ö minor_start) of the TTY device (either synchronous, asynchronous or
callout) specified on the command line by which 8253xcfg is invoked. 
The next time the port is initialized (usually on the first open after
every process that currently has the port open has closed it) these
registers are set with the values of their images.  The 8253xcfg command
can make a synchronous port clocking or non-clocking.  Note that even
though 8253xcfg operates on a TTY device, the open that finally sets the
registers with the values from the images can be either a synchronous
TTY, a network device or a synchronous character device open.  The
program uses the ATIS_IOCGPARAMS IOCTL to get the current values of the
images of the registers and employs the ATIS_IOCSPARAMS IOCTL to set the
current values of the images of the registers.  Here is the source of
the 8253xcfg program.

 

/*

 * Copyright (C) 2001 By Joachim Martillo, Telford Tools, Inc.

 *

 * 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 Free Software Foundation; either version

 * 2 of the License, or (at your option) any later version.

 *

 **/

 

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

#include <stdio.h>

#include <stdlib.h>

#include "8253xioc.h"

 

char *prompts[] =

  {

    "ccr0",

    "ccr1",

    "ccr2",

    "ccr3",

    "ccr4",

    "mode",

    "rlcr",

    0

  };

 

                                /* This application shows how to load the */

                                /* channel control, mode and rx frame
length */

                                /* check registers via an ioctl.*/

 

int main(int argc, char **argv)

{

  int fd;

  struct channelcontrol ccontrolold, ccontrolnew;

  char buffer[200];

  int count;

  int value;

  unsigned char *pointer;

  unsigned char *pointerold;

  char **promptpointer = prompts;

  int noprompt = 0;

 

  if(argc < 2)

    {

      fprintf(stderr, "Syntax: %s {portname} [-n] [ccr0 [ccr1 [ccr2
[ccr3 [ccr4 [mode [rlcr]]]]]]].\n", *argv);

      exit(-1);

    }

  fd = open(argv[1], O_RDWR);

  if(fd < 0)

    {

      perror("open failed.");

      exit(-2);

    }

 

  if((argc > 2) && !strcmp("-n", argv[2]))

    {

      noprompt = 1;

    }

 

                                /* get the current values */

  if(ioctl(fd, ATIS_IOCGPARAMS, &ccontrolold) < 0)

    {

      perror("ioctl failed.");

      exit(-3);

    }

                                /* set up the existing values as defaults */

  ccontrolnew = ccontrolold;

 

                                /* gather all new values from the
command line */

                                /* or via tty input.*/

  for(count = (2+noprompt), pointer = (unsigned char*) &ccontrolnew;
count < argc; ++count, ++pointer)

    {

      *pointer = atoi(argv[count]);

    }

  pointer = (unsigned char*) &ccontrolnew;

  pointerold = (unsigned char*) &ccontrolold;

  while(*promptpointer)

    {

      fprintf(stderr, "%s [%2.2x/%2.2x]: ",*promptpointer, *pointerold,
*pointer);

 

      if(!noprompt)

        {

          if(count = read(0, buffer, 150), count <= 0)

            {

              exit(0);

            }

          buffer[count] = '\0';

          if(buffer[0] != '\n')

            {

              sscanf(buffer, "%x", &value);

              *pointer = (unsigned char) value;

            }

        }

      else

        {

          fprintf(stderr, "\n");

        }

      ++pointerold;

      ++pointer;

      ++promptpointer;

    }

                                /* This ioctl does the actual register
load. */

  if(ioctl(fd, ATIS_IOCSPARAMS, &ccontrolnew) < 0)

    {

      perror("ioctl failed.");

      exit(-3);

    }

 

  fflush(stdout);

}

 

 


    8253xspeed

 

This program sets the custom baud rate of a serial port.  If the custom
baud rate of a serial port is non-zero, and if the standard baud rate
has been set to 38,400 bps, the next time the port is initialized, the
port will run at the custom baud rate.  If the custom baud rate were 0,
the port would run at the standard baud rate.   The 8352xpeed program is
invoked with a TTY device (either asynchronous, synchronous or callout),
but the effect also applies to the network device and the synchronous
character device that correspond to the same physical serial port.

 

Here is the source code for the 8253xspeed.

 

/*

 * Copyright (C) 2001 By Joachim Martillo, Telford Tools, Inc.

 *

 * 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 Free Software Foundation; either version

 * 2 of the License, or (at your option) any later version.

 *

 **/

 

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

#include <stdio.h>

#include <stdlib.h>

#include "8253xioc.h"

 

 

int main(int argc, char **argv)

{

  int fd;

  unsigned long oldspeed, newspeed;

  char buffer[200];

  int count;

  long value;

  int noprompt = 0;

  int epromindex;

 

  if(argc < 2)

    {

      fprintf(stderr, "Syntax: %s {portname} [-n] {new speed}.\n", *argv);

      exit(-1);

    }

  fd = open(argv[1], O_RDWR);

  if(fd < 0)

    {

      perror("open failed.");

      exit(-2);

    }

 

  if((argc > 2) && !strcmp("-n", argv[2]))

    {

      noprompt = 1;

    }

 

                                                                                                       
/* get the current values */

  if(ioctl(fd, ATIS_IOCGSPEED, &oldspeed) < 0)

    {

      perror("ioctl failed.");

      exit(-3);

    }

                                                                                                       
/* set up the existing values as defaults */

  newspeed = oldspeed;

                                                                                                       
/* gather all new values from the command line */

                                                                                                       
/* or via tty input.*/

  if(argc == (noprompt + 3))

    {

      newspeed = atoi(argv[count]);

    }

 

  fprintf(stderr, "speed [%ld/%ld]: ", oldspeed, newspeed);

 

  if(!noprompt)

    {

      if(count = read(0, buffer, 150), count <= 0)

                                                                                    
{

                                                                                    
  exit(0);

                                                                                    
}

      buffer[count] = '\0';

      if(buffer[0] != '\n')

                                                                                    
{

                                                                                    
  sscanf(buffer, "%ld", &newspeed);

                                                                                    
}

    }

  else

    {

      fprintf(stderr, "\n");

    }

 

                                                                                                       
/* This ioctl does the actual register load. */

  if(ioctl(fd, ATIS_IOCSSPEED, &newspeed) < 0)

    {

      perror("ioctl failed.");

      exit(-3);

    }

 

  fflush(stdout);

}

 

The ATIS_IOCGSPEED gets the value the custom baud rate for a serial port
while ATIS_IOCSSPEED sets the value of the custom baud rate for a serial
port.

 

 


    8253xpeer

 

The 8253xpeer example program reads and writes packets to the serial
port in synchronous mode.  The synchronous character driver to some
extent emulates the getmsg/putmsg functionality found in Solaris.  This
driver returns only one packet at a time to read and returns ENOMEM if
the receive buffer is not large enough to receive the current packet. 
The driver assumes that the data from a write is to be packetized into a
single packet.  The driver can provide asynchronous notification that
there is no more data queued to be transmitted at the serial port.  This
asynchronous notification informs the application program that a low
priority packet can now be written to the driver.  Such functionality is
useful to protocols like LAPB that distinguish low priority information
frames from high priority control frames.

 

To try out this program find the major device number associated with the
8253xc device in the /proc/devices file.  Select two ports to loop
together.  Connect them with a synchronous loopback cable.  Then execute
the following command for each of the ports.

 

mknod /dev//DevName1/ c /major-dev-num minor-dev-num-1/

/ /

mknod /dev//DevName2/ c /major-dev-num minor-dev-num-2/

 

Minor-dev-num-[1/2] correspond to the selected ports.

 

Select one of the ports to be clocking (the clocking end of the loopback
cable should connect to this port) and apply MAKECLOCKING to the
corresponding TTY.  Use stty or 8253xspeed and stty to set the speed on
the corresponding TTY port.

 

Then in one window run *8253xpeer /dev//DevName1/* and in another window
execute *8253xpeer /dev//DevName2./*  It should now be possible to send
and receive data in each of the windows.

 

Here is the program source.

 

/*

 * Copyright (C) 2001 By Joachim Martillo, Telford Tools, Inc.

 *

 * 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 Free Software Foundation; either version

 * 2 of the License, or (at your option) any later version.

 *

 **/

 

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

#include <stdio.h>

#include <stdlib.h>

#include "8253xioc.h"

#include <sys/poll.h>

 

struct pollfd pollarray[2];

 

 

char buffer[8192];

 

int main(int argc, char **argv)

{

  int fd;

  int status;

  int prompt = 1;

  int count;

 

  if(argc != 2)

    {

      fprintf(stderr, "Syntax: %s {portname}\n", *argv);

      exit(-1);

    }

  fd = open(argv[1], O_RDWR);

  if(fd < 0)

    {

      perror("open failed.");

      exit(-2);

    }

  do

    {

      if(prompt)

        {

          printf("Enter data: ");

          fflush(stdout);

          prompt = 0;

        }

      pollarray[0].fd = 0;

      pollarray[0].events = POLLIN;

      pollarray[0].revents = 0;

      pollarray[1].fd = fd;

      pollarray[1].events = POLLIN|POLLOUT;

      pollarray[1].revents = 0;

      status = poll(pollarray, 2, 10);

      switch(status)

        {

        case 0:

          break;

 

        case 1:

        case 2:

          if(pollarray[0].revents == POLLIN)

            {

              if(count = read(0, buffer, 150), count <= 0)

                {

                  perror("unable to read stdio.\n");

                  exit(0);

                }

              buffer[count] = '\0';

              if(count)

                {

                  if(pollarray[1].revents & POLLOUT)

                    {

                      if(write(pollarray[1].fd, buffer, count) <= 0)

                        {

                          perror("unable to write protodevice.\n");

                          exit(-1);

                        }

                    }

                  else

                    {

                      printf("Write of protodevice would block.\n");

                      fflush(stdout);

                    }

                }

              prompt = 1;

            }

          if(pollarray[1].revents & POLLIN)

            {

              if(count = read(pollarray[1].fd, buffer, 8192), count <= 0)

                {

                  perror("unable to read protodevice.\n");

                  exit(0);

                }

              buffer[count] = '\0';

              printf("\nRead: %s", buffer);

              fflush(stdout);

              prompt = 1;

            }

          break;

 

        default:

          break;

        }

    }

  while(status >= 0);

}

 

 


    8253xmode

 

The 8253xmode program sets the signaling mode of port on a multichannel
server 3500 extension board which has a programmable Sipex sp502
physical driver chip for each port.

 

The command syntax is the following

 

*8253xmode* /dev//{dev name} {mode}/

 

where mode is one of the following.

    * off
    * 232
    * 422
    * 485
    * 530
    * v.35

 

Note the minor devices associated with a multiserver increase
monotonically starting from the first connector on the upper left corner
if you are facing the connector side of the multiserver.  The numbering
goes from left to right and top to bottom without gaps  Thus, the
numbers on the multiserver itself may not map to the minor device number
as port number + minor device number of first port if the multiserver is
not fully populated.

 

Here is the source for the 8253xmode program.

 

/* -*- linux-c -*- */

/*

 * Copyright (C) 2001 By Joachim Martillo, Telford Tools, Inc.

 *

 * 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 Free Software Foundation; either version

 * 2 of the License, or (at your option) any later version.

 *

 **/

 

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

#include <stdio.h>

#include <stdlib.h>

#include "8253xioc.h"

 

static char *signaling[] =

{

        "OFF",

        "RS232",

        "RS422",

        "RS485",

        "RS449",

        "RS530",

        "V.35"

};

 

                                /* This application shows how to set sigmode

                                 * on those devices that support software

                                 * programmable signaling. */

int main(int argc, char **argv)

{

        int fd;

        unsigned int oldmode, newmode;

       

        if(argc != 3)

        {

                fprintf(stderr, "Syntax: %s {portname} {new mode}.\n",
*argv);

                fprintf(stderr, "{new mode} = off | 232 | 422 | 485 |
449 | 530 | v.35\n");

                exit(-1);

        }

        fd = open(argv[1], O_RDWR);

        if(fd < 0)

        {

                perror("open failed.");

                exit(-2);

        }

        if(!strcmp("off", argv[2]))

        {

                newmode = SP502_OFF_MODE;

        }

        else if(!strcmp("232", argv[2]))

        {

                newmode = SP502_RS232_MODE;

        }

        else if(!strcmp("422", argv[2]))

        {

                newmode = SP502_RS422_MODE;

        }

        else if(!strcmp("485", argv[2]))

        {

                newmode = SP502_RS485_MODE;

        }

        else if(!strcmp("449", argv[2]))

        {

                newmode = SP502_RS449_MODE;

        }

        else if(!strcmp("530", argv[2]))

        {

                newmode = SP502_EIA530_MODE;

        }

        else if(!strcmp("v.35", argv[2]))

        {

                newmode = SP502_V35_MODE;

        }

        else

        {

                fprintf(stderr, "Unknown mode %s.\n", argv[2]);

                fprintf(stderr, "Syntax: %s {portname} {new mode}.\n",
*argv);

                fprintf(stderr, "{new mode} = off | 232 | 422 | 485 |
449 | 530 | v.35\n");

                exit(-1);

        }

       

        /* get the current values */

        if(ioctl(fd, ATIS_IOCGSIGMODE, &oldmode) < 0)

        {

                perror("ATIS_IOCGSIGMODE ioctl failed.");

                exit(-3);

        }

        fprintf(stderr, "old mode = %s.\n", signaling[oldmode]);

       

        if(ioctl(fd, ATIS_IOCSSIGMODE, &newmode) < 0)

        {

                perror("ATIS_IOCSSIGMODE ioctl failed.");

                exit(-3);

        }

 

        /* get the current values */

        if(ioctl(fd, ATIS_IOCGSIGMODE, &oldmode) < 0)

        {

                perror("ATIS_IOCGSIGMODE ioctl failed.");

                exit(-3);

        }      

        fprintf(stderr, "new mode = %s.\n", signaling[oldmode]);

        fflush(stdout);

}

 

The 8253xmode program uses the ATIS_IOCSSIGMODE ioctl to set the new
physical signaling mode and employs the ATIS_IOCGSIGMODE ioctl to get
the original value and to verify the new mode.

 


  _Logic Structure of the ASLX Driver_

 


  /Data Structure Summary/

 

The key data structures that enable the driver logic are:

 

1.      SAB_BOARD structure ^Ö driver specific

2.      SAB_CHIP structure ^Ö driver specific

3.      SAB_PORT structure ^Ö driver specific

4.      AURA_CIM structure ^Ö driver specific (actually specific to the
multichannel server)

5.      RING_DESCRIPTOR ^Ö used by all the driver functionalities in the
transmission of data.

6.      DCONTROL2 ^Ö used by all the driver functionalities in managing
the transmission of data.

7.      struct sk_buff_head ^Ö two buffer lists are associated with the
SAB_PORT structure are used to track all the sk_buffs that are currently
in use at each port.

8.      struct tty_struct ^Ö one per port, standard structure by which
the TTY driver access low level routines for either asynchronous TTY,
synchronous TTY and callout functionality.  The SAB_PORT serves as the
private data structure associated with each 8253x TTY, which on a given
open can instantiate itself as a synchronous TTY, an asynchronous TTY or
as a call out device.

9.      struct net_device ^Ö one per port, standard network device
structure.  The SAB_PORT serves as the private data structure associated
with each 8253x network interface.

10. struct file_operations ^Ö one per port, standard character device
structure.  The SAB_PORT serves as the private data structure associated
with each 8253x character interface.

 

Thus the fundamental driver functionalities, the TTY device, the network
interface and the character device, share the port structure; and the
port structure contains the data is used to arbitrate driver
functionality access to a given physical port.  All the above driver
specific structures are dynamically allocated at driver initialization. 
They are maintained on lists (in come cases several lists, e.g., global,
per board, by interrupt+by board type and per chip lists).  The global
port list is used to map minor device numbers sequentially to ports. 
The per chip port list is used in interrupt processing.  Likewise the by
interrupt+by board type board lists are also used in interrupt processing.

 

The use and definition of the tty_struct (TTY and callout drivers),
net_device (network drivers) and file_operations (character drivers)
structures are found in the Linux. 


     


    Quick Overview of Standard Linux TTY, Network and Character Devices
    and Interaction with the ASLX Driver Design

 

The basic design of TTY/callout drivers, network drivers and character
drivers is specified by the Linux interface. 

 

The TTY driver uses the standard circular transmit buffer as found in
serial.c, which handles the PC com ports) while received characters pass
into a line disciple via the standard flip buffer logic found in serial.c

 

The network and character driver parts just follow the design described
in /Linux Device Drivers/ by Alessandro Rubini.

All the driver functionalities use a circular transmit buffer descriptor
ring and sk_buffers for receiving and transmitting data.  This uniform
approach to transception simplifies the driver logic immensely.

 

There is no use of a transmit done interrupt equivalent (unnecessary for
a non-dma design).  The driver can be compiled to free up transmitted
sk_buffs in the interrupt handler or in write routines. Investigation
shows that the driver performs better when transmitted buffers are freed
outside of the interrupt handlers.  The sk_* routines are in general
fairly performance costly.  As a further optimization, when sk_buffs are
freed in the write routines, if system write gets ahead of the
transmitter, the program logic will try to avoid releasing transmitted
buffers (in the TTY driver) but will try to reuse them if possible.

 

When data is received, chains of receive buffers are passed back to the
character device read routine or to a flush-to-line-discipline function
(defined in the ASLX driver to override the default flush_to_ldisc
routine defined in tty_io.c) in the case of the TTY driver
functionalities. The network driver just invokes the /netif_rx()/ 
routine at interrupt level to receive packets. Currently, the network
driver pre-allocates a receive buffer, but such pre-allocation is not
necessary, and no other driver functionalities make use of
pre-allocation of buffers.


    From Standard Driver Architectures to the ASLX Driver

 

The main difficulties to be overcome in the ASLX design fit into two
categories.

 

1.      No other Linux driver attempts to combine multiple TTY
functionalities with network and character driver functionalities.

2.      Even though the members of this Aurora product line all use
basically the same Siemens/Infineon interface chip, the details of
accessing this chip differ radically over the product line.  The adapter
cards use the PLX 9050 PCI bridge chip while the multichannel servers
use the AMCC 5920 PCI bridge chip.  In the latter case there is a
somewhat complex G-Link hardware protocol used for communication between
the host adapter card and the expansion chassis that hosts the extension
boards where the serial interface chips are located.  The adapter cards
differ in detecting and causing modem signal changes.

 


  /Creating a Uniform Device Programming Interface/

 

The most painful aspect of  of creating a multifunction driver for the
Aurora hardware is the difference of each Aurora adapter card or unit
from every other Aurora adapter card or unit. 

 

Signals are handled differently on ESCC2 (SAB82532) versus ESCC8
(SAB82538) based devices.  Interrupt processing is different on ESCC2,
ESCC8 and multichannel server devices.  The multichannel servers use an
AMCC bridge chip while the other devices use a PLX bridge chip.  
Multichannel servers and all other Aurora adapter cards access device
registers completely differently.

 

There are a set of data structures and macros that simplify access to
control registers for serial ports and that try to provide uniformity in
line control signal handling, which is complex because some signals are
defined in the serial port interface of the 8253X serial interface chip
while other signals are handled through the general parallel ports of
the 8253X chip. 

 

The bitwise definition of the line control signals differ on the
parallel ports of the 82532 and the 82538 based cards (including
multichannel server units). 

 

The macros (courtesy Francois Wautier) below provide a common interface
for raising and lowering control signals as well as for querying them.


   

/*

 * Raise a modem signal y on port x, tmpval must exist! */

#define RAISE(xx,y) \

{ \

          unsigned char __tmpval__; \

          __tmpval__= (xx)->readbyte((xx),(xx)->y.reg);\

          if((xx)->y.inverted)\

            __tmpval__ &= ~((xx)->y.mask);\

          else\

            __tmpval__ |= (xx)->y.mask;\

          __tmpval__ |= (xx)->y.cnst;\

          (xx)->y.val=1;\

          (xx)->writebyte((xx),(xx)->y.reg,__tmpval__);\

}

/*

 * Lower a modem signal y on port x, __tmpval__ must exist! */

#define LOWER(xx,y) \

{\

          unsigned char __tmpval__; \

          __tmpval__= (xx)->readbyte((xx),(xx)->y.reg);\

          if((xx)->y.inverted)\

            __tmpval__ |= (xx)->y.mask;\

          else\

            __tmpval__ &= ~((xx)->y.mask);\

          __tmpval__ |= (xx)->y.cnst;\

          (xx)->y.val=0;\

          (xx)->writebyte((xx),(xx)->y.reg,__tmpval__);\

}

 

#define ISON(xx,y) \

          ((xx)->y.inverted !=
(((xx)->readbyte((xx),(xx)->y.reg)&(xx)->y.mask)==(xx)->y.mask))

 

The inverted, cnst, and mask fields of the modem signal structure (y)
are specific to the type of serial communications controller.  The
readbyte and writebyte functions are specific to the different types of
Aurora hardware.

 

To hide the details of accessing control and data registers, the
SAB_PORT structure has a fields whose values are the functions to read a
device register, to write a device register,to  read a device FIFO and
to write a device FIFO (as well as to read and to write short words,
functions that could be used in implementing the readfifo and writefifo
routines).

 

Here is readfifo for non-multichannel server hardware.

 

/***************************************************************************

 * aura_readfifo:    Function to read the FIFO on a 4X20P, 8X20P or Sun
serial

 *               

 *

 *     Parameters   :

 *                   port:  The port being accessed

 *                   buf:   The address of a buffer where we should put

 *                          what we read

 *                  nbytes: How many chars to read.

 *

 *     Return value : none

 *

 *     Prerequisite : The port must have been opened

 *

 *     Remark       :

 *

 *     Author       : fw

 *

 *     Revision     : Oct 13 2000, creation

 ***************************************************************************/

void aura_readfifo(struct sab_port *port, unsigned char *buf, unsigned
int nbytes)

{

  int i;

  unsigned short *wptr = (unsigned short*) buf;

  int nwords = ((nbytes+1)/2);

  for(i = 0; i < nwords; i ++)

    {

      wptr[i] = readw(((unsigned short *)port->regs));

    }

}

 

Here is the readfifo function for multichannel servers.

 

void wmsaura_readfifo(struct sab_port *port, unsigned char *buf,
unsigned int nbytes)

{

#ifdef FIFO_DIRECT

  unsigned short fifo[32/2];    /* this array is word aligned

                                 * buf may not be word aligned*/

  unsigned int nwords;

  int i;

  int wcount;

  unsigned int address;

 

  if (nbytes == 0)

    {

      return;

    }

 

  wcount = ((nbytes + 1) >> 1);

  /* Read the thing into the local FIFO and copy it out. */

  address = (unsigned int) port->regs;

 

  for(i = 0; i < wcount; ++i)

    {

      fifo[i] = readw((unsigned short*)(address + CIMCMD_RDFIFOW));

    }

 

  memcpy((unsigned char*) buf, (unsigned char*) &(fifo[0]), (unsigned
int) nbytes);

 

#else           /* FIFO_DIRECT */

  unsigned short fifo[32/2];

  int i;

  int wcount;

  SAB_BOARD *bptr;

  unsigned int channel;

 

  if (nbytes == 0)

    {

      return;

    }

 

  bptr = port->board;

  wcount = ((nbytes + 1) >> 1);

  channel = (((unsigned char*) port->regs) - bptr->CIMCMD_REG); /*
should be properly shifted */

 

  /*

   * Trigger a cache read by writing the nwords - 1 to the

   *  magic place.

   */

 

  writeb((unsigned char) wcount, bptr->MICCMD_REG + (MICCMD_CACHETRIG +
channel));

 

  /*

   * Now, read out the contents.

   */

 

  channel >>= 1;

 

  for(i = 0; i < wcount; ++i)

    {

      fifo[i] = readw((unsigned short*)(bptr->FIFOCACHE_REG + (channel +
(i << 1))));

    }

 

  memcpy((unsigned char*) buf, (unsigned char*) &(fifo[0]), (unsigned
int) nbytes);

#endif          /* !FIFO_DIRECT */

}


   

Except at initialization time the driver code accesses serial
communications controller device registers only through fields in the
port structure.  The details of accessing the different types of
hardware are almost completely hidden from the driver program logic.

 

The only exception is the interrupt handler, which must understand some
of the details of the multichannel server.  Nevertheless, as is made
clear in the following section, from the standpoint of processing
interrupts there are really only two types of Aurora hardware, that
which is ESCC2 based and that which is ESCC8 based.  The details of the
difference of the two types of hardware is contained solely within the
8253xint.c file which also contains the logic by which an interrupt from
a multichannel server can be processed almost exactly like an interrupt
from an 8520P adapter card.

 


  /Code Structure Summary/


   


  The SAB8253X driver code has the following logic components

 

1.      a probe/initialization logic,

a.      bridge initialization/EEPROM parsing logic,

b.      structure allocation logic,

c.      interrupt request logic,

2.      the core logic, which handles all the non-interrupt processing
of the driver functionality,

a.      per port startup/shutdown logic,

b.      driver arbitration logic,

c.      skbuffer management logic,

3.      the interrupt handler logic,

a.      common interrupt port polling logic,

b.      skbuffer management logic,

4.      the termination/driver unload logic,

a.      interrupt shutdown logic,

b.      device shutdown logic,

c.      structure deallocation logic.

 

 

/Probe/Initialization Logic/

 

The probe/initialization logic sets up the CRC structures for the
network driver and then the tty_struct for the synchronous and
asynchronous TTY drivers.  Initializing the tty_struct involves setting
up pointers to the standard functions that the Linux TTY driver invokes
as well as some standard data and the /proc/tty/driver/auraserial
function and data.

 


The probe/initialization logic identifies all the multiport serial
adapters, compact PCI adapters and multiserver adapters in the system
and puts them on a list of board structures.  After identifying all the
boards, initializing the bridge chips and analyzing (possibly rewriting)
the serial EEPROM, the logic sets up all the chips on the boards and all
the ports on the chips. 

 

All chips are linked together in a list.  All ports are linked together
in a list.  The port list is linked together so that minor device N
corresponds to the Nth element of the port list.  A board structure
points to a list of chips on the board as well as a list of all ports on
the board.  Likewise a chip structure points to a list of all ports on
the chip.  Chip structures point back to the board structure associated
with the board on which the chip resides.  Likewise port structures
point back to the board and to the chip on which they reside.  This
interconnected list structure facilitates access to one type of
structure when a routine has been passed a pointer to another type of
structure.  All these structures are dynamically allocated via
/kmalloc()/.  When the driver is unloaded, memory is released by walking
the list. 

 

After setting up the board, chip and port structures, initialization of
the tty_struct is completed at this point because the maximum possible
number of serial TTYs is now known.  The, the standard network device
structure that is associated with each port is allocated and
initialized.  The network devices point to the associated port
structure.  The network devices are chained via a pointer in the
associated port structure.  This chaining facilitates release of the
network device structure memory when the driver is unloaded.

 

Each network device is registered after its network device structure is
initialized.  Network device initialization invokes the network device
initialization, which installs the standard network functions in the
network device structure and which sets up the sk_buff transmit ring as
well as the receive sk_buf.  Unlike the Solaris driver, when a complete
frame is received with no errors, it is immediately passed into the
network layer.  Thus, there is no receive ring of sk_buffs as one might
find in the driver of a device that could carry out chained DMA (e.g. a
DEC Tulip or an Hitachi SCA).

 

All sk_buffs associated with a network device that are currently in use
by the network driver are linked together in an sk_buff list (viz core
logic).   This list facilitates release of all sk_buff associated with a
network device when that network device is shut down.  This list may be
a problem if it becomes possible for a single sk_buff to be used
simultaneously by multiple network devices.  Currently, sk_buff headers
are not shared among network devices although sk_buff data can be
shared.  Thus, for the nonce this logic works correctly.

 

After completing of network device initialization, the
probe/initialization logic register the character driver.  Next, the
probe/initialization creates three lists of boards for each interrupt
(0-31).  One list contains 82532 based boards at that interrupt level. 
The next list contains 82538 based adapter cards at that interrupt
level.  The third list contains all multichannel server units at that
interrupt level.

 

Next the probe/initialization logic checks the lists associated with
each interrupt level.  If either list is non-null, the
probe/initialization logic requests that the general interrupt handler
be installed at this interrupt level and then it turns on PLX9050 or
AMCC5920 interrupts (as needed) into the Linux host for each card
associated with that interrupt level.  Thus, the interrupt handler polls
all boards/ports at a given interrupt level when the interrupt occurs. 
This approach is more efficient that installing one interrupt per board
and avoids some internal Linux limits on the number of interrupt
handlers that can be installed per interrupt.

 

At this point probe/intialization is complete and any serial port may be
used for asynchronous TTY, synchronous TTY, call out, network device
service or synchronous character device service.

 


/Core Logic/

 

The main problem of the core logic is arbitration of access to the
serial port, when and by which part of the driver a port is started and
when the port is shut down.

 

The asynchronous callout, asynchronous TTY, synchronous TTY devices,
synchronous character device and network device follow the following rules.

 

1.                  If there is an established point-to-point
connection, only the current process or process group that owns the
serial port may open the device.

2.                  If the asynchronous callout is open, opens of a
single TTY or character device type block until the connection completes.

3.                  Network device opens never block, and the network
layer opens a network device only once.

4.                  If a connection that belongs to a TTY device or
character device hangs up, eventually all opens of that device will close.

5.                  Hangup of on a network device does not guarantee a
close of the device, but a flag bit is set that permits a call out open
to restore the connection (note that a one-to-one map of TTY devices,
callout devices, character devices and network devices is implied.)

 

The network device open and block_til_ready* functions invoked from TTY
opens enforce this arbitration.

 

The logic works as follows:

 

    * The cua device associated with a port may only be opened one.
    * If a TTY or character device is open, the cua device may not be
      opened.
    * If the cua device is open, the network device cannot be opened and
      the TTY or character device block (multiple opens from the same
      process or process group are allowed).
    * If the network device is open, the TTY or character devices cannot
      be opened while the cua device blocks.
    * A hangup sends an interrupt to the TTY or character device while
      the network device shuts down its port and a (network blocked) cua
      device proceeds.
    * On the close of the cua device, blocked TTY and character devices
      proceed and the network device restarts its port.

 

When an open completes, the transmit_chars, receive_chars,  check_status
functions and associated data fields are set in the port structure,
these are used in the interrupt handler that never changes until the
driver is unloaded.

 

When a port is not in use, the asynchronous version of these fields and
functions are set in the port structure.  Some values must be present,
and the asynchronous ones are probably the least dangerous.

 

Besides the arbitration problem, the core logic addresses buffer
management for the network and character drivers.

 

The network layer passes a transmit sk_buff to the network driver.   The
buffer is inserted in the transmit ring or sets a transmit congestion
flag.  In either case, transmit is initialized if not already in
progress.  If the sk_buff is successfully inserted, it is also linked
into the driver sk_buff list which tracks all sk_buffs used by the
network driver.  On network device close all sk_buffs on the per port
sk_buff list are released.  This sk_buff list mechanism is used to avoid
memory leaks.

 

The TTY and character drivers packetize write data in an sk_buff (each
write creates a single sk_buff) and inserts it in the transmit ring if
possible or blocks (returns a failure in the case of TTY drivers). 
Transmit sk_buffs are also linked into the driver sk_buff list.  This
list is a convenience to assist deallocation during driver close.

 

The TTY and character drivers also maintains a receive sk_buff list. 
When the user application invokes the read system call, the data from
one sk_buff is passed up to the user application (or an error if the
read were not invoked with sufficient buffer space). 

 

The character driver can be configured via IOCTL to send asynchronous
user notification when the transmit ring empties (a design choice for
the standard driver fasync functionality).  This character driver design
emulates the Solaris putmsg/getmsg interface as far as possible and
makes it possible to implement in a user application protocols like
LAPB, which require that low priority packets only be queued to the
driver when the driver currently has no frames in the process of
transmission or queued for transmission.

 

On character device close all sk_buffs on the per port sk_buff and per
port receive sk_buff list are released.  This double sk_buff list
mechanism is used to avoid memory leaks.

 

/Interrupt Handler Logic/

 


      The following code comprises the combined interrupt and board/port
      polling logic.  The actual handler is /sab8253x_interrupt()/.  It
      walks through the 82532, adapter 82538 adapter and multichannel
      server lists at the interrupt level that is being processed and
      invokes inline /sab82532_interrupt()/ and /sab82538_interrupt()/. 
      Note that it temporary modifies multichannel server lists so that
      it like an 8520P adapter card to the /sab8253x_interrupt()/.  This
      logic works because the granularity of the PCI interrupt
      associated with the Aurora hardware is basically either a list of
      ESCC2s (the 4520P and 4520CP adapter cards) or a single ESCC8 (an
      8520P or one ESCC8 on a multichannel server remote card after the
      interrupt status has been queried).


       


      The current interrupt sources are identified and processed.  If
      any characters are available to be received, they are received one
      fifo at a time into the TTY flip buffer (a structure that is
      supposed to decrease TTY latency) or into an sk_buf (something
      like a Solaris mblk/dblk structure( in the case of the network or
      character driver.  Then if there are characters in the TTY
      circular transmit buffer or in the network or character driver
      sk_buff, they are loaded into the transmit fifo, one fifo at a
      time.  Finally, modem control status is checked.  If a hang up is
      detected, the serial driver hang up is scheduled at kernel
      scheduler priority (a slight difference from the standard serial
      driver which processes hang-ups at interrupt level) in order to
      avoid certain race conditions in the TTY driver that can occur on
      fast machines with many serial ports.  /do_serial_hangup()/
      invokes the TTY hangup routines in the case of TTY driver.  For a
      network connection in the event of a line disconnection, carrier
      is marked as off on the interface and the blocked cua device is
      woken so that it can proceed after the network port has shut down.

 

static void __inline__ sab82532_interrupt(int irq, void *dev_id, struct
pt_regs *regs)

{

  struct sab_port *port;

  struct sab_chip *chip=NULL;

  struct sab_board *bptr = (struct sab_board*) dev_id;

  union sab8253x_irq_status status;

  unsigned char gis;

 

  for(chip = bptr->board_chipbase; chip != NULL; chip =
chip->next_by_board)

    {

      port= chip->c_portbase;

      gis = READB(port, gis); /* Global! */

      status.stat=0;

     

            /* Since the PORT interrupt are global,

             * we do check all the ports for this chip

             */

           

                                /* A 2 ports chip */

         

      if(!(gis & SAB82532_GIS_MASK))

        {

          continue; /* no interrupt on this chip */

        }

         

      if (gis & SAB82532_GIS_ISA0)

        {

          status.sreg.isr0 = READB(port, isr0);

        }

      else

        {

          status.sreg.isr0 = 0;

        }

      if (gis & SAB82532_GIS_ISA1)

        {

          status.sreg.isr1 = READB(port, isr1);

        }

      else

        {

          status.sreg.isr1 = 0;

        }

         

      if (gis & SAB82532_GIS_PI)

        {

          status.sreg.pis = READB(port, pis);

        }

      else

        {

          status.sreg.pis = 0;

        }

               

      if (status.stat)

        {

          if (status.images[ISR0_IDX] & port->receive_test)

            {

              (*port->receive_chars)(port, &status);    /* when the fifo
is full */

                                                /* no time to schedule
thread*/

            }

         

          if ((status.images[port->dcd.irq] & port->dcd.irqmask) ||

              (status.images[port->cts.irq] & port->cts.irqmask) ||

              (status.images[port->dsr.irq] & port->dsr.irqmask) ||

              (status.images[ISR1_IDX] & port->check_status_test))

            {

              (*port->check_status)(port, &status); /* this stuff should
be */

              /* be moveable to scheduler */

              /* thread*/

            }

         

          if (status.images[ISR1_IDX] & port->transmit_test)

            {

              (*port->transmit_chars)(port, &status); /* needs to be
moved to task */

            }

        }

     

                                /* Get to next port on chip */

      port = port->next_by_chip;

      /* Port B */

      if (gis & SAB82532_GIS_ISB0)

        {

          status.images[ISR0_IDX] = READB(port, isr0);

        }

      else

        {

          status.images[ISR0_IDX] = 0;

        }

      if (gis & SAB82532_GIS_ISB1)

        {

          status.images[ISR1_IDX] = READB(port,isr1);

        }

      else

        {

          status.images[ISR1_IDX] = 0;

        }

      /* DO NOT SET PIS. IT was reset! */

     

     

      if (status.stat)

        {

          if (status.images[ISR0_IDX] & port->receive_test)

            {

              (*port->receive_chars)(port, &status);

            }

          if ((status.images[port->dcd.irq] & port->dcd.irqmask) ||

              (status.images[port->cts.irq] & port->cts.irqmask) ||

              (status.images[port->dsr.irq] & port->dsr.irqmask) ||

              (status.images[ISR1_IDX] & port->check_status_test))

            {

              (*port->check_status)(port, &status);

            }

          if (status.images[ISR1_IDX] & port->transmit_test)

            {

              (*port->transmit_chars)(port, &status);

            }

        }

    }

}

 

static void __inline__ sab82538_interrupt(int irq, void *dev_id, struct
pt_regs *regs)

{

  struct sab_port *port;

  struct sab_chip *chip=NULL;

  struct sab_board *bptr = (struct sab_board*) dev_id;

  union sab8253x_irq_status status;

  unsigned char gis,i;

 

  chip = bptr->board_chipbase;

  port= chip->c_portbase;

 

  gis = READB(port, gis); /* Global! */

  status.stat=0;

     

            /* Since the PORT interrupt are global,

             * we do check all the ports for this chip

             */

           

  /* 8 ports chip */

  if(!(gis & SAB82538_GIS_MASK))

    {

      return;

    }

             

  if(gis & SAB82538_GIS_CII)

    { /* A port interrupt! */

      /* Get the port */

      int portindex;

     

      portindex = (gis & SAB82538_GIS_CHNL_MASK);

     

      port = chip->c_portbase;

     

      while(portindex)

        {

          port = port->next_by_chip;

          --portindex;

        }

     

      status.images[ISR0_IDX] = READB(port,isr0);

      status.images[ISR1_IDX] = READB(port,isr1);

      if (gis & SAB82538_GIS_PIC)

        {

          status.images[PIS_IDX] =

            (*port->readbyte)(port,

                              ((unsigned char *)(port->regs)) +

                              SAB82538_REG_PIS_C);

        }

      else

        {

          status.images[PIS_IDX] = 0;

        }

     

      if (status.stat)

        {

          if (status.images[ISR0_IDX] & port->receive_test)

            {

              (*port->receive_chars)(port, &status);

            }

          if ((status.images[port->dcd.irq] & port->dcd.irqmask) ||

              (status.images[port->cts.irq] & port->cts.irqmask) ||

              (status.images[port->dsr.irq] & port->dsr.irqmask) ||

              (status.images[ISR1_IDX] & port->check_status_test))

            {

              (*port->check_status)(port, &status);

            }

          /*

           * We know that with 8 ports chip, the bit corresponding to
channel

           * number is used in the parallel port... So we clear it

           * Not too elegant!

           */

          status.images[PIS_IDX] &= ~(1 << (gis&SAB82538_GIS_CHNL_MASK));

          if (status.images[ISR1_IDX] & port->transmit_test)

            {

              (*port->transmit_chars)(port, &status);

            }

        }

    }

 

  /*

   * Now we handle the "channel interrupt" case. The chip manual for the

   * 8 ports chip states that "channel" and "port" interrupt are set

   * independently so we still must check the parrallel port

   *

   * We should probably redesign the whole thing to be less AD HOC that we

   * are now... We know that port C is used for DSR so we only check
that one.

   * PIS for port C was already recorded in  status.images[PIS_IDX], so we

   * check the ports that are set

   */

 

  if (status.images[PIS_IDX])

    {

      for(i=0, port = chip->c_portbase;

          i < chip->c_nports;

          i++, port=port->next_by_chip)

        {

          if(status.images[PIS_IDX] & (0x1 << i))

            { /* Match */

              /* Checking DSR */

              if(port->dsr.inverted)

                {

                  port->dsr.val = (((*port->readbyte)

                                    (port, port->dsr.reg) &

                                    port->dsr.mask) ? 0 : 1);

                }

              else

                {

                  port->dsr.val = ((*port->readbyte)(port, port->dsr.reg) &

                                   port->dsr.mask);

                }

             

              port->icount.dsr++;

              wake_up_interruptible(&port->delta_msr_wait);

            }

        }

    }

}

 

/*

 * This is the serial driver's generic interrupt routine

 */

 

void sab8253x_interrupt(int irq, void *dev_id, struct pt_regs *regs)

{

  extern SAB_BOARD *AuraBoardESCC2IrqRoot[];

  extern SAB_BOARD *AuraBoardESCC8IrqRoot[];

  extern SAB_BOARD *AuraBoardMCSIrqRoot[];

  AURA_CIM *cim;

  SAB_CHIP *chip;

  SAB_PORT *port;

  register SAB_BOARD *boardptr;

  register unsigned char intrmask;

  unsigned char stat;

  SAB_CHIP *save_chiplist;

  SAB_PORT *save_portlist;

 

  if((irq < 0) || (irq >= NUMINTS))

    {

      printk(KERN_ALERT "sab8253x: bad interrupt value %i.\n", irq);

      return;

    }

  /* walk through all the cards on the interrupt that occurred. */

  for(boardptr = AuraBoardESCC2IrqRoot[irq]; boardptr != NULL; boardptr
= boardptr->next_on_interrupt)

    {

      sab82532_interrupt(irq, boardptr, regs);

    }

 

  for(boardptr = AuraBoardESCC8IrqRoot[irq]; boardptr != NULL; boardptr
= boardptr->next_on_interrupt)

    {

      sab82538_interrupt(irq, boardptr, regs);

    } 

 

  for(boardptr = AuraBoardMCSIrqRoot[irq]; boardptr != NULL; boardptr =
boardptr->next_on_interrupt)

    {

 

      while(1)

        {

          writeb(0, (unsigned char*)(boardptr->CIMCMD_REG +
CIMCMD_WRINTDIS)); /* prevent EBs from raising

                                                                               
* any more ints through the

                                                                               
* host card */

          stat = ~(unsigned char) /* active low !!!!! */

            readw((unsigned short*)

                  (((unsigned char*)boardptr->CIMCMD_REG) +
CIMCMD_RDINT)); /* read out the ints */

                                /* write to the MIC csr to reset the PCI
interrupt */

          writeb(0, (unsigned char*)(boardptr->MICCMD_REG +
MICCMD_MICCSR)); /* reset the interrupt generation

                                                                       
      * hardware on the host card*/

                                /* now, write to the CIM interrupt ena
to re-enable interrupt generation */

          writeb(0, (unsigned char*)(boardptr->CIMCMD_REG +
CIMCMD_WRINTENA)); /* allow EBs to request ints

                                                                               
* through the host card */

          if(!stat)

            {

              break;

            }

          cim = boardptr->b_cimbase; /* cims in reverse order */

          for(intrmask = boardptr->b_intrmask;

              intrmask != 0;

              intrmask <<= 2, stat <<=2)

            {

              if(cim == NULL)

                {

                  break;        /* no cim no ports */

                }

              if((intrmask & 0xc0) == 0) /* means no cim for these ints */

                {               /* cim not on list do not go to next */

                  continue;

                }

              save_portlist = boardptr->board_portbase;

              save_chiplist = boardptr->board_chipbase;

                                /* the goal is temporarily to make the
structures

                                 * look like 8x20 structures -- thus if
I find

                                 * a bug related to escc8s I need fix it in

                                 * only one place. */

              switch(stat & 0xc0) /* possible ints */

                {

                default:

                  break;

                 

                case 0x80:      /* esccB */

                  chip = cim->ci_chipbase;

                  if(!chip)

                    {

                      printk(KERN_ALERT "aura mcs: missing cim.\n");

                      break;

                    }

                  chip = chip->next_by_cim;

                  if(!chip)

                    {

                      printk(KERN_ALERT "aura mcs: missing 2nd cim.\n");

                      break;

                    }

                  port = chip->c_portbase;

                  boardptr->board_portbase = port;

                  boardptr->board_chipbase = chip;

                  sab82538_interrupt(irq, boardptr, regs);               

                  break;

                 

                case 0x40:      /* esccA */

                  chip = cim->ci_chipbase;

                  if(!chip)

                    {

                      printk(KERN_ALERT "aura mcs: missing cim.\n");

                      break;

                    }

                  port = chip->c_portbase;

                  boardptr->board_portbase = port;

                  boardptr->board_chipbase = chip;

                  sab82538_interrupt(irq, boardptr, regs);               

                  break;

                 

                case 0xc0:      /* esccB and esccA */

                  chip = cim->ci_chipbase;

                  if(!chip)

                    {

                      printk(KERN_ALERT "aura mcs: missing cim.\n");

                      break;

                    }

                  port = chip->c_portbase;

                  boardptr->board_portbase = port;

                  boardptr->board_chipbase = chip;

                  sab82538_interrupt(irq, boardptr, regs);               

 

                  chip = cim->ci_chipbase;

                  if(!chip)

                    {

                      printk(KERN_ALERT "aura mcs: missing cim.\n");

                      break;

                    }

                  chip = chip->next_by_cim;

                  if(!chip)

                    {

                      printk(KERN_ALERT "aura mcs: missing 2nd cim.\n");

                      break;

                    }

                  port = chip->c_portbase;

                  boardptr->board_portbase = port;

                  boardptr->board_chipbase = chip;

                  sab82538_interrupt(irq, boardptr, regs);               

                  break;

                }

              boardptr->board_portbase = save_portlist;

              boardptr->board_chipbase = save_chiplist;

              cim = cim->next_by_mcs;

            }

        }

    }

}

 

Note that if there is no transmit in process when the
write/hard_start_transmit routine is invoked,  transmit is intitiated
from the core logic as if it took place in the interrupt handler.  This
approach differs from the ASE driver, which used to force an interrupt,
and then start the transmission.  Francois Wautier may have fixed that
logic to start the transmission either in the STREAMS put or service
routine.

 

/Driver Unload Logic/

 

The driver unload logic inverts the probe intialization logic.

 

At this point no ports should be in use and in fact every port should
have been put in the 8253x powered down state when each port underwent
its last close (or hangup which can actually complete after a close).

 

The tty driver is cleaned up, some dynamic TTY data structures are
deallocated, the bottom half associated with this driver is removed and
all TTY ports are deregistered.

 

The PLX or AMCC interrupts to the Linux host are disabled on each board,
and all interrupt handlers are freed.

 

Next all board and chip structures are deallocated (including physical
memory to virtual memory mappings associated with PLX9050 or AMCC 5920
and chip registers).  Then all network device structures are
deallocated.  (Note that all lingering sk_buffs were freed during the
close of the network device, which must have completed before the unload
of the driver module.)  And finally the port structures are
deallocated.  In deallocating the port structures, network driver
transmit rings and the receive sk_buff descriptor are deallocated if
they are present.  (They were only allocated if the port had ever been
used for a network device.)  At this point the driver has been
gracefully unloaded.

 

                                /* cleanup module/free up virtual memory */

                                /* space*/

void cleanup_module(void)

{

  SAB_BOARD *boardptr;

  SAB_CHIP *chipptr;

  SAB_PORT *portptr;

  int intr_val;

  extern void sab8253x_cleanup_ttydriver(void);

 

  printk(KERN_ALERT "auraXX50n: unloading AURAXX50 driver.\n");

 

  sab8253x_cleanup_ttydriver(); /* clean up tty */

 

                                /* unallocate and turn off ints */

  for(intr_val = 0; intr_val < NUMINTS; ++intr_val)

    {

      if((AuraBoardESCC2IrqRoot[intr_val] != NULL) ||
(AuraBoardESCC8IrqRoot[intr_val] != NULL))

        {

          for(boardptr = AuraBoardESCC2IrqRoot[intr_val]; boardptr !=
NULL; boardptr = boardptr->next_on_interrupt)

            {

              writel(PLX_INT_OFF, &(boardptr->b_bridge->intr));

            }

          for(boardptr = AuraBoardESCC8IrqRoot[intr_val]; boardptr !=
NULL; boardptr = boardptr->next_on_interrupt)

            {

              writel(PLX_INT_OFF, &(boardptr->b_bridge->intr));

            }

 

          free_irq(intr_val, &AuraBoardESCC2IrqRoot[intr_val]); /* free
up board int

                                                                 * note
that if two boards

                                                                 * share
an int, two int

                                                                 *
handlers were registered

                                                                 *

                                                                 */

        }

    }

 

                                /* disable chips and free board memory*/

  while(AuraBoardRoot)

    {

      boardptr = AuraBoardRoot;

      for(chipptr = boardptr->board_chipbase; chipptr != NULL; chipptr =
chipptr->next_by_board)

        {

          (*chipptr->int_disable)(chipptr); /* make sure no ints can
come int */

        }

      AuraBoardRoot = boardptr->nextboard;

      if(boardptr->virtbaseaddress0)

        {

          DEBUGPRINT((KERN_ALERT

                      "auraXX50n: unmapping virtual address %p.\n",

                      (void*)boardptr->virtbaseaddress0));

          iounmap((void*)boardptr->virtbaseaddress0);

          boardptr->virtbaseaddress0 = 0;

        }

      if(boardptr->virtbaseaddress2)

        {

          DEBUGPRINT((KERN_ALERT

                      "auraXX50n: unmapping virtual address %p.\n",

                      (void*)boardptr->virtbaseaddress2));

          iounmap((void*)boardptr->virtbaseaddress2);

          boardptr->virtbaseaddress2 = 0;

        }

      kfree(boardptr);

    }

 

  while(AuraChipRoot)           /* free chip memory */

    {

      chipptr = AuraChipRoot;

      AuraChipRoot = chipptr->next;

      kfree(chipptr);

    }

 

  while(Sab8253xRoot)           /* free up network stuff */

    {

      SAB_PORT *priv;

      priv = (SAB_PORT *)Sab8253xRoot->priv;

      unregister_netdev(Sab8253xRoot);

#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 4, 0)

      kfree(Sab8253xRoot.name);

#endif

      kfree(Sab8253xRoot);

      Sab8253xRoot = priv->next_dev;

    }

 

  while(AuraPortRoot)           /* free up port memory */

    {

      portptr = AuraPortRoot;

      AuraPortRoot = portptr->next;

      if(portptr->dcontrol2.receive)

        {

          kfree(portptr->dcontrol2.receive);

        }

      if(portptr->dcontrol2.transmit)

        {

          kfree(portptr->dcontrol2.transmit);

        }

      kfree(portptr);

    }

}

 

_Design Summary_

 

The 10 key data structures are shared by all the logic of the system. 
The logic enforces both exclusive and cooperative access to the physical
hardware on the basis of certain rules established by the core logic at
device/port open time. While the driver is mostly self-configuring, the
data structure sharing simplifies the user interface because an ioctl to
one driver functionality (e.g., the TTY functionality) configures the
rest of the driver functionality (e.g., the network and character device
functionality).   In other words, a single serial port acts virtually as
three arbitrated devices:  a TTY device, which may be asynchronous,
synchronous or call out, a network device or a character device.  Yet,
except at the time of initialization, time of driver unload and very
early in interrupt processing all hardware details are concealed and the
driver logic is applied to an abstract, simplified port entity.  Thus,
the user application interfaces to three abstract virtual devices, which
have a single configuration interface (otherwise it might be possible to
have an inconsistent configuration) and not to a complex real single
port in the context of an equally complex adapter card or unit. This
simplification makes it possible to provide a high degree of serial
functionality across the family of Aurora synchronous/asynchronous PCI
hardware through a straightforward uniform application interface.

 

