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