Программирование ARM ESP32 Inter-Processor Call Fri, March 29 2024  

Поделиться

Нашли опечатку?

Пожалуйста, сообщите об этом - просто выделите ошибочное слово или фразу и нажмите Shift Enter.

ESP32 Inter-Processor Call Печать
Добавил(а) microsin   

Из-за двухядерной природы ESP32 могут быть случаи, когда определенные callback-функции должны быть запущены в в контексте определенного ядра CPU:

• Когда выделяется ISR для источника прерывания от определенного ядра CPU (применимо также к очистке источника прерывания определенного CPU).
• На некоторых чипах (таких как ESP32) возможен доступ к памяти, который является эксклюзивным для определенного ядра CPU (например, доступ к RTC Fast Memory).
• Чтение регистров/состояния другого ядра CPU.

IPC (Inter-Processor Call) позволяет определенному CPU (назовем его calling CPU) вызвать выполнение callback-функции на другом CPU (назовем его target CPU). IPC позволяет выполнить callback-функцию на target CPU либо в контексте задачи, либо в высокоуровневом контексте прерывания (High Priority Interrupt, подробнее см. [2]). В зависимости от контекста, в котором выполняется callback-функция, к ней применяются различные ограничения.

Важное замечание: под сокращением IPC понимается именно взаимодействие между ядрами/процессорами (Inter-Processor Call), но не между процессами (не Inter-Process Communication), как это подразумевается в описании многих операционных систем.

[IPC в контексте задачи (Task Context)]

IPC реализует выполнение callback-функции в контексте задачи путем создания задачи (IPC task) для каждого CPU во время запуска приложения (startup). Когда calling CPU нуждается в выполнении callback-функции на target CPU, то она будет выполнена задачей IPC task в контексте target CPU.

Когда IPC используется в контексте задачи, пользователям нужно учитывать следующее:

• Callback-функции IPC должны быть идеальными в смысле простоты и краткости. IPC callback не должен пытаться выполнить блокирование или уступку контекста (yield).
• Задачи IPC task создаются с максимальным возможным приоритетом (т. е. configMAX_PRIORITIES - 1), в результате чего callback должен также выполняться с таким же приоритетом. Однако по умолчанию разрешена опция CONFIG_ESP_IPC_USES_CALLERS_PRIORITY, что приводит к временному понижению приоритета IPC task target CPU перед приоритетом calling CPU перед выполнением callback.
• В зависимости от сложности callback, пользователям может понадобиться сконфигурировать размер стека IPC task опцией CONFIG_ESP_IPC_TASK_STACK_SIZE.
• Функция IPC feature внутри защищена мьютексом. Таким образом, одновременный вызов IPC от двух или большего количества calling CPU будет обрабатываться по принципу очередности (приоритет отдается тому, кто первый).

Использование API. Callback-и IPC контекста задачи имеют следующие ограничения:

• У них должен быть тип void func(void *arg).
• Callback должен избегать блокировки или yield, поскольку это приведет к блокировке IPC task на target CPU, или уступке контекста.
• Callback должен избегать любых изменений аспекта IPC task (например, вызова vTaskPrioritySet(NULL, x)).

Функция IPC предоставляет API, перечисленное ниже, для выполнения callback в контексте задачи на target CPU. Это API позволяет для calling CPU заблокироваться до завершения выполнения callback, либо выполнить немедленный возврат, как только callback запустился на выполнение.

esp_ipc_call() приведет к вызову IPC на target CPU. Эта функция заблокируется до тех пор, пока target CPU IPC task не начнет выполнять callback.
esp_ipc_call_blocking() также приведет к вызову IPC на target CPU. Но эта функция заблокируется до тех пор, пока target CPU IPC task не завершит выполнять callback.

[IPC в контексте прерывания (ISR Context)]

В некоторых случаях нам нужно быстро получить состояние другого CPU (core dump, GDB stub, различные unit-тесты, обход проблем с DPORT). Для таких сценариев функция IPC поддерживает выполнение callback-ов в высокоприоритетном контексте прерывания (High Priority Interrupt context). Это реализовано путем резервирования High Priority Interrupt на каждом CPU для использования IPC. Когда для calling CPU нужно выполнить callback на target CPU, этот callback будет выполняться на target CPU в контексте High Priority Interrupt.

Когда IPC используется в контексте High Priority Interrupt, пользователям нужно учитывать следующее:

• Поскольку callback выполняется в контексте High Priority Interrupt, его код должен быть полностью на ассемблере. См. ниже "Использование API" для более подробного описания процесса создания callback-ов на ассемблере.

• Приоритет, зарезервированный для High Priority Interrupt, зависит от опции CONFIG_ESP_SYSTEM_CHECK_INT_LEVEL.

• Когда выполняется callback:

- Calling CPU запретит прерывания уровня 3 и ниже.
- Хотя приоритет зарезервированного прерывания зависит от CONFIG_ESP_SYSTEM_CHECK_INT_LEVEL, во время выполнения IPC ISR callback на target CPU будут запрещены прерывания уровня 5 и ниже, независимо от установленного значения CONFIG_ESP_SYSTEM_CHECK_INT_LEVEL.

Использование API. Callback-и IPC контекста High Priority Interrupt имеют следующие ограничения:

• Callback должен быть типа void func(void *arg), однако написан полностью на ассемблере.

• Callback запускается инструкцией CALLX0 с запретом register windowing, поэтому он:

- Не должен вызывать никакие инструкции, относящиеся к register window (например entry и retw).
- Не должен вызывать другие C-функции, поскольку запрещено register windowing.

• Callback должен быть помещен в IRAM по байтовому адресу, нацело делящемуся на 4 (выравнивание на 4-байтовое слово).

• При входе в callback / выходе из него автоматически сохраняются / восстанавливаются регистры a2, a3, a4, поэтому они могут свободно использоваться к коде callback. Callback должен использовать ТОЛЬКО ЭТИ регистры.

- a2 будет содержать void *arg для callback.
- a3/a4 свободны для использования как scratch-регистры.

Функция IPC предоставляет API, перечисленное ниже, для выполнения callback в контексте High Priority Interrupt на target CPU.

esp_ipc_isr_asm_call() приведет к вызову IPC на target CPU. Эта функция зацикливается на ожидании (busy-wait), пока target CPU не начнет выполнять свой callback.
esp_ipc_isr_asm_call_blocking()  приведет к вызову IPC на target CPU. Эта функция зацикливается на ожидании (busy-wait), пока target CPU не завершит свой callback.

Следующие блоки кода демонстрируют High Priority Interrupt IPC callback, написанный на ассемблере, который просто считывает счетчик циклов на target CPU.

/* esp_test_ipc_isr_get_cycle_count_other_cpu(void *arg) */
// Эта функция считывает CCOUNT на CPU, и сохранит его в переменную,
// на которую указывает arg. Здесь используются только регистры
// a2, a3 и a4.
.section    .iram1, "ax"
.align      4
.global     esp_test_ipc_isr_get_cycle_count_other_cpu
.type       esp_test_ipc_isr_get_cycle_count_other_cpu, @function
// Аргументы:
// a2 - void* arg
esp_test_ipc_isr_get_cycle_count_other_cpu:
rsr.ccount a3
s32i    a3, a2, 0
ret

Запуск High Priority Interrupt IPC:

unit32_t cycle_count;
 
esp_ipc_isr_asm_call_blocking (esp_test_ipc_isr_get_cycle_count_other_cpu,
                               (void *)cycle_count);

Количество доступных scratch-регистров обычно достаточно для большинства простых случаев. Но если Ваш callback требует большего количества регистров, то void *arg может указывать на буфер, который используется как область для сохранения регистров. Тогда callback сможет сохранить и восстановить большее количество регистров, см. пример system/ipc/ipc_isr. Для более сложных callbac-ов High Priority Interrupt IPC см. components/esp_system/port/arch/xtensa/esp_ipc_isr_routines.S и components/esp_system/test/test_ipc_isr.S.

High Priority Interrupt IPC API для удобства также представляет следующие функции, которые могут приостанавливать/возобновлять работу (stall/resume) target CPU. Это API используются High Priority Interrupt IPC, однако предоставляют свои собственные внутренние callback-и:

esp_ipc_isr_stall_other_cpu() приостанавливает target CPU. Calling CPU запрещает прерывания уровня 3 и ниже, пока target CPU ждет (busy-wait) с запрещенными прерываниями уровня 5 и ниже. При этом target CPU будет крутить циклы busy-wait до тех пор, пока не будет вызвана esp_ipc_isr_release_other_cpu().

esp_ipc_isr_release_other_cpu() возобновит выполнение на target CPU.

[Справочник по API]

IPC Task Context. Заголовочный файл для IPC в контексте задачи: components/esp_system/include/esp_ipc.h.

Функция Описание
esp_ipc_call Выполнит callback на указанном CPU. Этот callback должен быть типа esp_ipc_func_t, и он будет запущен в контексте задачи (IPC task) на target CPU.

• Эта функция заблокирует IPC task на target CPU, который начнет выполнять callback.
• Если в настоящий момент выполняется другой вызов IPC, то эта функция заблокируется до тех пор, пока не завершится выполняющийся текущий вызов IPC.
• Размер стека IPC task можно сконфигурировать опцией CONFIG_ESP_IPC_TASK_STACK_SIZE.
esp_ipc_call_blocking Выполнит callback на указанном CPU с блокировкой, пока этот callback не завершится. В остальном поведение функции идентично esp_ipc_call.

Примечание: если приложение скомпилировано в одноядерном режиме, то эти две функции для cpu_id == 1 вернут ESP_ERR_INVALID_ARG.

IPC ISR Context. Заголовочный файл для IPC в контексте прерывания: components/esp_system/include/esp_ipc_isr.h.

Функция Описание
esp_ipc_isr_asm_call Выполнит на другом CPU функцию callback, написанную на ассемблере. Он будет выполняться в контексте High Priority Interrupt. Эта функция заблокируется на busy-wait в критической секции до тех пор, пока другой CPU не запустит callback.
esp_ipc_isr_asm_call_blocking Выполнит на другом CPU функцию callback, написанную на ассемблере. Эта функция заблокируется на busy-wait в критической секции до тех пор, пока другой CPU не завершит callback. В остальном функция esp_ipc_isr_asm_call_blocking подобна esp_ipc_isr_asm_call.
esp_ipc_isr_stall_other_cpu Функция приостановит другой CPU, который будет находиться на ожидании (busy-wait) в контексте High Priority Interrupt. Другой CPU не возобновит выполнение, пока не будет вызвана функция esp_ipc_isr_release_other_cpu().

• Эта функция внутренне реализована с использованием IPC ISR.
• Эта функция используется для обхода проблемы DPORT.
• Если приостановка была сделана с помощью esp_ipc_isr_stall_pause(), то функция esp_ipc_isr_stall_other_cpu не даст никакого эффекта.
esp_ipc_isr_release_other_cpu Возобновит выполнение другого CPU, который был ранее приостановлен вызовом esp_ipc_isr_stall_other_cpu().

• Эта функция используется для обхода проблемы DPORT.
• Если приостановка была сделана с помощью esp_ipc_isr_stall_pause(), то функция esp_ipc_isr_release_other_cpu не даст никакого эффекта.
esp_ipc_isr_stall_pause Эта функция приостановит действие CPU stall. В этом случае вызовы esp_ipc_isr_stall_other_cpu() и esp_ipc_isr_release_other_cpu() не будут давать эффекта. Если уже выполняется IPC ISR, то эта функция будет находиться в ожидании (busy-wait), пока не вызов не завершится перед тем, как активируется пауза для CPU stall.
esp_ipc_isr_stall_abort Обрывает CPU stall на другом CPU, что было установлено ранее вызовом esp_ipc_isr_stall_other_cpu(). Эта функция оборвет stall не восстанавливаемым способом, поэтому она должна вызываться только в случае паники (что используется в коде обработчика паники panic()).
esp_ipc_isr_stall_resume Возобновит функционирование CPU stall, что ранее было поставлено на паузу вызовом esp_ipc_isr_stall_pause(). После этого снова могут нормально работать esp_ipc_isr_stall_other_cpu() и esp_ipc_isr_release_other_cpu().

Примечание: функции esp_ipc_isr_asm_call, esp_ipc_isr_asm_call_blocking, esp_ipc_isr_stall_other_cpu, esp_ipc_isr_release_other_cpu недоступны в одноядерном режиме приложения.

Подробное описание функций, типов данных, структур и макросов см. в [1].

[Ссылки]

1. ESP32 Inter-Processor Call site:docs.espressif.com.
2. ESP32 High-Level Interrupts site:docs.espressif.com.
3. ESP32: функции поддержки FreeRTOS.
4ESP-IDF FreeRTOS SMP.
5ESP-IDF FreeRTOS Task API.

 

Добавить комментарий


Защитный код
Обновить

Top of Page