From patchwork Sat Apr 11 22:11:31 2009 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Adrian McMenamin X-Patchwork-Id: 17766 Received: from vger.kernel.org (vger.kernel.org [209.132.176.167]) by demeter.kernel.org (8.14.2/8.14.2) with ESMTP id n3BMBxVB001951 for ; Sat, 11 Apr 2009 22:11:59 GMT Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1758914AbZDKWLz (ORCPT ); Sat, 11 Apr 2009 18:11:55 -0400 Received: (majordomo@vger.kernel.org) by vger.kernel.org id S1758273AbZDKWLz (ORCPT ); Sat, 11 Apr 2009 18:11:55 -0400 Received: from mk-filter-3-a-1.mail.uk.tiscali.com ([212.74.100.54]:59511 "EHLO mk-filter-3-a-1.mail.uk.tiscali.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1758927AbZDKWLt (ORCPT ); Sat, 11 Apr 2009 18:11:49 -0400 X-Trace: 176955153/mk-filter-3.mail.uk.tiscali.com/B2C/$b2c-THROTTLED-DYNAMIC/b2c-CUSTOMER-DYNAMIC-IP/79.69.83.115/None/adrian@newgolddream.dyndns.info X-SBRS: None X-RemoteIP: 79.69.83.115 X-IP-MAIL-FROM: adrian@newgolddream.dyndns.info X-MUA: Evolution 2.24.3 X-IP-BHB: Once X-IronPort-Anti-Spam-Filtered: true X-IronPort-Anti-Spam-Result: AlIGAI+y4ElPRVNz/2dsb2JhbACBUYEwtmsYBQiOXYJCCIEyBg X-IronPort-AV: E=Sophos;i="4.40,173,1238972400"; d="scan'208";a="176955153" Received: from 79-69-83-115.dynamic.dsl.as9105.com (HELO newgolddream.info) ([79.69.83.115]) by smtp.tiscali.co.uk with ESMTP; 11 Apr 2009 23:11:42 +0100 Received: from [192.168.62.105] (bossclass.tiscali.co.uk [192.168.62.105]) by newgolddream.info (8.14.3/8.14.3/Debian-4) with ESMTP id n3BMBWqk000771; Sat, 11 Apr 2009 23:11:33 +0100 Subject: [RFC][patch] VMUFAT filesystem - v2 From: Adrian McMenamin To: LKML , linux-fsdevel , viro Cc: linux-sh Date: Sat, 11 Apr 2009 23:11:31 +0100 Message-Id: <1239487891.6523.17.camel@localhost.localdomain> Mime-Version: 1.0 X-Mailer: Evolution 2.24.3 X-Spam-Status: No, score=-1.5 required=7.0 tests=ALL_TRUSTED,AWL,BAYES_00, URIBL_BLACK, URIBL_GREY, URIBL_RED, URIBL_RHS_DOB autolearn=no version=3.2.5 X-Spam-Checker-Version: SpamAssassin 3.2.5 (2008-06-10) on dragoneye X-Virus-Scanned: ClamAV 0.94.1rc1/9224/Fri Apr 10 22:49:29 2009 on newgolddream.info X-Virus-Status: Clean Sender: linux-sh-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-sh@vger.kernel.org Sega Dreamcast devices support "visual memory units" - and support for this hardware is now in the 2.6.30 release candidate kernels. The VMUs have a small slab of flash and it is possible to format this to use the various filesystems supported in the kernel (though the slab is only 128K) but the Dreamcast also has its own proprietary FAT type filesystem - and this is what this driver supports. The core of this filesystem code has been around for a few years for 2.4 and I posted a version 1 of this back in February (http://lkml.org/lkml/2009/2/14/127) but this is a much improved version which, for instance, properly supports the page cache. But there are still some rough edges, so just an RFC for now. I've written this to be hardware independent though it does take into account that it is most likely to be used with VMUs/flash. You can read more about the filesystem here: http://mc.pp.se/dc/vms/flashmem.html You can download an empty filesystem for testing here: http://newgolddream.dyndns.info/pristine I will be writing appropriate userland tools and more documentation in due course. fs/Kconfig | 1 + fs/Makefile | 1 + fs/vmufat/Kconfig | 14 + fs/vmufat/Makefile | 7 + fs/vmufat/inode.c | 1400 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 1423 insertions(+), 0 deletions(-) Signed-off by: Adrian McMenamin --- To unsubscribe from this list: send the line "unsubscribe linux-sh" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html diff --git a/fs/Kconfig b/fs/Kconfig index 9f7270f..b7cb23d 100644 --- a/fs/Kconfig +++ b/fs/Kconfig @@ -176,6 +176,7 @@ source "fs/romfs/Kconfig" source "fs/sysv/Kconfig" source "fs/ufs/Kconfig" source "fs/exofs/Kconfig" +source "fs/vmufat/Kconfig" config NILFS2_FS tristate "NILFS2 file system support (EXPERIMENTAL)" diff --git a/fs/Makefile b/fs/Makefile index af6d047..9d2d469 100644 --- a/fs/Makefile +++ b/fs/Makefile @@ -124,3 +124,4 @@ obj-$(CONFIG_OCFS2_FS) += ocfs2/ obj-$(CONFIG_BTRFS_FS) += btrfs/ obj-$(CONFIG_GFS2_FS) += gfs2/ obj-$(CONFIG_EXOFS_FS) += exofs/ +obj-$(CONFIG_VMUFAT_FS) += vmufat/ diff --git a/fs/vmufat/Kconfig b/fs/vmufat/Kconfig new file mode 100644 index 0000000..bdad4c6 --- /dev/null +++ b/fs/vmufat/Kconfig @@ -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 diff --git a/fs/vmufat/Makefile b/fs/vmufat/Makefile new file mode 100644 index 0000000..c716ead --- /dev/null +++ b/fs/vmufat/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for VMUFAT filesystem +# + +obj-$(CONFIG_VMUFAT_FS) += vmufat.o + +vmufat-objs := inode.o diff --git a/fs/vmufat/inode.c b/fs/vmufat/inode.c new file mode 100644 index 0000000..8ac0cba --- /dev/null +++ b/fs/vmufat/inode.c @@ -0,0 +1,1400 @@ +/* + * inode operations for the VMU file system + * + * Copyright (C) 2002 - 2009 Adrian McMenamin + * Copyright (C) 2002 Paul Mundt + * + * Released under the terms of the GNU GPL. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define VMUFAT_NAMELEN 12 + +/* GNU utils won't list files with inode num 0 */ +#define VMUFAT_ZEROBLOCK 32768 +#define VMU_BLK_SZ 512 +#define VMUFAT_MAGIC 0x55555555 + +static struct kmem_cache *vmufat_inode_cachep; +static struct kmem_cache *vmufat_blist_cachep; +static const struct inode_operations vmufat_inode_operations; +static const struct file_operations vmufat_file_operations; +static const struct address_space_operations vmufat_address_space_operations; +static const struct file_operations vmufat_file_dir_operations; +static const struct super_operations vmufat_super_operations; + +static struct inode *vmufat_get_inode(struct super_block *sb, long ino); +static int vmufat_list_blocks(struct inode *in); + +struct memcard { + long sb_bnum; + long fat_bnum; + long fat_len; + long dir_bnum; + long dir_len; + long numblocks; +}; + +struct vmufat_block_list { + struct list_head b_list; + int bno; +}; + +struct vmufat_inode { + struct vmufat_block_list blocks; + int nblcks; + struct inode vfs_inode; +}; + +static struct vmufat_inode *VMUFAT_I(struct inode *in) +{ + return container_of(in, struct vmufat_inode, vfs_inode); +} + +struct vmufat_file_info { + __u8 ftype; + __u8 copy_pro; + __u16 fblk; + char fname[12]; +}; + +static inline int vmufat_index(int fno) +{ + return (fno % 0x10) * 0x20; +} + +static inline int vmufat_index_16(int fno) +{ + return (fno % 0x10) * 0x10; +} + +/* VMU hardware is flaky, so let's compensate for that + * without losing hardare independence - + * as it is likely to be where this filesystem is used + */ +static inline struct buffer_head *vmufat_sb_bread(struct super_block *sb, + sector_t block) +{ + struct buffer_head *bh; + bh = sb_bread(sb, block); + if (bh) + return bh; + return sb_bread(sb, block); +} + +/* Linear day numbers of the respective 1sts in non-leap years. */ +static 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 *nd) +{ + struct super_block *sb; + struct memcard *vmudetails; + struct buffer_head *bh; + struct inode *ino; + char name[VMUFAT_NAMELEN]; + long blck_read; + int error = 0, fno = 0; + + if (dent->d_name.len > VMUFAT_NAMELEN) { + error = -ENAMETOOLONG; + goto out; + } + + sb = in->i_sb; + vmudetails = sb->s_fs_info; + blck_read = vmudetails->dir_bnum; + + bh = vmufat_sb_bread(sb, blck_read); + if (!bh) { + error = -EIO; + goto out; + } + + do { + /* Have we got a file? */ + if (bh->b_data[vmufat_index(fno)] == 0) + goto next; + + /* get file name */ + memcpy(name, + bh->b_data + 4 + vmufat_index(fno), VMUFAT_NAMELEN); + /* do names match ?*/ + if (memcmp(dent->d_name.name, name, dent->d_name.len) == 0) { + /* read the inode number from the directory */ + ino = vmufat_get_inode(sb, + le16_to_cpu(((u16 *) bh->b_data) + [1 + vmufat_index_16(fno)])); + if (!ino) { + error = -EACCES; + goto release_bh; + } + if (IS_ERR(ino)) { + error = PTR_ERR(ino); + goto release_bh; + } + /* return the entry */ + d_add(dent, ino); + goto release_bh; + } +next: + /* did not match, so try the next file */ + fno++; + /* do we need to move to the next block in the directory? */ + if (fno >= 0x10) { + fno = 0; + blck_read--; + if (blck_read <= + vmudetails->dir_bnum - vmudetails->dir_len) { + d_add(dent, NULL); + break; + } + brelse(bh); + bh = vmufat_sb_bread(sb, blck_read); + if (!bh) { + error = -EIO; + goto out; + } + } + } while (1); + +release_bh: + brelse(bh); +out: + return ERR_PTR(error); +} + +/* + * Find a free block in the FAT and mark it + * as the end of a file + */ +static int vmufat_find_free(struct super_block *sb) +{ + struct memcard *vmudetails = sb->s_fs_info; + int nextblock, x; + u16 fatdata; + struct buffer_head *bh_fat; + int error = 0; + + nextblock = vmudetails->fat_bnum + vmudetails->fat_len - 1; + x = VMU_BLK_SZ; + bh_fat = + vmufat_sb_bread(sb, nextblock); + if (!bh_fat) { + error = -EIO; + goto fail; + } + + do { + fatdata = ((u16 *) bh_fat->b_data)[x]; + if (fatdata == 0xfffc) + break; /*empty block */ + if (--x < 0) { + put_bh(bh_fat); + if (--nextblock >= vmudetails->fat_bnum) { + x = VMU_BLK_SZ; + bh_fat = vmufat_sb_bread(sb, nextblock); + if (!bh_fat) { + error = -EIO; + goto fail; + } + } else + break; + } + } while (1); + + if (nextblock < vmudetails->fat_bnum) { + printk(KERN_ERR "VMUFAT: device is full\n"); + error = -ENOSPC; + put_bh(bh_fat); + goto fail; + } + put_bh(bh_fat); + return x + (nextblock - vmudetails->fat_bnum) * VMU_BLK_SZ; + +fail: + return error; +} + +/* read the FAT for a given block */ +static u16 vmufat_get_fat(struct super_block *sb, long block) +{ + struct memcard *vmudetails = sb->s_fs_info; + struct buffer_head *bh; + int offset; + u16 block_content; + + offset = block/(VMU_BLK_SZ/2); + if (offset >= vmudetails->fat_len) + return 0xFFFE; + + bh = vmufat_sb_bread(sb, offset + 1 + + vmudetails->fat_bnum - vmudetails->fat_len); + if (!bh) + return 0xFFFF; + + block_content = ((u16 *)bh->b_data)[block % (VMU_BLK_SZ / 2)]; + put_bh(bh); + return block_content; +} + +/* set the FAT for a given block */ +static int vmufat_set_fat(struct super_block *sb, long block, u16 set) +{ + struct memcard *vmudetails = sb->s_fs_info; + struct buffer_head *bh; + int offset; + + offset = block/(VMU_BLK_SZ/2); + if (offset >= vmudetails->fat_len) + return -EINVAL; + + bh = vmufat_sb_bread(sb, offset + 1 + + vmudetails->fat_bnum - vmudetails->fat_len); + if (!bh) + return -EIO; + + ((u16 *) bh->b_data)[block % (VMU_BLK_SZ / 2)] = set; + mark_buffer_dirty(bh); + put_bh(bh); + return 0; +} + +static int vmufat_inode_create(struct inode *dir, struct dentry *de, + int imode, struct nameidata *nd) +{ + /* Create an inode */ + int x = 0, y, z, error = 0, q; + long blck_read; + struct inode *inode; + struct super_block *sb; + struct memcard *vmudetails; + struct buffer_head *bh_fat = NULL, *bh; + unsigned long unix_date; + int year, day, nl_day, month; /*inspired by FAT driver */ + u8 century, u8year; + + if (de->d_name.len > VMUFAT_NAMELEN) + return -ENAMETOOLONG; + + sb = dir->i_sb; + vmudetails = sb->s_fs_info; + + inode = new_inode(sb); + if (!inode) { + error = -ENOSPC; + goto out; + } + + /* Walk through blocks looking for place to write + * Is this an executible file? */ + if (imode & 73) { /*Octal 111 */ + inode->i_ino = VMUFAT_ZEROBLOCK; + /* But this already allocated? */ + if (vmufat_get_fat(sb, 0) != 0xFFFC) { + printk(KERN_ERR + "VMUFAT: cannot write executible file to" + " filesystem - block 0 already allocated.\n"); + error = -ENOSPC; + goto clean_inode; + } + q = 0; + } else { + q = vmufat_find_free(sb); + if (q < 0) { + error = q; + goto clean_inode; + } + inode->i_ino = q; + } + + error = vmufat_set_fat(sb, q, 0xFFFA); + if (error) + goto clean_inode; + + inode->i_uid = 0; + inode->i_gid = 0; + inode->i_mtime = inode->i_atime = inode->i_ctime = CURRENT_TIME; + inode->i_mode = imode; + inode->i_blocks = 1; + inode->i_sb = sb; + insert_inode_hash(inode); + inode->i_op = &vmufat_inode_operations; + inode->i_fop = &vmufat_file_operations; + inode->i_mapping->a_ops = &vmufat_address_space_operations; + + /* Write to the directory + * Now search for space for the directory entry */ + blck_read = vmudetails->dir_bnum; + bh = vmufat_sb_bread(sb, blck_read); + if (!bh) { + error = -EIO; + goto clean_inode; + } + + for (y = 0; y < (vmudetails->dir_len * 0x10); y++) { + if ((y / 0x10) > (vmudetails->dir_bnum - blck_read)) { + brelse(bh); + blck_read--; + bh = vmufat_sb_bread(sb, blck_read); + if (!bh) { + error = -EIO; + goto clean_fat; + } + } + if (((bh->b_data)[vmufat_index(y)]) == 0) + break; + } + /* Have the directory entry + * so now update it */ + z = vmufat_index(y); + if (inode->i_ino != VMUFAT_ZEROBLOCK) + bh->b_data[z] = 0x33; /* data file */ + else + bh->b_data[z] = 0xcc; + + if ((bh->b_data[z + 1] != (char) 0x00) && + (bh->b_data[z + 1] != (char) 0xff)) + bh->b_data[z + 1] = (char) 0x00; + + if (inode->i_ino != VMUFAT_ZEROBLOCK) { + ((u16 *) bh->b_data)[z / 2 + 1] = + cpu_to_le16(inode->i_ino); + ((u16 *) bh->b_data)[z / 2 + 0x0D] = 0; + } else { + ((u16 *) bh->b_data)[z / 2 + 1] = 0; + ((u16 *) bh->b_data)[z / 2 + 0x0D] = 1; + } + + /* Name */ + memset((char *) (bh->b_data + z + 0x04), '\0', 0x0C); + memcpy((char *) (bh->b_data + z + 0x04), ((de->d_name).name), + de->d_name.len); + + /* BCD timestamp it */ + unix_date = CURRENT_TIME.tv_sec; + day = unix_date / 86400 - 3652; + year = day / 365; + + if ((year + 3) / 4 + 365 * year > day) + year--; + + day -= (year + 3) / 4 + 365 * year; + if (day == 59 && !(year & 3)) { + nl_day = day; + month = 2; + } else { + nl_day = (year & 3) || day <= 59 ? day : day - 1; + for (month = 0; month < 12; month++) + if (day_n[month] > nl_day) + break; + } + + century = 19; + if (year > 19) + century = 20; + + bh->b_data[z + 0x10] = bin2bcd(century); + u8year = year + 80; + if (u8year > 99) + u8year = u8year - 100; + + bh->b_data[z + 0x11] = bin2bcd(u8year); + bh->b_data[z + 0x12] = bin2bcd(month); + bh->b_data[z + 0x13] = + bin2bcd(day - day_n[month - 1] + 1); + bh->b_data[z + 0x14] = + bin2bcd((unix_date / 3600) % 24); + bh->b_data[z + 0x15] = bin2bcd((unix_date / 60) % 60); + bh->b_data[z + 0x16] = bin2bcd(unix_date % 60); + + ((u16 *) bh->b_data)[z / 2 + 0x0C] = + cpu_to_le16(inode->i_blocks); + + inode->i_mtime.tv_sec = unix_date; + mark_buffer_dirty(bh); + brelse(bh); + + error = vmufat_list_blocks(inode); + if (error) + goto clean_fat; + + d_instantiate(de, inode); printk("created inode 0x%lX\n", inode->i_ino); + brelse(bh_fat); + return error; + +clean_fat: + ((u16 *)bh_fat->b_data)[x] = 0xfffc; + mark_buffer_dirty(bh_fat); + brelse(bh_fat); +clean_inode: + iput(inode); +out: + return error; +} + +static int vmufat_inode_rename(struct inode *in_source, + struct dentry *de_source, + struct inode *in_target, + struct dentry *de_target) +{ + return -EPERM; +} + +static int vmufat_readdir(struct file *filp, void *dirent, filldir_t filldir) +{ + int filenamelen, i, error = 0; + struct vmufat_file_info *saved_file = NULL; + struct dentry *dentry = filp->f_dentry; + struct inode *inode = dentry->d_inode; + struct super_block *sb = inode->i_sb; + struct memcard *vmudetails = sb->s_fs_info; + struct buffer_head *bh; + + int blck_read = vmudetails->dir_bnum; + bh = vmufat_sb_bread(sb, blck_read); + if (!bh) { + error = -EIO; + goto out; + } + + i = filp->f_pos; + + /* handle . for this directory and .. for parent */ + switch ((unsigned int) filp->f_pos) { + case 0: + if (filldir(dirent, ".", 1, i++, inode->i_ino, DT_DIR) < 0) + goto finish; + + filp->f_pos++; + case 1: + if (filldir(dirent, "..", 2, i++, + dentry->d_parent->d_inode->i_ino, DT_DIR) < 0) + goto finish; + + filp->f_pos++; + default: + break; + } + + /* trap reading beyond the end of the directory */ + if ((i - 2) > (vmudetails->dir_len * 0x10)) { + error = -EINVAL; + goto release_bh; + } + + saved_file = + kmalloc(sizeof(struct vmufat_file_info), GFP_KERNEL); + if (!saved_file) { + error = -ENOMEM; + goto release_bh; + } + + do { + if ((i - 2) / 0x10 > (vmudetails->dir_bnum - blck_read)) { + /* move to next block in directory */ + blck_read--; + if (vmudetails->dir_bnum - vmudetails->dir_len <= + blck_read) + break; + brelse(bh); + bh = vmufat_sb_bread(sb, blck_read); + if (!bh) { + kfree(saved_file); + error = -EIO; + goto out; + } + } + + saved_file->ftype = bh->b_data[vmufat_index(i - 2)]; + + if (saved_file->ftype == 0) + break; + + saved_file->fblk = + le16_to_cpu(((u16 *) bh->b_data)[1 + + vmufat_index_16(i - 2)]); + if (saved_file->fblk == 0) + saved_file->fblk = VMUFAT_ZEROBLOCK; + + memcpy(saved_file->fname, + bh->b_data + 4 + vmufat_index(i - 2), VMUFAT_NAMELEN); + filenamelen = strlen(saved_file->fname); + if (filenamelen > VMUFAT_NAMELEN) + filenamelen = VMUFAT_NAMELEN; + if (filldir + (dirent, saved_file->fname, filenamelen, i++, + saved_file->fblk, DT_REG) < 0) { + goto finish; + } + + filp->f_pos++; + } while (1); + +finish: + kfree(saved_file); +release_bh: + brelse(bh); +out: + return error; +} + + + +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 = VMUFAT_I(in); + struct vmufat_block_list *vb; + struct list_head *iter, *iter2; + + 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); +} + +static int vmufat_list_blocks(struct inode *in) +{ + struct vmufat_inode *vi = VMUFAT_I(in); + struct super_block *sb = in->i_sb; + long nextblock; + long ino = in->i_ino; + int fat_seg; + struct buffer_head *bh; + struct memcard *vmudetails; + int error; + struct list_head *iter, *iter2; + struct vmufat_block_list *vbl, *nvbl; + u16 fatdata; + + vmudetails = sb->s_fs_info; + nextblock = ino; + fat_seg = nextblock / (sb->s_blocksize / 2); + bh = vmufat_sb_bread(sb, vmudetails->fat_bnum - + (vmudetails->fat_len - 1) + fat_seg); + if (!bh) { + error = -EIO; + goto out; + } + + /* 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 = ((u16 *) bh->b_data) + [nextblock % (sb->s_blocksize / 2)]; + if (fatdata == 0xfffc) { + printk(KERN_WARNING "VMUFAT: FAT table appears to have" + " been corrupted.\n"); + error = -EIO; + goto unwind_out; + } + if (fatdata == 0xfffa) + break; /*end of file */ + if (fatdata / (sb->s_blocksize / 2) != fat_seg) { + brelse(bh); + fat_seg = fatdata / (sb->s_blocksize / 2); + bh = vmufat_sb_bread(sb, vmudetails->fat_bnum - + (vmudetails->fat_len - 1) + fat_seg); + if (!bh) { + error = -ENOMEM; + goto unwind_out; + } + } + nextblock = fatdata; + } while (1); + brelse(bh); + return 0; + +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); + } +out: + return error; +} + +static struct inode *vmufat_get_inode(struct super_block *sb, long ino) +{ + struct buffer_head *bh; + int error, blck_read, y, z; + struct inode *inode = iget_locked(sb, ino); + struct memcard *vmudetails = sb->s_fs_info; + long superblock_bno = vmudetails->sb_bnum; + + if (inode && (inode->i_state & I_NEW)) { + inode->i_uid = 0; + inode->i_gid = 0; + 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, 0x30); + + /* 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 { + blck_read = vmudetails->dir_bnum; + bh = vmufat_sb_bread(sb, blck_read); + if (!bh) { + error = -EIO; + goto failed; + } + + /* Mark file as regular type */ + inode->i_mode = S_IFREG; + + /* Scan through the directory to find matching file */ + for (y = 0; y < vmudetails->numblocks; y++) { + if ((y / 0x10) > + (vmudetails->dir_bnum - blck_read)) { + brelse(bh); + blck_read--; + bh = vmufat_sb_bread(sb, blck_read); + if (!bh) { + error = -EIO; + goto failed; + } + } + if (le16_to_cpu + (((u16 *) bh->b_data)[(y % 0x10) * 0x10 + + 0x01]) == ino) + break; + } + + if (y >= vmudetails->numblocks) { + brelse(bh); + printk(KERN_INFO + "vmufat: could not find this " + "file on filesystem\n"); + error = -ENOENT; + goto failed; + } + + /* identified the correct directory entry */ + z = vmufat_index(y); + inode->i_ctime.tv_sec = inode->i_mtime.tv_sec = + vmufat_get_date(bh, z + 0x10); + + /* 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 + z] == + 0x00) & ~(sb->s_flags & MS_RDONLY)) + inode->i_mode |= S_IWUGO; + /* Is file executible - ie a game */ + if ((((u8 *) bh->b_data)[z] == + 0xcc) & ~(sb->s_flags & MS_NOEXEC)) + inode->i_mode |= S_IXUGO; + + inode->i_fop = &vmufat_file_operations; + + inode->i_blocks = + le16_to_cpu(((u16 *) bh->b_data) + [vmufat_index_16(y) + 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); + } + + return inode; + +failed: + iget_failed(inode); + 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_scan(struct super_block *sb, struct kstatfs *buf) +{ + int error = 0; + int free = 0; + int x; + u16 fatdata; + struct buffer_head *bh_fat; + struct memcard *vmudetails = sb->s_fs_info; + long nextblock; + + /* Look through the FAT */ + nextblock = vmudetails->fat_bnum + vmudetails->fat_len - 1; + x = sb->s_blocksize; + bh_fat = vmufat_sb_bread(sb, nextblock); + if (!bh_fat) { + error = -EIO; + goto out; + } + + do { + fatdata = ((u16 *) bh_fat->b_data)[x]; + if (fatdata == 0xfffc) + free++; + if (--x < 0) { + brelse(bh_fat); + if (--nextblock >= vmudetails->fat_bnum) { + x = sb->s_blocksize; + bh_fat = vmufat_sb_bread(sb, nextblock); + if (!bh_fat) { + error = -EIO; + goto out; + } + } else + break; + } + } while (1); + + buf->f_bfree = free; + buf->f_bavail = free; + buf->f_blocks = vmudetails->numblocks; + +out: + return error; +} + +static int vmufat_statfs(struct dentry *dentry, struct kstatfs *buf) +{ + struct super_block *sb = dentry->d_sb; + int error; + + error = vmufat_scan(sb, buf); + if (error) + return error; + buf->f_type = VMUFAT_MAGIC; + buf->f_bsize = sb->s_blocksize; + buf->f_namelen = VMUFAT_NAMELEN; + + return 0; +} + +static void vmufat_delete_inode(struct inode *in) +{ + /* Delete inode by marking space as free in FAT + * no need to waste time and effort by actually + * wiping underlying data on media */ + struct buffer_head *bh, *bh_old; + struct super_block *sb; + struct memcard *vmudetails; + int z, y, x, w, v, blck_read, fat_seg; + u16 nextblock, fatdata; + + 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_ERR "vmufat: attempting to delete" + "inode beyond device size"); + return; + } + + /* Seek start of file and wonder through FAT */ + nextblock = in->i_ino; + + fat_seg = nextblock / (sb->s_blocksize / 2); + bh = vmufat_sb_bread(sb, vmudetails->fat_bnum - + (vmudetails->fat_len - 1) + fat_seg); + if (!bh) + goto minor_failure; + + do { + fatdata = ((u16 *) bh->b_data) + [nextblock % (sb->s_blocksize / 2)]; + if (fatdata == 0xfffc) + break; + ((u16 *) bh->b_data)[nextblock % (sb->s_blocksize / 2)] = + 0xfffc; + if (fatdata == 0xfffa) + break; /*end of file */ + if (fatdata / (sb->s_blocksize / 2) != fat_seg) { + mark_buffer_dirty(bh); + brelse(bh); + fat_seg = fatdata / (sb->s_blocksize / 2); + bh = vmufat_sb_bread(sb, vmudetails->fat_bnum - + (vmudetails->fat_len - 1) + fat_seg); + if (!bh) + goto failure; + } + nextblock = fatdata; + } while (1); + mark_buffer_dirty(bh); + brelse(bh); + + /* Now clean the directory entry + * Have to wander through this + * to find the appropriate entry */ + blck_read = vmudetails->dir_bnum; + bh = vmufat_sb_bread(sb, blck_read); + if (!bh) + goto failure; + + for (y = 0; y < (vmudetails->dir_len * 0x10); y++) { + if ((y / 0x10) > (vmudetails->dir_bnum - blck_read)) { + brelse(bh); + blck_read--; + bh = vmufat_sb_bread(sb, blck_read); + if (!bh) + goto failure; + } + if (le16_to_cpu + (((u16 *) bh->b_data)[(y % 0x10) * 0x10 + + 0x01]) == in->i_ino) + break; + } + + /* Found directory entry - so NULL it now */ + w = vmufat_index_16(y); + for (z = 0; z < 0x10; z++) + ((u16 *) bh->b_data)[w + z] = 0; + mark_buffer_dirty(bh); + /* Replace it with another entry - if one exists */ + x = y; + for (y = x + 1; y < (vmudetails->dir_len * 0x10); y++) { + if ((y / 0x10) > (vmudetails->dir_bnum - blck_read)) { + brelse(bh); + blck_read--; + bh = vmufat_sb_bread(sb, blck_read); + if (!bh) + return; + } + /* look for the end of entries in the FAT */ + if (bh->b_data[vmufat_index(y)] == 0) { + y--; + if (y == x) + break; /* At the end in any case */ + brelse(bh); + + /* force read of correct block */ + bh = vmufat_sb_bread(sb, vmudetails->dir_bnum - + y / 0x10); + if (!bh) + goto failure; + bh_old = + vmufat_sb_bread(sb, vmudetails->dir_bnum - + x / 0x10); + if (!bh_old) { + brelse(bh); + goto failure; + } + + /* + * Copy final directory entry into space created + * by the deletion of the inode + */ + w = vmufat_index_16(x); + v = vmufat_index_16(y); + for (z = 0; z < 0x10; z++) { + ((u16 *) bh_old->b_data)[w + z] = + ((u16 *) bh->b_data)[v + z]; + ((u16 *) bh->b_data)[v + z] = 0; + } + mark_buffer_dirty(bh); + if (x/0x10 != y/0x10) + mark_buffer_dirty(bh_old); + brelse(bh_old); + break; + } + } + + brelse(bh); + return; + +minor_failure: + printk(KERN_ERR "vmufat: failed to delete inode\n"); + return; +failure: + printk(KERN_ERR "vmufat: Failure to read device," + " could not delete inode - filesystem may be damaged\n"); + return; +} + +/* + * vmufat_unlink - delete a file pointed to + * by the dentry (only one directory in a + * vmufat fs so safe to ignore the inode + * upplied here + */ +static int vmufat_unlink(struct inode *dir, struct dentry *dentry) +{ + struct inode *in; + + in = dentry->d_inode; + if (!in) + return -EIO; + vmufat_delete_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 = VMUFAT_I(inode); + struct vmufat_block_list *vlist = &vin->blocks; + struct vmufat_block_list *vblk; + struct super_block *sb = inode->i_sb; + int cural; + int finblck, nxtblk; + struct list_head *iter; + sector_t cntdwn = iblock; + sector_t phys; + int error = -EINVAL; + + /* is this block already here? */ + printk("Inode 0x%lX has 0x%X blocks, trying to get block at sector 0x%lX\n", + inode->i_ino, vin->nblcks, iblock); + /* quick sanity check */ + if (vin->nblcks <= 0) + goto out; + 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) { + error = -EINVAL; + goto out; + } + /* + * check not looking for a block too far + * beyond the end of the existing file + */ + if (iblock > vin->nblcks) { + error = -EINVAL; + 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); + finblck = vblk->bno; + nxtblk = vmufat_find_free(sb); + if (nxtblk < 0) { + error = nxtblk; + goto out; + } + error = vmufat_set_fat(sb, finblck, (u16) nxtblk); + if (error) + goto out; + error = vmufat_set_fat(sb, nxtblk, 0xfffa); + 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 *file, struct address_space *mapping, + loff_t pos, unsigned len, unsigned flags, + struct page **pagep, void **fsdata) +{ + *pagep = NULL; + return block_write_begin(file, mapping, pos, len, flags, pagep, fsdata, + vmufat_get_block); +} + +static int vmufat_readpage(struct file *file, struct page *page) +{ + return block_read_full_page(page, vmufat_get_block); +} + +/* There are no inodes on the medium - vmufat_write_inode + * updates the directory entry + */ +static int vmufat_write_inode(struct inode *in, int wait) +{ + struct buffer_head *bh; + __u8 century, u8year; + unsigned long inode_num; + int y, blck_read, z; + unsigned long unix_date; + int year, day, nl_day, month; + struct super_block *sb = in->i_sb; + struct memcard *vmudetails = + ((struct memcard *) sb->s_fs_info); + + 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 */ + printk("Writing inode 0x%lX\n", inode_num); + blck_read = vmudetails->dir_bnum; + bh = vmufat_sb_bread(sb, blck_read); + if (!bh) + return -EIO; + + for (y = 0; y < vmudetails->numblocks; y++) { + if ((y / 0x10) > (vmudetails->dir_bnum - blck_read)) { + brelse(bh); + blck_read--; + bh = vmufat_sb_bread(sb, blck_read); + if (!bh) + return -EIO; + } + if (le16_to_cpu + (((__u16 *) bh->b_data)[vmufat_index_16(y) + + 0x01]) == inode_num) + break; + } + /* Have the directory entry + * so now update it */ + z = (y % 0x10) * 0x20; + if (inode_num != 0) + bh->b_data[z] = 0x33; /* data file */ + else + bh->b_data[z] = 0xcc; + if (bh->b_data[z + 1] != 0 + && bh->b_data[z + 1] != (char) 0xff) + bh->b_data[z + 1] = 0; + ((__u16 *) bh->b_data)[z / 2 + 1] = cpu_to_le16(inode_num); + + /* BCD timestamp it */ + unix_date = CURRENT_TIME.tv_sec; + day = unix_date / 86400 - 3652; + year = day / 365; + if ((year + 3) / 4 + 365 * year > day) + year--; + day -= (year + 3) / 4 + 365 * year; + if (day == 59 && !(year & 3)) { + nl_day = day; + month = 2; + } else { + nl_day = (year & 3) || day <= 59 ? day : day - 1; + for (month = 0; month < 12; month++) + if (day_n[month] > nl_day) + break; + } + + century = 19; + if (year > 19) + century = 20; + bh->b_data[z + 0x10] = bin2bcd(century); + u8year = year + 80; + if (u8year > 99) + u8year = u8year - 100; + bh->b_data[z + 0x11] = bin2bcd(u8year); + bh->b_data[z + 0x12] = bin2bcd(month); + bh->b_data[z + 0x13] = bin2bcd(day - day_n[month - 1] + 1); + bh->b_data[z + 0x14] = bin2bcd((unix_date / 3600) % 24); + bh->b_data[z + 0x15] = bin2bcd((unix_date / 60) % 60); + bh->b_data[z + 0x16] = bin2bcd(unix_date % 60); + + ((__u16 *) bh->b_data)[z / 2 + 0x0C] = cpu_to_le16(in->i_blocks); + if (inode_num != 0) + ((__u16 *) bh->b_data)[z / 2 + 0x0D] = 0; + else /* game */ + ((__u16 *) bh->b_data)[z / 2 + 0x0D] = cpu_to_le16(1); + in->i_mtime.tv_sec = unix_date; + mark_buffer_dirty(bh); + brelse(bh); + return 0; +} + +static int check_sb_format(struct buffer_head *bh) +{ + u32 s_magic = VMUFAT_MAGIC; + + if (!(((u32 *) bh->b_data)[0] == s_magic && + ((u32 *) bh->b_data)[1] == s_magic && + ((u32 *) bh->b_data)[2] == s_magic && + ((u32 *) bh->b_data)[3] == s_magic)) + return 0; + else + return 1; +} + +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 int vmufat_fill_super(struct super_block *sb, + void *data, int silent) +{ + /*Search for the superblock - assuming likely sizes are + * 128, 256, 512 and 1024 blocks */ + + struct buffer_head *bh; + struct memcard *vmudata; + int z; + struct inode *root_i; + int ret = -EINVAL; + + sb_set_blocksize(sb, VMU_BLK_SZ); + + /* Have to try lowest number first to avoid oops */ + for (z = 128; z < 1024; z = z * 2) { + bh = vmufat_sb_bread(sb, z - 1); + if (!bh) { + ret = -EIO; + goto out; + } + if (check_sb_format(bh)) + break; + brelse(bh); + if (z == 1024) { /* failed */ + printk(KERN_ERR + "vmufat: attempted to mount non vmufat medium" + " as vmufat\n"); + goto out; + } + } + /* Store this data in the super block */ + + vmudata = kmalloc(sizeof(struct memcard), GFP_KERNEL); + if (!vmudata) { + ret = -ENOMEM; + goto freebh_out; + } + + /* user blocks */ + vmudata->sb_bnum = z - 1; + vmudata->fat_bnum = le16_to_cpu(((u16 *) bh->b_data)[0x46 / 2]); + vmudata->fat_len = le16_to_cpu(((u16 *) bh->b_data)[0x48 / 2]); + vmudata->dir_bnum = le16_to_cpu(((u16 *) bh->b_data)[0x4a / 2]); + vmudata->dir_len = le16_to_cpu(((u16 *) bh->b_data)[0x4c / 2]); + /* return the true number of user available blocks - 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; + sb->s_fs_info = vmudata; + + sb->s_blocksize_bits = ilog2(VMU_BLK_SZ); + sb->s_magic = VMUFAT_MAGIC; + sb->s_op = &vmufat_super_operations; + + 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; + } + return 0; + +freeroot_out: + iput(root_i); +freevmudata_out: + kfree(vmudata); +freebh_out: + brelse(bh); +out: + return ret; + +} + +static 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, +}; + +static const struct super_operations vmufat_super_operations = { + .alloc_inode = vmufat_alloc_inode, + .destroy_inode = vmufat_destroy_inode, + .write_inode = vmufat_write_inode, + .delete_inode = vmufat_delete_inode, + .put_super = vmufat_put_super, + .statfs = vmufat_statfs, +}; + +static const struct inode_operations vmufat_inode_operations = { + .lookup = vmufat_inode_lookup, + .create = vmufat_inode_create, + .rename = vmufat_inode_rename, + .unlink = vmufat_unlink, +}; + +static const struct file_operations vmufat_file_dir_operations = { + .owner = THIS_MODULE, + .read = generic_read_dir, + .readdir = vmufat_readdir, + .fsync = file_fsync, +}; + +static 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, +}; + +static int vmufat_get_sb(struct file_system_type *fs_type, + int flags, const char *dev_name, void *data, struct vfsmount *mnt) +{ + return get_sb_bdev(fs_type, flags, dev_name, data, vmufat_fill_super, + mnt); +} + +static struct file_system_type vmufat_fs_type = { + .owner = THIS_MODULE, + .name = "vmufat", + .get_sb = vmufat_get_sb, + .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 for Sega Dreamcast VMU"); +MODULE_AUTHOR("Adrian McMenamin "); +MODULE_LICENSE("GPL");