Devicetree
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Jie Gan" <jie.gan@oss.qualcomm.com>
Cc: devicetree@vger.kernel.org, robh@kernel.org, conor+dt@kernel.org
Subject: Re: [PATCH v22 7/8] coresight: ctcu: enable byte-cntr for TMC ETR devices
Date: Tue, 10 Nov 2026 03:59:07 +0000	[thread overview]
Message-ID: <sashiko-outbox-206158@kernel.org> (raw)
In-Reply-To: <20261110-enable-byte-cntr-for-ctcu-v22-7-05d48c0eb110@oss.qualcomm.com>

Thank you for your contribution! Sashiko AI review found 7 potential
issues to consider:
- [Medium] coresight: ctcu: unsynchronized concurrent read() in
  tmc_byte_cntr_get_data()
- [Medium] coresight: ctcu: state desynchronization in
  ctcu_byte_cntr_start() and stop()
- [Medium] coresight: tmc: broken ETR_MODE_RESRV fallback in
  tmc_read_prepare_etr()
- [Medium] coresight: tmc: tmc_read() converts byte-cntr negative errors
  into EOF
- [Medium] coresight: ctcu: BYTECNTRVAL register not reset in
  ctcu_platform_remove()
- [Medium] coresight: ctcu: UAF race between byte_cntr_sysfs_ops and
  CTCU teardown
- [Medium] coresight: ctcu: byte-cntr state desync on generic read
  fallback and unprepare
--

commit a4fb3ce63a75e273453b0d77a16ba13d3f857042
Author: Jie Gan <jie.gan@oss.qualcomm.com>

coresight: ctcu: enable byte-cntr for TMC ETR devices

This commit adds CTCU byte-counter interrupt support for TMC ETR sinks so
trace data can be transferred from rotating ETR buffers to userspace while
tracing is active.

> diff --git a/drivers/hwtracing/coresight/coresight-ctcu-byte-cntr.c b/drivers/hwtracing/coresight/coresight-ctcu-byte-cntr.c
> new file mode 100644
> index 0000000000000..56a9aa2f693ab
> --- /dev/null
> +++ b/drivers/hwtracing/coresight/coresight-ctcu-byte-cntr.c
[ ... ]
> +static struct ctcu_byte_cntr *ctcu_get_byte_cntr(struct coresight_device *ctcu,
> +						 struct coresight_device *etr)
> +{
> +	struct ctcu_byte_cntr *byte_cntr_data;
> +	struct ctcu_drvdata *drvdata;
> +	int port;
> +
> +	/* The ETR may not be connected to a CTCU device */
> +	if (!ctcu)
> +		return NULL;
> +
> +	drvdata = dev_get_drvdata(ctcu->dev.parent);
> +	port = coresight_get_in_port(etr, ctcu);
> +	if (port < 0 || port >= ETR_MAX_NUM)
> +		return NULL;
> +
> +	byte_cntr_data = &drvdata->byte_cntr_data[port];
> +	/* The port is not initialized by ctcu_byte_cntr_init() */
> +	if (!byte_cntr_data->ctcu_drvdata)
> +		return NULL;
> +
> +	return byte_cntr_data;
> +}

[Severity: Medium]
Can an in-flight byte_cntr_sysfs_ops call race with ctcu_platform_remove()
and cause a use-after-free of ctcu, ctcu->pdata, or drvdata?

tmc_read_prepare_etr(), tmc_etr_get_sysfs_trace(), and
tmc_read_unprepare_etr() load byte_cntr_sysfs_ops with READ_ONCE() and call
into coresight-ctcu without holding a lock or pinning the CTCU device.
Because session_done is initialized as completed and is only reinitialized
after tmc_read_prepare_byte_cntr() looks up ctcu and byte_cntr_data (and
remains completed throughout generic ETR sysfs reads), ctcu_platform_remove()
does not wait for callers that have already loaded byte_cntr_sysfs_ops:

CPU0                                  CPU1
tmc_read_prepare_etr()
  ops = READ_ONCE(byte_cntr_sysfs_ops)
  ops->read_prepare(drvdata)
    ctcu = tmc_etr_get_ctcu_device()
                                      ctcu_platform_remove()
                                        wait_for_completion(&session_done)
                                        ctcu_byte_cntr_put_ops(drvdata)
                                        ctcu_remove(pdev)
                                          coresight_unregister(csdev)
    ctcu_get_byte_cntr(ctcu, ...)
      coresight_get_in_port(etr, ctcu)  <-- use-after-free

If a second CTCU device is present, ctcu_byte_cntr_put_ops() does not clear
byte_cntr_sysfs_ops at all when the first CTCU is removed.

> +
> +/*
> + * ctcu_byte_cntr_get_data() - reads data from the deactivated and filled buffer.
> + * The byte-cntr reading work reads data from the deactivated and filled buffer.
> + * The read operation waits for a buffer to become available, either filled or
> + * upon timeout, and then reads trace data from the synced buffer.
> + */
> +static ssize_t tmc_byte_cntr_get_data(struct tmc_drvdata *etr_drvdata, loff_t pos,
> +				      size_t len, char **bufpp)
> +{
> +	struct coresight_device *ctcu = tmc_etr_get_ctcu_device(etr_drvdata);
> +	struct device *dev = &etr_drvdata->csdev->dev;
> +	struct ctcu_byte_cntr *byte_cntr_data;
> +	struct etr_buf *sysfs_buf;
> +	atomic_t *irq_cnt;
> +	ssize_t actual;
> +	int ret;
> +
> +	/* No byte-cntr read session, let the caller use the generic read */
> +	byte_cntr_data = ctcu_get_byte_cntr(ctcu, etr_drvdata->csdev);
> +	if (!byte_cntr_data || !byte_cntr_data->irq_enabled ||
> +	    !byte_cntr_data->reading)
> +		return -EINVAL;
> +
> +	/*
> +	 * A zero-length read must not be treated as the end of the buffer,
> +	 * otherwise the unread data of the current buffer would be dropped.
> +	 */
> +	if (!len)
> +		return 0;
> +
> +	irq_cnt = &byte_cntr_data->irq_cnt;
> +
> +wait_buffer:
> +	if (!byte_cntr_data->buf_node) {
> +		ret = wait_event_interruptible_timeout(byte_cntr_data->wq,
> +				(atomic_read(irq_cnt) >= MAX_IRQ_CNT - 1) ||
> +				!byte_cntr_data->enable,
> +				BYTE_CNTR_TIMEOUT);
> +		if (ret < 0)
> +			return ret;
> +		/*
> +		 * The current etr_buf is almost full or timeout is triggered,
> +		 * so switch the buffer and mark the switched buffer as reading.
> +		 */
> +		if (byte_cntr_data->enable) {
> +			byte_cntr_data->buf_node =
> +				tmc_etr_switch_buf_node(etr_drvdata);
> +			if (!byte_cntr_data->buf_node) {
> +				/*
> +				 * The sink has stopped, let the generic read
> +				 * handle the last etr_buf.
> +				 */
> +				if (coresight_get_mode(etr_drvdata->csdev) != CS_MODE_SYSFS)
> +					return 0;
> +
> +				dev_err(dev, "Switch buffer failed for the byte-cntr\n");
> +				return -ENOMEM;
> +			}
> +
> +			/*
> +			 * Get the interrupt count of the switched out etr_buf
> +			 * and reset it for the next etr_buf.
> +			 */
> +			if (atomic_xchg(irq_cnt, 0) >= MAX_IRQ_CNT)
> +				dev_warn(dev, "Data overwrite happened\n");
> +		} else {
> +			/*
> +			 * The CTCU device is removed while the sink is still
> +			 * running. The generic read can't be used then as the
> +			 * buffer in use is not synced yet, end the reading.
> +			 */
> +			if (READ_ONCE(byte_cntr_data->removing) &&
> +			    coresight_get_mode(etr_drvdata->csdev) == CS_MODE_SYSFS)
> +				return -ENODEV;
> +
> +			/* Exit byte-cntr reading */
> +			return 0;
> +		}
> +	}
> +
> +	pos = byte_cntr_data->buf_node->pos;
> +	sysfs_buf = byte_cntr_data->buf_node->sysfs_buf;
> +	actual = tmc_etr_read_sysfs_buf(sysfs_buf, pos, len, bufpp);
> +	if (actual <= 0) {
> +		/* Reset buf_node upon reading is finished or failed */
> +		byte_cntr_data->buf_node->reading = false;
> +		byte_cntr_data->buf_node = NULL;
> +
> +		/*
> +		 * Nothing in the buffer, waiting for the next buffer
> +		 * to be filled.
> +		 */
> +		if (actual == 0)
> +			goto wait_buffer;
> +	}
> +
> +	return actual;
> +}

[Severity: Medium]
Can concurrent read() calls on the same open /dev/tmc_etrX file descriptor
race on byte_cntr_data->buf_node and cause a NULL pointer dereference?

/dev/tmc_etrX is a character device opened with nonseekable_open(), so VFS
does not serialize concurrent read() calls with file->f_pos_lock.  While
byte_cntr_data->reading prevents a second open(), two threads sharing one
open file descriptor can enter tmc_byte_cntr_get_data() concurrently:

CPU0                                  CPU1
tmc_byte_cntr_get_data()              tmc_byte_cntr_get_data()
  if (!byte_cntr_data->buf_node)        if (!byte_cntr_data->buf_node)
    wait_event_interruptible_timeout()    wait_event_interruptible_timeout()
    buf_node = tmc_etr_switch_buf_node()
                                          buf_node = tmc_etr_switch_buf_node()
                                          /* returns NULL: no free node */
                                          return -ENOMEM;
  pos = byte_cntr_data->buf_node->pos;  <-- NULL pointer dereference

Similarly, while one reader is in copy_to_user() in tmc_read() before
tmc_etr_update_buf_node_pos(), a second reader can reach the end of the
buffer, clear buf_node->reading, and call tmc_etr_switch_buf_node() to
restart ETR DMA into the buffer still being copied to userspace.

> +
> +static int tmc_read_prepare_byte_cntr(struct tmc_drvdata *etr_drvdata)
> +{
> +	struct coresight_device *ctcu = tmc_etr_get_ctcu_device(etr_drvdata);
> +	struct ctcu_byte_cntr *byte_cntr_data;
> +	unsigned long flags;
> +	int ret = 0;
[ ... ]
> +	reinit_completion(&byte_cntr_data->session_done);
> +	byte_cntr_data->reading = true;
> +	raw_spin_unlock_irqrestore(&byte_cntr_data->spin_lock, flags);
> +
> +	/* Setup an available etr_buf_list for byte-cntr */
> +	ret = tmc_create_etr_buf_list(etr_drvdata, 2);
> +	if (ret)
> +		goto err_reading;

[Severity: Medium]
What happens if the ETR is in ETR_MODE_RESRV when byte-cntr is enabled, or
if a second open() races with an active fallback read session?

ctcu_byte_cntr_start() does not reject ETR_MODE_RESRV, so
tmc_create_etr_buf_list() returns -EINVAL.  tmc_read_prepare_etr() treats
-EINVAL from read_prepare() as meaning byte-cntr is not configured and falls
back to the generic sysfs read path, calling __tmc_etr_disable_hw() and
setting drvdata->reading = true while byte_cntr_data->enable and
byte_cntr_data->irq_enabled remain true.

Because tmc_read_prepare_byte_cntr() sets byte_cntr_data->reading = true
before tmc_create_etr_buf_list() allocates a node and checks
drvdata->reading, a subsequent open() during that fallback read session
briefly sets byte_cntr_data->reading = true.  A concurrent read() in the
first session then enters tmc_byte_cntr_get_data() instead of reading
drvdata->sysfs_buf and fails with -ENOMEM because etr_buf_list is empty.

[ ... ]
> +static int tmc_read_unprepare_byte_cntr(struct tmc_drvdata *etr_drvdata)
> +{
> +	struct coresight_device *ctcu = tmc_etr_get_ctcu_device(etr_drvdata);
> +	struct ctcu_byte_cntr *byte_cntr_data;
> +
> +	/*
> +	 * Do the unprepare operation only when the byte_cntr_data->reading
> +	 * is truly set
> +	 */
> +	byte_cntr_data = ctcu_get_byte_cntr(ctcu, etr_drvdata->csdev);
> +	if (!byte_cntr_data || !byte_cntr_data->irq_enabled ||
> +	    !byte_cntr_data->reading)
> +		return -EINVAL;
> +
> +	tmc_clean_etr_buf_list(etr_drvdata);
> +
> +	/* disable_irq_wake() may sleep on slow-bus irqchips, call it unlocked */
> +	disable_irq_wake(byte_cntr_data->irq);
> +
> +	/*
> +	 * The byte-cntr register is not accessed here: the trace path, which
> +	 * keeps the CTCU powered, may have been released already. It is reset
> +	 * when the path is disabled.
> +	 */
> +	scoped_guard(raw_spinlock_irqsave, &byte_cntr_data->spin_lock) {
> +		byte_cntr_data->buf_node = NULL;
> +		byte_cntr_data->reading = false;

[Severity: Medium]
Does clearing etr_buf_list before clearing byte_cntr_data->reading allow a
new trace session to start with byte-cntr skipped?

ctcu_byte_cntr_start() skips enabling byte-cntr when
byte_cntr_data->reading is set, relying on tmc_enable_etr_sink_sysfs() to
fail with -EBUSY while etr_buf_list is non-empty.  Because
tmc_read_unprepare_byte_cntr() empties etr_buf_list and calls
disable_irq_wake() before clearing byte_cntr_data->reading, a concurrent
trace enable in that window sees byte_cntr_data->reading == true in
ctcu_byte_cntr_start() and an empty etr_buf_list in
tmc_enable_etr_sink_sysfs(), starting the trace without byte-cntr.

> +		/*
> +		 * Complete the session with reading cleared in one critical
> +		 * section, so that a new session can't start in between.
> +		 * ctcu_platform_remove() may free byte_cntr_data after this,
> +		 * don't use it anymore.
> +		 */
> +		complete_all(&byte_cntr_data->session_done);
> +	}
> +
> +	module_put(THIS_MODULE);
> +
> +	return 0;
> +}
> +
> +static const struct tmc_sysfs_ops byte_cntr_sysfs_ops = {
> +	.read_prepare	= tmc_read_prepare_byte_cntr,
> +	.read_unprepare	= tmc_read_unprepare_byte_cntr,
> +	.get_trace_data	= tmc_byte_cntr_get_data,
> +};
> +
> +/* Start the byte-cntr function when the path is enabled. */
> +void ctcu_byte_cntr_start(struct coresight_device *csdev, struct coresight_path *path)
> +{
> +	struct coresight_device *sink = coresight_get_sink(path);
> +	struct tmc_drvdata *etr_drvdata;
> +	struct ctcu_byte_cntr *byte_cntr_data;
> +
> +	byte_cntr_data = ctcu_get_byte_cntr(csdev, sink);
> +	if (!byte_cntr_data)
> +		return;
> +
> +	etr_drvdata = dev_get_drvdata(sink->dev.parent);
> +	guard(raw_spinlock_irqsave)(&byte_cntr_data->spin_lock);
> +	/*
> +	 * Don't start byte-cntr function when irq_enabled is not set, when the
> +	 * CTCU device is being removed, or when the read of the previous trace
> +	 * is still in progress: the sink can't be enabled until it is finished.
> +	 */
> +	if (!byte_cntr_data->irq_enabled || byte_cntr_data->removing ||
> +	    byte_cntr_data->reading)
> +		return;
> +
> +	/*
> +	 * Don't touch the count and the threshold if the byte-cntr is already
> +	 * started by another path of the same sink, as the buffer_size may have
> +	 * been changed since then while the etr_buf in use is not.
> +	 */
> +	if (byte_cntr_data->enable)
> +		return;
> +
> +	/* A new trace starts with an empty etr_buf */
> +	atomic_set(&byte_cntr_data->irq_cnt, 0);
> +
> +	/*
> +	 * Configure the byte-cntr register to enable IRQ while the path is
> +	 * enabled, the CTCU is powered then. The configured size is 5% of the
> +	 * buffer_size.
> +	 */
> +	ctcu_cfg_byte_cntr_reg(byte_cntr_data->ctcu_drvdata,
> +			       etr_drvdata->size / MAX_IRQ_CNT,
> +			       byte_cntr_data->irq_ctrl_offset);
> +	byte_cntr_data->enable = true;
> +}
> +
> +/* Stop the byte-cntr function when the path is disabled. */
> +void ctcu_byte_cntr_stop(struct coresight_device *csdev, struct coresight_path *path)
> +{
> +	struct coresight_device *sink = coresight_get_sink(path);
> +	struct ctcu_byte_cntr *byte_cntr_data;
> +
> +	if (coresight_get_mode(sink) == CS_MODE_SYSFS)
> +		return;

[Severity: Medium]
Can byte_cntr_data->irq_enabled, byte_cntr_data->enable, and the ETR sink
state get out of sync across start, read prepare, and stop?

The ABI documentation states that changing irq_enabled while tracing does
not start or stop byte-cntr for the running trace, but irq_enabled_store()
only checks byte_cntr_data[port].reading:

1. If a trace is started with irq_enabled == 1 and irq_enabled is set to 0
   before opening /dev/tmc_etrX, tmc_read_prepare_byte_cntr() sees
   !byte_cntr_data->irq_enabled and returns -EINVAL.  tmc_read_prepare_etr()
   then falls back to the generic read and stops the ETR while
   byte_cntr_data->enable and the IRQCTRL register remain active.

2. If a first path starts tracing on the ETR with irq_enabled == 0, and
   irq_enabled is then set to 1 before a second path to the same ETR is
   enabled, ctcu_byte_cntr_start() only checks byte_cntr_data->enable (not
   whether the sink is already active) and starts byte-cntr mid-trace using
   etr_drvdata->size.  If enabling a later component on that second path
   fails, ctcu_byte_cntr_stop() sees coresight_get_mode(sink) == CS_MODE_SYSFS
   from the first path and returns without undoing byte_cntr_data->enable or
   zeroing irq_ctrl_offset.

3. If a fallback generic read is open (etr_drvdata->reading == true) when
   the trace path is disabled, tmc_disable_etr_sink() returns -EBUSY and
   leaves the sink mode as CS_MODE_SYSFS, so ctcu_byte_cntr_stop() returns
   early without disabling irq_ctrl_offset or clearing byte_cntr_data->enable
   before the path is released.

> diff --git a/drivers/hwtracing/coresight/coresight-ctcu-core.c b/drivers/hwtracing/coresight/coresight-ctcu-core.c
> index 739105acb0a13..564ecc5ad5153 100644
> --- a/drivers/hwtracing/coresight/coresight-ctcu-core.c
> +++ b/drivers/hwtracing/coresight/coresight-ctcu-core.c
[ ... ]
> @@ -244,10 +355,59 @@ static int ctcu_platform_probe(struct platform_device *pdev)
>  static void ctcu_platform_remove(struct platform_device *pdev)
>  {
>  	struct ctcu_drvdata *drvdata = platform_get_drvdata(pdev);
> +	struct ctcu_byte_cntr *byte_cntr_data;
> +	unsigned long flags;
> +	int i;
>  
>  	if (WARN_ON(!drvdata))
>  		return;
>  
> +	/*
> +	 * Signal all active byte-cntr readers to exit, then wait for them to
> +	 * finish before resetting the ops pointer and freeing driver data.
> +	 * Without this, a reader blocked in wait_event_interruptible_timeout()
> +	 * would access the freed ctcu_drvdata wait-queue head (use-after-free).
> +	 *
> +	 * The wait ends only when the userspace closes the tmc_etr file, so an
> +	 * unbind with the file left open blocks until then. Unloading the module
> +	 * is rejected with -EBUSY while a read session is open, so only a manual
> +	 * unbind can block here.
> +	 */
> +	for (i = 0; i < ETR_MAX_NUM; i++) {
> +		byte_cntr_data = &drvdata->byte_cntr_data[i];
> +		/* The port was not initialized by ctcu_byte_cntr_init() */
> +		if (!byte_cntr_data->irq)
> +			continue;
> +
> +		raw_spin_lock_irqsave(&byte_cntr_data->spin_lock, flags);
> +		/*
> +		 * Set enable=false to signal teardown to racing readers, and
> +		 * removing=true to keep a new path from enabling byte-cntr again.
> +		 */
> +		byte_cntr_data->enable = false;
> +		byte_cntr_data->removing = true;
> +		raw_spin_unlock_irqrestore(&byte_cntr_data->spin_lock, flags);

[Severity: Medium]
Should ctcu_platform_remove() clear the hardware irq_ctrl_offset register
when byte_cntr_data->enable is true?

Setting byte_cntr_data->enable = false here without writing 0 to
irq_ctrl_offset means ctcu_byte_cntr_stop() will see enable == false and
skip clearing the register as well, leaving the CTCU hardware firing
byte-counter interrupts while wait_for_completion() waits for the file to be
closed and until the trace path is disabled.

> diff --git a/drivers/hwtracing/coresight/coresight-tmc-etr.c b/drivers/hwtracing/coresight/coresight-tmc-etr.c
> index cc341cdf8d8e5..d856f0a190f72 100644
> --- a/drivers/hwtracing/coresight/coresight-tmc-etr.c
> +++ b/drivers/hwtracing/coresight/coresight-tmc-etr.c
[ ... ]
> +ssize_t tmc_etr_get_sysfs_trace(struct tmc_drvdata *drvdata,
> +				loff_t pos, size_t len, char **bufpp)
> +{
> +	ssize_t ret;
> +	const struct tmc_sysfs_ops *byte_cntr_ops = READ_ONCE(byte_cntr_sysfs_ops);
> +
> +	if (byte_cntr_ops) {
> +		ret = byte_cntr_ops->get_trace_data(drvdata, pos, len, bufpp);
> +		/*
> +		 * -EINVAL means byte-cntr has no active read session. 0 means
> +		 * byte-cntr has no more data because the sink has stopped, the
> +		 * last etr_buf is read by the generic read. Anything else is
> +		 * the result of the byte-cntr session.
> +		 */
> +		if (ret != -EINVAL && ret != 0)
> +			return ret;
> +	}
> +
> +	return tmc_etr_read_sysfs_buf(drvdata->sysfs_buf, pos, len, bufpp);
> +}

[Severity: Medium]
Doesn't tmc_read() turn negative error codes from tmc_etr_get_sysfs_trace()
into 0 (EOF)?

tmc_byte_cntr_get_data() returns -ERESTARTSYS when interrupted by a signal
(as well as -ENOMEM and -ENODEV on failure), and tmc_etr_get_sysfs_trace()
propagates those negative values.  However, the caller in tmc_read() drops
all negative returns:

drivers/hwtracing/coresight/coresight-tmc-core.c:tmc_read() {
    ...
	actual = tmc_get_sysfs_trace(drvdata, *ppos, len, &bufp);
	if (actual <= 0)
		return 0;
    ...
}

Any signal delivered while waiting in wait_event_interruptible_timeout()
causes read() to return 0 (EOF) instead of -ERESTARTSYS, terminating trace
collection early while the ETR is still tracing.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261110-enable-byte-cntr-for-ctcu-v22-0-05d48c0eb110@oss.qualcomm.com?part=7

  reply	other threads:[~2026-11-10  3:59 UTC|newest]

Thread overview: 11+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-10  3:21 [PATCH v22 0/8] coresight: ctcu: Enable byte-cntr function for TMC ETR Jie Gan
2026-11-10  3:21 ` [PATCH v22 1/8] coresight: core: refactor ctcu_get_active_port and make it generic Jie Gan
2026-11-10  3:21 ` [PATCH v22 2/8] coresight: tmc: add create/clean functions for etr_buf_list Jie Gan
2026-11-10  3:21 ` [PATCH v22 3/8] coresight: tmc: add a function to switch the ETR to a free etr_buf Jie Gan
2026-11-10  3:50   ` sashiko-bot
2026-11-10  3:21 ` [PATCH v22 4/8] coresight: tmc: introduce tmc_sysfs_ops to wrap sysfs read operations Jie Gan
2026-11-10  3:21 ` [PATCH v22 5/8] coresight: etr: add a new function to retrieve the CTCU device Jie Gan
2026-11-10  3:21 ` [PATCH v22 6/8] dt-bindings: arm: add an interrupt property for Coresight CTCU Jie Gan
2026-11-10  3:21 ` [PATCH v22 7/8] coresight: ctcu: enable byte-cntr for TMC ETR devices Jie Gan
2026-11-10  3:59   ` sashiko-bot [this message]
2026-11-10  3:21 ` [PATCH v22 8/8] arm64: dts: qcom: lemans: add interrupts to CTCU device Jie Gan

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=sashiko-outbox-206158@kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=conor+dt@kernel.org \
    --cc=devicetree@vger.kernel.org \
    --cc=jie.gan@oss.qualcomm.com \
    --cc=robh@kernel.org \
    --cc=sashiko-reviews@lists.linux.dev \
    /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