Jump to content

    

Операции с кешем данных в ARM Cortex-A7

Recommended Posts

repstosw

Дано:

- ядро ARM32 Cortex-A7

- включенный MMU (физический адрес = виртуальный адрес) с проставленными битами кеширования(C=1) и буферизацией (B=1)

- графический ускоритель, который:  читает данные из региона памяти или записывает  данные в регион памяти

 

Мы знаем, что перед тем как периферал (DMA, упомянутый графический ускоритель, кодек,...) начнёт читать память, нужно сбросить содержимое кеша данных в память. Потому что перифералы не работают с кешем, а работают с памятью напрямую.

И наоборот, после того как периферал сбросит в память данные, нужно объявить кеш данных недостоверным(инвалидация кеша) перед использованием этих данных процессором(CPU). Потому что процессор читает данные через кеш, о дополнительных изменениях памяти перифералом ни он, ни кеш не знают.

 

Вроде ничего сложного. Но....  В интернете я нашёл несколько вариаций на тему "манипуляции с кешем данных".  Вот они:

 1)

/*
    c code declared as follows:
    int flush_clean_user_range(long start, long end);
*/

    .text
    .globl flush_clean_user_range

flush_clean_user_range:
	mrc	p15, 1, r3, c0, c0, 0		@ read CSIDR
	and	r3, r3, #7			@ cache line size encoding
	mov	r2, #16				@ size offset
	mov	r2, r2, lsl r3			@ actual cache line size
    sub	r3, r2, #1
    bic	r0, r0, r3
1:
    mcr	p15, 0, r0, c7, c14, 1			@ clean and flush D line to the point of unification
    add	r0, r0, r2
2:
    cmp	r0, r1
    blo	1b
    mov	r0, #0
    dsb
    mov	pc, lr

/*
 * Fault handling for the cache operation above. If the virtual address in r0
 * isn't mapped, just try the next page.
 */
9001:
    mov	r0, r0, lsr #12
    mov	r0, r0, lsl #12
    add	r0, r0, #4096
    b	2b

 

2)

#define CACHE_LINE_SIZE     32

void mmu_clean_dcache(uint32_t buffer,uint32_t size)
{
    unsigned int ptr;

    ptr = buffer & ~(CACHE_LINE_SIZE - 1);

    while (ptr < buffer + size)
    {
        __asm__ __volatile__ ( "mcr p15, 0, %0, c7, c10, 1" : "=r" (ptr) );
        ptr += CACHE_LINE_SIZE;
    }
}

 

3)

#define CACHE_LINE_SIZE     32

void mmu_invalidate_dcache(uint32_t buffer,uint32_t size)
{
    unsigned int ptr;

    ptr = buffer & ~(CACHE_LINE_SIZE - 1);

    while (ptr < buffer + size)
    {
        __asm__ __volatile__ ( "mcr p15, 0, %0, c7, c6, 1" : "=r" (ptr) );
        ptr += CACHE_LINE_SIZE;
    }
}

 

4)

/*
;*******************************************************************************
;єЇКэГыіЖ: flush_dcache
;єЇКэФ­РН: void flush_dcache( void )
;єЇКэ№¦ДЬ: flush data cache
;ИлїЪІОКэ: void
;·µ »Ш Цµ: void
;±ё    Чў:
;*******************************************************************************
*/
	.globl flush_dcache
flush_dcache:

	STMFD   sp!, {r0-r12, lr}
    MRC     p15, 1, r0, c0, c0, 1       @; read clidr
    ANDS    r3, r0, #0x7000000          @; extract loc from clidr
    MOV     r3, r3, lsr #23             @; left align loc bit field
    BEQ     finished                    @; if loc is 0, then no need to clean

    mov     r10, #0                     @; start clean at cache level 0
loop1:
    ADD     r2, r10, r10, lsr #1        @; work out 3x current cache level
    MOV     r1, r0, lsr r2              @; extract cache type bits from clidr
    AND     r1, r1, #7                  @; mask of the bits for current cache only
    CMP     r1, #2                      @; see what cache we have at this level
    BLT     skip                        @; skip if no cache, or just i-cache
    MCR     p15, 2, r10, c0, c0, 0      @; select current cache level in cssr
    ISB                                 @; isb to sych the new cssr&csidr
    MRC     p15, 1, r1, c0, c0, 0       @; read the new csidr
    AND     r2, r1, #7                  @; extract the length of the cache lines
    ADD     r2, r2, #4                  @; add 4 (line length offset)
    LDR     r4, =0x3ff
    ANDS    r4, r4, r1, lsr #3          @; find maximum number on the way size
    CLZ     r5, r4                      @; find bit position of way size increment
    LDR     r7, =0x7fff
    ANDS    r7, r7, r1, lsr #13         @; extract max number of the index size
loop2:
    MOV     r9, r4                      @; create working copy of max way size
loop3:
    ORR     r11, r10, r9, lsl r5        @; factor way and cache number into r11
    ORR     r11, r11, r7, lsl r2        @; factor index number into r11
    MCR     p15, 0, r11, c7, c14, 2     @; clean & invalidate by set/way
    SUBS    r9, r9, #1                  @; decrement the way
    BGE     loop3
    SUBS    r7, r7, #1                  @; decrement the index
    BGE     loop2
skip:
    ADD     r10, r10, #2                @; increment cache number
    CMP     r3, r10
    BGT     loop1
finished:
    MOV     r10, #0                     @; swith back to cache level 0
    MCR     p15, 2, r10, c0, c0, 0      @; select current cache level in cssr
    ISB
    LDMFD   sp!, {r0-r12, lr}
    MOV     pc, lr
/*

 

Иногда вместо 2) и 3) - делают везде 1).

Почему?

 

Какие из вариантов будут быстрее работать?

А какие медленно?

Что конкретно делает каждый из вариантов?

Edited by repstosw

Share this post


Link to post
Share on other sites

AleksBak
23 часа назад, repstosw сказал:

В интернете я нашёл несколько вариаций на тему "манипуляции с кешем данных"

А "родную" (CMSIS-Core (Cortex-A) - наверное) библиотеку нельзя использовать? Обычно, хорошие люди, ее используют. А "что быстрее" тут вроде бы не так уж и сложно определить при желании - внимательно все операции по шагам просмотреть/проверить. Но только есть ли в этом какой-то сокровенный смысл? (если посмотреть, то некоторые варианты тут почти не отличаются даже).

Share this post


Link to post
Share on other sites

Arlleex

Надергали не пойми откуда примеров. Полагаю, что 1-й и (2-й + 3-й) вовсе из разных проектов. Какой из них правильный? Да все, скорее, всего, правильные. Но каноничные варианты те, где раздельны flush/clean и invalidate. Что делает 1-й я не знаю - по описанию лишь flush-ит.

Share this post


Link to post
Share on other sites

repstosw
1 hour ago, Arlleex said:

Надергали не пойми откуда примеров. Полагаю, что 1-й и (2-й + 3-й) вовсе из разных проектов. Какой из них правильный? Да все, скорее, всего, правильные. Но каноничные варианты те, где раздельны flush/clean и invalidate. Что делает 1-й я не знаю - по описанию лишь flush-ит.

Мощно!

Тогда чем FLUSH от CLEAN отличаются?

FLUSH и CLEAN могут заменить INVALIDATE?

 

Повторюсь, в примерах видел что....

On 4/21/2022 at 7:01 PM, repstosw said:

Иногда вместо 2) и 3) - делают везде 1).

 

Share this post


Link to post
Share on other sites

GenaSPB

Я в описанных случаях могу посоветовать ПЕРЕД чтением по DMA делать CleanInvalidate нужной области (или Invalidate если  нет пересечений с чем-то еще)
Перед записью (когда периферия будет использовать буфер) после формирования данных - Clean. Invalidate - если нет пересечений с другими буферами и хотите оптимизировать скорость (т.е. если потом чтений процессор не будет выполнять из этой области).
 

Есть еще экзотические случаи - например проверяем статусы буферов EHCI - проверили и сделали invalidate - чтобы потом новое содержимое флагов иметь возможность проверить.

 

Для ясности могу посоветовать использовать CMSIS библиотеку - её функции 

Spoiler

//	MVA
//	For more information about the possible meaning when the table shows that an MVA is required
// 	see Terms used in describing the maintenance operations on page B2-1272.
// 	When the data is stated to be an MVA, it does not have to be cache line aligned.

__STATIC_FORCEINLINE void L1_CleanDCache_by_Addr(volatile void *addr, int32_t dsize)
{
	if (dsize > 0)
	{
		int32_t op_size = dsize + (((uintptr_t) addr) & (DCACHEROWSIZE - 1U));
		uintptr_t op_mva = (uintptr_t) addr;
		__DSB();
		do
		{
			__set_DCCMVAC(op_mva);	// Clean data cache line by address.
			op_mva += DCACHEROWSIZE;
			op_size -= DCACHEROWSIZE;
		} while (op_size > 0);
		__DMB();     // ensure the ordering of data cache maintenance operations and their effects
	}
}

__STATIC_FORCEINLINE void L1_CleanInvalidateDCache_by_Addr(volatile void *addr, int32_t dsize)
{
	if (dsize > 0)
	{
		int32_t op_size = dsize + (((uintptr_t) addr) & (DCACHEROWSIZE - 1U));
		uintptr_t op_mva = (uintptr_t) addr;
		__DSB();
		do
		{
			__set_DCCIMVAC(op_mva);	// Clean and Invalidate data cache by address.
			op_mva += DCACHEROWSIZE;
			op_size -= DCACHEROWSIZE;
		} while (op_size > 0);
		__DMB();     // ensure the ordering of data cache maintenance operations and their effects
	}
}

__STATIC_FORCEINLINE void L1_InvalidateDCache_by_Addr(volatile void *addr, int32_t dsize)
{
	if (dsize > 0)
	{
		int32_t op_size = dsize + (((uintptr_t) addr) & (DCACHEROWSIZE - 1U));
		uintptr_t op_mva = (uintptr_t) addr;
		do
		{
			__set_DCIMVAC(op_mva);	// Invalidate data cache line by address.
			op_mva += DCACHEROWSIZE;
			op_size -= DCACHEROWSIZE;
		} while (op_size > 0);
		// Cache Invalidate operation is not follow by memory-writes
		__DMB();     // ensure the ordering of data cache maintenance operations and their effects
	}
}

#if (__L2C_PRESENT == 1)

__STATIC_FORCEINLINE void L2_CleanDCache_by_Addr(volatile void *addr, int32_t dsize)
{
	if (dsize > 0)
	{
		int32_t op_size = dsize + (((uint32_t) addr) & (DCACHEROWSIZE - 1U));
		uint32_t op_addr = (uint32_t) addr /* & ~(DCACHEROWSIZE - 1U) */;
		do
		{
			// Clean cache by physical address
			L2C_310->CLEAN_LINE_PA = op_addr;	// Atomic operation. These operations stall the slave ports until they are complete.
			op_addr += DCACHEROWSIZE;
			op_size -= DCACHEROWSIZE;
		} while (op_size > 0);
	}
}

__STATIC_FORCEINLINE void L2_CleanInvalidateDCache_by_Addr(volatile void *addr, int32_t dsize)
{
	if (dsize > 0)
	{
		int32_t op_size = dsize + (((uint32_t) addr) & (DCACHEROWSIZE - 1U));
		uint32_t op_addr = (uint32_t) addr /* & ~(DCACHEROWSIZE - 1U) */;
		do
		{
			// Clean and Invalidate cache by physical address
			L2C_310->CLEAN_INV_LINE_PA = op_addr;	// Atomic operation. These operations stall the slave ports until they are complete.
			op_addr += DCACHEROWSIZE;
			op_size -= DCACHEROWSIZE;
		} while (op_size > 0);
	}
}

__STATIC_FORCEINLINE void L2_InvalidateDCache_by_Addr(volatile void *addr, int32_t dsize)
{
	if (dsize > 0)
	{
		int32_t op_size = dsize + (((uint32_t) addr) & (DCACHEROWSIZE - 1U));
		uint32_t op_addr = (uint32_t) addr /* & ~(DCACHEROWSIZE - 1U) */;
		do
		{
			// Invalidate cache by physical address
			L2C_310->INV_LINE_PA = op_addr;	// Atomic operation. These operations stall the slave ports until they are complete.
			op_addr += DCACHEROWSIZE;
			op_size -= DCACHEROWSIZE;
		} while (op_size > 0);
	}
}
#endif /* (__L2C_PRESENT == 1) */

// Записать содержимое кэша данных в память
// применяетмся после начальной инициализации среды выполнния
void FLASHMEMINITFUNC arm_hardware_flush_all(void)
{
	L1C_CleanInvalidateDCacheAll();
#if (__L2C_PRESENT == 1)
	L2C_CleanInvAllByWay();
#endif
}

// Сейчас в эту память будем читать по DMA
void arm_hardware_invalidate(uintptr_t addr, int_fast32_t dsize)
{
	L1_InvalidateDCache_by_Addr((void *) addr, dsize);
#if (__L2C_PRESENT == 1)
	L2_InvalidateDCache_by_Addr((void *) addr, dsize);
#endif /* (__L2C_PRESENT == 1) */
}

// Сейчас эта память будет записываться по DMA куда-то
void arm_hardware_flush(uintptr_t addr, int_fast32_t dsize)
{
	L1_CleanDCache_by_Addr((void *) addr, dsize);
#if (__L2C_PRESENT == 1)
	L2_CleanDCache_by_Addr((void *) addr, dsize);
#endif /* (__L2C_PRESENT == 1) */
}

// Сейчас эта память будет записываться по DMA куда-то. Потом содержимое не требуется
void arm_hardware_flush_invalidate(uintptr_t addr, int_fast32_t dsize)
{
	L1_CleanInvalidateDCache_by_Addr((void *) addr, dsize);
#if (__L2C_PRESENT == 1)
	L2_CleanInvalidateDCache_by_Addr((void *) addr, dsize);
#endif /* (__L2C_PRESENT == 1) */
}

 

Лежит тут:
https://github.com/ua1arn/hftrx/blob/08911deb021d612b234b6a691a83de508a137adc/src/hardware.c#L2018

 

  

On 4/21/2022 at 12:01 PM, repstosw said:

- графический ускоритель, который:  читает данные из региона памяти или записывает  данные в регион памяти

 

Из любопытства спрошу - что за процессор, от граф сопроцессора которого нашли описание?

Edited by GenaSPB

Share this post


Link to post
Share on other sites

Arlleex
10 минут назад, repstosw сказал:

Тогда чем FLUSH от CLEAN отличаются?

Буквами в названии, видимо. Вот, например.

Цитата

FLUSH и CLEAN могут заменить INVALIDATE?

Разумеется, нет.

Share this post


Link to post
Share on other sites

repstosw
5 hours ago, GenaSPB said:

Из любопытства спрошу - что за процессор, от граф сопроцессора которого нашли описание?

Это был просто пример.

Из последнего - расковырял JPEG-кодек Cedar для  Allwinner A13.  Он и читает и пишет в регионы памяти.  Поэтому снова стал актуален вопрос связанный с кешированием.

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.