From 6c6269a75ec8f85b776b43a4a92f8352edd9a5f2 Mon Sep 17 00:00:00 2001 From: MicroFish <2609948707@qq.com> Date: Thu, 5 Nov 2026 03:30:53 +0800 Subject: [PATCH 1/3] ata: reuse firmware-configured DMA with PIO fallback The firmware pairs its host timing with the drive's DMA mode. Probe each drive without sending SET FEATURES or programming vendor timing registers, then compare a PIO and a DMA read of the same sectors once at boot. A failed check, a missing BAR4, or a runtime bus-master error drops the drive to PIO; ATA retries the same request in PIO, ATAPI resumes from the completed offset, and a stuck BSY resets the channel. ATAPI capacity probing follows REQUEST SENSE retries and maps a missing medium to -ENOMEDIUM. Signed-off-by: MicroFish <2609948707@qq.com> Co-Authored-By: Claude Code --- drivers/block/ata/pata/atapi.c | 381 ++++++++++++++++-- drivers/block/ata/pata/ide.c | 530 +++++++++++++++++++++---- include/drivers/block/ata/pata/atapi.h | 9 + include/drivers/block/ata/pata/ide.h | 60 ++- 4 files changed, 857 insertions(+), 123 deletions(-) diff --git a/drivers/block/ata/pata/atapi.c b/drivers/block/ata/pata/atapi.c index 0ed0d529..eb4488c3 100644 --- a/drivers/block/ata/pata/atapi.c +++ b/drivers/block/ata/pata/atapi.c @@ -13,6 +13,8 @@ #include #include #include +#include +#include #include #if CONFIG_ATA @@ -20,6 +22,25 @@ /* ATAPI device instances */ atapi_device_t atapi_devices[4]; +/* Boot-time DMA/PIO comparison buffers, one block each */ +static uint16_t atapi_validate_pio_buf[2048]; +static uint16_t atapi_validate_dma_buf[2048]; + +/* Serialises use of the shared comparison buffers across channels */ +static spinlock_t atapi_validate_lock; + +static void atapi_validate_dma(uint8_t dev_index); +static int atapi_probe_capacity(uint8_t drive, uint32_t *lba_size, uint32_t *blk_size); + +/* Highest ATA/ATAPI major version advertised by the device (word 80 bit index) */ +static uint8_t atapi_major_version(const uint16_t *id_words) +{ + if (id_words[80] == 0xffff) return 0; + for (int v = 14; v >= 1; v--) + if (id_words[80] & (1 << v)) return (uint8_t)v; + return 0; +} + /* ATAPI status polling */ static uint8_t atapi_wait_busy(uint8_t channel) { @@ -38,8 +59,8 @@ static uint8_t atapi_wait_busy(uint8_t channel) return status; } -/* Poll until DRQ is set, or report an error state */ -static uint8_t atapi_wait_drq(uint8_t channel) +/* Poll until DRQ is set; log_error marks the command-phase call */ +static uint8_t atapi_wait_drq(uint8_t channel, uint8_t log_error) { uint8_t status; int timeout = 400000; @@ -47,8 +68,10 @@ static uint8_t atapi_wait_drq(uint8_t channel) while (timeout--) { status = ide_read(channel, ATA_REG_STATUS); if (status & ATA_SR_ERR) { - static DEFINE_RATELIMIT_STATE(ratelimit, PRINTK_RATELIMIT_TICKS, PRINTK_RATELIMIT_BURST); - if (ratelimit_allow(&ratelimit)) plogk("atapi: ERR on channel %u\n", channel); + if (log_error) { + static DEFINE_RATELIMIT_STATE(ratelimit, PRINTK_RATELIMIT_TICKS, PRINTK_RATELIMIT_BURST); + if (ratelimit_allow(&ratelimit)) plogk("atapi: ERR on channel %u\n", channel); + } return 2; } if (status & ATA_SR_DF) { @@ -133,37 +156,47 @@ uint8_t atapi_identify(uint8_t channel, uint8_t drive, uint8_t dev_index) atapi_devices[dev_index].capabilities = id_words[0] & 0xff; atapi_devices[dev_index].lba_size = 0; atapi_devices[dev_index].blk_size = 2048; + atapi_devices[dev_index].dma_enabled = 0; + atapi_devices[dev_index].dma_capable = 0; + atapi_devices[dev_index].dma_dir = 0; /* Extract model string */ for (k = 0; k < 40; k += 2) { atapi_devices[dev_index].model[k] = id_buf[ATA_IDENT_MODEL + k + 1]; atapi_devices[dev_index].model[k + 1] = id_buf[ATA_IDENT_MODEL + k]; } + atapi_devices[dev_index].model[40] = 0; + + for (k = 39; k > 0; k--) { + if (atapi_devices[dev_index].model[k] == ' ') { + atapi_devices[dev_index].model[k] = '\0'; + } else { + break; + } + } + + if ((id_words[49] & 0x100) && channels[channel].dma_ok) atapi_devices[dev_index].dma_capable = 1; + if ((id_words[62] & 0x8000) && atapi_major_version(id_words) >= 7) atapi_devices[dev_index].dma_dir = 1; } - /* Get real capacity via READ CAPACITY */ + /* Get real capacity via READ CAPACITY; slow drives report becoming ready */ { uint32_t lba = 0; uint32_t bsz = 0; - uint8_t cap_ret = atapi_read_capacity(dev_index, &lba, &bsz); + int cap_ret = atapi_probe_capacity(dev_index, &lba, &bsz); if (cap_ret == 0) { atapi_devices[dev_index].lba_size = lba; atapi_devices[dev_index].blk_size = bsz; - } else { - /* Fallback: some devices may need retry after TEST UNIT READY */ - atapi_test_unit_ready(dev_index); - nsleep(100000); - - cap_ret = atapi_read_capacity(dev_index, &lba, &bsz); - if (cap_ret == 0) { - atapi_devices[dev_index].lba_size = lba; - atapi_devices[dev_index].blk_size = bsz; - } } } + if (atapi_devices[dev_index].dma_capable && atapi_devices[dev_index].lba_size != 0 && atapi_devices[dev_index].blk_size != 0 + && atapi_devices[dev_index].blk_size <= sizeof(atapi_validate_pio_buf)) { + atapi_validate_dma(dev_index); + } + return 0; } @@ -186,12 +219,13 @@ void atapi_soft_reset(uint8_t drive) atapi_wait_busy(channel); } -/* Core ATAPI packet send function */ -uint8_t atapi_send_packet(uint8_t drive, const uint8_t *cdb, uint16_t byte_limit, uint8_t direction, void *buf, size_t *xfer_len) +/* Core ATAPI packet send; the caller holds the channel lock */ +static uint8_t atapi_send_packet_locked(uint8_t drive, const uint8_t *cdb, uint16_t byte_limit, uint8_t direction, void *buf, size_t *xfer_len, uint8_t log_error) { uint32_t channel = atapi_devices[drive].channel; uint32_t slavebit = atapi_devices[drive].drive; uint32_t bus = channels[channel].base; + uint8_t use_dma = (direction == ATAPI_PROT_DMA) && atapi_devices[drive].dma_enabled && channels[channel].dma_ok; uint8_t err; /* @@ -228,7 +262,7 @@ uint8_t atapi_send_packet(uint8_t drive, const uint8_t *cdb, uint16_t byte_limit channels[channel].irq_pending = 0; channels[channel].nIEN = 0x02; ide_write(channel, ATA_REG_CONTROL, 0x02); - ide_write(channel, ATA_REG_HDDEVSEL, slavebit << 4); + ide_write(channel, ATA_REG_HDDEVSEL, 0xa0 | (slavebit << 4)); /* Delay for device selection to settle, then wait for BSY clear */ for (int i = 0; i < 10; i++) ide_read(channel, ATA_REG_ALTSTATUS); @@ -240,10 +274,11 @@ uint8_t atapi_send_packet(uint8_t drive, const uint8_t *cdb, uint16_t byte_limit nsleep(100); } } + if (use_dma && (byte_limit == 0 || byte_limit > ATA_DMA_MAX_BYTES || !buf || !xfer_len || *xfer_len < byte_limit)) return 2; /* Set features and byte count limit */ - if (direction == ATAPI_PROT_DMA) { - ide_write(channel, ATA_REG_FEATURES, ATAPI_PKT_DMA); + if (use_dma) { + ide_write(channel, ATA_REG_FEATURES, ATAPI_PKT_DMA | (atapi_devices[drive].dma_dir ? ATAPI_DMADIR : 0)); } else { ide_write(channel, ATA_REG_FEATURES, 0); } @@ -254,7 +289,7 @@ uint8_t atapi_send_packet(uint8_t drive, const uint8_t *cdb, uint16_t byte_limit ide_write(channel, ATA_REG_COMMAND, ATA_CMD_PACKET); /* Wait for device to accept CDB */ - err = atapi_wait_drq(channel); + err = atapi_wait_drq(channel, log_error); if (err) return err; /* Check interrupt reason before sending CDB */ @@ -290,14 +325,32 @@ uint8_t atapi_send_packet(uint8_t drive, const uint8_t *cdb, uint16_t byte_limit return 0; } - /* PIO data transfer */ - if (direction == ATAPI_PROT_PIO && buf && xfer_len) { + /* DMA data phase (device to host only); the engine waits for the device's DMARQ */ + if (use_dma) { + uint16_t bytes = byte_limit; + + ide_bm_clear_status(channel); + ide_bm_set_prd(channel, bytes); + ide_bm_start(channel, 0); + + err = ide_bm_wait(channel); + if (err != 0) { + ide_bm_stop(channel); + ide_bm_clear_status(channel); + return err; + } + + dma_read_barrier(); + memcpy(buf, channels[channel].dma_buf, bytes); + *xfer_len = bytes; + } else if (direction != ATAPI_PROT_NODATA && buf && xfer_len) { + /* PIO data transfer */ size_t transferred = 0; uint16_t *word_buf = (uint16_t *)buf; while (transferred < *xfer_len) { /* Poll for data ready instead of IRQ - interrupts may not be enabled yet when init_ide() runs. */ - err = atapi_wait_drq(channel); + err = atapi_wait_drq(channel, 0); if (err) return err; uint8_t status = ide_read(channel, ATA_REG_STATUS); @@ -354,9 +407,73 @@ uint8_t atapi_send_packet(uint8_t drive, const uint8_t *cdb, uint16_t byte_limit } } + /* The final packet may report CHECK CONDITION after DRQ has dropped */ + if (ide_read(channel, ATA_REG_STATUS) & ATA_SR_ERR) return 2; return 0; } +/* + * The firmware paired host timing with the drive's DMA mode; reuse it instead of + * per-vendor chipset programming (Intel/VIA/SiS/AMD/NVIDIA/ATI/...). A boot-time + * DMA/PIO comparison validates it; full speed still needs those chipset drivers. + */ +static void atapi_validate_dma(uint8_t dev_index) +{ + uint8_t channel = atapi_devices[dev_index].channel; + uint32_t blk_size = atapi_devices[dev_index].blk_size; + uint8_t cdb[ATAPI_CDB_LEN] = {GPCMD_READ_12, 0, 0, 0, 0, 0, 0, 0, 0, 1}; + size_t pio_len = blk_size; + size_t dma_len = blk_size; + uint8_t dma_tried = 0; + uint8_t err; + + if (!channels[channel].dma_ok) { + atapi_devices[dev_index].dma_enabled = 0; + return; + } + + spin_lock(&atapi_validate_lock); + spin_lock(&channels[channel].lock); + + /* Start from PIO until the DMA comparison proves the firmware pairing */ + atapi_devices[dev_index].dma_enabled = 0; + + err = atapi_send_packet_locked(dev_index, cdb, (uint16_t)blk_size, ATAPI_PROT_PIO, atapi_validate_pio_buf, &pio_len, 1); + if (err == 0 && pio_len != blk_size) err = 4; + if (err == 0) { + dma_tried = 1; + atapi_devices[dev_index].dma_enabled = 1; + err = atapi_send_packet_locked(dev_index, cdb, (uint16_t)blk_size, ATAPI_PROT_DMA, atapi_validate_dma_buf, &dma_len, 1); + if (err == 0 && (dma_len != blk_size || memcmp(atapi_validate_pio_buf, atapi_validate_dma_buf, blk_size) != 0)) err = 4; + } + + if (err != 0) { + if (!dma_tried) { + atapi_devices[dev_index].dma_enabled = 0; + plogk("atapi: DMA validation skipped on drive %u (PIO read error %u), using PIO.\n", dev_index, err); + } else { + ide_dma_fallback(dev_index); + plogk("atapi: DMA validation failed on drive %u, using PIO.\n", dev_index); + } + } + + spin_unlock(&channels[channel].lock); + spin_unlock(&atapi_validate_lock); +} + +/* Core ATAPI packet send function */ +uint8_t atapi_send_packet(uint8_t drive, const uint8_t *cdb, uint16_t byte_limit, uint8_t direction, void *buf, size_t *xfer_len) +{ + uint8_t channel = atapi_devices[drive].channel; + uint8_t err; + + spin_lock(&channels[channel].lock); + err = atapi_send_packet_locked(drive, cdb, byte_limit, direction, buf, xfer_len, 1); + + spin_unlock(&channels[channel].lock); + return err; +} + /* TEST UNIT READY */ uint8_t atapi_test_unit_ready(uint8_t drive) { @@ -372,16 +489,15 @@ uint8_t atapi_inquiry(uint8_t drive, void *buf) return atapi_send_packet(drive, cdb, 36, ATAPI_PROT_PIO, buf, &len); } -/* READ CAPACITY */ -uint8_t atapi_read_capacity(uint8_t drive, uint32_t *lba_size, uint32_t *blk_size) +/* READ CAPACITY with the channel lock held */ +static uint8_t atapi_read_capacity_locked(uint8_t drive, uint32_t *lba_size, uint32_t *blk_size, uint8_t log_error) { uint8_t cdb[ATAPI_CDB_LEN] = {GPCMD_READ_CAPACITY, 0, 0, 0, 0, 0, 0, 0, 0, 0}; uint8_t cap_buf[8] = {0}; size_t len = 8; uint8_t err; - if (!lba_size || !blk_size) return 0xff; - err = atapi_send_packet(drive, cdb, 8, ATAPI_PROT_PIO, cap_buf, &len); + err = atapi_send_packet_locked(drive, cdb, 8, ATAPI_PROT_PIO, cap_buf, &len, log_error); if (err) return err; /* READ CAPACITY returns the last addressable LBA; the sector count is one more. */ @@ -391,8 +507,26 @@ uint8_t atapi_read_capacity(uint8_t drive, uint32_t *lba_size, uint32_t *blk_siz return 0; } -/* REQUEST SENSE */ -uint8_t atapi_request_sense(uint8_t drive, uint8_t *sense_key, uint8_t *asc, uint8_t *ascq) +/* READ CAPACITY */ +uint8_t atapi_read_capacity(uint8_t drive, uint32_t *lba_size, uint32_t *blk_size) +{ + uint8_t channel; + uint8_t err; + + if (!lba_size || !blk_size) return 0xff; + if (drive > 3 || !atapi_devices[drive].reserved) return 0xff; + + channel = atapi_devices[drive].channel; + spin_lock(&channels[channel].lock); + + err = atapi_read_capacity_locked(drive, lba_size, blk_size, 1); + spin_unlock(&channels[channel].lock); + + return err; +} + +/* REQUEST SENSE with the channel lock held */ +static uint8_t atapi_request_sense_locked(uint8_t drive, uint8_t *sense_key, uint8_t *asc, uint8_t *ascq, uint8_t log_error) { uint8_t cdb[ATAPI_CDB_LEN] = {GPCMD_REQUEST_SENSE, 0, 0, 0, SCSI_SENSE_BUFFER_SIZE, 0}; uint8_t sense_buf[SCSI_SENSE_BUFFER_SIZE]; @@ -400,7 +534,7 @@ uint8_t atapi_request_sense(uint8_t drive, uint8_t *sense_key, uint8_t *asc, uin uint8_t err; for (int i = 0; i < SCSI_SENSE_BUFFER_SIZE; i++) sense_buf[i] = 0; - err = atapi_send_packet(drive, cdb, SCSI_SENSE_BUFFER_SIZE, ATAPI_PROT_PIO, sense_buf, &len); + err = atapi_send_packet_locked(drive, cdb, SCSI_SENSE_BUFFER_SIZE, ATAPI_PROT_PIO, sense_buf, &len, log_error); if (err) return err; /* Sense data format: byte 0 = error code, byte 2 = sense key, byte 12 = ASC, byte 13 = ASCQ */ @@ -411,6 +545,106 @@ uint8_t atapi_request_sense(uint8_t drive, uint8_t *sense_key, uint8_t *asc, uin return 0; } +/* Clear the unit attention raised by a channel reset; the caller holds the lock */ +void atapi_reset_recover(uint8_t drive) +{ + uint8_t cdb[ATAPI_CDB_LEN] = {GPCMD_TEST_UNIT_READY, 0, 0, 0, 0, 0}; + uint8_t key = 0; + uint8_t asc = 0; + uint8_t ascq = 0; + + (void)atapi_send_packet_locked(drive, cdb, 0, ATAPI_PROT_NODATA, NULL, NULL, 0); + (void)atapi_request_sense_locked(drive, &key, &asc, &ascq, 0); +} + +/* Probe capacity with sense-driven retries; the caller does not hold the lock */ +static int atapi_probe_capacity(uint8_t drive, uint32_t *lba_size, uint32_t *blk_size) +{ + uint32_t channel = atapi_devices[drive].channel; + uint32_t lba = 0; + uint32_t blk = 0; + + for (int attempt = 0; attempt < 20; attempt++) { + uint8_t key = 0; + uint8_t asc = 0; + uint8_t ascq = 0; + uint8_t retryable = 0; + + spin_lock(&channels[channel].lock); + if (atapi_read_capacity_locked(drive, &lba, &blk, 0) == 0) { + spin_unlock(&channels[channel].lock); + if (lba == 0 || blk == 0 || blk > 4096) return -ENOMEDIUM; + *lba_size = lba; + *blk_size = blk; + return 0; + } + + /* + * A new disc reports unit attention or becoming ready; 0x3A also means empty, + * so it gets only two quick retries; the next command after a change clears it. + */ + if (atapi_request_sense_locked(drive, &key, &asc, &ascq, 0) == 0) { + if ((key == 6 && asc == 0x28) || (key == 2 && (asc == 0x04 || asc == 0x11))) { + retryable = 2; + } else if (key == 2 && asc == 0x3A && attempt < 2) { + retryable = 1; + } + } + spin_unlock(&channels[channel].lock); + + if (!retryable) break; + if (retryable == 2) msleep(50); + } + + return -ENOMEDIUM; +} + +/* REQUEST SENSE */ +uint8_t atapi_request_sense(uint8_t drive, uint8_t *sense_key, uint8_t *asc, uint8_t *ascq) +{ + uint8_t channel; + uint8_t err; + + if (drive > 3 || !atapi_devices[drive].reserved) return 0xff; + + channel = atapi_devices[drive].channel; + spin_lock(&channels[channel].lock); + + err = atapi_request_sense_locked(drive, sense_key, asc, ascq, 1); + spin_unlock(&channels[channel].lock); + + return err; +} + +/* Refresh capacity after a possible disc change. */ +int atapi_revalidate(uint8_t drive, uint32_t *lba_size, uint32_t *blk_size) +{ + int ret; + uint8_t was_empty; + + if (drive > 3 || !atapi_devices[drive].reserved || atapi_devices[drive].type != IDE_ATAPI) return -ENODEV; + if (!lba_size || !blk_size) return -EINVAL; + + was_empty = atapi_devices[drive].lba_size == 0; + ret = atapi_probe_capacity(drive, lba_size, blk_size); + + if (ret != 0) { + atapi_devices[drive].lba_size = 0; + ide_devices[drive].size = 0; + return ret; + } + + atapi_devices[drive].lba_size = *lba_size; + atapi_devices[drive].blk_size = *blk_size; + ide_devices[drive].size = *lba_size; + + /* An empty drive was never validated against the firmware's DMA pairing */ + if (was_empty && atapi_devices[drive].dma_capable && !atapi_devices[drive].dma_enabled && *blk_size <= sizeof(atapi_validate_pio_buf)) atapi_validate_dma(drive); + ide_devices[drive].dma_enabled = atapi_devices[drive].dma_enabled; + + return EOK; +} + /* START STOP UNIT (eject/load) */ uint8_t atapi_start_stop(uint8_t drive, uint8_t start, uint8_t loej) { @@ -420,22 +654,89 @@ uint8_t atapi_start_stop(uint8_t drive, uint8_t start, uint8_t loej) return atapi_send_packet(drive, cdb, 0, ATAPI_PROT_NODATA, NULL, NULL); } +/* Build a READ(12) CDB for count blocks starting at lba */ +static void atapi_read12_cdb(uint8_t *cdb, uint32_t lba, uint8_t count) +{ + memset(cdb, 0, ATAPI_CDB_LEN); + cdb[0] = GPCMD_READ_12; + cdb[2] = (lba >> 24) & 0xff; + cdb[3] = (lba >> 16) & 0xff; + cdb[4] = (lba >> 8) & 0xff; + cdb[5] = lba & 0xff; + cdb[8] = (count >> 8) & 0xff; + cdb[9] = count & 0xff; +} + /* Read sectors from ATAPI device (READ 12) */ uint8_t atapi_read(uint8_t drive, uint32_t lba, uint8_t num_sectors, uint16_t *buf) { + uint8_t channel = atapi_devices[drive].channel; uint32_t blk_size = atapi_devices[drive].blk_size; + uint32_t done = 0; + uint8_t err = 0; + if (blk_size == 0) blk_size = 2048; + if (blk_size > 0xfffe) return 2; - uint32_t total_bytes = (uint32_t)num_sectors * blk_size; - uint16_t byte_limit = (uint16_t)(total_bytes > 0xfffe ? 0xfffe : total_bytes); - size_t xfer_len = (size_t)total_bytes; + spin_lock(&channels[channel].lock); - /* Build READ(12) CDB: opcode, LBA[2..5], transfer length[6..9], flags, control */ - uint8_t cdb[ATAPI_CDB_LEN] = { - GPCMD_READ_12, 0, (lba >> 24) & 0xff, (lba >> 16) & 0xff, (lba >> 8) & 0xff, lba & 0xff, 0, 0, (num_sectors >> 8) & 0xff, num_sectors & 0xff, 0, 0, - }; + if (atapi_devices[drive].dma_enabled && channels[channel].dma_ok && blk_size <= ATA_DMA_MAX_BYTES) { + uint32_t per_packet = ATA_DMA_MAX_BYTES / blk_size; + + while (done < num_sectors) { + uint8_t chunk = num_sectors - done; + uint32_t chunk_lba = lba + done; + uint16_t byte_limit; + size_t xfer_len; + uint8_t cdb[ATAPI_CDB_LEN]; + + if (chunk > per_packet) chunk = (uint8_t)per_packet; + byte_limit = (uint16_t)(chunk * blk_size); + xfer_len = (size_t)chunk * blk_size; + atapi_read12_cdb(cdb, chunk_lba, chunk); + + err = atapi_send_packet_locked(drive, cdb, byte_limit, ATAPI_PROT_DMA, (uint8_t *)buf + ((size_t)done * blk_size), &xfer_len, 1); + if (err != 0) break; + done += chunk; + } + + if (err != 0) { + ide_dma_fallback(drive); + static DEFINE_RATELIMIT_STATE(ratelimit, PRINTK_RATELIMIT_TICKS, PRINTK_RATELIMIT_BURST); + if (ratelimit_allow(&ratelimit)) plogk("atapi: DMA error %u on drive %u, falling back to PIO.\n", err, drive); + err = 0; + } + } + + if (done < num_sectors) { + uint32_t per_packet = 0xfffe / blk_size; + + /* The 16-bit byte count limit caps each PACKET, so long reads are split */ + while (done < num_sectors) { + uint8_t chunk = num_sectors - done; + uint32_t chunk_lba = lba + done; + uint16_t byte_limit; + size_t xfer_len; + uint8_t cdb[ATAPI_CDB_LEN]; + + if (chunk > per_packet) chunk = (uint8_t)per_packet; + byte_limit = (uint16_t)(chunk * blk_size); + xfer_len = (size_t)chunk * blk_size; + atapi_read12_cdb(cdb, chunk_lba, chunk); + + err = atapi_send_packet_locked(drive, cdb, byte_limit, ATAPI_PROT_PIO, (uint8_t *)buf + ((size_t)done * blk_size), &xfer_len, 1); + if (err == 0 && xfer_len != (size_t)chunk * blk_size) err = 5; + if (err != 0) { + static DEFINE_RATELIMIT_STATE(ratelimit, PRINTK_RATELIMIT_TICKS, PRINTK_RATELIMIT_BURST); + if (ratelimit_allow(&ratelimit)) plogk("atapi: Read error %u on drive %u at block %u\n", err, drive, chunk_lba); + break; + } + done += chunk; + } + } - return atapi_send_packet(drive, cdb, byte_limit, ATAPI_PROT_PIO, buf, &xfer_len); + spin_unlock(&channels[channel].lock); + return err; } #endif diff --git a/drivers/block/ata/pata/ide.c b/drivers/block/ata/pata/ide.c index fdf232df..f1c81091 100644 --- a/drivers/block/ata/pata/ide.c +++ b/drivers/block/ata/pata/ide.c @@ -18,6 +18,8 @@ #include #include #include +#include +#include #if CONFIG_ATA @@ -38,6 +40,12 @@ ide_device_t ide_devices[4]; /* Data Array */ uint8_t ide_buf[2048] = {0}; +/* Boot-time DMA/PIO comparison buffers, 8 sectors each */ +static uint16_t ide_validate_pio[2048]; +static uint16_t ide_validate_dma[2048]; + +static uint8_t ide_validate_ata(uint8_t drive); + /* IDE interrupt handling for the primary channel (IRQ14). */ INTERRUPT_BEGIN static void ide_irq_primary(interrupt_frame_t *frame) { @@ -77,16 +85,106 @@ int ide_wait_irq(uint8_t channel) return 0; } +/* Clear the bus-master status bits */ +void ide_bm_clear_status(uint8_t channel) +{ + outb(channels[channel].bmide + ATA_BM_STATUS, ATA_BM_STATUS_CLEAR); +} + +/* One page per PRD keeps each region inside the bus-master 64 KiB boundary */ +void ide_bm_set_prd(uint8_t channel, uint16_t bytes) +{ + uint64_t phys = channels[channel].dma_buf_phys; + uint32_t left = bytes; + ata_prd_t *prd = channels[channel].prd; + ata_prd_t *last = prd; + + /* The table is only safe to rewrite with the engine stopped */ + ide_bm_stop(channel); + + while (left != 0) { + uint32_t chunk = left > 4096 ? 4096 : left; + prd->base = (uint32_t)phys; + prd->count = (uint16_t)chunk; + prd->flags = 0; + last = prd; + phys += chunk; + left -= chunk; + prd++; + } + + last->flags = ATA_PRD_EOT; + dma_write_barrier(); + outl(channels[channel].bmide + ATA_BM_PRDT, (uint32_t)channels[channel].prd_phys); +} + +/* Start the bus-master engine; write = host to device */ +void ide_bm_start(uint8_t channel, uint8_t write) +{ + /* Deassert START first so every transfer sees a 0 -> 1 edge */ + outb(channels[channel].bmide + ATA_BM_CMD, 0); + outb(channels[channel].bmide + ATA_BM_CMD, (uint8_t)((write ? 0 : ATA_BM_CMD_DIR) | ATA_BM_CMD_START)); +} + +/* Stop the bus-master engine */ +void ide_bm_stop(uint8_t channel) +{ + outb(channels[channel].bmide + ATA_BM_CMD, 0); +} + +/* Poll a bus-master transfer to completion */ +uint8_t ide_bm_wait(uint8_t channel) +{ + int timeout = IDE_POLL_RETRY; + uint8_t status = 0; + + for (;;) { + status = inb(channels[channel].bmide + ATA_BM_STATUS); + if (!(status & ATA_BM_STATUS_ACTIVE)) break; + if ((status & ATA_BM_STATUS_ERR) || (ide_read(channel, ATA_REG_STATUS) & ATA_SR_ERR)) { + ide_bm_stop(channel); + ide_bm_clear_status(channel); + return 2; + } + if (--timeout <= 0) { + ide_bm_stop(channel); + ide_bm_clear_status(channel); + return 3; + } + nsleep(10); + } + if (status & ATA_BM_STATUS_ERR) { + ide_bm_stop(channel); + ide_bm_clear_status(channel); + return 2; + } + + ide_bm_stop(channel); + ide_bm_clear_status(channel); + if (ide_polling(channel, 0) != 0) return 3; + + status = ide_read(channel, ATA_REG_STATUS); + if (status & ATA_SR_ERR) return 2; + if (status & ATA_SR_DF) return 1; + + return 0; +} + /* Setting up the IDE */ -static void ide_initialize(uint32_t BAR0, uint32_t BAR1, uint32_t BAR2, uint32_t BAR3, uint32_t BAR4) +static void ide_initialize(uint32_t BAR0, uint32_t BAR1, uint32_t BAR2, uint32_t BAR3, uint32_t BAR4, uint8_t bmide_valid) { int j, k, count = 0; for (int i = 0; i < 4; i++) ide_devices[i].reserved = 0; for (int i = 0; i < 2; i++) { - channels[i].lock.lock = 0; - channels[i].lock.rflags = 0; - channels[i].irq_pending = 0; + channels[i].lock.lock = 0; + channels[i].lock.rflags = 0; + channels[i].irq_pending = 0; + channels[i].dma_ok = 0; + channels[i].dma_buf = NULL; + channels[i].dma_buf_phys = 0; + channels[i].prd_phys = 0; + channels[i].prd = NULL; } plogk("ide: BAR0 = 0x%03x, BAR1 = 0x%03x, BAR2 = 0x%03x, BAR3 = 0x%03x, BAR4 = 0x%03x\n", BAR0, BAR1, BAR2, BAR3, BAR4); @@ -95,8 +193,28 @@ static void ide_initialize(uint32_t BAR0, uint32_t BAR1, uint32_t BAR2, uint32_t channels[ATA_PRIMARY].ctrl = (BAR1 & 0xfffffffc) + 0x3f4 * (!BAR1); channels[ATA_SECONDARY].base = (BAR2 & 0xfffffffc) + 0x170 * (!BAR2); channels[ATA_SECONDARY].ctrl = (BAR3 & 0xfffffffc) + 0x374 * (!BAR3); - channels[ATA_PRIMARY].bmide = (BAR4 & 0xfffffffc) + 0; - channels[ATA_SECONDARY].bmide = (BAR4 & 0xfffffffc) + 8; + channels[ATA_PRIMARY].bmide = bmide_valid ? (BAR4 & 0xfffffffc) + 0 : 0; + channels[ATA_SECONDARY].bmide = bmide_valid ? (BAR4 & 0xfffffffc) + 8 : 0; + + /* Allocate a bounce buffer and PRD table per channel below 4 GiB */ + if (bmide_valid) { + for (int i = 0; i < 2; i++) { + uint64_t buf_phys = alloc_frames_below(ATA_DMA_BUF_PAGES, 0x100000000ULL); + uint64_t prd_phys = alloc_frames_below(1, 0x100000000ULL); + + if (!buf_phys || !prd_phys) { + if (buf_phys) free_frames(buf_phys, ATA_DMA_BUF_PAGES); + if (prd_phys) free_frames(prd_phys, 1); + plogk("ide: Channel %u DMA buffer allocation failed, using PIO.\n", i); + continue; + } + channels[i].dma_buf = phys_to_virt(buf_phys); + channels[i].dma_buf_phys = buf_phys; + channels[i].prd_phys = prd_phys; + channels[i].prd = phys_to_virt(prd_phys); + channels[i].dma_ok = 1; + } + } /* Disable IRQ */ ide_write(ATA_PRIMARY, ATA_REG_CONTROL, 2); @@ -149,6 +267,7 @@ static void ide_initialize(uint32_t BAR0, uint32_t BAR1, uint32_t BAR2, uint32_t ide_devices[count].capabilities = 0; ide_devices[count].command_sets = 0; ide_devices[count].size = atapi_devices[count].lba_size; + ide_devices[count].dma_enabled = atapi_devices[count].dma_enabled; for (k = 0; k < 40; k++) ide_devices[count].model[k] = atapi_devices[count].model[k]; ide_devices[count].model[40] = 0; count++; @@ -194,18 +313,67 @@ static void ide_initialize(uint32_t BAR0, uint32_t BAR1, uint32_t BAR2, uint32_t ide_devices[count].model[k] = ide_buf[ATA_IDENT_MODEL + k + 1]; ide_devices[count].model[k + 1] = ide_buf[ATA_IDENT_MODEL + k]; } + ide_devices[count].model[40] = 0; + + for (k = 39; k > 0; k--) { + if (ide_devices[count].model[k] == ' ') { + ide_devices[count].model[k] = '\0'; + } else { + break; + } + } + + ide_devices[count].dma_enabled = ide_validate_ata(count); count++; } } + /* Release DMA resources for channels without drives */ + for (int i = 0; i < 2; i++) { + int used = 0; + + for (int d = 0; d < 4; d++) + if (ide_devices[d].reserved && ide_devices[d].channel == i) used = 1; + if (!used && channels[i].dma_ok) { + free_frames(channels[i].dma_buf_phys, ATA_DMA_BUF_PAGES); + free_frames(channels[i].prd_phys, 1); + channels[i].dma_ok = 0; + channels[i].dma_buf = NULL; + channels[i].dma_buf_phys = 0; + channels[i].prd_phys = 0; + channels[i].prd = NULL; + } + } + + { + uint8_t total = 0, dma = 0; + + for (int i = 0; i < 4; i++) { + /* Drives without media have no transfer mode to report */ + if (!ide_devices[i].reserved || ide_devices[i].size == 0) continue; + total++; + if (ide_devices[i].dma_enabled) dma++; + } + if (total != 0) { + if (dma == 0) { + plogk("ide: Transfer mode: PIO%s\n", bmide_valid ? "" : " (BAR4 unavailable)"); + } else if (dma == total) { + plogk("ide: Transfer mode: DMA (BIOS-configured)\n"); + } else { + plogk("ide: Transfer mode: DMA on %u/%u drives, PIO on the rest.\n", dma, total); + } + } + } + /* Print device information */ for (int i = 0; i < 4; i++) if (ide_devices[i].reserved == 1) { if (ide_devices[i].type == IDE_ATAPI) { - plogk("ide: Found ATAPI Drive %u blocks (%u bytes/block) - %s\n", atapi_devices[i].lba_size, atapi_devices[i].blk_size, ide_devices[i].model); + plogk("ide: Found ATAPI Drive %u blocks (%u bytes/block) - %s [%s]\n", atapi_devices[i].lba_size, atapi_devices[i].blk_size, ide_devices[i].model, + ide_devices[i].dma_enabled ? "DMA" : "PIO"); } else { - plogk("ide: Found ATA Drive %llu (KiB) - %s\n", (ide_devices[i].size / 2), ide_devices[i].model); + plogk("ide: Found ATA Drive %llu (KiB) - %s [%s]\n", (ide_devices[i].size / 2), ide_devices[i].model, ide_devices[i].dma_enabled ? "DMA" : "PIO"); } } } @@ -214,6 +382,28 @@ static void ide_initialize(uint32_t BAR0, uint32_t BAR1, uint32_t BAR2, uint32_t static uint8_t ide_print_error(uint32_t drive, uint8_t err) { if (err == 0) return err; + if (err == 4) { + static DEFINE_RATELIMIT_STATE(ratelimit, PRINTK_RATELIMIT_TICKS, PRINTK_RATELIMIT_BURST); + if (ratelimit_allow(&ratelimit)) plogk("ide: Write protected.\n"); + return 8; + } + if (ide_devices[drive].type == IDE_ATAPI) { + uint8_t key = 0; + uint8_t asc = 0; + uint8_t ascq = 0; + uint8_t serr; + static DEFINE_RATELIMIT_STATE(ratelimit, PRINTK_RATELIMIT_TICKS, PRINTK_RATELIMIT_BURST); + + /* Sense decides the no-media mapping, so always consume it; only the logs are limited */ + serr = atapi_request_sense(drive, &key, &asc, &ascq); + if (serr != 0) { + if (ratelimit_allow(&ratelimit)) plogk("atapi: REQUEST SENSE failed (%u), original error %u on drive %u\n", serr, err, drive); + return err; + } + if (ratelimit_allow(&ratelimit)) plogk("atapi: Sense key 0x%x, ASC 0x%02x, ASCQ 0x%02x on drive %u\n", key, asc, ascq, drive); + if (key == 2 && asc == 0x3A) return IDE_ERR_NO_MEDIUM; + return err; + } if (err == 1) { static DEFINE_RATELIMIT_STATE(ratelimit, PRINTK_RATELIMIT_TICKS, PRINTK_RATELIMIT_BURST); if (ratelimit_allow(&ratelimit)) plogk("ide: Device fault.\n"); @@ -264,10 +454,6 @@ static uint8_t ide_print_error(uint32_t drive, uint8_t err) static DEFINE_RATELIMIT_STATE(ratelimit, PRINTK_RATELIMIT_TICKS, PRINTK_RATELIMIT_BURST); if (ratelimit_allow(&ratelimit)) plogk("ide: Reads nothing.\n"); err = 23; - } else if (err == 4) { - static DEFINE_RATELIMIT_STATE(ratelimit, PRINTK_RATELIMIT_TICKS, PRINTK_RATELIMIT_BURST); - if (ratelimit_allow(&ratelimit)) plogk("ide: Write protected.\n"); - err = 8; } return err; } @@ -291,7 +477,7 @@ void init_ide(void) plogk("ide: Controller found at PCI %04x:%02x:%02x.%01x, vendor 0x%04x, device 0x%04x\n", bar_reg.parent->device->domain, bar_reg.parent->device->bus, bar_reg.parent->device->slot, bar_reg.parent->device->func, bar_reg.parent->vendor_id, bar_reg.parent->device_id); - pci_enable_device(bar_reg.parent, PCI_CMD_IO); + pci_enable_device(bar_reg.parent, PCI_CMD_IO | PCI_CMD_BUSMASTER); register_interrupt_handler(IRQ_14, (void *)ide_irq_primary, 0, 0x8e); register_interrupt_handler(IRQ_15, (void *)ide_irq_secondary, 0, 0x8e); @@ -301,7 +487,9 @@ void init_ide(void) cast.ptr = bars[idx].address; bar_addrs[idx] = cast.val; } - ide_initialize(bar_addrs[0], bar_addrs[1], bar_addrs[2], bar_addrs[3], bar_addrs[4]); + + uint8_t bmide_valid = (bars[4].type == input_output) && bar_addrs[4] != 0; + ide_initialize(bar_addrs[0], bar_addrs[1], bar_addrs[2], bar_addrs[3], bar_addrs[4], bmide_valid); } /* Read a byte of data from the specified register of the IDE device */ @@ -404,8 +592,8 @@ void ide_soft_reset(uint8_t drive) ide_polling(channel, 0); } -/* Flush write cache */ -uint8_t ide_flush_cache(uint8_t drive) +/* Flush write cache; the caller holds the channel lock */ +static uint8_t ide_flush_cache_locked(uint8_t drive) { uint8_t channel = ide_devices[drive].channel; uint8_t slavebit = ide_devices[drive].drive; @@ -414,7 +602,11 @@ uint8_t ide_flush_cache(uint8_t drive) int tout = IDE_POLL_RETRY; while (ide_read(channel, ATA_REG_STATUS) & ATA_SR_BSY) { - if (--tout <= 0) break; + if (--tout <= 0) { + static DEFINE_RATELIMIT_STATE(ratelimit, PRINTK_RATELIMIT_TICKS, PRINTK_RATELIMIT_BURST); + if (ratelimit_allow(&ratelimit)) plogk("ide: Cache flush BSY timeout on drive %u\n", drive); + return 3; + } nsleep(10); } ide_write(channel, ATA_REG_HDDEVSEL, 0xe0 | (slavebit << 4)); @@ -447,75 +639,80 @@ uint8_t ide_flush_cache(uint8_t drive) return 0; } -/* Read and write ATA devices */ -uint8_t ide_ata_access(uint8_t direction, uint8_t drive, uint64_t lba, uint8_t numsects, uint16_t *edi) +/* Flush write cache */ +uint8_t ide_flush_cache(uint8_t drive) { - uint8_t lba_mode, cmd; - uint8_t lba_io[6]; - uint32_t channel = ide_devices[drive].channel; - uint32_t slavebit = ide_devices[drive].drive; - uint32_t bus = channels[channel].base; - uint32_t words = 256; - uint16_t cyl, i; - uint8_t head, sect, err; - - /* Serialise the channel's shared registers across concurrent I/O. The ATA path is polled, so no IRQ is needed while the lock is held. */ + uint8_t channel = ide_devices[drive].channel; + uint8_t err; + spin_lock(&channels[channel].lock); channels[channel].irq_pending = 0; channels[channel].nIEN = 0x02; ide_write(channel, ATA_REG_CONTROL, 0x02); - if (lba >= 0x10000000) { - lba_mode = 2; - lba_io[0] = (lba & 0x000000ff) >> 0; - lba_io[1] = (lba & 0x0000ff00) >> 8; - lba_io[2] = (lba & 0x00ff0000) >> 16; + err = ide_flush_cache_locked(drive); + spin_unlock(&channels[channel].lock); + + return err; +} + +/* Program the task file for one transfer; the caller holds the channel lock */ +static uint8_t ide_ata_setup(uint8_t drive, uint64_t lba, uint8_t numsects, uint8_t *lba_mode) +{ + uint8_t channel = ide_devices[drive].channel; + uint8_t head = 0; + uint8_t lba_io[6] = {0}; + uint64_t cyl, sect; + + if (ide_devices[drive].command_sets & (1 << 26)) { + *lba_mode = 2; + lba_io[0] = (lba >> 0) & 0xff; + lba_io[1] = (lba >> 8) & 0xff; + lba_io[2] = (lba >> 16) & 0xff; lba_io[3] = (lba >> 24) & 0xff; lba_io[4] = (lba >> 32) & 0xff; lba_io[5] = (lba >> 40) & 0xff; - head = 0; } else if (ide_devices[drive].capabilities & 0x200) { - lba_mode = 1; - lba_io[0] = (lba & 0x00000ff) >> 0; - lba_io[1] = (lba & 0x000ff00) >> 8; - lba_io[2] = (lba & 0x0ff0000) >> 16; - lba_io[3] = 0; - lba_io[4] = 0; - lba_io[5] = 0; - head = (lba & 0xf000000) >> 24; + *lba_mode = 1; + lba_io[0] = (lba >> 0) & 0xff; + lba_io[1] = (lba >> 8) & 0xff; + lba_io[2] = (lba >> 16) & 0xff; + head = (lba >> 24) & 0x0f; } else { - lba_mode = 0; + *lba_mode = 0; sect = (lba % 63) + 1; cyl = (lba + 1 - sect) / ((uint64_t)16 * 63); lba_io[0] = sect; lba_io[1] = (cyl >> 0) & 0xff; lba_io[2] = (cyl >> 8) & 0xff; - lba_io[3] = 0; - lba_io[4] = 0; - lba_io[5] = 0; head = (lba + 1 - sect) % ((uint64_t)16 * 63) / (63); } - if (ide_polling(channel, 0) != 0) { - spin_unlock(&channels[channel].lock); - return 3; - } - if (lba_mode == 0) { - ide_write(channel, ATA_REG_HDDEVSEL, 0xa0 | (slavebit << 4) | head); - } else { - ide_write(channel, ATA_REG_HDDEVSEL, 0xe0 | (slavebit << 4) | head); - } - if (lba_mode == 2) { + if (ide_polling(channel, 0) != 0) return 3; + + ide_write(channel, ATA_REG_HDDEVSEL, (*lba_mode == 0 ? 0xa0 : 0xe0) | (ide_devices[drive].drive << 4) | head); + if (*lba_mode == 2) { ide_write(channel, ATA_REG_SECCOUNT1, 0); ide_write(channel, ATA_REG_LBA3, lba_io[3]); ide_write(channel, ATA_REG_LBA4, lba_io[4]); ide_write(channel, ATA_REG_LBA5, lba_io[5]); } - ide_write(channel, ATA_REG_SECCOUNT0, numsects); ide_write(channel, ATA_REG_LBA0, lba_io[0]); ide_write(channel, ATA_REG_LBA1, lba_io[1]); ide_write(channel, ATA_REG_LBA2, lba_io[2]); + return 0; +} + +/* Perform a PIO data transfer; the caller holds the channel lock and programmed the task file */ +static uint8_t ide_ata_pio_transfer(uint8_t direction, uint8_t drive, uint8_t lba_mode, uint8_t numsects, uint16_t *edi) +{ + uint8_t channel = ide_devices[drive].channel; + uint32_t bus = channels[channel].base; + uint16_t words = 256; + uint16_t i; + uint8_t cmd, err; + if (direction == ATA_READ) { cmd = lba_mode == 2 ? ATA_CMD_READ_PIO_EXT : ATA_CMD_READ_PIO; } else { @@ -523,38 +720,215 @@ uint8_t ide_ata_access(uint8_t direction, uint8_t drive, uint64_t lba, uint8_t n } ide_write(channel, ATA_REG_COMMAND, cmd); - if (direction == 0) { - /* PIO Read */ - uint16_t *word_ = edi; + if (direction == ATA_READ) { for (i = 0; i < numsects; i++) { err = ide_polling(channel, 1); - if (err != 0) { - spin_unlock(&channels[channel].lock); - return err; - } - insl(bus, (uint32_t *)(word_ + ((size_t)i * words)), words / 2); + if (err != 0) return err; + insl(bus, (uint32_t *)(edi + ((size_t)i * words)), words / 2); } } else { - /* PIO Write */ - uint16_t *word_ = edi; for (i = 0; i < numsects; i++) { - err = ide_polling(channel, 0); - if (err != 0) plogk("ide: PIO write poll error %u on drive %u LBA %llu\n", err, drive, lba); - for (uint32_t h = 0; h < words; h++) outw(bus, word_[(i * words) + h]); + err = ide_polling(channel, 1); + if (err != 0) return err; + for (uint32_t h = 0; h < words; h++) outw(bus, edi[((size_t)i * words) + h]); } - ide_write(channel, ATA_REG_COMMAND, (char[]) {ATA_CMD_CACHE_FLUSH, ATA_CMD_CACHE_FLUSH, ATA_CMD_CACHE_FLUSH_EXT}[lba_mode]); - err = ide_polling(channel, 0); - if (err != 0) plogk("ide: PIO write flush poll error %u on drive %u LBA %llu\n", err, drive, lba); + err = ide_flush_cache_locked(drive); + if (err != 0) return err; } - spin_unlock(&channels[channel].lock); return 0; } +/* Run one bus-master chunk; the caller holds the channel lock and programmed the task file */ +static uint8_t ide_ata_dma_chunk(uint8_t direction, uint8_t drive, uint8_t lba_mode, uint8_t numsects, uint16_t *edi) +{ + uint8_t channel = ide_devices[drive].channel; + uint16_t bytes = (uint16_t)(numsects * 512); + uint8_t cmd, err; + + if (direction == ATA_WRITE) { + memcpy(channels[channel].dma_buf, edi, bytes); + dma_write_barrier(); + } + + ide_bm_clear_status(channel); + ide_bm_set_prd(channel, bytes); + + if (direction == ATA_READ) { + cmd = lba_mode == 2 ? ATA_CMD_READ_DMA_EXT : ATA_CMD_READ_DMA; + } else { + cmd = lba_mode == 2 ? ATA_CMD_WRITE_DMA_EXT : ATA_CMD_WRITE_DMA; + } + ide_write(channel, ATA_REG_COMMAND, cmd); + ide_bm_start(channel, direction == ATA_WRITE); + + err = ide_bm_wait(channel); + if (err != 0) { + ide_bm_stop(channel); + return err; + } + if (direction == ATA_READ) { + dma_read_barrier(); + memcpy(edi, channels[channel].dma_buf, bytes); + } + + return 0; +} + +/* Perform a bus-master DMA transfer in bounce-buffer sized chunks */ +static uint8_t ide_ata_dma_transfer(uint8_t direction, uint8_t drive, uint64_t lba, uint8_t numsects, uint16_t *edi) +{ + uint8_t done = 0; + uint8_t err; + + while (done < numsects) { + uint8_t chunk = numsects - done; + uint8_t lba_mode = 0; + + if (chunk > ATA_DMA_MAX_SECTORS) chunk = ATA_DMA_MAX_SECTORS; + err = ide_ata_setup(drive, lba + done, chunk, &lba_mode); + if (err != 0) return err; + + if (lba_mode == 0) return 3; + err = ide_ata_dma_chunk(direction, drive, lba_mode, chunk, edi + ((size_t)done * 256)); + if (err != 0) return err; + + done += chunk; + } + if (direction == ATA_WRITE) return ide_flush_cache_locked(drive); + + return 0; +} + +/* Drop a drive to PIO after a bus-master error; a stuck BSY resets the channel */ +void ide_dma_fallback(uint8_t drive) +{ + uint8_t is_atapi = drive < 4 && atapi_devices[drive].reserved; + uint8_t channel = is_atapi ? atapi_devices[drive].channel : ide_devices[drive].channel; + + if (!channels[channel].bmide) return; + ide_bm_stop(channel); + ide_bm_clear_status(channel); + ide_devices[drive].dma_enabled = 0; + + if (is_atapi) atapi_devices[drive].dma_enabled = 0; + if (!(ide_read(channel, ATA_REG_STATUS) & ATA_SR_BSY)) return; + + /* A channel reset also clears the firmware's DMA mode */ + static DEFINE_RATELIMIT_STATE(ratelimit, PRINTK_RATELIMIT_TICKS, PRINTK_RATELIMIT_BURST); + if (ratelimit_allow(&ratelimit)) plogk("ide: Channel %u DMA timeout, resetting to PIO.\n", channel); + + if (is_atapi) { + atapi_soft_reset(drive); + } else { + ide_soft_reset(drive); + } + + /* SRST resets both drives on the channel, so every ATAPI drive needs its unit attention cleared */ + for (int d = 0; d < 4; d++) { + if (ide_devices[d].reserved && ide_devices[d].channel == channel) ide_devices[d].dma_enabled = 0; + if (atapi_devices[d].reserved && atapi_devices[d].channel == channel) { + atapi_devices[d].dma_enabled = 0; + atapi_reset_recover(d); + } + } + if (channels[channel].dma_ok) { + free_frames(channels[channel].dma_buf_phys, ATA_DMA_BUF_PAGES); + free_frames(channels[channel].prd_phys, 1); + channels[channel].dma_ok = 0; + channels[channel].dma_buf = NULL; + channels[channel].dma_buf_phys = 0; + channels[channel].prd_phys = 0; + channels[channel].prd = NULL; + } +} + +/* + * The firmware paired host timing with the drive's DMA mode; reuse it instead of + * per-vendor chipset programming (Intel/VIA/SiS/AMD/NVIDIA/ATI/...). A boot-time + * DMA/PIO comparison validates it; full speed still needs those chipset drivers. + */ +static uint8_t ide_validate_ata(uint8_t drive) +{ + uint8_t channel = ide_devices[drive].channel; + uint8_t lba_mode; + uint8_t dma_tried = 0; + uint8_t err; + + if (!channels[channel].dma_ok) return 0; + if (!(ide_devices[drive].capabilities & 0x100)) return 0; + if (!(ide_devices[drive].command_sets & (1 << 26)) && !(ide_devices[drive].capabilities & 0x200)) return 0; + if (ide_devices[drive].size < 8) return 0; + + spin_lock(&channels[channel].lock); + channels[channel].irq_pending = 0; + channels[channel].nIEN = 0x02; + ide_write(channel, ATA_REG_CONTROL, 0x02); + + err = ide_ata_setup(drive, 0, 8, &lba_mode); + if (err == 0) err = ide_ata_pio_transfer(ATA_READ, drive, lba_mode, 8, ide_validate_pio); + if (err == 0) { + dma_tried = 1; + err = ide_ata_dma_transfer(ATA_READ, drive, 0, 8, ide_validate_dma); + if (err == 0 && memcmp(ide_validate_pio, ide_validate_dma, 4096) != 0) err = 4; + } + + if (err != 0) { + if (!dma_tried) { + plogk("ide: DMA validation skipped on drive %u (PIO read error %u), using PIO.\n", drive, err); + spin_unlock(&channels[channel].lock); + return 0; + } + ide_dma_fallback(drive); + plogk("ide: DMA validation failed on drive %u, using PIO.\n", drive); + spin_unlock(&channels[channel].lock); + return 0; + } + + spin_unlock(&channels[channel].lock); + return 1; +} + +/* Read and write ATA devices */ +uint8_t ide_ata_access(uint8_t direction, uint8_t drive, uint64_t lba, uint8_t numsects, uint16_t *edi) +{ + uint8_t channel = ide_devices[drive].channel; + uint8_t lba_mode = 0; + uint8_t err; + + /* A zero sector count means 256 sectors in the task file */ + if (numsects == 0) return 0; + + /* Serialise the channel's shared registers across concurrent I/O. Both paths are polled, so no IRQ is needed while the lock is held. */ + spin_lock(&channels[channel].lock); + channels[channel].irq_pending = 0; + channels[channel].nIEN = 0x02; + ide_write(channel, ATA_REG_CONTROL, 0x02); + + if (ide_devices[drive].dma_enabled && channels[channel].dma_ok) { + err = ide_ata_dma_transfer(direction, drive, lba, numsects, edi); + if (err == 0) { + spin_unlock(&channels[channel].lock); + return 0; + } + + ide_dma_fallback(drive); + static DEFINE_RATELIMIT_STATE(ratelimit, PRINTK_RATELIMIT_TICKS, PRINTK_RATELIMIT_BURST); + if (ratelimit_allow(&ratelimit)) plogk("ide: DMA error %u on drive %u, falling back to PIO.\n", err, drive); + } + + err = ide_ata_setup(drive, lba, numsects, &lba_mode); + if (err == 0) err = ide_ata_pio_transfer(direction, drive, lba_mode, numsects, edi); + + spin_unlock(&channels[channel].lock); + return err; +} + /* Read multiple sectors from an IDE device */ int ide_read_sectors(uint8_t drive, uint8_t numsects, uint64_t lba, uint16_t *edi) { if (drive > 3 || !ide_devices[drive].reserved) return -ENODEV; - if ((lba >= ide_devices[drive].size || numsects > ide_devices[drive].size - lba) && (ide_devices[drive].type == IDE_ATA)) return -EINVAL; + if (ide_devices[drive].type == IDE_ATAPI && ide_devices[drive].size == 0) return -ENOMEDIUM; + if (lba >= ide_devices[drive].size || numsects > ide_devices[drive].size - lba) return -EINVAL; /* Reading in PIO mode via polling and IRQ */ uint8_t err = 0; @@ -563,7 +937,9 @@ int ide_read_sectors(uint8_t drive, uint8_t numsects, uint64_t lba, uint16_t *ed } else if (ide_devices[drive].type == IDE_ATAPI) { err = atapi_read(drive, lba, numsects, edi); } - (void)ide_print_error(drive, err); + + err = ide_print_error(drive, err); + if (err == IDE_ERR_NO_MEDIUM) return -ENOMEDIUM; return err ? -EIO : EOK; } diff --git a/include/drivers/block/ata/pata/atapi.h b/include/drivers/block/ata/pata/atapi.h index e226f1a3..d501ae37 100644 --- a/include/drivers/block/ata/pata/atapi.h +++ b/include/drivers/block/ata/pata/atapi.h @@ -79,6 +79,9 @@ typedef struct { uint32_t lba_size; uint32_t blk_size; uint8_t model[41]; + uint8_t dma_enabled; + uint8_t dma_capable; + uint8_t dma_dir; } atapi_device_t; extern atapi_device_t atapi_devices[4]; @@ -89,6 +92,9 @@ uint8_t atapi_identify(uint8_t channel, uint8_t drive, uint8_t dev_index); /* Soft reset the ATAPI device */ void atapi_soft_reset(uint8_t drive); +/* Clear the unit attention raised by a channel reset; the caller holds the channel lock */ +void atapi_reset_recover(uint8_t drive); + /* Send a TEST UNIT READY command */ uint8_t atapi_test_unit_ready(uint8_t drive); @@ -98,6 +104,9 @@ uint8_t atapi_inquiry(uint8_t drive, void *buf); /* Send READ CAPACITY, returning the LBA count and block size */ uint8_t atapi_read_capacity(uint8_t drive, uint32_t *lba_size, uint32_t *blk_size); +/* Refresh capacity after a possible disc change */ +int atapi_revalidate(uint8_t drive, uint32_t *lba_size, uint32_t *blk_size); + /* Send REQUEST SENSE, returning the sense key and ASC/ASCQ */ uint8_t atapi_request_sense(uint8_t drive, uint8_t *sense_key, uint8_t *asc, uint8_t *ascq); diff --git a/include/drivers/block/ata/pata/ide.h b/include/drivers/block/ata/pata/ide.h index 72bc8b69..d1adc61a 100644 --- a/include/drivers/block/ata/pata/ide.h +++ b/include/drivers/block/ata/pata/ide.h @@ -38,7 +38,6 @@ #define ATA_CMD_WRITE_PIO 0x30 #define ATA_CMD_WRITE_PIO_EXT 0x34 #define ATA_CMD_WRITE_DMA 0xca -#define ATA_CMD_SET_FEATURES 0xef #define IDE_ATA 0x00 #define IDE_ATAPI 0x01 @@ -46,6 +45,32 @@ #define IDE_POLL_RETRY 100000 #define IDE_IRQ_TIMEOUT 500000 +/* ide_print_error() result for a medium-not-present sense */ +#define IDE_ERR_NO_MEDIUM 24 + +/* Bus-master IDE register offsets within a channel's BAR4 window */ +#define ATA_BM_CMD 0x00 +#define ATA_BM_STATUS 0x02 +#define ATA_BM_PRDT 0x04 + +/* ATA_BM_CMD bits */ +#define ATA_BM_CMD_START 0x01 +#define ATA_BM_CMD_DIR 0x08 + +/* ATA_BM_STATUS bits */ +#define ATA_BM_STATUS_ACTIVE 0x01 +#define ATA_BM_STATUS_ERR 0x02 +#define ATA_BM_STATUS_INT 0x04 +#define ATA_BM_STATUS_CLEAR 0x06 + +/* PRD flags */ +#define ATA_PRD_EOT 0x8000 + +/* DMA bounce buffer geometry */ +#define ATA_DMA_BUF_PAGES 8 +#define ATA_DMA_MAX_BYTES (ATA_DMA_BUF_PAGES * 4096) +#define ATA_DMA_MAX_SECTORS 64 + #define ATA_MASTER 0x00 #define ATA_SLAVE 0x01 #define ATA_REG_DATA 0x00 @@ -72,17 +97,29 @@ #define ATA_READ 0x00 #define ATA_WRITE 0x01 +typedef struct { + uint32_t base; + uint16_t count; + uint16_t flags; +} __attribute__((packed)) ata_prd_t; + typedef struct { uint16_t base; uint16_t ctrl; uint16_t bmide; uint8_t nIEN; + /* Per-channel bus-master bounce buffer and PRD table */ + uint8_t dma_ok; + uint8_t *dma_buf; + uint64_t dma_buf_phys; + uint64_t prd_phys; + ata_prd_t *prd; + /* - * Serialises the channel's shared registers across concurrent I/O on - * the same channel (two CPUs accessing drives on one cable). The ATA - * path is polled, so the spinlock may be held across an access; the - * IRQ-driven ATAPI path releases it around the interrupt wait. + * Serialises the shared channel registers across concurrent I/O on one + * cable (two CPUs accessing two drives); both paths poll completion, so + * the lock is held for the whole transfer. */ spinlock_t lock; @@ -103,6 +140,7 @@ typedef struct { uint32_t command_sets; uint64_t size; uint8_t model[41]; + uint8_t dma_enabled; } ide_device_t; extern ide_channel_registers_t channels[2]; @@ -133,10 +171,20 @@ uint8_t ide_polling(uint8_t channel, uint32_t advanced_check); /* Soft reset the ATA device */ void ide_soft_reset(uint8_t drive); +/* Drop a drive to PIO after a bus-master error; a stuck BSY resets the channel */ +void ide_dma_fallback(uint8_t drive); + /* Flush the drive's write cache */ uint8_t ide_flush_cache(uint8_t drive); -/* Perform a PIO read or write on an ATA drive */ +/* Bus-master primitives; callers hold the channel lock */ +void ide_bm_clear_status(uint8_t channel); +void ide_bm_set_prd(uint8_t channel, uint16_t bytes); +void ide_bm_start(uint8_t channel, uint8_t write); +void ide_bm_stop(uint8_t channel); +uint8_t ide_bm_wait(uint8_t channel); + +/* Perform an ATA read or write (bus-master DMA when the BIOS mode validates, PIO otherwise) */ uint8_t ide_ata_access(uint8_t direction, uint8_t drive, uint64_t lba, uint8_t numsects, uint16_t *edi); /* Read multiple sectors from an IDE device (ATA or ATAPI); returns 0 or -errno */ From 09dacf6c992e8d1d5d89c47d4bb2561c67143a1b Mon Sep 17 00:00:00 2001 From: MicroFish <2609948707@qq.com> Date: Thu, 5 Nov 2026 03:31:01 +0800 Subject: [PATCH 2/3] satapi: recover the port and revalidate optical media A failed packet leaves the port's PxCI bit set and stalls every later command. Restart the port engine after a task-file error or timeout, and poll READ CAPACITY with REQUEST SENSE retries after a media change so a new disc is picked up after unit attention or becoming ready. Signed-off-by: MicroFish <2609948707@qq.com> Co-Authored-By: Claude Code --- drivers/block/ata/sata/satapi.c | 101 +++++++++++++++++++++++- include/drivers/block/ata/sata/satapi.h | 3 + 2 files changed, 101 insertions(+), 3 deletions(-) diff --git a/drivers/block/ata/sata/satapi.c b/drivers/block/ata/sata/satapi.c index adce94d7..cbcacbeb 100644 --- a/drivers/block/ata/sata/satapi.c +++ b/drivers/block/ata/sata/satapi.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -26,6 +27,25 @@ ahci_satapi_device_t ahci_satapi_devices[AHCI_MAX_DEVICES]; int ahci_satapi_device_count = 0; +/* Restart the port engine to release a failed command's PxCI bit. */ +static void satapi_port_recover(ahci_port_state_t *port) +{ + volatile uint8_t *p = port->port_mmio; + int tout; + + ahci_write32(p, PORT_CMD, ahci_read32(p, PORT_CMD) & ~PORT_CMD_ST); + + tout = 500000; + while (ahci_read32(p, PORT_CMD) & PORT_CMD_CR) + if (--tout <= 0) return; + + ahci_write32(p, PORT_CMD, ahci_read32(p, PORT_CMD) | PORT_CMD_ST); + + tout = 500000; + while (!(ahci_read32(p, PORT_CMD) & PORT_CMD_CR)) + if (--tout <= 0) return; +} + /* Issue an ATAPI packet command via AHCI */ static int satapi_issue_packet(ahci_port_state_t *port, int slot, const uint8_t *cdb, uint16_t cdb_len, uint8_t direction, int is_dma, uint64_t buf_phys, uint32_t byte_count) { @@ -73,7 +93,7 @@ static int satapi_issue_packet(ahci_port_state_t *port, int slot, const uint8_t hdr->ctbau = (uint32_t)(port->ct_phys >> 32); tout = 1000000; - while (ahci_read32(p, PORT_TFDATA) & 0x88) + while (ahci_read32(p, PORT_TFDATA) & 0x80) if (--tout <= 0) return -ETIMEDOUT; ahci_write32(p, PORT_IRQ_STAT, 0xFFFFFFFF); @@ -84,12 +104,19 @@ static int satapi_issue_packet(ahci_port_state_t *port, int slot, const uint8_t if (!(ahci_read32(p, PORT_CI) & (uint32_t)(1 << slot))) break; if (ahci_read32(p, PORT_IRQ_STAT) & PORT_IRQ_TF_ERR) { ahci_write32(p, PORT_IRQ_STAT, PORT_IRQ_TF_ERR); + satapi_port_recover(port); return -EIO; } - if (--tout <= 0) return -ETIMEDOUT; + if (--tout <= 0) { + satapi_port_recover(port); + return -ETIMEDOUT; + } } - if (ahci_read32(p, PORT_TFDATA) & 0x01) return -EIO; + if (ahci_read32(p, PORT_TFDATA) & 0x01) { + satapi_port_recover(port); + return -EIO; + } if (byte_count && hdr->prdbc != byte_count) return -EIO; return 0; } @@ -241,6 +268,74 @@ uint8_t ahci_satapi_read_capacity(uint8_t drive, uint32_t *lba_size, uint32_t *b return 0; } +/* Fetch sense data after a failed packet command. */ +static int satapi_request_sense(uint8_t drive, uint8_t *sense_key, uint8_t *asc) +{ + uint8_t cdb[SATAPI_CDB_LEN] = {GPCMD_REQUEST_SENSE, 0, 0, 0, SCSI_SENSE_BUFFER_SIZE, 0}; + uint8_t sense[SCSI_SENSE_BUFFER_SIZE]; + size_t len = sizeof(sense); + + memset(sense, 0, sizeof(sense)); + if (ahci_satapi_send_packet(drive, cdb, SCSI_SENSE_BUFFER_SIZE, SATAPI_PROT_PIO, sense, &len)) return -EIO; + + *sense_key = sense[2] & 0x0F; + *asc = sense[12]; + return 0; +} + +/* Refresh capacity after a possible disc change. */ +int ahci_satapi_revalidate(uint8_t drive, uint32_t *lba_size, uint32_t *blk_size) +{ + uint32_t lba = 0; + uint32_t blk = 0; + + if (drive >= AHCI_MAX_DEVICES || !ahci_satapi_devices[drive].reserved) return -ENODEV; + if (!lba_size || !blk_size) return -EINVAL; + + for (int attempt = 0; attempt < 20; attempt++) { + uint8_t key = 0; + uint8_t asc = 0; + uint8_t retryable; + + if (ahci_satapi_read_capacity(drive, &lba, &blk) == 0) { + if (lba == 0 || blk == 0 || blk > 4096) { + ahci_satapi_devices[drive].lba_size = 0; + ahci_devices[drive].size = 0; + return -ENOMEDIUM; + } + ahci_satapi_devices[drive].lba_size = lba; + ahci_satapi_devices[drive].blk_size = blk; + ahci_devices[drive].size = lba; + ahci_devices[drive].sector_size = blk; + *lba_size = lba; + *blk_size = blk; + return EOK; + } + lba = 0; + blk = 0; + + /* + * A new disc reports unit attention or becoming ready; 0x3A also means empty, + * so it gets only two quick retries; REQUEST SENSE clears the unit attention. + */ + retryable = 0; + if (satapi_request_sense(drive, &key, &asc) == 0) { + if ((key == 6 && asc == 0x28) || (key == 2 && (asc == 0x04 || asc == 0x11))) { + retryable = 2; + } else if (key == 2 && asc == 0x3A && attempt < 2) { + retryable = 1; + } + } + if (!retryable) break; + if (retryable == 2) msleep(50); + } + + ahci_satapi_devices[drive].lba_size = 0; + ahci_devices[drive].size = 0; + + return -ENOMEDIUM; +} + /* Send INQUIRY, writing the vendor and product identification into model */ uint8_t ahci_satapi_inquiry(uint8_t drive, char *model, size_t model_size) { diff --git a/include/drivers/block/ata/sata/satapi.h b/include/drivers/block/ata/sata/satapi.h index d5ba9d6c..d43b1962 100644 --- a/include/drivers/block/ata/sata/satapi.h +++ b/include/drivers/block/ata/sata/satapi.h @@ -48,6 +48,9 @@ uint8_t ahci_satapi_test_unit_ready(uint8_t drive); /* Send READ CAPACITY, returning the LBA count and block size */ uint8_t ahci_satapi_read_capacity(uint8_t drive, uint32_t *lba_size, uint32_t *blk_size); +/* Refresh capacity after a possible disc change */ +int ahci_satapi_revalidate(uint8_t drive, uint32_t *lba_size, uint32_t *blk_size); + /* Send INQUIRY, returning the vendor and product identification in model */ uint8_t ahci_satapi_inquiry(uint8_t drive, char *model, size_t model_size); From 643056fb3eab941a5afbe5b14ad0d29c002fe25d Mon Sep 17 00:00:00 2001 From: MicroFish <2609948707@qq.com> Date: Thu, 5 Nov 2026 03:31:05 +0800 Subject: [PATCH 3/3] block: revalidate removable media on open and report no medium Add an optional blockdev_ops.revalidate hook and call it from devtmpfs when a block node is opened, refreshing the node geometry after a disc change. Reads and writes on an empty ATAPI drive return -ENOMEDIUM, and the block open path propagates the refresh error so a mount reports the drive as having no medium. Signed-off-by: MicroFish <2609948707@qq.com> Co-Authored-By: Claude Code --- drivers/block/core/blockdev.c | 72 ++++++++++++++++++++++++++- fs/devtmpfs/devtmpfs.c | 34 +++++++++++++ include/drivers/block/core/blockdev.h | 4 ++ 3 files changed, 109 insertions(+), 1 deletion(-) diff --git a/drivers/block/core/blockdev.c b/drivers/block/core/blockdev.c index cb7b4f18..37833de2 100644 --- a/drivers/block/core/blockdev.c +++ b/drivers/block/core/blockdev.c @@ -67,12 +67,20 @@ static void blk_empty_reference(const struct blockdev_device *dev) (void)dev; } +/* Block I/O empty revalidate. */ +static int blk_empty_revalidate(struct blockdev_device *dev) +{ + (void)dev; + return EOK; +} + static struct blockdev_ops blk_empty_ops = { .read_sectors = blk_empty_read, .write_sectors = blk_empty_write, .flush = blk_empty_flush, .retain = blk_empty_reference, .release = blk_empty_reference, + .revalidate = blk_empty_revalidate, }; /* NVMe backend ops (forwarders to nvme.c) */ @@ -157,10 +165,30 @@ static int blk_ide_flush(const blockdev_device_t *dev) return ide_flush_cache(dev->drive) == 0 ? EOK : -EIO; } +/* Refresh PATA optical geometry after a media change. */ +static int blk_ide_revalidate(blockdev_device_t *dev) +{ + uint32_t lba = 0, blk = 0; + int status; + + if (!dev) return -EINVAL; + if (!dev->optical) return EOK; + + status = atapi_revalidate(dev->drive, &lba, &blk); + if (status == EOK) { + dev->sector_count = lba; + dev->sector_size = blk; + } else { + dev->sector_count = 0; + } + return status; +} + static struct blockdev_ops blk_ide_ops = { .read_sectors = blk_ide_read_sectors, .write_sectors = blk_ide_write_sectors, .flush = blk_ide_flush, + .revalidate = blk_ide_revalidate, }; static int blk_ide_type_id = -1; @@ -234,8 +262,28 @@ static int blk_ahci_atapi_read_sectors(const blockdev_device_t *dev, uint64_t lb return EOK; } +/* Refresh SATA optical geometry after a media change. */ +static int blk_ahci_atapi_revalidate(blockdev_device_t *dev) +{ + uint32_t lba = 0, blk = 0; + int status; + + if (!dev) return -EINVAL; + + status = ahci_satapi_revalidate(dev->drive, &lba, &blk); + if (status == EOK) { + dev->sector_count = lba; + dev->sector_size = blk; + } else { + dev->sector_count = 0; + } + + return status; +} + static struct blockdev_ops blk_ahci_atapi_ops = { .read_sectors = blk_ahci_atapi_read_sectors, + .revalidate = blk_ahci_atapi_revalidate, }; static int blk_ahci_atapi_type_id = -1; @@ -333,10 +381,16 @@ int blockdev_open_nvme(void *ns, blockdev_device_t *device) int blockdev_open_atapi(uint8_t drive, blockdev_device_t *device) { #if CONFIG_ATA + uint32_t lba = 0; + uint32_t blk = 0; + if (!device) return -EINVAL; if (drive > 3 || !atapi_devices[drive].reserved) return -ENODEV; if (atapi_devices[drive].type != IDE_ATAPI) return -EINVAL; + /* Pick up a disc inserted after the boot probe. */ + (void)atapi_revalidate(drive, &lba, &blk); + device->ops_id = (uint8_t)blk_ide_type(); device->backend_data = NULL; device->drive = drive; @@ -381,10 +435,16 @@ int blockdev_open_ahci(uint8_t drive, blockdev_device_t *device) int blockdev_open_ahci_atapi(uint8_t drive, blockdev_device_t *device) { #if CONFIG_ATA + uint32_t lba = 0; + uint32_t blk = 0; + if (!device) return -EINVAL; if (drive >= AHCI_MAX_DEVICES || !ahci_devices[drive].reserved) return -ENODEV; if (ahci_devices[drive].type != AHCI_DEV_SATAPI) return -EINVAL; + /* Pick up a disc inserted after the boot probe. */ + (void)ahci_satapi_revalidate(drive, &lba, &blk); + device->ops_id = (uint8_t)blk_ahci_atapi_type(); device->backend_data = NULL; device->drive = drive; @@ -706,6 +766,7 @@ int blockdev_read_sectors(const blockdev_device_t *device, uint64_t lba, uint32_ { if (!device) return -EINVAL; if (!count) return EOK; + if (device->optical && device->sector_count == 0) return -ENOMEDIUM; if (!buffer) return -EINVAL; if (lba >= device->sector_count || count > device->sector_count - lba) return -EINVAL; @@ -718,6 +779,7 @@ int blockdev_write_sectors(const blockdev_device_t *device, uint64_t lba, uint32 if (!device) return -EINVAL; if (!count) return EOK; if (device->read_only) return -EROFS; + if (device->optical && device->sector_count == 0) return -ENOMEDIUM; if (!buffer) return -EINVAL; if (lba >= device->sector_count || count > device->sector_count - lba) return -EINVAL; @@ -731,6 +793,13 @@ int blockdev_flush(const blockdev_device_t *device) return blk_ops(device, flush)(device); } +/* Refresh removable-media geometry through the backend. */ +int blockdev_revalidate(blockdev_device_t *device) +{ + if (!device) return -EINVAL; + return blk_ops(device, revalidate)(device); +} + /* True when the descriptor's sector geometry can address storage. */ bool blockdev_geometry_valid(const blockdev_device_t *device) { @@ -761,6 +830,7 @@ int blockdev_read_bytes(const blockdev_device_t *device, uint64_t offset, void * if (!device) return -EINVAL; if (!size) return EOK; if (!buffer || !blockdev_geometry_valid(device)) return -EINVAL; + if (device->optical && device->sector_count == 0) return -ENOMEDIUM; if (size > (size_t)128 * 1024 * 1024) return -EINVAL; device_bytes = device->sector_count * device->sector_size; @@ -786,7 +856,7 @@ int blockdev_read_bytes(const blockdev_device_t *device, uint64_t offset, void * static DEFINE_RATELIMIT_STATE(ratelimit, PRINTK_RATELIMIT_TICKS, PRINTK_RATELIMIT_BURST); if (ratelimit_allow(&ratelimit)) plogk("blockdev: Read failed at LBA %llu count %u (offset %llu size %zu): %d\n", start_sector, sector_count, offset, size, status); free(scratch); - return -EIO; + return status == -ENOMEDIUM ? -ENOMEDIUM : -EIO; } memcpy(buffer, scratch + sector_offset, size); diff --git a/fs/devtmpfs/devtmpfs.c b/fs/devtmpfs/devtmpfs.c index 67d2ad66..7ca7664c 100644 --- a/fs/devtmpfs/devtmpfs.c +++ b/fs/devtmpfs/devtmpfs.c @@ -207,6 +207,33 @@ static size_t devtmpfs_block_write(void *context, const void *buffer, size_t off return blockdev_write_bytes(device, offset, buffer, size) == EOK ? size : 0; } +/* Re-probe removable media and refresh the node geometry. */ +static int devtmpfs_block_refresh(vfs_node_t node) +{ + tmpfs_file_t *handle; + blockdev_device_t *device; + int status = EOK; + + if (!node || !(handle = node->handle) || !handle->device.ctx) return -ENODEV; + device = handle->device.ctx; + if (device->optical) status = blockdev_revalidate(device); + + node->blksz = device->sector_size; + node->size = device->sector_count * device->sector_size; + + return status; +} + +/* tmpfs device callback: refresh removable media on open. */ +static int devtmpfs_block_open(vfs_node_t node, uint64_t flags, void **private_data) +{ + (void)flags; + if (private_data) *private_data = NULL; + + (void)devtmpfs_block_refresh(node); + return EOK; +} + /* Geometry queries for whole disks and partition nodes. */ static int devtmpfs_block_ioctl(void *context, size_t request, void *argument) { @@ -258,6 +285,7 @@ static int devtmpfs_block_ops_init(tmpfs_device_ops_t *ops, const blockdev_devic ops->read = devtmpfs_block_read; ops->write = devtmpfs_block_write; ops->ioctl = devtmpfs_block_ioctl; + ops->open = devtmpfs_block_open; ops->destroy = devtmpfs_block_destroy; ops->ctx = context; return EOK; @@ -291,6 +319,12 @@ int devtmpfs_open_block_device(const char *path, blockdev_device_t *device) return -ENODEV; } + int status = devtmpfs_block_refresh(node); + if (status != EOK) { + vfs_close(node); + return status; + } + *device = *(blockdev_device_t *)handle->device.ctx; blockdev_retain(device); vfs_close(node); diff --git a/include/drivers/block/core/blockdev.h b/include/drivers/block/core/blockdev.h index 161471b5..7946ea47 100644 --- a/include/drivers/block/core/blockdev.h +++ b/include/drivers/block/core/blockdev.h @@ -46,6 +46,7 @@ typedef struct blockdev_ops { int (*flush)(const struct blockdev_device *dev); void (*retain)(const struct blockdev_device *dev); void (*release)(const struct blockdev_device *dev); + int (*revalidate)(struct blockdev_device *dev); } *blockdev_ops_t; typedef struct blockdev_device { @@ -107,6 +108,9 @@ int blockdev_write_sectors(const blockdev_device_t *device, uint64_t lba, uint32 /* Commit volatile device write caches, if the backend provides one. */ int blockdev_flush(const blockdev_device_t *device); +/* Refresh removable-media geometry; fixed disks pass through unchanged. */ +int blockdev_revalidate(blockdev_device_t *device); + /* True when the descriptor's sector geometry can address storage. */ bool blockdev_geometry_valid(const blockdev_device_t *device);