mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Karl Mehltretter <kmehltretter@gmail.com>
To: Arnd Bergmann <arnd@arndb.de>
Cc: Russell King <linux@armlinux.org.uk>,
	 Hans Ulli Kroll <ulli.kroll@googlemail.com>,
	Robin Murphy <robin.murphy@arm.com>,
	 Marek Szyprowski <m.szyprowski@samsung.com>,
	Will Deacon <will@kernel.org>, Christoph Hellwig <hch@lst.de>,
	 Ard Biesheuvel <ardb@kernel.org>,
	linux-arm-kernel@lists.infradead.org,
	 linux-kernel@vger.kernel.org, stable@vger.kernel.org,
	Linus Walleij <linusw@kernel.org>
Subject: Re: [PATCH 0/2] ARM: preserve DMA_FROM_DEVICE buffer contents
Date: Sat, 12 Sep 2026 08:59:01 +0200	[thread overview]
Message-ID: <aqT3UcHPXHIwlqqd@gmail.com> (raw)
In-Reply-To: <2ea28a17-f36f-4dfb-8e2a-375e7a3a4d6b@app.fastmail.com>

On Thu, Sep 10, 2026 at 11:14:28AM +0100, Arnd Bergmann wrote:

Hi Arnd,

> - actually measure the performance overhead: you already did the
>   work to test this on three separate arm implementations but did
>   not share performance numbers.
>   Can you quantify how much this costs us on the hardware you used?
> 

I measured DMA_FROM_DEVICE map/unmap round trips without a device transfer.
The 1 MiB results were unchanged within noise on Pi 400 and SAM9X75, and
faster on ARM11. The only reproducible increase was about 22 ns for a
256 B round trip on Pi 400.

These are the mean times per round trip. I wrote to the dirty buffers
before each iteration and left the clean buffers untouched.

                                         control     patched       change
  Cortex-A72, v7, 256 B dirty           353 ns         376 ns       +6.3%
  Cortex-A72, v7, 1 MiB dirty        73.840 us      73.994 us       +0.21%
  Cortex-A72, v7, 1 MiB clean        73.108 us      73.115 us       +0.01%

  ARM926EJ-S, legacy, 256 B dirty      1.208 us       1.203 us       -0.4%
  ARM926EJ-S, legacy, 1 MiB dirty    656.888 us     656.827 us       -0.009%
  ARM926EJ-S, legacy, 1 MiB clean    656.608 us     656.601 us       -0.001%

  ARM11 MPCore, v6, 256 B dirty        4.639 us       3.095 us      -33.3%
  ARM11 MPCore, v6, 1 MiB dirty       16.380 ms      15.550 ms       -5.1%
  ARM11 MPCore, v6, 1 MiB clean       16.380 ms      15.560 ms       -5.0%

Pi 400 and SAM9X75 used 786262be6048 and GCC 13.3, with three boots per
kernel. On Pi 400, only the v7 map change from patch 1 takes effect.
On SAM9X75, patch 2 changes map-time invalidate to clean-and-invalidate.
Unmap remains a no-op.

ARM11 used a 5.11-based tree and Clang/LLD 22.1.8 on both sides. Its clock
has 4 ms resolution, so I timed batches and subtracted a separate
memset-only batch for dirty buffers. I alternated kernels for five boots
each, with five batches per scenario per boot and 500,000 iterations per
256 B batch. Every patched boot's mean was below every control boot's
mean in all three scenarios.

Both ARM11 kernels invalidate at unmap, as current mainline does.
They differ only in whether they invalidate or clean at map time.

Below is the benchmark source used on Pi 400 and SAM9X75.

----- dma_from_device_benchmark.c -----
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Streaming DMA_FROM_DEVICE map/unmap cost benchmark.
 *
 * Times dma_map_single()+dma_unmap_single() round trips for a few
 * buffer size / dirty-state scenarios, to quantify the cost of the
 * architecture's cache-maintenance choice for DMA_FROM_DEVICE (e.g.
 * invalidate-at-map vs clean-at-map). No real DMA hardware is used or
 * required: only the architecture's cache-maintenance side effects of
 * the streaming DMA API are measured, against a throwaway platform
 * device, so this runs unmodified on any architecture.
 *
 * insmod dma_from_device_benchmark.ko and read dmesg for one line per
 * scenario: mean/min/max nanoseconds per round trip and ns per KiB.
 */
#include <linux/dma-mapping.h>
#include <linux/interrupt.h>
#include <linux/ktime.h>
#include <linux/math64.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/slab.h>

struct scenario {
	const char *name;
	size_t size;
	unsigned int reps;
	bool dirty;
};

static const struct scenario scenarios[] = {
	{ "small-dirty-256B", 256,      5000, true  },
	{ "large-dirty-1MiB", 1 << 20,  300,  true  },
	{ "large-clean-1MiB", 1 << 20,  300,  false },
};

static struct platform_device *pdev;

static int run_scenario(struct device *dev, const struct scenario *sc)
{
	u64 sum = 0, min = U64_MAX, max = 0;
	unsigned int i;
	u8 *buf;
	int ret = 0;

	buf = kmalloc(sc->size, GFP_KERNEL);
	if (!buf)
		return -ENOMEM;

	for (i = 0; i < sc->reps; i++) {
		unsigned long flags;
		dma_addr_t dma;
		u64 t0, t1, d;

		/* Re-dirty every line each pass; a "clean" scenario never
		 * writes buf at all, so it stays whatever the allocator left
		 * it as (steady-state after the first map/unmap invalidates
		 * it out of cache).
		 */
		if (sc->dirty)
			memset(buf, (u8)(i | 1), sc->size);

		local_irq_save(flags);
		t0 = ktime_get_ns();
		dma = dma_map_single(dev, buf, sc->size, DMA_FROM_DEVICE);
		if (!dma_mapping_error(dev, dma))
			dma_unmap_single(dev, dma, sc->size, DMA_FROM_DEVICE);
		t1 = ktime_get_ns();
		local_irq_restore(flags);

		if (dma_mapping_error(dev, dma)) {
			ret = -EIO;
			break;
		}

		d = t1 - t0;
		sum += d;
		min = min_t(u64, min, d);
		max = max_t(u64, max, d);
	}

	if (!ret) {
		u64 mean = sum, ns_per_kib;

		do_div(mean, sc->reps);
		ns_per_kib = mean * 1024;
		do_div(ns_per_kib, sc->size);
		pr_info("dmabench: %-16s size=%8zu reps=%u mean_ns=%llu min_ns=%llu max_ns=%llu ns_per_KiB=%llu\n",
			sc->name, sc->size, sc->reps, mean, min, max, ns_per_kib);
	} else
		pr_err("dmabench: %s: dma_map_single failed\n", sc->name);

	kfree(buf);
	return ret;
}

static int __init dmabench_init(void)
{
	struct device *dev;
	unsigned int i;
	int ret;

	pdev = platform_device_register_simple("dmabench", -1, NULL, 0);
	if (IS_ERR(pdev))
		return PTR_ERR(pdev);
	dev = &pdev->dev;

	ret = dma_coerce_mask_and_coherent(dev, DMA_BIT_MASK(32));
	if (ret) {
		platform_device_unregister(pdev);
		return ret;
	}

	pr_info("dmabench: START\n");
	for (i = 0; i < ARRAY_SIZE(scenarios); i++)
		run_scenario(dev, &scenarios[i]);
	pr_info("dmabench: DONE\n");
	return 0;
}

static void __exit dmabench_exit(void)
{
	platform_device_unregister(pdev);
}

module_init(dmabench_init);
module_exit(dmabench_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Streaming DMA_FROM_DEVICE map/unmap cost benchmark");

-- 
Karl

      parent reply	other threads:[~2026-09-12  6:59 UTC|newest]

Thread overview: 9+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-10  6:36 Karl Mehltretter
2026-09-10  6:36 ` [PATCH 1/2] ARM: dma-mapping: " Karl Mehltretter
2026-09-10  6:36 ` [PATCH 2/2] ARM: dma-mapping: flush FROM_DEVICE buffers on legacy backends Karl Mehltretter
2026-09-12  6:55   ` Karl Mehltretter
2026-09-10  6:48 ` [PATCH 0/2] ARM: preserve DMA_FROM_DEVICE buffer contents Karl Mehltretter
2026-09-10  9:14 ` Arnd Bergmann
2026-09-10 10:55   ` Will Deacon
2026-09-10 13:15     ` Arnd Bergmann
2026-09-12  6:59   ` Karl Mehltretter [this message]

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=aqT3UcHPXHIwlqqd@gmail.com \
    --to=kmehltretter@gmail.com \
    --cc=ardb@kernel.org \
    --cc=arnd@arndb.de \
    --cc=hch@lst.de \
    --cc=linusw@kernel.org \
    --cc=linux-arm-kernel@lists.infradead.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux@armlinux.org.uk \
    --cc=m.szyprowski@samsung.com \
    --cc=robin.murphy@arm.com \
    --cc=stable@vger.kernel.org \
    --cc=ulli.kroll@googlemail.com \
    --cc=will@kernel.org \
    /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

all inboxes | Powered by JetHome®