Niektóre programy nie tylko zajmują się przetwarzaniem danych, ale muszą też współpracować ze sprzętem, na przykład wykorzystać port szeregowy czy równoległy do przesyłania danych (czy to na drukarkę, czy do innego urządzenia). W tym artykule pokażę, jak wykrywać część urządzeń zainstalowanych w komputerze. Dobrze mieć spis przerwań Ralfa Brown'a pod ręką.
UWAGA: NIE należy badać pamięci RAM, zapisując do niej określone bity pod każdy możliwy adres i sprawdzając, czy uda się odczytać te same bity (brak pamięci sygnalizowany jest odczytaniem FF). Część urządzeń w komputerze (zwłaszcza PCI) jest mapowana do pamięci i zapisywanie do pewnych obszarów jest równoznaczne z zapisywaniem do tych urządzeń, co może je poważnie uszkodzić!
Do odkrycia zainstalowanej ilości pamięci RAM można skorzystać z następujących funkcji BIOSu: int 15h z EAX=0e820h, int 15h z EAX=0000E820h oraz int 12h (najlepiej w tej kolejności).
Pierwsza z nich korzysta z 32-bitowych rejestrów, więc dopiero od procesora 386 można sprawdzać, czy jest dostępna. Kolejne uruchomienia tej funkcji zwracają informacje o kolejnych obszarach pamięci i ich typie, tworząc tym samym BIOSową mapę pamięci. Ta funkcja przyjmuje następujące argumenty:
Jeśli wywołanie się powiedzie, funkcja zwraca, co następuje:
W przypadku niepowodzenia flaga CF=1. Przykładowe wywołanie wygląda tak:
mov ax, cs mov es, ax ; jeśli bufor jest w sekcji kodu mov eax, 0e820h mov edx, 534D4150h xor ebx, ebx mov ecx, 20 mov di, bufor int 15h jc blad ; tu operujemy na danych blad: ... bufor: b_adres dd 0, 0 b_dlugosc dd 0, 0 b_typ dd 0
Druga funkcja nie przyjmuje żadnych argumentów (poza numerem funkcji w AX) i zwraca ilość pamięci rozszerzonej od 1 MB do 16 MB, w kilobajtach, w AX. Jeśli wywołanie się nie powiedzie, flaga CF=1. Przykładowe wywołanie wygląda tak:
mov ax, 0E801h int 15h jc blad ; tu operujemy na danych blad:
Trzecia funkcja (przerwanie int 12h) w ogóle nie przyjmuje żadnych argumentów, a zwraca liczbę kilobajtów ciągłej pamięci od bezwzględnego adresu 00000h.
Wykrywanie portów, o których wie BIOS, jest bardzo łatwe. Wystarczy zajrzeć do BDA
(BIOS Data Area), czyli segmentu numer 40h, zawierającego dane BIOSu.
Adresy kolejnych portów szeregowych (maksymalnie czterech) jako 16-bitowe słowa
można znaleźć pod adresami 0040h:0000h, 0040h:0002h, 0040h:0004h, 0040h:0006h (choć ten
ostatni adres może służyć do innych celów na nowszych komputerach), zaś adresy
kolejnych portów równoległych (maksymalnie czterech) jako 16-bitowe słowa znajdują się pod
adresami 0040h:0008h, 0040h:000Ah, 0040h:000Ch, 0040h:000Eh.
Jeśli dodatkowo chcecie wykryć rodzaj portu szeregowego, polecam kod darmowego sterownika myszy dla DOSa - CuteMouse (a szczególnie plik comtest.asm). Sterownik jest napisany w asemblerze i można go pobrać oraz obejrzeć jego kod źródłowy za darmo.
Wykryć rodzaj portów równoległych można za pomocą układów nimi sterujących, na przykład
Intel 82091AA Advanced Integrated Peripheral
(porty 22h-23h). Kod dla
tego układu może wyglądać następująco:
mov al, 20h ; numer rejestru, który chcemy odczytać out 22h, al ; wysyłamy go na port adresu out 0edh, al ; opóźnienie in al, 23h ; odczytujemy dane z portu danych
Informacje o portach równoległych znajdują się w bitach 5 i 6 odczytanego bajtu. Jeśli bity te mają wartość 0, to porty równoległe pracują w trybie zgodności z ISA, jeśli 1 - w trybie zgodności z PS/2, jeśli 2 - w trybie EPP, jeśli 3 - w trybie ECP.
Karta ta ma dwa podstawowe porty: port adresu i stanu - 388h (do odczytu i zapisu) oraz port danych - 389h (tylko do zapisu). By zapisać coś do jednego z 244 rejestru karty, wysyłamy jego numer na port 388h, po czym wysyłamy dane na port 389h. Algorytm wykrywania karty składa się z następujących kroków (źródło: Programming the AdLib/Sound Blaster FM Music Chips, Version 2.0 (24 Feb 1992), Copyright © 1991, 1992 by Jeffrey S. Lee):
Między każdym zapisem do portu adresu i wysłaniem danych należy odczekać 12 cykli karty. Po zapisaniu danych należy odczekać 84 cykle karty, zanim jakakolwiek kolejna operacja będzie mogła zostać wykonana. Ale że wygodniej jest operować w języku operacji niż cykli procesora karty, te czasy oczekiwania wynoszą odpowiednio: 6 i 35 razy czas potrzebny na odczytanie portu adresu. Ja w razie czego użyję odpowiednio: 10 i 40 operacji.
Do wykrywania karty AdLib może posłużyć więc następujący kod:
pisz_adlib 4, 60h pisz_adlib 4, 80h mov dx, 388h in al, dx mov bl, al ; zachowanie stanu w kroku 3 pisz_adlib 2, 0FFh pisz_adlib 4, 21h mov ah, 86h xor cx, cx mov dx, 100 int 15h ; wykonanie pauzy na 100 mikrosekund jc blad mov dx, 388h in al, dx mov bh, al ; zachowanie stanu w kroku 7 pisz_adlib 4, 60h pisz_adlib 4, 80h and bx, 0E0E0h cmp bx, 0C000h ; sprawdzenie obu wyników (kroki 3 i 7) na raz je jest_adlib ; tu nie ma AdLib
gdzie makro pisz_adlib
wygląda tak:
%imacro pisz_adlib 2 ; %1 - numer rejestru, %2 - dane do wysłania mov dx, 388h mov al, %1 out dx, al mov cx, 10 %%loop1: ; opóźnienie pierwsze in al, dx loop %%loop1 inc dx ; port 389h mov al, %2 out dx, al dec dx mov cx, 40 %%loop2: ; opóźnienie drugie in al, dx loop %%loop2 %endm
Karta SoundBlaster może być zaprogramowana do korzystania z różnych portów podstawowych. Najczęściej spotykana wartość to 220h, ale możliwe są też między innymi 210h, 230h, 240h, 250h, 260h i 280h. Struktura jest podobna, jak w karcie AdLib: zakładając, że port bazowy to 220h, to dla lewego kanału portem adresu jest 220h, a portem danych - 221h, zaś dla prawego - odpowiednio 222h i 223h. Porty karty AdLib - 388h i 389h - służą do operacji na obu kanałach.
Wykrywanie tej karty przebiega tak samo, jak dla karty AdLib (procedura 9 kroków powyżej), ale skoro porty bazowe mogą być różne, proponuję następującą modyfikację makra do wysyłania danych:
%imacro pisz_sb 3 ; %1 - port bazowy, %2 - numer rejestru, %3 - dane mov dx, %1 mov al, %2 out dx, al mov cx, 6 %%loop1: ; opóźnienie pierwsze in al, dx loop %%loop1 inc dx ; port danych mov al, %3 out dx, al dec dx mov cx, 35 %%loop2: ; opóźnienie drugie in al, dx loop %%loop2 %endm
Jeśli BIOS wykryje jakieś dyski twarde, ich liczbę wpisuje do komórki pamięci pod adresem
0040h:007Eh (1 bajt).
Zakresy portów kontrolerów dysków twardych to:
01F0h-01F7h (pierwszy kontroler), 0170h-0177h (drugi). Są jeszcze 2 kontrolery, opisane
jako EIDE: 01E8h-01EFh (trzeci kontroler) i 168h-016Fh (czwarty).
Każdy kontroler może obsłużyć dwa dyski - Master i Slave. Wyboru dysku, na którym wykonywane są operacje, dokonuje się, zapisując do portu baza+6 (gdzie baza to 01F0h, 0170h, 01E8h lub 168h). Bity 7 i 5 muszą być równe 1, a bitem czwartym wybiera się dysk (0=pierwszy, 1=drugi).
Komendy wysyła się do portu baza+7, a dane (po 512 bajtów)
odczytuje się z portu bazowego. Przed wysłaniem komend należy sprawdzić, czy kontroler lub
dysk nie są zajęte. Robi się to odczytując port stanu, będący zarazem portem komend (czyli baza+7).
Bit 7 mówi, czy kontroler jest zajęty (powinien być równy zero), bit 6 - czy dysk jest gotowy
do operacji (powinien być równy 1), bit 4 - czy dysk przeszedł na właściwą pozycję (powinien
być równy 1). Reszta bitów jest nieistotna, jeśli chodzi o wysyłanie komend.
Portu statusu można użyć też, obok portu baza+1, do wykrywania błędów.
Możemy już więc napisać taki oto kod:
mov dx, 1f7h spr_dysk: in al, dx cmp al, 50h ; dysk gotowy, kontroler niezajęty jnz spr_dysk
Gdy dysk jest gotów na przyjmowanie komend, można zacząć wysyłać nasze żądania. Najpierw ustawiamy, do którego dysku będziemy chcieli wysyłać dane:
mov dx, 1f6h mov al, 10100000b ; bit 4 = 0, wiec pierwszy dysk out dx, al
Po tym, w razie czego, sprawdzamy ponownie gotowość dysku poprzednim kodem. Jeśli dysk jest gotów, wysyłamy komendę:
mov dx, 1f7h mov al, 0ech ; kod rozkazu identyfikacji out dx, al
Przed odczytaniem danych musimy jednak sprawdzić nie tylko, czy dysk już jest gotów (czy skończył przetwarzać żądanie), ale też to, czy dane już są gotowe do odebrania. Sprawdzamy to podobnie, jak poprzednio, zamieniając tylko 50h na 58h (co dodatkowo sprawdza, czy bufor sektorów dysku wymaga obsługi - czyli czy są już dla nas dane):
mov dx, 1f7h spr_dysk: in al, dx cmp al, 58h ; dysk gotowy, kontroler niezajęty, są dane jnz spr_dysk
Po sprawdzeniu, że dane są dostępne, odbieramy je, lecz w nietypowy sposób: zamiast odbierać po jednym bajcie, odbieramy pod dwa na raz, do rejestru AX, po czym zamieniamy jego połówki miejscami. Jest to związane ze sposobem wysyłania danych przez dysk. Kod wygląda tak:
mov cx, 512/2 ; tyle słów do przeczytania mov dx, 1f0h ; stąd czytać xor di, di ; wskaźnik do bufora czytaj: in ax, dx ; wczytaj 2 bajty z portu DX xchg al, ah ; zamień połówki miejscami mov [bufor+di], ax ; zapisz wynik do bufora add di, 2 ; przejdź na kolejną pozycję w buforze loop czytaj ... bufor: times 513 db 0 ; dość, by pomieścić 1 sektor
Dysk zwraca nam 512 bajtów. Model dysku znajdziecie pod adresem 14h w buforze, ma on długość 10 słów (20 bajtów). Numer seryjny jest pod adresem 36h w buforze, ma on długość 20 słów (40 bajtów). W obu tych przypadkach, jeśli pierwszym słowem pod wskazanym adresem jest zero, to dysk nie podał tych informacji.
Pozyskanie tych informacji od napędów optycznych (CD, DVD) różni się tylko kodem operacji - zamiast ECh jest to A1h.
Wykrywanie typów napędów dyskietek jest znacznie prostsze niż w przypadku dysków twardych. W czasie uruchamiania komputera, BIOS wyszukuje napędy dyskietek i wpisuje je do CMOSu, skąd można je łatwo odczytać. Ze te informacje odpowiada bajt numer 10h. Odczytanie go wygląda tak:
mov al, 10h ; numer bajtu do odczytania out 70h, al ; port adresu CMOSu out 0edh, al ; opóźnienie in al, 71h ; odczytanie wartości z portu danych CMOSu
Starsze 4 bity odczytanego bajtu odpowiadają pierwszemu napędowi, młodsze - drugiemu. I tak: wartość 0 oznacza brak danego napędu, 01h - 5,25 cala 360 kB, 02h - 5,25 cala 1,2 MB, 03h - 3,5 cala 720 kB, 04h - 3,5 cala 1,44 MB, 05h - 3,5 cala 2,88 MB.
Ogólnie wykrywanie myszy jako urządzenia może być dość skomplikowane, nie tylko ze względu na różnorodność złączy (szeregowa, PS/2, USB), ale także ze względu na różnorodność protokołów komunikacji z myszami. Wszystko to na szczęście jest zawarte w otwartym sterowniku myszy dla DOSa - CuteMouse. Sterownik jest napisany w asemblerze i można go pobrać oraz obejrzeć jego kod źródłowy za darmo.
Jeśli wystarczy Wam wiedzieć, czy jest załadowany jakikolwiek sterownik do myszy (co wskazywałoby na istnienie myszy), wystarczy taki oto kod:
xor ax, ax mov es, ax les di, [es:33h << 2] ; sprawdź, czy wektor przerwania ; sterownika myszy nie jest zerem mov ax, es or ax, di jz brak_myszy mov al, [es:di] cmp al, 0cfh ; sprawdź, czy procedura obsługi ; przerwania myszy nie składa się ; wyłącznie z instrukcji iret je brak_myszy xor ax, ax int 33h ; sprawdź, czy sterownik zgłasza mysz test ax, ax jz brak_myszy