1 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:
$ 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.
$ 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
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):
$ sudo su [sudo] password: [root] $ crontab -e
@reboot echo "0000:00:15.1" > /sys/bus/pci/devices/0000:00:15.1/driver/unbind
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.
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:
[root] $ edit /usr/lib/systemd/system/disable.device.target
Of course, by
edit I mean to use your favorite editor.
nano, or any other terminal or graphical editor you wish. Add the following into the file:
[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
#! /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] $ chmod 744 /etc/disable.device.sh
Finally, enable your service:
[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.