Я бачу кілька потенційних проблем з цими критичними розділами. Існують застереження та рішення для всього цього, але як підсумок:
- Ніщо не заважає компілятору переміщати код через ці макроси для оптимізації чи випадкових інших причин.
- Вони зберігають і відновлюють деякі частини стану процесора, компілятор очікує, що вбудована збірка залишиться в спокої (якщо не сказано інше).
- Ніщо не заважає перериванню виникати посередині послідовності та змінювати стан між тим, коли воно читається, і коли воно пишеться.
По-перше, вам обов'язково потрібні деякі бар'єри пам'яті компілятора . GCC реалізує це як клобер . В основному, це спосіб сказати компілятору "Ні, ви не можете переміщати доступ до пам'яті через цей фрагмент вбудованої збірки, оскільки це може вплинути на результат доступу до пам'яті." Зокрема, вам потрібні "memory"
і "cc"
клабер, і макроси початку і кінця. Це не дозволить перепорядкувати інші речі (наприклад, виклики функцій) відносно вбудованої збірки, оскільки компілятор знає, що вони можуть мати доступ до пам'яті. Я бачив стан GCC для утримання ARM у регістрах кодів стану в межах вбудованої збірки з "memory"
клоберами, тому вам точно потрібен "cc"
клоббер.
По-друге, ці критичні розділи економлять і відновлюють набагато більше, ніж лише те, чи включені переривання. Зокрема, вони зберігають та відновлюють більшу частину CPSR (Поточний реєстр статусу програми) (посилання призначено для Cortex-R4, оскільки я не зміг знайти хорошу діаграму для A9, але він повинен бути ідентичним). Є тонкі обмеження , навколо яких на самому ділі можуть бути змінені частини стану, але це більше , ніж потрібно тут.
Крім усього іншого, це включає коди умов (де результати подібних вказівок cmp
зберігаються, щоб наступні умовні вказівки могли діяти на результат). Компілятор, безумовно, збентежить це. Це легко вирішується за допомогою "cc"
клоберу, як було сказано вище. Однак цей код буде виходити з ладу кожен раз, тому він не буде схожий на те, з чим ви бачите проблеми. Дещо тим бомби часу, хоча в тому, що зміна випадкового іншого коду може змусити компілятора зробити щось трохи інше, що буде порушено цим.
Це також спробує зберегти / відновити ІТ-біти, які використовуються для реалізації умовного виконання Thumb . Зауважте, що якщо ви ніколи не виконуєте код Thumb, це не має значення. Я ніколи не з'ясовував, як вбудована збірка GCC має справу з бітами ІТ, крім того, щоб зробити висновок, що це не означає, що компілятор ніколи не повинен ставити вбудовану збірку в ІТ-блок і завжди очікує, що збірка закінчиться поза блоком ІТ. Я ніколи не бачив, щоб GCC генерував код, який порушує ці припущення, і я робив деякі досить складні вбудовані вбудовані з великою оптимізацією, тому я впевнений, що вони дотримуються. Це означає, що, ймовірно, насправді не буде спроб змінити біти ІТ, і в цьому випадку все в порядку. Спроба зміни цих бітів класифікується як "архітектурно непередбачувана", тому він може робити всілякі погані речі, але, ймовірно, взагалі нічого не зробить.
Остання категорія бітів, які будуть збережені / відновлені (окрім тих, які фактично відключають переривання), є бітами режиму. Вони, ймовірно, не зміняться, тому, мабуть, це не матиме значення, але якщо у вас є код, який свідомо змінює режими, ці секції переривання можуть спричинити проблеми. Перехід між привілейованим та користувальницьким режимом - це єдиний випадок цього, який я б очікував.
По-третє, нічого не заважає перерві змінювати інші частини CPSR між MRS
і MSR
в ARM_INT_LOCK
. Будь-які такі зміни можуть бути перезаписані. У більшості розумних систем асинхронні переривання не змінюють стан коду, який вони переривають (включаючи CPSR). Якщо вони це роблять, стає дуже важко міркувати про те, який код буде робити. Однак це можливо (зміна біта відключення FIQ мені здається найбільш ймовірним), тому вам слід врахувати, чи робить це ваша система.
Ось як я реалізував би це таким чином, щоб вирішити всі можливі проблеми, на які я вказав:
#define ARM_INT_KEY_TYPE unsigned int
#define ARM_INT_LOCK(key_) \
asm volatile(\
"mrs %[key], cpsr\n\t"\
"ands %[key], %[key], #0xC0\n\t"\
"cpsid if\n\t" : [key]"=r"(key_) :: "memory", "cc" );
#define ARM_INT_UNLOCK(key_) asm volatile (\
"tst %[key], #0x40\n\t"\
"beq 0f\n\t"\
"cpsie f\n\t"\
"0: tst %[key], #0x80\n\t"\
"beq 1f\n\t"\
"cpsie i\n\t"
"1:\n\t" :: [key]"r" (key_) : "memory", "cc")
Переконайтеся, що компілюйте, -mcpu=cortex-a9
оскільки принаймні деякі версії GCC (як-от моя) за замовчуванням старіші процесори ARM, які не підтримують cpsie
і cpsid
.
Я використовував ands
замість того , щоб тільки and
в ARM_INT_LOCK
так що інструкція 16-біт , якщо це використовується в Thumb коду. "cc"
Колошматити необхідно в будь-якому випадку, так що строго вигода розмір / продуктивність коду.
0
і 1
є місцевими етикетками , для довідки.
Вони повинні бути використані всіма тими ж способами, що і ваші версії. Це ARM_INT_LOCK
так само швидко / мало, як ваш оригінальний. На жаль, я не міг придумати спосіб ARM_INT_UNLOCK
безпечно в будь-якому місці поблизу, як мало інструкцій.
Якщо у вашій системі є обмеження щодо відключення IRQ та FIQ, це може бути спрощено. Наприклад, якщо вони завжди відключені разом, ви можете об'єднати в один cbz
+ cpsie if
подібний:
#define ARM_INT_UNLOCK(key_) asm volatile (\
"cbz %[key], 0f\n\t"\
"cpsie if\n\t"\
"0:\n\t" :: [key]"r" (key_) : "memory", "cc")
Крім того, якщо ви зовсім не переймаєтесь FIQ, тоді це схоже на те, щоб просто увімкнути / вимкнути їх повністю.
Якщо ви знаєте, що ніщо інше ніколи не змінює будь-який інший біт стану в CPSR між блокуванням і розблокуванням, то ви також можете використовувати продовжити щось дуже схоже на ваш вихідний код, за винятком обох "memory"
і "cc"
clobber в обох ARM_INT_LOCK
іARM_INT_UNLOCK