From c3415bde426cc579e0a4d72b97d828bd636419a3 Mon Sep 17 00:00:00 2001 From: Francesco Lavra Date: Tue, 14 Mar 2023 13:07:36 +0100 Subject: [PATCH] ATA: honor maximum number of sectors in each I/O request This commit adds a new function to the ATA disk driver to retrieve the maxiumum number of sectors that can be submitted in a single I/O request, which depends on whether LBA48 addressing is supported. This new function is called by both the x86_64 bootloader and the kernel ATA PCI driver, which check the maximum sector count value to split each I/O request so that it doesn't exceed the limit. This fixes booting on vsphere VMs with IDE disk emulation. --- platform/pc/boot/stage2.c | 10 ++++------ src/drivers/ata-pci.c | 27 +++++++++++++++++++++------ src/drivers/ata.c | 5 +++++ src/drivers/ata.h | 1 + 4 files changed, 31 insertions(+), 12 deletions(-) diff --git a/platform/pc/boot/stage2.c b/platform/pc/boot/stage2.c index 58978eb71..a7f5e61ce 100644 --- a/platform/pc/boot/stage2.c +++ b/platform/pc/boot/stage2.c @@ -113,10 +113,8 @@ closure_function(1, 1, void, stage2_bios_read, apply(req->completion, STATUS_OK); } -#define MAX_BLOCK_IO_SIZE (64 * 1024) - -closure_function(2, 1, void, stage2_ata_read, - struct ata *, dev, u64, offset, +closure_function(3, 1, void, stage2_ata_read, + struct ata *, dev, u64, offset, u64, io_max_blocks, storage_req, req) { if (req->op != STORAGE_OP_READSG) @@ -135,7 +133,7 @@ closure_function(2, 1, void, stage2_ata_read, merge m = allocate_merge(h, req->completion); status_handler k = apply_merge(m); while (blocks.start < blocks.end) { - u64 span = MIN(range_span(blocks), MAX_BLOCK_IO_SIZE >> SECTOR_OFFSET); + u64 span = MIN(range_span(blocks), bound(io_max_blocks)); sg_buf sgb = sg_list_head_peek(sg); void *dest = sgb->buf + sgb->offset; span = MIN(span, sg_buf_len(sgb) >> SECTOR_OFFSET); @@ -181,7 +179,7 @@ static storage_req_handler get_stage2_disk_read(heap general, u64 fs_offset) return closure(general, stage2_bios_read, fs_offset); } - return closure(general, stage2_ata_read, dev, fs_offset); + return closure(general, stage2_ata_read, dev, fs_offset, ata_get_io_max_blocks(dev)); } closure_function(0, 1, void, fail, diff --git a/src/drivers/ata-pci.c b/src/drivers/ata-pci.c index d42278790..f9437f0c9 100644 --- a/src/drivers/ata-pci.c +++ b/src/drivers/ata-pci.c @@ -82,6 +82,7 @@ declare_closure_struct(1, 0, void, ata_pci_service, typedef struct ata_pci { heap h; struct ata *ata; + u64 io_max_blocks; struct prd *prdt; /* physical region descriptor table */ u64 prdt_phys; struct pci_bar bmr; /* bus master register */ @@ -160,9 +161,10 @@ static boolean ata_pci_service_req(ata_pci apci, ata_pci_req req) ata_debug("%s: %R %v\n", __func__, req->remain, req->s); if (!is_ok(req->s)) goto done; - u64 byte_count = range_span(req->remain) * SECTOR_SIZE; - if (byte_count == 0) + u64 block_count = MIN(range_span(req->remain), apci->io_max_blocks); + if (block_count == 0) goto done; + u64 byte_count = block_count * SECTOR_SIZE; u64 buf_phys; int prd_count = 0; while (byte_count > 0 && prd_count < PRDT_ENTRIES) { @@ -186,8 +188,7 @@ static boolean ata_pci_service_req(ata_pci apci, ata_pci_req req) } if (prd_count > 0) { apci->prdt[prd_count - 1].ctrl = PRD_LAST_ENTRY; - range blocks = irange(req->remain.start, - req->remain.end - byte_count / SECTOR_SIZE); + range blocks = irangel(req->remain.start, block_count); ata_debug("%s: starting DMA for %R\n", __func__, blocks); pci_bar_write_4(&apci->bmr, ATA_BMR_PRDT(ATA_PRIMARY), apci->prdt_phys); if (!ata_io_cmd_dma(apci->ata, req->write, blocks)) { @@ -202,8 +203,21 @@ static boolean ata_pci_service_req(ata_pci apci, ata_pci_req req) } /* Couldn't use DMA, fall back to PIO. */ - apply(req->write ? apci->pio_write : apci->pio_read, req->buf, req->remain, - req->sh); + block_io bio = req->write ? apci->pio_write : apci->pio_read; + if (range_span(req->remain) <= apci->io_max_blocks) { + apply(bio, req->buf, req->remain, req->sh); + } else { + merge m = allocate_merge(apci->h, req->sh); + status_handler sh = apply_merge(m); + do { + range blocks = irangel(req->remain.start, block_count); + apply(bio, req->buf, blocks, apply_merge(m)); + req->buf += block_count << SECTOR_OFFSET; + req->remain.start += block_count; + block_count = MIN(range_span(req->remain), apci->io_max_blocks); + } while (block_count > 0); + apply(sh, STATUS_OK); + } return true; done: @@ -312,6 +326,7 @@ closure_function(3, 1, boolean, ata_pci_probe, dev->prdt = pointer_from_u64(u64_from_pointer(dev->prdt) + align_offset); dev->h = general; + dev->io_max_blocks = ata_get_io_max_blocks(dev->ata); pci_bar_init(d, &dev->bmr, 4, 0, -1); pci_set_bus_master(d); list_init(&dev->reqs); diff --git a/src/drivers/ata.c b/src/drivers/ata.c index e1eb473aa..bccd6410c 100644 --- a/src/drivers/ata.c +++ b/src/drivers/ata.c @@ -392,3 +392,8 @@ u64 ata_get_capacity(struct ata *dev) { return dev->capacity; } + +u64 ata_get_io_max_blocks(struct ata *dev) +{ + return (dev->command_sets & ATA_CS_LBA48) ? U64_FROM_BIT(16) : U64_FROM_BIT(8); +} diff --git a/src/drivers/ata.h b/src/drivers/ata.h index 8c8bd6c00..ffbdea194 100644 --- a/src/drivers/ata.h +++ b/src/drivers/ata.h @@ -4,6 +4,7 @@ struct ata *ata_alloc(heap general); void ata_dealloc(struct ata *); boolean ata_probe(struct ata *); u64 ata_get_capacity(struct ata *); +u64 ata_get_io_max_blocks(struct ata *dev); /* ATA commands (from sys/sys/ata.h) */ #define ATA_NOP 0x00 /* NOP */