Linux I/O port programmering mini-HOWTO Av: Riku Saikkonen Översättning till svenska: Sven Wilhelmsson, v ,28 December 1997 , Översatt: 10 Augusti 1998 I detta HOWTO dokument beskrivs hur man programmerar I/O portar samt hur man fördröjer eller mäter korta tidsintervall i user-mode Linux program för Intel x86 arkitekturen. ______________________________________________________________________ Innehållsförteckning 1. Inledning 2. Att använda I/O portar i C-program 2.1 Det vanliga sättet 2.2 En alternativ metod: 3. Avbrott (IRQs) och DMA 4. Högupplösande timing 4.1 Fördröjningar 4.1.1 (TT 4.1.2 nanosleep() 4.1.3 Fördröjningar med port I/O 4.1.4 Att fördröja med assemblerinstruktioner 4.1.5 rdtsc() för Pentium 4.2 Att mäta tid 5. Andra programmeringsspråk 6. Några användbara portar 6.1 Parallellporten 6.2 Spelporten (joystick) 6.3 Serieporten 7. Tips 8. Felsökning 9. Kod exempel 10. Erkännande ______________________________________________________________________ 1. Inledning I detta HOWTO dokument beskrivs hur man når I/O-portar och hur man fördröjer korta tidsintervall i user-mode Linux program som körs på Intel x86 arkitekturen. Detta dokumentet är en uppföljare till den mycket lilla IO-Port mini-HOWTO från samma författare. This document is Copyright 1995-1997 Riku Saikkonen. See the Linux HOWTO copyright for details. If you have corrections or something to add, feel free to e-mail me (Riku.Saikkonen@hut.fi)... Ändringar från tidigare publicerad version (Mar 30 1997): · Förklaringar angående inb_p/outb_p/ och port 0x80. · Tagit bort information om udelay(), eftersom nanosleep() medger ett renare sätt att göra samma sak. · Konverterat till Linuxdoc-SGML, och ändrat om något. · Många mindre tillägg och ändringar. 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 , 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 . 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 accesstillstå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 ickekonsekutiva portadresser. Se ioperm(2) manualen. ioperm() anropet kräver att ditt program har rootprivilegier. Det krävs att du antingen kör som root, eller gör setuid root. Du kan släppa rootprivilegierna 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 rootprivilegier 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 parameterordningen). 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 . 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 optimeringsflaggan 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. 3. Avbrott (IRQs) och DMA Man kan inte använda IRQ eller DMA direkt i en usermode process. Man måste skriva en kernel driver; se The Linux Kernel Hacker's Guide Där finns detaljer och kernel källkod som exempel. Man kan heller inte stänga av ett avbrott från ett user-mode program. 4. Högupplösande timing 4.1. Fördröjningar Först och främst måste sägas att det inte går att garantera user mode processer exakt kontroll avseende timing eftersom Linux är ett multiprocess system. Din process kan bli utskyfflad under vad som helst mellan 10 millisekunder upp till några sekunder (om belastningen är hög). Detta spelar emellertid ingen roll för flertalet program som använder I/O-portar. För att reducera effekterna, kan du med hjälp av kommandot nice ge din process hög prioritet. Se nice(2) manualen eller använd real-time scheduling enligt nedan. Om du behöver bättre tidsprecision än vad normala user-mode processer kan ge, så finns vissa förberedelser för 'user-mode real time' support. Linux 2.x kärnor har 'soft real time support', se manualen för sched_setscheduler(2). Det finns en speciell kärna som stöder hård realtid, se för ytterligare information om detta. 4.1.1. sleep() och usleep() Låt oss börja med de lätta funktionsanropen. För att fördröja flera sekunder, är det troligtvis bäst att använda sleep(). Fördröjningar på 10-tals millisekunder (ca 10 ms verkar vara minimum) görs med usleep(). Dessa funktioner frigör CPU för andra processer, så att ingen CPU-tid går förlorad. Se manualerna sleep(3) och usleep(3). Om fördröjningar är på mindre än 50 ms ( beror på din processor och dess belastning), tar det onödigt mycket tid att släppa CPUn, därför att det för Linux scheduler (för x86 arkitekturen) vanligtvis tar minst 10-30 millisekunder innan den återger din process kontrollen. Beroende på detta fördröjer usleep(3) något mer än vad du specificerar i dina parametrar, och alltid minst ca 10 ms. 4.1.2. nanosleep() I 2.0.x serien av Linuxkärnor finns ett nytt systemanrop: nanosleep() (se nanosleep(2) manualen), som möjliggör så korta fördröjningar som ett par mikrosekunder eller mer. Vid fördröjningar på mindre än 2 ms, om (och endast om) din process är satt till soft real time scheduling (med sched_setscheduler()), använder nanosleep() en vänteloop, i annat fall frigörs CPU på samma sätt som med usleep(). Vänteloopen använder udelay() (en intern kernelfunktion som används av många 'kernel drivers'), och loopens längd beräknas med hjälp av BogoMips värdet (det är bara denna sorts hastighet BogoMips värdet mäter noggrant). Se hur det fungerar i /usr/include/asm/delay.h 4.1.3. Fördröjningar med port I/O Ett annat sätt att fördröja ett fåtal mikrosekunder är att använda port I/O. Läsning eller skrivning på port 0x80 (se ovan hur man gör) tar nästan precis 1 mikrosekund oberoende av processortyp och hastighet. Du kan göra det upprepade gånger om du vill vänta ett antal mikrosekunder. Skrivning på denna port torde inte ha några skadliga sidoeffekter på någon standardmaskin och vissa 'kerneldrivers' använder denna metod. det är på detta sättet {in|out}[bw]_p() normalt gör sin fördröjning. (se asmio.h)/. Flertalet port I/O instruktioner i adressområdet 0-0x3ff tar nästan exakt 1 mikrosekund, så om du t.ex. använder parallellporten direkt, gör bara några extra inb() från porten för att skapa fördröjning. 4.1.4. Att fördröja med assemblerinstruktioner Om man känner till processortyp och klockhastighet, kan man hårdkoda korta fördröjningar med vissa assemblerinstruktioner (men kom ihåg, processen kan skyfflas ut när som helst, så fördröjningarna kan ibland bli längre). Tabellen nedan ger några exempel. För en 50MHz processor tar en klockcykel 20 ns. Instruktion i386 klock cykler i486 klock cykler nop 3 1 xchg %ax,%ax 3 3 or %ax,%ax 2 1 mov %ax,%ax 2 1 add %ax,0 2 1 tyvärr känner jag inte till Pentium; förmodligen nära i486. Jag hittar ingen instruktion som tar EN klockcykel i i386. Använd en- cykel instruktioner om du kan, annars kanske pipelinen i moderna processortyper förkortar tiden. Instruktionerna nop och xchg i tabellen bör inte ha några sidoeffekter. Övriga modifierar statusregistret, men det bör inte betyda något eftersom gcc detekterar detta. nop är ett bra val. Om du vill använda dem, skriv call asm("instruktion") i ditt program. Syntaxen ge i tabellen ovan. Vill du göra multipla instruktioner i en asm()-sats, så separera med semikolon. Till exempel exekveras i satsen asm(" nop; nop; nop; nop") fyra nop instruktioner, som fördröjer fyra klockcykler med i486 eller pentium (eller 12 cykler med i386). asm() översätts av gcc till inline assembler kod, så det blir inget overhead med funktionsanrop. Kortare fördröjningar än en klockcykel är inte möjligt med x86 arkitekturen. 4.1.5. rdtsc() för Pentium Med Pentium kan du erhålla antalet klockcykler som gått sedan senaste uppstart med hjälp av följande C kod: ______________________________________________________________________ extern __inline__ unsigned long long int rdtsc() { unsigned long long int x; __asm__ volatile (".byte 0x0f, 0x31" : "=A" (x)); return x; } ______________________________________________________________________ Du kan polla värdet för hur många cykler som helst. 4.2. Att mäta tid För att mäta tider med en sekunds upplösning, är det nog enklast att använda time(). Krävs bättre noggrannhet, ger gettimeofday() cirka en mikrosekunds upplösning (men se ovan angående 'scheduling', utskyffling). För Pentium är rdtsc kodfragmentet ovan noggrant till en klockcykel. Om din process skall ha en signal efter en viss tid, så använd setitimer() eller alarm(). Se manualsidorna. 5. Andra programmeringsspråk Beskrivningen ovan koncentrerar sig på programmeringsspråket C. Det bör vara tillämpbart även på C++ och Objective C. I assembler får man anropa ioperm() eller iopl() som i C, och därefter kan man använda I/O-port read/write instruktionerna direkt. I andra språk, såvida inte du kan infoga inline assembler eller C kod i ditt program eller använda ovannämnda systemanrop, är det nog enklast att skriva en C källkodsfil med funktionerna för I/O- portaccess och separatkompilera och länka den till övriga delar av ditt program. Eller använda /dev/port enligt ovan. 6. Några användbara portar Här följer programmeringsinformation om några portar som direkt kan användas för TTL (eller CMOS) digitala kretsar. Om du vill använda dessa eller andra gängse portar för det ändamål de är ägnade (t.ex. för att styra en printer eller modem), skall du troligen använda en befintlig drivrutin (vanligtvis inkluderad i kärnan) i stället för att programmera dessa portar direkt som beskrivs i detta HOWTO. Denna sektion är avsedd för dem som vill ansluta LCD- displayer, stegmotorer, eller annan speciell elektronik till en PC's standardport. Ska du styra en massproducerad produkt som t.ex. scanner (som har funnits på marknaden ett tag), sök efter en befintlig drivrutin. Hardware-HOWTO är ett bra ställe att börja. är en annan bra källa till information om hur man ansluter utrustning till datorer, och om elektronik i allmänhet. 6.1. Parallellporten Parallellportens basadress (kallad ''BASE'' nedan) är 0x3bc för /dev/lp0, 0x378 för /dev/lp1, och 0x278 för /dev/lp2. Ska du styra något som beter sig som en normal printer, se Printing-HOWTO . Förutom den vanliga output-only moden som beskrivs nedan, finns det en 'extended' bidirektionell mod i flertalet parallellportar. Information om detta och om de nyare ECP/EPP moderna (och om IEEE 1284 standarden allmänt), se och . Kom ihåg att eftersom du inte kan använda IRQs eller DMA i ett user-mode program, så kommer du nog att behöva skriva en 'kernel-driver' för att använda ECP/EPP. Jag tror att någon håller på att skriva en sådan driver men jag känner inte till några detaljer. Porten BASE+0 (Data port) styr data signalerna (D0 till D7 för bitarna 0 to 7, respektive; tillstånden: 0 = låg (0 V), 1 = hög (5 V)). Skrivning på denna port ställer ut data till donet. En läsning returnerar senast skrivna data i standard- eller extended-moden, eller data på stiften från en ansluten apparat i 'extended read mode'. Portarna BASE+1 ('Status port') är 'read-only', och returnerar tillståndet på följande insignaler: · Bits 0 och 1 är reserverade. · Bit 2 IRQ status (inget stift, vet inte hur detta fungerar) · Bit 3 ERROR (1=hög) · Bit 4 SLCT (1=hög) · Bit 5 PE (1=hög) · Bit 6 ACK (1=hög) · Bit 7 -BUSY (0=hög) (Vet inte vilka spänningar som motsvarar hög respektive låg.) Porten BASE+2 ('Control port') är 'write-only' (läsning returnerar senast inskrivna data), och styr följande status signaler: · Bit 0 -STROBE (0=hög) · Bit 1 AUTO_FD_XT (1=hög) · Bit 2 -INIT (0=hög) · Bit 3 SLCT_IN (1=hög) · Bit 4 aktiverar ('enables') parallell portens IRQ (vilket sker på uppflanken hos ACK) när den sätts till 1. · Bit 5 styr 'extended mode direction' (0 = skriv, 1 = läs), den är 'write-only' (läsning på denna bit returnerar ingenting meningsfullt). · Bits 6 och 7 är reserverade. (Återigen, är inte säker på vad som är hög och låg.) Pinout (ett 25-pin D-don , hona ) (i=input, o=output): 1io -STROBE, 2io D0, 3io D1, 4io D2, 5io D3, 6io D4, 7io D5, 8io D6, 9io D7, 10i ACK, 11i -BUSY, 12i PE, 13i SLCT, 14o AUTO_FD_XT, 15i ERROR, 16o -INIT, 17o SLCT_IN, 18-25 Ground IBM specifikationen säger att stiften 1, 14, 16, och 17 ('control- outputs') har 'open-collektor' utgångar dragna till 5 V genom ett 4.7 K motstånd (sänker 20 mA, ger 0.55 mA, högnivå utgång 5.0 V minus eventuellt spänningsfall). Övriga stift sänker 24 mA och ger 15 mA, och deras högnivå spänning är minst 2.4 V. Lågnivå spänningen är i båda fallen minst 0.5 V. Icke-IBM parallell portar avviker troligen från denna standard. För ytterligare information om detta se . Slutligen en varning: Var noga med jordningen. Jag har förstört flera parallellportar genom att ansluta dem med datorn igång. Det kan vara ett bra alternativ att använda parallellportar som inte sitter på moderkortet för sådana här saker. (Du kan antagligen få en andra parallellport med ett billigt standard 'multi-I/O' kort; Stäng bara av de portar du inte behöver, och sätt parallellkortets I/O-adress till en ledig adress. Du behöver inte bekymra dig om parallellportens IRQ, eftersom den vanligtvis inte används.) 6.2. Spelporten (joystick) Spelporten finns på adresserna 0x200-0x207. För att styra normala joystickar finns en kernel-level joystick driver, se , filename joystick-*. Pinout (ett 25-pin D-don , hona ) (i=input, o=output): · 1,8,9,15: +5 V (kraftmatning) · 4,5,12: Ground · 2,7,10,14: Digitala ingångar BA1, BA2, BB1, och BB2, respektive · 3,6,11,13: ''Analoga'' ingångar AX, AY, BX, och BY, respektive +5 V stiften verkar ofta vara direkt anslutna till moderkortets kraftmatning, så de bör klara ganska mycket ström, beroende på moderkort, kraftaggregat och spelport. De digitala ingångarna används till de två joystickarna (joystick A och joystick B, med två knappar vardera) som du kan ansluta till porten. De torde vara normala TTL ingångar, och du kan läsa deras status direkt på statusporten (se nedan). En joystick ger låg (0 V) status när knappen är nedtryckt och eljest hög (5 V från matningen via ett 1K motstånd). De så kallade analoga ingångarna mäter egentligen resistans. Spelportarna har en fyrfaldig one-shot multivibrator (ett 558 chip) anslutet till de fyra ingångarna. På varje ingångsshylsa i kontaktdonet finns ett 2.2K motstånd mellan ingångshylsan och multivibratorns 'open-collector' utgång, och en 0,01 uF 'timing' kondensator mellan multivibratorns utgång och jord. En joystick har en potentiometer för varje axel (X och Y), dragen mellan +5 V och respektive ingångshylsa ( AX och AY för joystick A, eller BX och BY för joystick B). När multivibratorn aktiveras sätts dess utgång hög (5 V) och den inväntar att timing kondensatorn når 3.3 V innan den sänker respektive utgång. På så sätt blir multivibratorns pulslängd proportionell mot potentiometerns resistans (dvs. joystickens position för respektive axel) enligt följande: R = (t - 24.2) / 0.011, där R är potentiometerns resistans och t pulsens längd i mikrosekun­ der. Således, för att läsa dessa analoga ingångar, skall man först aktivera multivibratorn (med en skrivning på porten; se nedan), sedan polla (göra upprepade läsningar) tillståndet för de fyra axlarna tills de går från hög till låg, och på så sätt mäta pulstiden. Denna pollning tar mycket CPU tid och i ett ickerealtids multiprocess system som Linux blir inte resultatet så tillförlitligt eftersom man inte kan polla kontinuerligt, såvida du inte gör en 'kernel-driver' och stänger avbrottsingångar när du pollar (vilket tar ännu mer CPU tid). Om du vet att signalen kommer dröja tiotals millisekunder så kan du anropa usleep() innan du börjar polla för att ge CPU tid till andra processer. Den enda I/O-port som du behöver nå är port 0x201 (de andra portarna beter sig precis likadant eller är inaktiva). En skrivning till porten (spelar ingen roll vad) aktiverar multivibratorn. Läsning från porten returnerar signalernas status: · Bit 0: AX (status (1=high) of the multivibrator output) · Bit 1: AY (status (1=high) of the multivibrator output) · Bit 2: BX (status (1=high) of the multivibrator output) · Bit 3: BY (status (1=high) of the multivibrator output) · Bit 4: BA1 (digital input, 1=high) · Bit 5: BA2 (digital input, 1=high) · Bit 6: BB1 (digital input, 1=high) · Bit 7: BB2 (digital input, 1=high) 6.3. Serieporten Om den apparat som du vill kommunicera med stöder något som liknar RS-232 bör du kunna använda en serieport för att tala med den. Linux drivrutin för serieportar bör räcka för nästan alla tänkbara tillämpningar ( du ska inte behöva programmera serieportarna direkt, och skulle så vara måste du skriva en 'kernel driver'); den är mycket flexibel, så att använda ickestandardiserade bithastigheter torde inte vara något problem. Se termios(3) manualen, eller seriedriverns källkod, (linux/drivers/char/serial.c), och för mer info om hur man programmerar serieportar på Unix system. 7. Tips Om du behöver bra analog I/O kan du ansluta ett ADC- och/eller DAC- chip till parallellporten (tips: kraft kan du ta från anslutningsdonet till spelporten eller från ett don till en yttre diskenhet eller använda ett separat kraftaggregat. Har du strömsnåla kretsar kan du ta kraftmatning från parallellporten. Du kan också köpa ett AD/DA-kort (de flesta äldre/långsammare typerna ansluts till I/O-portar. Eller, om det räcker med 1 eller 2 kanaler och måttlig noggrannhet, köp ett billigt ljudkort som har stöd från Linux sound driver. Ett sådant är också tämligen snabbt. Noggranna analoga apparater störs lätt om jordningen är bristfällig. Om du får problem av detta slag, kan du pröva att isolera din utrustning från datorn med hjälp av optokopplare. (på alla signaler mellan datorn och din utrustning. Försök att få matning till optokopplarna från datorn (lediga signaler från porten kan ge tillräckligt med kraft) för bästa isolation från störning. Letar du efter Linux mjukvara för mönsterkortframtagning, så finns det en fri sådan som kallas Pcb. Den torde göra ett bra jobb, åtminstone om inte du gör något alltför komplicerat. Den finn med i många Linux distributioner, och den finns tillgänglig i (filename pcb-*). 8. Felsökning Q1. Jag får segmentation faults när jag adresserar portar. A1. Antingen har ditt program inte rootprivilegier, eller har ioperm() falerat av någon annan orsak. Testa det returnerade värdet från ioperm(). Testa också att du verkligen adresserar de portar som du gett tillstånd till med ioperm() (se Q3). Om du använder 'delaying macros' (inb_p(), outb_p(), osv.), kom ihåg att du då måste anropa ioperm() för att ge accesstillstånd till adress 0x80. Q2. Jag kan inte hitta var in*(), out*() funktionerna definieras, och gcc klagar över odefinierade referenser. A2. Du kompilerade inte med optimeringsflaggan på (-O1 eller högre), och därför kunde inte gcc lösa upp de makron som finns i asm/io.h. Eller glömde du kanske #include . Q3. out*() gör ingenting, eller gör något konstigt. A3. Kolla ordningen på parametrarna; set skall vara outb(value, port), inte outb(port, value) som förekommer i MS-DOS. Q4. Jag vill köra en standard RS-232 device/parallel printer/joystick... A4. Då är det nog bäst att använda en befintlig driver (i Linux kernel eller en X-server eller någon annanstans) för detta. Drivrutinerna är vanligtvis mycket flexibla, så att även en ickestandard apparat fungerar vanligtvis med dem. Se info om standard portar ovan efter hänvisningar till dokumentation. 9. Kod exempel Här följer ett enkelt exempel på kod för I/O-port access: ______________________________________________________________________ /* * example.c: very simple example of port I/O * * This code does nothing useful, just a port write, a pause, * and a port read. Compile with 'gcc -O2 -o example example.c', * and run as root with './example'. */ #include #include #include #define BASEPORT 0x378 /* lp1 */ int main() { /* Get access to the ports */ if (ioperm(BASEPORT, 3, 1)) {perror("ioperm"); exit(1);} /* Set the data signals (D0-7) of the port to all low (0) */ outb(0, BASEPORT); /* Sleep for a while (100 ms) */ usleep(100000); /* Read from the status port (BASE+1) and display the result */ printf("status: %d\n", inb(BASEPORT + 1)); /* We don't need the ports anymore */ if (ioperm(BASEPORT, 3, 0)) {perror("ioperm"); exit(1);} exit(0); } /* end of example.c */ ______________________________________________________________________ 10. Erkännande Alltför många har bidragit till artikeln för att jag skall kunna räkna upp alla, men tack allesammans. Jag har inte besvarat alla bidrag som jag fått; ledsen för det, men återigen tack för all hjälp.