Wykrywanie sprzętu

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ą.



Wykrywanie ilości zainstalowanej pamięci RAM


(przeskocz wykrywanie pamięci)

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 szeregowych i równoległych


(przeskocz wykrywanie portów)

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.



Wykrywanie karty dźwiękowej AdLib


(przeskocz wykrywanie AdLib)

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):

  1. wyzerowanie obu czasomierzy poprzez zapisanie 60h do rejestru 4.
  2. włączenie przerwań, zapisując 80h do rejestru 4. UWAGA: to musi być krok oddzielny od pierwszego
  3. odczytanie stanu karty (port 388h) i zachowanie wyniku
  4. zapisanie FFh do rejestru 2 (czasomierz 1)
  5. uruchomienie czasomierza 1 poprzez zapisanie 21h do rejestru 4. Czasomierz 1 będzie zwiększał wartość zapisaną do rejestru 2 o 1 co każde 80 mikrosekund.
  6. odczekanie co najmniej 80 mikrosekund
  7. odczytanie stanu karty (port 388h) i zachowanie wyniku
  8. wyzerowanie czasomierzy i przerwań (krok 1 i 2)
  9. wyniki kroków 3 i 7 ANDować bitowo z wartością E0h. Wynikiem z kroku 3 powinna być wartość 0, a z kroku 7 - C0h. Jeśli obie się zgadzają, w komputerze zainstalowana jest karta AdLib.

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


Wykrywanie karty dźwiękowej SoundBlaster


(przeskocz wykrywanie SB)

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


Wykrywanie zainstalowanych dysków twardych


(przeskocz wykrywanie dysków)

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 napędów dyskietek


(przeskocz wykrywanie napędów dyskietek)

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.



Wykrywanie myszy


(przeskocz wykrywanie myszy)

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


Spis treści off-line (Alt+1)
Spis treści on-line (Alt+2)
Ułatwienia dla niepełnosprawnych (Alt+0)