Pisanie plików .SYS

Sterowniki w postaci plików .SYS dzielą się na 2 rodzaje:

Wszystkie te pliki łączy wspólna struktura, którą postaram się tutaj przedstawić. Informacje podane przeze mnie są wycinkiem z dokumentu Programmer's Technical Reference for MSDOS and the IBM PC, którego kopię można znaleźć na stronach systemu O3one (720 kB).

Pliki .SYS zaczynają się od adresu 0 (org 0), a nagłówek takiego pliku składa się z pięciu elementów:


(przeskocz elementy nagłówka)

Urządzenie znakowe to takie, które może wysyłać/odbierać pojedyncze bajty, na przykład CON, PRN, AUX. Można je otwierać jak normalne pliki.
Urządzenie blokowe to takie, które operują na blokach danych i są to zazwyczaj dyski.


(przeskocz tabelę atrybutów urządzenia)
Bity atrybutu i ich znaczenie
Numer bitu Znaczenie
0 =0 - to urządzenie nie jest standardowym urządzeniem wejścia
  =1 - to urządzenie jest standardowym urządzeniem wejścia
1 =0 - to urządzenie nie jest standardowym urządzeniem wyjścia
  =1 - to urządzenie jest standardowym urządzeniem wyjścia
2 =0 - to urządzenie nie jest urządzeniem NUL
  =1 - to urządzenie jest urządzeniem NUL
3 =0 - to urządzenie nie jest urządzeniem CLOCK
  =1 - to urządzenie jest urządzeniem CLOCK
4 =0 - należy używać standardowych procedur we/wy CON
  =1 - należy używać szybkich procedur we/wy ekranu (int 29h)
5-10 zarezerwowane, muszą być równe 0
11 =0 - to urządzenie nie obsługuje wymiennych nośników (domyślne dla DOS 2.x)
   =1 - to urządzenie obsługuje wymienne nośniki (tylko dla DOS 3.0+)
12 zarezerwowane, musi być równy 0
13 =0 - format IBM (urządzenia blokowe)
   =1 - format nie-IBM (urządzenia blokowe)
   =1 - obsługuje funkcję zapisywania danych aż do stanu zajętości (output till busy, urządzenia znakowe)
14 =0 - nie obsługuje IOCTL
   =1 - obsługuje IOCTL
15 =0 - urządzenie blokowe
   =1 - urządzenie znakowe

Ostatnie pole w nagłówku to nazwa urządzenia (w przypadku urządzeń znakowych) lub liczba jednostek/dysków obsługiwanych przez ten sterownik (urządzenia blokowe).


Procedura strategii (strategy routine).


(przeskocz procedurę strategii)

Za każdym razem, jak DOS chce coś od naszego sterownika, uruchamia procedurę strategii, podając w parze rejestrów ES:BX adres nagłówka żądania (request header). Zawiera on informacje o tym, co mamy zrobić.
Jedynym obowiązkowym zadaniem tej procedury jest zachowanie adresu z ES:BX w zmiennej lokalnej, aby można było potem odczytywać żądania w procedurze przerwania, która uruchamiana jest zaraz po procedurze strategii. Jeśli chcemy zrobić coś więcej, musimy zachować wszystkie rejestry (łącznie z flagami), które zmieniamy.
Procedura kończy się wywołaniem RETF, gdyż DOS uruchamia nasz sterownik wykonując CALL FAR.

Tak więc najprostszy przykład sprowadza się 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 procedurę przerwania)

Ta procedura jest odpowiedzialna za wykonywanie poleceń od systemu. Polecenia te są zawarte w nagłówku żądania, który teraz omówię.
W procedurze przerwania również należy zachować wszystkie modyfikowane rejestry i wrócić do DOSa poleceniem RETF. Procedura przerwania jest uruchamiana przez DOS tuż po powrocie z procedury strategii, która musi zachować bieżący adres nagłówka żądania.


(przeskocz opis nagłówka żądania)
Nagłówek żądania
Odległość od początku Długość Zawartość
0 1 bajt Długość w bajtach całego nagłówka i ewentualnych danych
1 1 Kod podjednostki w urządzeniach blokowych. Nieistotne dla urządzeń znakowych
2 1 Kod rozkazu
3 2 Status wykonania
5 8 zarezerwowane dla DOSa
0Ch różna Dane odpowiednie dla operacji

Kod podjednostki w urządzeniach blokowych jest istotny, gdy nasz sterownik obsługuje więcej niż 1 urządzenie.


(przeskocz listę rozkazów)
Kod rozkazu
Kod Nazwa Funkcja
0 INIT Inicjalizacja sterownika. Używane tylko raz.
1 MEDIA CHECK Sprawdzanie, czy zmieniono dysk od ostatniego sprawdzenia. Używane tylko w urządzeniach blokowych. Urządzenia znakowe nic nie robią.
2 BUILD BPB Stworzenie nowego BIOS Parameter Block (BPB). Używane tylko w urządzeniach blokowych. Urządzenia znakowe nic nie robią.
3 IOCTL INPUT Odczyt IOCTL. Uruchamiane tylko wtedy, gdy urządzenie ma ustawiony bit IOCTL.
4 INPUT Odczyt danych.
5 NONDESTRUCTIVE INPUT NO WAIT Odczyt danych.
6 INPUT STATUS Stan odczytu
7 INPUT FLUSH Opróżnienie kolejki wejściowej
8 OUTPUT Zapis danych.
9 OUTPUT Zapis danych z weryfikacją.
10 OUTPUT STATUS Stan zapisu
11 OUTPUT FLUSH Opróżnienie kolejki wyjściowej
12 IOCTL OUTPUT Zapis IOCTL. Uruchamiane tylko wtedy, gdy urządzenie ma ustawiony bit IOCTL.
13 DEVICE OPEN Uruchamiane tylko wtedy, gdy urządzenie ma ustawiony bit OPEN/CLOSE/RM.
14 DEVICE CLOSE Uruchamiane tylko wtedy, gdy urządzenie ma ustawiony bit OPEN/CLOSE/RM.
15 REMOVEABLE MEDIA Uruchamiane tylko wtedy, gdy urządzenie blokowe ma ustawiony bit OPEN/CLOSE/RM.
16 OUTPUT UNTIL BUSY Uruchamiane tylko wtedy, gdy urządzenie znakowe ma ustawiony bit 13.

Najważniejsze rozkazy są opisane dalej.


(przeskocz listę wyników działania)
Status wykonania zadania
bit Znaczenie
0-7 Kod błędu, gdy bit15 = 1
8 =1 oznacza Operacja zakończona
9 =1 oznacza Urządzenie zajęte
10-14 Zarezerwowane dla DOSa
15 =1 oznacza błąd


(przeskocz listę błędów sterownika)
Znaczenie numerów błędów
numer Typ błędu
0 naruszenie ochrony przed zapisem
1 nieznana jednostka
2 urządzenie nie jest gotowe
3 nieznana komenda
4 błąd CRC
5 nieprawidłowa długość struktury żądania dostępu do dysku
6 błąd wyszukania (seek error)
7 nieznany nośnik
8 sektor nie znaleziony
9 koniec papieru w drukarce
10 błąd zapisu
11 błąd odczytu
12 błąd ogólny
13 zarezerwowane
14 zarezerwowane
15 nieprawidłowa zmiana dysku



Rozkazy


(przeskocz listę rozkazów sterownika)

Przykład

Składając razem powyższe informacje, napisałem taki oto przykładowy plik .SYS.
Jest to sterownik wymyślonego urządzenia znakowego MYSZKA1, który obsługuje tylko funkcję INIT (oczywiście) i pobieranie danych z urządzenia, które sprowadza się do zwrócenia starego znacznika EOF (1Ah).

Aby było widać, że mój sterownik się ładuje (dzięki linii DEVICE=... w config.sys), dorobiłem kod wyświetlający na ekranie informację o ładowaniu.
Resztę zobaczcie sami:


(przeskocz przykładowy kod)
; Przykład sterownika typu .SYS
; Autor: Bogdan D.
; kontakt: bogdandr (małpka) op (kropka) pl
;
; kompilacja:
; nasm -O999 -w+orphan-labels -o protosys.sys -f bin protosys.asm

dd	0FFFFFFFFh		; wskaźnik na następny sterownik
				; -1, bo mamy tylko 1 urządzenie
dw	08000h			; atrybuty (urz. znakowe), output till busy (A000)
dw	strategia		; adres procedury strategii
dw	przerwanie		; adres procedury przerwania
db	"MYSZKA1 "	; nazwa urządzenia (8 znaków, dopełniane spacjami)

przerwanie:
	pushf
	push	es
	push	bx
	push	ax

	les	bx, [cs:request_header]	; ES:BX wskazuje na nagłówek żądania
	mov	al, [es:bx + 2]		; kod rozkazu

	test	al, al			; 0 = INIT
	jz	.init

	cmp	al, 4			; czy ktoś chce czytać dane?
	je	.czytanie

	cmp	al, 5
	je	.czytanie2

					; innych żądań nie obsługujemy

.koniec_przer:
					; słowo wyniku w [es:bx+3]

	mov	word [es:bx + 3], 100h	; mówimy, że wszystko zrobione

	pop	ax
	pop	bx
	pop	es
	popf

	retf

.init:
				; podajemy adres końca kodu, który ma
				; zostać w pamięci
				; można usunąć niepotrzebny już 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 niżej,
					; od lewej krawędzi
	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		; długość napisu
	mov	bp, init1		; adres napisu
	pop	es			; segment napisu = CS
	int	10h			; napis na ekran.
					; DH, DL wskazują pozycję.
	pop	es
	popa

	jmp	short .koniec_przer

.czytanie:			; jak ktoś chce czytać, zwracamy mu EOF
	push	es
	push	ax
	push	cx
	push	di

	mov	cx, [es:bx + 12h]	; liczba żądanych bajtów
	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 ktoś chce czytać, zwracamy mu EOF
	mov	byte [es:bx+0Dh], 1Ah
	jmp	short .koniec_przer

request_header	dd	0		; wskaźnik na nagłówek żądania

strategia:

	pushf
	mov	[cs:request_header], bx	; zapisujemy adres nagłówka żądania
	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 niżej,
					; od lewej krawędzi

	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		; długość napisu
	mov	bp, info1		; adres napisu
	pop	es			; segment napisu = CS
	int	10h			; napis na ekran.
					; DH, DL wskazują pozycję.
	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 pamięci
koniec:

Jak widać, było tu o wiele więcej opisu niż samej roboty i wcale nie okazało się to takie straszne.

Aby zobaczyć, czy nasz sterownik rzeczywiście został załadowany i ile zajmuje miejsca w pamięci, należy wydać polecenie mem /c/p.

Miłej zabawy.



Spis treści off-line (klawisz dostępu 1)
Spis treści on-line (klawisz dostępu 2)
Ułatwienia dla niepełnosprawnych (klawisz dostępu 0)