Linux I/O port programming mini-HOWTO: Używanie rejestrów wejścia/wyjścia w programach napisanych w C
Następna strona
Poprzednia strona
Spis treści
2. Używanie rejestrów wejścia/wyjścia w programach napisanych w C
2.1 Metoda zwykła
Procedury dostępu do portów (rejestrów) we/wy umieszczone są w
/usr/include/asm/io.h (lub w linux/include/asm-i386/io.h w źródłach jądra)
Procedury te występują w formie makr do wstawienia a więc wystarczy że zrobisz #include <asm/io.h>;
nie potrzebujesz już żadnych dodatkowych bibliotek.
Z powodu ograniczeń w gcc (obecna wersja 2.7.2.3 i poniżej) oraz egcs
(wszystkie wersje) programy wykorzystujące te procedury muszą być kompilowane
z włączoną optymalizacją (gcc -O1 lub więcej) lub możesz też
zrobić #define extern bez parametru (puste) zanim włączysz <asm/io.h>.
Do odpluskwiania (debugging) możesz użyć gcc -g -O (przynajmniej w nowoczesnych
wersjach gcc), chociaż optymalizacja może spowodować, iż debugger będzie
zachowywał się trochę dziwnie. Jeśli to sprawia Ci kłopot, procedury korzystające
z rejestrów we/wy wstaw do osobnego pliku i tylko ten plik poddaj optymalizacji
podczas kompilacji.
Zanim dostaniesz się do jakiegokolwiek portu we/wy, musisz nadać swemu
programowi uprawnienia do tego. Robi się to wywołując funkcję ioperm()
(zdeklarowaną w unistd.h i zdefiniowaną w jądrze) gdzieś w okolicach początku
programu (przed jakimkolwiek dostępem do portów) Składnia tego polecenia
to ioperm(skąd,ile,włacz) gdzie skąd jest pierwszym portem do którego
chcesz mieć dostęp a ile liczbą kolejnych portów do których chcesz mieć dostęp.
Dla przykładu ioperm(0x300, 5, 1) da ci dostęp do portów od 0x300 do 0x304
(w sumie pięc portów). Ostatni argument to wartośc logiczna mówiąca
o tym czy program otrzyma dostęp do portów (1 - prawda) bądź nie (0 - fałsz).
Możesz wywoływać ioperm() wiele razy aby włączyć wiele nieciągłych obszarów
rejestrów. Zajrzyj do ioperm(2) w podręczniku systemowym man po szczegóły
odnośnie składni.
Wywołanie ioperm() wymaga aby twój program miał uprawnienia root'a:
wobec czego albo musisz uruchamiać go jako root albo dać mu suid root'a.
Po wywołaniu ioperm() możesz już zrezygnować z uprawnień root'a.
Nie wymaga się abyś w sposób jawny wyłączał uprzywilejowany dostęp
do portów (czyli ioperm(....,0)) na końcu programu; robi się to automatycznie
wraz z zakończeniem procesu.
Funkcja setuid() w wypadku użytkownika bez przywilejów root'a nie odbiera
dostępu do portów przydzielonego przez ioperm() ale funkcja fork() już
tak. (proces potomny nie dostaje dostępu ale proces-rodzic go zachowuje)
ioperm() może jedynie umożliwić dostęp do portów od 0x000 do 0x3FF. Jeśli
chcesz używać innych portów (wyższych) musisz użyć funkcji iopl() (która
daje ci dostęp do wszystkich portów od razu) Użyj argumentu 3 (czyli iopl(3))
aby dać swemu programowi dostęp do wszystkich portów (bądź ostrożny
- dostęp do niewłaściwych portów może wywołać najróżniejsze usterki komputera).
Musisz mieć uprawnienia root'a aby wywołać iopl(). Po szczególy
zajrzyj do podręcznika systemowego man: iopl(2)
Teraz właściwy dostęp do portów.. Aby odczytać bajt (8 bitów) z
portu, wywołaj funkcję inb(port), zwraca ona odczytaną wartość.
Aby zapisać bajt do portu wywołaj outb(wartość,port) (zwróć uwagę na kolejność argumentów)
Aby odczytać słowo (16 bitów) z portów x i x+1 (jeden bajt z każdego łączone
w słowo za pomocą asemblerowej instrukcji inw) wywołaj inw(x) Aby zapisać
słowo do tych dwóch portów użyj outw(wartość,x) Jeśli nie jesteś pewien
której instrukcji użyć (operującej bajtem czy słowem) prawdopodobnie będziesz
chciał użyć inb() i outb() - większość urządzeń zaprojektowana jest z
bajtowo-zorientowanym dostępem do portów.
Zauważ, że wykonanie każdej instrukcji operującej na portach zajmuje co najmniej mikrosekundę.
Makra inb_p(),outb_p(),inw_p() i outw_p() działają identycznie z tymi
powyżej ale dodatkowo wykonują opóźnienie (około 1 mikrosekundy) po dostępie
do portu. Możesz spowodować że opóźnienie będzie wartości ok 4 mikrosekund
jeśli zrobisz #define REALLY_SLOW_IO zanim włączysz <asm/io.h>
Makra te zwykle (chyba że zrobisz #define SLOW_IO_BY_JUMPING, które jest raczej
mniej dokładne) używają zapisu do portu 0x80 zby uzyskać opóźnienie, wobec
czego musisz najpierw dać dostęp do portu 0x80 za pomocą ioperm(). Zapisy
do portu 0x80 nie powinny mieć wpływu na żadną częsć systemu. Jeśli chcesz
poznać bardziej wszechstronne metody dostępu do portów czytaj dalej.
W stosunkowo nowych dystrybucjach podręcznika systemowego man znajdują
się strony ioperm(2), iopl(2) oraz powyższych makr.
2.2 Metoda alternatywna: /dev/port
Innym sposobem dostępu do rejestrów I/O jest otworzenie urządzenia
/dev/port do zapisu lub/i odczytu za pomocą funkcji open()
(/dev/port to urządzenie znakowe, numer główny 1, poboczny 4)
(Funkcje f*() z biblioteki stdio mają wewnętrzne buforowanie więc ich unikaj)
Następnie wywołaj lseek() do odpowiedniego bajtu w tym pliku
(pozycja 0 = port 0x00 pozycja 1 = port 0x01 itd) i czytaj (read())
lub zapisuj (write()) bajt lub słowo.
Oczywiście aby to zadziałało twój program musi mieć możliwośc czytania i zapisywania do /dev/port.
Metoda ta jest prawdopodobnie wolniejsza od metody normalnej pokazanej powyżej
ale nie potrzebuje ani optymalizacji programu ani wywoływania ioperm().
Nie potrzebuje też uprawnień root'a jeśli nadasz zwykłym użytkownikom lub jakiejś
grupie prawa dostępu do /dev/port - jest to jednak nieporządane z punktu widzenia
bezpieczeństwa systemu, jako że możliwe jest wtedy uszkodzenie systemu,
a może nawet zdobycie przywilejów root'a przez bezpośredni dostęp
za pomocą /dev/port do dysków, kart sieciowych itp.
Następna strona
Poprzednia strona
Spis treści