Sichere Port-Zuweisung f�r Linuxger�tetreiber

ArticleCategory: [Choose a category, do not translate this]

KernelCorner

AuthorImage:[Here we need a little image from you]

[Photo of the Author]

TranslationInfo:[Author + translation history. mailto: or http://homepage, do not translate this]

original in en Dr. B. Thangaraju

en to de Robert Gummi

AboutTheAuthor:[A small biography about the author]

Dr. B. Thangaraju erhielt seinen Doktor in Physik an der Universit�t von Bharathidasan, Tamil Nadu und arbeitete f�nf Jahre lang als Assistent am indischen Institut der Wissenschaften. Er forschte �ber transparente, leitende D�nnschicht-Oxide (Transparent Conducting Oxides: TCO), Spr�hpyrolyse, photoakustische Methoden und p-n �berg�nge in Chalkogenid-Gl�sern. Er hat zehn Arbeiten in anerkannten wissenschaftlichen Zeitschriften ver�ffentlicht und seine Forschungsergebnisse auf mehr als sieben internationalen und nationalen Konferenzen pr�sentiert.

Zur Zeit arbeitet er als Manager bei Wipro Technologies in Indien. Seine augenblicklichen Studien- und Forschungsgebiete sind der Linux Kernel, Ger�tetreiber und Echtzeit-Linux.

Abstract:[Here you write a little summary]

Einen Ger�tetreiber zu schreiben ist eine Herausforderung und manchmal eine abenteuerliche Aufgabe. Sobald das Ger�t in der init_module Routine des Treibers registriert ist, sollten die Ressourcen f�r das Ger�t zugeteilt werden. Eine der Hauptressourcen f�r das Ger�t ist der Ein/Ausgabe Port. Der Programmierer mu� sehr darauf bedacht sein, einen ungenutzten Bereich der Port-Adressen zu vergeben. Zuerst soll der Treiber pr�fen, ob der Bereich schon genutzt wird oder noch frei ist und danach die Ports f�r das Ger�t anfordern. Wenn das Modul wieder aus dem Speicher entfernt wird, m�ssen die Ports wieder freigegeben werden. Dieser Artikel diskutiert die Schwierigkeiten einer zuverl�ssigen Zuweisung von Ein-/Ausgabeports f�r Linux Ger�tetreiber.

ArticleIllustration:[This is the title picture for your article]

[Illustration]

ArticleBody:[The article body]

Einleitung

Die Hauptsorge eines Ger�tetreiberentwicklers ist die Zuteilung der Ressourcen. Die Ressourcen sind I/O Ports, Speicher und IRQs. Dieser Arikel wird versuchen, die Grundlagen von I/O Subsystemen zu erl�utern und die Wichtigkeit der Ressourceneinteilung, insbesondere der I/O Ports. Er erkl�rt auch, wie man nach freien Port-Adressen f�r Ger�te sucht, sie anfordert und wieder freigibt.

Die Grundelemente der Hardware wie Ports, Busse und Controller versorgen eine breite Auswahl an Ein/Ausgabe-Ger�ten. Die Ger�tetreiber bieten eine einheitliche Schnittstelle zum I/O Subsystem, �hnlich wie die Systemaufrufe eine Schnittstelle zwischen den Applikationen und dem Betriebssystem bereitstellen. Es gibt viele Arten von Ger�ten, die an den Computer angeschlossen sind, zum Beispiel Massenspeicher wie Festplatten, Magnetb�nder, CD-ROMs und Floppies, Benutzerschnittstellen wie Tastatur, Maus und Bildschirm, Daten�bertragungsger�te wie Netzwerkkarten und Modems. Trotz dieser sehr unterschiedlichen Ger�tetypen mu� man nur verstehen, wie die Grundkonzepte aussehen, nach denen Ger�te angeschlossen werden und mit deren Hilfe Software die Hardware kontrolliert.

Grundlegendes Konzept

Ein Ger�t besteht aus zwei Teilen, einerseits der elektronischen Komponente, dem Steuerbaustein oder auch Controller genannt und andererseits dem mechanischen Teil. Der Steuerbaustein ist mit dem System �ber den Systembus verbunden. Normalerweise ist jedem Controller ein Satz (konfliktfreier) Portadressen zugewiesen. I/O Ports umfassen vier Registers�tze, n�mlich Status, Control, Data-In und Data-Out. Im Status-Register stehen Bits, die der Host lesen kann und die anzeigen, ob das aktuelle Kommando erfolgreich beendet wurde, ob ein weiteres Byte zum Lesen oder Schreiben bereit ist oder ob irgendwelche Fehler auftraten. In das Control-Register schreibt der Host, um ein Kommando zu starten oder den Zustand eines Ger�tes zu �ndern. In das Data-In Register gelangen Eingaben, �ber Data-Out werden Ausgaben an das System gesendet.

Die grundlegende Schnittstelle zwischen dem Prozessor und einem Ger�t ist also ein Satz Kontroll- und Statusregister. Wenn der Prozessor ein Programm ausf�hrt und auf eine das Ger�t betreffende Anweisung st��t, dann f�hrt er diese aus, indem er einen Befehl an das passende Ger�t sendet. Die Steuereinheit f�hrt die angeforderte Aktion aus, setzt die entsprechenden Bits im Status-Register und wartet. Der Prozessor ist daf�r zust�ndig, den Status des Ger�tes regelm��ig zu �berpr�fen, bis er sieht, da� die Operation abgeschlossen wurde. Der Parallel-Port-Treiber (Druckeranschlu�) zum Beispiel fragt die Bereitschaft des Druckers ab, Ausgabedaten zu empfangen. Wenn der Drucker nicht bereit ist, wird der Treiber eine Zeit lang schlafen (der Prozessor kann w�hrenddessen sinnvolle Arbeit verrichten) und es immer wieder versuchen, bis der Drucker bereit ist. Dieser sogenannte 'Polling'-Mechnismus verbessert die Systemleistung, andernfalls w�rde das System unn�tig auf das Ger�t warten, ohne eine sinnvolle Aufgabe zu erf�llen.

Die Register haben eine wohldefinierte Adresse im I/O-Bereich. Im allgemeinen werden diese Adressen beim Booten zugeordnet. Dabei benutzen sie Parameter, die in einer Konfigurationsdatei abgelegt sind und jedem Ger�t kann ein Adressbereich zugeordnet werden, wenn das Ger�t dauerhaft angeschlossen ist. Das bedeutet, wenn der Kernel die Ger�tetreiber f�r bestehende Ger�te enth�lt, dann k�nnen die zugeteilten I/O Port Adressbereiche im proc-Verzeichnis gespeichert werden. Man kann die Adressbereiche, die das System gerade benutzt durch Eingabe von $cat /proc/interrupts ansehen. Die erste Spalte der Ausgabe zeigt den Adressbereich und die zweite Spalte das Ger�t, dem diese Ports geh�ren. Einige Betriebssysteme haben die M�glichkeit, Ger�tetreiber als Modul ins laufende System zu laden. Dadurch kann ein beliebiges Ger�t an das laufende System angeschlossen und durch ein dynamisch nachgeladenes Treibermodul kontrolliert und darauf zugegriffen werden.

Das Konzept der Ger�tetreiber ist sehr abstrakt und stellt den untersten Softwarelevel dar, der auf dem Computer l�uft, da es direkt mit den Hardware-Features des Ger�tes verkn�pft ist. Jeder Ger�tetreiber verwaltet einen speziellen Ger�tetyp. Es gibt zeichen-, block- oder netzwerkorientierte Typen. Wenn eine Anwendung Zugriff auf das Ger�t anfordert, stellt der Kernel den Kontakt zum passenden Treiber her. Der Treiber gibt dann das Kommando an das spezielle Ger�t aus. Der Treiber ist eine Ansammlung von Funktionen: er hat Einsprungpunkte wie open, close, read, write, ioctl, llseek etc. Beim Laden des Moduls wird die init_module ( ) Funktion aufgerufen, beim Entfernen die cleanup_module ( ) Funktion. Das Ger�t ist im Treiber in der init_module ( ) Routine registriert.

Wenn ein Ger�t �ber init_module ( ) registriert wird, dann werden die f�r die Arbeit notwendigen Ressourcen f�r das Ger�t wie I/O Ports, Speicher und IRQ-Lines in der Funktion selber zugewiesen. Wenn man dem Ger�t eine falsche Speicheradresse zuteilt, gibt der Kernel die Fehlermeldung segmentation fault aus. Im Falle von I/O Ports aber wird der Kernel keine Meldung wie wrong I/O port ausgeben, sondern bereits vergebene Ports zuweisen, was zu einem Systemcrash f�hrt. Beim Entfernen des Moduls sollte das Ger�t wieder abgemeldet werden, das hei�t die Major Number wird freigegeben, ebenso die Ressourcen durch die cleanup_module ( ) Funktion.

Die Hauptaufgabe des Ger�tetreibers ist Lesen und Schreiben auf den I/O Ports. Daher sollte der Treiber sicher sein, da� die Port Adressen von dem Ger�t exklusiv genutzt werden. Es darf kein anderes Ger�t geben, welches diesen Adressbereich verwendet. Um das sicherzustellen, mu� der Treiber zuerst �berpr�fen, ob die Port Adresse bereits in Gebrauch ist oder nicht: sobald feststeht, da� die Adressen frei sind, kann er den Kernel auffordern, den Adressbereich seinem Ger�t zuzuweisen.

Zuverl�ssige Port-Zuweisung

Jetzt werden wir sehen, wie man mit Kernel-Funktionen die Ressourcenzuteilung und -freigabe durchf�hrt. Der praktische Ansatz ist am Linux 2.4 Kernel ausprobiert worden. Daher ist die komplette Durchf�hrung nur auf Linux Betriebssysteme anwendbar und nur begrenzt f�r andere UNIX Varianten.

Zun�chst wird ein Portbereich auf seine Verf�gbarkeit getestet durch

int check_region (unsigned long start, unsigned long len);

die Funktion gibt Null zur�ck, wenn der Adressbereich verf�gbar ist, beziehungsweise weniger als Null oder einen negativen Fehlercode ( -EBUSY oder -EINVAL), wenn er schon genutzt wird. Die Funktion erwartet zwei Argumente: start gibt den Beginn eines zusammenh�ngenden Bereichs an, und len die Anzahl an Ports dieses Bereiches.

Sobald ein Port verf�gbar ist, sollte er f�r das Ger�t reserviert werden. Das geschieht durch den Aufruf der request_region Funktion:

struct resource *request_region (unsigned long start, unsigned long len, char *name);

Die ersten beiden Argumente sind die gleichen wie vorher, die Zeigervariable name ist der Name des Ger�tes, dem die Adresse zugewiesen wird. Die Funktion gibt einen Zeiger auf "struct resource" zur�ck. Resource structure wird verwendet, um die Resourcenbereiche zu beschreiben, die in <linux/ioport.h> deklariert sind. Die strukturierte Variable hat das folgende Format:

struct resource {
        const char *name;
        unsigned long start, end;
        unsigned long flags;
        struct resource *parent, *sibiling, *child;
};
Wenn das Modul wieder aus dem Kernel entfernt wird, sollte der Port wieder f�r die Nutzung durch andere Ger�te freigegeben werden. Daf�r m�ssen wir die release_region ( ) Funktion in cleanup_module ( ) verwenden. Die Syntax der Funktion lautet:

void release_region ( unsigned long start, unsigned long len);

Die zwei Argumente bedeuten wieder das gleiche wie zuvor. Die drei genannten Funktionen sind eigentlich Makros, die in <linux/ioport.h> deklariert sind.

Beispielcode f�r Ger�teanschluss-Zuweisung

Das folgende Progamm zeigt die Zuordnung und Wieder-Freigabe von Ports f�r einen dynamisch geladenen Ger�tetreiber.

#include <linux/fs.h.>
#include <linux/ioport.h.>

struct file_operations fops;
unsigned long start, len;

int init_module (void)
{
 int status;
 start = 0xff90;
 len   = 0x90;

 register_chrdev(254,"your_device",&fops);

 status =  check_region (start, len);
 if (status == 0) {
     printk ("The ports are availave in that range\n");
     request_region(start,len,"your_device");
 } else {
     printk ("The ports are already in use. Try other range.\n");
     return (status);
 }
 return 0;
}

void cleanup_module (void)
{
 release_region(start, len);
 printk ("ports are freed successfully\n");
 unregister_chrdev(254,"your_device");}
 printk (" your device is unregistered\n");
}

Um Verwirrungen zu vermeiden, wurden in diesem Beispielcode Fehlerpr�fung und 'major number' Vergabe vermieden. Sobald der Port erfolgreich zugeordnet wurde, k�nnen wir dies im /proc Verzeichnis �berpr�fen:
$cat /proc/ioports

Kernel I/O Port-Funktion-Optionen f�r Treiber

Linux bietet eine Auswahl an Funktionen, um auf I/O Ports lesend oder schreibend zuzugreifen, die vom Format der Ports abh�ngen. Ports k�nnen 8, 16 oder 32 Bit breit sein. Die Linux kernel headers <asm/io.h> definieren die integrierten Funktionen, um auf I/O Ports zuzugreifen. Um 8 Bit, 16 Bit oder 32 Bit Ports zu lesen (inx) und zu schreiben (outx), verwendet man die folgenden Funktionen:



__u8 inb (unsigned int port);
void outb (__u8 data, unsigned int port);

__u16 inw (unsigned int port);
void outw(__u16 data, unsigned int port);

__u32 inl (unsigned int prot);
void outl (__u32 data, unsigned int port);


Um Zeichenketten mit mehr als einem Datenwort effizient zu �bertragen, gibt es folgende Funktionen:


void insb(unsigned int port, void *addr, unsigned long count);
void outsb(unsigned int port, void *addr, unsigned long count);


addr ist die Speicherstelle, an die geschrieben oder von der gelesen wird, und count stellt die Anzahl der Einheiten dar, die �bertragen werden sollen. Daten werden gelesen von oder geschrieben auf den Port "port".


void insw(unsigned int port, void *addr, unsigned long count);
void outsw(unsigned int port, void *addr, unsigned long count);

Lesen oder schreiben von 16 Bit Werten an einen einzelnen 16 Bit Port.


void insl(unsigned int port, void *addr, unsigned long count);
void outsl(unsigned int port, void *addr, unsigned long count);

Lesen oder schreiben von 32 Bit Werten an einen einzelnen 32 Bit Port

Danksagung

Der Autor dankt hiermit Herrn Jayasurya V, Manager Talent Transformation, Wipro Technologies, Indien, f�r das kritische Lesen des Manuskriptes.

Literatur