ESP32 Inter-Processor Call |
![]() |
Добавил(а) microsin | ||||||||||||||||||||||
Из-за двухядерной природы ESP32 могут быть случаи, когда определенные callback-функции должны быть запущены в в контексте определенного ядра CPU: • Когда выделяется ISR для источника прерывания от определенного ядра CPU (применимо также к очистке источника прерывания определенного 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). Использование API. Callback-и IPC контекста задачи имеют следующие ограничения: • У них должен быть тип void func(void *arg). Функция IPC предоставляет API, перечисленное ниже, для выполнения callback в контексте задачи на target CPU. Это API позволяет для calling CPU заблокироваться до завершения выполнения callback, либо выполнить немедленный возврат, как только callback запустился на выполнение. esp_ipc_call() приведет к вызову 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 и ниже. Использование API. Callback-и IPC контекста High Priority Interrupt имеют следующие ограничения: • Callback должен быть типа void func(void *arg), однако написан полностью на ассемблере. • Callback запускается инструкцией CALLX0 с запретом register windowing, поэтому он: - Не должен вызывать никакие инструкции, относящиеся к register window (например entry и retw). • Callback должен быть помещен в IRAM по байтовому адресу, нацело делящемуся на 4 (выравнивание на 4-байтовое слово). • При входе в callback / выходе из него автоматически сохраняются / восстанавливаются регистры a2, a3, a4, поэтому они могут свободно использоваться к коде callback. Callback должен использовать ТОЛЬКО ЭТИ регистры. - a2 будет содержать void *arg для callback. Функция IPC предоставляет API, перечисленное ниже, для выполнения callback в контексте High Priority Interrupt на target CPU. esp_ipc_isr_asm_call() приведет к вызову 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.
Примечание: если приложение скомпилировано в одноядерном режиме, то эти две функции для cpu_id == 1 вернут ESP_ERR_INVALID_ARG. IPC ISR Context. Заголовочный файл для IPC в контексте прерывания: components/esp_system/include/esp_ipc_isr.h.
Примечание: функции 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. |