                          - SEPTIC'S DEMOSKOLA -

                                 Lektion 9
                                 ¯¯¯¯¯¯¯¯¯
                        Skriven av Vicious / Septic

                                 21 Okt 93



                                 Inledning
                                 ¯¯¯¯¯¯¯¯¯
Efter ett kort uppehåll kör vi nu igång med ytterligare några assembler-
lektioner. Den här gången tänkte jag ta upp en ganska välkänd sak som
brukar förekomma i alla demos med lite stil, nämligen trackladdning!


                                   Teori
                                   ¯¯¯¯¯
När man sparar en fil i exempelvis Deluxe Paint tänker man inte mycket på
vad som egentligen händer. Filen bara sparas på disken så att man kan ladda
den återigen när man behöver den. Vad som egentligen händer är att AmigaDos
kollar om det finns någon ledig plats på disken och sen sparar ner vår fil
sektor för sektor på disken, plus att den uppdaterar en fillista så att den
kan hitta filen nästa gång vi ska ladda in den.
  Detta sker så att säga osynligt för oss genom AmigaDos, och vi behöver
egentligen inte bry oss så mycket om det.
  Som coder kanske man dock tycker AmigaDos är ganska jobbigt. Man måste
coda mycket systemvänligt om man ska kunna använda AmigaDos i sitt demo,
och ladda del för del från en diskett, och att coda systemvänligt ska vi ju
undvika :-) för det blir ju mycket jobbigare...
  Använder man AmigaDos filsystem är ju också nackdelen att alla kan kika
på filerna man har på disketten. På en trackladdad diskett är detta mycket
svårare, speciellt för "hackers" som inte kan trackloading.
  Alltså får man skippa AmigaDos filsystem och helt enkelt spara sina filer
för hand, och likaså ladda dem för hand.
  I en egen trackloader sparar man sina filer på disken direkt på
sektorerna, och sen får man hålla reda på var man sparade sin fil så att
man kan ladda den igen.
  Jag kanske skulle nämna det att en diskett är uppbyggd av 80 cylindrar.
Varje cylinder har ett track på ovansidan och ett på undersidan, alltså 160
tracks totalt. Varje track är uppbyggt av 11 sektorer, och varje sektor är
512 bytes. En diskett har alltså plats för 160 tracks multiplicerat med 11
sektorer multiplicerat med 512 bytes (160*11*512)=901120 bytes!
  Första två sektorerna innehåller alltid ett bootblock. Bootblocket är det
som ska starta upp disken (om den är bootbar, givetvis) och därför måste vi
också ta och kika på hur man konstruerar en loader i bootblocket.

Det finns två typer av trackladdning. DOS och HARDWARE. DOS är den enkla
metoden. HARDWARE är den svåra, men som används mest idag eftersom den
anses vara coolare. Jag tänkte gå igenom båda metoderna eftersom de båda
har fördelar och nackdelar.


                                 Register
                                 ¯¯¯¯¯¯¯¯
För dos-trackloading behöver man inte använda några register alls (det är
just därför som det heter DOS-trackloading) utan endast en biblioteks-
funktion som laddar från disken.
  Däremot behöver man använda lite nya saker vid HW-trackloading
(HW=HardWare) som jag förklara nu...
  Först och främst ska vi kika på Amigans timers. En timer är en räknare
som endast har till uppgift att räkna från ett visst specificerat tal ner
till noll. En sån hjärndöd uppgift, kanske ni tycker, men dessa timers är
mycket användbara när man måste vänta en viss tidsperiod som är KORTARE än
en frame, dvs 1/50:dels sekund. Tidigare har vi ju använt oss av
skärmstrålen för att vänta en viss tidsperiod, nämligen en frame, men när
man trackladdar behöver man ibland vänta endast några millisekunder, och då
kommer en timer väl till pass.
  Nu måste jag också påpeka - för det finns väl alltid nån som protesterar
och säger att det blir för jobbigt att använda timers - att man ska ABSOLUT
inte vänta en viss tidsperiod med hjälp av loop-kommandot DBF!!! Detta
kanske verkar smidigt, och fungerar bra på din egen dator, men så
filosoferade alla coders för två-tre år sen och längre tillbaka. Det är
också därför som inga av deras demos fungerar på A1200 idag, t.ex, för
deras trackloaders väntar med DBF-loopar som går mycket fortare på 1200:an.
  Okej, som jag sa så finns det flera timers. När systemet är igång så
används de blandannat till att skaka hand med tangentbordet vid vissa
tidpunkter, exec task switching och interrupts.
  När vi trackladdar har vi dock slängt ut systemet, så då spelar det ingen
roll om vi roffar åt oss en timer och använder den för att tajma vår
trackloader.
  En timer kan fungera på lite olika sätt. Den kan t.ex räkna ner till
noll, generera ett interrupt och sen sätta sitt ursprungsvärde igen och
pånytt räkna ner till noll. Eller så kan den räkna ner en gång och sen
stanna, och det är den metoden vi ska använda.
  I min trackloader har jag valt att använda CIAB Timer A. Vi ska kika på
hur enkelt den är uppbyggd.
  Först och främst har vi ett kontrollregister, CIAB Control Register A
($BFDE00). Det ser ut så här:

BIT   NAMN          FUNKTION
---   ----          --------
 0    START         1=Starta Timer A, 0=Stoppa Timer A
                    (Den här biten nollställs automatiskt när timern nått
                     noll i One-Shot mode)
 1    PBON          1=Timer A output till PB6, 0=PB6 normal
 2    OUTMODE       1=Toggle, 0=Pulse
 3    RUNMODE       1=One-Shot mode, 0=Continuos mode
 4    LOAD          1=Force load (Strobe bit, tvingar timern att börja om
                    räkningen)
 5    INMODE        1=Timer A räknar Positive CNT transitions
                    0=Timer A räknar 02 pulser
 6    SPMODE        1=Seriell port blir output, 0=Seriell port blir input
 7    UNUSED        Ej använd.

Sen måste man också föra in vilket värde Timern ska räkna ner ifrån. Det
för man in i CIAB Timer A lo byte ($BFD400) och hi byte ($BFD500). Sen är
det bara att testa bit 0 i kontroll-registret för att se när timern är
färdig.
  Jag tänker inte gå in närmare på hur dessa timers funkar. Det står
förklarat utförligt i Hårdvarumanualen så fixa den om ni är intresserade.

Att göra en HW-trackloader är verkligen ett mandomsprov för en coder. Här
måste man sköta diskdriven på allra lägsta nivå, dvs flytta läs/skriv
huvudet manuellt, byta disksida beroende på vilket track man läser på, läsa
in data från disketten med hjälp av några hårdvaruregister och sedan decoda
datan till formatet vi sparade i.
  Till att börja med har vi några register som styr läshuvudet och det är
CIAAPRA ($BFE001) och CIAAPRB ($BFD100). CIAAPRA känner vi blandannat igen
för att man läser av musknappen där, men det finns några bitar som är
intressanta vid HW-trackloading. Här kommer de intressanta bitarna:

BIT   NAMN          FUNKTION
---   ----          --------
 2    DSKCHANGE     Blir 0 när en disk tas ur driven.
 3    DSKPROT       Disken är skrivskyddad=0.
 4    DSKTRACK0     Är noll när läshuvudet befinner sig på track 0.
 5    DSKREADY      Disk ready. Blir noll när drivemotorn roterar med full
                    hastighet och driven är klar att ta emot kommandon.

I CIAAPRB har vi bara intressanta bitar! Se här:

BIT   NAMN          FUNKTION
---   ----          --------
 0    DSKSTEP       Flyttar läs/skrivhuvudet på diskdriven. Signalen måste
                    ges som en snabb puls, 1, sen 0 i minst 3 millisekunder
                    och sen 1 igen. (När man byter riktning måste man vänta
                    i minst 18 millisekunder innan man fortsätter.)
 1    DSKDIREC      Specificera riktningen som drivehuvudet skall åka. En
                    nolla anger riktningen inåt, mot centrum på disken, en
                    etta anger utåt.
 2    DSKSIDE       Anger vilken sida på disketten vi ska läsa. En nolla
                    betyder ovansidan, och en etta är undersidan. Alla
                    jämna tracks ligger på undersidan och de udda på
                    ovansidan. (Måste vara stabil i minst 100 mikrosekunder
                    innan man skriver till disketten.)
 3    DSKSEL0       Välj drive 0.
 4    DSKSEL1       Drive 1.
 5    DSKSEL2       Drive 2.
 6    DSKSEL3       Drive 3.
 7    DSKMOTOR      Disk motor kontroll. Används för att slå på disk motorn
                    på den drive man valt. Det går till så att man först
                    sätter diskdrive, sen rensar DSKMOTOR biten, väntar
                    kort och sen rensar diskdrive-biten på den drive man
                    valt.

Där har vi allt vi behöver för att styra diskdrivens läs och skrivhuvud.
Det var väl inte så farligt?
  Nu kan vi kika vidare på hårdvaruregistren som har hand om läs och
skrivning från diskett. Vad de egentligen styr är DISK DMA, speciellt gjort
för att läsa och skriva från disk.
  Vi kan börja med kontrollregistret ADKCON, som står för Audio Disk
Control, och som mycket riktigt innehåller bitar för att kontrollera ljud
och disk. Vi ska titta enbart på disk-bitarna:

BIT   NAMN          FUNKTION
---   ----          --------
15    SET/CLR       Sätter eller rensar bitar som är satta. (Precis hos
                    DMACon, BPLCon osv...)
14-13 PRECOMP0-1    00 Ingen prekompensation
                    01 140 nanosekunders prekompensation
                    10 280   "     "         "      "
                    11 560   "     "         "      "
12    MFMPREC       0=GCR Precompensation (Group Coded Recording)
                    1=MFM Precompensation (Normalt)
10    WORDSYNC      En etta möjliggör synchronisering med DSKSYNC-registret
09    MSBSYNC       En etta möjliggör synchronisering av den mest
                    signifikanta biten av läst data (används mest vid GCR)
08    FAST          En etta anger 2 mikrosekunder per bit cell (MFM)
                    En nolla ger 4 mikrosekunder (GCR)

Vidare har vi ett register som heter DSKLEN ($DFF024). Det anger disk DMA
datans längd i words. Bit 15 slår dock på Disk DMA och bit 14 anger om data
ska läsas (0) eller skrivas (1), så det är alltså bara bit 0-13 som anger
längden. Det kluriga är att man måste skriva TVÅ gånger till den här
addressen för att verkställa ordern.
  I DSKPT ($DFF020) skriver man in disk-pekar-destinationen som ett
longword, alltså var i minnet den MFM-kodade rawdatan ska läsas. Måste vara
till CHIP mem.
  I DSKSYNC ($DFF07E) skriver vi in diskens sync-värde, som vanligvis
brukar vara talet $4489!
  I INTENAR ($DFF01C) finns det dessutom en interrupt bit som sätts när ett
disk block lästs. Jag använder det för att kolla när disk DMA:n är klar.
  Just det, glöm för allt i världen inte av att slå på DISK DMA-biten i
DMACON också!
  Så långt, allt väl, men nu är kruxet det att all data vi läst in är kodad
på ett speciellt sätt, som kallas MFM-encoding. Där är varje bit två bits
istället, som ligger på följande sätt: En 1 är alltid 01. En 0 är 10 om
föregående också var noll, annars är den 00.

  Sådär, det var allt man behövde för HW-trackladdning.

Nu tänkte jag gå igenom dos-trackladdning lite lätt. Eftersom libraries
inte är min starka sida kan det bli lite krystat och svårförklarat nu, men
ni kan läsa mer om de här biblioteksfunktionerna i Rom Kernel Reference
Manualen.
  Vi ska alltså använda oss utav ett device som heter Trackdisk.device för
att ladda från disken.
  Först måste vi skapa ett IO-Request block som vi ska använda oss utav för
att ladda. Blocket ska vara 20 longwords, eller 80 bytes.
  Sedan använder vi AddPort och skapar en port som vi för in i
IO-requesten. Sen öppnar vi trackdisk.device.
  Efter det kan vi skriva in ett kommando till IO-requesten på den 28:e
byten. Här följer en lista på kommandon:

Nummer Kommando Funktion
------ -------- --------
   2   Read     Läs från disken
   3   Write    Skriv till disken
   4   Update   Uppdatera skriven data till disk
   5   Clear    Rensa tracks
   9   Motor    Slå på eller av diskmotorn
  10   Seek     Flytta skrivhuvudet till ett speciellt track
  11   Format   Formater tracks
  13   ChgNum   Kolla hur många gånger disken bytts i driven
  14   ChgState Finns nån disk i driven?
  15   ProtStat Kolla om disken är skrivskyddad

Jag tänkte inte gå in så noga på de olika kommandona. Vi ska bara komma
ihåg att kommandot READ (#2) kräver längden i byte 36 av IO-requesten,
destinationen i byte 40 och offseten i byte 44. Längden och offset måste
vara jämna multiplar av 512, alltså måste man skriva in det som SECTOR*512.
Vill man läsa femton sektorer med början på sektor tre, så blir offseten
3*512 och längden 15*512. Destinationen måste ligga i chipmem.
  Sen hoppar man bara till exec-funktionen DoIO (-456).
  När man konstruerar bootblock brukar man använda sig av en
dos-trackloader som laddar in den riktiga loadern, eftersom HW-trackloaders
brukar vara ganska stora. I bootblocket är det dock ganska smidigt med
dos-loaders, eftersom man får en pekare till en fördefinerad IO-struktur i
A1, som man enkelt kan använda. Alltså behöver man bara skriva in längd,
offset och destination och så laddas MAIN-loadern in.


                              Programexemplen
                              ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
Idag har vi flera spännande program att kika på. Ett exempel på ett boot-
block med en dostrackloader, en vanlig dosloader och en HW-trackloader.
  Första programmet är en dostrackloader. Den använder sig utav systemets
trackload-rutiner, och därför måste systemet vara intakt när man kör den.
Använder man en sån rutin i ett demo får man alltså inte stänga av systemet
och då kan man alltså inte använda hela minnet.
  Programmet är inte så svårt att förstå. Det läser helt enkelt ett visst
antal sectorer från en viss position på disken (mätt i sectorer) till en
adress i minnet.
  Nästa program, Lekt9b.s, är i princip samma program, fast det är omgjort
för att läggas i ett bootblock. När man startar ett program från
bootblocket får man en IO-struktur färdig att användas i register A1.
Därför behöver man inte allokera en själv och då blir ju programmet ganska
litet och smidigt. Eftersom programmet är skrivet för att fungera endast
från bootblocket kan du INTE köra det på vanligt sätt i assemblern, utan du
måste spara det på en disk och sen boota från disken. Hur du ska göra står
förklarat i programmets början.
  Programmet gör egentligen ingen nytta. Det läser bara några sectorer från
disken för att demonstrera hur man gör, och sen går det in i en evig loop
som bara kan brytas genom reset.

Det sista programmet är en komplett hardware trackloader. Den jobbar enbart
med tracks, inte sectorer alltså, så varje läsning innebär 5632 bytes.
  Programmet är ganska utförligt kommenterat så du kan läsa där hur det
hela fungerar.

Nu kanske du undrar hur man ska spara sina alster på en diskett så att man
kan använda trackloading sen. Jo, det är ganska enkelt, fast man måste
hålla reda på lite siffror som man kanske måste skriva ner på ett papper.
Först och främst ska bootblocket alltid ligga på sector 0 och 1 så där kan
vi inte spara något. Däremot på sector 2 och framåt är allt fritt. Om vi nu
vill spara t.ex en jättebra låt där som är 95789 bytes stor, måste vi först
räkna fram hur många sectorer den tar upp. Då gör vi bara så att vi
dividerar längden med 512, alltså 95789/512 och då får vi 187.09 sectorer.
Vi kan bara arbeta med hela sectorer och vi ser att vi behöver lite mer än
187 sectorer, så då får vi alltså spara 188 sectorer för att hela filen ska
komma med.
  I AsmOne kan man sen göra följande för att spara filen. Skriv in ett kort
program som ser ut så här:

   section   data,data_c
s: incbin    'låt-path/låtens-namn'

Assemblera sen detta och stoppa sen i den disk du ska spara låten på i DF0:
(disken får inte innehålla några viktiga AmigaDOS-filer! Dessa kommer i så
fall att förstöras!)
  Nu skriver du WS för Write Sector och anger S som RAM PTR, eftersom det
är där vår låt ligger i minnet. Sen skulle vi ju börja spara på sector 2,
och därför skriver vi också 2 på frågan om DISK PTR. Sen skriver vi längden
188 och sen var det klart. Nu sparas låten på sector 2 till 190 och det är
nu vi behöver lite papper och penna för att skriva upp de här siffrorna.
Annars kanske man sparar över låten nästa gång man ska spara en annan fil
på disken.
  Hardware Trackloadern jobbar endast med hela tracks, som sagt, och skulle
vi använda den i vårt demo istället så får vi göra lite annorlunda. Vi tar
reda på antal tracks istället för antal sectorer, och det gör man genom att
ta fillängden genom 11*512 (eftersom det finns 11 sectorer på varje track).
Alltså 95789/5632 och det blir 17.01. Vi behöver alltså lite mer än 17
tracks, och då blir det givetvis 18 tracks.
  Nu använder vi kommandot WT (Write Tracks) istället för WS, och anger
alla längder och offseter i tracks. Vi börjar alltså spara på track 1
istället för sector 2, och längden blir 18 istället för 188.
  Så, nu hoppas jag ni kan göra riktigt häftiga trackmos efter det här!

