Index: uspace/drv/block/ata_bd/ata_bd.c
===================================================================
--- uspace/drv/block/ata_bd/ata_bd.c	(revision 21989e5a1d36e65bebb408c0ca4bdb7b3590fe28)
+++ uspace/drv/block/ata_bd/ata_bd.c	(revision 64cf7a3bd1e3e66b8aa3b82148faced27efb35bb)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2013 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -49,4 +49,5 @@
 
 #include <ddi.h>
+#include <ddf/interrupt.h>
 #include <ddf/log.h>
 #include <async.h>
@@ -73,7 +74,4 @@
 #define NAME       "ata_bd"
 
-/** Number of defined legacy controller base addresses. */
-#define LEGACY_CTLS 4
-
 /**
  * Size of data returned from Identify Device or Identify Packet Device
@@ -84,4 +82,6 @@
 static errno_t ata_bd_init_io(ata_ctrl_t *ctrl);
 static void ata_bd_fini_io(ata_ctrl_t *ctrl);
+static errno_t ata_bd_init_irq(ata_ctrl_t *ctrl);
+static void ata_bd_fini_irq(ata_ctrl_t *ctrl);
 
 static errno_t ata_bd_open(bd_srvs_t *, bd_srv_t *);
@@ -121,4 +121,6 @@
 static errno_t wait_status(ata_ctrl_t *ctrl, unsigned set, unsigned n_reset,
     uint8_t *pstatus, unsigned timeout);
+static errno_t wait_irq(ata_ctrl_t *ctrl, uint8_t *pstatus, unsigned timeout);
+static void ata_irq_handler(ipc_call_t *call, ddf_dev_t *dev);
 
 bd_ops_t ata_bd_ops = {
@@ -133,4 +135,23 @@
 };
 
+static const irq_pio_range_t ata_irq_ranges[] = {
+	{
+		.base = 0,
+		.size = sizeof(ata_cmd_t)
+	}
+};
+
+/** ATA interrupt pseudo code. */
+static const irq_cmd_t ata_irq_cmds[] = {
+	{
+		.cmd = CMD_PIO_READ_8,
+		.addr = NULL,  /* will be patched in run-time */
+		.dstarg = 1
+	},
+	{
+		.cmd = CMD_ACCEPT
+	}
+};
+
 static disk_t *bd_srv_disk(bd_srv_t *bd)
 {
@@ -144,15 +165,19 @@
 
 /** Initialize ATA controller. */
-errno_t ata_ctrl_init(ata_ctrl_t *ctrl, ata_base_t *res)
+errno_t ata_ctrl_init(ata_ctrl_t *ctrl, ata_hwres_t *res)
 {
 	int i;
 	errno_t rc;
 	int n_disks;
+	bool irq_inited = false;
 
 	ddf_msg(LVL_DEBUG, "ata_ctrl_init()");
 
 	fibril_mutex_initialize(&ctrl->lock);
+	fibril_mutex_initialize(&ctrl->irq_lock);
+	fibril_condvar_initialize(&ctrl->irq_cv);
 	ctrl->cmd_physical = res->cmd;
 	ctrl->ctl_physical = res->ctl;
+	ctrl->irq = res->irq;
 
 	ddf_msg(LVL_NOTE, "I/O address %p/%p", (void *) ctrl->cmd_physical,
@@ -162,4 +187,10 @@
 	if (rc != EOK)
 		return rc;
+
+	rc = ata_bd_init_irq(ctrl);
+	if (rc != EOK)
+		return rc;
+
+	irq_inited = true;
 
 	for (i = 0; i < MAX_DISKS; i++) {
@@ -205,4 +236,6 @@
 		}
 	}
+	if (irq_inited)
+		ata_bd_fini_irq(ctrl);
 	ata_bd_fini_io(ctrl);
 	return rc;
@@ -228,4 +261,5 @@
 	}
 
+	ata_bd_fini_irq(ctrl);
 	ata_bd_fini_io(ctrl);
 	fibril_mutex_unlock(&ctrl->lock);
@@ -329,5 +363,4 @@
 
 	ctrl->ctl = vaddr;
-
 	return EOK;
 }
@@ -338,4 +371,78 @@
 	(void) ctrl;
 	/* XXX TODO */
+}
+
+/** Initialize IRQ. */
+static errno_t ata_bd_init_irq(ata_ctrl_t *ctrl)
+{
+	irq_code_t irq_code;
+	async_sess_t *parent_sess;
+	irq_pio_range_t *ranges;
+	irq_cmd_t *cmds;
+	errno_t rc;
+
+	if (ctrl->irq < 0)
+		return EOK;
+
+	ranges = malloc(sizeof(ata_irq_ranges));
+	if (ranges == NULL)
+		return ENOMEM;
+
+	cmds = malloc(sizeof(ata_irq_cmds));
+	if (cmds == NULL) {
+		free(cmds);
+		return ENOMEM;
+	}
+
+	memcpy(ranges, &ata_irq_ranges, sizeof(ata_irq_ranges));
+	ranges[0].base = ctrl->cmd_physical;
+	memcpy(cmds, &ata_irq_cmds, sizeof(ata_irq_cmds));
+	cmds[0].addr = &ctrl->cmd->status;
+
+	irq_code.rangecount = sizeof(ata_irq_ranges) / sizeof(irq_pio_range_t);
+	irq_code.ranges = ranges;
+	irq_code.cmdcount = sizeof(ata_irq_cmds) / sizeof(irq_cmd_t);
+	irq_code.cmds = cmds;
+
+	ddf_msg(LVL_NOTE, "IRQ %d", ctrl->irq);
+	rc = register_interrupt_handler(ctrl->dev, ctrl->irq, ata_irq_handler,
+	    &irq_code, &ctrl->ihandle);
+	if (rc != EOK) {
+		ddf_msg(LVL_ERROR, "Error registering IRQ.");
+		goto error;
+	}
+
+	parent_sess = ddf_dev_parent_sess_get(ctrl->dev);
+
+	rc = hw_res_enable_interrupt(parent_sess, ctrl->irq);
+	if (rc != EOK) {
+		ddf_msg(LVL_ERROR, "Error enabling IRQ.");
+		(void) unregister_interrupt_handler(ctrl->dev,
+		    ctrl->ihandle);
+		goto error;
+	}
+
+	free(ranges);
+	free(cmds);
+	return EOK;
+error:
+	free(ranges);
+	free(cmds);
+	return rc;
+}
+
+/** Clean up IRQ. */
+static void ata_bd_fini_irq(ata_ctrl_t *ctrl)
+{
+	errno_t rc;
+	async_sess_t *parent_sess;
+
+	parent_sess = ddf_dev_parent_sess_get(ctrl->dev);
+
+	rc = hw_res_disable_interrupt(parent_sess, ctrl->irq);
+	if (rc != EOK)
+		ddf_msg(LVL_ERROR, "Error disabling IRQ.");
+
+	(void) unregister_interrupt_handler(ctrl->dev, ctrl->ihandle);
 }
 
@@ -519,6 +626,8 @@
 	errno_t rc;
 
-	if (size < cnt * disk->block_size)
-		return EINVAL;
+	if (size < cnt * disk->block_size) {
+		rc = EINVAL;
+		goto error;
+	}
 
 	/* Maximum number of blocks to transfer at the same time */
@@ -534,5 +643,5 @@
 
 		if (rc != EOK)
-			return rc;
+			goto error;
 
 		ba += nb;
@@ -542,4 +651,7 @@
 
 	return EOK;
+error:
+	ddf_msg(LVL_DEBUG, "ata_bd_read_blocks: rc=%d", rc);
+	return rc;
 }
 
@@ -622,4 +734,5 @@
 	size_t bidx;
 	uint8_t status;
+	errno_t rc;
 
 	assert(nblocks > 0);
@@ -628,8 +741,16 @@
 	bidx = 0;
 	while (nblocks > 0) {
-		if (wait_status(ctrl, 0, ~SR_BSY, &status, TIMEOUT_BSY) != EOK)
+		if (ctrl->irq >= 0)
+			rc = wait_irq(ctrl, &status, TIMEOUT_BSY);
+		else
+			rc = wait_status(ctrl, 0, ~SR_BSY, &status, TIMEOUT_BSY);
+
+		if (rc != EOK) {
+			ddf_msg(LVL_DEBUG, "wait_irq/wait_status failed");
 			return EIO;
+		}
 
 		if ((status & SR_DRQ) == 0) {
+			ddf_msg(LVL_DEBUG, "DRQ == 0");
 			break;
 		}
@@ -644,8 +765,12 @@
 	}
 
-	if ((status & SR_ERR) != 0)
-		return EIO;
-	if (nblocks > 0)
-		return EIO;
+	if ((status & SR_ERR) != 0) {
+		ddf_msg(LVL_DEBUG, "status & SR_ERR != 0");
+		return EIO;
+	}
+	if (nblocks > 0) {
+		ddf_msg(LVL_DEBUG, "remaining nblocks = %zu", nblocks);
+		return EIO;
+	}
 
 	return EOK;
@@ -660,15 +785,19 @@
 	size_t bidx;
 	uint8_t status;
+	errno_t rc;
 
 	assert(nblocks > 0);
 	assert(blk_size % 2 == 0);
 
+	rc = wait_status(ctrl, 0, ~SR_BSY, &status, TIMEOUT_BSY);
+	if (rc != EOK)
+		return EIO;
+
 	bidx = 0;
 	while (nblocks > 0) {
-		if (wait_status(ctrl, 0, ~SR_BSY, &status, TIMEOUT_BSY) != EOK)
-			return EIO;
-
-		if ((status & SR_DRQ) == 0)
+		if ((status & SR_DRQ) == 0) {
+			ddf_msg(LVL_DEBUG, "pio_data_out: unexpected DRQ=0");
 			break;
+		}
 
 		/* Write data to the device buffer. */
@@ -678,4 +807,11 @@
 		}
 
+		if (ctrl->irq >= 0)
+			rc = wait_irq(ctrl, &status, TIMEOUT_BSY);
+		else
+			rc = wait_status(ctrl, 0, ~SR_BSY, &status, TIMEOUT_BSY);
+		if (rc != EOK)
+			return EIO;
+
 		--nblocks;
 	}
@@ -694,6 +830,12 @@
 	ata_ctrl_t *ctrl = disk->ctrl;
 	uint8_t status;
-
-	if (wait_status(ctrl, 0, ~SR_BSY, &status, TIMEOUT_BSY) != EOK)
+	errno_t rc;
+
+	if (ctrl->irq >= 0)
+		rc = wait_irq(ctrl, &status, TIMEOUT_BSY);
+	else
+		rc = wait_status(ctrl, 0, ~SR_BSY, &status, TIMEOUT_BSY);
+
+	if (rc != EOK)
 		return EIO;
 
@@ -738,20 +880,14 @@
 	pio_write_8(&ctrl->cmd->command, CMD_IDENTIFY_DRIVE);
 
-	if (wait_status(ctrl, 0, ~SR_BSY, &status, TIMEOUT_PROBE) != EOK)
-		return ETIMEOUT;
-
-	/*
-	 * If ERR is set, this may be a packet device, so return EIO to cause
-	 * the caller to check for one.
-	 */
-	if ((status & SR_ERR) != 0)
-		return EIO;
-
 	/*
 	 * For probing purposes we need to wait for some status bit to become
 	 * active - otherwise we could be fooled just by receiving all zeroes.
 	 */
-	if (wait_status(ctrl, SR_DRQ, ~SR_BSY, &status, TIMEOUT_PROBE) != EOK)
-		return ETIMEOUT;
+	if (wait_status(ctrl, SR_DRQ, ~SR_BSY, &status, TIMEOUT_PROBE) != EOK) {
+		if ((status & SR_ERR) == 0) {
+			/* Probably no device at all */
+			return ETIMEOUT;
+		}
+	}
 
 	return ata_pio_data_in(disk, buf, identify_data_size,
@@ -811,4 +947,5 @@
 	size_t bidx;
 	uint16_t val;
+	errno_t rc;
 
 	fibril_mutex_lock(&ctrl->lock);
@@ -848,5 +985,10 @@
 	remain = obuf_size;
 	while (remain > 0) {
-		if (wait_status(ctrl, 0, ~SR_BSY, &status, TIMEOUT_BSY) != EOK) {
+		if (ctrl->irq >= 0)
+			rc = wait_irq(ctrl, &status, TIMEOUT_BSY);
+		else
+			rc = wait_status(ctrl, 0, ~SR_BSY, &status, TIMEOUT_BSY);
+
+		if (rc != EOK) {
 			fibril_mutex_unlock(&ctrl->lock);
 			return EIO;
@@ -875,4 +1017,9 @@
 		remain -= data_size;
 	}
+
+	if (ctrl->irq >= 0)
+		rc = wait_irq(ctrl, &status, TIMEOUT_BSY);
+	else
+		rc = wait_status(ctrl, 0, ~SR_BSY, &status, TIMEOUT_BSY);
 
 	fibril_mutex_unlock(&ctrl->lock);
@@ -1047,6 +1194,8 @@
 
 	/* Compute block coordinates. */
-	if (coord_calc(disk, ba, &bc) != EOK)
+	if (coord_calc(disk, ba, &bc) != EOK) {
+		ddf_msg(LVL_NOTE, "ata_rcmd_read() -> coord_calc failed");
 		return EINVAL;
+	}
 
 	/* New value for Drive/Head register */
@@ -1062,4 +1211,5 @@
 	if (wait_status(ctrl, 0, ~SR_BSY, NULL, TIMEOUT_BSY) != EOK) {
 		fibril_mutex_unlock(&ctrl->lock);
+		ddf_msg(LVL_NOTE, "ata_rcmd_read() -> wait_status failed");
 		return EIO;
 	}
@@ -1069,4 +1219,5 @@
 	if (wait_status(ctrl, SR_DRDY, ~SR_BSY, NULL, TIMEOUT_DRDY) != EOK) {
 		fibril_mutex_unlock(&ctrl->lock);
+		ddf_msg(LVL_NOTE, "ata_rcmd_read() -> wait_status 2 failed");
 		return EIO;
 	}
@@ -1083,4 +1234,6 @@
 	fibril_mutex_unlock(&ctrl->lock);
 
+	if (rc != EOK)
+		ddf_msg(LVL_NOTE, "ata_rcmd_read() -> pio_data_in->%d", rc);
 	return rc;
 }
@@ -1355,4 +1508,47 @@
 }
 
+/** Wait for IRQ and return status.
+ *
+ * @param ctrl		Controller
+ * @param pstatus	Pointer where to store last read status or NULL.
+ * @param timeout	Timeout in 10ms units.
+ *
+ * @return		EOK on success, EIO on timeout.
+ */
+static errno_t wait_irq(ata_ctrl_t *ctrl, uint8_t *pstatus, unsigned timeout)
+{
+	fibril_mutex_lock(&ctrl->irq_lock);
+	while (!ctrl->irq_fired)
+		fibril_condvar_wait(&ctrl->irq_cv, &ctrl->irq_lock);
+
+	ctrl->irq_fired = false;
+	*pstatus = ctrl->irq_status;
+	fibril_mutex_unlock(&ctrl->irq_lock);
+	return EOK;
+}
+
+/** Interrupt handler.
+ *
+ * @param call Call data
+ * @param dev Device that caused the interrupt
+ */
+static void ata_irq_handler(ipc_call_t *call, ddf_dev_t *dev)
+{
+	ata_ctrl_t *ctrl = (ata_ctrl_t *)ddf_dev_data_get(dev);
+	uint8_t status;
+	async_sess_t *parent_sess;
+
+	status = ipc_get_arg1(call);
+
+	fibril_mutex_lock(&ctrl->irq_lock);
+	ctrl->irq_fired = true;
+	ctrl->irq_status = status;
+	fibril_mutex_unlock(&ctrl->irq_lock);
+	fibril_condvar_broadcast(&ctrl->irq_cv);
+
+	parent_sess = ddf_dev_parent_sess_get(dev);
+	hw_res_clear_interrupt(parent_sess, ctrl->irq);
+}
+
 /**
  * @}
Index: uspace/drv/block/ata_bd/ata_bd.h
===================================================================
--- uspace/drv/block/ata_bd/ata_bd.h	(revision 21989e5a1d36e65bebb408c0ca4bdb7b3590fe28)
+++ uspace/drv/block/ata_bd/ata_bd.h	(revision 64cf7a3bd1e3e66b8aa3b82148faced27efb35bb)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2009 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -40,4 +40,5 @@
 #include <ddf/driver.h>
 #include <fibril_synch.h>
+#include <stdbool.h>
 #include <str.h>
 #include <stdint.h>
@@ -47,9 +48,10 @@
 #define NAME "ata_bd"
 
-/** Base addresses for ATA I/O blocks. */
+/** ATA hardware resources */
 typedef struct {
 	uintptr_t cmd;	/**< Command block base address. */
 	uintptr_t ctl;	/**< Control block base address. */
-} ata_base_t;
+	int irq;	/**< IRQ */
+} ata_hwres_t;
 
 /** Timeout definitions. Unit is 10 ms. */
@@ -140,9 +142,22 @@
 	/** Control registers */
 	ata_ctl_t *ctl;
+	/** IRQ (-1 if not used) */
+	int irq;
+	/** IRQ handle */
+	cap_irq_handle_t ihandle;
 
 	/** Per-disk state. */
 	disk_t disk[MAX_DISKS];
 
+	/** Synchronize controller access */
 	fibril_mutex_t lock;
+	/** Synchronize access to irq_fired/irq_status */
+	fibril_mutex_t irq_lock;
+	/** Signalled by IRQ handler */
+	fibril_condvar_t irq_cv;
+	/** Set to true when interrupt occurs */
+	bool irq_fired;
+	/** Value of status register read by interrupt handler */
+	uint8_t irq_status;
 } ata_ctrl_t;
 
@@ -153,5 +168,5 @@
 } ata_fun_t;
 
-extern errno_t ata_ctrl_init(ata_ctrl_t *, ata_base_t *);
+extern errno_t ata_ctrl_init(ata_ctrl_t *, ata_hwres_t *);
 extern errno_t ata_ctrl_remove(ata_ctrl_t *);
 extern errno_t ata_ctrl_gone(ata_ctrl_t *);
Index: uspace/drv/block/ata_bd/main.c
===================================================================
--- uspace/drv/block/ata_bd/main.c	(revision 21989e5a1d36e65bebb408c0ca4bdb7b3590fe28)
+++ uspace/drv/block/ata_bd/main.c	(revision 64cf7a3bd1e3e66b8aa3b82148faced27efb35bb)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2013 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -66,5 +66,5 @@
 };
 
-static errno_t ata_get_res(ddf_dev_t *dev, ata_base_t *ata_res)
+static errno_t ata_get_res(ddf_dev_t *dev, ata_hwres_t *ata_res)
 {
 	async_sess_t *parent_sess;
@@ -86,4 +86,6 @@
 	}
 
+	/* I/O ranges */
+
 	addr_range_t *cmd_rng = &hw_res.io_ranges.ranges[0];
 	addr_range_t *ctl_rng = &hw_res.io_ranges.ranges[1];
@@ -101,4 +103,11 @@
 	}
 
+	/* IRQ */
+	if (hw_res.irqs.count > 0) {
+		ata_res->irq = hw_res.irqs.irqs[0];
+	} else {
+		ata_res->irq = -1;
+	}
+
 	return EOK;
 error:
@@ -115,5 +124,5 @@
 {
 	ata_ctrl_t *ctrl;
-	ata_base_t res;
+	ata_hwres_t res;
 	errno_t rc;
 
