A problem exists with the Linux 2.6 kernel as supplied with RHEL4/CentOS4 when it comes to reading the "PC Drive" of a Korg D3200 digital multitracker. This partition is shared via USB and presents itself as a standard USB mass storage device. However, something is up with the formatting of the FAT16 filesystem on that partition.
Problem description
Here are the log entries that occur when the D3200 is connected and set to "USB Slave Mode":
kernel: usb 1-2: new full speed USB device using address 2 kernel: SCSI subsystem initialized kernel: Initializing USB Mass Storage driver... kernel: scsi0 : SCSI emulation for USB Mass Storage devices kernel: Vendor: ST340014 Model: A Rev: 0 0 kernel: Type: Direct-Access ANSI SCSI revision: 02 kernel: USB Mass Storage device found at 2 kernel: usbcore: registered new driver usb-storage kernel: USB Mass Storage support registered. kernel: SCSI device sda: 78165360 512-byte hdwr sectors (40021 MB) kernel: sda: assuming drive cache: write through kernel: sda: sda1 sda2 sda4 kernel: Attached scsi disk sda at scsi0, channel 0, id 0, lun 0
That's all well and good. But the problem is that when I try to mount the drive, it won't mount, and I get the following in syslog:
kernel: FAT: invalid first entry of FAT (0xffffff8 != 0xf00f800) kernel: VFS: Can't find a valid FAT filesystem on dev sda4.
I'm no expert at this stuff, but it seems clear that what is at the start of the filesystem is not quite what the kernel is expecting. Continuing on with the presumption that the failing check is superfluous and the filesystem really is otherwise valid (because otherwise, I'm screwed), the quest now is to disable or add an exception to this check in the FAT driver source and recompile it. The following describes how to most easily accomplish this on a RHEL/CentOS/Fedora system, where the kernel source is distributed as an SRPM for the kernel package [1].
Download and prep the kernel source
The RHEL/CentOS Release Notes [2] say that one doesn't need an exploded kernel source tree in order to build a module, but you do kinda need the source tree if that's where the code for the module you're building is distributed, as the FAT driver is.
First, I downloaded the appropriate kernel.srpm and installed it into my RPM build area. If you have never built anything from SRPM before, you need to brush up a bit first [3]. At the very least, set up a ~/.rpmmacros file so that you can build RPMs as a non-root user, set up the necessary directory tree in a writeable location, then come back to this step. As an example, I'll use 2.6.9-34.EL, the current errata release for CentOS 4 Update 3.
localhost% wget http://mirror.centos.org/centos-4/4.3/os/SRPMS/kernel-2.6.9-34.EL.src.rpm localhost% rpm -K kernel-2.6.9-34.EL.src.rpm kernel-2.6.9-34.EL.src.rpm: (sha1) dsa sha1 md5 gpg OK localhost% rpm -ivh kernel-2.6.9-34.EL.src.rpm kernel ########################################### [100%]
Next, prep the source, to apply all patches and configuration options provided in the SRPM. You could simply extract the one needed file out of the main tarball, but it would be missing any other patches that your distribution may add. Running the rpmbuild prep process results in an exploded and patched source tree identical to that which the OS vendor uses to build the kernel binary they distribute.
localhost% rpmbuild --target=i686 -bp ~/rpmbuild/kernel-2.6.9/kernel-2.6.spec Building target platforms: i686 Building for target i686 Executing(%prep): [...] [...] + exit 0
Code to change
The source file that needs tweaking is linux-2.6.9/fs/fat/inode.c. The line that prints the above error message is located about 1000 lines down.
if (FAT_FIRST_ENT(sb, media) == first) { /* all is as it should be */ } else if (media == 0xf8 && FAT_FIRST_ENT(sb, 0xfe) == first) { /* bad, reported on pc9800 */ } else if (media == 0xf0 && FAT_FIRST_ENT(sb, 0xf8) == first) { /* bad, reported with a MO disk on win95/me */ } else if (first == 0) { /* bad, reported with a SmartMedia card */ } else { if (!silent) printk(KERN_ERR "FAT: invalid first entry of FAT " "(0x%x != 0x%x)\n", FAT_FIRST_ENT(sb, media), first); goto out_invalid; }
It would be easy enough to add another "else if" to check for the values returned from the Korg's filesystem, but I wondered if this had ever been addressed in later versions of the kernel. I grabbed the source to 2.6.16.9 and found that the above had been replaced with:
/* * The low byte of FAT's first entry must have same value with * media-field. But in real world, too many devices is * writing wrong value. So, removed that validity check. * * if (FAT_FIRST_ENT(sb, media) != first) */
In other words, the kernel used to try to be picky about this, but too much hardware doesn't live up to the spec, and it's easiest to just not bother checking than to add an exception for every non-compliant device. Simply remove the entire if/else block and be done with it.
Rebuild the module
If I was going to rebuild the entire kernel, I would need to diff out a patch file containing this change, and include that patch in the spec file, along with all the other things that must be done when rolling one's own kernel package. However, I'm lazy, and just want to rebuild this one little driver module, not the whole kernel. In order to do that, I need to edit the Makefile in the linux-2.6.9/fs/fat directory as follows:
# # Makefile for the Linux fat filesystem support. # obj-m := fat.o fat-objs := cache.o dir.o file.o inode.o misc.o fatfs_syms.o KDIR := /lib/modules/2.6.9-34.EL/build PWD := $(shell pwd) default: $(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules
Make sure that's a tab and not spaces on the last line, and that you have the correct kernel version in the KDIR line. If you're already running the kernel you're building the module for, you can use $(shell uname -r) in place of the hardcoded version number.
Now do a "make" to build the new FAT driver module.
localhost% setenv ARCH i386 localhost% make make -C /lib/modules/2.6.9-34.EL/build \ SUBDIRS=/home/brad/rpmbuild/BUILD/kernel-2.6.9/linux-2.6.9/fs/fat modules make[1]: Entering directory `/usr/src/kernels/2.6.9-34.EL-i686' CC [M] /home/brad/rpmbuild/BUILD/kernel-2.6.9/linux-2.6.9/fs/fat/cache.o CC [M] /home/brad/rpmbuild/BUILD/kernel-2.6.9/linux-2.6.9/fs/fat/dir.o CC [M] /home/brad/rpmbuild/BUILD/kernel-2.6.9/linux-2.6.9/fs/fat/file.o CC [M] /home/brad/rpmbuild/BUILD/kernel-2.6.9/linux-2.6.9/fs/fat/inode.o CC [M] /home/brad/rpmbuild/BUILD/kernel-2.6.9/linux-2.6.9/fs/fat/misc.o CC [M] /home/brad/rpmbuild/BUILD/kernel-2.6.9/linux-2.6.9/fs/fat/fatfs_syms.o LD [M] /home/brad/rpmbuild/BUILD/kernel-2.6.9/linux-2.6.9/fs/fat/fat.o Building modules, stage 2. MODPOST CC /home/brad/rpmbuild/BUILD/kernel-2.6.9/linux-2.6.9/fs/fat/fat.mod.o LD [M] /home/brad/rpmbuild/BUILD/kernel-2.6.9/linux-2.6.9/fs/fat/fat.ko make[1]: Leaving directory `/usr/src/kernels/2.6.9-34.EL-i686'
Installation
I simply renamed the original module, and copied the new one into its place. The old driver needs to be unloaded, and the new one loaded.
localhost# modprobe -r vfat localhost# modprobe -r fat localhost# mv /lib/modules/2.6.9-34.EL/kernel/fs/fat/fat.ko \ /lib/modules/2.6.9-34.EL/kernel/fs/fat/_fat_orig.ko localhost# cp fat.ko /lib/modules/2.6.9-34.EL/kernel/fs/fat/fat.ko localhost# chown root:root !!$ localhost# chmod 744 !!$ localhost# modprobe fat
Results
After making this change, I am able to mount the D3200's shared drive and read and write to it just fine.
Footnotes
- Kernel 2.6 On Fedora-based Systems
- Red Hat Enterprise Linux AS 4 Release Notes
- How to patch and rebuild an RPM package