Reputation: 97
Contextualizing the Problem
I am a beginner contributor to the Linux kernel.
Recently, I made a change to the SHPC (Standard Hot-Plug Controller) drivers, where I replaced the request_irq()
function with the request_threaded_irq()
function. This was a suggestion by Lukas Wunner in commit a0d58937404f5.
This way, when an interrupt with the IRQ number provided to request_threaded_irq()
is received, it executes a small part in the interrupt context, and the bulk of the hot-plug processing is executed in a separate thread. Therefore, I needed to create two functions: one function with some basic rules for handling the interrupt in the interrupt context and another function with the "remainder" of the hot-plug code to be executed outside the interrupt context, in a thread.
Here is the full diff of my change:
diff --git a/drivers/pci/hotplug/shpchp_hpc.c b/drivers/pci/hotplug/shpchp_hpc.c
index 012b9e3fe5b0..b82d2bc4b777 100644
--- a/drivers/pci/hotplug/shpchp_hpc.c
+++ b/drivers/pci/hotplug/shpchp_hpc.c
@@ -166,6 +166,8 @@
#define SLOT_SERR_INT_MASK 0x3
static irqreturn_t shpc_isr(int irq, void *dev_id);
+static irqreturn_t shpc_ist(int irq, void *dev_id);
+
static void start_int_poll_timer(struct controller *ctrl, int sec);
static inline u8 shpc_readb(struct controller *ctrl, int reg)
@@ -746,7 +748,7 @@ int shpchp_set_bus_speed_mode(struct slot *slot, enum pci_bus_speed value)
return retval;
}
-static irqreturn_t shpc_isr(int irq, void *dev_id)
+static irqreturn_t shpc_ist(int irq, void *dev_id)
{
struct controller *ctrl = (struct controller *)dev_id;
u32 serr_int, slot_reg, intr_loc, intr_loc2;
@@ -825,6 +827,21 @@ static irqreturn_t shpc_isr(int irq, void *dev_id)
return IRQ_HANDLED;
}
+static irqreturn_t shpc_isr(int irq, void *dev_id)
+{
+ pr_info("%s: isr for interrupt handler\n", __func__);
+
+ struct controller *ctrl = dev_id;
+ struct pci_dev *pdev = ctrl->pci_dev;
+
+ if (pdev->ignore_hotplug) {
+ pr_info("ignoring hotplug");
+ return IRQ_HANDLED;
+ }
+
+ return IRQ_WAKE_THREAD;
+}
+
static int shpc_get_max_bus_speed(struct controller *ctrl)
{
int retval = 0;
@@ -1001,8 +1018,9 @@ int shpc_init(struct controller *ctrl, struct pci_dev *pdev)
pci_set_master(pdev);
}
- rc = request_irq(ctrl->pci_dev->irq, shpc_isr, IRQF_SHARED,
- MY_NAME, (void *)ctrl);
+ rc = request_threaded_irq(ctrl->pci_dev->irq, shpc_isr, shpc_ist,
+ IRQF_SHARED, MY_NAME, (void *)ctrl);
+
ctrl_dbg(ctrl, "request_irq %d (returns %d)\n",
ctrl->pci_dev->irq, rc);
if (rc) {
To test it yourself, you can use:
git apply <path-to-diff-file>
The Challenge
The main issue is finding a way to test this change. I cannot find a method to simulate a device compatible with SHPC that can assume PCI hot-plug control, allowing the functions passed to request_threaded_irq()
to be executed.
Attempts So Far
QEMU
I am accustomed to testing in QEMU and on my own machine. After extensive research, I found that the closest I can get to testing with SHPC-compatible devices in QEMU is with the pci-bridge
option, passing the parameter shpc=on
.
Here is the command I used to start the newly compiled kernel in QEMU with an SHPC bridge:
qemu-system-x86_64 -kernel arch/x86_64/boot/bzImage -append "root=/dev/sda1 console=ttyS0 earlyprintk=serial" -drive file=ubuntu.qcow2,format=qcow2 -m 1024 -enable-kvm -net nic -net user,hostfwd=tcp::2222-:22 -nographic -d int,cpu_reset -device pci-bridge,id=pci.1,chassis_nr=1,shpc=on
The kernel starts normally, and the SHPC driver is initialized. However, it does not "take" PCI hot-plug control because it is a root bridge. Being a bridge, it is only a "link" for other PCI connections and should not handle hot-plug for devices connected to it. Therefore, my change regarding request_threaded_irq()
does not get executed because the bridge does not assume PCI hot-plug control.
After much searching, I couldn’t find another device that I could connect to trigger the SHPC driver.
QEMU + Telnet
I also tried starting QEMU with a monitor as a parameter:
qemu-system-x86_64 -kernel arch/x86_64/boot/bzImage -append "root=/dev/sda1 console=ttyS0 earlyprintk=serial" -drive file=ubuntu.qcow2,format=qcow2 -m 1024 -enable-kvm -monitor telnet:127.0.0.1:5555,server,nowait -nographic
Connected to Telnet:
telnet localhost
Then attempted to add a bridge:
device_add pci-bridge,id=pci.1,chassis_nr=1,shpc=on
No success, as it remains a root bridge, as I mentioned earlier.
QEMU + Telnet + e1000 (and Other Devices)
I also tried adding an e1000
device via telnet:
qemu-system-x86_64 -kernel arch/x86_64/boot/bzImage -append "root=/dev/sda1 console=ttyS0 earlyprintk=serial" -drive file=ubuntu.qcow2,format=qcow2 -m 1024 -enable-kvm -monitor telnet:127.0.0.1:5555,server,nowait -nographic -device pci-bridge,id=pci.1,chassis_nr=1,shpc=on
device_add e1000 id=net1,bus=pci.1
This did not work either, as the e1000 is not SHPC compatible. I also tried other devices, but none are compatible with SHPC except for bridges.
Connecting a Bridge to a Bridge
qemu-system-x86_64 -kernel arch/x86_64/boot/bzImage -append "root=/dev/sda1 console=ttyS0 earlyprintk=serial" -drive file=ubuntu.qcow2,format=qcow2 -m 1024 -enable-kvm -monitor telnet:127.0.0.1:5555,server,nowait -nographic -device pci-bridge,id=pci.1,chassis_nr=1,shpc=on
device_add pci-bridge,id=pci.2,bus=pci.1,shpc=on
This also didn’t work, as the second bridge is also a root bridge and does not handle IRQs.
Other Virtual Machines
On My Own Machine (Dell Latitude E7450)
No PCIe devices were SHPC compatible.
My Kernel .config
CONFIG_PCI=y
CONFIG_HOTPLUG_PCI=y
CONFIG_HOTPLUG_PCI_SHPC=y
# CONFIG_HOTPLUG_PCI_ACPI is not set
# CONFIG_HOTPLUG_PCI_CPCI is not set
Upvotes: 1
Views: 48