Hatred's Log Place

DON'T PANIC!

Nov 12, 2015 - 4 minute read - programming USB

О качестве документации и понимании сути

Есть такой чип: 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 то же самое):

Ну а официального способа определить, что мы находимся в прерывании попросту не существует. Есть три метода разного уровня костыльности:

  1. Читать регистр CPSR и проверять, что прерывания запрещены - делать вывод, что мы уже в обработчике прерывания. При существовании возможности вложенных прерываний можем так же сфейлиться.
  2. Мегахак, но работает 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;
    }
    
  3. Меньший хак: внутри 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;
    }
    

если будет портирование так же может сломаться (данной переменной нет), но сломается на линковке и не полезет в рантайм, что существенно лучше. В рантайме может сломаться, если вызов какого-то прерывания не обрамляется функциями сохранения и восстановления контекста, но это очень большой баг, который будет вам портить жизнь куда больше корректной проверки выполнения в контексте прерывания.

Tags: embedded fx3 programming

Перекличка IceWM: I'm alive!

comments powered by Disqus