How to test shpc on qemu

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

Answers (0)

Related Questions