Nästa Föregående Innehållsförteckning

2. Att använda I/O portar i C-program

2.1 Det vanliga sättet

Rutiner för att nå I/O-portar finns i/usr/include/asm/io.h (eller linux/include/asm-i386/io.h i 'the kernel source distribution'). Rutinerna där är inline makron, så det räcker att göra #include <asm/io.h>, inga ytterligare bibliotek behövs.

Beroende på brister i gcc (åtminstone i 2.7.2.3 och lägre) och i egcs (alla versioner), måste du kompilera källkod som använder dessa rutiner med optimeringsflaggan på (gcc -O1 eller högre), eller alternativt #define extern till ingenting, (dvs. #define extern på en i övrigt blank rad) innan du gör #include <asm/io.h>.

Om du vill avlusa, 'debug', kan du använda gcc -g -O (åtminstone med moderna versioner av gcc), även om optimeringen ibland gör att debuggern beter sig lite underligt. Om detta besvärar dig, kan du lägga de rutiner som anropar I/O-portarna i separata filer och kompilera endast dem med optimeringsflaggan på.

Innan du anropar en port, måste du ge ditt program tillstånd till detta. Detta gör man genom att anropa ioperm() funktionen (som finns deklarerad i unistd.h, och definierad i 'kernel') någonstans i början av ditt program, innan någon I/O-port anropas. Syntaxen är ioperm(from, num, turn_on), där from är den första portadressen som ska ges tillstånd och num är antalet konsekutiva adresser. Till exempel, ioperm(0x300, 5, 1) ger tillstånd till portarna 0x300 till 0x304 (totalt 5 portar). Det sista argumentet är en bool som specificerar om du vill ge access­tillstånd (true(1)) eller ta bort tillståndet (false(0)). Du kan använda ioperm() upprepade gånger för att ge tillstånd till icke­konsekutiva port­adresser. Se ioperm(2) manualen.

ioperm() anropet kräver att ditt program har root­privilegier. Det krävs att du antingen kör som root, eller gör setuid root. Du kan släppa root­privilegierna så snart du har anropat ioperm(). Det är inte nödvändigt att explicit släppa dina accessrättigheter med ioperm(..., 0) mot slutet av ditt program. Detta sker automatiskt när processen avslutas.

Om du gör setuid() till en non-root user förstörs inte de accessrättigheter som är redan givna av ioperm(), men fork() förstör dem (child processen får inga rättigheter, men parent behåller dem).

ioperm() kan endast ge access rättigheter till portarna 0x000 - 0x3ff. För att komma åt högre portadresser, kan man använda iopl(), som ger access till alla portar på en gång. Använd nivå 3 (dvs iopl(3)) för att ge ditt program tillgång till alla portar. (Men var försiktig - att skriva på fel port kan orsaka allehanda otrevliga saker med din dator). Du behöver root­privilegier för att anropa iopl(). Se iopl(2) manualen.

Sedan, för att komma åt portarna... För att läsa in en byte, (8 bitar) från en port, call inb(port), den returnerar den byte den läser. För att ställa ut, call outb(value,port) (notera parameter­ordningen). För att läsa in 16 bitar från port x och x+1 ,en byte från vardera, call inw(x) och för att ställa ut, call outw(value,x). Är du osäker på om du skall använda byte eller word instruktioner, är det troligen inb() och outb() - flertalet apparater konstrueras för bytevis portaccess. Notera att alla portaccesser tar åtminstone cirka en mikrosekund att utföra.

För övrigt fungerar makroanropen inb_p(), outb_p(), inw_p(), och outw_p() på samma sätt som ovannämnda, förutom att de lägger till ca en mikrosekund efter varje portaccess. Du kan göra fördröjningen ännu längre, ca 4 mikrosekunder, med #define REALLY_SLOW_IO innan du gör #include <asm/io.h>. Dessa makron gör normalt (såvida du inte gör #define SLOW_IO_BY_JUMPING, vilket blir mindre noggrant) access till port 0x80 för att skapa delay, så du behöver först ge accessrätt till port 0x80 med ioperm(). (Skrivning på port 0x80 påverkar ingenting). För mer flexibla delay-metoder, läs vidare.

Det finns sidor till ioperm(2), iopl(2) och ovannämnda makron i någorlunda färska utgåvor av Linux manual.

2.2 En alternativ metod: /dev/port

Ett annat sätt att komma åt I/O-portar är open() /dev/port (en 'character device', major number 1, minor 4) för läsning och/eller skrivning (stdio f*() funktionerna har intern buffring, så använd inte dem). Gör sedan lseek() till den aktuella byten i filen (fil position 0 = port 0x00, fil position 1 = 0x01, och så vidare), och read() eller write() en byte eller ett ord till eller från den.

Naturligtvis behöver ditt program accessrättigheter till /dev/port för att metoden skall fungera. Denna metod är sannolikt långsammare än den normala metoden enligt ovan, men behöver varken optimerings­flaggan vid kompilering eller ioperm(). Det behövs inte heller 'root access', bara du ger 'non-root user' eller 'group' access till /dev/port - låt vara att detta är dumt ur systemsäkerhetssynpunkt, eftersom det är möjligt att skada systemet, kanske till och med vinna 'root access', genom att använda /dev/port för att komma åt hårddisk, nätverkskort, etc. direkt.


Nästa Föregående Innehållsförteckning