; I implement a "BootServer" on the AppleTalk network ; The Server Desk Accessory installs me in the system heap and opens me include 'Structs.a' ; fe* constants ServerHeader dc.w $4000 ; dNeedLock dc.w 0 ; delay dc.w 0 ; evt mask dc.w 0 ; menu dc.w ServerOpen-ServerHeader dc.w 0 ; no Prime dc.w 0 ; no Control dc.w 0 ; no Status dc.w ServerClose-ServerHeader dc.b 14, '.NetBootServer' even dc.b 0, 0, $20, 1 ; version ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Inline globals are practical because we are locked in the sysheap ATBOOT_BLK_SIZE equ 256 ; multiple of 64, must match Makefile gNameTableEntry ; Installed using RegisterName dc.l 0 ; qLink dc.l 0 ; addrblock (our DDP skt at offset 7) odd dc.b 4, '0000' ; object (must match const in PRAM) dc.b 10, 'BootServer'; type (always this) dc.b 1, '*' ; zone even gABPBusyFlag ; Lockout flag: only one ABP request can be serviced at a time dc.w 0 gABPReplyParamBlk ; For writeDDP control calls dcb.b $32 gABPWriteDataStruct ; For writeDDP control calls (gathers buffers into one pkt) dcb.b 20 ; doesn't need to be this long at all odd gABPAddress ; Address of the Apple Boot Protocol client: ; set when socket listener gets a packet, used when replying dcb.b 15 ; totally obscure address format dc.b 10 ; ddp type dcb.b 1 ; backup of high byte of dest network ; (MUST copy byte 16 to 7 for every use) gABPImageReply ; reply to rbImageRequest packet dc.b 4 ; rbImageData dc.b 1 ; protocol version (always 1) dc.w 0 ; imageID (we always use 0) dc.w 0 ; increment block number here (offset 4) gABPUserReply ; reply to rbMapUser packet dc.b 2 ; rbUserReply dc.b 1 ; protocol version (always 1) dc.w 1 ; osID (always 1) dc.l $FFFFFFFF ; copy client's timestamp here (offset 4) dc.w ATBOOT_BLK_SIZE ; block size dc.w 0 ; imageID (we always use 0) dc.w 0 ; error code dc.l (BlobEnd-Blob)/ATBOOT_BLK_SIZE ; block count ; arbitrary <=568b "user record" begins at offset 18 here gEBPSocket dc.b $55 ; EBP socket number even gEBPPB dcb.b $34 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ServerOpen movem.l A0/A1,-(SP) ; Try *once*. AT might come up later but not because of us. bsr TryOpenMPP bne.s .couldNotOpenAppleTalk bsr Listen .couldNotOpenAppleTalk ; Be ready for the LAP Manager to open/close .MPP bsr RegisterForTransitions movem.l (SP)+,A0/A1 clr.w $10(A0) ; return noErr rts ServerClose movem.l A0/A1,-(SP) bsr StopListening movem.l (SP)+,A0/A1 clr.w D0 ; return noErr rts ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; TryOpenMPP ; Checking whether the "B" printer port is configured for AppleTalk is an ; application responsibility on 64K ROM machines (as documented in IM), ; but 128K ROM and later machines move this check to AppleTalk itself, ; for example to allow other AppleTalk interfaces. tst.w $28E ; ROM85 positive? bgt.s .returnYes ; it is always safe to try on 128K machines move.b $1FB,D0 ; SPConfig & 0xF == useFree (0) or useATalk (1)? and.b #$F,D0 sub.b #1,D0 ble.s .returnYes move.b $291,D0 ; PortBUse negative or useATalk (1)? blt.s .returnYes cmp.b #1,D0 beq.s .returnYes .returnNo moveq #1,D0 rts .returnYes sub #$32,SP move.l SP,A0 lea .MPP,A1 move.l A1,$12(A0) ; IOFileName = .MPP clr.b $1B(A0) ; IOPermssn dc.w $A000 ; _Open tst.w $10(A0) ; IOResult: check it bne.s .failOpen lea .ATP,A1 move.l A1,$12(A0) ; IOFileName = .ATP clr.b $1B(A0) ; IOPermssn dc.w $A000 ; _Open tst.w $10(A0) ; IOResult: check it .failOpen add #$32,SP rts .MPP dc.b 4, '.MPP', 0 .ATP dc.b 4, '.ATP', 0 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; RegisterForTransitions rts ; register for the LAP Manager queue if possible ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Listen sub #$32,SP ; OPEN DDP SOCKET (Apple Boot Protocol) move.w #-10,$18(SP) ; ioRefNum = MPP move.w #248,$1A(SP) ; csCode = openSkt clr.b $1C(SP) ; socket = auto-assign pea SocketListener move.l (SP)+,$1E(SP) ; listener move.l SP,A0 dc.w $A004 ; _Control lea gNameTableEntry+7,A0 ; lowest byte of AddrBlock move.b $1C(SP),(A0) ; save socket number there add #$32,SP lea gEBPPB,A0 ; OPEN ATP SOCKET (Elliot Boot Protocol) move.w #-11,$18(A0) ; ioRefNum = ATP move.w #254,$1A(A0) ; csCode = openATPSkt clr.b $1C(A0) ; atpSocket = any clr.l $1E(A0) ; addrBlock = any dc.w $A004 ; _Control lea gEBPSocket,A1 move.b $1C(A0),(A1) ; Some nasty debug code to work on... move.b $1C(A0),D7 ; get atpSocket move.w #$34-1,D0 .clrlp clr.b (A0,D0) dbra D0,.clrlp move.w #-11,$18(A0) ; ioRefNum = ATP move.b D7,$1C(A0) ; atpsocket ; NB: refnum, atpSocket already set move.w #253,$1A(A0) ; csCode = getRequest pea EBPHandler move.l (SP)+,$C(A0) ; ioCompletion clr.w $22(A0) ; reqLength = 0 clr.l $24(A0) ; reqPointer = none move.w #20,$22(A0) ; reqLength = 20 move.l $2AE,$24(A0) ; reqPointer = ROMBase dc.w $A404 ; _Control ,async sub #$32,SP ; ADVERTISE NBP SERVICE move.w #-10,$18(SP) ; ioRefNum = MPP move.w #253,$1A(SP) ; csCode = registerName move.b #7,$1C(SP) ; interval = 7*8 ticks = 1sec move.b #5,$1D(SP) ; count = 5 pea gNameTableEntry move.l (SP)+,$1E(SP) ; entityPtr = our NTE move.b #1,$22(SP) ; verifyFlag = do move.l SP,A0 dc.w $A004 ; _Control add #$32,SP rts StopListening sub #$32,SP ; REMOVE NBP SERVICE move.w #-10,$18(SP) ; ioRefNum = MPP move.w #252,$1A(SP) ; csCode = removeName pea gNameTableEntry move.l (SP)+,$1E(SP) ; entityPtr = our NTE move.l SP,A0 dc.w $A004 ; _Control add #$32,SP sub #$32,SP ; CLOSE DDP SOCKET move.w #-10,$18(SP) ; ioRefNum = MPP move.w 257,$1A(SP) ; csCode = closeSkt move.b gNameTableEntry+7,$1C(SP); socket = auto-assign move.l SP,A0 dc.w $A004 ; _Control add #$32,SP rts ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; SocketListener ; Socket listener registers documented in IM lea gABPBusyFlag,A5 bset.b #7,(A5) bne .TrashPacketStayingBusy lea gABPAddress,A5 ; RETURN ADDRESS SAVE CODE move.b 2(A2),11(A5) ; both types node (ALAP header starts A2+1) move.b -2(A3),13(A5) ; both types socket (DDP header ends A3) moveq #0,D3 ; short packet network = 0 cmp.b #1,3(A2) beq.s .short move.w 10(A2),D3 ; long packet network .short move.w D3,7(A5) ; network number as usual asr.w #8,D3 move.b D3,16(A5) ; backup high-order byte of network number ; otherwise would go to offset 7 ; Always get the command, version and two more bytes moveq #8,D3 jsr 2(A4) ; ReadRest command, version and 6 more bytes ;bne.s TrashPacket subq #6,A3 cmp.b #1,-(A3) ; version must be 1 bne.s .TrashPacket move.b -(A3),D2 subq.b #1,D2 beq.s .rbMapUser ; 1 = user record request subq.b #2,D2 ; 3 = imageRequest beq.s .rbImageRequest .TrashPacket lea gABPBusyFlag,A5 clr.b (A5) .TrashPacketStayingBusy moveq #0,D3 jmp 2(A4) ; ReadRest .rbMapUser ; "user record request" ; +0 (1b) command (already checked) ; +1 (1b) version (already checked) ; +2 (2b) osID (already slurped and ignored) ; +4 (4b) userData (timestamp to be returned in reply) ; +8 (32b) userName lea gABPUserReply+4,A0 move.l 4(A3),(A0) ; copy "userData" to the reply lea gABPWriteDataStruct+14,A1 clr.w -(A1) lea gABPUserReply,A0 move.l A0,-(A1) move.w #18+1,-(A1) lea gABPAddress,A0 move.l A0,-(A1) clr.w -(A1) move.b 16(A0),7(A0) ; repair the address struct lea gABPReplyParamBlk,A0 move.l A1,$1E(A0) ; wdsPointer lea AllDoneCompletionRoutine,A1 move.l A1,$C(A0) ; ioCompletion move.w #-10,$18(A0) ; ioRefNum = .MPP move.w #246,$1A(A0) ; csCode = writeDDP move.b gNameTableEntry+7,$1C(A0); our socket move.b #1,$1D(A0) ; set checksumFlag dc.w $A404 ; _Control ,async rts .rbImageRequest ; reply with rbImageReply ; +0 (1b) command (already checked) ; +1 (1b) version (already checked) ; +2 (2b) imageID (already slurped and ignored) ; +4 (1b) section, ie which 2MB chunk ; +5 (1b) flags ; +6 (2b) replyDelay ; +8 (<=512b) bitmap ; IGNORE the janky bitmap because the client implementation is buggy. ; It is reasonable just to send every packet instead (our image is tiny). lea gABPImageReply+4,A0 ; count of packets to send move.w #(BlobEnd-Blob)/ATBOOT_BLK_SIZE,(A0) ImageReplyCompletionRoutine ; fallthru from skt listener OR writeDDP completion lea gABPImageReply+4,A0 move.w (A0),D0 subq.w #1,D0 move.w D0,(A0) blt.s AllDoneCompletionRoutine move.w D0,D1 ; D0 = idx, D1 = offset mulu.w #ATBOOT_BLK_SIZE,D1 lea gABPWriteDataStruct+20,A1 clr.w -(A1) lea Blob,A0 lea (A0,D1),A0 move.l A0,-(A1) move.w #ATBOOT_BLK_SIZE,-(A1) lea gABPImageReply,A0 move.w D0,4(A0) move.l A0,-(A1) move.w #6,-(A1) lea gABPAddress,A0 move.l A0,-(A1) clr.w -(A1) move.b 16(A0),7(A0) ; repair the address struct lea gABPReplyParamBlk,A0 move.l A1,$1E(A0) ; wdsPointer lea ImageReplyCompletionRoutine,A1 move.l A1,$C(A0) ; ioCompletion move.w #-10,$18(A0) ; ioRefNum = .MPP move.w #246,$1A(A0) ; csCode = writeDDP move.b gNameTableEntry+7,$1C(A0); our socket move.b #1,$1D(A0) ; set checksumFlag dc.w $A404 ; _Control ,async rts AllDoneCompletionRoutine lea gABPBusyFlag,A0 clr.b (A0) rts ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; EBPHandler dc.w $A9FF ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Blob incbin 'Client.bin.summed' ; built from Client.a BlobEnd