Veo varios problemas potenciales con esas secciones críticas. Hay advertencias y soluciones a todos estos, pero como resumen:
- No hay nada que impida que el compilador mueva el código a través de estas macros, por optimización u otras razones aleatorias.
- Guardan y restauran algunas partes del estado del procesador que el compilador espera que el ensamblaje en línea deje solo (a menos que se indique lo contrario).
- No hay nada que evite que ocurra una interrupción en el medio de la secuencia y que cambie el estado entre cuando se lee y cuando se escribe.
En primer lugar, definitivamente necesita algunas barreras de memoria del compilador . GCC implementa estos como clobbers . Básicamente, esta es una manera de decirle al compilador "No, no puede mover los accesos a la memoria a través de este ensamblaje en línea porque podría afectar el resultado de los accesos a la memoria". Específicamente, necesita ambos "memory"
y "cc"
clobbers, tanto en las macros de inicio como de fin. Esto evitará que otras cosas (como llamadas a funciones) se reordenen también en relación con el ensamblado en línea, porque el compilador sabe que pueden tener accesos a la memoria. He visto GCC para el estado de retención ARM en registros de códigos de condición a través del ensamblaje en línea con "memory"
clobbers, por lo que definitivamente necesita el "cc"
clobber.
En segundo lugar, estas secciones críticas están guardando y restaurando mucho más que solo si las interrupciones están habilitadas. Específicamente, están guardando y restaurando la mayor parte del CPSR (Registro de estado del programa actual) (el enlace es para Cortex-R4 porque no pude encontrar un buen diagrama para un A9, pero debería ser idéntico). Existen restricciones sutiles sobre qué partes del estado se pueden modificar realmente, pero aquí es más que necesario.
Entre otras cosas, esto incluye los códigos de condición (donde cmp
se almacenan los resultados de instrucciones como para que las instrucciones condicionales posteriores puedan actuar sobre el resultado). El compilador definitivamente se confundirá con esto. Esto se puede solucionar fácilmente utilizando el "cc"
clobber como se mencionó anteriormente. Sin embargo, esto hará que el código falle cada vez, por lo que no suena como lo que está teniendo problemas. Sin embargo, es una bomba de tiempo, ya que modificar otro código aleatorio podría hacer que el compilador haga algo un poco diferente que se romperá con esto.
Esto también intentará guardar / restaurar los bits de TI, que se utilizan para implementar la ejecución condicional de Thumb . Tenga en cuenta que si nunca ejecuta el código Thumb, esto no importa. Nunca he descubierto cómo el ensamblaje en línea de GCC trata con los bits de TI, aparte de concluir que no lo hace, lo que significa que el compilador nunca debe colocar el ensamblaje en línea en un bloque de TI y siempre espera que el ensamblaje termine fuera de un bloque de TI. Nunca he visto a GCC generar código que viole estas suposiciones, y he realizado un ensamblaje en línea bastante complejo con una gran optimización, por lo que estoy razonablemente seguro de que se mantienen. Esto significa que probablemente no intentará cambiar los bits de TI, en cuyo caso todo está bien. Intentar modificar estos bits se clasifica como "arquitectónicamente impredecible", por lo que podría hacer todo tipo de cosas malas, pero probablemente no hará nada en absoluto.
La última categoría de bits que se guardará / restaurará (además de los que realmente deshabilitarán las interrupciones) son los bits de modo. Estos probablemente no cambiarán, por lo que probablemente no importará, pero si tiene algún código que cambie deliberadamente los modos, estas secciones de interrupción podrían causar problemas. Cambiar entre los modos privilegiado y de usuario es el único caso de hacer esto que esperaría.
En tercer lugar, no hay nada que impida que una interrupción cambie otras partes de CPSR entre MRS
y MSR
dentro ARM_INT_LOCK
. Cualquiera de estos cambios podría sobrescribirse. En la mayoría de los sistemas razonables, las interrupciones asincrónicas no cambian el estado del código que están interrumpiendo (incluido CPSR). Si lo hacen, se hace muy difícil razonar sobre lo que hará el código. Sin embargo, es posible (cambiar el bit de desactivación de FIQ me parece más probable), por lo que debe considerar si su sistema lo hace.
Así es como los implementaría de una manera que aborde todos los problemas potenciales que señalé:
#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")
Asegúrese de compilar -mcpu=cortex-a9
porque al menos algunas versiones de GCC (como la mía) tienen por defecto una CPU ARM más antigua que no admite cpsie
y cpsid
.
Usé en ands
lugar de solo and
en, ARM_INT_LOCK
así que es una instrucción de 16 bits si se usa en el código Thumb. El "cc"
clobber es necesario de todos modos, por lo que es estrictamente un beneficio de rendimiento / tamaño del código.
0
y 1
son etiquetas locales , para referencia.
Deben ser utilizables de la misma manera que sus versiones. El ARM_INT_LOCK
es tan rápido / pequeño como el original. Desafortunadamente, no pude encontrar una manera de hacerlo de ARM_INT_UNLOCK
manera segura en tan solo unas pocas instrucciones.
Si su sistema tiene restricciones cuando IRQ y FIQ están deshabilitados, esto podría simplificarse. Por ejemplo, si siempre están deshabilitados juntos, puede combinarlos en uno cbz
+ cpsie if
así:
#define ARM_INT_UNLOCK(key_) asm volatile (\
"cbz %[key], 0f\n\t"\
"cpsie if\n\t"\
"0:\n\t" :: [key]"r" (key_) : "memory", "cc")
Alternativamente, si no le importan los FIQ, entonces es similar a simplemente dejar de habilitarlos / deshabilitarlos por completo.
Si sabe que nada más cambia ninguno de los otros bits de estado en CPSR entre el bloqueo y el desbloqueo, entonces también puede usar continuar con algo muy similar a su código original, excepto con ambos "memory"
y "cc"
clobbers en ambos ARM_INT_LOCK
yARM_INT_UNLOCK