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

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



On Mon, May 25, 2009 at 10:40:35AM +0200, Antoine Kaufmann wrote:
> + 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);

Okay... wenn busmaster_regbase == 0, dann explizit 0 setzen? Bitte so wie beim Primären machen ;)

>      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
> 

Ansonsten ok, nachdem was ich gesehen hab ;) Rein damit.

-- 
Alexander Siol
alex@xxxxxxxxxx
dunklermeuchler@xxxxxxxxx

Attachment: signature.asc
Description: Digital signature