Realtek RTL8761B の Bluetooth 5 USB ドングルを Linux で動かす

最近安価で手に入るようになった Bluetooth 5.x の USB ドングルを Linux で使う手順です。
これらのドングルには大抵 Realtek の RTL8761B (フルネームはRTL8761BUV) というチップが使われており、Linux 2.6.32 以降に対応しています。

**更新履歴**

- 2023/11/23 Bluetooth 5.3 のファームウェアが非常に不安定なため削除
- 2023/10/24 Bluetooth 5.3 対応のファームウェアにアップグレードする手順を加筆
- 2021/02/20 Realtek 製ドライバを使う手順に書き直し
- 2020/10/27 Google Home、AirPods とペアリングできたので記事を修正

## 準備
カーネルに最初から入っている `btusb` には、デバイスをスキャンすると音がブツ切りになるという問題があったので、問題のなかった Realtek 謹製のドライバをビルドします。
ドライバをビルドする際にカーネルのソースコードとビルドツールが必要になるので事前に入れておきます。

```shell-session
$ sudo apt install linux-headers build-essential
```

### カーネルの再構築 （※読み飛ばしてもOK）
>**カーネルを自分でビルドしている人向けです。**

Realtek 製ドライバが `btusb` を置き換える形になるため、**カーネルが `btusb` を組み込まずにビルドされているか、モジュールとしてビルドされている必要があります。**

`modinfo btusb` の出力に `(builtin)` が含まれていなければ、この手順は必要ありません。

`btusb` をモジュールとしてビルドするには

```shell-session
# scripts/config -m BT_HCIBTUSB
```

を実行してからいつも通り `make` すれば OK です。

## ドライバのインストール
ASUS が販売しているドングル USB-BT500 の Linux 版ドライバとして、 Realtek 製ドライバのソースコード一式が配布されているのでこれを利用します。
[ASUS の公式サイト](https://www.asus.com/us/networking-iot-servers/adapters/all-series/usb-bt500/HelpDesk_Download/)からダウンロードできます。現時点での最新版は **v0202 (2020/12/08)** でした。

以下のコマンドを実行するとドライバとファームウェアがインストールされます。

```shell
wget https://dlcdnets.asus.com/pub/ASUS/wireless/USB-BT500/20200909_LINUX_BT_DRIVER_KERNEL_5.7_COEX_v0202.zip
unzip 20200909_LINUX_BT_DRIVER_KERNEL_5.7_COEX_v0202.zip
cd 20200806_LINUX_BT_DRIVER_RTL8761B_COEX_v0202/20200806_LINUX_BT_DRIVER_RTL8761B_COEX_v0202
# デバッグ出力がうるさいので消す
sed -i '/define RTKBT_DBG/c\#define RTKBT_DBG(fmt, arg...)' usb/bluetooth_usb_driver/rtk_coex.c
sudo make install INTERFACE=usb
```

>このコマンドは実行中のカーネルに対して行われるため、作業前にカーネルをアップデートした際は再起動してから行ってください。

## ドングルの認識
ドライバをロードします。

```shell-session
$ sudo modprobe rtk_btusb
```

```shell-session
$ hciconfig -a
hci0:	Type: Primary  Bus: USB
	BD Address: XX:XX:XX:XX:XX:XX  ACL MTU: 1021:6  SCO MTU: 255:12
	UP RUNNING PSCAN 
	RX bytes:854270 acl:21692 sco:0 events:66232 errors:0
	TX bytes:44662651 acl:65989 sco:0 commands:226 errors:0
	Features: 0xff 0xff 0xff 0xfe 0xdb 0xfd 0x7b 0x87
	Packet type: DM1 DM3 DM5 DH1 DH3 DH5 HV1 HV2 HV3 
	Link policy: RSWITCH HOLD SNIFF PARK 
	Link mode: PERIPHERAL ACCEPT 
	Name: 'XXXX'
	Class: 0x7c0104
	Service Classes: Rendering, Capturing, Object Transfer, Audio, Telephony
	Device Class: Computer, Desktop workstation
	HCI Version: 5.1 (0xa)  Revision: 0xdfc6
	LMP Version: 5.1 (0xa)  Subversion: 0xd922
	Manufacturer: Realtek Semiconductor Corporation (93)

```

`HCI Version: 5.1` (Bluetooth 5.1) のコントローラーとして認識されました。

## トラブルシューティング
### 頻繁に切断される、音が切れる
2.4 GHz 帯との干渉が原因の可能性が高いです。
USB 2.0 ポートに差すか、USB 2.0 の延長ケーブルを使って PC 本体から少し離れたところにドングルを設置することをおすすめします。
私の場合は延長ケーブルで接続性が劇的に改善されました。

### Linux 5.9 でドングルが認識されない
>**この問題は Linux 5.10.6 で修正されました。**
>[Bluetooth: Fix attempting to set RPA timeout when unsupported · torvalds/linux@a31489d](https://github.com/torvalds/linux/commit/a31489d2a368d2f9225ed6a6f595c63bc7d10de8)

私の環境 (Linux 5.9.1) ではカーネルはドングルを認識するのですが、bluetoothctl がドングルを全く認識しませんでした。GNOME の設定画面でも Bluetooth ドングルが繋がっていないと表示されます。
btmon で通信内容を見てみると、通信自体は問題なくできているようですがこのようなエラーが出ています。

```
< HCI Command: LE Set Resolvable P.. (0x08|0x002e) plen 2  #235 [hci0] 8.037958
        Timeout: 900 seconds
> HCI Event: Command Complete (0x0e) plen 4                #236 [hci0] 8.038849
      LE Set Resolvable Private Address Timeout (0x08|0x002e) ncmd 2
        Status: Unsupported Remote Feature / Unsupported LMP Feature (0x1a)
= Close Index: XX:XX:XX:XX:XX:XX                                [hci0] 8.038893
```

LE Set Resolvable Private Address Timeout という対応していないコマンドを発行して怒られているようです。
カーネルのコミットログを調べたところ、このコミットが怪しい感じがします。

https://github.com/torvalds/linux/commit/b2cc23398e8166b38f8715026273503b081c2a7a

Linux 5.9 で追加されたコードなので、それ以前のカーネルではこのドングルも正常に動作していたのかもしれません。
あまり他に影響しなさそうなコードなので丸ごと削除してみます。

```diff_c:rtl8761b.patch
diff --git a/include/net/bluetooth/hci.h b/include/net/bluetooth/hci.h
index c8e67042a3b1..74740a103c6f 100644
--- a/include/net/bluetooth/hci.h
+++ b/include/net/bluetooth/hci.h
@@ -1649,8 +1649,6 @@ struct hci_rp_le_read_resolv_list_size {
 
 #define HCI_OP_LE_SET_ADDR_RESOLV_ENABLE 0x202d
 
-#define HCI_OP_LE_SET_RPA_TIMEOUT	0x202e
-
 #define HCI_OP_LE_READ_MAX_DATA_LEN	0x202f
 struct hci_rp_le_read_max_data_len {
 	__u8	status;
diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c
index 502552d6e9af..e0872ba36aa1 100644
--- a/net/bluetooth/hci_core.c
+++ b/net/bluetooth/hci_core.c
@@ -763,14 +763,6 @@ static int hci_init3_req(struct hci_request *req, unsigned long opt)
 			hci_req_add(req, HCI_OP_LE_CLEAR_RESOLV_LIST, 0, NULL);
 		}
 
-		if (hdev->commands[35] & 0x40) {
-			__le16 rpa_timeout = cpu_to_le16(hdev->rpa_timeout);
-
-			/* Set RPA timeout */
-			hci_req_add(req, HCI_OP_LE_SET_RPA_TIMEOUT, 2,
-				    &rpa_timeout);
-		}
-
 		if (hdev->le_features[0] & HCI_LE_DATA_LEN_EXT) {
 			/* Read LE Maximum Data Length */
 			hci_req_add(req, HCI_OP_LE_READ_MAX_DATA_LEN, 0, NULL);
```

カーネルを再構築して再起動すると、それまで全く光らなかったドングルが青く発光し、 bluetoothctl からもコントローラーとして正しく認識されました。