   #Start Prev Next Contents

   Jak pisac programy w jezyku asembler pod Linuksem?

Czesc 7 - Porty, czyli lacznosc miedzy procesorem a innymi urzadzeniami

   Nie zastanawialiscie sie kiedys, jak procesor komunikuje sie z tymi
   wszystkimi urzadzeniami, ktore znajduja sie w komputerze?
   Teraz zajmiemy sie wlasnie sposobem, w jaki procesor uzyskuje dostep
   do urzadzen zewnetrznych (zewnetrznych dla procesora, niekoniecznie
   tych znajdujacych sie poza obudowa komputera).

   Mimo ze procesor moze porozumiewac z urzadzeniami przez wydzielone
   obszary RAM-u, to glownym sposobem komunikacji (gdy nie chcemy lub nie
   mozemy uzywac sterownikow) ciagle pozostaja porty. Jesli chcecie,
   mozecie wykonac komende "cat /proc/ioports", ktora powie, ktore
   urzadzenie zajmuje ktore porty.

   Porty sa to specjalne adresy, pod ktore procesor moze wysylac dane.
   Stanowia oddzielna "strefe" adresowa (16-bitowa, jak dalej zobaczymy,
   wiec najwyzszy teoretyczny numer portu wynosi 65535), choc czasami do
   niektorych portow mozna dostac sie przez pamiec RAM. Sa to porty
   mapowane do pamieci (memory-mapped), ktorymi nie bedziemy sie
   zajmowac.

   Lista przerwan Ralfa Brown'a (RBIL) zawiera plik ports.lst (ktory
   czasami trzeba osobno utworzyc - szczegoly w dokumentacji). W pliku
   tym znajduja sie szczegoly dotyczace calkiem sporej liczby portow
   odpowiadajacym roznym urzadzeniom. I tak, mamy na przyklad
     * Kontrolery DMA
     * Programowalny kontroler przerwan (Programmable Interrupt
       Controller, PIC)
     * Programowalny czasomierz (Programmable Interval Timer, PIT)
     * Kontroler klawiatury
     * CMOS
     * SoundBlaster i inne karty dzwiekowe
     * Karty graficzne i inne karty rozszerzen (na przyklad modem)
     * Porty COM, LPT
     * Kontrolery dyskow twardych
     * i wiele, wiele innych...

   No dobrze, wiemy co ma ktory port i tak dalej, ale jak z tego
   skorzystac?

   Procesor posiada dwie instrukcje przeznaczone specjalnie do tego celu.
   Sa to IN i OUT.
   Ich podstawowa skladnia wyglada tak:
        in al/ax/eax, numer_portu
        out numer_portu, al/ax/eax

   Uwagi:
    1. Jesli numer_portu jest wiekszy niz 255, to w jego miejsce musimy
       uzyc rejestru DX
    2. Do operacji na portach nie mozna uzywac innych rejestrow niz AL,
       AX lub EAX.
    3. Wczytane ilosci bajtow zaleza od rejestru, a ich pochodzenie - od
       rodzaju portu:
          + jesli port num jest 8-bitowy, to
            IN AL, num wczyta 1 bajt z portu o numerze num
            IN AX, num wczyta 1 bajt z portu num (do AL) i 1 bajt z portu
            num+1 (do AH)
            IN EAX, num wczyta po 1 bajcie z portow num, num+1, num+2 i
            num+3 i umiesci w odpowiednich czesciach rejestru EAX (od
            najmlodszej)
          + jesli port num jest 16-bitowy, to
            IN AX, num wczyta 2 bajty z portu o numerze num
            IN EAX, num wczyta 2 bajty z portu o numerze num i 2 bajty z
            portu o numerze num+1
          + jesli port num jest 32-bitowy, to
            IN EAX, num wczyta 4 bajty z portu o numerze num
    4. Podobne uwagi maja zastosowane dla instrukcji OUT

   Teraz bylaby dobra pora na jakis przyklad (majac na uwadze dobro
   swojego komputera, NIE URUCHAMIAJ PONIZSZYCH KOMEND):
        in      al, 0   ; pobierz bajt z portu 0
        out     60h, eax; wyslij 4 bajty na port 60h

        mov     dx, 300 ; 300 > 255, wiec musimy uzyc DX
        in      al, dx  ; wczytaj 1 bajt z portu 300
        out     dx, ax  ; wyslij 2 bajty na port 300

   Nie rozpisywalem sie tutaj za bardzo, bo ciekawsze i bardziej
   uzyteczne przyklady znajduja sie w moich mini-kursach (programowanie
   diod na klawiaturze, programowanie glosniczka).

   Jak juz wspomnialem wczesniej, porty umozliwiaja dostep do wielu
   urzadzen. Jesli wiec chcesz poeksperymentowac, nie wybieraj portow
   zajetych na przyklad przez kontrolery dyskow twardych, gdyz zabawa
   portami moze prowadzic do utraty danych lub uszkodzenia sprzetu.
   Dlatego wlasnie w nowszych systemach operacyjnych (tych pracujacych w
   trybie chronionym, jak na przyklad Linux) dostep do portow jest
   zabroniony dla zwyklych aplikacji (o prawa dostepu do portow trzeba
   prosic system operacyjny - zaraz zobaczymy, jak to zrobic).
   Jak wiec dzialaja na przyklad stare DOS-owe gry? Odpowiedz jest
   prosta: nie dzialaja w trybie chronionym. Windows uruchamia je w
   trybie udajacym tryb rzeczywisty (taki, w jakim pracuje DOS), co
   umozliwia im pelna kontrole nad sprzetem.
   Wszystkie programy, ktore dotad pisalismy tez uruchamiaja sie w tym
   samym trybie, wiec maja swobode w dostepie na przyklad do glosniczka
   czy karty dzwiekowej. Co innego programy pisane w nowszych
   kompilatorach na przyklad jezyka C - tutaj moze juz byc problem. Ale
   na szczescie my nie musimy sie tym martwic...

   Jeszcze jeden ciekawy przyklad - uzywanie CMOSu. CMOS ma 2 podstawowe
   porty: 70h, zwany portem adresu i 71h, zwany portem danych. Operacje
   sa proste i skladaja sie z 2 krokow:
    1. Na port 70h wyslij numer komorki (1 bajt), ktora chcesz odczytac
       lub zmienic. Polecam plik cmos.lst z RBIL, zawierajacy szczegolowy
       opis komorek CMOS-u
    2. Na port 71h wyslij dane, jesli chcesz zmienic komorke lub z portu
       71h odczytaj dane, jesli chcesz odczytac komorke

   Oto przyklad. Odczytamy tutaj czas w komputerze, a konkretnie -
   sekundy:
        mov     eax, 101        ; funkcja systemowa "sys_ioperm":
        mov     ebx, 70h        ; poczynajac od portu 70h
        mov     ecx, 20         ; tyle bajtow bedziemy mogli wyslac/odebrac
        mov     edx, 71h        ; koncowy numer portu
        int     80h             ; niestety, musimy byc rootem

        cmp     eax, 0          ; sprawdzamy, czy blad. Nie wiem,
                                ; co ta funkcja ma
                                ; zwracac, ale ten sposob zdaje
                                ; sie dzialac

        jl      koniec          ; jesli wystapil blad, to zapis do
                                ; portow, do ktorych nie mamy uprawnien,
                                ; zakonczy sie  "Segmentation fault"
                                ; ( "Naruszenie ochrony pamieci" )
        mov     al, 0
        out     70h, al

                                ; ustaw przerwe na milion nanosekund, czyli
                                ; jedna milisekunde
        mov     dword [ts1+timespec.tv_sec], 0
        mov     dword [ts1+timespec.tv_nsec], 1000000
; w FASMie:
;       mov     dword [ts1.tv_sec], 0
;       mov     dword [ts1.tv_nsec], 1000000

        mov     eax, 162        ; sys_nanosleep
        mov     ebx, ts1        ; adres struktury timespec
        mov     ecx, 0          ; adres wynikowej struktury timespec
        int     80h             ; wykonaj przerwe w programie

        in      al, 71h

    koniec:
        ; ...

; w FASMie:
;       segment readable writeable
section .data

; w FASMie:
;struc timespec
;{
;       .tv_sec:                rd 1
;       .tv_nsec:               rd 1
;}
;
;ts1: timespec

struc timespec
                .tv_sec:        resd 1
                .tv_nsec:       resd 1
endstruc

ts1 istruc timespec

   Wszystko jasne, oprocz bloku z wywolaniem sys_nanosleep. Po co to
   komu, pytacie?
   Przy wspolczesnych czestotliwosciach procesorow, CMOS (jak z reszta i
   inne uklady) moze po prostu nie zdazyc z odpowiedzia na nasza prosbe,
   gdyz od chwili wyslania numeru komorki do chwili odczytania danych
   mija za malo czasu. Dlatego robimy sobie przerwe na kilkanascie taktow
   zegara procesora.
   Kiedys miedzy operacjami na CMOSie zwyklo sie pisac jmp short $+2, co
   tez oczywiscie nie robilo nic, poza zajmowaniem czasu (to jest po
   prostu skok o 2 bajty do przodu od miejsca, gdzie zaczyna sie ta
   dwubajtowa instrukcja, czyli skok do nastepnej instrukcji), ale ta
   operacja juz nie trwa wystarczajaco dlugo, aby ja dalej stosowac.

   Komunikacja z urzadzeniami nie zawsze jednak musi wymagac uprawnien
   administratora i korzystania z funkcji sys_ioperm. Sporo rzeczy (na
   przyklad z klawiatura) mozna zrobic, korzystajac z funkcji sys_ioctl.

   W dzisiejszych czasach porty juz nie sa tak czesto uzywane, jak byly
   kiedys. Jest to spowodowane przede wszystkim wspomnianym trybem
   chronionym oraz tym, ze wszystkie urzadzenia maja juz wlasne
   sterowniki (majace wieksze uprawnienia do manipulowania sprzetem),
   ktore zajmuja sie wszystkimi operacjami I/O. Programista musi jedynie
   uruchomic odpowiednia funkcje i niczym sie nie przejmowac.

   Dawniej, portow uzywalo sie do sterowania grafika czy wysylania
   dzwiekow przez glosniczek lub karty dzwiekowe. Teraz tym wszystkim
   zajmuje sie za nas system operacyjny. Dzieki temu mozemy sie uchronic
   przed zniszczeniem sprzetu.

   Mimo iz rola portow juz nie jest taka duza, zdecydowalem sie je
   omowic, gdyz po prostu czasami moga sie przydac. I nie bedziecie
   zdziwieni, gdy ktos pokaze wam kod z jakimis dziwnymi instrukcjami IN
   i OUT...

   Szczegoly dotyczace instrukcji dostepu do portow takze znajdziecie,
   jak zwykle, u AMD i Intela.

   Milej zabawy.

   Poprzednia czesc kursu (klawisz dostepu 3)
   Kolejna czesc kursu (klawisz dostepu 4)
   Spis tresci off-line (klawisz dostepu 1)
   Spis tresci on-line (klawisz dostepu 2)
   Ulatwienia dla niepelnosprawnych (klawisz dostepu 0)
     _________________________________________________________________

Cwiczenia

    1. Zapoznaj sie z opisem CMOSu i napisz program, ktory wyswietli
       biezacy czas w postaci gg:mm:ss (z dwukropkami). Pamietaj o
       umieszczeniu opoznien w swoim programie i o uprawnieniach.
