[PATCH v2 3/4] dmaengine: switchtec-dma: add config sysfs attributes
Logan Gunthorpe <[email protected]> Mon, 27 Jul 2026 12:48:43 -0600
| Newsgroups | org.kernel.vger.dmaengine,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <[email protected]> |
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 <[email protected]> --- 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