Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Linux/drivers/net/ethernet/amazon/ena/   (Open Source Betriebssystem Version 6.17.9©)  Datei vom 24.10.2025 mit Größe 967 B image not shown  

Quellcode-Bibliothek vmu-flash.c   Sprache: unbekannt

 
// 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 (kref_read(&mtd->refcnt))
   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");

Messung V0.5 in Prozent
C=96 H=87 G=91

[0.15QuellennavigatorsProjekt 2026-06-05]