Перейти к содержанию
    

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

Дано:

- ядро 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).

Почему?

 

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

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

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

Изменено пользователем repstosw

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

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

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

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

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).

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Я в описанных случаях могу посоветовать ПЕРЕД чтением по 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:

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

 

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

Изменено пользователем GenaSPB

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

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

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

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

Цитата

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

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

5 hours ago, GenaSPB said:

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

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

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Присоединяйтесь к обсуждению

Вы можете написать сейчас и зарегистрироваться позже. Если у вас есть аккаунт, авторизуйтесь, чтобы опубликовать от имени своего аккаунта.

Гость
Ответить в этой теме...

×   Вставлено с форматированием.   Вставить как обычный текст

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

×
×
  • Создать...