1. Tutorials

My Linux Kernel Didn’t Support My Bluetooth Adapter, So I Patched It

.

I bought a cheap Bluetooth 5.0 adapter from a local market (What a mistake!), and it was too cheap to the level that it didn’t have a vendor name on it. Probably one of these poor-quality adapters that are being sold everywhere.

Sadly, it didn’t work on my Linux distribution (Kernels 5.5, 5.6, 5.7..). Bluetooth was always turned off and I couldn’t turn it on, and the adapter was classified as unknown by the kernel. And this was an issue because I don’t want to buy another Bluetooth adapter and spend more time searching on this problem.

Luckily, Linux is quite helpful in this regard. What I did was simply that I searched for the problem online, found a patch, applied it on the latest kernel’s source code, built the new kernel and installed it. That’s it.

And then, the Bluetooth adapter worked like charm.

This is a very simple tutorial that will guide you on how to fix your hardware issues with Linux using a real life scenario, which is the unknown Bluetooth adapter in our case. This is to help you get a general idea on how the kernel patching process works, and how you can possibly do the same thing to fix your non-compatible hardware issues with Linux, shall you face any in the future.

Finding The Needed Patch/Information Online

First, listing my Bluetooth USB type gave me the following information:

mhsabbagh@ryzenpc:~$ lsusb
....
Bus 001 Device 006: ID 0a12:0001 Cambridge Silicon Radio, Ltd Bluetooth Dongle (HCI mode)

The 0a12:0001 part is the most important part, because it is the ID of the chipset running inside the Bluetooth adapter. So you can use this keyword on Google to find related information and users having similar issues with the Bluetooth adapter.

A search in the online forums of various Linux distributions and how their users are dealing with Bluetooth issues on Linux showed me that Bluetooth adapters using CSR (Cambridge Silicon Radio) chipset have better Linux compatibility than the ones using Broadcom chipset, as the latter is so buggy with Linux. But sometimes, even CSR chip may have issues in identifying itself for the Linux kernel for the first time.

This gave me a relief, a hope that my Bluetooth adapter can work well on Linux, and that I just need to find a way to make it identifiable by the system.

Searching in the Bluetooth-related kernel reports on Bugzilla led me to the following 2013 bug report where users are having the exact same issue as I do.

Luckily, a good user provided a patch to solve the problem and he was asking for feedback on how his patch worked. Many users replied that the patch solved their problem, so this motivated me to apply it on my kernel too.

Here’s the patch file (Retrieved from the link above):

diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c
index 5f022e9cf..89686379a 100644
--- a/drivers/bluetooth/btusb.c
+++ b/drivers/bluetooth/btusb.c
@@ -1720,6 +1720,7 @@  static int btusb_setup_csr(struct hci_dev *hdev)
 {
 	struct hci_rp_read_local_version *rp;
 	struct sk_buff *skb;
+	bool is_fake = false;
 
 	BT_DBG("%s", hdev->name);
 
@@ -1739,18 +1740,69 @@  static int btusb_setup_csr(struct hci_dev *hdev)
 
 	rp = (struct hci_rp_read_local_version *)skb->data;
 
-	/* Detect controllers which aren't real CSR ones. */
+	/* Detect a wide host of Chinese controllers that aren't CSR.
+	 *
+	 * Known fake bcdDevices: 0x0100, 0x0134, 0x1915, 0x2520, 0x7558, 0x8891
+	 *
+	 * The main thing they have in common is that these are really popular low-cost
+	 * options that support newer Bluetooth versions but rely on heavy VID/PID
+	 * squatting of this poor old Bluetooth 1.1 device. Even sold as such.
+	 *
+	 * We detect actual CSR devices by checking that the HCI manufacturer code
+	 * is Cambridge Silicon Radio (10) and ensuring that LMP sub-version and
+	 * HCI rev values always match. As they both store the firmware number.
+	 */
 	if (le16_to_cpu(rp->manufacturer) != 10 ||
-	    le16_to_cpu(rp->lmp_subver) == 0x0c5c) {
+	    le16_to_cpu(rp->hci_rev) != le16_to_cpu(rp->lmp_subver))
+		is_fake = true;
+
+	/* Known legit CSR firmware build numbers and their supported BT versions:
+	 * - 1.1 (0x1) -> 0x0073, 0x020d, 0x033c, 0x034e
+	 * - 1.2 (0x2) ->                 0x04d9, 0x0529
+	 * - 2.0 (0x3) ->         0x07a6, 0x07ad, 0x0c5c
+	 * - 2.1 (0x4) ->         0x149c, 0x1735, 0x1899 (0x1899 is a BlueCore4-External)
+	 * - 4.0 (0x6) ->         0x1d86, 0x2031, 0x22bb
+	 *
+	 * e.g. Real CSR dongles with LMP subversion 0x73 are old enough that
+	 *      support BT 1.1 only; so it's a dead giveaway when some
+	 *      third-party BT 4.0 dongle reuses it.
+	 */
+	if (le16_to_cpu(rp->lmp_subver) <= 0x034e &&
+	    le16_to_cpu(rp->hci_ver) > BLUETOOTH_VER_1_1)
+		is_fake = true;
+
+	if (le16_to_cpu(rp->lmp_subver) <= 0x0529 &&
+	    le16_to_cpu(rp->hci_ver) > BLUETOOTH_VER_1_2)
+		is_fake = true;
+
+	if (le16_to_cpu(rp->lmp_subver) <= 0x0c5c &&
+	    le16_to_cpu(rp->hci_ver) > BLUETOOTH_VER_2_0)
+		is_fake = true;
+
+	if (le16_to_cpu(rp->lmp_subver) <= 0x1899 &&
+	    le16_to_cpu(rp->hci_ver) > BLUETOOTH_VER_2_1)
+		is_fake = true;
+
+	if (le16_to_cpu(rp->lmp_subver) <= 0x22bb &&
+	    le16_to_cpu(rp->hci_ver) > BLUETOOTH_VER_4_0)
+		is_fake = true;
+
+	if (is_fake) {
+		bt_dev_warn(hdev, "CSR: Unbranded CSR clone detected; adding workarounds...");
+
+		/* Generally these clones have big discrepancies between
+		 * advertised features and what's actually supported.
+		 * Probably will need to be expanded in the future;
+		 * without these the controller will lock up.
+		 */
+		set_bit(HCI_QUIRK_BROKEN_STORED_LINK_KEY, &hdev->quirks);
+		set_bit(HCI_QUIRK_BROKEN_ERR_DATA_REPORTING, &hdev->quirks);
+
 		/* Clear the reset quirk since this is not an actual
 		 * early Bluetooth 1.1 device from CSR.
 		 */
 		clear_bit(HCI_QUIRK_RESET_ON_CLOSE, &hdev->quirks);
-
-		/* These fake CSR controllers have all a broken
-		 * stored link key handling and so just disable it.
-		 */
-		set_bit(HCI_QUIRK_BROKEN_STORED_LINK_KEY, &hdev->quirks);
+		clear_bit(HCI_QUIRK_SIMULTANEOUS_DISCOVERY, &hdev->quirks);
 	}
 
 	kfree_skb(skb);
@@ -4001,11 +4053,13 @@  static int btusb_probe(struct usb_interface *intf,
 		if (bcdDevice < 0x117)
 			set_bit(HCI_QUIRK_RESET_ON_CLOSE, &hdev->quirks);
 
+		/* This must be set first in case we disable it for fakes */
+		set_bit(HCI_QUIRK_SIMULTANEOUS_DISCOVERY, &hdev->quirks);
+
 		/* Fake CSR devices with broken commands */
-		if (bcdDevice <= 0x100 || bcdDevice == 0x134)
+		if (udev->descriptor.idVendor  == 0x0a12 &&
+		    udev->descriptor.idProduct == 0x0001)
 			hdev->setup = btusb_setup_csr;
-
-		set_bit(HCI_QUIRK_SIMULTANEOUS_DISCOVERY, &hdev->quirks);
 	}
 
 	if (id->driver_info & BTUSB_SNIFFER) {
diff --git a/include/net/bluetooth/bluetooth.h b/include/net/bluetooth/bluetooth.h
index 181900553..155019220 100644
--- a/include/net/bluetooth/bluetooth.h
+++ b/include/net/bluetooth/bluetooth.h
@@ -41,6 +41,8 @@ 
 #define BLUETOOTH_VER_1_1	1
 #define BLUETOOTH_VER_1_2	2
 #define BLUETOOTH_VER_2_0	3
+#define BLUETOOTH_VER_2_1	4
+#define BLUETOOTH_VER_4_0	6
 
 /* Reserv for core and drivers use */
 #define BT_SKB_RESERVE	8
diff --git a/include/net/bluetooth/hci.h b/include/net/bluetooth/hci.h
index 16ab6ce87..1c321b6d1 100644
--- a/include/net/bluetooth/hci.h
+++ b/include/net/bluetooth/hci.h
@@ -227,6 +227,17 @@  enum {
 	 * supported.
 	 */
 	HCI_QUIRK_VALID_LE_STATES,
+
+	/* When this quirk is set, then erroneous data reporting
+	 * is ignored. This is mainly due to the fact that the HCI
+	 * Read Default Erroneous Data Reporting command is advertised,
+	 * but not supported; these controllers often reply with unknown
+	 * command and tend to lock up randomly. Needing a hard reset.
+	 *
+	 * This quirk can be set before hci_register_dev is called or
+	 * during the hdev->setup vendor callback.
+	 */
+	HCI_QUIRK_BROKEN_ERR_DATA_REPORTING,
 };
 
 /* HCI device flags */
diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c
index dbe2d79f2..48d7c7e23 100644
--- a/net/bluetooth/hci_core.c
+++ b/net/bluetooth/hci_core.c
@@ -606,7 +606,8 @@  static int hci_init3_req(struct hci_request *req, unsigned long opt)
 	if (hdev->commands[8] & 0x01)
 		hci_req_add(req, HCI_OP_READ_PAGE_SCAN_ACTIVITY, 0, NULL);
 
-	if (hdev->commands[18] & 0x04)
+	if (hdev->commands[18] & 0x04 &&
+	    !test_bit(HCI_QUIRK_BROKEN_ERR_DATA_REPORTING, &hdev->quirks))
 		hci_req_add(req, HCI_OP_READ_DEF_ERR_DATA_REPORTING, 0, NULL);
 
 	/* Some older Broadcom based Bluetooth 1.2 controllers do not
@@ -851,7 +852,8 @@  static int hci_init4_req(struct hci_request *req, unsigned long opt)
 	/* Set erroneous data reporting if supported to the wideband speech
 	 * setting value
 	 */
-	if (hdev->commands[18] & 0x08) {
+	if (hdev->commands[18] & 0x08 &&
+	    !test_bit(HCI_QUIRK_BROKEN_ERR_DATA_REPORTING, &hdev->quirks)) {
 		bool enabled = hci_dev_test_flag(hdev,
 						 HCI_WIDEBAND_SPEECH_ENABLED);
 

Retrieving the Latest Kernel Source Code

You can easily grab the latest kernel source code from the Kernel.org website. You can select the latest Git version, or grab the stable versions only, if you like. It doesn’t matter for our use case in this tutorial.

Alternatively, you can do it in one easy Git clone command:

git clone https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git

Applying the Patch on the Kernel Source Code

Now that you have both the fresh kernel and also the needed patch to apply, you’ll simply have to apply the patch on the kernel source code.

A patch file is nothing more than a text file containing which lines to add, remove and edit. So you can simply take the patch you found, save it to patch.diff file and then apply it using the following command:

patch -p1 < patch.diff

Make sure both your current Shell and patch files are located in the same kernel folder when you apply the patch. (Duh!).

The process should be done immediately, and now, your kernel source code is patched to fix the Bluetooth issue!

One additional thing that you might want to do is to modify is the drivers/bluetooth/btusb.c file manually. As mentioned by another comment in the Bugzilla bug report linked earlier, you’ll need to add the enable_autosuspend=0 parameter to that file too for the patch fo fully work (According to their experience). So let’s not waste time experimenting and do that right now, too.

Just replace the following line in that file:

static bool enable_autosuspend = IS_ENABLED(CONFIG_BT_HCIBTUSB_AUTOSUSPEND);

With this line:

static bool enable_autosuspend = 0;

And that’s it.

There’s another, better way of doing this during the configuration phase of the kernel, but let’s keep things quick for now.

Building the Linux Kernel From Source

Building the Linux kernel from source code might seem like a hard task for the first-timers, but it’s nothing more than few commands in the Shell. It is something that shouldn’t scare you at all, even if you didn’t do it before.

1 linux
Kernel’s nconfig tool.

Before running build process, we’ll need to configure our kernel to generate a .config file in the kernel source tree that contains the options to want to enable/disable in our kernel. For example, we might not want to load some modules or drivers in our build process, as we may not need them on our PCs, so we can disable them instead of waiting time compiling them.

The full detailed configuration process of the kernel is explained well on the Kernel Newbies website. However, we won’t need anything from this right now, we can just compile our kernel as it is.

First, run the ncurses configuration wizard using the following command:

make nconfig

And notice the terminal interface that will appear. You can go ahead in exploring the available options and turning on/off the features you want with the Space key. Normally you don’t need to change anything for now, but once you are done, you can just save the file via F6.

Now, we are ready to build our kernel.

You can build the kernel using 1 thread with make:

make

Or with multiple threads you specify (For faster compile time, e.g 4):

make -j4

Once done, you can build the modules:

make modules

Installing the New Patched Linux Kernel

And now, to install everything to our system files directories:

sudo make modules_install
sudo make install

That’s it!

Now, reboot your system and enter to the new installed kernel version (Because you’ll have multiple versions listed in GRUB, so make sure you select the one you installed), and you should see your Bluetooth adapter working like charm:

3 linux
Bluetooth working as expected

Bottom Line

As you can see, the process didn’t take too much time and work, and the commands we applied were very simple and basic. Yet, with this work, we were able to turn our non-functional hardware we bought into a functional one instead of wasting time and money in buying new hardware.

Linux is such a great kernel in this regard to allow for this easy process to take place in no time. Big thanks for the developer who provided the patch.

If you have any similar experiences with the kernel, we would love to hear them in the comments below.

.
People reacted to this story.
Comments to: My Linux Kernel Didn’t Support My Bluetooth Adapter, So I Patched It
  • August 3, 2020

    Great article! Just what I needed for my new-but-not-yet-working-bluetooth-dongle. What happens when the kernel gets updated? Is the patch still in place after an upgrade?

    Reply
    • August 3, 2020

      The kernel will never get updated because it is compiled manually by you. It is not controllable by your package manager.

      Your distribution, may, however, ship a newer version over time. And when this happens, you have to select the older/patched kernel that you compiled from the GRUB2 boot screen instead of the new one.

      Reply
Write a response

Your email address will not be published. Required fields are marked *