Index: kernel/arch/amd64/include/arch/mm/pat.h
===================================================================
--- kernel/arch/amd64/include/arch/mm/pat.h	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ kernel/arch/amd64/include/arch/mm/pat.h	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -1,3 +1,4 @@
 /*
+ * Copyright (c) 2024 Jiri Svoboda
  * Copyright (c) 2024 Jiří Zárevúcky
  * All rights reserved.
@@ -50,4 +51,5 @@
 } pat_type_t;
 
+#ifndef PROCESSOR_i486
 /**
  * Assign caching type for a particular combination of PAT,
@@ -65,4 +67,5 @@
 	write_msr(MSR_IA32_PAT, r);
 }
+#endif
 
 static inline bool pat_supported(void)
Index: kernel/arch/ia32/src/ia32.c
===================================================================
--- kernel/arch/ia32/src/ia32.c	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ kernel/arch/ia32/src/ia32.c	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -1,5 +1,5 @@
 /*
+ * Copyright (c) 2024 Jiri Svoboda
  * Copyright (c) 2001-2004 Jakub Jermar
- * Copyright (c) 2009 Jiri Svoboda
  * Copyright (c) 2009 Martin Decky
  * All rights reserved.
@@ -106,7 +106,9 @@
 	pm_init();
 
+#ifndef PROCESSOR_i486
 	/* Use PCD+PWT bit combination in PTE to mean write-combining mode. */
 	if (pat_supported())
 		pat_set_mapping(false, true, true, PAT_TYPE_WRITE_COMBINING);
+#endif
 
 	if (config.cpu_active == 1) {
Index: kernel/generic/include/debug.h
===================================================================
--- kernel/generic/include/debug.h	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ kernel/generic/include/debug.h	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -38,4 +38,5 @@
 #include <log.h>
 #include <printf/verify.h>
+#include <symtab.h>
 
 #define CALLER  ((uintptr_t) __builtin_return_address(0))
Index: kernel/generic/src/main/kinit.c
===================================================================
--- kernel/generic/src/main/kinit.c	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ kernel/generic/src/main/kinit.c	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -296,8 +296,5 @@
 			}
 
-		} else if (i == init.cnt - 1) {
-			/*
-			 * Assume the last task is the RAM disk.
-			 */
+		} else if (str_cmp(name, "initrd.img") == 0) {
 			init_rd((void *) init.tasks[i].paddr, init.tasks[i].size);
 		} else {
Index: meson/arch/amd64/meson.build
===================================================================
--- meson/arch/amd64/meson.build	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ meson/arch/amd64/meson.build	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -62,5 +62,5 @@
 	'drv/intctl/i8259',
 	'drv/platform/pc',
-	'drv/block/ata_bd',
+	'drv/block/isa-ide',
 	'drv/bus/pci/pciintel',
 	'drv/bus/isa',
@@ -76,5 +76,6 @@
 	'drv/audio/sb16',
 	'drv/block/ahci',
-	'drv/block/ata_bd',
+	'drv/block/isa-ide',
+	'drv/block/pci-ide',
 	'drv/block/usbmast',
 	'drv/block/virtio-blk',
Index: meson/arch/ia32/meson.build
===================================================================
--- meson/arch/ia32/meson.build	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ meson/arch/ia32/meson.build	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -68,5 +68,5 @@
 	'drv/intctl/i8259',
 	'drv/platform/pc',
-	'drv/block/ata_bd',
+	'drv/block/isa-ide',
 	'drv/bus/pci/pciintel',
 	'drv/bus/isa',
@@ -82,5 +82,6 @@
 	'drv/audio/sb16',
 	'drv/block/ahci',
-	'drv/block/ata_bd',
+	'drv/block/isa-ide',
+	'drv/block/pci-ide',
 	'drv/block/usbmast',
 	'drv/block/virtio-blk',
Index: meson/arch/mips32/meson.build
===================================================================
--- meson/arch/mips32/meson.build	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ meson/arch/mips32/meson.build	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -67,5 +67,5 @@
 		'drv/platform/malta',
 		'drv/intctl/i8259',
-		'drv/block/ata_bd',
+		'drv/block/isa-ide',
 		'drv/bus/pci/pciintel',
 		'drv/bus/isa',
Index: uspace/app/blkdump/blkdump.c
===================================================================
--- uspace/app/blkdump/blkdump.c	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ uspace/app/blkdump/blkdump.c	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -1,5 +1,5 @@
 /*
+ * Copyright (c) 2024 Jiri Svoboda
  * Copyright (c) 2011 Martin Sucha
- * Copyright (c) 2013 Jiri Svoboda
  * All rights reserved.
  *
@@ -150,5 +150,5 @@
 	}
 
-	rc = block_init(service_id, 2048);
+	rc = block_init(service_id);
 	if (rc != EOK)  {
 		printf(NAME ": Error initializing libblock.\n");
Index: uspace/app/dltest/dltest.c
===================================================================
--- uspace/app/dltest/dltest.c	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ uspace/app/dltest/dltest.c	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2016 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -571,4 +571,29 @@
 #ifdef DLTEST_LINKED
 
+/** Test if we can read the correct value of a public pointer variable.
+ *
+ * dl_public_ptr_var is initialized in libdltest to point to dl_public_var.
+ * This is done using a relocation. The main program (unless compiled with
+ * PIC or PIE) will contain a copy of dl_public_ptr_var. This needs
+ * to be copied using a COPY relocation. The relocations in the main
+ * program need to be processed after the relocations in the shared
+ * libraries (so that we copy the correct value).
+ */
+static bool test_public_ptr_var(void)
+{
+	int *ptr;
+
+	printf("Read dl_public_ptr_var directly...\n");
+	ptr = dl_public_ptr_var;
+
+	if (ptr != &dl_public_var) {
+		printf("FAILED\n");
+		return false;
+	}
+
+	printf("Passed\n");
+	return true;
+}
+
 /** Test directly calling function that returns a constant */
 static bool test_lnk_dl_get_constant(void)
@@ -920,4 +945,5 @@
 
 #ifndef STATIC_EXE
+
 	if (!test_dlfcn_dl_get_private_fib_var())
 		return 1;
@@ -974,4 +1000,7 @@
 
 	if (!test_lnk_read_public_uvar())
+		return 1;
+
+	if (!test_public_ptr_var())
 		return 1;
 
Index: uspace/app/edit/edit.c
===================================================================
--- uspace/app/edit/edit.c	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ uspace/app/edit/edit.c	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2023 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * Copyright (c) 2012 Martin Sucha
  * All rights reserved.
@@ -238,9 +238,21 @@
 
 static void edit_wnd_close(ui_window_t *, void *);
+static void edit_wnd_focus(ui_window_t *, void *, unsigned);
 static void edit_wnd_kbd_event(ui_window_t *, void *, kbd_event_t *);
+static void edit_wnd_unfocus(ui_window_t *, void *, unsigned);
 
 static ui_window_cb_t edit_window_cb = {
 	.close = edit_wnd_close,
-	.kbd = edit_wnd_kbd_event
+	.focus = edit_wnd_focus,
+	.kbd = edit_wnd_kbd_event,
+	.unfocus = edit_wnd_unfocus
+};
+
+static void edit_menubar_activate(ui_menu_bar_t *, void *);
+static void edit_menubar_deactivate(ui_menu_bar_t *, void *);
+
+static ui_menu_bar_cb_t edit_menubar_cb = {
+	.activate = edit_menubar_activate,
+	.deactivate = edit_menubar_deactivate
 };
 
@@ -431,4 +443,6 @@
 	}
 
+	ui_menu_bar_set_cb(edit->menubar, &edit_menubar_cb, (void *) edit);
+
 	rc = ui_menu_dd_create(edit->menubar, "~F~ile", NULL, &mfile);
 	if (rc != EOK) {
@@ -2221,4 +2235,19 @@
 }
 
+/** Window focus event
+ *
+ * @param window Window
+ * @param arg Argument (edit_t *)
+ * @param focus Focus number
+ */
+static void edit_wnd_focus(ui_window_t *window, void *arg, unsigned focus)
+{
+	edit_t *edit = (edit_t *)arg;
+
+	(void)edit;
+	pane_caret_display(&pane);
+	cursor_setvis(true);
+}
+
 /** Window keyboard event
  *
@@ -2242,4 +2271,45 @@
 }
 
+/** Window unfocus event
+ *
+ * @param window Window
+ * @param arg Argument (edit_t *)
+ * @param focus Focus number
+ */
+static void edit_wnd_unfocus(ui_window_t *window, void *arg, unsigned focus)
+{
+	edit_t *edit = (edit_t *) arg;
+
+	(void)edit;
+	cursor_setvis(false);
+}
+
+/** Menu bar activate event
+ *
+ * @param mbar Menu bar
+ * @param arg Argument (edit_t *)
+ */
+static void edit_menubar_activate(ui_menu_bar_t *mbar, void *arg)
+{
+	edit_t *edit = (edit_t *)arg;
+
+	(void)edit;
+	cursor_setvis(false);
+}
+
+/** Menu bar deactivate event
+ *
+ * @param mbar Menu bar
+ * @param arg Argument (edit_t *)
+ */
+static void edit_menubar_deactivate(ui_menu_bar_t *mbar, void *arg)
+{
+	edit_t *edit = (edit_t *)arg;
+
+	(void)edit;
+	pane_caret_display(&pane);
+	cursor_setvis(true);
+}
+
 /** File / Save menu entry selected.
  *
@@ -2407,13 +2477,9 @@
 {
 	edit_t *edit = (edit_t *)arg;
-	gfx_context_t *gc = ui_window_get_gc(edit->window);
 	char *cname;
 	errno_t rc;
 
+	(void)edit;
 	ui_file_dialog_destroy(dialog);
-	// TODO Smarter cursor management
-	pane.rflags |= REDRAW_CARET;
-	(void) pane_update(&pane);
-	gfx_cursor_set_visible(gc, true);
 
 	cname = str_dup(fname);
@@ -2441,11 +2507,7 @@
 {
 	edit_t *edit = (edit_t *)arg;
-	gfx_context_t *gc = ui_window_get_gc(edit->window);
-
+
+	(void)edit;
 	ui_file_dialog_destroy(dialog);
-	// TODO Smarter cursor management
-	pane.rflags |= REDRAW_CARET;
-	(void) pane_update(&pane);
-	gfx_cursor_set_visible(gc, true);
 }
 
@@ -2458,11 +2520,7 @@
 {
 	edit_t *edit = (edit_t *)arg;
-	gfx_context_t *gc = ui_window_get_gc(edit->window);
-
+
+	(void)edit;
 	ui_file_dialog_destroy(dialog);
-	// TODO Smarter cursor management
-	pane.rflags |= REDRAW_CARET;
-	(void) pane_update(&pane);
-	gfx_cursor_set_visible(gc, true);
 }
 
@@ -2477,5 +2535,4 @@
 {
 	edit_t *edit = (edit_t *) arg;
-	gfx_context_t *gc = ui_window_get_gc(edit->window);
 	char *endptr;
 	int line;
@@ -2489,8 +2546,6 @@
 
 	caret_move_absolute(line, pane.ideal_column, dir_before, false);
-	// TODO Smarter cursor management
+	(void)edit;
 	(void) pane_update(&pane);
-	gfx_cursor_set_visible(gc, true);
-	(void) gfx_update(gc);
 }
 
@@ -2503,11 +2558,7 @@
 {
 	edit_t *edit = (edit_t *) arg;
-	gfx_context_t *gc = ui_window_get_gc(edit->window);
-
+
+	(void)edit;
 	ui_prompt_dialog_destroy(dialog);
-	// TODO Smarter cursor management
-	pane.rflags |= REDRAW_CARET;
-	(void) pane_update(&pane);
-	gfx_cursor_set_visible(gc, true);
 }
 
@@ -2520,11 +2571,7 @@
 {
 	edit_t *edit = (edit_t *) arg;
-	gfx_context_t *gc = ui_window_get_gc(edit->window);
-
+
+	(void)edit;
 	ui_prompt_dialog_destroy(dialog);
-	// TODO Smarter cursor management
-	pane.rflags |= REDRAW_CARET;
-	(void) pane_update(&pane);
-	gfx_cursor_set_visible(gc, true);
 }
 
@@ -2539,8 +2586,8 @@
 {
 	edit_t *edit = (edit_t *) arg;
-	gfx_context_t *gc = ui_window_get_gc(edit->window);
 	char *pattern;
 	bool reverse;
 
+	(void)edit;
 	ui_prompt_dialog_destroy(dialog);
 
@@ -2559,8 +2606,5 @@
 	search(pattern, reverse);
 
-	// TODO Smarter cursor management
 	(void) pane_update(&pane);
-	gfx_cursor_set_visible(gc, true);
-	(void) gfx_update(gc);
 }
 
@@ -2573,11 +2617,7 @@
 {
 	edit_t *edit = (edit_t *) arg;
-	gfx_context_t *gc = ui_window_get_gc(edit->window);
-
+
+	(void)edit;
 	ui_prompt_dialog_destroy(dialog);
-	// TODO Smarter cursor management
-	pane.rflags |= REDRAW_CARET;
-	(void) pane_update(&pane);
-	gfx_cursor_set_visible(gc, true);
 }
 
@@ -2590,11 +2630,7 @@
 {
 	edit_t *edit = (edit_t *) arg;
-	gfx_context_t *gc = ui_window_get_gc(edit->window);
-
+
+	(void)edit;
 	ui_prompt_dialog_destroy(dialog);
-	// TODO Smarter cursor management
-	pane.rflags |= REDRAW_CARET;
-	(void) pane_update(&pane);
-	gfx_cursor_set_visible(gc, true);
 }
 
Index: uspace/app/hbench/benchlist.c
===================================================================
--- uspace/app/hbench/benchlist.c	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ uspace/app/hbench/benchlist.c	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2023 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * Copyright (c) 2018 Vojtech Horky
  * All rights reserved.
@@ -42,4 +42,6 @@
 	&benchmark_fibril_mutex,
 	&benchmark_file_read,
+	&benchmark_rand_read,
+	&benchmark_seq_read,
 	&benchmark_malloc1,
 	&benchmark_malloc2,
Index: uspace/app/hbench/disk/randread.c
===================================================================
--- uspace/app/hbench/disk/randread.c	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
+++ uspace/app/hbench/disk/randread.c	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -0,0 +1,146 @@
+/*
+ * 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 hbench
+ * @{
+ */
+
+#include <block.h>
+#include <loc.h>
+#include <str_error.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "../hbench.h"
+
+/** Execute disk random read benchmark. */
+static bool runner(bench_env_t *env, bench_run_t *run, uint64_t size)
+{
+	const char *disk;
+	const char *nbstr;
+	service_id_t svcid;
+	size_t block_size;
+	aoff64_t dev_nblocks;
+	aoff64_t baddr;
+	bool block_inited = false;
+	char *buf = NULL;
+	uint64_t i;
+	errno_t rc;
+	int nitem;
+	unsigned nb;
+
+	disk = bench_env_param_get(env, "disk", NULL);
+	if (disk == NULL) {
+		bench_run_fail(run, "You must specify 'disk' parameter.");
+		goto error;
+	}
+
+	nbstr = bench_env_param_get(env, "nb", "1");
+	nitem = sscanf(nbstr, "%u", &nb);
+	if (nitem < 1) {
+		bench_run_fail(run, "'nb' must be an integer number of blocks.");
+		goto error;
+	}
+
+	rc = loc_service_get_id(disk, &svcid, 0);
+	if (rc != EOK) {
+		bench_run_fail(run, "failed resolving device '%s'", disk);
+		goto error;
+	}
+
+	rc = block_init(svcid);
+	if (rc != EOK) {
+		bench_run_fail(run, "failed opening block device '%s'",
+		    disk);
+	}
+
+	block_inited = true;
+
+	rc = block_get_bsize(svcid, &block_size);
+	if (rc != EOK) {
+		bench_run_fail(run, "error determining device block size.");
+		goto error;
+	}
+
+	rc = block_get_nblocks(svcid, &dev_nblocks);
+	if (rc != EOK) {
+		bench_run_fail(run, "failed to obtain block device size.\n");
+		goto error;
+	}
+
+	if (dev_nblocks < nb) {
+		bench_run_fail(run, "device is smaller than %u blocks.\n",
+		    nb);
+		goto error;
+	}
+
+	buf = malloc(block_size * nb);
+	if (buf == NULL) {
+		bench_run_fail(run, "failed to allocate buffer (%zu bytes)",
+		    block_size * nb);
+		goto error;
+	}
+
+	bench_run_start(run);
+	for (i = 0; i < size; i++) {
+		/* Generate pseudo-random block address */
+		baddr = (rand() + rand() * RAND_MAX) % (dev_nblocks - nb + 1);
+
+		rc = block_read_direct(svcid, baddr, nb, buf);
+		if (rc != EOK) {
+			bench_run_fail(run, "failed to read blockd %llu-%llu: "
+			    "%s", (unsigned long long)baddr,
+			    (unsigned long long)(baddr + nb - 1),
+			    str_error(errno));
+			goto error;
+		}
+	}
+
+	bench_run_stop(run);
+	block_fini(svcid);
+	free(buf);
+
+	return true;
+error:
+	if (buf != NULL)
+		free(buf);
+	if (block_inited)
+		block_fini(svcid);
+	return false;
+}
+
+benchmark_t benchmark_rand_read = {
+	.name = "rand_read",
+	.desc = "Random disk read (must set 'disk' parameter).",
+	.entry = &runner,
+	.setup = NULL,
+	.teardown = NULL
+};
+
+/**
+ * @}
+ */
Index: uspace/app/hbench/disk/seqread.c
===================================================================
--- uspace/app/hbench/disk/seqread.c	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
+++ uspace/app/hbench/disk/seqread.c	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -0,0 +1,145 @@
+/*
+ * 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 hbench
+ * @{
+ */
+
+#include <block.h>
+#include <loc.h>
+#include <str_error.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "../hbench.h"
+
+/** Execute disk sequential read benchmark. */
+static bool runner(bench_env_t *env, bench_run_t *run, uint64_t size)
+{
+	const char *disk;
+	const char *nbstr;
+	service_id_t svcid;
+	size_t block_size;
+	aoff64_t dev_nblocks;
+	aoff64_t baddr;
+	bool block_inited = false;
+	char *buf = NULL;
+	uint64_t i;
+	errno_t rc;
+	int nitem;
+	unsigned nb;
+
+	disk = bench_env_param_get(env, "disk", NULL);
+	if (disk == NULL) {
+		bench_run_fail(run, "Yo must specify 'disk' parameter.");
+		goto error;
+	}
+
+	nbstr = bench_env_param_get(env, "nb", "1");
+	nitem = sscanf(nbstr, "%u", &nb);
+	if (nitem < 1) {
+		bench_run_fail(run, "'nb' must be an integer number of blocks.");
+		goto error;
+	}
+
+	rc = loc_service_get_id(disk, &svcid, 0);
+	if (rc != EOK) {
+		bench_run_fail(run, "failed resolving device '%s'", disk);
+		goto error;
+	}
+
+	rc = block_init(svcid);
+	if (rc != EOK) {
+		bench_run_fail(run, "failed opening block device '%s'",
+		    disk);
+	}
+
+	block_inited = true;
+
+	rc = block_get_bsize(svcid, &block_size);
+	if (rc != EOK) {
+		bench_run_fail(run, "error determining device block size.");
+		goto error;
+	}
+
+	rc = block_get_nblocks(svcid, &dev_nblocks);
+	if (rc != EOK) {
+		bench_run_fail(run, "failed to obtain block device size.\n");
+		goto error;
+	}
+
+	if (dev_nblocks < nb) {
+		bench_run_fail(run, "device is smaller than %u blocks.\n",
+		    nb);
+		goto error;
+	}
+
+	buf = malloc(block_size);
+	if (buf == NULL) {
+		bench_run_fail(run, "failed to allocate buffer (%zu bytes)",
+		    block_size);
+		goto error;
+	}
+
+	bench_run_start(run);
+	for (i = 0; i < size; i++) {
+		baddr = i % (dev_nblocks - nb + 1);
+
+		rc = block_read_direct(svcid, baddr, 1, buf);
+		if (rc != EOK) {
+			bench_run_fail(run, "failed to read blocks %llu-%llu: "
+			    "%s", (unsigned long long)baddr,
+			    (unsigned long long)(baddr + nb - 1),
+			    str_error(errno));
+			goto error;
+		}
+	}
+
+	bench_run_stop(run);
+	block_fini(svcid);
+	free(buf);
+
+	return true;
+error:
+	if (buf != NULL)
+		free(buf);
+	if (block_inited)
+		block_fini(svcid);
+	return false;
+}
+
+benchmark_t benchmark_seq_read = {
+	.name = "seq_read",
+	.desc = "Sequential disk read (must set 'disk' parameter).",
+	.entry = &runner,
+	.setup = NULL,
+	.teardown = NULL
+};
+
+/**
+ * @}
+ */
Index: uspace/app/hbench/hbench.h
===================================================================
--- uspace/app/hbench/hbench.h	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ uspace/app/hbench/hbench.h	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2023 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * Copyright (c) 2019 Vojtech Horky
  * All rights reserved.
@@ -135,4 +135,6 @@
 extern benchmark_t benchmark_fibril_mutex;
 extern benchmark_t benchmark_file_read;
+extern benchmark_t benchmark_rand_read;
+extern benchmark_t benchmark_seq_read;
 extern benchmark_t benchmark_malloc1;
 extern benchmark_t benchmark_malloc2;
Index: uspace/app/hbench/meson.build
===================================================================
--- uspace/app/hbench/meson.build	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ uspace/app/hbench/meson.build	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -1,4 +1,4 @@
 #
-# Copyright (c) 2023 Jiri Svoboda
+# Copyright (c) 2024 Jiri Svoboda
 # All rights reserved.
 #
@@ -27,5 +27,5 @@
 #
 
-deps = [ 'math', 'ipctest' ]
+deps = [ 'block', 'math', 'ipctest' ]
 src = files(
 	'benchlist.c',
@@ -34,4 +34,6 @@
 	'main.c',
 	'utils.c',
+	'disk/randread.c',
+	'disk/seqread.c',
 	'fs/dirread.c',
 	'fs/fileread.c',
Index: uspace/app/init/untar.c
===================================================================
--- uspace/app/init/untar.c	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ uspace/app/init/untar.c	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -1,3 +1,4 @@
 /*
+ * Copyright (c) 2024 Jiri Svoboda
  * Copyright (c) 2018 Martin Decky
  * All rights reserved.
@@ -59,5 +60,5 @@
 		return ret;
 
-	ret = block_init(state->sid, 4096);
+	ret = block_init(state->sid);
 	if (ret != EOK)
 		return ret;
Index: uspace/app/mkexfat/mkexfat.c
===================================================================
--- uspace/app/mkexfat/mkexfat.c	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ uspace/app/mkexfat/mkexfat.c	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -1,3 +1,4 @@
 /*
+ * Copyright (c) 2024 Jiri Svoboda
  * Copyright (c) 2012 Maurizio Lombardi
  * All rights reserved.
@@ -858,5 +859,5 @@
 	}
 
-	rc = block_init(service_id, 2048);
+	rc = block_init(service_id);
 	if (rc != EOK) {
 		printf(NAME ": Error initializing libblock.\n");
Index: uspace/app/mkfat/mkfat.c
===================================================================
--- uspace/app/mkfat/mkfat.c	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ uspace/app/mkfat/mkfat.c	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2010 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -66,5 +66,6 @@
 	default_fat_count		= 2,
 	default_reserved_clusters	= 2,
-	default_media_descriptor	= 0xF8 /**< fixed disk */
+	default_media_descriptor	= 0xF8 /**< fixed disk */,
+	fat32_root_cluster		= 2
 };
 
@@ -198,5 +199,5 @@
 	}
 
-	rc = block_init(service_id, 2048);
+	rc = block_init(service_id);
 	if (rc != EOK)  {
 		printf(NAME ": Error initializing libblock.\n");
@@ -313,5 +314,5 @@
 	cfg->reserved_sectors = 1 + cfg->addt_res_sectors;
 
-	/* Only correct for FAT12/16 (FAT32 has root dir stored in clusters */
+	/* Only correct for FAT12/16 (FAT32 has root dir stored in clusters) */
 	rd_sectors = div_round_up(cfg->root_ent_max * DIRENT_SIZE,
 	    cfg->sector_size);
@@ -434,4 +435,9 @@
 	}
 
+	if (cfg->fat_type == FAT32) {
+		/* Root dir is stored in cluster fat32_root_cluster */
+		addr += fat32_root_cluster * cfg->sectors_per_cluster;
+	}
+
 	/* Root directory */
 	printf("Writing root directory.\n");
@@ -529,5 +535,5 @@
 		bs->fat32.ebs = 0x29;
 		bs->fat32.id = host2uint32_t_be(vsn);
-		bs->fat32.root_cluster = 2;
+		bs->fat32.root_cluster = fat32_root_cluster;
 
 		(void) fat_label_encode(&bs->fat32.label, bs_label);
Index: uspace/app/mkmfs/mkmfs.c
===================================================================
--- uspace/app/mkmfs/mkmfs.c	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ uspace/app/mkmfs/mkmfs.c	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -1,3 +1,4 @@
 /*
+ * Copyright (c) 2024 Jiri Svoboda
  * Copyright (c) 2011 Maurizio Lombardi
  * All rights reserved.
@@ -211,5 +212,5 @@
 	}
 
-	rc = block_init(service_id, 2048);
+	rc = block_init(service_id);
 	if (rc != EOK)  {
 		printf(NAME ": Error initializing libblock.\n");
Index: uspace/app/sysinst/sysinst.c
===================================================================
--- uspace/app/sysinst/sysinst.c	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ uspace/app/sysinst/sysinst.c	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2018 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -62,5 +62,6 @@
  * in Grub notation).
  */
-#define DEFAULT_DEV "devices/\\hw\\sys\\00:01.0\\ata-c1\\d0"
+#define DEFAULT_DEV_0 "devices/\\hw\\sys\\00:01.1\\c0d0"
+#define DEFAULT_DEV_1 "devices/\\hw\\sys\\00:01.0\\ata1\\c0d0"
 //#define DEFAULT_DEV "devices/\\hw\\pci0\\00:01.2\\uhci_rh\\usb01_a1\\mass-storage0\\l0"
 /** Volume label for the new file system */
@@ -79,8 +80,34 @@
 #define BOOT_BLOCK_IDX 0 /* MBR */
 
+static const char *default_devs[] = {
+	DEFAULT_DEV_0,
+	DEFAULT_DEV_1,
+	NULL
+};
+
 static const char *sys_dirs[] = {
 	"/cfg",
-	"/data"
+	"/data",
+	NULL
 };
+
+/** Check the if the destination device exists.
+ *
+ * @param dev Disk device
+ *
+ * @return EOK on success or an error code
+ */
+static errno_t sysinst_check_dev(const char *dev)
+{
+	service_id_t sid;
+	errno_t rc;
+
+	rc = loc_service_get_id(dev, &sid, 0);
+	if (rc != EOK)
+		return rc;
+
+	(void)sid;
+	return EOK;
+}
 
 /** Label the destination device.
@@ -361,5 +388,5 @@
 
 	printf("sysinst_copy_boot_blocks: block_init.\n");
-	rc = block_init(sid, 512);
+	rc = block_init(sid);
 	if (rc != EOK)
 		return rc;
@@ -490,6 +517,20 @@
 int main(int argc, char *argv[])
 {
-	const char *dev = DEFAULT_DEV;
-	return sysinst_install(dev);
+	unsigned i;
+	errno_t rc;
+
+	i = 0;
+	while (default_devs[i] != NULL) {
+		rc = sysinst_check_dev(default_devs[i]);
+		if (rc == EOK)
+			break;
+	}
+
+	if (default_devs[i] == NULL) {
+		printf("Cannot determine installation device.\n");
+		return 1;
+	}
+
+	return sysinst_install(default_devs[i]);
 }
 
Index: uspace/drv/audio/hdaudio/hdaudio.c
===================================================================
--- uspace/drv/audio/hdaudio/hdaudio.c	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ uspace/drv/audio/hdaudio/hdaudio.c	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -58,5 +58,5 @@
 static errno_t hda_fun_offline(ddf_fun_t *fun);
 
-static void hdaudio_interrupt(ipc_call_t *, ddf_dev_t *);
+static void hdaudio_interrupt(ipc_call_t *, void *);
 
 static driver_ops_t driver_ops = {
@@ -266,5 +266,5 @@
 	cap_irq_handle_t irq_cap;
 	rc = register_interrupt_handler(dev, res.irqs.irqs[0],
-	    hdaudio_interrupt, &irq_code, &irq_cap);
+	    hdaudio_interrupt, (void *)hda, &irq_code, &irq_cap);
 	if (rc != EOK) {
 		ddf_msg(LVL_ERROR, "Failed registering interrupt handler: %s",
@@ -377,7 +377,12 @@
 }
 
-static void hdaudio_interrupt(ipc_call_t *icall, ddf_dev_t *dev)
-{
-	hda_t *hda = (hda_t *)ddf_dev_data_get(dev);
+/** HD Audio interrupt handler.
+ *
+ * @param icall IRQ event notification
+ * @param arg Argument (hda_t *)
+ */
+static void hdaudio_interrupt(ipc_call_t *icall, void *arg)
+{
+	hda_t *hda = (hda_t *)arg;
 
 	if (0)
Index: uspace/drv/audio/sb16/main.c
===================================================================
--- uspace/drv/audio/sb16/main.c	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ uspace/drv/audio/sb16/main.c	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -76,7 +76,12 @@
 }
 
-static void irq_handler(ipc_call_t *call, ddf_dev_t *dev)
-{
-	sb16_t *sb16_dev = ddf_dev_data_get(dev);
+/** SB16 IRQ handler.
+ *
+ * @param call IRQ event notification
+ * @param arg Argument (sb16_t *)
+ */
+static void irq_handler(ipc_call_t *call, void *arg)
+{
+	sb16_t *sb16_dev = (sb16_t *)arg;
 	sb16_interrupt(sb16_dev);
 }
@@ -124,5 +129,5 @@
 
 	rc = register_interrupt_handler(device, irq, irq_handler,
-	    &irq_code, &irq_cap);
+	    (void *)soft_state, &irq_code, &irq_cap);
 	if (rc != EOK) {
 		ddf_log_error("Failed to register irq handler: %s.",
Index: uspace/drv/block/ahci/ahci.c
===================================================================
--- uspace/drv/block/ahci/ahci.c	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ uspace/drv/block/ahci/ahci.c	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -162,10 +162,4 @@
 }
 
-/** Get AHCI structure from DDF device. */
-static ahci_dev_t *dev_ahci_dev(ddf_dev_t *dev)
-{
-	return ddf_dev_data_get(dev);
-}
-
 /** Get SATA device name.
  *
@@ -891,10 +885,9 @@
  *
  * @param icall The IPC call structure.
- * @param dev   DDF device structure.
- *
- */
-static void ahci_interrupt(ipc_call_t *icall, ddf_dev_t *dev)
-{
-	ahci_dev_t *ahci = dev_ahci_dev(dev);
+ * @param arg   Argument (ahci_dev_t *)
+ */
+static void ahci_interrupt(ipc_call_t *icall, void *arg)
+{
+	ahci_dev_t *ahci = (ahci_dev_t *)arg;
 	unsigned int port = ipc_get_arg1(icall);
 	ahci_port_is_t pxis = ipc_get_arg2(icall);
@@ -1186,5 +1179,6 @@
 	cap_irq_handle_t irq_cap;
 	errno_t rc = register_interrupt_handler(dev,
-	    hw_res_parsed.irqs.irqs[0], ahci_interrupt, &ct, &irq_cap);
+	    hw_res_parsed.irqs.irqs[0], ahci_interrupt, (void *)ahci, &ct,
+	    &irq_cap);
 	if (rc != EOK) {
 		ddf_msg(LVL_ERROR, "Failed registering interrupt handler.");
Index: pace/drv/block/ata_bd/ata_bd.c
===================================================================
--- uspace/drv/block/ata_bd/ata_bd.c	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ 	(revision )
@@ -1,1293 +1,0 @@
-/*
- * Copyright (c) 2013 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
- * @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.
- */
-
-#include <ddi.h>
-#include <ddf/log.h>
-#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>
-#include <stdio.h>
-#include <stddef.h>
-#include <str.h>
-#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"
-
-/** Number of defined legacy controller base addresses. */
-#define LEGACY_CTLS 4
-
-/**
- * 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);
-static void ata_bd_fini_io(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 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);
-
-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 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_base_t *res)
-{
-	int i;
-	errno_t rc;
-	int n_disks;
-
-	ddf_msg(LVL_DEBUG, "ata_ctrl_init()");
-
-	fibril_mutex_initialize(&ctrl->lock);
-	ctrl->cmd_physical = res->cmd;
-	ctrl->ctl_physical = res->ctl;
-
-	ddf_msg(LVL_NOTE, "I/O address %p/%p", (void *) ctrl->cmd_physical,
-	    (void *) ctrl->ctl_physical);
-
-	rc = ata_bd_init_io(ctrl);
-	if (rc != EOK)
-		return rc;
-
-	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;
-	}
-
-	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);
-		}
-	}
-	ata_bd_fini_io(ctrl);
-	return rc;
-}
-
-/** Remove ATA controller. */
-errno_t ata_ctrl_remove(ata_ctrl_t *ctrl)
-{
-	int i;
-	errno_t rc;
-
-	ddf_msg(LVL_DEBUG, ": ata_ctrl_remove()");
-
-	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;
-		}
-	}
-
-	ata_bd_fini_io(ctrl);
-	fibril_mutex_unlock(&ctrl->lock);
-
-	return EOK;
-}
-
-/** Surprise removal of ATA controller. */
-errno_t ata_ctrl_gone(ata_ctrl_t *ctrl)
-{
-	int i;
-	errno_t rc;
-
-	ddf_msg(LVL_DEBUG, "ata_ctrl_gone()");
-
-	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;
-		}
-	}
-
-	ata_bd_fini_io(ctrl);
-	fibril_mutex_unlock(&ctrl->lock);
-
-	return EOK;
-}
-
-/** 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)
-{
-	errno_t rc;
-	void *vaddr;
-
-	rc = pio_enable((void *) ctrl->cmd_physical, sizeof(ata_cmd_t), &vaddr);
-	if (rc != EOK) {
-		ddf_msg(LVL_ERROR, "Cannot initialize device I/O space.");
-		return rc;
-	}
-
-	ctrl->cmd = vaddr;
-
-	rc = pio_enable((void *) ctrl->ctl_physical, sizeof(ata_ctl_t), &vaddr);
-	if (rc != EOK) {
-		ddf_msg(LVL_ERROR, "Cannot initialize device I/O space.");
-		return rc;
-	}
-
-	ctrl->ctl = vaddr;
-
-	return EOK;
-}
-
-/** Clean up device I/O. */
-static void ata_bd_fini_io(ata_ctrl_t *ctrl)
-{
-	(void) ctrl;
-	/* XXX TODO */
-}
-
-/** 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);
-	errno_t rc;
-
-	if (size < cnt * disk->block_size)
-		return EINVAL;
-
-	while (cnt > 0) {
-		if (disk->dev_type == ata_reg_dev) {
-			rc = ata_rcmd_read(disk, ba, 1, buf);
-		} else {
-			rc = ata_pcmd_read_12(disk, ba, 1, buf,
-			    disk->block_size);
-		}
-
-		if (rc != EOK)
-			return rc;
-
-		++ba;
-		--cnt;
-		buf += disk->block_size;
-	}
-
-	return EOK;
-}
-
-/** 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);
-	errno_t rc;
-
-	if (disk->dev_type != ata_reg_dev)
-		return ENOTSUP;
-
-	if (size < cnt * disk->block_size)
-		return EINVAL;
-
-	while (cnt > 0) {
-		rc = ata_rcmd_write(disk, ba, 1, buf);
-		if (rc != EOK)
-			return rc;
-
-		++ba;
-		--cnt;
-		buf += disk->block_size;
-	}
-
-	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;
-	uint8_t status;
-
-	/* XXX Support multiple blocks */
-	assert(nblocks == 1);
-	assert(blk_size % 2 == 0);
-
-	if (wait_status(ctrl, 0, ~SR_BSY, &status, TIMEOUT_BSY) != EOK)
-		return EIO;
-
-	if ((status & SR_DRQ) != 0) {
-		/* 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)[i] = data;
-		}
-	}
-
-	if ((status & SR_ERR) != 0)
-		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;
-	uint8_t status;
-
-	/* XXX Support multiple blocks */
-	assert(nblocks == 1);
-	assert(blk_size % 2 == 0);
-
-	if (wait_status(ctrl, 0, ~SR_BSY, &status, TIMEOUT_BSY) != EOK)
-		return EIO;
-
-	if ((status & SR_DRQ) != 0) {
-		/* Write data to the device buffer. */
-
-		for (i = 0; i < blk_size / 2; i++) {
-			pio_write_16(&ctrl->cmd->data_port, ((uint16_t *) buf)[i]);
-		}
-	}
-
-	if (status & SR_ERR)
-		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;
-
-	if (wait_status(ctrl, 0, ~SR_BSY, &status, TIMEOUT_BSY) != 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);
-
-	if (wait_status(ctrl, 0, ~SR_BSY, &status, TIMEOUT_PROBE) != EOK)
-		return ETIMEOUT;
-
-	/*
-	 * If ERR is set, this may be a packet device, so return EIO to cause
-	 * the caller to check for one.
-	 */
-	if ((status & SR_ERR) != 0)
-		return EIO;
-
-	/*
-	 * For probing purposes we need to wait for some status bit to become
-	 * active - otherwise we could be fooled just by receiving all zeroes.
-	 */
-	if (wait_status(ctrl, SR_DRQ, ~SR_BSY, &status, TIMEOUT_PROBE) != EOK)
-		return ETIMEOUT;
-
-	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;
-	uint16_t val;
-
-	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]);
-
-	if (wait_status(ctrl, 0, ~SR_BSY, &status, TIMEOUT_BSY) != EOK) {
-		fibril_mutex_unlock(&ctrl->lock);
-		return EIO;
-	}
-
-	if ((status & SR_DRQ) == 0) {
-		fibril_mutex_unlock(&ctrl->lock);
-		return EIO;
-	}
-
-	/* 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)[i] = val;
-	}
-
-	fibril_mutex_unlock(&ctrl->lock);
-
-	if (status & SR_ERR)
-		return EIO;
-
-	if (rcvd_size != NULL)
-		*rcvd_size = data_size;
-	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)
-		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);
-		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, 1);
-
-	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);
-
-	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, 1);
-
-	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;
-}
-
-/** 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;
-}
-
-/**
- * @}
- */
Index: pace/drv/block/ata_bd/ata_bd.h
===================================================================
--- uspace/drv/block/ata_bd/ata_bd.h	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ 	(revision )
@@ -1,164 +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 driver definitions.
- */
-
-#ifndef __ATA_BD_H__
-#define __ATA_BD_H__
-
-#include <async.h>
-#include <bd_srv.h>
-#include <ddf/driver.h>
-#include <fibril_synch.h>
-#include <str.h>
-#include <stdint.h>
-#include <stddef.h>
-#include "ata_hw.h"
-
-#define NAME "ata_bd"
-
-/** Base addresses for ATA I/O blocks. */
-typedef struct {
-	uintptr_t cmd;	/**< Command block base address. */
-	uintptr_t ctl;	/**< Control block base address. */
-} ata_base_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 */
-typedef struct ata_ctrl {
-	/** DDF device */
-	ddf_dev_t *dev;
-	/** I/O base address of the command registers */
-	uintptr_t cmd_physical;
-	/** I/O base address of the control registers */
-	uintptr_t ctl_physical;
-
-	/** Command registers */
-	ata_cmd_t *cmd;
-	/** Control registers */
-	ata_ctl_t *ctl;
-
-	/** Per-disk state. */
-	disk_t disk[MAX_DISKS];
-
-	fibril_mutex_t lock;
-} ata_ctrl_t;
-
-typedef struct ata_fun {
-	ddf_fun_t *fun;
-	disk_t *disk;
-	bd_srvs_t bds;
-} ata_fun_t;
-
-extern errno_t ata_ctrl_init(ata_ctrl_t *, ata_base_t *);
-extern errno_t ata_ctrl_remove(ata_ctrl_t *);
-extern errno_t ata_ctrl_gone(ata_ctrl_t *);
-
-extern bd_ops_t ata_bd_ops;
-
-#endif
-
-/** @}
- */
Index: pace/drv/block/ata_bd/ata_bd.ma
===================================================================
--- uspace/drv/block/ata_bd/ata_bd.ma	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ 	(revision )
@@ -1,1 +1,0 @@
-10 isa/ata_bd
Index: pace/drv/block/ata_bd/ata_hw.h
===================================================================
--- uspace/drv/block/ata_bd/ata_hw.h	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ 	(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: pace/drv/block/ata_bd/doc/doxygroups.h
===================================================================
--- uspace/drv/block/ata_bd/doc/doxygroups.h	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ 	(revision )
@@ -1,4 +1,0 @@
-/** @addtogroup ata_bd ata_bd
- * @brief ATA disk driver
- * @ingroup drvs
- */
Index: pace/drv/block/ata_bd/main.c
===================================================================
--- uspace/drv/block/ata_bd/main.c	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ 	(revision )
@@ -1,349 +1,0 @@
-/*
- * Copyright (c) 2013 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
- */
-
-#include <assert.h>
-#include <stdio.h>
-#include <errno.h>
-#include <str_error.h>
-#include <ddf/driver.h>
-#include <ddf/log.h>
-#include <device/hw_res_parsed.h>
-
-#include "ata_bd.h"
-#include "main.h"
-
-static errno_t ata_dev_add(ddf_dev_t *dev);
-static errno_t ata_dev_remove(ddf_dev_t *dev);
-static errno_t ata_dev_gone(ddf_dev_t *dev);
-static errno_t ata_fun_online(ddf_fun_t *fun);
-static errno_t ata_fun_offline(ddf_fun_t *fun);
-
-static void ata_bd_connection(ipc_call_t *, void *);
-
-static driver_ops_t driver_ops = {
-	.dev_add = &ata_dev_add,
-	.dev_remove = &ata_dev_remove,
-	.dev_gone = &ata_dev_gone,
-	.fun_online = &ata_fun_online,
-	.fun_offline = &ata_fun_offline
-};
-
-static driver_t ata_driver = {
-	.name = NAME,
-	.driver_ops = &driver_ops
-};
-
-static errno_t ata_get_res(ddf_dev_t *dev, ata_base_t *ata_res)
-{
-	async_sess_t *parent_sess;
-	hw_res_list_parsed_t hw_res;
-	errno_t rc;
-
-	parent_sess = ddf_dev_parent_sess_get(dev);
-	if (parent_sess == NULL)
-		return ENOMEM;
-
-	hw_res_list_parsed_init(&hw_res);
-	rc = hw_res_get_list_parsed(parent_sess, &hw_res, 0);
-	if (rc != EOK)
-		return rc;
-
-	if (hw_res.io_ranges.count != 2) {
-		rc = EINVAL;
-		goto error;
-	}
-
-	addr_range_t *cmd_rng = &hw_res.io_ranges.ranges[0];
-	addr_range_t *ctl_rng = &hw_res.io_ranges.ranges[1];
-	ata_res->cmd = RNGABS(*cmd_rng);
-	ata_res->ctl = RNGABS(*ctl_rng);
-
-	if (RNGSZ(*ctl_rng) < sizeof(ata_ctl_t)) {
-		rc = EINVAL;
-		goto error;
-	}
-
-	if (RNGSZ(*cmd_rng) < sizeof(ata_cmd_t)) {
-		rc = EINVAL;
-		goto error;
-	}
-
-	return EOK;
-error:
-	hw_res_list_parsed_clean(&hw_res);
-	return rc;
-}
-
-/** Add new device
- *
- * @param  dev New device
- * @return     EOK on success or an error code.
- */
-static errno_t ata_dev_add(ddf_dev_t *dev)
-{
-	ata_ctrl_t *ctrl;
-	ata_base_t res;
-	errno_t rc;
-
-	rc = ata_get_res(dev, &res);
-	if (rc != EOK) {
-		ddf_msg(LVL_ERROR, "Invalid HW resource configuration.");
-		return EINVAL;
-	}
-
-	ctrl = ddf_dev_data_alloc(dev, sizeof(ata_ctrl_t));
-	if (ctrl == NULL) {
-		ddf_msg(LVL_ERROR, "Failed allocating soft state.");
-		rc = ENOMEM;
-		goto error;
-	}
-
-	ctrl->dev = dev;
-
-	rc = ata_ctrl_init(ctrl, &res);
-	if (rc == ENOENT)
-		goto error;
-
-	if (rc != EOK) {
-		ddf_msg(LVL_ERROR, "Failed initializing ATA controller.");
-		rc = EIO;
-		goto error;
-	}
-
-	return EOK;
-error:
-	return rc;
-}
-
-static char *ata_fun_name(disk_t *disk)
-{
-	char *fun_name;
-
-	if (asprintf(&fun_name, "d%u", disk->disk_id) < 0)
-		return NULL;
-
-	return fun_name;
-}
-
-errno_t ata_fun_create(disk_t *disk)
-{
-	ata_ctrl_t *ctrl = disk->ctrl;
-	errno_t rc;
-	char *fun_name = NULL;
-	ddf_fun_t *fun = NULL;
-	ata_fun_t *afun = NULL;
-	bool bound = false;
-
-	fun_name = ata_fun_name(disk);
-	if (fun_name == NULL) {
-		ddf_msg(LVL_ERROR, "Out of memory.");
-		rc = ENOMEM;
-		goto error;
-	}
-
-	fun = ddf_fun_create(ctrl->dev, fun_exposed, fun_name);
-	if (fun == NULL) {
-		ddf_msg(LVL_ERROR, "Failed creating DDF function.");
-		rc = ENOMEM;
-		goto error;
-	}
-
-	/* Allocate soft state */
-	afun = ddf_fun_data_alloc(fun, sizeof(ata_fun_t));
-	if (afun == NULL) {
-		ddf_msg(LVL_ERROR, "Failed allocating softstate.");
-		rc = ENOMEM;
-		goto error;
-	}
-
-	afun->fun = fun;
-	afun->disk = disk;
-
-	bd_srvs_init(&afun->bds);
-	afun->bds.ops = &ata_bd_ops;
-	afun->bds.sarg = disk;
-
-	/* Set up a connection handler. */
-	ddf_fun_set_conn_handler(fun, ata_bd_connection);
-
-	rc = ddf_fun_bind(fun);
-	if (rc != EOK) {
-		ddf_msg(LVL_ERROR, "Failed binding DDF function %s: %s",
-		    fun_name, str_error(rc));
-		goto error;
-	}
-
-	bound = true;
-
-	rc = ddf_fun_add_to_category(fun, "disk");
-	if (rc != EOK) {
-		ddf_msg(LVL_ERROR, "Failed adding function %s to "
-		    "category 'disk': %s", fun_name, str_error(rc));
-		goto error;
-	}
-
-	free(fun_name);
-	disk->afun = afun;
-	return EOK;
-error:
-	if (bound)
-		ddf_fun_unbind(fun);
-	if (fun != NULL)
-		ddf_fun_destroy(fun);
-	if (fun_name != NULL)
-		free(fun_name);
-
-	return rc;
-}
-
-errno_t ata_fun_remove(disk_t *disk)
-{
-	errno_t rc;
-	char *fun_name;
-
-	if (disk->afun == NULL)
-		return EOK;
-
-	fun_name = ata_fun_name(disk);
-	if (fun_name == NULL) {
-		ddf_msg(LVL_ERROR, "Out of memory.");
-		rc = ENOMEM;
-		goto error;
-	}
-
-	ddf_msg(LVL_DEBUG, "ata_fun_remove(%p, '%s')", disk, fun_name);
-	rc = ddf_fun_offline(disk->afun->fun);
-	if (rc != EOK) {
-		ddf_msg(LVL_ERROR, "Error offlining function '%s'.", fun_name);
-		goto error;
-	}
-
-	rc = ddf_fun_unbind(disk->afun->fun);
-	if (rc != EOK) {
-		ddf_msg(LVL_ERROR, "Failed unbinding function '%s'.", fun_name);
-		goto error;
-	}
-
-	ddf_fun_destroy(disk->afun->fun);
-	disk->afun = NULL;
-	free(fun_name);
-	return EOK;
-error:
-	if (fun_name != NULL)
-		free(fun_name);
-	return rc;
-}
-
-errno_t ata_fun_unbind(disk_t *disk)
-{
-	errno_t rc;
-	char *fun_name;
-
-	if (disk->afun == NULL)
-		return EOK;
-
-	fun_name = ata_fun_name(disk);
-	if (fun_name == NULL) {
-		ddf_msg(LVL_ERROR, "Out of memory.");
-		rc = ENOMEM;
-		goto error;
-	}
-
-	ddf_msg(LVL_DEBUG, "ata_fun_unbind(%p, '%s')", disk, fun_name);
-	rc = ddf_fun_unbind(disk->afun->fun);
-	if (rc != EOK) {
-		ddf_msg(LVL_ERROR, "Failed unbinding function '%s'.", fun_name);
-		goto error;
-	}
-
-	ddf_fun_destroy(disk->afun->fun);
-	disk->afun = NULL;
-	free(fun_name);
-	return EOK;
-error:
-	if (fun_name != NULL)
-		free(fun_name);
-	return rc;
-}
-
-static errno_t ata_dev_remove(ddf_dev_t *dev)
-{
-	ata_ctrl_t *ctrl = (ata_ctrl_t *)ddf_dev_data_get(dev);
-
-	ddf_msg(LVL_DEBUG, "ata_dev_remove(%p)", dev);
-
-	return ata_ctrl_remove(ctrl);
-}
-
-static errno_t ata_dev_gone(ddf_dev_t *dev)
-{
-	ata_ctrl_t *ctrl = (ata_ctrl_t *)ddf_dev_data_get(dev);
-
-	ddf_msg(LVL_DEBUG, "ata_dev_gone(%p)", dev);
-
-	return ata_ctrl_gone(ctrl);
-}
-
-static errno_t ata_fun_online(ddf_fun_t *fun)
-{
-	ddf_msg(LVL_DEBUG, "ata_fun_online()");
-	return ddf_fun_online(fun);
-}
-
-static errno_t ata_fun_offline(ddf_fun_t *fun)
-{
-	ddf_msg(LVL_DEBUG, "ata_fun_offline()");
-	return ddf_fun_offline(fun);
-}
-
-/** Block device connection handler */
-static void ata_bd_connection(ipc_call_t *icall, void *arg)
-{
-	ata_fun_t *afun;
-
-	afun = (ata_fun_t *) ddf_fun_data_get((ddf_fun_t *)arg);
-	bd_conn(icall, &afun->bds);
-}
-
-int main(int argc, char *argv[])
-{
-	printf(NAME ": HelenOS ATA(PI) device driver\n");
-	ddf_log_init(NAME);
-	return ddf_driver_main(&ata_driver);
-}
-
-/**
- * @}
- */
Index: pace/drv/block/ata_bd/main.h
===================================================================
--- uspace/drv/block/ata_bd/main.h	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ 	(revision )
@@ -1,47 +1,0 @@
-/*
- * Copyright (c) 2013 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 driver main module
- */
-
-#ifndef __ATA_MAIN_H__
-#define __ATA_MAIN_H__
-
-#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 *);
-
-#endif
-
-/** @}
- */
Index: pace/drv/block/ata_bd/meson.build
===================================================================
--- uspace/drv/block/ata_bd/meson.build	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ 	(revision )
@@ -1,30 +1,0 @@
-#
-# Copyright (c) 2013 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 = [ 'scsi' ]
-src = files('ata_bd.c', 'main.c')
Index: uspace/drv/block/ddisk/ddisk.c
===================================================================
--- uspace/drv/block/ddisk/ddisk.c	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ uspace/drv/block/ddisk/ddisk.c	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -64,5 +64,5 @@
 static void ddisk_bd_connection(ipc_call_t *, void *);
 
-static void ddisk_irq_handler(ipc_call_t *, ddf_dev_t *);
+static void ddisk_irq_handler(ipc_call_t *, void *);
 
 static driver_ops_t driver_ops = {
@@ -176,10 +176,15 @@
 };
 
-void ddisk_irq_handler(ipc_call_t *icall, ddf_dev_t *dev)
+/** Ddisk IRQ handler.
+ *
+ * @param icall IRQ event notification
+ * @param arg Argument (ddisk_t *)
+ */
+void ddisk_irq_handler(ipc_call_t *icall, void *arg)
 {
 	ddf_msg(LVL_DEBUG, "ddisk_irq_handler(), status=%" PRIx32,
 	    (uint32_t) ipc_get_arg1(icall));
 
-	ddisk_t *ddisk = (ddisk_t *) ddf_dev_data_get(dev);
+	ddisk_t *ddisk = (ddisk_t *)arg;
 
 	fibril_mutex_lock(&ddisk->lock);
@@ -512,5 +517,5 @@
 	ddisk_irq_commands[3].addr = (void *) &res_phys->command;
 	rc = register_interrupt_handler(dev, ddisk->ddisk_res.irq,
-	    ddisk_irq_handler, &ddisk_irq_code, &ddisk->irq_cap);
+	    ddisk_irq_handler, (void *)ddisk, &ddisk_irq_code, &ddisk->irq_cap);
 	if (rc != EOK) {
 		ddf_msg(LVL_ERROR, "Failed to register interrupt handler.");
Index: uspace/drv/block/isa-ide/doc/doxygroups.h
===================================================================
--- uspace/drv/block/isa-ide/doc/doxygroups.h	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
+++ uspace/drv/block/isa-ide/doc/doxygroups.h	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -0,0 +1,4 @@
+/** @addtogroup isa-ide isa-ide
+ * @brief ISA IDE driver
+ * @ingroup drvs
+ */
Index: uspace/drv/block/isa-ide/isa-ide.c
===================================================================
--- uspace/drv/block/isa-ide/isa-ide.c	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
+++ uspace/drv/block/isa-ide/isa-ide.c	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -0,0 +1,508 @@
+/*
+ * 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 isa-ide
+ * @{
+ */
+
+/**
+ * @file
+ * @brief ISA IDE driver
+ */
+
+#include <ddi.h>
+#include <ddf/interrupt.h>
+#include <ddf/log.h>
+#include <async.h>
+#include <as.h>
+#include <fibril_synch.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <str.h>
+#include <inttypes.h>
+#include <errno.h>
+
+#include "isa-ide.h"
+#include "main.h"
+
+static errno_t isa_ide_init_io(isa_ide_channel_t *);
+static void isa_ide_fini_io(isa_ide_channel_t *);
+static errno_t isa_ide_init_irq(isa_ide_channel_t *);
+static void isa_ide_fini_irq(isa_ide_channel_t *);
+static void isa_ide_irq_handler(ipc_call_t *, void *);
+
+static void isa_ide_write_data_16(void *, uint16_t *, size_t);
+static void isa_ide_read_data_16(void *, uint16_t *, size_t);
+static void isa_ide_write_cmd_8(void *, uint16_t, uint8_t);
+static uint8_t isa_ide_read_cmd_8(void *, uint16_t);
+static void isa_ide_write_ctl_8(void *, uint16_t, uint8_t);
+static uint8_t isa_ide_read_ctl_8(void *, uint16_t);
+static errno_t isa_ide_irq_enable(void *);
+static errno_t isa_ide_irq_disable(void *);
+static errno_t isa_ide_add_device(void *, unsigned, void *);
+static errno_t isa_ide_remove_device(void *, unsigned);
+static void isa_ide_msg_debug(void *, char *);
+static void isa_ide_msg_note(void *, char *);
+static void isa_ide_msg_warn(void *, char *);
+static void isa_ide_msg_error(void *, char *);
+
+static const irq_pio_range_t isa_ide_irq_ranges[] = {
+	{
+		.base = 0,
+		.size = sizeof(ata_cmd_t)
+	}
+};
+
+/** IDE interrupt pseudo code. */
+static const irq_cmd_t isa_ide_irq_cmds[] = {
+	{
+		.cmd = CMD_PIO_READ_8,
+		.addr = NULL,  /* will be patched in run-time */
+		.dstarg = 1
+	},
+	{
+		.cmd = CMD_ACCEPT
+	}
+};
+
+/** Initialize ISA IDE channel. */
+errno_t isa_ide_channel_init(isa_ide_ctrl_t *ctrl, isa_ide_channel_t *chan,
+    unsigned chan_id, isa_ide_hwres_t *res)
+{
+	errno_t rc;
+	bool irq_inited = false;
+	ata_params_t params;
+
+	ddf_msg(LVL_DEBUG, "isa_ide_ctrl_init()");
+
+	chan->ctrl = ctrl;
+	chan->chan_id = chan_id;
+	fibril_mutex_initialize(&chan->lock);
+	if (chan_id == 0) {
+		chan->cmd_physical = res->cmd1;
+		chan->ctl_physical = res->ctl1;
+		chan->irq = res->irq1;
+	} else {
+		chan->cmd_physical = res->cmd2;
+		chan->ctl_physical = res->ctl2;
+		chan->irq = res->irq2;
+	}
+
+	ddf_msg(LVL_NOTE, "I/O address %p/%p", (void *) chan->cmd_physical,
+	    (void *) chan->ctl_physical);
+
+	ddf_msg(LVL_DEBUG, "Init I/O");
+	rc = isa_ide_init_io(chan);
+	if (rc != EOK)
+		return rc;
+
+	ddf_msg(LVL_DEBUG, "Init IRQ");
+	rc = isa_ide_init_irq(chan);
+	if (rc != EOK) {
+		ddf_msg(LVL_NOTE, "init IRQ failed");
+		return rc;
+	}
+
+	irq_inited = true;
+
+	ddf_msg(LVL_DEBUG, "isa_ide_ctrl_init(): Initialize IDE channel");
+
+	params.arg = (void *)chan;
+	params.have_irq = (chan->irq >= 0) ? true : false;
+	params.write_data_16 = isa_ide_write_data_16;
+	params.read_data_16 = isa_ide_read_data_16;
+	params.write_cmd_8 = isa_ide_write_cmd_8;
+	params.read_cmd_8 = isa_ide_read_cmd_8;
+	params.write_ctl_8 = isa_ide_write_ctl_8;
+	params.read_ctl_8 = isa_ide_read_ctl_8;
+	params.irq_enable = isa_ide_irq_enable;
+	params.irq_disable = isa_ide_irq_disable;
+	params.add_device = isa_ide_add_device;
+	params.remove_device = isa_ide_remove_device;
+	params.msg_debug = isa_ide_msg_debug;
+	params.msg_note = isa_ide_msg_note;
+	params.msg_warn = isa_ide_msg_warn;
+	params.msg_error = isa_ide_msg_error;
+
+	rc = ata_channel_create(&params, &chan->channel);
+	if (rc != EOK)
+		goto error;
+
+	rc = ata_channel_initialize(chan->channel);
+	if (rc != EOK)
+		goto error;
+
+	ddf_msg(LVL_DEBUG, "isa_ide_ctrl_init: DONE");
+	return EOK;
+error:
+	if (irq_inited)
+		isa_ide_fini_irq(chan);
+	isa_ide_fini_io(chan);
+	return rc;
+}
+
+/** Finalize ISA IDE channel. */
+errno_t isa_ide_channel_fini(isa_ide_channel_t *chan)
+{
+	errno_t rc;
+
+	ddf_msg(LVL_DEBUG, ": isa_ide_ctrl_remove()");
+
+	fibril_mutex_lock(&chan->lock);
+
+	rc = ata_channel_destroy(chan->channel);
+	if (rc != EOK) {
+		fibril_mutex_unlock(&chan->lock);
+		return rc;
+	}
+
+	isa_ide_fini_irq(chan);
+	isa_ide_fini_io(chan);
+	fibril_mutex_unlock(&chan->lock);
+
+	return EOK;
+}
+
+/** Enable device I/O. */
+static errno_t isa_ide_init_io(isa_ide_channel_t *chan)
+{
+	errno_t rc;
+	void *vaddr;
+
+	rc = pio_enable((void *) chan->cmd_physical, sizeof(ata_cmd_t), &vaddr);
+	if (rc != EOK) {
+		ddf_msg(LVL_ERROR, "Cannot initialize device I/O space.");
+		return rc;
+	}
+
+	chan->cmd = vaddr;
+
+	rc = pio_enable((void *) chan->ctl_physical, sizeof(ata_ctl_t), &vaddr);
+	if (rc != EOK) {
+		ddf_msg(LVL_ERROR, "Cannot initialize device I/O space.");
+		return rc;
+	}
+
+	chan->ctl = vaddr;
+	return EOK;
+}
+
+/** Clean up device I/O. */
+static void isa_ide_fini_io(isa_ide_channel_t *chan)
+{
+	(void) chan;
+	/* XXX TODO */
+}
+
+/** Initialize IRQ. */
+static errno_t isa_ide_init_irq(isa_ide_channel_t *chan)
+{
+	irq_code_t irq_code;
+	irq_pio_range_t *ranges;
+	irq_cmd_t *cmds;
+	errno_t rc;
+
+	if (chan->irq < 0)
+		return EOK;
+
+	ranges = malloc(sizeof(isa_ide_irq_ranges));
+	if (ranges == NULL)
+		return ENOMEM;
+
+	cmds = malloc(sizeof(isa_ide_irq_cmds));
+	if (cmds == NULL) {
+		free(cmds);
+		return ENOMEM;
+	}
+
+	memcpy(ranges, &isa_ide_irq_ranges, sizeof(isa_ide_irq_ranges));
+	ranges[0].base = chan->cmd_physical;
+	memcpy(cmds, &isa_ide_irq_cmds, sizeof(isa_ide_irq_cmds));
+	cmds[0].addr = &chan->cmd->status;
+
+	irq_code.rangecount = sizeof(isa_ide_irq_ranges) / sizeof(irq_pio_range_t);
+	irq_code.ranges = ranges;
+	irq_code.cmdcount = sizeof(isa_ide_irq_cmds) / sizeof(irq_cmd_t);
+	irq_code.cmds = cmds;
+
+	ddf_msg(LVL_NOTE, "IRQ %d", chan->irq);
+	rc = register_interrupt_handler(chan->ctrl->dev, chan->irq,
+	    isa_ide_irq_handler, (void *)chan, &irq_code, &chan->ihandle);
+	if (rc != EOK) {
+		ddf_msg(LVL_ERROR, "Error registering IRQ.");
+		goto error;
+	}
+
+	ddf_msg(LVL_DEBUG, "Interrupt handler registered");
+	free(ranges);
+	free(cmds);
+	return EOK;
+error:
+	free(ranges);
+	free(cmds);
+	return rc;
+}
+
+/** Clean up IRQ. */
+static void isa_ide_fini_irq(isa_ide_channel_t *chan)
+{
+	errno_t rc;
+	async_sess_t *parent_sess;
+
+	parent_sess = ddf_dev_parent_sess_get(chan->ctrl->dev);
+
+	rc = hw_res_disable_interrupt(parent_sess, chan->irq);
+	if (rc != EOK)
+		ddf_msg(LVL_ERROR, "Error disabling IRQ.");
+
+	(void) unregister_interrupt_handler(chan->ctrl->dev, chan->ihandle);
+}
+
+/** Interrupt handler.
+ *
+ * @param call Call data
+ * @param arg Argument (isa_ide_channel_t *)
+ */
+static void isa_ide_irq_handler(ipc_call_t *call, void *arg)
+{
+	isa_ide_channel_t *chan = (isa_ide_channel_t *)arg;
+	uint8_t status;
+	async_sess_t *parent_sess;
+
+	status = ipc_get_arg1(call);
+	ata_channel_irq(chan->channel, status);
+
+	parent_sess = ddf_dev_parent_sess_get(chan->ctrl->dev);
+	hw_res_clear_interrupt(parent_sess, chan->irq);
+}
+
+/** Write the data register callback handler.
+ *
+ * @param arg Argument (isa_ide_channel_t *)
+ * @param data Data
+ * @param nwords Number of words to write
+ */
+static void isa_ide_write_data_16(void *arg, uint16_t *data, size_t nwords)
+{
+	isa_ide_channel_t *chan = (isa_ide_channel_t *)arg;
+	size_t i;
+
+	for (i = 0; i < nwords; i++)
+		pio_write_16(&chan->cmd->data_port, data[i]);
+}
+
+/** Read the data register callback handler.
+ *
+ * @param arg Argument (isa_ide_channel_t *)
+ * @param buf Destination buffer
+ * @param nwords Number of words to read
+ */
+static void isa_ide_read_data_16(void *arg, uint16_t *buf, size_t nwords)
+{
+	isa_ide_channel_t *chan = (isa_ide_channel_t *)arg;
+	size_t i;
+
+	for (i = 0; i < nwords; i++)
+		buf[i] = pio_read_16(&chan->cmd->data_port);
+}
+
+/** Write command register callback handler.
+ *
+ * @param arg Argument (isa_ide_channel_t *)
+ * @param off Register offset
+ * @param value Value to write to command register
+ */
+static void isa_ide_write_cmd_8(void *arg, uint16_t off, uint8_t value)
+{
+	isa_ide_channel_t *chan = (isa_ide_channel_t *)arg;
+
+	pio_write_8(((ioport8_t *)chan->cmd) + off, value);
+}
+
+/** Read command register callback handler.
+ *
+ * @param arg Argument (isa_ide_channel_t *)
+ * @param off Register offset
+ * @return value Value read from command register
+ */
+static uint8_t isa_ide_read_cmd_8(void *arg, uint16_t off)
+{
+	isa_ide_channel_t *chan = (isa_ide_channel_t *)arg;
+
+	return pio_read_8(((ioport8_t *)chan->cmd) + off);
+}
+
+/** Write control register callback handler.
+ *
+ * @param arg Argument (isa_ide_channel_t *)
+ * @param off Register offset
+ * @param value Value to write to control register
+ */
+static void isa_ide_write_ctl_8(void *arg, uint16_t off, uint8_t value)
+{
+	isa_ide_channel_t *chan = (isa_ide_channel_t *)arg;
+
+	pio_write_8(((ioport8_t *)chan->ctl) + off, value);
+}
+
+/** Read control register callback handler.
+ *
+ * @param arg Argument (isa_ide_channel_t *)
+ * @param off Register offset
+ * @return value Value read from control register
+ */
+static uint8_t isa_ide_read_ctl_8(void *arg, uint16_t off)
+{
+	isa_ide_channel_t *chan = (isa_ide_channel_t *)arg;
+
+	return pio_read_8(((ioport8_t *)chan->ctl) + off);
+}
+
+/** Enable IRQ callback handler
+ *
+ * @param arg Argument (isa_ide_channel_t *)
+ * @return EOK on success or an error code
+ */
+static errno_t isa_ide_irq_enable(void *arg)
+{
+	isa_ide_channel_t *chan = (isa_ide_channel_t *)arg;
+	async_sess_t *parent_sess;
+	errno_t rc;
+
+	ddf_msg(LVL_DEBUG, "Enable IRQ %d for channel %u",
+	    chan->irq, chan->chan_id);
+
+	parent_sess = ddf_dev_parent_sess_get(chan->ctrl->dev);
+
+	rc = hw_res_enable_interrupt(parent_sess, chan->irq);
+	if (rc != EOK) {
+		ddf_msg(LVL_ERROR, "Error enabling IRQ.");
+		return rc;
+	}
+
+	return EOK;
+}
+
+/** Disable IRQ callback handler
+ *
+ * @param arg Argument (isa_ide_channel_t *)
+ * @return EOK on success or an error code
+ */
+static errno_t isa_ide_irq_disable(void *arg)
+{
+	isa_ide_channel_t *chan = (isa_ide_channel_t *)arg;
+	async_sess_t *parent_sess;
+	errno_t rc;
+
+	ddf_msg(LVL_DEBUG, "Disable IRQ");
+
+	parent_sess = ddf_dev_parent_sess_get(chan->ctrl->dev);
+
+	rc = hw_res_disable_interrupt(parent_sess, chan->irq);
+	if (rc != EOK) {
+		ddf_msg(LVL_ERROR, "Error disabling IRQ.");
+		return rc;
+	}
+
+	return EOK;
+}
+
+/** Add ATA device callback handler.
+ *
+ * @param arg Argument (isa_ide_channel_t *)
+ * @param idx Device index
+ * $param charg Connection handler argument
+ * @return EOK on success or an error code
+ */
+static errno_t isa_ide_add_device(void *arg, unsigned idx, void *charg)
+{
+	isa_ide_channel_t *chan = (isa_ide_channel_t *)arg;
+	return isa_ide_fun_create(chan, idx, charg);
+}
+
+/** Remove ATA device callback handler.
+ *
+ * @param arg Argument (isa_ide_channel_t *)
+ * @param idx Device index
+ * @return EOK on success or an error code
+ */
+static errno_t isa_ide_remove_device(void *arg, unsigned idx)
+{
+	isa_ide_channel_t *chan = (isa_ide_channel_t *)arg;
+	return isa_ide_fun_remove(chan, idx);
+}
+
+/** Debug message callback handler.
+ *
+ * @param arg Argument (isa_ide_channel_t *)
+ * @param msg Message
+ */
+static void isa_ide_msg_debug(void *arg, char *msg)
+{
+	(void)arg;
+	ddf_msg(LVL_DEBUG, "%s", msg);
+}
+
+/** Notice message callback handler.
+ *
+ * @param arg Argument (isa_ide_channel_t *)
+ * @param msg Message
+ */
+static void isa_ide_msg_note(void *arg, char *msg)
+{
+	(void)arg;
+	ddf_msg(LVL_NOTE, "%s", msg);
+}
+
+/** Warning message callback handler.
+ *
+ * @param arg Argument (isa_ide_channel_t *)
+ * @param msg Message
+ */
+static void isa_ide_msg_warn(void *arg, char *msg)
+{
+	(void)arg;
+	ddf_msg(LVL_WARN, "%s", msg);
+}
+
+/** Error message callback handler.
+ *
+ * @param arg Argument (isa_ide_channel_t *)
+ * @param msg Message
+ */
+static void isa_ide_msg_error(void *arg, char *msg)
+{
+	(void)arg;
+	ddf_msg(LVL_ERROR, "%s", msg);
+}
+
+/**
+ * @}
+ */
Index: uspace/drv/block/isa-ide/isa-ide.h
===================================================================
--- uspace/drv/block/isa-ide/isa-ide.h	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
+++ uspace/drv/block/isa-ide/isa-ide.h	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -0,0 +1,110 @@
+/*
+ * 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 isa-ide
+ * @{
+ */
+/** @file ISA IDE driver definitions.
+ */
+
+#ifndef ISA_IDE_H
+#define ISA_IDE_H
+
+#include <ata/ata.h>
+#include <ata/ata_hw.h>
+#include <ddf/driver.h>
+#include <fibril_synch.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+#define NAME "isa-ide"
+
+/** ISA IDE hardware resources */
+typedef struct {
+	uintptr_t cmd1;	/**< Primary channel command block base address. */
+	uintptr_t ctl1;	/**< Primary channel control block base address. */
+	uintptr_t cmd2;	/**< Secondary channel command block base address. */
+	uintptr_t ctl2;	/**< Secondary channel control block base address. */
+	int irq1;	/**< Primary channel IRQ */
+	int irq2;	/**< Secondary channel IRQ */
+} isa_ide_hwres_t;
+
+/** ISA IDE channel */
+typedef struct isa_ide_channel {
+	/** Parent controller */
+	struct isa_ide_ctrl *ctrl;
+	/** I/O base address of the command registers */
+	uintptr_t cmd_physical;
+	/** I/O base address of the control registers */
+	uintptr_t ctl_physical;
+
+	/** Command registers */
+	ata_cmd_t *cmd;
+	/** Control registers */
+	ata_ctl_t *ctl;
+	/** IRQ (-1 if not used) */
+	int irq;
+	/** IRQ handle */
+	cap_irq_handle_t ihandle;
+
+	/** Synchronize controller access */
+	fibril_mutex_t lock;
+	/** Value of status register read by interrupt handler */
+	uint8_t irq_status;
+
+	/** Libata ATA channel */
+	ata_channel_t *channel;
+	struct isa_ide_fun *fun[2];
+
+	/** Channel ID */
+	unsigned chan_id;
+} isa_ide_channel_t;
+
+/** ISA IDE controller */
+typedef struct isa_ide_ctrl {
+	/** DDF device */
+	ddf_dev_t *dev;
+
+	/** Primary and secondary channel */
+	isa_ide_channel_t channel[2];
+} isa_ide_ctrl_t;
+
+/** ISA IDE function */
+typedef struct isa_ide_fun {
+	ddf_fun_t *fun;
+	void *charg;
+} isa_ide_fun_t;
+
+extern errno_t isa_ide_channel_init(isa_ide_ctrl_t *, isa_ide_channel_t *,
+    unsigned, isa_ide_hwres_t *);
+extern errno_t isa_ide_channel_fini(isa_ide_channel_t *);
+
+#endif
+
+/** @}
+ */
Index: uspace/drv/block/isa-ide/isa-ide.ma
===================================================================
--- uspace/drv/block/isa-ide/isa-ide.ma	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
+++ uspace/drv/block/isa-ide/isa-ide.ma	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -0,0 +1,1 @@
+10 isa/ide
Index: uspace/drv/block/isa-ide/main.c
===================================================================
--- uspace/drv/block/isa-ide/main.c	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
+++ uspace/drv/block/isa-ide/main.c	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -0,0 +1,405 @@
+/*
+ * 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 isa-ide
+ * @{
+ */
+
+/** @file
+ */
+
+#include <assert.h>
+#include <stdio.h>
+#include <errno.h>
+#include <str_error.h>
+#include <ddf/driver.h>
+#include <ddf/log.h>
+#include <device/hw_res_parsed.h>
+
+#include "isa-ide.h"
+#include "main.h"
+
+static errno_t isa_ide_dev_add(ddf_dev_t *dev);
+static errno_t isa_ide_dev_remove(ddf_dev_t *dev);
+static errno_t isa_ide_dev_gone(ddf_dev_t *dev);
+static errno_t isa_ide_fun_online(ddf_fun_t *fun);
+static errno_t isa_ide_fun_offline(ddf_fun_t *fun);
+
+static void isa_ide_connection(ipc_call_t *, void *);
+
+static driver_ops_t driver_ops = {
+	.dev_add = &isa_ide_dev_add,
+	.dev_remove = &isa_ide_dev_remove,
+	.dev_gone = &isa_ide_dev_gone,
+	.fun_online = &isa_ide_fun_online,
+	.fun_offline = &isa_ide_fun_offline
+};
+
+static driver_t isa_ide_driver = {
+	.name = NAME,
+	.driver_ops = &driver_ops
+};
+
+static errno_t isa_ide_get_res(ddf_dev_t *dev, isa_ide_hwres_t *res)
+{
+	async_sess_t *parent_sess;
+	hw_res_list_parsed_t hw_res;
+	hw_res_flags_t flags;
+	errno_t rc;
+
+	parent_sess = ddf_dev_parent_sess_get(dev);
+	if (parent_sess == NULL)
+		return ENOMEM;
+
+	rc = hw_res_get_flags(parent_sess, &flags);
+	if (rc != EOK)
+		return rc;
+
+	/*
+	 * Prevent attaching to the legacy ISA IDE register block
+	 * on a system with PCI not to conflict with PCI IDE.
+	 *
+	 * XXX This is a simplification. If we had a PCI-based system without
+	 * PCI-IDE or with PCI-IDE disabled and would still like to use
+	 * an ISA IDE controller, this would prevent us from doing so.
+	 */
+	if (flags & hwf_isa_bridge) {
+		ddf_msg(LVL_NOTE, "Will not attach to PCI/ISA bridge.");
+		return EIO;
+	}
+
+	hw_res_list_parsed_init(&hw_res);
+	rc = hw_res_get_list_parsed(parent_sess, &hw_res, 0);
+	if (rc != EOK)
+		return rc;
+
+	if (hw_res.io_ranges.count != 4) {
+		rc = EINVAL;
+		goto error;
+	}
+
+	/* I/O ranges */
+
+	addr_range_t *cmd1_rng = &hw_res.io_ranges.ranges[0];
+	addr_range_t *ctl1_rng = &hw_res.io_ranges.ranges[1];
+	addr_range_t *cmd2_rng = &hw_res.io_ranges.ranges[2];
+	addr_range_t *ctl2_rng = &hw_res.io_ranges.ranges[3];
+	res->cmd1 = RNGABS(*cmd1_rng);
+	res->ctl1 = RNGABS(*ctl1_rng);
+	res->cmd2 = RNGABS(*cmd2_rng);
+	res->ctl2 = RNGABS(*ctl2_rng);
+
+	if (RNGSZ(*ctl1_rng) < sizeof(ata_ctl_t)) {
+		rc = EINVAL;
+		goto error;
+	}
+
+	if (RNGSZ(*cmd1_rng) < sizeof(ata_cmd_t)) {
+		rc = EINVAL;
+		goto error;
+	}
+
+	if (RNGSZ(*ctl2_rng) < sizeof(ata_ctl_t)) {
+		rc = EINVAL;
+		goto error;
+	}
+
+	if (RNGSZ(*cmd2_rng) < sizeof(ata_cmd_t)) {
+		rc = EINVAL;
+		goto error;
+	}
+
+	/* IRQ */
+	if (hw_res.irqs.count > 0) {
+		res->irq1 = hw_res.irqs.irqs[0];
+	} else {
+		res->irq1 = -1;
+	}
+
+	if (hw_res.irqs.count > 1) {
+		res->irq2 = hw_res.irqs.irqs[1];
+	} else {
+		res->irq2 = -1;
+	}
+
+	return EOK;
+error:
+	hw_res_list_parsed_clean(&hw_res);
+	return rc;
+}
+
+/** Add new device
+ *
+ * @param  dev New device
+ * @return     EOK on success or an error code.
+ */
+static errno_t isa_ide_dev_add(ddf_dev_t *dev)
+{
+	isa_ide_ctrl_t *ctrl;
+	isa_ide_hwres_t res;
+	errno_t rc;
+
+	rc = isa_ide_get_res(dev, &res);
+	if (rc != EOK) {
+		ddf_msg(LVL_ERROR, "Invalid HW resource configuration.");
+		return EINVAL;
+	}
+
+	ctrl = ddf_dev_data_alloc(dev, sizeof(isa_ide_ctrl_t));
+	if (ctrl == NULL) {
+		ddf_msg(LVL_ERROR, "Failed allocating soft state.");
+		rc = ENOMEM;
+		goto error;
+	}
+
+	ctrl->dev = dev;
+
+	rc = isa_ide_channel_init(ctrl, &ctrl->channel[0], 0, &res);
+	if (rc == ENOENT)
+		goto error;
+
+	rc = isa_ide_channel_init(ctrl, &ctrl->channel[1], 1, &res);
+	if (rc == ENOENT)
+		goto error;
+
+	if (rc != EOK) {
+		ddf_msg(LVL_ERROR, "Failed initializing ATA controller.");
+		rc = EIO;
+		goto error;
+	}
+
+	return EOK;
+error:
+	return rc;
+}
+
+static char *isa_ide_fun_name(isa_ide_channel_t *chan, unsigned idx)
+{
+	char *fun_name;
+
+	if (asprintf(&fun_name, "c%ud%u", chan->chan_id, idx) < 0)
+		return NULL;
+
+	return fun_name;
+}
+
+errno_t isa_ide_fun_create(isa_ide_channel_t *chan, unsigned idx, void *charg)
+{
+	errno_t rc;
+	char *fun_name = NULL;
+	ddf_fun_t *fun = NULL;
+	isa_ide_fun_t *ifun = NULL;
+	bool bound = false;
+
+	fun_name = isa_ide_fun_name(chan, idx);
+	if (fun_name == NULL) {
+		ddf_msg(LVL_ERROR, "Out of memory.");
+		rc = ENOMEM;
+		goto error;
+	}
+
+	fun = ddf_fun_create(chan->ctrl->dev, fun_exposed, fun_name);
+	if (fun == NULL) {
+		ddf_msg(LVL_ERROR, "Failed creating DDF function.");
+		rc = ENOMEM;
+		goto error;
+	}
+
+	/* Allocate soft state */
+	ifun = ddf_fun_data_alloc(fun, sizeof(isa_ide_fun_t));
+	if (ifun == NULL) {
+		ddf_msg(LVL_ERROR, "Failed allocating softstate.");
+		rc = ENOMEM;
+		goto error;
+	}
+
+	ifun->fun = fun;
+	ifun->charg = charg;
+
+	/* Set up a connection handler. */
+	ddf_fun_set_conn_handler(fun, isa_ide_connection);
+
+	rc = ddf_fun_bind(fun);
+	if (rc != EOK) {
+		ddf_msg(LVL_ERROR, "Failed binding DDF function %s: %s",
+		    fun_name, str_error(rc));
+		goto error;
+	}
+
+	bound = true;
+
+	rc = ddf_fun_add_to_category(fun, "disk");
+	if (rc != EOK) {
+		ddf_msg(LVL_ERROR, "Failed adding function %s to "
+		    "category 'disk': %s", fun_name, str_error(rc));
+		goto error;
+	}
+
+	free(fun_name);
+	return EOK;
+error:
+	if (bound)
+		ddf_fun_unbind(fun);
+	if (fun != NULL)
+		ddf_fun_destroy(fun);
+	if (fun_name != NULL)
+		free(fun_name);
+
+	return rc;
+}
+
+errno_t isa_ide_fun_remove(isa_ide_channel_t *chan, unsigned idx)
+{
+	errno_t rc;
+	char *fun_name;
+	isa_ide_fun_t *ifun = chan->fun[idx];
+
+	fun_name = isa_ide_fun_name(chan, idx);
+	if (fun_name == NULL) {
+		ddf_msg(LVL_ERROR, "Out of memory.");
+		rc = ENOMEM;
+		goto error;
+	}
+
+	ddf_msg(LVL_DEBUG, "isa_ide_fun_remove(%p, '%s')", ifun, fun_name);
+	rc = ddf_fun_offline(ifun->fun);
+	if (rc != EOK) {
+		ddf_msg(LVL_ERROR, "Error offlining function '%s'.", fun_name);
+		goto error;
+	}
+
+	rc = ddf_fun_unbind(ifun->fun);
+	if (rc != EOK) {
+		ddf_msg(LVL_ERROR, "Failed unbinding function '%s'.", fun_name);
+		goto error;
+	}
+
+	ddf_fun_destroy(ifun->fun);
+	free(fun_name);
+	return EOK;
+error:
+	if (fun_name != NULL)
+		free(fun_name);
+	return rc;
+}
+
+errno_t isa_ide_fun_unbind(isa_ide_channel_t *chan, unsigned idx)
+{
+	errno_t rc;
+	char *fun_name;
+	isa_ide_fun_t *ifun = chan->fun[idx];
+
+	fun_name = isa_ide_fun_name(chan, idx);
+	if (fun_name == NULL) {
+		ddf_msg(LVL_ERROR, "Out of memory.");
+		rc = ENOMEM;
+		goto error;
+	}
+
+	ddf_msg(LVL_DEBUG, "isa_ide_fun_unbind(%p, '%s')", ifun, fun_name);
+	rc = ddf_fun_unbind(ifun->fun);
+	if (rc != EOK) {
+		ddf_msg(LVL_ERROR, "Failed unbinding function '%s'.", fun_name);
+		goto error;
+	}
+
+	ddf_fun_destroy(ifun->fun);
+	free(fun_name);
+	return EOK;
+error:
+	if (fun_name != NULL)
+		free(fun_name);
+	return rc;
+}
+
+static errno_t isa_ide_dev_remove(ddf_dev_t *dev)
+{
+	isa_ide_ctrl_t *ctrl = (isa_ide_ctrl_t *)ddf_dev_data_get(dev);
+	errno_t rc;
+
+	ddf_msg(LVL_DEBUG, "isa_ide_dev_remove(%p)", dev);
+
+	rc = isa_ide_channel_fini(&ctrl->channel[0]);
+	if (rc != EOK)
+		return rc;
+
+	rc = isa_ide_channel_fini(&ctrl->channel[1]);
+	if (rc != EOK)
+		return rc;
+
+	return EOK;
+}
+
+static errno_t isa_ide_dev_gone(ddf_dev_t *dev)
+{
+	isa_ide_ctrl_t *ctrl = (isa_ide_ctrl_t *)ddf_dev_data_get(dev);
+	errno_t rc;
+
+	ddf_msg(LVL_DEBUG, "isa_ide_dev_gone(%p)", dev);
+
+	rc = isa_ide_channel_fini(&ctrl->channel[0]);
+	if (rc != EOK)
+		return rc;
+
+	rc = isa_ide_channel_fini(&ctrl->channel[1]);
+	if (rc != EOK)
+		return rc;
+
+	return EOK;
+}
+
+static errno_t isa_ide_fun_online(ddf_fun_t *fun)
+{
+	ddf_msg(LVL_DEBUG, "isa_ide_fun_online()");
+	return ddf_fun_online(fun);
+}
+
+static errno_t isa_ide_fun_offline(ddf_fun_t *fun)
+{
+	ddf_msg(LVL_DEBUG, "isa_ide_fun_offline()");
+	return ddf_fun_offline(fun);
+}
+
+static void isa_ide_connection(ipc_call_t *icall, void *arg)
+{
+	isa_ide_fun_t *ifun;
+
+	ifun = (isa_ide_fun_t *) ddf_fun_data_get((ddf_fun_t *)arg);
+	ata_connection(icall, ifun->charg);
+}
+
+int main(int argc, char *argv[])
+{
+	printf(NAME ": HelenOS ISA IDE device driver\n");
+	ddf_log_init(NAME);
+	return ddf_driver_main(&isa_ide_driver);
+}
+
+/**
+ * @}
+ */
Index: uspace/drv/block/isa-ide/main.h
===================================================================
--- uspace/drv/block/isa-ide/main.h	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
+++ uspace/drv/block/isa-ide/main.h	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -0,0 +1,47 @@
+/*
+ * 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 isa-ide
+ * @{
+ */
+/** @file ISA IDE driver main module
+ */
+
+#ifndef MAIN_H
+#define MAIN_H
+
+#include "isa-ide.h"
+
+extern errno_t isa_ide_fun_create(isa_ide_channel_t *, unsigned, void *);
+extern errno_t isa_ide_fun_remove(isa_ide_channel_t *, unsigned);
+extern errno_t isa_ide_fun_unbind(isa_ide_channel_t *, unsigned);
+
+#endif
+
+/** @}
+ */
Index: uspace/drv/block/isa-ide/meson.build
===================================================================
--- uspace/drv/block/isa-ide/meson.build	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
+++ uspace/drv/block/isa-ide/meson.build	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -0,0 +1,30 @@
+#
+# 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 = [ 'ata', 'scsi' ]
+src = files('isa-ide.c', 'main.c')
Index: uspace/drv/block/pci-ide/doc/doxygroups.h
===================================================================
--- uspace/drv/block/pci-ide/doc/doxygroups.h	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
+++ uspace/drv/block/pci-ide/doc/doxygroups.h	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -0,0 +1,4 @@
+/** @addtogroup pci-ide pci-ide
+ * @brief PCI IDE driver
+ * @ingroup drvs
+ */
Index: uspace/drv/block/pci-ide/main.c
===================================================================
--- uspace/drv/block/pci-ide/main.c	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
+++ uspace/drv/block/pci-ide/main.c	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -0,0 +1,383 @@
+/*
+ * 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 pci-ide
+ * @{
+ */
+
+/** @file
+ */
+
+#include <assert.h>
+#include <stdio.h>
+#include <errno.h>
+#include <str_error.h>
+#include <ddf/driver.h>
+#include <ddf/log.h>
+#include <device/hw_res_parsed.h>
+
+#include "pci-ide.h"
+#include "pci-ide_hw.h"
+#include "main.h"
+
+static errno_t pci_ide_dev_add(ddf_dev_t *dev);
+static errno_t pci_ide_dev_remove(ddf_dev_t *dev);
+static errno_t pci_ide_dev_gone(ddf_dev_t *dev);
+static errno_t pci_ide_fun_online(ddf_fun_t *fun);
+static errno_t pci_ide_fun_offline(ddf_fun_t *fun);
+
+static void pci_ide_connection(ipc_call_t *, void *);
+
+static driver_ops_t driver_ops = {
+	.dev_add = &pci_ide_dev_add,
+	.dev_remove = &pci_ide_dev_remove,
+	.dev_gone = &pci_ide_dev_gone,
+	.fun_online = &pci_ide_fun_online,
+	.fun_offline = &pci_ide_fun_offline
+};
+
+static driver_t pci_ide_driver = {
+	.name = NAME,
+	.driver_ops = &driver_ops
+};
+
+static errno_t pci_ide_get_res(ddf_dev_t *dev, pci_ide_hwres_t *res)
+{
+	async_sess_t *parent_sess;
+	hw_res_list_parsed_t hw_res;
+	errno_t rc;
+
+	parent_sess = ddf_dev_parent_sess_get(dev);
+	if (parent_sess == NULL)
+		return ENOMEM;
+
+	hw_res_list_parsed_init(&hw_res);
+	rc = hw_res_get_list_parsed(parent_sess, &hw_res, 0);
+	if (rc != EOK)
+		return rc;
+
+	if (hw_res.io_ranges.count != 1) {
+		rc = EINVAL;
+		goto error;
+	}
+
+	/* Legacty ISA I/O ranges are fixed */
+
+	res->cmd1 = pci_ide_ata_cmd_p;
+	res->ctl1 = pci_ide_ata_ctl_p;
+	res->cmd2 = pci_ide_ata_cmd_s;
+	res->ctl2 = pci_ide_ata_ctl_s;
+
+	/* PCI I/O range */
+	addr_range_t *bmregs_rng = &hw_res.io_ranges.ranges[0];
+	res->bmregs = RNGABS(*bmregs_rng);
+
+	ddf_msg(LVL_NOTE, "sizes: %zu", RNGSZ(*bmregs_rng));
+
+	if (RNGSZ(*bmregs_rng) < sizeof(pci_ide_regs_t)) {
+		rc = EINVAL;
+		goto error;
+	}
+
+	/* IRQ */
+	if (hw_res.irqs.count > 0) {
+		res->irq1 = hw_res.irqs.irqs[0];
+	} else {
+		res->irq1 = -1;
+	}
+
+	if (hw_res.irqs.count > 1) {
+		res->irq2 = hw_res.irqs.irqs[1];
+	} else {
+		res->irq2 = -1;
+	}
+
+	return EOK;
+error:
+	hw_res_list_parsed_clean(&hw_res);
+	return rc;
+}
+
+/** Add new device
+ *
+ * @param  dev New device
+ * @return     EOK on success or an error code.
+ */
+static errno_t pci_ide_dev_add(ddf_dev_t *dev)
+{
+	pci_ide_ctrl_t *ctrl;
+	pci_ide_hwres_t res;
+	errno_t rc;
+
+	rc = pci_ide_get_res(dev, &res);
+	if (rc != EOK) {
+		ddf_msg(LVL_ERROR, "Invalid HW resource configuration.");
+		return EINVAL;
+	}
+
+	ctrl = ddf_dev_data_alloc(dev, sizeof(pci_ide_ctrl_t));
+	if (ctrl == NULL) {
+		ddf_msg(LVL_ERROR, "Failed allocating soft state.");
+		rc = ENOMEM;
+		goto error;
+	}
+
+	ctrl->dev = dev;
+
+	rc = pci_ide_ctrl_init(ctrl, &res);
+	if (rc != EOK)
+		goto error;
+
+	rc = pci_ide_channel_init(ctrl, &ctrl->channel[0], 0, &res);
+	if (rc == ENOENT)
+		goto error;
+
+	rc = pci_ide_channel_init(ctrl, &ctrl->channel[1], 1, &res);
+	if (rc == ENOENT)
+		goto error;
+
+	if (rc != EOK) {
+		ddf_msg(LVL_ERROR, "Failed initializing ATA controller.");
+		rc = EIO;
+		goto error;
+	}
+
+	return EOK;
+error:
+	return rc;
+}
+
+static char *pci_ide_fun_name(pci_ide_channel_t *chan, unsigned idx)
+{
+	char *fun_name;
+
+	if (asprintf(&fun_name, "c%ud%u", chan->chan_id, idx) < 0)
+		return NULL;
+
+	return fun_name;
+}
+
+errno_t pci_ide_fun_create(pci_ide_channel_t *chan, unsigned idx, void *charg)
+{
+	errno_t rc;
+	char *fun_name = NULL;
+	ddf_fun_t *fun = NULL;
+	pci_ide_fun_t *ifun = NULL;
+	bool bound = false;
+
+	fun_name = pci_ide_fun_name(chan, idx);
+	if (fun_name == NULL) {
+		ddf_msg(LVL_ERROR, "Out of memory.");
+		rc = ENOMEM;
+		goto error;
+	}
+
+	fun = ddf_fun_create(chan->ctrl->dev, fun_exposed, fun_name);
+	if (fun == NULL) {
+		ddf_msg(LVL_ERROR, "Failed creating DDF function.");
+		rc = ENOMEM;
+		goto error;
+	}
+
+	/* Allocate soft state */
+	ifun = ddf_fun_data_alloc(fun, sizeof(pci_ide_fun_t));
+	if (ifun == NULL) {
+		ddf_msg(LVL_ERROR, "Failed allocating softstate.");
+		rc = ENOMEM;
+		goto error;
+	}
+
+	ifun->fun = fun;
+	ifun->charg = charg;
+
+	/* Set up a connection handler. */
+	ddf_fun_set_conn_handler(fun, pci_ide_connection);
+
+	rc = ddf_fun_bind(fun);
+	if (rc != EOK) {
+		ddf_msg(LVL_ERROR, "Failed binding DDF function %s: %s",
+		    fun_name, str_error(rc));
+		goto error;
+	}
+
+	bound = true;
+
+	rc = ddf_fun_add_to_category(fun, "disk");
+	if (rc != EOK) {
+		ddf_msg(LVL_ERROR, "Failed adding function %s to "
+		    "category 'disk': %s", fun_name, str_error(rc));
+		goto error;
+	}
+
+	free(fun_name);
+	return EOK;
+error:
+	if (bound)
+		ddf_fun_unbind(fun);
+	if (fun != NULL)
+		ddf_fun_destroy(fun);
+	if (fun_name != NULL)
+		free(fun_name);
+
+	return rc;
+}
+
+errno_t pci_ide_fun_remove(pci_ide_channel_t *chan, unsigned idx)
+{
+	errno_t rc;
+	char *fun_name;
+	pci_ide_fun_t *ifun = chan->fun[idx];
+
+	fun_name = pci_ide_fun_name(chan, idx);
+	if (fun_name == NULL) {
+		ddf_msg(LVL_ERROR, "Out of memory.");
+		rc = ENOMEM;
+		goto error;
+	}
+
+	ddf_msg(LVL_DEBUG, "pci_ide_fun_remove(%p, '%s')", ifun, fun_name);
+	rc = ddf_fun_offline(ifun->fun);
+	if (rc != EOK) {
+		ddf_msg(LVL_ERROR, "Error offlining function '%s'.", fun_name);
+		goto error;
+	}
+
+	rc = ddf_fun_unbind(ifun->fun);
+	if (rc != EOK) {
+		ddf_msg(LVL_ERROR, "Failed unbinding function '%s'.", fun_name);
+		goto error;
+	}
+
+	ddf_fun_destroy(ifun->fun);
+	free(fun_name);
+	return EOK;
+error:
+	if (fun_name != NULL)
+		free(fun_name);
+	return rc;
+}
+
+errno_t pci_ide_fun_unbind(pci_ide_channel_t *chan, unsigned idx)
+{
+	errno_t rc;
+	char *fun_name;
+	pci_ide_fun_t *ifun = chan->fun[idx];
+
+	fun_name = pci_ide_fun_name(chan, idx);
+	if (fun_name == NULL) {
+		ddf_msg(LVL_ERROR, "Out of memory.");
+		rc = ENOMEM;
+		goto error;
+	}
+
+	ddf_msg(LVL_DEBUG, "pci_ide_fun_unbind(%p, '%s')", ifun, fun_name);
+	rc = ddf_fun_unbind(ifun->fun);
+	if (rc != EOK) {
+		ddf_msg(LVL_ERROR, "Failed unbinding function '%s'.", fun_name);
+		goto error;
+	}
+
+	ddf_fun_destroy(ifun->fun);
+	free(fun_name);
+	return EOK;
+error:
+	if (fun_name != NULL)
+		free(fun_name);
+	return rc;
+}
+
+static errno_t pci_ide_dev_remove(ddf_dev_t *dev)
+{
+	pci_ide_ctrl_t *ctrl = (pci_ide_ctrl_t *)ddf_dev_data_get(dev);
+	errno_t rc;
+
+	ddf_msg(LVL_DEBUG, "pci_ide_dev_remove(%p)", dev);
+
+	rc = pci_ide_channel_fini(&ctrl->channel[0]);
+	if (rc != EOK)
+		return rc;
+
+	rc = pci_ide_channel_fini(&ctrl->channel[1]);
+	if (rc != EOK)
+		return rc;
+
+	return EOK;
+}
+
+static errno_t pci_ide_dev_gone(ddf_dev_t *dev)
+{
+	pci_ide_ctrl_t *ctrl = (pci_ide_ctrl_t *)ddf_dev_data_get(dev);
+	errno_t rc;
+
+	ddf_msg(LVL_DEBUG, "pci_ide_dev_gone(%p)", dev);
+
+	rc = pci_ide_ctrl_fini(ctrl);
+	if (rc != EOK)
+		return rc;
+
+	rc = pci_ide_channel_fini(&ctrl->channel[0]);
+	if (rc != EOK)
+		return rc;
+
+	rc = pci_ide_channel_fini(&ctrl->channel[1]);
+	if (rc != EOK)
+		return rc;
+
+	return EOK;
+}
+
+static errno_t pci_ide_fun_online(ddf_fun_t *fun)
+{
+	ddf_msg(LVL_DEBUG, "pci_ide_fun_online()");
+	return ddf_fun_online(fun);
+}
+
+static errno_t pci_ide_fun_offline(ddf_fun_t *fun)
+{
+	ddf_msg(LVL_DEBUG, "pci_ide_fun_offline()");
+	return ddf_fun_offline(fun);
+}
+
+static void pci_ide_connection(ipc_call_t *icall, void *arg)
+{
+	pci_ide_fun_t *ifun;
+
+	ifun = (pci_ide_fun_t *) ddf_fun_data_get((ddf_fun_t *)arg);
+	ata_connection(icall, ifun->charg);
+}
+
+int main(int argc, char *argv[])
+{
+	printf(NAME ": HelenOS PCI IDE device driver\n");
+	ddf_log_init(NAME);
+	return ddf_driver_main(&pci_ide_driver);
+}
+
+/**
+ * @}
+ */
Index: uspace/drv/block/pci-ide/main.h
===================================================================
--- uspace/drv/block/pci-ide/main.h	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
+++ uspace/drv/block/pci-ide/main.h	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -0,0 +1,47 @@
+/*
+ * 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 pci-ide
+ * @{
+ */
+/** @file PCI IDE driver main module
+ */
+
+#ifndef MAIN_H
+#define MAIN_H
+
+#include "pci-ide.h"
+
+extern errno_t pci_ide_fun_create(pci_ide_channel_t *, unsigned, void *);
+extern errno_t pci_ide_fun_remove(pci_ide_channel_t *, unsigned);
+extern errno_t pci_ide_fun_unbind(pci_ide_channel_t *, unsigned);
+
+#endif
+
+/** @}
+ */
Index: uspace/drv/block/pci-ide/meson.build
===================================================================
--- uspace/drv/block/pci-ide/meson.build	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
+++ uspace/drv/block/pci-ide/meson.build	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -0,0 +1,30 @@
+#
+# 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 = [ 'ata', 'scsi' ]
+src = files('pci-ide.c', 'main.c')
Index: uspace/drv/block/pci-ide/pci-ide.c
===================================================================
--- uspace/drv/block/pci-ide/pci-ide.c	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
+++ uspace/drv/block/pci-ide/pci-ide.c	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -0,0 +1,719 @@
+/*
+ * 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 pci-ide
+ * @{
+ */
+
+/**
+ * @file
+ * @brief PCI IDE driver
+ */
+
+#include <assert.h>
+#include <ddi.h>
+#include <ddf/interrupt.h>
+#include <ddf/log.h>
+#include <async.h>
+#include <as.h>
+#include <fibril_synch.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <str.h>
+#include <inttypes.h>
+#include <errno.h>
+
+#include "pci-ide.h"
+#include "main.h"
+
+static errno_t pci_ide_init_io(pci_ide_channel_t *);
+static void pci_ide_fini_io(pci_ide_channel_t *);
+static errno_t pci_ide_init_irq(pci_ide_channel_t *);
+static void pci_ide_fini_irq(pci_ide_channel_t *);
+static void pci_ide_irq_handler(ipc_call_t *, void *);
+
+static void pci_ide_write_data_16(void *, uint16_t *, size_t);
+static void pci_ide_read_data_16(void *, uint16_t *, size_t);
+static void pci_ide_write_cmd_8(void *, uint16_t, uint8_t);
+static uint8_t pci_ide_read_cmd_8(void *, uint16_t);
+static void pci_ide_write_ctl_8(void *, uint16_t, uint8_t);
+static uint8_t pci_ide_read_ctl_8(void *, uint16_t);
+static errno_t pci_ide_irq_enable(void *);
+static errno_t pci_ide_irq_disable(void *);
+static void pci_ide_dma_chan_setup(void *, void *, size_t, ata_dma_dir_t);
+static void pci_ide_dma_chan_teardown(void *);
+static errno_t pci_ide_add_device(void *, unsigned, void *);
+static errno_t pci_ide_remove_device(void *, unsigned);
+static void pci_ide_msg_debug(void *, char *);
+static void pci_ide_msg_note(void *, char *);
+static void pci_ide_msg_warn(void *, char *);
+static void pci_ide_msg_error(void *, char *);
+
+static const irq_pio_range_t pci_ide_irq_ranges[] = {
+	{
+		.base = 0,
+		.size = sizeof(ata_cmd_t)
+	},
+	{
+		.base = 0,
+		.size = sizeof(pci_ide_regs_t)
+	}
+};
+
+/** IDE interrupt pseudo code. */
+static const irq_cmd_t pci_ide_irq_cmds[] = {
+	/* [0] Read BMISX */
+	{
+		.cmd = CMD_PIO_READ_8,
+		.addr = NULL,  /* will be patched in run-time */
+		.dstarg = 1
+	},
+	/* [1] Test BMISX.IDEINTS */
+	{
+		.cmd = CMD_AND,
+		.value = bmisx_ideints,
+		.srcarg = 1,
+		.dstarg = 2
+	},
+	/* [2] if (BMISX.IDEINTS != 0) { */
+	{
+		.cmd = CMD_PREDICATE,
+		.srcarg = 2,
+		.value = 3
+	},
+	/*
+	 * [3] Clear BMISX.IDEINTS by writing 1 to it. This clears bits 6,5,
+	 * but that should not matter.
+	 */
+	{
+		.cmd = CMD_PIO_WRITE_8,
+		.addr = NULL,  /* will be patched in run-time */
+		.value = bmisx_ideints
+	},
+	/* [4] Read IDE status register */
+	{
+		.cmd = CMD_PIO_READ_8,
+		.addr = NULL,  /* will be patched in run-time */
+		.dstarg = 1
+	},
+	/* [5] Accept */
+	{
+		.cmd = CMD_ACCEPT
+	},
+	/* [6] } else { Decline */
+	{
+		.cmd = CMD_DECLINE
+	}
+	/* } */
+};
+
+/** Initialize PCI IDE controller.
+ *
+ * @param ctrl PCI IDE controller
+ * @param res Hardware resources
+ *
+ * @return EOK on success or an error code
+ */
+errno_t pci_ide_ctrl_init(pci_ide_ctrl_t *ctrl, pci_ide_hwres_t *res)
+{
+	errno_t rc;
+	void *vaddr;
+
+	ddf_msg(LVL_DEBUG, "pci_ide_ctrl_init()");
+	ctrl->bmregs_physical = res->bmregs;
+
+	ddf_msg(LVL_NOTE, "Bus master IDE regs I/O address: 0x%lx",
+	    ctrl->bmregs_physical);
+
+	rc = pio_enable((void *)ctrl->bmregs_physical, sizeof(pci_ide_regs_t),
+	    &vaddr);
+	if (rc != EOK) {
+		ddf_msg(LVL_ERROR, "Cannot initialize device I/O space.");
+		return rc;
+	}
+
+	ctrl->bmregs = vaddr;
+
+	ddf_msg(LVL_DEBUG, "pci_ide_ctrl_init: DONE");
+	return EOK;
+}
+
+/** Finalize PCI IDE controller.
+ *
+ * @param ctrl PCI IDE controller
+ * @return EOK on success or an error code
+ */
+errno_t pci_ide_ctrl_fini(pci_ide_ctrl_t *ctrl)
+{
+	ddf_msg(LVL_DEBUG, ": pci_ide_ctrl_fini()");
+
+	// XXX TODO
+	return EOK;
+}
+
+/** Initialize PCI IDE channel. */
+errno_t pci_ide_channel_init(pci_ide_ctrl_t *ctrl, pci_ide_channel_t *chan,
+    unsigned chan_id, pci_ide_hwres_t *res)
+{
+	errno_t rc;
+	bool irq_inited = false;
+	ata_params_t params;
+	void *buffer;
+
+	ddf_msg(LVL_DEBUG, "pci_ide_channel_init()");
+
+	chan->ctrl = ctrl;
+	chan->chan_id = chan_id;
+	fibril_mutex_initialize(&chan->lock);
+	if (chan_id == 0) {
+		chan->cmd_physical = res->cmd1;
+		chan->ctl_physical = res->ctl1;
+		chan->irq = res->irq1;
+	} else {
+		chan->cmd_physical = res->cmd2;
+		chan->ctl_physical = res->ctl2;
+		chan->irq = res->irq2;
+	}
+
+	chan->dma_buf_size = 8192;
+
+	ddf_msg(LVL_NOTE, "I/O address %p/%p", (void *) chan->cmd_physical,
+	    (void *) chan->ctl_physical);
+
+	ddf_msg(LVL_DEBUG, "Init I/O");
+	rc = pci_ide_init_io(chan);
+	if (rc != EOK)
+		return rc;
+
+	ddf_msg(LVL_DEBUG, "Init IRQ");
+	rc = pci_ide_init_irq(chan);
+	if (rc != EOK) {
+		ddf_msg(LVL_NOTE, "init IRQ failed");
+		goto error;
+	}
+
+	irq_inited = true;
+
+	ddf_msg(LVL_DEBUG, "Allocate PRD table");
+
+	buffer = AS_AREA_ANY;
+	rc = dmamem_map_anonymous(sizeof (pci_ide_prd_t), DMAMEM_4GiB | 0xffff,
+	    AS_AREA_WRITE | AS_AREA_READ, 0, &chan->prdt_pa, &buffer);
+	if (rc != EOK) {
+		ddf_msg(LVL_NOTE, "Failed allocating PRD table.");
+		goto error;
+	}
+
+	chan->prdt = (pci_ide_prd_t *)buffer;
+
+	ddf_msg(LVL_DEBUG, "Allocate DMA buffer");
+
+	buffer = AS_AREA_ANY;
+	rc = dmamem_map_anonymous(chan->dma_buf_size, DMAMEM_4GiB | 0xffff,
+	    AS_AREA_WRITE | AS_AREA_READ, 0, &chan->dma_buf_pa, &buffer);
+	if (rc != EOK) {
+		ddf_msg(LVL_NOTE, "Failed allocating PRD table.");
+		goto error;
+	}
+
+	chan->dma_buf = buffer;
+
+	/* Populate PRD with information on our single DMA buffer */
+	chan->prdt->pba = host2uint32_t_le(chan->dma_buf_pa);
+	chan->prdt->bcnt = host2uint16_t_le(chan->dma_buf_size);
+	chan->prdt->eot_res = host2uint16_t_le(pci_ide_prd_eot);
+
+	/* Program PRD table pointer register */
+	if (chan_id == 0) {
+		pio_write_32(&chan->ctrl->bmregs->bmidtpp,
+		    (uint32_t)chan->prdt_pa);
+	} else {
+		pio_write_32(&chan->ctrl->bmregs->bmidtps,
+		    (uint32_t)chan->prdt_pa);
+	}
+
+	ddf_msg(LVL_DEBUG, "pci_ide_channel_init(): Initialize IDE channel");
+
+	params.arg = (void *)chan;
+	params.have_irq = (chan->irq >= 0) ? true : false;
+	params.use_dma = true;
+	params.max_dma_xfer = chan->dma_buf_size;
+	params.write_data_16 = pci_ide_write_data_16;
+	params.read_data_16 = pci_ide_read_data_16;
+	params.write_cmd_8 = pci_ide_write_cmd_8;
+	params.read_cmd_8 = pci_ide_read_cmd_8;
+	params.write_ctl_8 = pci_ide_write_ctl_8;
+	params.read_ctl_8 = pci_ide_read_ctl_8;
+	params.irq_enable = pci_ide_irq_enable;
+	params.irq_disable = pci_ide_irq_disable;
+	params.dma_chan_setup = pci_ide_dma_chan_setup;
+	params.dma_chan_teardown = pci_ide_dma_chan_teardown;
+	params.add_device = pci_ide_add_device;
+	params.remove_device = pci_ide_remove_device;
+	params.msg_debug = pci_ide_msg_debug;
+	params.msg_note = pci_ide_msg_note;
+	params.msg_warn = pci_ide_msg_warn;
+	params.msg_error = pci_ide_msg_error;
+
+	rc = ata_channel_create(&params, &chan->channel);
+	if (rc != EOK)
+		goto error;
+
+	rc = ata_channel_initialize(chan->channel);
+	if (rc != EOK)
+		goto error;
+
+	ddf_msg(LVL_DEBUG, "pci_ide_channel_init: DONE");
+	return EOK;
+error:
+	if (irq_inited)
+		pci_ide_fini_irq(chan);
+	pci_ide_fini_io(chan);
+	return rc;
+}
+
+/** Finalize PCI IDE channel. */
+errno_t pci_ide_channel_fini(pci_ide_channel_t *chan)
+{
+	errno_t rc;
+
+	ddf_msg(LVL_DEBUG, ": pci_ide_channel_fini()");
+
+	fibril_mutex_lock(&chan->lock);
+
+	rc = ata_channel_destroy(chan->channel);
+	if (rc != EOK) {
+		fibril_mutex_unlock(&chan->lock);
+		return rc;
+	}
+
+	pci_ide_fini_irq(chan);
+	pci_ide_fini_io(chan);
+	fibril_mutex_unlock(&chan->lock);
+
+	return EOK;
+}
+
+/** Enable device I/O. */
+static errno_t pci_ide_init_io(pci_ide_channel_t *chan)
+{
+	errno_t rc;
+	void *vaddr;
+
+	rc = pio_enable((void *) chan->cmd_physical, sizeof(ata_cmd_t), &vaddr);
+	if (rc != EOK) {
+		ddf_msg(LVL_ERROR, "Cannot initialize device I/O space.");
+		return rc;
+	}
+
+	chan->cmd = vaddr;
+
+	rc = pio_enable((void *) chan->ctl_physical, sizeof(ata_ctl_t), &vaddr);
+	if (rc != EOK) {
+		ddf_msg(LVL_ERROR, "Cannot initialize device I/O space.");
+		return rc;
+	}
+
+	chan->ctl = vaddr;
+
+	rc = pio_enable((void *) chan->cmd_physical, sizeof(ata_cmd_t), &vaddr);
+	if (rc != EOK) {
+		ddf_msg(LVL_ERROR, "Cannot initialize device I/O space.");
+		return rc;
+	}
+
+	chan->cmd = vaddr;
+
+	return EOK;
+}
+
+/** Clean up device I/O. */
+static void pci_ide_fini_io(pci_ide_channel_t *chan)
+{
+	(void) chan;
+	/* XXX TODO */
+}
+
+/** Initialize IRQ. */
+static errno_t pci_ide_init_irq(pci_ide_channel_t *chan)
+{
+	irq_code_t irq_code;
+	irq_pio_range_t *ranges;
+	irq_cmd_t *cmds;
+	uint8_t *bmisx;
+	errno_t rc;
+
+	if (chan->irq < 0)
+		return EOK;
+
+	ranges = malloc(sizeof(pci_ide_irq_ranges));
+	if (ranges == NULL)
+		return ENOMEM;
+
+	cmds = malloc(sizeof(pci_ide_irq_cmds));
+	if (cmds == NULL) {
+		free(cmds);
+		return ENOMEM;
+	}
+
+	/* Bus master IDE status register (primary or secondary) */
+	bmisx = chan->chan_id == 0 ?
+	    &chan->ctrl->bmregs->bmisp :
+	    &chan->ctrl->bmregs->bmiss;
+
+	memcpy(ranges, &pci_ide_irq_ranges, sizeof(pci_ide_irq_ranges));
+	ranges[0].base = chan->cmd_physical;
+	ranges[1].base = chan->ctrl->bmregs_physical;
+
+	memcpy(cmds, &pci_ide_irq_cmds, sizeof(pci_ide_irq_cmds));
+	cmds[0].addr = bmisx;
+	cmds[3].addr = bmisx;
+	cmds[4].addr = &chan->cmd->status;
+
+	irq_code.rangecount = sizeof(pci_ide_irq_ranges) / sizeof(irq_pio_range_t);
+	irq_code.ranges = ranges;
+	irq_code.cmdcount = sizeof(pci_ide_irq_cmds) / sizeof(irq_cmd_t);
+	irq_code.cmds = cmds;
+
+	ddf_msg(LVL_NOTE, "IRQ %d", chan->irq);
+	rc = register_interrupt_handler(chan->ctrl->dev, chan->irq,
+	    pci_ide_irq_handler, (void *)chan, &irq_code, &chan->ihandle);
+	if (rc != EOK) {
+		ddf_msg(LVL_ERROR, "Error registering IRQ.");
+		goto error;
+	}
+
+	ddf_msg(LVL_DEBUG, "Interrupt handler registered");
+	free(ranges);
+	free(cmds);
+	return EOK;
+error:
+	free(ranges);
+	free(cmds);
+	return rc;
+}
+
+/** Clean up IRQ. */
+static void pci_ide_fini_irq(pci_ide_channel_t *chan)
+{
+	errno_t rc;
+	async_sess_t *parent_sess;
+
+	parent_sess = ddf_dev_parent_sess_get(chan->ctrl->dev);
+
+	rc = hw_res_disable_interrupt(parent_sess, chan->irq);
+	if (rc != EOK)
+		ddf_msg(LVL_ERROR, "Error disabling IRQ.");
+
+	(void) unregister_interrupt_handler(chan->ctrl->dev, chan->ihandle);
+}
+
+/** Interrupt handler.
+ *
+ * @param call Call data
+ * @param arg Argument (pci_ide_channel_t *)
+ */
+static void pci_ide_irq_handler(ipc_call_t *call, void *arg)
+{
+	pci_ide_channel_t *chan = (pci_ide_channel_t *)arg;
+	uint8_t status;
+	async_sess_t *parent_sess;
+
+	status = ipc_get_arg1(call);
+	ata_channel_irq(chan->channel, status);
+
+	parent_sess = ddf_dev_parent_sess_get(chan->ctrl->dev);
+	hw_res_clear_interrupt(parent_sess, chan->irq);
+}
+
+/** Write the data register callback handler.
+ *
+ * @param arg Argument (pci_ide_channel_t *)
+ * @param data Data
+ * @param nwords Number of words to write
+ */
+static void pci_ide_write_data_16(void *arg, uint16_t *data, size_t nwords)
+{
+	pci_ide_channel_t *chan = (pci_ide_channel_t *)arg;
+	size_t i;
+
+	for (i = 0; i < nwords; i++)
+		pio_write_16(&chan->cmd->data_port, data[i]);
+}
+
+/** Read the data register callback handler.
+ *
+ * @param arg Argument (pci_ide_channel_t *)
+ * @param buf Destination buffer
+ * @param nwords Number of words to read
+ */
+static void pci_ide_read_data_16(void *arg, uint16_t *buf, size_t nwords)
+{
+	pci_ide_channel_t *chan = (pci_ide_channel_t *)arg;
+	size_t i;
+
+	for (i = 0; i < nwords; i++)
+		buf[i] = pio_read_16(&chan->cmd->data_port);
+}
+
+/** Write command register callback handler.
+ *
+ * @param arg Argument (pci_ide_channel_t *)
+ * @param off Register offset
+ * @param value Value to write to command register
+ */
+static void pci_ide_write_cmd_8(void *arg, uint16_t off, uint8_t value)
+{
+	pci_ide_channel_t *chan = (pci_ide_channel_t *)arg;
+
+	pio_write_8(((ioport8_t *)chan->cmd) + off, value);
+}
+
+/** Read command register callback handler.
+ *
+ * @param arg Argument (pci_ide_channel_t *)
+ * @param off Register offset
+ * @return value Value read from command register
+ */
+static uint8_t pci_ide_read_cmd_8(void *arg, uint16_t off)
+{
+	pci_ide_channel_t *chan = (pci_ide_channel_t *)arg;
+
+	return pio_read_8(((ioport8_t *)chan->cmd) + off);
+}
+
+/** Write control register callback handler.
+ *
+ * @param arg Argument (pci_ide_channel_t *)
+ * @param off Register offset
+ * @param value Value to write to control register
+ */
+static void pci_ide_write_ctl_8(void *arg, uint16_t off, uint8_t value)
+{
+	pci_ide_channel_t *chan = (pci_ide_channel_t *)arg;
+
+	pio_write_8(((ioport8_t *)chan->ctl) + off, value);
+}
+
+/** Read control register callback handler.
+ *
+ * @param arg Argument (pci_ide_channel_t *)
+ * @param off Register offset
+ * @return value Value read from control register
+ */
+static uint8_t pci_ide_read_ctl_8(void *arg, uint16_t off)
+{
+	pci_ide_channel_t *chan = (pci_ide_channel_t *)arg;
+
+	return pio_read_8(((ioport8_t *)chan->ctl) + off);
+}
+
+/** Enable IRQ callback handler
+ *
+ * @param arg Argument (pci_ide_channel_t *)
+ * @return EOK on success or an error code
+ */
+static errno_t pci_ide_irq_enable(void *arg)
+{
+	pci_ide_channel_t *chan = (pci_ide_channel_t *)arg;
+	async_sess_t *parent_sess;
+	errno_t rc;
+
+	ddf_msg(LVL_DEBUG, "Enable IRQ %d for channel %u",
+	    chan->irq, chan->chan_id);
+
+	parent_sess = ddf_dev_parent_sess_get(chan->ctrl->dev);
+
+	rc = hw_res_enable_interrupt(parent_sess, chan->irq);
+	if (rc != EOK) {
+		ddf_msg(LVL_ERROR, "Error enabling IRQ.");
+		return rc;
+	}
+
+	return EOK;
+}
+
+/** Disable IRQ callback handler
+ *
+ * @param arg Argument (pci_ide_channel_t *)
+ * @return EOK on success or an error code
+ */
+static errno_t pci_ide_irq_disable(void *arg)
+{
+	pci_ide_channel_t *chan = (pci_ide_channel_t *)arg;
+	async_sess_t *parent_sess;
+	errno_t rc;
+
+	ddf_msg(LVL_DEBUG, "Disable IRQ");
+
+	parent_sess = ddf_dev_parent_sess_get(chan->ctrl->dev);
+
+	rc = hw_res_disable_interrupt(parent_sess, chan->irq);
+	if (rc != EOK) {
+		ddf_msg(LVL_ERROR, "Error disabling IRQ.");
+		return rc;
+	}
+
+	return EOK;
+}
+
+/** Set up DMA channel callback handler
+ *
+ * @param arg Argument (pci_ide_channel_t *)
+ * @param buf Buffer
+ * @param buf_size Buffer size
+ * @param dir DMA transfer direction
+ */
+static void pci_ide_dma_chan_setup(void *arg, void *buf, size_t buf_size,
+    ata_dma_dir_t dir)
+{
+	pci_ide_channel_t *chan = (pci_ide_channel_t *)arg;
+	uint8_t *bmicx;
+	uint8_t val;
+
+	/* Needed later in teardown */
+	chan->cur_dir = dir;
+	chan->cur_buf = buf;
+	chan->cur_buf_size = buf_size;
+
+	if (dir == ata_dma_write) {
+		assert(buf_size <= chan->dma_buf_size);
+		memcpy(chan->dma_buf, buf, buf_size);
+	}
+
+	/* Primary or secondary channel control register */
+	bmicx = (chan->chan_id == 0) ? &chan->ctrl->bmregs->bmicp :
+	    &chan->ctrl->bmregs->bmics;
+
+	/* Set read / write */
+	val = (dir == ata_dma_write) ? bmicx_rwcon : 0;
+	pio_write_8(bmicx, val);
+
+	/* Start bus master DMA engine */
+	val = val | bmicx_ssbm;
+	pio_write_8(bmicx, val);
+}
+
+/** Tear down DMA channel callback handler
+ *
+ * @param arg Argument (pci_ide_channel_t *)
+ */
+static void pci_ide_dma_chan_teardown(void *arg)
+{
+	pci_ide_channel_t *chan = (pci_ide_channel_t *)arg;
+	uint8_t *bmicx;
+	uint8_t val;
+
+	/* Primary or secondary channel control register */
+	bmicx = (chan->chan_id == 0) ? &chan->ctrl->bmregs->bmicp :
+	    &chan->ctrl->bmregs->bmics;
+
+	/* Stop bus master DMA engine clear SSBM but keep RWCON the same */
+	val = (chan->cur_dir == ata_dma_write) ? bmicx_rwcon : 0;
+	pio_write_8(bmicx, val);
+
+	if (chan->cur_dir == ata_dma_read) {
+		assert(chan->cur_buf_size <= chan->dma_buf_size);
+		memcpy(chan->cur_buf, chan->dma_buf, chan->cur_buf_size);
+	}
+}
+
+/** Add ATA device callback handler.
+ *
+ * @param arg Argument (pci_ide_channel_t *)
+ * @param idx Device index
+ * $param charg Connection handler argument
+ * @return EOK on success or an error code
+ */
+static errno_t pci_ide_add_device(void *arg, unsigned idx, void *charg)
+{
+	pci_ide_channel_t *chan = (pci_ide_channel_t *)arg;
+	return pci_ide_fun_create(chan, idx, charg);
+}
+
+/** Remove ATA device callback handler.
+ *
+ * @param arg Argument (pci_ide_channel_t *)
+ * @param idx Device index
+ * @return EOK on success or an error code
+ */
+static errno_t pci_ide_remove_device(void *arg, unsigned idx)
+{
+	pci_ide_channel_t *chan = (pci_ide_channel_t *)arg;
+	return pci_ide_fun_remove(chan, idx);
+}
+
+/** Debug message callback handler.
+ *
+ * @param arg Argument (pci_ide_channel_t *)
+ * @param msg Message
+ */
+static void pci_ide_msg_debug(void *arg, char *msg)
+{
+	(void)arg;
+	ddf_msg(LVL_DEBUG, "%s", msg);
+}
+
+/** Notice message callback handler.
+ *
+ * @param arg Argument (pci_ide_channel_t *)
+ * @param msg Message
+ */
+static void pci_ide_msg_note(void *arg, char *msg)
+{
+	(void)arg;
+	ddf_msg(LVL_NOTE, "%s", msg);
+}
+
+/** Warning message callback handler.
+ *
+ * @param arg Argument (pci_ide_channel_t *)
+ * @param msg Message
+ */
+static void pci_ide_msg_warn(void *arg, char *msg)
+{
+	(void)arg;
+	ddf_msg(LVL_WARN, "%s", msg);
+}
+
+/** Error message callback handler.
+ *
+ * @param arg Argument (pci_ide_channel_t *)
+ * @param msg Message
+ */
+static void pci_ide_msg_error(void *arg, char *msg)
+{
+	(void)arg;
+	ddf_msg(LVL_ERROR, "%s", msg);
+}
+
+/**
+ * @}
+ */
Index: uspace/drv/block/pci-ide/pci-ide.h
===================================================================
--- uspace/drv/block/pci-ide/pci-ide.h	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
+++ uspace/drv/block/pci-ide/pci-ide.h	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -0,0 +1,135 @@
+/*
+ * 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 pci-ide
+ * @{
+ */
+/** @file PCI IDE driver definitions.
+ */
+
+#ifndef PCI_IDE_H
+#define PCI_IDE_H
+
+#include <ata/ata.h>
+#include <ata/ata_hw.h>
+#include <ddf/driver.h>
+#include <fibril_synch.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include "pci-ide_hw.h"
+
+#define NAME "pci-ide"
+
+/** PCI IDE hardware resources */
+typedef struct {
+	uintptr_t bmregs; /** PCI Bus Master register block base address. */
+	uintptr_t cmd1;	/**< Primary channel command block base address. */
+	uintptr_t ctl1;	/**< Primary channel control block base address. */
+	uintptr_t cmd2;	/**< Secondary channel command block base address. */
+	uintptr_t ctl2;	/**< Secondary channel control block base address. */
+	int irq1;	/**< Primary channel IRQ */
+	int irq2;	/**< Secondary channel IRQ */
+} pci_ide_hwres_t;
+
+/** PCI IDE channel */
+typedef struct pci_ide_channel {
+	/** Parent controller */
+	struct pci_ide_ctrl *ctrl;
+	/** I/O base address of the command registers */
+	uintptr_t cmd_physical;
+	/** I/O base address of the control registers */
+	uintptr_t ctl_physical;
+
+	/** Command registers */
+	ata_cmd_t *cmd;
+	/** Control registers */
+	ata_ctl_t *ctl;
+	/** IRQ (-1 if not used) */
+	int irq;
+	/** IRQ handle */
+	cap_irq_handle_t ihandle;
+
+	/** Synchronize controller access */
+	fibril_mutex_t lock;
+	/** Value of status register read by interrupt handler */
+	uint8_t irq_status;
+
+	/** Physical region descriptor table */
+	pci_ide_prd_t *prdt;
+	/** Physical region descriptor table physical address */
+	uintptr_t prdt_pa;
+	/** DMA buffer */
+	void *dma_buf;
+	/** DMA buffer physical address */
+	uintptr_t dma_buf_pa;
+	/** DMA buffer size */
+	size_t dma_buf_size;
+	/** Current DMA transfer direction */
+	ata_dma_dir_t cur_dir;
+	/** Current data buffer */
+	void *cur_buf;
+	/** Current data buffer size */
+	size_t cur_buf_size;
+
+	/** Libata ATA channel */
+	ata_channel_t *channel;
+	struct pci_ide_fun *fun[2];
+
+	/** Channel ID */
+	unsigned chan_id;
+} pci_ide_channel_t;
+
+/** ISA IDE controller */
+typedef struct pci_ide_ctrl {
+	/** DDF device */
+	ddf_dev_t *dev;
+
+	/** I/O base address of bus master IDE registers */
+	uintptr_t bmregs_physical;
+	/** Bus master IDE registers */
+	pci_ide_regs_t *bmregs;
+	/** Primary and secondary channel */
+	pci_ide_channel_t channel[2];
+} pci_ide_ctrl_t;
+
+/** PCI IDE function */
+typedef struct pci_ide_fun {
+	ddf_fun_t *fun;
+	void *charg;
+} pci_ide_fun_t;
+
+extern errno_t pci_ide_ctrl_init(pci_ide_ctrl_t *, pci_ide_hwres_t *);
+extern errno_t pci_ide_ctrl_fini(pci_ide_ctrl_t *);
+extern errno_t pci_ide_channel_init(pci_ide_ctrl_t *, pci_ide_channel_t *,
+    unsigned, pci_ide_hwres_t *);
+extern errno_t pci_ide_channel_fini(pci_ide_channel_t *);
+
+#endif
+
+/** @}
+ */
Index: uspace/drv/block/pci-ide/pci-ide.ma
===================================================================
--- uspace/drv/block/pci-ide/pci-ide.ma	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
+++ uspace/drv/block/pci-ide/pci-ide.ma	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -0,0 +1,1 @@
+10 pci/ven=8086&dev=7010
Index: uspace/drv/block/pci-ide/pci-ide_hw.h
===================================================================
--- uspace/drv/block/pci-ide/pci-ide_hw.h	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
+++ uspace/drv/block/pci-ide/pci-ide_hw.h	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -0,0 +1,117 @@
+/*
+ * 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 pci-ide
+ * @{
+ */
+/** @file PCI IDE hardware protocol (registers, data structures).
+ *
+ * Based on Intel 82371AB PCI-TO-ISA / IDE XCELERATOR (PIIX4) document.
+ */
+
+#ifndef PCI_IDE_HW_H
+#define PCI_IDE_HW_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+/** PCI Bus Master IDE I/O Registers */
+typedef struct {
+	/** Bus Master IDE Command (primary) */
+	uint8_t bmicp;
+	uint8_t rsvd1;
+	/** Bus Master IDE Command (secondary) */
+	uint8_t bmisp;
+	uint8_t rsvd3;
+	/** Bus Master IDE Descriptor Table Pointer (primary) */
+	uint32_t bmidtpp;
+	/** Bus Master IDE Status (secondary) */
+	uint8_t bmics;
+	uint8_t rsvd9;
+	/** Bus Master IDE Status (secondary) */
+	uint8_t bmiss;
+	uint8_t rsvd11;
+	/** Bus Master IDE Descriptor Table Pointer (secondary) */
+	uint32_t bmidtps;
+} pci_ide_regs_t;
+
+enum pci_ide_bmicx_bits {
+	/** Bus Master Read/Write Control */
+	bmicx_rwcon = 0x08,
+	/** Start/Stop Bus Master */
+	bmicx_ssbm = 0x01
+};
+
+enum pci_ide_bmisx_bits {
+	/** Drive 1 DMA Capable */
+	bmisx_dma1cap = 0x40,
+	/** Drive 0 DMA Capable */
+	bmisx_dma0cap = 0x20,
+	/** IDE Interrupte Status */
+	bmisx_ideints = 0x04,
+	/** IDE DMA Error */
+	bmisx_idedmaerr = 0x02,
+	/** Bus Master IDR Active */
+	bmisx_bmidea = 0x01
+};
+
+#define PCI_IDE_CFG_IDETIM 0x40
+#define PCI_IDE_CFG_SIDETIM 0x44
+#define PCI_IDE_CFG_UDMACTL 0x48
+#define PCI_IDE_CFG_UDMATIM 0x4a
+
+/*
+ * For PIIX we need to use ATA ports at fixed legacy ISA addresses.
+ * There are no corresponding PCI I/O ranges and these adresses are
+ * fixed and cannot be reconfigured.
+ */
+enum {
+	pci_ide_ata_cmd_p = 0x01f0,
+	pci_ide_ata_ctl_p = 0x03f4,
+	pci_ide_ata_cmd_s = 0x0170,
+	pci_ide_ata_ctl_s = 0x0374
+};
+
+enum {
+	pci_ide_prd_eot = 0x8000
+};
+
+/** PIIX physical region descriptor */
+typedef struct {
+	/** Physical base address */
+	uint32_t pba;
+	/** Byte count */
+	uint16_t bcnt;
+	/** EOT / reserved */
+	uint16_t eot_res;
+} pci_ide_prd_t;
+
+#endif
+
+/** @}
+ */
Index: uspace/drv/block/virtio-blk/virtio-blk.c
===================================================================
--- uspace/drv/block/virtio-blk/virtio-blk.c	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ uspace/drv/block/virtio-blk/virtio-blk.c	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -73,7 +73,12 @@
 };
 
-static void virtio_blk_irq_handler(ipc_call_t *icall, ddf_dev_t *dev)
-{
-	virtio_blk_t *virtio_blk = (virtio_blk_t *) ddf_dev_data_get(dev);
+/** VirtIO block IRQ handler.
+ *
+ * @param icall IRQ event notification
+ * @param arg Argument (virtio_blk_t *)
+ */
+static void virtio_blk_irq_handler(ipc_call_t *icall, void *arg)
+{
+	virtio_blk_t *virtio_blk = (virtio_blk_t *)arg;
 	virtio_dev_t *vdev = &virtio_blk->virtio_dev;
 
@@ -145,5 +150,6 @@
 
 	return register_interrupt_handler(dev, virtio_blk->irq,
-	    virtio_blk_irq_handler, &irq_code, &virtio_blk->irq_handle);
+	    virtio_blk_irq_handler, (void *)virtio_blk, &irq_code,
+	    &virtio_blk->irq_handle);
 }
 
Index: uspace/drv/bus/isa/isa.c
===================================================================
--- uspace/drv/bus/isa/isa.c	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ uspace/drv/bus/isa/isa.c	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2018 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * Copyright (c) 2010 Lenka Trochtova
  * Copyright (c) 2011 Jan Vesely
@@ -72,8 +72,9 @@
 #define EBUS_CHILD_FUN_CONF_PATH "/drv/isa/ebus.dev"
 
-#define ISA_MAX_HW_RES 5
+#define ISA_MAX_HW_RES 6
 
 typedef struct {
 	fibril_mutex_t mutex;
+	bool pci_isa_bridge;
 	uint16_t pci_vendor_id;
 	uint16_t pci_device_id;
@@ -92,4 +93,5 @@
 	hw_resource_list_t hw_resources;
 	link_t bus_link;
+	isa_bus_t *bus;
 } isa_fun_t;
 
@@ -204,4 +206,18 @@
 }
 
+static errno_t isa_fun_get_flags(ddf_fun_t *fnode, hw_res_flags_t *rflags)
+{
+	isa_fun_t *fun = isa_fun(fnode);
+	hw_res_flags_t flags;
+
+	flags = 0;
+	if (fun->bus->pci_isa_bridge)
+		flags |= hwf_isa_bridge;
+
+	ddf_msg(LVL_NOTE, "isa_fun_get_flags: returning 0x%x", flags);
+	*rflags = flags;
+	return EOK;
+}
+
 static hw_res_ops_t isa_fun_hw_res_ops = {
 	.get_resource_list = isa_fun_get_resources,
@@ -211,4 +227,5 @@
 	.dma_channel_setup = isa_fun_setup_dma,
 	.dma_channel_remain = isa_fun_remain_dma,
+	.get_flags = isa_fun_get_flags
 };
 
@@ -264,4 +281,5 @@
 	fibril_mutex_initialize(&fun->mutex);
 	fun->hw_resources.resources = fun->resources;
+	fun->bus = isa;
 
 	fun->fnode = fnode;
@@ -743,8 +761,12 @@
 	if (rc != EOK) {
 		ddf_msg(LVL_NOTE, "Cannot read PCI config. Assuming ISA classic.");
+		isa->pci_isa_bridge = false;
 		isa->pci_vendor_id = 0;
 		isa->pci_device_id = 0;
 		isa->pci_class = BASE_CLASS_BRIDGE;
 		isa->pci_subclass = SUB_CLASS_BRIDGE_ISA;
+	} else {
+		ddf_msg(LVL_NOTE, "ISA Bridge");
+		isa->pci_isa_bridge = true;
 	}
 
Index: uspace/drv/bus/isa/isa.dev
===================================================================
--- uspace/drv/bus/isa/isa.dev	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ uspace/drv/bus/isa/isa.dev	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -51,21 +51,10 @@
 	io_range 70 2
 
-ata-c1:
-	match 100 isa/ata_bd
+ide1:
+	match 100 isa/ide
 	io_range 0x1f0 8
 	io_range 0x3f0 8
-
-ata-c2:
-	match 100 isa/ata_bd
 	io_range 0x170 8
 	io_range 0x370 8
-
-ata-c3:
-	match 100 isa/ata_bd
-	io_range 0x1e8 8
-	io_range 0x3e8 8
-
-ata-c4:
-	match 100 isa/ata_bd
-	io_range 0x168 8
-	io_range 0x368 8
+	irq 14
+	irq 15
Index: uspace/drv/char/i8042/i8042.c
===================================================================
--- uspace/drv/char/i8042/i8042.c	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ uspace/drv/char/i8042/i8042.c	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -123,11 +123,10 @@
  * Write new data to the corresponding buffer.
  *
- * @param call pointerr to call data.
- * @param dev  Device that caued the interrupt.
- *
- */
-static void i8042_irq_handler(ipc_call_t *call, ddf_dev_t *dev)
-{
-	i8042_t *controller = ddf_dev_data_get(dev);
+ * @param call Pointer to call data.
+ * @param arg  Argument (i8042_t *)
+ */
+static void i8042_irq_handler(ipc_call_t *call, void *arg)
+{
+	i8042_t *controller = (i8042_t *)arg;
 	errno_t rc;
 
@@ -147,5 +146,5 @@
 	fibril_condvar_broadcast(&port->buf_cv);
 
-	async_sess_t *parent_sess = ddf_dev_parent_sess_get(dev);
+	async_sess_t *parent_sess = ddf_dev_parent_sess_get(controller->dev);
 	hw_res_clear_interrupt(parent_sess, port->irq);
 }
@@ -178,4 +177,6 @@
 	bool aux_bound = false;
 
+	dev->dev = ddf_dev;
+
 	if (regs->size < sizeof(i8042_regs_t)) {
 		rc = EINVAL;
@@ -288,5 +289,5 @@
 	cap_irq_handle_t kbd_ihandle;
 	rc = register_interrupt_handler(ddf_dev, irq_kbd,
-	    i8042_irq_handler, &irq_code, &kbd_ihandle);
+	    i8042_irq_handler, (void *)dev, &irq_code, &kbd_ihandle);
 	if (rc != EOK) {
 		ddf_msg(LVL_ERROR, "Failed set handler for kbd: %s.",
@@ -297,5 +298,5 @@
 	cap_irq_handle_t mouse_ihandle;
 	rc = register_interrupt_handler(ddf_dev, irq_mouse,
-	    i8042_irq_handler, &irq_code, &mouse_ihandle);
+	    i8042_irq_handler, (void *)dev, &irq_code, &mouse_ihandle);
 	if (rc != EOK) {
 		ddf_msg(LVL_ERROR, "Failed set handler for mouse: %s.",
Index: uspace/drv/char/i8042/i8042.h
===================================================================
--- uspace/drv/char/i8042/i8042.h	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ uspace/drv/char/i8042/i8042.h	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * Copyright (c) 2006 Josef Cejka
  * Copyright (c) 2011 Jan Vesely
@@ -82,4 +82,6 @@
 /** i8042 Controller. */
 typedef struct i8042 {
+	/** DDF device */
+	ddf_dev_t *dev;
 	/** I/O registers. */
 	i8042_regs_t *regs;
Index: uspace/drv/char/ns8250/ns8250.c
===================================================================
--- uspace/drv/char/ns8250/ns8250.c	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ uspace/drv/char/ns8250/ns8250.c	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -783,10 +783,10 @@
  * data and reading the line status register.
  *
- * @param dev The serial port device.
- *
- */
-static inline void ns8250_interrupt_handler(ipc_call_t *icall, ddf_dev_t *dev)
-{
-	ns8250_t *ns = dev_ns8250(dev);
+ * @pram icall IRQ event notificatoin
+ * @param arg Argument (ns8250_t *)
+ */
+static inline void ns8250_interrupt_handler(ipc_call_t *icall, void *arg)
+{
+	ns8250_t *ns = (ns8250_t *)arg;
 	uint8_t iir = pio_read_8(&ns->regs->iid);
 	if ((iir & NS8250_IID_CAUSE_MASK) == NS8250_IID_CAUSE_RXSTATUS) {
@@ -809,5 +809,5 @@
 {
 	return register_interrupt_handler(ns->dev, ns->irq,
-	    ns8250_interrupt_handler, NULL, ihandle);
+	    ns8250_interrupt_handler, (void *)ns, NULL, ihandle);
 }
 
Index: uspace/drv/char/pl050/pl050.c
===================================================================
--- uspace/drv/char/pl050/pl050.c	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ uspace/drv/char/pl050/pl050.c	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -143,7 +143,12 @@
 }
 
-static void pl050_interrupt(ipc_call_t *call, ddf_dev_t *dev)
-{
-	pl050_t *pl050 = (pl050_t *)ddf_dev_data_get(dev);
+/** PL050 interrupt handler
+ *
+ * @param call IRQ event notification
+ * @param arg Argument (pl050_t *)
+ */
+static void pl050_interrupt(ipc_call_t *call, void *arg)
+{
+	pl050_t *pl050 = (pl050_t *)arg;
 	size_t nidx;
 
@@ -220,5 +225,6 @@
 	cap_irq_handle_t ihandle;
 	rc = register_interrupt_handler(pl050->dev,
-	    res.irqs.irqs[0], pl050_interrupt, &pl050_irq_code, &ihandle);
+	    res.irqs.irqs[0], pl050_interrupt, (void *)pl050, &pl050_irq_code,
+	    &ihandle);
 	if (rc != EOK) {
 		ddf_msg(LVL_ERROR, "Failed registering interrupt handler. (%s)",
Index: uspace/drv/meson.build
===================================================================
--- uspace/drv/meson.build	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ uspace/drv/meson.build	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -33,6 +33,7 @@
 	'root/virt',
 	'block/ahci',
-	'block/ata_bd',
 	'block/ddisk',
+	'block/isa-ide',
+	'block/pci-ide',
 	'block/usbmast',
 	'block/virtio-blk',
Index: uspace/drv/nic/e1k/e1k.c
===================================================================
--- uspace/drv/nic/e1k/e1k.c	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ uspace/drv/nic/e1k/e1k.c	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -1247,12 +1247,11 @@
  *
  * @param icall IPC call structure
- * @param dev   E1000 device
- *
- */
-static void e1000_interrupt_handler(ipc_call_t *icall,
-    ddf_dev_t *dev)
+ * @param arg   Argument (nic_t *)
+ *
+ */
+static void e1000_interrupt_handler(ipc_call_t *icall, void *arg)
 {
 	uint32_t icr = (uint32_t) ipc_get_arg2(icall);
-	nic_t *nic = NIC_DATA_DEV(dev);
+	nic_t *nic = (nic_t *)arg;
 	e1000_t *e1000 = DRIVER_DATA_NIC(nic);
 
@@ -1286,5 +1285,5 @@
 
 	errno_t rc = register_interrupt_handler(nic_get_ddf_dev(nic), e1000->irq,
-	    e1000_interrupt_handler, &e1000_irq_code, handle);
+	    e1000_interrupt_handler, (void *)nic, &e1000_irq_code, handle);
 
 	fibril_mutex_unlock(&irq_reg_mutex);
Index: uspace/drv/nic/ne2k/ne2k.c
===================================================================
--- uspace/drv/nic/ne2k/ne2k.c	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ uspace/drv/nic/ne2k/ne2k.c	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -122,5 +122,5 @@
 };
 
-static void ne2k_interrupt_handler(ipc_call_t *, ddf_dev_t *);
+static void ne2k_interrupt_handler(ipc_call_t *, void *);
 
 static errno_t ne2k_register_interrupt(nic_t *nic_data,
@@ -162,5 +162,6 @@
 
 	return register_interrupt_handler(nic_get_ddf_dev(nic_data),
-	    ne2k->irq, ne2k_interrupt_handler, &ne2k->code, handle);
+	    ne2k->irq, ne2k_interrupt_handler, (void *)nic_data, &ne2k->code,
+	    handle);
 }
 
@@ -238,7 +239,12 @@
 }
 
-void ne2k_interrupt_handler(ipc_call_t *call, ddf_dev_t *dev)
-{
-	nic_t *nic_data = DRIVER_DATA(dev);
+/** NE2K interrupt handler
+ *
+ * @param call IRQ event notification
+ * @param arg Argument (nic_t *)
+ */
+void ne2k_interrupt_handler(ipc_call_t *call, void *arg)
+{
+	nic_t *nic_data = (nic_t *)arg;
 	ne2k_interrupt(nic_data, IRQ_GET_ISR(*call), IRQ_GET_TSR(*call));
 }
Index: uspace/drv/nic/rtl8139/driver.c
===================================================================
--- uspace/drv/nic/rtl8139/driver.c	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ uspace/drv/nic/rtl8139/driver.c	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -820,14 +820,11 @@
  *
  * @param icall  The IPC call structure
- * @param dev    The rtl8139 device
- *
- */
-static void rtl8139_interrupt_handler(ipc_call_t *icall, ddf_dev_t *dev)
-{
-	assert(dev);
-	assert(icall);
-
+ * @param arg    Argument (nic_t *)
+ *
+ */
+static void rtl8139_interrupt_handler(ipc_call_t *icall, void *arg)
+{
 	uint16_t isr = (uint16_t) ipc_get_arg2(icall);
-	nic_t *nic_data = nic_get_from_ddf_dev(dev);
+	nic_t *nic_data = (nic_t *)arg;
 	rtl8139_t *rtl8139 = nic_get_specific(nic_data);
 
@@ -862,5 +859,6 @@
 	rtl8139_irq_code.cmds[3].addr = rtl8139->io_addr + IMR;
 	errno_t rc = register_interrupt_handler(nic_get_ddf_dev(nic_data),
-	    rtl8139->irq, rtl8139_interrupt_handler, &rtl8139_irq_code, handle);
+	    rtl8139->irq, rtl8139_interrupt_handler, (void *)nic_data,
+	    &rtl8139_irq_code, handle);
 
 	RTL8139_IRQ_STRUCT_UNLOCK();
Index: uspace/drv/nic/rtl8169/driver.c
===================================================================
--- uspace/drv/nic/rtl8169/driver.c	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ uspace/drv/nic/rtl8169/driver.c	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -74,5 +74,5 @@
 static errno_t rtl8169_on_stopped(nic_t *nic_data);
 static void rtl8169_send_frame(nic_t *nic_data, void *data, size_t size);
-static void rtl8169_irq_handler(ipc_call_t *icall, ddf_dev_t *dev);
+static void rtl8169_irq_handler(ipc_call_t *icall, void *);
 static inline errno_t rtl8169_register_int_handler(nic_t *nic_data,
     cap_irq_handle_t *handle);
@@ -372,5 +372,6 @@
 	rtl8169_irq_code.cmds[3].addr = rtl8169->regs + IMR;
 	errno_t rc = register_interrupt_handler(nic_get_ddf_dev(nic_data),
-	    rtl8169->irq, rtl8169_irq_handler, &rtl8169_irq_code, handle);
+	    rtl8169->irq, rtl8169_irq_handler, (void *)rtl8169,
+	    &rtl8169_irq_code, handle);
 
 	return rc;
@@ -1034,12 +1035,13 @@
 }
 
-static void rtl8169_irq_handler(ipc_call_t *icall, ddf_dev_t *dev)
-{
-	assert(dev);
-	assert(icall);
-
+/** RTL8169 IRQ handler.
+ *
+ * @param icall IRQ event notification
+ * @param arg Argument (rtl8169_t *)
+ */
+static void rtl8169_irq_handler(ipc_call_t *icall, void *arg)
+{
 	uint16_t isr = (uint16_t) ipc_get_arg2(icall) & INT_KNOWN;
-	nic_t *nic_data = nic_get_from_ddf_dev(dev);
-	rtl8169_t *rtl8169 = nic_get_specific(nic_data);
+	rtl8169_t *rtl8169 = (rtl8169_t *)arg;
 
 	ddf_msg(LVL_DEBUG, "rtl8169_irq_handler(): isr=0x%04x", isr);
@@ -1051,5 +1053,5 @@
 		/* Packet underrun or link change */
 		if (isr & INT_PUN) {
-			rtl8169_link_change(dev);
+			rtl8169_link_change(rtl8169->dev);
 			pio_write_16(rtl8169->regs + ISR, INT_PUN);
 		}
@@ -1057,5 +1059,5 @@
 		/* Transmit notification */
 		if (isr & (INT_TER | INT_TOK | INT_TDU)) {
-			rtl8169_transmit_done(dev);
+			rtl8169_transmit_done(rtl8169->dev);
 			pio_write_16(rtl8169->regs + ISR, (INT_TER | INT_TOK | INT_TDU));
 		}
@@ -1073,5 +1075,5 @@
 
 		if (isr & (INT_RER | INT_ROK)) {
-			rtl8169_receive_done(dev);
+			rtl8169_receive_done(rtl8169->dev);
 			pio_write_16(rtl8169->regs + ISR, (INT_RER | INT_ROK));
 		}
Index: uspace/drv/nic/virtio-net/virtio-net.c
===================================================================
--- uspace/drv/nic/virtio-net/virtio-net.c	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ uspace/drv/nic/virtio-net/virtio-net.c	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -71,7 +71,12 @@
 };
 
-static void virtio_net_irq_handler(ipc_call_t *icall, ddf_dev_t *dev)
-{
-	nic_t *nic = ddf_dev_data_get(dev);
+/** VirtIO net IRQ handler.
+ *
+ * @param icall IRQ event notification
+ * @param arg Argument (nic_t *)
+ */
+static void virtio_net_irq_handler(ipc_call_t *icall, void *arg)
+{
+	nic_t *nic = (nic_t *)arg;
 	virtio_net_t *virtio_net = nic_get_specific(nic);
 	virtio_dev_t *vdev = &virtio_net->virtio_dev;
@@ -165,5 +170,6 @@
 
 	return register_interrupt_handler(dev, virtio_net->irq,
-	    virtio_net_irq_handler, &irq_code, &virtio_net->irq_handle);
+	    virtio_net_irq_handler, (void *)nic, &irq_code,
+	    &virtio_net->irq_handle);
 }
 
Index: uspace/lib/ata/include/ata/ata.h
===================================================================
--- uspace/lib/ata/include/ata/ata.h	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
+++ uspace/lib/ata/include/ata/ata.h	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -0,0 +1,209 @@
+/*
+ * 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"
+
+struct ata_device;
+
+/** ATA DMA direction */
+typedef enum {
+	/** DMA read */
+	ata_dma_read,
+	/** DMA write */
+	ata_dma_write
+} ata_dma_dir_t;
+
+/** ATA channel creation parameters */
+typedef struct {
+	/** Argument to callback functions */
+	void *arg;
+	/** IRQ is available */
+	bool have_irq;
+	/** Use DMA transfers */
+	bool use_dma;
+	/** Maximum number of bytes that we can DMA in one I/O operation */
+	size_t max_dma_xfer;
+	/** 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 *);
+	/** Set up DMA channel */
+	void (*dma_chan_setup)(void *, void *, size_t, ata_dma_dir_t);
+	/** Set up DMA channel */
+	void (*dma_chan_teardown)(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 87488278a46c7a401ce6ee33baff59600dfccf37)
+++ uspace/lib/ata/include/ata/ata_hw.h	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -0,0 +1,272 @@
+/*
+ * 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)
+#define REG_FEATURES offsetof(ata_cmd_t, features)
+
+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_READ_DMA_EXT	= 0x25,
+	CMD_WRITE_SECTORS	= 0x30,
+	CMD_WRITE_SECTORS_EXT	= 0x34,
+	CMD_WRITE_DMA_EXT	= 0x35,
+	CMD_PACKET		= 0xA0,
+	CMD_IDENTIFY_PKT_DEV	= 0xA1,
+	CMD_READ_DMA		= 0xC8,
+	CMD_WRITE_DMA		= 0xCA,
+	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 87488278a46c7a401ce6ee33baff59600dfccf37)
+++ uspace/lib/ata/meson.build	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -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 87488278a46c7a401ce6ee33baff59600dfccf37)
+++ uspace/lib/ata/src/ata.c	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -0,0 +1,1738 @@
+/*
+ * 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 void ata_dma_chan_setup(ata_device_t *, void *, size_t, ata_dma_dir_t);
+static void ata_dma_chan_teardown(ata_device_t *);
+
+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;
+	bool dev_added[MAX_DEVICES];
+
+	for (i = 0; i < MAX_DEVICES; i++)
+		dev_added[i] = 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;
+		}
+		dev_added[i] = true;
+		++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 (dev_added[i]) {
+			rc = ata_device_remove(&chan->device[i]);
+			if (rc != 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)
+{
+	if (!chan->params.have_irq)
+		return EOK;
+
+	return chan->params.irq_enable(chan->params.arg);
+}
+
+/** Clean up IRQ. */
+static void ata_bd_fini_irq(ata_channel_t *chan)
+{
+	if (!chan->params.have_irq)
+		return;
+
+	(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;
+}
+
+/** DMA command protocol.
+ *
+ * @param device ATA device
+ * @param cmd Command code
+ * @param buf Data buffer
+ * @param buf_size Data buffer size in bytes
+ * @param dir DMA direction
+ *
+ * @return EOK on success or an error code
+ */
+static errno_t ata_dma_proto(ata_device_t *device, uint8_t cmd, void *buf,
+    size_t buf_size, ata_dma_dir_t dir)
+{
+	ata_channel_t *chan = device->chan;
+	uint8_t status;
+	errno_t rc;
+
+	/* Set up DMA channel */
+	ata_dma_chan_setup(device, buf, buf_size, dir);
+
+	ata_write_cmd_8(chan, REG_COMMAND, cmd);
+
+	if (chan->params.have_irq)
+		rc = wait_irq(chan, &status, TIMEOUT_BSY);
+	else
+		rc = wait_status(chan, 0, ~SR_BSY, &status, TIMEOUT_BSY);
+
+	/* Tear down DMA channel */
+	ata_dma_chan_teardown(device);
+
+	if (rc != EOK) {
+		ata_msg_debug(chan, "wait_irq/wait_status failed");
+		return EIO;
+	}
+
+	if ((status & SR_ERR) != 0) {
+		ata_msg_debug(chan, "status & SR_ERR != 0");
+		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);
+}
+
+/** Read data using PIO during a PACKET command.
+ *
+ * @param device Device
+ * @param obuf Output buffer
+ * @param obuf_size Output buffer size
+ * @param rcvd_size Place to store number of bytes actually transferred
+ *                  or @c NULL
+ */
+static errno_t ata_packet_pio_data_in(ata_device_t *device, void *obuf,
+    size_t obuf_size, size_t *rcvd_size)
+{
+	ata_channel_t *chan = device->chan;
+	size_t data_size;
+	size_t remain;
+	uint8_t status;
+	errno_t rc;
+
+	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);
+
+	if (status & SR_ERR)
+		return EIO;
+
+	if (rcvd_size != NULL)
+		*rcvd_size = obuf_size - remain;
+
+	return EOK;
+}
+
+/** Transfer data using DMA during PACKET command.
+ *
+ * @param device Device
+ * @param buf Buffer
+ * @param buf_size Buffer size
+ * @param dir DMA direction
+ *
+ * @return EOK on success or an error code
+ */
+static errno_t ata_packet_dma(ata_device_t *device, void *buf, size_t buf_size,
+    ata_dma_dir_t dir)
+{
+	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) {
+		ata_msg_debug(chan, "wait_irq/wait_status failed");
+		return EIO;
+	}
+
+	if ((status & SR_ERR) != 0) {
+		ata_msg_debug(chan, "status & SR_ERR != 0");
+		return EIO;
+	}
+
+	return EOK;
+}
+
+/** 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;
+	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;
+	}
+
+	if (chan->params.use_dma) {
+		/* Set up DMA channel */
+		ata_dma_chan_setup(device, obuf, obuf_size, ata_dma_read);
+		ata_write_cmd_8(chan, REG_FEATURES, 0x01); // XXX
+	} else {
+		/*
+		 * Byte count <- max. number of bytes we can read in one
+		 * PIO 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) {
+		if (chan->params.use_dma)
+			ata_dma_chan_teardown(device);
+		fibril_mutex_unlock(&chan->lock);
+		return EIO;
+	}
+
+	/* Write command packet. */
+	ata_write_data_16(chan, ((uint16_t *) cpkt), (cpkt_size + 1) / 2);
+
+	if (chan->params.use_dma) {
+		/* Read data using DMA */
+		rc = ata_packet_dma(device, obuf, obuf_size, ata_dma_read);
+		if (rc == EOK && rcvd_size != NULL)
+			*rcvd_size = obuf_size;
+		ata_dma_chan_teardown(device);
+	} else {
+		/* Read data using PIO */
+		rc = ata_packet_pio_data_in(device, obuf, obuf_size, rcvd_size);
+	}
+
+	fibril_mutex_unlock(&chan->lock);
+
+	if (rc != EOK)
+		return rc;
+
+	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;
+	uint8_t cmd;
+	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);
+
+	if (chan->params.use_dma) {
+		cmd = (device->amode == am_lba48) ? CMD_READ_DMA_EXT :
+		    CMD_READ_DMA;
+
+		rc = ata_dma_proto(device, cmd, buf,
+		    blk_cnt * device->block_size, ata_dma_read);
+		if (rc != EOK) {
+			ata_msg_note(chan, "ata_rcmd_read() -> dma_proto->%d",
+			    rc);
+		}
+	} else {
+		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);
+		if (rc != EOK) {
+			ata_msg_note(chan, "ata_rcmd_read() -> pio_data_in->%d",
+			    rc);
+		}
+	}
+
+	fibril_mutex_unlock(&chan->lock);
+
+	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;
+	uint8_t cmd;
+	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);
+
+	if (chan->params.use_dma) {
+		cmd = (device->amode == am_lba48) ? CMD_WRITE_DMA_EXT :
+		    CMD_WRITE_DMA;
+
+		rc = ata_dma_proto(device, cmd, (void *)buf,
+		    cnt * device->block_size, ata_dma_write);
+		if (rc != EOK) {
+			ata_msg_note(chan, "ata_rcmd_write() -> dma_proto->%d",
+			    rc);
+		}
+	} else {
+		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);
+		if (rc != EOK) {
+			ata_msg_note(chan,
+			    "ata_rcmd_read() -> pio_data_out->%d", rc);
+		}
+	}
+
+	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;
+	size_t dma_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.
+	 */
+	dma_maxnb = d->chan->params.max_dma_xfer / d->block_size;
+	if (dma_maxnb < maxnb)
+		maxnb = dma_maxnb;
+
+	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;
+}
+
+/** Set up DMA channel.
+ *
+ * @param device ATA device
+ * @param buf Data buffer
+ * @param buf_size Data buffer size in bytes
+ * @param dir DMA direction
+ */
+static void ata_dma_chan_setup(ata_device_t *device, void *buf,
+    size_t buf_size, ata_dma_dir_t dir)
+{
+	device->chan->params.dma_chan_setup(device->chan->params.arg,
+	    buf, buf_size, dir);
+}
+
+/** Tear down DMA channel.
+ *
+ * @param device ATA device
+ */
+static void ata_dma_chan_teardown(ata_device_t *device)
+{
+	device->chan->params.dma_chan_teardown(device->chan->params.arg);
+}
+
+/** 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/bithenge/src/helenos/block.c
===================================================================
--- uspace/lib/bithenge/src/helenos/block.c	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ uspace/lib/bithenge/src/helenos/block.c	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -1,3 +1,4 @@
 /*
+ * Copyright (c) 2024 Jiri Svoboda
  * Copyright (c) 2012 Sean Bartell
  * All rights reserved.
@@ -103,5 +104,5 @@
 	// Initialize libblock
 	errno_t rc;
-	rc = block_init(service_id, 2048);
+	rc = block_init(service_id);
 	if (rc != EOK)
 		return rc;
Index: uspace/lib/block/block.c
===================================================================
--- uspace/lib/block/block.c	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ uspace/lib/block/block.c	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -1,3 +1,4 @@
 /*
+ * Copyright (c) 2024 Jiri Svoboda
  * Copyright (c) 2008 Jakub Jermar
  * Copyright (c) 2008 Martin Decky
@@ -145,5 +146,5 @@
 }
 
-errno_t block_init(service_id_t service_id, size_t comm_size)
+errno_t block_init(service_id_t service_id)
 {
 	bd_t *bd;
Index: uspace/lib/block/block.h
===================================================================
--- uspace/lib/block/block.h	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ uspace/lib/block/block.h	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -1,3 +1,4 @@
 /*
+ * Copyright (c) 2024 Jiri Svoboda
  * Copyright (c) 2008 Jakub Jermar
  * Copyright (c) 2008 Martin Decky
@@ -99,5 +100,5 @@
 };
 
-extern errno_t block_init(service_id_t, size_t);
+extern errno_t block_init(service_id_t);
 extern void block_fini(service_id_t);
 
Index: uspace/lib/c/generic/dbgcon.c
===================================================================
--- uspace/lib/c/generic/dbgcon.c	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
+++ uspace/lib/c/generic/dbgcon.c	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -0,0 +1,45 @@
+/*
+ * 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 libc
+ * @{
+ */
+/** @file
+ */
+
+#include <dbgcon.h>
+#include <libc.h>
+#include <stdbool.h>
+
+bool dbgcon_enable(void)
+{
+	return __SYSCALL0(SYS_DEBUG_CONSOLE);
+}
+
+/** @}
+ */
Index: uspace/lib/c/generic/device/hw_res.c
===================================================================
--- uspace/lib/c/generic/device/hw_res.c	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ uspace/lib/c/generic/device/hw_res.c	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -1,3 +1,4 @@
 /*
+ * Copyright (c) 2024 Jiri Svoboda
  * Copyright (c) 2010 Lenka Trochtova
  * All rights reserved.
@@ -161,4 +162,28 @@
 }
 
+/** Get bus flags.
+ *
+ * @param sess HW res session
+ * @param rflags Place to store the flags
+ *
+ * @return Error code.
+ *
+ */
+errno_t hw_res_get_flags(async_sess_t *sess, hw_res_flags_t *rflags)
+{
+	async_exch_t *exch = async_exchange_begin(sess);
+
+	sysarg_t flags;
+	const errno_t ret = async_req_1_1(exch, DEV_IFACE_ID(HW_RES_DEV_IFACE),
+	    HW_RES_GET_FLAGS, &flags);
+
+	async_exchange_end(exch);
+
+	if (ret == EOK)
+		*rflags = flags;
+
+	return ret;
+}
+
 /** @}
  */
Index: uspace/lib/c/generic/rndgen.c
===================================================================
--- uspace/lib/c/generic/rndgen.c	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ uspace/lib/c/generic/rndgen.c	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -92,4 +92,30 @@
 }
 
+/** Generate random 16-bit integer.
+ *
+ * @param rndgen Random number generator
+ * @param rw Place to store random 16-bit integer
+ * @return EOK on success or error code
+ */
+errno_t rndgen_uint16(rndgen_t *rndgen, uint16_t *rw)
+{
+	int i;
+	uint8_t b;
+	uint16_t w;
+	errno_t rc;
+
+	w = 0;
+	for (i = 0; i < 2; i++) {
+		rc = rndgen_uint8(rndgen, &b);
+		if (rc != EOK)
+			return rc;
+
+		w = (w << 8) | b;
+	}
+
+	*rw = w;
+	return EOK;
+}
+
 /** Generate random 32-bit integer.
  *
Index: uspace/lib/c/generic/rtld/module.c
===================================================================
--- uspace/lib/c/generic/rtld/module.c	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ uspace/lib/c/generic/rtld/module.c	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2008 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -343,9 +343,21 @@
 {
 	list_foreach(rtld->modules, modules_link, module_t, m) {
-		/* Skip rtld module, since it has already been processed */
-		if (m != &rtld->rtld) {
+		/*
+		 * Skip rtld module, since it has already been processed.
+		 * Skip start / main program -- leave it for later
+		 */
+		if (m != &rtld->rtld && m != start) {
 			module_process_relocs(m);
 		}
 	}
+
+	/*
+	 * Now that shared libraries have been processed and their variables
+	 * are thus initialized, we can process the main program,
+	 * which may contain COPY relocations that copy value from shared
+	 * library variables to instances of those variables defined
+	 * in the main program.
+	 */
+	module_process_relocs(start);
 }
 
Index: uspace/lib/c/generic/uuid.c
===================================================================
--- uspace/lib/c/generic/uuid.c	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ uspace/lib/c/generic/uuid.c	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -41,4 +41,13 @@
 #include <stdio.h>
 
+static void encode16_be(uint8_t *, uint16_t);
+static void encode16_le(uint8_t *, uint16_t);
+static void encode32_be(uint8_t *, uint32_t);
+static void encode32_le(uint8_t *, uint32_t);
+static uint16_t decode16_be(uint8_t *);
+static uint16_t decode16_le(uint8_t *);
+static uint32_t decode32_be(uint8_t *);
+static uint32_t decode32_le(uint8_t *);
+
 /** Generate UUID.
  *
@@ -56,6 +65,36 @@
 		return EIO;
 
-	for (i = 0; i < uuid_bytes; i++) {
-		rc = rndgen_uint8(rndgen, &uuid->b[i]);
+	rc = rndgen_uint32(rndgen, &uuid->time_low);
+	if (rc != EOK) {
+		rc = EIO;
+		goto error;
+	}
+
+	rc = rndgen_uint16(rndgen, &uuid->time_mid);
+	if (rc != EOK) {
+		rc = EIO;
+		goto error;
+	}
+
+	rc = rndgen_uint16(rndgen, &uuid->time_hi_and_version);
+	if (rc != EOK) {
+		rc = EIO;
+		goto error;
+	}
+
+	rc = rndgen_uint8(rndgen, &uuid->clock_seq_hi_and_reserved);
+	if (rc != EOK) {
+		rc = EIO;
+		goto error;
+	}
+
+	rc = rndgen_uint8(rndgen, &uuid->clock_seq_low);
+	if (rc != EOK) {
+		rc = EIO;
+		goto error;
+	}
+
+	for (i = 0; i < _UUID_NODE_LEN; i++) {
+		rc = rndgen_uint8(rndgen, &uuid->node[i]);
 		if (rc != EOK) {
 			rc = EIO;
@@ -65,6 +104,6 @@
 
 	/* Version 4 UUID from random or pseudo-random numbers */
-	uuid->b[6] = (uuid->b[6] & 0x0f) | 0x40;
-	uuid->b[8] = (uuid->b[8] & 0x3f) | 0x80;
+	uuid->time_hi_and_version = (uuid->time_hi_and_version & 0x0fff) | 0x4000;
+	uuid->clock_seq_hi_and_reserved = (uuid->clock_seq_hi_and_reserved & 0x3f) | 0x80;
 
 error:
@@ -73,4 +112,5 @@
 }
 
+
 /** Encode UUID into binary form per RFC 4122.
  *
@@ -82,6 +122,12 @@
 	int i;
 
-	for (i = 0; i < uuid_bytes; i++)
-		buf[i] = uuid->b[i];
+	encode32_be(buf, uuid->time_low);
+	encode16_be(buf + 4, uuid->time_mid);
+	encode16_be(buf + 6, uuid->time_hi_and_version);
+	buf[8] = uuid->clock_seq_hi_and_reserved;
+	buf[9] = uuid->clock_seq_low;
+
+	for (i = 0; i < _UUID_NODE_LEN; i++)
+		buf[10 + i] = uuid->node[i];
 }
 
@@ -95,6 +141,50 @@
 	int i;
 
-	for (i = 0; i < uuid_bytes; i++)
-		uuid->b[i] = buf[i];
+	uuid->time_low = decode32_be(buf);
+	uuid->time_mid = decode16_be(buf + 4);
+	uuid->time_hi_and_version = decode16_be(buf + 6);
+	uuid->clock_seq_hi_and_reserved = buf[8];
+	uuid->clock_seq_low = buf[9];
+
+	for (i = 0; i < _UUID_NODE_LEN; i++)
+		uuid->node[i] = buf[10 + i];
+}
+
+/** Encode UUID into little-endian (GPT) binary form.
+ *
+ * @param uuid UUID
+ * @param buf 16-byte destination buffer
+ */
+void uuid_encode_le(uuid_t *uuid, uint8_t *buf)
+{
+	int i;
+
+	encode32_le(buf, uuid->time_low);
+	encode16_le(buf + 4, uuid->time_mid);
+	encode16_le(buf + 6, uuid->time_hi_and_version);
+	buf[8] = uuid->clock_seq_hi_and_reserved;
+	buf[9] = uuid->clock_seq_low;
+
+	for (i = 0; i < _UUID_NODE_LEN; i++)
+		buf[10 + i] = uuid->node[i];
+}
+
+/** Decode UUID from little-endian (GPT) binary form.
+ *
+ * @param buf 16-byte source buffer
+ * @param uuid Place to store UUID
+ */
+void uuid_decode_le(uint8_t *buf, uuid_t *uuid)
+{
+	int i;
+
+	uuid->time_low = decode32_le(buf);
+	uuid->time_mid = decode16_le(buf + 4);
+	uuid->time_hi_and_version = decode16_le(buf + 6);
+	uuid->clock_seq_hi_and_reserved = buf[8];
+	uuid->clock_seq_low = buf[9];
+
+	for (i = 0; i < _UUID_NODE_LEN; i++)
+		uuid->node[i] = buf[10 + i];
 }
 
@@ -142,20 +232,16 @@
 		return EINVAL;
 
-	uuid->b[0] = time_low >> 24;
-	uuid->b[1] = (time_low >> 16) & 0xff;
-	uuid->b[2] = (time_low >> 8) & 0xff;
-	uuid->b[3] = time_low & 0xff;
-
-	uuid->b[4] = time_mid >> 8;
-	uuid->b[5] = time_mid & 0xff;
-
-	uuid->b[6] = time_ver >> 8;
-	uuid->b[7] = time_ver & 0xff;
-
-	uuid->b[8] = clock >> 8;
-	uuid->b[9] = clock & 0xff;
-
-	for (i = 0; i < 6; i++)
-		uuid->b[10 + i] = (node >> 8 * (5 - i)) & 0xff;
+	uuid->time_low = time_low;
+
+	uuid->time_mid = time_mid;
+
+	uuid->time_hi_and_version = time_ver;
+
+	uuid->clock_seq_hi_and_reserved = clock >> 8;
+
+	uuid->clock_seq_low = clock & 0xff;
+
+	for (i = 0; i < _UUID_NODE_LEN; i++)
+		uuid->node[i] = (node >> 8 * (5 - i)) & 0xff;
 
 	if (endptr != NULL) {
@@ -183,9 +269,12 @@
 		return ENOMEM;
 
-	const char *format = "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x";
+	const char *format = "%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x";
 	if (uppercase)
-		format = "%02X%02X%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X%02X%02X%02X%02X";
-
-	int ret = snprintf(str, size, format, uuid->b[0], uuid->b[1], uuid->b[2], uuid->b[3], uuid->b[4], uuid->b[5], uuid->b[6], uuid->b[7], uuid->b[8], uuid->b[9], uuid->b[10], uuid->b[11], uuid->b[12], uuid->b[13], uuid->b[14], uuid->b[15]);
+		format = "%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X";
+
+	int ret = snprintf(str, size, format, uuid->time_low, uuid->time_mid,
+	    uuid->time_hi_and_version, uuid->clock_seq_hi_and_reserved,
+	    uuid->clock_seq_low, uuid->node[0], uuid->node[1], uuid->node[2],
+	    uuid->node[3], uuid->node[4], uuid->node[5]);
 
 	if (ret != 36) {
@@ -198,4 +287,52 @@
 }
 
+static void encode16_be(uint8_t *buf, uint16_t value)
+{
+	buf[0] = (value >> 8) & 0xff;
+	buf[1] = value & 0xff;
+}
+
+static void encode16_le(uint8_t *buf, uint16_t value)
+{
+	buf[0] = value & 0xff;
+	buf[1] = (value >> 8) & 0xff;
+}
+
+static void encode32_be(uint8_t *buf, uint32_t value)
+{
+	buf[0] = (value >> 24) & 0xff;
+	buf[1] = (value >> 16) & 0xff;
+	buf[2] = (value >> 8) & 0xff;
+	buf[3] = value & 0xff;
+}
+
+static void encode32_le(uint8_t *buf, uint32_t value)
+{
+	buf[0] = value & 0xff;
+	buf[1] = (value >> 8) & 0xff;
+	buf[2] = (value >> 16) & 0xff;
+	buf[3] = (value >> 24) & 0xff;
+}
+
+static uint16_t decode16_be(uint8_t *buf)
+{
+	return ((buf[0] << 8) | buf[1]);
+}
+
+static uint16_t decode16_le(uint8_t *buf)
+{
+	return ((buf[1] << 8) | buf[0]);
+}
+
+static uint32_t decode32_be(uint8_t *buf)
+{
+	return ((buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3]);
+}
+
+static uint32_t decode32_le(uint8_t *buf)
+{
+	return ((buf[3] << 24) | (buf[2] << 16) | (buf[1] << 8) | buf[0]);
+}
+
 /** @}
  */
Index: uspace/lib/c/include/dbgcon.h
===================================================================
--- uspace/lib/c/include/dbgcon.h	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
+++ uspace/lib/c/include/dbgcon.h	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -0,0 +1,45 @@
+/*
+ * 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 libc
+ * @{
+ */
+/** @file
+ */
+
+#ifndef _LIBC_DBGCON_H_
+#define _LIBC_DBGCON_H_
+
+#include <stdbool.h>
+
+extern bool dbgcon_enable(void);
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/c/include/device/hw_res.h
===================================================================
--- uspace/lib/c/include/device/hw_res.h	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ uspace/lib/c/include/device/hw_res.h	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -1,3 +1,4 @@
 /*
+ * Copyright (c) 2024 Jiri Svoboda
  * Copyright (c) 2010 Lenka Trochtova
  * All rights reserved.
@@ -56,4 +57,5 @@
 	HW_RES_DMA_CHANNEL_SETUP,
 	HW_RES_DMA_CHANNEL_REMAIN,
+	HW_RES_GET_FLAGS
 } hw_res_method_t;
 
@@ -116,4 +118,9 @@
 }
 
+typedef enum {
+	/** This is an PCI/ISA bridge, not 'classic' ISA bus */
+	hwf_isa_bridge = 0x1
+} hw_res_flags_t;
+
 extern errno_t hw_res_get_resource_list(async_sess_t *, hw_resource_list_t *);
 extern errno_t hw_res_enable_interrupt(async_sess_t *, int);
@@ -124,4 +131,5 @@
     uint32_t, uint8_t);
 extern errno_t hw_res_dma_channel_remain(async_sess_t *, unsigned, size_t *);
+extern errno_t hw_res_get_flags(async_sess_t *, hw_res_flags_t *);
 
 #endif
Index: uspace/lib/c/include/rndgen.h
===================================================================
--- uspace/lib/c/include/rndgen.h	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ uspace/lib/c/include/rndgen.h	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -47,4 +47,5 @@
 extern void rndgen_destroy(rndgen_t *);
 extern errno_t rndgen_uint8(rndgen_t *, uint8_t *);
+extern errno_t rndgen_uint16(rndgen_t *, uint16_t *);
 extern errno_t rndgen_uint32(rndgen_t *, uint32_t *);
 
Index: uspace/lib/c/include/types/uuid.h
===================================================================
--- uspace/lib/c/include/types/uuid.h	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ uspace/lib/c/include/types/uuid.h	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -38,11 +38,15 @@
 #include <stdint.h>
 
-enum {
-	uuid_bytes = 16
-};
+#define _UUID_NODE_LEN 6
 
 /** Universally Unique Identifier */
+
 typedef struct {
-	uint8_t b[uuid_bytes];
+	uint32_t time_low;
+	uint16_t time_mid;
+	uint16_t time_hi_and_version;
+	uint8_t clock_seq_hi_and_reserved;
+	uint8_t clock_seq_low;
+	uint8_t node[_UUID_NODE_LEN];
 } uuid_t;
 
Index: uspace/lib/c/include/uuid.h
===================================================================
--- uspace/lib/c/include/uuid.h	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ uspace/lib/c/include/uuid.h	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -43,4 +43,6 @@
 extern void uuid_encode(uuid_t *, uint8_t *);
 extern void uuid_decode(uint8_t *, uuid_t *);
+extern void uuid_encode_le(uuid_t *, uint8_t *);
+extern void uuid_decode_le(uint8_t *, uuid_t *);
 extern errno_t uuid_parse(const char *, uuid_t *, const char **);
 extern errno_t uuid_format(uuid_t *, char **, bool);
Index: uspace/lib/c/meson.build
===================================================================
--- uspace/lib/c/meson.build	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ uspace/lib/c/meson.build	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -1,4 +1,4 @@
 #
-# Copyright (c) 2023 Jiri Svoboda
+# Copyright (c) 2024 Jiri Svoboda
 # Copyright (c) 2005 Martin Decky
 # Copyright (c) 2007 Jakub Jermar
@@ -83,4 +83,5 @@
 	'generic/config.c',
 	'generic/context.c',
+	'generic/dbgcon.c',
 	'generic/device/clock_dev.c',
 	'generic/device/hw_res.c',
Index: uspace/lib/c/test/uuid.c
===================================================================
--- uspace/lib/c/test/uuid.c	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ uspace/lib/c/test/uuid.c	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -50,10 +50,10 @@
 static bool uuid_valid(uuid_t uuid)
 {
-	if (!(uuid.b[6] & 0x40)) {
+	if (!((uuid.time_hi_and_version & 0xf000) & 0x4000)) {
 		return false;
 	}
 
-	int f = (uuid.b[8] & 0x80) || (uuid.b[8] & 0x90);
-	f = f || (uuid.b[8] & 0xA0) || (uuid.b[8] & 0xB0);
+	int f = (uuid.clock_seq_hi_and_reserved & 0x80) || (uuid.clock_seq_hi_and_reserved & 0x90);
+	f = f || (uuid.clock_seq_hi_and_reserved & 0xA0) || (uuid.clock_seq_hi_and_reserved & 0xB0);
 	if (!f) {
 		return false;
Index: uspace/lib/console/src/console.c
===================================================================
--- uspace/lib/console/src/console.c	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ uspace/lib/console/src/console.c	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * Copyright (c) 2006 Josef Cejka
  * Copyright (c) 2006 Jakub Vana
@@ -36,4 +36,5 @@
 
 #include <as.h>
+#include <dbgcon.h>
 #include <libc.h>
 #include <async.h>
@@ -77,5 +78,5 @@
 bool console_kcon(void)
 {
-	return __SYSCALL0(SYS_DEBUG_CONSOLE);
+	return dbgcon_enable();
 }
 
Index: uspace/lib/dltest/dltest.c
===================================================================
--- uspace/lib/dltest/dltest.c	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ uspace/lib/dltest/dltest.c	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2016 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -43,4 +43,6 @@
 /** Public initialized variable */
 int dl_public_var = dl_public_var_val;
+/** Public variable initialized with the address of a symbol */
+int *dl_public_ptr_var = &dl_public_var;
 /** Public uninitialized variable */
 int dl_public_uvar;
Index: uspace/lib/dltest/libdltest.h
===================================================================
--- uspace/lib/dltest/libdltest.h	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ uspace/lib/dltest/libdltest.h	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2016 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -67,4 +67,5 @@
 extern int dl_public_var;
 extern int dl_public_uvar;
+extern int *dl_public_ptr_var;
 extern fibril_local int dl_public_fib_var;
 extern fibril_local int dl_public_fib_uvar;
Index: uspace/lib/drv/generic/interrupt.c
===================================================================
--- uspace/lib/drv/generic/interrupt.c	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ uspace/lib/drv/generic/interrupt.c	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -1,3 +1,4 @@
 /*
+ * Copyright (c) 2024 Jiri Svoboda
  * Copyright (c) 2010 Lenka Trochtova
  * All rights reserved.
@@ -43,9 +44,10 @@
 
 errno_t register_interrupt_handler(ddf_dev_t *dev, int irq,
-    interrupt_handler_t *handler, const irq_code_t *irq_code,
+    interrupt_handler_t *handler, void *arg, const irq_code_t *irq_code,
     cap_irq_handle_t *handle)
 {
+	(void)dev;
 	return async_irq_subscribe(irq, (async_notification_handler_t) handler,
-	    dev, irq_code, handle);
+	    arg, irq_code, handle);
 }
 
Index: uspace/lib/drv/generic/remote_hw_res.c
===================================================================
--- uspace/lib/drv/generic/remote_hw_res.c	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ uspace/lib/drv/generic/remote_hw_res.c	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -1,3 +1,4 @@
 /*
+ * Copyright (c) 2024 Jiri Svoboda
  * Copyright (c) 2010 Lenka Trochtova
  * Copyright (c) 2011 Jan Vesely
@@ -47,4 +48,5 @@
 static void remote_hw_res_dma_channel_setup(ddf_fun_t *, void *, ipc_call_t *);
 static void remote_hw_res_dma_channel_remain(ddf_fun_t *, void *, ipc_call_t *);
+static void remote_hw_res_get_flags(ddf_fun_t *, void *, ipc_call_t *);
 
 static const remote_iface_func_ptr_t remote_hw_res_iface_ops [] = {
@@ -55,4 +57,5 @@
 	[HW_RES_DMA_CHANNEL_SETUP] = &remote_hw_res_dma_channel_setup,
 	[HW_RES_DMA_CHANNEL_REMAIN] = &remote_hw_res_dma_channel_remain,
+	[HW_RES_GET_FLAGS] = &remote_hw_res_get_flags
 };
 
@@ -171,4 +174,19 @@
 }
 
+static void remote_hw_res_get_flags(ddf_fun_t *fun, void *ops,
+    ipc_call_t *call)
+{
+	hw_res_ops_t *hw_res_ops = ops;
+
+	if (hw_res_ops->get_flags == NULL) {
+		async_answer_0(call, ENOTSUP);
+		return;
+	}
+
+	hw_res_flags_t flags = 0;
+	const errno_t ret = hw_res_ops->get_flags(fun, &flags);
+	async_answer_1(call, ret, flags);
+}
+
 /**
  * @}
Index: uspace/lib/drv/include/ddf/interrupt.h
===================================================================
--- uspace/lib/drv/include/ddf/interrupt.h	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ uspace/lib/drv/include/ddf/interrupt.h	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -1,3 +1,4 @@
 /*
+ * Copyright (c) 2024 Jiri Svoboda
  * Copyright (c) 2010 Lenka Trochtova
  * All rights reserved.
@@ -48,8 +49,8 @@
  */
 
-typedef void interrupt_handler_t(ipc_call_t *, ddf_dev_t *);
+typedef void interrupt_handler_t(ipc_call_t *, void *);
 
-extern errno_t register_interrupt_handler(ddf_dev_t *, int, interrupt_handler_t *,
-    const irq_code_t *, cap_irq_handle_t *);
+extern errno_t register_interrupt_handler(ddf_dev_t *, int,
+    interrupt_handler_t *, void *, const irq_code_t *, cap_irq_handle_t *);
 extern errno_t unregister_interrupt_handler(ddf_dev_t *, cap_irq_handle_t);
 
Index: uspace/lib/drv/include/ops/hw_res.h
===================================================================
--- uspace/lib/drv/include/ops/hw_res.h	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ uspace/lib/drv/include/ops/hw_res.h	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -1,3 +1,4 @@
 /*
+ * Copyright (c) 2024 Jiri Svoboda
  * Copyright (c) 2010 Lenka Trochtova
  * Copyright (c) 2011 Jan Vesely
@@ -49,4 +50,5 @@
 	errno_t (*dma_channel_setup)(ddf_fun_t *, unsigned, uint32_t, uint32_t, uint8_t);
 	errno_t (*dma_channel_remain)(ddf_fun_t *, unsigned, size_t *);
+	errno_t (*get_flags)(ddf_fun_t *, hw_res_flags_t *);
 } hw_res_ops_t;
 
Index: uspace/lib/ext4/src/filesystem.c
===================================================================
--- uspace/lib/ext4/src/filesystem.c	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ uspace/lib/ext4/src/filesystem.c	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2018 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * Copyright (c) 2011 Martin Sucha
  * Copyright (c) 2012 Frantisek Princ
@@ -83,5 +83,5 @@
 
 	/* Initialize block library (4096 is size of communication channel) */
-	rc = block_init(fs->device, 4096);
+	rc = block_init(fs->device);
 	if (rc != EOK)
 		goto err;
@@ -265,5 +265,5 @@
 
 	/* Initialize block library (4096 is size of communication channel) */
-	rc = block_init(service_id, 4096);
+	rc = block_init(service_id);
 	if (rc != EOK)
 		goto err;
Index: uspace/lib/fdisk/src/fdisk.c
===================================================================
--- uspace/lib/fdisk/src/fdisk.c	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ uspace/lib/fdisk/src/fdisk.c	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2015 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -810,7 +810,16 @@
 }
 
+/** Destroy partition.
+ *
+ * @param part Partition
+ * @return EOK on success or error code
+ */
 errno_t fdisk_part_destroy(fdisk_part_t *part)
 {
 	errno_t rc;
+
+	rc = vol_part_eject(part->dev->fdisk->vol, part->svc_id);
+	if (rc != EOK)
+		return EIO;
 
 	rc = vbd_part_delete(part->dev->fdisk->vbd, part->part_id);
Index: uspace/lib/inet/meson.build
===================================================================
--- uspace/lib/inet/meson.build	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ uspace/lib/inet/meson.build	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -1,4 +1,4 @@
 #
-# Copyright (c) 2021 Jiri Svoboda
+# Copyright (c) 2024 Jiri Svoboda
 # All rights reserved.
 #
@@ -46,4 +46,5 @@
 
 test_src = files(
+	'test/addr.c',
 	'test/eth_addr.c',
 	'test/main.c',
Index: uspace/lib/inet/src/addr.c
===================================================================
--- uspace/lib/inet/src/addr.c	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ uspace/lib/inet/src/addr.c	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * Copyright (c) 2013 Martin Decky
  * All rights reserved.
@@ -379,6 +379,9 @@
 				wildcard_size = 16 - i;
 				cur = gend + 2;
+				continue;
 			}
 		}
+
+		cur = gend + 1;
 	}
 
Index: uspace/lib/inet/test/addr.c
===================================================================
--- uspace/lib/inet/test/addr.c	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
+++ uspace/lib/inet/test/addr.c	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -0,0 +1,199 @@
+/*
+ * 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.
+ */
+
+#include <errno.h>
+#include <inet/addr.h>
+#include <pcut/pcut.h>
+
+PCUT_INIT;
+
+PCUT_TEST_SUITE(addr);
+
+/** Test inet_addr_parse() with unabbreviated address */
+PCUT_TEST(inet_addr_parse_full)
+{
+	errno_t rc;
+	inet_addr_t addr;
+	char *endptr;
+
+	rc = inet_addr_parse("1122:3344:5566:7788:99aa:bbcc:ddee:ff00/", &addr,
+	    &endptr);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_EQUALS('/', *endptr);
+
+	PCUT_ASSERT_INT_EQUALS(ip_v6, addr.version);
+
+	PCUT_ASSERT_INT_EQUALS(0x11, addr.addr6[0]);
+	PCUT_ASSERT_INT_EQUALS(0x22, addr.addr6[1]);
+	PCUT_ASSERT_INT_EQUALS(0x33, addr.addr6[2]);
+	PCUT_ASSERT_INT_EQUALS(0x44, addr.addr6[3]);
+	PCUT_ASSERT_INT_EQUALS(0x55, addr.addr6[4]);
+	PCUT_ASSERT_INT_EQUALS(0x66, addr.addr6[5]);
+	PCUT_ASSERT_INT_EQUALS(0x77, addr.addr6[6]);
+	PCUT_ASSERT_INT_EQUALS(0x88, addr.addr6[7]);
+
+	PCUT_ASSERT_INT_EQUALS(0x99, addr.addr6[8]);
+	PCUT_ASSERT_INT_EQUALS(0xaa, addr.addr6[9]);
+	PCUT_ASSERT_INT_EQUALS(0xbb, addr.addr6[10]);
+	PCUT_ASSERT_INT_EQUALS(0xcc, addr.addr6[11]);
+	PCUT_ASSERT_INT_EQUALS(0xdd, addr.addr6[12]);
+	PCUT_ASSERT_INT_EQUALS(0xee, addr.addr6[13]);
+	PCUT_ASSERT_INT_EQUALS(0xff, addr.addr6[14]);
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[15]);
+}
+
+/** Test inet_addr_parse() with short groups (< 0x1000) */
+PCUT_TEST(inet_addr_parse_shortgr)
+{
+	errno_t rc;
+	inet_addr_t addr;
+	char *endptr;
+
+	rc = inet_addr_parse("1:22:333:4444:5:66:777:8888/", &addr,
+	    &endptr);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_EQUALS('/', *endptr);
+
+	PCUT_ASSERT_INT_EQUALS(ip_v6, addr.version);
+
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[0]);
+	PCUT_ASSERT_INT_EQUALS(0x01, addr.addr6[1]);
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[2]);
+	PCUT_ASSERT_INT_EQUALS(0x22, addr.addr6[3]);
+	PCUT_ASSERT_INT_EQUALS(0x03, addr.addr6[4]);
+	PCUT_ASSERT_INT_EQUALS(0x33, addr.addr6[5]);
+	PCUT_ASSERT_INT_EQUALS(0x44, addr.addr6[6]);
+	PCUT_ASSERT_INT_EQUALS(0x44, addr.addr6[7]);
+
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[8]);
+	PCUT_ASSERT_INT_EQUALS(0x05, addr.addr6[9]);
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[10]);
+	PCUT_ASSERT_INT_EQUALS(0x66, addr.addr6[11]);
+	PCUT_ASSERT_INT_EQUALS(0x07, addr.addr6[12]);
+	PCUT_ASSERT_INT_EQUALS(0x77, addr.addr6[13]);
+	PCUT_ASSERT_INT_EQUALS(0x88, addr.addr6[14]);
+	PCUT_ASSERT_INT_EQUALS(0x88, addr.addr6[15]);
+}
+
+/** Test inet_addr_parse() with wildcard at the beginning */
+PCUT_TEST(inet_addr_parse_wcbegin)
+{
+	errno_t rc;
+	inet_addr_t addr;
+	char *endptr;
+
+	rc = inet_addr_parse("::1234/", &addr, &endptr);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_EQUALS('/', *endptr);
+
+	PCUT_ASSERT_INT_EQUALS(ip_v6, addr.version);
+
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[0]);
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[1]);
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[2]);
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[3]);
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[4]);
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[5]);
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[6]);
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[7]);
+
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[8]);
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[9]);
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[10]);
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[11]);
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[12]);
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[13]);
+	PCUT_ASSERT_INT_EQUALS(0x12, addr.addr6[14]);
+	PCUT_ASSERT_INT_EQUALS(0x34, addr.addr6[15]);
+}
+
+/** Test inet_addr_parse() with wildcard in the middle */
+PCUT_TEST(inet_addr_parse_wcmiddle)
+{
+	errno_t rc;
+	inet_addr_t addr;
+	char *endptr;
+
+	rc = inet_addr_parse("1122:3344::5566/", &addr, &endptr);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_EQUALS('/', *endptr);
+
+	PCUT_ASSERT_INT_EQUALS(ip_v6, addr.version);
+
+	PCUT_ASSERT_INT_EQUALS(0x11, addr.addr6[0]);
+	PCUT_ASSERT_INT_EQUALS(0x22, addr.addr6[1]);
+	PCUT_ASSERT_INT_EQUALS(0x33, addr.addr6[2]);
+	PCUT_ASSERT_INT_EQUALS(0x44, addr.addr6[3]);
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[4]);
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[5]);
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[6]);
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[7]);
+
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[8]);
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[9]);
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[10]);
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[11]);
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[12]);
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[13]);
+	PCUT_ASSERT_INT_EQUALS(0x55, addr.addr6[14]);
+	PCUT_ASSERT_INT_EQUALS(0x66, addr.addr6[15]);
+}
+
+/** Test inet_addr_parse() with wildcard at the end */
+PCUT_TEST(inet_addr_parse_wcend)
+{
+	errno_t rc;
+	inet_addr_t addr;
+	char *endptr;
+
+	rc = inet_addr_parse("1234:5678::/", &addr, &endptr);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_EQUALS('/', *endptr);
+
+	PCUT_ASSERT_INT_EQUALS(ip_v6, addr.version);
+
+	PCUT_ASSERT_INT_EQUALS(0x12, addr.addr6[0]);
+	PCUT_ASSERT_INT_EQUALS(0x34, addr.addr6[1]);
+	PCUT_ASSERT_INT_EQUALS(0x56, addr.addr6[2]);
+	PCUT_ASSERT_INT_EQUALS(0x78, addr.addr6[3]);
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[4]);
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[5]);
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[6]);
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[7]);
+
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[8]);
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[9]);
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[10]);
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[11]);
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[12]);
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[13]);
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[14]);
+	PCUT_ASSERT_INT_EQUALS(0x00, addr.addr6[15]);
+}
+
+PCUT_EXPORT(addr);
Index: uspace/lib/inet/test/main.c
===================================================================
--- uspace/lib/inet/test/main.c	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ uspace/lib/inet/test/main.c	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -31,4 +31,5 @@
 PCUT_INIT;
 
+PCUT_IMPORT(addr);
 PCUT_IMPORT(eth_addr);
 
Index: uspace/lib/label/src/gpt.c
===================================================================
--- uspace/lib/label/src/gpt.c	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ uspace/lib/label/src/gpt.c	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -443,5 +443,5 @@
 		gpt_hdr->first_usable_lba = host2uint64_t_le(ba_min);
 		gpt_hdr->last_usable_lba = host2uint64_t_le(ba_max);
-		uuid_encode(&disk_uuid, gpt_hdr->disk_guid);
+		uuid_encode_le(&disk_uuid, gpt_hdr->disk_guid);
 		gpt_hdr->entry_lba = host2uint64_t_le(ptba[i]);
 		gpt_hdr->num_entries = host2uint32_t_le(num_entries);
@@ -812,6 +812,6 @@
 
 	memset(pte, 0, sizeof(gpt_entry_t));
-	uuid_encode(&part->ptype.t.uuid, pte->part_type);
-	uuid_encode(&part->part_uuid, pte->part_id);
+	uuid_encode_le(&part->ptype.t.uuid, pte->part_type);
+	uuid_encode_le(&part->part_uuid, pte->part_id);
 	pte->start_lba = host2uint64_t_le(part->block0);
 	pte->end_lba = host2uint64_t_le(eblock);
@@ -849,6 +849,6 @@
 	part->nblocks = b1 - b0 + 1;
 	part->ptype.fmt = lptf_uuid;
-	uuid_decode(pte->part_type, &part->ptype.t.uuid);
-	uuid_decode(pte->part_id, &part->part_uuid);
+	uuid_decode_le(pte->part_type, &part->ptype.t.uuid);
+	uuid_decode_le(pte->part_id, &part->part_uuid);
 
 	part->label = label;
Index: uspace/lib/meson.build
===================================================================
--- uspace/lib/meson.build	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ uspace/lib/meson.build	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -112,4 +112,5 @@
 	'virtio',
 
+	'ata',
 	'ieee80211',
 	'ddev',
Index: uspace/lib/ui/include/types/ui/menubar.h
===================================================================
--- uspace/lib/ui/include/types/ui/menubar.h	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ uspace/lib/ui/include/types/ui/menubar.h	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -40,4 +40,12 @@
 typedef struct ui_menu_bar ui_menu_bar_t;
 
+/** Menu bar callbacks */
+typedef struct ui_menu_bar_cb {
+	/** Activated */
+	void (*activate)(ui_menu_bar_t *, void *);
+	/** Deactivated */
+	void (*deactivate)(ui_menu_bar_t *, void *);
+} ui_menu_bar_cb_t;
+
 #endif
 
Index: uspace/lib/ui/include/ui/menubar.h
===================================================================
--- uspace/lib/ui/include/ui/menubar.h	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ uspace/lib/ui/include/ui/menubar.h	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2023 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -52,4 +52,5 @@
     ui_menu_bar_t **);
 extern void ui_menu_bar_destroy(ui_menu_bar_t *);
+extern void ui_menu_bar_set_cb(ui_menu_bar_t *, ui_menu_bar_cb_t *, void *);
 extern ui_control_t *ui_menu_bar_ctl(ui_menu_bar_t *);
 extern void ui_menu_bar_set_rect(ui_menu_bar_t *, gfx_rect_t *);
Index: uspace/lib/ui/private/menubar.h
===================================================================
--- uspace/lib/ui/private/menubar.h	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ uspace/lib/ui/private/menubar.h	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2023 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -64,4 +64,8 @@
 	/** List of menu drop-downs (ui_menu_dd_t) */
 	list_t menudds;
+	/** Callbacks */
+	struct ui_menu_bar_cb *cb;
+	/** Callback argument */
+	void *arg;
 };
 
Index: uspace/lib/ui/src/menubar.c
===================================================================
--- uspace/lib/ui/src/menubar.c	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ uspace/lib/ui/src/menubar.c	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2023 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -66,4 +66,6 @@
 static ui_evclaim_t ui_menu_bar_ctl_kbd_event(void *, kbd_event_t *);
 static ui_evclaim_t ui_menu_bar_ctl_pos_event(void *, pos_event_t *);
+static void ui_menu_bar_activate_ev(ui_menu_bar_t *);
+static void ui_menu_bar_deactivate_ev(ui_menu_bar_t *);
 
 /** Menu bar control ops */
@@ -131,4 +133,16 @@
 	ui_control_delete(mbar->control);
 	free(mbar);
+}
+
+/** Set menu bar callbacks.
+ *
+ * @param mbar Menu bar
+ * @param cb Callbacks
+ * @param arg Callback argument
+ */
+void ui_menu_bar_set_cb(ui_menu_bar_t *mbar, ui_menu_bar_cb_t *cb, void *arg)
+{
+	mbar->cb = cb;
+	mbar->arg = arg;
 }
 
@@ -288,6 +302,10 @@
 		}
 
+		if (!mbar->active)
+			ui_menu_bar_activate_ev(mbar);
 		mbar->active = true;
 	} else {
+		if (mbar->active)
+			ui_menu_bar_deactivate_ev(mbar);
 		mbar->active = false;
 	}
@@ -640,4 +658,5 @@
 
 	(void) ui_menu_bar_paint(mbar);
+	ui_menu_bar_activate_ev(mbar);
 }
 
@@ -649,4 +668,5 @@
 {
 	ui_menu_bar_select(mbar, NULL, false, 0);
+	ui_menu_bar_deactivate_ev(mbar);
 }
 
@@ -700,4 +720,24 @@
 }
 
+/** Send menu bar activate event.
+ *
+ * @param mbar Menu bar
+ */
+static void ui_menu_bar_activate_ev(ui_menu_bar_t *mbar)
+{
+	if (mbar->cb != NULL && mbar->cb->activate != NULL)
+		mbar->cb->activate(mbar, mbar->arg);
+}
+
+/** Send menu bar deactivate event.
+ *
+ * @param mbar Menu bar
+ */
+static void ui_menu_bar_deactivate_ev(ui_menu_bar_t *mbar)
+{
+	if (mbar->cb != NULL && mbar->cb->deactivate != NULL)
+		mbar->cb->deactivate(mbar, mbar->arg);
+}
+
 /** @}
  */
Index: uspace/lib/ui/src/window.c
===================================================================
--- uspace/lib/ui/src/window.c	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ uspace/lib/ui/src/window.c	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2023 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -318,4 +318,5 @@
 {
 	ui_window_t *window;
+	ui_window_t *pwindow = NULL;
 	display_info_t info;
 	gfx_coord2_t scr_dims;
@@ -505,5 +506,15 @@
 	*rwindow = window;
 
+	if (ui_is_fullscreen(ui))
+		pwindow = ui_window_get_active(ui);
+
 	list_append(&window->lwindows, &ui->windows);
+
+	if (ui_is_fullscreen(ui)) {
+		/* Send unfocus event to previously active window */
+		if (pwindow != NULL)
+			ui_window_send_unfocus(pwindow, 0);
+	}
+
 	return EOK;
 error:
@@ -531,4 +542,5 @@
 {
 	ui_t *ui;
+	ui_window_t *nwindow;
 
 	if (window == NULL)
@@ -558,4 +570,8 @@
 	if (ui_is_fullscreen(ui)) {
 		ui_paint(ui);
+		/* Send focus event to newly active window */
+		nwindow = ui_window_get_active(ui);
+		if (nwindow != NULL)
+			ui_window_send_focus(nwindow, 0);
 	}
 
Index: uspace/lib/usbhost/src/hcd.c
===================================================================
--- uspace/lib/usbhost/src/hcd.c	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ uspace/lib/usbhost/src/hcd.c	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -95,9 +95,11 @@
  * TODO: Make the bus mechanism less flexible in irq handling and remove the
  * lookup.
- */
-static void irq_handler(ipc_call_t *call, ddf_dev_t *dev)
-{
-	assert(dev);
-	hc_device_t *hcd = dev_to_hcd(dev);
+ *
+ * @param call Interrupt notification
+ * @param arg Argument (hc_device_t *)
+ */
+static void irq_handler(ipc_call_t *call, void *arg)
+{
+	hc_device_t *hcd = (hc_device_t *)arg;
 
 	const uint32_t status = ipc_get_arg1(call);
@@ -178,5 +180,5 @@
 	cap_irq_handle_t ihandle;
 	ret = register_interrupt_handler(hcd->ddf_dev, irq, irq_handler,
-	    &irq_code, &ihandle);
+	    (void *)hcd, &irq_code, &ihandle);
 	irq_code_clean(&irq_code);
 	if (ret != EOK) {
Index: uspace/srv/bd/vbd/disk.c
===================================================================
--- uspace/srv/bd/vbd/disk.c	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ uspace/srv/bd/vbd/disk.c	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2023 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -499,5 +499,5 @@
 
 	log_msg(LOG_DEFAULT, LVL_DEBUG, "block_init(%zu)", sid);
-	rc = block_init(sid, 2048);
+	rc = block_init(sid);
 	if (rc != EOK) {
 		log_msg(LOG_DEFAULT, LVL_ERROR, "Failed opening block device %s.",
Index: uspace/srv/fs/cdfs/cdfs_ops.c
===================================================================
--- uspace/srv/fs/cdfs/cdfs_ops.c	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ uspace/srv/fs/cdfs/cdfs_ops.c	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -1,3 +1,4 @@
 /*
+ * Copyright (c) 2024 Jiri Svoboda
  * Copyright (c) 2011 Martin Decky
  * All rights reserved.
@@ -1093,5 +1094,5 @@
 
 	/* Initialize the block layer */
-	errno_t rc = block_init(service_id, BLOCK_SIZE);
+	errno_t rc = block_init(service_id);
 	if (rc != EOK)
 		return rc;
@@ -1150,5 +1151,5 @@
 {
 	/* Initialize the block layer */
-	errno_t rc = block_init(service_id, BLOCK_SIZE);
+	errno_t rc = block_init(service_id);
 	if (rc != EOK)
 		return rc;
Index: uspace/srv/fs/exfat/exfat_ops.c
===================================================================
--- uspace/srv/fs/exfat/exfat_ops.c	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ uspace/srv/fs/exfat/exfat_ops.c	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -1,3 +1,4 @@
 /*
+ * Copyright (c) 2024 Jiri Svoboda
  * Copyright (c) 2008 Jakub Jermar
  * Copyright (c) 2011 Oleg Romanenko
@@ -1020,5 +1021,5 @@
 
 	/* initialize libblock */
-	rc = block_init(service_id, BS_SIZE);
+	rc = block_init(service_id);
 	if (rc != EOK)
 		return rc;
Index: uspace/srv/fs/fat/fat_ops.c
===================================================================
--- uspace/srv/fs/fat/fat_ops.c	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ uspace/srv/fs/fat/fat_ops.c	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -1,3 +1,4 @@
 /*
+ * Copyright (c) 2024 Jiri Svoboda
  * Copyright (c) 2008 Jakub Jermar
  * Copyright (c) 2011 Oleg Romanenko
@@ -916,5 +917,5 @@
 
 	/* initialize libblock */
-	rc = block_init(service_id, BS_SIZE);
+	rc = block_init(service_id);
 	if (rc != EOK)
 		return rc;
Index: uspace/srv/fs/mfs/mfs_ops.c
===================================================================
--- uspace/srv/fs/mfs/mfs_ops.c	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ uspace/srv/fs/mfs/mfs_ops.c	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -1,3 +1,4 @@
 /*
+ * Copyright (c) 2024 Jiri Svoboda
  * Copyright (c) 2011 Maurizio Lombardi
  * All rights reserved.
@@ -283,5 +284,5 @@
 
 	/* Initialize libblock */
-	rc = block_init(service_id, 4096);
+	rc = block_init(service_id);
 	if (rc != EOK)
 		return rc;
@@ -310,5 +311,5 @@
 
 	/* Initialize libblock */
-	rc = block_init(service_id, 4096);
+	rc = block_init(service_id);
 	if (rc != EOK)
 		return rc;
Index: uspace/srv/fs/udf/udf.h
===================================================================
--- uspace/srv/fs/udf/udf.h	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ uspace/srv/fs/udf/udf.h	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -1,3 +1,4 @@
 /*
+ * Copyright (c) 2024 Jiri Svoboda
  * Copyright (c) 2008 Jakub Jermar
  * Copyright (c) 2012 Julia Medvedeva
@@ -51,6 +52,4 @@
 
 #define BS_BLOCK     0
-#define MIN_SIZE     512
-#define MAX_SIZE     8192
 #define DEFAULT_VOL  0
 
Index: uspace/srv/fs/udf/udf_ops.c
===================================================================
--- uspace/srv/fs/udf/udf_ops.c	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ uspace/srv/fs/udf/udf_ops.c	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -1,3 +1,4 @@
 /*
+ * Copyright (c) 2024 Jiri Svoboda
  * Copyright (c) 2008 Jakub Jermar
  * Copyright (c) 2012 Julia Medvedeva
@@ -323,5 +324,5 @@
 
 	/* initialize block cache */
-	errno_t rc = block_init(service_id, MAX_SIZE);
+	errno_t rc = block_init(service_id);
 	if (rc != EOK) {
 		free(instance);
Index: uspace/srv/hid/console/console.c
===================================================================
--- uspace/srv/hid/console/console.c	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ uspace/srv/hid/console/console.c	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2023 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * Copyright (c) 2011 Martin Decky
  * All rights reserved.
@@ -366,7 +366,13 @@
 {
 	cons_event_t event;
-
+	bool alt;
+	bool shift;
+
+	alt = (mods & KM_ALT) != 0 && (mods & (KM_CTRL | KM_SHIFT)) == 0;
+	shift = (mods & KM_SHIFT) != 0 && (mods & (KM_CTRL | KM_ALT)) == 0;
+
+	/* Switch console on Alt+Fn or Shift+Fn */
 	if ((key >= KC_F1) && (key <= KC_F1 + CONSOLE_COUNT) &&
-	    ((mods & KM_CTRL) == 0)) {
+	    (alt || shift)) {
 		cons_switch(key - KC_F1);
 	} else {
Index: uspace/srv/volsrv/empty.c
===================================================================
--- uspace/srv/volsrv/empty.c	(revision 2a0c827c42eafb070de3bbe3c98a1d8a2b77d719)
+++ uspace/srv/volsrv/empty.c	(revision 87488278a46c7a401ce6ee33baff59600dfccf37)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2015 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -62,5 +62,5 @@
 	label_bd_t lbd;
 
-	rc = block_init(sid, 2048);
+	rc = block_init(sid);
 	if (rc != EOK) {
 		log_msg(LOG_DEFAULT, LVL_ERROR, "Error opening "
@@ -83,5 +83,5 @@
 	label_bd_t lbd;
 
-	rc = block_init(sid, 2048);
+	rc = block_init(sid);
 	if (rc != EOK) {
 		log_msg(LOG_DEFAULT, LVL_ERROR, "Error opening "
