Re: [PATCH v5 5/5] PCI: endpoint: pci-epf-vntb: manage ntb_dev lifetime and fix vpci bus teardown

Koichiro Den <[email protected]> Tue, 3 Mar 2026 22:42:47 +0900
Newsgroups dev.linux.lists.ntb,org.kernel.vger.linux-kernel,org.kernel.vger.linux-pci
Message-ID <mipdls67csyyrugf4rjx3qqtbxes4sjjtluy3psecnadcgcs7k@rn42d3m6ggsf>
On Tue, Mar 03, 2026 at 10:52:05AM +0530, Manivannan Sadhasivam wrote:
> On Thu, Feb 26, 2026 at 05:41:42PM +0900, Koichiro Den wrote:
> > Currently ntb_dev is embedded in epf_ntb, while configfs allows starting
> > or stopping controller and linking or unlinking functions as you want.
> > In fact, re-linking and re-starting is not possible with the embedded
> > design and leads to oopses.
> > 
> > Allocate ntb_dev with devm and add a .remove callback to the pci driver
> > that calls ntb_unregister_device(). This allows a fresh device to be
> > created on the next .bind call.
> > 
> > With these changes, the controller can now be stopped, a function
> > unlinked, configfs settings updated, and the controller re-linked and
> > restarted without rebooting the endpoint, as long as the underlying
> > pci_epc_ops .stop() operation is non-destructive, and .start() can
> > restore normal operations.
> > 
> > Reviewed-by: Frank Li <[email protected]>
> > Signed-off-by: Koichiro Den <[email protected]>
> > ---
> > Changes since v4:
> >   - Adjusted context due to commit:
> >     dc693d606644 ("PCI: endpoint: pci-epf-vntb: Add MSI doorbell support")
> > 
> >  drivers/pci/endpoint/functions/pci-epf-vntb.c | 53 ++++++++++++++-----
> >  1 file changed, 40 insertions(+), 13 deletions(-)
> > 
> > diff --git a/drivers/pci/endpoint/functions/pci-epf-vntb.c b/drivers/pci/endpoint/functions/pci-epf-vntb.c
> > index f353e9a57194..41a2f42e8a39 100644
> > --- a/drivers/pci/endpoint/functions/pci-epf-vntb.c
> > +++ b/drivers/pci/endpoint/functions/pci-epf-vntb.c
> > @@ -120,7 +120,7 @@ struct epf_ntb_ctrl {
> >  } __packed;
> >  
> >  struct epf_ntb {
> > -	struct ntb_dev ntb;
> > +	struct ntb_dev *ntb;
> >  	struct pci_epf *epf;
> >  	struct config_group group;
> >  
> > @@ -147,10 +147,16 @@ struct epf_ntb {
> >  	void __iomem *vpci_mw_addr[MAX_MW];
> >  
> >  	struct delayed_work cmd_handler;
> > +
> > +	struct pci_bus *vpci_bus;
> >  };
> >  
> >  #define to_epf_ntb(epf_group) container_of((epf_group), struct epf_ntb, group)
> > -#define ntb_ndev(__ntb) container_of(__ntb, struct epf_ntb, ntb)
> > +
> > +static inline struct epf_ntb *ntb_ndev(struct ntb_dev *ntb)
> 
> Nit: No need of 'inline' keyword in a .c file.

Thanks for catching this. I'll drop the 'inline' in v6.


While revisiting Patch 5/5 carefully, I also noticed a lifetime issue around
cmd_handler (and doorbell IRQ handlers).

1. Race window between pci_unregister_driver() and work/IRQ teardown

   With this patch, the following window exists:

   -> epf_ntb_unbind()
      -> pci_unregister_driver()
         ...
         -> pci_vntb_remove()
            -> ntb_unregister_device()
            -> ndev->ntb = NULL ----------------------------- (A)
      -> epf_ntb_epc_cleanup()
         -> disable_delayed_work_sync(&ntb->cmd_handler) ---- (B)
         -> epf_ntb_db_bar_clear() (free_irq, etc.) --------- (C)

   If cmd_handler runs between (A) and (B) (or if epf_ntb_doorbell_handler()
   runs between (A) and (C)), they may call ntb_{db,link}_event(ndev->ntb, ...)
   with ndev->ntb == NULL, leading to a NULL deref.  Also, since the doorbell
   IRQs are requested during bind, the IRQ handler can run even before
   pci_vntb_probe() ever sets up ndev->ntb.

   In current mainline, cmd_handler is initially scheduled in
   epf_ntb_epc_init(), and the doorbell IRQs are also requested during bind,
   i.e. both are set up before pci_register_driver() and ndev->ntb becomes
   valid.

   I think the cleanest solution is to tie cmd_handler and the "enabled" state
   of the doorbell IRQs to the lifetime of struct ntb_dev:

   - Move the initial scheduling (queue_work(..., &ndev->cmd_handler.work)) to
     pci_vntb_probe(), after ntb_register_device() succeeds, and stop it in
     pci_vntb_remove() using disable_delayed_work_sync() before
     ntb_unregister_device().

   - Keep request_irq()/free_irq() in the existing bind/unbind paths, but gate
     delivery in vPCI probe/remove: keep the doorbell IRQs disabled until
     pci_vntb_probe() succeeds (enable_irq() after ndev->ntb becomes valid), and
     disable_irq() them in pci_vntb_remove() before clearing ndev->ntb /
     ntb_unregister_device().

   That way, the unsafe windows I wrote above disappear, and the lifetime of
   struct ntb_dev and its associated resources are managed symmetrically across
   bind/unbind and probe/remove paths.

2. Stale ndev->ntb on probe failure

   pci_vntb_probe() assigns ndev->ntb before ntb_register_device(). If probe
   fails after devm_kzalloc(), ndev->ntb may still hold a stale pointer. I'll
   fix this by avoiding publishing ndev->ntb until ntb_register_device()
   succeeds (or clear it on the error path).

I'll include these fixes into v6.

Best regards,
Koichiro

> 
> - Mani
> 
> > +{
> > +	return (struct epf_ntb *)ntb->pdev->sysdata;
> > +}
> >  
> >  static struct pci_epf_header epf_ntb_header = {
> >  	.vendorid	= PCI_ANY_ID,
> > @@ -176,7 +182,7 @@ static int epf_ntb_link_up(struct epf_ntb *ntb, bool link_up)
> >  	else
> >  		ntb->reg->link_status &= ~LINK_STATUS_UP;
> >  
> > -	ntb_link_event(&ntb->ntb);
> > +	ntb_link_event(ntb->ntb);
> >  	return 0;
> >  }
> >  
> > @@ -264,7 +270,7 @@ static void epf_ntb_cmd_handler(struct work_struct *work)
> >  	for (i = 1; i < ntb->db_count && !ntb->msi_doorbell; i++) {
> >  		if (ntb->epf_db[i]) {
> >  			atomic64_or(1 << (i - 1), &ntb->db);
> > -			ntb_db_event(&ntb->ntb, i);
> > +			ntb_db_event(ntb->ntb, i);
> >  			ntb->epf_db[i] = 0;
> >  		}
> >  	}
> > @@ -333,7 +339,7 @@ static irqreturn_t epf_ntb_doorbell_handler(int irq, void *data)
> >  	for (i = 1; i < ntb->db_count; i++)
> >  		if (irq == ntb->epf->db_msg[i].virq) {
> >  			atomic64_or(1 << (i - 1), &ntb->db);
> > -			ntb_db_event(&ntb->ntb, i);
> > +			ntb_db_event(ntb->ntb, i);
> >  		}
> >  
> >  	return IRQ_HANDLED;
> > @@ -1237,6 +1243,7 @@ static int vpci_scan_bus(void *sysdata)
> >  		pr_err("create pci bus failed\n");
> >  		return -EINVAL;
> >  	}
> > +	ndev->vpci_bus = vpci_bus;
> >  
> >  	pci_bus_add_devices(vpci_bus);
> >  
> > @@ -1281,7 +1288,7 @@ static int vntb_epf_mw_set_trans(struct ntb_dev *ndev, int pidx, int idx,
> >  	int ret;
> >  	struct device *dev;
> >  
> > -	dev = &ntb->ntb.dev;
> > +	dev = &ntb->ntb->dev;
> >  	barno = ntb->epf_ntb_bar[BAR_MW1 + idx];
> >  	epf_bar = &ntb->epf->bar[barno];
> >  	epf_bar->phys_addr = addr;
> > @@ -1381,7 +1388,7 @@ static int vntb_epf_peer_db_set(struct ntb_dev *ndev, u64 db_bits)
> >  	ret = pci_epc_raise_irq(ntb->epf->epc, func_no, vfunc_no,
> >  				PCI_IRQ_MSI, interrupt_num + 1);
> >  	if (ret)
> > -		dev_err(&ntb->ntb.dev, "Failed to raise IRQ\n");
> > +		dev_err(&ntb->ntb->dev, "Failed to raise IRQ\n");
> >  
> >  	return ret;
> >  }
> > @@ -1468,9 +1475,12 @@ static int pci_vntb_probe(struct pci_dev *pdev, const struct pci_device_id *id)
> >  	struct epf_ntb *ndev = (struct epf_ntb *)pdev->sysdata;
> >  	struct device *dev = &pdev->dev;
> >  
> > -	ndev->ntb.pdev = pdev;
> > -	ndev->ntb.topo = NTB_TOPO_NONE;
> > -	ndev->ntb.ops =  &vntb_epf_ops;
> > +	ndev->ntb = devm_kzalloc(dev, sizeof(*ndev->ntb), GFP_KERNEL);
> > +	if (!ndev->ntb)
> > +		return -ENOMEM;
> > +	ndev->ntb->pdev = pdev;
> > +	ndev->ntb->topo = NTB_TOPO_NONE;
> > +	ndev->ntb->ops = &vntb_epf_ops;
> >  
> >  	ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32));
> >  	if (ret) {
> > @@ -1478,7 +1488,7 @@ static int pci_vntb_probe(struct pci_dev *pdev, const struct pci_device_id *id)
> >  		return ret;
> >  	}
> >  
> > -	ret = ntb_register_device(&ndev->ntb);
> > +	ret = ntb_register_device(ndev->ntb);
> >  	if (ret) {
> >  		dev_err(dev, "Failed to register NTB device\n");
> >  		return ret;
> > @@ -1488,6 +1498,17 @@ static int pci_vntb_probe(struct pci_dev *pdev, const struct pci_device_id *id)
> >  	return 0;
> >  }
> >  
> > +static void pci_vntb_remove(struct pci_dev *pdev)
> > +{
> > +	struct epf_ntb *ndev = (struct epf_ntb *)pdev->sysdata;
> > +
> > +	if (!ndev || !ndev->ntb)
> > +		return;
> > +
> > +	ntb_unregister_device(ndev->ntb);
> > +	ndev->ntb = NULL;
> > +}
> > +
> >  static struct pci_device_id pci_vntb_table[] = {
> >  	{
> >  		PCI_DEVICE(0xffff, 0xffff),
> > @@ -1499,6 +1520,7 @@ static struct pci_driver vntb_pci_driver = {
> >  	.name           = "pci-vntb",
> >  	.id_table       = pci_vntb_table,
> >  	.probe          = pci_vntb_probe,
> > +	.remove         = pci_vntb_remove,
> >  };
> >  
> >  /* ============ PCIe EPF Driver Bind ====================*/
> > @@ -1581,10 +1603,15 @@ static void epf_ntb_unbind(struct pci_epf *epf)
> >  {
> >  	struct epf_ntb *ntb = epf_get_drvdata(epf);
> >  
> > +	pci_unregister_driver(&vntb_pci_driver);
> > +
> > +	pci_lock_rescan_remove();
> > +	pci_stop_root_bus(ntb->vpci_bus);
> > +	pci_remove_root_bus(ntb->vpci_bus);
> > +	pci_unlock_rescan_remove();
> > +
> >  	epf_ntb_epc_cleanup(ntb);
> >  	epf_ntb_config_spad_bar_free(ntb);
> > -
> > -	pci_unregister_driver(&vntb_pci_driver);
> >  }
> >  
> >  // EPF driver probe
> > -- 
> > 2.51.0
> > 
> 
> -- 
> மணிவண்ணன் சதாசிவம்