To head this off before I get questions: yes, dear Reader, it is 2021, and I am writing about a floppy disk drive. Specifically, a USB floppy disk drive that’s connected to a Raspberry Pi.
This is a story about tracing a weird behaviour in a complex computer system.
I’ve always thought floppy disks were super cool. They’re really pleasingly tactile; they have this super good tchünk sound when you insert and eject disks, they’re able to hold a whole 1.44 MB of data1, and they have a nice use-metaphor – you can tell when your computer is reading and writing to them because they’re hella noisy, and I think that’s actually a really useful property for removable media. Remember how nice it was to insert a VHS tape? At a certain point the player starts pulling the cassette out of your hand, and it’s clear that you’re agreed on how the relationship works. There’s no USB superposition, there’s no futzing around with drivers or waiting for things to show up in the operating system; you get a kind of gritty, visceral feedback right from the first touch.
I like the tactile-ness of floppy disks so much that back in 2018 I started building an audio player based on the metaphor:
Floppy disk drives don’t really support media detection though; there’s no way of knowing that a disk is in the drive without trying to read a sector from it. So for this project, my method of checking for disk presence was originally to:
- Try reading a file that should be on the disk
- If we got something, then the disk is present!
- If we didn’t, then try creating that file
- If that also fails, then probably the disk isn’t available.
- DO IT ALL AGAIN
The disk was super quiet when reading the same file over and over again. This was an accident, but I was grateful for it considering this was going to be a music player. I’d put the quietness down to the fact that the file I’m reading is very small (~100 bytes; it’s just a link to a Spotify playlist), and fits on one magnetic track (~9kb). My hypothesis was, moving the read head between tracks is the noisy part, spinning the disk is comparatively quiet. But then, I also noticed that the floppy disk drive was making a weird clicking sound when there wasn’t anything in there.
Oh, it’s probably because I’m trying to read from the disk in a loop! I guess it’s looking for the file, but doesn’t know where it is and has to poke around the disk a bit.
I closed my program. But then the clicking sound stayed. Why is it clicking?
Step 1: Google it
Let’s describe our symptoms to Google:
Oh, these are all results for Windows. Let’s try again:
After reading, googling, reading some more, here’s what I learned: the linux kernel will “poll” drives with removable media to check that they haven’t been removed, and apparently you can turn that off by using something called “udev” rules. I didn’t know anything about those, so I read up on the basics of udev, and along the way discovered that udev is mostly just for configuring stuff automatically when a device connects to the system. It turns out we don’t need udev for short-term changes and testing, and I can just write settings directly to the kernel2 🤯.
So, let’s turn off the probing for this disk:
# As root, assuming /dev/sda is the path to the floppy disk:
echo 0 >/sys/`udevadm info --query=path /dev/sda`/events_poll_msecs
Hmmm, ok! Turning off the probing seems to make everything quiet. Amazing… but now my program thinks that the disk is still inserted even when it’s not:
Huh. I’ve broken my disk detection logic. That sounds like a caching problem.
Part 2: Google, again
Back to Google:
Again, summarising the situation:
You can apparently make cache-less reads of a raw disk (see
man 8 raw
), but it seems kinda deprecated? The manpage says:Rather than using raw devices, applications should prefer
open(2)
devices, such as/dev/sda1
, with theO_DIRECT
flag.This feels like it’d need sudo, and sounds like you’d lose the whole filesystem, which is a can of worms I’m not ready to open.
I found out that the programs
xxd
anddd
would allow me to read from the disk in direct mode – both of them read the device block by block, and can print the data to the screen. But when I ran these programs, I noticed that the disk was noisy. Real noisy.
And then I put two and two together: the disk cache was the reason why reading the same file over and over again was quiet.
So the information we have at this point:
- We need a way of detecting whether the disk is inserted or not. Linux normally takes care of this through in-kernel polling, but polling makes the nasty clicking sound.
- We can disable kernel polling, but then we have to read the disk raw if we want to figure out if it’s still inserted, and that’s also noisy!
So I started wondering, why does the kernel probing make the nasty clicking sound? What is it doing to find out if there’s a disk? Someone on a forum post somewhere mentioned that there was a “quiet probe mode” for some other type of floppy disk, so I wondered if I had the option to do something similar.
If this polling is happening in the kernel, I guess that means it’s time to… read the kernel source?
Part 3: Kernel diving
Way back when I was an Android developer, I discovered reading the source code as a debugging strategy. Documentation is great, but it’s not always available, and it can never cover all the side effects comprehensively – it’s rare that method documentation contain notes like “Warning! Some devices might make a whole bunch of noise when you call this”.
First, let’s download the source for the linux version we’re using.
# Print kernel version
$ uname -r
5.4.51-v7+
$ git clone --depth 1 --branch v5.4 [email protected]:torvalds/linux.git
Now, the kernel has a lot of code, so let’s narrow down our search space by printing the kernel logs, and then watching for messages when we insert / remove disks. We’ll want to crank up the verbosity of the kernel logs first.
# Crank to MAX VERBOSITY (on my system, this will give us up to
# INFO, but we'd need to recompile the kernel if we wanted DEBUG)
echo 7 | sudo tee /proc/sys/kernel/printk
# Now print and watch the kernel logs
dmesg -w
Now:
- plug in the USB drive
- insert a disk
- mount and read the disk:
pmount -s -t vfat /dev/sda floppy cat /media/floppy/link.txt
- eject the disk.
I got logs like this. I’ve annotated them with what I did to the disk that might’ve triggered the event:
📝 connect drive over USB
[ 160.808721] usb 1-1.2: new full-speed USB device number 7 using dwc_otg
[ 160.942361] usb 1-1.2: New USB device found, idVendor=03ee, idProduct=6901, bcdDevice= 2.00
[ 160.946707] usb 1-1.2: New USB device strings: Mfr=1, Product=2, SerialNumber=0
[ 160.949088] usb 1-1.2: Product: MITSUMI0
[ 160.951423] usb 1-1.2: Manufacturer: MITSUMI0
[ 160.955302] usb-storage 1-1.2:1.0: USB Mass Storage device detected
[ 160.988923] scsi host0: usb-storage 1-1.2:1.0
[ 162.051822] scsi 0:0:0:0: Direct-Access MITSUMI USB UFDD 061M 0.00 PQ: 0 ANSI: 0 CCS
[ 162.056954] sd 0:0:0:0: Attached scsi generic sg0 type 0
[ 162.115750] sd 0:0:0:0: Power-on or device reset occurred
[ 162.307854] sd 0:0:0:0: [sda] Attached SCSI removable disk
📝 insert disk
[ 180.035797] sd 0:0:0:0: [sda] 2880 512-byte logical blocks: (1.47 MB/1.41 MiB)
[ 181.087570] sda:
📝 ran pmount command
[ 218.367027] FAT-fs (sda): Volume was not properly unmounted. Some data may be corrupt. Please run fsck.
📝 ejected disk
[ 265.700035] VFS: busy inodes on changed media or resized disk sda
Hmmm, this wasn’t as useful as I expected, but there’s already a bunch of kernel modules listed here that we can search the source for (usb
, usb-storage
, scsi
, sd
).
In addition, through my adventures with udev
, I’d learned that you can find out info about your USB devices using the (appropriately named) usb-devices
command:
$ usb-devices
...
T: Bus=01 Lev=02 Prnt=02 Port=01 Cnt=02 Dev#= 4 Spd=12 MxCh= 0
D: Ver= 1.10 Cls=00(>ifc ) Sub=00 Prot=00 MxPS= 8 #Cfgs= 1
P: Vendor=03ee ProdID=6901 Rev=02.00
S: Manufacturer=MITSUMI0
S: Product=MITSUMI0
C: #Ifs= 1 Cfg#= 1 Atr=80 MxPwr=98mA
I: If#=0x0 Alt= 0 #EPs= 3 Cls=08(stor.) Sub=04 Prot=00 Driver=usb-storage
This gives us another note: the driver for this device is the ‘usb-storage’ driver, not the floppy driver.
The output I got from all this was enough that I was able to Ctrl+F my way through the source until I found a starting point.
Part 3½: A whirlwind tour of USB floppy drivers in the kernel source
Like onions, parfaits, and ogres, linux drivers have layers. It turns out that this USB-connected floppy drive is a daisy-chained set of drivers / kernel modules:
usb-storage
is at the lowest level.get_protocol()
in this file switches based on the device subclass, which in this case is ‘04’ (Sub=04
in theusb-devices
output), which is the “Uniform Floppy Interface (UFI)”. Yep, USB hotpluggable storage has a specific subclass for floppy disks!
scsi
andsd
is the next level – the usb storage driver adds a ‘SCSI host’, with some tweaks for speaking the UFI protocol3. It also registers a ‘SCSI Disk’ (sd
) for the logical storage device connected to the SCSI host4.sd
provides a bunch of callbacks to agenhd
(generic hard disk) object, which (I think) implements the required functionality so that the SCSI device is usable as a block-layer device5.
Amazing! I then remembered that I’d modified the /sys/.../events_poll_msecs
file to turn kernel polling on and off, and wondered if that was actually defined in the kernel, and if it could give me a pointer to the poll code?
- Searching for
events_poll_msecs
reveals a pair of methods ingenhd.c
:disk_events_poll_msecs_show
, anddisk_events_poll_msecs_store
, which presumably get and set the polling interval. - Hunting around a bit helped me find the
disk_check_events
method, which callsdisk->fops->check_events
, which is a callback configured by a lower level of the driver stack. - We can trace it all the way back to
sd_check_events
insd.c
… - which calls a method named
scsi_test_unit_ready
6… - which sends a protocol instruction called
TEST_UNIT_READY
over the wire.
Ok! That’s… hmm. It looks like all the computer is doing is saying to the USB floppy drive “HEY. Are you ready?” And then the floppy drive is saying
BRZT BRZT BZRT
no,
not yet sorry..
It seems like we’re not going to be able to make it quieter without looking at the floppy firmware, and that feels like something I don’t wanna touch.
Hmmm. Ok. Let’s recheck everything we know:
- The tchk sound is an invariable side-effect of checking for a disk when there is no disk. This is undesirable because… it gets annoying really quickly.
- If we disable polling, we disable the tchk sound, but we also lose the ability to detect when a disk has been ejected, unless we use raw mode on the disk.
- Disabling polling without raw mode is undesirable because I want the music to stop when you eject the disk (just like every other music player ever).
- But if we use raw mode on the disk, then the disk is mega noisy all the time, and that’s also not acceptable – again, this is a music player.
At this point, I was pretty stumped – I was sitting at my desk, staring blankly at the Raspberry Pi and attached floppy drive.
And then it hit me:
Part 4: A dirty, dirty hardware solution
Look how much the frame travels when you insert a disk! That’s like, a good 5 mm!
Hmm… I wonder if we could just… glue a switch onto the case and read that from the Raspberry Pi’s GPIO pins?
And then let’s wire it to the Raspberry Pi:
Now, my program’s going to use the Raspberry Pi’s GPIO as a trigger for detecting when the floppy disk is inserted or ejected.
- If the pin moves from high → low:
- The disk has been inserted
- Mount the disk, read the file, start playing music
- If the pin moves from low → high:
- The disk has been ejected
- Unmount the disk, stop playing music.
There are some huge advantages to this:
- After we detect an insert, we read just once and then never need to read again. As such, the disk is extremely quiet. Which is a nice property for an audio player.
- Disk ejection detection is much snappier than it was before? Previously it would take up to ~2 seconds (the probe time) to notice that the disk had been ejected. Now, it’s instant, and powered by the magic of interrupts, so I don’t even need to write a dirty poll loop or anything.
- It actually works 😅
There are also some disadvantages:
- Having a hardware hack seems more complex and brittle than having everything in software? But on the other hand, I was relying upon so many coincidences, side effects and implementation details in software that I… actually think this is more reliable.
Obscure, but knowable, magic
Now I’ve got a floppy disk drive that does its work quietly and gives me signals about when disks are inserted or not, and after three days of digging through source code and trying to understand parts of linux, I ended up solving it by gluing on an upside-down keyswitch.
I guess the lesson here is: Computers may be magic, and maybe even obscure magic, but they’re certainly not unknowable magic. And even then, sometimes the best thing you can do is just bypass all the magic and implement a cheap parlour trick 🤹🏻♂️.
That’s enough to fit most classic works of Russian literature, or, like, a 20th of the New York Times homepage! ↩︎
I cannot remember where I learned about this initially, but Bite-Sized Linux is a good intro to some adjacent concepts! ↩︎
SCSI is an old-school protocol that’s been used for storage since I remember computers. I had no idea that USB storage secretly used SCSI protocols under the hood, but computers are full of shenanigans like this! ↩︎
If you’ve ever wondered what the
sd
in/dev/sda
stands for, it’s SCSI Disk! ↩︎This is vague because I didn’t need to dive in deeper here, but Block Device Drivers is a great tutorial about how block devices work. ↩︎
This method also has an amazing comment that explicitly references the Imoega Jaz 1G as a device that it’s trying to accomodate. ↩︎