Re: [PATCH v11] PCI: Add device-specific reset for Qualcomm devices
Manivannan Sadhasivam <[email protected]> Mon, 29 Jun 2026 18:01:37 +0200
| Newsgroups | dev.linux.lists.mhi,org.infradead.lists.ath11k,org.infradead.lists.ath12k,org.kernel.vger.linux-kernel,org.kernel.vger.linux-pci,org.kernel.vger.linux-wireless |
|---|---|
| Message-ID | <5mrwcurlf6xnqla5x5idpt7ug34nv2eqpijnjmccgdwazrmwyv@64tixxe4aa57> |
On Fri, Jun 26, 2026 at 07:50:23AM +0200, Jose Ignacio Tornos Martinez wrote: > Some Qualcomm PCIe devices (WCN6855/WCN7850 WiFi cards, SDX62/SDX65 modems) > lack working reset methods for VFIO passthrough scenarios. These devices > have no FLR capability, advertise NoSoftRst+ (blocking PM reset), and have > broken bus reset. > > The problem manifests in VFIO passthrough scenarios: > > - WCN6855 (17cb:1103) and WCN7850 (17cb:1107) WiFi devices: > Normal VM operation works fine, including clean shutdown/reboot. > However, when the VM terminates uncleanly (crash, force-off), VFIO > attempts to reset the device before it can be assigned to another VM. > Without a working reset method, the device remains in an undefined state, > preventing reuse. > > - SDX62/SDX65 (17cb:0308) 5G modems: Never successfully initialize even > on first VM assignment without proper reset capability. > > Add device-specific reset methods using BAR-space hardware reset registers > that exist in these devices: > > - WCN6855/WCN7850 WiFi devices use SoC global reset via BAR0 (sequence from > ath11k/ath12k driver: ath11k_pci_soc_global_reset(), ath11k_pci_sw_reset(), > ath11k_mhi_set_mhictrl_reset()): > - Write/clear reset bit at offset 0x3008 > - Wait for PCIe link recovery (up to 5 seconds) > - Clear MHI controller SYSERR status at offset 0x38 > > - SDX62/SDX65 modem devices use MHI SoC reset via BAR0 (sequence from MHI > driver: mhi_soc_reset(), mhi_pci_reset_prepare()): > - Write reset request to offset 0xb0 > - Wait 2 seconds for reset completion > > These are true hardware reset mechanisms (not power management or firmware > error recovery), providing proper device reset for VFIO scenarios. > > Testing was performed on desktop platforms with M.2 WiFi and modem cards > using M.2-to-PCIe adapters, including extensive force-reset cycling to > verify stability. > > Signed-off-by: Jose Ignacio Tornos Martinez <[email protected]> Couple of comments below, including one bug due to my previous suggestion. Once those are addressed, feel free to add: Reviewed-by: Manivannan Sadhasivam <[email protected]> > --- > v11: Address Manivannan Sadhasivam feedback: > - Remove unused QUALCOMM_WIFI_MHISTATUS define > - Use PCI_ERROR_RESPONSE instead of 0xffffffff > - Sort device IDs in ascending order (0x0308, 0x1103, 0x1107) > v10: https://lore.kernel.org/all/[email protected]/ > > drivers/pci/quirks.c | 117 +++++++++++++++++++++++++++++++++++++++++++ > 1 file changed, 117 insertions(+) > > diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c > index 431c021d7414..0de606366200 100644 > --- a/drivers/pci/quirks.c > +++ b/drivers/pci/quirks.c > @@ -4240,6 +4240,120 @@ static int reset_hinic_vf_dev(struct pci_dev *pdev, bool probe) > return 0; > } > > +#define QUALCOMM_WIFI_PCIE_SOC_GLOBAL_RESET 0x3008 > +#define QUALCOMM_WIFI_PCIE_SOC_GLOBAL_RESET_V BIT(0) > +#define QUALCOMM_WIFI_MHICTRL 0x38 > +#define QUALCOMM_WIFI_MHICTRL_RESET_MASK 0x2 Qcom calls these WiFi devices as WLAN devices. So I'd prefer to use this term all around even for function names. > + > +/* > + * Qualcomm WiFi device-specific reset using SoC global reset via BAR0 > + * registers. > + */ > +static int reset_qualcomm_wifi(struct pci_dev *pdev, bool probe) > +{ > + bool link_recovered = false; > + unsigned long timeout; > + void __iomem *bar; > + u32 val; > + u16 cmd; > + > + if (probe) > + return 0; > + > + if (pdev->current_state != PCI_D0) > + return -EINVAL; > + > + pci_read_config_word(pdev, PCI_COMMAND, &cmd); > + pci_write_config_word(pdev, PCI_COMMAND, cmd | PCI_COMMAND_MEMORY); > + > + bar = pci_iomap(pdev, 0, 0); > + if (!bar) { > + pci_write_config_word(pdev, PCI_COMMAND, cmd); > + return -ENODEV; > + } > + > + val = ioread32(bar + QUALCOMM_WIFI_PCIE_SOC_GLOBAL_RESET); > + val |= QUALCOMM_WIFI_PCIE_SOC_GLOBAL_RESET_V; > + iowrite32(val, bar + QUALCOMM_WIFI_PCIE_SOC_GLOBAL_RESET); > + ioread32(bar + QUALCOMM_WIFI_PCIE_SOC_GLOBAL_RESET); > + > + msleep(10); > + > + val &= ~QUALCOMM_WIFI_PCIE_SOC_GLOBAL_RESET_V; > + iowrite32(val, bar + QUALCOMM_WIFI_PCIE_SOC_GLOBAL_RESET); > + ioread32(bar + QUALCOMM_WIFI_PCIE_SOC_GLOBAL_RESET); > + > + msleep(10); > + > + timeout = jiffies + msecs_to_jiffies(5000); > + while (time_before(jiffies, timeout)) { > + val = ioread32(bar + QUALCOMM_WIFI_PCIE_SOC_GLOBAL_RESET); > + if (val != PCI_ERROR_RESPONSE) { As Sashiko also pointed out, this will always evaluate to true due to PCI_ERROR_RESPONSE being ~0ULL. Please use: if (!PCI_POSSIBLE_ERROR(val)) Sorry for the wrong suggestion earlier. - Mani -- மணிவண்ணன் சதாசிவம்