DMA Engine development
 help / color / mirror / Atom feed
* [PATCH v2 0/4] Add sysfs interface to switchtec-dma
@ 2026-07-27 18:48 Logan Gunthorpe
  2026-07-27 18:48 ` [PATCH v2 1/4] dmaengine: add per-channel sysfs attribute groups via chan_groups Logan Gunthorpe
                   ` (3 more replies)
  0 siblings, 4 replies; 5+ messages in thread
From: Logan Gunthorpe @ 2026-07-27 18:48 UTC (permalink / raw)
  To: dmaengine, linux-kernel, Vinod Koul
  Cc: Frank Li, Christoph Hellwig, Christophe Jaillet, Dave Jiang,
	Thomas Weißschuh, Kelvin Cao, Logan Gunthorpe

This patch set adds a handful of sysfs attributes to the switchtec-dma
driver.

The first two patches clean up and generalize the technique that
ioat used to add sysfs attributes to a dma channel. The other two
patches add a couple different sets of sysfs attributes that are useful
in configuring and monitoring the hardware.

This patch set is based on the miscellaneous fixes series I sent
earlier[1].

Logan

[1] https://lore.kernel.org/all/20260727181526.9672-1-logang@deltatee.com/

Changes since v1:
 - Replaced the per-channel embedded-kobject approach entirely with a
   new dma_device.chan_groups mechanism that attaches driver sysfs
   attributes directly to the channel's existing struct device
   instead of a second, separately-managed kobject. (Per Sashiko)
 - Fixed the busy-channel check in perf_cfg_store(): it now returns
   -EBUSY instead of falsely reporting success, and the racy lockless
   chan->client_count check has been replaced with a hw_cfg_locked
   flag that's set/cleared under hw_ctrl_lock from
   alloc/free_chan_resources(), closing a TOCTOU window against
   concurrent channel allocation. (Per Sashiko)
 - Took the hw_ctrl_lock around the perf_cfg read-modify-write
   so concurrent sysfs writes to different config attributes can't
   interleave and lose an update. (Per Sashiko)
 - Converted latency_selector_show() from strcat() to
   sysfs_emit()/sysfs_emit_at(). (Per Sashiko)
 - The PCI1008 device ID patch has been removed from this
   series, expanded and sent separately.

Logan Gunthorpe (4):
  dmaengine: add per-channel sysfs attribute groups via chan_groups
  dmaengine: ioatdma: convert per-channel sysfs to chan_groups
  dmaengine: switchtec-dma: add config sysfs attributes
  dmaengine: switchtec-dma: add pmon sysfs attributes

 drivers/dma/dmaengine.c     |  27 +++
 drivers/dma/dmaengine.h     |  19 ++
 drivers/dma/ioat/dma.h      |   6 +-
 drivers/dma/ioat/init.c     |   5 +-
 drivers/dma/ioat/sysfs.c    | 166 ++++++-------
 drivers/dma/switchtec_dma.c | 452 ++++++++++++++++++++++++++++++++++++
 include/linux/dmaengine.h   |   4 +
 7 files changed, 569 insertions(+), 110 deletions(-)


base-commit: 0428de2e30c4690e119db5ee4b27725216b61de8
--
2.47.3

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

* [PATCH v2 1/4] dmaengine: add per-channel sysfs attribute groups via chan_groups
  2026-07-27 18:48 [PATCH v2 0/4] Add sysfs interface to switchtec-dma Logan Gunthorpe
@ 2026-07-27 18:48 ` Logan Gunthorpe
  2026-07-27 18:48 ` [PATCH v2 2/4] dmaengine: ioatdma: convert per-channel sysfs to chan_groups Logan Gunthorpe
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 5+ messages in thread
From: Logan Gunthorpe @ 2026-07-27 18:48 UTC (permalink / raw)
  To: dmaengine, linux-kernel, Vinod Koul
  Cc: Frank Li, Christoph Hellwig, Christophe Jaillet, Dave Jiang,
	Thomas Weißschuh, Kelvin Cao, Logan Gunthorpe

Each channel already gets its own struct device (dma_chan_dev),
registered with device_register()/device_unregister() and torn down
correctly by chan_dev_release(). Let drivers add their own sysfs
attributes to that device directly, via a new dma_device.chan_groups
field.

This avoids drivers needing a second, separately-managed kobject to
expose driver-specific attributes. That's a use-after-free issue:
kobject_put() can invoke the ktype's release() and free the structure
embedding dma_chan while the core (or the driver) still expects to
reference it afterward, so avoiding it requires careful ordering and
bookkeeping that's easy to get wrong. The channel's struct device
already has the correct lifetime, and sysfs_create_group() supports
the same named-subdirectory layout (attribute_group.name) that a bare
kobject would provide.

To let show()/store() callbacks safely recover the struct dma_chan from
the struct device they're attached to without exposing dma_list_mutex
itself to drivers, add a small dma_chan_from_dev_lock() /
dma_chan_from_dev_unlock() pair that take / release the lock, and
wrap them in a dma_chan_from_dev CLASS so callers can write:

	CLASS(dma_chan_from_dev, c)(dev);
	if (!c)
		return -ENODEV;

Signed-off-by: Logan Gunthorpe <logang@deltatee.com>
---
 drivers/dma/dmaengine.c   | 27 +++++++++++++++++++++++++++
 drivers/dma/dmaengine.h   | 19 +++++++++++++++++++
 include/linux/dmaengine.h |  4 ++++
 3 files changed, 50 insertions(+)

diff --git a/drivers/dma/dmaengine.c b/drivers/dma/dmaengine.c
index 9049171df857..060a0a482eb3 100644
--- a/drivers/dma/dmaengine.c
+++ b/drivers/dma/dmaengine.c
@@ -161,6 +161,32 @@ static struct dma_chan *dev_to_dma_chan(struct device *dev)
 	return chan_dev->chan;
 }
 
+/**
+ * dma_chan_from_dev_lock - take dma_list_mutex and convert a channel's
+ *	struct device to its dma_chan
+ * @dev: the channel's struct device, embedded in struct dma_chan_dev
+ *
+ * Returns NULL if the channel has already been unregistered. Pairs with
+ * dma_chan_from_dev_unlock(); see the dma_chan_from_dev CLASS in
+ * drivers/dma/dmaengine.h.
+ */
+struct dma_chan *dma_chan_from_dev_lock(struct device *dev)
+{
+	mutex_lock(&dma_list_mutex);
+	return dev_to_dma_chan(dev);
+}
+EXPORT_SYMBOL_GPL(dma_chan_from_dev_lock);
+
+/**
+ * dma_chan_from_dev_unlock - release the lock taken by dma_chan_from_dev_lock()
+ * @chan: unused; matches the value produced by dma_chan_from_dev_lock()
+ */
+void dma_chan_from_dev_unlock(struct dma_chan *chan)
+{
+	mutex_unlock(&dma_list_mutex);
+}
+EXPORT_SYMBOL_GPL(dma_chan_from_dev_unlock);
+
 static ssize_t memcpy_count_show(struct device *dev,
 				 struct device_attribute *attr, char *buf)
 {
@@ -1098,6 +1124,7 @@ static int __dma_async_device_channel_register(struct dma_device *device,
 
 	chan->dev->device.class = &dma_devclass;
 	chan->dev->device.parent = device->dev;
+	chan->dev->device.groups = device->chan_groups;
 	chan->dev->chan = chan;
 	chan->dev->dev_id = device->dev_id;
 	if (!name)
diff --git a/drivers/dma/dmaengine.h b/drivers/dma/dmaengine.h
index 53f16d3f0029..224126e64d54 100644
--- a/drivers/dma/dmaengine.h
+++ b/drivers/dma/dmaengine.h
@@ -182,6 +182,25 @@ dmaengine_desc_callback_valid(struct dmaengine_desc_callback *cb)
 struct dma_chan *dma_get_slave_channel(struct dma_chan *chan);
 struct dma_chan *dma_get_any_slave_channel(struct dma_device *device);
 
+/*
+ * dma_chan_from_dev_lock() / dma_chan_from_dev_unlock() bracket a critical
+ * section across which a channel's struct device can be safely converted
+ * back to its struct dma_chan: dma_chan_from_dev_lock() returns NULL if the
+ * channel has already been unregistered, and the lock it takes must be held
+ * for as long as the returned channel (or anything derived from it) is
+ * accessed.
+ *
+ * Use these through the dma_chan_from_dev CLASS below rather than calling
+ * them directly.
+ */
+struct dma_chan *dma_chan_from_dev_lock(struct device *dev);
+void dma_chan_from_dev_unlock(struct dma_chan *chan);
+
+DEFINE_CLASS(dma_chan_from_dev, struct dma_chan *,
+	     dma_chan_from_dev_unlock(_T),
+	     dma_chan_from_dev_lock(dev),
+	     struct device *dev)
+
 #ifdef CONFIG_DEBUG_FS
 #include <linux/debugfs.h>
 
diff --git a/include/linux/dmaengine.h b/include/linux/dmaengine.h
index b3d251c9734e..f4e7861b2507 100644
--- a/include/linux/dmaengine.h
+++ b/include/linux/dmaengine.h
@@ -803,6 +803,9 @@ struct dma_filter {
  * @dev: struct device reference for dma mapping api
  * @owner: owner module (automatically set based on the provided dev)
  * @chan_ida: unique channel ID
+ * @chan_groups: optional NULL-terminated array of sysfs attribute groups
+ *	added to each channel's struct device, for driver-specific per-channel
+ *	attributes
  * @src_addr_widths: bit mask of src addr widths the device supports
  *	Width is specified in bytes, e.g. for a device supporting
  *	a width of 4 the mask should have BIT(4) set.
@@ -886,6 +889,7 @@ struct dma_device {
 	struct device *dev;
 	struct module *owner;
 	struct ida chan_ida;
+	const struct attribute_group **chan_groups;
 
 	u32 src_addr_widths;
 	u32 dst_addr_widths;
-- 
2.47.3


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

* [PATCH v2 2/4] dmaengine: ioatdma: convert per-channel sysfs to chan_groups
  2026-07-27 18:48 [PATCH v2 0/4] Add sysfs interface to switchtec-dma Logan Gunthorpe
  2026-07-27 18:48 ` [PATCH v2 1/4] dmaengine: add per-channel sysfs attribute groups via chan_groups Logan Gunthorpe
@ 2026-07-27 18:48 ` Logan Gunthorpe
  2026-07-27 18:48 ` [PATCH v2 3/4] dmaengine: switchtec-dma: add config sysfs attributes Logan Gunthorpe
  2026-07-27 18:48 ` [PATCH v2 4/4] dmaengine: switchtec-dma: add pmon " Logan Gunthorpe
  3 siblings, 0 replies; 5+ messages in thread
From: Logan Gunthorpe @ 2026-07-27 18:48 UTC (permalink / raw)
  To: dmaengine, linux-kernel, Vinod Koul
  Cc: Frank Li, Christoph Hellwig, Christophe Jaillet, Dave Jiang,
	Thomas Weißschuh, Kelvin Cao, Logan Gunthorpe

Use the new dma_device.chan_groups mechanism to add ioat's "quickdata"
per-channel attributes directly to the channel's existing struct
device, instead of a second, separately managed kobject
(ioat_ktype/ioat_kobject_add()/ioat_kobject_del()).

Each show()/store() now recovers the struct dma_chan via
dev_to_dma_chan() under dma_list_mutex, the same safe pattern already
used by the core attributes, returning -ENODEV once the channel has
been unregistered rather than reading it after the fact.

Signed-off-by: Logan Gunthorpe <logang@deltatee.com>
---
 drivers/dma/ioat/dma.h   |   6 +-
 drivers/dma/ioat/init.c  |   5 +-
 drivers/dma/ioat/sysfs.c | 166 +++++++++++++++------------------------
 3 files changed, 67 insertions(+), 110 deletions(-)

diff --git a/drivers/dma/ioat/dma.h b/drivers/dma/ioat/dma.h
index e8a880f338c6..d36cc2df74ea 100644
--- a/drivers/dma/ioat/dma.h
+++ b/drivers/dma/ioat/dma.h
@@ -103,7 +103,6 @@ struct ioatdma_chan {
 	#define IOAT_CHAN_DOWN 0
 	#define IOAT_COMPLETION_ACK 1
 	#define IOAT_RESET_PENDING 2
-	#define IOAT_KOBJ_INIT_FAIL 3
 	#define IOAT_RUN 5
 	#define IOAT_CHAN_ACTIVE 6
 	struct timer_list timer;
@@ -112,7 +111,6 @@ struct ioatdma_chan {
 	dma_addr_t completion_dma;
 	u64 *completion;
 	struct tasklet_struct cleanup_task;
-	struct kobject kobj;
 
 /* ioat v2 / v3 channel attributes
  * @xfercap_log; log2 of channel max transfer length (for fast division)
@@ -190,7 +188,7 @@ struct ioat_ring_ent {
 };
 
 extern int ioat_pending_level;
-extern const struct kobj_type ioat_ktype;
+extern const struct attribute_group *ioat_groups[];
 extern struct kmem_cache *ioat_cache;
 extern struct kmem_cache *ioat_sed_cache;
 
@@ -393,8 +391,6 @@ void ioat_issue_pending(struct dma_chan *chan);
 /* IOAT Init functions */
 bool is_bwd_ioat(struct pci_dev *pdev);
 struct dca_provider *ioat_dca_init(struct pci_dev *pdev, void __iomem *iobase);
-void ioat_kobject_add(struct ioatdma_device *ioat_dma, const struct kobj_type *type);
-void ioat_kobject_del(struct ioatdma_device *ioat_dma);
 int ioat_dma_setup_interrupts(struct ioatdma_device *ioat_dma);
 void ioat_stop(struct ioatdma_chan *ioat_chan);
 #endif /* IOATDMA_H */
diff --git a/drivers/dma/ioat/init.c b/drivers/dma/ioat/init.c
index a57024c4b066..984078920c2b 100644
--- a/drivers/dma/ioat/init.c
+++ b/drivers/dma/ioat/init.c
@@ -541,8 +541,6 @@ static void ioat_dma_remove(struct ioatdma_device *ioat_dma)
 
 	ioat_disable_interrupts(ioat_dma);
 
-	ioat_kobject_del(ioat_dma);
-
 	dma_async_device_unregister(dma);
 }
 
@@ -1185,12 +1183,11 @@ static int ioat3_dma_probe(struct ioatdma_device *ioat_dma, int dca)
 		goto err_disable_interrupts;
 	}
 
+	dma->chan_groups = ioat_groups;
 	err = dma_async_device_register(&ioat_dma->dma_dev);
 	if (err)
 		goto err_disable_interrupts;
 
-	ioat_kobject_add(ioat_dma, &ioat_ktype);
-
 	if (dca)
 		ioat_dma->dca = ioat_dca_init(pdev, ioat_dma->reg_base);
 
diff --git a/drivers/dma/ioat/sysfs.c b/drivers/dma/ioat/sysfs.c
index 976134df8108..c24cf12f1ab3 100644
--- a/drivers/dma/ioat/sysfs.c
+++ b/drivers/dma/ioat/sysfs.c
@@ -14,134 +14,95 @@
 
 #include "../dmaengine.h"
 
-struct ioat_sysfs_entry {
-	struct attribute attr;
-	ssize_t (*show)(struct dma_chan *, char *);
-	ssize_t (*store)(struct dma_chan *, const char *, size_t);
-};
-
-static ssize_t cap_show(struct dma_chan *c, char *page)
+static ssize_t cap_show(struct device *dev, struct device_attribute *attr,
+			char *page)
 {
-	struct dma_device *dma = c->device;
+	struct dma_device *dma;
 
+	CLASS(dma_chan_from_dev, c)(dev);
+
+	if (!c)
+		return -ENODEV;
+
+	dma = c->device;
 	return sysfs_emit(page, "copy%s%s%s%s%s\n",
 		dma_has_cap(DMA_PQ, dma->cap_mask) ? " pq" : "",
 		dma_has_cap(DMA_PQ_VAL, dma->cap_mask) ? " pq_val" : "",
 		dma_has_cap(DMA_XOR, dma->cap_mask) ? " xor" : "",
 		dma_has_cap(DMA_XOR_VAL, dma->cap_mask) ? " xor_val" : "",
 		dma_has_cap(DMA_INTERRUPT, dma->cap_mask) ? " intr" : "");
-
 }
-static const struct ioat_sysfs_entry ioat_cap_attr = __ATTR_RO(cap);
+static DEVICE_ATTR_RO(cap);
 
-static ssize_t version_show(struct dma_chan *c, char *page)
+static ssize_t version_show(struct device *dev, struct device_attribute *attr,
+			    char *page)
 {
-	struct dma_device *dma = c->device;
-	struct ioatdma_device *ioat_dma = to_ioatdma_device(dma);
+	struct ioatdma_device *ioat_dma;
 
+	CLASS(dma_chan_from_dev, c)(dev);
+
+	if (!c)
+		return -ENODEV;
+
+	ioat_dma = to_ioatdma_device(c->device);
 	return sysfs_emit(page, "%d.%d\n",
 			   ioat_dma->version >> 4, ioat_dma->version & 0xf);
 }
-static const struct ioat_sysfs_entry ioat_version_attr = __ATTR_RO(version);
+static DEVICE_ATTR_RO(version);
 
-static ssize_t
-ioat_attr_show(struct kobject *kobj, struct attribute *attr, char *page)
+static ssize_t ring_size_show(struct device *dev, struct device_attribute *attr,
+			      char *page)
 {
-	const struct ioat_sysfs_entry *entry;
-	struct ioatdma_chan *ioat_chan;
+	CLASS(dma_chan_from_dev, c)(dev);
 
-	entry = container_of_const(attr, struct ioat_sysfs_entry, attr);
-	ioat_chan = container_of(kobj, struct ioatdma_chan, kobj);
+	if (!c)
+		return -ENODEV;
 
-	if (!entry->show)
-		return -EIO;
-	return entry->show(&ioat_chan->dma_chan, page);
-}
-
-static ssize_t
-ioat_attr_store(struct kobject *kobj, struct attribute *attr,
-const char *page, size_t count)
-{
-	const struct ioat_sysfs_entry *entry;
-	struct ioatdma_chan *ioat_chan;
-
-	entry = container_of_const(attr, struct ioat_sysfs_entry, attr);
-	ioat_chan = container_of(kobj, struct ioatdma_chan, kobj);
-
-	if (!entry->store)
-		return -EIO;
-	return entry->store(&ioat_chan->dma_chan, page, count);
-}
-
-static const struct sysfs_ops ioat_sysfs_ops = {
-	.show	= ioat_attr_show,
-	.store  = ioat_attr_store,
-};
-
-void ioat_kobject_add(struct ioatdma_device *ioat_dma, const struct kobj_type *type)
-{
-	struct dma_device *dma = &ioat_dma->dma_dev;
-	struct dma_chan *c;
-
-	list_for_each_entry(c, &dma->channels, device_node) {
-		struct ioatdma_chan *ioat_chan = to_ioat_chan(c);
-		struct kobject *parent = &c->dev->device.kobj;
-		int err;
-
-		err = kobject_init_and_add(&ioat_chan->kobj, type,
-					   parent, "quickdata");
-		if (err) {
-			dev_warn(to_dev(ioat_chan),
-				 "sysfs init error (%d), continuing...\n", err);
-			kobject_put(&ioat_chan->kobj);
-			set_bit(IOAT_KOBJ_INIT_FAIL, &ioat_chan->state);
-		}
-	}
-}
-
-void ioat_kobject_del(struct ioatdma_device *ioat_dma)
-{
-	struct dma_device *dma = &ioat_dma->dma_dev;
-	struct dma_chan *c;
-
-	list_for_each_entry(c, &dma->channels, device_node) {
-		struct ioatdma_chan *ioat_chan = to_ioat_chan(c);
-
-		if (!test_bit(IOAT_KOBJ_INIT_FAIL, &ioat_chan->state)) {
-			kobject_del(&ioat_chan->kobj);
-			kobject_put(&ioat_chan->kobj);
-		}
-	}
-}
-
-static ssize_t ring_size_show(struct dma_chan *c, char *page)
-{
 	struct ioatdma_chan *ioat_chan = to_ioat_chan(c);
 
 	return sysfs_emit(page, "%d\n", (1 << ioat_chan->alloc_order) & ~1);
 }
-static const struct ioat_sysfs_entry ring_size_attr = __ATTR_RO(ring_size);
+static DEVICE_ATTR_RO(ring_size);
 
-static ssize_t ring_active_show(struct dma_chan *c, char *page)
+static ssize_t ring_active_show(struct device *dev,
+				struct device_attribute *attr, char *page)
 {
+	CLASS(dma_chan_from_dev, c)(dev);
+
+	if (!c)
+		return -ENODEV;
+
 	struct ioatdma_chan *ioat_chan = to_ioat_chan(c);
 
 	/* ...taken outside the lock, no need to be precise */
 	return sysfs_emit(page, "%d\n", ioat_ring_active(ioat_chan));
 }
-static const struct ioat_sysfs_entry ring_active_attr = __ATTR_RO(ring_active);
+static DEVICE_ATTR_RO(ring_active);
 
-static ssize_t intr_coalesce_show(struct dma_chan *c, char *page)
+static ssize_t intr_coalesce_show(struct device *dev,
+				  struct device_attribute *attr, char *page)
 {
+	CLASS(dma_chan_from_dev, c)(dev);
+
+	if (!c)
+		return -ENODEV;
+
 	struct ioatdma_chan *ioat_chan = to_ioat_chan(c);
 
 	return sysfs_emit(page, "%d\n", ioat_chan->intr_coalesce);
 }
 
-static ssize_t intr_coalesce_store(struct dma_chan *c, const char *page,
-size_t count)
+static ssize_t intr_coalesce_store(struct device *dev,
+				   struct device_attribute *attr,
+				   const char *page, size_t count)
 {
 	int intr_coalesce = 0;
+
+	CLASS(dma_chan_from_dev, c)(dev);
+
+	if (!c)
+		return -ENODEV;
+
 	struct ioatdma_chan *ioat_chan = to_ioat_chan(c);
 
 	if (sscanf(page, "%du", &intr_coalesce) != -1) {
@@ -153,20 +114,23 @@ size_t count)
 
 	return count;
 }
+static DEVICE_ATTR_RW(intr_coalesce);
 
-static const struct ioat_sysfs_entry intr_coalesce_attr = __ATTR_RW(intr_coalesce);
-
-static const struct attribute *const ioat_attrs[] = {
-	&ring_size_attr.attr,
-	&ring_active_attr.attr,
-	&ioat_cap_attr.attr,
-	&ioat_version_attr.attr,
-	&intr_coalesce_attr.attr,
+static struct attribute *ioat_attrs[] = {
+	&dev_attr_ring_size.attr,
+	&dev_attr_ring_active.attr,
+	&dev_attr_cap.attr,
+	&dev_attr_version.attr,
+	&dev_attr_intr_coalesce.attr,
 	NULL,
 };
-ATTRIBUTE_GROUPS(ioat);
 
-const struct kobj_type ioat_ktype = {
-	.sysfs_ops = &ioat_sysfs_ops,
-	.default_groups = ioat_groups,
+static const struct attribute_group ioat_attr_group = {
+	.name = "quickdata",
+	.attrs = ioat_attrs,
+};
+
+const struct attribute_group *ioat_groups[] = {
+	&ioat_attr_group,
+	NULL,
 };
-- 
2.47.3


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

* [PATCH v2 3/4] dmaengine: switchtec-dma: add config sysfs attributes
  2026-07-27 18:48 [PATCH v2 0/4] Add sysfs interface to switchtec-dma Logan Gunthorpe
  2026-07-27 18:48 ` [PATCH v2 1/4] dmaengine: add per-channel sysfs attribute groups via chan_groups Logan Gunthorpe
  2026-07-27 18:48 ` [PATCH v2 2/4] dmaengine: ioatdma: convert per-channel sysfs to chan_groups Logan Gunthorpe
@ 2026-07-27 18:48 ` Logan Gunthorpe
  2026-07-27 18:48 ` [PATCH v2 4/4] dmaengine: switchtec-dma: add pmon " Logan Gunthorpe
  3 siblings, 0 replies; 5+ messages in thread
From: Logan Gunthorpe @ 2026-07-27 18:48 UTC (permalink / raw)
  To: dmaengine, linux-kernel, Vinod Koul
  Cc: Frank Li, Christoph Hellwig, Christophe Jaillet, Dave Jiang,
	Thomas Weißschuh, Kelvin Cao, Logan Gunthorpe

Add sysfs configuration options for switchtec-dma channels, using
dma_device.chan_groups the same way the ioatdma conversion does:
attach a "switchtec-config" attribute group directly to the channel's
existing struct device, with each show()/store() recovering the
struct dma_chan via the dma_chan_from_dev CLASS.

The read-modify-write of the shared perf_cfg register is protected by
hw_ctrl_lock, and writes are rejected with -EBUSY while the channel is
allocated to a client (hw_cfg_locked), instead of a TOCTOU-prone
chan->client_count check.

Signed-off-by: Logan Gunthorpe <logang@deltatee.com>
---
 drivers/dma/switchtec_dma.c | 179 ++++++++++++++++++++++++++++++++++++
 1 file changed, 179 insertions(+)

diff --git a/drivers/dma/switchtec_dma.c b/drivers/dma/switchtec_dma.c
index d73506b5cabc..d74eb48e8eca 100644
--- a/drivers/dma/switchtec_dma.c
+++ b/drivers/dma/switchtec_dma.c
@@ -132,6 +132,11 @@ struct switchtec_dma_chan {
 
 	/* Serialize hardware control register access */
 	spinlock_t hw_ctrl_lock;
+	/*
+	 * Set while the channel is allocated to a client; blocks sysfs
+	 * hw config changes
+	 */
+	bool hw_cfg_locked;
 
 	struct tasklet_struct desc_task;
 
@@ -1029,6 +1034,10 @@ static int switchtec_dma_alloc_chan_resources(struct dma_chan *chan)
 	dev_dbg(&chan->dev->device, "MRRS:        0x%x\n",
 		FIELD_GET(PERF_MRRS_MASK, perf_cfg));
 
+	spin_lock(&swdma_chan->hw_ctrl_lock);
+	swdma_chan->hw_cfg_locked = true;
+	spin_unlock(&swdma_chan->hw_ctrl_lock);
+
 	return SWITCHTEC_DMA_SQ_SIZE;
 
 err_ring_inactive:
@@ -1047,11 +1056,180 @@ static int switchtec_dma_alloc_chan_resources(struct dma_chan *chan)
 	return rc;
 }
 
+static __always_inline ssize_t perf_cfg_show(struct device *dev, char *page,
+					     unsigned int mask)
+{
+	struct switchtec_dma_chan *swdma_chan;
+	struct chan_fw_regs __iomem *chan_fw;
+	u32 perf_cfg;
+	int value;
+
+	CLASS(dma_chan_from_dev, c)(dev);
+
+	if (!c)
+		return -ENODEV;
+
+	swdma_chan = container_of(c, struct switchtec_dma_chan, dma_chan);
+	chan_fw = swdma_chan->mmio_chan_fw;
+
+	rcu_read_lock();
+	if (!rcu_dereference(swdma_chan->swdma_dev->pdev)) {
+		rcu_read_unlock();
+		return -ENODEV;
+	}
+
+	perf_cfg = readl(&chan_fw->perf_cfg);
+	value = field_get(mask, perf_cfg);
+
+	rcu_read_unlock();
+	return sysfs_emit(page, "0x%x\n", value);
+}
+
+static __always_inline ssize_t perf_cfg_store(struct device *dev,
+					      const char *page, size_t count,
+					      unsigned int mask)
+{
+	struct switchtec_dma_chan *swdma_chan;
+	struct chan_fw_regs __iomem *chan_fw;
+	ssize_t ret = count;
+	u32 perf_cfg;
+	int value;
+
+	CLASS(dma_chan_from_dev, c)(dev);
+
+	if (!c)
+		return -ENODEV;
+
+	swdma_chan = container_of(c, struct switchtec_dma_chan, dma_chan);
+	chan_fw = swdma_chan->mmio_chan_fw;
+
+	if (kstrtoint(page, 0, &value) < 0)
+		return -EINVAL;
+
+	if (value < 0 || value > field_max(mask))
+		return -EINVAL;
+
+	rcu_read_lock();
+	if (!rcu_dereference(swdma_chan->swdma_dev->pdev)) {
+		ret = -ENODEV;
+		goto err_unlock;
+	}
+
+	spin_lock(&swdma_chan->hw_ctrl_lock);
+	if (swdma_chan->hw_cfg_locked) {
+		spin_unlock(&swdma_chan->hw_ctrl_lock);
+		ret = -EBUSY;
+		goto err_unlock;
+	}
+
+	perf_cfg = readl(&chan_fw->perf_cfg);
+	perf_cfg = (perf_cfg & ~mask) | field_prep(mask, value);
+	writel(perf_cfg, &chan_fw->perf_cfg);
+	spin_unlock(&swdma_chan->hw_ctrl_lock);
+
+err_unlock:
+	rcu_read_unlock();
+	return ret;
+}
+
+static ssize_t burst_scale_show(struct device *dev,
+				struct device_attribute *attr, char *page)
+{
+	return perf_cfg_show(dev, page, PERF_BURST_SCALE_MASK);
+}
+
+static ssize_t burst_scale_store(struct device *dev,
+				 struct device_attribute *attr,
+				 const char *page, size_t count)
+{
+	return perf_cfg_store(dev, page, count, PERF_BURST_SCALE_MASK);
+}
+static DEVICE_ATTR_RW(burst_scale);
+
+static ssize_t mrrs_show(struct device *dev,
+			 struct device_attribute *attr, char *page)
+{
+	return perf_cfg_show(dev, page, PERF_MRRS_MASK);
+}
+
+static ssize_t mrrs_store(struct device *dev,
+			  struct device_attribute *attr,
+			  const char *page, size_t count)
+{
+	return perf_cfg_store(dev, page, count, PERF_MRRS_MASK);
+}
+static DEVICE_ATTR_RW(mrrs);
+
+static ssize_t interval_show(struct device *dev,
+			     struct device_attribute *attr, char *page)
+{
+	return perf_cfg_show(dev, page, PERF_INTERVAL_MASK);
+}
+
+static ssize_t interval_store(struct device *dev,
+			      struct device_attribute *attr,
+			      const char *page, size_t count)
+{
+	return perf_cfg_store(dev, page, count, PERF_INTERVAL_MASK);
+}
+static DEVICE_ATTR_RW(interval);
+
+static ssize_t burst_size_show(struct device *dev,
+			       struct device_attribute *attr, char *page)
+{
+	return perf_cfg_show(dev, page, PERF_BURST_SIZE_MASK);
+}
+
+static ssize_t burst_size_store(struct device *dev,
+				struct device_attribute *attr,
+				const char *page, size_t count)
+{
+	return perf_cfg_store(dev, page, count, PERF_BURST_SIZE_MASK);
+}
+static DEVICE_ATTR_RW(burst_size);
+
+static ssize_t arb_weight_show(struct device *dev,
+			       struct device_attribute *attr, char *page)
+{
+	return perf_cfg_show(dev, page, PERF_ARB_WEIGHT_MASK);
+}
+
+static ssize_t arb_weight_store(struct device *dev,
+				struct device_attribute *attr,
+				const char *page, size_t count)
+{
+	return perf_cfg_store(dev, page, count, PERF_ARB_WEIGHT_MASK);
+}
+static DEVICE_ATTR_RW(arb_weight);
+
+static struct attribute *switchtec_config_attrs[] = {
+	&dev_attr_burst_scale.attr,
+	&dev_attr_mrrs.attr,
+	&dev_attr_interval.attr,
+	&dev_attr_burst_size.attr,
+	&dev_attr_arb_weight.attr,
+	NULL,
+};
+
+static const struct attribute_group switchtec_config_group = {
+	.name = "switchtec-config",
+	.attrs = switchtec_config_attrs,
+};
+
+static const struct attribute_group *switchtec_groups[] = {
+	&switchtec_config_group,
+	NULL,
+};
+
 static void switchtec_dma_free_chan_resources(struct dma_chan *chan)
 {
 	struct switchtec_dma_chan *swdma_chan =
 		container_of(chan, struct switchtec_dma_chan, dma_chan);
 
+	spin_lock(&swdma_chan->hw_ctrl_lock);
+	swdma_chan->hw_cfg_locked = false;
+	spin_unlock(&swdma_chan->hw_ctrl_lock);
+
 	spin_lock_bh(&swdma_chan->submit_lock);
 	swdma_chan->ring_active = false;
 	spin_unlock_bh(&swdma_chan->submit_lock);
@@ -1318,6 +1496,7 @@ static int switchtec_dma_create(struct pci_dev *pdev)
 	dma->device_terminate_all = switchtec_dma_terminate_all;
 	dma->device_synchronize = switchtec_dma_synchronize;
 	dma->device_release = switchtec_dma_release;
+	dma->chan_groups = switchtec_groups;
 
 	rc = dma_async_device_register(dma);
 	if (rc) {
-- 
2.47.3


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

* [PATCH v2 4/4] dmaengine: switchtec-dma: add pmon sysfs attributes
  2026-07-27 18:48 [PATCH v2 0/4] Add sysfs interface to switchtec-dma Logan Gunthorpe
                   ` (2 preceding siblings ...)
  2026-07-27 18:48 ` [PATCH v2 3/4] dmaengine: switchtec-dma: add config sysfs attributes Logan Gunthorpe
@ 2026-07-27 18:48 ` Logan Gunthorpe
  3 siblings, 0 replies; 5+ messages in thread
From: Logan Gunthorpe @ 2026-07-27 18:48 UTC (permalink / raw)
  To: dmaengine, linux-kernel, Vinod Koul
  Cc: Frank Li, Christoph Hellwig, Christophe Jaillet, Dave Jiang,
	Thomas Weißschuh, Kelvin Cao, Logan Gunthorpe

Switchtec hardware exposes some performance monitor registers which
can be used to monitor various statistics of the hardware.

Expose these as a "switchtec-pmon" sysfs attribute group, alongside
the "switchtec-config" group added previously.

The various registers are read through a small shared pmon_show()
helper parameterized by a reader function pointer, rather than a
macro, to avoid generating the whole read + rcu_read_lock/unlock +
sysfs_emit sequence per attribute.

Signed-off-by: Logan Gunthorpe <logang@deltatee.com>
---
 drivers/dma/switchtec_dma.c | 273 ++++++++++++++++++++++++++++++++++++
 1 file changed, 273 insertions(+)

diff --git a/drivers/dma/switchtec_dma.c b/drivers/dma/switchtec_dma.c
index d74eb48e8eca..e0976aa74068 100644
--- a/drivers/dma/switchtec_dma.c
+++ b/drivers/dma/switchtec_dma.c
@@ -12,6 +12,7 @@
 #include <linux/pci.h>
 #include <linux/delay.h>
 #include <linux/iopoll.h>
+#include <linux/io-64-nonatomic-lo-hi.h>
 
 #include "dmaengine.h"
 
@@ -91,6 +92,12 @@ struct chan_hw_regs {
 #define SE_BUF_LEN_MASK		GENMASK_U32(20, 12)
 #define SE_THRESH_MASK		GENMASK_U32(31, 23)
 
+#define SWITCHTEC_LAT_SE_FETCH   BIT(0)
+#define SWITCHTEC_LAT_VDM        BIT(1)
+#define SWITCHTEC_LAT_RD_IMM     BIT(2)
+#define SWITCHTEC_LAT_FW_NP      BIT(3)
+#define SWITCHTEC_LAT_SE_PROCESS BIT(4)
+
 #define SWITCHTEC_CHAN_ENABLE	BIT(1)
 
 struct chan_fw_regs {
@@ -1216,8 +1223,274 @@ static const struct attribute_group switchtec_config_group = {
 	.attrs = switchtec_config_attrs,
 };
 
+static ssize_t pmon_show(struct device *dev, char *page,
+			 u64 (*reader)(struct chan_fw_regs __iomem *chan_fw))
+{
+	struct switchtec_dma_chan *swdma_chan;
+	u64 val;
+
+	CLASS(dma_chan_from_dev, c)(dev);
+
+	if (!c)
+		return -ENODEV;
+
+	swdma_chan = container_of(c, struct switchtec_dma_chan, dma_chan);
+
+	rcu_read_lock();
+	if (!rcu_dereference(swdma_chan->swdma_dev->pdev)) {
+		rcu_read_unlock();
+		return -ENODEV;
+	}
+
+	val = reader(swdma_chan->mmio_chan_fw);
+	rcu_read_unlock();
+
+	return sysfs_emit(page, "0x%llx\n", val);
+}
+
+static u64 read_se_count(struct chan_fw_regs __iomem *chan_fw)
+{
+	return lo_hi_readq(&chan_fw->perf_fetched_se_cnt_lo);
+}
+
+static ssize_t se_count_show(struct device *dev,
+			     struct device_attribute *attr, char *page)
+{
+	return pmon_show(dev, page, read_se_count);
+}
+static DEVICE_ATTR_RO(se_count);
+
+static u64 read_byte_count(struct chan_fw_regs __iomem *chan_fw)
+{
+	return lo_hi_readq(&chan_fw->perf_byte_cnt_lo);
+}
+
+static ssize_t byte_count_show(struct device *dev,
+			       struct device_attribute *attr, char *page)
+{
+	return pmon_show(dev, page, read_byte_count);
+}
+static DEVICE_ATTR_RO(byte_count);
+
+static u64 read_se_pending(struct chan_fw_regs __iomem *chan_fw)
+{
+	return readw(&chan_fw->perf_se_pending);
+}
+
+static ssize_t se_pending_show(struct device *dev,
+			       struct device_attribute *attr, char *page)
+{
+	return pmon_show(dev, page, read_se_pending);
+}
+static DEVICE_ATTR_RO(se_pending);
+
+static u64 read_se_buf_empty(struct chan_fw_regs __iomem *chan_fw)
+{
+	return readw(&chan_fw->perf_se_buf_empty);
+}
+
+static ssize_t se_buf_empty_show(struct device *dev,
+				 struct device_attribute *attr, char *page)
+{
+	return pmon_show(dev, page, read_se_buf_empty);
+}
+static DEVICE_ATTR_RO(se_buf_empty);
+
+static u64 read_chan_idle(struct chan_fw_regs __iomem *chan_fw)
+{
+	return readl(&chan_fw->perf_chan_idle);
+}
+
+static ssize_t chan_idle_show(struct device *dev,
+			      struct device_attribute *attr, char *page)
+{
+	return pmon_show(dev, page, read_chan_idle);
+}
+static DEVICE_ATTR_RO(chan_idle);
+
+static u64 read_latency_max(struct chan_fw_regs __iomem *chan_fw)
+{
+	return readl(&chan_fw->perf_lat_max);
+}
+
+static ssize_t latency_max_show(struct device *dev,
+				struct device_attribute *attr, char *page)
+{
+	return pmon_show(dev, page, read_latency_max);
+}
+static DEVICE_ATTR_RO(latency_max);
+
+static u64 read_latency_min(struct chan_fw_regs __iomem *chan_fw)
+{
+	return readl(&chan_fw->perf_lat_min);
+}
+
+static ssize_t latency_min_show(struct device *dev,
+				struct device_attribute *attr, char *page)
+{
+	return pmon_show(dev, page, read_latency_min);
+}
+static DEVICE_ATTR_RO(latency_min);
+
+static u64 read_latency_last(struct chan_fw_regs __iomem *chan_fw)
+{
+	return readl(&chan_fw->perf_lat_last);
+}
+
+static ssize_t latency_last_show(struct device *dev,
+				 struct device_attribute *attr, char *page)
+{
+	return pmon_show(dev, page, read_latency_last);
+}
+static DEVICE_ATTR_RO(latency_last);
+
+static ssize_t latency_selector_show(struct device *dev,
+				     struct device_attribute *attr, char *page)
+{
+	struct switchtec_dma_chan *swdma_chan;
+	struct chan_fw_regs __iomem *chan_fw;
+	u32 lat = 0;
+	int len;
+
+	CLASS(dma_chan_from_dev, c)(dev);
+
+	if (!c)
+		return -ENODEV;
+
+	swdma_chan = container_of(c, struct switchtec_dma_chan, dma_chan);
+	chan_fw = swdma_chan->mmio_chan_fw;
+
+	rcu_read_lock();
+	if (!rcu_dereference(swdma_chan->swdma_dev->pdev)) {
+		rcu_read_unlock();
+		return -ENODEV;
+	}
+
+	lat = readl(&chan_fw->perf_latency_selector);
+	rcu_read_unlock();
+
+	len = sysfs_emit(page,
+			 "To select a latency type, write the type number\n");
+	len += sysfs_emit_at(page, len, "(1 ~ 5) to latency_selector\n\n");
+
+	len += sysfs_emit_at(page, len, "Latency Types:\n");
+	len += sysfs_emit_at(page, len, "(1) SE Fetch latency");
+	if (lat & SWITCHTEC_LAT_SE_FETCH)
+		len += sysfs_emit_at(page, len, " (*)\n");
+	else
+		len += sysfs_emit_at(page, len, "\n");
+
+	len += sysfs_emit_at(page, len, "(2) VDM latency");
+	if (lat & SWITCHTEC_LAT_VDM)
+		len += sysfs_emit_at(page, len, " (*)\n");
+	else
+		len += sysfs_emit_at(page, len, "\n");
+
+	len += sysfs_emit_at(page, len, "(3) Read Immediate latency");
+	if (lat & SWITCHTEC_LAT_RD_IMM)
+		len += sysfs_emit_at(page, len, " (*)\n");
+	else
+		len += sysfs_emit_at(page, len, "\n");
+
+	len += sysfs_emit_at(page, len, "(4) SE Processing latency");
+	if (lat & SWITCHTEC_LAT_SE_PROCESS)
+		len += sysfs_emit_at(page, len, " (*)\n");
+	else
+		len += sysfs_emit_at(page, len, "\n");
+
+	len += sysfs_emit_at(page, len, "(5) FW NP TLP latency");
+	if (lat & SWITCHTEC_LAT_FW_NP)
+		len += sysfs_emit_at(page, len, " (*)\n");
+	else
+		len += sysfs_emit_at(page, len, "\n");
+
+	return len;
+}
+
+static ssize_t latency_selector_store(struct device *dev,
+				      struct device_attribute *attr,
+				      const char *page, size_t count)
+{
+	struct switchtec_dma_chan *swdma_chan;
+	struct chan_fw_regs __iomem *chan_fw;
+	ssize_t ret = count;
+	int lat_type;
+
+	CLASS(dma_chan_from_dev, c)(dev);
+
+	if (!c)
+		return -ENODEV;
+
+	swdma_chan = container_of(c, struct switchtec_dma_chan, dma_chan);
+	chan_fw = swdma_chan->mmio_chan_fw;
+
+	if (kstrtoint(page, 0, &lat_type) < 0)
+		return -EINVAL;
+
+	switch (lat_type) {
+	case 1:
+		lat_type = SWITCHTEC_LAT_SE_FETCH;
+		break;
+	case 2:
+		lat_type = SWITCHTEC_LAT_VDM;
+		break;
+	case 3:
+		lat_type = SWITCHTEC_LAT_RD_IMM;
+		break;
+	case 4:
+		lat_type = SWITCHTEC_LAT_SE_PROCESS;
+		break;
+	case 5:
+		lat_type = SWITCHTEC_LAT_FW_NP;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	rcu_read_lock();
+	if (!rcu_dereference(swdma_chan->swdma_dev->pdev)) {
+		ret = -ENODEV;
+		goto err_unlock;
+	}
+
+	spin_lock(&swdma_chan->hw_ctrl_lock);
+	if (swdma_chan->hw_cfg_locked) {
+		spin_unlock(&swdma_chan->hw_ctrl_lock);
+		ret = -EBUSY;
+		goto err_unlock;
+	}
+
+	writel(lat_type, &chan_fw->perf_latency_selector);
+	spin_unlock(&swdma_chan->hw_ctrl_lock);
+
+err_unlock:
+	rcu_read_unlock();
+
+	return ret;
+}
+static DEVICE_ATTR_RW(latency_selector);
+
+static struct attribute *switchtec_pmon_attrs[] = {
+	&dev_attr_se_count.attr,
+	&dev_attr_byte_count.attr,
+	&dev_attr_se_pending.attr,
+	&dev_attr_se_buf_empty.attr,
+	&dev_attr_chan_idle.attr,
+	&dev_attr_latency_max.attr,
+	&dev_attr_latency_min.attr,
+	&dev_attr_latency_last.attr,
+	&dev_attr_latency_selector.attr,
+	NULL,
+};
+
+static const struct attribute_group switchtec_pmon_group = {
+	.name = "switchtec-pmon",
+	.attrs = switchtec_pmon_attrs,
+};
+
 static const struct attribute_group *switchtec_groups[] = {
 	&switchtec_config_group,
+	&switchtec_pmon_group,
 	NULL,
 };
 
-- 
2.47.3


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

end of thread, other threads:[~2026-07-27 19:02 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-27 18:48 [PATCH v2 0/4] Add sysfs interface to switchtec-dma Logan Gunthorpe
2026-07-27 18:48 ` [PATCH v2 1/4] dmaengine: add per-channel sysfs attribute groups via chan_groups Logan Gunthorpe
2026-07-27 18:48 ` [PATCH v2 2/4] dmaengine: ioatdma: convert per-channel sysfs to chan_groups Logan Gunthorpe
2026-07-27 18:48 ` [PATCH v2 3/4] dmaengine: switchtec-dma: add config sysfs attributes Logan Gunthorpe
2026-07-27 18:48 ` [PATCH v2 4/4] dmaengine: switchtec-dma: add pmon " Logan Gunthorpe

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox