Есть такой чип: Cypress FX3. Это чип для построения USB 3.0 устройств. Внутри железная реализация протокола, 512кБ памяти и ядро ARM926E-JS. Cypress предоставляет SDK, в котором поставляется RTOS ThreadX и необходимый код для работы с их железом.
Как и классических встраеваемых SDK есть возможность ставить пользовательские колбеки и, по сути, управлять и строить логику конкретного приложения, ну и реализовать нужное вам USB 3.0 (2.0 тоже) устройство.
Отмечу, что сервисы (в терминах ThreadX) ThreadX напрямую не вызываются, они замаплены примерно так:
/***************************** Mutex functions ****************************/
#define CyU3PMutexCreate(mutex_ptr,priority_inherit) <br/>
tx_mutex_create(mutex_ptr,0,priority_inherit)
#define CyU3PMutexDestroy tx_mutex_delete
#define CyU3PMutexGet tx_mutex_get
#define CyU3PMutexPut tx_mutex_put
Ещё отмечу, что некоторые сервисы имеют ограничения на вызов из различных контекстов, например, из контекста ISR (прерывания). В частности нельзя создавать никакие объекты операционной системы типа мутексов, эвент-групп, потоков и т.д. Любой такой вызов из ISR (не только, но не будем вдаваться в подробности) приведёт или к непредвиденному поведению или, в случае использования защитных обёрток((та же функция, но начинается с _txe_XXX
, а не _tx_XXX
- дополнительный оверхед, но по умолчанию грамотно разбросано: критичные замаплены на _txe_XXX
, а некритичные на _tx_XXX
)), вернёт ошибку.
Понятно: что-бы не нарваться на неприятности, нужно грамотно смотреть и не смешивать общий код и код обработчика прерывания. Но по рукожопости разработчиков Cypress (я не расист, но судя по форуму - сплошь индусы), часть колбеков может вызываться как из контекста потока (причём, некоторые прямо во время регистрации - прямо из потока регистратора), так и из контекста прерывания. Пример такого колбека: CyU3PUsbRegisterEventCallback()
.
Причём документация(( http://www.cypress.com/system/files/document/files/FX3APIGuide_0.pdf)) явно молчит о контексте исполнения.
Ок, нам нужно как-то уметь различать, откуда мы выполняемся, что бы пусть код по разным веткам для потока и для ISR((у ISR ещё и стек оооочень маленький и длинная цепочка вызовов может сделать stack overflow)). Сами разработчики предлагают использовать для этого функцию CyU3PThreadIdentify()
вот выдержка из документации((
http://www.cypress.com/system/files/document/files/FX3APIGuide_0.pdf)), страница 445, пункт 5.27.12.72:
This function returns a pointer to the thread structure corresponding to the active thread, or NULL if called from interrupt context.
И они сами и используют её так, например в cyfxtx.cpp (часть проектного дерева), в методе CyU3PMemAlloc(uint32_t size)
:
/* Cannot wait in interrupt context */
if (CyU3PThreadIdentify ())
{
status = CyU3PByteAlloc (&glMemBytePool, (void **)&ret_p, size, CY_U3P_MEM_ALLOC_TIMEOUT);
}
else
{
status = CyU3PByteAlloc (&glMemBytePool, (void **)&ret_p, size, CYU3P_NO_WAIT);
}
А теперь перейдём к самому вкусному.
Эта функция мапится в вызов ThreadX:
#define CyU3PThreadIdentify tx_thread_identify
Открываем документацию на ThreadX(( http://rtos.com/images/uploads/ThreadX_User_Guide_V5.pdf)) и на странице 238 читаем само описание и, особо, примечение с восклицательным знаком:
If this service is called from an ISR, the return value represents the thread running prior to the executing interrupt handler.
Вкупе с самим описанием:
This service returns a pointer to the currently executing thread. If no thread is executing, this service returns a null pointer.
Т.е. NULL
будет возвращён только в случае если вызов был произведён из ISR и НЕ БЫЛ прерван ни один поток: то есть все потоки или спали или были заблокированы на примитивах синхронизации.
Перефразируя: эта функция, будучи вызванной из контекста ISR, может вернуть не NULL
.
Занавес.
Т.е. много кода, что с самом SDK, что в пользовательском коде опирается на предположение, которое неверно и, как следствие, может падать или исполняться неверно при неблагоприятных стечениях обстоятельств.
Собственно у нас на проекте я и отхватил с этим.
Ну и примеры ошибочного использования с недрах SDK (версия 1.3.1, но в 1.3.3 то же самое):
- https://github.com/nickdademo/cypress-fx3-sdk-linux/blob/b142a1612d874e259f66cf171bf58c596bfe9dba/FX3_SDK_1_3_1_SRC/sdk/firmware/src/lpp/cyu3i2s.c#L49
- https://github.com/nickdademo/cypress-fx3-sdk-linux/blob/b142a1612d874e259f66cf171bf58c596bfe9dba/FX3_SDK_1_3_1_SRC/sdk/firmware/src/lpp/cyu3uart.c#L50 - UART, по документации, можно использовать для легковесного логирования из ISR: можно намертво зависнуть.
- https://github.com/nickdademo/cypress-fx3-sdk-linux/blob/b142a1612d874e259f66cf171bf58c596bfe9dba/FX3_SDK_1_3_1_SRC/sdk/firmware/src/lpp/cyu3i2c.c#L55
- https://github.com/nickdademo/cypress-fx3-sdk-linux/blob/b142a1612d874e259f66cf171bf58c596bfe9dba/FX3_SDK_1_3_1_SRC/sdk/firmware/src/lpp/cyu3spi.c#L48
- https://github.com/nickdademo/cypress-fx3-sdk-linux/blob/b142a1612d874e259f66cf171bf58c596bfe9dba/FX3_SDK_1_3_1_SRC/sdk/firmware/src/system/cyu3system.c#L98 - тут не фатально, усыплять устройство из ISR вообще дурная идея
- https://github.com/nickdademo/cypress-fx3-sdk-linux/blob/b142a1612d874e259f66cf171bf58c596bfe9dba/FX3_SDK_1_3_1_SRC/sdk/firmware/src/dma/cyu3multichannelutils.c#L287 - не фатально
- https://github.com/nickdademo/cypress-fx3-sdk-linux/blob/b142a1612d874e259f66cf171bf58c596bfe9dba/FX3_SDK_1_3_1_SRC/sdk/firmware/src/dma/cyu3multichannel.c#L1326 - не фатально
- https://github.com/nickdademo/cypress-fx3-sdk-linux/blob/b142a1612d874e259f66cf171bf58c596bfe9dba/FX3_SDK_1_3_1_SRC/sdk/firmware/src/pport/cyu3pib.c#L567 - не фатально
Ну а официального способа определить, что мы находимся в прерывании попросту не существует. Есть три метода разного уровня костыльности:
- Читать регистр CPSR и проверять, что прерывания запрещены - делать вывод, что мы уже в обработчике прерывания. При существовании возможности вложенных прерываний можем так же сфейлиться.
- Мегахак, но работает 100% на данной платформе: читать регистр SP (Stack Pointeer) и проверять, что он меньше 0x40000000 - тогда мы в обработчике прерывания. Может сломаться если потребуется код переносить куда-то ещё, причём сломаться в рантайме, что очень плохо. Выглядеть это будет как-то так:
inline bool isr_active() { uint32_t sp; __asm volatile ("mov %0, SP<br/>n<br/>t" :"=r"(sp) :/**/ :); return (sp < 0x40000000) ? true : false; }
- Меньший хак: внутри ThreadX есть переменная
_tx_thread_system_state
, если её значение больше 0, то мы в прерывании или система ещё не проиницализировалась (тогда там 0xf0f0f0f0). Можно использовать так:inline bool isr_active() { static_assert(sizeof(void*) == 4, "Checked to work only on 32 bit CPUs"); extern uint32_t _tx_thread_system_state; return _tx_thread_system_state > 0; }
если будет портирование так же может сломаться (данной переменной нет), но сломается на линковке и не полезет в рантайм, что существенно лучше. В рантайме может сломаться, если вызов какого-то прерывания не обрамляется функциями сохранения и восстановления контекста, но это очень большой баг, который будет вам портить жизнь куда больше корректной проверки выполнения в контексте прерывания.