January 8, 2026

Kyu - ARM - Aarch64 cache -- network driver

To make the business of flushing and invalidating more concrete, it would be useful to consider it from a top down approach. In other words to examine some of the uses of cache maintenance routines by U-boot in a typical network driver.

Invalidating is what ARM literature calls invalidating and involves just tossing out what is in the cache.

Flushing is what ARM literature calls "cleaning" and can optionally also invalidate the entries at the same time. Cleaning involves pushing information in the cache to main memory and then marking the cache entry as clean.

In the network driver, whenever data is read from the network, DMA places the data into main memory without informing the cache. This means that any cache entries refering to the buffer address range should be invalidated.

In the network driver, when software writes data to a buffer, those writes may have only been done to the cache. Before the network hardware is told to write the buffer, the cache must be flushed to ensure that all the data written is in main memory.

There are also other data structures (buffer rings) shared by both software and the network hardware that may require flushing or invalidating.

A real example

In the U-boot source, the file drivers/net/sun8i_emac.c holds the driver for the Allwinner H5 chip that I am working with. In it we find these macros:
#define cache_clean_descriptor(desc)                    \
    flush_dcache_range((uintptr_t)(desc),               \
               (uintptr_t)(desc) + sizeof(struct emac_dma_desc))

#define cache_inv_descriptor(desc)                  \
    invalidate_dcache_range((uintptr_t)(desc),          \
                   (uintptr_t)(desc) + sizeof(struct emac_dma_desc))
These are used to flush or invalidate the data structures that deal with a specific network buffer.

When data is written to a buffer, we see this:

    /* Flush data to be sent */
    flush_dcache_range(data_start, data_end);

When data is received from the network, we see this:

/* make sure we read from DRAM, not our cache */
    invalidate_dcache_range(data_start,
                data_start + roundup(length, ARCH_DMA_MINALIGN));

Two routines in cache_v8.c

This focuses our attention upon two routines in arch/arm/cpu/armv8/cache_v8.c
void invalidate_dcache_range(unsigned long start, unsigned long stop);
void flush_dcache_range(unsigned long start, unsigned long stop);
Both of these are trivial wrappers around the assembly language routines:
__asm_invalidate_dcache_range(start, stop);
__asm_flush_dcache_range(start, stop);
What is interesting is that the network driver code for sun8i_emac.c is portable between 32 bit arm and 64 bit arm. Cache handling routines with exactly the same names and arguments exist in the cache support libraries for each architecture to make this possible. This will be nice for me as I will soon be wanting the compile the network driver from the H3 chip (32 bit) for the H5 chip (64 bit). I should only need to provide the required cache handling routines for aarch64 to make this work.

Other use of cache routines in U-boot

We need to look at code when things get initialized, when the MMU gets set up, and generally search and wee what surprises turn up. The fact that we have a common API for a set of cache routines shared by aarch32 and aarch64 makes this interesting.


Have any comments? Questions? Drop me a line!

Tom's electronics pages / tom@mmto.org