NanoPi NEO / NanoPi NEO2 NASキットでSSD TRIMを有効にする

:::note alert 
一見TRIMの設定ができたように見えていたが後日問題（後述）が発覚した。
原因等が分かるまでは重要な環境では実行しないでほしい。（2023/1/10 追加）
:::

# はじめに

NanoPi NEO / NanoPi NEO2 NASキットに搭載したSATA SSDが`fstrim`コマンドを使用してTRIM操作できないので対処した。

# 環境

+ NanoPi NEO / NanoPi NEO2
+ NanoPi NEO / NanoPi NEO2 NASキット（[1-bay NAS Kit v1.2 for NanoPi NEO&NEO2](https://www.friendlyelec.com/index.php?route=product/product&product_id=222)）
+ Armbian 22.11.1 Bullseye (Kernel 5.15.80)


# 状況

SATA SSDに移動してある`/`パーティションに対して`fstrim`コマンドを実行するとエラーメッセージが出る。
```
# fstrim -v /
fstrim: /: the discard operation is not supported
```

NanoPi NEO / NanoPi NEO2 NASキットでは、SATA SSDは内部的にUSB接続されている。USB接続されたSSDは環境や設定によってTRIMできないらしい。

# 対処手順

１． `root`で`/etc/udev/rules.d/`にルールファイルを作成する。ファイル名は例えば`99-usap-trim.rules` にする（ファイル名の拡張子が`rules`ならよい）

```
ACTION=="add|change", ATTRS{idVendor}=="152d", ATTRS{idProduct}=="0578", SUBSYSTEM=="scsi_disk", ATTR{provisioning_mode}="unmap"
```

`152d`、`0578`は、lsusbコマンドの出力結果を元にしている。

２． システムを再起動

３．trim出来るようになった。
```
# fstrim -v /
/: 461.5 MiB (483868672 bytes) trimmed
```

<!-- 
４．一応確認 

実際にTRIMされたかを確認する方法はないようなので、それなりの容量のファイルを削除する前後で書き込み性能がどれくらい変わるかを確認してみる。
今回は、`dd`コマンドを使用して簡易的に書込み性能を測定してみた（1GBの書込みを3回、キャッシュ使用なし）

```
# dd if=/dev/zero of=/export/backup/test.img bs=1M count=1024 oflag=direct
1024+0 レコード入力
1024+0 レコード出力
1073741824 bytes (1.1 GB, 1.0 GiB) copied, 33.641 s, 31.9 MB/s
#
# dd if=/dev/zero of=/export/backup/test.img bs=1M count=1024 oflag=direct
1024+0 レコード入力
1024+0 レコード出力
1073741824 bytes (1.1 GB, 1.0 GiB) copied, 33.4901 s, 32.1 MB/s
#
# dd if=/dev/zero of=/export/backup/test.img bs=1M count=1024 oflag=direct
1024+0 レコード入力
1024+0 レコード出力
1073741824 bytes (1.1 GB, 1.0 GiB) copied, 33.403 s, 32.1 MB/s
#
# df -BG /export/backup/
ファイルシス   1G-ブロック  使用 使用可 使用% マウント位置
/dev/sda2             461G  105G   333G   25% /export/backup
:
＜/dev/sda2（容量461GB）のSSDに対して約100GBのファイルを削除＞
:
# df -BG /export/backup/
ファイルシス   1G-ブロック  使用 使用可 使用% マウント位置
/dev/sda2             461G   16G   422G    4% /export/backup
#
# fstrim -v /export/backup/
/export/backup/: 1.3 GiB (1409781760 bytes) trimmed
#
# dd if=/dev/zero of=/export/backup/test.img bs=1M count=1024 oflag=direct
1024+0 レコード入力
1024+0 レコード出力
1073741824 bytes (1.1 GB, 1.0 GiB) copied, 33.8339 s, 31.7 MB/s
#
# dd if=/dev/zero of=/export/backup/test.img bs=1M count=1024 oflag=direct
1024+0 レコード入力
1024+0 レコード出力
1073741824 bytes (1.1 GB, 1.0 GiB) copied, 33.2792 s, 32.3 MB/s
#
# dd if=/dev/zero of=/export/backup/test.img bs=1M count=1024 oflag=direct
1024+0 レコード入力
1024+0 レコード出力
1073741824 bytes (1.1 GB, 1.0 GiB) copied, 33.6139 s, 31.9 MB/s
#
```
TRIM前後で約33MB/sぐらいで変わらない。TRIM操作は実行されているのだろうか？

【疑問1】
90GB削除したのに1.3GiBしかTRIMされていない。（表示される数字は当てにならないらしいが・・・）

【疑問2】
33GB/sしか出ないのは低すぎないか？USB2.0接続だと約60MB/s出るはず。サーマルリミットに当たっているのであれば強制冷却すればいいはず。

【疑問3】
/dev/zeroを入力にすると、SSD側で圧縮されて性能がうまく測れない？
trim後に/dev/urandomを入力にしたら約20MB/sまで性能が落ちた。（trim後）

【疑問4】
削除したデータ量（約20%）が少なすぎるのか？
SSDの使用率をいったんほぼ100%にする必要がある？
-->

# 発覚した問題

`dmesg`コマンドで確認すると以前は出ていなかったセクターエラーがたくさん出ていることに気が付いた。（sdaはTRIM対象のSSD）なお、この時点で破損したファイル等は確認できていない。

```
[294720.241259] sd 0:0:0:0: [sda] tag#18 UNKNOWN(0x2003) Result: hostbyte=0x00 driverbyte=DRIVER_OK cmd_age=0s
[294720.241332] sd 0:0:0:0: [sda] tag#18 Sense Key : 0x5 [current]
[294720.241347] sd 0:0:0:0: [sda] tag#18 ASC=0x26 ASCQ=0x0
[294720.241369] sd 0:0:0:0: [sda] tag#18 CDB: opcode=0x42 42 00 00 00 00 00 00 00 18 00
[294720.241384] blk_update_request: critical target error, dev sda, sector 16853776 op 0x3:(DISCARD) flags 0x800 phys_seg 1 prio class 0
```

操作するにしたがってセクターエラーのメッセージが増えているようだったのでSDカードで一度リブートしてからSSDのファイルシステムを`fsck`コマンドで修復した。その後はセクターエラーがいったん出なくなった。

当初`fstrim`コマンド発行後にセクターエラーが出ているかとも思ったが、しばらく起動しておくと`fstrim`コマンドを発行しなくてもセクターエラーが出ることがあった。

状況としては、
+ 対象のSSD（Intel 525s）はTRIM対応
+ OS（Armbian、Debian bullseyeベース ）、ファイルシステム（ext4）もTRIM対応
+ 対象のSSDは`discard`オプション**なし**でマウントされている（`discard`オプションで自動的にTIRMが実行される）

で、いろいろ調べてみるとOSインストール時点で`fstrim.timer`サービスが有効になっていて、週1回TRIMが自動実行される設定になっていた。そのためセクターエラーが出ている可能性が濃厚と判断。別のNanoPi NEO+NASキットでも`fstrim.timer`実行直後にセクターエラーが多発していることも確認。

残るポイントはUSB→eSATA変換の周り（JMicronチップ、ファームウェア、ドライバ）周りだが、そもそもTRIMできなくて問題が発生しているわけではないので、また時間がある時に追加調査しようと考えている。（NASキットに使われているJMicronチップはファームウェアの問題も報告されているので、JMicronチップのファームウェアを無理矢理バージョンアップすればいいかもしれない）

# 補足情報

:::note warn
当初JMicronチップはOS（armbian）が認識している`JMS578`と記載していた。しかし、チップのシルク印刷が`JMS567`であり、メーカーの仕様上も`JMS567`であることから`JMS567`に記事の記載を変更した。いろいろ調べてみると`JMS578`のIDを返すファームウェアが乗っている`JMS567`である可能性が高そう。
:::

NanoPi NEO / NanoPi NEO2とSATA SSDはNASキット基板上のUSB-SATA変換チップ（`JMicron JMS567`）を経由してUSB（USB2.0=480Mbps、遅い）接続されている。そして`JMicron JMS567`とSATA SSDの間はSATA3（6Gbps）で接続されている。

plantUMLで図にするとこんな感じ。
<!--
plantUMLについてはオフィシャル記事参照
https://qiita.com/Qiita/items/c686397e4a0f4f11683d#plantuml%E3%82%92%E4%BD%BF%E3%81%86
-->
```plantuml
@startuml
node NanoPi as "NanoPi NEO\rNanoPi NEO2"
node SSD as "SATA SSD"
node JMS567 as "JMicron JMS567"
NanoPi == JMS567 : USB 2.0 (480Mbps)
JMS567 == SSD : SATA3 (6Gbps)
@enduml
```

`lsusb`コマンドの出力結果で`Bus 004 Device 002`と認識されているのが`JMicron JMS567`。ベンダーIDが`152d`、プロダクトIDが`0578`になっている。（ネットを検索すると、`JMS567`や`JMS578`について複数のプロダクトIDが見つかる[^JMS567プロダクトID][^JMS578プロダクトID]。`lsusb`コマンドの結果は`JMS567`/`JMS578`に関してはあまりあてにできないのかもしれない）

[^JMS567プロダクトID]: https://devicehunt.com/view/type/usb/vendor/152D/device/0567
[^JMS578プロダクトID]: https://devicehunt.com/view/type/usb/vendor/152D/device/0578


```
# lsusb
Bus 008 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 005 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 007 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 004 Device 002: ID 152d:0578 JMicron Technology Corp. / JMicron USA Technology Corp. JMS578 SATA 6Gb/s
Bus 004 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 006 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 003 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 002 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 009 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
```
SATA SSDはUSB2.0（480Mbps）接続だが、ちゃんとUASP対応になっている（`Bus 04.Port 1`につながっている`Dev 2`が`Driver=uas`になっている）
```
# lsusb -t
/:  Bus 09.Port 1: Dev 1, Class=root_hub, Driver=musb-hdrc/1p, 480M
/:  Bus 08.Port 1: Dev 1, Class=root_hub, Driver=ohci-platform/1p, 12M
/:  Bus 07.Port 1: Dev 1, Class=root_hub, Driver=ohci-platform/1p, 12M
/:  Bus 06.Port 1: Dev 1, Class=root_hub, Driver=ohci-platform/1p, 12M
/:  Bus 05.Port 1: Dev 1, Class=root_hub, Driver=ehci-platform/1p, 480M
/:  Bus 04.Port 1: Dev 1, Class=root_hub, Driver=ehci-platform/1p, 480M
    |__ Port 1: Dev 2, If 0, Class=Mass Storage, Driver=uas, 480M
/:  Bus 03.Port 1: Dev 1, Class=root_hub, Driver=ehci-platform/1p, 480M
/:  Bus 02.Port 1: Dev 1, Class=root_hub, Driver=ohci-platform/1p, 12M
/:  Bus 01.Port 1: Dev 1, Class=root_hub, Driver=ehci-platform/1p, 480M
```


# 参考情報

+ [「USB Attached SCSI」Wikipedia](https://ja.wikipedia.org/wiki/USB_Attached_SCSI)
+ [純規の暇人趣味ブログ「【Linux】USB接続の外付けSSDでtrimできるかもしれない方法 」](https://jyn.jp/linux-uasp-ssd-trim/)

<!-- 
+ [web-development-kb-ja.site「LinuxでUSB3.0 UASP（USB接続SCSIプロトコル）モードが有効になっているかどうかを確認するにはどうすればよいですか？」](https://www.web-development-kb-ja.site/ja/linux/linux%E3%81%A7usb30-uasp%EF%BC%88usb%E6%8E%A5%E7%B6%9Ascsi%E3%83%97%E3%83%AD%E3%83%88%E3%82%B3%E3%83%AB%EF%BC%89%E3%83%A2%E3%83%BC%E3%83%89%E3%81%8C%E6%9C%89%E5%8A%B9%E3%81%AB%E3%81%AA%E3%81%A3%E3%81%A6%E3%81%84%E3%82%8B%E3%81%8B%E3%81%A9%E3%81%86%E3%81%8B%E3%82%92%E7%A2%BA%E8%AA%8D%E3%81%99%E3%82%8B%E3%81%AB%E3%81%AF%E3%81%A9%E3%81%86%E3%81%99%E3%82%8C%E3%81%B0%E3%82%88%E3%81%84%E3%81%A7%E3%81%99%E3%81%8B%EF%BC%9F/958567305/)
-->