All of lore.kernel.org
 help / color / mirror / Atom feed
From: Alex Williamson <alex@shazbot.org>
To: <mhonap@nvidia.com>
Cc: <jgg@ziepe.ca>, <ankita@nvidia.com>, <jic23@kernel.org>,
	<dave.jiang@intel.com>, <alejandro.lucero-palau@amd.com>,
	<smadhavan@nvidia.com>, <corbet@lwn.net>,
	<skhan@linuxfoundation.org>, <dave@stgolabs.net>,
	<alison.schofield@intel.com>, <vishal.l.verma@intel.com>,
	<iweiny@kernel.org>, <ming.li@zohomail.com>, <yishaih@nvidia.com>,
	<skolothumtho@nvidia.com>, <kevin.tian@intel.com>,
	<bhelgaas@google.com>, <dmatlack@google.com>, <kees@kernel.org>,
	<gustavoars@kernel.org>, <cjia@nvidia.com>, <kjaju@nvidia.com>,
	<vsethi@nvidia.com>, <zhiw@nvidia.com>,
	<linux-doc@vger.kernel.org>, <linux-kernel@vger.kernel.org>,
	<kvm@vger.kernel.org>, <linux-cxl@vger.kernel.org>,
	<linux-pci@vger.kernel.org>, <linux-kselftest@vger.kernel.org>,
	<linux-hardening@vger.kernel.org>,
	alex@shazbot.org
Subject: Re: [PATCH v6 04/31] PCI/CXL: Let the driver that owns the HDM range run CXL Reset
Date: Wed, 4 Nov 2026 16:54:22 -0700	[thread overview]
Message-ID: <20261104165422.64b53bc4@shazbot.org> (raw)
In-Reply-To: <20261016161839.2765592-5-mhonap@nvidia.com>

On Fri, 16 Oct 2026 21:48:11 +0530
<mhonap@nvidia.com> wrote:

> From: Manish Honap <mhonap@nvidia.com>
> 
> vfio-cxl passes a CXL Type-2 device to a guest and must run CXL Reset at
> the vfio reset points. The cxl_reset PCI reset method does not fit that
> caller:
> 
> - It requests every HPA range the device decodes. vfio-cxl holds that
>   range with request_mem_region_exclusive() while it exposes the memory,
>   so the request fails with -EBUSY.
> - When the reset cannot run, the method returns -ENOTTY and the PCI core
>   moves on to the next method. That is FLR, which leaves device memory
>   intact for the next user.
> - The snapshot and restore helpers that vfio-cxl needs after a bus reset
>   and on return from low power are not exported.
> 
> Let cxl_hdm_quiesce_prepare() take the resource the caller already holds
> and skip the request for ranges inside it. pci_cxl_reset_owned() accepts
> only a resource marked busy and exclusive, as
> request_mem_region_exclusive() leaves it. The CPU caches are still
> written back and invalidated for every range before and after the reset,
> as the reset method does.
> 
> Add:
> - pci_cxl_reset_owned(): runs the reset method's sequence for a caller that
>   holds the device lock and returns any failure to that caller
> 
> - pci_cxl_reset_capable(): checks the cached HDM state and the CPU cache
>   invalidation that the reset-method probe skips.
> 
> - pci_cxl_hdm_snapshot(): copies the HDM state cached at enumeration
>   without requiring the device lock. A driver needs this copy in probe,
>   which the PCI core may run in a worker on the device's NUMA node, where
>   device_lock_assert() would warn although the binding task holds the
>   lock. pci_cxl_reset_prepare() becomes that copy plus the lock assertion.
> 
> Export:
> - pci_cxl_reset_owned()
> - pci_cxl_reset_capable()
> - pci_cxl_hdm_snapshot()
> - cxl_restore_state_after_pci_reset()
> 
> to the vfio-cxl module only, and declare them in include/cxl/pci.h.
> 
> CXL Reset always clears memory, so there is no caller Memory Clear choice.
> 
> Assisted-by: LLM
> Signed-off-by: Manish Honap <mhonap@nvidia.com>
> ---
>  drivers/cxl/core/hdm_state.c |  22 ++++--
>  drivers/pci/cxl.c            | 136 ++++++++++++++++++++++++++++-------
>  drivers/pci/pci.h            |  18 +----
>  include/cxl/hdm.h            |   3 +-
>  include/cxl/pci.h            |  39 ++++++++++
>  5 files changed, 171 insertions(+), 47 deletions(-)
> 
> diff --git a/drivers/cxl/core/hdm_state.c b/drivers/cxl/core/hdm_state.c
> index 95daeda0ef80..9452047e3b97 100644
> --- a/drivers/cxl/core/hdm_state.c
> +++ b/drivers/cxl/core/hdm_state.c
> @@ -267,7 +267,8 @@ static int cxl_hdm_ranges_collect(struct cxl_hdm_quiesce *q, struct device *dev,
>  	return 0;
>  }
>  
> -static int cxl_hdm_ranges_request(struct cxl_hdm_quiesce *q)
> +static int cxl_hdm_ranges_request(struct cxl_hdm_quiesce *q,
> +				  const struct resource *owned)
>  {
>  	struct cxl_hdm_range *range;
>  
> @@ -275,6 +276,12 @@ static int cxl_hdm_ranges_request(struct cxl_hdm_quiesce *q)
>  
>  	list_for_each_entry(range, &q->ranges, list) {
>  		const struct range *hpa_range = &range->hpa_range;
> +		struct resource res = DEFINE_RES_MEM(hpa_range->start,
> +						     range->len);
> +
> +		/* The caller already holds this range exclusively. */
> +		if (owned && resource_contains(owned, &res))
> +			continue;
>  
>  		range->res = request_mem_region(hpa_range->start, range->len,
>  						"cxl_reset");
> @@ -316,15 +323,20 @@ static int cxl_hdm_ranges_invalidate(struct cxl_hdm_quiesce *q)
>   * @dev: device for error messages
>   * @q: quiesce state to initialize
>   * @snapshot: HDM state from cxl_hdm_cache_snapshot()
> + * @owned: resource the caller holds exclusively over the ranges, or NULL
>   *
>   * Reserve every enabled system-physical range in @snapshot exclusively, then
> - * write back and invalidate CPU caches for them. Call
> - * cxl_hdm_quiesce_destroy() afterwards whether or not this succeeds.
> + * write back and invalidate CPU caches for them. A range inside @owned is
> + * not requested again; the caller's reservation already excludes other
> + * users.
> + *
> + * Call cxl_hdm_quiesce_destroy() afterwards whether or not this succeeds.
>   *
>   * Return: 0 on success, or a negative errno.
>   */
>  int cxl_hdm_quiesce_prepare(struct device *dev, struct cxl_hdm_quiesce *q,
> -			    const struct cxl_hdm_info *snapshot)
> +			    const struct cxl_hdm_info *snapshot,
> +			    const struct resource *owned)
>  {
>  	int rc;
>  
> @@ -340,7 +352,7 @@ int cxl_hdm_quiesce_prepare(struct device *dev, struct cxl_hdm_quiesce *q,
>  	if (rc)
>  		return rc;
>  
> -	rc = cxl_hdm_ranges_request(q);
> +	rc = cxl_hdm_ranges_request(q, owned);
>  	if (rc)
>  		return rc;
>  
> diff --git a/drivers/pci/cxl.c b/drivers/pci/cxl.c
> index ed0b6f619c70..b4d467506c54 100644
> --- a/drivers/pci/cxl.c
> +++ b/drivers/pci/cxl.c
> @@ -372,9 +372,32 @@ static int cxl_reset_save_restored_state(struct pci_dev *pdev, u16 command)
>  	return rc;
>  }
>  
> +/**
> + * pci_cxl_hdm_snapshot() - copy the HDM state cached at enumeration
> + * @pdev: CXL device
> + *
> + * The cache is read under cxl_rwsem.dpa, so the caller needs no device
> + * lock. A driver can call this from probe, which the PCI core may run in a
> + * worker on the device's NUMA node while the binding task holds the lock.
> + *
> + * Return: a copy to free with kfree(); NULL when @pdev has no cached HDM
> + * state; or an ERR_PTR() on failure.
> + */
> +struct cxl_hdm_info *pci_cxl_hdm_snapshot(struct pci_dev *pdev)
> +{
> +	struct cxl_hdm_info *snapshot;
> +
> +	snapshot = cxl_hdm_cache_snapshot(&pdev->hdm);
> +	if (IS_ERR(snapshot) && PTR_ERR(snapshot) == -ENXIO)
> +		return NULL;
> +
> +	return snapshot;
> +}
> +EXPORT_SYMBOL_FOR_MODULES(pci_cxl_hdm_snapshot, "vfio-cxl");

Why are any of these module locked?  What great risk do they expose
that only vfio-cxl should have access to versus just a NS export?

We shouldn't set a precedent for vfio specific APIs and I certainly
don't want to be defending such a thing down the road.

It's unfortunate that we're intermixing "snapshot" from CXL but using
it like a "save state" from PCI.  Maybe we could bridge the gap at the
PCI API?

> +
>  /**
>   * pci_cxl_reset_prepare() - allocate HDM state storage ahead of a bus reset
> - * @pdev: device about to be reset
> + * @pdev: device about to be reset, with its device lock held
>   *
>   * Copy the cached HDM state before the reset, so that restoring cannot fail
>   * for lack of memory. The decoder settings are refreshed when the state is
> @@ -386,14 +409,8 @@ static int cxl_reset_save_restored_state(struct pci_dev *pdev, u16 command)
>   */
>  struct cxl_hdm_info *pci_cxl_reset_prepare(struct pci_dev *pdev)
>  {
> -	struct cxl_hdm_info *snapshot;
> -
>  	device_lock_assert(&pdev->dev);
> -	snapshot = cxl_hdm_cache_snapshot(&pdev->hdm);
> -	if (IS_ERR(snapshot) && PTR_ERR(snapshot) == -ENXIO)
> -		return NULL;
> -
> -	return snapshot;
> +	return pci_cxl_hdm_snapshot(pdev);
>  }
>  
>  /**
> @@ -433,6 +450,7 @@ int cxl_restore_state_after_pci_reset(struct pci_dev *pdev,
>  	cxl_region_quiesce_unlock();
>  	return rc;
>  }
> +EXPORT_SYMBOL_FOR_MODULES(cxl_restore_state_after_pci_reset, "vfio-cxl");
>  
>  /*
>   * CXL r4.0 sec 9.7.2 defines the reset completion timeout encodings.
> @@ -642,7 +660,8 @@ static int __cxl_reset_execute(struct pci_dev *pdev, int dvsec, u16 cap,
>  	return rc;
>  }
>  
> -static int cxl_reset_quiesced(struct pci_dev *pdev, int dvsec, u16 cap)
> +static int cxl_reset_quiesced(struct pci_dev *pdev, int dvsec, u16 cap,
> +			      const struct resource *owned)
>  	__must_hold(cxl_region_quiesce)
>  {
>  	struct cxl_hdm_info *snapshot __free(kfree) = NULL;
> @@ -663,7 +682,7 @@ static int cxl_reset_quiesced(struct pci_dev *pdev, int dvsec, u16 cap)
>  		return PTR_ERR(snapshot);
>  	}
>  
> -	rc = cxl_hdm_quiesce_prepare(&pdev->dev, &q, snapshot);
> +	rc = cxl_hdm_quiesce_prepare(&pdev->dev, &q, snapshot, owned);
>  	if (rc) {
>  		cxl_hdm_quiesce_destroy(&q);
>  		return rc;
> @@ -686,32 +705,40 @@ static int cxl_reset_quiesced(struct pci_dev *pdev, int dvsec, u16 cap)
>  	return cxl_reset_save_restored_state(pdev, command);
>  }
>  
> -static int cxl_reset_execute(struct pci_dev *pdev, int dvsec, u16 cap)
> +static int cxl_reset_execute(struct pci_dev *pdev, int dvsec, u16 cap,
> +			     const struct resource *owned)
>  {
>  	int rc;
>  
> +	/* Raw reset callers may not have saved the PCI state needed for restore. */
> +	if (!pdev->state_saved) {
> +		pci_err(pdev, "CXL reset requires saved PCI state\n");
> +		return -EINVAL;
> +	}
> +
>  	cxl_region_quiesce_lock();
> -	rc = cxl_reset_quiesced(pdev, dvsec, cap);
> +	rc = cxl_reset_quiesced(pdev, dvsec, cap, owned);
>  	cxl_region_quiesce_unlock();
>  
>  	return rc;
>  }
>  
> -int cxl_reset_function(struct pci_dev *pdev, bool probe)
> +static int cxl_reset_check(struct pci_dev *pdev, bool probe, u16 *dvsec,
> +			   u16 *cap)
>  {
> -	u16 dvsec, cap, ctrl;
> +	u16 ctrl;
>  	int rc;
>  
> -	rc = cxl_pci_device_dvsec(pdev, &dvsec, &cap);
> +	rc = cxl_pci_device_dvsec(pdev, dvsec, cap);
>  	if (rc)
>  		return probe ? -ENOTTY : rc;
> -	if (!(cap & PCI_DVSEC_CXL_CACHE_CAPABLE) ||
> -	    !(cap & PCI_DVSEC_CXL_MEM_CAPABLE) ||
> -	    !(cap & PCI_DVSEC_CXL_RST_CAPABLE) ||
> -	    !(cap & PCI_DVSEC_CXL_RST_MEM_CLR_CAPABLE))
> +	if (!(*cap & PCI_DVSEC_CXL_CACHE_CAPABLE) ||
> +	    !(*cap & PCI_DVSEC_CXL_MEM_CAPABLE) ||
> +	    !(*cap & PCI_DVSEC_CXL_RST_CAPABLE) ||
> +	    !(*cap & PCI_DVSEC_CXL_RST_MEM_CLR_CAPABLE))
>  		return -ENOTTY;
>  
> -	rc = pci_read_config_word(pdev, dvsec + PCI_DVSEC_CXL_CTRL, &ctrl);
> +	rc = pci_read_config_word(pdev, *dvsec + PCI_DVSEC_CXL_CTRL, &ctrl);
>  	if (rc)
>  		return probe ? -ENOTTY : pcibios_err_to_errno(rc);
>  	if (PCI_POSSIBLE_ERROR(ctrl))
> @@ -737,11 +764,70 @@ int cxl_reset_function(struct pci_dev *pdev, bool probe)
>  	if (!cpu_cache_has_invalidate_memregion())
>  		return -ENOTTY;
>  
> -	/* Raw reset callers may not have saved the PCI state needed for restore. */
> -	if (!pdev->state_saved) {
> -		pci_err(pdev, "CXL reset requires saved PCI state\n");
> +	return 0;
> +}
> +
> +int cxl_reset_function(struct pci_dev *pdev, bool probe)
> +{
> +	u16 dvsec, cap;
> +	int rc;
> +
> +	rc = cxl_reset_check(pdev, probe, &dvsec, &cap);
> +	if (rc || probe)
> +		return rc;
> +
> +	return cxl_reset_execute(pdev, dvsec, cap, NULL);
> +}
> +
> +/**
> + * pci_cxl_reset_capable() - check whether CXL Reset can run on a device now
> + * @pdev: CXL device
> + *
> + * Unlike the reset-method probe, this also checks the cached HDM state and
> + * the CPU cache invalidation that the reset needs.
> + *
> + * Return: true when pci_cxl_reset_owned() can attempt the reset.
> + */
> +bool pci_cxl_reset_capable(struct pci_dev *pdev)
> +{
> +	u16 dvsec, cap;
> +
> +	return !cxl_reset_check(pdev, false, &dvsec, &cap);
> +}
> +EXPORT_SYMBOL_FOR_MODULES(pci_cxl_reset_capable, "vfio-cxl");
> +
> +/**
> + * pci_cxl_reset_owned() - CXL Reset for the driver that owns the HDM range
> + * @pdev: CXL device, with its device lock held and PCI state saved
> + * @owned: resource the caller reserved over the device's HPA range with
> + *	   request_mem_region_exclusive(), or NULL
> + *
> + * Run the same sequence as the cxl_reset method. Ranges inside @owned are not
> + * requested again, so a driver that keeps the HPA range reserved while it
> + * exposes the memory can reset the device. The CPU caches are still
> + * invalidated for every range before and after the reset.
> + *
> + * Return: 0 on success, or a negative errno. Unlike the reset method, a
> + * device that cannot be reset returns an error to the caller instead of
> + * falling through to another reset method. -EINVAL if @owned is not a busy,
> + * exclusive reservation.
> + */
> +int pci_cxl_reset_owned(struct pci_dev *pdev, const struct resource *owned)
> +{
> +	const unsigned long held = IORESOURCE_BUSY | IORESOURCE_EXCLUSIVE;
> +	u16 dvsec, cap;
> +	int rc;
> +
> +	device_lock_assert(&pdev->dev);
> +
> +	/* Only a busy, exclusive reservation keeps other users off the range. */
> +	if (owned && WARN_ON_ONCE((owned->flags & held) != held))
>  		return -EINVAL;
> -	}
>  
> -	return cxl_reset_execute(pdev, dvsec, cap);
> +	rc = cxl_reset_check(pdev, false, &dvsec, &cap);
> +	if (rc)
> +		return rc;
> +
> +	return cxl_reset_execute(pdev, dvsec, cap, owned);
>  }

There's a lot of asymmetric sketchiness through this path.
pdev->state_saved is required to be set by the caller, but the state
gets restored and state_saved invalided deep inside
cxl_restore_pci_state_for_hdm_restore() on this path. That's not at all
symmetric to the PCI conventions and the split ownership model is poor.
Even worse, it's vfio-pci-core doing the pci_save_state() on behalf of
the cxl_ops.reset callback.

Where does bus-master get cleared?  Seems we've only got half of
pci_dev_save_and_disable() implemented here and the save has become the
caller's responsibility.  Thanks,

Alex

  reply	other threads:[~2026-11-04 23:55 UTC|newest]

Thread overview: 44+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-16 16:18 [PATCH v6 00/31] vfio/pci: Add CXL Type-2 device passthrough support mhonap
2026-10-16 16:18 ` [PATCH v6 01/31] cxl/regs: Split the BAR block request and ioremap helpers mhonap
2026-10-16 16:18 ` [PATCH v6 02/31] cxl/regs: Let a BAR-owning driver own the component register block mhonap
2026-10-16 16:18 ` [PATCH v6 03/31] cxl: Move component register defines to uapi/cxl/cxl_regs.h mhonap
2026-11-04 23:52   ` Alex Williamson
2026-10-16 16:18 ` [PATCH v6 04/31] PCI/CXL: Let the driver that owns the HDM range run CXL Reset mhonap
2026-11-04 23:54   ` Alex Williamson [this message]
2026-10-16 16:18 ` [PATCH v6 05/31] PCI: Refuse bus and slot reset below a CXL port with SBR masked mhonap
2026-11-04 23:53   ` Alex Williamson
2026-10-16 16:18 ` [PATCH v6 06/31] vfio/pci: Add the CXL provider ops registration interface mhonap
2026-11-04 23:53   ` Alex Williamson
2026-10-16 16:18 ` [PATCH v6 07/31] vfio/pci: Detect CXL devices and load the CXL provider on demand mhonap
2026-11-04 23:53   ` Alex Williamson
2026-10-16 16:18 ` [PATCH v6 08/31] vfio/pci: Add a generic excluded-range list mhonap
2026-11-06 15:59   ` Alex Williamson
2026-10-16 16:18 ` [PATCH v6 09/31] vfio/pci: Advertise the mmap areas around excluded ranges mhonap
2026-11-06 20:39   ` Alex Williamson
2026-10-16 16:18 ` [PATCH v6 10/31] vfio/pci: Refuse ioeventfds and dma-buf exports over " mhonap
2026-11-06 20:52   ` Alex Williamson
2026-10-16 16:18 ` [PATCH v6 11/31] vfio/pci: Virtualize the CXL DVSEC in vfio_pci_config.c mhonap
2026-11-06 22:10   ` Alex Williamson
2026-10-16 16:18 ` [PATCH v6 12/31] vfio/pci: Call the CXL open and close hooks around device use mhonap
2026-11-06 22:51   ` Alex Williamson
2026-10-16 16:18 ` [PATCH v6 13/31] vfio/pci: Bracket PCI resets with the CXL reset hooks mhonap
2026-11-06 23:44   ` Alex Williamson
2026-10-16 16:18 ` [PATCH v6 14/31] vfio/pci: Provide an opt-out for the CXL Type-2 extensions mhonap
2026-10-16 16:18 ` [PATCH v6 15/31] vfio/pci: Route every reset point through a try-reset helper mhonap
2026-11-04 23:52   ` Alex Williamson
2026-10-16 16:18 ` [PATCH v6 16/31] vfio/pci: Revoke and re-arm one device-specific region mhonap
2026-10-16 16:18 ` [PATCH v6 17/31] vfio/pci: Call the device provider across low-power transitions mhonap
2026-10-16 16:18 ` [PATCH v6 18/31] vfio/cxl: Add the vfio-cxl provider module skeleton mhonap
2026-10-16 16:18 ` [PATCH v6 19/31] vfio/cxl: Create the CXL memdev and set media ready at bind mhonap
2026-10-16 16:18 ` [PATCH v6 20/31] vfio/cxl: Own the whole component register BAR mhonap
2026-10-16 16:18 ` [PATCH v6 21/31] vfio/cxl: Expose the HDM memory region to the guest mhonap
2026-10-16 16:18 ` [PATCH v6 22/31] vfio/cxl: Allow mmap of the HDM memory region mhonap
2026-10-16 16:18 ` [PATCH v6 23/31] vfio/cxl: Contain HDM memory errors with memory_failure() mhonap
2026-10-16 16:18 ` [PATCH v6 24/31] vfio/cxl: Expose the HDM decoder registers read-only to the guest mhonap
2026-10-16 16:18 ` [PATCH v6 25/31] vfio/cxl: Exclude the HDM decoder registers from direct BAR access mhonap
2026-10-16 16:18 ` [PATCH v6 26/31] vfio/cxl: Restore the HDM decoder after a hot reset mhonap
2026-10-16 16:18 ` [PATCH v6 27/31] vfio/cxl: Describe the CXL device and decoder geometry to userspace mhonap
2026-10-16 16:18 ` [PATCH v6 28/31] vfio/cxl: Export the HDM memory region as a dma-buf mhonap
2026-10-16 16:18 ` [PATCH v6 29/31] vfio/cxl: Run the CXL reset at the vfio reset points mhonap
2026-10-16 16:18 ` [PATCH v6 30/31] Documentation: vfio-pci: Document CXL Type-2 device passthrough mhonap
2026-10-16 16:18 ` [PATCH v6 31/31] selftests/vfio: Add CXL Type-2 passthrough tests mhonap

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261104165422.64b53bc4@shazbot.org \
    --to=alex@shazbot.org \
    --cc=alejandro.lucero-palau@amd.com \
    --cc=alison.schofield@intel.com \
    --cc=ankita@nvidia.com \
    --cc=bhelgaas@google.com \
    --cc=cjia@nvidia.com \
    --cc=corbet@lwn.net \
    --cc=dave.jiang@intel.com \
    --cc=dave@stgolabs.net \
    --cc=dmatlack@google.com \
    --cc=gustavoars@kernel.org \
    --cc=iweiny@kernel.org \
    --cc=jgg@ziepe.ca \
    --cc=jic23@kernel.org \
    --cc=kees@kernel.org \
    --cc=kevin.tian@intel.com \
    --cc=kjaju@nvidia.com \
    --cc=kvm@vger.kernel.org \
    --cc=linux-cxl@vger.kernel.org \
    --cc=linux-doc@vger.kernel.org \
    --cc=linux-hardening@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-kselftest@vger.kernel.org \
    --cc=linux-pci@vger.kernel.org \
    --cc=mhonap@nvidia.com \
    --cc=ming.li@zohomail.com \
    --cc=skhan@linuxfoundation.org \
    --cc=skolothumtho@nvidia.com \
    --cc=smadhavan@nvidia.com \
    --cc=vishal.l.verma@intel.com \
    --cc=vsethi@nvidia.com \
    --cc=yishaih@nvidia.com \
    --cc=zhiw@nvidia.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.