Investigating the slow file transfer of calculator under Linux
Posté le 06/11/2026 20:12
One of the longstanding issues I encountered when switching from Windows to Linux is the file transfer of Casio USB mass storage calculators between both platforms. That is, Windows is much faster than Linux in this case. For instance, transferring a 3.4 MB KhiCAS MPM add-in to my Graph Math+ takes about 3-4 minutes in Linux, whereas it is only 30 seconds at most in Windows. To figure out what happened during the write operations, I decided to capture and dissect these UMS packets using Wireshark, and I have to say this is wild.
For context, UMS devices are managed by the
usb-storage driver in Linux. The parameter
max_sectors controls the maximum amount of data that will be transfered to or from the device in a single command. Its default value is 240, which means a maximum transferred data size of 120 KiB where a sector is 512 bytes. The send retrial happens when dCSWDataResidue is non-zero and bCSWStatus = 0 (partial success), and its maximum is SD_MAX_RETRIES = 5. dCSWDataResidue must represent the difference between the amount of data expected from dCBWDataTransferLength and the actual amount of data transferred.
Let's take look at the 1 MB file capture in Linux. Applying
usbms.dCBWSignature filter:
Each 4096-byte packet advances LBA as expected and has a duration of 0.01 s. Keep this delta in mind because it will be important later. Now you can see the following 6 packets of 120 KiB data are at LBA = 0x5e70, and it is the same for the next group at LBA = 0x5f60 and so on. Applying additional
usbms.dCSWSignature filter and zooming in LBA = 0x5e70:
The calculator USB driver reports dCSWDataResidue = 118784, implying it only receives 122880 - 118784 = 4096 bytes from the host, and here is the problem. If the calculator really receives 4 KiB of data only, its delta should be the same as the one for the previous packets which is 0.01 s. However the value here is 0.3 s, thus having the same flash write speed, i.e. 4096 / 0.01 = 400 KiB/s and 122880 / 0.3 = 400 KiB/s. Combining with the fact that bCSWStatus = 0x00 (Good), this indicates the 120 KiB of data is actually written to the calculator storage on the first packet, and its driver is lying about the data residue that misleads the Linux SCSI driver to re-send the exact CBW command for 5 times. In Windows case, USBSTOR ignores the entire field and continues to send the next CBW, hence a faster speed.
Even worse, the misleading residue also affects the read operation, which leads to a detection failure that surfaces whenever I update systemd to 261 or higher. Lephenixnoir has previously
documented on this matter and identified that it was due to the constant I/O errors generated from the USB calculator. Taking an example from his libblkid log:
[ +0.000011] sd 0:0:0:0: [sda] tag#0 CDB: Read(10) 28 00 00 00 01 f0 00 00 10 00
[ +0.000006] I/O error, dev sda, sector 496 op 0x0:(READ) flags 0x880700 phys_seg 2 prio class 2
[ +0.028607] sd 0:0:0:0: [sda] tag#0 FAILED Result: hostbyte=DID_ERROR driverbyte=DRIVER_OK cmd_age=0s
The 8th and 9th byte of CDB
00 10 represents the transfer length that specifies the number of contiguous logical blocks to read. Here 0x0010 = 16 blocks * 512 bytes = 8 KiB, which is larger than 4 KiB and triggers the bug.
We can reproduce this with the following bash script, and the expected result would be "ok" for the first 4k and "EIO" for the rest.
for bs in 1k 2k 3k 4k 5k 6k 7k 8k 9k 10k; do
sudo dd if=/dev/sda1 of=/dev/null bs=$bs count=1 iflag=direct \
2>/dev/null && echo "$bs ok" || echo "$bs EIO"
done
Linux has various "USB quirks" flags to deal with finicky storages, and the relevant one for our calculators is US_FL_IGNORE_RESIDUE in
drivers/usb/storage/unusual_dev.h. The following patch was
submitted to the upstream Linux kernel by Cakeisalie5 and is under review. Thanks @Cake <3
UNUSUAL_DEV( 0x07cf, 0x6102, 0x0100, 0x0100,
"Casio",
"fx-CP400",
USB_SC_DEVICE, USB_PR_DEVICE, NULL,
US_FL_IGNORE_RESIDUE ),
UNUSUAL_DEV( 0x07cf, 0x6103, 0x0100, 0x0100,
"Casio",
"fx-CG50",
USB_SC_DEVICE, USB_PR_DEVICE, NULL,
US_FL_IGNORE_RESIDUE ),
While waiting for the merge, you can immediately apply the equivalent fix with the following commands:
echo "options usb-storage quirks=07cf:6102:r,07cf:6103:r" | sudo tee /etc/modprobe.d/casio-usb-quirks.conf
sudo modprobe -r uas; sleep 2; sudo modprobe -r usb-storage; sudo modprobe usb-storage
The temporary alternative is to set
max_sectors_kb to 4 so that every read is then split into 4096-byte commands and the residue arithmetic never goes wrong:
echo 4 | sudo tee /sys/block/sda/queue/max_sectors_kb
sudo udevadm trigger
In a nutshell:
Citer : Posté le 07/11/2026 02:58 | #
Hey. Sorry for the inactivity, been a bit busy lately.
Thanks for looking into this; Nonetheless I've been inactive, I will make sure to get it upstream faster by getting a
Tested-by tag
Also one more thing, if you have anything like this in the future, please don't go into personal messages on
the forum, write me an email or reply to that Linux port branch. I'd be happy to help.
And again,
Thanks
GPG : F747 7C61 CC79 5021 E060 6117 1E55 9E70 DF46 E38B
Citer : Posté le 07/11/2026 03:46 | #
Got you tested and Tested-by tag
I replied to your friend and the maintainer who Acked-by it
(for some reason Planet-casio flagged and removed my message, or magic).
Good job on the patch.
If you need any help with this next time,
write me an email or reply to Linux development
thread.
GPG : F747 7C61 CC79 5021 E060 6117 1E55 9E70 DF46 E38B
Citer : Posté le 07/11/2026 05:44 | #
Got you tested and Tested-by tag
I replied to your friend and the maintainer who Acked-by it
(for some reason Planet-casio flagged and removed my message, or magic).
Thanks! Linux kernel 7.4 LTS is on the way, and I sure hope it gets merged before the official release
Also one more thing, if you have anything like this in the future, please don't go into personal messages on
the forum, write me an email or reply to that Linux port branch. I'd be happy to help.
Noted