Powrót do spisu treści

Rozdział 6

OBSŁUGA NOWYCH URZĄDZEŃ

    System operacyjny Atari był projektowany tak, aby umożliwić maksymalną elastyczność. Oczywiste było, że powstaną w przyszłości nowe urządzenia. Z tego powodu tabela procedur obsługi urządzeń zewnętrznych została umieszczona w pamięci RAM i przewidziano możliwość jej rozszerzania do jedenastu wpisów. Projektanci Atari założyli ponadto, że powstaną w przyszłości urządzenia peryferyjne korzystające z szyny równoległej komputera i nazwali je "nowymi urządzeniami" (new device). Do ich obsługi przewidziano liczne procedury systemu operacyjnego oraz wiele rejestrów w obszarze zmiennych systemowych. Jedynym - na razie - takim urządzeniem jest twardy dysk Supra 20 MB.

    Dla procedur nowych urządzeń przewidziana jest struktura tzw. listy liniowej. Polega ona na tym, że każdy element listy wskazuje na element następny. Dołączenie do środka listy nowego elementu (np. między elementy n i n+1) wymaga skopiowania wskaźnika z elementu n (wskazującego na n+1) do nowego elementu, a następnie ustawienia wskaźnika elementu n na nowy element.

    Trzeba jeszcze powiedzieć, do czego służy ta struktura. Otóż zakłada się, że procedury obsługi nowych urządzeń będą składały się z elementów zorganizowanych w listę liniową. Kolejne elementy mogą zostać umieszczone w dowolnym miejscu pamięci, a ich następstwo jest ustalane właśnie przez listę liniową.

    Chciałbym jeszcze przeprosić za ewentualne nieścisłości, które mogą znaleźć się w tym rozdziale. Niestety trudno jest pisać o współpracy komputera z urządzeniami, które są nieznane lub - co gorsza - jeszcze nie istnieją.

6.1. Instalowanie nowego urządzenia

    Podczas startu systemu (RESET) wywoływana jest procedura LINKSOM, której zadaniem jest ustawienie elementów listy liniowej. Jej przebieg jest zupełnie odmienny przy starcie zimnym i gorącym. Dlatego najpierw sprawdzana jest zawartość wskaźnika WARMST i na tej podstawie wybierany jest odpowiedni wariant procedury.
            0100 ;LINK SOMething
            0110 ;
            0120 CHCKFF = $CB56
            0130 CHLINK = $03FB
            0140 CKEY =  $03E9
            0150 DCBINI = $E7BE
            0160 DVSTAT = $02EA
            0170 DVTMOT = $02EC
            0180 INITLD = $E7DE
            0190 LINKCD = $E89E
            0200 LINKWM = $E894
            0210 MEMLO = $02E7
            0220 MEMTOP = $02E5
            0230 REVNUM = $02ED
            0240 TEMP1 = $0312
            0250 TEMP2 = $0313
            0260 WARMST = $08
            0270 ZCHAIN = $4A
            0280 ;
            0290     *=  $E739
            0300 ;
            0310     LDA WARMST
            0320     BEQ CDS
            0330     LDA # <CKEY
            0340     STA ZCHAIN
            0350     LDA # >CKEY
            0360     STA ZCHAIN+1
            0370 NEXT LDY #$12
            0380     CLC
            0390     LDA (ZCHAIN),Y
            0400     TAX
            0410     INY
            0420     ADC (ZCHAIN),Y
            0430     BEQ EXIT
            0440     LDA (ZCHAIN),Y
            0450     STA ZCHAIN+1
            0460     STX ZCHAIN
            0470     JSR CHCKFF
            0480     BNE EXIT
            0490     JSR LINKWM
            0500     BCS EXIT
            0510     BCC NEXT
            0520 CDS LDA #$00
            0530     STA CHLINK
            0540     STA CHLINK+1
            0550     LDA #$4F
            0560     BNE GDV
            0570 END LDA #$00
            0580     TAY
            0590     JSR DCBINI
            0600     BPL MEM
            0610 EXIT RTS
            0620 MEM CLC
            0630     LDA MEMLO
            0640     ADC DVSTAT
            0650     STA TEMP1
            0660     LDA MEMLO+1
            0670     ADC DVSTAT+1
            0680     STA TEMP2
            0690     SEC
            0700     LDA MEMTOP
            0710     SBC TEMP1
            0720     LDA MEMTOP+1
            0730     SBC TEMP2
            0740     BCS ADR
            0750 LOOP LDA #$4E
            0760 GDV TAY
            0770     JSR DCBINI
            0780     JMP END
            0790 ADR LDA DVTMOT
            0800     LDX MEMLO
            0810     STX DVTMOT
            0820     LDX MEMLO+1
            0830     STX REVNUM
            0840     JSR INITLD
            0850     BMI LOOP
            0860     SEC
            0870     JSR LINKCD
            0880     BCS LOOP
            0890     BCC END
    Przy zimnym starcie systemu zerowany jest rejestr CHAIN i, po umieszczeniu w akumulatorze i rejestrze Y wartości $4F, wywoływana jest procedura DCBINI. Ma ona stwierdzić obecność urządzenia i jego gotowość do pracy. Następnie przez kolejne wywołania DCBINI odczytywane są elementy procedury urządzenia. Czynność ta jest powtarzana, dopóki kolejne wywołanie DCBINI z wartością $00 w akumulatorze i rejestrze Y nie da wyniku negatywnego.

    Sam proces odczytu jest skomplikowany. W wyniku wywołania DCBINI z wartościami $00 w rejestrze DVSTAT znajduje się wielkość elementu (liczba bajtów). Jest ona dodawana do dolnej granicy wolnej pamięci MEMLO i porównywana z górną granicą MEMTOP. Jeśli element nie mieści się w pamięci, to urządzenie jest o tym informowane przez wywołanie DCBINI z wartością $4E, a następnie pętla jest powtarzana.

    Jeżeli rozmiar elementu pozwala na umieszczenie go w pamięci, to wywoływana jest procedura INITLD, która przygotowuje i wykonuje odczyt elementu. Błędny odczyt powoduje wywołanie DCBINI z wartością $4E i kolejne powtórzenie pętli, Poprawnie odczytany element jest dołączany do listy liniowej przez procedurę LINK.

    Odmienny jest przebieg procedury LINKSOM przy gorącym starcie. Kolejno sprawdzane jest następstwo elementów listy, aż do napotkania wskaźnika zerowego, który oznacza koniec listy. Każdy element jest przy tym sprawdzany przez procedurę CHCKFF. Negatywny wynik powoduje opuszczenie procedury. Jeśli zaś wynik kontroli jest pozytywny, to procedura LINK umieszcza element na końcu listy. Nieprawidłowy przebieg LINK kończy procedurę, a w przeciwnym razie poszukiwany jest następny element.

    Procedura DCBINI przepisuje do bloku DCB parametry z tabeli TSIOIN, a w rejestrach DAUX1 i DAUX2 umieszcza wartości przekazane jej odpowiednio w akumulatorze i rejestrze Y. Na końcu wykonywany jest bezpośredni skok do procedury SIOINT, która obsługuje transmisję. Wynikiem wykonanej operacji są cztery bajty umieszczone w rejestrze DVSTAT. Oznaczają one rozmiar elementu oraz adres dla SIO. Ostatni bajt jest niewykorzystany (może zawierać numer wersji).
            0100 ;DCB INItiation
            0110 ;
            0120 DAUX1 = $030A
            0130 DAUX2 = $030B
            0140 DDEVIC = $0300
            0150 JSIOINT = $E459
            0160 TSIOIN = $E7D4
            0170 ;
            0180     *=  $E7BE
            0190 ;
            0200     PHA
            0210     LDX #$09
            0220 NEXT LDA TSIOIN,X
            0230     STA DDEVIC,X
            0240     DEX
            0250     BPL NEXT
            0260     STY DAUX2
            0270     PLA
            0280     STA DAUX1
            0290     JMP JSIOINT
    Wartości znajdujące się w tabeli TSIOIN oznaczają parametry dla DCB w kolejności: DDEVIC ($4F), DUNIT ($01), DCMND ($40 - GET DATA), DSTAT ($40 - odczyt), DBUFA ($02EA - DVSTAT), DTIMLO ($1E) i DBYT ($04).
            0100 ;Table SIO INitiation
            0110 ;
            0120     *=  $E7D4
            0130 ;
            0140     .BYTE $4F,$01,$40,$40,$EA
            0150     .BYTE $02,$1E,$00,$04,$00
    Procedurą kontrolującą poprawność elementu jest CHCKFF. Oblicza ona sumę 18 bajtów poczynając od bajtu wskazanego przez rejestr ZCHAIN. Suma ta powinna mieć wartość $FF, w akumulatorze jest wtedy $00. Jeśli jest inaczej to akumulator zawiera różnicę między obliczoną wartością a $FF.
            0100 ;CHeCK for $FF
            0110 ;
            0120 ZCHAIN = $4A
            0130 ;
            0140     *=  $CB56
            0150 ;
            0160     LDY #$11
            0170     LDA #$00
            0180     CLC
            0190 LOOP ADC (ZCHAIN),Y
            0200     DEY
            0210     BPL LOOP
            0220     ADC #$00
            0230     EOR #$FF
            0240     RTS

6.1.1. Odczyt elementu

    W celu odczytania elementu struktury wywoływana jest procedura INITLD. Jej zadaniem jest przygotowanie transmisji.
            0100 ;INITiation LoaDer
            0110 ;
            0120 DVTMOT = $02EC
            0130 GBYTEA = $02CF
            0140 GETBYT = $E816
            0150 LOADAD = $02D1
            0160 LOADER = $C745
            0170 TEMP1 = $312
            0180 TEMP2 = $313
            0190 TEMP3 = $315
            0200 ZLOADA = $02D3
            0210 ;
            0220     *=  $E7DE
            0230 ;
            0240     STA TEMP2
            0250     LDX #$00
            0260     STX TEMP1
            0270     DEX
            0280     STX TEMP3
            0290     LDA DVTMOT
            0300     ROR A
            0310     BCC LDH
            0320     INC DVTMOT
            0330     BNE LDH
            0340     INC DVTMOT+1
            0350 LDH LDA DVTMOT
            0360     STA LOADAD
            0370     LDA DVTMOT+1
            0380     STA LOADAD+1
            0390     LDA # <GETBYT
            0400     STA GBYTEA
            0410     LDA # >GETBYT
            0420     STA GBYTEA+1
            0430     LDA #$80
            0440     STA ZLOADA
            0450     JMP LOADER
    Na początku zawartość akumulatora jest zapisywana do pomocniczego rejestru TEMP2, a rejestry TEMP1 i TEMP3 otrzymują odpowiednio wartości $00 i $FF. Obliczony uprzednio adres wczytywania jest przenoszony z DVTMOT do rejestru LOADAD (LOAD ADdress), a wektor GBYTEA (Get BYTE Address) jest ustawiany na adres procedury GETBYT. Następnie znacznik ZLOADA otrzymuje wartość $80 i wykonywany jest bezpośredni skok do procedury LOADER.
            0100 ;LOADER routine
            0110 ;
            0120 GBYTEA = $02CF
            0130 HIBYTE = $0288
            0140 LCOUNT = $0233
            0150 LOADAD = $02D1
            0160 NEWVEC = $C8E4
            0170 RECLEN = $0245
            0180 RUNADR = $02C9
            0190 ;
            0200     *=  C745
            0210 ;
            0220     LDX #$05
            0230 NEXT LDA #$00
            0240     STA RUNADR,X
            0250     DEX
            0260     BPL NEXT
            0270 LOOP1 LDA #$00
            0280     STA LCOUNT
            0290     JSR GBYTAC
            0300     LDY #$9C
            0310     BCS EXIT
            0320     STA HIBYTE
            0330     JSR GBYTAC
            0340     LDY #$9C
            0350     BCS EXIT
            0360     STA RECLEN
            0370     LDA HIBYTE
            0380     CMP #$0B
            0390     BEQ HENDRT
            0400     ROL A
            0410     TAX
            0420     LDA NEWVEC,X
            0430     STA RUNADR
            0440     LDA NEWVEC+1,X
            0450     STA RUNADR+1
            0460 LOOP2 LDA RECLEN
            0470     CMP LCOUNT
            0480     BEQ LOOP1
            0490     JSR GBYTAC
            0500     LDY #$9C
            0510     BCS EXIT
            0520     JSR RUNADC
            0530     INC LCOUNT
            0540     BNE LOOP2
            0550 EXIT RTS
            0560 ;
            0570 ;Handle END Record Type
            0580 ;
            0590 HENDRT JSR GBYTAC
            0600     LDY #$9C
            0610     BCS END
            0620     STA RUNADR
            0630     JSR GBYTAC
            0640     LDY #$9C
            0650     BCS END
            0660     STA RUNADR+1
            0670     LDA RECLEN
            0680     CMP #$01
            0690     BEQ SUC
            0700     BCC ERR
            0710     CLC
            0720     LDA RUNADR
            0730     ADC LOADAD
            0740     TAY
            0750     LDA RUNADR+1
            0760     ADC LOADAD+1
            0770 SET STY RUNADR
            0780     STA RUNADR+1
            0790 SUC LDY #$01
            0800 END RTS
            0810 ERR LDY #$00
            0820     LDA #$00
            0830     BEQ SET
            0840 GBYTAC JMP (GBYTEA)
            0850 RUNADC JMP (RUNADR)
    Na początku zerowane jest sześć bajtów poczynając od adresu RUNADR (są to rejestry RUNADR, HIUSED i ZHIUSE). Po ustawieniu licznika LCOUNT (Loader COUNTer) na zero rozpoczyna się pętla odczytu. Najpierw dwukrotnie wywoływana jest procedura GETBYT, a odczytane przez nią wartości są umieszczane kolejno w rejestrach HIBYTE (HIgh BYTE) i RECLEN (RECord LENgth). Zasygnalizowanie błędu po zakończeniu GETBYT zawsze powoduje umieszczenie w rejestrze Y kodu błędu $9C (żadne ze źródeł nie podaje znaczenia tego kodu) i przerwanie procedury LOADER.

    Przy pierwszym wywołaniu procedury GETBYT z LOADER rejestr TEMP3 zawiera wartość $FF. Powoduje to wpisanie do TEMP3 wartości $80 i wywołanie procedury GTNXBL i odczyt bloku 128 bajtów. Pierwszy bajt bloku jest umieszczany w akumulatorze i przekazywany do procedury LOADER. Każde następne wywołanie GETBYT powoduje zwrócenie kolejnego bajtu bloku, aż do jego wyczerpania. Wtedy znów wywoływana jest procedura GTNXBL.

    Pierwszy bajt bloku (przepisany do HIBYTE) służy jako indeks tabeli wektorów NEWVEC, natomiast drugi (RECLEN) określa długość odczytanego bloku. Jeżeli RECLEN jest równy licznikowi LCOUNT, to pętla jest powtarzana. Jej opuszczenie następuje po odczytaniu wartości HIBYTE równej $0B. Następuje wtedy przejście do etykiety HENDRT.

    Przy RECLEN różnym od LCOUNT uruchamiana jest pętla wewnętrzna, która kończy się po zrównaniu tych wartości. Podczas niej kolejne bajty odczytane przez GETBYT są przekazywane do procedury, której adres znajduje się w rejestrze RUNADR. Po jej zakończeniu licznik LCOUNT jest zwiększany i pętla się powtarza.
            0100 ;GET BYTe
            0110 ;
            0120 CSCB  = $03FD
            0130 GTNXBL = $E833
            0140 TEMP3 = $0315
            0150 ;
            0160     *=  $E816
            0170 ;
            0180     LDX TEMP3
            0190     INX
            0200     STX TEMP3
            0210     BEQ GPL
            0220 NEXT LDX TEMP3
            0230     LDA (CSCB-$80),X
            0240     CLC
            0250     RTS
            0260 GPL LDA #$80
            0270     STA TEMP3
            0280     JSR GTNXBL
            0290     BPL NEXT
            0300     SEC
            0310     RTS
    Po przerwaniu pętli procedura GETBYT jest wywoływana jeszcze dwa razy. Odczytane bajty umieszczane są w rejestrze RUNADR. Gdy RECLEN ma wartość 1, procedura się kończy, a gdy 0 - to zerowany jest RUNADR. W innym przypadku do zawartości RUNADR dodawana jest jeszcze zawartość LOADAD.
            0100 ;GeT NeXt BLock
            0110 ;
            0120 DAUX1 = $030A
            0130 DDEVIC = $0300
            0140 JSIOINT = $E459
            0150 SIOTAB = $E851
            0160 TEMP1 = $0312
            0170 TEMP2 = $0313
            0180 ;
            0190     *=  $E833
            0200 ;
            0210     LDX #$0B
            0220 NEXT LDA SIOTAB,X
            0230     STA DDEVIC,X
            0240     DEX
            0250     BPL NEXT
            0260     LDX TEMP1
            0270     STX DAUX1
            0280     INX
            0290     STX TEMP1
            0300     LDA TEMP2
            0310     STA DDEVIC
            0320     JMP JSIOINT
    Procedura GTNXBL wywoływana przez GETBYT przepisuje do DCB parametry transmisji z tabeli SIOTAB. Następnie do rejestru DAUX1 określającego numer rekordu wpisuje wartość pobraną z TEMP1, który z kolei jest zwiększany. Na końcu z rejestru TEMP2 przepisuje kod urządzenia umieszczony tam przez procedurę INITLD i wykonuje skok do SIOINT.
            0100 ;SIO TABle
            0110 ;
            0120     *=  $E851
            0130 ;
            0140     .BYTE $00,$01,$26,$40
            0150     .BYTE $FD,$03,$1E,$00
            0160     .BYTE $80,$00,$00,$00
    Wartości znajdujące się w tabeli SIOTAB oznaczają parametry dla DCB w kolejności: DDEVIC ($00), DUNIT ($01), DCMND ($26 - NOTE SECTOR), DSTAT ($40 - odczyt), DBUFA ($03FD - CSCB), DTIMLO ($1E), DBYT ($80 - 128 bajtów) oraz DAUX1 i DAUX2 ($00).

6.1.2. Przetwarzanie elementu

    Każdy bajt odczytany przez GETBYT jest przekazywany do procedury wskazanej wektorem RUNADR. Wektor ten jest pobierany przez procedurę LOADER z tabeli adresowej NEWVEC według wartości HIBYTE. Warto zwrócić uwagę na fakt, że układ tej tabeli jest bardzo zbliżony do układu tabeli COMTAB, która jest wykorzystywana przez CIO (zob. rozdział 2.1. - str. 15).
            0100 ;NEW device VECtor
            0110 ;
            0120 ADD28E = $C86D
            0130 ADDGET = $C8B5
            0140 ADDWRD = $C892
            0150 HENDRT = $C795
            0160 PUTCHR = $C7D5
            0170 ;
            0180     *=  $C8E4
            0190 ;
            0200     .WORD PUTCHR
            0210     .WORD PUTCHR
            0220     .WORD ADDWRD
            0230     .WORD ADDWRD
            0240     .WORD ADDWRD
            0250     .WORD ADDWRD
            0260     .WORD ADD28E
            0270     .WORD ADD28E
            0280     .WORD ADDGET
            0290     .WORD ADDGET
            0300     .WORD PUTCHR
            0310     .WORD HENDRT
    Teraz zostaną kolejno opisane procedury umieszczone w powyższej tabeli. Trzeba przy tym pamiętać, że w każdej z nich daną wejściową jest bajt odczytany przez GETBYT i umieszczony w akumulatorze.

    Procedura PUTCHR jest wywoływana, gdy rejestr HIBYTE ma wartość $00, $01 lub $0A. Jej przebieg jest zależny od stanu licznika LCOUNT. Gdy jest on równy zero, to zawartość akumulatora jest tylko zapisywana do młodszego bajtu rejestrów RELADR i NEWADR, a procedura się kończy. Gdy LCOUNT jest równy 1, to bajt z akumulatora jest umieszczany w starszym bajcie tych rejestrów i następuje przejście do drugiej fazy PUTCHR.
            0100 ;PUT CHaRacter
            0110 ;
            0120 HIBYTE = $0288
            0130 HIUSED = $02CB
            0140 LCOUNT = $0233
            0150 LOADAD = $02D1
            0160 LTEMP = $36
            0170 MEMTOP = $02E5
            0180 NEWADR = $028E
            0190 RECLEN = $0245
            0200 RELADR = $024A
            0210 ;
            0220     *=  $C7D5
            0230 ;
            0240     LDY LCOUNT
            0250     CPY #$01
            0260     BEQ ADD
            0270     BCS RELTXT
            0280     STA RELADR
            0290     STA NEWADR
            0300     BCC EXIT
            0310 ADD STA RELADR+1
            0320     STA NEWADR+1
            0330     LDX #$00
            0340     LDA HIBYTE
            0350     BEQ ADL
            0360     CMP #$0A
            0370     BEQ ADN
            0380     LDX #$02
            0390 ADL CLC
            0400     LDA RELADR
            0410     ADC LOADAD,X
            0420     STA NEWADR
            0430     LDA RELADR+1
            0440     ADC LOADAD+1,X
            0450     STA NEWADR+1
            0460 ADN CLC
            0470     LDA NEWADR
            0480     ADC RECLEN
            0490     PHA
            0500     LDA #$00
            0510     ADC NEWADR+1
            0520     TAY
            0530     PLA
            0540     SEC
            0550     SBC #$02
            0560     BCS BPS
            0570     DEY
            0580 BPS PHA
            0590     TYA
            0600     CMP HIUSED+1,X
            0610     PLA
            0620     BCC LOD
            0630     BNE SAV
            0640     CMP HIUSED,X
            0650     BCC LOD
            0660 SAV STA HIUSED,X
            0670     PHA
            0680     TYA
            0690     STA HIUSED+1,X
            0700     PLA
            0710 LOD LDX HIBYTE
            0720     CPX #$01
            0730     BEQ EXIT
            0740     CPY MEMTOP+1
            0750     BCC EXIT
            0760     BNE ERR
            0770     CMP MEMTOP
            0780     BCC EXIT
            0790 ERR PLA
            0800     PLA
            0810     LDY #$9D
            0820 EXIT RTS
            0830 ;
            0840 ;RELocate TeXT in memory
            0850 ;
            0860 RELTXT SEC
            0870     PHA
            0880     LDA LCOUNT
            0890     SBC #$02
            0900     CLC
            0910     ADC NEWADR
            0920     STA LTEMP
            0930     LDA #$00
            0940     ADC NEWADR+1
            0950     STA LTEMP+1
            0960     PLA
            0970     LDY #$00
            0980     STA (LTEMP),Y
            0990     JMP EXIT
    Dalsze działanie zależy od wartości HIBYTE. Przy HIBYTE równym zero do RELADR jest dodawane LOADAD, a przy równym jeden ZLOADA i rezultat jest umieszczany w NEWADR. Wartość $0A w HIBYTE powoduje ominięcie tego etapu. Następnie zawartość NEWADR jest zwiększana o długość rekordu RECLEN i zmniejszana o 2. Uzyskany wynik jest porównywany z zawartością rejestru HIUSED (HIgh USED address) i gdy jest większy, to zostaje do niego przepisany. Na końcu wykonywane jest jeszcze porównanie otrzymanego adresu z wartością MEMTOP. Jeżeli adres jest większy, to w rejestrze Y sygnalizowany jest błąd $9D. Wystąpienie błędu każdorazowo powoduje zdjęcie ze stosu dwóch bajtów i powrót z procedury PUTCHR do miejsca wywołania INITLD.

    Zupełnie odmienny jest przebieg procedury, gdy licznik LCOUNT ma wartość większą od 1. Do adresu zawartego w NEWADR dodawany jest stan LCOUNT zmniejszony o 2 i uzyskany wynik jest wpisywany do rejestru przejściowego LTEMP (Loader TEMPorary register). Przekazany w akumulatorze znak jest teraz umieszczany pod adresem wskazywanym przez LTEMP i procedura się kończy.

    Wartości HIBYTE z zakresu od $02 do $05 powodują wywołanie procedury ADDWRD. Najpierw dodawane są zawartości akumulatora i NEWADR, a wynik umieszczany jest w LTEMP. Następnie do bajtu wskazywanego przez LTEMP dodawana jest zawartość rejestru LOADAD (dla HIBYTE mniejszego od 4) lub ZLOADA (dla HIBYTE większego od 3).
            0100 ;ADDition for Write/ReaD
            0110 ;
            0120 HIBYTE = $0288
            0130 LOADAD = $02D1
            0140 LTEMP = $36
            0150 NEWADR = $028E
            0160 ;
            0170     *=  $C892
            0180 ;
            0190     LDX #$00
            0200     LDY HIBYTE
            0210     CPY #$04
            0220     BCC BPS
            0230     LDX #$02
            0240 BPS CLC
            0250     ADC NEWADR
            0260     STA LTEMP
            0270     LDA #$00
            0280     ADC NEWADR+1
            0290     STA LTEMP+1
            0300     LDY #$00
            0310     LDA (LTEMP),Y
            0320     CLC
            0330     ADC LOADAD,X
            0340     STA (LTEMP),Y
            0350     RTS
    Przy HIBYTE równym $06 lub $07 wywoływana jest procedura ADD28E. Wykonywane jest przez nią także dodawanie zawartości LOADAD według LTEMP, jak w ADDWRD, lecz teraz operacja dotyczy dwóch bajtów: wskazywanego przez wektor LTEMP i następnego.
            0100 ;ADDition $028E
            0110 ;
            0120 LOADAD = $02D1
            0130 LTEMP = $36
            0140 NEWADR = $028E
            0150 ;
            0160     *=  $C86D
            0170 ;
            0180     CLC
            0190     ADC NEWADR
            0200     STA LTEMP
            0210     LDA #$00
            0220     ADC NEWADR+1
            0230     STA LTEMP+1
            0240     LDY #$00
            0250     LDA (LTEMP),Y
            0260     CLC
            0270     ADC LOADAD
            0280     STA (LTEMP),Y
            0290     INC LTEMP
            0300     BNE BPS
            0310     INC LTEMP+1
            0320 BPS LDA (LTEMP),Y
            0330     ADC LOADAD+1
            0340     STA (LTEMP),Y
            0350     RTS
    Przebieg procedury ADDGET wywoływanej dla zawartości HIBYTE równej $08 lub $09 jest zależny od parzystości licznika LCOUNT, co jest rozpoznawane według bitu 0.
            0100 ;ADDition for GET
            0110 ;
            0120 HIBYTE = $0288
            0130 LCOUNT = $0233
            0140 LOADAD = $02D1
            0150 LTEMP = $36
            0160 NEWADR = $028E
            0170 ;
            0180     *=  $C8B5
            0190 ;
            0200     PHA
            0210     LDA LCOUNT
            0220     ROR A
            0230     PLA
            0240     BCS ADD
            0250     CLC
            0260     ADC NEWADR
            0270     STA LTEMP
            0280     LDA #$00
            0290     ADC NEWADR+1
            0300     STA LTEMP+1
            0310     LDY #$00
            0320     LDA (LTEMP),Y
            0330     STA HIBYTE
            0340 EXIT RTS
            0350 ADD CLC
            0360     ADC LOADAD
            0370     LDA #$00
            0380     ADC LOADAD+1
            0390     ADC HIBYTE
            0400     LDY #$00
            0410     STA (LTEMP),Y
            0420     BEQ EXIT
    Przy parzystej wartości LCOUNT (bit 0 skasowany) zawartość akumulatora jest dodawana do NEWADR, a uzyskany wynik jest traktowany jako wektor. Bajt wskazany przez ten wektor jest odczytywany i umieszczany w rejestrze HIBYTE.

    Nieparzysta wartość LCOUNT (bit 0 ustawiony) powoduje zsumowanie zawartości akumulatora oraz rejestrów LOADAD i HIBYTE. Uzyskany rezultat jest umieszczany w komórce wskazanej wektorem LTEMP.

6.1.3. Dołączenie elementu do listy

    Każdy odczytany element struktury musi zostać umieszczony we właściwym miejscu listy liniowej. Operacja ta jest przeprowadzana przez procedurę LINK. Procedura ta posiada trzy punkty początkowe. Jeden z nich (LINKCD) jest wykorzystywany przy zimnym starcie systemu, drugi (LINKWM) przy starcie gorącym, a trzeci (LINK) jest normalnym adresem wywołania procedury.
            0100 ;LINK routine
            0110 ;
            0120 CALVEC = $E900
            0130 CHCKFF = $CB56
            0140 DVTMOT = $02EC
            0150 MEMLO = $02E7
            0160 SRCHLS = $E85D
            0170 TEMP1 = $0312
            0180 TEMP2 = $0313
            0190 UNLINK = $E915
            0200 ZCHAIN = $4A
            0210 ;
            0220     *=  $E894
            0230 ;
            0240 ;LINK WarM start
            0250 LINKWM SEC
            0260     PHP
            0270     BCS INIHND
            0280 ;LINK
            0290 LINK STA DVTMOT+1
            0300     STY DVTMOT
            0310 ;
            0320 ;LINK ColD start
            0330 LINKCD PHP
            0340     LDA #$00
            0350     TAY
            0360     JSR SRCHLS
            0370     BCS ERR
            0380     LDY #$12
            0390     LDA DVTMOT
            0400     STA (ZCHAIN),Y
            0410     TAX
            0420     INY
            0430     LDA DVMOT+1
            0440     STA (ZCHAIN),Y
            0450     STX ZCHAIN
            0460     STA ZCHAIN+1
            0470     LDA #$00
            0480     STA (ZCHAIN),Y
            0490     DEY
            0500     STA (ZCHAIN),Y
            0510 ;INItialize HaNDler
            0520 INIHND JSR CALVEC
            0530     BCC SUCC
            0540     LDA DVTMOT+1
            0550     LDY DVTMOT
            0560     JSR UNLINK
            0570 ERR PLP
            0580     SEC
            0590     RTS
            0600 SUCC PLP
            0610     BCS UPDMLO
            0620     LDA #$00
            0630     LDY #$10
            0640     STA (ZCHAIN),Y
            0650     INY
            0660     STA (ZCHAIN),Y
            0670 UPDMLO CLC
            0680     LDY #$10
            0690     LDA MEMLO
            0700     ADC (ZCHAIN),Y
            0710     STA MEMLO
            0720     INY
            0730     LDA MEMLO+1
            0740     ADC (ZCHAIN),Y
            0750     STA MEMLO+1
            0760     LDY #$0F
            0770     LDA #$00
            0780     STA (ZCHAIN),Y
            0790     JSR CHCKFF
            0800     LDY #$0F
            0810     STA (ZCHAIN),Y
            0820     CLC
            0830     RTS
    Rozpoczęcie procedury od LINKWM powoduje opuszczenie całej pierwszej fazy procedury. Po ustawieniu bitu Carry i zapamiętaniu na stosie rejestru statusu procesora wykonywany jest skok do drugiej części oznaczonej etykietą INIHND.

    Po rozpoczęciu od etykiety LINKCD akumulator i rejestr Y są zerowane i wywoływana jest procedura SRCHLS. Wyszukuje ona ostatni element struktury i umieszcza jego adres w rejestrze ZCHAIN. Gdy taki element nie został znaleziony, to po odtworzeniu ze stosu rejestru statusu i ustawieniu bitu Carry procedura LINK jest przerywana. Następnie wskaźnik tego elementu jest ustawiany tak, aby wskazywał na nowy element. Adres nowego elementu jest umieszczany w rejestrze ZCHAIN, a jego wskaźnik jest zerowany.

    Teraz element jest poprzez wywołanie procedury CALVEC inicjowany. W przypadku niepowodzenia ponownie pobierany jest jego adres z DVTMOT i wywoływana jest procedura UNLINK, która usuwa element z listy. Dzięki temu lista zawiera tylko prawidłowo zainicjowane struktury.

    Potem odtwarzany jest ze stosu rejestr statusu procesora. Jeżeli ma on skasowany bit Carry, to zerowane są bajty 16 i 17 struktury wskazywanej przez wektor ZCHAIN. Oznacza to, że nowy element mieści się poniżej dolnej granicy wolnej pamięci (MEMLO). Następnie zawartości bajtów 16 i 17 są dodawane do wartości MEMLO, aby zabezpieczyć nowy element przed ingerencją programu użytkownika.

    Na zakończenie wywoływana jest procedura CHCKFF, która oblicza sumę kontrolną i zwraca wynik odjęcia jej od $FF. Wynik ten jest umieszczany w 15 bajcie elementu. Teraz po wywołaniu CHCKFF bit Carry będzie skasowany, a w akumulatorze znajdzie się zero.
            0100 ;SeaRCH LiSt
            0110 ;
            0120 CHCKFF = $CB56
            0130 CKEY =  $03E9
            0140 TEMP1 = $0312
            0150 TEMP2 = $0313
            0160 ZCHAIN = $4A
            0170 ;
            0180     *=  $E85D
            0190 ;
            0200     STY TEMP1
            0210     STA TEMP2
            0220     LDA # <CKEY
            0230     STA ZCHAIN
            0240     LDA # >CKEY
            0250     STA ZCHAIN+1
            0260 LOOP LDY #$12
            0270     LDA (ZCHAIN),Y
            0280     TAX
            0290     INY
            0300     LDA (ZCHAIN),Y
            0310     CMP TEMP2
            0320     BNE FIND
            0330     CPX TEMP1
            0340     BNE FIND
            0350     CLC
            0360     RTS
            0370 FIND CMP #$00
            0380     BNE FOUND
            0390     CPX #$00
            0400     BNE FOUND
            0410 ERR SEC
            0420     RTS
            0430 FOUND STX ZCHAIN
            0440     STA ZCHAIN+1
            0450     JSR CHCKFF
            0460     BNE ERR
            0470     BEQ LOOP
    Procedura SRCHLS przeszukuje listę liniową w celu odnalezienia elementu, którego wskaźnik ma parametry umieszczone przed wywołaniem SRCHLS w akumulatorze i rejestrze Y. Przekazane wartości są najpierw zapamiętywane w rejestrach TEMP1 i TEMP2. Następnie sprawdzany jest wskaźnik elementu. Gdy jest to poszukiwany element, procedura kończy się ze skasowanym bitem Carry.

    W przeciwnym przypadku sprawdza się, czy jest to ostatni element listy (wskaźnik równy zero). Jeśli tak, to procedura kończy się z ustawionym bitem Carry. Jeżeli nie jest to jeszcze koniec listy, to po przepisaniu adresu do rejestru ZCHAIN wywoływana jest procedura CHCKFF. Pozytywny wynik sprawdzenia sumy kontrolnej powoduje przejście do początku pętli i badanie następnego elementu. Błąd kończy procedurę SRCHLS z ustawionym bitem Carry.
            0100 ;CALl VECtor
            0110 ;
            0120 TEMP1 = $0312
            0130 TEMP2 = $0313
            0140 ZCHAIN = $4A
            0150 ;
            0160     *=  $E900
            0170 ;
            0180     CLC
            0190     LDA ZCHAIN
            0200     ADC #$0C
            0210     STA TEMP1
            0220     LDA ZCHAIN+1
            0230     ADC #$00
            0240     STA TEMP2
            0250     JMP (TEMP1)
    Inicjowanie elementu struktury odbywa się poprzez procedurę CALVEC. Odczytuje ona 12 i 13 bajt elementu oraz umieszcza je w rejestrach TEMP1 i TEMP2. Następnie według tego wektora wykonywany jest skok do procedury inicjującej. Wyraźnie widać tu analogię z tabelami adresowymi CIO, w których od dwunastego bajtu znajduje się instrukcja skoku do procedury inicjowania urządzenia.

    Gdy zachodzi konieczność usunięcia elementu z listy liniowej (np. po niepoprawnej inicjalizacji), to wywoływana jest procedura UNLINK. Adres elementu do usunięcia jest przekazywany do niej w akumulatorze i rejestrze Y. Z tymi wartościami wywoływana jest najpierw procedura SRCHLS, która wyszukuje element do usunięcia.

    Teraz wskaźnik elementu poprzedzającego jest ustawiany na element następny i w ten sposób zbędny element jest już usunięty z listy. Dokładniej metodę wykonania tej operacji można prześledzić na wydruku procedury. Jeżeli usunięcie elementu odbyło się poprawnie, to procedura kończy się ze skasowanym bitem Carry, w innym wypadku Carry jest ustawiany.
            0100 ;UNLINK routine
            0110 ;
            0120 CHCKFF = $CB56
            0130 COLDST = $0244
            0140 SRCHLS = $E85D
            0150 ZCHAIN = $4A
            0160 ;
            0170 ;
            0180     *=  $E915
            0190 ;
            0200     JSR SRCHLS
            0210     BCS EXIT
            0220     TAY
            0230     LDA ZCHAIN
            0240     PHA
            0250     LDA ZCHAIN+1
            0260     PHA
            0270     STX ZCHAIN
            0280     STY ZCHAIN+1
            0290     LDA COLDST
            0300     BNE COLD
            0310     LDY #$10
            0320     CLC
            0330     LDA (ZCHAIN),Y
            0340     INY
            0350     ADC (ZCHAIN),Y
            0360     BNE END
            0370     JSR CHCKFF
            0380     BNE END
            0390 COLD LDY #$12
            0400     LDA (ZCHAIN),Y
            0410     TAX
            0420     INY
            0430     LDA (ZCHAIN),Y
            0440     TAY
            0450     PLA
            0460     STA ZCHAIN+1
            0470     PLA
            0480     STA ZCHAIN
            0490     TYA
            0500     LDY #$13
            0510     STA (ZCHAIN),Y
            0520     DEY
            0530     TXA
            0540     STA (ZCHAIN),Y
            0550     CLC
            0560     RTS
            0570 END PLA
            0580     PLA
            0590 EXIT SEC
            0600     RTS

6.2. Komunikacja z nowym urządzeniem

    Pierwszą operacją konieczną do współpracy z urządzeniem przez CIO jest otwarcie kanału IOCB dla transmisji. Gdy otwierająca kanał procedura CIOOPN stwierdzi w rejestrze HNDLOD wartość różną od zera lub nie znajdzie wpisu urządzenia w tabeli HATABS, to wywoływana jest procedura SPCHND przygotowująca komunikację z nowym urządzeniem.
            0100 ;SPeCial HaNDler
            0110 ;
            0120 DCBINI = $E7BE
            0130 DVSTAT = $02EA
            0140 ICAX3 = $034C
            0150 ICAX4 = $034D
            0160 ICAX5Z = $2E
            0170 ICBAZ = $24
            0180 ICDNOZ = $21
            0190 ICHIDZ = $20
            0200 ICPTZ = $26
            0210 PUTBYT = $EF26
            0220 ;
            0230     *=  $EEF9
            0240 ;
            0250     LDY #$00
            0260     LDA (ICBAZ),Y
            0270     LDY ICDNOZ
            0280     JSR DCBINI
            0290     BPL NERR
            0300     LDY #$82
            0310     RTS
            0320 NERR LDA #$7F
            0330     STA ICHIDZ
            0340     LDA # <[PUTBYT-1]
            0350     STA ICPTZ
            0360     LDA # >[PUTBYT-1]
            0370     STA ICPTZ+1
            0380     LDA DVSTAT+2
            0390     LDX ICAX5Z
            0400     STA ICAX4,X
            0410     LDY #$00
            0420     LDA (ICBAZ),Y
            0430     STA ICAX3,X
            0440     LDY #$01
            0450     RTS
    Najpierw wywołuje ona procedurę DCBINI, która sprawdza istnienie urządzenia. Jej negatywny wynik powoduje wpisanie do rejestru Y kodu błędu $82 i przerwanie procedury SPCHND. Po otrzymaniu od urządzenia odpowiedzi, indeks w HATABS (ICHIDZ) jest ustawiany na wartość $7F, która sygnalizuje, że jest to nowe urządzenie. Jako wektor procedury obsługi wpisywany jest adres procedury PUTBYT. Następnie do ICAX4 przepisywany jest adres z DVSTAT+2, a do ICAX3 właściwa nazwa urządzenia z bufora.
    Każde następne żądanie komunikacji z nowym urządzeniem musi być poprzedzone wpisaniem do rejestru HNDLOD wartości różnej od zera (rejestr ICHIDZ zawiera $7F). Po stwierdzeniu takich wartości CIOMAIN wywołuje procedurę PRPLNK, która przygotowuje urządzenie do transmisji.
            0100 ;PRePare LiNK
            0110 ;
            0120 DFVHND = $E716
            0130 ICAX3 = $034C
            0140 ICAX4 = $034D
            0150 ICAX5Z = $2E
            0160 ICCHID = $0340
            0170 ICCOMT = $17
            0180 ICHIDZ = $20
            0190 INIOPN = $E55C
            0200 INITLD = $E7DE
            0210 LINKCD = $E89E
            0220 NEXDER = $E510
            0230 ;
            0240     *=  $CA29
            0250 ;
            0260     LDX ICAX5Z
            0270     LDA ICAX4,X
            0280     JSR INITLD
            0290     BCS EXIT
            0300     CLC
            0310     JSR LINKCD
            0320     BCS EXIT
            0330     LDX ICAX5Z
            0340     LDA ICAX3,X
            0350     JSR FDVHND
            0360     BCS EXIT
            0370     LDX ICAX5Z
            0380     STA ICCHID,X
            0390     STA ICHIDZ
            0400     LDA #$03
            0410     STA ICCOMT
            0420     JMP INIOPN
            0430 EXIT JMP NEXDER
    Najpierw do akumulatora przenoszony jest bajt z ICAX4 i wywoływana jest procedura INITLD, która inicjuje odpowiedni element. Następnie procedura LINK umieszcza ten element w liście liniowej. Teraz z rejestru ICAX3 jest pobierany właściwy kod urządzenia i procedura FDVHND (część DEVNUM) odszukuje w tabeli HATABS wektor tabeli adresowej tego urządzenia. Znaleziony indeks w HATABS jest przepisywany do rejestrów ICCHID i ICHIDZ. Wykrycie błędu podczas PRPLNK powoduje skok do procedury CIOMAIN, która sygnalizuje błąd NON EXISTENT DEVICE (urządzenie nie istnieje). W innym razie, po ustaleniu kodu operacji na $03 (OPEN), procedura kończy się skokiem do INIOPN (wewnątrz procedury CIOOPN).

    Po otwarciu kanału IOCB dla nowego urządzenia w rejestrze ICPTZ znajduje się adres procedury PUTBYT. Spełnia ona zadania zbliżone do CIO i przy porównaniu obu tych procedur bardzo łatwo wykryć podobieństwa.

    Na początku sprawdzana jest poprawność numeru IOCB (identycznie jak w CIOMAIN) oraz wartość znacznika HNDLOD. Pozytywny wynik powoduje przepisanie odpowiedniego IOCB do ZIOCB i wywołanie procedury PRPLNK. W przeciwnym przypadku do rejestru Y wpisywany jest kod błędu i procedura się kończy.
            0100 ;PUT BYTe routine
            0110 ;
            0120 HNDLOD = $02E9
            0130 ICAX5Z = $2E
            0140 ICCHID = $0340
            0150 ICHIDZ = $20
            0160 ICPTZ = $26
            0170 PRPLNK = $CA29
            0180 ;
            0190     *=  $EF26
            0200 ;
            0210     PHA
            0220     TXA
            0230     PHA
            0240     AND #$0F
            0250     BNE BIN
            0260     CPX #$80
            0270     BPL BIN
            0280     LDA HNDLOD
            0290     BNE SUC
            0300     LDY #$82
            0310 ERR PLA
            0320     PLA
            0330     CPY #$00
            0340     RTS
            0350 BIN LDY #$86
            0360     BMI ERR
            0370 SUC STX ICAX5Z
            0380     LDY #$00
            0390 NEXT LDA ICCHID,X
            0400     STA ICHIDZ,Y
            0410     INX
            0420     INY
            0430     CPY #$0C
            0440     BMI NEXT
            0450     JSR PRPLNK
            0460     BMI ERR
            0470     PLA
            0480     TAX
            0490     PLA
            0500     TAY
            0510     LDA ICPTZ+1
            0520     PHA
            0530     LDA ICPTZ
            0540     PHA
            0550     TYA
            0560     LDY #$92
            0570     RTS
    Po poprawnym zakończeniu PRPLNK wektor z rejestru ICPTZ jest przepisywany na stos. Po wykonaniu rozkazu RTS program jest kontynuowany od umieszczonego na stosie adresu. W CIO analogiczną operację wykonują procedury GOHAND i CIOJMP.

    System operacyjny Atari zawiera jeszcze tabelę adresową, która może być wykorzystana przez nowe urządzenie. Ma ona strukturę wymaganą przez CIO i może być wykorzystana po wpisaniu jej adresu do HATABS.
            0100 ;CALl TABle
            0110 ;
            0120 NEWINIT = $C90C
            0130 NWDVC1 = $C991
            0140 NWDVC3 = $C996
            0150 NWDVC5 = $C99B
            0160 NWDVC7 = $C9A0
            0170 NWDVC9 = $C9A5
            0180 NWDVCB = $C9AA
            0190 ;
            0200     *=  $E48F
            0210 ;
            0220     .WORD NWDVC1-1
            0230     .WORD NWDVC3-1
            0240     .WORD NWDVC5-1
            0250     .WORD NWDVC7-1
            0260     .WORD NWDVC9-1
            0270     .WORD NWDVCA-1
            0280     JMP NEWINIT
            0290     .BYTE $00
    Zawarte w tej tabeli adresy wskazują na tabelę wywołań procedur. Wszystkie operacje są rozpoczynane przez procedurę CHKNWP, a ich rodzaj jest rozpoznawany według zawartości rejestru Y.
            0100 ;NEW DeVice Call
            0110 ;
            0120 CHKNWP = $C9DC
            0130 ;
            0140     *=  $C991
            0150 ;
            0160 NWDVC1 LDY #$01
            0170     JMP CHKNWP
            0180 NWDVC3 LDY #$03
            0190     JMP CHKNWP
            0200 NWDVC5 LDY #$05
            0210     JMP CHKNWP
            0220 NWDVC7 LDY #$07
            0230     JMP CHKNWP
            0240 NWDVC9 LDY #$09
            0250     JMP CHKNWP
            0260 NWDVCB LDY #$0B
            0270     JMP CHKNWP
    Procedura ta odszukuje poprzez wywołanie GETLOW aktywne urządzenie o najmniejszym numerze i przekazuje mu sterowanie poprzez wywołanie NEWPER. Czynności te powtarzane są w pętli, aż do obsłużenia wszystkich urządzeń. Ponadto znaczną część procedury zajmują operacje przechowania niezbędnych parametrów i ich późniejszego odtworzenia.
            0100 ;CHecK NeW device Port
            0110 ;
            0120 CRITIC = $42
            0130 GETLOW = $C9AF
            0140 NEWPER = $C9CA
            0150 PDVREG = $D1FF
            0160 PDVRS = $0248
            0170 PPTMPA = $024C
            0180 PPTMPX = $024D
            0190 ;
            0200     *=  $C9DC
            0210 ;
            0220     STA PPTMPA
            0230     STX PPTMPX
            0240     LDA CRITIC
            0250     PHA
            0260     LDA #$01
            0270     STA CRITIC
            0280     LDX #$08
            0290 LOOP JSR GETLOW
            0300     BEQ NEXDV
            0310     TXA
            0320     PHA
            0330     TYA
            0340     PHA
            0350     JSR NEWPER
            0360     BCC NEXT
            0370     STA PPTMPA
            0380     PLA
            0390     PLA
            0400     JMP END
            0410 NEXDV LDY #$82
            0420 END LDA #$00
            0430     STA PDVRS
            0440     STA PDVREG
            0450     PLA
            0460     STA CRITIC
            0470     LDA PPTMPA
            0480     STY PPTMPX
            0490     LDY PPTMPX
            0500     RTS
            0510 NEXT PLA
            0520     TAY
            0530     PLA
            0540     TAX
            0550     BCC LOOP
    Działanie procedury NEWPER jest identyczne, jak znajdujących się w podsystemie CIO procedur GOHAND i CIOJMP (zob. str. 24). Jednakże adres wywoływanej procedury jest pobierany z pamięci ROM nowego urządzenia zależnie od operacji. Zawartość rejestru Y określa kolejno: PDVOPV (Parallel DeVice OPen Vector), PDVCLV (Parallel Device CLose Vector), PDVGBV (Parallel Device Get Byte Vector), PDVPBV (Parallel Device Put Byte Vector), PDVSTV (Parallel Device STatus Vector) i PDVSPV (Parallel Device SPecial Vector).
            0100 ;NEW device Port ERror
            0110 ;
            0120 PDVOPV = $D80D
            0130 PPTMPA = $024C
            0140 PPTMPX = $024D
            0150 ;
            0160     *=  $C9CA
            0170 ;
            0180     LDA PDVOPV,Y
            0190     PHA
            0200     DEY
            0210     LDA PDVOPV,Y
            0220     PHA
            0230     LDA PPTMPA
            0240     LDX PPTMPX
            0250     LDY #$92
            0260     RTS
Zientara Wojciech: Mapa pamięci Atari XL/XE. Procedury wejścia-wyjścia, SOETO, Warszawa, 1988.