I/O-Speicher verwenden

Trotz der Beliebtheit von I/O-Ports in der x86-Welt läuft der wichtigste Mechanismus zur Kommunikation mit Geräten immer noch über in den Speicher abgebildete Register und Gerätespeicher. Beide werden I/O-Speicher genannt, weil die Software den Unterschied zwischen Registern und Speichern nicht sehen kann.

I/O-Speicher ist einfach ein Bereich von RAM-artigen Speicherstellen, die das Gerät dem Prozessor über den Bus verfügbar macht. Dieser Speicher kann für diverse Zwecke wie das Speichern von Videodaten oder Ethernet-Paketen verwendet werden, aber auch Geräteregister implementieren, die sich wie I/O-Ports verhalten (d. h. sie haben Nebeneffekte beim Lesen und Schreiben).

Wie auf den I/O-Speicher zugegriffen wird, ist von der Computer-Architektur, dem Bus und dem Gerät abhängig, auch wenn die Prinzipien überall gleich sind. Wir werden in diesem Kapitel hauptsächlich auf ISA- und PCI-Speicher eingehen, aber auch allgemeine Informationen liefern. Der Zugriff auf PCI-Speicher wird hier zwar eingeführt, PCI wird aber gründlich erst in Kapitel 15 behandelt.

Je nach Computer-Plattform und verwendetem Bus kann auf I/O-Speicher über Seitentabellen zugegriffen werden oder nicht. Wenn der Zugriff über Seitentabellen erfolgt, muß der Kernel zunächst dafür sorgen, daß die physikalische Adresse von Ihrem Treiber aus sichtbar ist (dazu muß üblicherweise vor allen I/O-Operationen ioremap aufgerufen werden). Wenn keine Seitentabellen verwendet werden, sehen die I/O-Speicherstellen I/O-Ports ziemlich ähnlich, und Sie können mit den passenden Wrapper-Funktionen einfach darauf schreiben und daraus lesen.

Die direkte Verwendung von Zeigern auf I/O-Speicher ist nicht ratsam, unabhängig davon, ob ioremap für den Zugriff auf I/O-Speicher notwendig ist oder nicht. Auch wenn I/O-Speicher (wie in "the Section called I/O-Ports und I/O-Speicher" besprochen) auf Hardware-Ebene wie normales RAM angesprochen wird, legt die in “the Section called I/O-Register und konventioneller Speicher” empfohlene besondere Vorsicht nahe, normale Zeiger zu vermeiden . Die Wrapper-Funktionen für den Zugriff auf I/O-Speicher sind sowohl auf allen Plattformen sicher als auch wegoptimiert, wenn das direkte Dereferenzieren eines Zeigers die Operation durchführen könnte.

Selbst wenn das Dereferenzieren eines Zeigers (zur Zeit) auf der x86-Plattform möglcih ist, machen Sie Ihren Treiber schlechter lesbar und unportabel, wenn Sie nicht die vorgesehenen Makros verwenden.

In Kapitel 2 hatten wir bereits erwähnt, daß Gerätespeicherbereiche vor der Verwendung alloziert werden müssen. Das funktioniert ähnlich wie das Registrieren von I/O-Ports und wird durch die folgenden Funktionen erledigt:


int check_mem_region(unsigned long start, unsigned long len);
void request_mem_region(unsigned long start, unsigned long len,
char *name);
void release_mem_region(unsigned long start, unsigned long len);

Das Argument start ist die physikalische Adresse des Speicherbereichs vor jeder Abbildung. Die Funktionen werden normalerweise etwa so verwendet:


if (check_mem_region(mem_addr, mem_size)) {
    printk("drivername: memory already in use\n");
    return -EBUSY;
}
    request_mem_region(mem_addr, mem_size, "drivername");

    [...]

    release_mem_region(mem_addr, mem_size);

Direkt abgebildeter Speicher

Einige Computer-Plattformen reservieren einen Teil ihres Speicher-Adreßraumes für I/O-Speicherstellen und schalten die Speicherverwaltung für (virtuelle) Adressen in diesem Speicherbereich automatisch ab.

Die in PDAs verwendeten MIPS-Prozessoren machen dies auf sehr interessante Weise. Zwei Adressbereiche mit je 512 MByte werden direkt auf physikalische Adressen abgebildet. Jeder Speicherzugriff auf einen dieser Adreßbereiche geht an der MMU wie auch am Cache vorbei. Ein Abschnitt von diesen 512 Megabytes ist für Peripherie-Geräte reserviert; Treiber können auf ihren I/O-Speicher direkt durch Verwendung des nicht gecachten Adreßbereichs zugreifen.

Andere Plattformen haben andere Möglichkeiten, um direkt abgebildete Adreßbereiche zur Verfügung zu stellen: Manche haben spezielle Adreßräume, um physikalische Adressen zu dereferenzieren (beispielsweise verwendet SPARC64 zu diesem Zweck einen besonderen “address space identifier”), andere verwenden virtuelle Adressen, die so eingerichtet sind, daß sie die Prozessor-Caches umgehen.

Auch wenn Sie auf einen direkt abgebildeten I/O-Speicherbereich zugreifen müssen, sollten Sie trotzdem nicht Ihre I/O-Zeiger dereferenzieren, auch wenn Sie damit auf manchen Architekturen vielleicht durchkommen. Um Code zu schreiben, der auf verschiedenen Systemen und in verschiedenen Kernel-Versionen funktioniert, müssen Sie direkte Zugriffe vermeiden und statt dessen die folgenden Funktionen verwenden:

unsigned readb(address);, unsigned readw(address);, unsigned readl(address);

Diese Makros holen 8 Bit, 16 Bit und 32 Bit breite Datenwerte aus dem I/O-Speicher. Der Vorteil der Verwendung von Makros liegt in der Typenlosigkeit des Arguments: address wird vor der Verwendung gecastet, denn der Wert “ist nicht eindeutig ein Integer oder ein Zeiger, und wir akzeptieren beides” (übersetzt aus asm-alpha/io.h). Weder die Funktionen zum Lesen noch die zum Schreiben überprüfen die Gültigkeit von adresse, weil sie genauso schnell wie das Dereferenzieren von Zeigern sein sollten (und, wie wir inzwischen wissen, manchmal genau dazu expandieren).

void writeb(unsigned value, address);, void writew(unsigned value, address);, void writel(unsigned value, address);

Entsprechend dienen diese Funktionen (Makros) dazu, 8-Bit-, 16-Bit- und 32-Bit-Datenelemente zu schreiben.

memset_io(address, value, count);

Wenn Sie memset auf I/O-Speicher benötigen, dann können Sie diese Funktion verwenden, die die Semantik des originalen memset beibehält.

memcpy_fromio(dest, source, num);, memcpy_toio(dest, source, num);

Diese Funktionen verschieben Datenblocks in den und aus dem I/O-Speicher und verhalten sich wie die C-Bibliotheksroutine memcpy.

In modernen Versionen des Kernels sind diese Funktionen auf allen Architekturen vorhanden. Die Implementation unterscheidet sich aber; auf manchen sind sie Makros, die zu Zeiger-Operationen expandieren, auf anderen sind sie richtige Funktionen. Als Treiber-Autor müssen Sie sich darüber aber keine Gedanken machen, solange Sie sie verwenden.

Manche 64-Bit-Plattformen verfügen auch über readq und writeq für acht Byte (Quad-Wort) breite Speicheroperationen auf dem PCI-Bus. Die Quad-Wort-Bezeichnung ist ein historisches Überbleibsel aus der Zeit, als alle richtigen Prozessoren 16 Bit breite Worte hatten. Das L für 32-Bit-Werte ist inzwischen eigentlich auch nicht mehr korrekt, aber wenn man alle die Funktionen umbenennen würde, würde das nur noch mehr Verwirrung stiften.

short für I/O-Speicher verwenden

Das Beispiel-Modul short, das wir weiter oben eingeführt hatten, um auf I/O-Ports zuzugreifen, kann auch für I/O-Speicher verwendet werden. Zu diesem Zweck müssen Sie beim Laden angeben, daß Sie I/O-Speicher verwenden wollen, und Sie müssen auch die Basisadresse ändern, um sie auf Ihren I/O-Bereich zeigen zu lassen.

So haben wir beispielsweise short verwendet, um die Debug-LEDs auf einem MIPS-Entwicklungssystem aufleuchten zu lassen:

 mips.root# ./short_load use_mem=1 base=0xb7ffffc0
 mips.root# echo -n 7 > /dev/short0

short wird für I/O-Speicher genauso wie für I/O-Ports verwendet, aber weil es für I/O-Speicher keine wartenden oder String-Anweisungen gibt, passiert beim Zugriff auf /dev/short0p und /dev/short0s genau das gleiche wie bei /dev/short0.

Das folgende Fragment zeigt die Schleife, die short verwendet, um in einen Speicherbereich zu schreiben:


while (count--) {
    writeb(*(ptr++), address);
    wmb();
}

Beachten Sie die Verwendung einer Schreib-Speicherbarriere. Weil writeb auf vielen Architekturen vermutlich eine direkte Zuweisung ist, muß die Speicherbarriere verwendet werden, um sicherzustellen, daß die Schreiboperationen in der erwarteten Reihenfolge erfolgen.

Von der Software abgebildeter I/O-Speicher

Von der MIPS-Prozessorklasse einmal abgesehen, ist das direkte Abbilden von I/O-Speicher noch ziemlich selten; insbesondere, wenn ein Peripherie-Bus verwendet wird (was meistens der Fall ist).

Die gängigste Hardware- und Software-Anordnung für I/O-Speicher sieht folgendermaßen aus: Geräte befinden sich an wohlbekannten physikalischen Adressen, aber die CPU hat keine vordefinierte virtuelle Adresse, um auf die Geräte zuzugreifen. Die wohlbekannte physikalische Adresse kann entweder im Gerät fest verdrahtet sein oder von der System-Firmware beim Booten zugewiesen werden. Ersteres ist beispielsweise bei ISA-Geräten der Fall, deren Adressen entweder in die Schaltkreise des Gerätes eingebrannt sind, im lokalen Gerätespeicher statisch zugewiesen werden oder mittels physikalischer Jumper gesetzt werden. Letzteres ist bei PCI-Geräten der Fall, deren Adressen von der System-Software zugewiesen und in den Gerätespeicher geschrieben werden, wo sie nur so lange vorhanden sind, wie das Gerät eingeschaltet ist.

Auf jeden Fall muß es eine Möglichkeit geben, einem Gerät eine virtuelle Adresse zuzuweisen, damit Software auf den I/O-Speicher zugreifen kann. Dies ist die Aufgabe der Funktion ioremap, die in “the Section called vmalloc und Freunde in Kapitel 7” eingeführt wurde. Diese Funktion, die im vorigen Kapitel behandelt wurde, weil es bei ihr um die Speicherverwaltung geht, ist speziell dazu gedacht, I/O-Speicherbereichen virtuelle Adressen zuzuweisen. Außerdem haben die Kernel-Entwickler ioremap so implementiert, daß die Funktion nichts macht, wenn sie auf direkt abgebildete I/O-Adressen angewendet wird.

Mit ioremap (und iounmap) kann ein Gerätetreiber auf beliebige I/O-Speicheradressen zugreifen, egal, ob diese nun direkt in den virtuellen Adreßraum abgebildet werden oder nicht. Denken Sie aber daran, daß diese Adressen nicht direkt referenziert werden sollten; benutzen Sie statt dessen Funktionen wie readb. Wir könnten short so schreiben, daß der Treiber sowohl mit MIPS-I/O-Speicher als auch mit dem gängigeren ISA/PCI-x86-Speicher arbeitet, indem wir das Modul mit ioremap und iounmap ausstatten, wenn der Parameter use_mem verwendet wird.

Bevor wir Ihnen zeigen, wie short die Funktionen aufruft, werfen wir lieber noch einen Blick auf die Prototypen der Funktionen und führen einige Details ein, die wir im letzten Kapitel übergangen haben.

Die Funktionen haben folgende Definition:


#include <asm/io.h>
void *ioremap(unsigned long phys_addr, unsigned long size);
void *ioremap_nocache(unsigned long phys_addr, unsigned long size);
void iounmap(void * addr);

Zunächst einmal wird Ihnen vielleicht die neue Funktion ioremap_nocache auffallen. Wir haben diese in Kapitel 7 nicht behandelt, weil sie eindeutig Hardware-bezogen ist. Um aus einer der Kernel-Header-Dateien zu übersetzen: “(Die Funktion) ist nützlich, wenn einige Kontrollregister sich in einem solchen Bereich befinden und das Kombinieren von Schreiboperationen oder das Cachen von Leseoperationen nicht erwünscht sind.” Auf den meisten Computer-Plattformen ist die Implementation dieser Funktion in den Situationen identisch mit ioremap, in denen der gesamte I/O-Speicher bereits über Adressen sichtbar ist, die nicht gecacht werden können – es gibt in diesem Fall keinen Grund, eine Spezialversion von ioremap zu implementieren.

Ein weiteres wichtiges Merkmal von ioremap ist das unterschiedliche Verhalten in Version 2.0 und späteren Versionen. In Linux 2.0 (die Funktion hieß damals vremap) weigerte sich die Funktion, Speicherbereiche abzubilden, die nicht an Seitengrenzen ausgerichtet waren. Das war auch sinnvoll so, weil auf CPU-Ebene alles in Einheiten von ganzen Seiten passiert. Manchmal müssen Sie aber kleine Bereiche von I/O-Registern abbilden, deren (physikalische) Adressen nicht an Seitengrenzen ausgerichtet sind. Um dieses neue Bedürfnis befriedigen zu können, sind Kernel ab der Version 2.1.131 auch in der Lage, nicht ausgerichtete Adressen abzubilden.

Unser Modul short benutzt nicht ioremap direkt, sondern verwendet den folgenden Code, um abwärtsportabel mit 2.0-Kerneln zu sein und trotzdem auf Register zugreifen zu können, die nicht an Seitengrenzen liegen:


/* Einen nicht (notwendigerweise) ausgerichteten Port-Bereich abbilden */
void *short_remap(unsigned long phys_addr)
{
    /* Der Code stammt hauptsaechlich aus arch/any/mm/ioremap.c */
    unsigned long offset, last_addr, size;

    last_addr = phys_addr + SHORT_NR_PORTS - 1;
    offset = phys_addr & ˜PAGE_MASK;

    /* Anfang und Ende auf eine volle Seite ausrichten */
    phys_addr &= PAGE_MASK;
    size = PAGE_ALIGN(last_addr) - phys_addr;
    return ioremap(phys_addr, size) + offset;
}

/* Einen mit short_remap geholten Bereich wieder ausblenden */
void short_unmap(void *virt_add)
{
    iounmap((void *)((unsigned long)virt_add & PAGE_MASK));
}


ISA-Speicher unter 1 MByte

Einer der bekanntesten I/O-Speicherbereiche ist der ISA-Bereich auf PCs. Dies ist der Speicherbereich zwischen 640 KByte (0x0A0000) und 1 MByte (0x100000). Der Bereich erscheint also mitten im normalen System-RAM. Diese Positionierung scheint vielleicht etwas merkwürdig zu sein, ist aber eine Folge einer in den frühen achtziger Jahren getroffenen Entscheidung, als 640 KByte Speicher mehr zu sein schien, als jemals irgendjemand benutzen würde.

Dieser Speicherbereich gehört zur Klasse des nicht direkt abgebildeten Speichers.[1] Sie können mit dem short-Modul wie oben beschrieben ein paar Bytes in diesen Speicherbereich schreiben, wenn Sie use_mem beim Laden setzen.

Obwohl es ISA-I/O-Speicher nur in x86-Computern gibt, halten wir es dennoch für sinnvoll, einige Worte und einen Beispiel-Treiber darauf zu verwenden.

Wir werden in diesem Kapitel nicht über PCI-Speicher sprechen, weil das die sauberste Form von I/O-Speicher ist: Sobald Sie die physikalische Adresse kennen, können Sie diese einfach abbilden und darauf zugreifen. Das “Problem” mit PCI-I/O-Speicher besteht vielmehr darin, daß wir nicht so einfach ein funktionierendes Beispiel für dieses Kapitel konstruieren können, weil wir nicht im voraus wissen können, auf welche physikalischen Adressen Ihr PCI-Speicher abgebildet wird oder wann man gefahrlos auf diese Bereiche zugreifen kann. Wir beschreiben hier den ISA-Speicherbereich, da er sowohl weniger sauber ist als auch besser dafür geeignet, funktionierenden Beispiel-Code vorzuführen.

Um den Zugriff auf ISA-Speicher zu zeigen, benutzen wir noch ein weiteres albernes kleines Modul (das ebenfalls in den Beispiel-Quellen enthalten ist). Dieses heißt einfach silly als Akronym für “Simple Tool for Unloading and Printing ISA Data” (oder so ähnlich).

Das Modul ergänzt die Funktionalität von short, indem es Zugriff auf den gesamten 384 KByte großen Speicherbereichs gewährt und alle I/O-Funktionen vorführt. Es enthält vier Geräteknoten, die die gleiche Aufgabe mit verschiedenen Datenübertragungsfunktionen durchführen. Die silly-Geräte dienen ähnlich wie /dev/mem als Fenster auf den I/O-Speicher. Sie können Daten lesen und schreiben und mit lseek auf eine beliebige I/O-Speicheradresse springen.

Weil silly den Zugriff auf ISA-Speicher ermöglicht, müssen als erstes die physikalischen ISA-Adressen auf virtuelle Kernel-Adressen abgebildet werden. In den frühen Tagen des Linux-Kernels konnte man einfach einem Zeiger eine ISA-Adresse zuweisen und ihn dann direkt dereferenzieren. In der modernen Welt müssen wir aber mit dem virtuellen Speichersystem arbeiten und zuerst den Speicherbereich abbilden. Dies geschieht, wie oben bei short beschrieben, mit ioremap:


#define ISA_BASE    0xA0000
#define ISA_MAX    0x100000  /* allgemeiner Speicherzugriff */

    /* diese Zeile steht in silly_init */
    io_base = ioremap(ISA_BASE, ISA_MAX - ISA_BASE);

ioremap gibt einen Zeigerwert zurück, der von readb und den anderen in “the Section called Direkt abgebildeter Speicher” beschriebenen Funktionen verwendet werden kann.

Werfen wir noch einen Blick auf unser Beispiel-Modul, um zu sehen, wie diese Funktionen verwendet werden können. /dev/sillyb mit der Minor-Nummer 0 greift auf den I/O-Speicher mit readb und writeb zu. Der folgende Code zeigt die Implementation für read, mit der der Adreßbereich 0xA0000-0xFFFFF als virtuelle Datei im Bereich 0-0x5FFFF zur Verfügung gestellt wird. Die Funktion read ist als switch-Anweisung über die verschiedenen Zugriffsmodi strukturiert; hier sehen Sie den Fall sillyb:


case M_8:
  while (count) {
      *ptr = readb(add);
      add++; count--; ptr++;
  }
  break;

Die nächsten beiden Geräte sind /dev/sillyw (Minor-Nummer 1) und /dev/sillyl (Minor-Nummer 2). Sie arbeiten wie /dev/sillyb, verwenden aber 16-Bit- bzw. 32-Bit-Funktionen. Hier folgt die write-Implementation von sillyl, die ebenfalls Bestandteil einer switch-Anweisung ist:


case M_32:
  while (count >= 4) {
      writel(*(u32 *)ptr, add);
      add+=4; count-=4; ptr+=4;
  }
  break;

Das letzte Gerät ist /dev/sillycp (Minor-Nummer 3), das die memcpy_*io-Funktionen verwendet, um die gleiche Aufgabe zu erledigen. Der Kern der read-Implementation sieht wie folgt aus:


case M_memcpy:
  memcpy_fromio(ptr, add, count);
  break;

Weil ioremap dazu verwendet wird, auf den ISA-Speicherbereich zuzugreifen, muß silly iounmap aufrufen, wenn das Modul entladen wird:


iounmap(io_base);


isa_readb und verwandte Funktionen

Ein Blick in die Kernel-Quellen offenbart noch einen weiteren Satz von Routinen mit Namen wie isa_readb. Tatsächlich hat jede der gerade beschriebenen Funktionen ein isa_-Äquivalent. Diese Funktionen ermöglichen den Zugriff auf den ISA-Speicher, ohne vorher ioremap aufrufen zu müssen. Die Kernel-Entwickler haben allerdings verlauten lassen, daß diese Treiber nicht mehr als vorübergehende Hilfen für das Portieren von Treibern sind und in der Zukunft verschwinden könnten. Sie sollten daher nicht verwendet werden.

Nach ISA-Speicher suchen

Auch wenn die meisten modernen Geräte bessere I/O-Bus-Architekturen wie PCI verwenden, muß man sich als Programmierer mitunter dennoch mit ISA-Geräten und deren I/O-Speicher auseinandersetzen, weswegen wir hier eine Seite mit diesem Thema zubringen. Wir sprechen hier nicht über den hohen ISA-Speicher (das sogenannte Speicherloch im physikalischen Adreßbereich von 15 MByte bis 16 MByte), weil diese Art von I/O-Speicher heutzutage äußerst selten ist und von den meisten modernen Hauptplatinen und Kerneln nicht unterstützt wird. Um auf diesen Bereich des I/O-Speichers zuzugreifen, müssen Sie den Initialisierungscode des Kernels verändern, und das wollen wir hier lieber nicht behandeln.

Bei der Verwendung von in den Speicher abgebildeten ISA-Geräten ignoriert der Treiber-Autor oft, wo im physikalischen Adreßraum sich der relevante I/O-Speicher befindet, weil die eigentliche Adresse oft vom Benutzer aus einer Reihe von möglichen Alternativen ausgewählt wird. Es kann aber auch einfach notwendig sein festzustellen, ob es an einer bestimmten Adresse ein Gerät gibt oder nicht.

Das Speicherverwaltungssystem kann beim Suchen hilfreich sein, denn es kann die Speicherbereiche identifizieren, die bereits von einem anderen Treiber mit Beschlag belegt worden sind. Der Speicherverwalter kann Ihnen aber nichts über Geräte sagen, deren Treiber nicht geladen sind, oder ob ein bestimmter Bereich das Gerät enthält, an dem Sie interessiert sind. Daher kann es trotzdem noch notwendig sein, den Speicher zu testen, um zu sehen, was sich dort befindet. Sie werden dabei auf einen von drei verschiedenen Fällen stoßen: an der Adresse ist RAM abgebildet, es ist dort ROM abgebildet (etwa das VGA-BIOS) oder der Bereich ist frei.

Der Beispiel-Code von skull zeigt eine Möglichkeit, mit solchem Speicher umzugehen, da aber skull keinem physikalischen Gerät zugehörig ist, gibt dieser Treiber einfach Informationen über den Speicherbereich von 640 KByte bis 1 MByte aus und beendet sich dann. Der Code zum Analysieren des Speichers verdient aber trotzdem eine Beschreibung, damit Sie sehen können, wie man Speicher absuchen kann.

Der Code zur Suche nach RAM-Segmenten verwendet cli, um Interrupts abzuschalten, da diese Segmente nur durch physikalisches Schreiben und erneutes Lesen der Daten getestet werden können und echtes RAM von einem Interrupt-Handler mitten in unseren Tests verändert werden könnte. Der folgende Code ist nicht vollständig narrensicher, weil er RAM-Speicher auf Karten zur Datenerfassung für leere Bereiche halten könnte, wenn das Gerät aktiv versucht, den eigenen Speicher zu beschreiben, während unser Code diesen Bereich absucht. Diese Situation ist aber recht unwahrscheinlich.


unsigned char oldval, newval; /* Werte, die aus dem Speicher gelesen werden */
unsigned long flags;          /* speichert System-Flags */
unsigned long add, i;
void *base;

/* Mit ioremap bekommen wir einen Handle auf unseren Bereich */
base = ioremap(ISA_REGION_BEGIN, ISA_REGION_END - ISA_REGION_BEGIN);
base -= ISA_REGION_BEGIN;  /* Einmal versetzen */

/* Das gesamte Speicherloch in 2-KByte-Schritten absuchen */
for (add = ISA_REGION_BEGIN; add < ISA_REGION_END; add += STEP) {
   /*
    * Bereits alloziert?
    */
   if (check_mem_region (add, 2048)) {
          printk(KERN_INFO "%lx: Allocated\n", add);
          continue;
   }
   /*
    * Am Anfang des Bereichs lesen und schreiben und abwarten, was passiert.
    */
   save_flags(flags);
   cli();
   oldval = readb (base + add);  /* Ein Byte lesen */
   writeb (oldval⁁0xff, base + add);
   mb();
   newval = readb (base + add);
   writeb (oldval, base + add);
   restore_flags(flags);

   if ((oldval⁁newval) == 0xff) {  /* unsere Aenderung ist noch da: das ist RAM */
       printk(KERN_INFO "%lx: RAM\n", add);
       continue;
   }
   if ((oldval⁁newval) != 0) {  /* Zufaellige Bits haben sich veraendert: leer */
       printk(KERN_INFO "%lx: empty\n", add);
       continue;
   }

   /*
    * Erweiterungs-ROM (das beim Booten vom BIOS ausgeführt wird) hat
    * eine Signatur, bei der das erste Byte 0x55 und das zweite 0xaa ist
    * und das dritte Byte die Groeße des Speichers angibt.
    */
   if ( (oldval == 0x55) && (readb (base + add + 1) == 0xaa)) {
       int size = 512 * readb (base + add + 2);
       printk(KERN_INFO "%lx: Expansion ROM, %i bytes\n",
              add, size);
       add += (size & ˜2048) - 2048; /* skip it */
       continue;
   }

   /*
    * Wenn die obigen Tests fehlgeschlagen sind, wissen wir immer noch
    * nicht, ob es sich um ROM handelt oder ob der Bereich leer ist. Weil
    * leerer Speicher als 0x00, 0xff oder als unteres Adreßbyte erscheinen
    * kann, muessen wir mehrere Bytes ausprobieren: Wenn wenigstens eines
    * davon unterschiedlich von diesen drei Werten ist, dann ist dies ROM
    * (allerdings kein Boot-ROM).
    */
   printk(KERN_INFO "%lx: ", add);
   for (i=0; i<5; i++) {
       unsigned long radd = add + 57*(i+1);  /* ein "zufaelliger" Wert */
       unsigned char val = readb (base + radd);
       if (val && val != 0xFF && val != ((unsigned long) radd&0xFF))
          break;
   }
   printk("%s\n", i==5 ? "empty" : "ROM");
}

Die Erkennung von Speicher verursacht keine Kollisionen mit anderen Geräten, solange Sie während der Suche alle veränderten Bytes wieder in den Ursprungszustand zurücksetzen. Allerdings kann das Schreiben in den Speicher eines anderen Geräts immer unerwünschte Konsequenzen haben. Daher sollte ein solches Absuchen des Speichers vermieden werden, was aber nicht immer möglich ist, wenn man es mit älterer Hardware zu tun hat.

Fußnoten

[1]

Das stimmt eigentlich nicht ganz. Der Speicherbereich ist so klein und wird so oft verwendet, daß der Kernel beim Booten Seitentabellen für den Zugriff auf diese Adressen aufbaut. Die virtuellen Adressen für den Zugriff sind aber nicht die gleichen wie die des physikalischen Speichers, weswegen ioremap trotzdem benötigt wird. Außerdem war dieser Bereich in der Version 2.0 des Kernels direkt abgebildet; siehe dazu “the Section called Abwärtskompatibilität”.