   #Start Contents

                             Pisanie plikow .SYS

   Sterowniki w postaci plikow .SYS dziela sie na 2 rodzaje:
     * Nie-DOS-owe pliki .SYS (ladowane z config.sys poleceniem
       DEVICE=...) zazwyczaj zawieraja sterowniki takich urzadzen
       zewnetrznych jak na przyklad CD-ROM.
     * Pliki .SYS systemu DOS - na przyklad MSDOS.SYS czy IO.SYS. Te
       pliki zawieraja sterowniki urzadzen standardowych, jak konsola CON
       czy drukarka PRN.

   Wszystkie te pliki laczy wspolna struktura, ktora postaram sie tutaj
   przedstawic. Informacje podane przeze mnie sa wycinkiem z dokumentu
   "Programmer's Technical Reference for MSDOS and the IBM PC", ktorego
   kopie mozna znalezc na stronach systemu O3one (720 kB).

   Pliki .SYS zaczynaja sie od adresu 0 (org 0), a naglowek takiego pliku
   sklada sie z pieciu elementow:
   (przeskocz elementy naglowka)
     * DWORD - pelny adres (najpierw offset, potem segment) do nastepnego
       takiego naglowka jak ten, jesli nasz plik .SYS obsluguje wiecej
       niz jedno urzadzenie. Jesli mamy tylko jeden sterownik w naszym
       pliku, wpisujemy tutaj wartosc -1, czyli FFFF:FFFF.
     * WORD - atrybut urzadzenia (opisany dalej)
     * WORD - offset "procedury strategii" danego sterownika (opisane
       dalej)
     * WORD - offset "procedury przerwania" danego sterownika (opisane
       dalej)
     * 8 bajtow - nazwa (urzadzenie znakowe) dopelniana w razie potrzeby
       spacjami do osmiu znakow lub liczba jednostek (urzadzenie blokowe)

   Urzadzenie znakowe to takie, ktore moze wysylac/odbierac pojedyncze
   bajty, na przyklad CON, PRN, AUX. Mozna je otwierac jak normalne
   pliki.
   Urzadzenie blokowe to takie, ktore operuja na blokach danych i sa to
   zazwyczaj dyski.
   (przeskocz tabele atrybutow urzadzenia)

   CAPTION: Bity atrybutu i ich znaczenie

   Numer bitu Znaczenie
   0 =0 - to urzadzenie nie jest standardowym urzadzeniem wejscia
     =1 - to urzadzenie jest standardowym urzadzeniem wejscia
   1 =0 - to urzadzenie nie jest standardowym urzadzeniem wyjscia
     =1 - to urzadzenie jest standardowym urzadzeniem wyjscia
   2 =0 - to urzadzenie nie jest urzadzeniem NUL
     =1 - to urzadzenie jest urzadzeniem NUL
   3 =0 - to urzadzenie nie jest urzadzeniem CLOCK
     =1 - to urzadzenie jest urzadzeniem CLOCK
   4 =0 - nalezy uzywac standardowych procedur we/wy CON
     =1 - nalezy uzywac szybkich procedur we/wy ekranu (int 29h)
   5-10 zarezerwowane, musza byc rowne 0
   11 =0 - to urzadzenie nie obsluguje wymiennych nosnikow (domyslne dla
   DOS 2.x)
      =1 - to urzadzenie obsluguje wymienne nosniki (tylko dla DOS 3.0+)
   12 zarezerwowane, musi byc rowny 0
   13 =0 - format IBM (urzadzenia blokowe)
      =1 - format nie-IBM (urzadzenia blokowe)
      =1 - obsluguje funkcje zapisywania danych az do stanu zajetosci
   ("output till busy", urzadzenia znakowe)
   14 =0 - nie obsluguje IOCTL
      =1 - obsluguje IOCTL
   15 =0 - urzadzenie blokowe
      =1 - urzadzenie znakowe

   Ostatnie pole w naglowku to nazwa urzadzenia (w przypadku urzadzen
   znakowych) lub liczba jednostek/dyskow obslugiwanych przez ten
   sterownik (urzadzenia blokowe).
     _________________________________________________________________

"Procedura strategii" (strategy routine).

   (przeskocz procedure strategii)

   Za kazdym razem, jak DOS chce cos od naszego sterownika, uruchamia
   procedure strategii, podajac w parze rejestrow ES:BX adres naglowka
   zadania (request header). Zawiera on informacje o tym, co mamy zrobic.
   Jedynym obowiazkowym zadaniem tej procedury jest zachowanie adresu z
   ES:BX w zmiennej lokalnej, aby mozna bylo potem odczytywac zadania w
   procedurze przerwania, ktora uruchamiana jest zaraz po procedurze
   strategii. Jesli chcemy zrobic cos wiecej, musimy zachowac wszystkie
   rejestry (lacznie z flagami), ktore zmieniamy.
   Procedura konczy sie wywolaniem RETF, gdyz DOS uruchamia nasz
   sterownik wykonujac CALL FAR.

   Tak wiec najprostszy przyklad sprowadza sie do:
        mov     word cs:[nagl_zad], bx          ; NASM : [cs:nagl_zad]
        mov     word cs:[nagl_zad+2], es        ; NASM : [cs:nagl_zad+2]
        retf
     _________________________________________________________________

"Procedura przerwania" (interrupt routine).

   (przeskocz procedure przerwania)

   Ta procedura jest odpowiedzialna za wykonywanie polecen od systemu.
   Polecenia te sa zawarte w naglowku zadania, ktory teraz omowie.
   W procedurze przerwania rowniez nalezy zachowac wszystkie modyfikowane
   rejestry i wrocic do DOSa poleceniem RETF. Procedura przerwania jest
   uruchamiana przez DOS tuz po powrocie z procedury strategii, ktora
   musi zachowac biezacy adres naglowka zadania.
   (przeskocz opis naglowka zadania)

   CAPTION: Naglowek zadania

   Odleglosc od poczatku Dlugosc Zawartosc
   0 1 bajt Dlugosc w bajtach calego naglowka i ewentualnych danych
   1 1 Kod podjednostki w urzadzeniach blokowych. Nieistotne dla urzadzen
   znakowych
   2 1 Kod rozkazu
   3 2 Status wykonania
   5 8 zarezerwowane dla DOSa
   0Ch rozna Dane odpowiednie dla operacji

   Kod podjednostki w urzadzeniach blokowych jest istotny, gdy nasz
   sterownik obsluguje wiecej niz 1 urzadzenie.
   (przeskocz liste rozkazow)

   CAPTION: Kod rozkazu

   Kod Nazwa Funkcja
   0 INIT Inicjalizacja sterownika. Uzywane tylko raz.
   1 MEDIA CHECK Sprawdzanie, czy zmieniono dysk od ostatniego
   sprawdzenia. Uzywane tylko w urzadzeniach blokowych. Urzadzenia
   znakowe nic nie robia.
   2 BUILD BPB Stworzenie nowego BIOS Parameter Block (BPB). Uzywane
   tylko w urzadzeniach blokowych. Urzadzenia znakowe nic nie robia.
   3 IOCTL INPUT Odczyt IOCTL. Uruchamiane tylko wtedy, gdy urzadzenie ma
   ustawiony bit IOCTL.
   4 INPUT Odczyt danych.
   5 NONDESTRUCTIVE INPUT NO WAIT Odczyt danych.
   6 INPUT STATUS Stan odczytu
   7 INPUT FLUSH Oproznienie kolejki wejsciowej
   8 OUTPUT Zapis danych.
   9 OUTPUT Zapis danych z weryfikacja.
   10 OUTPUT STATUS Stan zapisu
   11 OUTPUT FLUSH Oproznienie kolejki wyjsciowej
   12 IOCTL OUTPUT Zapis IOCTL. Uruchamiane tylko wtedy, gdy urzadzenie
   ma ustawiony bit IOCTL.
   13 DEVICE OPEN Uruchamiane tylko wtedy, gdy urzadzenie ma ustawiony
   bit OPEN/CLOSE/RM.
   14 DEVICE CLOSE Uruchamiane tylko wtedy, gdy urzadzenie ma ustawiony
   bit OPEN/CLOSE/RM.
   15 REMOVEABLE MEDIA Uruchamiane tylko wtedy, gdy urzadzenie blokowe ma
   ustawiony bit OPEN/CLOSE/RM.
   16 OUTPUT UNTIL BUSY Uruchamiane tylko wtedy, gdy urzadzenie znakowe
   ma ustawiony bit 13.

   Najwazniejsze rozkazy sa opisane dalej.
   (przeskocz liste wynikow dzialania)

   CAPTION: Status wykonania zadania

    bit             Znaczenie
   0-7   Kod bledu, gdy bit15 = 1
   8     =1 oznacza "Operacja zakonczona"
   9     =1 oznacza "Urzadzenie zajete"
   10-14 Zarezerwowane dla DOSa
   15    =1 oznacza "blad"

   (przeskocz liste bledow sterownika)

   CAPTION: Znaczenie numerow bledow

   numer                        Typ bledu
   0     naruszenie ochrony przed zapisem
   1     nieznana jednostka
   2     urzadzenie nie jest gotowe
   3     nieznana komenda
   4     blad CRC
   5     nieprawidlowa dlugosc struktury zadania dostepu do dysku
   6     blad wyszukania (seek error)
   7     nieznany nosnik
   8     sektor nie znaleziony
   9     koniec papieru w drukarce
   10    blad zapisu
   11    blad odczytu
   12    blad ogolny
   13    zarezerwowane
   14    zarezerwowane
   15    nieprawidlowa zmiana dysku
     _________________________________________________________________

Rozkazy

   (przeskocz liste rozkazow sterownika)
     * INIT
       (przeskocz rozkaz init)
       ES:BX wskazuje na strukture zawierajaca naglowek zadania i dane.
       Ta struktura wyglada tak:

   CAPTION: Naglowek zadania

   Odleglosc od poczatku Dlugosc Zawartosc
   0 13 bajtow Naglowek zadania
   0Dh 1 Liczba jednostek w urzadzeniach blokowych. Nieistotne dla
   urzadzen znakowych
   0Eh? 4 Offset i segment konca kodu naszego sterownika. Mowi DOSowi,
   ile pamieci mozna zwolnic (wymieniony wczesniej dokument podaje tutaj
   offset 11h, ktory nie jest prawidlowy).
   12h? 4 Wskaznik na tablice BPB (nieistotne dla urzadzen znakowych) /
   wskaznik na reszte argumentow (wymieniony wczesniej dokument podaje
   tutaj offset 15h).
   16h? 1 numer dysku (DOS 3.0+) (wymieniony wczesniej dokument podaje
   tutaj offset 19h).
       W czasie inicjalizacji nalezy:
         1. ustawic liczbe jednostek (tylko w urzadzeniach blokowych).
            Wpisac 0, jesli nie mozna uruchomic urzadzenia.
         2. ustawic wskaznik na tablice BPB (tylko w urzadzeniach
            blokowych)
         3. wykonac czynnosci inicjalizacyjne (na przyklad modemow,
            drukarek)
         4. ustawic adres konca rezydentnego kodu. Wstawic CS:0, jesli
            nie mozna uruchomic urzadzenia.
         5. ustawic odpowiedni status w naglowku zadania
     * Odczyt/Zapis (funkcje: 3, 4, 8, 9, 12, 16)
       (przeskocz rozkazy odczytu i zapisu)
       ES:BX wskazuje na strukture zawierajaca naglowek zadania i dane.
       Ta struktura wyglada tak:

   CAPTION: Naglowek zadania

   Odleglosc od poczatku Dlugosc Zawartosc
   0 13 bajtow Naglowek zadania
   0Dh 1 Bajt deskryptora nosnika z BPB (Media Descriptor Byte)
   0Eh 4 Offset i segment bufora, z ktorego dane beda odczytywane/ do
   ktorego dane beda zapisywane.
   12h 2 Liczba bajtow/sektorow do zapisania/odczytania.
   14h 1 Poczatkowy numer sektora (tylko urzadzenia blokowe). Nie ma
   znaczenia dla urzadzen znakowych.
   16h 4 Offset i segment identyfikatora napedu (volume ID), gdy zwrocono
   kod bledu 0Fh.
       W czasie tej operacji nalezy:
         1. ustawic odpowiedni status w naglowku zadania
         2. wykonac zadanie
         3. ustawic rzeczywista liczbe przeniesionych bajtow/sektorow
     * NONDESTRUCTIVE INPUT NO WAIT
       (przeskocz rozkaz NONDESTRUCTIVE INPUT NO WAIT)
       Ten odczyt rozni sie od innych tym, ze nie usuwa odczytanych
       danych z bufora.
       ES:BX wskazuje na strukture zawierajaca naglowek zadania i dane.
       Ta struktura wyglada tak:

   CAPTION: Naglowek zadania

       Odleglosc od poczatku Dlugosc           Zawartosc
       0                     13 bajtow Naglowek zadania
       0Dh                   1         Bajt odczytany z urzadzenia
       W czasie tej operacji nalezy:
         1. zwrocic bajt odczytany z urzadzenia
         2. ustawic odpowiedni status w naglowku zadania
     * INPUT FLUSH
       (przeskocz rozkaz INPUT FLUSH)
       Wymuszenie wykonania wszystkich operacji odczytu, o ktorych wie
       sterownik.
       ES:BX wskazuje na naglowek zadania.
       W czasie tej operacji nalezy:
         1. ustawic odpowiedni status w naglowku zadania
     * OUTPUT FLUSH
       (przeskocz rozkaz OUTPUT FLUSH)
       Wymuszenie wykonania wszystkich operacji zapisu, o ktorych wie
       sterownik.
       ES:BX wskazuje na naglowek zadania.
       W czasie tej operacji nalezy:
         1. ustawic odpowiedni status w naglowku zadania
     _________________________________________________________________

Przyklad

   Skladajac razem powyzsze informacje, napisalem taki oto przykladowy
   plik .SYS.
   Jest to sterownik wymyslonego urzadzenia znakowego "MYSZKA1", ktory
   obsluguje tylko funkcje INIT (oczywiscie) i "pobieranie danych" z
   urzadzenia, ktore sprowadza sie do zwrocenia starego znacznika EOF
   (1Ah).

   Aby bylo widac, ze moj sterownik sie laduje (dzieki linii DEVICE=... w
   config.sys), dorobilem kod wyswietlajacy na ekranie informacje o
   ladowaniu.
   Reszte zobaczcie sami:
   (przeskocz przykladowy kod)
; Przyklad sterownika typu .SYS
; Autor: Bogdan D.
; kontakt: bogdandr (malpka) op (kropka) pl
;
; kompilacja:
; nasm -O999 -w+orphan-labels -o protosys.sys -f bin protosys.asm

dd      0FFFFFFFFh              ; wskaznik na nastepny sterownik
                                ; -1, bo mamy tylko 1 urzadzenie
dw      08000h                  ; atrybuty (urz. znakowe), output till busy (A0
00)
dw      strategia               ; adres procedury strategii
dw      przerwanie              ; adres procedury przerwania
db      "MYSZKA1 "      ; nazwa urzadzenia (8 znakow, dopelniane spacjami)

przerwanie:
        pushf
        push    es
        push    bx
        push    ax

        les     bx, [cs:request_header] ; ES:BX wskazuje na naglowek zadania
        mov     al, [es:bx + 2]         ; kod rozkazu

        test    al, al                  ; 0 = INIT
        jz      .init

        cmp     al, 4                   ; czy ktos chce czytac dane?
        je      .czytanie

        cmp     al, 5
        je      .czytanie2

                                        ; innych zadan nie obslugujemy

.koniec_przer:
                                        ; slowo wyniku w [es:bx+3]

        mov     word [es:bx + 3], 100h  ; mowimy, ze wszystko zrobione

        pop     ax
        pop     bx
        pop     es
        popf

        retf

.init:
                                ; podajemy adres konca kodu, ktory ma
                                ; zostac w pamieci
                                ; mozna usunac niepotrzebny juz kod
        mov     word [es:bx + 0eh], koniec
        mov     [es:bx + 10h], cs
        pusha
        push    es

        mov     ah, 3                   ; pobranie aktualnej pozycji kursora
        xor     bx, bx
        int     10h                     ; DH, DL - wiersz, kolumna kursora

        inc     dh
        xor     dl, dl                  ; idziemy o 1 wiersz nizej,
                                        ; od lewej krawedzi
        push    cs
        mov     ax, 1301h               ; AH=funkcja pisania na ekran.
                                        ; AL=przesuwaj kursor
        mov     bx, 7                   ; normalne znaki (szary na czarnym)
        mov     cx, init1_dl            ; dlugosc napisu
        mov     bp, init1               ; adres napisu
        pop     es                      ; segment napisu = CS
        int     10h                     ; napis na ekran.
                                        ; DH, DL wskazuja pozycje.
        pop     es
        popa

        jmp     short .koniec_przer

.czytanie:                      ; jak ktos chce czytac, zwracamy mu EOF
        push    es
        push    ax
        push    cx
        push    di

        mov     cx, [es:bx + 12h]       ; liczba zadanych bajtow
        les     di, [es:bx + 0Eh]       ; adres czytania/zapisywania
        mov     al, 1Ah                 ; 1ah = EOF
        rep     stosb                   ; zapisujemy

        pop     di
        pop     cx
        pop     ax
        pop     es
        jmp     short .koniec_przer

.czytanie2:                     ; jak ktos chce czytac, zwracamy mu EOF
        mov     byte [es:bx+0Dh], 1Ah
        jmp     short .koniec_przer

request_header  dd      0               ; wskaznik na naglowek zadania

strategia:

        pushf
        mov     [cs:request_header], bx ; zapisujemy adres naglowka zadania
        mov     [cs:request_header+2], es

        cmp     byte [cs:pierwsze], 1
        jne     .nie_pisz

        mov     byte [cs:pierwsze], 0
        pusha
        push    es

        mov     ah, 3                   ; pobranie aktualnej pozycji kursora
        xor     bx, bx
        int     10h                     ; DH, DL - wiersz, kolumna kursora

        inc     dh
        xor     dl, dl                  ; idziemy o 1 wiersz nizej,
                                        ; od lewej krawedzi

        push    cs
        mov     ax, 1301h               ; AH=funkcja pisania na ekran.
                                        ; AL=przesuwaj kursor
        mov     bx, 7                   ; normalne znaki (szary na czarnym)
        mov     cx, info1_dl            ; dlugosc napisu
        mov     bp, info1               ; adres napisu
        pop     es                      ; segment napisu = CS
        int     10h                     ; napis na ekran.
                                        ; DH, DL wskazuja pozycje.
        pop     es
        popa

.nie_pisz:
        popf
        retf

info1           db      "*** Uruchamianie sterownika MYSZKA1...",10,13,10,13
info1_dl        equ     $ - info1
init1           db      "*** INIT", 13, 10, 13, 10
init1_dl        equ     $ - init1
pierwsze        db      1

; wszystko od tego miejsca zostanie wyrzucone z pamieci
koniec:

   Jak widac, bylo tu o wiele wiecej opisu niz samej roboty i wcale nie
   okazalo sie to takie straszne.

   Aby zobaczyc, czy nasz sterownik rzeczywiscie zostal zaladowany i ile
   zajmuje miejsca w pamieci, nalezy wydac polecenie mem /c/p.

   Milej zabawy.

   Spis tresci off-line (klawisz dostepu 1)
   Spis tresci on-line (klawisz dostepu 2)
   Ulatwienia dla niepelnosprawnych (klawisz dostepu 0)
