diff mbox series

[v9] block: Add n64 cart driver

Message ID 20210122110043.f36cd22df1233754753518c1@gmx.com (mailing list archive)
State Superseded
Headers show
Series [v9] block: Add n64 cart driver | expand

Commit Message

Lauri Kasanen Jan. 22, 2021, 9 a.m. UTC
This adds support for the Nintendo 64 console's carts. Carts are a
read-only media ranging from 8mb to 64mb.

Only one cart can be connected at once, and switching it requires a
reboot.

No module support to save RAM, as the target has 8mb RAM.

Signed-off-by: Lauri Kasanen <cand@gmx.com>
---
 drivers/block/Kconfig   |   6 ++
 drivers/block/Makefile  |   1 +
 drivers/block/n64cart.c | 211 ++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 218 insertions(+)
 create mode 100644 drivers/block/n64cart.c

v9:
cosmetics
min_t

v8:
SZ_64K
remove barriers
add defines for block domain constants
__blk_mq_end_request
remove register_blkdev via GENHD_FL_EXT_DEVT
dma_alloc_noncoherent, sync

--
2.6.2

Comments

Lauri Kasanen Jan. 22, 2021, 1:58 p.m. UTC | #1
On Fri, 22 Jan 2021 11:17:27 +0000
Christoph Hellwig <hch@infradead.org> wrote:

> Sorry for not noticing this earlier, but why do you need the bounce
> buffer vs just mapping the data buffer?

I think I've answered this once already a few iterations before. DMA
alignment restrictions, not sure what guarantees the bio buffer has.

- Lauri
Christoph Hellwig Jan. 22, 2021, 4:18 p.m. UTC | #2
On Fri, Jan 22, 2021 at 03:58:01PM +0200, Lauri Kasanen wrote:
> On Fri, 22 Jan 2021 11:17:27 +0000
> Christoph Hellwig <hch@infradead.org> wrote:
> 
> > Sorry for not noticing this earlier, but why do you need the bounce
> > buffer vs just mapping the data buffer?
> 
> I think I've answered this once already a few iterations before. DMA
> alignment restrictions, not sure what guarantees the bio buffer has.

Your driver can communicate the required alignment using the
blk_queue_update_dma_alignment API.
Lauri Kasanen Jan. 22, 2021, 4:23 p.m. UTC | #3
On Fri, 22 Jan 2021 16:18:04 +0000
Christoph Hellwig <hch@infradead.org> wrote:

> On Fri, Jan 22, 2021 at 03:58:01PM +0200, Lauri Kasanen wrote:
> > On Fri, 22 Jan 2021 11:17:27 +0000
> > Christoph Hellwig <hch@infradead.org> wrote:
> >
> > > Sorry for not noticing this earlier, but why do you need the bounce
> > > buffer vs just mapping the data buffer?
> >
> > I think I've answered this once already a few iterations before. DMA
> > alignment restrictions, not sure what guarantees the bio buffer has.
>
> Your driver can communicate the required alignment using the
> blk_queue_update_dma_alignment API.

Is it guaranteed to be physically contiguous?

- Lauri
Christoph Hellwig Jan. 22, 2021, 4:39 p.m. UTC | #4
On Fri, Jan 22, 2021 at 06:23:49PM +0200, Lauri Kasanen wrote:
> > Your driver can communicate the required alignment using the
> > blk_queue_update_dma_alignment API.
> 
> Is it guaranteed to be physically contiguous?

The alignment requirement applies to each bio_vec.

What alignment does the hardware require?
Lauri Kasanen Jan. 22, 2021, 4:56 p.m. UTC | #5
On Fri, 22 Jan 2021 16:39:13 +0000
Christoph Hellwig <hch@infradead.org> wrote:

> On Fri, Jan 22, 2021 at 06:23:49PM +0200, Lauri Kasanen wrote:
> > > Your driver can communicate the required alignment using the
> > > blk_queue_update_dma_alignment API.
> >
> > Is it guaranteed to be physically contiguous?
>
> The alignment requirement applies to each bio_vec.
>
> What alignment does the hardware require?

It requires 8-byte alignment, but the buffer must also be physically
contiguous.

I grepped around, but apparently no driver under drivers/block does
direct DMA to the bio buffer. They all use their own buffer and memcpy,
like this patch. ps3vram, ps3disk, amiflop, etc etc.

If all existing drivers use their own buffer, why is a different
approach required for new drivers?

- Lauri
Christoph Hellwig Jan. 22, 2021, 5:08 p.m. UTC | #6
On Fri, Jan 22, 2021 at 06:56:19PM +0200, Lauri Kasanen wrote:
> > What alignment does the hardware require?
> 
> It requires 8-byte alignment, but the buffer must also be physically
> contiguous.

If you want a single contiguous buffer you must use the
blk_queue_max_segments() API to ask for a single contiguous segment.

> I grepped around, but apparently no driver under drivers/block does
> direct DMA to the bio buffer. They all use their own buffer and memcpy,
> like this patch. ps3vram, ps3disk, amiflop, etc etc.
> 
> If all existing drivers use their own buffer, why is a different
> approach required for new drivers?

That selections is a bunch of weirdo legacy drivers.  If you want
to look at something reasonably modern but still simple take a look
at drivers/block/virtio_blk.c.
Keith Busch Jan. 22, 2021, 5:11 p.m. UTC | #7
On Fri, Jan 22, 2021 at 06:56:19PM +0200, Lauri Kasanen wrote:
> On Fri, 22 Jan 2021 16:39:13 +0000
> Christoph Hellwig <hch@infradead.org> wrote:
> 
> > On Fri, Jan 22, 2021 at 06:23:49PM +0200, Lauri Kasanen wrote:
> > > > Your driver can communicate the required alignment using the
> > > > blk_queue_update_dma_alignment API.
> > >
> > > Is it guaranteed to be physically contiguous?
> >
> > The alignment requirement applies to each bio_vec.
> >
> > What alignment does the hardware require?
> 
> It requires 8-byte alignment, but the buffer must also be physically
> contiguous.

Each bio_vec segment is physically contiguous.

> I grepped around, but apparently no driver under drivers/block does
> direct DMA to the bio buffer. They all use their own buffer and memcpy,
> like this patch. ps3vram, ps3disk, amiflop, etc etc.

block/brd.c
 
> If all existing drivers use their own buffer, why is a different
> approach required for new drivers?

It's certainly more efficient to send the data directly to the
destination without an unnecessary middle-man. And 64k seems like quite
a lot of memory to sequestor considering how little you have to start
with.
Lauri Kasanen Jan. 22, 2021, 6:01 p.m. UTC | #8
On Fri, 22 Jan 2021 09:11:20 -0800
Keith Busch <kbusch@kernel.org> wrote:
> Each bio_vec segment is physically contiguous.

What's the official way to get that buffer's physical address? I
couldn't find any driver that does DMA to the bio buffer, to use as an
example. Maybe used wrong keywords.

> > I grepped around, but apparently no driver under drivers/block does
> > direct DMA to the bio buffer. They all use their own buffer and memcpy,
> > like this patch. ps3vram, ps3disk, amiflop, etc etc.
>
> block/brd.c

That uses memcpy, not DMA? It uses the virtual address. I was hoping
for an example that does direct DMA to the bio_data buffer.

- Lauri
Christoph Hellwig Jan. 22, 2021, 6:20 p.m. UTC | #9
On Fri, Jan 22, 2021 at 08:01:17PM +0200, Lauri Kasanen wrote:
> On Fri, 22 Jan 2021 09:11:20 -0800
> Keith Busch <kbusch@kernel.org> wrote:
> > Each bio_vec segment is physically contiguous.
> 
> What's the official way to get that buffer's physical address? I
> couldn't find any driver that does DMA to the bio buffer, to use as an
> example. Maybe used wrong keywords.

In Linux we never DMA to the physical address, but the DMA address.

For a bio with just a single bvec (that is a single contigous segment)
we have the dma_map_bvec helper that is only used by the nvme-pci
driver for special optimizations in the case of a single contigous
segment.
diff mbox series

Patch

diff --git a/drivers/block/Kconfig b/drivers/block/Kconfig
index ecceaaa..924d768 100644
--- a/drivers/block/Kconfig
+++ b/drivers/block/Kconfig
@@ -72,6 +72,12 @@  config AMIGA_Z2RAM
 	  To compile this driver as a module, choose M here: the
 	  module will be called z2ram.

+config N64CART
+	bool "N64 cart support"
+	depends on MACH_NINTENDO64
+	help
+	  Support for the N64 cart.
+
 config CDROM
 	tristate
 	select BLK_SCSI_REQUEST
diff --git a/drivers/block/Makefile b/drivers/block/Makefile
index e1f6311..b9642cf 100644
--- a/drivers/block/Makefile
+++ b/drivers/block/Makefile
@@ -17,6 +17,7 @@  obj-$(CONFIG_PS3_DISK)		+= ps3disk.o
 obj-$(CONFIG_PS3_VRAM)		+= ps3vram.o
 obj-$(CONFIG_ATARI_FLOPPY)	+= ataflop.o
 obj-$(CONFIG_AMIGA_Z2RAM)	+= z2ram.o
+obj-$(CONFIG_N64CART)		+= n64cart.o
 obj-$(CONFIG_BLK_DEV_RAM)	+= brd.o
 obj-$(CONFIG_BLK_DEV_LOOP)	+= loop.o
 obj-$(CONFIG_XILINX_SYSACE)	+= xsysace.o
diff --git a/drivers/block/n64cart.c b/drivers/block/n64cart.c
new file mode 100644
index 0000000..01dd008
--- /dev/null
+++ b/drivers/block/n64cart.c
@@ -0,0 +1,211 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Support for the N64 cart.
+ *
+ * Copyright (c) 2021 Lauri Kasanen
+ */
+
+#include <linux/bitops.h>
+#include <linux/blk-mq.h>
+#include <linux/dma-mapping.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+MODULE_AUTHOR("Lauri Kasanen <cand@gmx.com>");
+MODULE_DESCRIPTION("Driver for the N64 cart");
+MODULE_LICENSE("GPL");
+
+#define BUFSIZE SZ_64K
+
+static unsigned int start, size;
+static struct request_queue *queue;
+static struct blk_mq_tag_set tag_set;
+static struct gendisk *disk;
+
+static void *buf;
+static dma_addr_t dma_addr;
+
+static u32 __iomem *reg_base;
+
+#define PI_DRAM_REG		0
+#define PI_CART_REG		1
+#define PI_READ_REG		2
+#define PI_WRITE_REG		3
+#define PI_STATUS_REG		4
+
+#define PI_STATUS_DMA_BUSY	(1 << 0)
+#define PI_STATUS_IO_BUSY	(1 << 1)
+
+#define CART_DOMAIN		0x10000000
+#define CART_MAX		0x1FFFFFFF
+
+static void n64cart_write_reg(const u8 reg, const u32 value)
+{
+	writel(value, reg_base + reg);
+}
+
+static u32 n64cart_read_reg(const u8 reg)
+{
+	return readl(reg_base + reg);
+}
+
+static void n64cart_wait_dma(void)
+{
+	while (n64cart_read_reg(PI_STATUS_REG) &
+		(PI_STATUS_DMA_BUSY | PI_STATUS_IO_BUSY))
+		cpu_relax();
+}
+
+static blk_status_t n64cart_get_seg(struct request *req)
+{
+	u32 bstart = blk_rq_pos(req) * 512;
+	u32 len = blk_rq_cur_bytes(req);
+	void *dst = bio_data(req->bio);
+
+	if (bstart + len > size)
+		return BLK_STS_IOERR;
+
+	bstart += start;
+
+	while (len) {
+		const u32 curlen = min_t(u32, len, BUFSIZE);
+
+		dma_sync_single_for_device(disk_to_dev(disk), dma_addr,
+					   BUFSIZE, DMA_FROM_DEVICE);
+
+		n64cart_wait_dma();
+
+		n64cart_write_reg(PI_DRAM_REG, dma_addr);
+		n64cart_write_reg(PI_CART_REG,
+				  (bstart | CART_DOMAIN) & CART_MAX);
+		n64cart_write_reg(PI_WRITE_REG, curlen - 1);
+
+		n64cart_wait_dma();
+
+		dma_sync_single_for_cpu(disk_to_dev(disk), dma_addr,
+					BUFSIZE, DMA_FROM_DEVICE);
+
+		memcpy(dst, buf, curlen);
+
+		len -= curlen;
+		dst += curlen;
+		bstart += curlen;
+	}
+
+	return BLK_STS_OK;
+}
+
+static blk_status_t n64cart_queue_rq(struct blk_mq_hw_ctx *hctx,
+				     const struct blk_mq_queue_data *bd)
+{
+	struct request *req = bd->rq;
+	blk_status_t err;
+
+	blk_mq_start_request(req);
+
+	do {
+		err = n64cart_get_seg(req);
+	} while (blk_update_request(req, err, blk_rq_cur_bytes(req)));
+
+	__blk_mq_end_request(req, BLK_STS_OK);
+	return BLK_STS_OK;
+}
+
+static const struct blk_mq_ops n64cart_mq_ops = {
+	.queue_rq = n64cart_queue_rq,
+};
+
+static const struct block_device_operations n64cart_fops = {
+	.owner		= THIS_MODULE,
+};
+
+/*
+ * The target device is embedded and RAM-constrained. We save RAM
+ * by initializing in __init code that gets dropped late in boot.
+ * For the same reason there is no module or unloading support.
+ */
+static int __init n64cart_probe(struct platform_device *pdev)
+{
+	int err;
+
+	if (!start || !size) {
+		pr_err("n64cart: start and size not specified\n");
+		return -ENODEV;
+	}
+
+	if (size & 4095) {
+		pr_err("n64cart: size must be a multiple of 4K\n");
+		return -ENODEV;
+	}
+
+	queue = blk_mq_init_sq_queue(&tag_set, &n64cart_mq_ops, 1,
+				     BLK_MQ_F_SHOULD_MERGE | BLK_MQ_F_BLOCKING);
+	if (IS_ERR(queue)) {
+		return PTR_ERR(queue);
+	}
+
+	buf = dma_alloc_noncoherent(&pdev->dev, BUFSIZE, &dma_addr,
+				    DMA_FROM_DEVICE, GFP_KERNEL);
+	if (!buf) {
+		err = -ENOMEM;
+		goto fail_queue;
+	}
+
+	reg_base = devm_platform_ioremap_resource(pdev, 0);
+	if (!reg_base) {
+		err = -EINVAL;
+		goto fail_dma;
+	}
+
+	disk = alloc_disk(0);
+	if (!disk) {
+		err = -ENOMEM;
+		goto fail_dma;
+	}
+
+	disk->first_minor = 0;
+	disk->queue = queue;
+	disk->flags = GENHD_FL_NO_PART_SCAN | GENHD_FL_EXT_DEVT;
+	disk->fops = &n64cart_fops;
+	strcpy(disk->disk_name, "n64cart");
+
+	set_capacity(disk, size / 512);
+	set_disk_ro(disk, 1);
+
+	blk_queue_flag_set(QUEUE_FLAG_NONROT, queue);
+	blk_queue_physical_block_size(queue, 4096);
+	blk_queue_logical_block_size(queue, 4096);
+
+	add_disk(disk);
+
+	pr_info("n64cart: %u kb disk\n", size / 1024);
+
+	return 0;
+fail_dma:
+	dma_free_noncoherent(&pdev->dev, BUFSIZE, buf, dma_addr,
+			     DMA_FROM_DEVICE);
+fail_queue:
+	blk_cleanup_queue(queue);
+
+	return err;
+}
+
+static struct platform_driver n64cart_driver = {
+	.driver = {
+		.name = "n64cart",
+	},
+};
+
+static int __init n64cart_init(void)
+{
+	return platform_driver_probe(&n64cart_driver, n64cart_probe);
+}
+
+module_param(start, uint, 0);
+MODULE_PARM_DESC(start, "Start address of the cart block data");
+
+module_param(size, uint, 0);
+MODULE_PARM_DESC(size, "Size of the cart block data, in bytes");
+
+module_init(n64cart_init);