diff mbox series

[1/4] cxl: mbox: Preparatory move of functions to core/mbox.c and cxlmbox.h

Message ID 20231016125323.18318-2-Jonathan.Cameron@huawei.com
State New, archived
Headers show
Series CXL: Standalone switch CCI driver | expand

Commit Message

Jonathan Cameron Oct. 16, 2023, 12:53 p.m. UTC
A later patch will modify this code to separate out the mbox
functionality from the memdev.  This patch is intended to make
that a little more readable.  Related move of cxl_err(), cxl_cmd_err()
to make them accessible from other parts of the cxl core code.

Reviewed-by: Davidlohr Bueso <dave@stgolabs.net>
Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
---
 drivers/cxl/core/mbox.c | 261 ++++++++++++++++++++++++++++++++++++++-
 drivers/cxl/cxlmbox.h   | 146 ++++++++++++++++++++++
 drivers/cxl/cxlmem.h    | 148 ++--------------------
 drivers/cxl/pci.c       | 265 +---------------------------------------
 4 files changed, 418 insertions(+), 402 deletions(-)

Comments

Dan Williams Nov. 13, 2023, 12:09 a.m. UTC | #1
Jonathan Cameron wrote:
> A later patch will modify this code to separate out the mbox
> functionality from the memdev.  This patch is intended to make
> that a little more readable.  Related move of cxl_err(), cxl_cmd_err()
> to make them accessible from other parts of the cxl core code.
> 
> Reviewed-by: Davidlohr Bueso <dave@stgolabs.net>
> Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
> ---
>  drivers/cxl/core/mbox.c | 261 ++++++++++++++++++++++++++++++++++++++-
>  drivers/cxl/cxlmbox.h   | 146 ++++++++++++++++++++++
>  drivers/cxl/cxlmem.h    | 148 ++--------------------
>  drivers/cxl/pci.c       | 265 +---------------------------------------
>  4 files changed, 418 insertions(+), 402 deletions(-)

Given that the CXL mailbox format is being adopted by other standards
efforts in PCIe and OCP I would expect that this functionality is better
served moving out of cxl_core.ko into its own compilation unit.

Something like drivers/cxl/cci/, that CXL can select. The s/mbox/cci/
rename is a proposal to both drop a letter out of the acronym and
perhaps make the code a bit more discoverable / palatable for folks
coming from those non-CXL specs to find the Linux code. However, if
folks think that's too much thrash I am ok with drivers/cxl/mbox/.

As for the policy for raw commands. I would still like for there to be
some discipline of registratants to this facility to classify production
vs debug commands to give Linux distributors a single place to set policy.
I.e. keep up the need for consumers of this to define "Linux" commands
to avoid the "raw" warning.

That said, how much of what switch CCI wants to do should be an ioctl()
ABI vs sysfs / configfs? The raw commands are really only there for
prototyping until production flows are established.
Sumanesh Samanta Nov. 13, 2023, 7:39 p.m. UTC | #2
>>Given that the CXL mailbox format is being adopted by other standards
>>efforts in PCIe and OCP I would expect that this functionality is better
>>served moving out of cxl_core.ko into its own compilation unit.

Hi Dan, Jonathan,

Agree with the statement above, and in fact, I think that there should
be a way applications can send CCI commands even from hosts without
CXL root port.
Consider a CXL switch that is connected to two hosts, one with CXL
root port, and other having a pure PCIe root port.
Ideally applications on either host should be able to communicate
with/configure the switch in the same way from both the hosts (CXL
capable or not).

If the CCI mailbox driver is completely dependent on the CXL root
port, then it will not even load on a PCIe root port, even if we
implement the 0x0c0b00 class code in a CXL switch.
In this respect, a CXL switch can implement the PCIe MMPT interface
too, so that PCIe based drivers can access that interface to send
mailbox commands.
The idea is, even if the CCI mailbox driver does not load in a non-CXL
root port, an application can use the MMPT interface to manage the
driver.
Please let me know if you think that will work?

One potential problem I see is that in CXL root port, both CCI and
MMPT mailbox will be available, which might lead to conflict if two
applications use the two interfaces at the same time.
Ultimately I think having a root port independent mailbox driver (that
works in both CXL and PCIe root port) would be helpful for switches
that can connect to both CXL and PCIe root ports.

Would appreciate any insight on this.

sincerely,
Sumanesh




On Sun, Nov 12, 2023 at 5:10 PM Dan Williams <dan.j.williams@intel.com> wrote:
>
> Jonathan Cameron wrote:
> > A later patch will modify this code to separate out the mbox
> > functionality from the memdev.  This patch is intended to make
> > that a little more readable.  Related move of cxl_err(), cxl_cmd_err()
> > to make them accessible from other parts of the cxl core code.
> >
> > Reviewed-by: Davidlohr Bueso <dave@stgolabs.net>
> > Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
> > ---
> >  drivers/cxl/core/mbox.c | 261 ++++++++++++++++++++++++++++++++++++++-
> >  drivers/cxl/cxlmbox.h   | 146 ++++++++++++++++++++++
> >  drivers/cxl/cxlmem.h    | 148 ++--------------------
> >  drivers/cxl/pci.c       | 265 +---------------------------------------
> >  4 files changed, 418 insertions(+), 402 deletions(-)
>
> Given that the CXL mailbox format is being adopted by other standards
> efforts in PCIe and OCP I would expect that this functionality is better
> served moving out of cxl_core.ko into its own compilation unit.
>
> Something like drivers/cxl/cci/, that CXL can select. The s/mbox/cci/
> rename is a proposal to both drop a letter out of the acronym and
> perhaps make the code a bit more discoverable / palatable for folks
> coming from those non-CXL specs to find the Linux code. However, if
> folks think that's too much thrash I am ok with drivers/cxl/mbox/.
>
> As for the policy for raw commands. I would still like for there to be
> some discipline of registratants to this facility to classify production
> vs debug commands to give Linux distributors a single place to set policy.
> I.e. keep up the need for consumers of this to define "Linux" commands
> to avoid the "raw" warning.
>
> That said, how much of what switch CCI wants to do should be an ioctl()
> ABI vs sysfs / configfs? The raw commands are really only there for
> prototyping until production flows are established.
>
Jonathan Cameron Nov. 28, 2023, 5:26 p.m. UTC | #3
On Mon, 13 Nov 2023 12:39:15 -0700
Sumanesh Samanta <sumanesh.samanta@broadcom.com> wrote:

> >>Given that the CXL mailbox format is being adopted by other standards
> >>efforts in PCIe and OCP I would expect that this functionality is better
> >>served moving out of cxl_core.ko into its own compilation unit.  

Makes sense to break it out to a separate module. I was kind of assuming that
would happen later and the fact it isn't is more about history of this patch
set than anything else (predates MMPT surfacing)

> 
> Hi Dan, Jonathan,
> 
> Agree with the statement above, and in fact, I think that there should
> be a way applications can send CCI commands even from hosts without
> CXL root port.
> Consider a CXL switch that is connected to two hosts, one with CXL
> root port, and other having a pure PCIe root port.
> Ideally applications on either host should be able to communicate
> with/configure the switch in the same way from both the hosts (CXL
> capable or not).


Absolutely agree, but I think this should just work with the current code.
It's using stuff from the library module, not stuff that is loaded just
when the ACPI tables say it's a CXL system. We can reduce what is pulled
in by doing what Dan suggests but that's a software modularity question
rather than anything about the hardware supported.

> 
> If the CCI mailbox driver is completely dependent on the CXL root
> port, then it will not even load on a PCIe root port, even if we
> implement the 0x0c0b00 class code in a CXL switch.
> In this respect, a CXL switch can implement the PCIe MMPT interface
> too, so that PCIe based drivers can access that interface to send
> mailbox commands.
> The idea is, even if the CCI mailbox driver does not load in a non-CXL
> root port, an application can use the MMPT interface to manage the
> driver.
> Please let me know if you think that will work?
> 
> One potential problem I see is that in CXL root port, both CCI and
> MMPT mailbox will be available, which might lead to conflict if two
> applications use the two interfaces at the same time.
> Ultimately I think having a root port independent mailbox driver (that
> works in both CXL and PCIe root port) would be helpful for switches
> that can connect to both CXL and PCIe root ports.

'Watch this space' as interaction of MMPT and CXL is not
something the CXL spec speaks about and there are some corners
that need to be resolved.  Take questions about this to appropriate
standards orgs as we can't resolve this here.

However, the switch CCI is not associated with the root port it's associated
with a PCIe function that can be on a PCIe bus.  Whether we end up with
PCIe software support MMPT without needing to be part of a driver binding
to the class code (which inherently here means it understands the CXL mailbox)
is an interesting question.  I find it unlikely that Linux will support such
'bare' MMPT instances but maybe...

Jonathan


> 
> Would appreciate any insight on this.
> 
> sincerely,
> Sumanesh
> 
> 
> 
> 
> On Sun, Nov 12, 2023 at 5:10 PM Dan Williams <dan.j.williams@intel.com> wrote:
> >
> > Jonathan Cameron wrote:  
> > > A later patch will modify this code to separate out the mbox
> > > functionality from the memdev.  This patch is intended to make
> > > that a little more readable.  Related move of cxl_err(), cxl_cmd_err()
> > > to make them accessible from other parts of the cxl core code.
> > >
> > > Reviewed-by: Davidlohr Bueso <dave@stgolabs.net>
> > > Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
> > > ---
> > >  drivers/cxl/core/mbox.c | 261 ++++++++++++++++++++++++++++++++++++++-
> > >  drivers/cxl/cxlmbox.h   | 146 ++++++++++++++++++++++
> > >  drivers/cxl/cxlmem.h    | 148 ++--------------------
> > >  drivers/cxl/pci.c       | 265 +---------------------------------------
> > >  4 files changed, 418 insertions(+), 402 deletions(-)  
> >
> > Given that the CXL mailbox format is being adopted by other standards
> > efforts in PCIe and OCP I would expect that this functionality is better
> > served moving out of cxl_core.ko into its own compilation unit.
> >
> > Something like drivers/cxl/cci/, that CXL can select. The s/mbox/cci/
> > rename is a proposal to both drop a letter out of the acronym and
> > perhaps make the code a bit more discoverable / palatable for folks
> > coming from those non-CXL specs to find the Linux code. However, if
> > folks think that's too much thrash I am ok with drivers/cxl/mbox/.
> >
> > As for the policy for raw commands. I would still like for there to be
> > some discipline of registratants to this facility to classify production
> > vs debug commands to give Linux distributors a single place to set policy.
> > I.e. keep up the need for consumers of this to define "Linux" commands
> > to avoid the "raw" warning.
> >
> > That said, how much of what switch CCI wants to do should be an ioctl()
> > ABI vs sysfs / configfs? The raw commands are really only there for
> > prototyping until production flows are established.
> >  
>
Jonathan Cameron Nov. 28, 2023, 5:42 p.m. UTC | #4
On Sun, 12 Nov 2023 16:09:35 -0800
Dan Williams <dan.j.williams@intel.com> wrote:

> Jonathan Cameron wrote:
> > A later patch will modify this code to separate out the mbox
> > functionality from the memdev.  This patch is intended to make
> > that a little more readable.  Related move of cxl_err(), cxl_cmd_err()
> > to make them accessible from other parts of the cxl core code.
> > 
> > Reviewed-by: Davidlohr Bueso <dave@stgolabs.net>
> > Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
> > ---
> >  drivers/cxl/core/mbox.c | 261 ++++++++++++++++++++++++++++++++++++++-
> >  drivers/cxl/cxlmbox.h   | 146 ++++++++++++++++++++++
> >  drivers/cxl/cxlmem.h    | 148 ++--------------------
> >  drivers/cxl/pci.c       | 265 +---------------------------------------
> >  4 files changed, 418 insertions(+), 402 deletions(-)  
> 
> Given that the CXL mailbox format is being adopted by other standards
> efforts in PCIe and OCP I would expect that this functionality is better
> served moving out of cxl_core.ko into its own compilation unit.

Makes sense.

> 
> Something like drivers/cxl/cci/, that CXL can select. The s/mbox/cci/
> rename is a proposal to both drop a letter out of the acronym and
> perhaps make the code a bit more discoverable / palatable for folks
> coming from those non-CXL specs to find the Linux code. However, if
> folks think that's too much thrash I am ok with drivers/cxl/mbox/.

Medium term I might see how bad it is to just rip it out of CXL entirely
and push it to drivers/pci/mmpt.c (with a few hooks to deal with
CXL parts). However, right now I think that's a step 2 (as MMPT whilst
published isn't widely implemented yet)

> 
> As for the policy for raw commands. I would still like for there to be
> some discipline of registratants to this facility to classify production
> vs debug commands to give Linux distributors a single place to set policy.
> I.e. keep up the need for consumers of this to define "Linux" commands
> to avoid the "raw" warning.
> 
> That said, how much of what switch CCI wants to do should be an ioctl()
> ABI vs sysfs / configfs? The raw commands are really only there for
> prototyping until production flows are established.

I'd love us to be in the world where the kernel modeled the topology
being controlled nicely even though it would get complex. There is also
a clear argument that this complexity isn't of interest to the kernel
so it doesn't really belong there anyway.
The main issue here is that the 'other' interface I'd expect a userspace tool
to be poking is MCTP.  There the software model is a network port.
So all the smarts pretty much have to be in userspace.

If the assumption is that it's all in userspace, the kernel won't have enough
visibility to do high quality sanity checking + pretty much everything you
set with the FMAPI is destructive, most likely to 'other hosts'.

We could maybe add commands (so no taint) for the non destructive stuff like
querying topology, and maybe even DCD add / remove (not forced) with the
tunneling wrap up pushed down into the kernel. I'll admit I'm doubtful and
think it likely any software stack is going to use raw anyway because they
have it all implemented for the MCTP path.

I expect we'll get requests to drop the taint on the Switch Mailbox CCI
raw command and it's much harder to argue against that it is for type 3
devices.

Jonathan
diff mbox series

Patch

diff --git a/drivers/cxl/core/mbox.c b/drivers/cxl/core/mbox.c
index 4df4f614f490..5f58923a52ef 100644
--- a/drivers/cxl/core/mbox.c
+++ b/drivers/cxl/core/mbox.c
@@ -1,10 +1,12 @@ 
 // SPDX-License-Identifier: GPL-2.0-only
 /* Copyright(c) 2020 Intel Corporation. All rights reserved. */
+#include <linux/io-64-nonatomic-lo-hi.h>
 #include <linux/security.h>
 #include <linux/debugfs.h>
 #include <linux/ktime.h>
 #include <linux/mutex.h>
 #include <asm/unaligned.h>
+#include <cxlmbox.h>
 #include <cxlpci.h>
 #include <cxlmem.h>
 #include <cxl.h>
@@ -12,6 +14,14 @@ 
 #include "core.h"
 #include "trace.h"
 
+
+/* CXL 2.0 - 8.2.8.4 */
+#define CXL_MAILBOX_TIMEOUT_MS (2 * HZ)
+
+#define cxl_doorbell_busy(cxlds)                                                \
+	(readl((cxlds)->regs.mbox + CXLDEV_MBOX_CTRL_OFFSET) &                  \
+	 CXLDEV_MBOX_CTRL_DOORBELL)
+
 static bool cxl_raw_allow_all;
 
 /**
@@ -219,6 +229,253 @@  static const char *cxl_mem_opcode_to_name(u16 opcode)
 	return cxl_command_names[c->info.id].name;
 }
 
+int cxl_pci_mbox_wait_for_doorbell(struct cxl_dev_state *cxlds)
+{
+	const unsigned long start = jiffies;
+	unsigned long end = start;
+
+	while (cxl_doorbell_busy(cxlds)) {
+		end = jiffies;
+
+		if (time_after(end, start + CXL_MAILBOX_TIMEOUT_MS)) {
+			/* Check again in case preempted before timeout test */
+			if (!cxl_doorbell_busy(cxlds))
+				break;
+			return -ETIMEDOUT;
+		}
+		cpu_relax();
+	}
+
+	dev_dbg(cxlds->dev, "Doorbell wait took %dms",
+		jiffies_to_msecs(end) - jiffies_to_msecs(start));
+	return 0;
+}
+EXPORT_SYMBOL_NS_GPL(cxl_pci_mbox_wait_for_doorbell, CXL);
+
+bool cxl_mbox_background_complete(struct cxl_dev_state *cxlds)
+{
+	u64 reg;
+
+	reg = readq(cxlds->regs.mbox + CXLDEV_MBOX_BG_CMD_STATUS_OFFSET);
+	return FIELD_GET(CXLDEV_MBOX_BG_CMD_COMMAND_PCT_MASK, reg) == 100;
+}
+EXPORT_SYMBOL_NS_GPL(cxl_mbox_background_complete, CXL);
+
+/**
+ * __cxl_pci_mbox_send_cmd() - Execute a mailbox command
+ * @mds: The memory device driver data
+ * @mbox_cmd: Command to send to the memory device.
+ *
+ * Context: Any context. Expects mbox_mutex to be held.
+ * Return: -ETIMEDOUT if timeout occurred waiting for completion. 0 on success.
+ *         Caller should check the return code in @mbox_cmd to make sure it
+ *         succeeded.
+ *
+ * This is a generic form of the CXL mailbox send command thus only using the
+ * registers defined by the mailbox capability ID - CXL 2.0 8.2.8.4. Memory
+ * devices, and perhaps other types of CXL devices may have further information
+ * available upon error conditions. Driver facilities wishing to send mailbox
+ * commands should use the wrapper command.
+ *
+ * The CXL spec allows for up to two mailboxes. The intention is for the primary
+ * mailbox to be OS controlled and the secondary mailbox to be used by system
+ * firmware. This allows the OS and firmware to communicate with the device and
+ * not need to coordinate with each other. The driver only uses the primary
+ * mailbox.
+ */
+static int __cxl_pci_mbox_send_cmd(struct cxl_memdev_state *mds,
+				   struct cxl_mbox_cmd *mbox_cmd)
+{
+	struct cxl_dev_state *cxlds = &mds->cxlds;
+	void __iomem *payload = cxlds->regs.mbox + CXLDEV_MBOX_PAYLOAD_OFFSET;
+	struct device *dev = cxlds->dev;
+	u64 cmd_reg, status_reg;
+	size_t out_len;
+	int rc;
+
+	lockdep_assert_held(&mds->mbox_mutex);
+
+	/*
+	 * Here are the steps from 8.2.8.4 of the CXL 2.0 spec.
+	 *   1. Caller reads MB Control Register to verify doorbell is clear
+	 *   2. Caller writes Command Register
+	 *   3. Caller writes Command Payload Registers if input payload is non-empty
+	 *   4. Caller writes MB Control Register to set doorbell
+	 *   5. Caller either polls for doorbell to be clear or waits for interrupt if configured
+	 *   6. Caller reads MB Status Register to fetch Return code
+	 *   7. If command successful, Caller reads Command Register to get Payload Length
+	 *   8. If output payload is non-empty, host reads Command Payload Registers
+	 *
+	 * Hardware is free to do whatever it wants before the doorbell is rung,
+	 * and isn't allowed to change anything after it clears the doorbell. As
+	 * such, steps 2 and 3 can happen in any order, and steps 6, 7, 8 can
+	 * also happen in any order (though some orders might not make sense).
+	 */
+
+	/* #1 */
+	if (cxl_doorbell_busy(cxlds)) {
+		u64 md_status =
+			readq(cxlds->regs.memdev + CXLMDEV_STATUS_OFFSET);
+
+		cxl_cmd_err(cxlds->dev, mbox_cmd, md_status,
+			    "mailbox queue busy");
+		return -EBUSY;
+	}
+
+	/*
+	 * With sanitize polling, hardware might be done and the poller still
+	 * not be in sync. Ensure no new command comes in until so. Keep the
+	 * hardware semantics and only allow device health status.
+	 */
+	if (mds->security.poll_tmo_secs > 0) {
+		if (mbox_cmd->opcode != CXL_MBOX_OP_GET_HEALTH_INFO)
+			return -EBUSY;
+	}
+
+	cmd_reg = FIELD_PREP(CXLDEV_MBOX_CMD_COMMAND_OPCODE_MASK,
+			     mbox_cmd->opcode);
+	if (mbox_cmd->size_in) {
+		if (WARN_ON(!mbox_cmd->payload_in))
+			return -EINVAL;
+
+		cmd_reg |= FIELD_PREP(CXLDEV_MBOX_CMD_PAYLOAD_LENGTH_MASK,
+				      mbox_cmd->size_in);
+		memcpy_toio(payload, mbox_cmd->payload_in, mbox_cmd->size_in);
+	}
+
+	/* #2, #3 */
+	writeq(cmd_reg, cxlds->regs.mbox + CXLDEV_MBOX_CMD_OFFSET);
+
+	/* #4 */
+	dev_dbg(dev, "Sending command: 0x%04x\n", mbox_cmd->opcode);
+	writel(CXLDEV_MBOX_CTRL_DOORBELL,
+	       cxlds->regs.mbox + CXLDEV_MBOX_CTRL_OFFSET);
+
+	/* #5 */
+	rc = cxl_pci_mbox_wait_for_doorbell(cxlds);
+	if (rc == -ETIMEDOUT) {
+		u64 md_status = readq(cxlds->regs.memdev + CXLMDEV_STATUS_OFFSET);
+
+		cxl_cmd_err(cxlds->dev, mbox_cmd, md_status, "mailbox timeout");
+		return rc;
+	}
+
+	/* #6 */
+	status_reg = readq(cxlds->regs.mbox + CXLDEV_MBOX_STATUS_OFFSET);
+	mbox_cmd->return_code =
+		FIELD_GET(CXLDEV_MBOX_STATUS_RET_CODE_MASK, status_reg);
+
+	/*
+	 * Handle the background command in a synchronous manner.
+	 *
+	 * All other mailbox commands will serialize/queue on the mbox_mutex,
+	 * which we currently hold. Furthermore this also guarantees that
+	 * cxl_mbox_background_complete() checks are safe amongst each other,
+	 * in that no new bg operation can occur in between.
+	 *
+	 * Background operations are timesliced in accordance with the nature
+	 * of the command. In the event of timeout, the mailbox state is
+	 * indeterminate until the next successful command submission and the
+	 * driver can get back in sync with the hardware state.
+	 */
+	if (mbox_cmd->return_code == CXL_MBOX_CMD_RC_BACKGROUND) {
+		u64 bg_status_reg;
+		int i, timeout;
+
+		/*
+		 * Sanitization is a special case which monopolizes the device
+		 * and cannot be timesliced. Handle asynchronously instead,
+		 * and allow userspace to poll(2) for completion.
+		 */
+		if (mbox_cmd->opcode == CXL_MBOX_OP_SANITIZE) {
+			if (mds->security.poll) {
+				/* hold the device throughout */
+				get_device(cxlds->dev);
+
+				/* give first timeout a second */
+				timeout = 1;
+				mds->security.poll_tmo_secs = timeout;
+				queue_delayed_work(system_wq,
+						   &mds->security.poll_dwork,
+						   timeout * HZ);
+			}
+
+			dev_dbg(dev, "Sanitization operation started\n");
+			goto success;
+		}
+
+		dev_dbg(dev, "Mailbox background operation (0x%04x) started\n",
+			mbox_cmd->opcode);
+
+		timeout = mbox_cmd->poll_interval_ms;
+		for (i = 0; i < mbox_cmd->poll_count; i++) {
+			if (rcuwait_wait_event_timeout(&mds->mbox_wait,
+				       cxl_mbox_background_complete(cxlds),
+				       TASK_UNINTERRUPTIBLE,
+				       msecs_to_jiffies(timeout)) > 0)
+				break;
+		}
+
+		if (!cxl_mbox_background_complete(cxlds)) {
+			dev_err(dev, "timeout waiting for background (%d ms)\n",
+				timeout * mbox_cmd->poll_count);
+			return -ETIMEDOUT;
+		}
+
+		bg_status_reg = readq(cxlds->regs.mbox +
+				      CXLDEV_MBOX_BG_CMD_STATUS_OFFSET);
+		mbox_cmd->return_code =
+			FIELD_GET(CXLDEV_MBOX_BG_CMD_COMMAND_RC_MASK,
+				  bg_status_reg);
+		dev_dbg(dev,
+			"Mailbox background operation (0x%04x) completed\n",
+			mbox_cmd->opcode);
+	}
+
+	if (mbox_cmd->return_code != CXL_MBOX_CMD_RC_SUCCESS) {
+		dev_dbg(dev, "Mailbox operation had an error: %s\n",
+			cxl_mbox_cmd_rc2str(mbox_cmd));
+		return 0; /* completed but caller must check return_code */
+	}
+
+success:
+	/* #7 */
+	cmd_reg = readq(cxlds->regs.mbox + CXLDEV_MBOX_CMD_OFFSET);
+	out_len = FIELD_GET(CXLDEV_MBOX_CMD_PAYLOAD_LENGTH_MASK, cmd_reg);
+
+	/* #8 */
+	if (out_len && mbox_cmd->payload_out) {
+		/*
+		 * Sanitize the copy. If hardware misbehaves, out_len per the
+		 * spec can actually be greater than the max allowed size (21
+		 * bits available but spec defined 1M max). The caller also may
+		 * have requested less data than the hardware supplied even
+		 * within spec.
+		 */
+		size_t n;
+
+		n = min3(mbox_cmd->size_out, mds->payload_size, out_len);
+		memcpy_fromio(mbox_cmd->payload_out, payload, n);
+		mbox_cmd->size_out = n;
+	} else {
+		mbox_cmd->size_out = 0;
+	}
+
+	return 0;
+}
+
+static int cxl_pci_mbox_send(struct cxl_memdev_state *mds,
+			     struct cxl_mbox_cmd *cmd)
+{
+	int rc;
+
+	mutex_lock_io(&mds->mbox_mutex);
+	rc = __cxl_pci_mbox_send_cmd(mds, cmd);
+	mutex_unlock(&mds->mbox_mutex);
+
+	return rc;
+}
+
 /**
  * cxl_internal_send_cmd() - Kernel internal interface to send a mailbox command
  * @mds: The driver data for the operation
@@ -249,7 +506,7 @@  int cxl_internal_send_cmd(struct cxl_memdev_state *mds,
 
 	out_size = mbox_cmd->size_out;
 	min_out = mbox_cmd->min_out;
-	rc = mds->mbox_send(mds, mbox_cmd);
+	rc = cxl_pci_mbox_send(mds, mbox_cmd);
 	/*
 	 * EIO is reserved for a payload size mismatch and mbox_send()
 	 * may not return this error.
@@ -588,7 +845,7 @@  static int handle_mailbox_cmd_from_user(struct cxl_memdev_state *mds,
 		cxl_mem_opcode_to_name(mbox_cmd->opcode),
 		mbox_cmd->opcode, mbox_cmd->size_in);
 
-	rc = mds->mbox_send(mds, mbox_cmd);
+	rc = cxl_pci_mbox_send(mds, mbox_cmd);
 	if (rc)
 		goto out;
 
diff --git a/drivers/cxl/cxlmbox.h b/drivers/cxl/cxlmbox.h
new file mode 100644
index 000000000000..2b83b870a5e6
--- /dev/null
+++ b/drivers/cxl/cxlmbox.h
@@ -0,0 +1,146 @@ 
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+#ifndef __CXLMBOX_H__
+#define __CXLMBOX_H__
+
+struct cxl_dev_state;
+int cxl_pci_mbox_wait_for_doorbell(struct cxl_dev_state *cxlds);
+bool cxl_mbox_background_complete(struct cxl_dev_state *cxlds);
+
+/**
+ * struct cxl_mbox_cmd - A command to be submitted to hardware.
+ * @opcode: (input) The command set and command submitted to hardware.
+ * @payload_in: (input) Pointer to the input payload.
+ * @payload_out: (output) Pointer to the output payload. Must be allocated by
+ *		 the caller.
+ * @size_in: (input) Number of bytes to load from @payload_in.
+ * @size_out: (input) Max number of bytes loaded into @payload_out.
+ *            (output) Number of bytes generated by the device. For fixed size
+ *            outputs commands this is always expected to be deterministic. For
+ *            variable sized output commands, it tells the exact number of bytes
+ *            written.
+ * @min_out: (input) internal command output payload size validation
+ * @poll_count: (input) Number of timeouts to attempt.
+ * @poll_interval_ms: (input) Time between mailbox background command polling
+ *                    interval timeouts.
+ * @return_code: (output) Error code returned from hardware.
+ *
+ * This is the primary mechanism used to send commands to the hardware.
+ * All the fields except @payload_* correspond exactly to the fields described in
+ * Command Register section of the CXL 2.0 8.2.8.4.5. @payload_in and
+ * @payload_out are written to, and read from the Command Payload Registers
+ * defined in CXL 2.0 8.2.8.4.8.
+ */
+struct cxl_mbox_cmd {
+	u16 opcode;
+	void *payload_in;
+	void *payload_out;
+	size_t size_in;
+	size_t size_out;
+	size_t min_out;
+	int poll_count;
+	int poll_interval_ms;
+	u16 return_code;
+};
+
+/*
+ * Per CXL 3.0 Section 8.2.8.4.5.1
+ */
+#define CMD_CMD_RC_TABLE							\
+	C(SUCCESS, 0, NULL),							\
+	C(BACKGROUND, -ENXIO, "background cmd started successfully"),           \
+	C(INPUT, -ENXIO, "cmd input was invalid"),				\
+	C(UNSUPPORTED, -ENXIO, "cmd is not supported"),				\
+	C(INTERNAL, -ENXIO, "internal device error"),				\
+	C(RETRY, -ENXIO, "temporary error, retry once"),			\
+	C(BUSY, -ENXIO, "ongoing background operation"),			\
+	C(MEDIADISABLED, -ENXIO, "media access is disabled"),			\
+	C(FWINPROGRESS, -ENXIO,	"one FW package can be transferred at a time"), \
+	C(FWOOO, -ENXIO, "FW package content was transferred out of order"),    \
+	C(FWAUTH, -ENXIO, "FW package authentication failed"),			\
+	C(FWSLOT, -ENXIO, "FW slot is not supported for requested operation"),  \
+	C(FWROLLBACK, -ENXIO, "rolled back to the previous active FW"),         \
+	C(FWRESET, -ENXIO, "FW failed to activate, needs cold reset"),		\
+	C(HANDLE, -ENXIO, "one or more Event Record Handles were invalid"),     \
+	C(PADDR, -EFAULT, "physical address specified is invalid"),		\
+	C(POISONLMT, -ENXIO, "poison injection limit has been reached"),        \
+	C(MEDIAFAILURE, -ENXIO, "permanent issue with the media"),		\
+	C(ABORT, -ENXIO, "background cmd was aborted by device"),               \
+	C(SECURITY, -ENXIO, "not valid in the current security state"),         \
+	C(PASSPHRASE, -ENXIO, "phrase doesn't match current set passphrase"),   \
+	C(MBUNSUPPORTED, -ENXIO, "unsupported on the mailbox it was issued on"),\
+	C(PAYLOADLEN, -ENXIO, "invalid payload length"),			\
+	C(LOG, -ENXIO, "invalid or unsupported log page"),			\
+	C(INTERRUPTED, -ENXIO, "asynchronous event occurred"),			\
+	C(FEATUREVERSION, -ENXIO, "unsupported feature version"),		\
+	C(FEATURESELVALUE, -ENXIO, "unsupported feature selection value"),	\
+	C(FEATURETRANSFERIP, -ENXIO, "feature transfer in progress"),		\
+	C(FEATURETRANSFEROOO, -ENXIO, "feature transfer out of order"),		\
+	C(RESOURCEEXHAUSTED, -ENXIO, "resources are exhausted"),		\
+	C(EXTLIST, -ENXIO, "invalid Extent List"),				\
+
+#undef C
+#define C(a, b, c) CXL_MBOX_CMD_RC_##a
+enum  { CMD_CMD_RC_TABLE };
+#undef C
+#define C(a, b, c) { b, c }
+struct cxl_mbox_cmd_rc {
+	int err;
+	const char *desc;
+};
+
+static const
+struct cxl_mbox_cmd_rc cxl_mbox_cmd_rctable[] = { CMD_CMD_RC_TABLE };
+#undef C
+
+static inline const char *cxl_mbox_cmd_rc2str(struct cxl_mbox_cmd *mbox_cmd)
+{
+	return cxl_mbox_cmd_rctable[mbox_cmd->return_code].desc;
+}
+
+static inline int cxl_mbox_cmd_rc2errno(struct cxl_mbox_cmd *mbox_cmd)
+{
+	return cxl_mbox_cmd_rctable[mbox_cmd->return_code].err;
+}
+
+enum cxl_opcode {
+	CXL_MBOX_OP_INVALID		= 0x0000,
+	CXL_MBOX_OP_RAW			= CXL_MBOX_OP_INVALID,
+	CXL_MBOX_OP_GET_EVENT_RECORD	= 0x0100,
+	CXL_MBOX_OP_CLEAR_EVENT_RECORD	= 0x0101,
+	CXL_MBOX_OP_GET_EVT_INT_POLICY	= 0x0102,
+	CXL_MBOX_OP_SET_EVT_INT_POLICY	= 0x0103,
+	CXL_MBOX_OP_GET_FW_INFO		= 0x0200,
+	CXL_MBOX_OP_TRANSFER_FW		= 0x0201,
+	CXL_MBOX_OP_ACTIVATE_FW		= 0x0202,
+	CXL_MBOX_OP_SET_TIMESTAMP	= 0x0301,
+	CXL_MBOX_OP_GET_SUPPORTED_LOGS	= 0x0400,
+	CXL_MBOX_OP_GET_LOG		= 0x0401,
+	CXL_MBOX_OP_IDENTIFY		= 0x4000,
+	CXL_MBOX_OP_GET_PARTITION_INFO	= 0x4100,
+	CXL_MBOX_OP_SET_PARTITION_INFO	= 0x4101,
+	CXL_MBOX_OP_GET_LSA		= 0x4102,
+	CXL_MBOX_OP_SET_LSA		= 0x4103,
+	CXL_MBOX_OP_GET_HEALTH_INFO	= 0x4200,
+	CXL_MBOX_OP_GET_ALERT_CONFIG	= 0x4201,
+	CXL_MBOX_OP_SET_ALERT_CONFIG	= 0x4202,
+	CXL_MBOX_OP_GET_SHUTDOWN_STATE	= 0x4203,
+	CXL_MBOX_OP_SET_SHUTDOWN_STATE	= 0x4204,
+	CXL_MBOX_OP_GET_POISON		= 0x4300,
+	CXL_MBOX_OP_INJECT_POISON	= 0x4301,
+	CXL_MBOX_OP_CLEAR_POISON	= 0x4302,
+	CXL_MBOX_OP_GET_SCAN_MEDIA_CAPS	= 0x4303,
+	CXL_MBOX_OP_SCAN_MEDIA		= 0x4304,
+	CXL_MBOX_OP_GET_SCAN_MEDIA	= 0x4305,
+	CXL_MBOX_OP_SANITIZE		= 0x4400,
+	CXL_MBOX_OP_SECURE_ERASE	= 0x4401,
+	CXL_MBOX_OP_GET_SECURITY_STATE	= 0x4500,
+	CXL_MBOX_OP_SET_PASSPHRASE	= 0x4501,
+	CXL_MBOX_OP_DISABLE_PASSPHRASE	= 0x4502,
+	CXL_MBOX_OP_UNLOCK		= 0x4503,
+	CXL_MBOX_OP_FREEZE_SECURITY	= 0x4504,
+	CXL_MBOX_OP_PASSPHRASE_SECURE_ERASE	= 0x4505,
+	CXL_MBOX_OP_MAX			= 0x10000
+};
+
+#endif /* __CXLMBOX_H__ */
diff --git a/drivers/cxl/cxlmem.h b/drivers/cxl/cxlmem.h
index 706f8a6d1ef4..2b4f4c932501 100644
--- a/drivers/cxl/cxlmem.h
+++ b/drivers/cxl/cxlmem.h
@@ -6,6 +6,7 @@ 
 #include <linux/cdev.h>
 #include <linux/uuid.h>
 #include <linux/rcuwait.h>
+#include "cxlmbox.h"
 #include "cxl.h"
 
 /* CXL 2.0 8.2.8.5.1.1 Memory Device Status Register */
@@ -31,6 +32,17 @@ 
 	(FIELD_GET(CXLMDEV_RESET_NEEDED_MASK, status) !=                       \
 	 CXLMDEV_RESET_NEEDED_NOT)
 
+#define cxl_err(dev, status, msg)                                        \
+	dev_err_ratelimited(dev, msg ", device state %s%s\n",                  \
+			    status & CXLMDEV_DEV_FATAL ? " fatal" : "",        \
+			    status & CXLMDEV_FW_HALT ? " firmware-halt" : "")
+
+#define cxl_cmd_err(dev, cmd, status, msg)                               \
+	dev_err_ratelimited(dev, msg " (opcode: %#x), device state %s%s\n",    \
+			    (cmd)->opcode,                                     \
+			    status & CXLMDEV_DEV_FATAL ? " fatal" : "",        \
+			    status & CXLMDEV_FW_HALT ? " firmware-halt" : "")
+
 /**
  * struct cxl_memdev - CXL bus object representing a Type-3 Memory Device
  * @dev: driver core device object
@@ -100,102 +112,6 @@  static inline struct cxl_ep *cxl_ep_load(struct cxl_port *port,
 	return xa_load(&port->endpoints, (unsigned long)&cxlmd->dev);
 }
 
-/**
- * struct cxl_mbox_cmd - A command to be submitted to hardware.
- * @opcode: (input) The command set and command submitted to hardware.
- * @payload_in: (input) Pointer to the input payload.
- * @payload_out: (output) Pointer to the output payload. Must be allocated by
- *		 the caller.
- * @size_in: (input) Number of bytes to load from @payload_in.
- * @size_out: (input) Max number of bytes loaded into @payload_out.
- *            (output) Number of bytes generated by the device. For fixed size
- *            outputs commands this is always expected to be deterministic. For
- *            variable sized output commands, it tells the exact number of bytes
- *            written.
- * @min_out: (input) internal command output payload size validation
- * @poll_count: (input) Number of timeouts to attempt.
- * @poll_interval_ms: (input) Time between mailbox background command polling
- *                    interval timeouts.
- * @return_code: (output) Error code returned from hardware.
- *
- * This is the primary mechanism used to send commands to the hardware.
- * All the fields except @payload_* correspond exactly to the fields described in
- * Command Register section of the CXL 2.0 8.2.8.4.5. @payload_in and
- * @payload_out are written to, and read from the Command Payload Registers
- * defined in CXL 2.0 8.2.8.4.8.
- */
-struct cxl_mbox_cmd {
-	u16 opcode;
-	void *payload_in;
-	void *payload_out;
-	size_t size_in;
-	size_t size_out;
-	size_t min_out;
-	int poll_count;
-	int poll_interval_ms;
-	u16 return_code;
-};
-
-/*
- * Per CXL 3.0 Section 8.2.8.4.5.1
- */
-#define CMD_CMD_RC_TABLE							\
-	C(SUCCESS, 0, NULL),							\
-	C(BACKGROUND, -ENXIO, "background cmd started successfully"),           \
-	C(INPUT, -ENXIO, "cmd input was invalid"),				\
-	C(UNSUPPORTED, -ENXIO, "cmd is not supported"),				\
-	C(INTERNAL, -ENXIO, "internal device error"),				\
-	C(RETRY, -ENXIO, "temporary error, retry once"),			\
-	C(BUSY, -ENXIO, "ongoing background operation"),			\
-	C(MEDIADISABLED, -ENXIO, "media access is disabled"),			\
-	C(FWINPROGRESS, -ENXIO,	"one FW package can be transferred at a time"), \
-	C(FWOOO, -ENXIO, "FW package content was transferred out of order"),    \
-	C(FWAUTH, -ENXIO, "FW package authentication failed"),			\
-	C(FWSLOT, -ENXIO, "FW slot is not supported for requested operation"),  \
-	C(FWROLLBACK, -ENXIO, "rolled back to the previous active FW"),         \
-	C(FWRESET, -ENXIO, "FW failed to activate, needs cold reset"),		\
-	C(HANDLE, -ENXIO, "one or more Event Record Handles were invalid"),     \
-	C(PADDR, -EFAULT, "physical address specified is invalid"),		\
-	C(POISONLMT, -ENXIO, "poison injection limit has been reached"),        \
-	C(MEDIAFAILURE, -ENXIO, "permanent issue with the media"),		\
-	C(ABORT, -ENXIO, "background cmd was aborted by device"),               \
-	C(SECURITY, -ENXIO, "not valid in the current security state"),         \
-	C(PASSPHRASE, -ENXIO, "phrase doesn't match current set passphrase"),   \
-	C(MBUNSUPPORTED, -ENXIO, "unsupported on the mailbox it was issued on"),\
-	C(PAYLOADLEN, -ENXIO, "invalid payload length"),			\
-	C(LOG, -ENXIO, "invalid or unsupported log page"),			\
-	C(INTERRUPTED, -ENXIO, "asynchronous event occured"),			\
-	C(FEATUREVERSION, -ENXIO, "unsupported feature version"),		\
-	C(FEATURESELVALUE, -ENXIO, "unsupported feature selection value"),	\
-	C(FEATURETRANSFERIP, -ENXIO, "feature transfer in progress"),		\
-	C(FEATURETRANSFEROOO, -ENXIO, "feature transfer out of order"),		\
-	C(RESOURCEEXHAUSTED, -ENXIO, "resources are exhausted"),		\
-	C(EXTLIST, -ENXIO, "invalid Extent List"),				\
-
-#undef C
-#define C(a, b, c) CXL_MBOX_CMD_RC_##a
-enum  { CMD_CMD_RC_TABLE };
-#undef C
-#define C(a, b, c) { b, c }
-struct cxl_mbox_cmd_rc {
-	int err;
-	const char *desc;
-};
-
-static const
-struct cxl_mbox_cmd_rc cxl_mbox_cmd_rctable[] ={ CMD_CMD_RC_TABLE };
-#undef C
-
-static inline const char *cxl_mbox_cmd_rc2str(struct cxl_mbox_cmd *mbox_cmd)
-{
-	return cxl_mbox_cmd_rctable[mbox_cmd->return_code].desc;
-}
-
-static inline int cxl_mbox_cmd_rc2errno(struct cxl_mbox_cmd *mbox_cmd)
-{
-	return cxl_mbox_cmd_rctable[mbox_cmd->return_code].err;
-}
-
 /*
  * CXL 2.0 - Memory capacity multiplier
  * See Section 8.2.9.5
@@ -490,46 +406,6 @@  to_cxl_memdev_state(struct cxl_dev_state *cxlds)
 	return container_of(cxlds, struct cxl_memdev_state, cxlds);
 }
 
-enum cxl_opcode {
-	CXL_MBOX_OP_INVALID		= 0x0000,
-	CXL_MBOX_OP_RAW			= CXL_MBOX_OP_INVALID,
-	CXL_MBOX_OP_GET_EVENT_RECORD	= 0x0100,
-	CXL_MBOX_OP_CLEAR_EVENT_RECORD	= 0x0101,
-	CXL_MBOX_OP_GET_EVT_INT_POLICY	= 0x0102,
-	CXL_MBOX_OP_SET_EVT_INT_POLICY	= 0x0103,
-	CXL_MBOX_OP_GET_FW_INFO		= 0x0200,
-	CXL_MBOX_OP_TRANSFER_FW		= 0x0201,
-	CXL_MBOX_OP_ACTIVATE_FW		= 0x0202,
-	CXL_MBOX_OP_SET_TIMESTAMP	= 0x0301,
-	CXL_MBOX_OP_GET_SUPPORTED_LOGS	= 0x0400,
-	CXL_MBOX_OP_GET_LOG		= 0x0401,
-	CXL_MBOX_OP_IDENTIFY		= 0x4000,
-	CXL_MBOX_OP_GET_PARTITION_INFO	= 0x4100,
-	CXL_MBOX_OP_SET_PARTITION_INFO	= 0x4101,
-	CXL_MBOX_OP_GET_LSA		= 0x4102,
-	CXL_MBOX_OP_SET_LSA		= 0x4103,
-	CXL_MBOX_OP_GET_HEALTH_INFO	= 0x4200,
-	CXL_MBOX_OP_GET_ALERT_CONFIG	= 0x4201,
-	CXL_MBOX_OP_SET_ALERT_CONFIG	= 0x4202,
-	CXL_MBOX_OP_GET_SHUTDOWN_STATE	= 0x4203,
-	CXL_MBOX_OP_SET_SHUTDOWN_STATE	= 0x4204,
-	CXL_MBOX_OP_GET_POISON		= 0x4300,
-	CXL_MBOX_OP_INJECT_POISON	= 0x4301,
-	CXL_MBOX_OP_CLEAR_POISON	= 0x4302,
-	CXL_MBOX_OP_GET_SCAN_MEDIA_CAPS	= 0x4303,
-	CXL_MBOX_OP_SCAN_MEDIA		= 0x4304,
-	CXL_MBOX_OP_GET_SCAN_MEDIA	= 0x4305,
-	CXL_MBOX_OP_SANITIZE		= 0x4400,
-	CXL_MBOX_OP_SECURE_ERASE	= 0x4401,
-	CXL_MBOX_OP_GET_SECURITY_STATE	= 0x4500,
-	CXL_MBOX_OP_SET_PASSPHRASE	= 0x4501,
-	CXL_MBOX_OP_DISABLE_PASSPHRASE	= 0x4502,
-	CXL_MBOX_OP_UNLOCK		= 0x4503,
-	CXL_MBOX_OP_FREEZE_SECURITY	= 0x4504,
-	CXL_MBOX_OP_PASSPHRASE_SECURE_ERASE	= 0x4505,
-	CXL_MBOX_OP_MAX			= 0x10000
-};
-
 #define DEFINE_CXL_CEL_UUID                                                    \
 	UUID_INIT(0xda9c0b5, 0xbf41, 0x4b78, 0x8f, 0x79, 0x96, 0xb1, 0x62,     \
 		  0x3b, 0x3f, 0x17)
diff --git a/drivers/cxl/pci.c b/drivers/cxl/pci.c
index 44a21ab7add5..833508e01bfe 100644
--- a/drivers/cxl/pci.c
+++ b/drivers/cxl/pci.c
@@ -10,6 +10,7 @@ 
 #include <linux/pci.h>
 #include <linux/aer.h>
 #include <linux/io.h>
+#include "cxlmbox.h"
 #include "cxlmem.h"
 #include "cxlpci.h"
 #include "cxl.h"
@@ -32,13 +33,6 @@ 
  *  - Registers a CXL mailbox with cxl_core.
  */
 
-#define cxl_doorbell_busy(cxlds)                                                \
-	(readl((cxlds)->regs.mbox + CXLDEV_MBOX_CTRL_OFFSET) &                  \
-	 CXLDEV_MBOX_CTRL_DOORBELL)
-
-/* CXL 2.0 - 8.2.8.4 */
-#define CXL_MAILBOX_TIMEOUT_MS (2 * HZ)
-
 /*
  * CXL 2.0 ECN "Add Mailbox Ready Time" defines a capability field to
  * dictate how long to wait for the mailbox to become ready. The new
@@ -52,39 +46,6 @@  static unsigned short mbox_ready_timeout = 60;
 module_param(mbox_ready_timeout, ushort, 0644);
 MODULE_PARM_DESC(mbox_ready_timeout, "seconds to wait for mailbox ready");
 
-static int cxl_pci_mbox_wait_for_doorbell(struct cxl_dev_state *cxlds)
-{
-	const unsigned long start = jiffies;
-	unsigned long end = start;
-
-	while (cxl_doorbell_busy(cxlds)) {
-		end = jiffies;
-
-		if (time_after(end, start + CXL_MAILBOX_TIMEOUT_MS)) {
-			/* Check again in case preempted before timeout test */
-			if (!cxl_doorbell_busy(cxlds))
-				break;
-			return -ETIMEDOUT;
-		}
-		cpu_relax();
-	}
-
-	dev_dbg(cxlds->dev, "Doorbell wait took %dms",
-		jiffies_to_msecs(end) - jiffies_to_msecs(start));
-	return 0;
-}
-
-#define cxl_err(dev, status, msg)                                        \
-	dev_err_ratelimited(dev, msg ", device state %s%s\n",                  \
-			    status & CXLMDEV_DEV_FATAL ? " fatal" : "",        \
-			    status & CXLMDEV_FW_HALT ? " firmware-halt" : "")
-
-#define cxl_cmd_err(dev, cmd, status, msg)                               \
-	dev_err_ratelimited(dev, msg " (opcode: %#x), device state %s%s\n",    \
-			    (cmd)->opcode,                                     \
-			    status & CXLMDEV_DEV_FATAL ? " fatal" : "",        \
-			    status & CXLMDEV_FW_HALT ? " firmware-halt" : "")
-
 struct cxl_dev_id {
 	struct cxl_dev_state *cxlds;
 };
@@ -106,14 +67,6 @@  static int cxl_request_irq(struct cxl_dev_state *cxlds, int irq,
 					 NULL, dev_id);
 }
 
-static bool cxl_mbox_background_complete(struct cxl_dev_state *cxlds)
-{
-	u64 reg;
-
-	reg = readq(cxlds->regs.mbox + CXLDEV_MBOX_BG_CMD_STATUS_OFFSET);
-	return FIELD_GET(CXLDEV_MBOX_BG_CMD_COMMAND_PCT_MASK, reg) == 100;
-}
-
 static irqreturn_t cxl_pci_mbox_irq(int irq, void *id)
 {
 	u64 reg;
@@ -168,221 +121,6 @@  static void cxl_mbox_sanitize_work(struct work_struct *work)
 	mutex_unlock(&mds->mbox_mutex);
 }
 
-/**
- * __cxl_pci_mbox_send_cmd() - Execute a mailbox command
- * @mds: The memory device driver data
- * @mbox_cmd: Command to send to the memory device.
- *
- * Context: Any context. Expects mbox_mutex to be held.
- * Return: -ETIMEDOUT if timeout occurred waiting for completion. 0 on success.
- *         Caller should check the return code in @mbox_cmd to make sure it
- *         succeeded.
- *
- * This is a generic form of the CXL mailbox send command thus only using the
- * registers defined by the mailbox capability ID - CXL 2.0 8.2.8.4. Memory
- * devices, and perhaps other types of CXL devices may have further information
- * available upon error conditions. Driver facilities wishing to send mailbox
- * commands should use the wrapper command.
- *
- * The CXL spec allows for up to two mailboxes. The intention is for the primary
- * mailbox to be OS controlled and the secondary mailbox to be used by system
- * firmware. This allows the OS and firmware to communicate with the device and
- * not need to coordinate with each other. The driver only uses the primary
- * mailbox.
- */
-static int __cxl_pci_mbox_send_cmd(struct cxl_memdev_state *mds,
-				   struct cxl_mbox_cmd *mbox_cmd)
-{
-	struct cxl_dev_state *cxlds = &mds->cxlds;
-	void __iomem *payload = cxlds->regs.mbox + CXLDEV_MBOX_PAYLOAD_OFFSET;
-	struct device *dev = cxlds->dev;
-	u64 cmd_reg, status_reg;
-	size_t out_len;
-	int rc;
-
-	lockdep_assert_held(&mds->mbox_mutex);
-
-	/*
-	 * Here are the steps from 8.2.8.4 of the CXL 2.0 spec.
-	 *   1. Caller reads MB Control Register to verify doorbell is clear
-	 *   2. Caller writes Command Register
-	 *   3. Caller writes Command Payload Registers if input payload is non-empty
-	 *   4. Caller writes MB Control Register to set doorbell
-	 *   5. Caller either polls for doorbell to be clear or waits for interrupt if configured
-	 *   6. Caller reads MB Status Register to fetch Return code
-	 *   7. If command successful, Caller reads Command Register to get Payload Length
-	 *   8. If output payload is non-empty, host reads Command Payload Registers
-	 *
-	 * Hardware is free to do whatever it wants before the doorbell is rung,
-	 * and isn't allowed to change anything after it clears the doorbell. As
-	 * such, steps 2 and 3 can happen in any order, and steps 6, 7, 8 can
-	 * also happen in any order (though some orders might not make sense).
-	 */
-
-	/* #1 */
-	if (cxl_doorbell_busy(cxlds)) {
-		u64 md_status =
-			readq(cxlds->regs.memdev + CXLMDEV_STATUS_OFFSET);
-
-		cxl_cmd_err(cxlds->dev, mbox_cmd, md_status,
-			    "mailbox queue busy");
-		return -EBUSY;
-	}
-
-	/*
-	 * With sanitize polling, hardware might be done and the poller still
-	 * not be in sync. Ensure no new command comes in until so. Keep the
-	 * hardware semantics and only allow device health status.
-	 */
-	if (mds->security.poll_tmo_secs > 0) {
-		if (mbox_cmd->opcode != CXL_MBOX_OP_GET_HEALTH_INFO)
-			return -EBUSY;
-	}
-
-	cmd_reg = FIELD_PREP(CXLDEV_MBOX_CMD_COMMAND_OPCODE_MASK,
-			     mbox_cmd->opcode);
-	if (mbox_cmd->size_in) {
-		if (WARN_ON(!mbox_cmd->payload_in))
-			return -EINVAL;
-
-		cmd_reg |= FIELD_PREP(CXLDEV_MBOX_CMD_PAYLOAD_LENGTH_MASK,
-				      mbox_cmd->size_in);
-		memcpy_toio(payload, mbox_cmd->payload_in, mbox_cmd->size_in);
-	}
-
-	/* #2, #3 */
-	writeq(cmd_reg, cxlds->regs.mbox + CXLDEV_MBOX_CMD_OFFSET);
-
-	/* #4 */
-	dev_dbg(dev, "Sending command: 0x%04x\n", mbox_cmd->opcode);
-	writel(CXLDEV_MBOX_CTRL_DOORBELL,
-	       cxlds->regs.mbox + CXLDEV_MBOX_CTRL_OFFSET);
-
-	/* #5 */
-	rc = cxl_pci_mbox_wait_for_doorbell(cxlds);
-	if (rc == -ETIMEDOUT) {
-		u64 md_status = readq(cxlds->regs.memdev + CXLMDEV_STATUS_OFFSET);
-
-		cxl_cmd_err(cxlds->dev, mbox_cmd, md_status, "mailbox timeout");
-		return rc;
-	}
-
-	/* #6 */
-	status_reg = readq(cxlds->regs.mbox + CXLDEV_MBOX_STATUS_OFFSET);
-	mbox_cmd->return_code =
-		FIELD_GET(CXLDEV_MBOX_STATUS_RET_CODE_MASK, status_reg);
-
-	/*
-	 * Handle the background command in a synchronous manner.
-	 *
-	 * All other mailbox commands will serialize/queue on the mbox_mutex,
-	 * which we currently hold. Furthermore this also guarantees that
-	 * cxl_mbox_background_complete() checks are safe amongst each other,
-	 * in that no new bg operation can occur in between.
-	 *
-	 * Background operations are timesliced in accordance with the nature
-	 * of the command. In the event of timeout, the mailbox state is
-	 * indeterminate until the next successful command submission and the
-	 * driver can get back in sync with the hardware state.
-	 */
-	if (mbox_cmd->return_code == CXL_MBOX_CMD_RC_BACKGROUND) {
-		u64 bg_status_reg;
-		int i, timeout;
-
-		/*
-		 * Sanitization is a special case which monopolizes the device
-		 * and cannot be timesliced. Handle asynchronously instead,
-		 * and allow userspace to poll(2) for completion.
-		 */
-		if (mbox_cmd->opcode == CXL_MBOX_OP_SANITIZE) {
-			if (mds->security.poll) {
-				/* hold the device throughout */
-				get_device(cxlds->dev);
-
-				/* give first timeout a second */
-				timeout = 1;
-				mds->security.poll_tmo_secs = timeout;
-				queue_delayed_work(system_wq,
-						   &mds->security.poll_dwork,
-						   timeout * HZ);
-			}
-
-			dev_dbg(dev, "Sanitization operation started\n");
-			goto success;
-		}
-
-		dev_dbg(dev, "Mailbox background operation (0x%04x) started\n",
-			mbox_cmd->opcode);
-
-		timeout = mbox_cmd->poll_interval_ms;
-		for (i = 0; i < mbox_cmd->poll_count; i++) {
-			if (rcuwait_wait_event_timeout(&mds->mbox_wait,
-				       cxl_mbox_background_complete(cxlds),
-				       TASK_UNINTERRUPTIBLE,
-				       msecs_to_jiffies(timeout)) > 0)
-				break;
-		}
-
-		if (!cxl_mbox_background_complete(cxlds)) {
-			dev_err(dev, "timeout waiting for background (%d ms)\n",
-				timeout * mbox_cmd->poll_count);
-			return -ETIMEDOUT;
-		}
-
-		bg_status_reg = readq(cxlds->regs.mbox +
-				      CXLDEV_MBOX_BG_CMD_STATUS_OFFSET);
-		mbox_cmd->return_code =
-			FIELD_GET(CXLDEV_MBOX_BG_CMD_COMMAND_RC_MASK,
-				  bg_status_reg);
-		dev_dbg(dev,
-			"Mailbox background operation (0x%04x) completed\n",
-			mbox_cmd->opcode);
-	}
-
-	if (mbox_cmd->return_code != CXL_MBOX_CMD_RC_SUCCESS) {
-		dev_dbg(dev, "Mailbox operation had an error: %s\n",
-			cxl_mbox_cmd_rc2str(mbox_cmd));
-		return 0; /* completed but caller must check return_code */
-	}
-
-success:
-	/* #7 */
-	cmd_reg = readq(cxlds->regs.mbox + CXLDEV_MBOX_CMD_OFFSET);
-	out_len = FIELD_GET(CXLDEV_MBOX_CMD_PAYLOAD_LENGTH_MASK, cmd_reg);
-
-	/* #8 */
-	if (out_len && mbox_cmd->payload_out) {
-		/*
-		 * Sanitize the copy. If hardware misbehaves, out_len per the
-		 * spec can actually be greater than the max allowed size (21
-		 * bits available but spec defined 1M max). The caller also may
-		 * have requested less data than the hardware supplied even
-		 * within spec.
-		 */
-		size_t n;
-
-		n = min3(mbox_cmd->size_out, mds->payload_size, out_len);
-		memcpy_fromio(mbox_cmd->payload_out, payload, n);
-		mbox_cmd->size_out = n;
-	} else {
-		mbox_cmd->size_out = 0;
-	}
-
-	return 0;
-}
-
-static int cxl_pci_mbox_send(struct cxl_memdev_state *mds,
-			     struct cxl_mbox_cmd *cmd)
-{
-	int rc;
-
-	mutex_lock_io(&mds->mbox_mutex);
-	rc = __cxl_pci_mbox_send_cmd(mds, cmd);
-	mutex_unlock(&mds->mbox_mutex);
-
-	return rc;
-}
-
 static int cxl_pci_setup_mailbox(struct cxl_memdev_state *mds)
 {
 	struct cxl_dev_state *cxlds = &mds->cxlds;
@@ -416,7 +154,6 @@  static int cxl_pci_setup_mailbox(struct cxl_memdev_state *mds)
 		return -ETIMEDOUT;
 	}
 
-	mds->mbox_send = cxl_pci_mbox_send;
 	mds->payload_size =
 		1 << FIELD_GET(CXLDEV_MBOX_CAP_PAYLOAD_SIZE_MASK, cap);