All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] ata: libata: Add debug dump around error handling and link resets
@ 2026-10-16  8:56 Junxian Huang
  2026-10-16  9:07 ` sashiko-bot
  0 siblings, 1 reply; 2+ messages in thread
From: Junxian Huang @ 2026-10-16  8:56 UTC (permalink / raw)
  To: dlemoal, cassel, linux-ide; +Cc: yangxingui, huangjunxian6, linuxarm

Add a generic debug_dump() operation to struct ata_port_operations
so LLDDs can dump their own controller debug state around error
handling.

For the standard AHCI driver, implement this hook to dump the AHCI
controller registers through tracepoints:

  ahci_global_softreset_start / _done   - HBA global registers
  ahci_global_hardreset_start / _done
  ahci_global_eh_start / _done
  ahci_port_softreset_start / _done     - port registers
  ahci_port_hardreset_start / _done
  ahci_port_eh_start / _done

These tracepoints help debug error and link reset issues.

Example usage:

  $ echo 1 > /sys/kernel/tracing/events/libahci/ahci_global_eh_start/enable
  $ echo 1 > /sys/kernel/tracing/events/libahci/ahci_port_eh_start/enable
  ... trigger an error ...
  $ cat /sys/kernel/tracing/trace
  scsi_eh_4-899 [024] 717.683782: ahci_global_eh_start: ata_port=5 \
    global={0xef377fe1,...}
  scsi_eh_4-899 [024] 717.683808: ahci_port_eh_start: ata_port=5 \
    port={0xfffc0000,...}

Signed-off-by: Junxian Huang <huangjunxian6@hisilicon.com>
---
 drivers/ata/libahci.c          |  57 +++++++++++++++++
 drivers/ata/libata-eh.c        |   6 ++
 include/linux/libata.h         |   6 ++
 include/trace/events/libahci.h | 114 +++++++++++++++++++++++++++++++++
 4 files changed, 183 insertions(+)
 create mode 100644 include/trace/events/libahci.h

diff --git a/drivers/ata/libahci.c b/drivers/ata/libahci.c
index 08ab56e95566..4e1a327d8e68 100644
--- a/drivers/ata/libahci.c
+++ b/drivers/ata/libahci.c
@@ -30,6 +30,10 @@
 #include <scsi/scsi_cmnd.h>
 #include <linux/libata.h>
 #include <linux/pci.h>
+
+#define CREATE_TRACE_POINTS
+#include <trace/events/libahci.h>
+
 #include "ahci.h"
 #include "libata.h"
 
@@ -50,6 +54,7 @@ static ssize_t ahci_led_store(struct ata_port *ap, const char *buf,
 			      size_t size);
 static ssize_t ahci_transmit_led_message(struct ata_port *ap, u32 state,
 					ssize_t size);
+static void ahci_debug_dump(struct ata_port *ap, enum ata_eh_dump phase);
 
 
 
@@ -168,6 +173,7 @@ struct ata_port_operations ahci_ops = {
 	.reset.postreset	= ahci_postreset,
 	.pmp_reset.softreset	= ahci_softreset,
 	.error_handler		= ahci_error_handler,
+	.debug_dump		= ahci_debug_dump,
 	.post_internal_cmd	= ahci_post_internal_cmd,
 	.dev_config		= ahci_dev_config,
 
@@ -1457,6 +1463,49 @@ static int ahci_exec_polled_cmd(struct ata_port *ap, int pmp,
 	return 0;
 }
 
+#define AHCI_DUMP_FN(site)					\
+static void ahci_dump_##site(struct ata_link *link)		\
+{								\
+	struct ahci_host_priv *hpriv = link->ap->host->private_data; \
+	void __iomem *port_mmio = ahci_port_base(link->ap);	\
+	int i;							\
+								\
+	if (trace_ahci_global_##site##_enabled()) {		\
+		u32 global_regs[AHCI_TRACE_GLOBAL_REGS];	\
+								\
+		for (i = 0; i < AHCI_TRACE_GLOBAL_REGS; i++)	\
+			global_regs[i] = readl(hpriv->mmio + i * 4); \
+								\
+		trace_ahci_global_##site(link, global_regs);	\
+	}							\
+								\
+	if (trace_ahci_port_##site##_enabled()) {		\
+		u32 port_regs[AHCI_TRACE_PORT_REGS];		\
+								\
+		for (i = 0; i < AHCI_TRACE_PORT_REGS; i++)	\
+			port_regs[i] = readl(port_mmio + i * 4); \
+								\
+		trace_ahci_port_##site(link, port_regs);	\
+	}							\
+}
+
+AHCI_DUMP_FN(softreset_start)
+AHCI_DUMP_FN(softreset_done)
+AHCI_DUMP_FN(hardreset_start)
+AHCI_DUMP_FN(hardreset_done)
+AHCI_DUMP_FN(eh_start)
+AHCI_DUMP_FN(eh_done)
+#undef AHCI_DUMP_FN
+
+static void ahci_debug_dump(struct ata_port *ap,
+			      enum ata_eh_dump phase)
+{
+	if (phase == ATA_EH_DUMP_ENTER)
+		ahci_dump_eh_start(&ap->link);
+	else
+		ahci_dump_eh_done(&ap->link);
+}
+
 int ahci_do_softreset(struct ata_link *link, unsigned int *class,
 		      int pmp, unsigned long deadline,
 		      int (*check_ready)(struct ata_link *link))
@@ -1471,6 +1520,8 @@ int ahci_do_softreset(struct ata_link *link, unsigned int *class,
 	bool fbs_disabled = false;
 	int rc;
 
+	ahci_dump_softreset_start(link);
+
 	/* prepare for SRST (AHCI-1.1 10.4.1) */
 	rc = ahci_kick_engine(ap);
 	if (rc && rc != -EOPNOTSUPP)
@@ -1530,6 +1581,8 @@ int ahci_do_softreset(struct ata_link *link, unsigned int *class,
 	if (fbs_disabled)
 		ahci_enable_fbs(ap);
 
+	ahci_dump_softreset_done(link);
+
 	return 0;
 
  fail:
@@ -1613,6 +1666,8 @@ int ahci_do_hardreset(struct ata_link *link, unsigned int *class,
 	struct ata_taskfile tf;
 	int rc;
 
+	ahci_dump_hardreset_start(link);
+
 	hpriv->stop_engine(ap);
 
 	/* clear D2H reception area to properly wait for D2H FIS */
@@ -1630,6 +1685,8 @@ int ahci_do_hardreset(struct ata_link *link, unsigned int *class,
 	if (*online)
 		*class = ahci_dev_classify(ap);
 
+	ahci_dump_hardreset_done(link);
+
 	return rc;
 }
 EXPORT_SYMBOL_GPL(ahci_do_hardreset);
diff --git a/drivers/ata/libata-eh.c b/drivers/ata/libata-eh.c
index c93c24b22d9c..a8eb651ffb60 100644
--- a/drivers/ata/libata-eh.c
+++ b/drivers/ata/libata-eh.c
@@ -573,6 +573,9 @@ void ata_scsi_error(struct Scsi_Host *host)
 	int nr_timedout;
 	LIST_HEAD(eh_work_q);
 
+	if (ap->ops->debug_dump)
+		ap->ops->debug_dump(ap, ATA_EH_DUMP_ENTER);
+
 	spin_lock_irqsave(host->host_lock, flags);
 	list_splice_init(&host->eh_cmd_q, &eh_work_q);
 	spin_unlock_irqrestore(host->host_lock, flags);
@@ -591,6 +594,9 @@ void ata_scsi_error(struct Scsi_Host *host)
 		scsi_eh_flush_done_q(&ap->eh_done_q);
 
 	WARN_ON(!list_empty(&eh_work_q));
+
+	if (ap->ops->debug_dump)
+		ap->ops->debug_dump(ap, ATA_EH_DUMP_EXIT);
 }
 
 /**
diff --git a/include/linux/libata.h b/include/linux/libata.h
index 313e96173b19..71b658dd4bdb 100644
--- a/include/linux/libata.h
+++ b/include/linux/libata.h
@@ -966,6 +966,11 @@ struct ata_reset_operations {
 	ata_postreset_fn_t	postreset;
 };
 
+enum ata_eh_dump {
+	ATA_EH_DUMP_ENTER,
+	ATA_EH_DUMP_EXIT,
+};
+
 struct ata_port_operations {
 	/*
 	 * Command execution
@@ -1000,6 +1005,7 @@ struct ata_port_operations {
 	void (*post_internal_cmd)(struct ata_queued_cmd *qc);
 	void (*sched_eh)(struct ata_port *ap);
 	void (*end_eh)(struct ata_port *ap);
+	void (*debug_dump)(struct ata_port *ap, enum ata_eh_dump phase);
 
 	/*
 	 * Optional features
diff --git a/include/trace/events/libahci.h b/include/trace/events/libahci.h
new file mode 100644
index 000000000000..33ff2487cc84
--- /dev/null
+++ b/include/trace/events/libahci.h
@@ -0,0 +1,114 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2026 Hisilicon Limited.
+ */
+#undef TRACE_SYSTEM
+#define TRACE_SYSTEM libahci
+
+#if !defined(_TRACE_LIBAHCI_H) || defined(TRACE_HEADER_MULTI_READ)
+#define _TRACE_LIBAHCI_H
+
+#include <linux/libata.h>
+#include <linux/tracepoint.h>
+
+#define AHCI_TRACE_GLOBAL_REGS	64
+#define AHCI_TRACE_PORT_REGS	32
+
+DECLARE_EVENT_CLASS(ahci_global_reg_dump,
+
+	TP_PROTO(struct ata_link *link, const u32 *global_regs),
+
+	TP_ARGS(link, global_regs),
+
+	TP_STRUCT__entry(
+		__field(unsigned int, ata_port)
+		__array(u32, global_regs, AHCI_TRACE_GLOBAL_REGS)
+	),
+
+	TP_fast_assign(
+		__entry->ata_port = link->ap->print_id;
+		memcpy(__entry->global_regs, global_regs,
+		       sizeof(__entry->global_regs));
+	),
+
+	TP_printk("ata_port=%u global=%s",
+		  __entry->ata_port,
+		  __print_array(__entry->global_regs,
+				AHCI_TRACE_GLOBAL_REGS, 4))
+);
+
+DEFINE_EVENT(ahci_global_reg_dump, ahci_global_softreset_start,
+	     TP_PROTO(struct ata_link *link, const u32 *global_regs),
+	     TP_ARGS(link, global_regs));
+
+DEFINE_EVENT(ahci_global_reg_dump, ahci_global_softreset_done,
+	     TP_PROTO(struct ata_link *link, const u32 *global_regs),
+	     TP_ARGS(link, global_regs));
+
+DEFINE_EVENT(ahci_global_reg_dump, ahci_global_hardreset_start,
+	     TP_PROTO(struct ata_link *link, const u32 *global_regs),
+	     TP_ARGS(link, global_regs));
+
+DEFINE_EVENT(ahci_global_reg_dump, ahci_global_hardreset_done,
+	     TP_PROTO(struct ata_link *link, const u32 *global_regs),
+	     TP_ARGS(link, global_regs));
+
+DEFINE_EVENT(ahci_global_reg_dump, ahci_global_eh_start,
+	     TP_PROTO(struct ata_link *link, const u32 *global_regs),
+	     TP_ARGS(link, global_regs));
+
+DEFINE_EVENT(ahci_global_reg_dump, ahci_global_eh_done,
+	     TP_PROTO(struct ata_link *link, const u32 *global_regs),
+	     TP_ARGS(link, global_regs));
+
+DECLARE_EVENT_CLASS(ahci_port_reg_dump,
+
+	TP_PROTO(struct ata_link *link, const u32 *port_regs),
+
+	TP_ARGS(link, port_regs),
+
+	TP_STRUCT__entry(
+		__field(unsigned int, ata_port)
+		__array(u32, port_regs, AHCI_TRACE_PORT_REGS)
+	),
+
+	TP_fast_assign(
+		__entry->ata_port = link->ap->print_id;
+		memcpy(__entry->port_regs, port_regs,
+		       sizeof(__entry->port_regs));
+	),
+
+	TP_printk("ata_port=%u port=%s",
+		  __entry->ata_port,
+		  __print_array(__entry->port_regs,
+				AHCI_TRACE_PORT_REGS, 4))
+);
+
+DEFINE_EVENT(ahci_port_reg_dump, ahci_port_softreset_start,
+	     TP_PROTO(struct ata_link *link, const u32 *port_regs),
+	     TP_ARGS(link, port_regs));
+
+DEFINE_EVENT(ahci_port_reg_dump, ahci_port_softreset_done,
+	     TP_PROTO(struct ata_link *link, const u32 *port_regs),
+	     TP_ARGS(link, port_regs));
+
+DEFINE_EVENT(ahci_port_reg_dump, ahci_port_hardreset_start,
+	     TP_PROTO(struct ata_link *link, const u32 *port_regs),
+	     TP_ARGS(link, port_regs));
+
+DEFINE_EVENT(ahci_port_reg_dump, ahci_port_hardreset_done,
+	     TP_PROTO(struct ata_link *link, const u32 *port_regs),
+	     TP_ARGS(link, port_regs));
+
+DEFINE_EVENT(ahci_port_reg_dump, ahci_port_eh_start,
+	     TP_PROTO(struct ata_link *link, const u32 *port_regs),
+	     TP_ARGS(link, port_regs));
+
+DEFINE_EVENT(ahci_port_reg_dump, ahci_port_eh_done,
+	     TP_PROTO(struct ata_link *link, const u32 *port_regs),
+	     TP_ARGS(link, port_regs));
+
+#endif /* _TRACE_LIBAHCI_H */
+
+/* This part must be outside protection */
+#include <trace/define_trace.h>
-- 
2.33.0


^ permalink raw reply related	[flat|nested] 2+ messages in thread

* Re: [PATCH] ata: libata: Add debug dump around error handling and link resets
  2026-10-16  8:56 [PATCH] ata: libata: Add debug dump around error handling and link resets Junxian Huang
@ 2026-10-16  9:07 ` sashiko-bot
  0 siblings, 0 replies; 2+ messages in thread
From: sashiko-bot @ 2026-10-16  9:07 UTC (permalink / raw)
  To: Junxian Huang, linux-ide; +Cc: dlemoal, cassel

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] libahci: unmapped MMIO reads cause Data Abort on ARM platforms
- [Low] ata: missing ahci_dump_softreset_done() on softreset error path
--

commit 6261261e408a96bdca27f7aab27ae104e6a98735
Author: Junxian Huang <huangjunxian6@hisilicon.com>

ata: libata: Add debug dump around error handling and link resets

Add a generic debug_dump() operation to struct ata_port_operations so LLDDs
can dump their own controller debug state around error handling. For the
standard AHCI driver, implement this hook to dump the AHCI controller
registers through tracepoints.

> diff --git a/drivers/ata/libahci.c b/drivers/ata/libahci.c
> index 08ab56e955662..4e1a327d8e680 100644
> --- a/drivers/ata/libahci.c
> +++ b/drivers/ata/libahci.c

[ ... ]

> @@ -1457,6 +1463,49 @@ static int ahci_exec_polled_cmd(struct ata_port *ap, int pmp,
>  	return 0;
>  }
>  
> +#define AHCI_DUMP_FN(site)					\
> +static void ahci_dump_##site(struct ata_link *link)		\
> +{								\
> +	struct ahci_host_priv *hpriv = link->ap->host->private_data; \
> +	void __iomem *port_mmio = ahci_port_base(link->ap);	\
> +	int i;							\
> +								\
> +	if (trace_ahci_global_##site##_enabled()) {		\
> +		u32 global_regs[AHCI_TRACE_GLOBAL_REGS];	\
> +								\
> +		for (i = 0; i < AHCI_TRACE_GLOBAL_REGS; i++)	\
> +			global_regs[i] = readl(hpriv->mmio + i * 4); \

[Severity: High]
Does blindly reading global registers beyond implemented standard offsets
trigger bus errors on many ARM platforms?

On embedded ARM platforms using libahci, the AHCI IP is mapped on an AXI/AHB
bus which will commonly respond with a DECERR or SLVERR when accessing
unmapped, reserved, or unimplemented vendor-specific offsets.

This translates to an asynchronous SError or synchronous Data Abort exception,
causing a kernel panic when the tracepoint is enabled. Additionally, reading
vendor-specific registers without knowing their semantics can trigger
read-to-clear or state-altering side effects.

> +								\
> +		trace_ahci_global_##site(link, global_regs);	\
> +	}							\
> +								\
> +	if (trace_ahci_port_##site##_enabled()) {		\
> +		u32 port_regs[AHCI_TRACE_PORT_REGS];		\
> +								\
> +		for (i = 0; i < AHCI_TRACE_PORT_REGS; i++)	\
> +			port_regs[i] = readl(port_mmio + i * 4); \

[Severity: High]
Similarly, can blindly reading port registers up to offset 0x7C risk
accessing unimplemented offsets, causing SError or Data Abort on ARM?

> +								\
> +		trace_ahci_port_##site(link, port_regs);	\
> +	}							\
> +}

[ ... ]

> @@ -1530,6 +1581,8 @@ int ahci_do_softreset(struct ata_link *link, unsigned int *class,
>  	if (fbs_disabled)
>  		ahci_enable_fbs(ap);
>  
> +	ahci_dump_softreset_done(link);
> +
>  	return 0;
>  
>   fail:
>  	ata_link_err(link, "softreset failed (%s)\n", reason);
>  	return rc;
>  }

[Severity: Low]
Is the tracepoint ahci_dump_softreset_done() missing in the error path?

If ahci_do_softreset() encounters an error and jumps to the fail label,
ahci_dump_softreset_done() is not emitted. This results in unbalanced trace
events and loses valuable hardware debug state right when it is most needed
to debug the failure.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261016085657.3703353-1-huangjunxian6@hisilicon.com?part=1

^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2026-10-16  9:07 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-16  8:56 [PATCH] ata: libata: Add debug dump around error handling and link resets Junxian Huang
2026-10-16  9:07 ` sashiko-bot

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.