[comp.protocols.appletalk] networking comments

Aaron.Wohl@GNOME.CS.CMU.EDU.UUCP (04/15/87)

Some questions and answers on network programming on a Macintosh:

How does a Desk Accessory (DA) use AppleTalk for asyncronous calls? For
example, looking in "Inside Macintosh" (IM) at DDPWrite (Vol.II, pg. 283) and
asyncronous calls (Vol.II, pg. 273), you see that when an async call finishes
a network event is generated.  However, there isn't any way for a DA
to get network events, and you can't just let the host program get
them, because there isn't any way for the host program to tell that the event
is ment for a DA.  The answer is that the formalisims detailed in the
AppleTalk assembly language section are different from those [NotInROM]
described in the Pascal section.

Pascal for DDPWrite takes the abRechandle, locks it and does a asyncronous
Control call on the .MPP (AppleTalk) driver.  The done routine of this
request posts the network event. To use DDPWrite from a DA, you must make
up the parm block and do the control call yourself. To do this you must either
have your own done routine set some status information, or simply poll the
parameter block in your DA periodic routine.

In order to allow maximum flexibilty to user programs and to allow
vertical tasks to use a network driver, the driver should be able to be
loaded into 1) the system heap, 2) the application heap, and  3) the above bufptr.

IM gives you information concerning how a device tells the device manager that 
it has completed a request (jIODONE), but there is no mention of what request to
complete. My network driver will allow multiple outstanding read requests, but
how do I say which request is completing?  How does AppleTalk handle this?

Device driver requests must complete in the order of arrival.  Read requests 
are not handled thru the device manager.  The AppleTalk device has dispatch
tables internally (for ALAP and DDP).  An entry is placed in these by AttachPH
and OpenSkt.  When a packet begins to arrive, execution is depatched
to the users entry in the appropriate table by the hardware interrupt
handler that gets control on a SCC B channel interrupt.  So for an individual 
device manager call there is no driver request.

But what if my driver has multiple speed links (e.g. AppleTalk and
ethernet), such that if I queue a write for a low speed link and
then two packets to write for a highspeed link?  The control
interface calls for AppleTalk already exist and haven't changed
to handle this on a Mac II (which can have multiple interfaces).  Also
the device manager still does not handle completions out of order.
So, I don't know about his one. Anyone know a hack/undocumented feature
to have requests complete out of order?

The dispatch mechanisim is fine for handling AppleTalk which has
small headers. What about IP/TCP and others with large headers which
are inclined to use a queue?  Dispatching as AppleTalk does
automatically separates the application heap read buffers from
the system heap/bufptr buffers. A problem only happens if there is a alap listener
for a vertical task living above bufptr and get gets a packet and inputs
the packet somewhere in its space. If the application should quit just 
afterwoods there is no danger of it going away. It isn't known who an IP 
family packet is for until after alot of data has come in; too much to want
to copy to the right place.

Schemes for allowing vertical interrupt routines to use IP:
	
    1)Allocate static space (bufptr or sysheap) for all packet buffers.
	  This should be avoided as not flexible enough as it would be
	  necessary to reboot to change it.
	
    2)A mixed strategy in which each IP device user supplys enough buffer
	  space for itself.  But since packets can't be separated,
	  user packets may be in a DA's buffers, the DA packets in
	  vertical routines buffers, and the vertical routines packets
	  in user buffers.  The only potential problem is vertical packets
	  in user/DA buffers. As with the others, a program won't ask for
	  a  packet that it knows has arrived after the program is gone.
	  If we allow an IP port to say that a packet has arrived, then up
      until the time the packet is released it may not be available when asked
      for.  But what about Switcher? Yes, user programs would have to allow
      for a packet going away also.
	  Another method: Allowing buffers to go away is sort of a cute hack, but
	  I think it would tend to force buffer copying. If at a higher level
	  you are emulating a user protocol that doesn't allow for packets
	  going away, you would have to make a copy of a bufffer if the user
      routines asked if there was a packet to be sure to have it.
	  However, since I don't know of any high level network interfaces that 
      let you get a packet without releasing it, you might tell the high level 
      system when there is a packet. If the packet buffer needs to be destroyed 
      before it is asked for, then make up bad packet, bad checksum, bad type
      etc, since everyone allows packets to be garbled.
	
    3)Allow each IP port to use a different DDP port. The new
	  AppleTalk drivers allow many ddp ports. This would also allow different IP 
      implementations to exist in the same machine. Currently everyone uses a 
      fixed DDP type and DDP socket. What would have to be 
      changed to allow this?
	
    4)Allocate fixed space for all the packet headers (at the IP level)
	  in the system heap or bufptr. However, since by the time the
	  header is in, the port is known, have separate buffers available
	  for packets of different life times.  Unfortunetly for IP the
	  destination port doesn't tend to be available until after the header
	  options, which are long and can be of variable length.

Buffer fragmentation:

Packets tend to come in different sizes. The packets sizes depend 
on the protocol. For example, for Telnet there are little packets
for typein and large packets with one system terminal buffer worth
of text.  Systems that use a heap for packet storage can spend
a lot of time cutting up and reassembling packets into the same
size units. On c.cs.cmu.edu for example, the the BSP (byte stream
protocol (old ethernet)) code spends more than half its time
making packets of (statistically speaking) two sizes.
Therefore in order to reduce wasted space and increase efficiency,
network drivers should consider accepting/allocating a free pool of
packets cut up into fixed sizes.

This is especially the case where the size of an incoming packet can be
determined ahead of time and copying can be avoided. Even if the size is 
not known ahead of time, packets tend to come in maximum size and small size.
A minimal size data packet sitting in a maximum size buffer might be worth 
copying to a short buffer.

If I assert a NBP name from a program and the program exits the name
goes away. If I assert a name from a vertical task it stays around
across launches. Why/how?  When NBP gets a goodbye kiss it scans all
it's name blocks and removes the names of blocks in the application heap.

What does the AppleBus variables pointer point to?  This varies
from version to version of the driver. As with the Mac+ these contain
the protocol dispatches, temporaries for interrupt routines, etc (see
attached documentation).

What if I open AppleTalk (Mac512 not E or +) with heap scrabble on and the system
crashes?  The .MPP driver in the system file open routine does a set
handle size (to free the space used by the AppleTalk initilization
routines) from the open routine.  This places the block where
execution returns to after the SetHandleSize in the free space.
So, heap scrabble is the problem, since SetHandleSize is documented
to never compact if the block is geting smaller.  But still the
driver is silly to depend on executing code in the memory managers
free list.

If I don't call system task, NBP still defends it's name. It does this
through a combination of getting dispatches from the DDP protocol table as 
packets for it arrive, and having a vertical retrace task.

The AppleTalk calls are listed in the list of calls that cause garbage
collection. How can they be done from a vertical routine?  The Pascal
glue cannot be called as that interface uses handles.  Most of the
control calls to the AppleTalk driver can be made from vertical routines.
Attach/detach of lap and ddp are known to work fine. Versions of AppleTalk
before the shared file system did not allow more than one NBP call to be
active at a time (there was a global with the current request number).

Why are all the Appletalk calls Control calls on the driver, and not
read/write/status? This is because read and write calls are for streams 
of data, and also it makes for one common entry point.

You don't know assembler, and need to make a vertical routine.
How do you do the Control() call to .MPP from .C or Pascal?  
Especially as your test programs just seem to crash. 
Look at IM Vol. II, pg. 283. csParam is defined as an 
ARRAY [0..10] OF INTEGER. Now look at IM Vol.lI, pg. 323 under
LookupName. It talks of parameter block offsets upto 42.
What is the correspondance? AppleTalk misuses the cntrlParam parameter
of control calls. It is supposed to be 22 (sizeof(ARRAY [0..10] OF INTEGER))
bytes long. The Control() call (IM Vol.ll, pg. 279) allocates a 
ParamBlockRec for you, copies the csCode you pass to csCode, and copies 
22 bytes from the address you specify from csParamPtr argument. For
AppleTalk this is not corrent. You need to be able to look at the results of
the call and since the data pointed to by csParamPtr is copied
to some internal buffer you cannot find it.

Therefore, you need to make up your own ParamBlockRec. csCode of the 
ParamBlockRec is the csCode at offset 26 of the ParamBlockRec talked about
in IM Vol.II, pg. 23. For example, after the csCode, are the rest of the
AppleTalk parameters are at the offset that the csParam should be.


Here is how the SCC hardware and interrupt routines receive AppleTalk
packets.  See figure 4 in "Inside Macintosh", pages III-26, to see how 
the signals from AppleTalk get to SCC (they come in on RXD- and RXD+ from 
the little box hanging out back).  

The SCC (Zilog 8530) serial communications controller receives the bits and 
assembles them into bytes. If you are going to be programming the SCC, you
will need to get a copy of the Z8530 section of a Zilog data book, which will 
explain (to some extent) what the zillions of registers are for.

Macintosh to SCC I/O Interface:
  
  The SCC has two registers presented to the bus for each channel (A and B).
  Each register can be read and written to.  For writing to the SCC
  use the address in system global SCCWr + (aData(6), bData(4), aCtl(2),
  bCtl(0)) depending on which register you want.  For reading to the SCC, use
  SCCRd, which is accessed the same way. 
  
  To read or write to the SCC, you generally do a write to the control port
  to tell it which internal register you want. Then you do a read or a write 
  to the control port again to get/send the data you want  Be sure to have at 
  least one instruction delay between accesses (one NOP instruction is fine) 
  to meet the SCC timing requirments.

Macintosh to SCC Interrupts:

  There are two channels, SCC B and SCC A, which are basically independent in 
  the operation and seperation of their registers and wiring.  The only 
  exceptions are RR2 and RR3 (read registers 2 and 3). RR3 is the interrupt
  pending status register and can only be read from channel B.  RR2 is
  the interrupt vector register (SCC interrupts do not vector according to 
  this hardware vector, they vector according to 68000 interrupt level only - 
  more on this later).  If you read RR2 from the B side it always
  returns the interrupt vector that the SCC was initilized with (which should
  always be set to zero or the ROM interrupt handler will be confused). 
  However, when you read from side A the interrupt vector is modified by the
  8 different possible pending interrupts (according to the highest SCC
  priority).


  When the SCC requests an interrupt from the 68K, the 68K pushes the SR 
  (status /CCR register) and PC onto the stack.  Since the SCC is on level
  2 (see "Inside Macintosh", pages III-197 fig 4) the 68K loads the contents 
  of the long word at $68 into the program counter.  On my Macintosh Plus, 
  $68 points to $401A84, which does the following:
  	save D0-D3/A0-A3;	/* preserve the users registers */
  	A0 := SCCRd;		/* setup SCC read/write addresses */
  	A1 := SCCWr;
  	D0 := *SCCRd & 0xe0;	/* see who is interrupting within the SCC */
	if(D0>=8) {		/* channel A? */
	  A0 +=2;		/* yes, change SCC addresses to point to */
	  A1 +=2;		/* SCC A registers instead of B registers */
	  }			/* else A0/A1 point to B */
	A2 := Lvl2DT[D0];	/* select service routine */
	JSR (A2)		/* call the service routine */
	restore D0-D3/A0-A3;	/* put the users register back */
	RTE			/* return from interrupt */
	
 Put your own transmit buffer empty, receive special condition, and
 receive character interrupt handlers into Lvl2DT. Do *NOT* change
 the external status entry in Lvl2DT because the mouse vertical and
 horizotal position sensors are connected to a general purpose input to the
 SCC.  If the mouse moves vertically you get SCC B external interrupts.  So
 do not change the external interrupt in Lvl2DT, as it checks to see if the
 interrupt is due to the mouse or communications, and vectors thru ExtStsDT.

 Again on my Macintosh Plus, Lvl2DT+4 (SCC B external) points to this routine 
 in  the ROM:
 
 channel_B_external_interrupt:
   D0 :=SCCRd+bCtl;	/* get SCC status */
   A2 :=ExtStsDt;	/* point to the B side of ExtStsDt */
   A3 := &SCCBSts;	/* point to SCC status byte in low memory */
   goto common_external_interrupt
   
 channel_A_external_interrupt:
   D0 :=SCCRd+aCtl;	/* get SCC status */
   A2 :=ExtStsDt+8;	/* point to the A side of ExtStsDt */
   A3 := &SCCASts;	/* point to SCC status byte in low memory */

 common_external_interrupt:
   /* here we set D1 to be those status bits in RR0 (abort, tx undeflow,
      CTS (clear to send), sync/hunt, DCD (mouse), Tx empty, zero in baud
      generator, Rx available, anded with those channels that are enabled.
 
      In other words, D1 is those RR0 conditions that are different that
      we are interested in.
   */
   D1 := ( *SCCxSts Xor RR0) && RR15;
   /* save current state so next interrupt can know what is different */
   *SCCxSts := D0;
   /* is the *ONLY* new status change due to the mouse? */
   if( D1 = 8)		/* check DCD SCC input */
     A2 +=2;		/* yes,just the mouse, select mouse interrupt routine */
   /* *************** very important ************** */
   /* tell the SCC that the current interrupt had been serviced, (end IUS
      interrupt under service.  NOTE: for all the other routines you
      might write interrupt handlers for (data interrupt, trasmit, special)
      they must do a IUS to the SCC.  For the external interrupt, the
      user routine does not, as it is done right here.
   */
   WW0 := $10;	/* end interrupt under service */
   /* go to user external service routine.
      registers:
        D0 - SCC status
        D1 - SCC status that is different since the last interrupt
        D2 - trash
        D3 - trash
        A0 - SCC read address
        A1 - SCC write address
        A2 - trash
        A3 - trash
      You must preserve all the other regs.  D0-D3/A0-A3 may be trashed.
      N.B.  SCC communication conditions have priority over mouse, so if
      there is a communications status change at the same time the mouse moves
      then this JMP (A2) only calls the communication routine.  Some 
      communication routines have been seen to look at D1 and notice that 
      there is a mouse interrupt also and jump to mouse routine when done 
      servicing the communication interrupt.  This is fine, but in that 
      case preserve A0,A1, D0,D1 as the mouse routine needs them.
    */
   JMP (A2)
   
*****************************************************************************