Linux CXL
 help / color / mirror / Atom feed
From: "Lucero Palau, Alejandro" <alejandro.lucero-palau@amd.com>
To: Richard Cheng <icheng@nvidia.com>
Cc: linux-cxl@vger.kernel.org, netdev@vger.kernel.org,
	edward.cree@amd.com, davem@davemloft.net, kuba@kernel.org,
	pabeni@redhat.com, edumazet@google.com, dave.jiang@intel.com,
	Alejandro Lucero <alucerop@amd.com>
Subject: Re: [RFC 1/2] cxl/memdev: add support for mutipf device
Date: Tue, 25 Aug 2026 08:37:24 +0100	[thread overview]
Message-ID: <a313b340-9263-4204-b0b2-bc492cf746a4@amd.com> (raw)
In-Reply-To: <aov-jfhJtNtZJxlN@MWDK4CY14F>


On 24/08/2026 09:33, Richard Cheng wrote:
> On Fri, Aug 21, 2026 at 04:51:33PM +0800,alejandro.lucero-palau@amd.com wrote:
>> From: Alejandro Lucero<alucerop@amd.com>
>>
>> A PCI device can present multiple Physical Functions(PFs) but the CXL
>> specs restrict to the first one, PF0, the discovery and management of
>> CXL capabilities accessed through a PF0 BAR. Other non-PF0 PFs need to
>> obtain the CXL.mem range to work with somehow.
>>
>> Although this could be handled internally by an accelerator/Type2
>> driver, it requires to properly handle changes to the CXL mem device,
>> mainly its release by the CXL core, but also potential CXL device
>> resets. When this release happens, those other PFs need to be told about
>> it.
>>
>> Implement a way for non-PF0 PFs to register/unregister to the memdev
>> linked to the PF0 device. At memdev release, trigger the release of
>> those non-PF0 PFs devices registered to such memdev from the driver they
>> are bound to.
>>
>> Signed-off-by: Alejandro Lucero<alucerop@amd.com>
>> ---
>>   drivers/cxl/core/memdev.c | 122 ++++++++++++++++++++++++++++++++++++++
>>   drivers/cxl/cxlmem.h      |   1 +
>>   include/cxl/cxl.h         |   4 ++
>>   3 files changed, 127 insertions(+)
>>
>> diff --git a/drivers/cxl/core/memdev.c b/drivers/cxl/core/memdev.c
>> index b3419df586b9..327c4da3208f 100644
>> --- a/drivers/cxl/core/memdev.c
>> +++ b/drivers/cxl/core/memdev.c
>> @@ -26,6 +26,35 @@ static void cxl_memdev_release(struct device *dev)
>>   {
>>   	struct cxl_memdev *cxlmd = to_cxl_memdev(dev);
>>   	struct device *parent = dev->parent;
>> +	struct device *sibling;
>> +	unsigned long index;
>> +
>> +	/*
>> +	 * Type2 multipf support implies other non-PF0 PFs could be having a
>> +	 * temporal reference to the memdev, only for registering/unregistering
>> +	 * as sibling, requiring to postpone the memdev release and the sibling
>> +	 * management until no further references. While detach_memdev() calls
>> +	 * for pf0 release from its driver (parent device of the memdev device)
>> +	 * it is not safe to invoke for sibling PFs to be detached at that time
>> +	 * as it could race with PFs registering/unregistering as memdev siblings.
>> +	 */
>> +	if (cxlmd->attach) {
>> +		xa_for_each(&cxlmd->siblings, index, sibling) {
>> +			device_release_driver(sibling);
>> +			xa_erase(&cxlmd->siblings, index);
>> +		}
>> +
>> +		/* Several possibilities trigger a memdev release with one being
>> +		 * its parent device (Type2 device) released from its driver. If
>> +		 * so, such release is the context for this function, precluding
>> +		 * the mutex lock and therefore safely avoiding to invoke the
>> +		 * release again which would trigger a deadlock.
>> +		 */
>> +		if (mutex_trylock(&cxlmd->dev.parent->mutex)) {
>> +			mutex_unlock(&cxlmd->dev.parent->mutex);
>> +			device_release_driver(cxlmd->dev.parent);
>> +		}
>> +	}
>>   
>>   	ida_free(&cxl_memdev_ida, cxlmd->id);
>>   	kfree(cxlmd);
>> @@ -795,6 +824,7 @@ static struct cxl_memdev *cxl_memdev_alloc(struct cxl_dev_state *cxlds,
>>   
>>   	cdev = &cxlmd->cdev;
>>   	cdev_init(cdev, fops);
>> +	xa_init(&cxlmd->siblings);
>>   	return cxlmd;
>>   
>>   err:
>> @@ -802,6 +832,98 @@ static struct cxl_memdev *cxl_memdev_alloc(struct cxl_dev_state *cxlds,
>>   	return ERR_PTR(rc);
>>   }
>>   
>> +static int match_memdev_by_parent_device(struct device *dev, const void *data)
>> +{
>> +	const struct device *pf_dev = data;
>> +	struct cxl_memdev *cxlmd;
>> +
>> +	if (!is_cxl_memdev(dev))
>> +		return 0;
>> +
>> +	cxlmd = to_cxl_memdev(dev);
>> +	return (cxlmd->cxlds->dev == pf_dev);
>> +}
>> +
>> +/**
>> + * cxl_get_pf0_memdev - register as PF0's memdev sibling
>> + * @pf0: device for PF0 used to match current memdevs.
>> + * @pfx: device to register as sibling to PF0's memdev.
>> + * @index: where to register the device in the xarray.
>> + * @range: to be set with the PF0's memdev range.
>> + *
>> + * Return: PF0 memdev pointer or error.
>> + */
>> +struct cxl_memdev *cxl_get_pf0_memdev(struct device *pf0, struct device *pfx,
>> +				      unsigned long index, struct range *range)
>> +{
>> +	struct cxl_attach_region *attach;
>> +	struct cxl_memdev *cxlmd;
>> +	struct device *mem_dev __free(put_device) =
>> +		bus_find_device(&cxl_bus_type, NULL, pf0,
>> +				match_memdev_by_parent_device);
>> +
>> +	if (!mem_dev)
>> +		return ERR_PTR(-ENODEV);
>> +
>> +	cxlmd = to_cxl_memdev(mem_dev);
>> +
>> +	/*
>> +	 * we got the cxl_memdev and the implicit get_device in bus_find_device
>> +	 * makes the next steps safe.
>> +	 */
>> +
>> +	xa_store(&cxlmd->siblings, index, pfx, GFP_KERNEL);
>> +	attach = container_of(cxlmd->attach, struct cxl_attach_region, attach);
>> +
>> +	/*
>> +	 * The cxlmd object does exist and it can be found in the cxl bus after
>> +	 * creation but before attach probe setting the proper HPA range. If so,
>> +	 * the caller will need to try later.
>> +	 */
>> +	if (attach->hpa_range.end == -1)
>> +		return ERR_PTR(-EPROBE_DEFER);
>> +
>> +	range->start =  attach->hpa_range.start;
>> +	range->end =  attach->hpa_range.end;
>> +
>> +	return to_cxl_memdev(mem_dev);
>> +}
>> +EXPORT_SYMBOL_NS_GPL(cxl_get_pf0_memdev, "CXL");
>> +
>> +/**
>> + * cxl_put_pf0_memdev - unregister as PF0's memdev sibling
>> + * @pf0: device for PF0 used to match current memdevs.
>> + * @pfx: device to register as sibling to PF0's memdev.
>> + * @index: where to unregister the device in the xarray.
>> + *
>> + */
>> +void cxl_put_pf0_memdev(struct device *pf0, struct device *pfx,
>> +			unsigned long index)
>> +{
>> +	struct cxl_memdev *cxlmd;
>> +	struct device *mem_dev __free(put_device) =
>> +		bus_find_device(&cxl_bus_type, NULL, pf0,
>> +				match_memdev_by_parent_device);
>> +
>> +	/*
>> +	 * This is not an error but a possibility if triggered by PF0 being
>> +	 * released which triggers the caller driver releasing pfx. It should
>> +	 * not happen if the caller driver does the release of pfx independently
>> +	 * but we do not have a simple way to ensure this here.
>> +	 */
>> +	if (!mem_dev)
>> +		return;
>> +
>> +	/*
>> +	 * we got the cxl_memdev and the implicit get_device in bus_find_device
>> +	 * makes the next steps safe.
>> +	 */
>> +
>> +	cxlmd = to_cxl_memdev(mem_dev);
>> +	xa_erase(&cxlmd->siblings, index);
>> +}
>> +EXPORT_SYMBOL_NS_GPL(cxl_put_pf0_memdev, "CXL");
>> +
>>   static long __cxl_memdev_ioctl(struct cxl_memdev *cxlmd, unsigned int cmd,
>>   			       unsigned long arg)
>>   {
>> diff --git a/drivers/cxl/cxlmem.h b/drivers/cxl/cxlmem.h
>> index c401e3a1af06..430014c4a046 100644
>> --- a/drivers/cxl/cxlmem.h
>> +++ b/drivers/cxl/cxlmem.h
>> @@ -54,6 +54,7 @@
>>    */
>>   struct cxl_memdev {
>>   	struct device dev;
>> +	struct xarray siblings;
>>   	struct cdev cdev;
>>   	struct cxl_dev_state *cxlds;
>>   	struct work_struct detach_work;
>> diff --git a/include/cxl/cxl.h b/include/cxl/cxl.h
>> index 802b143de83d..883ce9f1b73f 100644
>> --- a/include/cxl/cxl.h
>> +++ b/include/cxl/cxl.h
>> @@ -228,4 +228,8 @@ struct cxl_memdev *devm_cxl_probe_mem(struct cxl_dev_state *cxlds,
>>   				      struct range *range);
>>   
>>   int cxl_set_capacity(struct cxl_dev_state *cxlds, u64 capacity);
>> +struct cxl_memdev *cxl_get_pf0_memdev(struct device *pf0, struct device *pfx,
>> +				      unsigned long index, struct range *range);
>> +void cxl_put_pf0_memdev(struct device *pf0, struct device *pfx, unsigned long index);
>> +//struct range *cxl_get_memdev_region_range(struct cxl_memdev *cxlmd);
>>   #endif /* __CXL_CXL_H__ */
>> -- 
>> 2.34.1
>>
>>
> Hi Alejandro,


Hi Richard,


Thanks for the review.


Replying below to your suggestion.


> I agree that non-PF0 functions must stop using the CXL range before it disappears.
> However, I don't think cxl_memdev_release() is the right place to unbind them.


For solving concurrency issues, I had to delay the real detachment to 
the time memdev is released. This is the problem:


1) PF2 is bound to the driver and probed. The code path reaches the 
point where the PF0 memdev is obtained for adding this PF as a sibling.

2) PF0 memdev detach happens. The non-PF0 functions attached need to be 
told triggering their driver's release.


Both are going to modify the list of attached non-PF0 functions, so some 
locking is required, or the memdev could go without awareness of the 
incoming sibling. For example, the xa_store will happen, as a memdev 
reference was taken, but the memdev detachment based on xa_for_each 
could miss it (RCU read) and nothing else will not look later on for it, 
leaving PF2 using it as the driver release call does not happen. Note 
same situation but for PF2 being independently release do not need the 
locking since xarrays intrinsics will be enough and no problem if 
xa_erase happens twice as the index is unique per PF.


The problem with locking is if the second thread gets it, the first one 
will be waiting. Then the second thread will call for PF2 detachment 
implying call to driver release ... which will try to get the PF2 device 
lock in use by the independent PF2 release ... leading to a deadlock. 
Checking if the lock can be obtained is not enough for dismissing the 
call to driver release.


> For Type-2 device, PF0 driver creates the memdev, and therefore the memdev
> is tied to the PF0 driver, while CXL core manages its attachment to
> the topology.
>
> cxl_memdev_release() is the final object release callback. It can run much
> later than the CXL attachment teardown.
> For example, an open /dev/cxl/memX file holds a device ref. PF0 and the CXL
> region can be torn down while that file remains open. During that time,
> other PF driver remain bound with mapping to a range that's no longer valid.


Right. I did not think about this possibility, but I think the memdev is 
safe to be used as long as the related CXL region is present which 
requires to preserve the endpoint HDM (plus the Root Port related HDM). 
In other words, I think for this case extra device references to 
cxlr->dev are necessary. I need to think about this with more care.


> I suggest replacing cxl_get_pf0_memdev() and cxl_put_pf0_memdev() with another
> helper, e.g.:
>
> int cxl_memdev_link_consumer(struct device *pf0, struct device *consumer, struct range *range);
>   
> It should live in cxl/core/memdev.c , and the behavior is something like
>
> 1. Find PF0's memdev and take a temp ref.
> 2. Lock the memdev
> 3. Verify that the memdev is still registered, driver-bound, attached, and has a valid HPA range
> 4. Create a managed devce link via device_link_add(consumer, &cxlmd->dev, DL_FLAG_AUTOREMOVE_CONSUMER);


Interesting approach.


Not sure this could do the proper thing though. 
DL_FLAG_AUTOREMOVE_CONSUMER seems to remove the link, cxlmd->dev in your 
case, when consume driver unbinds ... but it is the other way what we 
need. Maybe I do not understand well all the implications with this 
approach, so let me study it.

Thanks!

> 5. Copy the HPA range
> 6. Unlock the memdev and drop the temp ref
>
> This helper can return only an error code and the range. The sfc driver doesn't need the cxl_memdev pointer then, and
> no put helper would be needed.
>
> And driver core would unbind the non-PF0 consumer before unbinding or removing the supplier ( memdev ).
>
> I think this can remove the sibling xarray , raw device pointers and mutex_trylock() context check.
>
> Would this modle work for your teardown requirements ?
>
> Best regards,
> Richard Cheng.

  reply	other threads:[~2026-08-25  7:37 UTC|newest]

Thread overview: 7+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-21 15:51 [RFC 0/2] Type2 multipf support alejandro.lucero-palau
2026-08-21 15:51 ` [RFC 1/2] cxl/memdev: add support for mutipf device alejandro.lucero-palau
2026-08-22 14:43   ` sashiko-bot
2026-08-24  8:33   ` Richard Cheng
2026-08-25  7:37     ` Lucero Palau, Alejandro [this message]
2026-08-21 15:51 ` [RFC 2/2] sfc: add multipf support alejandro.lucero-palau
2026-08-22 14:43   ` sashiko-bot

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=a313b340-9263-4204-b0b2-bc492cf746a4@amd.com \
    --to=alejandro.lucero-palau@amd.com \
    --cc=alucerop@amd.com \
    --cc=dave.jiang@intel.com \
    --cc=davem@davemloft.net \
    --cc=edumazet@google.com \
    --cc=edward.cree@amd.com \
    --cc=icheng@nvidia.com \
    --cc=kuba@kernel.org \
    --cc=linux-cxl@vger.kernel.org \
    --cc=netdev@vger.kernel.org \
    --cc=pabeni@redhat.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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox