Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Add support for VMUFAT

  • Loading branch information...
commit 47305b24487ce6d6d9264bddf2ccf75e5123c1b1 1 parent 0eec698
Adrian McMenamin authored
83 Documentation/filesystems/vmufat.txt
View
@@ -0,0 +1,83 @@
+VMUFAT FILESYSTEM
+
+VMUFAT is the simple file allocation table (FAT) based filesystem used in Sega
+Dreamcast Visual Memory Units (VMUs) and various Dreamcast emulators and
+Android apps etc.
+
+It is not recommended for general use, but does not require a Dreamcast.
+
+All the physical VMU devices that were made were of the same size 256 blocks
+of 512 octets - 128KB in total. But the specification supports a wider range
+of sizes and the filesystem in the Linux kernel is capable of handling volumes
+of a size between 4 blocks and 65536 blocks.
+
+The standard 256 block VMU is described below:
+
+BLOCK NO CONTENT
+0 Space used by Dreamcast
+ to save files
+199
+200 Space which can be used but
+ which the Dreamcast ignores
+240
+241 Directory (can hold 208
+ file records)
+253
+254 File Allocation Table (FAT)
+255 Root Block
+
+The standard VMU filesystem has 241 blocks which can be used for file storage
+but a Dreamcast will only use 200 of these. The Linux kernel driver prefers to
+use blocks 0 - 199 when allocating blocks to files, but will use blocks 200 -
+240 if lower numbered blocks are not available.
+
+An executible file (generally a game written in the native machine code of
+the VMU's microcontroller) must begin at block 0 and be stored linearly in
+the volume.
+
+DIRECTORY
+The directory contains records 32 octets long (format detail below from Marcus
+Comstedt's website - http://mc.pp.se/dc/vms/flashmem.html)
+0x00 : file type (0x00 = no file, 0x33 = data, 0xcc = game)
+0x01 : copy protect (0x00 = copy ok, 0xff = copy protected)
+0x02-0x03 : 16 bits (little endian) : location of first block
+0x04-0x0f : ASCII string : filename (12 characters)
+0x10-0x17 : Binary Coded Decimal timestamp: file creation time
+0x18-0x19 : 16 bits (little endian) : file size (in blocks)
+0x1a-0x1b : 16 bits (little endian) : offset of header (in blocks)
+ from file start
+
+Header positioning is a matter for executible files written in native
+code for a physical VMU (an 8 bit Sanyo microcontroller).
+
+BCD dates are encoded as follows:
+Century prefix (eg., 20)
+Year (eg., 12)
+Month (eg., 02)
+Day in month (eg., 29)
+Hour (eg., 20)
+Minute (eg., 49)
+Second (eg., 22)
+Day of week (Monday is 00, Sunday is 06)
+
+FILE ALLOCATION TABLE
+Every block in the volume is mapped to the FAT eg., block 0 of the VMU maps to
+the first 16 bits of the FAT and so on. Blocks marked 0xFFFC are empty, blocks
+allocated to a file are either numbered with with next block or, if the final
+block are marked 0xFFFA.
+
+ROOT BLOCK
+The first 16 octets are marked as 0x55 - we use this to provide the "magic
+number" for this filesystem. The octets at 0x10 - 0x14 are used to determine
+the colour the representation of VMU is displayed in by a Dreamcast and our
+filesystem does not touch these.
+Octets 0x30 - 0x37 contain the BCD timestamp of the VMU.
+Octets 0x46 - 0x47 contain the location (little endian format) of the FAT
+Octets 0x48 - 0x49 contain the size (little endian) of the FAT
+Octets 0x4A - 0x4B contain the location (little endian) of the Directory
+Octets 0x4C - 0x4D contain the size (little endian) of the Directory
+Octets 0x4E - 0x4F is Dreamcast specific (icon shape)
+Octets 0x50 - 0x51 contain the number (little endian) of user blocks - NB this
+is marked as 200 in a physical VMU although there are in fact 241 usable
+blocks - the reason appears to be that the Directory is not big enough to
+support 1 block files in all user blocks.
1  fs/Kconfig
View
@@ -215,6 +215,7 @@ source "fs/pstore/Kconfig"
source "fs/sysv/Kconfig"
source "fs/ufs/Kconfig"
source "fs/exofs/Kconfig"
+source "fs/vmufat/Kconfig"
endif # MISC_FILESYSTEMS
1  fs/Makefile
View
@@ -124,3 +124,4 @@ obj-$(CONFIG_GFS2_FS) += gfs2/
obj-y += exofs/ # Multiple modules
obj-$(CONFIG_CEPH_FS) += ceph/
obj-$(CONFIG_PSTORE) += pstore/
+obj-$(CONFIG_VMUFAT_FS) += vmufat/
14 fs/vmufat/Kconfig
View
@@ -0,0 +1,14 @@
+config VMUFAT_FS
+ tristate "Dreamcast VMU FAT filesystem"
+ depends on BLOCK
+ help
+ This implements the simple FAT type filesystem found on SEGA
+ Dreamcast visual memory units.
+
+ Dreamcast users who want to mount their VMUs to view the native
+ filesystem will say 'Y' here. The filesystem is hardware independent
+ but is not recommended for any serious use in other circumstances, so
+ just about everyone else should say 'N'.
+
+ To compile this as a module say 'M' here. The module will be called
+ vmufat
7 fs/vmufat/Makefile
View
@@ -0,0 +1,7 @@
+#
+# Makefile for VMUFAT filesystem
+#
+
+obj-$(CONFIG_VMUFAT_FS) += vmufat.o
+
+vmufat-y := inode.o super.o
903 fs/vmufat/inode.c
View
@@ -0,0 +1,903 @@
+/*
+ * VMUFAT file system
+ *
+ * Copyright (C) 2002 - 2012 Adrian McMenamin
+ * Copyright (C) 2002 Paul Mundt
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include <linux/fs.h>
+#include <linux/bcd.h>
+#include <linux/rtc.h>
+#include <linux/slab.h>
+#include <linux/sched.h>
+#include <linux/magic.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/statfs.h>
+#include <linux/buffer_head.h>
+#include "vmufat.h"
+
+const struct inode_operations vmufat_inode_operations;
+const struct file_operations vmufat_file_operations;
+const struct address_space_operations vmufat_address_space_operations;
+const struct file_operations vmufat_file_dir_operations;
+struct kmem_cache *vmufat_blist_cachep;
+/* Linear day numbers of the respective 1sts in non-leap years. */
+int day_n[] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334};
+
+static struct dentry *vmufat_inode_lookup(struct inode *in, struct dentry *dent,
+ struct nameidata *ignored)
+{
+ struct super_block *sb;
+ struct memcard *vmudetails;
+ struct buffer_head *bh = NULL;
+ struct inode *ino;
+ int i, j, error = 0;
+
+ if (dent->d_name.len > VMUFAT_NAMELEN) {
+ error = -ENAMETOOLONG;
+ goto out;
+ }
+ sb = in->i_sb;
+ vmudetails = sb->s_fs_info;
+
+ for (i = vmudetails->dir_bnum;
+ i > vmudetails->dir_bnum - vmudetails->dir_len; i--) {
+ brelse(bh);
+ bh = vmufat_sb_bread(sb, i);
+ if (!bh) {
+ error = -EIO;
+ goto out;
+ }
+ for (j = 0; j < VMU_DIR_ENTRIES_PER_BLOCK; j++) {
+ int record_offset = j * VMU_DIR_RECORD_LEN;
+ if (bh->b_data[record_offset] == 0)
+ goto fail;
+ if (memcmp(dent->d_name.name,
+ bh->b_data + record_offset + VMUFAT_NAME_OFFSET,
+ dent->d_name.len) == 0) {
+ ino = vmufat_get_inode(sb,
+ le16_to_cpu(((u16 *) bh->b_data)
+ [record_offset
+ + VMUFAT_FIRSTBLOCK_OFFSET16]));
+ if (IS_ERR(ino)) {
+ error = PTR_ERR(ino);
+ goto out;
+ } else if (!ino) {
+ error = -EACCES;
+ goto out;
+ }
+ d_add(dent, ino);
+ goto out;
+ }
+ }
+ }
+fail:
+ d_add(dent, NULL); /* Did not find the file */
+out:
+ brelse(bh);
+ return ERR_PTR(error);
+}
+
+static int vmufat_get_freeblock(int start, int end, struct buffer_head *bh)
+{
+ int i, ret = -1;
+ __le16 fatdata;
+
+ for (i = start; i >= end; i--) {
+ fatdata = le16_to_cpu(((u16 *)bh->b_data)[i]);
+ if (fatdata == VMUFAT_UNALLOCATED) {
+ ret = i;
+ break;
+ }
+ }
+ return ret;
+}
+
+/*
+ * Find a block marked free in the FAT
+ */
+static int vmufat_find_free(struct super_block *sb)
+{
+ struct memcard *vmudetails;
+ int testblk, fatblk, ret;
+ struct buffer_head *bh_fat;
+
+ vmudetails = sb->s_fs_info;
+
+ for (fatblk = vmudetails->fat_bnum;
+ fatblk > vmudetails->fat_bnum - vmudetails->fat_len;
+ fatblk--) {
+ bh_fat = vmufat_sb_bread(sb, fatblk);
+ if (!bh_fat) {
+ ret = -EIO;
+ goto fail;
+ }
+
+ /* Handle 256 block VMUs like physical devices
+ * and other VMUs more simply
+ */
+ if (vmudetails->sb_bnum != VMU_BLK_SZ16) {
+ /* Cannot be physical VMU */
+ testblk = vmufat_get_freeblock(VMU_BLK_SZ16, 0, bh_fat);
+ put_bh(bh_fat);
+ if (testblk >= 0)
+ goto out_of_loop;
+ } else { /* Physical VMU or logical VMU with same size */
+ testblk = vmufat_get_freeblock(VMUFAT_START_ALLOC, 0,
+ bh_fat);
+ if (testblk >= 0) {
+ put_bh(bh_fat);
+ goto out_of_loop;
+ }
+ /* Only allocate to higher blocks if no space left */
+ testblk = vmufat_get_freeblock(VMU_BLK_SZ16,
+ VMUFAT_START_ALLOC + 1, bh_fat);
+ put_bh(bh_fat);
+ if (testblk > VMUFAT_START_ALLOC)
+ goto out_of_loop;
+ }
+ }
+ printk(KERN_INFO "VMUFAT: volume is full\n");
+ ret = -ENOSPC;
+ goto fail;
+
+out_of_loop:
+ ret = (fatblk - 1 - vmudetails->fat_bnum + vmudetails->fat_len)
+ * VMU_BLK_SZ16 + testblk;
+fail:
+ return ret;
+}
+
+/* read the FAT for a given block */
+u16 vmufat_get_fat(struct super_block *sb, long block)
+{
+ struct buffer_head *bufhead;
+ int offset;
+ u16 block_content = VMUFAT_ERROR;
+ struct memcard *vmudetails = sb->s_fs_info;
+
+ /* which block in the FAT */
+ offset = block / VMU_BLK_SZ16;
+ if (offset >= vmudetails->fat_len)
+ goto out;
+
+ /* fat_bnum points to highest block in FAT */
+ bufhead = vmufat_sb_bread(sb, offset + 1 +
+ vmudetails->fat_bnum - vmudetails->fat_len);
+ if (!bufhead)
+ goto out;
+ /* look inside the block */
+ block_content = le16_to_cpu(((u16 *)bufhead->b_data)
+ [block % VMU_BLK_SZ16]);
+ put_bh(bufhead);
+out:
+ return block_content;
+}
+
+/* set the FAT for a given block */
+static int vmufat_set_fat(struct super_block *sb, long block,
+ u16 data_to_set)
+{
+ struct buffer_head *bh;
+ int offset, error = 0;
+ struct memcard *vmudetails = sb->s_fs_info;
+
+ offset = block / VMU_BLK_SZ16;
+ if (offset >= vmudetails->fat_len) {
+ error = -EINVAL;
+ goto out;
+ }
+ bh = vmufat_sb_bread(sb, offset + 1 +
+ vmudetails->fat_bnum - vmudetails->fat_len);
+ if (!bh) {
+ error = -EIO;
+ goto out;
+ }
+ ((u16 *) bh->b_data)[block % VMU_BLK_SZ16] = cpu_to_le16(data_to_set);
+ mark_buffer_dirty(bh);
+ put_bh(bh);
+out:
+ return error;
+}
+
+
+static void vmufat_save_bcd_nortc(struct inode *in, char *bh, int index_to_dir)
+{
+ long years, days;
+ unsigned char bcd_century, nl_day, bcd_month;
+ unsigned char u8year;
+ __kernel_time_t unix_date;
+
+ unix_date = in->i_mtime.tv_sec;
+ days = unix_date / SECONDS_PER_DAY;
+ years = days / DAYS_PER_YEAR;
+ /* 1 Jan gets 1 day later after every leap year */
+ if ((years + 3) / 4 + DAYS_PER_YEAR * years >= days)
+ years--;
+ /* rebase days to account for leap years */
+ days -= (years + 3) / 4 + DAYS_PER_YEAR * years;
+ /* 1 Jan is Day 1 */
+ days++;
+ if (days == (FEB28 + 1) && !(years % 4)) {
+ nl_day = days;
+ bcd_month = 2;
+ } else {
+ nl_day = (years % 4) || days <= FEB28 ? days : days - 1;
+ for (bcd_month = 0; bcd_month < 12; bcd_month++)
+ if (day_n[bcd_month] > nl_day)
+ break;
+ }
+
+ bcd_century = 19;
+ /* TODO:accounts for 21st century but will fail in 2100
+ because of leap days */
+ if (years > 29)
+ bcd_century += 1 + (years - 30)/100;
+
+ bh[index_to_dir + VMUFAT_DIR_CENT] = bin2bcd(bcd_century);
+ u8year = years + 70; /* account for epoch */
+ if (u8year > 99)
+ u8year = u8year - 100;
+
+ bh[index_to_dir + VMUFAT_DIR_YEAR] = bin2bcd(u8year);
+ bh[index_to_dir + VMUFAT_DIR_MONTH] = bin2bcd(bcd_month);
+ bh[index_to_dir + VMUFAT_DIR_DAY] =
+ bin2bcd(days - day_n[bcd_month - 1]);
+ bh[index_to_dir + VMUFAT_DIR_HOUR] =
+ bin2bcd((unix_date / SECONDS_PER_HOUR) % HOURS_PER_DAY);
+ bh[index_to_dir + VMUFAT_DIR_MIN] =
+ bin2bcd((unix_date / SIXTY_MINS_OR_SECS)
+ % SIXTY_MINS_OR_SECS);
+ bh[index_to_dir + VMUFAT_DIR_SEC] =
+ bin2bcd(unix_date % SIXTY_MINS_OR_SECS);
+}
+
+static void vmufat_save_bcd_rtc(struct rtc_device *rtc, struct inode *in,
+ char *bh, int index_to_dir)
+{
+ struct rtc_time now;
+
+ if (rtc_read_time(rtc, &now) < 0)
+ vmufat_save_bcd_nortc(in, bh, index_to_dir);
+ bh[index_to_dir + VMUFAT_DIR_CENT] = bin2bcd((char)(now.tm_year/100));
+ bh[index_to_dir + VMUFAT_DIR_YEAR] = bin2bcd((char)(now.tm_year % 100));
+ bh[index_to_dir + VMUFAT_DIR_MONTH] = bin2bcd((char)(now.tm_mon));
+ bh[index_to_dir + VMUFAT_DIR_DAY] = bin2bcd((char)(now.tm_mday));
+ bh[index_to_dir + VMUFAT_DIR_HOUR] = bin2bcd((char)(now.tm_hour));
+ bh[index_to_dir + VMUFAT_DIR_MIN] = bin2bcd((char)(now.tm_min));
+ bh[index_to_dir + VMUFAT_DIR_SEC] = bin2bcd((char)(now.tm_sec));
+ bh[index_to_dir + VMUFAT_DIR_DOW] = bin2bcd((char)(now.tm_wday));
+}
+
+/*
+ * write out the date in bcd format
+ * in the appropriate part of the
+ * directory entry
+ */
+void vmufat_save_bcd(struct inode *in, char *bh, int index_to_dir)
+{
+ struct rtc_device *rtc;
+ rtc = rtc_class_open("rtc0");
+ if (!rtc)
+ vmufat_save_bcd_nortc(in, bh, index_to_dir);
+ else {
+ vmufat_save_bcd_rtc(rtc, in, bh, index_to_dir);
+ rtc_class_close(rtc);
+ }
+}
+
+static int vmufat_allocate_inode(umode_t imode,
+ struct super_block *sb, struct inode *in)
+{
+ int error = 0;
+ /* Executable files must be at the start of the volume */
+ if (imode & EXEC) {
+ in->i_ino = VMUFAT_ZEROBLOCK;
+ if (vmufat_get_fat(sb, 0) != VMUFAT_UNALLOCATED) {
+ printk(KERN_INFO "VMUFAT: cannot write excutable "
+ "file. Volume block 0 already allocated.\n");
+ error = -ENOSPC;
+ goto out;
+ }
+ } else {
+ error = vmufat_find_free(sb);
+ if (error >= 0)
+ in->i_ino = error;
+ }
+out:
+ return error;
+}
+
+static void vmufat_setup_inode(struct inode *in, umode_t imode,
+ struct super_block *sb)
+{
+ in->i_uid = current_fsuid();
+ in->i_gid = current_fsgid();
+ in->i_mtime = in->i_atime = in->i_ctime = CURRENT_TIME;
+ in->i_mode = imode;
+ in->i_blocks = 1;
+ in->i_sb = sb;
+ insert_inode_hash(in);
+ in->i_op = &vmufat_inode_operations;
+ in->i_fop = &vmufat_file_operations;
+ in->i_mapping->a_ops = &vmufat_address_space_operations;
+}
+
+static void vmu_handle_zeroblock(int recno, struct buffer_head *bh, int ino)
+{
+ /* offset and header offset settings */
+ if (ino != VMUFAT_ZEROBLOCK) {
+ ((u16 *) bh->b_data)[recno + VMUFAT_START_OFFSET16] =
+ cpu_to_le16(ino);
+ ((u16 *) bh->b_data)[recno + VMUFAT_HEADER_OFFSET16] = 0;
+ } else {
+ ((u16 *) bh->b_data)[recno + VMUFAT_START_OFFSET16] = 0;
+ ((u16 *) bh->b_data)[recno + VMUFAT_HEADER_OFFSET16] =
+ cpu_to_le16(1);
+ }
+}
+
+static void vmu_write_name(int recno, struct buffer_head *bh, char *name,
+ int len)
+{
+ memset((char *) (bh->b_data + recno + VMUFAT_NAME_OFFSET), '\0',
+ VMUFAT_NAMELEN);
+ memcpy((char *) (bh->b_data + recno + VMUFAT_NAME_OFFSET),
+ name, len);
+}
+
+static int vmufat_inode_create(struct inode *dir, struct dentry *de,
+ umode_t imode, struct nameidata *nd)
+{
+ int i, j, entry, found = 0, error = 0, freeblock;
+ struct inode *inode;
+ struct super_block *sb;
+ struct memcard *vmudetails;
+ struct buffer_head *bh = NULL;
+
+ sb = dir->i_sb;
+ vmudetails = sb->s_fs_info;
+ inode = new_inode(sb);
+ if (!inode) {
+ error = -ENOSPC;
+ goto out;
+ }
+
+ mutex_lock(&vmudetails->mutex);
+ freeblock = vmufat_allocate_inode(imode, sb, inode);
+ if (freeblock < 0) {
+ mutex_unlock(&vmudetails->mutex);
+ error = freeblock;
+ goto clean_inode;
+ }
+ /* mark as single block file - may grow later */
+ error = vmufat_set_fat(sb, freeblock, VMUFAT_FILE_END);
+ mutex_unlock(&vmudetails->mutex);
+ if (error)
+ goto clean_inode;
+
+ vmufat_setup_inode(inode, imode, sb);
+
+ /* Write to the directory
+ * Now search for space for the directory entry */
+ mutex_lock(&vmudetails->mutex);
+ for (i = vmudetails->dir_bnum;
+ i > vmudetails->dir_bnum - vmudetails->dir_len; i--) {
+ brelse(bh);
+ bh = vmufat_sb_bread(sb, i);
+ if (!bh) {
+ mutex_unlock(&vmudetails->mutex);
+ error = -EIO;
+ goto clean_fat;
+ }
+ for (j = 0; j < VMU_DIR_ENTRIES_PER_BLOCK; j++) {
+ entry = j * VMU_DIR_RECORD_LEN;
+ if (((bh->b_data)[entry]) == 0) {
+ found = 1;
+ goto dir_space_found;
+ }
+ }
+ }
+ if (found == 0)
+ goto clean_fat;
+dir_space_found:
+ /* Have the directory entry
+ * so now update it */
+ if (imode & EXEC)
+ bh->b_data[entry] = VMU_GAME;
+ else
+ bh->b_data[entry] = VMU_DATA;
+
+ /* copy protection settings */
+ if (bh->b_data[entry + 1] != (char) NOCOPY)
+ bh->b_data[entry + 1] = (char) CANCOPY;
+
+ vmu_handle_zeroblock(entry / 2, bh, inode->i_ino);
+ vmu_write_name(entry, bh, (char *) de->d_name.name, de->d_name.len);
+
+ /* BCD timestamp it */
+ vmufat_save_bcd(inode, bh->b_data, entry);
+
+ ((u16 *) bh->b_data)[entry / 2 + VMUFAT_SIZE_OFFSET16] =
+ cpu_to_le16(inode->i_blocks);
+ mark_buffer_dirty(bh);
+ brelse(bh);
+ error = vmufat_list_blocks(inode);
+ if (error)
+ goto clean_fat;
+ mutex_unlock(&vmudetails->mutex);
+ d_instantiate(de, inode);
+ return error;
+
+clean_fat:
+ vmufat_set_fat(sb, freeblock, VMUFAT_UNALLOCATED);
+ mutex_unlock(&vmudetails->mutex);
+clean_inode:
+ iput(inode);
+out:
+ if (error < 0)
+ printk(KERN_INFO "VMUFAT: inode creation fails with error"
+ " %i\n", error);
+ return error;
+}
+
+static int vmufat_readdir(struct file *filp, void *dirent, filldir_t filldir)
+{
+ int filenamelen, index, j, k, error = 0;
+ struct vmufat_file_info *saved_file = NULL;
+ struct dentry *dentry;
+ struct inode *inode;
+ struct super_block *sb;
+ struct memcard *vmudetails;
+ struct buffer_head *bh = NULL;
+
+ dentry = filp->f_dentry;
+ inode = dentry->d_inode;
+ sb = inode->i_sb;
+ vmudetails = sb->s_fs_info;
+ index = filp->f_pos;
+ /* handle . for this directory and .. for parent */
+ switch ((unsigned int) filp->f_pos) {
+ case 0:
+ error = filldir(dirent, ".", 1, index++, inode->i_ino, DT_DIR);
+ if (error < 0)
+ goto out;
+ case 1:
+ error = filldir(dirent, "..", 2, index++,
+ dentry->d_parent->d_inode->i_ino, DT_DIR);
+ if (error < 0)
+ goto out;
+ default:
+ break;
+ }
+
+ saved_file =
+ kmalloc(sizeof(struct vmufat_file_info), GFP_KERNEL);
+ if (!saved_file) {
+ error = -ENOMEM;
+ goto out;
+ }
+
+ for (j = vmudetails->dir_bnum -
+ (index - 2) / VMU_DIR_ENTRIES_PER_BLOCK;
+ j > vmudetails->dir_bnum - vmudetails->dir_len; j--) {
+ brelse(bh);
+ bh = vmufat_sb_bread(sb, j);
+ if (!bh) {
+ error = -EIO;
+ goto finish;
+ }
+ for (k = (index - 2) % VMU_DIR_ENTRIES_PER_BLOCK;
+ k < VMU_DIR_ENTRIES_PER_BLOCK; k++) {
+ int pos, pos16;
+ pos = k * VMU_DIR_RECORD_LEN;
+ pos16 = k * VMU_DIR_RECORD_LEN16;
+ saved_file->ftype = bh->b_data[pos];
+ if (saved_file->ftype == 0)
+ goto finish;
+ saved_file->fblk =
+ le16_to_cpu(((u16 *) bh->b_data)[pos16 + 1]);
+ if (saved_file->fblk == 0)
+ saved_file->fblk = VMUFAT_ZEROBLOCK;
+ memcpy(saved_file->fname,
+ bh->b_data + pos + VMUFAT_NAME_OFFSET,
+ VMUFAT_NAMELEN);
+ filenamelen = strlen(saved_file->fname);
+ if (filenamelen > VMUFAT_NAMELEN)
+ filenamelen = VMUFAT_NAMELEN;
+ error = filldir(dirent, saved_file->fname, filenamelen,
+ index++, saved_file->fblk, DT_REG);
+ if (error < 0)
+ goto finish;
+ }
+ }
+
+finish:
+ filp->f_pos = index;
+ kfree(saved_file);
+ brelse(bh);
+out:
+ return error;
+}
+
+
+int vmufat_list_blocks(struct inode *in)
+{
+ struct vmufat_inode *vi;
+ struct super_block *sb;
+ long nextblock;
+ long ino;
+ struct memcard *vmudetails;
+ int error = -EINVAL;
+ struct list_head *iter, *iter2;
+ struct vmufat_block_list *vbl, *nvbl;
+ u16 fatdata;
+
+ vi = VMUFAT_I(in);
+ if (!vi)
+ goto out;
+ sb = in->i_sb;
+ ino = in->i_ino;
+ vmudetails = sb->s_fs_info;
+ error = 0;
+ nextblock = ino;
+ if (nextblock == VMUFAT_ZEROBLOCK)
+ nextblock = 0;
+
+ /* Delete any previous list of blocks */
+ list_for_each_safe(iter, iter2, &vi->blocks.b_list) {
+ vbl = list_entry(iter, struct vmufat_block_list, b_list);
+ list_del(iter);
+ kmem_cache_free(vmufat_blist_cachep, vbl);
+ }
+ vi->nblcks = 0;
+ do {
+ vbl = kmem_cache_alloc(vmufat_blist_cachep,
+ GFP_KERNEL);
+ if (!vbl) {
+ error = -ENOMEM;
+ goto unwind_out;
+ }
+ INIT_LIST_HEAD(&vbl->b_list);
+ vbl->bno = nextblock;
+ list_add_tail(&vbl->b_list, &vi->blocks.b_list);
+ vi->nblcks++;
+
+ /* Find next block in the FAT - if there is one */
+ fatdata = vmufat_get_fat(sb, nextblock);
+ if (fatdata == VMUFAT_UNALLOCATED) {
+ printk(KERN_ERR "VMUFAT: FAT table appears to have"
+ " been corrupted.\n");
+ error = -EIO;
+ goto unwind_out;
+ }
+ if (fatdata == VMUFAT_FILE_END)
+ break;
+ nextblock = fatdata;
+ } while (1);
+out:
+ return error;
+
+unwind_out:
+ list_for_each_entry_safe(vbl, nvbl, &vi->blocks.b_list, b_list) {
+ list_del_init(&vbl->b_list);
+ kmem_cache_free(vmufat_blist_cachep, vbl);
+ }
+ return error;
+}
+
+static int vmufat_clean_fat(struct super_block *sb, int inum)
+{
+ int error = 0;
+ u16 fatword, nextword;
+
+ nextword = inum;
+ do {
+ fatword = vmufat_get_fat(sb, nextword);
+ if (fatword == VMUFAT_ERROR) {
+ error = -EIO;
+ break;
+ }
+ error = vmufat_set_fat(sb, nextword, VMUFAT_UNALLOCATED);
+ if (error)
+ break;
+ if (fatword == VMUFAT_FILE_END)
+ break;
+ nextword = fatword;
+ } while (1);
+
+ return error;
+}
+
+/*
+ * Delete inode by marking space as free in FAT
+ * no need to waste time and effort by actually
+ * wiping underlying data on media
+ */
+static void vmufat_remove_inode(struct inode *in)
+{
+ struct buffer_head *bh = NULL, *bh_old = NULL;
+ struct super_block *sb;
+ struct memcard *vmudetails;
+ int i, j, k, l, startpt, found = 0;
+
+ if (in->i_ino == VMUFAT_ZEROBLOCK)
+ in->i_ino = 0;
+ sb = in->i_sb;
+ vmudetails = sb->s_fs_info;
+ if (in->i_ino > vmudetails->fat_len * sb->s_blocksize / 2) {
+ printk(KERN_WARNING "VMUFAT: attempting to delete"
+ "inode beyond device size");
+ goto ret;
+ }
+
+ mutex_lock(&vmudetails->mutex);
+ if (vmufat_clean_fat(sb, in->i_ino)) {
+ mutex_unlock(&vmudetails->mutex);
+ goto failure;
+ }
+
+ /* Now clean the directory entry
+ * Have to wander through this
+ * to find the appropriate entry */
+ for (i = vmudetails->dir_bnum;
+ i > vmudetails->dir_bnum - vmudetails->dir_len; i--) {
+ brelse(bh);
+ bh = vmufat_sb_bread(sb, i);
+ if (!bh) {
+ mutex_unlock(&vmudetails->mutex);
+ goto failure;
+ }
+ for (j = 0; j < VMU_DIR_ENTRIES_PER_BLOCK; j++) {
+ if (bh->b_data[j * VMU_DIR_RECORD_LEN] == 0) {
+ mutex_unlock(&vmudetails->mutex);
+ goto failure;
+ }
+ if (le16_to_cpu(((u16 *) bh->b_data)
+ [j * VMU_DIR_RECORD_LEN16 +
+ VMUFAT_FIRSTBLOCK_OFFSET16]) == in->i_ino) {
+ found = 1;
+ goto found;
+ }
+ }
+ }
+found:
+ if (found == 0) {
+ mutex_unlock(&vmudetails->mutex);
+ goto failure;
+ }
+
+ /* Found directory entry - so NULL it now */
+ for (k = 0; k < VMU_DIR_RECORD_LEN; k++)
+ bh->b_data[j * VMU_DIR_RECORD_LEN + k] = 0;
+ mark_buffer_dirty(bh);
+ /* Patch up directory, by moving up last file */
+ found = 0;
+ startpt = j + 1;
+ for (l = i; l > vmudetails->dir_bnum - vmudetails->dir_len; l--) {
+ bh_old = vmufat_sb_bread(sb, l);
+ if (!bh_old) {
+ mutex_unlock(&vmudetails->mutex);
+ goto failure;
+ }
+ for (k = startpt; k < VMU_DIR_ENTRIES_PER_BLOCK; k++) {
+ if (bh_old->b_data[k * VMU_DIR_RECORD_LEN] == 0) {
+ found = 1;
+ brelse(bh_old);
+ goto lastdirfound;
+ }
+ }
+ startpt = 0;
+ brelse(bh_old);
+ }
+lastdirfound:
+ if (found == 0) { /* full directory */
+ l = vmudetails->dir_bnum - vmudetails->dir_len + 1;
+ k = VMU_DIR_ENTRIES_PER_BLOCK;
+ } else if (l == i && k == j + 1) /* deleted entry was last in dir */
+ goto finish;
+ else if (k == 0) {
+ l = l + 1;
+ k = VMU_DIR_ENTRIES_PER_BLOCK;
+ if (l == i && k == j + 1)
+ goto finish;
+ }
+ /* fill gap first then wipe out old entry */
+ bh_old = vmufat_sb_bread(sb, l);
+ if (!bh_old) {
+ mutex_unlock(&vmudetails->mutex);
+ brelse(bh);
+ goto failure;
+ }
+ for (i = 0; i < VMU_DIR_RECORD_LEN; i++) {
+ bh->b_data[j * VMU_DIR_RECORD_LEN + i] =
+ bh_old->b_data[(k - 1) * VMU_DIR_RECORD_LEN + i];
+ bh_old->b_data[(k - 1) * VMU_DIR_RECORD_LEN + i] = 0;
+ }
+ mark_buffer_dirty(bh_old);
+ mark_buffer_dirty(bh);
+ brelse(bh_old);
+
+finish:
+ mutex_unlock(&vmudetails->mutex);
+ brelse(bh);
+ return;
+
+failure:
+ printk(KERN_WARNING "VMUFAT: Failure to read volume,"
+ " could not delete inode - filesystem may be damaged\n");
+ret:
+ return;
+}
+
+/*
+ * vmufat_unlink - delete a file pointed to
+ * by the dentry (only one directory in a
+ * vmufat fs so safe to ignore the inode
+ * supplied here
+ */
+static int vmufat_unlink(struct inode *dir, struct dentry *dentry)
+{
+ struct inode *in;
+ in = dentry->d_inode;
+ vmufat_remove_inode(in);
+ return 0;
+}
+
+static int vmufat_get_block(struct inode *inode, sector_t iblock,
+ struct buffer_head *bh_result, int create)
+{
+ struct vmufat_inode *vin;
+ struct vmufat_block_list *vlist, *vblk;
+ struct super_block *sb;
+ struct memcard *vmudetails;
+ int cural;
+ int finblk, nxtblk, exeblk;
+ struct list_head *iter;
+ sector_t cntdwn = iblock;
+ sector_t phys;
+ int error = -EINVAL;
+
+ vin = VMUFAT_I(inode);
+ if (!vin || !(&vin->blocks) || vin->nblcks <= 0)
+ goto out;
+ vlist = &vin->blocks;
+ sb = inode->i_sb;
+ vmudetails = sb->s_fs_info;
+
+ if (iblock < vin->nblcks) {
+ /* block is already here so read it into the buffer head */
+ list_for_each(iter, &vlist->b_list) {
+ if (cntdwn-- == 0)
+ break;
+ }
+ vblk = list_entry(iter, struct vmufat_block_list, b_list);
+ clear_buffer_new(bh_result);
+ error = 0;
+ phys = vblk->bno;
+ goto got_it;
+ }
+ if (!create)
+ goto out;
+ /*
+ * check not looking for a block too far
+ * beyond the end of the existing file
+ */
+ if (iblock > vin->nblcks)
+ goto out;
+
+ /* if looking for a block that is not current - allocate it */
+ cural = vin->nblcks;
+ list_for_each(iter, &vlist->b_list) {
+ if (cural-- == 1)
+ break;
+ }
+ vblk = list_entry(iter, struct vmufat_block_list, b_list);
+ finblk = vblk->bno;
+
+ mutex_lock(&vmudetails->mutex);
+ /* Exec files have to be linear */
+ if (inode->i_ino == 0) {
+ exeblk = vmufat_get_fat(sb, finblk + 1);
+ if (exeblk != VMUFAT_UNALLOCATED) {
+ mutex_unlock(&vmudetails->mutex);
+ printk(KERN_WARNING "VMUFAT: Cannot allocate linear "
+ "space needed for executible\n");
+ error = -ENOSPC;
+ goto out;
+ }
+ nxtblk = finblk + 1;
+ } else {
+ nxtblk = vmufat_find_free(sb);
+ if (nxtblk < 0) {
+ mutex_unlock(&vmudetails->mutex);
+ error = nxtblk;
+ goto out;
+ }
+ }
+ error = vmufat_set_fat(sb, finblk, nxtblk);
+ if (error) {
+ mutex_unlock(&vmudetails->mutex);
+ goto out;
+ }
+ error = vmufat_set_fat(sb, nxtblk, VMUFAT_FILE_END);
+ mutex_unlock(&vmudetails->mutex);
+ if (error)
+ goto out;
+ error = vmufat_list_blocks(inode);
+ mark_inode_dirty(inode);
+ if (error)
+ goto out;
+ set_buffer_new(bh_result);
+ phys = nxtblk;
+ error = 0;
+got_it:
+ map_bh(bh_result, sb, phys);
+out:
+ return error;
+}
+
+static int vmufat_writepage(struct page *page, struct writeback_control *wbc)
+{
+ return block_write_full_page(page, vmufat_get_block, wbc);
+}
+
+static int vmufat_write_begin(struct file *filp, struct address_space *mapping,
+ loff_t pos, unsigned len, unsigned flags,
+ struct page **pagep, void **fsdata)
+{
+ *pagep = NULL;
+ return block_write_begin(mapping, pos, len, flags, pagep,
+ vmufat_get_block);
+}
+
+static int vmufat_readpage(struct file *file, struct page *page)
+{
+ return block_read_full_page(page, vmufat_get_block);
+}
+
+
+const struct address_space_operations
+ vmufat_address_space_operations = {
+ .readpage = vmufat_readpage,
+ .writepage = vmufat_writepage,
+ .write_begin = vmufat_write_begin,
+ .write_end = generic_write_end,
+};
+
+const struct inode_operations vmufat_inode_operations = {
+ .lookup = vmufat_inode_lookup,
+ .create = vmufat_inode_create,
+ .unlink = vmufat_unlink,
+};
+
+const struct file_operations vmufat_file_dir_operations = {
+ .owner = THIS_MODULE,
+ .read = generic_read_dir,
+ .readdir = vmufat_readdir,
+ .fsync = generic_file_fsync,
+};
+
+const struct file_operations vmufat_file_operations = {
+ .llseek = generic_file_llseek,
+ .read = do_sync_read,
+ .write = do_sync_write,
+ .aio_read = generic_file_aio_read,
+ .aio_write = generic_file_aio_write,
+ .fsync = generic_file_fsync,
+};
522 fs/vmufat/super.c
View
@@ -0,0 +1,522 @@
+/*
+ * VMUFAT file system
+ *
+ * Copyright (C) 2002 - 2012 Adrian McMenamin
+ * Copyright (C) 2002 Paul Mundt
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include <linux/fs.h>
+#include <linux/bcd.h>
+#include <linux/rtc.h>
+#include <linux/slab.h>
+#include <linux/sched.h>
+#include <linux/magic.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/statfs.h>
+#include <linux/buffer_head.h>
+#include "vmufat.h"
+
+static struct kmem_cache *vmufat_inode_cachep;
+static const struct super_operations vmufat_super_operations;
+extern int *day_n;
+extern struct kmem_cache *vmufat_blist_cachep;
+extern const struct inode_operations vmufat_inode_operations;
+extern const struct file_operations vmufat_file_operations;
+extern const struct address_space_operations vmufat_address_space_operations;
+extern const struct file_operations vmufat_file_dir_operations;
+
+static long vmufat_get_date(struct buffer_head *bh, int offset)
+{
+ int century, year, month, day, hour, minute, second;
+ century = bcd2bin(bh->b_data[offset++]);
+ year = bcd2bin(bh->b_data[offset++]);
+ month = bcd2bin(bh->b_data[offset++]);
+ day = bcd2bin(bh->b_data[offset++]);
+ hour = bcd2bin(bh->b_data[offset++]);
+ minute = bcd2bin(bh->b_data[offset++]);
+ second = bcd2bin(bh->b_data[offset]);
+
+ return mktime(century * 100 + year, month, day, hour, minute,
+ second);
+}
+
+static struct inode *vmufat_alloc_inode(struct super_block *sb)
+{
+ struct vmufat_inode *vi = kmem_cache_alloc(vmufat_inode_cachep,
+ GFP_KERNEL);
+ if (!vi)
+ return NULL;
+
+ INIT_LIST_HEAD(&vi->blocks.b_list);
+ return &vi->vfs_inode;
+}
+
+static void vmufat_destroy_inode(struct inode *in)
+{
+ struct vmufat_inode *vi;
+ struct vmufat_block_list *vb;
+ struct list_head *iter, *iter2;
+ vi = VMUFAT_I(in);
+ if (!vi)
+ return;
+
+ list_for_each_safe(iter, iter2, &vi->blocks.b_list) {
+ vb = list_entry(iter, struct vmufat_block_list, b_list);
+ list_del(iter);
+ kmem_cache_free(vmufat_blist_cachep, vb);
+ }
+ kmem_cache_free(vmufat_inode_cachep, vi);
+}
+
+struct inode *vmufat_get_inode(struct super_block *sb, long ino)
+{
+ struct buffer_head *bh = NULL;
+ int error = 0, i, j, found = 0;
+ int offsetindir;
+ struct inode *inode;
+ struct memcard *vmudetails;
+ long superblock_bno;
+
+ vmudetails = sb->s_fs_info;
+ inode = iget_locked(sb, ino);
+ if (!inode) {
+ error = -EIO;
+ goto reterror;
+ }
+ superblock_bno = vmudetails->sb_bnum;
+
+ if (inode->i_state & I_NEW) {
+ inode->i_uid = current_fsuid();
+ inode->i_gid = current_fsgid();
+ inode->i_mode &= ~S_IFMT;
+ if (inode->i_ino == superblock_bno) {
+ bh = vmufat_sb_bread(sb, inode->i_ino);
+ if (!bh) {
+ error = -EIO;
+ goto failed;
+ }
+ inode->i_ctime.tv_sec = inode->i_mtime.tv_sec =
+ vmufat_get_date(bh, VMUFAT_SB_DATEOFFSET);
+
+ /* Mark as a directory */
+ inode->i_mode = S_IFDIR | S_IRUGO | S_IXUGO;
+ inode->i_op = &vmufat_inode_operations;
+ inode->i_fop = &vmufat_file_dir_operations;
+ } else {
+ /* Mark file as regular type */
+ inode->i_mode = S_IFREG | S_IRUGO | S_IWUSR;
+
+ /* Scan through the directory to find matching file */
+ for (i = vmudetails->dir_bnum;
+ i > vmudetails->dir_bnum - vmudetails->dir_len;
+ i--) {
+ brelse(bh);
+ bh = vmufat_sb_bread(sb, i);
+ if (!bh) {
+ error = -EIO;
+ goto failed;
+ }
+ for (j = 0; j < VMU_DIR_ENTRIES_PER_BLOCK; j++)
+ {
+ if (bh->b_data[j * VMU_DIR_RECORD_LEN]
+ == 0)
+ goto notfound;
+ if (le16_to_cpu(((u16 *) bh->b_data)
+ [j * VMU_DIR_RECORD_LEN16 +
+ VMUFAT_FIRSTBLOCK_OFFSET16]) == ino) {
+ found = 1;
+ goto found;
+ }
+ }
+ }
+notfound:
+ error = -ENOENT;
+ goto failed;
+found:
+ /* identified the correct directory entry */
+ offsetindir = j * VMU_DIR_RECORD_LEN;
+ inode->i_ctime.tv_sec = inode->i_mtime.tv_sec =
+ vmufat_get_date(bh,
+ offsetindir + VMUFAT_FILE_DATEOFFSET);
+
+ /* Execute if a game, write if not copy protected */
+ inode->i_mode &= ~(S_IWUGO | S_IXUGO);
+ inode->i_mode |= S_IRUGO;
+
+ /* Mode - is it write protected? */
+ if ((((u8 *) bh->b_data)[0x01 + offsetindir] ==
+ 0x00) & ~(sb->s_flags & MS_RDONLY))
+ inode->i_mode |= S_IWUSR;
+ /* Is file executible - ie a game */
+ if ((((u8 *) bh->b_data)[offsetindir] ==
+ 0xcc) & ~(sb->s_flags & MS_NOEXEC))
+ inode->i_mode |= S_IXUSR;
+
+ inode->i_fop = &vmufat_file_operations;
+
+ inode->i_blocks =
+ le16_to_cpu(((u16 *) bh->b_data)
+ [offsetindir / 2 + 0x0C]);
+ inode->i_size = inode->i_blocks * sb->s_blocksize;
+
+ inode->i_mapping->a_ops =
+ &vmufat_address_space_operations;
+ inode->i_op = &vmufat_inode_operations;
+ inode->i_fop = &vmufat_file_operations;
+ error = vmufat_list_blocks(inode);
+ if (error)
+ goto failed;
+ }
+ inode->i_atime = CURRENT_TIME;
+ unlock_new_inode(inode);
+ }
+ brelse(bh);
+ return inode;
+
+failed:
+ iget_failed(inode);
+reterror:
+ brelse(bh);
+ return ERR_PTR(error);
+}
+
+static void vmufat_put_super(struct super_block *sb)
+{
+ sb->s_dev = 0;
+ kfree(sb->s_fs_info);
+}
+
+static int vmufat_count_freeblocks(struct super_block *sb,
+ struct kstatfs *kstatbuf)
+{
+ int i, free = 0, error = 0;
+ struct memcard *vmudetails = sb->s_fs_info;
+
+ /* Look through the FAT */
+ for (i = 0; i < vmudetails->numblocks; i++) {
+ if (vmufat_get_fat(sb, i) == VMUFAT_UNALLOCATED)
+ free++;
+ }
+ kstatbuf->f_bfree = free;
+ kstatbuf->f_bavail = free;
+ kstatbuf->f_blocks = vmudetails->numblocks;
+ return error;
+}
+
+static int vmufat_statfs(struct dentry *dentry, struct kstatfs *kstatbuf)
+{
+ int error;
+ struct super_block *sb;
+
+ sb = dentry->d_sb;
+ error = vmufat_count_freeblocks(sb, kstatbuf);
+ if (error)
+ goto out;
+ kstatbuf->f_type = VMUFAT_MAGIC;
+ kstatbuf->f_bsize = sb->s_blocksize;
+ kstatbuf->f_namelen = VMUFAT_NAMELEN;
+out:
+ return error;
+}
+
+/* Remove inode from memory */
+static void vmufat_evict_inode(struct inode *in)
+{
+ truncate_inode_pages(&in->i_data, 0);
+ invalidate_inode_buffers(in);
+ in->i_size = 0;
+ end_writeback(in);
+}
+
+/*
+ * There are no inodes on the medium - vmufat_write_inode
+ * updates the directory entry
+ */
+static int vmufat_write_inode(struct inode *in, struct writeback_control *wbc)
+{
+ struct buffer_head *bh = NULL;
+ unsigned long inode_num;
+ int i, j, found = 0, error = 0;
+ int pos, pos16;
+ struct super_block *sb;
+ struct memcard *vmudetails;
+ sb = in->i_sb;
+ vmudetails = sb->s_fs_info;
+
+ /* As most real world devices are flash we
+ * won't update the superblock every time */
+ if (in->i_ino == vmudetails->sb_bnum)
+ goto out;
+ if (in->i_ino == VMUFAT_ZEROBLOCK)
+ inode_num = 0;
+ else
+ inode_num = in->i_ino;
+
+ /* update the directory and inode details */
+ /* Now search for the directory entry */
+ mutex_lock(&vmudetails->mutex);
+ for (i = vmudetails->dir_bnum;
+ i > vmudetails->dir_bnum - vmudetails->dir_len; i--) {
+ bh = vmufat_sb_bread(sb, i);
+ if (!bh) {
+ mutex_unlock(&vmudetails->mutex);
+ error = -EIO;
+ goto out;
+ }
+ for (j = 0; j < VMU_DIR_ENTRIES_PER_BLOCK; j++) {
+ pos = j * VMU_DIR_RECORD_LEN;
+ pos16 = j * VMU_DIR_RECORD_LEN16;
+ if (bh->b_data[pos] == 0) {
+ mutex_unlock(&vmudetails->mutex);
+ brelse(bh);
+ error = -ENOENT;
+ goto out;
+ }
+ if (le16_to_cpu(((u16 *)bh->b_data)
+ [pos16 + VMUFAT_FIRSTBLOCK_OFFSET16])
+ == inode_num) {
+ found = 1;
+ goto found;
+ }
+ }
+ brelse(bh);
+ }
+found:
+ if (found == 0) {
+ mutex_unlock(&vmudetails->mutex);
+ error = -EIO;
+ goto out;
+ }
+
+ /* Have the directory entry
+ * so now update it */
+ if (inode_num != 0)
+ bh->b_data[pos] = VMU_DATA;
+ else
+ bh->b_data[pos] = VMU_GAME;
+ if (bh->b_data[pos + 1] != 0 && bh->b_data[pos + 1] != (char) 0xff)
+ bh->b_data[pos + 1] = 0;
+ ((u16 *) bh->b_data)[pos16 + 1] = cpu_to_le16(inode_num);
+
+ /* BCD timestamp it */
+ in->i_mtime = CURRENT_TIME;
+ vmufat_save_bcd(in, bh->b_data, pos);
+
+ ((u16 *) bh->b_data)[pos16 + VMUFAT_SIZE_OFFSET16] =
+ cpu_to_le16(in->i_blocks);
+ if (inode_num != 0)
+ ((u16 *) bh->b_data)[pos16 + VMUFAT_HEADER_OFFSET16] = 0;
+ else /* game */
+ ((u16 *) bh->b_data)[pos16 + VMUFAT_HEADER_OFFSET16]
+ = cpu_to_le16(1);
+ mutex_unlock(&vmudetails->mutex);
+ mark_buffer_dirty(bh);
+ brelse(bh);
+out:
+ return error;
+}
+
+static int check_sb_format(struct buffer_head *bh)
+{
+ return (((u32 *) bh->b_data)[0] == VMUFAT_MAGIC &&
+ ((u32 *) bh->b_data)[1] == VMUFAT_MAGIC &&
+ ((u32 *) bh->b_data)[2] == VMUFAT_MAGIC &&
+ ((u32 *) bh->b_data)[3] == VMUFAT_MAGIC);
+}
+
+static void vmufat_populate_vmudata(struct memcard *vmudata,
+ struct buffer_head *bh, int test_sz)
+{
+ vmudata->sb_bnum = test_sz - 1;
+ vmudata->fat_bnum =
+ le16_to_cpu(((u16 *) bh->b_data)[VMU_LOCATION_FAT]);
+ vmudata->fat_len =
+ le16_to_cpu(((u16 *) bh->b_data)[VMU_LOCATION_FATLEN]);
+ vmudata->dir_bnum =
+ le16_to_cpu(((u16 *) bh->b_data)[VMU_LOCATION_DIR]);
+ vmudata->dir_len =
+ le16_to_cpu(((u16 *) bh->b_data)[VMU_LOCATION_DIRLEN]);
+ /* return the true number of user available blocks - physical VMUs
+ * return a neat 200 and ignore 40 blocks of usable space -
+ * we get round that in a hardware neutral way */
+ vmudata->numblocks = vmudata->dir_bnum - vmudata->dir_len + 1;
+}
+
+static int vmufat_get_size(struct super_block *sb, struct buffer_head **bh)
+{
+ int i;
+
+ for (i = VMUFAT_MIN_BLK; i <= VMUFAT_MAX_BLK; i = i * 2) {
+ brelse(*bh);
+ *bh = vmufat_sb_bread(sb, i - 1);
+ if (*bh == NULL) {
+ i = -EIO;
+ goto out;
+ }
+ if (check_sb_format(*bh))
+ break;
+ }
+ if (i > VMUFAT_MAX_BLK) {
+ brelse(*bh);
+ i = -ENOENT;
+ }
+out:
+ return i;
+}
+
+static int vmufat_fill_super(struct super_block *sb,
+ void *data, int silent)
+{
+ struct buffer_head *bh = NULL;
+ struct memcard *vmudata;
+ int test_sz;
+ struct inode *root_i;
+ int ret = 0;
+
+ sb_set_blocksize(sb, VMU_BLK_SZ);
+ sb->s_blocksize_bits = ilog2(VMU_BLK_SZ);
+ sb->s_magic = VMUFAT_MAGIC;
+ sb->s_op = &vmufat_super_operations;
+
+ /*
+ * Hardware VMUs are 256 blocks in size but
+ * the specification allows for other sizes
+ */
+ test_sz = vmufat_get_size(sb, &bh);
+ if (test_sz < VMUFAT_MIN_BLK) {
+ printk(KERN_ERR "VMUFAT: attempted to mount corrupted vmufat "
+ "or non-vmufat violume as vmufat.\n");
+ ret = test_sz;
+ goto out;
+ }
+
+ vmudata = kmalloc(sizeof(struct memcard), GFP_KERNEL);
+ if (!vmudata) {
+ ret = -ENOMEM;
+ goto freebh_out;
+ }
+ vmufat_populate_vmudata(vmudata, bh, test_sz);
+ mutex_init(&vmudata->mutex);
+ sb->s_fs_info = vmudata;
+
+ root_i = vmufat_get_inode(sb, vmudata->sb_bnum);
+ if (!root_i) {
+ printk(KERN_ERR "VMUFAT: get root inode failed\n");
+ ret = -ENOMEM;
+ goto freevmudata_out;
+ }
+ if (IS_ERR(root_i)) {
+ printk(KERN_ERR "VMUFAT: get root"
+ " inode failed - error 0x%lX\n",
+ PTR_ERR(root_i));
+ ret = PTR_ERR(root_i);
+ goto freevmudata_out;
+ }
+
+ sb->s_root = d_alloc_root(root_i);
+ if (!sb->s_root) {
+ ret = -EIO;
+ goto freeroot_out;
+ }
+ goto out;
+
+freeroot_out:
+ iput(root_i);
+freevmudata_out:
+ kfree(vmudata);
+freebh_out:
+ brelse(bh);
+out:
+ return ret;
+}
+
+static void init_once(void *foo)
+{
+ struct vmufat_inode *vi = foo;
+
+ vi->nblcks = 0;
+ inode_init_once(&vi->vfs_inode);
+}
+
+static int init_inodecache(void)
+{
+ vmufat_inode_cachep = kmem_cache_create("vmufat_inode_cache",
+ sizeof(struct vmufat_inode), 0,
+ SLAB_RECLAIM_ACCOUNT|SLAB_MEM_SPREAD, init_once);
+ if (!vmufat_inode_cachep)
+ return -ENOMEM;
+
+ vmufat_blist_cachep = kmem_cache_create("vmufat_blocklist_cache",
+ sizeof(struct vmufat_block_list), 0, SLAB_MEM_SPREAD, NULL);
+ if (!vmufat_blist_cachep) {
+ kmem_cache_destroy(vmufat_inode_cachep);
+ return -ENOMEM;
+ }
+ return 0;
+}
+
+static void destroy_inodecache(void)
+{
+ kmem_cache_destroy(vmufat_blist_cachep);
+ kmem_cache_destroy(vmufat_inode_cachep);
+}
+
+static const struct super_operations vmufat_super_operations = {
+ .alloc_inode = vmufat_alloc_inode,
+ .destroy_inode = vmufat_destroy_inode,
+ .write_inode = vmufat_write_inode,
+ .evict_inode = vmufat_evict_inode,
+ .put_super = vmufat_put_super,
+ .statfs = vmufat_statfs,
+};
+
+static struct dentry *vmufat_mount(struct file_system_type *fs_type,
+ int flags, const char *dev_name, void *data)
+{
+ return mount_bdev(fs_type, flags, dev_name, data, vmufat_fill_super);
+}
+
+static struct file_system_type vmufat_fs_type = {
+ .owner = THIS_MODULE,
+ .name = "vmufat",
+ .mount = vmufat_mount,
+ .kill_sb = kill_block_super,
+ .fs_flags = FS_REQUIRES_DEV,
+};
+
+static int __init init_vmufat_fs(void)
+{
+ int err;
+ err = init_inodecache();
+ if (err)
+ return err;
+ return register_filesystem(&vmufat_fs_type);
+}
+
+static void __exit exit_vmufat_fs(void)
+{
+ destroy_inodecache();
+ unregister_filesystem(&vmufat_fs_type);
+}
+
+module_init(init_vmufat_fs);
+module_exit(exit_vmufat_fs);
+
+MODULE_DESCRIPTION("Filesystem used in Sega Dreamcast VMU");
+MODULE_AUTHOR("Adrian McMenamin <adrianmcmenamin@gmail.com>");
+MODULE_LICENSE("GPL");
115 fs/vmufat/vmufat.h
View
@@ -0,0 +1,115 @@
+/* Header file for VMUFAT filesystem */
+#ifndef _VMUFAT_H_
+#define _VMUFAT_H_
+
+/* maximum length of file name */
+#define VMUFAT_NAMELEN 12
+
+/* GNU utils won't list files with inode num 0 */
+#define VMUFAT_ZEROBLOCK 0x10000
+#define VMU_BLK_SZ 512
+#define VMU_BLK_SZ16 256
+
+/* file allocation table markers */
+#define VMUFAT_FILE_END 0xFFFA
+#define VMUFAT_UNALLOCATED 0xFFFC
+#define VMUFAT_ERROR 0xFFFF
+
+/* parameters for possible VMU volume sizes */
+#define VMUFAT_MIN_BLK 0x04
+#define VMUFAT_MAX_BLK 0x10000
+#define VMUFAT_START_ALLOC 199
+
+/* specifcations for directory entries */
+#define VMU_DIR_RECORD_LEN 0x20
+#define VMU_DIR_RECORD_LEN16 0x10
+#define VMU_DIR_ENTRIES_PER_BLOCK 0x10
+#define VMUFAT_NAME_OFFSET 0x04
+#define VMUFAT_FIRSTBLOCK_OFFSET16 0x01
+#define VMUFAT_START_OFFSET16 0x01
+#define VMUFAT_SIZE_OFFSET16 0x0C
+#define VMUFAT_HEADER_OFFSET16 0x0D
+
+/* File types used in directory */
+#define VMU_GAME 0xCC
+#define VMU_DATA 0x33
+
+/* filesystem locations marked in the root block */
+#define VMU_LOCATION_FAT 0x23
+#define VMU_LOCATION_FATLEN 0x24
+#define VMU_LOCATION_DIR 0x25
+#define VMU_LOCATION_DIRLEN 0x26
+#define VMU_LOCATION_USRLEN 0x28 /* reports false figure */
+
+/* date offsets */
+#define VMUFAT_SB_DATEOFFSET 0x30
+#define VMUFAT_FILE_DATEOFFSET 0x10
+
+#define EXEC 0111
+#define NOCOPY 0xFF
+#define CANCOPY 0x00
+
+u16 vmufat_get_fat(struct super_block *sb, long block);
+void vmufat_save_bcd(struct inode *in, char *bh, int index_to_dir);
+struct inode *vmufat_get_inode(struct super_block *sb, long ino);
+int vmufat_list_blocks(struct inode *in);
+
+enum vmufat_date {
+ VMUFAT_DIR_CENT = 0x10,
+ VMUFAT_DIR_YEAR,
+ VMUFAT_DIR_MONTH,
+ VMUFAT_DIR_DAY,
+ VMUFAT_DIR_HOUR,
+ VMUFAT_DIR_MIN,
+ VMUFAT_DIR_SEC,
+ VMUFAT_DIR_DOW
+};
+
+/* constants for BCD conversion */
+#define SECONDS_PER_DAY 86400
+#define DAYS_PER_YEAR 365
+#define SECONDS_PER_HOUR 3600
+#define HOURS_PER_DAY 24
+#define SIXTY_MINS_OR_SECS 60
+#define FEB28 59
+
+struct memcard {
+ unsigned int sb_bnum;
+ unsigned int fat_bnum;
+ unsigned int fat_len;
+ unsigned int dir_bnum;
+ unsigned int dir_len;
+ unsigned int numblocks;
+ struct mutex mutex;
+};
+
+struct vmufat_block_list {
+ struct list_head b_list;
+ int bno;
+};
+
+struct vmufat_inode {
+ struct vmufat_block_list blocks;
+ unsigned int nblcks;
+ struct inode vfs_inode;
+};
+
+static inline struct vmufat_inode *VMUFAT_I(struct inode *in)
+{
+ return container_of(in, struct vmufat_inode, vfs_inode);
+}
+
+struct vmufat_file_info {
+ u8 ftype;
+ int fblk;
+ char fname[VMUFAT_NAMELEN];
+};
+
+static inline struct buffer_head *vmufat_sb_bread(struct super_block *sb,
+ sector_t block)
+{
+ if (!sb)
+ return NULL;
+ return sb_bread(sb, block);
+}
+#endif
1  include/linux/magic.h
View
@@ -29,6 +29,7 @@
#define JFFS2_SUPER_MAGIC 0x72b6
#define ANON_INODE_FS_MAGIC 0x09041934
#define PSTOREFS_MAGIC 0x6165676C
+#define VMUFAT_MAGIC 0x55555555 /* opening bytes of root */
#define MINIX_SUPER_MAGIC 0x137F /* minix v1 fs, 14 char names */
#define MINIX_SUPER_MAGIC2 0x138F /* minix v1 fs, 30 char names */
Please sign in to comment.
Something went wrong with that request. Please try again.