Re: [PATCH 05/10] mailbox: apple: Add DockChannel FIFO controller

Yureka Lilian <[email protected]>
Newsgroups dev.linux.lists.asahi,dev.linux.lists.iommu,org.infradead.lists.linux-arm-kernel,org.kernel.vger.linux-devicetree,org.kernel.vger.linux-input,org.kernel.vger.linux-kernel
Message-ID <[email protected]>
On 6/30/26 14:54, Michael Reeves via B4 Relay wrote:
> From: Michael Reeves <[email protected]>
>
> DockChannel is a hardware FIFO used by Apple coprocessors for
> low-latency byte-stream communication with the AP.
>
> Add a mailbox controller that preallocates RX storage, tracks IRQ
> enable state in software, and reports TX completion from the TX-empty
> interrupt.
>
> Reject messages larger than the FIFO and return -EBUSY while the
> previous message is still pending. This keeps the provider usable for
> future small-message clients such as serial transports without a TX
> worker.
>
> Co-developed-by: Hector Martin <[email protected]>
> Signed-off-by: Hector Martin <[email protected]>
> Signed-off-by: Michael Reeves <[email protected]>
> ---
>   MAINTAINERS                               |   2 +
>   drivers/mailbox/Kconfig                   |  12 +
>   drivers/mailbox/Makefile                  |   2 +
>   drivers/mailbox/apple-dockchannel.c       | 380 ++++++++++++++++++++++++++++++
>   include/linux/mailbox/apple-dockchannel.h |  29 +++
>   5 files changed, 425 insertions(+)
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 1f3c2cdb6e19..ed68452c0ad6 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -2628,6 +2628,7 @@ F:	drivers/input/touchscreen/apple_z2.c
>   F:	drivers/iommu/apple-dart.c
>   F:	drivers/iommu/io-pgtable-dart.c
>   F:	drivers/irqchip/irq-apple-aic.c
> +F:	drivers/mailbox/apple-dockchannel.c
>   F:	drivers/mfd/macsmc.c
>   F:	drivers/nvme/host/apple.c
>   F:	drivers/nvmem/apple-efuses.c
> @@ -2646,6 +2647,7 @@ F:	drivers/video/backlight/apple_dwi_bl.c
>   F:	drivers/watchdog/apple_wdt.c
>   F:	include/dt-bindings/interrupt-controller/apple-aic.h
>   F:	include/dt-bindings/pinctrl/apple.h
> +F:	include/linux/mailbox/apple-dockchannel.h
>   F:	include/linux/mfd/macsmc.h
>   F:	include/linux/soc/apple/*
>   F:	include/uapi/drm/asahi_drm.h
> diff --git a/drivers/mailbox/Kconfig b/drivers/mailbox/Kconfig
> index 3062ee352f78..f1af76d19f1e 100644
> --- a/drivers/mailbox/Kconfig
> +++ b/drivers/mailbox/Kconfig
> @@ -36,6 +36,18 @@ config ARM_MHU_V3
>   	  that provides different means of transports: supported extensions
>   	  will be discovered and possibly managed at probe-time.
>   
> +config APPLE_DOCKCHANNEL
> +	tristate "Apple DockChannel FIFO mailbox"
> +	depends on ARCH_APPLE || COMPILE_TEST
> +	depends on HAS_IOMEM
> +	depends on OF
> +	help
> +	  DockChannel is a hardware FIFO used on Apple Silicon SoCs for
> +	  communication between the application processor and co-processors.
> +	  This driver exposes DockChannel FIFOs through the mailbox framework.
> +
> +	  Say Y here if you have an M2 or later Apple MacBook.
> +
>   config AST2700_MBOX
>   	tristate "ASPEED AST2700 IPC driver"
>   	depends on ARCH_ASPEED || COMPILE_TEST
> diff --git a/drivers/mailbox/Makefile b/drivers/mailbox/Makefile
> index 944d8ea39f34..4f3405064269 100644
> --- a/drivers/mailbox/Makefile
> +++ b/drivers/mailbox/Makefile
> @@ -11,6 +11,8 @@ obj-$(CONFIG_ARM_MHU_V2)	+= arm_mhuv2.o
>   
>   obj-$(CONFIG_ARM_MHU_V3)	+= arm_mhuv3.o
>   
> +obj-$(CONFIG_APPLE_DOCKCHANNEL)	+= apple-dockchannel.o
> +
>   obj-$(CONFIG_AST2700_MBOX)	+= ast2700-mailbox.o
>   
>   obj-$(CONFIG_CV1800_MBOX)	+= cv1800-mailbox.o
> diff --git a/drivers/mailbox/apple-dockchannel.c b/drivers/mailbox/apple-dockchannel.c
> new file mode 100644
> index 000000000000..bae183db1307
> --- /dev/null
> +++ b/drivers/mailbox/apple-dockchannel.c
> @@ -0,0 +1,380 @@
> +// SPDX-License-Identifier: GPL-2.0-only OR MIT
> +/*
> + * Apple DockChannel mailbox controller
> + *
> + * Copyright The Asahi Linux Contributors
> + *
> + * DockChannel is a byte FIFO used by Apple co-processors. This driver exposes a
> + * single FIFO pair as a Linux mailbox channel and moves payload bytes with PIO.
> + * There is no DMA involved, so relaxed MMIO accessors are sufficient for the
> + * FIFO accesses themselves.
> + */
> +
> +#include <linux/device.h>
> +#include <linux/interrupt.h>
> +#include <linux/io.h>
> +#include <linux/mailbox/apple-dockchannel.h>
> +#include <linux/mailbox_controller.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/platform_device.h>
> +#include <linux/spinlock.h>
> +#include <linux/unaligned.h>
> +
> +#define APPLE_DOCKCHANNEL_FIFO_SIZE	0x800
> +
> +#define IRQ_MASK			0x0
> +#define IRQ_FLAG			0x4
> +
> +#define IRQ_TX				BIT(2)
> +#define IRQ_RX				BIT(3)
> +
> +#define CONFIG_TX_THRESH		0x0
> +#define CONFIG_RX_THRESH		0x4
> +
> +#define DATA_TX8			0x4
> +#define DATA_TX32			0x10
> +#define DATA_TX_FREE			0x14
> +#define DATA_RX8			0x1c
> +#define DATA_RX32			0x28
> +#define DATA_RX_COUNT			0x2c
> +
> +struct apple_dockchannel {
> +	struct device *dev;
> +	struct mbox_controller controller;
> +	struct mbox_chan chan;
> +
> +	void __iomem *irq_base;
> +	void __iomem *config_base;
> +	void __iomem *data_base;
> +	int irq;
> +
> +	spinlock_t lock; /* protects IRQ mask and TX state */
> +	u32 irq_mask;
> +
> +	const u8 *tx_buf;
> +	size_t tx_len;
> +	size_t tx_pos;
> +	bool tx_active;
> +
> +	u8 rx_buf[APPLE_DOCKCHANNEL_FIFO_SIZE];
> +};
> +
> +static void apple_dockchannel_irq_update(struct apple_dockchannel *dc,
> +					 u32 bits, bool enable)
> +{
> +	if (enable)
> +		dc->irq_mask |= bits;
> +	else
> +		dc->irq_mask &= ~bits;
> +	writel_relaxed(dc->irq_mask, dc->irq_base + IRQ_MASK);
> +}
> +
> +static void apple_dockchannel_irq_enable(struct apple_dockchannel *dc, u32 bits)
> +{
> +	/*
> +	 * IRQ_FLAG is write-to-clear. Clear stale latched flags before
> +	 * unmasking so the next interrupt reflects current FIFO state.
> +	 */
> +	writel_relaxed(bits, dc->irq_base + IRQ_FLAG);
> +	apple_dockchannel_irq_update(dc, bits, true);
> +}
> +
> +static void apple_dockchannel_irq_disable(struct apple_dockchannel *dc, u32 bits)
> +{
> +	apple_dockchannel_irq_update(dc, bits, false);
> +}
> +
> +static bool apple_dockchannel_tx_empty(struct apple_dockchannel *dc)
> +{
> +	return readl_relaxed(dc->data_base + DATA_TX_FREE) ==
> +	       APPLE_DOCKCHANNEL_FIFO_SIZE;
> +}
> +
> +static void apple_dockchannel_write_pending(struct apple_dockchannel *dc)
> +{
> +	size_t left = dc->tx_len - dc->tx_pos;
> +	const u8 *p = dc->tx_buf + dc->tx_pos;
> +
> +	while (left) {
> +		size_t avail;
> +		size_t block;
> +
> +		avail = readl_relaxed(dc->data_base + DATA_TX_FREE);
> +		if (!avail)
> +			break;
> +
> +		block = min(left, avail);
> +
> +		while (block >= sizeof(u32)) {
> +			writel_relaxed(get_unaligned_le32(p),
> +				       dc->data_base + DATA_TX32);
> +			p += sizeof(u32);
> +			left -= sizeof(u32);
> +			block -= sizeof(u32);
> +		}
> +
> +		while (block) {
> +			writeb_relaxed(*p++, dc->data_base + DATA_TX8);

This must be writel_relaxed, as writeb_relaxed in this mmio region will 
cause an SError. This is why we have separate addresses putting 8-bits 
and 32-bits into the FIFO I guess.

> +			left--;
> +			block--;
> +		}
> +	}
> +
> +	dc->tx_pos = dc->tx_len - left;
> +}
> +
> +static void apple_dockchannel_read(struct apple_dockchannel *dc, void *buf,
> +				   size_t count)
> +{
> +	u8 *p = buf;
> +	size_t left = count;
> +
> +	while (left >= sizeof(u32)) {
> +		put_unaligned_le32(readl_relaxed(dc->data_base + DATA_RX32), p);
> +		p += sizeof(u32);
> +		left -= sizeof(u32);
> +	}
> +
> +	while (left) {
> +		/*
> +		 * The byte FIFO register returns the byte in bits [15:8] on
> +		 * these instances.
> +		 */
> +		*p++ = readl_relaxed(dc->data_base + DATA_RX8) >> 8;
> +		left--;
> +	}
> +}
> +
> +static int apple_dockchannel_send_data(struct mbox_chan *chan, void *data)
> +{
> +	struct apple_dockchannel *dc = chan->con_priv;
> +	struct apple_dockchannel_msg *msg = data;
> +	unsigned long flags;
> +
> +	if (!msg || !msg->data || !msg->len)
> +		return -EINVAL;
> +
> +	if (msg->len > APPLE_DOCKCHANNEL_FIFO_SIZE)
> +		return -EMSGSIZE;
> +
> +	spin_lock_irqsave(&dc->lock, flags);
> +
> +	if (dc->tx_active || !apple_dockchannel_tx_empty(dc)) {
> +		spin_unlock_irqrestore(&dc->lock, flags);
> +		return -EBUSY;
> +	}
> +
> +	dc->tx_buf = msg->data;
> +	dc->tx_len = msg->len;
> +	dc->tx_pos = 0;
> +	dc->tx_active = true;
> +
> +	apple_dockchannel_write_pending(dc);
> +	writel_relaxed(APPLE_DOCKCHANNEL_FIFO_SIZE,
> +		       dc->config_base + CONFIG_TX_THRESH);
> +	apple_dockchannel_irq_enable(dc, IRQ_TX);
> +
> +	spin_unlock_irqrestore(&dc->lock, flags);
> +
> +	return 0;
> +}
> +
> +static int apple_dockchannel_startup(struct mbox_chan *chan)
> +{
> +	struct apple_dockchannel *dc = chan->con_priv;
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&dc->lock, flags);
> +	/*
> +	 * The mailbox framework has no per-client RX threshold. Use byte
> +	 * granularity because UART-style DockChannel clients require it.
> +	 */
> +	writel_relaxed(1, dc->config_base + CONFIG_RX_THRESH);
> +	apple_dockchannel_irq_enable(dc, IRQ_RX);
> +	spin_unlock_irqrestore(&dc->lock, flags);
> +
> +	enable_irq(dc->irq);
> +
> +	return 0;
> +}
> +
> +static void apple_dockchannel_shutdown(struct mbox_chan *chan)
> +{
> +	struct apple_dockchannel *dc = chan->con_priv;
> +	unsigned long flags;
> +
> +	disable_irq(dc->irq);
> +
> +	spin_lock_irqsave(&dc->lock, flags);
> +	apple_dockchannel_irq_disable(dc, IRQ_TX | IRQ_RX);
> +	dc->tx_active = false;
> +	spin_unlock_irqrestore(&dc->lock, flags);
> +}
> +
> +static const struct mbox_chan_ops apple_dockchannel_mbox_ops = {
> +	.send_data = apple_dockchannel_send_data,
> +	.startup = apple_dockchannel_startup,
> +	.shutdown = apple_dockchannel_shutdown,
> +};
> +
> +static irqreturn_t apple_dockchannel_irq(int irq, void *data)
> +{
> +	struct apple_dockchannel *dc = data;
> +	u32 flags;
> +	u32 pending;
> +	bool tx_done = false;
> +
> +	flags = readl_relaxed(dc->irq_base + IRQ_FLAG);
> +
> +	spin_lock(&dc->lock);
> +
> +	pending = flags & dc->irq_mask & (IRQ_TX | IRQ_RX);
> +	if (!pending)
> +		goto out_unlock_none;
> +
> +	if (pending & IRQ_TX) {
> +		if (apple_dockchannel_tx_empty(dc)) {
> +			apple_dockchannel_irq_disable(dc, IRQ_TX);
> +			tx_done = dc->tx_active;
> +			dc->tx_active = false;
> +		} else {
> +			pending &= ~IRQ_TX;
> +		}
> +	}
> +
> +	writel_relaxed(pending, dc->irq_base + IRQ_FLAG);
> +
> +	spin_unlock(&dc->lock);
> +
> +	if (tx_done)
> +		mbox_chan_txdone(&dc->chan, 0);
> +
> +	if (pending & IRQ_RX)
> +		return IRQ_WAKE_THREAD;
> +
> +	if (pending)
> +		return IRQ_HANDLED;
> +
> +	return IRQ_NONE;
> +
> +out_unlock_none:
> +	spin_unlock(&dc->lock);
> +
> +	if (flags & (IRQ_TX | IRQ_RX))
> +		writel_relaxed(flags & (IRQ_TX | IRQ_RX),
> +			       dc->irq_base + IRQ_FLAG);
> +
> +	return IRQ_NONE;
> +}
> +
> +static irqreturn_t apple_dockchannel_irq_thread(int irq, void *data)
> +{
> +	struct apple_dockchannel *dc = data;
> +
> +	for (;;) {
> +		struct apple_dockchannel_msg msg;
> +		size_t avail;
> +
> +		avail = readl_relaxed(dc->data_base + DATA_RX_COUNT);
> +		if (!avail)
> +			break;
> +
> +		avail = min_t(size_t, avail, APPLE_DOCKCHANNEL_FIFO_SIZE);
> +
> +		apple_dockchannel_read(dc, dc->rx_buf, avail);
> +
> +		msg.data = dc->rx_buf;
> +		msg.len = avail;
> +		mbox_chan_received_data(&dc->chan, &msg);
> +	}
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static struct mbox_chan *
> +apple_dockchannel_of_xlate(struct mbox_controller *mbox,
> +			   const struct of_phandle_args *spec)
> +{
> +	if (spec->args_count != 0)
> +		return ERR_PTR(-EINVAL);
> +
> +	return &mbox->chans[0];
> +}
> +
> +static int apple_dockchannel_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct apple_dockchannel *dc;
> +	int ret;
> +
> +	dc = devm_kzalloc(dev, sizeof(*dc), GFP_KERNEL);
> +	if (!dc)
> +		return -ENOMEM;
> +
> +	dc->dev = dev;
> +	spin_lock_init(&dc->lock);
> +	platform_set_drvdata(pdev, dc);
> +
> +	dc->irq_base = devm_platform_ioremap_resource_byname(pdev, "irq");
> +	if (IS_ERR(dc->irq_base))
> +		return PTR_ERR(dc->irq_base);
> +
> +	dc->config_base = devm_platform_ioremap_resource_byname(pdev, "config");
> +	if (IS_ERR(dc->config_base))
> +		return PTR_ERR(dc->config_base);
> +
> +	dc->data_base = devm_platform_ioremap_resource_byname(pdev, "data");
> +	if (IS_ERR(dc->data_base))
> +		return PTR_ERR(dc->data_base);
> +
> +	writel_relaxed(0, dc->irq_base + IRQ_MASK);
> +	writel_relaxed(~0, dc->irq_base + IRQ_FLAG);
> +
> +	dc->irq = platform_get_irq(pdev, 0);
> +	if (dc->irq < 0)
> +		return dc->irq;
> +
> +	ret = devm_request_threaded_irq(dev, dc->irq, apple_dockchannel_irq,
> +					apple_dockchannel_irq_thread, IRQF_ONESHOT,
> +					dev_name(dev), dc);
> +	if (ret)
> +		return dev_err_probe(dev, ret, "failed to request IRQ\n");
> +
> +	disable_irq(dc->irq);
> +
> +	dc->chan.con_priv = dc;
> +	dc->controller.dev = dev;
> +	dc->controller.ops = &apple_dockchannel_mbox_ops;
> +	dc->controller.chans = &dc->chan;
> +	dc->controller.num_chans = 1;
> +	dc->controller.txdone_irq = true;
> +	dc->controller.of_xlate = apple_dockchannel_of_xlate;
> +
> +	ret = devm_mbox_controller_register(dev, &dc->controller);
> +	if (ret)
> +		return dev_err_probe(dev, ret, "failed to register mailbox\n");
> +
> +	return 0;
> +}
> +
> +static const struct of_device_id apple_dockchannel_of_match[] = {
> +	{ .compatible = "apple,t8122-dockchannel" },
> +	{ .compatible = "apple,t8112-dockchannel" },
> +	{},
> +};
> +MODULE_DEVICE_TABLE(of, apple_dockchannel_of_match);
> +
> +static struct platform_driver apple_dockchannel_driver = {
> +	.driver = {
> +		.name = "apple-dockchannel",
> +		.of_match_table = apple_dockchannel_of_match,
> +	},
> +	.probe = apple_dockchannel_probe,
> +};
> +module_platform_driver(apple_dockchannel_driver);
> +
> +MODULE_DESCRIPTION("Apple DockChannel mailbox controller");
> +MODULE_AUTHOR("Hector Martin <[email protected]>");
> +MODULE_AUTHOR("Michael Reeves <[email protected]>");
> +MODULE_LICENSE("Dual MIT/GPL");
> diff --git a/include/linux/mailbox/apple-dockchannel.h b/include/linux/mailbox/apple-dockchannel.h
> new file mode 100644
> index 000000000000..04d2fc44f12f
> --- /dev/null
> +++ b/include/linux/mailbox/apple-dockchannel.h
> @@ -0,0 +1,29 @@
> +/* SPDX-License-Identifier: GPL-2.0-only OR MIT */
> +/*
> + * Apple DockChannel mailbox message format.
> + *
> + * Copyright The Asahi Linux Contributors
> + */
> +
> +#ifndef _LINUX_MAILBOX_APPLE_DOCKCHANNEL_H_
> +#define _LINUX_MAILBOX_APPLE_DOCKCHANNEL_H_
> +
> +#include <linux/types.h>
> +
> +/**
> + * struct apple_dockchannel_msg - DockChannel mailbox payload
> + * @data: Pointer to the byte stream payload
> + * @len: Number of payload bytes
> + *
> + * For TX, @data must remain valid until mbox_send_message() completes or the
> + * client receives tx_done in non-blocking mode.
> + *
> + * For RX, @data is owned by the controller and is valid only for the duration
> + * of the rx_callback.
> + */
> +struct apple_dockchannel_msg {
> +	void *data;
> +	size_t len;
> +};
> +
> +#endif /* _LINUX_MAILBOX_APPLE_DOCKCHANNEL_H_ */
>
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.