Index: uspace/drv/block/ata_bd/ata_bd.c
===================================================================
--- uspace/drv/block/ata_bd/ata_bd.c	(revision 64cf7a3bd1e3e66b8aa3b82148faced27efb35bb)
+++ uspace/drv/block/ata_bd/ata_bd.c	(revision 2791fbb7c1f204fd66ed105c6cc1ddf9e8f5bedf)
@@ -33,17 +33,7 @@
 /**
  * @file
- * @brief ATA disk driver
- *
- * This driver supports CHS, 28-bit and 48-bit LBA addressing, as well as
- * PACKET devices. It only uses PIO transfers. There is no support DMA
- * or any other fancy features such as S.M.A.R.T, removable devices, etc.
- *
- * This driver is based on the ATA-1, ATA-2, ATA-3 and ATA/ATAPI-4 through 7
- * standards, as published by the ANSI, NCITS and INCITS standards bodies,
- * which are freely available. This driver contains no vendor-specific
- * code at this moment.
- *
- * The driver services a single controller which can have up to two disks
- * attached.
+ * @brief ISA ATA driver
+ *
+ * The driver services a single IDE channel.
  */
 
@@ -53,9 +43,5 @@
 #include <async.h>
 #include <as.h>
-#include <bd_srv.h>
 #include <fibril_synch.h>
-#include <scsi/mmc.h>
-#include <scsi/sbc.h>
-#include <scsi/spc.h>
 #include <stdint.h>
 #include <stdbool.h>
@@ -65,18 +51,9 @@
 #include <inttypes.h>
 #include <errno.h>
-#include <byteorder.h>
-#include <macros.h>
-
-#include "ata_hw.h"
+
 #include "ata_bd.h"
 #include "main.h"
 
 #define NAME       "ata_bd"
-
-/**
- * Size of data returned from Identify Device or Identify Packet Device
- * command.
- */
-static const size_t identify_data_size = 512;
 
 static errno_t ata_bd_init_io(ata_ctrl_t *ctrl);
@@ -84,54 +61,20 @@
 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 *);
-static errno_t ata_bd_close(bd_srv_t *);
-static errno_t ata_bd_read_blocks(bd_srv_t *, uint64_t ba, size_t cnt, void *buf,
-    size_t);
-static errno_t ata_bd_read_toc(bd_srv_t *, uint8_t session, void *buf, size_t);
-static errno_t ata_bd_write_blocks(bd_srv_t *, uint64_t ba, size_t cnt,
-    const void *buf, size_t);
-static errno_t ata_bd_get_block_size(bd_srv_t *, size_t *);
-static errno_t ata_bd_get_num_blocks(bd_srv_t *, aoff64_t *);
-static errno_t ata_bd_sync_cache(bd_srv_t *, aoff64_t, size_t);
-
-static errno_t ata_rcmd_read(disk_t *disk, uint64_t ba, size_t cnt,
-    void *buf);
-static errno_t ata_rcmd_write(disk_t *disk, uint64_t ba, size_t cnt,
-    const void *buf);
-static errno_t ata_rcmd_flush_cache(disk_t *disk);
-static errno_t disk_init(ata_ctrl_t *ctrl, disk_t *d, int disk_id);
-static errno_t ata_identify_dev(disk_t *disk, void *buf);
-static errno_t ata_identify_pkt_dev(disk_t *disk, void *buf);
-static errno_t ata_cmd_packet(disk_t *disk, const void *cpkt, size_t cpkt_size,
-    void *obuf, size_t obuf_size, size_t *rcvd_size);
-static errno_t ata_pcmd_inquiry(disk_t *disk, void *obuf, size_t obuf_size,
-    size_t *rcvd_size);
-static errno_t ata_pcmd_read_12(disk_t *disk, uint64_t ba, size_t cnt,
-    void *obuf, size_t obuf_size);
-static errno_t ata_pcmd_read_capacity(disk_t *disk, uint64_t *nblocks,
-    size_t *block_size);
-static errno_t ata_pcmd_read_toc(disk_t *disk, uint8_t ses,
-    void *obuf, size_t obuf_size);
-static void disk_print_summary(disk_t *d);
-static size_t ata_disk_maxnb(disk_t *d);
-static errno_t coord_calc(disk_t *d, uint64_t ba, block_coord_t *bc);
-static void coord_sc_program(ata_ctrl_t *ctrl, const block_coord_t *bc,
-    uint16_t scnt);
-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 = {
-	.open = ata_bd_open,
-	.close = ata_bd_close,
-	.read_blocks = ata_bd_read_blocks,
-	.read_toc = ata_bd_read_toc,
-	.write_blocks = ata_bd_write_blocks,
-	.get_block_size = ata_bd_get_block_size,
-	.get_num_blocks = ata_bd_get_num_blocks,
-	.sync_cache = ata_bd_sync_cache
-};
+static void ata_write_data_16(void *, uint16_t *, size_t);
+static void ata_read_data_16(void *, uint16_t *, size_t);
+static void ata_write_cmd_8(void *, uint16_t, uint8_t);
+static uint8_t ata_read_cmd_8(void *, uint16_t);
+static void ata_write_ctl_8(void *, uint16_t, uint8_t);
+static uint8_t ata_read_ctl_8(void *, uint16_t);
+static errno_t ata_irq_enable(void *);
+static errno_t ata_irq_disable(void *);
+static errno_t ata_add_device(void *, unsigned, void *);
+static errno_t ata_remove_device(void *, unsigned);
+static void ata_msg_debug(void *, char *);
+static void ata_msg_note(void *, char *);
+static void ata_msg_warn(void *, char *);
+static void ata_msg_error(void *, char *);
 
 static const irq_pio_range_t ata_irq_ranges[] = {
@@ -154,27 +97,14 @@
 };
 
-static disk_t *bd_srv_disk(bd_srv_t *bd)
-{
-	return (disk_t *)bd->srvs->sarg;
-}
-
-static int disk_dev_idx(disk_t *disk)
-{
-	return (disk->disk_id & 1);
-}
-
 /** Initialize ATA controller. */
 errno_t ata_ctrl_init(ata_ctrl_t *ctrl, ata_hwres_t *res)
 {
-	int i;
-	errno_t rc;
-	int n_disks;
+	errno_t rc;
 	bool irq_inited = false;
+	ata_params_t params;
 
 	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;
@@ -184,56 +114,48 @@
 	    (void *) ctrl->ctl_physical);
 
+	ddf_msg(LVL_DEBUG, "Init I/O");
 	rc = ata_bd_init_io(ctrl);
 	if (rc != EOK)
 		return rc;
 
+	ddf_msg(LVL_DEBUG, "Init IRQ");
 	rc = ata_bd_init_irq(ctrl);
+	if (rc != EOK) {
+		ddf_msg(LVL_NOTE, "init IRQ failed");
+		return rc;
+	}
+
+	irq_inited = true;
+
+	ddf_msg(LVL_DEBUG, "ata_ctrl_init(): Initialize ATA channel");
+
+	params.arg = (void *)ctrl;
+	params.have_irq = (ctrl->irq >= 0) ? true : false;
+	params.write_data_16 = ata_write_data_16;
+	params.read_data_16 = ata_read_data_16;
+	params.write_cmd_8 = ata_write_cmd_8;
+	params.read_cmd_8 = ata_read_cmd_8;
+	params.write_ctl_8 = ata_write_ctl_8;
+	params.read_ctl_8 = ata_read_ctl_8;
+	params.irq_enable = ata_irq_enable;
+	params.irq_disable = ata_irq_disable;
+	params.add_device = ata_add_device;
+	params.remove_device = ata_remove_device;
+	params.msg_debug = ata_msg_debug;
+	params.msg_note = ata_msg_note;
+	params.msg_warn = ata_msg_warn;
+	params.msg_error = ata_msg_error;
+
+	rc = ata_channel_create(&params, &ctrl->channel);
 	if (rc != EOK)
-		return rc;
-
-	irq_inited = true;
-
-	for (i = 0; i < MAX_DISKS; i++) {
-		ddf_msg(LVL_DEBUG, "Identify drive %d...", i);
-
-		rc = disk_init(ctrl, &ctrl->disk[i], i);
-
-		if (rc == EOK) {
-			disk_print_summary(&ctrl->disk[i]);
-		} else {
-			ddf_msg(LVL_DEBUG, "Not found.");
-		}
-	}
-
-	n_disks = 0;
-
-	for (i = 0; i < MAX_DISKS; i++) {
-		/* Skip unattached drives. */
-		if (ctrl->disk[i].present == false)
-			continue;
-
-		rc = ata_fun_create(&ctrl->disk[i]);
-		if (rc != EOK) {
-			ddf_msg(LVL_ERROR, "Unable to create function for "
-			    "disk %d.", i);
-			goto error;
-		}
-		++n_disks;
-	}
-
-	if (n_disks == 0) {
-		ddf_msg(LVL_WARN, "No disks detected.");
-		rc = ENOENT;
 		goto error;
-	}
-
+
+	rc = ata_channel_initialize(ctrl->channel);
+	if (rc != EOK)
+		goto error;
+
+	ddf_msg(LVL_DEBUG, "ata_ctrl_init: DONE");
 	return EOK;
 error:
-	for (i = 0; i < MAX_DISKS; i++) {
-		if (ata_fun_remove(&ctrl->disk[i]) != EOK) {
-			ddf_msg(LVL_ERROR, "Unable to clean up function for "
-			    "disk %d.", i);
-		}
-	}
 	if (irq_inited)
 		ata_bd_fini_irq(ctrl);
@@ -245,5 +167,4 @@
 errno_t ata_ctrl_remove(ata_ctrl_t *ctrl)
 {
-	int i;
 	errno_t rc;
 
@@ -252,11 +173,8 @@
 	fibril_mutex_lock(&ctrl->lock);
 
-	for (i = 0; i < MAX_DISKS; i++) {
-		rc = ata_fun_remove(&ctrl->disk[i]);
-		if (rc != EOK) {
-			ddf_msg(LVL_ERROR, "Unable to clean up function for "
-			    "disk %d.", i);
-			return rc;
-		}
+	rc = ata_channel_destroy(ctrl->channel);
+	if (rc != EOK) {
+		fibril_mutex_unlock(&ctrl->lock);
+		return rc;
 	}
 
@@ -271,5 +189,4 @@
 errno_t ata_ctrl_gone(ata_ctrl_t *ctrl)
 {
-	int i;
 	errno_t rc;
 
@@ -278,11 +195,8 @@
 	fibril_mutex_lock(&ctrl->lock);
 
-	for (i = 0; i < MAX_DISKS; i++) {
-		rc = ata_fun_unbind(&ctrl->disk[i]);
-		if (rc != EOK) {
-			ddf_msg(LVL_ERROR, "Unable to clean up function for "
-			    "disk %d.", i);
-			return rc;
-		}
+	rc = ata_channel_destroy(ctrl->channel);
+	if (rc != EOK) {
+		fibril_mutex_unlock(&ctrl->lock);
+		return rc;
 	}
 
@@ -293,53 +207,4 @@
 }
 
-/** Print one-line device summary. */
-static void disk_print_summary(disk_t *d)
-{
-	uint64_t mbytes;
-	char *atype = NULL;
-	char *cap = NULL;
-	int rc;
-
-	if (d->dev_type == ata_reg_dev) {
-		switch (d->amode) {
-		case am_chs:
-			rc = asprintf(&atype, "CHS %u cylinders, %u heads, "
-			    "%u sectors", d->geom.cylinders, d->geom.heads,
-			    d->geom.sectors);
-			if (rc < 0) {
-				/* Out of memory */
-				atype = NULL;
-			}
-			break;
-		case am_lba28:
-			atype = str_dup("LBA-28");
-			break;
-		case am_lba48:
-			atype = str_dup("LBA-48");
-			break;
-		}
-	} else {
-		atype = str_dup("PACKET");
-	}
-
-	if (atype == NULL)
-		return;
-
-	mbytes = d->blocks / (2 * 1024);
-	if (mbytes > 0) {
-		rc = asprintf(&cap, " %" PRIu64 " MB.", mbytes);
-		if (rc < 0) {
-			cap = NULL;
-			goto cleanup;
-		}
-	}
-
-	ddf_msg(LVL_NOTE, "%s: %s %" PRIu64 " blocks%s", d->model, atype,
-	    d->blocks, cap);
-cleanup:
-	free(atype);
-	free(cap);
-}
-
 /** Enable device I/O. */
 static errno_t ata_bd_init_io(ata_ctrl_t *ctrl)
@@ -377,5 +242,4 @@
 {
 	irq_code_t irq_code;
-	async_sess_t *parent_sess;
 	irq_pio_range_t *ranges;
 	irq_cmd_t *cmds;
@@ -413,14 +277,5 @@
 	}
 
-	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;
-	}
-
+	ddf_msg(LVL_DEBUG, "Interrupt handler registered");
 	free(ranges);
 	free(cmds);
@@ -447,1085 +302,4 @@
 }
 
-/** Initialize a disk.
- *
- * Probes for a disk, determines its parameters and initializes
- * the disk structure.
- */
-static errno_t disk_init(ata_ctrl_t *ctrl, disk_t *d, int disk_id)
-{
-	identify_data_t idata;
-	uint8_t model[40];
-	scsi_std_inquiry_data_t inq_data;
-	size_t isize;
-	uint16_t w;
-	uint8_t c;
-	uint16_t bc;
-	uint64_t nblocks;
-	size_t block_size;
-	size_t pos, len;
-	errno_t rc;
-	unsigned i;
-
-	d->ctrl = ctrl;
-	d->disk_id = disk_id;
-	d->present = false;
-	d->afun = NULL;
-
-	/* Try identify command. */
-	rc = ata_identify_dev(d, &idata);
-	if (rc == EOK) {
-		/* Success. It's a register (non-packet) device. */
-		ddf_msg(LVL_DEBUG, "ATA register-only device found.");
-		d->dev_type = ata_reg_dev;
-	} else if (rc == EIO) {
-		/*
-		 * There is something, but not a register device. Check to see
-		 * whether the IDENTIFY command left the packet signature in
-		 * the registers in case this is a packet device.
-		 *
-		 * According to the ATA specification, the LBA low and
-		 * interrupt reason registers should be set to 0x01. However,
-		 * there are many devices that do not follow this and only set
-		 * the byte count registers. So, only check these.
-		 */
-		bc = ((uint16_t)pio_read_8(&ctrl->cmd->cylinder_high) << 8) |
-		    pio_read_8(&ctrl->cmd->cylinder_low);
-
-		if (bc == PDEV_SIGNATURE_BC) {
-			rc = ata_identify_pkt_dev(d, &idata);
-			if (rc == EOK) {
-				/* We have a packet device. */
-				d->dev_type = ata_pkt_dev;
-			} else {
-				return EIO;
-			}
-		} else {
-			/* Nope. Something's there, but not recognized. */
-			return EIO;
-		}
-	} else {
-		/* Operation timed out. That means there is no device there. */
-		return EIO;
-	}
-
-	if (d->dev_type == ata_pkt_dev) {
-		/* Packet device */
-		d->amode = 0;
-
-		d->geom.cylinders = 0;
-		d->geom.heads = 0;
-		d->geom.sectors = 0;
-
-		d->blocks = 0;
-	} else if ((idata.caps & rd_cap_lba) == 0) {
-		/* Device only supports CHS addressing. */
-		d->amode = am_chs;
-
-		d->geom.cylinders = idata.cylinders;
-		d->geom.heads = idata.heads;
-		d->geom.sectors = idata.sectors;
-
-		d->blocks = d->geom.cylinders * d->geom.heads * d->geom.sectors;
-	} else if ((idata.cmd_set1 & cs1_addr48) == 0) {
-		/* Device only supports LBA-28 addressing. */
-		d->amode = am_lba28;
-
-		d->geom.cylinders = 0;
-		d->geom.heads = 0;
-		d->geom.sectors = 0;
-
-		d->blocks =
-		    (uint32_t) idata.total_lba28_0 |
-		    ((uint32_t) idata.total_lba28_1 << 16);
-	} else {
-		/* Device supports LBA-48 addressing. */
-		d->amode = am_lba48;
-
-		d->geom.cylinders = 0;
-		d->geom.heads = 0;
-		d->geom.sectors = 0;
-
-		d->blocks =
-		    (uint64_t) idata.total_lba48_0 |
-		    ((uint64_t) idata.total_lba48_1 << 16) |
-		    ((uint64_t) idata.total_lba48_2 << 32) |
-		    ((uint64_t) idata.total_lba48_3 << 48);
-	}
-
-	/*
-	 * Convert model name to string representation.
-	 */
-	for (i = 0; i < 20; i++) {
-		w = idata.model_name[i];
-		model[2 * i] = w >> 8;
-		model[2 * i + 1] = w & 0x00ff;
-	}
-
-	len = 40;
-	while (len > 0 && model[len - 1] == 0x20)
-		--len;
-
-	pos = 0;
-	for (i = 0; i < len; ++i) {
-		c = model[i];
-		if (c >= 0x80)
-			c = '?';
-
-		chr_encode(c, d->model, &pos, 40);
-	}
-	d->model[pos] = '\0';
-
-	if (d->dev_type == ata_pkt_dev) {
-		/* Send inquiry. */
-		rc = ata_pcmd_inquiry(d, &inq_data, sizeof(inq_data), &isize);
-		if (rc != EOK || isize < sizeof(inq_data)) {
-			ddf_msg(LVL_ERROR, "Device inquiry failed.");
-			d->present = false;
-			return EIO;
-		}
-
-		/* Check device type. */
-		if (INQUIRY_PDEV_TYPE(inq_data.pqual_devtype) != SCSI_DEV_CD_DVD)
-			ddf_msg(LVL_WARN, "Peripheral device type is not CD-ROM.");
-
-		rc = ata_pcmd_read_capacity(d, &nblocks, &block_size);
-		if (rc != EOK) {
-			ddf_msg(LVL_ERROR, "Read capacity command failed.");
-			d->present = false;
-			return EIO;
-		}
-
-		d->blocks = nblocks;
-		d->block_size = block_size;
-	} else {
-		/* Assume register Read always uses 512-byte blocks. */
-		d->block_size = 512;
-	}
-
-	d->present = true;
-	return EOK;
-}
-
-static errno_t ata_bd_open(bd_srvs_t *bds, bd_srv_t *bd)
-{
-	return EOK;
-}
-
-static errno_t ata_bd_close(bd_srv_t *bd)
-{
-	return EOK;
-}
-
-/** Read multiple blocks from the device. */
-static errno_t ata_bd_read_blocks(bd_srv_t *bd, uint64_t ba, size_t cnt,
-    void *buf, size_t size)
-{
-	disk_t *disk = bd_srv_disk(bd);
-	size_t maxnb;
-	size_t nb;
-	errno_t rc;
-
-	if (size < cnt * disk->block_size) {
-		rc = EINVAL;
-		goto error;
-	}
-
-	/* Maximum number of blocks to transfer at the same time */
-	maxnb = ata_disk_maxnb(disk);
-	while (cnt > 0) {
-		nb = min(maxnb, cnt);
-		if (disk->dev_type == ata_reg_dev) {
-			rc = ata_rcmd_read(disk, ba, nb, buf);
-		} else {
-			rc = ata_pcmd_read_12(disk, ba, nb, buf,
-			    disk->block_size);
-		}
-
-		if (rc != EOK)
-			goto error;
-
-		ba += nb;
-		cnt -= nb;
-		buf += disk->block_size * nb;
-	}
-
-	return EOK;
-error:
-	ddf_msg(LVL_DEBUG, "ata_bd_read_blocks: rc=%d", rc);
-	return rc;
-}
-
-/** Read TOC from device. */
-static errno_t ata_bd_read_toc(bd_srv_t *bd, uint8_t session, void *buf, size_t size)
-{
-	disk_t *disk = bd_srv_disk(bd);
-
-	return ata_pcmd_read_toc(disk, session, buf, size);
-}
-
-/** Write multiple blocks to the device. */
-static errno_t ata_bd_write_blocks(bd_srv_t *bd, uint64_t ba, size_t cnt,
-    const void *buf, size_t size)
-{
-	disk_t *disk = bd_srv_disk(bd);
-	size_t maxnb;
-	size_t nb;
-	errno_t rc;
-
-	if (disk->dev_type != ata_reg_dev)
-		return ENOTSUP;
-
-	if (size < cnt * disk->block_size)
-		return EINVAL;
-
-	/* Maximum number of blocks to transfer at the same time */
-	maxnb = ata_disk_maxnb(disk);
-	while (cnt > 0) {
-		nb = min(maxnb, cnt);
-		rc = ata_rcmd_write(disk, ba, nb, buf);
-		if (rc != EOK)
-			return rc;
-
-		ba += nb;
-		cnt -= nb;
-		buf += disk->block_size * nb;
-	}
-
-	return EOK;
-}
-
-/** Get device block size. */
-static errno_t ata_bd_get_block_size(bd_srv_t *bd, size_t *rbsize)
-{
-	disk_t *disk = bd_srv_disk(bd);
-
-	*rbsize = disk->block_size;
-	return EOK;
-}
-
-/** Get device number of blocks. */
-static errno_t ata_bd_get_num_blocks(bd_srv_t *bd, aoff64_t *rnb)
-{
-	disk_t *disk = bd_srv_disk(bd);
-
-	*rnb = disk->blocks;
-	return EOK;
-}
-
-/** Flush cache. */
-static errno_t ata_bd_sync_cache(bd_srv_t *bd, uint64_t ba, size_t cnt)
-{
-	disk_t *disk = bd_srv_disk(bd);
-
-	/* ATA cannot flush just some blocks, we just flush everything. */
-	(void)ba;
-	(void)cnt;
-
-	return ata_rcmd_flush_cache(disk);
-}
-
-/** PIO data-in command protocol. */
-static errno_t ata_pio_data_in(disk_t *disk, void *obuf, size_t obuf_size,
-    size_t blk_size, size_t nblocks)
-{
-	ata_ctrl_t *ctrl = disk->ctrl;
-	uint16_t data;
-	size_t i;
-	size_t bidx;
-	uint8_t status;
-	errno_t rc;
-
-	assert(nblocks > 0);
-	assert(blk_size % 2 == 0);
-
-	bidx = 0;
-	while (nblocks > 0) {
-		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;
-		}
-
-		/* Read data from the device buffer. */
-		for (i = 0; i < blk_size / 2; i++) {
-			data = pio_read_16(&ctrl->cmd->data_port);
-			((uint16_t *) obuf)[bidx++] = data;
-		}
-
-		--nblocks;
-	}
-
-	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;
-}
-
-/** PIO data-out command protocol. */
-static errno_t ata_pio_data_out(disk_t *disk, const void *buf, size_t buf_size,
-    size_t blk_size, size_t nblocks)
-{
-	ata_ctrl_t *ctrl = disk->ctrl;
-	size_t i;
-	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 ((status & SR_DRQ) == 0) {
-			ddf_msg(LVL_DEBUG, "pio_data_out: unexpected DRQ=0");
-			break;
-		}
-
-		/* Write data to the device buffer. */
-		for (i = 0; i < blk_size / 2; i++) {
-			pio_write_16(&ctrl->cmd->data_port,
-			    ((uint16_t *) buf)[bidx++]);
-		}
-
-		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;
-	}
-
-	if (status & SR_ERR)
-		return EIO;
-	if (nblocks > 0)
-		return EIO;
-
-	return EOK;
-}
-
-/** PIO non-data command protocol. */
-static errno_t ata_pio_nondata(disk_t *disk)
-{
-	ata_ctrl_t *ctrl = disk->ctrl;
-	uint8_t status;
-	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;
-
-	if (status & SR_ERR)
-		return EIO;
-
-	return EOK;
-}
-
-/** Issue IDENTIFY DEVICE command.
- *
- * Reads @c identify data into the provided buffer. This is used to detect
- * whether an ATA device is present and if so, to determine its parameters.
- *
- * @param disk		Disk
- * @param buf		Pointer to a 512-byte buffer.
- *
- * @return		ETIMEOUT on timeout (this can mean the device is
- *			not present). EIO if device responds with error.
- */
-static errno_t ata_identify_dev(disk_t *disk, void *buf)
-{
-	ata_ctrl_t *ctrl = disk->ctrl;
-	uint8_t status;
-	uint8_t drv_head;
-
-	drv_head = ((disk_dev_idx(disk) != 0) ? DHR_DRV : 0);
-
-	if (wait_status(ctrl, 0, ~SR_BSY, NULL, TIMEOUT_PROBE) != EOK)
-		return ETIMEOUT;
-
-	pio_write_8(&ctrl->cmd->drive_head, drv_head);
-
-	/*
-	 * Do not wait for DRDY to be set in case this is a packet device.
-	 * We determine whether the device is present by waiting for DRQ to be
-	 * set after issuing the command.
-	 */
-	if (wait_status(ctrl, 0, ~SR_BSY, NULL, TIMEOUT_PROBE) != EOK)
-		return ETIMEOUT;
-
-	pio_write_8(&ctrl->cmd->command, CMD_IDENTIFY_DRIVE);
-
-	/*
-	 * 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) {
-		if ((status & SR_ERR) == 0) {
-			/* Probably no device at all */
-			return ETIMEOUT;
-		}
-	}
-
-	return ata_pio_data_in(disk, buf, identify_data_size,
-	    identify_data_size, 1);
-}
-
-/** Issue Identify Packet Device command.
- *
- * Reads @c identify data into the provided buffer. This is used to detect
- * whether an ATAPI device is present and if so, to determine its parameters.
- *
- * @param disk		Disk
- * @param buf		Pointer to a 512-byte buffer.
- */
-static errno_t ata_identify_pkt_dev(disk_t *disk, void *buf)
-{
-	ata_ctrl_t *ctrl = disk->ctrl;
-	uint8_t drv_head;
-
-	drv_head = ((disk_dev_idx(disk) != 0) ? DHR_DRV : 0);
-
-	if (wait_status(ctrl, 0, ~SR_BSY, NULL, TIMEOUT_PROBE) != EOK)
-		return EIO;
-
-	pio_write_8(&ctrl->cmd->drive_head, drv_head);
-
-	/* For ATAPI commands we do not need to wait for DRDY. */
-	if (wait_status(ctrl, 0, ~SR_BSY, NULL, TIMEOUT_PROBE) != EOK)
-		return EIO;
-
-	pio_write_8(&ctrl->cmd->command, CMD_IDENTIFY_PKT_DEV);
-
-	return ata_pio_data_in(disk, buf, identify_data_size,
-	    identify_data_size, 1);
-}
-
-/** Issue packet command (i. e. write a command packet to the device).
- *
- * Only data-in commands are supported (e.g. inquiry, read).
- *
- * @param disk		Disk
- * @param obuf		Buffer for storing data read from device
- * @param obuf_size	Size of obuf in bytes
- * @param rcvd_size	Place to store number of bytes read or @c NULL
- *
- * @return EOK on success, EIO on error.
- */
-static errno_t ata_cmd_packet(disk_t *disk, const void *cpkt, size_t cpkt_size,
-    void *obuf, size_t obuf_size, size_t *rcvd_size)
-{
-	ata_ctrl_t *ctrl = disk->ctrl;
-	size_t i;
-	uint8_t status;
-	uint8_t drv_head;
-	size_t data_size;
-	size_t remain;
-	size_t bidx;
-	uint16_t val;
-	errno_t rc;
-
-	fibril_mutex_lock(&ctrl->lock);
-
-	/* New value for Drive/Head register */
-	drv_head =
-	    ((disk_dev_idx(disk) != 0) ? DHR_DRV : 0);
-
-	if (wait_status(ctrl, 0, ~SR_BSY, NULL, TIMEOUT_PROBE) != EOK) {
-		fibril_mutex_unlock(&ctrl->lock);
-		return EIO;
-	}
-
-	pio_write_8(&ctrl->cmd->drive_head, drv_head);
-
-	if (wait_status(ctrl, 0, ~(SR_BSY | SR_DRQ), NULL, TIMEOUT_BSY) != EOK) {
-		fibril_mutex_unlock(&ctrl->lock);
-		return EIO;
-	}
-
-	/* Byte count <- max. number of bytes we can read in one transfer. */
-	pio_write_8(&ctrl->cmd->cylinder_low, 0xfe);
-	pio_write_8(&ctrl->cmd->cylinder_high, 0xff);
-
-	pio_write_8(&ctrl->cmd->command, CMD_PACKET);
-
-	if (wait_status(ctrl, SR_DRQ, ~SR_BSY, &status, TIMEOUT_BSY) != EOK) {
-		fibril_mutex_unlock(&ctrl->lock);
-		return EIO;
-	}
-
-	/* Write command packet. */
-	for (i = 0; i < (cpkt_size + 1) / 2; i++)
-		pio_write_16(&ctrl->cmd->data_port, ((uint16_t *) cpkt)[i]);
-
-	bidx = 0;
-	remain = obuf_size;
-	while (remain > 0) {
-		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;
-		}
-
-		if ((status & SR_DRQ) == 0)
-			break;
-
-		/* Read byte count. */
-		data_size = (uint16_t) pio_read_8(&ctrl->cmd->cylinder_low) +
-		    ((uint16_t) pio_read_8(&ctrl->cmd->cylinder_high) << 8);
-
-		/* Check whether data fits into output buffer. */
-		if (data_size > obuf_size) {
-			/* Output buffer is too small to store data. */
-			fibril_mutex_unlock(&ctrl->lock);
-			return EIO;
-		}
-
-		/* Read data from the device buffer. */
-		for (i = 0; i < (data_size + 1) / 2; i++) {
-			val = pio_read_16(&ctrl->cmd->data_port);
-			((uint16_t *) obuf)[bidx++] = val;
-		}
-
-		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);
-
-	if (status & SR_ERR)
-		return EIO;
-
-	if (rcvd_size != NULL)
-		*rcvd_size = obuf_size - remain;
-	return EOK;
-}
-
-/** Issue ATAPI Inquiry.
- *
- * @param disk		Disk
- * @param obuf		Buffer for storing inquiry data read from device
- * @param obuf_size	Size of obuf in bytes
- *
- * @return EOK on success, EIO on error.
- */
-static errno_t ata_pcmd_inquiry(disk_t *disk, void *obuf, size_t obuf_size,
-    size_t *rcvd_size)
-{
-	uint8_t cpb[12];
-	scsi_cdb_inquiry_t *cp = (scsi_cdb_inquiry_t *)cpb;
-	errno_t rc;
-
-	memset(cpb, 0, sizeof(cpb));
-
-	/*
-	 * For SFF 8020 compliance the inquiry must be padded to 12 bytes
-	 * and allocation length must fit in one byte.
-	 */
-	cp->op_code = SCSI_CMD_INQUIRY;
-
-	/* Allocation length */
-	cp->alloc_len = host2uint16_t_be(min(obuf_size, 0xff));
-
-	rc = ata_cmd_packet(disk, cpb, sizeof(cpb), obuf, obuf_size, rcvd_size);
-	if (rc != EOK)
-		return rc;
-
-	return EOK;
-}
-
-/** Issue ATAPI read capacity(10) command.
- *
- * @param disk		Disk
- * @param nblocks	Place to store number of blocks
- * @param block_size	Place to store block size
- *
- * @return EOK on success, EIO on error.
- */
-static errno_t ata_pcmd_read_capacity(disk_t *disk, uint64_t *nblocks,
-    size_t *block_size)
-{
-	scsi_cdb_read_capacity_10_t cdb;
-	scsi_read_capacity_10_data_t data;
-	size_t rsize;
-	errno_t rc;
-
-	memset(&cdb, 0, sizeof(cdb));
-	cdb.op_code = SCSI_CMD_READ_CAPACITY_10;
-
-	rc = ata_cmd_packet(disk, &cdb, sizeof(cdb), &data, sizeof(data), &rsize);
-	if (rc != EOK)
-		return rc;
-
-	if (rsize != sizeof(data))
-		return EIO;
-
-	*nblocks = uint32_t_be2host(data.last_lba) + 1;
-	*block_size = uint32_t_be2host(data.block_size);
-
-	return EOK;
-}
-
-/** Issue ATAPI read(12) command.
- *
- * Output buffer must be large enough to hold the data, otherwise the
- * function will fail.
- *
- * @param disk		Disk
- * @param ba		Starting block address
- * @param cnt		Number of blocks to read
- * @param obuf		Buffer for storing inquiry data read from device
- * @param obuf_size	Size of obuf in bytes
- *
- * @return EOK on success, EIO on error.
- */
-static errno_t ata_pcmd_read_12(disk_t *disk, uint64_t ba, size_t cnt,
-    void *obuf, size_t obuf_size)
-{
-	scsi_cdb_read_12_t cp;
-	errno_t rc;
-
-	if (ba > UINT32_MAX)
-		return EINVAL;
-
-	memset(&cp, 0, sizeof(cp));
-
-	cp.op_code = SCSI_CMD_READ_12;
-	cp.lba = host2uint32_t_be(ba);
-	cp.xfer_len = host2uint32_t_be(cnt);
-
-	rc = ata_cmd_packet(disk, &cp, sizeof(cp), obuf, obuf_size, NULL);
-	if (rc != EOK)
-		return rc;
-
-	return EOK;
-}
-
-/** Issue ATAPI read TOC command.
- *
- * Read TOC in 'multi-session' format (first and last session number
- * with last session LBA).
- *
- * http://suif.stanford.edu/~csapuntz/specs/INF-8020.PDF page 171
- *
- * Output buffer must be large enough to hold the data, otherwise the
- * function will fail.
- *
- * @param disk		Disk
- * @param session	Starting session
- * @param obuf		Buffer for storing inquiry data read from device
- * @param obuf_size	Size of obuf in bytes
- *
- * @return EOK on success, EIO on error.
- */
-static errno_t ata_pcmd_read_toc(disk_t *disk, uint8_t session, void *obuf,
-    size_t obuf_size)
-{
-	uint8_t cpb[12];
-	scsi_cdb_read_toc_t *cp = (scsi_cdb_read_toc_t *)cpb;
-	errno_t rc;
-
-	memset(cpb, 0, sizeof(cpb));
-
-	cp->op_code = SCSI_CMD_READ_TOC;
-	cp->msf = 0;
-	cp->format = 0x01; /* 0x01 = multi-session mode */
-	cp->track_sess_no = session;
-	cp->alloc_len = host2uint16_t_be(obuf_size);
-	cp->control = 0x40; /* 0x01 = multi-session mode (shifted to MSB) */
-
-	rc = ata_cmd_packet(disk, cpb, sizeof(cpb), obuf, obuf_size, NULL);
-	if (rc != EOK)
-		return rc;
-
-	return EOK;
-}
-
-/** Read a physical block from the device.
- *
- * @param disk		Disk
- * @param ba		Address the first block.
- * @param cnt		Number of blocks to transfer.
- * @param buf		Buffer for holding the data.
- *
- * @return EOK on success, EIO on error.
- */
-static errno_t ata_rcmd_read(disk_t *disk, uint64_t ba, size_t blk_cnt,
-    void *buf)
-{
-	ata_ctrl_t *ctrl = disk->ctrl;
-	uint8_t drv_head;
-	block_coord_t bc;
-	errno_t rc;
-
-	/* Silence warning. */
-	memset(&bc, 0, sizeof(bc));
-
-	/* Compute block coordinates. */
-	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 */
-	drv_head =
-	    ((disk_dev_idx(disk) != 0) ? DHR_DRV : 0) |
-	    ((disk->amode != am_chs) ? DHR_LBA : 0) |
-	    (bc.h & 0x0f);
-
-	fibril_mutex_lock(&ctrl->lock);
-
-	/* Program a Read Sectors operation. */
-
-	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;
-	}
-
-	pio_write_8(&ctrl->cmd->drive_head, drv_head);
-
-	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;
-	}
-
-	/* Program block coordinates into the device. */
-	coord_sc_program(ctrl, &bc, blk_cnt);
-
-	pio_write_8(&ctrl->cmd->command, disk->amode == am_lba48 ?
-	    CMD_READ_SECTORS_EXT : CMD_READ_SECTORS);
-
-	rc = ata_pio_data_in(disk, buf, blk_cnt * disk->block_size,
-	    disk->block_size, blk_cnt);
-
-	fibril_mutex_unlock(&ctrl->lock);
-
-	if (rc != EOK)
-		ddf_msg(LVL_NOTE, "ata_rcmd_read() -> pio_data_in->%d", rc);
-	return rc;
-}
-
-/** Write a physical block to the device.
- *
- * @param disk		Disk
- * @param ba		Address of the first block.
- * @param cnt		Number of blocks to transfer.
- * @param buf		Buffer holding the data to write.
- *
- * @return EOK on success, EIO on error.
- */
-static errno_t ata_rcmd_write(disk_t *disk, uint64_t ba, size_t cnt,
-    const void *buf)
-{
-	ata_ctrl_t *ctrl = disk->ctrl;
-	uint8_t drv_head;
-	block_coord_t bc;
-	errno_t rc;
-
-	/* Silence warning. */
-	memset(&bc, 0, sizeof(bc));
-
-	/* Compute block coordinates. */
-	if (coord_calc(disk, ba, &bc) != EOK)
-		return EINVAL;
-
-	/* New value for Drive/Head register */
-	drv_head =
-	    ((disk_dev_idx(disk) != 0) ? DHR_DRV : 0) |
-	    ((disk->amode != am_chs) ? DHR_LBA : 0) |
-	    (bc.h & 0x0f);
-
-	fibril_mutex_lock(&ctrl->lock);
-
-	/* Program a Write Sectors operation. */
-
-	if (wait_status(ctrl, 0, ~SR_BSY, NULL, TIMEOUT_BSY) != EOK) {
-		fibril_mutex_unlock(&ctrl->lock);
-		return EIO;
-	}
-
-	pio_write_8(&ctrl->cmd->drive_head, drv_head);
-
-	if (wait_status(ctrl, SR_DRDY, ~SR_BSY, NULL, TIMEOUT_DRDY) != EOK) {
-		fibril_mutex_unlock(&ctrl->lock);
-		return EIO;
-	}
-
-	/* Program block coordinates into the device. */
-	coord_sc_program(ctrl, &bc, cnt);
-
-	pio_write_8(&ctrl->cmd->command, disk->amode == am_lba48 ?
-	    CMD_WRITE_SECTORS_EXT : CMD_WRITE_SECTORS);
-
-	rc = ata_pio_data_out(disk, buf, cnt * disk->block_size,
-	    disk->block_size, cnt);
-
-	fibril_mutex_unlock(&ctrl->lock);
-	return rc;
-}
-
-/** Flush cached data to nonvolatile storage.
- *
- * @param disk		Disk
- *
- * @return EOK on success, EIO on error.
- */
-static errno_t ata_rcmd_flush_cache(disk_t *disk)
-{
-	ata_ctrl_t *ctrl = disk->ctrl;
-	uint8_t drv_head;
-	errno_t rc;
-
-	/* New value for Drive/Head register */
-	drv_head =
-	    (disk_dev_idx(disk) != 0) ? DHR_DRV : 0;
-
-	fibril_mutex_lock(&ctrl->lock);
-
-	/* Program a Flush Cache operation. */
-
-	if (wait_status(ctrl, 0, ~SR_BSY, NULL, TIMEOUT_BSY) != EOK) {
-		fibril_mutex_unlock(&ctrl->lock);
-		return EIO;
-	}
-
-	pio_write_8(&ctrl->cmd->drive_head, drv_head);
-
-	if (wait_status(ctrl, SR_DRDY, ~SR_BSY, NULL, TIMEOUT_DRDY) != EOK) {
-		fibril_mutex_unlock(&ctrl->lock);
-		return EIO;
-	}
-
-	pio_write_8(&ctrl->cmd->command, CMD_FLUSH_CACHE);
-
-	rc = ata_pio_nondata(disk);
-
-	fibril_mutex_unlock(&ctrl->lock);
-	return rc;
-}
-
-/** Get the maximum number of blocks to be transferred in one I/O
- *
- * @param d Disk
- * @return Maximum number of blocks
- */
-static size_t ata_disk_maxnb(disk_t *d)
-{
-	size_t maxnb;
-
-	maxnb = 0;
-
-	if (d->dev_type == ata_pkt_dev) {
-		/* Could be more depending on SCSI command support */
-		maxnb = 0x100;
-	} else {
-		switch (d->amode) {
-		case am_chs:
-		case am_lba28:
-			maxnb = 0x100;
-			break;
-		case am_lba48:
-			maxnb = 0x10000;
-			break;
-		}
-	}
-
-	/*
-	 * If using DMA, this needs to be further restricted not to
-	 * exceed DMA buffer size.
-	 */
-	return maxnb;
-}
-
-/** Calculate block coordinates.
- *
- * Calculates block coordinates in the best coordinate system supported
- * by the device. These can be later programmed into the device using
- * @c coord_sc_program().
- *
- * @return EOK on success or EINVAL if block index is past end of device.
- */
-static errno_t coord_calc(disk_t *d, uint64_t ba, block_coord_t *bc)
-{
-	uint64_t c;
-	uint64_t idx;
-
-	/* Check device bounds. */
-	if (ba >= d->blocks)
-		return EINVAL;
-
-	bc->amode = d->amode;
-
-	switch (d->amode) {
-	case am_chs:
-		/* Compute CHS coordinates. */
-		c = ba / (d->geom.heads * d->geom.sectors);
-		idx = ba % (d->geom.heads * d->geom.sectors);
-
-		bc->cyl_lo = c & 0xff;
-		bc->cyl_hi = (c >> 8) & 0xff;
-		bc->h      = (idx / d->geom.sectors) & 0x0f;
-		bc->sector = (1 + (idx % d->geom.sectors)) & 0xff;
-		break;
-
-	case am_lba28:
-		/* Compute LBA-28 coordinates. */
-		bc->c0 = ba & 0xff;		/* bits 0-7 */
-		bc->c1 = (ba >> 8) & 0xff;	/* bits 8-15 */
-		bc->c2 = (ba >> 16) & 0xff;	/* bits 16-23 */
-		bc->h  = (ba >> 24) & 0x0f;	/* bits 24-27 */
-		break;
-
-	case am_lba48:
-		/* Compute LBA-48 coordinates. */
-		bc->c0 = ba & 0xff;		/* bits 0-7 */
-		bc->c1 = (ba >> 8) & 0xff;	/* bits 8-15 */
-		bc->c2 = (ba >> 16) & 0xff;	/* bits 16-23 */
-		bc->c3 = (ba >> 24) & 0xff;	/* bits 24-31 */
-		bc->c4 = (ba >> 32) & 0xff;	/* bits 32-39 */
-		bc->c5 = (ba >> 40) & 0xff;	/* bits 40-47 */
-		bc->h  = 0;
-		break;
-	}
-
-	return EOK;
-}
-
-/** Program block coordinates and sector count into ATA registers.
- *
- * Note that bc->h must be programmed separately into the device/head register.
- *
- * @param ctrl		Controller
- * @param bc		Block coordinates
- * @param scnt		Sector count
- */
-static void coord_sc_program(ata_ctrl_t *ctrl, const block_coord_t *bc,
-    uint16_t scnt)
-{
-	ata_cmd_t *cmd = ctrl->cmd;
-
-	if (bc->amode == am_lba48) {
-		/* Write high-order bits. */
-		pio_write_8(&cmd->sector_count, scnt >> 8);
-		pio_write_8(&cmd->sector_number, bc->c3);
-		pio_write_8(&cmd->cylinder_low, bc->c4);
-		pio_write_8(&cmd->cylinder_high, bc->c5);
-	}
-
-	/* Write low-order bits. */
-	pio_write_8(&cmd->sector_count, scnt & 0x00ff);
-	pio_write_8(&cmd->sector_number, bc->c0);
-	pio_write_8(&cmd->cylinder_low, bc->c1);
-	pio_write_8(&cmd->cylinder_high, bc->c2);
-}
-
-/** Wait until some status bits are set and some are reset.
- *
- * Example: wait_status(ctrl, SR_DRDY, ~SR_BSY, ...) waits for SR_DRDY to become
- * set and SR_BSY to become reset.
- *
- * @param ctrl		Controller
- * @param set		Combination if bits which must be all set.
- * @param n_reset	Negated combination of bits which must be all reset.
- * @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_status(ata_ctrl_t *ctrl, unsigned set, unsigned n_reset,
-    uint8_t *pstatus, unsigned timeout)
-{
-	uint8_t status;
-	int cnt;
-
-	status = pio_read_8(&ctrl->cmd->status);
-
-	/*
-	 * This is crude, yet simple. First try with 1us delays
-	 * (most likely the device will respond very fast). If not,
-	 * start trying every 10 ms.
-	 */
-
-	cnt = 100;
-	while ((status & ~n_reset) != 0 || (status & set) != set) {
-		--cnt;
-		if (cnt <= 0)
-			break;
-
-		status = pio_read_8(&ctrl->cmd->status);
-	}
-
-	cnt = timeout;
-	while ((status & ~n_reset) != 0 || (status & set) != set) {
-		fibril_usleep(10000);
-		--cnt;
-		if (cnt <= 0)
-			break;
-
-		status = pio_read_8(&ctrl->cmd->status);
-	}
-
-	if (pstatus)
-		*pstatus = status;
-
-	if (cnt == 0)
-		return EIO;
-
-	return EOK;
-}
-
-/** 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.
  *
@@ -1540,10 +314,5 @@
 
 	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);
+	ata_channel_irq(ctrl->channel, status);
 
 	parent_sess = ddf_dev_parent_sess_get(dev);
@@ -1551,4 +320,207 @@
 }
 
+/** Write the data register callback handler.
+ *
+ * @param arg Argument (ata_ctrl_t *)
+ * @param data Data
+ * @param nwords Number of words to write
+ */
+static void ata_write_data_16(void *arg, uint16_t *data, size_t nwords)
+{
+	ata_ctrl_t *ctrl = (ata_ctrl_t *)arg;
+	size_t i;
+
+	for (i = 0; i < nwords; i++)
+		pio_write_16(&ctrl->cmd->data_port, data[i]);
+}
+
+/** Read the data register callback handler.
+ *
+ * @param arg Argument (ata_ctrl_t *)
+ * @param buf Destination buffer
+ * @param nwords Number of words to read
+ */
+static void ata_read_data_16(void *arg, uint16_t *buf, size_t nwords)
+{
+	ata_ctrl_t *ctrl = (ata_ctrl_t *)arg;
+	size_t i;
+
+	for (i = 0; i < nwords; i++)
+		buf[i] = pio_read_16(&ctrl->cmd->data_port);
+}
+
+/** Write command register callback handler.
+ *
+ * @param arg Argument (ata_ctrl_t *)
+ * @param off Register offset
+ * @param value Value to write to command register
+ */
+static void ata_write_cmd_8(void *arg, uint16_t off, uint8_t value)
+{
+	ata_ctrl_t *ctrl = (ata_ctrl_t *)arg;
+
+	pio_write_8(((ioport8_t *)ctrl->cmd) + off, value);
+}
+
+/** Read command register callback handler.
+ *
+ * @param arg Argument (ata_ctrl_t *)
+ * @param off Register offset
+ * @return value Value read from command register
+ */
+static uint8_t ata_read_cmd_8(void *arg, uint16_t off)
+{
+	ata_ctrl_t *ctrl = (ata_ctrl_t *)arg;
+
+	return pio_read_8(((ioport8_t *)ctrl->cmd) + off);
+}
+
+/** Write control register callback handler.
+ *
+ * @param arg Argument (ata_ctrl_t *)
+ * @param off Register offset
+ * @param value Value to write to control register
+ */
+static void ata_write_ctl_8(void *arg, uint16_t off, uint8_t value)
+{
+	ata_ctrl_t *ctrl = (ata_ctrl_t *)arg;
+
+	pio_write_8(((ioport8_t *)ctrl->ctl) + off, value);
+}
+
+/** Read control register callback handler.
+ *
+ * @param arg Argument (ata_ctrl_t *)
+ * @param off Register offset
+ * @return value Value read from control register
+ */
+static uint8_t ata_read_ctl_8(void *arg, uint16_t off)
+{
+	ata_ctrl_t *ctrl = (ata_ctrl_t *)arg;
+
+	return pio_read_8(((ioport8_t *)ctrl->ctl) + off);
+}
+
+/** Enable IRQ callback handler
+ *
+ * @param arg Argument (ata_ctrl_t *)
+ * @return EOK on success or an error code
+ */
+static errno_t ata_irq_enable(void *arg)
+{
+	ata_ctrl_t *ctrl = (ata_ctrl_t *)arg;
+	async_sess_t *parent_sess;
+	errno_t rc;
+
+	ddf_msg(LVL_DEBUG, "Enable IRQ");
+
+	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);
+		return rc;
+	}
+
+	return EOK;
+}
+
+/** Disable IRQ callback handler
+ *
+ * @param arg Argument (ata_ctrl_t *)
+ * @return EOK on success or an error code
+ */
+static errno_t ata_irq_disable(void *arg)
+{
+	ata_ctrl_t *ctrl = (ata_ctrl_t *)arg;
+	async_sess_t *parent_sess;
+	errno_t rc;
+
+	ddf_msg(LVL_DEBUG, "Disable IRQ");
+
+	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 enabling IRQ.");
+		(void) unregister_interrupt_handler(ctrl->dev,
+		    ctrl->ihandle);
+		return rc;
+	}
+
+	return EOK;
+}
+
+/** Add ATA device callback handler.
+ *
+ * @param arg Argument (ata_ctrl_t *)
+ * @param idx Device index
+ * $param charg Connection handler argument
+ * @return EOK on success or an error code
+ */
+static errno_t ata_add_device(void *arg, unsigned idx, void *charg)
+{
+	ata_ctrl_t *ctrl = (ata_ctrl_t *)arg;
+	return ata_fun_create(ctrl, idx, charg);
+}
+
+/** Remove ATA device callback handler.
+ *
+ * @param arg Argument (ata_ctrl_t *)
+ * @param idx Device index
+ * @return EOK on success or an error code
+ */
+static errno_t ata_remove_device(void *arg, unsigned idx)
+{
+	ata_ctrl_t *ctrl = (ata_ctrl_t *)arg;
+	return ata_fun_remove(ctrl, idx);
+}
+
+/** Debug message callback handler.
+ *
+ * @param arg Argument (ata_ctrl_t *)
+ * @param msg Message
+ */
+static void ata_msg_debug(void *arg, char *msg)
+{
+	(void)arg;
+	ddf_msg(LVL_DEBUG, "%s", msg);
+}
+
+/** Notice message callback handler.
+ *
+ * @param arg Argument (ata_ctrl_t *)
+ * @param msg Message
+ */
+static void ata_msg_note(void *arg, char *msg)
+{
+	(void)arg;
+	ddf_msg(LVL_NOTE, "%s", msg);
+}
+
+/** Warning message callback handler.
+ *
+ * @param arg Argument (ata_ctrl_t *)
+ * @param msg Message
+ */
+static void ata_msg_warn(void *arg, char *msg)
+{
+	(void)arg;
+	ddf_msg(LVL_WARN, "%s", msg);
+}
+
+/** Error message callback handler.
+ *
+ * @param arg Argument (ata_ctrl_t *)
+ * @param msg Message
+ */
+static void ata_msg_error(void *arg, char *msg)
+{
+	(void)arg;
+	ddf_msg(LVL_ERROR, "%s", msg);
+}
+
 /**
  * @}
Index: uspace/drv/block/ata_bd/ata_bd.h
===================================================================
--- uspace/drv/block/ata_bd/ata_bd.h	(revision 64cf7a3bd1e3e66b8aa3b82148faced27efb35bb)
+++ uspace/drv/block/ata_bd/ata_bd.h	(revision 2791fbb7c1f204fd66ed105c6cc1ddf9e8f5bedf)
@@ -36,13 +36,10 @@
 #define __ATA_BD_H__
 
-#include <async.h>
-#include <bd_srv.h>
+#include <ata/ata.h>
+#include <ata/ata_hw.h>
 #include <ddf/driver.h>
 #include <fibril_synch.h>
 #include <stdbool.h>
-#include <str.h>
 #include <stdint.h>
-#include <stddef.h>
-#include "ata_hw.h"
 
 #define NAME "ata_bd"
@@ -54,78 +51,4 @@
 	int irq;	/**< IRQ */
 } ata_hwres_t;
-
-/** Timeout definitions. Unit is 10 ms. */
-enum ata_timeout {
-	TIMEOUT_PROBE	=  100, /*  1 s */
-	TIMEOUT_BSY	=  100, /*  1 s */
-	TIMEOUT_DRDY	= 1000  /* 10 s */
-};
-
-enum ata_dev_type {
-	ata_reg_dev,	/* Register device (no packet feature set support) */
-	ata_pkt_dev	/* Packet device (supports packet feature set). */
-};
-
-/** Register device block addressing mode. */
-enum rd_addr_mode {
-	am_chs,		/**< CHS block addressing */
-	am_lba28,	/**< LBA-28 block addressing */
-	am_lba48	/**< LBA-48 block addressing */
-};
-
-/** Block coordinates */
-typedef struct {
-	enum rd_addr_mode amode;
-
-	union {
-		/** CHS coordinates */
-		struct {
-			uint8_t sector;
-			uint8_t cyl_lo;
-			uint8_t cyl_hi;
-		};
-		/** LBA coordinates */
-		struct {
-			uint8_t c0;
-			uint8_t c1;
-			uint8_t c2;
-			uint8_t c3;
-			uint8_t c4;
-			uint8_t c5;
-		};
-	};
-
-	/** Lower 4 bits for device/head register */
-	uint8_t h;
-} block_coord_t;
-
-/** ATA device state structure. */
-typedef struct {
-	bool present;
-	struct ata_ctrl *ctrl;
-	struct ata_fun *afun;
-
-	/** Device type */
-	enum ata_dev_type dev_type;
-
-	/** Addressing mode to use (if register device) */
-	enum rd_addr_mode amode;
-
-	/*
-	 * Geometry. Only valid if operating in CHS mode.
-	 */
-	struct {
-		unsigned heads;
-		unsigned cylinders;
-		unsigned sectors;
-	} geom;
-
-	uint64_t blocks;
-	size_t block_size;
-
-	char model[STR_BOUNDS(40) + 1];
-
-	int disk_id;
-} disk_t;
 
 /** ATA controller */
@@ -147,23 +70,17 @@
 	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;
+
+	/** Libata ATA channel */
+	ata_channel_t *channel;
+	struct ata_fun *fun[2];
 } ata_ctrl_t;
 
 typedef struct ata_fun {
 	ddf_fun_t *fun;
-	disk_t *disk;
-	bd_srvs_t bds;
+	void *charg;
 } ata_fun_t;
 
@@ -172,6 +89,4 @@
 extern errno_t ata_ctrl_gone(ata_ctrl_t *);
 
-extern bd_ops_t ata_bd_ops;
-
 #endif
 
Index: pace/drv/block/ata_bd/ata_hw.h
===================================================================
--- uspace/drv/block/ata_bd/ata_hw.h	(revision 64cf7a3bd1e3e66b8aa3b82148faced27efb35bb)
+++ 	(revision )
@@ -1,258 +1,0 @@
-/*
- * Copyright (c) 2009 Jiri Svoboda
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- * - Redistributions in binary form must reproduce the above copyright
- *   notice, this list of conditions and the following disclaimer in the
- *   documentation and/or other materials provided with the distribution.
- * - The name of the author may not be used to endorse or promote products
- *   derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
- * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-/** @addtogroup ata_bd
- * @{
- */
-/** @file ATA hardware protocol (registers, data structures).
- */
-
-#ifndef __ATA_HW_H__
-#define __ATA_HW_H__
-
-#include <stdint.h>
-
-enum {
-	CTL_READ_START  = 0,
-	CTL_WRITE_START = 1,
-};
-
-enum {
-	STATUS_FAILURE = 0
-};
-
-enum {
-	MAX_DISKS	= 2
-};
-
-/** ATA Command Register Block. */
-typedef union {
-	/* Read/Write */
-	struct {
-		uint16_t data_port;
-		uint8_t sector_count;
-		uint8_t sector_number;
-		uint8_t cylinder_low;
-		uint8_t cylinder_high;
-		uint8_t drive_head;
-		uint8_t pad_rw0;
-	};
-
-	/* Read Only */
-	struct {
-		uint8_t pad_ro0;
-		uint8_t error;
-		uint8_t pad_ro1[5];
-		uint8_t status;
-	};
-
-	/* Write Only */
-	struct {
-		uint8_t pad_wo0;
-		uint8_t features;
-		uint8_t pad_wo1[5];
-		uint8_t command;
-	};
-} ata_cmd_t;
-
-typedef union {
-	/* Read */
-	struct {
-		uint8_t pad0[6];
-		uint8_t alt_status;
-		uint8_t drive_address;
-	};
-
-	/* Write */
-	struct {
-		uint8_t pad1[6];
-		uint8_t device_control;
-		uint8_t pad2;
-	};
-} ata_ctl_t;
-
-enum devctl_bits {
-	DCR_SRST	= 0x04, /**< Software Reset */
-	DCR_nIEN	= 0x02  /**< Interrupt Enable (negated) */
-};
-
-enum status_bits {
-	SR_BSY		= 0x80, /**< Busy */
-	SR_DRDY		= 0x40, /**< Drive Ready */
-	SR_DWF		= 0x20, /**< Drive Write Fault */
-	SR_DSC		= 0x10, /**< Drive Seek Complete */
-	SR_DRQ		= 0x08, /**< Data Request */
-	SR_CORR		= 0x04, /**< Corrected Data */
-	SR_IDX		= 0x02, /**< Index */
-	SR_ERR		= 0x01  /**< Error */
-};
-
-enum drive_head_bits {
-	DHR_LBA		= 0x40,	/**< Use LBA addressing mode */
-	DHR_DRV		= 0x10	/**< Select device 1 */
-};
-
-enum error_bits {
-	ER_BBK		= 0x80, /**< Bad Block Detected */
-	ER_UNC		= 0x40, /**< Uncorrectable Data Error */
-	ER_MC		= 0x20, /**< Media Changed */
-	ER_IDNF		= 0x10, /**< ID Not Found */
-	ER_MCR		= 0x08, /**< Media Change Request */
-	ER_ABRT		= 0x04, /**< Aborted Command */
-	ER_TK0NF	= 0x02, /**< Track 0 Not Found */
-	ER_AMNF		= 0x01  /**< Address Mark Not Found */
-};
-
-enum ata_command {
-	CMD_READ_SECTORS	= 0x20,
-	CMD_READ_SECTORS_EXT	= 0x24,
-	CMD_WRITE_SECTORS	= 0x30,
-	CMD_WRITE_SECTORS_EXT	= 0x34,
-	CMD_PACKET		= 0xA0,
-	CMD_IDENTIFY_PKT_DEV	= 0xA1,
-	CMD_IDENTIFY_DRIVE	= 0xEC,
-	CMD_FLUSH_CACHE		= 0xE7
-};
-
-/** Data returned from identify device and identify packet device command. */
-typedef struct {
-	uint16_t gen_conf;
-	uint16_t cylinders;
-	uint16_t _res2;
-	uint16_t heads;
-	uint16_t _vs4;
-	uint16_t _vs5;
-	uint16_t sectors;
-	uint16_t _vs7;
-	uint16_t _vs8;
-	uint16_t _vs9;
-
-	uint16_t serial_number[10];
-	uint16_t _vs20;
-	uint16_t _vs21;
-	uint16_t vs_bytes;
-	uint16_t firmware_rev[4];
-	uint16_t model_name[20];
-
-	uint16_t max_rw_multiple;
-	uint16_t _res48;
-	uint16_t caps;		/* Different meaning for packet device */
-	uint16_t _res50;
-	uint16_t pio_timing;
-	uint16_t dma_timing;
-
-	uint16_t validity;
-	uint16_t cur_cyl;
-	uint16_t cur_heads;
-	uint16_t cur_sectors;
-	uint16_t cur_capacity0;
-	uint16_t cur_capacity1;
-	uint16_t mss;
-	uint16_t total_lba28_0;
-	uint16_t total_lba28_1;
-	uint16_t sw_dma;
-	uint16_t mw_dma;
-	uint16_t pio_modes;
-	uint16_t min_mw_dma_cycle;
-	uint16_t rec_mw_dma_cycle;
-	uint16_t min_raw_pio_cycle;
-	uint16_t min_iordy_pio_cycle;
-
-	uint16_t _res69;
-	uint16_t _res70;
-	uint16_t _res71;
-	uint16_t _res72;
-	uint16_t _res73;
-	uint16_t _res74;
-
-	uint16_t queue_depth;
-	uint16_t _res76[1 + 79 - 76];
-	uint16_t version_maj;
-	uint16_t version_min;
-	uint16_t cmd_set0;
-	uint16_t cmd_set1;
-	uint16_t csf_sup_ext;
-	uint16_t csf_enabled0;
-	uint16_t csf_enabled1;
-	uint16_t csf_default;
-	uint16_t udma;
-
-	uint16_t _res89[1 + 99 - 89];
-
-	/* Total number of blocks in LBA-48 addressing */
-	uint16_t total_lba48_0;
-	uint16_t total_lba48_1;
-	uint16_t total_lba48_2;
-	uint16_t total_lba48_3;
-
-	/* Note: more fields are defined in ATA/ATAPI-7 */
-	uint16_t _res104[1 + 127 - 104];
-	uint16_t _vs128[1 + 159 - 128];
-	uint16_t _res160[1 + 255 - 160];
-} identify_data_t;
-
-/** Capability bits for register device. */
-enum ata_regdev_caps {
-	rd_cap_iordy		= 0x0800,
-	rd_cap_iordy_cbd	= 0x0400,
-	rd_cap_lba		= 0x0200,
-	rd_cap_dma		= 0x0100
-};
-
-/** Capability bits for packet device. */
-enum ata_pktdev_caps {
-	pd_cap_ildma		= 0x8000,
-	pd_cap_cmdqueue		= 0x4000,
-	pd_cap_overlap		= 0x2000,
-	pd_cap_need_softreset	= 0x1000,	/* Obsolete (ATAPI-6) */
-	pd_cap_iordy		= 0x0800,
-	pd_cap_iordy_dis	= 0x0400,
-	pd_cap_lba		= 0x0200,	/* Must be on */
-	pd_cap_dma		= 0x0100
-};
-
-/** Bits of @c identify_data_t.cmd_set1 */
-enum ata_cs1 {
-	cs1_addr48	= 0x0400	/**< 48-bit address feature set */
-};
-
-/** Extract value of device type from scsi_std_inquiry_data_t.pqual_devtype */
-#define INQUIRY_PDEV_TYPE(val) ((val) & 0x1f)
-
-enum ata_pdev_signature {
-	/**
-	 * Signature put by a packet device in byte count register
-	 * in response to Identify command.
-	 */
-	PDEV_SIGNATURE_BC	= 0xEB14
-};
-
-#endif
-
-/** @}
- */
Index: uspace/drv/block/ata_bd/main.c
===================================================================
--- uspace/drv/block/ata_bd/main.c	(revision 64cf7a3bd1e3e66b8aa3b82148faced27efb35bb)
+++ uspace/drv/block/ata_bd/main.c	(revision 2791fbb7c1f204fd66ed105c6cc1ddf9e8f5bedf)
@@ -157,9 +157,9 @@
 }
 
-static char *ata_fun_name(disk_t *disk)
+static char *ata_fun_name(unsigned idx)
 {
 	char *fun_name;
 
-	if (asprintf(&fun_name, "d%u", disk->disk_id) < 0)
+	if (asprintf(&fun_name, "d%u", idx) < 0)
 		return NULL;
 
@@ -167,7 +167,6 @@
 }
 
-errno_t ata_fun_create(disk_t *disk)
-{
-	ata_ctrl_t *ctrl = disk->ctrl;
+errno_t ata_fun_create(ata_ctrl_t *ctrl, unsigned idx, void *charg)
+{
 	errno_t rc;
 	char *fun_name = NULL;
@@ -176,5 +175,5 @@
 	bool bound = false;
 
-	fun_name = ata_fun_name(disk);
+	fun_name = ata_fun_name(idx);
 	if (fun_name == NULL) {
 		ddf_msg(LVL_ERROR, "Out of memory.");
@@ -199,9 +198,5 @@
 
 	afun->fun = fun;
-	afun->disk = disk;
-
-	bd_srvs_init(&afun->bds);
-	afun->bds.ops = &ata_bd_ops;
-	afun->bds.sarg = disk;
+	afun->charg = charg;
 
 	/* Set up a connection handler. */
@@ -225,5 +220,4 @@
 
 	free(fun_name);
-	disk->afun = afun;
 	return EOK;
 error:
@@ -238,13 +232,11 @@
 }
 
-errno_t ata_fun_remove(disk_t *disk)
+errno_t ata_fun_remove(ata_ctrl_t *ctrl, unsigned idx)
 {
 	errno_t rc;
 	char *fun_name;
-
-	if (disk->afun == NULL)
-		return EOK;
-
-	fun_name = ata_fun_name(disk);
+	ata_fun_t *afun = ctrl->fun[idx];
+
+	fun_name = ata_fun_name(idx);
 	if (fun_name == NULL) {
 		ddf_msg(LVL_ERROR, "Out of memory.");
@@ -253,6 +245,6 @@
 	}
 
-	ddf_msg(LVL_DEBUG, "ata_fun_remove(%p, '%s')", disk, fun_name);
-	rc = ddf_fun_offline(disk->afun->fun);
+	ddf_msg(LVL_DEBUG, "ata_fun_remove(%p, '%s')", afun, fun_name);
+	rc = ddf_fun_offline(afun->fun);
 	if (rc != EOK) {
 		ddf_msg(LVL_ERROR, "Error offlining function '%s'.", fun_name);
@@ -260,5 +252,5 @@
 	}
 
-	rc = ddf_fun_unbind(disk->afun->fun);
+	rc = ddf_fun_unbind(afun->fun);
 	if (rc != EOK) {
 		ddf_msg(LVL_ERROR, "Failed unbinding function '%s'.", fun_name);
@@ -266,6 +258,5 @@
 	}
 
-	ddf_fun_destroy(disk->afun->fun);
-	disk->afun = NULL;
+	ddf_fun_destroy(afun->fun);
 	free(fun_name);
 	return EOK;
@@ -276,13 +267,11 @@
 }
 
-errno_t ata_fun_unbind(disk_t *disk)
+errno_t ata_fun_unbind(ata_ctrl_t *ctrl, unsigned idx)
 {
 	errno_t rc;
 	char *fun_name;
-
-	if (disk->afun == NULL)
-		return EOK;
-
-	fun_name = ata_fun_name(disk);
+	ata_fun_t *afun = ctrl->fun[idx];
+
+	fun_name = ata_fun_name(idx);
 	if (fun_name == NULL) {
 		ddf_msg(LVL_ERROR, "Out of memory.");
@@ -291,6 +280,6 @@
 	}
 
-	ddf_msg(LVL_DEBUG, "ata_fun_unbind(%p, '%s')", disk, fun_name);
-	rc = ddf_fun_unbind(disk->afun->fun);
+	ddf_msg(LVL_DEBUG, "ata_fun_unbind(%p, '%s')", afun, fun_name);
+	rc = ddf_fun_unbind(afun->fun);
 	if (rc != EOK) {
 		ddf_msg(LVL_ERROR, "Failed unbinding function '%s'.", fun_name);
@@ -298,6 +287,5 @@
 	}
 
-	ddf_fun_destroy(disk->afun->fun);
-	disk->afun = NULL;
+	ddf_fun_destroy(afun->fun);
 	free(fun_name);
 	return EOK;
@@ -338,5 +326,4 @@
 }
 
-/** Block device connection handler */
 static void ata_bd_connection(ipc_call_t *icall, void *arg)
 {
@@ -344,5 +331,5 @@
 
 	afun = (ata_fun_t *) ddf_fun_data_get((ddf_fun_t *)arg);
-	bd_conn(icall, &afun->bds);
+	ata_connection(icall, afun->charg);
 }
 
Index: uspace/drv/block/ata_bd/main.h
===================================================================
--- uspace/drv/block/ata_bd/main.h	(revision 64cf7a3bd1e3e66b8aa3b82148faced27efb35bb)
+++ uspace/drv/block/ata_bd/main.h	(revision 2791fbb7c1f204fd66ed105c6cc1ddf9e8f5bedf)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2013 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -38,7 +38,7 @@
 #include "ata_bd.h"
 
-extern errno_t ata_fun_create(disk_t *);
-extern errno_t ata_fun_remove(disk_t *);
-extern errno_t ata_fun_unbind(disk_t *);
+extern errno_t ata_fun_create(ata_ctrl_t *, unsigned, void *);
+extern errno_t ata_fun_remove(ata_ctrl_t *, unsigned);
+extern errno_t ata_fun_unbind(ata_ctrl_t *, unsigned);
 
 #endif
Index: uspace/drv/block/ata_bd/meson.build
===================================================================
--- uspace/drv/block/ata_bd/meson.build	(revision 64cf7a3bd1e3e66b8aa3b82148faced27efb35bb)
+++ uspace/drv/block/ata_bd/meson.build	(revision 2791fbb7c1f204fd66ed105c6cc1ddf9e8f5bedf)
@@ -27,4 +27,4 @@
 #
 
-deps = [ 'scsi' ]
+deps = [ 'ata', 'scsi' ]
 src = files('ata_bd.c', 'main.c')
Index: uspace/lib/ata/include/ata/ata.h
===================================================================
--- uspace/lib/ata/include/ata/ata.h	(revision 2791fbb7c1f204fd66ed105c6cc1ddf9e8f5bedf)
+++ uspace/lib/ata/include/ata/ata.h	(revision 2791fbb7c1f204fd66ed105c6cc1ddf9e8f5bedf)
@@ -0,0 +1,195 @@
+/*
+ * Copyright (c) 2024 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup libata
+ * @{
+ */
+/** @file ATA driver library definitions.
+ */
+
+#ifndef LIBATA_ATA_H
+#define LIBATA_ATA_H
+
+#include <async.h>
+#include <bd_srv.h>
+#include <fibril_synch.h>
+#include <stdbool.h>
+#include <str.h>
+#include <stdint.h>
+#include <stddef.h>
+#include "ata_hw.h"
+
+#define NAME "ata_bd"
+
+struct ata_device;
+
+/** ATA channel creation parameters */
+typedef struct {
+	/** Argument to callback functions */
+	void *arg;
+	/** IRQ is available */
+	bool have_irq;
+	/** Read 16 bits from the data port */
+	void (*write_data_16)(void *, uint16_t *, size_t);
+	/** Write 16 bits to the data port */
+	void (*read_data_16)(void *, uint16_t *, size_t);
+	/** Read 8 bits from an 8-bit command register */
+	void (*write_cmd_8)(void *, uint16_t, uint8_t);
+	/** Writes 8 bits to an 8-bit command register */
+	uint8_t (*read_cmd_8)(void *, uint16_t);
+	/** Read 8 bits from a control register */
+	void (*write_ctl_8)(void *, uint16_t, uint8_t);
+	/** Write 8 bits to a control register */
+	uint8_t (*read_ctl_8)(void *, uint16_t);
+	/** Enable interrupts */
+	errno_t (*irq_enable)(void *);
+	/** Disable interrupts */
+	errno_t (*irq_disable)(void *);
+	/** Add new device */
+	errno_t (*add_device)(void *, unsigned, void *);
+	/** Remove device */
+	errno_t (*remove_device)(void *, unsigned);
+	/** Log notice message */
+	void (*msg_note)(void *, char *);
+	/** Log error message */
+	void (*msg_error)(void *, char *);
+	/** Log warning message */
+	void (*msg_warn)(void *, char *);
+	/** Log debug message */
+	void (*msg_debug)(void *, char *);
+} ata_params_t;
+
+/** Timeout definitions. Unit is 10 ms. */
+enum ata_timeout {
+	TIMEOUT_PROBE	=  100, /*  1 s */
+	TIMEOUT_BSY	=  100, /*  1 s */
+	TIMEOUT_DRDY	= 1000  /* 10 s */
+};
+
+enum ata_dev_type {
+	ata_reg_dev,	/* Register device (no packet feature set support) */
+	ata_pkt_dev	/* Packet device (supports packet feature set). */
+};
+
+/** Register device block addressing mode. */
+enum rd_addr_mode {
+	am_chs,		/**< CHS block addressing */
+	am_lba28,	/**< LBA-28 block addressing */
+	am_lba48	/**< LBA-48 block addressing */
+};
+
+/** Block coordinates */
+typedef struct {
+	enum rd_addr_mode amode;
+
+	union {
+		/** CHS coordinates */
+		struct {
+			uint8_t sector;
+			uint8_t cyl_lo;
+			uint8_t cyl_hi;
+		};
+		/** LBA coordinates */
+		struct {
+			uint8_t c0;
+			uint8_t c1;
+			uint8_t c2;
+			uint8_t c3;
+			uint8_t c4;
+			uint8_t c5;
+		};
+	};
+
+	/** Lower 4 bits for device/head register */
+	uint8_t h;
+} block_coord_t;
+
+/** ATA device state structure. */
+typedef struct ata_device {
+	bool present;
+	struct ata_channel *chan;
+
+	/** Device type */
+	enum ata_dev_type dev_type;
+
+	/** Addressing mode to use (if register device) */
+	enum rd_addr_mode amode;
+
+	/*
+	 * Geometry. Only valid if operating in CHS mode.
+	 */
+	struct {
+		unsigned heads;
+		unsigned cylinders;
+		unsigned sectors;
+	} geom;
+
+	uint64_t blocks;
+	size_t block_size;
+
+	char model[STR_BOUNDS(40) + 1];
+
+	int device_id;
+	bd_srvs_t bds;
+} ata_device_t;
+
+/** ATA channel */
+typedef struct ata_channel {
+	/** Parameters */
+	ata_params_t params;
+
+	/** Command registers */
+	ata_cmd_t *cmd;
+	/** Control registers */
+	ata_ctl_t *ctl;
+
+	/** Per-device state. */
+	ata_device_t device[MAX_DEVICES];
+
+	/** Synchronize channel 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_channel_t;
+
+extern errno_t ata_channel_create(ata_params_t *, ata_channel_t **);
+extern errno_t ata_channel_initialize(ata_channel_t *);
+extern errno_t ata_channel_destroy(ata_channel_t *);
+extern void ata_channel_irq(ata_channel_t *, uint8_t);
+extern void ata_connection(ipc_call_t *, ata_device_t *);
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/ata/include/ata/ata_hw.h
===================================================================
--- uspace/lib/ata/include/ata/ata_hw.h	(revision 2791fbb7c1f204fd66ed105c6cc1ddf9e8f5bedf)
+++ uspace/lib/ata/include/ata/ata_hw.h	(revision 2791fbb7c1f204fd66ed105c6cc1ddf9e8f5bedf)
@@ -0,0 +1,267 @@
+/*
+ * Copyright (c) 2024 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup libata
+ * @{
+ */
+/** @file ATA hardware protocol (registers, data structures).
+ */
+
+#ifndef LIBATA_ATA_HW_H
+#define LIBATA_ATA_HW_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+enum {
+	CTL_READ_START  = 0,
+	CTL_WRITE_START = 1,
+};
+
+enum {
+	STATUS_FAILURE = 0
+};
+
+enum {
+	MAX_DEVICES	= 2
+};
+
+/** ATA Command Register Block. */
+typedef union {
+	/* Read/Write */
+	struct {
+		uint16_t data_port;
+		uint8_t sector_count;
+		uint8_t sector_number;
+		uint8_t cylinder_low;
+		uint8_t cylinder_high;
+		uint8_t drive_head;
+		uint8_t pad_rw0;
+	};
+
+	/* Read Only */
+	struct {
+		uint8_t pad_ro0;
+		uint8_t error;
+		uint8_t pad_ro1[5];
+		uint8_t status;
+	};
+
+	/* Write Only */
+	struct {
+		uint8_t pad_wo0;
+		uint8_t features;
+		uint8_t pad_wo1[5];
+		uint8_t command;
+	};
+} ata_cmd_t;
+
+typedef union {
+	/* Read */
+	struct {
+		uint8_t pad0[6];
+		uint8_t alt_status;
+		uint8_t drive_address;
+	};
+
+	/* Write */
+	struct {
+		uint8_t pad1[6];
+		uint8_t device_control;
+		uint8_t pad2;
+	};
+} ata_ctl_t;
+
+#define REG_SECTOR_COUNT offsetof(ata_cmd_t, sector_count)
+#define REG_SECTOR_NUMBER offsetof(ata_cmd_t, sector_number)
+#define REG_CYLINDER_LOW offsetof(ata_cmd_t, cylinder_low)
+#define REG_CYLINDER_HIGH offsetof(ata_cmd_t, cylinder_high)
+#define REG_DRIVE_HEAD offsetof(ata_cmd_t, drive_head)
+#define REG_STATUS offsetof(ata_cmd_t, status)
+#define REG_COMMAND offsetof(ata_cmd_t, command)
+
+enum devctl_bits {
+	DCR_SRST	= 0x04, /**< Software Reset */
+	DCR_nIEN	= 0x02  /**< Interrupt Enable (negated) */
+};
+
+enum status_bits {
+	SR_BSY		= 0x80, /**< Busy */
+	SR_DRDY		= 0x40, /**< Drive Ready */
+	SR_DWF		= 0x20, /**< Drive Write Fault */
+	SR_DSC		= 0x10, /**< Drive Seek Complete */
+	SR_DRQ		= 0x08, /**< Data Request */
+	SR_CORR		= 0x04, /**< Corrected Data */
+	SR_IDX		= 0x02, /**< Index */
+	SR_ERR		= 0x01  /**< Error */
+};
+
+enum drive_head_bits {
+	DHR_LBA		= 0x40,	/**< Use LBA addressing mode */
+	DHR_DRV		= 0x10	/**< Select device 1 */
+};
+
+enum error_bits {
+	ER_BBK		= 0x80, /**< Bad Block Detected */
+	ER_UNC		= 0x40, /**< Uncorrectable Data Error */
+	ER_MC		= 0x20, /**< Media Changed */
+	ER_IDNF		= 0x10, /**< ID Not Found */
+	ER_MCR		= 0x08, /**< Media Change Request */
+	ER_ABRT		= 0x04, /**< Aborted Command */
+	ER_TK0NF	= 0x02, /**< Track 0 Not Found */
+	ER_AMNF		= 0x01  /**< Address Mark Not Found */
+};
+
+enum ata_command {
+	CMD_READ_SECTORS	= 0x20,
+	CMD_READ_SECTORS_EXT	= 0x24,
+	CMD_WRITE_SECTORS	= 0x30,
+	CMD_WRITE_SECTORS_EXT	= 0x34,
+	CMD_PACKET		= 0xA0,
+	CMD_IDENTIFY_PKT_DEV	= 0xA1,
+	CMD_IDENTIFY_DRIVE	= 0xEC,
+	CMD_FLUSH_CACHE		= 0xE7
+};
+
+/** Data returned from identify device and identify packet device command. */
+typedef struct {
+	uint16_t gen_conf;
+	uint16_t cylinders;
+	uint16_t _res2;
+	uint16_t heads;
+	uint16_t _vs4;
+	uint16_t _vs5;
+	uint16_t sectors;
+	uint16_t _vs7;
+	uint16_t _vs8;
+	uint16_t _vs9;
+
+	uint16_t serial_number[10];
+	uint16_t _vs20;
+	uint16_t _vs21;
+	uint16_t vs_bytes;
+	uint16_t firmware_rev[4];
+	uint16_t model_name[20];
+
+	uint16_t max_rw_multiple;
+	uint16_t _res48;
+	uint16_t caps;		/* Different meaning for packet device */
+	uint16_t _res50;
+	uint16_t pio_timing;
+	uint16_t dma_timing;
+
+	uint16_t validity;
+	uint16_t cur_cyl;
+	uint16_t cur_heads;
+	uint16_t cur_sectors;
+	uint16_t cur_capacity0;
+	uint16_t cur_capacity1;
+	uint16_t mss;
+	uint16_t total_lba28_0;
+	uint16_t total_lba28_1;
+	uint16_t sw_dma;
+	uint16_t mw_dma;
+	uint16_t pio_modes;
+	uint16_t min_mw_dma_cycle;
+	uint16_t rec_mw_dma_cycle;
+	uint16_t min_raw_pio_cycle;
+	uint16_t min_iordy_pio_cycle;
+
+	uint16_t _res69;
+	uint16_t _res70;
+	uint16_t _res71;
+	uint16_t _res72;
+	uint16_t _res73;
+	uint16_t _res74;
+
+	uint16_t queue_depth;
+	uint16_t _res76[1 + 79 - 76];
+	uint16_t version_maj;
+	uint16_t version_min;
+	uint16_t cmd_set0;
+	uint16_t cmd_set1;
+	uint16_t csf_sup_ext;
+	uint16_t csf_enabled0;
+	uint16_t csf_enabled1;
+	uint16_t csf_default;
+	uint16_t udma;
+
+	uint16_t _res89[1 + 99 - 89];
+
+	/* Total number of blocks in LBA-48 addressing */
+	uint16_t total_lba48_0;
+	uint16_t total_lba48_1;
+	uint16_t total_lba48_2;
+	uint16_t total_lba48_3;
+
+	/* Note: more fields are defined in ATA/ATAPI-7 */
+	uint16_t _res104[1 + 127 - 104];
+	uint16_t _vs128[1 + 159 - 128];
+	uint16_t _res160[1 + 255 - 160];
+} identify_data_t;
+
+/** Capability bits for register device. */
+enum ata_regdev_caps {
+	rd_cap_iordy		= 0x0800,
+	rd_cap_iordy_cbd	= 0x0400,
+	rd_cap_lba		= 0x0200,
+	rd_cap_dma		= 0x0100
+};
+
+/** Capability bits for packet device. */
+enum ata_pktdev_caps {
+	pd_cap_ildma		= 0x8000,
+	pd_cap_cmdqueue		= 0x4000,
+	pd_cap_overlap		= 0x2000,
+	pd_cap_need_softreset	= 0x1000,	/* Obsolete (ATAPI-6) */
+	pd_cap_iordy		= 0x0800,
+	pd_cap_iordy_dis	= 0x0400,
+	pd_cap_lba		= 0x0200,	/* Must be on */
+	pd_cap_dma		= 0x0100
+};
+
+/** Bits of @c identify_data_t.cmd_set1 */
+enum ata_cs1 {
+	cs1_addr48	= 0x0400	/**< 48-bit address feature set */
+};
+
+/** Extract value of device type from scsi_std_inquiry_data_t.pqual_devtype */
+#define INQUIRY_PDEV_TYPE(val) ((val) & 0x1f)
+
+enum ata_pdev_signature {
+	/**
+	 * Signature put by a packet device in byte count register
+	 * in response to Identify command.
+	 */
+	PDEV_SIGNATURE_BC	= 0xEB14
+};
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/ata/meson.build
===================================================================
--- uspace/lib/ata/meson.build	(revision 2791fbb7c1f204fd66ed105c6cc1ddf9e8f5bedf)
+++ uspace/lib/ata/meson.build	(revision 2791fbb7c1f204fd66ed105c6cc1ddf9e8f5bedf)
@@ -0,0 +1,32 @@
+#
+# Copyright (c) 2024 Jiri Svoboda
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# - Redistributions of source code must retain the above copyright
+#   notice, this list of conditions and the following disclaimer.
+# - Redistributions in binary form must reproduce the above copyright
+#   notice, this list of conditions and the following disclaimer in the
+#   documentation and/or other materials provided with the distribution.
+# - The name of the author may not be used to endorse or promote products
+#   derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+
+deps = [ 'device', 'scsi' ]
+src = files(
+	'src/ata.c'
+)
Index: uspace/lib/ata/src/ata.c
===================================================================
--- uspace/lib/ata/src/ata.c	(revision 2791fbb7c1f204fd66ed105c6cc1ddf9e8f5bedf)
+++ uspace/lib/ata/src/ata.c	(revision 2791fbb7c1f204fd66ed105c6cc1ddf9e8f5bedf)
@@ -0,0 +1,1540 @@
+/*
+ * Copyright (c) 2024 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup libata
+ * @{
+ */
+
+/**
+ * @file
+ * @brief ATA disk driver library
+ *
+ * This driver library implements the generic part of ATA/ATAPI. It is
+ * meant to be used by a driver which implements the actual transport
+ * (such as ISA, PCI).
+ *
+ * This driver libary supports CHS, 28-bit and 48-bit LBA addressing,
+ * as well as PACKET devices. It supports PIO transfers and IRQ.
+ *
+ * There is no support DMA, S.M.A.R.T or removable devices.
+ *
+ * This driver is based on the ATA-1, ATA-2, ATA-3 and ATA/ATAPI-4 through 7
+ * standards, as published by the ANSI, NCITS and INCITS standards bodies,
+ * which are freely available. This driver contains no vendor-specific
+ * code at this moment.
+ */
+
+#include <bd_srv.h>
+#include <byteorder.h>
+#include <errno.h>
+#include <macros.h>
+#include <scsi/mmc.h>
+#include <scsi/sbc.h>
+#include <scsi/spc.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "ata/ata.h"
+#include "ata/ata_hw.h"
+
+#define MSG_BUF_SIZE 256
+
+/**
+ * Size of data returned from Identify Device or Identify Packet Device
+ * command.
+ */
+static const size_t identify_data_size = 512;
+static void ata_msg_note(ata_channel_t *, const char *, ...);
+static void ata_msg_debug(ata_channel_t *, const char *, ...);
+static void ata_msg_warn(ata_channel_t *, const char *, ...);
+static void ata_msg_error(ata_channel_t *, const char *, ...);
+static errno_t ata_device_add(ata_device_t *);
+static errno_t ata_device_remove(ata_device_t *);
+static void ata_read_data_16(ata_channel_t *, uint16_t *, size_t);
+static void ata_write_data_16(ata_channel_t *, uint16_t *, size_t);
+static uint8_t ata_read_cmd_8(ata_channel_t *, uint16_t);
+static void ata_write_cmd_8(ata_channel_t *, uint16_t, uint8_t);
+
+static errno_t ata_bd_init_irq(ata_channel_t *);
+static void ata_bd_fini_irq(ata_channel_t *);
+
+static errno_t ata_bd_open(bd_srvs_t *, bd_srv_t *);
+static errno_t ata_bd_close(bd_srv_t *);
+static errno_t ata_bd_read_blocks(bd_srv_t *, uint64_t, size_t, void *, size_t);
+static errno_t ata_bd_read_toc(bd_srv_t *, uint8_t, void *, size_t);
+static errno_t ata_bd_write_blocks(bd_srv_t *, uint64_t, size_t, const void *,
+    size_t);
+static errno_t ata_bd_get_block_size(bd_srv_t *, size_t *);
+static errno_t ata_bd_get_num_blocks(bd_srv_t *, aoff64_t *);
+static errno_t ata_bd_sync_cache(bd_srv_t *, aoff64_t, size_t);
+
+static errno_t ata_rcmd_read(ata_device_t *, uint64_t, size_t, void *);
+static errno_t ata_rcmd_write(ata_device_t *, uint64_t, size_t,
+    const void *);
+static errno_t ata_rcmd_flush_cache(ata_device_t *);
+static errno_t ata_device_init(ata_channel_t *, ata_device_t *, int);
+static errno_t ata_identify_dev(ata_device_t *, void *);
+static errno_t ata_identify_pkt_dev(ata_device_t *, void *);
+static errno_t ata_cmd_packet(ata_device_t *, const void *, size_t, void *,
+    size_t, size_t *);
+static errno_t ata_pcmd_inquiry(ata_device_t *, void *, size_t, size_t *);
+static errno_t ata_pcmd_read_12(ata_device_t *, uint64_t, size_t, void *, size_t);
+static errno_t ata_pcmd_read_capacity(ata_device_t *, uint64_t *, size_t *);
+static errno_t ata_pcmd_read_toc(ata_device_t *, uint8_t, void *, size_t);
+static void disk_print_summary(ata_device_t *);
+static size_t ata_disk_maxnb(ata_device_t *);
+static errno_t coord_calc(ata_device_t *, uint64_t, block_coord_t *);
+static void coord_sc_program(ata_channel_t *, const block_coord_t *, uint16_t);
+static errno_t wait_status(ata_channel_t *, unsigned, unsigned, uint8_t *,
+    unsigned);
+static errno_t wait_irq(ata_channel_t *, uint8_t *, unsigned);
+
+static bd_ops_t ata_bd_ops = {
+	.open = ata_bd_open,
+	.close = ata_bd_close,
+	.read_blocks = ata_bd_read_blocks,
+	.read_toc = ata_bd_read_toc,
+	.write_blocks = ata_bd_write_blocks,
+	.get_block_size = ata_bd_get_block_size,
+	.get_num_blocks = ata_bd_get_num_blocks,
+	.sync_cache = ata_bd_sync_cache
+};
+
+static ata_device_t *bd_srv_device(bd_srv_t *bd)
+{
+	return (ata_device_t *)bd->srvs->sarg;
+}
+
+static int disk_dev_idx(ata_device_t *device)
+{
+	return (device->device_id & 1);
+}
+
+/** Create ATA channel.
+ *
+ * @param params Channel creation parameters
+ * @param rchan Place to store pointer to new channel
+ * @return EOK on success or an error code
+ */
+errno_t ata_channel_create(ata_params_t *params, ata_channel_t **rchan)
+{
+	ata_channel_t *chan;
+
+	chan = calloc(1, sizeof(ata_channel_t));
+	if (chan == NULL)
+		return ENOMEM;
+
+	chan->params = *params;
+	ata_msg_debug(chan, "ata_channel_create()");
+
+	fibril_mutex_initialize(&chan->lock);
+	fibril_mutex_initialize(&chan->irq_lock);
+	fibril_condvar_initialize(&chan->irq_cv);
+
+	*rchan = chan;
+	return EOK;
+}
+
+/** Initialize ATA channel.
+ *
+ * @param params Channel creation parameters
+ * @param rchan Place to store pointer to new channel
+ * @return EOK on success or an error code
+ */
+errno_t ata_channel_initialize(ata_channel_t *chan)
+{
+	int i;
+	errno_t rc;
+	int n_disks;
+	bool irq_inited = false;
+
+	ata_msg_debug(chan, "ata_channel_initialize()");
+
+	rc = ata_bd_init_irq(chan);
+	if (rc != EOK)
+		return rc;
+
+	irq_inited = true;
+
+	for (i = 0; i < MAX_DEVICES; i++) {
+		ata_msg_debug(chan, "Identify drive %d...", i);
+
+		rc = ata_device_init(chan, &chan->device[i], i);
+
+		if (rc == EOK) {
+			disk_print_summary(&chan->device[i]);
+		} else {
+			ata_msg_debug(chan, "Not found.");
+		}
+	}
+
+	n_disks = 0;
+
+	for (i = 0; i < MAX_DEVICES; i++) {
+		/* Skip unattached devices. */
+		if (chan->device[i].present == false)
+			continue;
+
+		rc = ata_device_add(&chan->device[i]);
+		if (rc != EOK) {
+			ata_msg_error(chan, "Unable to add device %d.", i);
+			goto error;
+		}
+		++n_disks;
+	}
+
+	if (n_disks == 0) {
+		ata_msg_warn(chan, "No devices detected.");
+		rc = ENOENT;
+		goto error;
+	}
+
+	return EOK;
+error:
+	for (i = 0; i < MAX_DEVICES; i++) {
+		if (ata_device_add(&chan->device[i]) != EOK) {
+			ata_msg_error(chan, "Unable to remove device %d.", i);
+		}
+	}
+	if (irq_inited)
+		ata_bd_fini_irq(chan);
+	return rc;
+}
+
+/** Destroy ATA channel. */
+errno_t ata_channel_destroy(ata_channel_t *chan)
+{
+	int i;
+	errno_t rc;
+
+	ata_msg_debug(chan, ": ata_channel_destroy()");
+
+	fibril_mutex_lock(&chan->lock);
+
+	for (i = 0; i < MAX_DEVICES; i++) {
+		rc = ata_device_remove(&chan->device[i]);
+		if (rc != EOK) {
+			ata_msg_error(chan, "Unable to remove device %d.", i);
+			return rc;
+		}
+	}
+
+	ata_bd_fini_irq(chan);
+	fibril_mutex_unlock(&chan->lock);
+
+	return EOK;
+}
+
+/** Add ATA device.
+ *
+ * @param d Device
+ * @return EOK on success or an error code
+ */
+static errno_t ata_device_add(ata_device_t *d)
+{
+	bd_srvs_init(&d->bds);
+	d->bds.ops = &ata_bd_ops;
+	d->bds.sarg = (void *)d;
+
+	return d->chan->params.add_device(d->chan->params.arg, d->device_id,
+	    (void *)d);
+}
+
+/** Remove ATA device.
+ *
+ * @param d Device
+ * @return EOK on success or an error code
+ */
+static errno_t ata_device_remove(ata_device_t *d)
+{
+	return d->chan->params.remove_device(d->chan->params.arg, d->device_id);
+}
+
+/** Read 16 bits from data port.
+ *
+ * @param chan ATA channel
+ * @param buf Buffer to hold data
+ * @param nwords Number of words to read
+ */
+static void ata_read_data_16(ata_channel_t *chan, uint16_t *buf,
+    size_t nwords)
+{
+	chan->params.read_data_16(chan->params.arg, buf, nwords);
+}
+
+/** Write 16 bits to data port.
+ *
+ * @param chan ATA channel
+ * @param data Data
+ * @param nwords Number of words to write
+ */
+static void ata_write_data_16(ata_channel_t *chan, uint16_t *data,
+    size_t nwords)
+{
+	chan->params.write_data_16(chan->params.arg, data, nwords);
+}
+
+/** Read 8 bits from 8-bit command port.
+ *
+ * @param chan ATA channel
+ * @param port Port number
+ * @return 8-bit register value
+ */
+static uint8_t ata_read_cmd_8(ata_channel_t *chan, uint16_t port)
+{
+	return chan->params.read_cmd_8(chan->params.arg, port);
+}
+
+/** Write 8 bits to 8-bit command port.
+ *
+ * @param chan ATA channel
+ * @param port Port number
+ * @param value Register value
+ */
+static void ata_write_cmd_8(ata_channel_t *chan, uint16_t port, uint8_t value)
+{
+	return chan->params.write_cmd_8(chan->params.arg, port, value);
+}
+
+/** Log a notice message.
+ *
+ * @param chan Channel
+ * @param fmt Format
+ */
+static void ata_msg_note(ata_channel_t *chan, const char *fmt, ...)
+{
+	va_list ap;
+	char buf[MSG_BUF_SIZE];
+
+	va_start(ap, fmt);
+	vsnprintf(buf, sizeof(buf), fmt, ap);
+	va_end(ap);
+
+	chan->params.msg_note(chan->params.arg, buf);
+}
+
+/** Log a debug message.
+ *
+ * @param chan Channel
+ * @param fmt Format
+ */
+static void ata_msg_debug(ata_channel_t *chan, const char *fmt, ...)
+{
+	va_list ap;
+	char buf[MSG_BUF_SIZE];
+
+	va_start(ap, fmt);
+	vsnprintf(buf, sizeof(buf), fmt, ap);
+	va_end(ap);
+
+	chan->params.msg_debug(chan->params.arg, buf);
+}
+
+/** Log a warning message.
+ *
+ * @param chan Channel
+ * @param fmt Format
+ */
+static void ata_msg_warn(ata_channel_t *chan, const char *fmt, ...)
+{
+	va_list ap;
+	char buf[MSG_BUF_SIZE];
+
+	va_start(ap, fmt);
+	vsnprintf(buf, sizeof(buf), fmt, ap);
+	va_end(ap);
+
+	chan->params.msg_warn(chan->params.arg, buf);
+}
+
+/** Log an error message.
+ *
+ * @param chan Channel
+ * @param fmt Format
+ */
+static void ata_msg_error(ata_channel_t *chan, const char *fmt, ...)
+{
+	va_list ap;
+	char buf[MSG_BUF_SIZE];
+
+	va_start(ap, fmt);
+	vsnprintf(buf, sizeof(buf), fmt, ap);
+	va_end(ap);
+
+	chan->params.msg_error(chan->params.arg, buf);
+}
+
+/** Print one-line device summary. */
+static void disk_print_summary(ata_device_t *d)
+{
+	uint64_t mbytes;
+	char *atype = NULL;
+	char *cap = NULL;
+	int rc;
+
+	if (d->dev_type == ata_reg_dev) {
+		switch (d->amode) {
+		case am_chs:
+			rc = asprintf(&atype, "CHS %u cylinders, %u heads, "
+			    "%u sectors", d->geom.cylinders, d->geom.heads,
+			    d->geom.sectors);
+			if (rc < 0) {
+				/* Out of memory */
+				atype = NULL;
+			}
+			break;
+		case am_lba28:
+			atype = str_dup("LBA-28");
+			break;
+		case am_lba48:
+			atype = str_dup("LBA-48");
+			break;
+		}
+	} else {
+		atype = str_dup("PACKET");
+	}
+
+	if (atype == NULL)
+		return;
+
+	mbytes = d->blocks / (2 * 1024);
+	if (mbytes > 0) {
+		rc = asprintf(&cap, " %" PRIu64 " MB.", mbytes);
+		if (rc < 0) {
+			cap = NULL;
+			goto cleanup;
+		}
+	}
+
+	ata_msg_note(d->chan, "%s: %s %" PRIu64 " blocks%s", d->model, atype,
+	    d->blocks, cap);
+cleanup:
+	free(atype);
+	free(cap);
+}
+
+/** Initialize IRQ. */
+static errno_t ata_bd_init_irq(ata_channel_t *chan)
+{
+	return chan->params.irq_enable(chan->params.arg);
+}
+
+/** Clean up IRQ. */
+static void ata_bd_fini_irq(ata_channel_t *chan)
+{
+	(void)chan->params.irq_disable(chan->params.arg);
+}
+
+/** Initialize a device.
+ *
+ * Probes for a device, determines its parameters and initializes
+ * the device structure.
+ */
+static errno_t ata_device_init(ata_channel_t *chan, ata_device_t *d,
+    int device_id)
+{
+	identify_data_t idata;
+	uint8_t model[40];
+	scsi_std_inquiry_data_t inq_data;
+	size_t isize;
+	uint16_t w;
+	uint8_t c;
+	uint16_t bc;
+	uint64_t nblocks;
+	size_t block_size;
+	size_t pos, len;
+	errno_t rc;
+	unsigned i;
+
+	d->chan = chan;
+	d->device_id = device_id;
+	d->present = false;
+
+	/* Try identify command. */
+	rc = ata_identify_dev(d, &idata);
+	if (rc == EOK) {
+		/* Success. It's a register (non-packet) device. */
+		ata_msg_debug(chan, "ATA register-only device found.");
+		d->dev_type = ata_reg_dev;
+	} else if (rc == EIO) {
+		/*
+		 * There is something, but not a register device. Check to see
+		 * whether the IDENTIFY command left the packet signature in
+		 * the registers in case this is a packet device.
+		 *
+		 * According to the ATA specification, the LBA low and
+		 * interrupt reason registers should be set to 0x01. However,
+		 * there are many devices that do not follow this and only set
+		 * the byte count registers. So, only check these.
+		 */
+		bc = ((uint16_t)ata_read_cmd_8(chan, REG_CYLINDER_HIGH) << 8) |
+		    ata_read_cmd_8(chan, REG_CYLINDER_LOW);
+
+		if (bc == PDEV_SIGNATURE_BC) {
+			rc = ata_identify_pkt_dev(d, &idata);
+			if (rc == EOK) {
+				/* We have a packet device. */
+				d->dev_type = ata_pkt_dev;
+			} else {
+				return EIO;
+			}
+		} else {
+			/* Nope. Something's there, but not recognized. */
+			return EIO;
+		}
+	} else {
+		/* Operation timed out. That means there is no device there. */
+		return EIO;
+	}
+
+	if (d->dev_type == ata_pkt_dev) {
+		/* Packet device */
+		d->amode = 0;
+
+		d->geom.cylinders = 0;
+		d->geom.heads = 0;
+		d->geom.sectors = 0;
+
+		d->blocks = 0;
+	} else if ((idata.caps & rd_cap_lba) == 0) {
+		/* Device only supports CHS addressing. */
+		d->amode = am_chs;
+
+		d->geom.cylinders = idata.cylinders;
+		d->geom.heads = idata.heads;
+		d->geom.sectors = idata.sectors;
+
+		d->blocks = d->geom.cylinders * d->geom.heads * d->geom.sectors;
+	} else if ((idata.cmd_set1 & cs1_addr48) == 0) {
+		/* Device only supports LBA-28 addressing. */
+		d->amode = am_lba28;
+
+		d->geom.cylinders = 0;
+		d->geom.heads = 0;
+		d->geom.sectors = 0;
+
+		d->blocks =
+		    (uint32_t) idata.total_lba28_0 |
+		    ((uint32_t) idata.total_lba28_1 << 16);
+	} else {
+		/* Device supports LBA-48 addressing. */
+		d->amode = am_lba48;
+
+		d->geom.cylinders = 0;
+		d->geom.heads = 0;
+		d->geom.sectors = 0;
+
+		d->blocks =
+		    (uint64_t) idata.total_lba48_0 |
+		    ((uint64_t) idata.total_lba48_1 << 16) |
+		    ((uint64_t) idata.total_lba48_2 << 32) |
+		    ((uint64_t) idata.total_lba48_3 << 48);
+	}
+
+	/*
+	 * Convert model name to string representation.
+	 */
+	for (i = 0; i < 20; i++) {
+		w = idata.model_name[i];
+		model[2 * i] = w >> 8;
+		model[2 * i + 1] = w & 0x00ff;
+	}
+
+	len = 40;
+	while (len > 0 && model[len - 1] == 0x20)
+		--len;
+
+	pos = 0;
+	for (i = 0; i < len; ++i) {
+		c = model[i];
+		if (c >= 0x80)
+			c = '?';
+
+		chr_encode(c, d->model, &pos, 40);
+	}
+	d->model[pos] = '\0';
+
+	if (d->dev_type == ata_pkt_dev) {
+		/* Send inquiry. */
+		rc = ata_pcmd_inquiry(d, &inq_data, sizeof(inq_data), &isize);
+		if (rc != EOK || isize < sizeof(inq_data)) {
+			ata_msg_error(chan, "Device inquiry failed.");
+			d->present = false;
+			return EIO;
+		}
+
+		/* Check device type. */
+		if (INQUIRY_PDEV_TYPE(inq_data.pqual_devtype) != SCSI_DEV_CD_DVD)
+			ata_msg_warn(chan, "Peripheral device type is not CD-ROM.");
+
+		rc = ata_pcmd_read_capacity(d, &nblocks, &block_size);
+		if (rc != EOK) {
+			ata_msg_error(chan, "Read capacity command failed.");
+			d->present = false;
+			return EIO;
+		}
+
+		d->blocks = nblocks;
+		d->block_size = block_size;
+	} else {
+		/* Assume register Read always uses 512-byte blocks. */
+		d->block_size = 512;
+	}
+
+	d->present = true;
+	return EOK;
+}
+
+static errno_t ata_bd_open(bd_srvs_t *bds, bd_srv_t *bd)
+{
+	return EOK;
+}
+
+static errno_t ata_bd_close(bd_srv_t *bd)
+{
+	return EOK;
+}
+
+/** Read multiple blocks from the device. */
+static errno_t ata_bd_read_blocks(bd_srv_t *bd, uint64_t ba, size_t cnt,
+    void *buf, size_t size)
+{
+	ata_device_t *device = bd_srv_device(bd);
+	size_t maxnb;
+	size_t nb;
+	errno_t rc;
+
+	if (size < cnt * device->block_size) {
+		rc = EINVAL;
+		goto error;
+	}
+
+	/* Maximum number of blocks to transfer at the same time */
+	maxnb = ata_disk_maxnb(device);
+	while (cnt > 0) {
+		nb = min(maxnb, cnt);
+		if (device->dev_type == ata_reg_dev) {
+			rc = ata_rcmd_read(device, ba, nb, buf);
+		} else {
+			rc = ata_pcmd_read_12(device, ba, nb, buf,
+			    device->block_size);
+		}
+
+		if (rc != EOK)
+			goto error;
+
+		ba += nb;
+		cnt -= nb;
+		buf += device->block_size * nb;
+	}
+
+	return EOK;
+error:
+	ata_msg_debug(device->chan, "ata_bd_read_blocks: rc=%d", rc);
+	return rc;
+}
+
+/** Read TOC from device. */
+static errno_t ata_bd_read_toc(bd_srv_t *bd, uint8_t session, void *buf, size_t size)
+{
+	ata_device_t *device = bd_srv_device(bd);
+
+	return ata_pcmd_read_toc(device, session, buf, size);
+}
+
+/** Write multiple blocks to the device. */
+static errno_t ata_bd_write_blocks(bd_srv_t *bd, uint64_t ba, size_t cnt,
+    const void *buf, size_t size)
+{
+	ata_device_t *device = bd_srv_device(bd);
+	size_t maxnb;
+	size_t nb;
+	errno_t rc;
+
+	if (device->dev_type != ata_reg_dev)
+		return ENOTSUP;
+
+	if (size < cnt * device->block_size)
+		return EINVAL;
+
+	/* Maximum number of blocks to transfer at the same time */
+	maxnb = ata_disk_maxnb(device);
+	while (cnt > 0) {
+		nb = min(maxnb, cnt);
+		rc = ata_rcmd_write(device, ba, nb, buf);
+		if (rc != EOK)
+			return rc;
+
+		ba += nb;
+		cnt -= nb;
+		buf += device->block_size * nb;
+	}
+
+	return EOK;
+}
+
+/** Get device block size. */
+static errno_t ata_bd_get_block_size(bd_srv_t *bd, size_t *rbsize)
+{
+	ata_device_t *device = bd_srv_device(bd);
+
+	*rbsize = device->block_size;
+	return EOK;
+}
+
+/** Get device number of blocks. */
+static errno_t ata_bd_get_num_blocks(bd_srv_t *bd, aoff64_t *rnb)
+{
+	ata_device_t *device = bd_srv_device(bd);
+
+	*rnb = device->blocks;
+	return EOK;
+}
+
+/** Flush cache. */
+static errno_t ata_bd_sync_cache(bd_srv_t *bd, uint64_t ba, size_t cnt)
+{
+	ata_device_t *device = bd_srv_device(bd);
+
+	/* ATA cannot flush just some blocks, we just flush everything. */
+	(void)ba;
+	(void)cnt;
+
+	return ata_rcmd_flush_cache(device);
+}
+
+/** PIO data-in command protocol. */
+static errno_t ata_pio_data_in(ata_device_t *device, void *obuf, size_t obuf_size,
+    size_t blk_size, size_t nblocks)
+{
+	ata_channel_t *chan = device->chan;
+	uint8_t status;
+	errno_t rc;
+
+	assert(nblocks > 0);
+	assert(blk_size % 2 == 0);
+
+	while (nblocks > 0) {
+		if (chan->params.have_irq)
+			rc = wait_irq(chan, &status, TIMEOUT_BSY);
+		else
+			rc = wait_status(chan, 0, ~SR_BSY, &status, TIMEOUT_BSY);
+
+		if (rc != EOK) {
+			ata_msg_debug(chan, "wait_irq/wait_status failed");
+			return EIO;
+		}
+
+		if ((status & SR_DRQ) == 0) {
+			ata_msg_debug(chan, "DRQ == 0");
+			break;
+		}
+
+		/* Read data from the device buffer. */
+		ata_read_data_16(chan, (uint16_t *)obuf, blk_size / 2);
+		obuf += blk_size;
+
+		--nblocks;
+	}
+
+	if ((status & SR_ERR) != 0) {
+		ata_msg_debug(chan, "status & SR_ERR != 0");
+		return EIO;
+	}
+	if (nblocks > 0) {
+		ata_msg_debug(chan, "remaining nblocks = %zu", nblocks);
+		return EIO;
+	}
+
+	return EOK;
+}
+
+/** PIO data-out command protocol. */
+static errno_t ata_pio_data_out(ata_device_t *device, const void *buf, size_t buf_size,
+    size_t blk_size, size_t nblocks)
+{
+	ata_channel_t *chan = device->chan;
+	uint8_t status;
+	errno_t rc;
+
+	assert(nblocks > 0);
+	assert(blk_size % 2 == 0);
+
+	rc = wait_status(chan, 0, ~SR_BSY, &status, TIMEOUT_BSY);
+	if (rc != EOK)
+		return EIO;
+
+	while (nblocks > 0) {
+		if ((status & SR_DRQ) == 0) {
+			ata_msg_debug(chan, "pio_data_out: unexpected DRQ=0");
+			break;
+		}
+
+		/* Write data to the device buffer. */
+		ata_write_data_16(chan, (uint16_t *)buf, blk_size / 2);
+		buf += blk_size;
+
+		if (chan->params.have_irq)
+			rc = wait_irq(chan, &status, TIMEOUT_BSY);
+		else
+			rc = wait_status(chan, 0, ~SR_BSY, &status, TIMEOUT_BSY);
+		if (rc != EOK)
+			return EIO;
+
+		--nblocks;
+	}
+
+	if (status & SR_ERR)
+		return EIO;
+	if (nblocks > 0)
+		return EIO;
+
+	return EOK;
+}
+
+/** PIO non-data command protocol. */
+static errno_t ata_pio_nondata(ata_device_t *device)
+{
+	ata_channel_t *chan = device->chan;
+	uint8_t status;
+	errno_t rc;
+
+	if (chan->params.have_irq)
+		rc = wait_irq(chan, &status, TIMEOUT_BSY);
+	else
+		rc = wait_status(chan, 0, ~SR_BSY, &status, TIMEOUT_BSY);
+
+	if (rc != EOK)
+		return EIO;
+
+	if (status & SR_ERR)
+		return EIO;
+
+	return EOK;
+}
+
+/** Issue IDENTIFY DEVICE command.
+ *
+ * Reads @c identify data into the provided buffer. This is used to detect
+ * whether an ATA device is present and if so, to determine its parameters.
+ *
+ * @param device	Device
+ * @param buf		Pointer to a 512-byte buffer.
+ *
+ * @return		ETIMEOUT on timeout (this can mean the device is
+ *			not present). EIO if device responds with error.
+ */
+static errno_t ata_identify_dev(ata_device_t *device, void *buf)
+{
+	ata_channel_t *chan = device->chan;
+	uint8_t status;
+	uint8_t drv_head;
+
+	drv_head = ((disk_dev_idx(device) != 0) ? DHR_DRV : 0);
+
+	if (wait_status(chan, 0, ~SR_BSY, NULL, TIMEOUT_PROBE) != EOK)
+		return ETIMEOUT;
+
+	ata_write_cmd_8(chan, REG_DRIVE_HEAD, drv_head);
+
+	/*
+	 * Do not wait for DRDY to be set in case this is a packet device.
+	 * We determine whether the device is present by waiting for DRQ to be
+	 * set after issuing the command.
+	 */
+	if (wait_status(chan, 0, ~SR_BSY, NULL, TIMEOUT_PROBE) != EOK)
+		return ETIMEOUT;
+
+	ata_write_cmd_8(chan, REG_COMMAND, CMD_IDENTIFY_DRIVE);
+
+	/*
+	 * 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(chan, 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(device, buf, identify_data_size,
+	    identify_data_size, 1);
+}
+
+/** Issue Identify Packet Device command.
+ *
+ * Reads @c identify data into the provided buffer. This is used to detect
+ * whether an ATAPI device is present and if so, to determine its parameters.
+ *
+ * @param device	Device
+ * @param buf		Pointer to a 512-byte buffer.
+ */
+static errno_t ata_identify_pkt_dev(ata_device_t *device, void *buf)
+{
+	ata_channel_t *chan = device->chan;
+	uint8_t drv_head;
+
+	drv_head = ((disk_dev_idx(device) != 0) ? DHR_DRV : 0);
+
+	if (wait_status(chan, 0, ~SR_BSY, NULL, TIMEOUT_PROBE) != EOK)
+		return EIO;
+
+	ata_write_cmd_8(chan, REG_DRIVE_HEAD, drv_head);
+
+	/* For ATAPI commands we do not need to wait for DRDY. */
+	if (wait_status(chan, 0, ~SR_BSY, NULL, TIMEOUT_PROBE) != EOK)
+		return EIO;
+
+	ata_write_cmd_8(chan, REG_COMMAND, CMD_IDENTIFY_PKT_DEV);
+
+	return ata_pio_data_in(device, buf, identify_data_size,
+	    identify_data_size, 1);
+}
+
+/** Issue packet command (i. e. write a command packet to the device).
+ *
+ * Only data-in commands are supported (e.g. inquiry, read).
+ *
+ * @param device	Device
+ * @param obuf		Buffer for storing data read from device
+ * @param obuf_size	Size of obuf in bytes
+ * @param rcvd_size	Place to store number of bytes read or @c NULL
+ *
+ * @return EOK on success, EIO on error.
+ */
+static errno_t ata_cmd_packet(ata_device_t *device, const void *cpkt, size_t cpkt_size,
+    void *obuf, size_t obuf_size, size_t *rcvd_size)
+{
+	ata_channel_t *chan = device->chan;
+	uint8_t status;
+	uint8_t drv_head;
+	size_t data_size;
+	size_t remain;
+	errno_t rc;
+
+	fibril_mutex_lock(&chan->lock);
+
+	/* New value for Drive/Head register */
+	drv_head =
+	    ((disk_dev_idx(device) != 0) ? DHR_DRV : 0);
+
+	if (wait_status(chan, 0, ~SR_BSY, NULL, TIMEOUT_PROBE) != EOK) {
+		fibril_mutex_unlock(&chan->lock);
+		return EIO;
+	}
+
+	ata_write_cmd_8(chan, REG_DRIVE_HEAD, drv_head);
+
+	if (wait_status(chan, 0, ~(SR_BSY | SR_DRQ), NULL, TIMEOUT_BSY) != EOK) {
+		fibril_mutex_unlock(&chan->lock);
+		return EIO;
+	}
+
+	/* Byte count <- max. number of bytes we can read in one transfer. */
+	ata_write_cmd_8(chan, REG_CYLINDER_LOW, 0xfe);
+	ata_write_cmd_8(chan, REG_CYLINDER_HIGH, 0xff);
+
+	ata_write_cmd_8(chan, REG_COMMAND, CMD_PACKET);
+
+	if (wait_status(chan, SR_DRQ, ~SR_BSY, &status, TIMEOUT_BSY) != EOK) {
+		fibril_mutex_unlock(&chan->lock);
+		return EIO;
+	}
+
+	/* Write command packet. */
+	ata_write_data_16(chan, ((uint16_t *) cpkt), (cpkt_size + 1) / 2);
+
+	remain = obuf_size;
+	while (remain > 0) {
+		if (chan->params.have_irq)
+			rc = wait_irq(chan, &status, TIMEOUT_BSY);
+		else
+			rc = wait_status(chan, 0, ~SR_BSY, &status, TIMEOUT_BSY);
+
+		if (rc != EOK) {
+			fibril_mutex_unlock(&chan->lock);
+			return EIO;
+		}
+
+		if ((status & SR_DRQ) == 0)
+			break;
+
+		/* Read byte count. */
+		data_size = (uint16_t) ata_read_cmd_8(chan, REG_CYLINDER_LOW) +
+		    ((uint16_t) ata_read_cmd_8(chan, REG_CYLINDER_HIGH) << 8);
+
+		/* Check whether data fits into output buffer. */
+		if (data_size > obuf_size) {
+			/* Output buffer is too small to store data. */
+			fibril_mutex_unlock(&chan->lock);
+			return EIO;
+		}
+
+		/* Read data from the device buffer. */
+		ata_read_data_16(chan, obuf, (data_size + 1) / 2);
+		obuf += data_size;
+
+		remain -= data_size;
+	}
+
+	if (chan->params.have_irq)
+		rc = wait_irq(chan, &status, TIMEOUT_BSY);
+	else
+		rc = wait_status(chan, 0, ~SR_BSY, &status, TIMEOUT_BSY);
+
+	fibril_mutex_unlock(&chan->lock);
+
+	if (status & SR_ERR)
+		return EIO;
+
+	if (rcvd_size != NULL)
+		*rcvd_size = obuf_size - remain;
+	return EOK;
+}
+
+/** Issue ATAPI Inquiry.
+ *
+ * @param device	Device
+ * @param obuf		Buffer for storing inquiry data read from device
+ * @param obuf_size	Size of obuf in bytes
+ *
+ * @return EOK on success, EIO on error.
+ */
+static errno_t ata_pcmd_inquiry(ata_device_t *device, void *obuf, size_t obuf_size,
+    size_t *rcvd_size)
+{
+	uint8_t cpb[12];
+	scsi_cdb_inquiry_t *cp = (scsi_cdb_inquiry_t *)cpb;
+	errno_t rc;
+
+	memset(cpb, 0, sizeof(cpb));
+
+	/*
+	 * For SFF 8020 compliance the inquiry must be padded to 12 bytes
+	 * and allocation length must fit in one byte.
+	 */
+	cp->op_code = SCSI_CMD_INQUIRY;
+
+	/* Allocation length */
+	cp->alloc_len = host2uint16_t_be(min(obuf_size, 0xff));
+
+	rc = ata_cmd_packet(device, cpb, sizeof(cpb), obuf, obuf_size, rcvd_size);
+	if (rc != EOK)
+		return rc;
+
+	return EOK;
+}
+
+/** Issue ATAPI read capacity(10) command.
+ *
+ * @param device	Device
+ * @param nblocks	Place to store number of blocks
+ * @param block_size	Place to store block size
+ *
+ * @return EOK on success, EIO on error.
+ */
+static errno_t ata_pcmd_read_capacity(ata_device_t *device, uint64_t *nblocks,
+    size_t *block_size)
+{
+	scsi_cdb_read_capacity_10_t cdb;
+	scsi_read_capacity_10_data_t data;
+	size_t rsize;
+	errno_t rc;
+
+	memset(&cdb, 0, sizeof(cdb));
+	cdb.op_code = SCSI_CMD_READ_CAPACITY_10;
+
+	rc = ata_cmd_packet(device, &cdb, sizeof(cdb), &data, sizeof(data), &rsize);
+	if (rc != EOK)
+		return rc;
+
+	if (rsize != sizeof(data))
+		return EIO;
+
+	*nblocks = uint32_t_be2host(data.last_lba) + 1;
+	*block_size = uint32_t_be2host(data.block_size);
+
+	return EOK;
+}
+
+/** Issue ATAPI read(12) command.
+ *
+ * Output buffer must be large enough to hold the data, otherwise the
+ * function will fail.
+ *
+ * @param device	Device
+ * @param ba		Starting block address
+ * @param cnt		Number of blocks to read
+ * @param obuf		Buffer for storing inquiry data read from device
+ * @param obuf_size	Size of obuf in bytes
+ *
+ * @return EOK on success, EIO on error.
+ */
+static errno_t ata_pcmd_read_12(ata_device_t *device, uint64_t ba, size_t cnt,
+    void *obuf, size_t obuf_size)
+{
+	scsi_cdb_read_12_t cp;
+	errno_t rc;
+
+	if (ba > UINT32_MAX)
+		return EINVAL;
+
+	memset(&cp, 0, sizeof(cp));
+
+	cp.op_code = SCSI_CMD_READ_12;
+	cp.lba = host2uint32_t_be(ba);
+	cp.xfer_len = host2uint32_t_be(cnt);
+
+	rc = ata_cmd_packet(device, &cp, sizeof(cp), obuf, obuf_size, NULL);
+	if (rc != EOK)
+		return rc;
+
+	return EOK;
+}
+
+/** Issue ATAPI read TOC command.
+ *
+ * Read TOC in 'multi-session' format (first and last session number
+ * with last session LBA).
+ *
+ * http://suif.stanford.edu/~csapuntz/specs/INF-8020.PDF page 171
+ *
+ * Output buffer must be large enough to hold the data, otherwise the
+ * function will fail.
+ *
+ * @param device	Device
+ * @param session	Starting session
+ * @param obuf		Buffer for storing inquiry data read from device
+ * @param obuf_size	Size of obuf in bytes
+ *
+ * @return EOK on success, EIO on error.
+ */
+static errno_t ata_pcmd_read_toc(ata_device_t *device, uint8_t session,
+    void *obuf, size_t obuf_size)
+{
+	uint8_t cpb[12];
+	scsi_cdb_read_toc_t *cp = (scsi_cdb_read_toc_t *)cpb;
+	errno_t rc;
+
+	memset(cpb, 0, sizeof(cpb));
+
+	cp->op_code = SCSI_CMD_READ_TOC;
+	cp->msf = 0;
+	cp->format = 0x01; /* 0x01 = multi-session mode */
+	cp->track_sess_no = session;
+	cp->alloc_len = host2uint16_t_be(obuf_size);
+	cp->control = 0x40; /* 0x01 = multi-session mode (shifted to MSB) */
+
+	rc = ata_cmd_packet(device, cpb, sizeof(cpb), obuf, obuf_size, NULL);
+	if (rc != EOK)
+		return rc;
+
+	return EOK;
+}
+
+/** Read a physical block from the device.
+ *
+ * @param device	Device
+ * @param ba		Address the first block.
+ * @param cnt		Number of blocks to transfer.
+ * @param buf		Buffer for holding the data.
+ *
+ * @return EOK on success, EIO on error.
+ */
+static errno_t ata_rcmd_read(ata_device_t *device, uint64_t ba, size_t blk_cnt,
+    void *buf)
+{
+	ata_channel_t *chan = device->chan;
+	uint8_t drv_head;
+	block_coord_t bc;
+	errno_t rc;
+
+	/* Silence warning. */
+	memset(&bc, 0, sizeof(bc));
+
+	/* Compute block coordinates. */
+	if (coord_calc(device, ba, &bc) != EOK) {
+		ata_msg_note(chan, "ata_rcmd_read() -> coord_calc failed");
+		return EINVAL;
+	}
+
+	/* New value for Drive/Head register */
+	drv_head =
+	    ((disk_dev_idx(device) != 0) ? DHR_DRV : 0) |
+	    ((device->amode != am_chs) ? DHR_LBA : 0) |
+	    (bc.h & 0x0f);
+
+	fibril_mutex_lock(&chan->lock);
+
+	/* Program a Read Sectors operation. */
+
+	if (wait_status(chan, 0, ~SR_BSY, NULL, TIMEOUT_BSY) != EOK) {
+		fibril_mutex_unlock(&chan->lock);
+		ata_msg_note(chan, "ata_rcmd_read() -> wait_status failed");
+		return EIO;
+	}
+
+	ata_write_cmd_8(chan, REG_DRIVE_HEAD, drv_head);
+
+	if (wait_status(chan, SR_DRDY, ~SR_BSY, NULL, TIMEOUT_DRDY) != EOK) {
+		fibril_mutex_unlock(&chan->lock);
+		ata_msg_note(chan, "ata_rcmd_read() -> wait_status 2 failed");
+		return EIO;
+	}
+
+	/* Program block coordinates into the device. */
+	coord_sc_program(chan, &bc, blk_cnt);
+
+	ata_write_cmd_8(chan, REG_COMMAND, device->amode == am_lba48 ?
+	    CMD_READ_SECTORS_EXT : CMD_READ_SECTORS);
+
+	rc = ata_pio_data_in(device, buf, blk_cnt * device->block_size,
+	    device->block_size, blk_cnt);
+
+	fibril_mutex_unlock(&chan->lock);
+
+	if (rc != EOK)
+		ata_msg_note(chan, "ata_rcmd_read() -> pio_data_in->%d", rc);
+	return rc;
+}
+
+/** Write a physical block to the device.
+ *
+ * @param device	Device
+ * @param ba		Address of the first block.
+ * @param cnt		Number of blocks to transfer.
+ * @param buf		Buffer holding the data to write.
+ *
+ * @return EOK on success, EIO on error.
+ */
+static errno_t ata_rcmd_write(ata_device_t *device, uint64_t ba, size_t cnt,
+    const void *buf)
+{
+	ata_channel_t *chan = device->chan;
+	uint8_t drv_head;
+	block_coord_t bc;
+	errno_t rc;
+
+	/* Silence warning. */
+	memset(&bc, 0, sizeof(bc));
+
+	/* Compute block coordinates. */
+	if (coord_calc(device, ba, &bc) != EOK)
+		return EINVAL;
+
+	/* New value for Drive/Head register */
+	drv_head =
+	    ((disk_dev_idx(device) != 0) ? DHR_DRV : 0) |
+	    ((device->amode != am_chs) ? DHR_LBA : 0) |
+	    (bc.h & 0x0f);
+
+	fibril_mutex_lock(&chan->lock);
+
+	/* Program a Write Sectors operation. */
+
+	if (wait_status(chan, 0, ~SR_BSY, NULL, TIMEOUT_BSY) != EOK) {
+		fibril_mutex_unlock(&chan->lock);
+		return EIO;
+	}
+
+	ata_write_cmd_8(chan, REG_DRIVE_HEAD, drv_head);
+
+	if (wait_status(chan, SR_DRDY, ~SR_BSY, NULL, TIMEOUT_DRDY) != EOK) {
+		fibril_mutex_unlock(&chan->lock);
+		return EIO;
+	}
+
+	/* Program block coordinates into the device. */
+	coord_sc_program(chan, &bc, cnt);
+
+	ata_write_cmd_8(chan, REG_COMMAND, device->amode == am_lba48 ?
+	    CMD_WRITE_SECTORS_EXT : CMD_WRITE_SECTORS);
+
+	rc = ata_pio_data_out(device, buf, cnt * device->block_size,
+	    device->block_size, cnt);
+
+	fibril_mutex_unlock(&chan->lock);
+	return rc;
+}
+
+/** Flush cached data to nonvolatile storage.
+ *
+ * @param device	Device
+ *
+ * @return EOK on success, EIO on error.
+ */
+static errno_t ata_rcmd_flush_cache(ata_device_t *device)
+{
+	ata_channel_t *chan = device->chan;
+	uint8_t drv_head;
+	errno_t rc;
+
+	/* New value for Drive/Head register */
+	drv_head =
+	    (disk_dev_idx(device) != 0) ? DHR_DRV : 0;
+
+	fibril_mutex_lock(&chan->lock);
+
+	/* Program a Flush Cache operation. */
+
+	if (wait_status(chan, 0, ~SR_BSY, NULL, TIMEOUT_BSY) != EOK) {
+		fibril_mutex_unlock(&chan->lock);
+		return EIO;
+	}
+
+	ata_write_cmd_8(chan, REG_DRIVE_HEAD, drv_head);
+
+	if (wait_status(chan, SR_DRDY, ~SR_BSY, NULL, TIMEOUT_DRDY) != EOK) {
+		fibril_mutex_unlock(&chan->lock);
+		return EIO;
+	}
+
+	ata_write_cmd_8(chan, REG_COMMAND, CMD_FLUSH_CACHE);
+
+	rc = ata_pio_nondata(device);
+
+	fibril_mutex_unlock(&chan->lock);
+	return rc;
+}
+
+/** Get the maximum number of blocks to be transferred in one I/O
+ *
+ * @param d Device
+ * @return Maximum number of blocks
+ */
+static size_t ata_disk_maxnb(ata_device_t *d)
+{
+	size_t maxnb;
+
+	maxnb = 0;
+
+	if (d->dev_type == ata_pkt_dev) {
+		/* Could be more depending on SCSI command support */
+		maxnb = 0x100;
+	} else {
+		switch (d->amode) {
+		case am_chs:
+		case am_lba28:
+			maxnb = 0x100;
+			break;
+		case am_lba48:
+			maxnb = 0x10000;
+			break;
+		}
+	}
+
+	/*
+	 * If using DMA, this needs to be further restricted not to
+	 * exceed DMA buffer size.
+	 */
+	return maxnb;
+}
+
+/** Calculate block coordinates.
+ *
+ * Calculates block coordinates in the best coordinate system supported
+ * by the device. These can be later programmed into the device using
+ * @c coord_sc_program().
+ *
+ * @return EOK on success or EINVAL if block index is past end of device.
+ */
+static errno_t coord_calc(ata_device_t *d, uint64_t ba, block_coord_t *bc)
+{
+	uint64_t c;
+	uint64_t idx;
+
+	/* Check device bounds. */
+	if (ba >= d->blocks)
+		return EINVAL;
+
+	bc->amode = d->amode;
+
+	switch (d->amode) {
+	case am_chs:
+		/* Compute CHS coordinates. */
+		c = ba / (d->geom.heads * d->geom.sectors);
+		idx = ba % (d->geom.heads * d->geom.sectors);
+
+		bc->cyl_lo = c & 0xff;
+		bc->cyl_hi = (c >> 8) & 0xff;
+		bc->h      = (idx / d->geom.sectors) & 0x0f;
+		bc->sector = (1 + (idx % d->geom.sectors)) & 0xff;
+		break;
+
+	case am_lba28:
+		/* Compute LBA-28 coordinates. */
+		bc->c0 = ba & 0xff;		/* bits 0-7 */
+		bc->c1 = (ba >> 8) & 0xff;	/* bits 8-15 */
+		bc->c2 = (ba >> 16) & 0xff;	/* bits 16-23 */
+		bc->h  = (ba >> 24) & 0x0f;	/* bits 24-27 */
+		break;
+
+	case am_lba48:
+		/* Compute LBA-48 coordinates. */
+		bc->c0 = ba & 0xff;		/* bits 0-7 */
+		bc->c1 = (ba >> 8) & 0xff;	/* bits 8-15 */
+		bc->c2 = (ba >> 16) & 0xff;	/* bits 16-23 */
+		bc->c3 = (ba >> 24) & 0xff;	/* bits 24-31 */
+		bc->c4 = (ba >> 32) & 0xff;	/* bits 32-39 */
+		bc->c5 = (ba >> 40) & 0xff;	/* bits 40-47 */
+		bc->h  = 0;
+		break;
+	}
+
+	return EOK;
+}
+
+/** Program block coordinates and sector count into ATA registers.
+ *
+ * Note that bc->h must be programmed separately into the device/head register.
+ *
+ * @param chan		Channel
+ * @param bc		Block coordinates
+ * @param scnt		Sector count
+ */
+static void coord_sc_program(ata_channel_t *chan, const block_coord_t *bc,
+    uint16_t scnt)
+{
+	if (bc->amode == am_lba48) {
+		/* Write high-order bits. */
+		ata_write_cmd_8(chan, REG_SECTOR_COUNT, scnt >> 8);
+		ata_write_cmd_8(chan, REG_SECTOR_NUMBER, bc->c3);
+		ata_write_cmd_8(chan, REG_CYLINDER_LOW, bc->c4);
+		ata_write_cmd_8(chan, REG_CYLINDER_HIGH, bc->c5);
+	}
+
+	/* Write low-order bits. */
+	ata_write_cmd_8(chan, REG_SECTOR_COUNT, scnt & 0x00ff);
+	ata_write_cmd_8(chan, REG_SECTOR_NUMBER, bc->c0);
+	ata_write_cmd_8(chan, REG_CYLINDER_LOW, bc->c1);
+	ata_write_cmd_8(chan, REG_CYLINDER_HIGH, bc->c2);
+}
+
+/** Wait until some status bits are set and some are reset.
+ *
+ * Example: wait_status(chan, SR_DRDY, ~SR_BSY, ...) waits for SR_DRDY to become
+ * set and SR_BSY to become reset.
+ *
+ * @param chan		Channel
+ * @param set		Combination if bits which must be all set.
+ * @param n_reset	Negated combination of bits which must be all reset.
+ * @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_status(ata_channel_t *chan, unsigned set, unsigned n_reset,
+    uint8_t *pstatus, unsigned timeout)
+{
+	uint8_t status;
+	int cnt;
+
+	status = ata_read_cmd_8(chan, REG_STATUS);
+
+	/*
+	 * This is crude, yet simple. First try with 1us delays
+	 * (most likely the device will respond very fast). If not,
+	 * start trying every 10 ms.
+	 */
+
+	cnt = 100;
+	while ((status & ~n_reset) != 0 || (status & set) != set) {
+		--cnt;
+		if (cnt <= 0)
+			break;
+
+		status = ata_read_cmd_8(chan, REG_STATUS);
+	}
+
+	cnt = timeout;
+	while ((status & ~n_reset) != 0 || (status & set) != set) {
+		fibril_usleep(10000);
+		--cnt;
+		if (cnt <= 0)
+			break;
+
+		status = ata_read_cmd_8(chan, REG_STATUS);
+	}
+
+	if (pstatus)
+		*pstatus = status;
+
+	if (cnt == 0)
+		return EIO;
+
+	return EOK;
+}
+
+/** Wait for IRQ and return status.
+ *
+ * @param chan		Channel
+ * @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_channel_t *chan, uint8_t *pstatus, unsigned timeout)
+{
+	fibril_mutex_lock(&chan->irq_lock);
+	while (!chan->irq_fired)
+		fibril_condvar_wait(&chan->irq_cv, &chan->irq_lock);
+
+	chan->irq_fired = false;
+	*pstatus = chan->irq_status;
+	fibril_mutex_unlock(&chan->irq_lock);
+	return EOK;
+}
+
+/** Interrupt handler.
+ *
+ * @param chan ATA channel
+ * @param status Status read by interrupt handler
+ */
+void ata_channel_irq(ata_channel_t *chan, uint8_t status)
+{
+	fibril_mutex_lock(&chan->irq_lock);
+	chan->irq_fired = true;
+	chan->irq_status = status;
+	fibril_mutex_unlock(&chan->irq_lock);
+	fibril_condvar_broadcast(&chan->irq_cv);
+}
+
+/** Block device connection handler */
+void ata_connection(ipc_call_t *icall, ata_device_t *device)
+{
+	bd_conn(icall, &device->bds);
+}
+
+/**
+ * @}
+ */
Index: uspace/lib/meson.build
===================================================================
--- uspace/lib/meson.build	(revision 64cf7a3bd1e3e66b8aa3b82148faced27efb35bb)
+++ uspace/lib/meson.build	(revision 2791fbb7c1f204fd66ed105c6cc1ddf9e8f5bedf)
@@ -111,4 +111,5 @@
 	'virtio',
 
+	'ata',
 	'ieee80211',
 	'ddev',
