From nobody Mon Jun 15 13:56:23 2026 Received: from mail-wm1-f53.google.com (mail-wm1-f53.google.com [209.85.128.53]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 9C56F40DFC5 for ; Sat, 11 Apr 2026 15:12:12 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.53 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775920334; cv=none; b=toZMB1TdeMdUFB/mT5ug+ywdQjgU/81+ava7W6GDoehrp3Jt33hlXsDXR3bRi0q/hOb4cle9MsAQ7IJHf1Mdg6PrndkHtium+pxsMIJJ3iRTReZ0aN5wqz5+RKdI8Gea0lnRhRvi2kw9+ZpdjWCdaQy/Qq3A3ypmuDu2hQfqvHw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775920334; c=relaxed/simple; bh=BJ04Gv7+B2m7jZaIi40hW4hNN3KlgK/Ajt5HF+AnKnQ=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=BXzdWC2rNzbfaDqvl1wnHm2UJ1IGXlR7AOXbmFy4kPvFlZxv44aJgoGueFz0PTjsmjGE1+3NiD19M8Bx2hx/RPDvgnzkimZly+cTyXjVCGHfuA7FKmKWxjOXh6xaTC+7fFHh6pfR0AIOwlbcJy6wdu2dlC5VDoKjiNYZQvG8nvk= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=OW2t+xLz; arc=none smtp.client-ip=209.85.128.53 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="OW2t+xLz" Received: by mail-wm1-f53.google.com with SMTP id 5b1f17b1804b1-4852b81c73aso29196985e9.3 for ; Sat, 11 Apr 2026 08:12:12 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1775920331; x=1776525131; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=lPAb2Zu1xqpWj4DS9q88UMB36cViMy8+t4UmASklm7E=; b=OW2t+xLz1DaUYdQ1LnzY7kB7G+3Cw5iMcTlKb7pirDVWxRsrpIeeMclC15kme7VpJ1 cD5uGSr9x/X3KyZP9KnSJ+1b/75wZDqyg9NwvhnUtQ+WhU3aF+Zg11YBMFlRauUbC158 M5bK9MmFpiAxlUbMOOIO09Bjtjvqc8YQm+fCXdZEqGYMzJod7x2+1yBAEKGDO6jmS5vr +x5Kx7cu+OD9HYu50Fg8aLRtm+s2qrvqwxOYGKnDA9dSsP/os96mDfWeNfBr4t0m0OBQ 0vGP354vpeS796Yg+H3llTpWMaQS+80Uc0h9jURQB+azSYuSIJcGUIQDkHD6bmax1fHs ZMrw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1775920331; x=1776525131; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=lPAb2Zu1xqpWj4DS9q88UMB36cViMy8+t4UmASklm7E=; b=sCxhwviJrtcjLbF/G+7E0jcXE017wOJH0U+2L0wDbxR1b/JppJsPY5R1t8wOXJfjrz J0iXZJ7b1SUBl19T8miDW9qmTEchmHXIChjOKwpNbcNty/hA6CHxohYBfRlfaPy41SeW XragrhE2c0kgFYB32wCkTcefm6SilpQ0mhZ6o+elbkcxb9Q980eagidMRuecgjrNazQx 7doCPdejkaJroSTS+dXFeo79TvCabzDaveY8CfI1srjSSsgWlkqj9DwPnZQu9J27UfCw Z5zzrxXQouDNpNby8HgzLv5fHrY39TStFP7lKQbueP05wSAE0j9mghPZ9bOAfwSkN4y4 TYrQ== X-Forwarded-Encrypted: i=1; AJvYcCXn7KRtpZUunjpzKHcdSTsoHpsod8SBnXHw+G73UMllCMThm0oY0MHRmt+LNSoEtlr252pwyLQZ0DNhGWI=@vger.kernel.org X-Gm-Message-State: AOJu0Yw10wgIXV01b4a0qnE7M2pSNcnvugv3iQb31KW+cVQCgDZ9tpno tGQQDz7aHmpRLIQkmnuc3xEykGuckYoaQYvd+xNL1PGUG6aBsIbyv4l1VeOWiQ== X-Gm-Gg: AeBDiesCW/kfFJYuuNtE10WaxGxyC8Y8UI1VzWOiXaeDQeIKtDFdVlIT8wwWoZj68W0 mRV8RKf/W6genDc5o842MNZ2VM7232X1EDvaC5Q6FPrOdL5JvTY7ubUnGoLVG9xh3y/Qf3TyVSa houbmN8yBdMxTaeZsxmrr5S75yuicqVO8OJxqMlvsNC8wf75r5Uc1BqHoOLBEpvOlwOH++oQwGI 0BkQ6aOXwIC7ehW7IleqCOd+WA5Mr4zftgKr6y4If8B3hvfsYpileRmwKBF8zgvZBVQscimpbo9 PRPlmDLe7fskZiyUnPGHX+ZtAMq2mw20NN+FDQkfkktCqVQ0GLOj1WugXT28/xFx6If1dw3w3c1 bDF17TivaSiffMjCsXOWzGemnFpsH3rb4U+j6l4Dh+9/dgsk236lGI5ojpiDPIYIh9T+iBpzEYm 6L+g1EgyzeDToOmKKp2X6BuMzaHONhgdTezRZZ1+QNpDL+RYvQY4KBppe0M517PMvNLJDmqlhNI 4xMIxQvbOMrdJJFHJyQ X-Received: by 2002:a05:600c:c0da:b0:488:ab5b:d711 with SMTP id 5b1f17b1804b1-488d6864b6cmr76496725e9.23.1775920330880; Sat, 11 Apr 2026 08:12:10 -0700 (PDT) Received: from localhost.localdomain ([2a02:c7c:37d6:c100:81d8:4e83:a60e:1a81]) by smtp.googlemail.com with ESMTPSA id ffacd0b85a97d-43d63e5c981sm17167835f8f.33.2026.04.11.08.12.10 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 11 Apr 2026 08:12:10 -0700 (PDT) From: Adrian McMenamin To: Linux FS development Cc: Adrian McMenamin , Linux kernel mailing list Subject: [PATCH 1/5 -v2] Add vmufat super.c Date: Sat, 11 Apr 2026 16:11:35 +0100 Message-ID: <20260411151155.321214-2-adrianmcmenamin@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260411151155.321214-1-adrianmcmenamin@gmail.com> References: <20260411151155.321214-1-adrianmcmenamin@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" super.c provides superblock services for VMUFAT. A VMUFAT filesystem includes a system block (at block 255 on a factory supp= lied VMU) which contains information about the volume size, the file allocation table and the directory. This code accesses this information. Using this information super.c also handles basic creation and removal of f= iles (represented as inodes, though there are no physical inodes on the underlyi= ng medium). It also initialises (and destroys) caches of inode structures and block lis= ts (each inode structure holds a linked list of the blocks used by a file). The code supports, as default behaviour, the Dreamcast policy of placing executables in lower block numbers and data files in higher block numbers but it will not fail if this policy cannot be followed. Signed-off-by: Adrian McMenamin --- fs/vmufat/super.c | 581 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 581 insertions(+) create mode 100644 fs/vmufat/super.c diff --git a/fs/vmufat/super.c b/fs/vmufat/super.c new file mode 100644 index 000000000000..69f4852f3d03 --- /dev/null +++ b/fs/vmufat/super.c @@ -0,0 +1,581 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * VMUFAT file system + * + * Copyright (C) 2002-2012, 2025, 2026 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, U= SA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "vmufat.h" + +static struct kmem_cache *vmufat_inode_cachep; +struct kmem_cache *vmufat_blist_cachep; +static const struct super_operations vmufat_super_operations; +extern int *day_n; +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_operatio= ns; +extern const struct file_operations vmufat_file_dir_operations; + +static time64_t vmufat_get_date(struct buffer_head *bh, int offset) +{ + int century, year, month, day, hour, minute, second; + century =3D bcd2bin(bh->b_data[offset++]); + year =3D bcd2bin(bh->b_data[offset++]); + month =3D bcd2bin(bh->b_data[offset++]); + day =3D bcd2bin(bh->b_data[offset++]); + hour =3D bcd2bin(bh->b_data[offset++]); + minute =3D bcd2bin(bh->b_data[offset++]); + second =3D bcd2bin(bh->b_data[offset]); + + return mktime64(century * 100 + year, month, day, hour, minute, + second); +} + +static struct inode *vmufat_alloc_inode(struct super_block *sb) +{ + struct vmufat_inode *vi =3D kmem_cache_alloc(vmufat_inode_cachep, + GFP_KERNEL); + if (!vi) + return NULL; + + INIT_LIST_HEAD(&vi->blocks); + return &vi->vfs_inode; +} + +static void vmufat_destroy_inode(struct inode *in) +{ + struct vmufat_inode *vi; + struct vmufat_block *iter, *iter2; + vi =3D VMUFAT_I(in); + if (!vi) + return; + + list_for_each_entry_safe(iter, iter2, &vi->blocks, b_list) { + list_del(&iter->b_list); + kmem_cache_free(vmufat_blist_cachep, iter); + } + kmem_cache_free(vmufat_inode_cachep, vi); +} + +struct inode *vmufat_get_inode(struct super_block *sb, long ino) +{ + struct buffer_head *bh =3D NULL; + int error =3D 0, i, j; + int offsetindir; + struct inode *inode; + struct memcard *vmudetails; + long superblock_bno; + struct timespec64 current_time; + + inode =3D iget_locked(sb, ino); + if (!inode) { + error =3D -ENOENT; + goto reterror; + } + vmudetails =3D sb->s_fs_info; + if (!vmudetails) { + error =3D -ENOMEM; + goto reterror; + } + superblock_bno =3D vmudetails->sb_bnum; + + if (inode_state_read_once(inode) & I_NEW) { + inode->i_uid =3D current_fsuid(); + inode->i_gid =3D current_fsgid(); + inode->i_mode &=3D ~S_IFMT; + if (inode->i_ino =3D=3D superblock_bno) { + bh =3D vmufat_sb_bread(sb, inode->i_ino); + if (!bh) { + error =3D -ENOENT; + goto failed; + } + inode->i_ctime_sec =3D inode->i_mtime_sec =3D + vmufat_get_date(bh, VMUFAT_SB_DATEOFFSET); + + /* Mark as a directory */ + inode->i_mode =3D S_IFDIR | S_IRUGO | S_IXUGO; + inode->i_op =3D &vmufat_inode_operations; + inode->i_fop =3D &vmufat_file_dir_operations; + } else { + /* Mark file as regular type */ + inode->i_mode =3D S_IFREG | S_IRUGO | S_IWUSR; + + /* Scan through the directory to find matching file */ + for (i =3D 0; i < vmudetails->dir_len; i++) { + brelse(bh); + bh =3D vmufat_sb_bread(sb, + vmudetails->dir_bnum - i); + if (!bh) { + error =3D -ENOMEM; + goto failed; + } + for (j =3D 0; j < VMU_DIR_ENTRIES_PER_BLOCK; j++) + { + u16 ino_read =3D le16_to_cpu(((u16 *) + bh->b_data)[j *=20 + VMU_DIR_RECORD_LEN16 +=20 + VMUFAT_FIRSTBLOCK_OFFSET16]); + if (ino_read =3D=3D ino) { + goto found; + } + } + } + error =3D -ENOENT; + goto failed; +found: + /* identified the correct directory entry */ + offsetindir =3D j * VMU_DIR_RECORD_LEN; + inode->i_ctime_sec =3D inode->i_mtime_sec =3D + vmufat_get_date(bh, + offsetindir + VMUFAT_FILE_DATEOFFSET); + /* Execute if a game, write if not copy protected */ + inode->i_mode &=3D ~(S_IWUGO | S_IXUGO); + inode->i_mode |=3D S_IRUGO; + /* Mode - is it write protected? */ + if ((((u8 *) bh->b_data)[0x01 + offsetindir] =3D=3D 0x00) & + ~(sb->s_flags & SB_RDONLY)) { + inode->i_mode |=3D S_IWUSR; + } + /* Is file executible - ie a game */ + if ((((u8 *) bh->b_data)[offsetindir] =3D=3D 0xcc) & + ~(sb->s_flags & SB_NOEXEC)) { + inode->i_mode |=3D S_IXUSR; + } + inode->i_blocks =3D + le16_to_cpu(((u16 *) bh->b_data) + [offsetindir / 2 + 0x0C]); + inode->i_size =3D inode->i_blocks * sb->s_blocksize; + inode->i_mapping->a_ops =3D + &vmufat_address_space_operations; + inode->i_op =3D &vmufat_inode_operations; + inode->i_fop =3D &vmufat_file_operations; + error =3D vmufat_list_blocks(inode); + if (error) + goto failed; + } + ktime_get_coarse_real_ts64(¤t_time); + inode->i_atime_sec =3D current_time.tv_sec; + 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 =3D 0; + kfree(sb->s_fs_info); +} + +static void vmufat_count_freeblocks(struct super_block *sb, + struct kstatfs *kstatbuf) +{ + int i, free =3D 0; + struct memcard *vmudetails =3D sb->s_fs_info; + + /* Look through the FAT */ + for (i =3D 0; i < vmudetails->numblocks; i++) { + if (vmufat_get_fat(sb, i) =3D=3D VMUFAT_UNALLOCATED) { + free++; + } + } + kstatbuf->f_bfree =3D free; + kstatbuf->f_bavail =3D free; + kstatbuf->f_blocks =3D vmudetails->sb_bnum + 1; +} + +int vmufat_count_files(struct super_block *sb) +{ + int error =3D -1; + int files_found =3D 0; + struct buffer_head *bh =3D NULL; + struct memcard *vmudetails; + // get directory + vmudetails =3D sb->s_fs_info; + if (!vmudetails) { + return error; + } + for (int i =3D vmudetails->dir_bnum; + i > vmudetails->dir_bnum - vmudetails->dir_len; i--) { + brelse(bh); + bh =3D vmufat_sb_bread(sb, i); + if (!bh) { + error =3D -EIO; + return error; + } + for (int j =3D 0; j < VMU_DIR_ENTRIES_PER_BLOCK; j++) { + int record_offset =3D j * VMU_DIR_RECORD_LEN; + if (bh->b_data[record_offset] !=3D 0) { + files_found++; + } + } + } + return files_found; +} + +static int vmufat_count_file_space(struct super_block *sb) +{ + int error =3D -1; + int files_available =3D 0; + struct buffer_head *bh =3D NULL; + struct memcard *vmudetails; + // get directory + if (!sb) { + return error; + } + vmudetails =3D sb->s_fs_info; + if (!vmudetails) { + return error; + } + for (int i =3D vmudetails->dir_bnum; + i > vmudetails->numblocks; i--) { + brelse(bh); + bh =3D vmufat_sb_bread(sb, i); + if (!bh) { + error =3D -EIO; + return error; + } + for (int j =3D 0; j < VMU_DIR_ENTRIES_PER_BLOCK; j++) { + int record_offset =3D j * VMU_DIR_RECORD_LEN; + if (bh->b_data[record_offset] =3D=3D 0) { + files_available++; + } + } + } + return files_available; +} + +static int vmufat_statfs(struct dentry *dentry, struct kstatfs *kstatbuf) +{ + struct super_block *sb; + sb =3D dentry->d_sb; + vmufat_count_freeblocks(sb, kstatbuf); + kstatbuf->f_type =3D VMUFAT_SUPER_MAGIC; + kstatbuf->f_bsize =3D sb->s_blocksize; + kstatbuf->f_namelen =3D VMUFAT_NAMELEN; + kstatbuf->f_files =3D vmufat_count_files(sb); + kstatbuf->f_ffree =3D vmufat_count_file_space(sb); + if (kstatbuf->f_ffree > kstatbuf->f_bfree) { + kstatbuf->f_ffree =3D kstatbuf->f_bfree; + } + return 0; +} + +/* 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 =3D 0; + clear_inode(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 =3D NULL; + unsigned long inode_num; + int i, j;=20 + int found =3D 0; + int pos, pos16; + struct super_block *sb; + struct memcard *vmudetails; + struct timespec64 current_time; + sb =3D in->i_sb; + vmudetails =3D sb->s_fs_info; + + /* As most real world devices are flash we + * won't update the superblock every time */ + if (in->i_ino =3D=3D vmudetails->sb_bnum) + return 0; + if (in->i_ino =3D=3D VMUFAT_ZEROBLOCK) + inode_num =3D 0; + else + inode_num =3D in->i_ino; + + /* update the directory and inode details */ + /* Now search for the directory entry */ + mutex_lock(&vmudetails->mutex); + for (i =3D vmudetails->dir_bnum; + i > vmudetails->dir_bnum - vmudetails->dir_len; i--) { + bh =3D vmufat_sb_bread(sb, i); + if (!bh) { + mutex_unlock(&vmudetails->mutex); + return -ENXIO; + } + for (j =3D 0; j < VMU_DIR_ENTRIES_PER_BLOCK; j++) { + pos =3D j * VMU_DIR_RECORD_LEN; + pos16 =3D j * VMU_DIR_RECORD_LEN16; + if (bh->b_data[pos] =3D=3D 0) { + mutex_unlock(&vmudetails->mutex); + brelse(bh); + return -ENOENT; + } + if (le16_to_cpu(((u16 *)bh->b_data) + [pos16 + VMUFAT_FIRSTBLOCK_OFFSET16]) + =3D=3D inode_num) { + found =3D 1; + goto found; + } + } + brelse(bh); + } +found: + if (found =3D=3D 0) { + mutex_unlock(&vmudetails->mutex); + return -ENXIO; + } + /* BCD timestamp it */ + ktime_get_coarse_real_ts64(¤t_time); + in->i_mtime_sec =3D current_time.tv_sec; + vmufat_save_bcd(in, bh->b_data, pos); + mutex_unlock(&vmudetails->mutex); + mark_buffer_dirty(bh); + brelse(bh); + return 0; +} + +static int check_sb_format(struct buffer_head *bh) +{ + return (((u32 *) bh->b_data)[0] =3D=3D VMUFAT_SUPER_MAGIC && + ((u32 *) bh->b_data)[1] =3D=3D VMUFAT_SUPER_MAGIC && + ((u32 *) bh->b_data)[2] =3D=3D VMUFAT_SUPER_MAGIC && + ((u32 *) bh->b_data)[3] =3D=3D VMUFAT_SUPER_MAGIC); +} + +static void vmufat_populate_vmudata(struct memcard *vmudata, + struct buffer_head *bh, int test_sz) +{ + vmudata->sb_bnum =3D test_sz - 1; + vmudata->fat_bnum =3D + le16_to_cpu(((u16 *) bh->b_data)[VMU_LOCATION_FAT]); + vmudata->fat_len =3D + le16_to_cpu(((u16 *) bh->b_data)[VMU_LOCATION_FATLEN]); + vmudata->dir_bnum =3D + le16_to_cpu(((u16 *) bh->b_data)[VMU_LOCATION_DIR]); + vmudata->dir_len =3D + le16_to_cpu(((u16 *) bh->b_data)[VMU_LOCATION_DIRLEN]); + if (test_sz =3D=3D VMU_PHYS_SZ) { + vmudata->numblocks =3D + le16_to_cpu(((u16 *) bh->b_data)[VMU_LOCATION_USRLEN]); + if (vmudata->numblocks =3D=3D 0) { + vmudata->numblocks =3D + vmudata->dir_bnum; + } + } else { + /* 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 =3D + vmudata->dir_bnum; + } +} + +static int vmufat_get_size(struct super_block *sb, struct buffer_head **bh) +{ + int i; + for (i =3D VMUFAT_MIN_BLK; i <=3D VMUFAT_MAX_BLK; i =3D i * 2) { + brelse(*bh); + *bh =3D vmufat_sb_bread(sb, i - 1); + if (*bh =3D=3D NULL) { + return -EIO; + } + if (check_sb_format(*bh)) + break; + } + if (i > VMUFAT_MAX_BLK) { + return -ENOENT; + } + return i; +} + +static int vmufat_fill_super(struct super_block *sb, struct fs_context *fc) +{ + struct buffer_head *bh =3D NULL; + struct memcard *vmudata =3D NULL; + int test_sz; + struct inode *root_i; + int ret =3D 0; + + vmudata =3D kzalloc(sizeof(struct memcard), GFP_KERNEL); + if (!vmudata) { + return -ENOMEM; + } + sb->s_fs_info =3D vmudata; + sb_set_blocksize(sb, VMU_BLK_SZ); + sb->s_blocksize_bits =3D ilog2(VMU_BLK_SZ); + sb->s_magic =3D VMUFAT_SUPER_MAGIC; + sb->s_op =3D &vmufat_super_operations; + + /*=20 + * Hardware VMUs are 256 blocks in size but + * the specification allows for other sizes + */ + test_sz =3D vmufat_get_size(sb, &bh); + if (test_sz < VMUFAT_MIN_BLK) { + printk(KERN_ERR "VMUFAT: attempted to mount corrupted vmufat " + "or non-vmufat volume as vmufat.\n"); + ret =3D test_sz; + goto freebh_out; + } + + vmufat_populate_vmudata(vmudata, bh, test_sz); + mutex_init(&vmudata->mutex); + + root_i =3D vmufat_get_inode(sb, vmudata->sb_bnum); + if (!root_i) { + printk(KERN_ERR "VMUFAT: get root inode failed\n"); + ret =3D -ENOMEM; + goto freebh_out; + } + if (IS_ERR(root_i)) { + printk(KERN_ERR "VMUFAT: get root" + " inode failed - error 0x%lX\n", + PTR_ERR(root_i)); + ret =3D PTR_ERR(root_i); + goto freebh_out; + } + + sb->s_root =3D d_make_root(root_i); + if (!sb->s_root) { + ret =3D -EIO; + goto freebh_out; + } + else=20 + goto out; + +freebh_out: + brelse(bh); + kfree(vmudata); +out: + return ret; +} + +static void init_once(void *foo) +{ + struct vmufat_inode *vi =3D foo; + vi->nblcks =3D 0; + inode_init_once(&vi->vfs_inode); +} + +static int init_inodecache(void) +{ + vmufat_blist_cachep =3D kmem_cache_create("vmufat_block_cache", + sizeof(struct vmufat_block), 0, SLAB_RECLAIM_ACCOUNT, 0); + if (!vmufat_blist_cachep) { + printk(KERN_CRIT + "VMUFAT: Could not create block list cache.\n"); + return -ENOMEM; + } + vmufat_inode_cachep =3D kmem_cache_create("vmufat_inode_cache", + sizeof(struct vmufat_inode), 0, SLAB_RECLAIM_ACCOUNT, + init_once); + if (!vmufat_inode_cachep) { + printk(KERN_CRIT + "VMUFAT: Could not create inode cache.\n"); + kmem_cache_destroy(vmufat_inode_cachep); + return -ENOMEM; + } + return 0; +} + +static void destroy_inodecache(void) +{ + // ensure the caches are up to date before we try to destroy them + rcu_barrier(); + kmem_cache_destroy(vmufat_inode_cachep); + rcu_barrier(); + kmem_cache_destroy(vmufat_blist_cachep); +} + +static const struct super_operations vmufat_super_operations =3D { + .alloc_inode =3D vmufat_alloc_inode, + .destroy_inode =3D vmufat_destroy_inode, + .write_inode =3D vmufat_write_inode, + .evict_inode =3D vmufat_evict_inode, + .put_super =3D vmufat_put_super, + .statfs =3D vmufat_statfs, +}; + +static int vmufat_get_tree(struct fs_context *fc) +{ + return get_tree_bdev(fc, vmufat_fill_super); +} + +static const struct fs_context_operations vmufat_fs_context_ops =3D { + .get_tree =3D vmufat_get_tree, +}; + +static int vmufat_init_fs_context(struct fs_context *fc) +{ + fc->ops =3D &vmufat_fs_context_ops; + return 0; +} + +static struct file_system_type vmufat_fs_type =3D { + .owner =3D THIS_MODULE, + .name =3D "vmufat", + .kill_sb =3D kill_block_super, + .fs_flags =3D FS_REQUIRES_DEV, + .init_fs_context =3D vmufat_init_fs_context, +}; + +static int __init init_vmufat_fs(void) +{ + int err; + err =3D init_inodecache(); + if (err) + return err; + else + return register_filesystem(&vmufat_fs_type); +} + +static void __exit exit_vmufat_fs(void) +{ + destroy_inodecache(); + // ensure all caches removed before we disappear + rcu_barrier(); + 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 "); +MODULE_LICENSE("GPL"); --=20 2.43.0 From nobody Mon Jun 15 13:56:23 2026 Received: from mail-wr1-f46.google.com (mail-wr1-f46.google.com [209.85.221.46]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id A5D4838B7B0 for ; Sat, 11 Apr 2026 15:12:15 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.221.46 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775920338; cv=none; b=D7B0kIy9eS/Kd3xGuIL41aNxNxTjYUookj/LKY08PGSidfQOofvz7O9NFchLFbvbc5GAPyxztMDdU7Yv5NpO78Pnw+rST9M4sPPxeeUmg/rVyrWqNYTy+P+x+d9FgSb5afb2Ro3r/kiqjUZmJqKPDflQdttD9R7asE9E0b2CTfI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775920338; c=relaxed/simple; bh=b+uC1Ty3SN4Mvz2XHXtIiBgCvrcu1N8V2jPk1JPP+dg=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=guSe3X+IByJ1KGI9DwXRByw7HLsVNbW4kiDO7MDS8nTCAj3KJyu2eTHtXVsPGxLkadRbteN+TE+hsq1Hq55C7PJwDjKBNaI1OEv0aMbQWZPedpNYz6bcZjfdskfgkzcz/kfVhPD5CIA9LkHqHSZZC44RLdG7zwZ9aEeItQGJQHI= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=sPYdmxw/; arc=none smtp.client-ip=209.85.221.46 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="sPYdmxw/" Received: by mail-wr1-f46.google.com with SMTP id ffacd0b85a97d-43b8982c2f4so1784457f8f.2 for ; Sat, 11 Apr 2026 08:12:15 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1775920334; x=1776525134; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=bwFDWCw2g3Vd0JKkioInvJjYYRvipGaOS8ApK7MbSoU=; b=sPYdmxw/nySLhej5KaicytOzJ6gowSqGsew6jW4j0xTtrCKSaQLspRXYbMo5+4B8bx bMs01sKUQZPhe0olNDalqaO9SLYh+G9aUlXhq99REm9YeymR/Dnb260LA9olevkbZXaR fATq/KMK0Y365GkgjinCIdbsNtAIQPFv85VnfmfRu8WOVK0LxayV1rlKKlOjBAcVHI1x 4tZIxZLUqJfudo9qZWtbg6+5p2Q2FoUradvaJFRCgxB59bUcofHrWH/xxHXOVirGsVr0 v2ixSW8HtV1v22JoOaoImNVTWm+nMQArI3rk1GHkcWaDJO6aioz9o5qbZfOmz+XsJA4I v5mA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1775920334; x=1776525134; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=bwFDWCw2g3Vd0JKkioInvJjYYRvipGaOS8ApK7MbSoU=; b=GcETQtV9dCd9WmhNM7gp/zm6M2J1yifQ2D0iQR3Bt3CfUFsUlI4xlsAOkvqAsi47jk 8m+RHECBI4q8FZvU5efdpMMJghqbNfpZZniq7FEZ5Z4fm+sTdaZ5BqlVhZ8SlIbw3/10 zmeUYzEHgOp96U16lrPmJd94Eo0hvIy7Bz9fmMSojsnN1uXmCtwuUAZinY17jLp/dZTV RXmtdFqFhNMCXmrvbEq4kCn2l9eBobVVkm44NvfdzRQ+VyuiGHOj9ONP07HXbdSfIwJX 3l7UnPJiE6Zseb4uiu4hmcdNyIScAItM+rWzUAzEquOof8c+3GG0vy8IhYkcw+OFgstO /xug== X-Forwarded-Encrypted: i=1; AJvYcCW31DhJDbu2xLm2kq6EeVSFZG75GmsRKydJSIyMnh8rllP9e6z/1lB0hXegMKCN0mHmHoiDOIjSCRiW2iI=@vger.kernel.org X-Gm-Message-State: AOJu0YzymWetARqNGpm/i8DZs/RUmp6vEqthDXjkf34BxB2nmJCgH6Uq LWOgzlkTiZ2+pdy2ZpfyJu1c1F6MGPoAFb5YK9V01ygxI9lJe9O48AZn X-Gm-Gg: AeBDievGpsuot+4iujgE6ewjjSfAuvW7Nm+0nZWlE07iFTgRr2d35sGyIf/dnq/hBor zhLPWV6UtErDnMu3f4H0Ng6GbIaCCzt6UMcUm4uCp/z213DpobVXbsBiiX22PRWupcSGLLFw8qy G6zcSkKBc3h3nIbBFL6vx2Wttnat3ACToAfZr+I/qxbNx4HQ35VFxAm1ZQPQUo6TOyNMWxDlhpY uKith65m7mjBVAFlLshZ7flwSEMnVPuXD0BECVcvOKEtnTV1U7Sn3aZUvxoR7hMtBH5DcKsn7Se 7cDWSi3JNRid4G+SyRf8qvM5jk9zc4ZcWng7SlTpXtS+ZMjY5GX/Q93PmodC3x0QGWbDnZBs8SV KEBTrVT5KMW7ouBrEDayxMEItL4lrKcmt3/GUtmBnGRBVrQNtf9qykKarVT4tBXpistN0ukyiIg pC43H3SwdwRlWhGBxyY+Y73g+AZ/lSmwjcXTfUuxIG4O2wpG/MsROjSwua9IeDansxjFgRvs6U6 puWVBkAIN66xgllcFav X-Received: by 2002:a05:6000:2f86:b0:43b:87bf:89cc with SMTP id ffacd0b85a97d-43d642cb604mr10514527f8f.49.1775920332569; Sat, 11 Apr 2026 08:12:12 -0700 (PDT) Received: from localhost.localdomain ([2a02:c7c:37d6:c100:81d8:4e83:a60e:1a81]) by smtp.googlemail.com with ESMTPSA id ffacd0b85a97d-43d63e5c981sm17167835f8f.33.2026.04.11.08.12.11 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 11 Apr 2026 08:12:12 -0700 (PDT) From: Adrian McMenamin To: Linux FS development Cc: Adrian McMenamin , Linux kernel mailing list Subject: [PATCH 2/5 -v2] Add vmufat inode.c Date: Sat, 11 Apr 2026 16:11:36 +0100 Message-ID: <20260411151155.321214-3-adrianmcmenamin@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260411151155.321214-1-adrianmcmenamin@gmail.com> References: <20260411151155.321214-1-adrianmcmenamin@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" inode.c supports inode operations on the VMUFAT filesystem. There are no physical inodes on VMUFAT, as files are referenced via a file allocation table. The file system is not particularly sophisticated and many operations are executed by generic functions. This code does, though, oversee creation of new files on the volume, their removal and reading files off the volume. Physical VMUs use flash and so, as a general rule, the filesystem code avoi= ds excessive writes to the medium, though this may not be an issue for VMUFAT volumes on other media. This code also handles timestamping of files, and uses code that will work = on both 32- and 64-bit systems. Signed-off-by: Adrian McMenamin --- fs/vmufat/inode.c | 1017 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1017 insertions(+) create mode 100644 fs/vmufat/inode.c diff --git a/fs/vmufat/inode.c b/fs/vmufat/inode.c new file mode 100644 index 000000000000..62f3e371393e --- /dev/null +++ b/fs/vmufat/inode.c @@ -0,0 +1,1017 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * VMUFAT file system + * + * Copyright (C) 2002-2012, 2025, 2026 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, U= SA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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; +extern struct kmem_cache *vmufat_blist_cachep; +/* Linear day numbers of the respective 1sts in non-leap years. */ +int day_n[] =3D {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334}; + +static struct dentry *vmufat_inode_lookup(struct inode *in, struct dentry = *dent, + unsigned int ignored) +{ + struct super_block *sb; + struct memcard *vmudetails; + struct buffer_head *bh =3D NULL; + struct inode *ino; + int i, j; + int error =3D 0; + + if (dent->d_name.len > VMUFAT_NAMELEN) { + return ERR_PTR(-ENAMETOOLONG); + } + sb =3D in->i_sb; + vmudetails =3D sb->s_fs_info; + + for (i =3D vmudetails->dir_bnum; + i > vmudetails->dir_bnum - vmudetails->dir_len; i--) { + brelse(bh); + bh =3D vmufat_sb_bread(sb, i); + if (!bh) { + return ERR_PTR(-EIO); + } + for (j =3D 0; j < VMU_DIR_ENTRIES_PER_BLOCK; j++) { + int record_offset =3D j * VMU_DIR_RECORD_LEN; + if (bh->b_data[record_offset] =3D=3D 0) + goto insert_negative; + if (memcmp(dent->d_name.name, + bh->b_data + record_offset + + VMUFAT_NAME_OFFSET, dent->d_name.len) =3D=3D 0) { + ino =3D vmufat_get_inode(sb, + le16_to_cpu(((u16 *) bh->b_data) + [(record_offset / 2) + + VMUFAT_FIRSTBLOCK_OFFSET16])); + if (IS_ERR(ino)) { + error =3D PTR_ERR(ino); + goto out; + } else if (!ino) { + error =3D -EACCES; + goto out; + } + d_add(dent, ino); + goto out; + } + } + } +insert_negative: + d_add(dent, NULL); /* Did not find the file */ +out: + brelse(bh); + return ERR_PTR(error); +} + + +/* Search for free block in inclusive range */ +static int vmufat_get_freeblock(int start, int end, struct buffer_head *bh) +{ + int i, ret =3D -1; + __le16 fatdata; + if (start < end) { + for (i =3D start; i <=3D end; i++) { + fatdata =3D le16_to_cpu(((u16 *)bh->b_data)[i]); + if (fatdata =3D=3D VMUFAT_UNALLOCATED) { + ret =3D i; + break; + } + } + } else { + for (i =3D start; i >=3D end; i--) { + fatdata =3D le16_to_cpu(((u16 *)bh->b_data)[i]); + if (fatdata =3D=3D VMUFAT_UNALLOCATED) { + ret =3D i; + break; + } + } + } + return ret; +} + +/* + * Find a block marked free in the FAT - for exec files + * Dreamcasts expect data and exec files to be stored differently + * - we attempt to replicate that with *enforcing* a policy + */ +static int vmufat_find_free_forward(struct super_block *sb) +{ + struct memcard *vmudetails; + int testblk, fatblk, i; + struct buffer_head *bh_fat; + vmudetails =3D sb->s_fs_info; + fatblk =3D vmudetails->fat_bnum; + for (i =3D 0; i < vmudetails->fat_len; i++) { + bh_fat =3D vmufat_sb_bread(sb, fatblk + i); + if (!bh_fat) { + return -EIO; + } + testblk =3D vmufat_get_freeblock(0, VMU_BLK_SZ16 - 1, bh_fat); + brelse(bh_fat); + if (testblk >=3D 0 && testblk + (i * VMU_BLK_SZ16) <=20 + vmudetails->numblocks) { + return testblk + (i * VMU_BLK_SZ16); + } + } + printk(KERN_INFO "VMUFAT: volume is full, cannot save game file\n"); + return -ENOSPC; +} + +/* And for data files*/ +static int vmufat_find_free_backward(struct super_block *sb) +{ + struct memcard *vmudetails; + int testblk; + int i, readblk; + struct buffer_head *bh_fat; + + vmudetails =3D sb->s_fs_info; + readblk =3D (vmudetails->numblocks - 1) % VMU_BLK_SZ16; + + for (i =3D vmudetails->fat_len - 1; i >=3D 0; i--) { + bh_fat =3D vmufat_sb_bread(sb, i + vmudetails->fat_bnum); + if (!bh_fat) { + return -EIO; + } + testblk =3D vmufat_get_freeblock(readblk, 0, bh_fat); + brelse(bh_fat); + if (testblk >=3D 0) { + return testblk + (i * VMU_BLK_SZ16); + } + readblk =3D VMU_BLK_SZ16 - 1; + } + printk(KERN_INFO "VMUFAT: volume is full, cannot save data file\n"); + return -ENOSPC; +} + +/* 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 =3D VMUFAT_ERROR; + struct memcard *vmudetails =3D sb->s_fs_info; + + /* which block in the FAT */ + offset =3D block / VMU_BLK_SZ16; + if (offset >=3D vmudetails->fat_len) + goto out; + /* fat_bnum points to lowest block in FAT */ + bufhead =3D vmufat_sb_bread(sb, offset + vmudetails->fat_bnum); + if (!bufhead) + goto out; + /* look inside the block */ + block_content =3D le16_to_cpu(((u16 *)bufhead->b_data) + [block % VMU_BLK_SZ16]); + brelse(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) +{ + struct buffer_head *bh; + int offset; + struct memcard *vmudetails =3D sb->s_fs_info; + + offset =3D block / VMU_BLK_SZ16; + if (offset >=3D vmudetails->fat_len) { + return -EINVAL; + } + bh =3D vmufat_sb_bread(sb, offset + vmudetails->fat_bnum); + if (!bh) { + return -EIO; + } + ((u16 *) bh->b_data)[block % VMU_BLK_SZ16] =3D cpu_to_le16(data); + mark_buffer_dirty(bh); + brelse(bh); + return 0; +} + +/* No real time clock or real time clock read fails */ +static void vmufat_save_bcd_no_rtc(struct inode *in, char *bh, int index_t= o_dir) +{ + u32 years, days, seconds; + unsigned char bcd_century, nl_day, bcd_month; + unsigned char u8year; + time64_t unix_date, substitute_date; + unix_date =3D in->i_mtime_sec; + substitute_date =3D unix_date; + do_div(substitute_date, SECONDS_PER_DAY); + days =3D (u32)substitute_date; /* cast not an issue for next 5 million ye= ars+ */ + years =3D days / DAYS_PER_YEAR; + /* 1 Jan gets 1 day later after every leap year */ + if ((years + 3) / 4 + DAYS_PER_YEAR * years >=3D days) + years--; + /* rebase days to account for leap years */ + days -=3D (years + 3) / 4 + DAYS_PER_YEAR * years; + // 2100 is not a leap year + if (years >=3D CENTURY22 + 1) { + days--; + } + /* 1 Jan is Day 1 */ + days++; + if (days =3D=3D (FEB28 + 1) && !(years % 4) && (years !=3D CENTURY22)) { + nl_day =3D days; + bcd_month =3D 2; + } else { + nl_day =3D (years % 4) || days <=3D FEB28 ? days : days - 1; + for (bcd_month =3D 0; bcd_month < 12; bcd_month++) { + if (day_n[bcd_month] > nl_day) + break; + } + } + + bcd_century =3D 19; + if (years > 29) + bcd_century +=3D 1 + (years - CENTURY21)/100; + + bh[index_to_dir + VMUFAT_DIR_CENT] =3D bin2bcd(bcd_century); + u8year =3D years + START_OF_EPOCH; /* account for epoch */ + if (u8year > 99) + u8year =3D u8year - 100; + + bh[index_to_dir + VMUFAT_DIR_YEAR] =3D bin2bcd(u8year); + bh[index_to_dir + VMUFAT_DIR_MONTH] =3D bin2bcd(bcd_month); + bh[index_to_dir + VMUFAT_DIR_DAY] =3D + bin2bcd(days - day_n[bcd_month - 1]); + substitute_date =3D unix_date; + do_div(substitute_date, SECONDS_PER_HOUR); + bh[index_to_dir + VMUFAT_DIR_HOUR] =3D + bin2bcd(do_div(substitute_date, HOURS_PER_DAY)); + seconds =3D do_div(unix_date, SIXTY_MINS_OR_SECS); + bh[index_to_dir + VMUFAT_DIR_MIN] =3D + bin2bcd(unix_date); + bh[index_to_dir + VMUFAT_DIR_SEC] =3D + bin2bcd(seconds); +} + +#ifdef CONFIG_RTC_CLASS +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)) + vmufat_save_bcd_no_rtc(in, bh, index_to_dir); + else { + bh[index_to_dir + VMUFAT_DIR_CENT] =3D bin2bcd((char)((now.tm_year + 190= 0)/100)); + bh[index_to_dir + VMUFAT_DIR_YEAR] =3D + bin2bcd((char)(now.tm_year % 100)); + bh[index_to_dir + VMUFAT_DIR_MONTH] =3D bin2bcd((char)(now.tm_mon + 1)); + bh[index_to_dir + VMUFAT_DIR_DAY] =3D bin2bcd((char)(now.tm_mday)); + bh[index_to_dir + VMUFAT_DIR_HOUR] =3D bin2bcd((char)(now.tm_hour)); + bh[index_to_dir + VMUFAT_DIR_MIN] =3D bin2bcd((char)(now.tm_min)); + bh[index_to_dir + VMUFAT_DIR_SEC] =3D bin2bcd((char)(now.tm_sec)); + bh[index_to_dir + VMUFAT_DIR_DOW] =3D bin2bcd((char)((now.tm_wday + 6)) + % DAYS_PER_WEEK); + } +} +#endif + +/* + * 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) +{ +#ifdef CONFIG_RTC_CLASS + struct rtc_device *rtc; + rtc =3D rtc_class_open("rtc0"); + if (!rtc) +#endif + vmufat_save_bcd_no_rtc(in, bh, index_to_dir); +#ifdef CONFIG_RTC_CLASS + else { + vmufat_save_bcd_rtc(rtc, in, bh, index_to_dir); + rtc_class_close(rtc); + } +#endif +} + +static int vmufat_allocate_inode(umode_t imode, + struct super_block *sb, struct inode *in) +{ + struct vmufat_inode *vin =3D VMUFAT_I(in); + int error =3D 0; + /* Executable files should be at the start of the volume if possible */ + if (imode & EXEC) { + vin->ft =3D GAME; + if (vmufat_get_fat(sb, 0) !=3D VMUFAT_UNALLOCATED) { + /* Warning for anyone concerned about playable games */ + printk(KERN_WARNING=20 + "VMUFAT: Warning cannot write excutable " + "file to block 0: already allocated.\n"); + } + else { + in->i_ino =3D VMUFAT_ZEROBLOCK; + return error; + } + error =3D vmufat_find_free_forward(sb); + } else { + vin->ft =3D DATA; + error =3D vmufat_find_free_backward(sb); + } + if (error =3D=3D 0) { + in->i_ino =3D VMUFAT_ZEROBLOCK; + } else if (error > 0) { + in->i_ino =3D error; + } + return error; +} + +static void vmufat_setup_inode(struct inode *in, struct super_block *sb) +{ + simple_inode_init_ts(in); + in->i_blocks =3D 1; + in->i_sb =3D sb; + in->i_op =3D &vmufat_inode_operations; + in->i_fop =3D &vmufat_file_operations; + in->i_mapping->a_ops =3D &vmufat_address_space_operations; + insert_inode_hash(in); +} + +static void vmu_handle_zeroblock(int recno, struct buffer_head *bh, int in= o) +{ + /* offset and header offset settings */ + if (ino !=3D VMUFAT_ZEROBLOCK) { + ((u16 *) bh->b_data)[recno + VMUFAT_START_OFFSET16] =3D + cpu_to_le16(ino); + ((u16 *) bh->b_data)[recno + VMUFAT_HEADER_OFFSET16] =3D 0; + } else { + ((u16 *) bh->b_data)[recno + VMUFAT_START_OFFSET16] =3D 0; + ((u16 *) bh->b_data)[recno + VMUFAT_HEADER_OFFSET16] =3D + cpu_to_le16(1); + } +} + +static void vmu_write_name(int recno, struct buffer_head *bh, char *name, + int len) +{ + if ((bh->b_size - recno - VMUFAT_NAME_OFFSET) >=3D VMUFAT_NAMELEN && + (bh->b_size - recno - VMUFAT_NAME_OFFSET) >=3D 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 mnt_idmap *idmap, struct inode *dir, + struct dentry *de, umode_t imode, bool excl) +{ + int i, j, entry, error =3D 0, freeblock; + struct inode *inode; + struct super_block *sb; + struct memcard *vmudetails; + struct buffer_head *bh =3D NULL; + + sb =3D dir->i_sb; + vmudetails =3D sb->s_fs_info; + inode =3D new_inode(sb); + if (!inode) { + error =3D -ENOSPC; + goto out; + } + + mutex_lock(&vmudetails->mutex); + freeblock =3D vmufat_allocate_inode(imode, sb, inode); + if (freeblock < 0) { + mutex_unlock(&vmudetails->mutex); + error =3D freeblock; + goto clean_inode; + } + /* mark as single block file - may grow later */ + error =3D vmufat_set_fat(sb, freeblock, VMUFAT_FILE_END); + mutex_unlock(&vmudetails->mutex); + if (error) + goto clean_inode; + inode_init_owner(&nop_mnt_idmap, inode, dir, imode); + vmufat_setup_inode(inode, sb); + + /* Write to the directory + * Now search for space for the directory entry */ + mutex_lock(&vmudetails->mutex); + for (i =3D vmudetails->dir_bnum; + i > vmudetails->dir_bnum - vmudetails->dir_len; i--) { + brelse(bh); + bh =3D vmufat_sb_bread(sb, i); + if (!bh) { + mutex_unlock(&vmudetails->mutex); + error =3D -EIO; + goto clean_fat; + } + for (j =3D 0; j < VMU_DIR_ENTRIES_PER_BLOCK; j++) { + entry =3D j * VMU_DIR_RECORD_LEN; + if (((bh->b_data)[entry]) =3D=3D 0) { + goto dir_space_found; + } + } + } + goto clean_fat; +dir_space_found: + /* Have the directory entry + * so now update it */ + if (imode & EXEC) + bh->b_data[entry] =3D VMU_GAME; + else + bh->b_data[entry] =3D VMU_DATA; + + /* copy protection settings */ + if (bh->b_data[entry + 1] !=3D (char) NOCOPY) + bh->b_data[entry + 1] =3D (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] =3D + cpu_to_le16(inode->i_blocks); + mark_buffer_dirty(bh); + brelse(bh); + + error =3D vmufat_list_blocks(inode); + if (error) + goto clean_fat; + mutex_unlock(&vmudetails->mutex); + mark_inode_dirty(inode); + 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_ERR "VMUFAT: inode creation fails with error" + " %i\n", error); + return error; +} + +static int vmufat_readdir(struct file *filp, struct dir_context *ctx) +{ + int filenamelen, index, j, k; + int error =3D 0; + struct vmufat_file_info *saved_file =3D NULL; + struct dentry *dentry; + struct inode *inode; + struct super_block *sb; + struct memcard *vmudetails; + struct buffer_head *bh =3D NULL; + + inode =3D file_inode(filp); + dentry =3D filp->f_path.dentry; + sb =3D inode->i_sb; + vmudetails =3D sb->s_fs_info; + index =3D ctx->pos; + /* handle . for this directory and .. for parent */ + switch ((unsigned int) ctx->pos) { + case 0: + error =3D ctx->actor(ctx, ".", 1, index++, inode->i_ino, DT_DIR); + ctx->pos++; + if (error < 0) + return error; + fallthrough; + case 1: + error =3D ctx->actor(ctx, "..", 2, index++, + dentry->d_parent->d_inode->i_ino, DT_DIR); + ctx->pos++; + if (error < 0) + return error; + default: + break; + } + + saved_file =3D + kmalloc(sizeof(struct vmufat_file_info), GFP_KERNEL); + if (!saved_file) { + return -ENOMEM; + } + + for (j =3D vmudetails->dir_bnum - + (index - 2) / VMU_DIR_ENTRIES_PER_BLOCK; + j > vmudetails->dir_bnum - vmudetails->dir_len; j--) { + brelse(bh); + bh =3D vmufat_sb_bread(sb, j); + if (!bh) { + return -EIO; + } + for (k =3D (index - 2) % VMU_DIR_ENTRIES_PER_BLOCK; + k < VMU_DIR_ENTRIES_PER_BLOCK; k++) { + int pos, pos16; + pos =3D k * VMU_DIR_RECORD_LEN; + pos16 =3D k * VMU_DIR_RECORD_LEN16; + saved_file->ftype =3D bh->b_data[pos]; + if (saved_file->ftype =3D=3D 0) + goto finish; + saved_file->fblk =3D + le16_to_cpu(((u16 *) bh->b_data) + [pos16 + VMUFAT_START_OFFSET16]); + if (saved_file->fblk =3D=3D 0) + saved_file->fblk =3D VMUFAT_ZEROBLOCK; + if ((bh->b_size - pos - VMUFAT_NAME_OFFSET) >=3D + VMUFAT_NAMELEN) { + memcpy(saved_file->fname, + bh->b_data + pos + VMUFAT_NAME_OFFSET, + VMUFAT_NAMELEN); + } + filenamelen =3D strnlen(saved_file->fname, + VMUFAT_NAMELEN); + error =3D ctx->actor(ctx, saved_file->fname, filenamelen, + index++, saved_file->fblk, DT_REG); + ctx->pos++; + if (error < 0) + goto finish; + } + } +finish: + ctx->pos =3D index; + kfree(saved_file); + brelse(bh); + 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 =3D -EINVAL; + struct vmufat_block *iter, *iter2, *vbl; + u16 fatdata; + + vi =3D VMUFAT_I(in); + if (!vi) + return error; + sb =3D in->i_sb; + ino =3D in->i_ino; + vmudetails =3D sb->s_fs_info; + error =3D 0; + nextblock =3D ino; + if (nextblock =3D=3D VMUFAT_ZEROBLOCK) + nextblock =3D 0; + + /* Delete any previous list of blocks */ + list_for_each_entry_safe(iter, iter2, &vi->blocks, b_list) { + list_del(&iter->b_list); + kmem_cache_free(vmufat_blist_cachep, iter); + } + vi->nblcks =3D 0; + do { + vbl =3D kmem_cache_alloc(vmufat_blist_cachep, + GFP_KERNEL); + if (!vbl) { + error =3D -ENOMEM; + goto unwind_out; + } + INIT_LIST_HEAD(&vbl->b_list); + vbl->bno =3D nextblock; + list_add_tail(&vbl->b_list, &vi->blocks); + vi->nblcks++; + + /* Find next block in the FAT - if there is one */ + fatdata =3D vmufat_get_fat(sb, nextblock); + if (fatdata =3D=3D VMUFAT_UNALLOCATED) { + printk(KERN_ERR "VMUFAT: FAT table appears to have" + " been corrupted.\n"); + error =3D -EIO; + goto unwind_out; + } + if (fatdata =3D=3D VMUFAT_FILE_END) + break; + nextblock =3D fatdata; + } while (1); + return error; + +unwind_out: + list_for_each_entry_safe(iter, iter2, &vi->blocks, b_list) { + list_del(&iter->b_list); + kmem_cache_free(vmufat_blist_cachep, iter); + } + return error; +} + +static int vmufat_clean_fat(struct super_block *sb, int inum) +{ + int error =3D 0; + u16 fatword, nextword; + + nextword =3D inum; + do { + fatword =3D vmufat_get_fat(sb, nextword); + if (fatword =3D=3D VMUFAT_ERROR) { + error =3D -EIO; + printk(KERN_ERR + "VMUFAT: Failure while cleaning FAT.\n"); + break; + } + error =3D vmufat_set_fat(sb, nextword, VMUFAT_UNALLOCATED); + if (error) + break; + if (fatword =3D=3D VMUFAT_FILE_END) + break; + nextword =3D 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 =3D NULL, *bh_old =3D NULL; + struct super_block *sb; + struct memcard *vmudetails; + int i, j, k, l, x; + bool found, first =3D true; + + if (in->i_ino =3D=3D VMUFAT_ZEROBLOCK) + in->i_ino =3D 0; + sb =3D in->i_sb; + vmudetails =3D 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; + } + + 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 walk through entries + * to find the appropriate entry */ + for (x =3D vmudetails->dir_bnum; + x > vmudetails->dir_bnum - vmudetails->dir_len; x--) { + brelse(bh); + bh =3D vmufat_sb_bread(sb, x); + if (!bh) { + mutex_unlock(&vmudetails->mutex); + goto failure; + } + for (j =3D 0; j < VMU_DIR_ENTRIES_PER_BLOCK; j++) { + if (bh->b_data[j * VMU_DIR_RECORD_LEN] =3D=3D 0) { + mutex_unlock(&vmudetails->mutex); + brelse(bh); + goto failure; + } + if (le16_to_cpu(((u16 *) bh->b_data) + [j * VMU_DIR_RECORD_LEN16 + + VMUFAT_FIRSTBLOCK_OFFSET16]) =3D=3D in->i_ino) { + found =3D true; + goto found_bk; + } + } + } +found_bk: + // actually found nothing - an error or failure of some sort + if (!found) { + brelse(bh); + goto failure; + } + // have found the directory entry, so first we clean it out + for (i =3D 0; i < VMU_DIR_RECORD_LEN; i++) { + bh->b_data[i + (j * VMU_DIR_RECORD_LEN)] =3D 0; + } + mark_buffer_dirty(bh); + // now test if there are further records + found =3D false; + for (i =3D x; i > vmudetails->dir_bnum - vmudetails->dir_len; i--) { + brelse(bh_old); + bh_old =3D vmufat_sb_bread(sb, i); + if (first) { + for (l =3D j; l < VMU_DIR_ENTRIES_PER_BLOCK; l++) { + if (first) { + first =3D false; + continue; + } + if (bh_old->b_data[l * VMU_DIR_RECORD_LEN] + =3D=3D 0) { + found =3D true; + goto found_final; + } + } + } else { + for (l =3D 0; l < VMU_DIR_ENTRIES_PER_BLOCK; l++) { + if (bh_old->b_data[l * VMU_DIR_RECORD_LEN] + =3D=3D 0) { + found =3D true; + goto found_final; + } + } + } + } +found_final: + if (!found) { + // cleaned entry was final entry + mutex_unlock(&vmudetails->mutex); + brelse(bh_old); + brelse(bh); + return; + } + // copy the final entry into the empty slot and zero the final entry + if (l =3D=3D VMU_DIR_ENTRIES_PER_BLOCK) { + l =3D 0; + brelse(bh_old); + bh_old =3D vmufat_sb_bread(sb, i - 1); + } else { + l--; + } + for (k =3D 0; k < VMU_DIR_RECORD_LEN; k++) { + bh->b_data[k + (j * VMU_DIR_RECORD_LEN)] =3D + bh_old->b_data[k + (l * VMU_DIR_RECORD_LEN)]; + bh_old->b_data[k + (l * VMU_DIR_RECORD_LEN)] =3D 0; + } + mark_buffer_dirty(bh); + mark_buffer_dirty(bh_old); + mutex_unlock(&vmudetails->mutex); + brelse(bh); + brelse(bh_old); + return; + +failure: + printk(KERN_WARNING "VMUFAT: Failure to read volume," + " 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 + * supplied here + */ +static int vmufat_unlink(struct inode *dir, struct dentry *dentry) +{ + struct inode *in; + in =3D dentry->d_inode; + vmufat_remove_inode(in); + return 0; +} + +/* Update the directory record */ +static int vmufat_increment_filesize(struct inode *inode) +{ + struct super_block *sb =3D inode->i_sb; + struct buffer_head *bh =3D NULL; + struct memcard *vmudetails =3D sb->s_fs_info; + unsigned long ino_num =3D inode->i_ino; + int error =3D 0; + + if (ino_num =3D=3D VMUFAT_ZEROBLOCK) { + ino_num =3D 0; + } + + for (int i =3D vmudetails->dir_bnum; + i > vmudetails->dir_bnum - vmudetails->dir_len; i--) { + brelse(bh); + bh =3D vmufat_sb_bread(sb, i); + if (!bh) { + error =3D -EIO; + return error; + } + for (int j =3D 0; j < VMU_DIR_ENTRIES_PER_BLOCK; j++) { + int record_offset =3D j * VMU_DIR_RECORD_LEN; + if (bh->b_data[record_offset] =3D=3D 0) { + continue; + } + if (le16_to_cpu(((u16 *) bh->b_data) + [(j * VMU_DIR_RECORD_LEN16) + + VMUFAT_FIRSTBLOCK_OFFSET16]) !=3D ino_num) { + continue; + } + int current_count =3D le16_to_cpu(((u16 *) bh->b_data) + [(j * VMU_DIR_RECORD_LEN16) + VMUFAT_SIZE_OFFSET16]); + current_count++; + ((u16 *) bh->b_data) + [(j * VMU_DIR_RECORD_LEN16) + VMUFAT_SIZE_OFFSET16] =3D + cpu_to_le16(current_count); + mark_buffer_dirty(bh); + goto done; + } + } +done: + brelse(bh); + return error; +} + +static int vmufat_get_block(struct inode *inode, sector_t iblock, + struct buffer_head *bh_result, int create) +{ + struct vmufat_inode *vin; + struct list_head *vlist; + struct vmufat_block *vblk; + struct super_block *sb; + struct memcard *vmudetails; + int cural; + int finblk, nxtblk, exeblk; + struct list_head *iter; + sector_t cntdwn =3D iblock; + sector_t phys; + int error =3D -EINVAL; + + vin =3D VMUFAT_I(inode); + if (!vin || vin->nblcks <=3D 0) + return -EINVAL; + vlist =3D &vin->blocks; + sb =3D inode->i_sb; + vmudetails =3D sb->s_fs_info; + + if (iblock < vin->nblcks) { + /* block is already here so read it into the buffer head */ + list_for_each(iter, vlist) { + if (cntdwn-- =3D=3D 0) + break; + } + vblk =3D list_entry(iter, struct vmufat_block, b_list); + clear_buffer_new(bh_result); + error =3D 0; + phys =3D 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 =3D vin->nblcks; + list_for_each(iter, vlist) { + if (cural-- =3D=3D 1) + break; + } + vblk =3D list_entry(iter, struct vmufat_block, b_list); + finblk =3D vblk->bno; + + mutex_lock(&vmudetails->mutex); + /* Exec files have to be linear on real VMUs */ + /* But this is policy so we warn but don't fail */ + if (inode->i_ino =3D=3D 0) { + exeblk =3D vmufat_get_fat(sb, finblk + 1); + if (exeblk !=3D VMUFAT_UNALLOCATED) { + mutex_unlock(&vmudetails->mutex); + printk(KERN_WARNING "VMUFAT: Cannot allocate linear " + "space needed for executible\n"); + mutex_lock(&vmudetails->mutex); + nxtblk =3D vmufat_find_free_forward(sb); + if (nxtblk < 0) { + mutex_unlock(&vmudetails->mutex); + return nxtblk; + } + } + else { + nxtblk =3D finblk + 1; + } + } else { + if (vin->ft =3D=3D GAME) { + nxtblk =3D vmufat_find_free_forward(sb); + } else { + nxtblk =3D vmufat_find_free_backward(sb); + } + if (nxtblk < 0) { + mutex_unlock(&vmudetails->mutex); + return nxtblk; + } + } + error =3D vmufat_set_fat(sb, finblk, nxtblk); + if (!error) { + error =3D vmufat_increment_filesize(inode); + } + if (error) { + mutex_unlock(&vmudetails->mutex); + return error; + } + error =3D vmufat_set_fat(sb, nxtblk, VMUFAT_FILE_END); + mutex_unlock(&vmudetails->mutex); + if (error) + return error; + error =3D vmufat_list_blocks(inode); + mark_inode_dirty(inode); + if (error) + return error; + set_buffer_new(bh_result); + phys =3D nxtblk; + error =3D 0; +got_it: + map_bh(bh_result, sb, phys); +out: + return error; +} + +static int vmufat_writepages(struct address_space *mapping, struct writeba= ck_control *wbc) +{ + return mpage_writepages(mapping, wbc, vmufat_get_block); +} + +static int vmufat_write_begin(const struct kiocb *iocb, struct address_spa= ce *mapping, + loff_t pos, unsigned len, struct folio **foliop, void **fsdata) +{ + *foliop =3D NULL; + return block_write_begin(mapping, pos, len, foliop, vmufat_get_block); +} + +static int vmufat_read_folio(struct file *file, struct folio *folio) +{ + return block_read_full_folio(folio, vmufat_get_block); +} + +static int vmufat_getattr(struct mnt_idmap *idmap, const struct path *path, + struct kstat *stat, u32 request_mask, unsigned int query_flags) +{ + struct super_block *sb; + struct memcard *vmudetails; + struct inode *inode =3D d_inode(path->dentry); + generic_fillattr(&nop_mnt_idmap, request_mask, inode, stat); + /* correct for superblock to give directory size */ + /* as all metadata combined */ + sb =3D inode->i_sb; + if (sb) { + vmudetails =3D sb->s_fs_info; + if (vmudetails && vmudetails->sb_bnum =3D=3D inode->i_ino) { + stat->size =3D (vmudetails->fat_len + vmudetails->dir_len + 1) *=20 + VMU_BLK_SZ; + stat->nlink =3D vmufat_count_files(sb) + 2; + } + } + return 0; +} + +const struct address_space_operations + vmufat_address_space_operations =3D { + .read_folio =3D vmufat_read_folio, + .writepages =3D vmufat_writepages, + .write_begin =3D vmufat_write_begin, + .write_end =3D generic_write_end, +}; + +const struct inode_operations vmufat_inode_operations =3D { + .lookup =3D vmufat_inode_lookup, + .create =3D vmufat_inode_create, + .unlink =3D vmufat_unlink, + .getattr =3D vmufat_getattr, +}; + +const struct file_operations vmufat_file_dir_operations =3D { + .read =3D generic_read_dir, + .iterate_shared =3D vmufat_readdir, + .fsync =3D generic_file_fsync, + .llseek =3D generic_file_llseek, +}; + +const struct file_operations vmufat_file_operations =3D { + .llseek =3D generic_file_llseek, + .read_iter =3D generic_file_read_iter, + .write_iter =3D generic_file_write_iter, + .fsync =3D generic_file_fsync, +}; --=20 2.43.0 From nobody Mon Jun 15 13:56:23 2026 Received: from mail-wr1-f49.google.com (mail-wr1-f49.google.com [209.85.221.49]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id DBC3D38B7DB for ; Sat, 11 Apr 2026 15:12:15 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.221.49 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775920337; cv=none; b=QV47umYq7XNPQOxWsnQZjOxJnaOIzziprk63KRghWitJNB5M9dlacKh8wwZovejZoI0T9FrefxSgPaNSDpvVZ0sFP4HlLGi8M6TUPzhZeelGibpnjqxZU85tCHLr/TsbfTUWD2Re+J5DFzkYCh9O7IA1BqhMqB2jzNFYPK0QIv8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775920337; c=relaxed/simple; bh=RC9ve88mLXnSmouMvk6p6ZQIoV10ehxQDLmv7e6C0vg=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=BiFh0o/qAzpLb3s6ZzzooJVpE5ZTc1cMwm9B2vIVZIv3M2NJeA2hllRb1R9ObDO4rdZ8VcqaIZ0kbWA5M3ut7HBPB6nlP93ArAbEVlA8Fj3lCQLtTF7NSlEPCoIz+m3Uk5AcI9oV5FLHPuPzKDANj0G8hKr1WWotBUJs3p2co3o= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=q24cfckX; arc=none smtp.client-ip=209.85.221.49 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="q24cfckX" Received: by mail-wr1-f49.google.com with SMTP id ffacd0b85a97d-43d17bb1c1dso2384485f8f.2 for ; Sat, 11 Apr 2026 08:12:15 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1775920334; x=1776525134; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=2tdtenha4z2OiHm8HeH2S1Akton2Og6DEskpUOZMq1w=; b=q24cfckXQSBBv1eiZ60hE0bbR+wI6wE/llM+7vgXL8MJrCzcmD4VB0GslTxQKxaohn /lkJuiTgXONpPpvBdkuUpfOsCNztHbZLS7NvdT7oMxqG8G5hV/hzBcBDvryAwDWtaHDf CcQFfk2VU4DrCsOvK+rneM5XIYU+tUVID6brD9LAx2awf+GcIQ6hagMiBxJEO8IzHJ3b RyxnfDRHGhn+h+C4CD5NOFidzE4Rr1cJaka5KbvoJSzlJ6vza6jXPtb7RNPYA2nhe5K2 8XVFzy9NRhhiRdEZEPcpbYbhsGrOKIkeJS/eJnZ5dywgwFyVIJYSZLr8yrYm3WJItZSV MI2g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1775920334; x=1776525134; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=2tdtenha4z2OiHm8HeH2S1Akton2Og6DEskpUOZMq1w=; b=qq+MTucjCWWffErkUePWGA3w36YY/5n1irGh2eoCKpxf1CgFgnI/0rpg0UCWrXyHjh 8X3J8IpRxTEvq3ol30V5cy6AGWv2FAPueFtZGHgZc0JoLeOIgnHkpQVfJhYWOY16UX1w 1Cqx+acr0vIkwy+qAL34Z9y2xsf5i5cRsI9SbTbVFQq5JD8FOMk/192uR9qiI8Y3TuMX RD3oWyHxirmVC+sLL0jcHYMMB7/6LPq/xZ0J+NB2XRcBA3183VhoCHkb+Dk1GXmqd5Jw a1Yx0P3iTHPv+KPL44oLsk66kNoegaXNHvK8OtDvQuslC54sUbhao/r/eW4I6blhhyAK 9C8g== X-Forwarded-Encrypted: i=1; AJvYcCWYEsRCViXeGSd9eYZjHPtrym019pcG4FM8ryrr45TJHrzlvInpF8X8ZBxPBH7a2uYSijmNGvqiO9XhCNU=@vger.kernel.org X-Gm-Message-State: AOJu0YyqSSRJonTfNr6e9ikuM0IYWpBTZ3pPBNWMWMLLVT5fyRxWUoaQ CQwfqg6KpBbDK0bNseckRPAXyX5aTSFkXKkqtX3huWj+sZjuVnqRBaYHkSZB6g== X-Gm-Gg: AeBDievQegOqyb+aE8VDNC886rKNu+XPSAvZB69otrcCKYqweFTiUxpRgMBl+SPpUGZ INdIl6HW/nha2iJAt/J/xncvF6Q6SelF8SR5AFldv8pbrhKVPegVqLeeF3emJ96WjvTFjrnfyYt PvWMKWeUrNwLMYI7P4IkLmtLu2DR+ekkg78bDP+4AntMuoVLHwSQyChhEAcj09x2aw9YH5N8Na3 q/hH0UNsB/Pibv3VuXBIc/j637gMF0YGb3yhgynIKQT4oshxuHnGTm4FRQixQwKE182Pk6BkdaO qxR/Vxhb04+N0rrxTr9UQxr+VbEpTYsdmO+eCRikDUINrFSc5b9v357QNBUIOQB6f049matmX5Y HMA0tpHu//eYQiFw+zCJkOPKjDjYkmmtROYhC/HQ0wES9mBszEblGcbSBgSM7GB/aXlMdElGBPS +kg9uT05aL9xHmt0Dcdqw/8xFXishnVQWwgEEX7FJmd1zUML+3nQw8BARGFQDQWVaL3dgw5wvUq QAI0f64j8FtcA+2uWEFhxTJy2aUUac= X-Received: by 2002:adf:f707:0:b0:43d:68bd:f590 with SMTP id ffacd0b85a97d-43d68bdf5c4mr4378396f8f.6.1775920334157; Sat, 11 Apr 2026 08:12:14 -0700 (PDT) Received: from localhost.localdomain ([2a02:c7c:37d6:c100:81d8:4e83:a60e:1a81]) by smtp.googlemail.com with ESMTPSA id ffacd0b85a97d-43d63e5c981sm17167835f8f.33.2026.04.11.08.12.13 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 11 Apr 2026 08:12:13 -0700 (PDT) From: Adrian McMenamin To: Linux FS development Cc: Adrian McMenamin , Linux kernel mailing list Subject: [PATCH 3/5] Add vmufat header Date: Sat, 11 Apr 2026 16:11:37 +0100 Message-ID: <20260411151155.321214-4-adrianmcmenamin@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260411151155.321214-1-adrianmcmenamin@gmail.com> References: <20260411151155.321214-1-adrianmcmenamin@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" vmufat.h provides constants and common data structures to inode.c and super.c as well as a wrapper around calls to sb_read. Signed-off-by: Adrian McMenamin --- fs/vmufat/vmufat.h | 126 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 fs/vmufat/vmufat.h diff --git a/fs/vmufat/vmufat.h b/fs/vmufat/vmufat.h new file mode 100644 index 000000000000..369ee59738ee --- /dev/null +++ b/fs/vmufat/vmufat.h @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* Header file for VMUFAT filesystem */ +#ifndef _VMUFAT_H_ +#define _VMUFAT_H_ + +/* maximum length of file name */ +#define VMUFAT_NAMELEN 12 + +/* Some GNU utils might not list files with inode num 0 */ +#define VMUFAT_ZEROBLOCK 0x10000 +#define VMU_BLK_SZ 0x200 +#define VMU_BLK_SZ16 0x100 +#define VMU_PHYS_SZ 0x100 + +/* 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 + +/* 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 /* set to 200 by real hardware */ + +/* 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 =3D 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 +#define DAYS_PER_WEEK 7 +#define START_OF_EPOCH 70 +#define CENTURY21 30 +#define CENTURY22 130 + +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 { + struct list_head b_list; + int bno; +}; + +struct vmufat_inode { + struct list_head blocks; + unsigned int nblcks; + enum vmufat_file_type { + GAME =3D 1, + DATA =3D 2 + } ft; + 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 struct buffer_head *vmufat_sb_bread(struct super_block *sb, + sector_t block) +{ + if (!sb) + return NULL; + return sb_bread(sb, block); +} + +int vmufat_count_files(struct super_block *sb); +#endif --=20 2.43.0 From nobody Mon Jun 15 13:56:24 2026 Received: from mail-wr1-f53.google.com (mail-wr1-f53.google.com [209.85.221.53]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id AF7C438551A for ; Sat, 11 Apr 2026 15:12:17 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.221.53 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775920339; cv=none; b=ang26sG43iXM2nM+ZcnR1PUT9+N29KtRhNdg/4NjVb4Wah90ia8ZOQfzDQCIIauJYo69tWJoVBqGnr6gmFqVLaQphQCVt+WRtGYWEW51krOjbeysebZaVdNi+RtjoPkN4EvqVh034r5iu8EjZ05it3dBm4eMyswlXZrmLtxRNb8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775920339; c=relaxed/simple; bh=lbJIgS8JoP7yrdxL3apU7ip1p1tcoU9c8Z0Q5PBZu1c=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=izWcBewWN7R23cEZwG36GzttLmYqoRq4gt14pStYgfl3oNJd4gbaKv1zgjK5vbSKV/UY371x0d8H3KGkGuDWeL+aIFnejQ1q0puDuwc0XJI3QgeqnOugqY26jX7cKefMSvjeirXZkE9nrfT+Rvfiej7+mLSLtWLj8aW0/8gMRmk= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=mGF/o8+l; arc=none smtp.client-ip=209.85.221.53 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="mGF/o8+l" Received: by mail-wr1-f53.google.com with SMTP id ffacd0b85a97d-43cf5fbacc9so1412804f8f.1 for ; Sat, 11 Apr 2026 08:12:17 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1775920336; x=1776525136; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=6zgbfd4qk9eHr8cYBg6jrVQCT/+PJttNZMyaURX0hWw=; b=mGF/o8+lzTr35OGrTVFkvni0glNpUBmUEr6dsciaQnu11H7wQs14rXTTMufcT0kuBu MZqrE+aeU3RZg/0zD3vCDdVQok6WoottTbCsRnKCUji0xEfmxeisK9S91xegRFk1fsVu oJeskDYlZ7x/1ijJ5MOygHwPxMd7jrQQj1hw7Iigjo4LZv4P2gsqzMMxLnculoy9ub1C mGRaahm/XjaVRl5q8A3lM1H93TNE1zqOoH2gjlbYyVB1mzOUkyqd86WNIDJ4unDBaq0G 9UgNZzp+NkX+R0nKmC97XOILsXlAE/TEkk3oGsnryBXy9/OK7+p6WDVxnyAoceR3yVXt TuRA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1775920336; x=1776525136; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=6zgbfd4qk9eHr8cYBg6jrVQCT/+PJttNZMyaURX0hWw=; b=WZKLC5oWuyYIPwyG9V1vgQbjwUy81AAZpDHM44PNtNgzQ2wHZMvPbI184tWVlOkIVo i6/gUx1g2HNeqKYmMlAeZM9lWIIFgdvRS05jnKs8VRB0e5q0yov8nc1c5ApR+DSHaOxY naW22RuHhWD0ipfVPaMTDqvJBV7pVEOsSnSAN1U98XgoOFjIARM36jdf/lSpn/pn9hFT nmCPYlcxrq97tlZpmh7zJ551H1Gp3FAM/Z28a+IOJNjsR226ZCFtFLV8INGig3F5TVkH k6z8JnIIXN0vqOYVv+fkEcYkLxvW57UNxlLH+uZsKW5KPSZ/tFpD9wnookPwIXWNbBPg 41bw== X-Forwarded-Encrypted: i=1; AJvYcCWYgHE///GTrvlE796abhf7Ja+fQWBtJVYY2DlqHc38InJUwFt0IOEPe0B/LPBFqRpxPgrOLwitEeoyjzc=@vger.kernel.org X-Gm-Message-State: AOJu0YyGXCM63vgzf0SsTwiYTGAwkCxnNbIxYrmcLvMqFZwUjhc/qArL f0uMXvMYFxf/5Q8jhPavXK3BV3zz1Q58AEmzVx9rko/juo4n/21zTt74 X-Gm-Gg: AeBDievV+gIRGNUvy6htqYBFQjc8hJJxTsYPqtCZ8QQLgYLLJKTJWhAF17LtQl1r9SC aBAVx9VXuIoq1QXfS3QWiXxET+qa4KmXuH2+JyQ5R+6/03MkaeEoaXFi7ohroxgNAEud0cV4Tkr Z+n2cff9vcalJOIzQQNRpjbVlluwFO3W63utINSI9vASacSPSkZHOjYb4VfqJ+UHscHEKOj64yh sKsqaQ9sxahrpYmZuGMPs+PMw5Zt8mgGBTIA2uUFpuvCHETBNmdDevx7Bd+/iY5x7iB8o/d1Xvq dd3IYKDr/d4GbVVHR9xHsv3UodLWiCDFZ8NAsRih9pabtFdyKkdqw2Kf3uH1IiriY6uh0nHr3o2 mQi3qm4amlSRzOKpOMeMvC744WwnkQnMBl00FWKj1ogDaL/MxgGjiK7SDEaRCZ0pOAWmySLD4nd RCpgd1GIzX+ZnI2wXBcqx5Y7yUZVUFx0cgNhhWsr9PTQoc2sp8o/rBjpXsf1de4VOjfB/D0FCfw jxnTQz5AfxxVy6b196N X-Received: by 2002:a5d:6f04:0:b0:43b:4951:6b37 with SMTP id ffacd0b85a97d-43d595c4c41mr16183829f8f.15.1775920335644; Sat, 11 Apr 2026 08:12:15 -0700 (PDT) Received: from localhost.localdomain ([2a02:c7c:37d6:c100:81d8:4e83:a60e:1a81]) by smtp.googlemail.com with ESMTPSA id ffacd0b85a97d-43d63e5c981sm17167835f8f.33.2026.04.11.08.12.15 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 11 Apr 2026 08:12:15 -0700 (PDT) From: Adrian McMenamin To: Linux FS development Cc: Adrian McMenamin , Linux kernel mailing list Subject: [PATCH 4/5 -v2] Add vmufat documentation Date: Sat, 11 Apr 2026 16:11:38 +0100 Message-ID: <20260411151155.321214-5-adrianmcmenamin@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260411151155.321214-1-adrianmcmenamin@gmail.com> References: <20260411151155.321214-1-adrianmcmenamin@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" vmufat.rst documents the VMUFAT filesystem Signed-off-by: Adrian McMenamin --- Documentation/filesystems/vmufat.rst | 127 +++++++++++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 Documentation/filesystems/vmufat.rst diff --git a/Documentation/filesystems/vmufat.rst b/Documentation/filesyste= ms/vmufat.rst new file mode 100644 index 000000000000..f2dfb15c0cfb --- /dev/null +++ b/Documentation/filesystems/vmufat.rst @@ -0,0 +1,127 @@ +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D +VMUFAT FILESYSTEM +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +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 bl= ocks +of 512 octets (8 bit bytes) - 128KB in total. But the specification suppor= ts a=20 +wider range of sizes and the filesystem in the Linux kernel is capable of +handling volumes of a size between 256 blocks and 65536 blocks. + +The standard 256 block VMU is described below: + ++----------+---------------------------------------------+ +| BLOCK NO | CONTENT | ++=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D+=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D+ +| 0-199 | Space used by Dreamcast to save files | ++----------+---------------------------------------------+ +| 200-240 | Space used by the Dreamcast system | ++----------+---------------------------------------------+ +| 241-253 | Directory (can hold 208 file records) | ++----------+---------------------------------------------+ +| 254 | File Allocation Table (FAT) | ++----------+---------------------------------------------+ +| 255 | Root Block | ++----------+---------------------------------------------+ + +The standard VMU filesystem has 241 blocks which can be used for file stor= age +but a Dreamcast will only use 200 of these. The other 41 are for Dreamcast= system +use, though the filesystem can access them if set up as a general file sto= re. +If a device is formatted as strictly compatible with the Dreamcast those b= locks +will not be accessible. Most use cases will want that strict compatibility. + +An executable 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. The filesystem driver seeks to match that policy but it is not +absolutely constrained by it. + +=3D=3D=3D=3D=3D=3D=3D=3D=3D +Directory +=3D=3D=3D=3D=3D=3D=3D=3D=3D + +The directory contains records 32 octets long (format detail below from Ma= rcus +Comstedt's website - http://mc.pp.se/dc/vms/flashmem.html) + +0x00 : file type (0x00 =3D no file, 0x33 =3D data, 0xcc =3D game) + +0x01 : copy protect (0x00 =3D copy ok, 0xff =3D 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) + + +Header positioning is a matter for executable 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) + +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D +File allocation table +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +Every block in the volume is mapped to the FAT eg., block 0 of the VMU map= s to +the first 16 bits of the FAT and so on. Blocks marked 0xFFFC are empty, bl= ocks +allocated to a file are either numbered with with next block or, if the fi= nal +block are marked 0xFFFA. + +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D +Root block +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +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 determi= ne +the colour the representation of the VMU is displayed in by a Dreamcast an= d 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. A filesystem formatted for strict +compatibility will respect this boundary, one formatted as a general +file store will not and will instead seek to use all writeable blocks. + +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D +Formatting tool +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +A formatting tool is available at https://github.com/mcmenaminadrian/mkfs.= vmufat + +A physical, factory-set, VMU is unlikely to require reformatting, of cours= e. --=20 2.43.0 From nobody Mon Jun 15 13:56:24 2026 Received: from mail-wm1-f45.google.com (mail-wm1-f45.google.com [209.85.128.45]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 2384538F249 for ; Sat, 11 Apr 2026 15:12:18 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.45 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775920346; cv=none; b=bz8nFPgDS0nH2pTltAwiXoXap1kuDinPK0kl3GLHqwgBXjLnmDwMp6Z4Gctu5yTFcfPKD4nG9bg+2C2T2yzs1iv56V6Bba+hPVnXqQceT6LkDgFMrC2HG/M5/2WPjptu7zVLZW1xr9gHm8IriNguNf9gaaUiF2Jckk6UahhRh2o= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775920346; c=relaxed/simple; bh=XpDQNhU0YAgq6eWgBkxxt+03udeTHH945hMY8DgZpdI=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=jagt97h872FoM56lEs0+UVZG38Qp28Mbu58C+rfrWdMz6PuAbUJUOWVKrnR6vnliavVTCHXX1YDfi8gQng8OEyoawHnV7gYnsjapsb64qiFcETSJOaxfPQPgs53IwKHIF5m5H8ev+8sr4WbhwxnaG0GoXsA/+FjsPX/khr1nvjo= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=s7JmSZmO; arc=none smtp.client-ip=209.85.128.45 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="s7JmSZmO" Received: by mail-wm1-f45.google.com with SMTP id 5b1f17b1804b1-488e1a8ac40so3359315e9.2 for ; Sat, 11 Apr 2026 08:12:18 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1775920337; x=1776525137; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=EKlpdpark2ywyu/DKZmFr/LkIVCpBUEOyvYhEoA5byc=; b=s7JmSZmOCEchjQZ6m97Ol3pITUhK9kFyU2lEnJmop6IaFDBSyiWc3mwsTFBS7ngalT Nto0LQfISc3usmRDwu2JpuxLIznbM1nlHSDi/yFXqiSZ04aVGq1ARyqsEvPm56M+j9iM 9P2zyTemYrCIqcUqh+wdG+lG/SNiyyPMBN4SQmlwztC/Mmdc7N1yY9yAbQZ7QRSEDyxV dGNw1PlHZf/DsH8Wvm9uKt8iCxT3eaFhtFqRf8koXQ4ZaNqi6wMFveYvAsN+ae+zI0mM kvtWQ1hx1UGrAqxBm+v6asd1t/lDgwfbEfQPLpg6vqDuNHK9j8D/+AUTTDv0de20G37p quRw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1775920337; x=1776525137; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=EKlpdpark2ywyu/DKZmFr/LkIVCpBUEOyvYhEoA5byc=; b=hGiBSV3Rt5mkoGgnkMDYr3X8f68ANZ4e3o9me1rM6Gh61rTWZUS1R+0o/En5TKzTHq If0UcmxOv4H9Pewx5jX4/Ocsu7XkSH075vPsdsZSuKr9C7XqueP2ksqw1zF6UGggXTKz r6Yed0gjrwrpxNN7n7FJbl+9o29NfXoj2PAXV0unc/TsDT51PaR63+XU9dIQ1MjkPX35 N0OEPZhuTNtNYj21haPBZeYl02hK8PQdFzg7AFxy0l+R3KYXFI6ZOVqJlSTgYIBuQlfI lj56kW8Qzb/eXMqqLclvaQAr3t5xGI1yG8LOTImojdwGCr1MXFia6qF6Smx4Pyl5reCN CD2A== X-Forwarded-Encrypted: i=1; AJvYcCUZ7JVnEJMHCRuV7wADrtcDkjCoMqTnx2dbLppS5SgpiOOSDqB2hrgJAj2UnVn4xvP4nso9dvqIxZk1Tfo=@vger.kernel.org X-Gm-Message-State: AOJu0YyKStrofHiY9TJWb1DrJu8w+rilX3OH9g0b7hVgcznbmdx9myoO /xDUlygJ65c1es+oJ47F+pLC17zuSXxoYDX2Z9jc2OTkh83dRgqw3r8kMo1xfw== X-Gm-Gg: AeBDiet2kDuYnoLvxa9IcQfwBAIPfVFz0hXersFMbFRyfCCHmG1ZwwhGlhtmMrOQ9pA CR6LHxAAldt3iAaN64CGsm4RCpctnrcGUFWuHXIx63hOgQX3uDy6N27geAOJMmYq6PFb/aF2tVy 2ofq1VAiJQjPLmP74jb081q2ekBZXkG6YtDvy0IL5v76iWZtsXsFmgRLS8PaJvi8VD4o47Tn6Cu MirB5wGd2IIx31FqWQxcTfEmUS/7D4AvN0027yGvOI7qQ0qNZzeeQXV8a7da368OlEC/o5hGjKh 4glc2iH1JV/IyIBTvR28asYHVSs7uX7c7gpsdHI9rYxJVumySCoUBYUm2WEN7hQ2tUOt7k1vV9M sCq3eBm3X0Dq/hVlpf0O0ylztxWGOeRi5NUQZIx132X/C76evxIdPCpvMqtDNAzZlvFKNQaJz8d 7JNY6djwQ878uscHKQWx1aN0/E4iHglWrBlOaXRLIRGGn7ScQlmBai8oXdbWiVrKJNhmlfOaxDM 04rKOfjAQH2uXC0hqGy X-Received: by 2002:a05:600c:64c9:b0:488:c40b:c8b9 with SMTP id 5b1f17b1804b1-488d67b8d4emr108745565e9.3.1775920337258; Sat, 11 Apr 2026 08:12:17 -0700 (PDT) Received: from localhost.localdomain ([2a02:c7c:37d6:c100:81d8:4e83:a60e:1a81]) by smtp.googlemail.com with ESMTPSA id ffacd0b85a97d-43d63e5c981sm17167835f8f.33.2026.04.11.08.12.16 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 11 Apr 2026 08:12:16 -0700 (PDT) From: Adrian McMenamin To: Linux FS development Cc: Adrian McMenamin , Linux kernel mailing list Subject: [PATCH 5/5 -v2] miscellaneous vmufat changes and additions Date: Sat, 11 Apr 2026 16:11:39 +0100 Message-ID: <20260411151155.321214-6-adrianmcmenamin@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260411151155.321214-1-adrianmcmenamin@gmail.com> References: <20260411151155.321214-1-adrianmcmenamin@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Documentation/filesystems/index.rst Update the documentation index file to link to the VMUFAT documentation.. fs/Kconfig Update the fs Kconfig to make the VMUFAT option available. fs/Makefile Update the fs Makefile to allow VMUFAT to be built. fs/vmufat/Kconfig Advise kernel builders on whether or not to build VMUFAT code. fs/vmufat/Makefile Add a makefile to build the VMUFAT code as a built-in or a module. include/uapi/linux/magic.h 0x55555555 is used mark the VMUFAT superblock, add it as an indentifying magic number. Signed-off-by: Adrian McMenamin --- Documentation/filesystems/index.rst | 1 + fs/Kconfig | 1 + fs/Makefile | 1 + fs/vmufat/Kconfig | 14 ++++++++++++++ fs/vmufat/Makefile | 7 +++++++ include/uapi/linux/magic.h | 1 + 6 files changed, 25 insertions(+) create mode 100644 fs/vmufat/Kconfig create mode 100644 fs/vmufat/Makefile diff --git a/Documentation/filesystems/index.rst b/Documentation/filesystem= s/index.rst index f4873197587d..893b585d17a9 100644 --- a/Documentation/filesystems/index.rst +++ b/Documentation/filesystems/index.rst @@ -120,5 +120,6 @@ Documentation for filesystem implementations. udf virtiofs vfat + vmufat xfs/index zonefs diff --git a/fs/Kconfig b/fs/Kconfig index 0bfdaecaa877..89a37c9c58a0 100644 --- a/fs/Kconfig +++ b/fs/Kconfig @@ -332,6 +332,7 @@ source "fs/pstore/Kconfig" source "fs/ufs/Kconfig" source "fs/erofs/Kconfig" source "fs/vboxsf/Kconfig" +source "fs/vmufat/Kconfig" =20 endif # MISC_FILESYSTEMS =20 diff --git a/fs/Makefile b/fs/Makefile index cf4a745e9679..3cdc3d7b804c 100644 --- a/fs/Makefile +++ b/fs/Makefile @@ -129,3 +129,4 @@ obj-$(CONFIG_VBOXSF_FS) +=3D vboxsf/ obj-$(CONFIG_ZONEFS_FS) +=3D zonefs/ obj-$(CONFIG_BPF_LSM) +=3D bpf_fs_kfuncs.o obj-$(CONFIG_RESCTRL_FS) +=3D resctrl/ +obj-$(CONFIG_VMUFAT_FS) +=3D vmufat/ diff --git a/fs/vmufat/Kconfig b/fs/vmufat/Kconfig new file mode 100644 index 000000000000..92553a246911 --- /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 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 000000000000..da423b0c7eec --- /dev/null +++ b/fs/vmufat/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for VMUFAT filesystem +# + +obj-$(CONFIG_VMUFAT_FS) +=3D vmufat.o + +vmufat-y :=3D inode.o super.o diff --git a/include/uapi/linux/magic.h b/include/uapi/linux/magic.h index 4f2da935a76c..7a3ab866ef48 100644 --- a/include/uapi/linux/magic.h +++ b/include/uapi/linux/magic.h @@ -54,6 +54,7 @@ #define QNX4_SUPER_MAGIC 0x002f /* qnx4 fs detection */ #define QNX6_SUPER_MAGIC 0x68191122 /* qnx6 fs detection */ #define AFS_FS_MAGIC 0x6B414653 +#define VMUFAT_SUPER_MAGIC 0x55555555 /* opening bytes of root */ =20 =20 #define REISERFS_SUPER_MAGIC 0x52654973 /* used by gcc */ --=20 2.43.0