Enabling TRIM on an external SSD on a Raspberry Pi

I've been doing a lot of benchmarking and testing with the Raspberry Pi 4 and SSDs connected via USB. I explored UASP Support, which USB SSDs are the fastest, and I'm now booting my Pis from USB SSDs.

Anyways, one thing that I have wondered about—and some people have asked me about—is TRIM support.

I'm working on a new video for my YouTube channel that will go into some more detail on which of the drives I tested support TRIM, but while I was researching for that video, I also found that TRIM support in Linux is not as simple as it seems at first glance—it's definitely not plug-and-play, in my experience.

While internal microSD cards seem to support TRIM out of the box, none of the external USB drives I tested supported it out of the box. They all needed a little help!

Much of the data in this post I attribute to this excellent comment by tom.ty89 on the Pi Forums.

What is TRIM? Why should I care about it?

This blog post is not going to get into the weeds on TRIM; I recommend this article on Crucial.com if you want to learn about it.

Does my SSD support TRIM?

In Linux, you can check if TRIM is currently supported by running one of the following commands (this blog post assumes you're booting off the SSD, so it's device /dev/sda—if you are not booting from the SSD you're checking, substitute accordingly!):

$ sudo fstrim -v /

If this reports back fstrim: /: the discard operation is not supported, then TRIM is not enabled.

You can also check with:

$ lsblk -D

If the DISC-MAX value is 0B, then TRIM is not enabled.

Now, being enabled and being supported in firmware are two different things. Some of my drives actually support TRIM even if it's not enabled out of the box.

For example, testing with my Corsair Flash Voyager GTX flash drive, I was able to determine the firmware supports TRIM, and I was then able to manually enable TRIM following tom.ty89's instructions.

Checking if the Firmware supports TRIM

To check if the device firmware supports TRIM, switch to the root user (otherwise you'll need to use sudo before most of the rest of the commands in this post), and install a couple utilities we'll need for the rest of this process:

$ sudo su
# apt-get install -y sg3-utils lsscsi

Run the following command and check the Maximum unmap LBA count:

# sg_vpd -p bl /dev/sda
Block limits VPD page (SBC):
...
  Maximum unmap LBA count: 4194240
  Maximum unmap block descriptor count: 1
...

Take note of it, then run the following command and check the Unmap command supported (LBPU):

# sg_vpd -p lbpv /dev/sda
Logical block provisioning VPD page (SBC):
  Unmap command supported (LBPU): 1
...

If the Maximum unmap LBA count is greater than 0, and Unmap command supported (LBPU) is 1, then the device firmware likely supports TRIM.

Warning: A few devices seemed to indicate they supported TRIM in firmware, like my Arcanite USB 3.1 Flash Drive... but when I tried enabling it I got a few errors. Then I tried running fstrim -v / on a whim, and ended up corrupting the drive's firmware, to the point it won't mount and can't be formatted anymore. So make sure you have a backup of any important data before you try on a drive that might not actually support TRIM!

Enabling TRIM

Now, if you know your device firmware supports TRIM, but it's just not in use currently, we can work on enabling TRIM.

Check on the current provisioning_mode for all drives attached to the Pi:

# find /sys/ -name provisioning_mode -exec grep -H . {} + | sort
/sys/devices/platform/scb/fd500000.pcie/pci0000:00/0000:00:00.0/0000:01:00.0/usb2/2-1/2-1:1.0/host0/target0:0:0/0:0:0:0/scsi_disk/0:0:0:0/provisioning_mode:full

We're going to need to change the provisioning_mode from full to unmap; but if you have more than one drive attached, you need to confirm which drive you need to change. You can do that using lsscsi:

# lsscsi
[0:0:0:0]    disk    Corsair  Voyager GTX      0     /dev/sda

Once you've confirmed which drive you need to change (in my case, there's only one, making this very easy), change the value from full to unmap in the path that the find command returned:

echo unmap > /sys/devices/platform/scb/fd500000.pcie/pci0000:00/0000:00:00.0/0000:01:00.0/usb2/2-1/2-1:1.0/host0/target0:0:0/0:0:0:0/scsi_disk/0:0:0:0/provisioning_mode

Run the find command again to confirm the provisioning_mode is now unmap.

Now, you need to update the discard_max_bytes value for the drive, based on the Maximum unmap LBA count value you got from the sg_vpd -p bl /dev/sda command earlier, times the Logical block length value you get from the sg_readcap -l /dev/sda command. In my case (your values may be different):

# echo $((4194240*512))
2147450880

Then write that value into the drive's discard_max_bytes setting. In my case:

# echo 2147450880 > /sys/block/sda/queue/discard_max_bytes

Now, to confirm TRIM is enabled, run:

# fstrim -v /
/: 117.6 MiB (123346944 bytes) trimmed

It should not give an error, and depending on how many blocks it needs to clean up, it could take a few seconds (or longer!).

Making it stick

These values will all be reset next time you reboot the Pi. After a reboot, you get:

# fstrim /
fstrim: /: the discard operation is not supported

So, to make the rules stick, you need to add a udev rule:

# nano /etc/udev/rules.d/10-trim.rules

And add the following in that file:

ACTION=="add|change", ATTRS{idVendor}=="1b1c", ATTRS{idProduct}=="1a0e", SUBSYSTEM=="scsi_disk", ATTR{provisioning_mode}="unmap"

Now, you're probably wondering, _where the heck did he get the idVendor and idProduct? I used the handy lsusb utility:

# lsusb
Bus 002 Device 002: ID 1b1c:1a0e Corsair 
Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 001 Device 002: ID 2109:3431 VIA Labs, Inc. Hub
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

And looking at the 'Corsair' line, the vendor is the first part of the identifier (1b1c), and the product is the second part (1a0e). If you want a ton of detailed information about a USB device, use lsusb -vd 1b1c:1a0e (with the vendor:product specified).

Anyways, make sure to save your 10-trim.rules file, then reboot the Pi. Try running fstrim again, and make sure it works:

$ sudo fstrim -v /
/: 111.4 GiB (119574462464 bytes) trimmed

The first time fstrim is run after a reboot, it will trim all the free space, which is why it gives such a large number. From that point on, the kernel will track changed blocks and trim only that data until the next boot.

Automatic trimming

The last thing you will need to do to make sure the TRIM command is run automatically in the background (so you don't need to run fstrim manually) is to enable the built-in fstrim.timer.

To do that, run the command:

$ sudo systemctl enable fstrim.timer

By default, it will run weekly. Yay, now you have TRIM! That is, if your drive supports it. Check back in later and subscribe to my blog or YouTube channel—I'll be posting a video and blog post with data on which drives support TRIM!

Comments

An update, based on the bold Warning in the middle of this page: Apparently I have corrupted the firmware/controller of my Arcanite USB 3.1 flash drive by trying to enable TRIM on it. If I try formatting it on my Mac with Disk Utility I get Unable to write to the last block of the device. : (-69760). It doesn't boot anymore on the Pi. It won't mount on my Dell laptop, and if I try writing to it with dd, it now writes at like 200 KiB/s (it used to write at 50+ MiB/s). I'll check on dd later to see if it finishes writing a new image after 6 hours :P

Excellent and detailed technical writing. Applied TRIM successfully per your steps for my Kingston SA400 SATAIII SSD 2.5 Inch 120 GB (SA400S37) --> hooked via usb3 to rpi4 running Raspbian.

If it is useful to anyone, I have a Sabrent USB3.0 drive enclosure (EC-UASP) which has the chipset JMS561U (I believe this drive can have different chipsets). Looking up this chipset it states that TRIM is not supported, however Sabrent had an update on their website for the firmware. Thought I would run the risk and try this. Well it worked! Now I have TRIM running successfully! Great article!

Thank you for writing this article! Going to script this out tonight.

Ooh, I thought about doing that but just haven't had the time. If you can throw something up on gist.github.com I'll add a link to it in the post!

And what about rpi3 B/B+ ?

Can I use that procedure to enable trim on my internal SSD connected on USB with a sata to usb adapter ? (using some ASMedia chipset ASM1051E/ASM1053E/ASM1153E)

Just a small addition to this fantastic article.

I assume it does always trim all the free space when fstrim is called manually. At least for me the output corresponds to the free space according to df.

I recommend enabling online-discard. This enabled the filesystem to discard blocks on the go instead of once a week. For ext4 just add the option "discard" to /etc/fstab.

Couldn't find the details for the msata drive attached using a usb hat thing with lsusb. This worked:

 hwinfo --disk

Thanks for all your hard work.

Hi Jeff thanks for sharing! I'm stuck on this step, trying to update the discard_max_bytes value for my drive:

root@ubuntu:/home/ubuntu# echo 10737418240 > /sys/block/sda/queue/discard_max_bytes
bash: echo: error de escritura: Argumento inválido

I give a write error: not valid argument... any idea what I'm doing wrong?

Same error: Invalid argument with my 1TB Samsung USB drive. echo 68719476736 > /sys/block/sda/queue/discard_max_bytes (block size is 512 and the Maximum unmap LBA count is 134217728

The reason you get 111.4GB after a reboot is because that's the amount of free space and the kernel doesn't know if those sectors are dirty or have ever been trimmed - and there's no way to keep that on disk or in the filesystem. It'll just trim everything to start. Once that's done, the kernel can start to keep track of which sectors have been written and freed, and are therefore eligible for trimming... at least as long as the device remains connected.

Great blog. Even as a relative newbie i was able to get trim enabled on my Crucial MX500 wtith Eluteng adapter.
Thanks for sharing your knowledge.

From what I can see, nothing in the instructions here is preserving the value for discard_max_bytes across a reboot.