[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[tyndur-devel] [PATCH 4/4] ata: Unterstuetzung fuer DMA



+ ata: Unterstuetzung fuer DMA

Signed-off-by: Antoine Kaufmann <toni@xxxxxxxxxx>
---
 build/config/grub_hd.cfg      |    1 +
 src/modules/cdi/ata/ata.c     |   68 ++++++++++++++++-------
 src/modules/cdi/ata/device.c  |   26 ++++++++-
 src/modules/cdi/ata/device.h  |   53 ++++++++++++++++--
 src/modules/cdi/ata/main.c    |   69 ++++++++++++++++++++---
 src/modules/cdi/ata/request.c |  122 +++++++++++++++++++++++++++++++++++++++++
 6 files changed, 302 insertions(+), 37 deletions(-)

diff --git a/build/config/grub_hd.cfg b/build/config/grub_hd.cfg
index f29cd6a..cd0f4f2 100644
--- a/build/config/grub_hd.cfg
+++ b/build/config/grub_hd.cfg
@@ -1,6 +1,7 @@
 title tyndur
 kernel /boot/tyndur debug=s
 module /modules/init boot=file:/
+module /modules/pci
 module /modules/ata
 module /modules/ext2
 module /modules/console servmgr:/term servmgr:/term
diff --git a/src/modules/cdi/ata/ata.c b/src/modules/cdi/ata/ata.c
index 14e777b..0646be9 100644
--- a/src/modules/cdi/ata/ata.c
+++ b/src/modules/cdi/ata/ata.c
@@ -1,5 +1,5 @@
 /*  
- * Copyright (c) 2007 The tyndur Project. All rights reserved.
+ * Copyright (c) 2007-2009 The tyndur Project. All rights reserved.
  *
  * This code is derived from software contributed to the tyndur Project
  * by Antoine Kaufmann.
@@ -86,6 +86,11 @@ int ata_drv_identify(struct ata_device* dev)
         dev->dev.storage.block_count = id.lba_sector_count;
     }
 
+    // Pruefen ob DMA unterstuetzt wird
+    if (id.capabilities.dma) {
+        dev->dma = 1;
+    }
+
     // Wenn keiner der LBA-Modi unterstuetzt wird, muss abgebrochen werden, da
     // CHS noch nicht implementiert ist.
     if (!dev->lba48 && !dev->lba28) {
@@ -122,38 +127,59 @@ static int ata_drv_rw_sectors(struct ata_device* dev, int direction,
     uint16_t current_count;
     void* current_buffer = buffer;
     uint64_t lba = start;
-
+    int max_count;
     // Anzahl der Sektoren die noch uebrig sind
     size_t count_left = count;
 
-    // Solange wie noch Sektoren uebrig sind, wird gelesen
-    while (count_left > 0) {
-        // Entscheiden wieviele Sektoren im aktuellen Durchlauf gelesen werden
-        if (count_left > 256) {
-            current_count = 256;
+
+
+    // Request vorbereiten
+    request.dev = dev;
+    request.flags.poll = 0;
+    request.flags.ata = 0;
+    request.flags.lba = 1;
+
+    // Richtung festlegen
+    if (direction == 0) {
+        request.flags.direction = READ;
+    } else {
+        request.flags.direction = WRITE;
+    }
+
+
+    if (dev->dma && dev->controller->dma_use) {
+        // DMA
+        max_count = ATA_DMA_MAXSIZE / ATA_SECTOR_SIZE;
+        if (direction) {
+            request.registers.ata.command = WRITE_SECTORS_DMA;
         } else {
-            current_count = count_left;
+            request.registers.ata.command = READ_SECTORS_DMA;
+        }
+        request.protocol = DMA;
+    } else {
+        // PIO
+        max_count = 256;
+        if (direction) {
+            request.registers.ata.command = WRITE_SECTORS;
+        } else {
+            request.registers.ata.command = READ_SECTORS;
         }
-        
-        // Request vorbereiten
-        request.dev = dev;
-        // TODO: DMA, UltraDMA...
         request.protocol = PIO;
 
-        // FIXME
+        // Mit PIO pollen wir lieber, da IRQs dort _wirklich langsam_ sind
         request.flags.poll = 1;
-        request.flags.ata = 0;
-        request.flags.lba = 1;
+    }
 
-        // Richtung festlegen
-        if (direction == 0) {
-            request.flags.direction = READ;
-            request.registers.ata.command = READ_SECTORS;
+    // Solange wie noch Sektoren uebrig sind, wird gelesen
+    while (count_left > 0) {
+        // Entscheiden wieviele Sektoren im aktuellen Durchlauf gelesen werden
+        if (count_left > max_count) {
+            current_count = max_count;
         } else {
-            request.flags.direction = WRITE;
-            request.registers.ata.command = WRITE_SECTORS;
+            current_count = count_left;
         }
         
+
         // Achtung: Beim casten nach uint8_t wird bei 256 Sektoren eine 0.
         // Das macht aber nichts, da in der Spezifikation festgelegt ist,
         // dass 256 Sektoren gelesen werden sollen, wenn im count-Register
diff --git a/src/modules/cdi/ata/device.c b/src/modules/cdi/ata/device.c
index 7c896b8..4ec7696 100644
--- a/src/modules/cdi/ata/device.c
+++ b/src/modules/cdi/ata/device.c
@@ -231,8 +231,13 @@ void ata_init_controller(struct ata_controller* controller)
     }
     if (cdi_ioports_alloc(controller->port_ctl_base, 1) != 0) {
         DEBUG("Fehler beim allozieren der I/O-Ports\n");
-        cdi_ioports_free(controller->port_cmd_base, 8);
-        return;
+        goto error_free_cmdbase;
+    }
+    if (controller->port_bmr_base &&
+        (cdi_ioports_alloc(controller->port_bmr_base, 8) != 0))
+    {
+        DEBUG("ata: Fehler beim allozieren der I/O-Ports\n");
+        goto error_free_ctlbase;
     }
     
     // Da NIEN nicht ueberall sauber funktioniert, muss jetzt trotzdem schon
@@ -315,6 +320,23 @@ void ata_init_controller(struct ata_controller* controller)
             free(dev);
         }
     }
+
+    // Abschliessend wird noch DMA vorbereitet, wenn moeglich
+    if (controller->port_bmr_base) {
+        cdi_alloc_phys_mem(sizeof(uint64_t), (void**) &controller->prdt_virt,
+            (void**) &controller->prdt_phys);
+        cdi_alloc_phys_mem(ATA_DMA_MAXSIZE, (void**) &controller->dma_buf_virt,
+            (void**) &controller->dma_buf_phys);
+
+        controller->dma_use = 1;
+    }
+
+    return;
+
+error_free_ctlbase:
+    cdi_ioports_free(controller->port_ctl_base, 1);
+error_free_cmdbase:
+    cdi_ioports_free(controller->port_cmd_base, 8);
 }
 
 void ata_remove_controller(struct ata_controller* controller)
diff --git a/src/modules/cdi/ata/device.h b/src/modules/cdi/ata/device.h
index 54dfb80..36a86d1 100644
--- a/src/modules/cdi/ata/device.h
+++ b/src/modules/cdi/ata/device.h
@@ -1,5 +1,5 @@
 /*  
- * Copyright (c) 2007 The tyndur Project. All rights reserved.
+ * Copyright (c) 2007-2009 The tyndur Project. All rights reserved.
  *
  * This code is derived from software contributed to the tyndur Project
  * by Antoine Kaufmann.
@@ -48,6 +48,10 @@
 
 #define ATAPI_ENABLE
 
+#define PCI_CLASS_ATA           0x01
+#define PCI_SUBCLASS_ATA        0x01
+#define PCI_VENDOR_VIA          0x1106
+
 #define ATA_PRIMARY_CMD_BASE    0x1F0
 #define ATA_PRIMARY_CTL_BASE    0x3F6
 #define ATA_PRIMARY_IRQ         14
@@ -76,6 +80,7 @@
 // Diese Register werden ueber die port_cmd_base angesprochen
 #define REG_DATA                0x0
 #define REG_ERROR               0x1
+#define REG_FEATURES            0x1
 #define REG_SEC_CNT             0x2
 #define REG_LBA_LOW             0x3
 #define REG_LBA_MID             0x4
@@ -110,6 +115,24 @@
 #define CONTROL_SRST            (1 << 2)
 #define CONTROL_NIEN            (1 << 1)
 
+
+// Busmaster-Register (in port_bmr_base)
+#define BMR_COMMAND             0x0
+#define BMR_STATUS              0x2
+#define BMR_PRDT                0x4
+
+// Bits im Busmaster Command Register
+#define BMR_CMD_START           (1 << 0)
+#define BMR_CMD_WRITE           (1 << 3)
+
+// Bits im Busmaster Status Register
+#define BMR_STATUS_ERROR        (1 << 1)
+#define BMR_STATUS_IRQ          (1 << 2)
+
+/// Maximale Groesse eines DMA-Transfers
+#define ATA_DMA_MAXSIZE         (64 * 1024)
+
+
 // Debug
 #ifdef DEBUG_ENABLE
     #define DEBUG(fmt, ...) printf("ata: " fmt, __VA_ARGS__)
@@ -320,7 +343,10 @@ struct ata_device {
 
     // 1 Wenn das Geraet lba28 unterstuetzt
     uint8_t                     lba28;
-    
+
+    /// 1 wenn das Geraet DMA unterstuetzt
+    uint8_t                     dma;
+
     // Funktionen fuer den Zugriff auf dieses Geraet
     int (*read_sectors) (struct ata_device* dev, uint64_t start, size_t count,
         void* dest);
@@ -335,14 +361,26 @@ struct ata_controller {
     uint8_t                     id;
     uint16_t                    port_cmd_base;
     uint16_t                    port_ctl_base;
+    uint16_t                    port_bmr_base;
     uint16_t                    irq;
-    uint16_t                    irq_cnt;
 
     // Wird auf 1 gesetzt wenn IRQs benutzt werden sollen, also das NIEN-Bit im
     // Control register nicht aktiviert ist.
     int                         irq_use;
+    /// Wird auf 1 gesetzt wenn DMA benutzt werden darf
+    int                         dma_use;
     // HACKKK ;-)
     struct ata_device           irq_dev;
+
+
+    /// Physische Adresse der Physical Region Descriptor Table (fuer DMA)
+    uint64_t*                   prdt_phys;
+    /// Virtuelle Adresse der Physical Region Descriptor Table (fuer DMA)
+    uint64_t*                   prdt_virt;
+    /// Physische Adresse des DMA-Puffers
+    void*                       dma_buf_phys;
+    /// Virtuelle Adresse des DMA-Puffers
+    void*                       dma_buf_virt;
 };
 
 struct ata_request {
@@ -350,7 +388,8 @@ struct ata_request {
     
     enum {
         NON_DATA,
-        PIO
+        PIO,
+        DMA,
     } protocol;
 
     // Flags fuer die Uebertragung
@@ -378,10 +417,14 @@ struct ata_request {
                 IDENTIFY_PACKET_DEVICE = 0xA1,
                 PACKET = 0xA0,
                 READ_SECTORS = 0x20,
-                WRITE_SECTORS = 0x30
+                READ_SECTORS_DMA = 0xC8,
+                SET_FEATURES = 0xEF,
+                WRITE_SECTORS = 0x30,
+                WRITE_SECTORS_DMA = 0xCA,
             } command;
             uint8_t count;
             uint64_t lba;
+            uint8_t features;
         } ata;
     } registers;
 
diff --git a/src/modules/cdi/ata/main.c b/src/modules/cdi/ata/main.c
index 750f7b2..97b470d 100644
--- a/src/modules/cdi/ata/main.c
+++ b/src/modules/cdi/ata/main.c
@@ -1,5 +1,5 @@
 /*  
- * Copyright (c) 2007 The tyndur Project. All rights reserved.
+ * Copyright (c) 2007-2009 The tyndur Project. All rights reserved.
  *
  * This code is derived from software contributed to the tyndur Project
  * by Antoine Kaufmann.
@@ -39,6 +39,7 @@
 
 #include "cdi/storage.h"
 #include "cdi/lists.h"
+#include "cdi/pci.h"
 
 #include "device.h"
 
@@ -48,18 +49,18 @@ static const char* driver_storage_name = "ata";
 static const char* driver_scsi_name = "atapi";
 static cdi_list_t controller_list = NULL;
 
-static void ata_driver_init(void);
+static void ata_driver_init(int argc, char* argv[]);
 static void ata_driver_destroy(struct cdi_driver* driver);
 static void atapi_driver_destroy(struct cdi_driver* driver);
 
 #ifdef CDI_STANDALONE
-int main(void)
+int main(int argc, char* argv[])
 #else
-int init_ata(void)
+int init_ata(int argc, char* argv[])
 #endif
 {
     cdi_init();
-    ata_driver_init();
+    ata_driver_init(argc, argv);
     cdi_storage_driver_register((struct cdi_storage_driver*) &driver_storage);
     cdi_scsi_driver_register((struct cdi_scsi_driver*) &driver_scsi);
 
@@ -73,9 +74,14 @@ int init_ata(void)
 /**
  * Initialisiert die Datenstrukturen fuer den sis900-Treiber
  */
-static void ata_driver_init()
+static void ata_driver_init(int argc, char* argv[])
 {
     struct ata_controller* controller;
+    uint16_t busmaster_regbase = 0;
+    struct cdi_pci_device* pci_dev;
+    cdi_list_t pci_devices;
+    int i;
+    int j;
 
     // Konstruktor der Vaterklasse
     cdi_storage_driver_init((struct cdi_storage_driver*) &driver_storage);
@@ -99,11 +105,55 @@ static void ata_driver_init()
     
     // Liste mit Controllern initialisieren
     controller_list = cdi_list_create();
-    
+
+
+    // PCI-Geraet fuer Controller finden
+    pci_devices = cdi_list_create();
+    cdi_pci_get_all_devices(pci_devices);
+    for (i = 0; (pci_dev = cdi_list_get(pci_devices, i)) && !busmaster_regbase;
+        i++)
+    {
+        struct cdi_pci_resource* res;
+
+        if ((pci_dev->class_id != PCI_CLASS_ATA) ||
+            (pci_dev->subclass_id != PCI_SUBCLASS_ATA))
+        {
+            continue;
+        }
+
+        // Jetzt noch die Ressource finden mit den Busmaster-Registern
+        // TODO: Das funktioniert so vermutlich nicht ueberall, da es
+        // warscheinlich auch Kontroller mit nur einem Kanal gibt und solche bei
+        // denen die BM-Register im Speicher gemappt sind
+        for (j = 0; (res = cdi_list_get(pci_dev->resources, j)); j++) {
+            if ((res->type == CDI_PCI_IOPORTS) && (res->length == 16)) {
+                busmaster_regbase = res->start;
+                break;
+            }
+        }
+    }
+
+    // Kaputte VIA-Kontroller sollten nur PIO benutzen, da es bei DMA zu
+    // haengern kommt. Dabei handelt es sich um den Kontroller 82C686B
+    if (pci_dev && (pci_dev->vendor_id == PCI_VENDOR_VIA) &&
+        (pci_dev->device_id == 0x686))
+    {
+        busmaster_regbase = 0;
+    } else {
+        // Wenn der nodma-Parameter uebergeben wurde, deaktivieren wir dma auch
+        for (i = 1; i < argc; i++) {
+            if (!strcmp(argv[i], "nodma")) {
+                busmaster_regbase = 0;
+                break;
+            }
+        }
+    }
+
     // Primaeren Controller vorbereiten
-    controller = malloc(sizeof(*controller));
+    controller = calloc(1, sizeof(*controller));
     controller->port_cmd_base = ATA_PRIMARY_CMD_BASE;
     controller->port_ctl_base = ATA_PRIMARY_CTL_BASE;
+    controller->port_bmr_base = busmaster_regbase;
     controller->irq = ATA_PRIMARY_IRQ;
     controller->id = 0;
     controller->storage = (struct cdi_storage_driver*) &driver_storage;
@@ -112,9 +162,10 @@ static void ata_driver_init()
     cdi_list_push(controller_list, controller);
     
     // Sekundaeren Controller vorbereiten
-    controller = malloc(sizeof(*controller));
+    controller = calloc(1, sizeof(*controller));
     controller->port_cmd_base = ATA_SECONDARY_CMD_BASE;
     controller->port_ctl_base = ATA_SECONDARY_CTL_BASE;
+    controller->port_bmr_base = (busmaster_regbase ? busmaster_regbase : 0);
     controller->irq = ATA_SECONDARY_IRQ;
     controller->id = 1;
     controller->storage = (struct cdi_storage_driver*) &driver_storage;
diff --git a/src/modules/cdi/ata/request.c b/src/modules/cdi/ata/request.c
index 90e0d67..957661e 100644
--- a/src/modules/cdi/ata/request.c
+++ b/src/modules/cdi/ata/request.c
@@ -120,6 +120,9 @@ static int ata_request_command(struct ata_request* request)
     // TODO: HOB
     ata_reg_outb(ctrl, REG_CONTROL, control);
 
+    // Features-Register schreiben
+    ata_reg_outb(ctrl, REG_FEATURES, request->registers.ata.features);
+
     // Count-Register schrieben
     ata_reg_outb(ctrl, REG_SEC_CNT, request->registers.ata.count);
     
@@ -415,6 +418,109 @@ int ata_protocol_pio_out(struct ata_request* request)
 }
 
 /**
+ * Initialisiert DMA fuer einen Transfer
+ */
+static int ata_request_dma_init(struct ata_request* request)
+{
+    struct ata_device* dev = request->dev;
+    struct ata_controller* ctrl = dev->controller;
+    uint64_t size = request->block_size * request->block_count;
+
+    *ctrl->prdt_virt = (uint32_t) ctrl->dma_buf_phys;
+    // Groesse nicht ueber 64K, 0 == 64K
+    *ctrl->prdt_virt |= (size & (ATA_DMA_MAXSIZE - 1)) << 32L;
+    // Letzter Eintrag in PRDT
+    *ctrl->prdt_virt |= (uint64_t) 1L << 63L;
+
+    // Die laufenden Transfers anhalten
+    cdi_outb(ctrl->port_bmr_base + BMR_COMMAND, 0);
+    cdi_outb(ctrl->port_bmr_base + BMR_STATUS,
+        cdi_inb(ctrl->port_bmr_base + BMR_STATUS) | BMR_STATUS_ERROR |
+        BMR_STATUS_IRQ);
+
+    // Adresse der PRDT eintragen
+    cdi_outl(ctrl->port_bmr_base + BMR_PRDT, (uint32_t) ctrl->prdt_phys);
+
+    if (request->flags.direction != READ) {
+        memcpy(ctrl->dma_buf_virt, request->buffer, size);
+    }
+    return 1;
+}
+
+/**
+ * Verarbeitet einen ATA-Request bei dem Daten ueber DMA uebertragen werden
+ * sollen
+ */
+static int ata_protocol_dma(struct ata_request* request)
+{
+    struct ata_device* dev = request->dev;
+    struct ata_controller* ctrl = dev->controller;
+
+    // Aktueller Status im Protokoll
+    enum {
+        IRQ_WAIT,
+        CHECK_STATUS,
+    } state;
+
+    // Wozu das lesen und dieser Register gut ist, weiss ich nicht, doch ich
+    // habe es so in verschiedenen Treibern gesehen, deshalb gehe ich mal davon
+    // aus, dass es notwendig ist.
+    cdi_inb(ctrl->port_bmr_base + BMR_COMMAND);
+    cdi_inb(ctrl->port_bmr_base + BMR_STATUS);
+    // Busmastering starten
+    if (request->flags.direction != READ) {
+        cdi_outb(ctrl->port_bmr_base + BMR_COMMAND, BMR_CMD_START |
+            BMR_CMD_WRITE);
+    } else {
+        cdi_outb(ctrl->port_bmr_base + BMR_COMMAND, BMR_CMD_START);
+    }
+    cdi_inb(ctrl->port_bmr_base + BMR_COMMAND);
+    cdi_inb(ctrl->port_bmr_base + BMR_STATUS);
+
+
+    if (request->flags.poll) {
+        state = CHECK_STATUS;
+    } else {
+        state = IRQ_WAIT;
+    }
+
+    while (1) {
+        switch (state) {
+            case IRQ_WAIT:
+                // Auf IRQ warten
+                if (!ata_wait_irq(ctrl, ATA_IRQ_TIMEOUT)) {
+                    request->error = IRQ_TIMEOUT;
+                    DEBUG("dma IRQ-Timeout\n");
+                    return 0;
+                }
+
+                state = CHECK_STATUS;
+                break;
+
+            case CHECK_STATUS: {
+                uint8_t status = ata_reg_inb(ctrl, REG_STATUS);
+
+                // Sicherstellen dass der Transfer abgeschlossen ist. Bei
+                // polling ist das hier noch nicht umbedingt der Fall.
+                if ((status & (STATUS_BSY | STATUS_DRQ)) == 0) {
+                    cdi_inb(ctrl->port_bmr_base + BMR_STATUS);
+                    cdi_outb(ctrl->port_bmr_base + BMR_COMMAND, 0);
+                    goto out_success;
+                }
+                break;
+            }
+        }
+    }
+
+out_success:
+    if (request->flags.direction == READ) {
+        memcpy(request->buffer, ctrl->dma_buf_virt,
+            request->block_size * request->block_count);
+    }
+    return 1;
+}
+
+/**
  * Fuehrt einen ATA-Request aus.
  *
  * @return 1 Wenn der Request erfolgreich bearbeitet wurde, 0 sonst
@@ -422,6 +528,13 @@ int ata_protocol_pio_out(struct ata_request* request)
 int ata_request(struct ata_request* request)
 {
    // printf("ata: [%d:%d] Request command=%x count=%x lba=%llx protocol=%x\n", request->dev->controller->id, request->dev->id, request->registers.ata.command, request->registers.ata.count, request->registers.ata.lba, request->protocol);
+
+    // Bei einem DMA-Request muss der DMA-Controller erst vorbereitet werden
+    if ((request->protocol == DMA) && !ata_request_dma_init(request)) {
+        DEBUG("ata: Fehler beim Initialisieren des DMA-Controllers\n");
+        return 0;
+    }
+
     // Befehl ausfuehren
     if (!ata_request_command(request)) {
         DEBUG("Fehler bei der Befehlsausfuehrung\n");
@@ -447,6 +560,15 @@ int ata_request(struct ata_request* request)
                 return 0;
             }
             break;
+
+        case DMA:
+            if (!ata_protocol_dma(request)) {
+                return 0;
+            }
+            break;
+
+        default:
+            return 0;
     }
     return 1;
 }
-- 
1.6.0.6