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/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/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); 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);