Index: uspace/drv/block/pc-floppy/doc/doxygroups.h
===================================================================
--- uspace/drv/block/pc-floppy/doc/doxygroups.h	(revision dc5647ed5a125308dfb3f30e9c035e9392912353)
+++ uspace/drv/block/pc-floppy/doc/doxygroups.h	(revision dc5647ed5a125308dfb3f30e9c035e9392912353)
@@ -0,0 +1,4 @@
+/** @addtogroup pc-floppy pc-floppy
+ * @brief PC Floppy Disk driver
+ * @ingroup drvs
+ */
Index: uspace/drv/block/pc-floppy/main.c
===================================================================
--- uspace/drv/block/pc-floppy/main.c	(revision dc5647ed5a125308dfb3f30e9c035e9392912353)
+++ uspace/drv/block/pc-floppy/main.c	(revision dc5647ed5a125308dfb3f30e9c035e9392912353)
@@ -0,0 +1,224 @@
+/*
+ * 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 pc-floppy
+ * @{
+ */
+
+/** @file PC floppy disk driver main
+ */
+
+#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 "pc-floppy.h"
+
+static errno_t pc_fdc_dev_add(ddf_dev_t *dev);
+static errno_t pc_fdc_dev_remove(ddf_dev_t *dev);
+static errno_t pc_fdc_dev_gone(ddf_dev_t *dev);
+static errno_t pc_fdc_fun_online(ddf_fun_t *fun);
+static errno_t pc_fdc_fun_offline(ddf_fun_t *fun);
+
+static driver_ops_t driver_ops = {
+	.dev_add = &pc_fdc_dev_add,
+	.dev_remove = &pc_fdc_dev_remove,
+	.dev_gone = &pc_fdc_dev_gone,
+	.fun_online = &pc_fdc_fun_online,
+	.fun_offline = &pc_fdc_fun_offline
+};
+
+static driver_t pc_fdc_driver = {
+	.name = NAME,
+	.driver_ops = &driver_ops
+};
+
+/** Parse FDC hardware resources.
+ *
+ * @param dev DDF device (from which we get resources)
+ * @param res Place to store FDC hardware resources
+ * @return EOK on success or an error code
+ */
+static errno_t pc_fdc_get_res(ddf_dev_t *dev, pc_fdc_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;
+
+	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;
+	}
+
+	/* I/O range */
+
+	addr_range_t *regs_rng = &hw_res.io_ranges.ranges[0];
+	res->regs = RNGABS(*regs_rng);
+
+	if (RNGSZ(*regs_rng) < sizeof(pc_fdc_regs_t)) {
+		rc = EINVAL;
+		goto error;
+	}
+
+	/* IRQ */
+	ddf_msg(LVL_NOTE, "irqs count=%zu", hw_res.irqs.count);
+	if (hw_res.irqs.count > 0) {
+		ddf_msg(LVL_NOTE, "set IRQ=%u", hw_res.irqs.irqs[0]);
+		res->irq = hw_res.irqs.irqs[0];
+	} else {
+		ddf_msg(LVL_NOTE, "set IRQ=-1");
+		res->irq = -1;
+	}
+
+	/* DMA channel */
+	if (hw_res.dma_channels.count > 0) {
+		res->dma = hw_res.dma_channels.channels[0];
+		ddf_msg(LVL_NOTE, "DMA channel %u", res->dma);
+	} else {
+		res->dma = -1;
+	}
+
+	return EOK;
+error:
+	hw_res_list_parsed_clean(&hw_res);
+	return rc;
+}
+
+/** Add new FDC device
+ *
+ * @param dev New device
+ * @return EOK on success or an error code.
+ */
+static errno_t pc_fdc_dev_add(ddf_dev_t *dev)
+{
+	pc_fdc_t *fdc;
+	pc_fdc_hwres_t res;
+	errno_t rc;
+
+	rc = pc_fdc_get_res(dev, &res);
+	if (rc != EOK) {
+		ddf_msg(LVL_ERROR, "Invalid HW resource configuration.");
+		return EINVAL;
+	}
+
+	rc = pc_fdc_create(dev, &res, &fdc);
+	if (rc == ENOENT)
+		goto error;
+
+	if (rc != EOK) {
+		ddf_msg(LVL_ERROR, "Failed initializing floppy drive "
+		    "controller.");
+		rc = EIO;
+		goto error;
+	}
+
+	return EOK;
+error:
+	return rc;
+}
+
+/** Remove FDC device.
+ *
+ * @param dev Device
+ * @return EOK on success or an error code
+ */
+static errno_t pc_fdc_dev_remove(ddf_dev_t *dev)
+{
+	pc_fdc_t *fdc = (pc_fdc_t *)ddf_dev_data_get(dev);
+	errno_t rc;
+
+	ddf_msg(LVL_DEBUG, "pc_fdc_dev_remove(%p)", dev);
+
+	rc = pc_fdc_destroy(fdc);
+	if (rc != EOK)
+		return rc;
+
+	return EOK;
+}
+
+/** Suprise removal of FDC device.
+ *
+ * @param dev Device
+ * @return EOK on success or an error code
+ */
+static errno_t pc_fdc_dev_gone(ddf_dev_t *dev)
+{
+	(void)dev;
+	return ENOTSUP;
+}
+
+/** Online FDC function.
+ *
+ * @param fun DDF function
+ * @return EOK on success or an error code
+ */
+static errno_t pc_fdc_fun_online(ddf_fun_t *fun)
+{
+	ddf_msg(LVL_DEBUG, "pc_fdc_fun_online()");
+	return ddf_fun_online(fun);
+}
+
+/** Offline FDC function.
+ *
+ * @param fun DDF function
+ * @return EOK on success or an error code
+ */
+static errno_t pc_fdc_fun_offline(ddf_fun_t *fun)
+{
+	ddf_msg(LVL_DEBUG, "pc_fdc_fun_offline()");
+	return ddf_fun_offline(fun);
+}
+
+int main(int argc, char *argv[])
+{
+	printf(NAME ": HelenOS PC floppy disk driver\n");
+	ddf_log_init(NAME);
+	return ddf_driver_main(&pc_fdc_driver);
+}
+
+/**
+ * @}
+ */
Index: uspace/drv/block/pc-floppy/meson.build
===================================================================
--- uspace/drv/block/pc-floppy/meson.build	(revision dc5647ed5a125308dfb3f30e9c035e9392912353)
+++ uspace/drv/block/pc-floppy/meson.build	(revision dc5647ed5a125308dfb3f30e9c035e9392912353)
@@ -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 = [ 'device' ]
+src = files('pc-floppy.c', 'main.c')
Index: uspace/drv/block/pc-floppy/pc-floppy.c
===================================================================
--- uspace/drv/block/pc-floppy/pc-floppy.c	(revision dc5647ed5a125308dfb3f30e9c035e9392912353)
+++ uspace/drv/block/pc-floppy/pc-floppy.c	(revision dc5647ed5a125308dfb3f30e9c035e9392912353)
@@ -0,0 +1,1004 @@
+/*
+ * 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 pc-floppy
+ * @{
+ */
+
+/**
+ * @file
+ * @brief PC floppy disk driver
+ */
+
+#include <bd_srv.h>
+#include <ddi.h>
+#include <ddf/interrupt.h>
+#include <ddf/log.h>
+#include <async.h>
+#include <as.h>
+#include <errno.h>
+#include <fibril_synch.h>
+#include <macros.h>
+#include <perf.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <str.h>
+#include <str_error.h>
+#include <inttypes.h>
+#include <errno.h>
+
+#include "pc-floppy.h"
+
+static errno_t pc_fdc_init_io(pc_fdc_t *);
+static void pc_fdc_fini_io(pc_fdc_t *);
+static errno_t pc_fdc_init_irq(pc_fdc_t *);
+static void pc_fdc_fini_irq(pc_fdc_t *);
+static void pc_fdc_irq_handler(ipc_call_t *, void *);
+
+static errno_t pc_fdc_drive_create(pc_fdc_t *, unsigned, pc_fdc_drive_t **);
+
+static errno_t pc_fdc_reset(pc_fdc_t *);
+static errno_t pc_fdc_read_id(pc_fdc_t *, bool, uint8_t, uint8_t);
+static errno_t pc_fdc_sense_int_sts(pc_fdc_t *);
+
+static errno_t pc_fdc_bd_open(bd_srvs_t *, bd_srv_t *);
+static errno_t pc_fdc_bd_close(bd_srv_t *);
+static errno_t pc_fdc_bd_read_blocks(bd_srv_t *, uint64_t, size_t, void *, size_t);
+static errno_t pc_fdc_bd_read_toc(bd_srv_t *, uint8_t, void *, size_t);
+static errno_t pc_fdc_bd_write_blocks(bd_srv_t *, uint64_t, size_t, const void *,
+    size_t);
+static errno_t pc_fdc_bd_get_block_size(bd_srv_t *, size_t *);
+static errno_t pc_fdc_bd_get_num_blocks(bd_srv_t *, aoff64_t *);
+static errno_t pc_fdc_bd_sync_cache(bd_srv_t *, aoff64_t, size_t);
+
+static bd_ops_t pc_fdc_bd_ops = {
+	.open = pc_fdc_bd_open,
+	.close = pc_fdc_bd_close,
+	.read_blocks = pc_fdc_bd_read_blocks,
+	.read_toc = pc_fdc_bd_read_toc,
+	.write_blocks = pc_fdc_bd_write_blocks,
+	.get_block_size = pc_fdc_bd_get_block_size,
+	.get_num_blocks = pc_fdc_bd_get_num_blocks,
+	.sync_cache = pc_fdc_bd_sync_cache
+};
+
+enum {
+	msr_read_cycles = 100,
+	fdc_def_dma_buf_size = 4096
+};
+
+static const irq_pio_range_t pc_fdc_irq_ranges[] = {
+	{
+		.base = 0,
+		.size = sizeof(pc_fdc_regs_t)
+	}
+};
+
+/** PC floppy interrupt pseudo code.
+ *
+ * Floppy interrupts are complex. Since we don't want to change the handler
+ * all the time, need to handle both status-ful and status-less IRQs.
+ *
+ * XXX Do we need to wait for MSR.RQM == 1? That's really difficult
+ * in pseudocode.
+ *
+ * if (MSR.DIO == 0)
+ *    send SENSE INT STATUS;
+ *
+ * st[0] = read DATA;
+ * [repeat 5 times]
+ *   if (MSR.DIO == 1)
+ *       st[1] = read DATA;
+ *
+ */
+static const irq_cmd_t pc_fdc_irq_cmds[] = {
+	{
+		.cmd = CMD_ACCEPT
+	}
+
+};
+
+/** Create PC floppy controller driver instnace.
+ *
+ * @param dev DDF device
+ * @param res HW resources
+ * @param rfdc Place to store pointer to floppy controller
+ */
+errno_t pc_fdc_create(ddf_dev_t *dev, pc_fdc_hwres_t *res, pc_fdc_t **rfdc)
+{
+	errno_t rc;
+	pc_fdc_t *fdc;
+	bool irq_inited = false;
+	void *buffer = NULL;
+
+	ddf_msg(LVL_DEBUG, "pc_fdc_init()");
+
+	fdc = ddf_dev_data_alloc(dev, sizeof(pc_fdc_t));
+	if (fdc == NULL) {
+		ddf_msg(LVL_ERROR, "Failed allocating FDC.");
+		rc = ENOMEM;
+		goto error;
+	}
+
+	fdc->dev = dev;
+
+	fibril_mutex_initialize(&fdc->lock);
+	fdc->regs_physical = res->regs;
+	fdc->irq = res->irq;
+	fdc->dma = res->dma;
+
+	ddf_msg(LVL_NOTE, "I/O address %p", (void *)fdc->regs_physical);
+
+	ddf_msg(LVL_DEBUG, "Init I/O");
+	rc = pc_fdc_init_io(fdc);
+	if (rc != EOK)
+		return rc;
+
+	ddf_msg(LVL_DEBUG, "Init IRQ");
+	rc = pc_fdc_init_irq(fdc);
+	if (rc != EOK) {
+		ddf_msg(LVL_NOTE, "Init IRQ failed");
+		return rc;
+	}
+
+	irq_inited = true;
+
+	ddf_msg(LVL_DEBUG, "pc_fdc_ctrl_init(): read ID");
+
+	fdc->dma_buf_size = fdc_def_dma_buf_size;
+
+	buffer = AS_AREA_ANY;
+	rc = dmamem_map_anonymous(fdc->dma_buf_size, DMAMEM_1MiB | 0xffff,
+	    AS_AREA_WRITE | AS_AREA_READ, 0, &fdc->dma_buf_pa, &buffer);
+	if (rc != EOK) {
+		ddf_msg(LVL_NOTE, "Failed allocating PRD table.");
+		goto error;
+	}
+
+	fdc->dma_buf = buffer;
+
+	ddf_msg(LVL_NOTE, "pc_fdc_ctrl_init: reset controller...");
+	(void)pc_fdc_reset(fdc);
+	ddf_msg(LVL_NOTE, "pc_fdc_ctrl_init: read_ID..");
+
+	ddf_msg(LVL_NOTE, "pc_fdc_ctrl_init: MSR=0x%x",
+	    pio_read_8(&fdc->regs->msr));
+	ddf_msg(LVL_NOTE, "pc_fdc_ctrl_init: DIR=0x%x",
+	    pio_read_8(&fdc->regs->dir));
+	ddf_msg(LVL_NOTE, "pc_fdc_ctrl_init: SRA=0x%x",
+	    pio_read_8(&fdc->regs->sra));
+	ddf_msg(LVL_NOTE, "pc_fdc_ctrl_init: SRB=0x%x",
+	    pio_read_8(&fdc->regs->srb));
+
+	rc = pc_fdc_sense_int_sts(fdc);
+	if (rc != EOK)
+		return rc;
+	rc = pc_fdc_sense_int_sts(fdc);
+	if (rc != EOK)
+		return rc;
+	rc = pc_fdc_sense_int_sts(fdc);
+	if (rc != EOK)
+		return rc;
+	rc = pc_fdc_sense_int_sts(fdc);
+	if (rc != EOK)
+		return rc;
+
+	/* Read ID MFM, D0, H0 */
+	rc = pc_fdc_read_id(fdc, true, 0, 0);
+	ddf_msg(LVL_NOTE, "pc_fdc_ctrl_init: read_ID -> %d", rc);
+
+	rc = pc_fdc_drive_create(fdc, 0, &fdc->drive[0]);
+	if (rc != EOK) {
+		ddf_msg(LVL_NOTE, "pc_fdc_ctrl_init: pc_fdc_drive_create "
+		    "failed");
+		goto error;
+	}
+
+	fdc->drive[0]->sec_size = 512;
+	fdc->drive[0]->cylinders = 80;
+	fdc->drive[0]->heads = 2;
+	fdc->drive[0]->sectors = 18;
+
+	ddf_msg(LVL_DEBUG, "pc_fdc_ctrl_init: DONE");
+	return EOK;
+error:
+	if (buffer != NULL)
+		dmamem_unmap_anonymous(buffer);
+	if (irq_inited)
+		pc_fdc_fini_irq(fdc);
+	pc_fdc_fini_io(fdc);
+	return rc;
+}
+
+/** Destroy floppy controller instance.
+ *
+ * @param fdc Floppy controller
+ */
+errno_t pc_fdc_destroy(pc_fdc_t *fdc)
+{
+	ddf_msg(LVL_DEBUG, ": pc_fdc_destroy()");
+
+	fibril_mutex_lock(&fdc->lock);
+
+	pc_fdc_fini_irq(fdc);
+	pc_fdc_fini_io(fdc);
+	fibril_mutex_unlock(&fdc->lock);
+
+	free(fdc);
+	return EOK;
+}
+
+/** Enable device I/O.
+ *
+ * @param fdc Floppy controller
+ * @return EOK on success or an error code
+ */
+static errno_t pc_fdc_init_io(pc_fdc_t *fdc)
+{
+	errno_t rc;
+	void *vaddr;
+
+	rc = pio_enable((void *) fdc->regs_physical, sizeof(pc_fdc_regs_t),
+	    &vaddr);
+	if (rc != EOK) {
+		ddf_msg(LVL_ERROR, "Cannot initialize device I/O space.");
+		return rc;
+	}
+
+	fdc->regs = vaddr;
+	return EOK;
+}
+
+/** Clean up device I/O.
+ *
+ * @param fdc Floppy controller
+ */
+static void pc_fdc_fini_io(pc_fdc_t *fdc)
+{
+	(void)fdc;
+	/* XXX TODO */
+}
+
+/** Initialize IRQ.
+ *
+ * @param fdc Floppy controller
+ * @return EOK on success or an error code
+ */
+static errno_t pc_fdc_init_irq(pc_fdc_t *fdc)
+{
+	irq_code_t irq_code;
+	irq_pio_range_t *ranges;
+	irq_cmd_t *cmds;
+	async_sess_t *parent_sess;
+	errno_t rc;
+
+	if (fdc->irq < 0)
+		return EOK;
+
+	ranges = malloc(sizeof(pc_fdc_irq_ranges));
+	if (ranges == NULL)
+		return ENOMEM;
+
+	cmds = malloc(sizeof(pc_fdc_irq_cmds));
+	if (cmds == NULL) {
+		free(cmds);
+		return ENOMEM;
+	}
+
+	memcpy(ranges, &pc_fdc_irq_ranges, sizeof(pc_fdc_irq_ranges));
+	ranges[0].base = fdc->regs_physical;
+	memcpy(cmds, &pc_fdc_irq_cmds, sizeof(pc_fdc_irq_cmds));
+
+	irq_code.rangecount = sizeof(pc_fdc_irq_ranges) / sizeof(irq_pio_range_t);
+	irq_code.ranges = ranges;
+	irq_code.cmdcount = sizeof(pc_fdc_irq_cmds) / sizeof(irq_cmd_t);
+	irq_code.cmds = cmds;
+
+	ddf_msg(LVL_NOTE, "IRQ %d", fdc->irq);
+
+	rc = register_interrupt_handler(fdc->dev, fdc->irq,
+	    pc_fdc_irq_handler, (void *)fdc, &irq_code, &fdc->ihandle);
+	if (rc != EOK) {
+		ddf_msg(LVL_ERROR, "Error registering IRQ.");
+		goto error;
+	}
+
+	parent_sess = ddf_dev_parent_sess_get(fdc->dev);
+
+	rc = hw_res_enable_interrupt(parent_sess, fdc->irq);
+	if (rc != EOK) {
+		ddf_msg(LVL_ERROR, "Error enabling IRQ.");
+		return rc;
+	}
+
+	ddf_msg(LVL_DEBUG, "Interrupt handler registered");
+	free(ranges);
+	free(cmds);
+	return EOK;
+error:
+	free(ranges);
+	free(cmds);
+	return rc;
+}
+
+/** Clean up IRQ.
+ *
+ * @param fdc Floppy disk controller
+ */
+static void pc_fdc_fini_irq(pc_fdc_t *fdc)
+{
+	errno_t rc;
+	async_sess_t *parent_sess;
+
+	parent_sess = ddf_dev_parent_sess_get(fdc->dev);
+
+	rc = hw_res_disable_interrupt(parent_sess, fdc->irq);
+	if (rc != EOK)
+		ddf_msg(LVL_ERROR, "Error disabling IRQ.");
+
+	(void) unregister_interrupt_handler(fdc->dev, fdc->ihandle);
+}
+
+/** Get DDF function name for drive.
+ *
+ * @param fdc Floppy disk controller
+ * @param idx Drive index
+ * @return Function name (newly allocated string)
+ */
+static char *pc_fdc_fun_name(pc_fdc_t *fdc, unsigned idx)
+{
+	char *fun_name;
+
+	if (asprintf(&fun_name, "d%u", idx) < 0)
+		return NULL;
+
+	return fun_name;
+}
+
+/** Block device connection handler.
+ *
+ * @param icall Incoming call (pc_fdc_drive_t *)
+ * @param arg Argument
+ */
+static void pc_fdc_connection(ipc_call_t *icall, void *arg)
+{
+	pc_fdc_drive_t *drive;
+
+	drive = (pc_fdc_drive_t *)ddf_fun_data_get((ddf_fun_t *)arg);
+	bd_conn(icall, &drive->bds);
+}
+
+/** Create floppy drive object.
+ *
+ * @param fdc Floppy disk controller
+ * @param idx Drive index
+ * @param rdrive Place to store pointer to drive
+ *
+ * @return EOK on success or an error code
+ */
+static errno_t pc_fdc_drive_create(pc_fdc_t *fdc, unsigned idx,
+    pc_fdc_drive_t **rdrive)
+{
+	errno_t rc;
+	char *fun_name = NULL;
+	ddf_fun_t *fun = NULL;
+	pc_fdc_drive_t *drive = NULL;
+	bool bound = false;
+
+	fun_name = pc_fdc_fun_name(fdc, idx);
+	if (fun_name == NULL) {
+		ddf_msg(LVL_ERROR, "Out of memory.");
+		rc = ENOMEM;
+		goto error;
+	}
+
+	fun = ddf_fun_create(fdc->dev, fun_exposed, fun_name);
+	if (fun == NULL) {
+		ddf_msg(LVL_ERROR, "Failed creating DDF function.");
+		rc = ENOMEM;
+		goto error;
+	}
+
+	/* Allocate soft state */
+	drive = ddf_fun_data_alloc(fun, sizeof(pc_fdc_drive_t));
+	if (drive == NULL) {
+		ddf_msg(LVL_ERROR, "Failed allocating softstate.");
+		rc = ENOMEM;
+		goto error;
+	}
+
+	drive->fdc = fdc;
+	drive->fun = fun;
+
+	bd_srvs_init(&drive->bds);
+	drive->bds.ops = &pc_fdc_bd_ops;
+	drive->bds.sarg = (void *)drive;
+
+	/* Set up a connection handler. */
+	ddf_fun_set_conn_handler(fun, pc_fdc_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, "partition");
+	if (rc != EOK) {
+		ddf_msg(LVL_ERROR, "Failed adding function %s to "
+		    "category 'partition': %s", fun_name, str_error(rc));
+		goto error;
+	}
+
+	free(fun_name);
+	*rdrive = drive;
+	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;
+}
+
+/** Send byte to FDC data register.
+ *
+ * @param fdc Floppy controller
+ * @param byte Data byte
+ *
+ * @return EOK on success or an error code
+ */
+static errno_t pc_fdc_send_byte(pc_fdc_t *fdc, uint8_t byte)
+{
+	unsigned cnt;
+	uint8_t status;
+	stopwatch_t sw;
+	nsec_t nsec;
+
+	/*
+	 * We need to wait for up to 250 usec in total
+	 * per Intel 82077AA programming guidelines
+	 */
+	stopwatch_init(&sw);
+	stopwatch_start(&sw);
+
+	status = pio_read_8(&fdc->regs->msr);
+	ddf_msg(LVL_NOTE, "pc_fdc_send_byte: status=0x%x", status);
+	do {
+		cnt = msr_read_cycles;
+		while (cnt > 0) {
+			if ((status & fmsr_rqm) != 0 &&
+			    (status & fmsr_dio) == 0) {
+				pio_write_8(&fdc->regs->data, byte);
+				return EOK;
+			}
+
+			--cnt;
+			status = pio_read_8(&fdc->regs->msr);
+		}
+
+		stopwatch_stop(&sw);
+		nsec = stopwatch_get_nanos(&sw);
+		ddf_msg(LVL_NOTE, "nsec=%lld", nsec);
+	} while (nsec < 1000 * msr_max_wait_usec);
+
+	ddf_msg(LVL_NOTE, "pc_fdc_send_byte: FAILED (status=0x%x)", status);
+	return EIO;
+}
+
+/** Send a block of data to FDC data register.
+ *
+ * @param fdc Floppy controller
+ * @param data Data block
+ * @parma nbytes Size of data block in bytes
+ *
+ * @return EOK on success or an error code
+ */
+static errno_t pc_fdc_send(pc_fdc_t *fdc, const void *data, size_t nbytes)
+{
+	size_t i;
+	errno_t rc;
+	uint8_t status;
+
+	for (i = 0; i < nbytes; i++) {
+		rc = pc_fdc_send_byte(fdc, ((uint8_t *)data)[i]);
+		if (rc != EOK)
+			return rc;
+	}
+
+	status = pio_read_8(&fdc->regs->msr);
+	ddf_msg(LVL_NOTE, "pc_fdc_send: final status=0x%x", status);
+	return EOK;
+}
+
+/** Get byte from FDC data register.
+ *
+ * @param fdc Floppy controller
+ * @param byte Place to store data byte
+ *
+ * @return EOK on success or an error code
+ */
+static errno_t pc_fdc_get_byte(pc_fdc_t *fdc, uint8_t *byte)
+{
+	unsigned cnt;
+	uint8_t status;
+	stopwatch_t sw;
+	nsec_t nsec;
+
+	/*
+	 * We need to wait for up to 250 usec in total
+	 * per Intel 82077AA programming guidelines
+	 */
+	stopwatch_init(&sw);
+	stopwatch_start(&sw);
+
+	status = pio_read_8(&fdc->regs->msr);
+	ddf_msg(LVL_NOTE, "pc_fdc_get_byte: status=0x%x", status);
+	do {
+		cnt = msr_read_cycles;
+		while (cnt > 0) {
+			if ((status & fmsr_rqm) != 0 &&
+			    (status & fmsr_dio) != 0) {
+				*byte = pio_read_8(&fdc->regs->data);
+				return EOK;
+			}
+
+			--cnt;
+			status = pio_read_8(&fdc->regs->msr);
+		}
+
+		stopwatch_stop(&sw);
+		nsec = stopwatch_get_nanos(&sw);
+	} while (nsec / 1000 < 1000 * msr_max_wait_usec);
+
+	ddf_msg(LVL_NOTE, "pc_fdc_get_byte: FAILED (status=0x%x)", status);
+	return EIO;
+}
+
+/** Receive a block of data to FDC data register.
+ *
+ * @param fdc Floppy controller
+ * @param buf Data buffer
+ * @param bsize Size of data buffer in bytes
+ *
+ * @return EOK on success or an error code
+ */
+static errno_t pc_fdc_get(pc_fdc_t *fdc, void *buf, size_t bsize)
+{
+	size_t i;
+	errno_t rc;
+	uint8_t status;
+
+	for (i = 0; i < bsize; i++) {
+		rc = pc_fdc_get_byte(fdc, &((uint8_t *)buf)[i]);
+		if (rc != EOK) {
+			ddf_msg(LVL_NOTE, "pc_fdc_get: abort after "
+			    "reading %zu bytes", i);
+			return rc;
+		}
+	}
+
+	ddf_msg(LVL_NOTE, "pc_fdc_get: successfully read %zu bytes", i);
+	status = pio_read_8(&fdc->regs->msr);
+	ddf_msg(LVL_NOTE, "pc_fdc_get: final status=0x%x", status);
+	return EOK;
+}
+
+/** Reset floppy controller.
+ *
+ * @param fdc Floppy controller
+ * @return EOK on success or an error code
+ */
+static errno_t pc_fdc_reset(pc_fdc_t *fdc)
+{
+	uint8_t dor;
+
+	/* Use DSR reset for 82072 (or older) compatibility */
+	pio_write_8(&fdc->regs->dsr, fdsr_sw_reset | fdsr_drate_500kbps);
+
+	/* Clear DOR reset in case it was set (i.e., nreset := 1) */
+	dor = pio_read_8(&fdc->regs->dor);
+	ddf_msg(LVL_NOTE, "pc_fdc_reset: old DOR=0x%x, DOR := 0x%x", dor,
+	    dor & ~fdor_nreset);
+	pio_write_8(&fdc->regs->dor, dor & ~fdor_nreset);
+
+	dor = pio_read_8(&fdc->regs->dor);
+	ddf_msg(LVL_NOTE, "pc_fdc_reset: read DOR: value= 0x%x", dor);
+
+	fibril_usleep(4);
+
+	ddf_msg(LVL_NOTE, "pc_fdc_reset: old DOR=0x%x, DOR := 0x%x", dor,
+	    dor | fdor_nreset | fdor_ndmagate);
+	pio_write_8(&fdc->regs->dor, dor | fdor_nreset | fdor_ndmagate);
+
+	dor = pio_read_8(&fdc->regs->dor);
+	ddf_msg(LVL_NOTE, "pc_fdc_reset: read DOR: value= 0x%x", dor);
+
+	return EOK;
+}
+
+/** Perform Read ID command.
+ *
+ * @param fdc Floppy controller
+ * @param mfm Enable MFM (double density) mode
+ * @param drive Drive (0-3)
+ * @param head Head (0-1)
+ *
+ * @return EOK on success or an error code
+ */
+static errno_t pc_fdc_read_id(pc_fdc_t *fdc, bool mfm, uint8_t drive,
+    uint8_t head)
+{
+	pc_fdc_read_id_data_t cmd;
+	pc_fdc_cmd_status_t status;
+	uint8_t dor;
+	errno_t rc;
+
+	dor = pio_read_8(&fdc->regs->dor);
+	ddf_msg(LVL_NOTE, "pc_fdc_read_id: read DOR: value= 0x%x", dor);
+
+	dor |= fdor_me0; /* turn drive 0 motor on */
+	dor = (dor & ~0x03) | 0x00; /* select drive 0 */
+	pio_write_8(&fdc->regs->dor, dor);
+	ddf_msg(LVL_NOTE, "pc_fdc_read_id: DOR := 0x%x", dor);
+
+	dor = pio_read_8(&fdc->regs->dor);
+	ddf_msg(LVL_NOTE, "pc_fdc_read_id: read DOR: value= 0x%x", dor);
+
+	/* 500 ms to let drive spin up */
+	fibril_usleep(500 * 1000);
+
+	cmd.flags_cc = fcf_mf | fcc_read_id;
+	cmd.hd_us = 0x00;
+
+	ddf_msg(LVL_NOTE, "read ID: send");
+	rc = pc_fdc_send(fdc, &cmd, sizeof(cmd));
+	if (rc != EOK) {
+		ddf_msg(LVL_WARN, "Failed sending READ ID command.");
+		return rc;
+	}
+
+	ddf_msg(LVL_NOTE, "read ID: get");
+	rc = pc_fdc_get(fdc, &status, sizeof(status));
+	if (rc != EOK) {
+		ddf_msg(LVL_WARN, "Failed getting status for READ ID");
+		return rc;
+	}
+
+	ddf_msg(LVL_NOTE, "read ID: DONE");
+	ddf_msg(LVL_NOTE, "st0=0x%x st1=0x%x st2=0x%x cyl=%u head=%u rec=%u "
+	    "number=%u", status.st0, status.st1, status.st2,
+	    status.cyl, status.head, status.rec, status.number);
+
+	/* Check for success status */
+	if ((status.st0 & fsr0_ic_mask) != 0)
+		return EIO;
+
+	return EOK;
+}
+
+/** Perform Read Data command.
+ *
+ * @param drive Floppy drive
+ * @param cyl Cylinder
+ * @param head Head
+ * @param sec Sector
+ * @param buf Destination buffer
+ * @param buf_size Destination buffer size
+ *
+ * @return EOK on success or an error code
+ */
+static errno_t pc_fdc_drive_read_data(pc_fdc_drive_t *drive,
+    uint8_t cyl, uint8_t head, uint8_t sec, void *buf, size_t buf_size)
+{
+	pc_fdc_t *fdc = drive->fdc;
+	pc_fdc_cmd_data_t cmd;
+	pc_fdc_cmd_status_t status;
+	async_sess_t *sess;
+	size_t csize;
+	errno_t rc;
+
+	ddf_msg(LVL_NOTE, "pc_fdc_drive_read_data");
+
+	memset(fdc->dma_buf, 0, fdc->dma_buf_size);
+
+	sess = ddf_dev_parent_sess_get(fdc->dev);
+	ddf_msg(LVL_NOTE, "hw_res_dma_channel_setup(sess=%p, chan=%d "
+	    "pa=%lu size=%zu", sess, fdc->dma, fdc->dma_buf_pa,
+	    fdc->dma_buf_size);
+	rc = hw_res_dma_channel_setup(sess, fdc->dma, fdc->dma_buf_pa,
+	    fdc->dma_buf_size, DMA_MODE_READ | DMA_MODE_AUTO |
+	    DMA_MODE_ON_DEMAND);
+	ddf_msg(LVL_NOTE, "hw_res_dma_channel_setup->%d", rc);
+
+	cmd.flags_cc = fcf_mf | fcc_read_data;
+	cmd.hd_us = (head & 1) << 2 | 0x00 /* drive 0 */;
+	cmd.cyl = cyl;
+	cmd.head = head;
+	cmd.rec = sec;
+	cmd.number = 2; /* 512 bytes */
+	cmd.eot = sec;
+	cmd.gpl = 0x1b;
+	cmd.dtl = 0xff;
+
+	ddf_msg(LVL_NOTE, "read data: send");
+	rc = pc_fdc_send(fdc, &cmd, sizeof(cmd));
+	if (rc != EOK) {
+		ddf_msg(LVL_WARN, "Failed sending Read Data command.");
+		return rc;
+	}
+
+	ddf_msg(LVL_NOTE, "read data: get");
+	rc = pc_fdc_get(fdc, &status, sizeof(status));
+	if (rc != EOK) {
+		ddf_msg(LVL_WARN, "Failed getting status for Read Data");
+		return rc;
+	}
+
+	ddf_msg(LVL_NOTE, "read data: DONE");
+	ddf_msg(LVL_NOTE, "st0=0x%x st1=0x%x st2=0x%x cyl=%u head=%u rec=%u "
+	    "number=%u", status.st0, status.st1, status.st2,
+	    status.cyl, status.head, status.rec, status.number);
+
+	/* Check for success status */
+	if ((status.st0 & fsr0_ic_mask) != 0)
+		return EIO;
+
+	/* Copy data from DMA buffer to destination buffer */
+	csize = min(fdc->dma_buf_size, buf_size);
+	memcpy(buf, fdc->dma_buf, csize);
+
+	return EOK;
+}
+
+/** Perform Sense Interrupt Status command.
+ *
+ * @param fdc Floppy controller
+ * @param mfm Enable MFM (double density) mode
+ * @param drive Drive (0-3)
+ * @param head Head (0-1)
+ *
+ * @return EOK on success or an error code
+ */
+static errno_t pc_fdc_sense_int_sts(pc_fdc_t *fdc)
+{
+	pc_fdc_sense_int_sts_data_t cmd;
+	pc_fdc_sense_int_sts_status_t status;
+	errno_t rc;
+
+	cmd.cc = fcc_sense_int_sts;
+
+	ddf_msg(LVL_NOTE, "Sense Interrupt Status: send");
+	rc = pc_fdc_send(fdc, &cmd, sizeof(cmd));
+	if (rc != EOK) {
+		ddf_msg(LVL_WARN, "Failed sending Sense Interrupt Status "
+		    "command.");
+		return rc;
+	}
+
+	ddf_msg(LVL_NOTE, "Sense Interrupt Status: get");
+	rc = pc_fdc_get(fdc, &status, sizeof(status));
+	if (rc != EOK) {
+		ddf_msg(LVL_WARN, "Failed getting status for Sense Interrupt "
+		    "Status");
+		return rc;
+	}
+
+	ddf_msg(LVL_NOTE, "Sense Interrupt Status: DONE");
+	ddf_msg(LVL_NOTE, "st0=0x%x pcn=0x%x", status.st0, status.pcn);
+
+	return EOK;
+}
+
+/** Interrupt handler.
+ *
+ * @param call Call data
+ * @param arg Argument (pc_fdc_channel_t *)
+ */
+static void pc_fdc_irq_handler(ipc_call_t *call, void *arg)
+{
+	pc_fdc_t *fdc = (pc_fdc_t *)arg;
+	uint8_t st0;
+	uint8_t st1;
+	uint8_t st2;
+	uint8_t c, h, n;
+	async_sess_t *parent_sess;
+
+	st0 = ipc_get_arg1(call);
+	st1 = ipc_get_arg2(call);
+	st2 = ipc_get_arg3(call);
+	c = ipc_get_arg4(call);
+	h = ipc_get_arg5(call);
+	n = ipc_get_imethod(call);
+	ddf_msg(LVL_NOTE, "pc_fdc_irq_handler st0=%x st1=%x st2=%x c=%u h=%u n=%u",
+	    st0, st1, st2, c, h, n);
+
+	parent_sess = ddf_dev_parent_sess_get(fdc->dev);
+	hw_res_clear_interrupt(parent_sess, fdc->irq);
+}
+
+/** Get floppy drive from block device service.
+ *
+ * @param bd Block device
+ * @return Floppy drive
+ */
+static pc_fdc_drive_t *bd_srv_drive(bd_srv_t *bd)
+{
+	return (pc_fdc_drive_t *)bd->srvs->sarg;
+}
+
+/** Convert LBA to CHS.
+ *
+ * @param drive Floppy drive
+ * @param ba Logical block address
+ * @param cyl Place to store cylinder number
+ * @param head Place to store head number
+ * @param sec Place to store sector number
+ */
+static void pc_fdc_drive_ba_to_chs(pc_fdc_drive_t *drive, uint64_t ba,
+    uint8_t *cyl, uint8_t *head, uint8_t *sec)
+{
+	unsigned ch;
+
+	*sec = 1 + (ba % drive->sectors);
+	ch = ba / drive->sectors;
+	*head = ch % drive->heads;
+	*cyl = ch / drive->heads;
+}
+
+/** Open block device.
+ *
+ * @param bds Block device server
+ * @param bd Block device
+ * @return EOK on success or an error code
+ */
+static errno_t pc_fdc_bd_open(bd_srvs_t *bds, bd_srv_t *bd)
+{
+	return EOK;
+}
+
+/** Close block device.
+ *
+ * @param bd Block device
+ * @return EOK on success or an error code
+ */
+static errno_t pc_fdc_bd_close(bd_srv_t *bd)
+{
+	return EOK;
+}
+
+/** Read multiple blocks from block device.
+ *
+ * @param bd Block device
+ * @param ba Address of first block
+ * @param cnt Number of blocks
+ * @param buf Destination buffer
+ * @param size Size of destination buffer in bytes
+ *
+ * @return EOK on success or an error code
+ */
+static errno_t pc_fdc_bd_read_blocks(bd_srv_t *bd, uint64_t ba, size_t cnt,
+    void *buf, size_t size)
+{
+	pc_fdc_drive_t *drive = bd_srv_drive(bd);
+	uint8_t cyl, head, sec;
+	errno_t rc;
+
+	ddf_msg(LVL_NOTE, "pc_fdc_bd_read_blocks");
+
+	if (size < cnt * drive->sec_size) {
+		rc = EINVAL;
+		goto error;
+	}
+
+	/* Maximum number of blocks to transfer at the same time */
+	while (cnt > 0) {
+		pc_fdc_drive_ba_to_chs(drive, ba, &cyl, &head, &sec);
+
+		/* Read one block */
+		rc = pc_fdc_drive_read_data(drive, cyl, head, sec, buf,
+		    drive->sec_size);
+		if (rc != EOK)
+			goto error;
+
+		++ba;
+		--cnt;
+		buf += drive->sec_size;
+	}
+
+	return EOK;
+error:
+	ddf_msg(LVL_NOTE, "pc_fdc_bd_read_blocks: rc=%d", rc);
+	return rc;
+}
+
+/** Read TOC from block device.
+ *
+ * @param bd Block device
+ * @param session Session number
+ * @param buf Destination buffer
+ * @param size Size of destination buffer in bytes
+ *
+ * @return EOK on success or an error code
+ */
+static errno_t pc_fdc_bd_read_toc(bd_srv_t *bd, uint8_t session, void *buf, size_t size)
+{
+	return ENOTSUP;
+}
+
+/** Write multiple blocks to block device.
+ *
+ * @param bd Block device
+ * @param ba Address of first block
+ * @param cnt Number of blocks
+ * @param buf Source buffer
+ * @param size Size of source buffer in bytes
+ *
+ * @return EOK on success or an error code
+ */
+static errno_t pc_fdc_bd_write_blocks(bd_srv_t *bd, uint64_t ba, size_t cnt,
+    const void *buf, size_t size)
+{
+	return ENOTSUP;
+}
+
+/** Get device block size. */
+static errno_t pc_fdc_bd_get_block_size(bd_srv_t *bd, size_t *rbsize)
+{
+	pc_fdc_drive_t *drive = bd_srv_drive(bd);
+
+	*rbsize = drive->sec_size;
+	return EOK;
+}
+
+/** Get device number of blocks. */
+static errno_t pc_fdc_bd_get_num_blocks(bd_srv_t *bd, aoff64_t *rnb)
+{
+	pc_fdc_drive_t *drive = bd_srv_drive(bd);
+
+	*rnb = drive->cylinders * drive->heads * drive->sectors;
+	return EOK;
+}
+
+/** Flush cache. */
+static errno_t pc_fdc_bd_sync_cache(bd_srv_t *bd, uint64_t ba, size_t cnt)
+{
+	return EOK;
+}
+
+/**
+ * @}
+ */
Index: uspace/drv/block/pc-floppy/pc-floppy.h
===================================================================
--- uspace/drv/block/pc-floppy/pc-floppy.h	(revision dc5647ed5a125308dfb3f30e9c035e9392912353)
+++ uspace/drv/block/pc-floppy/pc-floppy.h	(revision dc5647ed5a125308dfb3f30e9c035e9392912353)
@@ -0,0 +1,104 @@
+/*
+ * 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 pc-floppy
+ * @{
+ */
+/** @file PC floppy disk driver
+ */
+
+#ifndef PC_FLOPPY_H
+#define PC_FLOPPY_H
+
+#include <bd_srv.h>
+#include <ddf/driver.h>
+#include <fibril_synch.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include "pc-floppy_hw.h"
+
+#define NAME "pc-floppy"
+
+/** PC floppy controller hardware resources */
+typedef struct {
+	/** I/O registers */
+	uintptr_t regs;
+	/** IRQ */
+	int irq;
+	/** DMA channel */
+	int dma;
+} pc_fdc_hwres_t;
+
+/** PC floppy disk controller */
+typedef struct pc_fdc {
+	/** DDF device */
+	ddf_dev_t *dev;
+	/** I/O base address of the registers */
+	uintptr_t regs_physical;
+
+	/** Command registers */
+	pc_fdc_regs_t *regs;
+	/** IRQ (-1 if not used) */
+	int irq;
+	/** DMA (-1 if not used) */
+	int dma;
+	/** IRQ handle */
+	cap_irq_handle_t ihandle;
+
+	/** DMA buffer */
+	void *dma_buf;
+	/** DMA buffer physical address */
+	uintptr_t dma_buf_pa;
+	/** DMA buffer size */
+	size_t dma_buf_size;
+
+	/** Synchronize controller access */
+	fibril_mutex_t lock;
+
+	struct pc_fdc_drive *drive[2];
+} pc_fdc_t;
+
+/** PC floppy drive */
+typedef struct pc_fdc_drive {
+	pc_fdc_t *fdc;
+	ddf_fun_t *fun;
+	void *charg;
+	size_t sec_size;
+	unsigned cylinders;
+	unsigned heads;
+	unsigned sectors;
+	bd_srvs_t bds;
+} pc_fdc_drive_t;
+
+extern errno_t pc_fdc_create(ddf_dev_t *, pc_fdc_hwres_t *, pc_fdc_t **);
+extern errno_t pc_fdc_destroy(pc_fdc_t *);
+
+#endif
+
+/** @}
+ */
Index: uspace/drv/block/pc-floppy/pc-floppy.ma
===================================================================
--- uspace/drv/block/pc-floppy/pc-floppy.ma	(revision dc5647ed5a125308dfb3f30e9c035e9392912353)
+++ uspace/drv/block/pc-floppy/pc-floppy.ma	(revision dc5647ed5a125308dfb3f30e9c035e9392912353)
@@ -0,0 +1,1 @@
+10 isa/fdc
Index: uspace/drv/block/pc-floppy/pc-floppy_hw.h
===================================================================
--- uspace/drv/block/pc-floppy/pc-floppy_hw.h	(revision dc5647ed5a125308dfb3f30e9c035e9392912353)
+++ uspace/drv/block/pc-floppy/pc-floppy_hw.h	(revision dc5647ed5a125308dfb3f30e9c035e9392912353)
@@ -0,0 +1,411 @@
+/*
+ * 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 PC Floppy Disk hardware definitions
+ *
+ * Based on
+ *  - NEC uPD765A datasheet
+ *  - Intel 82077AA Floppy Controller Datasheet
+ */
+
+#ifndef PC_FLOPPY_HW_H
+#define PC_FLOPPY_HW_H
+
+/** Command Codes */
+typedef enum {
+	/** Read Data */
+	fcc_read_data = 0x06,
+	/** Read Delete Data */
+	fcc_read_ddata = 0x0c,
+	/** Write Data */
+	fcc_write_data = 0x05,
+	/** Write Deleted Data */
+	fcc_write_ddata = 0x09,
+	/** Read a Track */
+	fcc_read_track = 0x02,
+	/** Read ID */
+	fcc_read_id = 0x0a,
+	/** Format a Track */
+	fcc_format_track = 0x0d,
+	/** Scan Equal */
+	fcc_scan_equal = 0x11,
+	/** Scan Low or Equal */
+	fcc_scan_lequal = 0x19,
+	/** Scan High or Equal */
+	fcc_scan_hequal = 0x1d,
+	/** Recalibrate */
+	fcc_recalibrate = 0x07,
+	/** Sense Interrupt Status */
+	fcc_sense_int_sts = 0x08,
+	/** Specify */
+	fcc_specify = 0x03,
+	/** Sense Drive Status */
+	fcc_sense_drv_sts = 0x04,
+	/** Seek */
+	fcc_seek = 0x0f
+} pc_fdc_cmd_code_t;
+
+/** MT|MF|SK flags used in flags_cc byte */
+typedef enum {
+	/** Multi Track */
+	fcf_mt = 0x80,
+	/** FM or MFM mode */
+	fcf_mf = 0x40,
+	/** Skip deleted address mark */
+	fcf_sk = 0x20
+} pc_fcd_flags_t;
+
+/** Command parameters common for most data commands */
+typedef struct {
+	/** [MT] | MF | [SK] | command code */
+	uint8_t flags_cc;
+	/** XXXXX | HD | US1 | US0 */
+	uint8_t hd_us;
+	/** Cylinder number */
+	uint8_t cyl;
+	/** Head number */
+	uint8_t head;
+	/** Record number */
+	uint8_t rec;
+	/** Number */
+	uint8_t number;
+	/** End of Track */
+	uint8_t eot;
+	/** Gap Length */
+	uint8_t gpl;
+	/** Data Length */
+	uint8_t dtl;
+} pc_fdc_cmd_data_t;
+
+/** Status data common for most commands */
+typedef struct {
+	/** Status 0 */
+	uint8_t st0;
+	/** Status 1 */
+	uint8_t st1;
+	/** Status 2 */
+	uint8_t st2;
+	/** Cylinder number */
+	uint8_t cyl;
+	/** Head number */
+	uint8_t head;
+	/** Record number */
+	uint8_t rec;
+	/** Number */
+	uint8_t number;
+} pc_fdc_cmd_status_t;
+
+/** Command parameters for Read ID command */
+typedef struct {
+	/** 0 | MF | 0 | command code */
+	uint8_t flags_cc;
+	/** XXXXX | HD | US1 | US0 */
+	uint8_t hd_us;
+} pc_fdc_read_id_data_t;
+
+/** Command parameters for Format a Track command */
+typedef struct {
+	/** 0 | MF | 0 | command code */
+	uint8_t flags_cc;
+	/** XXXXX | HD | US1 | US0 */
+	uint8_t hd_us;
+	/** Number */
+	uint8_t number;
+	/** Sectors per Cylinder */
+	uint8_t sec_cyl;
+	/** Gap Length */
+	uint8_t gpl;
+	/** Data Pattern */
+	uint8_t dpat;
+} pc_fdc_format_track_data_t;
+
+/** Command parameters for Recalibrate command */
+typedef struct {
+	/** 0 | 0 | 0 | command code */
+	uint8_t cc;
+	/** XXXXX | 0 | US1 | US0 */
+	uint8_t us;
+} pc_fdc_recalibrate_data_t;
+
+/** Command parameters for Sense Interrupt Status command */
+typedef struct {
+	/** 0 | 0 | 0 | command code */
+	uint8_t cc;
+} pc_fdc_sense_int_sts_data_t;
+
+/** Status data common for Sense Interrupt Status command */
+typedef struct {
+	/** Status 0 */
+	uint8_t st0;
+	/** Present Cylinder Number */
+	uint8_t pcn;
+} pc_fdc_sense_int_sts_status_t;
+
+/** Command parameters for Specify command */
+typedef struct {
+	/** 0 | 0 | 0 | command code */
+	uint8_t cc;
+	/** Step Rate Time, Head Unload Time */
+	uint8_t srt_hut;
+	/** Head Load Time, Non-DMA Mode */
+	uint8_t hlt_nd;
+} pc_fdc_secify_data_t;
+
+/** Command parameters for Sense Drive Status command */
+typedef struct {
+	/** 0 | 0 | 0 | command code */
+	uint8_t cc;
+	/** XXXXX | HD | US1 | US0 */
+	uint8_t hd_us;
+} pc_fdc_sense_drive_sts_data_t;
+
+/** Command parameters for Seek command */
+typedef struct {
+	/** 0 | 0 | 0 | command code */
+	uint8_t cc;
+	/** XXXXX | HD | US1 | US0 */
+	uint8_t hd_us;
+} pc_fdc_seek_data_t;
+
+/** Bits in Status Register A (SRA) PS/2 Mode */
+enum {
+	fsra2_int_pending = 0x80,
+	fsra2_ndrv2 = 0x40,
+	fsra2_step = 0x20,
+	fsra2_ntrk0 = 0x10,
+	fsra2_hdsel = 0x08,
+	fsra2_nindx = 0x04,
+	fsra2_nwp = 0x02,
+	fsra2_dir = 0x01
+};
+
+/** Bits in Status Register A (SRA) Model 30 Mode */
+enum {
+	fsra3_int_pending = 0x80,
+	fsra3_drq = 0x40,
+	fsra3_step_ff = 0x20,
+	fsra3_trko = 0x10,
+	fsra3_nhdsel = 0x08,
+	fsra3_index = 0x04,
+	fsra3_wp = 0x02,
+	fsra3_ndir = 0x01
+};
+
+/** Bits in Status Register B (SRB) PS/2 Mode */
+enum {
+	fsrb_d0sel = 0x20,
+	fsrb_wrd_tgl = 0x10,
+	fsrb_rdd_tgl = 0x08,
+	fsrb_we = 0x04,
+	fsrb_me1 = 0x02,
+	fsrb_me0 = 0x01
+};
+
+/** Bits in Status Register B (SRB) Model 30 Mode */
+enum {
+	fsrb_ndrv2 = 0x80,
+	fsrb_nds1 = 0x40,
+	fsrb_nds0 = 0x20,
+	fsrb_wrd_ff = 0x10,
+	fsrb_rdd_ff = 0x08,
+	fsrb_we_ff = 0x04,
+	fsrb_nds3 = 0x02,
+	fsrb_nds2 = 0x01
+};
+
+/** Bits in Digital Output Register (DOR) */
+enum {
+	fdor_me3 = 0x80,
+	fdor_me2 = 0x40,
+	fdor_me1 = 0x20,
+	fdor_me0 = 0x10,
+	fdor_ndmagate = 0x08,
+	fdor_nreset = 0x04,
+	fdor_ds1 = 0x02,
+	fdor_ds0 = 0x01
+};
+
+/** Bits in Tape Drive Register (TDR) */
+enum {
+	ftdr_ts1 = 0x02,
+	ftdr_ts0 = 0x01
+};
+
+/** Bits in Datarate Select Register (DSR) */
+enum {
+	fdsr_sw_reset = 0x80,
+	fdsr_power_down = 0x40,
+	fdsr_precomp2 = 0x10,
+	fdsr_precomp1 = 0x08,
+	fdsr_precomp0 = 0x04,
+	fdsr_drate_sel1 = 0x02,
+	fdsr_drate_sel0 = 0x01
+};
+
+/** Combined values of DSR.DRATE_SEL1/0 */
+enum {
+	fdsr_drate_1mbps = 0x03,
+	fdsr_drate_500kbps = 0x00,
+	fdsr_drate_300kbps = 0x01,
+	fdsr_drate_250kbps = 0x02
+};
+
+/** Bits in Main Status Register (MSR) */
+enum {
+	/** Request for Master */
+	fmsr_rqm = 0x80,
+	/** Data Input/Output */
+	fmsr_dio = 0x40,
+	/** Execution Mode */
+	fmsr_exm = 0x20,
+	/** FDC Busy */
+	fmsr_cb = 0x10,
+	/** FDD 3 Busy */
+	fmsr_d3b = 0x08,
+	/** FDD 2 Busy */
+	fmsr_d2b = 0x04,
+	/** FDD 1 Busy */
+	fmsr_d1b = 0x02,
+	/** FDD 0 Busy */
+	fmsr_d0b = 0x01,
+};
+
+/** Bits in Digital Input Register, PC-AT Mode */
+enum {
+	fdira_dsk_chg = 0x80
+};
+
+/** Bits in Digital Input Register, PS/2 Mode */
+enum {
+	fdir2_dsk_chg = 0x80,
+	fdir2_drate_sel1 = 0x04,
+	fdir2_drate_sel0 = 0x02,
+	fdir2_nhigh_dens = 0x01
+};
+
+/** Bits in Digital Input Register, Model 30 Mode */
+enum {
+	fdir3_dsk_chg = 0x80,
+	fdir3_ndma_gate = 0x08,
+	fdir3_noprec = 0x04,
+	fdir3_drate_sel1 = 0x02,
+	fdir3_drate_sel0 = 0x01
+};
+
+/** Bits in Configuration Control Register (CCR) */
+enum {
+	fccr_noprec = 0x04,
+	fccr_drate_sel1 = 0x02,
+	fccr_drate_sel0 = 0x01
+};
+
+/** Bits in Status Register 0 (SR0) */
+enum {
+	fsr0_ic_mask = 0xc0,
+	fsr0_ic_normal = 0x00,
+	fsr0_ic_abnormal = 0x40,
+	fsr0_ic_invcmd = 0x80,
+	fsr0_ic_abnormal_poll = 0xc0,
+	fsr0_seek_end = 0x20,
+	fsr0_equip_check = 0x10,
+	fsr0_head_addr = 0x04,
+	fsr0_ds1 = 0x02,
+	fsr0_ds0 = 0x01
+};
+
+/** Bits in Status Register 1 (SR1) */
+enum {
+	fsr1_end_of_cyl = 0x80,
+	fsr1_data_error = 0x20,
+	fsr1_overr_underr = 0x10,
+	fsr1_no_data = 0x04,
+	fsr1_not_writable = 0x02,
+	fsr1_missing_am = 0x01
+};
+
+/** Bits in Status Register 2 (SR2) */
+enum {
+	fsr2_control_mark = 0x40,
+	fsr1_derr_df = 0x20,
+	fsr1_wrong_cyl = 0x10,
+	fsr1_bad_cyl = 0x02,
+	fsr1_missing_dam = 0x01
+};
+
+/** Registers */
+typedef union {
+	struct { /* read only */
+		/** Status Register A */
+		uint8_t sra;
+		/** Starus Register B */
+		uint8_t srb;
+		/** Padding */
+		uint8_t ro_pad2[2];
+		/** Main Status Register */
+		uint8_t msr;
+		/** Padding */
+		uint8_t ro_pad5[2];
+		/** Digital Inut Register */
+		uint8_t dir;
+	};
+	struct { /* write only */
+		/** Padding */
+		uint8_t wo_pad0[4];
+		/** Datarate Select Register */
+		uint8_t dsr;
+		/** Padding */
+		uint8_t wo_pad5[2];
+		/** Configuration Control Register */
+		uint8_t ccr;
+	};
+	struct { /* read/write */
+		/** Padding */
+		uint8_t rw_pad0[2];
+		/** Digital Output Register */
+		uint8_t dor;
+		/** Tape Drive Register */
+		uint8_t tdr;
+		/** Padding */
+		uint8_t rw_pad4;
+		/** Data (FIFO) */
+		uint8_t data;
+	};
+} pc_fdc_regs_t;
+
+enum {
+	/** Max. time we need to wait for MSR status */
+	msr_max_wait_usec = 250
+};
+
+#endif
+
+/** @}
+ */
Index: uspace/drv/block/pci-ide/pci-ide.c
===================================================================
--- uspace/drv/block/pci-ide/pci-ide.c	(revision e3997a897c134db63e3b799a0b3885fb90fd2731)
+++ uspace/drv/block/pci-ide/pci-ide.c	(revision dc5647ed5a125308dfb3f30e9c035e9392912353)
@@ -186,5 +186,5 @@
 	bool irq_inited = false;
 	ata_params_t params;
-	void *buffer;
+	void *buffer = NULL;
 
 	ddf_msg(LVL_DEBUG, "pci_ide_channel_init()");
@@ -294,4 +294,6 @@
 	return EOK;
 error:
+	if (buffer != NULL)
+		dmamem_unmap_anonymous(buffer);
 	if (irq_inited)
 		pci_ide_fini_irq(chan);
@@ -315,4 +317,5 @@
 	}
 
+	dmamem_unmap_anonymous(chan->dma_buf);
 	pci_ide_fini_irq(chan);
 	pci_ide_fini_io(chan);
Index: uspace/drv/bus/isa/isa.dev
===================================================================
--- uspace/drv/bus/isa/isa.dev	(revision e3997a897c134db63e3b799a0b3885fb90fd2731)
+++ uspace/drv/bus/isa/isa.dev	(revision dc5647ed5a125308dfb3f30e9c035e9392912353)
@@ -51,4 +51,10 @@
 	io_range 70 2
 
+fdc:
+	match 100 isa/fdc
+	io_range 0x3f0 8
+	irq 6
+	dma 2
+
 ide1:
 	match 100 isa/ide
Index: uspace/drv/meson.build
===================================================================
--- uspace/drv/meson.build	(revision e3997a897c134db63e3b799a0b3885fb90fd2731)
+++ uspace/drv/meson.build	(revision dc5647ed5a125308dfb3f30e9c035e9392912353)
@@ -35,4 +35,5 @@
 	'block/ddisk',
 	'block/isa-ide',
+	'block/pc-floppy',
 	'block/pci-ide',
 	'block/usbmast',
Index: uspace/lib/c/include/ddi.h
===================================================================
--- uspace/lib/c/include/ddi.h	(revision e3997a897c134db63e3b799a0b3885fb90fd2731)
+++ uspace/lib/c/include/ddi.h	(revision dc5647ed5a125308dfb3f30e9c035e9392912353)
@@ -47,4 +47,5 @@
 #include <task.h>
 
+#define DMAMEM_1MiB   ((uintptr_t) UINT64_C(0xfffffffffff00000))
 #define DMAMEM_16MiB  ((uintptr_t) UINT64_C(0xffffffffff000000))
 #define DMAMEM_4GiB   ((uintptr_t) UINT64_C(0xffffffff00000000))
