Common Driver Interface — Änderungen 7080 → 7677

Zeile 11: Zeile 11:
 
* '''storage''': Massenspeicher (vorhandene Treiber: floppy, ata)
 
* '''storage''': Massenspeicher (vorhandene Treiber: floppy, ata)
  
−
Ein Betriebssystem muss dabei nicht zwingend die Schnittstellen aller Module implementieren. Alle Treiber benutzen Funktionen aus ''core'', aber es ist ohne weiteres möglich, z.B. neben ''core'' nur die Schnittstelle für Netzwerktreiber zu implementieren.
+
Ein Betriebssystem muss dabei nicht zwingend die Schnittstellen aller Module implementieren. Alle Treiber benutzen Funktionen aus ''core'', aber es ist ohne weiteres möglich, z. B. neben ''core'' nur die Schnittstelle für Netzwerktreiber zu implementieren.
  
 
=== Core ===
 
=== Core ===
Zeile 45: Zeile 45:
 
Zunächst eine Vorbemerkung: CDI wurde erstmals in týndur implementiert, das nach wie vor das Referenzsystem für CDI darstellt. týndur ist ein Mikrokernelsystem und aus diesem Grund liegt gelegentlich in Erklärungen die Annahme vor, dass ein Treiber ein eigenständiges Programm ist. CDI funktioniert aber auch mit einem monolithischen Kernel, aus der main-Funktion wird dann eine gewöhnliche Funktion, die zur Initialisierung des Treibers aufgerufen wird.
 
Zunächst eine Vorbemerkung: CDI wurde erstmals in týndur implementiert, das nach wie vor das Referenzsystem für CDI darstellt. týndur ist ein Mikrokernelsystem und aus diesem Grund liegt gelegentlich in Erklärungen die Annahme vor, dass ein Treiber ein eigenständiges Programm ist. CDI funktioniert aber auch mit einem monolithischen Kernel, aus der main-Funktion wird dann eine gewöhnliche Funktion, die zur Initialisierung des Treibers aufgerufen wird.
  
−
Sobald diese Funktion aufgerufen wird, registriert sie Datenstrukturen zur Beschreibung des Treibers bei CDI und führt die Initialisierung des Treibers durch. Im Allgemeinen werden dabei Geräte erkannt (z.B. auf dem PCI-Bus) und wiederum bei CDI registriert. Anschließend wird als letzte Aktion im Hauptprogramm die Funktion ''cdi_run_drivers'' aufgerufen, die bei einem Mikrokernelkonzept die Kontrolle an CDI übergibt und bei einem Monolithen im Allgemeinen leer ist und damit zum Kernel zurückkehrt, der die Treiberinitialisierung aufgerufen hat.
+
Sobald diese Funktion aufgerufen wird, registriert sie Datenstrukturen zur Beschreibung des Treibers bei CDI und führt die Initialisierung des Treibers durch. Im Allgemeinen werden dabei Geräte erkannt (z. B. auf dem PCI-Bus) und wiederum bei CDI registriert. Anschließend wird als letzte Aktion im Hauptprogramm die Funktion ''cdi_run_drivers'' aufgerufen, die bei einem Mikrokernelkonzept die Kontrolle an CDI übergibt und bei einem Monolithen im Allgemeinen leer ist und damit zum Kernel zurückkehrt, der die Treiberinitialisierung aufgerufen hat.
  
 
Der Treiber kommt ab diesem Zeitpunkt nur noch über Callbacks ins Spiel. Die Datenstrukturen zur Registrierung des Treibers enthalten Funktionspointer, die von CDI aufgerufen, wenn vom Treiber eine Aktion nötig ist. Das kann beispielsweise sein, wenn ein Interrupt für den Treiber eingetroffen ist oder wenn der Netzwerktreiber ein Ethernet-Paket versenden soll.
 
Der Treiber kommt ab diesem Zeitpunkt nur noch über Callbacks ins Spiel. Die Datenstrukturen zur Registrierung des Treibers enthalten Funktionspointer, die von CDI aufgerufen, wenn vom Treiber eine Aktion nötig ist. Das kann beispielsweise sein, wenn ein Interrupt für den Treiber eingetroffen ist oder wenn der Netzwerktreiber ein Ethernet-Paket versenden soll.
Zeile 68: Zeile 68:
 
   struct cdi_driver *driver;
 
   struct cdi_driver *driver;
 
};</c>
 
};</c>
−
Diese Struktur repräsentiert ein Gerät. "type" gibt den Typ an, "name" den Namen und "driver" einen Pointer zu einer Struktur, die den Gerätetreiber repräsentiert.
+
Diese Struktur repräsentiert ein Gerät. „type“ gibt den Typ an, „name“ den Namen und „driver“ einen Pointer zu einer Struktur, die den Gerätetreiber repräsentiert.
 
<c>struct cdi_driver
 
<c>struct cdi_driver
 
{
 
{
Zeile 78: Zeile 78:
 
   void (*destroy)(struct cdi_driver *driver);
 
   void (*destroy)(struct cdi_driver *driver);
 
};</c>
 
};</c>
−
Diese Struktur wiederum repräsentiert einen Gerätetreiber. "type" gibt den Typ an, "name" den Namen und "devices" ist eine Liste der registrierten Geräte. Die Funktion init_device() muss vom Betriebssystem aufgerufen werden, um ein Gerät zu initialisieren, remove_device() entfernt ein Gerät und destroy() deinitialisiert den Treiber.
+
Diese Struktur wiederum repräsentiert einen Gerätetreiber. „type“ gibt den Typ an, „name“ den Namen und „devices“ ist eine Liste der registrierten Geräte. Die Funktion init_device() muss vom Betriebssystem aufgerufen werden, um ein Gerät zu initialisieren, remove_device() entfernt ein Gerät und destroy() deinitialisiert den Treiber.
 
<c>struct cdi_pci_device
 
<c>struct cdi_pci_device
 
{
 
{
Zeile 93: Zeile 93:
 
   cdi_list_t resources;
 
   cdi_list_t resources;
 
};</c>
 
};</c>
−
Mit dieser Struktur wird ein [[PCI]]-Gerät beschrieben. "bus" ist der Bus, auf dem es sich befindet, "dev" das Gerät und "function" die Funktion. "vendor_id" gibt die Hersteller-ID an, "device_id" die Geräte-ID. Die Classcodes befinden sich in "class_id" (Base class), "subclass_id" (Subclass) und "interface_id" (Programming interface). "rev_id" enthält die Revisions-ID. In "irq" steht der IRQ, den das Gerät verwendet. "resources" ist eine Liste mit den I/O- und Speicherresourcen, ihre Elemente sind vom Typ "struct cdi_pci_resource".
+
Mit dieser Struktur wird ein [[PCI]]-Gerät beschrieben. „bus“ ist der Bus, auf dem es sich befindet, „dev“ das Gerät und „function“ die Funktion. „vendor_id“ gibt die Hersteller-ID an, „device_id“ die Geräte-ID. Die Classcodes befinden sich in „class_id“ (Base class), „subclass_id“ (Subclass) und „interface_id“ (Programming interface). „rev_id“ enthält die Revisions-ID. In „irq“ steht der IRQ, den das Gerät verwendet. „resources“ ist eine Liste mit den I/O- und Speicherresourcen, ihre Elemente sind vom Typ „struct cdi_pci_resource“.
 
<c>typedef enum
 
<c>typedef enum
 
{
 
{
Zeile 99: Zeile 99:
 
   CDI_PCI_IOPORTS
 
   CDI_PCI_IOPORTS
 
} cdi_res_t;</c>
 
} cdi_res_t;</c>
−
"cdi_res_t" gibt den Typ einer PCI-Resource an (I/O-Ports oder Speicherbereich).
+
„cdi_res_t“ gibt den Typ einer PCI-Resource an (I/O-Ports oder Speicherbereich).
 
<c>struct cdi_pci_resource
 
<c>struct cdi_pci_resource
 
{
 
{
Zeile 108: Zeile 108:
 
   void        *address;
 
   void        *address;
 
};</c>
 
};</c>
−
Informationen zu einer Resource eines PCI-Gerätes erhält man aus dieser Struktur. "type" gibt den Typ an (I/O-Ports oder Speicherbereich), "start" den Beginn, "length" die Länge, "index" den Index der Resource (0 ist die erste usw.) und "address" gibt bei Speicherbereichen ihre virtuelle Adresse an (vorher muss der Bereich mit cdi_pci_alloc_memory() reserviert worden sein).
+
Informationen zu einer Resource eines PCI-Gerätes erhält man aus dieser Struktur. „type“ gibt den Typ an (I/O-Ports oder Speicherbereich), „start“ den Beginn, „length“ die Länge, „index“ den Index der Resource (0 ist die erste usw.) und „address“ gibt bei Speicherbereichen ihre virtuelle Adresse an (vorher muss der Bereich mit cdi_pci_alloc_memory() reserviert worden sein).
 
<c>typedef ??? cdi_list_t;</c>
 
<c>typedef ??? cdi_list_t;</c>
−
"cdi_list_t" repräsentiert eine Linked-List. Dieser Datentyp muss ein Pointer sein, in týndur ist er ein Pointer auf eine Struktur mit den Elementen "anchor" (zeigt auf das erste Element der Liste) und "size" (Anzahl der Elemente in der Liste). Die Elemente der Liste wiederum müssen einen Pointer speichern können (der Wert des Elements) und sollten desweiteren einen Pointer auf das nächste Element enthalten (eine Linked-List eben).
+
„cdi_list_t“ repräsentiert eine Linked-List. Dieser Datentyp muss ein Pointer sein, in týndur ist er ein Pointer auf eine Struktur mit den Elementen „anchor“ (zeigt auf das erste Element der Liste) und „size“ (Anzahl der Elemente in der Liste). Die Elemente der Liste wiederum müssen einen Pointer speichern können (der Wert des Elements) und sollten desweiteren einen Pointer auf das nächste Element enthalten (eine Linked-List eben).
  
 
==== Funktionen ====
 
==== Funktionen ====
Zeile 116: Zeile 116:
 
===== cdi.h =====
 
===== cdi.h =====
 
<c>void cdi_init(void);</c>
 
<c>void cdi_init(void);</c>
−
Initialisiert die CDI-Strukturen des Betriebssystems, muss vor Benutzung anderer CDI-Funktionen aufgerufen werden - wiederholte Aufrufe müssen ohne Effekt bleiben.
+
Initialisiert die CDI-Strukturen des Betriebssystems, muss vor Benutzung anderer CDI-Funktionen aufgerufen werden – wiederholte Aufrufe müssen ohne Effekt bleiben.
 
<c>void cdi_run_drivers(void);</c>
 
<c>void cdi_run_drivers(void);</c>
−
Diese Funktion startet alle registrierten Treiber. Das Betriebssystem sollte alle registrierten Treiber durchgehen und bei allen für einen Treiber registrierten Geräten (Die Liste "(struct cdi_driver *)->devices") im Feld "driver" (in "struct cdi_device") den entsprechenden Treiber eintragen sowie, wenn "(struct cdi_driver *)->init_device" ungleich NULL ist, diese Funktion mit dem Gerät als Parameter aufrufen.
+
Diese Funktion startet alle registrierten Treiber. Das Betriebssystem sollte alle registrierten Treiber durchgehen und bei allen für einen Treiber registrierten Geräten (Die Liste „(struct cdi_driver *)->devices“) im Feld „driver“ (in „struct cdi_device“) den entsprechenden Treiber eintragen sowie, wenn „(struct cdi_driver *)->init_device“ ungleich NULL ist, diese Funktion mit dem Gerät als Parameter aufrufen.
 
<c>void cdi_driver_init(struct cdi_driver *driver);</c>
 
<c>void cdi_driver_init(struct cdi_driver *driver);</c>
−
Initialisiert den angegebenen Treiber. Der Treiber hat bereits alle Felder in "driver" initialisiert, lediglich die "devices"-Liste muss vom Betriebssystem mittels cdi_list_create() erstellt werden.
+
Initialisiert den angegebenen Treiber. Der Treiber hat bereits alle Felder in „driver“ initialisiert, lediglich die „devices“-Liste muss vom Betriebssystem mittels cdi_list_create() erstellt werden.
 
<c>void cdi_driver_destroy(struct cdi_driver *driver);</c>
 
<c>void cdi_driver_destroy(struct cdi_driver *driver);</c>
−
Deinitialisiert den angegebenen Treiber. Ist der Funktionspointer "driver->remove_device" ungleich NULL, so sollte diese Funktion für alle registrierten Geräte (in der Liste "driver->devices") aufgerufen werden.
+
Deinitialisiert den angegebenen Treiber. Ist der Funktionspointer „driver->remove_device“ ungleich NULL, so sollte diese Funktion für alle registrierten Geräte (in der Liste „driver->devices“) aufgerufen werden.
 
<c>void cdi_driver_register(struct cdi_driver *driver);</c>
 
<c>void cdi_driver_register(struct cdi_driver *driver);</c>
 
Registriert einen CDI-Treiber. Es bietet sich an, ihn in einer globalen Liste einzutragen und diese Liste dann beim cdi_run_drivers()-Aufruf abzuarbeiten.
 
Registriert einen CDI-Treiber. Es bietet sich an, ihn in einer globalen Liste einzutragen und diese Liste dann beim cdi_run_drivers()-Aufruf abzuarbeiten.
Zeile 128: Zeile 128:
 
===== cdi/misc.h =====
 
===== cdi/misc.h =====
 
<c>void cdi_register_irq(uint8_t irq, void (*handler)(struct cdi_device*), struct cdi_device *device);</c>
 
<c>void cdi_register_irq(uint8_t irq, void (*handler)(struct cdi_device*), struct cdi_device *device);</c>
−
Registriert eine ISR "handler" für den angegebenen IRQ "irq". Der ISR muss beim Aufruf das angegebene CDI-Gerät "device" als Parameter übergeben werden.
+
Registriert eine ISR „handler“ für den angegebenen IRQ „irq“. Der ISR muss beim Aufruf das angegebene CDI-Gerät „device“ als Parameter übergeben werden.
 
<c>int cdi_wait_irq(uint8_t irq, uint32_t timeout);</c>
 
<c>int cdi_wait_irq(uint8_t irq, uint32_t timeout);</c>
−
Wartet, bis der angegeben IRQ aufgetreten ist. "timeout" gibt die Zeit in Millisekunden an, die maximal gewartet werden soll. Ist der IRQ seit dem letzten cdi_reset_wait_irq() und vor "timeout" aufgetreten, wird 0 zurückgegeben, sonst -1.
+
Wartet, bis der angegeben IRQ aufgetreten ist. „timeout“ gibt die Zeit in Millisekunden an, die maximal gewartet werden soll. Ist der IRQ seit dem letzten cdi_reset_wait_irq() und vor „timeout“ aufgetreten, wird 0 zurückgegeben, sonst -1.
 
<c>int cdi_reset_wait_irq(uint8_t irq);</c>
 
<c>int cdi_reset_wait_irq(uint8_t irq);</c>
 
Setzt den Status (aufgerufen oder nicht) des IRQs zurück.
 
Setzt den Status (aufgerufen oder nicht) des IRQs zurück.
 
<c>int cdi_alloc_phys_mem(size_t size, void **vaddr, void **paddr);</c>
 
<c>int cdi_alloc_phys_mem(size_t size, void **vaddr, void **paddr);</c>
−
Reserviert physisch kontinuierlichen Speicher der Größe "size". Der Beginn wird in "**paddr" gespeichert, die virtuelle Adresse in "**vaddr". Bei Erfolg wird 0 zurückgegeben, sonst -1.
+
Reserviert physisch kontinuierlichen Speicher der Größe „size“. Der Beginn wird in „**paddr“ gespeichert, die virtuelle Adresse in „**vaddr“. Bei Erfolg wird 0 zurückgegeben, sonst -1.
 
<c>void *cdi_alloc_phys_addr(size_t size, uintptr_t paddr);</c>
 
<c>void *cdi_alloc_phys_addr(size_t size, uintptr_t paddr);</c>
−
Reserviert physisch kontinuierlichen Speicher der Größe "size" an der physischen Adresse "paddr". Bei Erfolg wird die virtuelle Adresse zurückgegeben, im Fehlerfall hingegen NULL.
+
Reserviert physisch kontinuierlichen Speicher der Größe „size“ an der physischen Adresse „paddr“. Bei Erfolg wird die virtuelle Adresse zurückgegeben, im Fehlerfall hingegen NULL.
 
<c>int cdi_ioports_alloc(uint16_t start, uint16_t count);</c>
 
<c>int cdi_ioports_alloc(uint16_t start, uint16_t count);</c>
−
Reserviert "count" I/O-Ports ab dem Port "start". Bei Erfolg wird 0 zurückgegeben, im Fehlerfall -1.
+
Reserviert „count“ I/O-Ports ab dem Port „start“. Bei Erfolg wird 0 zurückgegeben, im Fehlerfall -1.
 
<c>int cdi_ioports_free(uint16_t start, uint16_t count);</c>
 
<c>int cdi_ioports_free(uint16_t start, uint16_t count);</c>
−
Gibt reservierte "count" I/O-Ports ab "start" wieder frei. Bei Erfolg ist der Rückgabewert 0, sonst -1.
+
Gibt reservierte „count“ I/O-Ports ab „start“ wieder frei. Bei Erfolg ist der Rückgabewert 0, sonst -1.
 
<c>void cdi_sleep_ms(uint32_t ms);</c>
 
<c>void cdi_sleep_ms(uint32_t ms);</c>
−
Wartet "ms" Millisekunden.
+
Wartet „ms“ Millisekunden.
  
 
===== cdi/pci.h =====
 
===== cdi/pci.h =====
 
<c>void cdi_pci_get_all_devices(cdi_list_t list);</c>
 
<c>void cdi_pci_get_all_devices(cdi_list_t list);</c>
−
Fügt alle vorhandenen PCI-Geräte in die Liste "list" ein.
+
Fügt alle vorhandenen PCI-Geräte in die Liste „list“ ein.
 
<c>void cdi_pci_device_destroy(struct cdi_pci_device *device);</c>
 
<c>void cdi_pci_device_destroy(struct cdi_pci_device *device);</c>
 
Gibt die Informationen zum PCI-Gerät frei.
 
Gibt die Informationen zum PCI-Gerät frei.
Zeile 180: Zeile 180:
 
Gibt 1 zurück, wenn die Liste leer ist, sonst 0.
 
Gibt 1 zurück, wenn die Liste leer ist, sonst 0.
 
<c>void *cdi_list_get(cdi_list_t list, size_t index);</c>
 
<c>void *cdi_list_get(cdi_list_t list, size_t index);</c>
−
Gibt den Wert des Elements am Index "index" zurück (oder NULL bei Fehler).
+
Gibt den Wert des Elements am Index „index“ zurück (oder NULL bei Fehler).
 
<c>cdi_list_t cdi_list_insert(cdi_list_t list, size_t index, void *value);</c>
 
<c>cdi_list_t cdi_list_insert(cdi_list_t list, size_t index, void *value);</c>
−
Fügt ein Element in die Liste an der Position "index" ein und gibt die Liste zurück (oder NULL bei Fehler).
+
Fügt ein Element in die Liste an der Position „index“ ein und gibt die Liste zurück (oder NULL bei Fehler).
 
<c>void *cdi_list_remove(cdi_list_t list, size_t index);</c>
 
<c>void *cdi_list_remove(cdi_list_t list, size_t index);</c>
−
Entfernt das Listenelement an der Stelle "index" und gibt seinen Wert zurück (oder NULL bei Fehler).
+
Entfernt das Listenelement an der Stelle „index“ und gibt seinen Wert zurück (oder NULL bei Fehler).
 
<c>size_t cdi_list_size(cdi_list_t list);</c>
 
<c>size_t cdi_list_size(cdi_list_t list);</c>
 
Gibt die Anzahl der Elemente in der Liste zurück.
 
Gibt die Anzahl der Elemente in der Liste zurück.
Zeile 197: Zeile 197:
 
   void (*send_packet)(struct cdi_net_device *device, void *data, size_t size);
 
   void (*send_packet)(struct cdi_net_device *device, void *data, size_t size);
 
};</c>
 
};</c>
−
Diese Struktur beschreibt ein CDI.net-Gerät. "dev" ist eine Struktur, die das Gerät im Allgemeinen repräsentiert, "mac" gibt die MAC-Adresse an und "number" die Nummer der Netzwerkkarte. Mit der Funktion send_packet() können Pakete gesendet werden, wobei "device" ein Pointer auf diese Struktur ist, "data" ein Pointer auf die zu sendenden Daten, während "size" die Größe des Puffers "data" angibt. Wichtig: Man darf nicht erwarten, dass send_packet() reentrant (also gegen [[Race Condition|Race Conditions]] abgesichert) ist! Das Betriebssystem muss somit selbst dafür Sorge tragen, dass diese Funktion immer nur von maximal einem einzigen Prozess durchlaufen wird.
+
Diese Struktur beschreibt ein CDI.net-Gerät. „dev“ ist eine Struktur, die das Gerät im Allgemeinen repräsentiert, „mac“ gibt die MAC-Adresse an und „number“ die Nummer der Netzwerkkarte. Mit der Funktion send_packet() können Pakete gesendet werden, wobei „device“ ein Pointer auf diese Struktur ist, „data“ ein Pointer auf die zu sendenden Daten, während „size“ die Größe des Puffers „data“ angibt. Wichtig: Man darf nicht erwarten, dass send_packet() reentrant (also gegen [[Race Condition|Race Conditions]] abgesichert) ist! Das Betriebssystem muss somit selbst dafür Sorge tragen, dass diese Funktion immer nur von maximal einem einzigen Prozess durchlaufen wird.
 
<c>struct cdi_net_driver
 
<c>struct cdi_net_driver
 
{
 
{
 
   struct cdi_driver drv;
 
   struct cdi_driver drv;
 
};</c>
 
};</c>
−
Mit dieser Struktur werden CDI.net-Treiber beschrieben. Sie enthält lediglich die Struktur "struct cdi_driver", die den Treiber im Allgmeinen repräsentiert.
+
Mit dieser Struktur werden CDI.net-Treiber beschrieben. Sie enthält lediglich die Struktur „struct cdi_driver“, die den Treiber im Allgmeinen repräsentiert.
  
 
==== Funktionen ====
 
==== Funktionen ====
 
<c>void cdi_net_driver_init(struct cdi_net_driver *driver);</c>
 
<c>void cdi_net_driver_init(struct cdi_net_driver *driver);</c>
−
Diese Funktion wird aufgerufen, um einen CDI.net-Treiber "driver" zu initialisieren. Das Betriebssystem sollte "driver->drv.type" auf CDI_NETWORK setzen und anschließend "cdi_driver_init((struct cdi_driver *)driver)" aufrufen.
+
Diese Funktion wird aufgerufen, um einen CDI.net-Treiber „driver“ zu initialisieren. Das Betriebssystem sollte „driver->drv.type“ auf CDI_NETWORK setzen und anschließend „cdi_driver_init((struct cdi_driver *)driver)“ aufrufen.
 
<c>void cdi_net_driver_destroy(struct cdi_net_driver *driver);</c>
 
<c>void cdi_net_driver_destroy(struct cdi_net_driver *driver);</c>
 
Diese Funktion wiederum deinitialisiert einen CDI.net-Treiber.
 
Diese Funktion wiederum deinitialisiert einen CDI.net-Treiber.
 
<c>void cdi_net_device_init(struct cdi_net_device *device);</c>
 
<c>void cdi_net_device_init(struct cdi_net_device *device);</c>
−
Mit dieser Funktion werden CDI.net-Geräte (also Netzwerkkarten) initialisiert. Der Treiber hat alle vorgegebenen Felder in der "cdi_net_device"-Struktur "device" außer "number" bereits initialisiert. "number" muss vom Betriebssystem gesetzt werden.
+
Mit dieser Funktion werden CDI.net-Geräte (also Netzwerkkarten) initialisiert. Der Treiber hat alle vorgegebenen Felder in der „cdi_net_device“-Struktur „device“ außer „number“ bereits initialisiert. „number“ muss vom Betriebssystem gesetzt werden.
 
<c>void cdi_net_receive(struct cdi_net_device *device, void *buffer, size_t size);</c>
 
<c>void cdi_net_receive(struct cdi_net_device *device, void *buffer, size_t size);</c>
−
Diese Funktion wird vom CDI.net-Treiber aufgerufen, wenn Daten von einer Netzwerkkarte empfangen wurden. "device" gibt die Netzwerkkarte an, "buffer" ist ein Pointer zum empfangenen Paket und "size" enthält die Größe des Pakets.
+
Diese Funktion wird vom CDI.net-Treiber aufgerufen, wenn Daten von einer Netzwerkkarte empfangen wurden. „device“ gibt die Netzwerkkarte an, „buffer“ ist ein Pointer zum empfangenen Paket und „size“ enthält die Größe des Pakets.
  
 
== Betriebssysteme, welche CDI benutzen ==
 
== Betriebssysteme, welche CDI benutzen ==