// SPDX-License-Identifier: GPL-2.0-only /* vmu-flash.c * Driver for SEGA Dreamcast Visual Memory Unit * * Copyright (c) Adrian McMenamin 2002 - 2009 * Copyright (c) Paul Mundt 2001 */ #include <linux/init.h> #include <linux/slab.h> #include <linux/sched.h> #include <linux/delay.h> #include <linux/maple.h> #include <linux/mtd/mtd.h> #include <linux/mtd/map.h> struct vmu_cache { unsigned char *buffer; /* Cache */ unsigned int block; /* Which block was cached */ unsigned long jiffies_atc; /* When was it cached? */ int valid; }; struct mdev_part { struct maple_device *mdev; int partition; }; struct vmupart { u16 user_blocks; u16 root_block; u16 numblocks; char *name; struct vmu_cache *pcache; }; struct memcard { u16 tempA; u16 tempB; u32 partitions; u32 blocklen; u32 writecnt; u32 readcnt; u32 removable; int partition; int read; unsigned char *blockread; struct vmupart *parts; struct mtd_info *mtd; }; struct vmu_block { unsigned int num; /* block number */ unsigned int ofs; /* block offset */ }; static struct vmu_block *ofs_to_block(unsigned long src_ofs, struct mtd_info *mtd, int partition) { struct vmu_block *vblock; struct maple_device *mdev; struct memcard *card; struct mdev_part *mpart; int num; mpart = mtd->priv; mdev = mpart->mdev; card = maple_get_drvdata(mdev); if (src_ofs >= card->parts[partition].numblocks * card->blocklen) goto failed; num = src_ofs / card->blocklen; if (num > card->parts[partition].numblocks) goto failed; vblock = kmalloc(sizeof(struct vmu_block), GFP_KERNEL); if (!vblock) goto failed; vblock->num = num; vblock->ofs = src_ofs % card->blocklen; return vblock; failed: return NULL; } /* Maple bus callback function for reads */ static void vmu_blockread(struct mapleq *mq) { struct maple_device *mdev; struct memcard *card; mdev = mq->dev; card = maple_get_drvdata(mdev); /* copy the read in data */ if (unlikely(!card->blockread)) return; memcpy(card->blockread, mq->recvbuf->buf + 12, card->blocklen/card->readcnt); } /* Interface with maple bus to read blocks * caching the results so that other parts * of the driver can access block reads */ static int maple_vmu_read_block(unsigned int num, unsigned char *buf, struct mtd_info *mtd) { struct memcard *card; struct mdev_part *mpart; struct maple_device *mdev; int partition, error = 0, x, wait; unsigned char *blockread = NULL; struct vmu_cache *pcache; __be32 sendbuf; mpart = mtd->priv; mdev = mpart->mdev; partition = mpart->partition; card = maple_get_drvdata(mdev); pcache = card->parts[partition].pcache; pcache->valid = 0; /* prepare the cache for this block */ if (!pcache->buffer) { pcache->buffer = kmalloc(card->blocklen, GFP_KERNEL); if (!pcache->buffer) { dev_err(&mdev->dev, "VMU at (%d, %d) - read fails due" " to lack of memory\n", mdev->port, mdev->unit); error = -ENOMEM; goto outB; } } /* * Reads may be phased - again the hardware spec * supports this - though may not be any devices in * the wild that implement it, but we will here */ for (x = 0; x < card->readcnt; x++) { sendbuf = cpu_to_be32(partition << 24 | x << 16 | num); if (atomic_read(&mdev->busy) == 1) { wait_event_interruptible_timeout(mdev->maple_wait, atomic_read(&mdev->busy) == 0, HZ); if (atomic_read(&mdev->busy) == 1) { dev_notice(&mdev->dev, "VMU at (%d, %d)" " is busy\n", mdev->port, mdev->unit); error = -EAGAIN; goto outB; } } atomic_set(&mdev->busy, 1); blockread = kmalloc(card->blocklen/card->readcnt, GFP_KERNEL); if (!blockread) { error = -ENOMEM; atomic_set(&mdev->busy, 0); goto outB; } card->blockread = blockread; maple_getcond_callback(mdev, vmu_blockread, 0, MAPLE_FUNC_MEMCARD); error = maple_add_packet(mdev, MAPLE_FUNC_MEMCARD, MAPLE_COMMAND_BREAD, 2, &sendbuf); /* Very long timeouts seem to be needed when box is stressed */ wait = wait_event_interruptible_timeout(mdev->maple_wait, (atomic_read(&mdev->busy) == 0 || atomic_read(&mdev->busy) == 2), HZ * 3); /* * MTD layer does not handle hotplugging well * so have to return errors when VMU is unplugged * in the middle of a read (busy == 2) */ if (error || atomic_read(&mdev->busy) == 2) { if (atomic_read(&mdev->busy) == 2) error = -ENXIO; atomic_set(&mdev->busy, 0); card->blockread = NULL; goto outA; } if (wait == 0 || wait == -ERESTARTSYS) { card->blockread = NULL; atomic_set(&mdev->busy, 0); error = -EIO; list_del_init(&(mdev->mq->list)); kfree(mdev->mq->sendbuf); mdev->mq->sendbuf = NULL; if (wait == -ERESTARTSYS) { dev_warn(&mdev->dev, "VMU read on (%d, %d)" " interrupted on block 0x%X\n", mdev->port, mdev->unit, num); } else dev_notice(&mdev->dev, "VMU read on (%d, %d)" " timed out on block 0x%X\n", mdev->port, mdev->unit, num); goto outA; } memcpy(buf + (card->blocklen/card->readcnt) * x, blockread, card->blocklen/card->readcnt); memcpy(pcache->buffer + (card->blocklen/card->readcnt) * x, card->blockread, card->blocklen/card->readcnt); card->blockread = NULL; pcache->block = num; pcache->jiffies_atc = jiffies; pcache->valid = 1; kfree(blockread); } return error; outA: kfree(blockread); outB: return error; } /* communicate with maple bus for phased writing */ static int maple_vmu_write_block(unsigned int num, const unsigned char *buf, struct mtd_info *mtd) { struct memcard *card; struct mdev_part *mpart; struct maple_device *mdev; int partition, error, locking, x, phaselen, wait; __be32 *sendbuf; mpart = mtd->priv; mdev = mpart->mdev; partition = mpart->partition; card = maple_get_drvdata(mdev); phaselen = card->blocklen/card->writecnt; sendbuf = kmalloc(phaselen + 4, GFP_KERNEL); if (!sendbuf) { error = -ENOMEM; goto fail_nosendbuf; } for (x = 0; x < card->writecnt; x++) { sendbuf[0] = cpu_to_be32(partition << 24 | x << 16 | num); memcpy(&sendbuf[1], buf + phaselen * x, phaselen); /* wait until the device is not busy doing something else * or 1 second - which ever is longer */ if (atomic_read(&mdev->busy) == 1) { wait_event_interruptible_timeout(mdev->maple_wait, atomic_read(&mdev->busy) == 0, HZ); if (atomic_read(&mdev->busy) == 1) { error = -EBUSY; dev_notice(&mdev->dev, "VMU write at (%d, %d)" "failed - device is busy\n", mdev->port, mdev->unit); goto fail_nolock; } } atomic_set(&mdev->busy, 1); locking = maple_add_packet(mdev, MAPLE_FUNC_MEMCARD, MAPLE_COMMAND_BWRITE, phaselen / 4 + 2, sendbuf); wait = wait_event_interruptible_timeout(mdev->maple_wait, atomic_read(&mdev->busy) == 0, HZ/10); if (locking) { error = -EIO; atomic_set(&mdev->busy, 0); goto fail_nolock; } if (atomic_read(&mdev->busy) == 2) { atomic_set(&mdev->busy, 0); } else if (wait == 0 || wait == -ERESTARTSYS) { error = -EIO; dev_warn(&mdev->dev, "Write at (%d, %d) of block" " 0x%X at phase %d failed: could not" " communicate with VMU", mdev->port, mdev->unit, num, x); atomic_set(&mdev->busy, 0); kfree(mdev->mq->sendbuf); mdev->mq->sendbuf = NULL; list_del_init(&(mdev->mq->list)); goto fail_nolock; } } kfree(sendbuf); return card->blocklen; fail_nolock: kfree(sendbuf); fail_nosendbuf: dev_err(&mdev->dev, "VMU (%d, %d): write failed\n", mdev->port, mdev->unit); return error; } /* mtd function to simulate reading byte by byte */ static unsigned char vmu_flash_read_char(unsigned long ofs, int *retval, struct mtd_info *mtd) { struct vmu_block *vblock; struct memcard *card; struct mdev_part *mpart; struct maple_device *mdev; unsigned char *buf, ret; int partition, error; mpart = mtd->priv; mdev = mpart->mdev; partition = mpart->partition; card = maple_get_drvdata(mdev); *retval = 0; buf = kmalloc(card->blocklen, GFP_KERNEL); if (!buf) { *retval = 1; ret = -ENOMEM; goto finish; } vblock = ofs_to_block(ofs, mtd, partition); if (!vblock) { *retval = 3; ret = -ENOMEM; goto out_buf; } error = maple_vmu_read_block(vblock->num, buf, mtd); if (error) { ret = error; *retval = 2; goto out_vblock; } ret = buf[vblock->ofs]; out_vblock: kfree(vblock); out_buf: kfree(buf); finish: return ret; } /* mtd higher order function to read flash */ static int vmu_flash_read(struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf) { struct maple_device *mdev; struct memcard *card; struct mdev_part *mpart; struct vmu_cache *pcache; struct vmu_block *vblock; int index = 0, retval, partition, leftover, numblocks; unsigned char cx; mpart = mtd->priv; mdev = mpart->mdev; partition = mpart->partition; card = maple_get_drvdata(mdev); numblocks = card->parts[partition].numblocks; if (from + len > numblocks * card->blocklen) len = numblocks * card->blocklen - from; if (len == 0) return -EIO; /* Have we cached this bit already? */ pcache = card->parts[partition].pcache; do { vblock = ofs_to_block(from + index, mtd, partition); if (!vblock) return -ENOMEM; /* Have we cached this and is the cache valid and timely? */ if (pcache->valid && time_before(jiffies, pcache->jiffies_atc + HZ) && (pcache->block == vblock->num)) { /* we have cached it, so do necessary copying */ leftover = card->blocklen - vblock->ofs; if (vblock->ofs + len - index < card->blocklen) { /* only a bit of this block to copy */ memcpy(buf + index, pcache->buffer + vblock->ofs, len - index); index = len; } else { /* otherwise copy remainder of whole block */ memcpy(buf + index, pcache->buffer + vblock->ofs, leftover); index += leftover; } } else { /* * Not cached so read one byte - * but cache the rest of the block */ cx = vmu_flash_read_char(from + index, &retval, mtd); if (retval) { *retlen = index; kfree(vblock); return cx; } memset(buf + index, cx, 1); index++; } kfree(vblock); } while (len > index); *retlen = index; return 0; } static int vmu_flash_write(struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf) { struct maple_device *mdev; struct memcard *card; struct mdev_part *mpart; int index = 0, partition, error = 0, numblocks; struct vmu_cache *pcache; struct vmu_block *vblock; unsigned char *buffer; mpart = mtd->priv; mdev = mpart->mdev; partition = mpart->partition; card = maple_get_drvdata(mdev); numblocks = card->parts[partition].numblocks; if (to + len > numblocks * card->blocklen) len = numblocks * card->blocklen - to; if (len == 0) { error = -EIO; goto failed; } vblock = ofs_to_block(to, mtd, partition); if (!vblock) { error = -ENOMEM; goto failed; } buffer = kmalloc(card->blocklen, GFP_KERNEL); if (!buffer) { error = -ENOMEM; goto fail_buffer; } do { /* Read in the block we are to write to */ error = maple_vmu_read_block(vblock->num, buffer, mtd); if (error) goto fail_io; do { buffer[vblock->ofs] = buf[index]; vblock->ofs++; index++; if (index >= len) break; } while (vblock->ofs < card->blocklen); /* write out new buffer */ error = maple_vmu_write_block(vblock->num, buffer, mtd); /* invalidate the cache */ pcache = card->parts[partition].pcache; pcache->valid = 0; if (error != card->blocklen) goto fail_io; vblock->num++; vblock->ofs = 0; } while (len > index); kfree(buffer); *retlen = index; kfree(vblock); return 0; fail_io: kfree(buffer); fail_buffer: kfree(vblock); failed: dev_err(&mdev->dev, "VMU write failing with error %d\n", error); return error; } static void vmu_flash_sync(struct mtd_info *mtd) { /* Do nothing here */ } /* Maple bus callback function to recursively query hardware details */ static void vmu_queryblocks(struct mapleq *mq) { struct maple_device *mdev; unsigned short *res; struct memcard *card; __be32 partnum; struct vmu_cache *pcache; struct mdev_part *mpart; struct mtd_info *mtd_cur; struct vmupart *part_cur; int error; mdev = mq->dev; card = maple_get_drvdata(mdev); res = (unsigned short *) (mq->recvbuf->buf); card->tempA = res[12]; card->tempB = res[6]; dev_info(&mdev->dev, "VMU device at partition %d has %d user " "blocks with a root block at %d\n", card->partition, card->tempA, card->tempB); part_cur = &card->parts[card->partition]; part_cur->user_blocks = card->tempA; part_cur->root_block = card->tempB; part_cur->numblocks = card->tempB + 1; part_cur->name = kmalloc(12, GFP_KERNEL); if (!part_cur->name) goto fail_name; sprintf(part_cur->name, "vmu%d.%d.%d", mdev->port, mdev->unit, card->partition); mtd_cur = &card->mtd[card->partition]; mtd_cur->name = part_cur->name; mtd_cur->type = 8; mtd_cur->flags = MTD_WRITEABLE|MTD_NO_ERASE; mtd_cur->size = part_cur->numblocks * card->blocklen; mtd_cur->erasesize = card->blocklen; mtd_cur->_write = vmu_flash_write; mtd_cur->_read = vmu_flash_read; mtd_cur->_sync = vmu_flash_sync; mtd_cur->writesize = card->blocklen; mpart = kmalloc(sizeof(struct mdev_part), GFP_KERNEL); if (!mpart) goto fail_mpart; mpart->mdev = mdev; mpart->partition = card->partition; mtd_cur->priv = mpart; mtd_cur->owner = THIS_MODULE; pcache = kzalloc(sizeof(struct vmu_cache), GFP_KERNEL); if (!pcache) goto fail_cache_create; part_cur->pcache = pcache; error = mtd_device_register(mtd_cur, NULL, 0); if (error) goto fail_mtd_register; maple_getcond_callback(mdev, NULL, 0, MAPLE_FUNC_MEMCARD); /* * Set up a recursive call to the (probably theoretical) * second or more partition */ if (++card->partition < card->partitions) { partnum = cpu_to_be32(card->partition << 24); maple_getcond_callback(mdev, vmu_queryblocks, 0, MAPLE_FUNC_MEMCARD); maple_add_packet(mdev, MAPLE_FUNC_MEMCARD, MAPLE_COMMAND_GETMINFO, 2, &partnum); } return; fail_mtd_register: dev_err(&mdev->dev, "Could not register maple device at (%d, %d)" "error is 0x%X\n", mdev->port, mdev->unit, error); for (error = 0; error <= card->partition; error++) { kfree(((card->parts)[error]).pcache); ((card->parts)[error]).pcache = NULL; } fail_cache_create: fail_mpart: for (error = 0; error <= card->partition; error++) { kfree(((card->mtd)[error]).priv); ((card->mtd)[error]).priv = NULL; } maple_getcond_callback(mdev, NULL, 0, MAPLE_FUNC_MEMCARD); kfree(part_cur->name); fail_name: return; } /* Handles very basic info about the flash, queries for details */ static int vmu_connect(struct maple_device *mdev) { unsigned long test_flash_data, basic_flash_data; int c, error; struct memcard *card; u32 partnum = 0; test_flash_data = be32_to_cpu(mdev->devinfo.function); /* Need to count how many bits are set - to find out which * function_data element has details of the memory card */ c = hweight_long(test_flash_data); basic_flash_data = be32_to_cpu(mdev->devinfo.function_data[c - 1]); card = kmalloc(sizeof(struct memcard), GFP_KERNEL); if (!card) { error = -ENOMEM; goto fail_nomem; } card->partitions = (basic_flash_data >> 24 & 0xFF) + 1; card->blocklen = ((basic_flash_data >> 16 & 0xFF) + 1) << 5; card->writecnt = basic_flash_data >> 12 & 0xF; card->readcnt = basic_flash_data >> 8 & 0xF; card->removable = basic_flash_data >> 7 & 1; card->partition = 0; /* * Not sure there are actually any multi-partition devices in the * real world, but the hardware supports them, so, so will we */ card->parts = kmalloc_array(card->partitions, sizeof(struct vmupart), GFP_KERNEL); if (!card->parts) { error = -ENOMEM; goto fail_partitions; } card->mtd = kmalloc_array(card->partitions, sizeof(struct mtd_info), GFP_KERNEL); if (!card->mtd) { error = -ENOMEM; goto fail_mtd_info; } maple_set_drvdata(mdev, card); /* * We want to trap meminfo not get cond * so set interval to zero, but rely on maple bus * driver to pass back the results of the meminfo */ maple_getcond_callback(mdev, vmu_queryblocks, 0, MAPLE_FUNC_MEMCARD); /* Make sure we are clear to go */ if (atomic_read(&mdev->busy) == 1) { wait_event_interruptible_timeout(mdev->maple_wait, atomic_read(&mdev->busy) == 0, HZ); if (atomic_read(&mdev->busy) == 1) { dev_notice(&mdev->dev, "VMU at (%d, %d) is busy\n", mdev->port, mdev->unit); error = -EAGAIN; goto fail_device_busy; } } atomic_set(&mdev->busy, 1); /* * Set up the minfo call: vmu_queryblocks will handle * the information passed back */ error = maple_add_packet(mdev, MAPLE_FUNC_MEMCARD, MAPLE_COMMAND_GETMINFO, 2, &partnum); if (error) { dev_err(&mdev->dev, "Could not lock VMU at (%d, %d)" " error is 0x%X\n", mdev->port, mdev->unit, error); goto fail_mtd_info; } return 0; fail_device_busy: kfree(card->mtd); fail_mtd_info: kfree(card->parts); fail_partitions: kfree(card); fail_nomem: return error; } static void vmu_disconnect(struct maple_device *mdev) { struct memcard *card; struct mdev_part *mpart; int x; mdev->callback = NULL; card = maple_get_drvdata(mdev); for (x = 0; x < card->partitions; x++) { mpart = ((card->mtd)[x]).priv; mpart->mdev = NULL; mtd_device_unregister(&((card->mtd)[x])); kfree(((card->parts)[x]).name); } kfree(card->parts); kfree(card->mtd); kfree(card); } /* Callback to handle eccentricities of both mtd subsystem * and general flakyness of Dreamcast VMUs */ static int vmu_can_unload(struct maple_device *mdev) { struct memcard *card; int x; struct mtd_info *mtd; card = maple_get_drvdata(mdev); for (x = 0; x < card->partitions; x++) { mtd = &((card->mtd)[x]); if (mtd->usecount > 0) return 0; } return 1; } #define ERRSTR "VMU at (%d, %d) file error -" static void vmu_file_error(struct maple_device *mdev, void *recvbuf) { enum maple_file_errors error = ((int *)recvbuf)[1]; switch (error) { case MAPLE_FILEERR_INVALID_PARTITION: dev_notice(&mdev->dev, ERRSTR " invalid partition number\n", mdev->port, mdev->unit); break; case MAPLE_FILEERR_PHASE_ERROR: dev_notice(&mdev->dev, ERRSTR " phase error\n", mdev->port, mdev->unit); break; case MAPLE_FILEERR_INVALID_BLOCK: dev_notice(&mdev->dev, ERRSTR " invalid block number\n", mdev->port, mdev->unit); break; case MAPLE_FILEERR_WRITE_ERROR: dev_notice(&mdev->dev, ERRSTR " write error\n", mdev->port, mdev->unit); break; case MAPLE_FILEERR_INVALID_WRITE_LENGTH: dev_notice(&mdev->dev, ERRSTR " invalid write length\n", mdev->port, mdev->unit); break; case MAPLE_FILEERR_BAD_CRC: dev_notice(&mdev->dev, ERRSTR " bad CRC\n", mdev->port, mdev->unit); break; default: dev_notice(&mdev->dev, ERRSTR " 0x%X\n", mdev->port, mdev->unit, error); } } static int probe_maple_vmu(struct device *dev) { struct maple_device *mdev = to_maple_dev(dev); struct maple_driver *mdrv = to_maple_driver(dev->driver); mdev->can_unload = vmu_can_unload; mdev->fileerr_handler = vmu_file_error; mdev->driver = mdrv; return vmu_connect(mdev); } static int remove_maple_vmu(struct device *dev) { struct maple_device *mdev = to_maple_dev(dev); vmu_disconnect(mdev); return 0; } static struct maple_driver vmu_flash_driver = { .function = MAPLE_FUNC_MEMCARD, .drv = { .name = "Dreamcast_visual_memory", .probe = probe_maple_vmu, .remove = remove_maple_vmu, }, }; static int __init vmu_flash_map_init(void) { return maple_driver_register(&vmu_flash_driver); } static void __exit vmu_flash_map_exit(void) { maple_driver_unregister(&vmu_flash_driver); } module_init(vmu_flash_map_init); module_exit(vmu_flash_map_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Adrian McMenamin"); MODULE_DESCRIPTION("Flash mapping for Sega Dreamcast visual memory");