Linux terminal with mostly green text against a black background showing a directory structure and the results of a few other commands.

Deactivating Troublesome Devices in Linux

Sometimes you may run into a situation where a device may not act right. In most cases you can just disable a driver and that works. In other situations, it’s on a controller that handles multiple devices. Disabling a driver will disable multiple devices. So how do you only disabled one specific device without affecting others?

This happened to me on Linux. My touchpad works fine on Windows, but on Linux… not so much. It works, but I noticed some strange slowness in my system. Looking at top I noticed there were no individual processes using a lot of resources, but one of the CPU’s cores was pegged between 80-98% usage. Ouch.

I scoured the internet and just got a whole bunch of “find the driver that’s causing the problem and blacklist it” results. I tried that but it also disabled the touchscreen. The touchscreen was not causing a problem… but it was attached to the same serial controller hub.

Looking at the interrupts I could see that the touchpad, an ELAN (unknown more recent model) was spewing interrupts like a fountain and eating up an entire CPU core:

Bash
$ cat /proc/interrupts
CPU0       CPU1       CPU2       CPU3       CPU4       CPU5       CPU6       CPU7
1:          0          0          0          0          0          0          0      18400  IR-IO-APIC    1-edge      i8042
8:          0          0          0          0          0          0          0          0  IR-IO-APIC    8-edge      rtc0
9:          0         46          0          0          0          0          0          0  IR-IO-APIC    9-fasteoi   acpi
14:         0          0          0          0          0          0          0          0  IR-IO-APIC   14-fasteoi   INT34C5:00
16:         0          0          0          0          0       1964          0          0  IR-IO-APIC   16-fasteoi   intel_ish_ipc, i801_smbus
27:         0       1317          0          0          0          0          0          0  IR-IO-APIC   27-fasteoi   i2c_designware.0, idma64.0
28:         0         91     392167          0          0          0          0          0  IR-IO-APIC   28-fasteoi
29:         0          0          0          0          0          0          0          0  IR-IO-APIC   29-fasteoi   i2c_designware.2, idma64.2
30:         0          0          0          0          0          0          0          0  IR-IO-APIC   30-fasteoi   i2c_designware.3, idma64.3
43:         0          0          0          0       2212          0          0          0  IR-IO-APIC   43-fasteoi   ELAN9009:00
57:         0          0          0          1          0          0          0          0  IR-IO-APIC   57-fasteoi

You can see that IRQ 28 was spamming CPU3 with interrupts prior to it being disabled.

After a lot of digging I found that these ELAN touchpads are notorious for requiring “passcodes” to function properly. Apparently they have a specific “knock” that needs to occur so that they will stop spamming interrupts. The Windows drivers apparently have these knocks. Linux developers either have to guess or try to reverse engineer. Not a high priority for obvious reasons. Linux builders just avoid such devices in computers that will be running Linux.

How do you match up an interrupt to an address? Not figured that out yet so you might have to do some trial and error on a few devices (it’s pretty easy using the echo function later in this post). If anyone knows how to match an interrupt port to a device address, let me know so I can update this post.

Bash
$ lspci -mm
...
00:15.0 "Serial bus controller" "Intel Corporation" "Tiger Lake-LP Serial IO I2C Controller #0" -r20 -p00 "ASUSTeK Computer Inc." "Device 1402"
00:15.1 "Serial bus controller" "Intel Corporation" "Tiger Lake-LP Serial IO I2C Controller #1" -r20 -p00 "ASUSTeK Computer Inc." "Device 1402"
00:15.2 "Serial bus controller" "Intel Corporation" "Tiger Lake-LP Serial IO I2C Controller #2" -r20 -p00 "ASUSTeK Computer Inc." "Device 1402"
00:15.3 "Serial bus controller" "Intel Corporation" "Tiger Lake-LP Serial IO I2C Controller #3" -r20 -p00 "ASUSTeK Computer Inc." "Device 1402"
...

Afer some trial-and-error I determined that IRQ 28 was associated with device 00:15.1.

There had to be a way to disable it while keeping the touchscreen working. After a lot of digging I finally found a 1-line command to disable a specific device by it’s address. But how would I get it to disable every boot? Turns out you can do that via the crontab (you need to be the root user):

Bash
$ sudo su
[sudo] password:
[root] $ crontab -e
Bash
@reboot echo "0000:00:15.1" > /sys/bus/pci/devices/0000:00:15.1/driver/unbind

However…

This is not the best solution. It does run every time the computer is started/restarted, but it happens very late in the boot process. That allows the boot to still get stuck while the part of the CPU is busy trying to handle a flood of interrupts. I needed a way to have it run earlier in the boot process. I discovered an easy way to do just that using a target unit. 💡 The following process is for systemd, if you are using another service manager the process will be different, but should be a similar way to hook into the boot process.

Target units are scripts that run at a specific point in the boot process. In this case, I run it after sysinit.target, which is the earliest you can run within the boot process. How you do this is by creating a .target file and enable it:

Bash
[root] $ edit /usr/lib/systemd/system/disable.device.target

Of course, by edit I mean to use your favorite editor. vim, nano, or any other terminal or graphical editor you wish. Add the following into the file:

INI
[Unit]
Description=Disable Device
Before=basic.target
After=local-fs.target sysinit.target
DefaultDependencies=no

[Service]
Type=oneshot
ExecStart=/etc/disable.device.sh

[Install]
WantedBy=basic.target

You can change where the actual script file is located, I simply picked /etc but you can put it anywhere you wish. Next you need to add the contents of the script. Again, use your favorite editor on /etc/disable.device.sh:

Bash
#! /bin/sh
echo "0000:00:15.1" > /sys/bus/pci/devices/0000:00:15.1/driver/unbind

Then, make sure the script can be executed by the root user:

Bash
[root] $ chmod 744 /etc/disable.device.sh

Finally, enable your service:

Bash
[root] $ systemctl enable disable.device

That’s it. On the next boot (and every boot after), the device will automatically be disabled early in the boot process.


Comments

Leave a Reply

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