PRUN: A Parallel RUN for NadaNet

Michael J. Mahon - November 29, 2004

Introduction

PRUN is an Applesoft program that runs on the master machine. Its purpose is to boot any AppleCrate machines needing network booting, start a Message Server if needed, and then run a user-selected Applesoft BASIC program on all remaining AppleCrate machines. It could be easily altered to run different programs on various machines or to run BASIC programs on other serving machines on the network.

The Program

The Applesoft BASIC program, PRUN, is presented here, with liberal commentary:

 100  REM  Run Parallel BASIC program
 110  REM       MJM - 08/16/04
 115  REM     Revised  11/08/04
 120 :

After finding out whether a Message server should be started, we initialize NadaNet. This initialization presumes that NadaNet, with the AmperNada extensions, has already been installed below the OS, HIMEM has been adjusted, and an initializing call has been made. The following GOSUB merely verifies that NadaNet is installed and sets up some pointer variables for application use. Next, we take a "census" of all machines from 2 up to MX to determine which ones are serving and, if so, what type of machine they are. If the Message Server (ID=2) has been started, then that is noted. The disk prefix for program loads is defined for convenience.

 300  INPUT "Is Message Server needed? ";A$
 310 NEEDMS = A$ = "Y" OR A$ = "y"
 320 :
 500  GOSUB 60000: REM  Initialize
 510  GOSUB 61000: REM  Take census
 520 MSRV =  PEEK (ITBL + 2) = 3: REM  Save pre-boot Msg Server status
 530 PFX$ = "/ap/merlin/work/nadanet/"
 540 :

The next lines set up the boot code and pointers to it in preparation for booting any network-bootable machines needing boot service, then call &SERVE(100) to enter the master's service loop with a count such that it will stay in the service loop for a couple of seconds (since, if nothing is requested, each RCVPKT call times out in about 20ms, and this specifies 100 iterations).

The purpose of this &SERVE call is to process the BOOT requests of any unbooted AppleCrate slave machines. When we return from SERVE, all slave machines requiring boot will have been booted and will be running their service loops.

 550  REM  Boot unbooted slaves
 560  PRINT  CHR$ (4)"bload "PFX$"nada.crate,A"BUF
 570 PSRT =  PEEK (BUF + 2) * 256: REM   Prog start
 580 PLNG =  PEEK (48840) + 256 *  PEEK (48841): REM  Prog length
 590  & BOOTCODE(PSRT,PLNG,BUF)
 600  & SERVE(100): REM  For BOOT
 610  PRINT "Boot of slaves completed."
 620 :

When booting is completed, we retake a census to determine how many slaves are present. If machine #2 is required as the message server, we require that there be at least two slaves, so that there will be a machine to do work.

 630  GOSUB 61000: REM  Re-take census
 640  IF MSRV OR  NOT NEEDMS GOTO 1000: REM  Already running or not needed
 650 :
 660  REM  Load Message Server
 670  IF  NOT  PEEK (ITBL + 2) THEN  PRINT "No Msg Server (2)": STOP 
 680  PRINT  CHR$ (4)"bload "PFX$"nada.mserve,A"BUF
 690 PSRT =  PEEK (BUF + 2) * 256: REM  Prog start
 700 PLNG =  PEEK (48840) + 256 *  PEEK (48841): REM  Prog length
 710  REM  Poke NADA.MSERVE code
 720  &  POKE (2,PSRT,PLNG,BUF)
 730  &  CALL (): REM  Run Message Server
 740  PRINT "Message Server now running on 2"
 750 MSRV = 1: POKE ITBL + 2,3: REM  Mark Msg Server
 760 :

Now we load the "worker" Applesoft program into each slave machine. This is a multi-step process, since Applesoft must first be coldstarted on each slave machine while retaining control, then the program is POKEd into slave memory, then Applesoft is RUN. (A coldstart would not be necessary on a machine running a normal ROM, since BASIC is coldstarted after RESET.)

To begin, we BLOAD the program into a safe area of the master's memory and save its length, then iterate over each of the slave machines, &POKEing it into their memories. Note that while PRUN runs the same program in each slave machine, it would be quite easy to load each slave machine with an arbitrary program for more complex workloads.

 1000  REM  Load Applesoft program
 1010  PRINT "Run what program? ";: INPUT "";P$
 1020  IF P$ = "" THEN  PRINT  CHR$ (4)"catalog": GOTO 1010
 1030 BUF = 3 * 256: REM  $300 utility buffer
 1040 PBUF = 2 * 4096: REM  Program buffer $2000
 1050  PRINT  CHR$ (4)"bload "P$",a"PBUF",TBAS"
 1060 PLNG =  PEEK (48840) + 256 *  PEEK (48841): REM  Prog Length
 1070 PSTR = (8 * 256 + 1): REM  Prog start = $0801
 1080 PGND = PSTR + PLNG: REM  Prog end = Start + Length
 1090 :
 1100  FOR D = 2 TO MX
 1110  IF  PEEK (ITBL + D) <  > 2 GOTO 1370: REM  Only AppleCrate machines
 1120 :

To coldstart Applesoft and retain control, we patch CSW to print to the local video buffer and KSW to call the SERVER loop. The effect is to display (on a local monitor) the Applesoft carriage returns and prompts, but to return control to the SERVER loop as soon as Applesoft requests keyboard input.

 1130  POKE BUF,15 * 16: POKE BUF + 1,15 * 16 + 13: REM  COUT1 ($FDF0)
 1140  POKE BUF + 2,205: POKE BUF + 3,3: REM  Server loop $3CD
 1150 CSW = 3 * 16 + 6: REM  $0036
 1160  &  POKE (D,CSW,4,BUF): REM  Set CSW = COUT1, KSW = Server loop
 1170 :
 1180  &  CALL (D,14 * 4096): REM  Cold start Applesoft ($E000)
 1190 :
 1200  POKE BUF + 1,184: POKE BUF,0: REM  $B800
 1210 HMEM = 7 * 16 + 3: REM  $0073
 1220  &  POKE (D,HMEM,2,BUF): REM  HIMEM: $B800
 1230 :
 1240  &  POKE (D,PSTR,PLNG,PBUF): REM  POKE the BASIC program
 1250 :

Then we POKE pointers dependent on the length of the Applesoft program and simulate a "RUN" command:

 1260  POKE BUF + 1,PGND / 256: POKE BUF,PGND - 256 *  PEEK (BUF + 1)
 1270 VARTAB = 6 * 16 + 9: REM  $0069
 1280  &  POKE (D,VARTAB,2,BUF): REM  Set VARTAB = PGND
 1290 :
 1300 PRGND = 10 * 16 + 15: REM  $00AF
 1310  &  POKE (D,PRGND,2,BUF): REM  Set PRGEND = PGND
 1320 :
 1330 RNPROG = 13 * 4096 + 5 * 256 + 6 * 16 + 6: REM  $D566
 1340  &  CALL (D,RNPROG): REM  Start slave program
 1350 :
 1360  PRINT "BASIC prog '"P$"' running on "D
 1370  NEXT D
 1380  END 
 1390 :

This is the standard "suffix" for NadaNet applications, added by EXECing the text file NADADEFS. The subroutine at 60000 sets up a few named pointers. The subroutine at 61000 performs a "census" of serving machines on the net and saves the results in a table at ITBL. (Note that because the census loop is probing machines that may not be serving (or even exist), it temporarily sets the &TIMEOUT value down to 3 * 60ms. so that unresponsive machines do not stall the &PEEK for very long. The default timeout is restored when the census is completed.)

 60000  REM  NadaNet Definitions for Applesoft
 60010  REM         MJM - 11/08/04
 60020 :
 60030 NP = 3 * 256 + 12 * 16 + 15: REM  NADANET page ($3CF)
 60040  IF  PEEK (NP - 2) <  > 76 THEN  PRINT "NadaNet not loaded.": STOP 
 60050  IF  PEEK (NP) <  > 145 THEN  PRINT "Not ProDOS version of NadaNet.": STOP 
 60060 :
 60070 BUF = 4 * 4096: REM  $4000
 60080 MX = 10: REM  Max machine ID
 60090 BSLF = NP - 3: REM  Boot ID in $3CC
 60100  & IDTBL(ITBL): REM  ID table
 60120  RETURN 
 60130 :
 61000  REM  Take census of serving machines
 61010  & TIMEOUT(3)
 61020  FOR D = 2 TO MX: REM  PEEK to verify service
 61030  &  PEEK #(D,NP,4,BUF): REM  PEEK config bytes
 61040  IF  PEEK (1) THEN TY$ = "not":TY = 0: GOTO 61100
 61050 TY$ = "(unknown)":TY = 16
 61060  IF  PEEK (BUF) = 184 THEN TY$ = "(Crate)":TY = 2
 61070  IF  PEEK (BUF) = 8 THEN TY$ = "(Mserve)":TY = 3
 61080  IF  PEEK (BUF) = 145 THEN TY$ = "(ProDOS)":TY = 4
 61090  IF  PEEK (BUF) = 141 THEN TY$ = "(DOS)":TY = 3
 61100  POKE ITBL + D,TY
 61110  PRINT "Machine "D" "TY$" serving."
 61120  NEXT D
 61130  & TIMEOUT(): REM  Reset retrys
 61140  PRINT "Census completed."
 61150  RETURN