[PATCH v4 02/10] dmaengine: Support bus widths of 32 bytes and above
From: Nuno Sá
Date: Fri Sep 11 2026 - 13:40:11 EST
The src_addr_widths and dst_addr_widths capability masks encode each
supported width as a bit whose position equals the corresponding
enum dma_slave_buswidth value (e.g. DMA_SLAVE_BUSWIDTH_4_BYTES sets bit
4). As these masks are plain u32, widths of 32 bytes and above
(DMA_SLAVE_BUSWIDTH_32/64/128_BYTES map to bits 32, 64 and 128) cannot
be represented at all.
Introduce bitmap-based bus width capabilities that span the full enum
range, through a new dma_buswidth_mask_t type modeled after
dma_cap_mask_t. To allow DMA controller drivers to be converted
incrementally, the legacy dma_device u32 fields are kept alongside the
new masks and the core folds a legacy-only driver's u32 into the mask
when the device is registered, so consumers only ever have to look at
the mask.
The accessors live in a new include/linux/dma/engine/widthmask.h instead
of in linux/dmaengine.h, so that only their users pay for the
linux/bitmap.h include. They all take a dma_buswidth_mask_t, which means
the interface will not change once the legacy fields are dropped.
The fold is bidirectional while both representations coexist. A driver
that only fills in the legacy u32 gets its mask derived from it, so the
consumers already converted see it. A driver that only fills in the mask
gets its legacy u32 derived from the mask, so the consumers not
converted yet, which read the legacy dma_slave_caps fields, keep working.
On top of that, dma_get_slave_caps() derives the legacy dma_slave_caps
masks from the new ones when a device_caps() callback adjusted them, so
that a converted controller narrowing its per-channel capabilities is
still seen by the consumers not converted yet, while a driver adjusting
the legacy masks directly keeps working.
Signed-off-by: Nuno Sá <nuno.sa@xxxxxxxxxx>
---
drivers/dma/dmaengine.c | 52 ++++++++++-
include/linux/dma/engine/types.h | 14 +++
include/linux/dma/engine/widthmask.h | 166 +++++++++++++++++++++++++++++++++++
include/linux/dmaengine.h | 38 +++++---
4 files changed, 257 insertions(+), 13 deletions(-)
diff --git a/drivers/dma/dmaengine.c b/drivers/dma/dmaengine.c
index 6ffd8bd82154..8e68921cf01d 100644
--- a/drivers/dma/dmaengine.c
+++ b/drivers/dma/dmaengine.c
@@ -33,7 +33,9 @@
#include <linux/acpi.h>
#include <linux/acpi_dma.h>
+#include <linux/bitmap.h>
#include <linux/device.h>
+#include <linux/dma/engine/widthmask.h>
#include <linux/dma-mapping.h>
#include <linux/dmaengine.h>
#include <linux/hardirq.h>
@@ -592,8 +594,11 @@ int dma_get_slave_caps(struct dma_chan *chan, struct dma_slave_caps *caps)
if (!device->directions)
return -ENXIO;
+ dma_bus_width_copy(caps->src_bus_widths, device->src_bus_widths);
+ dma_bus_width_copy(caps->dst_bus_widths, device->dst_bus_widths);
caps->src_addr_widths = device->src_addr_widths;
caps->dst_addr_widths = device->dst_addr_widths;
+
caps->directions = device->directions;
caps->min_burst = device->min_burst;
caps->max_burst = device->max_burst;
@@ -611,9 +616,31 @@ int dma_get_slave_caps(struct dma_chan *chan, struct dma_slave_caps *caps)
* callback to override the generic capabilities with
* channel-specific ones.
*/
- if (device->device_caps)
+ if (device->device_caps) {
device->device_caps(chan, caps);
+ /*
+ * A driver already converted to the bus width interface
+ * adjusts the masks, so derive the legacy capabilities from
+ * them for the consumers not converted yet. Drivers not
+ * converted adjust the legacy capabilities directly, in which
+ * case there is nothing to do.
+ *
+ * Goes away with the legacy dma_slave_caps fields.
+ */
+ if (!bitmap_equal(caps->src_bus_widths.bits,
+ device->src_bus_widths.bits,
+ DMA_SLAVE_BUSWIDTH_MAX))
+ caps->src_addr_widths = bitmap_read(caps->src_bus_widths.bits,
+ 0, 32);
+
+ if (!bitmap_equal(caps->dst_bus_widths.bits,
+ device->dst_bus_widths.bits,
+ DMA_SLAVE_BUSWIDTH_MAX))
+ caps->dst_addr_widths = bitmap_read(caps->dst_bus_widths.bits,
+ 0, 32);
+ }
+
return 0;
}
EXPORT_SYMBOL_GPL(dma_get_slave_caps);
@@ -1170,6 +1197,27 @@ void dma_async_device_channel_unregister(struct dma_device *device,
}
EXPORT_SYMBOL_GPL(dma_async_device_channel_unregister);
+/*
+ * DMA controller drivers not converted to the bus width helpers only fill in
+ * the legacy u32 masks, which cannot hold widths of 32 bytes and above. Fold
+ * them into the mask so that consumers only ever have to look at the mask.
+ *
+ * Goes away with the legacy dma_device fields.
+ */
+static void dma_device_fold_legacy_bus_widths(struct dma_device *device)
+{
+ if (bitmap_empty(device->src_bus_widths.bits, DMA_SLAVE_BUSWIDTH_MAX))
+ bitmap_from_arr32(device->src_bus_widths.bits, &device->src_addr_widths, 32);
+ /* consumers not converted yet still read the legacy caps */
+ else if (!device->src_addr_widths)
+ device->src_addr_widths = bitmap_read(device->src_bus_widths.bits, 0, 32);
+
+ if (bitmap_empty(device->dst_bus_widths.bits, DMA_SLAVE_BUSWIDTH_MAX))
+ bitmap_from_arr32(device->dst_bus_widths.bits, &device->dst_addr_widths, 32);
+ else if (!device->dst_addr_widths)
+ device->dst_addr_widths = bitmap_read(device->dst_bus_widths.bits, 0, 32);
+}
+
/**
* dma_async_device_register - registers DMA devices found
* @device: pointer to &struct dma_device
@@ -1231,6 +1279,8 @@ int dma_async_device_register(struct dma_device *device)
dev_dbg(device->dev,
"WARN: Device release is not defined so it is not safe to unbind this driver while in use\n");
+ dma_device_fold_legacy_bus_widths(device);
+
kref_init(&device->ref);
/* note: this only matters in the
diff --git a/include/linux/dma/engine/types.h b/include/linux/dma/engine/types.h
index 2e8a266e42ef..41c8771f9c0d 100644
--- a/include/linux/dma/engine/types.h
+++ b/include/linux/dma/engine/types.h
@@ -24,6 +24,7 @@
* @DMA_SLAVE_BUSWIDTH_32_BYTES: 32 bytes wide bus
* @DMA_SLAVE_BUSWIDTH_64_BYTES: 64 bytes wide bus
* @DMA_SLAVE_BUSWIDTH_128_BYTES: 128 bytes wide bus
+ * @DMA_SLAVE_BUSWIDTH_MAX: number of bus widths, not a valid width
*/
enum dma_slave_buswidth {
DMA_SLAVE_BUSWIDTH_UNDEFINED = 0,
@@ -36,6 +37,19 @@ enum dma_slave_buswidth {
DMA_SLAVE_BUSWIDTH_32_BYTES = 32,
DMA_SLAVE_BUSWIDTH_64_BYTES = 64,
DMA_SLAVE_BUSWIDTH_128_BYTES = 128,
+ DMA_SLAVE_BUSWIDTH_MAX
};
+/**
+ * typedef dma_buswidth_mask_t - bus width capabilities bitmap modeled after
+ * dma_cap_mask_t.
+ *
+ * Each supported bus width is represented by the bit whose position equals the
+ * corresponding enum dma_slave_buswidth value, e.g. a device supporting a bus
+ * width of 4 bytes has bit 4 set.
+ */
+typedef struct {
+ DECLARE_BITMAP(bits, DMA_SLAVE_BUSWIDTH_MAX);
+} dma_buswidth_mask_t;
+
#endif /* LINUX_DMA_ENGINE_TYPES_H */
diff --git a/include/linux/dma/engine/widthmask.h b/include/linux/dma/engine/widthmask.h
new file mode 100644
index 000000000000..eb50fc2f81cc
--- /dev/null
+++ b/include/linux/dma/engine/widthmask.h
@@ -0,0 +1,166 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Bus width capabilities of DMA engine devices and channels.
+ */
+#ifndef LINUX_DMA_ENGINE_WIDTHMASK_H
+#define LINUX_DMA_ENGINE_WIDTHMASK_H
+
+#include <linux/bitmap.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+
+#include <linux/dma/engine/types.h>
+
+/**
+ * dma_bus_width_valid - test if a bus width is a valid one
+ * @width: bus width to validate
+ *
+ * Return: true if @width is a valid &enum dma_slave_buswidth, false otherwise.
+ */
+static inline bool dma_bus_width_valid(enum dma_slave_buswidth width)
+{
+ switch (width) {
+ case DMA_SLAVE_BUSWIDTH_UNDEFINED:
+ case DMA_SLAVE_BUSWIDTH_1_BYTE:
+ case DMA_SLAVE_BUSWIDTH_2_BYTES:
+ case DMA_SLAVE_BUSWIDTH_3_BYTES:
+ case DMA_SLAVE_BUSWIDTH_4_BYTES:
+ case DMA_SLAVE_BUSWIDTH_8_BYTES:
+ case DMA_SLAVE_BUSWIDTH_16_BYTES:
+ case DMA_SLAVE_BUSWIDTH_32_BYTES:
+ case DMA_SLAVE_BUSWIDTH_64_BYTES:
+ case DMA_SLAVE_BUSWIDTH_128_BYTES:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static inline int __dma_bus_width_set_many(dma_buswidth_mask_t *mask,
+ const enum dma_slave_buswidth *widths,
+ unsigned int n_widths)
+{
+ for (unsigned int i = 0; i < n_widths; i++) {
+ if (!dma_bus_width_valid(widths[i]))
+ return -EINVAL;
+
+ __set_bit(widths[i], mask->bits);
+ }
+
+ return 0;
+}
+
+/**
+ * dma_bus_width_set_many - set the supported bus widths
+ * @mask: bus width mask
+ * @widths: array of supported bus widths
+ * @n_widths: number of entries in @widths
+ *
+ * Return: 0 on success, -EINVAL if @widths contains an invalid bus width. Note
+ * that the bus widths validated before the failing one are still set.
+ */
+#define dma_bus_width_set_many(mask, widths, n_widths) \
+ __dma_bus_width_set_many(&(mask), (widths), (n_widths))
+
+/**
+ * dma_bus_width_set - set a single supported bus width
+ * @mask: bus width mask
+ * @width: supported bus width
+ *
+ * Return: 0 on success, -EINVAL if @width is invalid.
+ */
+#define dma_bus_width_set(mask, width) \
+ __dma_bus_width_set_many(&(mask), (const enum dma_slave_buswidth[]){ (width) }, 1)
+
+static inline int __dma_bus_width_clear(dma_buswidth_mask_t *mask,
+ enum dma_slave_buswidth width)
+{
+ if (!dma_bus_width_valid(width))
+ return -EINVAL;
+
+ __clear_bit(width, mask->bits);
+
+ return 0;
+}
+
+/**
+ * dma_bus_width_clear - remove a bus width from a bus width mask
+ * @mask: bus width mask
+ * @width: bus width to clear
+ *
+ * Return: 0 on success, -EINVAL if @width is invalid.
+ */
+#define dma_bus_width_clear(mask, width) __dma_bus_width_clear(&(mask), (width))
+
+static inline bool __dma_bus_width_test(const dma_buswidth_mask_t *mask,
+ enum dma_slave_buswidth width)
+{
+ if (!dma_bus_width_valid(width))
+ return false;
+
+ return test_bit(width, mask->bits);
+}
+
+/**
+ * dma_bus_width_test - test if a bus width is part of a bus width mask
+ * @mask: bus width mask
+ * @width: bus width to test
+ *
+ * Return: true if @width is set in @mask, false otherwise.
+ */
+#define dma_bus_width_test(mask, width) __dma_bus_width_test(&(mask), (width))
+
+static inline enum dma_slave_buswidth
+__dma_bus_width_min(const dma_buswidth_mask_t *mask)
+{
+ enum dma_slave_buswidth width;
+
+ width = find_first_bit(mask->bits, DMA_SLAVE_BUSWIDTH_MAX);
+ if (width == DMA_SLAVE_BUSWIDTH_MAX)
+ return DMA_SLAVE_BUSWIDTH_UNDEFINED;
+
+ return width;
+}
+
+/**
+ * dma_bus_width_min - get the smallest bus width of a bus width mask
+ * @mask: bus width mask
+ *
+ * Return: the smallest bus width set in @mask, or
+ * %DMA_SLAVE_BUSWIDTH_UNDEFINED if @mask is empty.
+ */
+#define dma_bus_width_min(mask) __dma_bus_width_min(&(mask))
+
+static inline void __dma_bus_width_copy(dma_buswidth_mask_t *dst,
+ const dma_buswidth_mask_t *src)
+{
+ bitmap_copy(dst->bits, src->bits, DMA_SLAVE_BUSWIDTH_MAX);
+}
+
+/**
+ * dma_bus_width_copy - copy a bus width mask
+ * @dst: bus width mask to copy to
+ * @src: bus width mask to copy from
+ */
+#define dma_bus_width_copy(dst, src) __dma_bus_width_copy(&(dst), &(src))
+
+static inline bool __dma_bus_width_and(dma_buswidth_mask_t *dst,
+ const dma_buswidth_mask_t *src1,
+ const dma_buswidth_mask_t *src2)
+{
+ return bitmap_and(dst->bits, src1->bits, src2->bits,
+ DMA_SLAVE_BUSWIDTH_MAX);
+}
+
+/**
+ * dma_bus_width_and - intersect two bus width masks
+ * @dst: bus width mask to store the result in
+ * @src1: first bus width mask
+ * @src2: second bus width mask
+ *
+ * Return: true if @dst has at least one bus width set, false otherwise.
+ */
+#define dma_bus_width_and(dst, src1, src2) \
+ __dma_bus_width_and(&(dst), &(src1), &(src2))
+
+#endif /* LINUX_DMA_ENGINE_WIDTHMASK_H */
diff --git a/include/linux/dmaengine.h b/include/linux/dmaengine.h
index 573e5ea34707..8236a1b274fb 100644
--- a/include/linux/dmaengine.h
+++ b/include/linux/dmaengine.h
@@ -5,6 +5,7 @@
#ifndef LINUX_DMAENGINE_H
#define LINUX_DMAENGINE_H
+#include <linux/bitops.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/uio.h>
@@ -481,10 +482,11 @@ enum dma_residue_granularity {
/**
* struct dma_slave_caps - expose capabilities of a slave channel only
- * @src_addr_widths: bit mask of src addr widths the channel supports.
- * Width is specified in bytes, e.g. for a channel supporting
- * a width of 4 the mask should have BIT(4) set.
- * @dst_addr_widths: bit mask of dst addr widths the channel supports
+ * @src_bus_widths: mask of source bus widths the channel supports.
+ * @src_addr_widths: legacy bit mask of source bus widths the channel supports.
+ * @dst_bus_widths: mask of destination bus widths the channel supports.
+ * @dst_addr_widths: legacy bit mask of destination bus widths the channel
+ * supports.
* @directions: bit mask of slave directions the channel supports.
* Since the enum dma_transfer_direction is not defined as bit flag for
* each type, the dma controller should set BIT(<TYPE>) and same
@@ -503,8 +505,14 @@ enum dma_residue_granularity {
* resubmitted multiple times
*/
struct dma_slave_caps {
- u32 src_addr_widths;
- u32 dst_addr_widths;
+ struct {
+ dma_buswidth_mask_t src_bus_widths;
+ u32 src_addr_widths;
+ };
+ struct {
+ dma_buswidth_mask_t dst_bus_widths;
+ u32 dst_addr_widths;
+ };
u32 directions;
u32 min_burst;
u32 max_burst;
@@ -797,10 +805,10 @@ 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
- * @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.
- * @dst_addr_widths: bit mask of dst addr widths the device supports
+ * @src_bus_widths: mask of source bus widths the device supports.
+ * @src_addr_widths: legacy bit mask of source bus widths the device supports.
+ * @dst_bus_widths: mask of destination bus widths the device supports.
+ * @dst_addr_widths: legacy bit mask of destination bus widths the device supports.
* @directions: bit mask of slave directions the device supports.
* Since the enum dma_transfer_direction is not defined as bit flag for
* each type, the dma controller should set BIT(<TYPE>) and same
@@ -882,8 +890,14 @@ struct dma_device {
struct module *owner;
struct ida chan_ida;
- u32 src_addr_widths;
- u32 dst_addr_widths;
+ struct {
+ dma_buswidth_mask_t src_bus_widths;
+ u32 src_addr_widths;
+ };
+ struct {
+ dma_buswidth_mask_t dst_bus_widths;
+ u32 dst_addr_widths;
+ };
u32 directions;
u32 min_burst;
u32 max_burst;
--
2.55.0