-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathhotfix-vmufat.patch
More file actions
7832 lines (7275 loc) · 238 KB
/
hotfix-vmufat.patch
File metadata and controls
7832 lines (7275 loc) · 238 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
From 6de03ddd0f024c585d2957c58e67b42f602f8f62 Mon Sep 17 00:00:00 2001
From: Adrian McMenamin <adrianmcmenamin@gmail.com>
Date: Thu, 4 Dec 2025 16:34:29 +0000
Subject: [PATCH 001/101] Documentation update
---
Documentation/filesystems/vmufat.rst | 105 +++++++++++++++++++++++++++
1 file changed, 105 insertions(+)
create mode 100644 Documentation/filesystems/vmufat.rst
diff --git a/Documentation/filesystems/vmufat.rst b/Documentation/filesystems/vmufat.rst
new file mode 100644
index 00000000000000..cde87531e9f607
--- /dev/null
+++ b/Documentation/filesystems/vmufat.rst
@@ -0,0 +1,105 @@
+==================
+VMUFAT FILESYSTEM
+==================
+
+VMUFAT is the simple file allocation table (FAT) based filesystem used in Sega
+Dreamcast Visual Memory Units (VMUs) and various Dreamcast emulators and
+Android apps etc.
+
+It is not recommended for general use, but does not require a Dreamcast.
+
+All the physical VMU devices that were made were of the same size 256 blocks
+of 512 octets - 128KB in total. But the specification supports a wider range
+of sizes and the filesystem in the Linux kernel is capable of handling volumes
+of a size between 4 blocks and 65536 blocks.
+
+The standard 256 block VMU is described below:
+
+======== =======
+BLOCK NO CONTENT
+======== =======
+0 - 199 Space used by Dreamcast to save files
+200 - 240 Space which can be used but which the Dreamcast ignores
+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 storage
+but a Dreamcast will only use 200 of these. The Linux kernel driver prefers to
+use blocks 0 - 199 when allocating blocks to files, but will use blocks 200 -
+240 if lower numbered blocks are not available.
+
+An executible file (generally a game written in the native machine code of
+the VMU's microcontroller) must begin at block 0 and be stored linearly in
+the volume.
+
+=========
+DIRECTORY
+=========
+
+The directory contains records 32 octets long (format detail below from Marcus
+Comstedt's website - http://mc.pp.se/dc/vms/flashmem.html)
+
+0x00 : file type (0x00 = no file, 0x33 = data, 0xcc = game)
+0x01 : copy protect (0x00 = copy ok, 0xff = copy protected)
+0x02-0x03 : 16 bits (little endian) : location of first block
+0x04-0x0f : ASCII string : filename (12 characters)
+0x10-0x17 : Binary Coded Decimal timestamp: file creation time
+0x18-0x19 : 16 bits (little endian) : file size (in blocks)
+0x1a-0x1b : 16 bits (little endian) : offset of header (in blocks)
+ from file start
+
+Header positioning is a matter for executible files written in native
+code for a physical VMU (an 8 bit Sanyo microcontroller).
+
+BCD dates are encoded as follows:
+
+Century prefix (eg., 20)
+
+Year (eg., 12)
+
+Month (eg., 02)
+
+Day in month (eg., 29)
+
+Hour (eg., 20)
+
+Minute (eg., 49)
+
+Second (eg., 22)
+
+Day of week (Monday is 00, Sunday is 06)
+
+=====================
+FILE ALLOCATION TABLE
+=====================
+
+Every block in the volume is mapped to the FAT eg., block 0 of the VMU maps to
+the first 16 bits of the FAT and so on. Blocks marked 0xFFFC are empty, blocks
+allocated to a file are either numbered with with next block or, if the final
+block are marked 0xFFFA.
+
+**ROOT BLOCK**
+
+The first 16 octets are marked as 0x55 - we use this to provide the *magic
+number* for this filesystem. The octets at 0x10 - 0x14 are used to determine
+the colour the representation of the VMU is displayed in by a Dreamcast and our
+filesystem does not touch these.
+
+Octets 0x30 - 0x37 contain the BCD timestamp of the VMU.
+
+Octets 0x46 - 0x47 contain the location (little endian format) of the FAT
+
+Octets 0x48 - 0x49 contain the size (little endian) of the FAT
+
+Octets 0x4A - 0x4B contain the location (little endian) of the Directory
+
+Octets 0x4C - 0x4D contain the size (little endian) of the Directory
+
+Octets 0x4E - 0x4F is Dreamcast specific (icon shape)
+
+Octets 0x50 - 0x51 contain the number (little endian) of user blocks - NB this
+is marked as 200 in a physical VMU although there are in fact 241 usable
+blocks - the reason appears to be that the Directory is not big enough to
+support 1 block files in all user blocks.
\ No newline at end of file
From 84056c419d5d2f10ee213a78c924b37b26161c16 Mon Sep 17 00:00:00 2001
From: Adrian McMenamin <adrianmcmenamin@gmail.com>
Date: Thu, 4 Dec 2025 16:44:39 +0000
Subject: [PATCH 002/101] Makefile and related changes
---
fs/Kconfig | 1 +
fs/Makefile | 1 +
fs/vmufat/Kconfig | 14 ++++++++++++++
fs/vmufat/Makefile | 7 +++++++
4 files changed, 23 insertions(+)
create mode 100644 fs/vmufat/Kconfig
create mode 100644 fs/vmufat/Makefile
diff --git a/fs/Kconfig b/fs/Kconfig
index 0bfdaecaa87751..89a37c9c58a09d 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"
endif # MISC_FILESYSTEMS
diff --git a/fs/Makefile b/fs/Makefile
index cf4a745e967984..3cdc3d7b804c76 100644
--- a/fs/Makefile
+++ b/fs/Makefile
@@ -129,3 +129,4 @@ obj-$(CONFIG_VBOXSF_FS) += vboxsf/
obj-$(CONFIG_ZONEFS_FS) += zonefs/
obj-$(CONFIG_BPF_LSM) += bpf_fs_kfuncs.o
obj-$(CONFIG_RESCTRL_FS) += resctrl/
+obj-$(CONFIG_VMUFAT_FS) += vmufat/
diff --git a/fs/vmufat/Kconfig b/fs/vmufat/Kconfig
new file mode 100644
index 00000000000000..cdc7282924b362
--- /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
\ No newline at end of file
diff --git a/fs/vmufat/Makefile b/fs/vmufat/Makefile
new file mode 100644
index 00000000000000..da423b0c7eec86
--- /dev/null
+++ b/fs/vmufat/Makefile
@@ -0,0 +1,7 @@
+#
+# Makefile for VMUFAT filesystem
+#
+
+obj-$(CONFIG_VMUFAT_FS) += vmufat.o
+
+vmufat-y := inode.o super.o
From 7c45c15eb250c13c68ac5da887f2e15741352529 Mon Sep 17 00:00:00 2001
From: Adrian McMenamin <adrianmcmenamin@gmail.com>
Date: Thu, 4 Dec 2025 16:49:01 +0000
Subject: [PATCH 003/101] vmufat driver code files
---
fs/vmufat/inode.c | 902 +++++++++++++++++++++++++++++++++++++++++++++
fs/vmufat/super.c | 545 +++++++++++++++++++++++++++
fs/vmufat/vmufat.h | 116 ++++++
3 files changed, 1563 insertions(+)
create mode 100644 fs/vmufat/inode.c
create mode 100644 fs/vmufat/super.c
create mode 100644 fs/vmufat/vmufat.h
diff --git a/fs/vmufat/inode.c b/fs/vmufat/inode.c
new file mode 100644
index 00000000000000..88903c1678a740
--- /dev/null
+++ b/fs/vmufat/inode.c
@@ -0,0 +1,902 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * VMUFAT file system
+ *
+ * Copyright (C) 2002 - 2012, 2025 Adrian McMenamin
+ * Copyright (C) 2002 Paul Mundt
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include <linux/fs.h>
+#include <linux/bcd.h>
+#include <linux/rtc.h>
+#include <linux/slab.h>
+#include <linux/sched.h>
+#include <linux/magic.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/statfs.h>
+#include <linux/buffer_head.h>
+#include <linux/mpage.h>
+#include "vmufat.h"
+
+const struct inode_operations vmufat_inode_operations;
+const struct file_operations vmufat_file_operations;
+const struct address_space_operations vmufat_address_space_operations;
+const struct file_operations vmufat_file_dir_operations;
+struct kmem_cache *vmufat_blist_cachep;
+/* Linear day numbers of the respective 1sts in non-leap years. */
+int day_n[] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334};
+
+static struct dentry *vmufat_inode_lookup(struct inode *in, struct dentry *dent,
+ unsigned int ignored)
+{
+ struct super_block *sb;
+ struct memcard *vmudetails;
+ struct buffer_head *bh = NULL;
+ struct inode *ino;
+ int i, j, error = 0;
+
+ if (dent->d_name.len > VMUFAT_NAMELEN) {
+ error = -ENAMETOOLONG;
+ goto out;
+ }
+ sb = in->i_sb;
+ vmudetails = sb->s_fs_info;
+
+ for (i = vmudetails->dir_bnum;
+ i > vmudetails->dir_bnum - vmudetails->dir_len; i--) {
+ brelse(bh);
+ bh = vmufat_sb_bread(sb, i);
+ if (!bh) {
+ error = -EIO;
+ goto out;
+ }
+ for (j = 0; j < VMU_DIR_ENTRIES_PER_BLOCK; j++) {
+ int record_offset = j * VMU_DIR_RECORD_LEN;
+ if (bh->b_data[record_offset] == 0)
+ goto insert_negative;
+ if (memcmp(dent->d_name.name,
+ bh->b_data + record_offset + VMUFAT_NAME_OFFSET,
+ dent->d_name.len) == 0) {
+ ino = vmufat_get_inode(sb,
+ le16_to_cpu(((u16 *) bh->b_data)
+ [record_offset
+ + VMUFAT_FIRSTBLOCK_OFFSET16]));
+ if (IS_ERR(ino)) {
+ error = PTR_ERR(ino);
+ goto out;
+ } else if (!ino) {
+ error = -EACCES;
+ goto out;
+ }
+ d_add(dent, ino);
+ goto out;
+ }
+ }
+ }
+insert_negative:
+ d_add(dent, NULL); /* Did not find the file */
+out:
+ brelse(bh);
+ return ERR_PTR(error);
+}
+
+static int vmufat_get_freeblock(int start, int end, struct buffer_head *bh)
+{
+ int i, ret = -1;
+ __le16 fatdata;
+
+ for (i = start; i >= end; i--) {
+ fatdata = le16_to_cpu(((u16 *)bh->b_data)[i]);
+ if (fatdata == VMUFAT_UNALLOCATED) {
+ ret = i;
+ break;
+ }
+ }
+ return ret;
+}
+
+/*
+ * Find a block marked free in the FAT
+ */
+static int vmufat_find_free(struct super_block *sb)
+{
+ struct memcard *vmudetails;
+ int testblk, fatblk, ret;
+ struct buffer_head *bh_fat;
+
+ vmudetails = sb->s_fs_info;
+
+ for (fatblk = vmudetails->fat_bnum;
+ fatblk > vmudetails->fat_bnum - vmudetails->fat_len;
+ fatblk--) {
+ bh_fat = vmufat_sb_bread(sb, fatblk);
+ if (!bh_fat) {
+ ret = -EIO;
+ goto fail;
+ }
+
+ /* Handle 256 block VMUs like physical devices
+ * and other VMUs more simply
+ */
+ if (vmudetails->sb_bnum != VMU_BLK_SZ16) {
+ /* Cannot be physical VMU */
+ testblk = vmufat_get_freeblock(VMU_BLK_SZ16, 0, bh_fat);
+ put_bh(bh_fat);
+ if (testblk >= 0)
+ goto out_of_loop;
+ } else { /* Physical VMU or logical VMU with same size */
+ testblk = vmufat_get_freeblock(VMUFAT_START_ALLOC, 0,
+ bh_fat);
+ if (testblk >= 0) {
+ put_bh(bh_fat);
+ goto out_of_loop;
+ }
+ /* Only allocate to higher blocks if no space left */
+ testblk = vmufat_get_freeblock(VMU_BLK_SZ16,
+ VMUFAT_START_ALLOC + 1, bh_fat);
+ put_bh(bh_fat);
+ if (testblk > VMUFAT_START_ALLOC)
+ goto out_of_loop;
+ }
+ }
+ printk(KERN_INFO "VMUFAT: volume is full\n");
+ ret = -ENOSPC;
+ goto fail;
+
+out_of_loop:
+ ret = (fatblk - 1 - vmudetails->fat_bnum + vmudetails->fat_len)
+ * VMU_BLK_SZ16 + testblk;
+fail:
+ return ret;
+}
+
+/* read the FAT for a given block */
+u16 vmufat_get_fat(struct super_block *sb, long block)
+{
+ struct buffer_head *bufhead;
+ int offset;
+ u16 block_content = VMUFAT_ERROR;
+ struct memcard *vmudetails = sb->s_fs_info;
+
+ /* which block in the FAT */
+ offset = block / VMU_BLK_SZ16;
+ if (offset >= vmudetails->fat_len)
+ goto out;
+
+ /* fat_bnum points to highest block in FAT */
+ bufhead = vmufat_sb_bread(sb, offset + 1 +
+ vmudetails->fat_bnum - vmudetails->fat_len);
+ if (!bufhead)
+ goto out;
+ /* look inside the block */
+ block_content = le16_to_cpu(((u16 *)bufhead->b_data)
+ [block % VMU_BLK_SZ16]);
+ put_bh(bufhead);
+out:
+ return block_content;
+}
+
+/* set the FAT for a given block */
+static int vmufat_set_fat(struct super_block *sb, long block,
+ u16 data_to_set)
+{
+ struct buffer_head *bh;
+ int offset, error = 0;
+ struct memcard *vmudetails = sb->s_fs_info;
+
+ offset = block / VMU_BLK_SZ16;
+ if (offset >= vmudetails->fat_len) {
+ error = -EINVAL;
+ goto out;
+ }
+ bh = vmufat_sb_bread(sb, offset + 1 +
+ vmudetails->fat_bnum - vmudetails->fat_len);
+ if (!bh) {
+ error = -EIO;
+ goto out;
+ }
+ ((u16 *) bh->b_data)[block % VMU_BLK_SZ16] = cpu_to_le16(data_to_set);
+ mark_buffer_dirty(bh);
+ put_bh(bh);
+out:
+ return error;
+}
+
+
+static void vmufat_save_bcd_nortc(struct inode *in, char *bh, int index_to_dir)
+{
+ long years, days;
+ unsigned char bcd_century, nl_day, bcd_month;
+ unsigned char u8year;
+ time64_t unix_date;
+
+ unix_date = in->i_mtime_sec;
+ days = unix_date / SECONDS_PER_DAY;
+ years = days / DAYS_PER_YEAR;
+ /* 1 Jan gets 1 day later after every leap year */
+ if ((years + 3) / 4 + DAYS_PER_YEAR * years >= days)
+ years--;
+ /* rebase days to account for leap years */
+ days -= (years + 3) / 4 + DAYS_PER_YEAR * years;
+ /* 1 Jan is Day 1 */
+ days++;
+ if (days == (FEB28 + 1) && !(years % 4)) {
+ nl_day = days;
+ bcd_month = 2;
+ } else {
+ nl_day = (years % 4) || days <= FEB28 ? days : days - 1;
+ for (bcd_month = 0; bcd_month < 12; bcd_month++)
+ if (day_n[bcd_month] > nl_day)
+ break;
+ }
+
+ bcd_century = 19;
+ /* TODO:accounts for 21st century but will fail in 2100
+ because of leap days */
+ if (years > 29)
+ bcd_century += 1 + (years - 30)/100;
+
+ bh[index_to_dir + VMUFAT_DIR_CENT] = bin2bcd(bcd_century);
+ u8year = years + 70; /* account for epoch */
+ if (u8year > 99)
+ u8year = u8year - 100;
+
+ bh[index_to_dir + VMUFAT_DIR_YEAR] = bin2bcd(u8year);
+ bh[index_to_dir + VMUFAT_DIR_MONTH] = bin2bcd(bcd_month);
+ bh[index_to_dir + VMUFAT_DIR_DAY] =
+ bin2bcd(days - day_n[bcd_month - 1]);
+ bh[index_to_dir + VMUFAT_DIR_HOUR] =
+ bin2bcd((unix_date / SECONDS_PER_HOUR) % HOURS_PER_DAY);
+ bh[index_to_dir + VMUFAT_DIR_MIN] =
+ bin2bcd((unix_date / SIXTY_MINS_OR_SECS)
+ % SIXTY_MINS_OR_SECS);
+ bh[index_to_dir + VMUFAT_DIR_SEC] =
+ bin2bcd(unix_date % SIXTY_MINS_OR_SECS);
+}
+
+static void vmufat_save_bcd_rtc(struct rtc_device *rtc, struct inode *in,
+ char *bh, int index_to_dir)
+{
+ struct rtc_time now;
+
+ if (rtc_read_time(rtc, &now) < 0)
+ vmufat_save_bcd_nortc(in, bh, index_to_dir);
+ bh[index_to_dir + VMUFAT_DIR_CENT] = bin2bcd((char)(now.tm_year/100));
+ bh[index_to_dir + VMUFAT_DIR_YEAR] = bin2bcd((char)(now.tm_year % 100));
+ bh[index_to_dir + VMUFAT_DIR_MONTH] = bin2bcd((char)(now.tm_mon));
+ bh[index_to_dir + VMUFAT_DIR_DAY] = bin2bcd((char)(now.tm_mday));
+ bh[index_to_dir + VMUFAT_DIR_HOUR] = bin2bcd((char)(now.tm_hour));
+ bh[index_to_dir + VMUFAT_DIR_MIN] = bin2bcd((char)(now.tm_min));
+ bh[index_to_dir + VMUFAT_DIR_SEC] = bin2bcd((char)(now.tm_sec));
+ bh[index_to_dir + VMUFAT_DIR_DOW] = bin2bcd((char)(now.tm_wday));
+}
+
+/*
+ * write out the date in bcd format
+ * in the appropriate part of the
+ * directory entry
+ */
+void vmufat_save_bcd(struct inode *in, char *bh, int index_to_dir)
+{
+ struct rtc_device *rtc;
+ rtc = rtc_class_open("rtc0");
+ if (!rtc)
+ vmufat_save_bcd_nortc(in, bh, index_to_dir);
+ else {
+ vmufat_save_bcd_rtc(rtc, in, bh, index_to_dir);
+ rtc_class_close(rtc);
+ }
+}
+
+static int vmufat_allocate_inode(umode_t imode,
+ struct super_block *sb, struct inode *in)
+{
+ int error = 0;
+ /* Executable files must be at the start of the volume */
+ if (imode & EXEC) {
+ in->i_ino = VMUFAT_ZEROBLOCK;
+ if (vmufat_get_fat(sb, 0) != VMUFAT_UNALLOCATED) {
+ printk(KERN_INFO "VMUFAT: cannot write excutable "
+ "file. Volume block 0 already allocated.\n");
+ error = -ENOSPC;
+ goto out;
+ }
+ } else {
+ error = vmufat_find_free(sb);
+ if (error >= 0)
+ in->i_ino = error;
+ }
+out:
+ return error;
+}
+
+static void vmufat_setup_inode(struct inode *in, umode_t imode,
+ struct super_block *sb)
+{
+ struct timespec64 current_time;
+ in->i_uid = current_fsuid();
+ in->i_gid = current_fsgid();
+ ktime_get_coarse_real_ts64(¤t_time);
+ in->i_mtime_sec = in->i_atime_sec = in->i_ctime_sec = current_time.tv_sec;
+ in->i_mode = imode;
+ in->i_blocks = 1;
+ in->i_sb = sb;
+ insert_inode_hash(in);
+ in->i_op = &vmufat_inode_operations;
+ in->i_fop = &vmufat_file_operations;
+ in->i_mapping->a_ops = &vmufat_address_space_operations;
+}
+
+static void vmu_handle_zeroblock(int recno, struct buffer_head *bh, int ino)
+{
+ /* offset and header offset settings */
+ if (ino != VMUFAT_ZEROBLOCK) {
+ ((u16 *) bh->b_data)[recno + VMUFAT_START_OFFSET16] =
+ cpu_to_le16(ino);
+ ((u16 *) bh->b_data)[recno + VMUFAT_HEADER_OFFSET16] = 0;
+ } else {
+ ((u16 *) bh->b_data)[recno + VMUFAT_START_OFFSET16] = 0;
+ ((u16 *) bh->b_data)[recno + VMUFAT_HEADER_OFFSET16] =
+ cpu_to_le16(1);
+ }
+}
+
+static void vmu_write_name(int recno, struct buffer_head *bh, char *name,
+ int len)
+{
+ memset((char *) (bh->b_data + recno + VMUFAT_NAME_OFFSET), '\0',
+ VMUFAT_NAMELEN);
+ memcpy((char *) (bh->b_data + recno + VMUFAT_NAME_OFFSET),
+ name, len);
+}
+
+static int vmufat_inode_create(struct mnt_idmap *idmap, struct inode *dir,
+ struct dentry *de, umode_t imode, bool excl)
+{
+ int i, j, entry, found = 0, error = 0, freeblock;
+ struct inode *inode;
+ struct super_block *sb;
+ struct memcard *vmudetails;
+ struct buffer_head *bh = NULL;
+
+ sb = dir->i_sb;
+ vmudetails = sb->s_fs_info;
+ inode = new_inode(sb);
+ if (!inode) {
+ error = -ENOSPC;
+ goto out;
+ }
+
+ mutex_lock(&vmudetails->mutex);
+ freeblock = vmufat_allocate_inode(imode, sb, inode);
+ if (freeblock < 0) {
+ mutex_unlock(&vmudetails->mutex);
+ error = freeblock;
+ goto clean_inode;
+ }
+ /* mark as single block file - may grow later */
+ error = vmufat_set_fat(sb, freeblock, VMUFAT_FILE_END);
+ mutex_unlock(&vmudetails->mutex);
+ if (error)
+ goto clean_inode;
+
+ vmufat_setup_inode(inode, imode, sb);
+
+ /* Write to the directory
+ * Now search for space for the directory entry */
+ mutex_lock(&vmudetails->mutex);
+ for (i = vmudetails->dir_bnum;
+ i > vmudetails->dir_bnum - vmudetails->dir_len; i--) {
+ brelse(bh);
+ bh = vmufat_sb_bread(sb, i);
+ if (!bh) {
+ mutex_unlock(&vmudetails->mutex);
+ error = -EIO;
+ goto clean_fat;
+ }
+ for (j = 0; j < VMU_DIR_ENTRIES_PER_BLOCK; j++) {
+ entry = j * VMU_DIR_RECORD_LEN;
+ if (((bh->b_data)[entry]) == 0) {
+ found = 1;
+ goto dir_space_found;
+ }
+ }
+ }
+ if (found == 0)
+ goto clean_fat;
+dir_space_found:
+ /* Have the directory entry
+ * so now update it */
+ if (imode & EXEC)
+ bh->b_data[entry] = VMU_GAME;
+ else
+ bh->b_data[entry] = VMU_DATA;
+
+ /* copy protection settings */
+ if (bh->b_data[entry + 1] != (char) NOCOPY)
+ bh->b_data[entry + 1] = (char) CANCOPY;
+
+ vmu_handle_zeroblock(entry / 2, bh, inode->i_ino);
+ vmu_write_name(entry, bh, (char *) de->d_name.name, de->d_name.len);
+
+ /* BCD timestamp it */
+ vmufat_save_bcd(inode, bh->b_data, entry);
+
+ ((u16 *) bh->b_data)[entry / 2 + VMUFAT_SIZE_OFFSET16] =
+ cpu_to_le16(inode->i_blocks);
+ mark_buffer_dirty(bh);
+ brelse(bh);
+ error = vmufat_list_blocks(inode);
+ if (error)
+ goto clean_fat;
+ mutex_unlock(&vmudetails->mutex);
+ d_instantiate(de, inode);
+ return error;
+
+clean_fat:
+ vmufat_set_fat(sb, freeblock, VMUFAT_UNALLOCATED);
+ mutex_unlock(&vmudetails->mutex);
+clean_inode:
+ iput(inode);
+out:
+ if (error < 0)
+ printk(KERN_INFO "VMUFAT: inode creation fails with error"
+ " %i\n", error);
+ return error;
+}
+
+static int vmufat_readdir(struct file *filp, struct dir_context *ctx)
+{
+ int filenamelen, index, j, k, error = 0;
+ struct vmufat_file_info *saved_file = NULL;
+ struct dentry *dentry;
+ struct inode *inode;
+ struct super_block *sb;
+ struct memcard *vmudetails;
+ struct buffer_head *bh = NULL;
+
+ inode = file_inode(filp);
+ dentry = filp->f_path.dentry;
+ sb = inode->i_sb;
+ vmudetails = sb->s_fs_info;
+ index = filp->f_pos;
+ /* handle . for this directory and .. for parent */
+ switch ((unsigned int) filp->f_pos) {
+ case 0:
+ error = ctx->actor(ctx, ".", 1, index++, inode->i_ino, DT_DIR);
+ if (error < 0)
+ goto out;
+ case 1:
+ error = ctx->actor(ctx, "..", 2, index++,
+ dentry->d_parent->d_inode->i_ino, DT_DIR);
+ if (error < 0)
+ goto out;
+ default:
+ break;
+ }
+
+ saved_file =
+ kmalloc(sizeof(struct vmufat_file_info), GFP_KERNEL);
+ if (!saved_file) {
+ error = -ENOMEM;
+ goto out;
+ }
+
+ for (j = vmudetails->dir_bnum -
+ (index - 2) / VMU_DIR_ENTRIES_PER_BLOCK;
+ j > vmudetails->dir_bnum - vmudetails->dir_len; j--) {
+ brelse(bh);
+ bh = vmufat_sb_bread(sb, j);
+ if (!bh) {
+ error = -EIO;
+ goto finish;
+ }
+ for (k = (index - 2) % VMU_DIR_ENTRIES_PER_BLOCK;
+ k < VMU_DIR_ENTRIES_PER_BLOCK; k++) {
+ int pos, pos16;
+ pos = k * VMU_DIR_RECORD_LEN;
+ pos16 = k * VMU_DIR_RECORD_LEN16;
+ saved_file->ftype = bh->b_data[pos];
+ if (saved_file->ftype == 0)
+ goto finish;
+ saved_file->fblk =
+ le16_to_cpu(((u16 *) bh->b_data)[pos16 + 1]);
+ if (saved_file->fblk == 0)
+ saved_file->fblk = VMUFAT_ZEROBLOCK;
+ memcpy(saved_file->fname,
+ bh->b_data + pos + VMUFAT_NAME_OFFSET,
+ VMUFAT_NAMELEN);
+ filenamelen = strlen(saved_file->fname);
+ if (filenamelen > VMUFAT_NAMELEN)
+ filenamelen = VMUFAT_NAMELEN;
+ error = ctx->actor(ctx, saved_file->fname, filenamelen,
+ index++, saved_file->fblk, DT_REG);
+ if (error < 0)
+ goto finish;
+ }
+ }
+
+finish:
+ filp->f_pos = index;
+ kfree(saved_file);
+ brelse(bh);
+out:
+ return error;
+}
+
+
+int vmufat_list_blocks(struct inode *in)
+{
+ struct vmufat_inode *vi;
+ struct super_block *sb;
+ long nextblock;
+ long ino;
+ struct memcard *vmudetails;
+ int error = -EINVAL;
+ struct list_head *iter, *iter2;
+ struct vmufat_block_list *vbl, *nvbl;
+ u16 fatdata;
+
+ vi = VMUFAT_I(in);
+ if (!vi)
+ goto out;
+ sb = in->i_sb;
+ ino = in->i_ino;
+ vmudetails = sb->s_fs_info;
+ error = 0;
+ nextblock = ino;
+ if (nextblock == VMUFAT_ZEROBLOCK)
+ nextblock = 0;
+
+ /* Delete any previous list of blocks */
+ list_for_each_safe(iter, iter2, &vi->blocks.b_list) {
+ vbl = list_entry(iter, struct vmufat_block_list, b_list);
+ list_del(iter);
+ kmem_cache_free(vmufat_blist_cachep, vbl);
+ }
+ vi->nblcks = 0;
+ do {
+ vbl = kmem_cache_alloc(vmufat_blist_cachep,
+ GFP_KERNEL);
+ if (!vbl) {
+ error = -ENOMEM;
+ goto unwind_out;
+ }
+ INIT_LIST_HEAD(&vbl->b_list);
+ vbl->bno = nextblock;
+ list_add_tail(&vbl->b_list, &vi->blocks.b_list);
+ vi->nblcks++;
+
+ /* Find next block in the FAT - if there is one */
+ fatdata = vmufat_get_fat(sb, nextblock);
+ if (fatdata == VMUFAT_UNALLOCATED) {
+ printk(KERN_ERR "VMUFAT: FAT table appears to have"
+ " been corrupted.\n");
+ error = -EIO;
+ goto unwind_out;
+ }
+ if (fatdata == VMUFAT_FILE_END)
+ break;
+ nextblock = fatdata;
+ } while (1);
+out:
+ return error;
+
+unwind_out:
+ list_for_each_entry_safe(vbl, nvbl, &vi->blocks.b_list, b_list) {
+ list_del_init(&vbl->b_list);
+ kmem_cache_free(vmufat_blist_cachep, vbl);
+ }
+ return error;
+}
+
+static int vmufat_clean_fat(struct super_block *sb, int inum)
+{
+ int error = 0;
+ u16 fatword, nextword;
+
+ nextword = inum;
+ do {
+ fatword = vmufat_get_fat(sb, nextword);
+ if (fatword == VMUFAT_ERROR) {
+ error = -EIO;
+ break;
+ }
+ error = vmufat_set_fat(sb, nextword, VMUFAT_UNALLOCATED);
+ if (error)
+ break;
+ if (fatword == VMUFAT_FILE_END)
+ break;
+ nextword = fatword;
+ } while (1);
+
+ return error;
+}
+
+/*
+ * Delete inode by marking space as free in FAT
+ * no need to waste time and effort by actually
+ * wiping underlying data on media
+ */
+static void vmufat_remove_inode(struct inode *in)
+{
+ struct buffer_head *bh = NULL, *bh_old = NULL;
+ struct super_block *sb;
+ struct memcard *vmudetails;
+ int i, j, k, l, startpt, found = 0;
+
+ if (in->i_ino == VMUFAT_ZEROBLOCK)
+ in->i_ino = 0;
+ sb = in->i_sb;
+ vmudetails = sb->s_fs_info;
+ if (in->i_ino > vmudetails->fat_len * sb->s_blocksize / 2) {
+ printk(KERN_WARNING "VMUFAT: attempting to delete"
+ "inode beyond device size");
+ goto ret;
+ }
+
+ mutex_lock(&vmudetails->mutex);
+ if (vmufat_clean_fat(sb, in->i_ino)) {
+ mutex_unlock(&vmudetails->mutex);
+ goto failure;
+ }
+
+ /* Now clean the directory entry
+ * Have to wander through this
+ * to find the appropriate entry */
+ for (i = vmudetails->dir_bnum;
+ i > vmudetails->dir_bnum - vmudetails->dir_len; i--) {
+ brelse(bh);
+ bh = vmufat_sb_bread(sb, i);
+ if (!bh) {
+ mutex_unlock(&vmudetails->mutex);
+ goto failure;
+ }
+ for (j = 0; j < VMU_DIR_ENTRIES_PER_BLOCK; j++) {
+ if (bh->b_data[j * VMU_DIR_RECORD_LEN] == 0) {
+ mutex_unlock(&vmudetails->mutex);
+ goto failure;
+ }
+ if (le16_to_cpu(((u16 *) bh->b_data)
+ [j * VMU_DIR_RECORD_LEN16 +
+ VMUFAT_FIRSTBLOCK_OFFSET16]) == in->i_ino) {
+ found = 1;
+ goto found;
+ }
+ }
+ }
+found:
+ if (found == 0) {
+ mutex_unlock(&vmudetails->mutex);
+ goto failure;
+ }
+
+ /* Found directory entry - so NULL it now */
+ for (k = 0; k < VMU_DIR_RECORD_LEN; k++)
+ bh->b_data[j * VMU_DIR_RECORD_LEN + k] = 0;
+ mark_buffer_dirty(bh);
+ /* Patch up directory, by moving up last file */
+ found = 0;
+ startpt = j + 1;
+ for (l = i; l > vmudetails->dir_bnum - vmudetails->dir_len; l--) {
+ bh_old = vmufat_sb_bread(sb, l);
+ if (!bh_old) {
+ mutex_unlock(&vmudetails->mutex);
+ goto failure;
+ }
+ for (k = startpt; k < VMU_DIR_ENTRIES_PER_BLOCK; k++) {
+ if (bh_old->b_data[k * VMU_DIR_RECORD_LEN] == 0) {
+ found = 1;
+ brelse(bh_old);
+ goto lastdirfound;
+ }
+ }
+ startpt = 0;
+ brelse(bh_old);
+ }
+lastdirfound:
+ if (found == 0) { /* full directory */
+ l = vmudetails->dir_bnum - vmudetails->dir_len + 1;
+ k = VMU_DIR_ENTRIES_PER_BLOCK;
+ } else if (l == i && k == j + 1) /* deleted entry was last in dir */
+ goto finish;
+ else if (k == 0) {
+ l = l + 1;
+ k = VMU_DIR_ENTRIES_PER_BLOCK;
+ if (l == i && k == j + 1)
+ goto finish;
+ }
+ /* fill gap first then wipe out old entry */
+ bh_old = vmufat_sb_bread(sb, l);
+ if (!bh_old) {
+ mutex_unlock(&vmudetails->mutex);
+ brelse(bh);
+ goto failure;
+ }
+ for (i = 0; i < VMU_DIR_RECORD_LEN; i++) {
+ bh->b_data[j * VMU_DIR_RECORD_LEN + i] =
+ bh_old->b_data[(k - 1) * VMU_DIR_RECORD_LEN + i];
+ bh_old->b_data[(k - 1) * VMU_DIR_RECORD_LEN + i] = 0;
+ }
+ mark_buffer_dirty(bh_old);
+ mark_buffer_dirty(bh);
+ brelse(bh_old);
+
+finish:
+ mutex_unlock(&vmudetails->mutex);
+ brelse(bh);
+ return;
+
+failure:
+ printk(KERN_WARNING "VMUFAT: Failure to read volume,"
+ " could not delete inode - filesystem may be damaged\n");
+ret:
+ return;
+}
+
+/*
+ * vmufat_unlink - delete a file pointed to
+ * by the dentry (only one directory in a
+ * vmufat fs so safe to ignore the inode
+ * supplied here
+ */
+static int vmufat_unlink(struct inode *dir, struct dentry *dentry)
+{
+ struct inode *in;
+ in = dentry->d_inode;
+ vmufat_remove_inode(in);
+ return 0;
+}
+
+static int vmufat_get_block(struct inode *inode, sector_t iblock,
+ struct buffer_head *bh_result, int create)
+{
+ struct vmufat_inode *vin;
+ struct vmufat_block_list *vlist, *vblk;
+ struct super_block *sb;
+ struct memcard *vmudetails;
+ int cural;
+ int finblk, nxtblk, exeblk;
+ struct list_head *iter;
+ sector_t cntdwn = iblock;
+ sector_t phys;
+ int error = -EINVAL;
+
+ vin = VMUFAT_I(inode);
+ if (!vin || vin->nblcks <= 0)
+ goto out;
+ vlist = &vin->blocks;
+ sb = inode->i_sb;
+ vmudetails = sb->s_fs_info;
+
+ if (iblock < vin->nblcks) {