wiring serial rs232 read raspberry c gcc arm raspberry-pi bare-metal

serial - La aplicación se cuelga al llamar a printf a uart con raspberry pi de metal desnudo



serial read raspberry pi (2)

Estoy tratando de implementar una aplicación bare metal en el raspberry pi y quiero conectar stdout al mini uart para propósitos de depuración.

He seguido el proceso descrito aquí y aquí

He creado una función uart_putc que parece funcionar perfectamente, lo que me permite imprimir mensajes en el puerto COM de mi PC. Luego implementé _write syscall, haciendo que llame a mi función uart_putc para la salida. Esto funciona bien si paso un literal de cadena simple a parámetros literales adicionales de printf o cualquier parámetro no literal. Nada se imprime en el puerto serie y después de unas pocas llamadas, la aplicación se cuelga.

¿Alguien tiene alguna idea de lo que podría estar yendo mal? Feliz de proporcionar más información si es necesario ...

void uart_putc(char c) { while(1) { if(aux[AUX_MU_LSR]&0x20) break; led_blink(); // Blink the LED off then on again to // make sure we aren''t stuck in here } aux[AUX_MU_IO] = c; }

...

int _write(int file, char* ptr, int len) { int todo; for (todo = 0; todo < len; todo++) { uart_putc(*ptr++); } return len; }

...

while(1) { printf("Hello World/r/n"); // Works } while(1) { printf("Hello %s/r/n", "World"); // This doesn''t print anything // and will hang after about five calls } char* s = (char*)malloc(sizeof(char) * 100); // Heap or stack it doesn''t matter strcpy(s, "Hello World/r/n"); while(1) { printf(s); // This doesn''t print anything // and will hang after about five calls } while(1) { for (i = 0; i < 13; i++) { uart_putc(s[i]); // Works } }

Actualizar

Estoy usando newlib y _write funciona correctamente cuando se llama directamente. snprintf parece exhibir el mismo problema, es decir

snprintf(s, 100, "hello world/r/n"); // Works snprintf(s, 100, "hello %s/r/n", "world"); // Doesn''t work

Mi implementación de _sbrk fue copiada de la página a la que se hace referencia en mi OP

char *heap_end = 0; caddr_t _sbrk(int incr) { extern char heap_low; /* Defined by the linker */ extern char heap_top; /* Defined by the linker */ char *prev_heap_end; if (heap_end == 0) { heap_end = &heap_low; } prev_heap_end = heap_end; if (heap_end + incr > &heap_top) { /* Heap and stack collision */ return (caddr_t)0; } heap_end += incr; return (caddr_t) prev_heap_end; }

Secuencia de comandos de enlace

OUTPUT_FORMAT("elf32-littlearm", "elf32-bigarm", "elf32-littlearm") OUTPUT_ARCH(arm) ENTRY(_start) SEARCH_DIR("=/usr/local/lib"); SEARCH_DIR("=/lib"); SEARCH_DIR("=/usr/lib"); SECTIONS { /* Read-only sections, merged into text segment: */ PROVIDE (__executable_start = SEGMENT_START("text-segment", 0x8000)); . = SEGMENT_START("text-segment", 0x8000); . = 0x8000; .ro : { *(.text.startup) *(.text) *(.rodata) } .rw : { *(.data) __bss_start__ = .; *(.bss) __bss_end__ = .; *(COMMON) } . = ALIGN(8); heap_low = .; /* for _sbrk */ . = . + 0x10000; /* 64kB of heap memory */ heap_top = .; /* for _sbrk */ . = . + 0x10000; /* 64kB of stack memory */ stack_top = .; /* for startup.s */ }

empieza

.section ".text.startup" .global _start _start: ldr sp, =stack_top // The c-startup b _cstartup _inf_loop: b _inf_loop

Actualización 2

Otros experimentos relacionados con snprintf:

snprintf(s, 100, "hello world/r/n"); // Works snprintf(s, 100, "hello %s/r/n", "world"); // Doesn''t work snprintf(s, 100, "hello %d/r/n", 1); // Doesn''t work char s[100]; char t[100]; strcpy(s, "hello world/r/n"); snprintf(t, 100, s); // Doesn''t work


Esto no parece un problema de UART, sino un problema de biblioteca.

Si quiere asegurarse de que mi suposición es correcta, llame a _write() directamente y vea, si funciona. Lo más probable es que así sea. Además, supongo que estás usando newlib .

Si _write() funciona como se esperaba, el problema se limita a las capas superiores de printf . Desafortunadamente printf es como una cebolla, tienes que pelarla capa por capa y te hará llorar.

Solo por diversión, un fragmento del código fuente newlib :

/* * Actual printf innards. * * This code is large and complicated... */

Afortunadamente, todavía hay algunas maneras de solucionar el problema sin perderse en vfprintf.c . Tal vez el punto de partida más fácil es probar snprintf() , ya que carece de los problemas de administración de memoria. El código de asignación de memoria incluye cosas como sbrk que puede ser un problema. Uno estaría tentado a pensar que la administración de la memoria está bien, ya que malloc() aparentemente funciona, pero ese no es siempre el caso. ( malloc() puede verse bien incluso si da direcciones incorrectas, pero se dañará la memoria).

¡Háganos saber dónde obtiene estos pasos de depuración! (Mi conjetura es que sbrk no funciona por alguna razón, y eso arruina la administración de la memoria).

Actualización Como parece que el problema no está en la asignación de memoria, al menos no solo en la asignación de memoria, tenemos que abordar la cebolla. Espero que no estés usando un maquillaje demasiado pesado ... (Esto me hace llorar, y no estoy 100% seguro de que el análisis a continuación sea el correcto. Así que tómalo con un poco de sal).

¿Qué sucede en newlib cuando se llama printf ? La historia está en la fuente newlib en la carpeta newlib/libc/stidio .

Capa 1: printf()

Primero, printf.c :

int _DEFUN(printf, (fmt), const char *__restrict fmt _DOTS) { int ret; va_list ap; struct _reent *ptr = _REENT; _REENT_SMALL_CHECK_INIT (ptr); va_start (ap, fmt); ret = _vfprintf_r (ptr, _stdout_r (ptr), fmt, ap); va_end (ap); return ret; }

Bastante sencillo. Si algo sale mal, es:

  • _REENT_SMALL_CHECK_INIT(ptr); o
  • manejo de varargs

No creo que la reentrada sea un problema aquí, así que me concentraría en los varargs. Tal vez sería una buena idea hacer un código de prueba varargs mínimo, que luego mostraría si están rotos. (No veo por qué se romperían, pero en un sistema integrado es más seguro no asumir nada).

Capa 2: _vfprintf_r()

Esta es una versión interna de vfprintf estándar (varargs-versión de file- printf ) con código reentrante. Se define en vfprintf.c . Viene en varios sabores dependiendo de qué switches se han utilizado durante la compilación de la biblioteca: STRING_ONLY (sin asignación de memoria) y / o NO_FLOATING_POINT . Asumiré que tienes la versión completa, en cuyo caso la función correcta se puede encontrar con el nombre _VFPRINTF_R (algunos #define _VFPRINTF_R han estado ocurriendo).

El código no es demasiado fácil de leer, primero cientos de líneas de la función consisten en declarar muchas variables (dependiendo de las opciones de compilación) y una docena de macros. Sin embargo, lo primero que realmente hace la función es un bucle sin fin para escanear la cadena de formato para % . Cuando encuentra un /0 lugar, lo hace goto done; (Sí, también tiene goto s, me gustaría lanzar esto a la revisión del código ...)

Sin embargo, esto nos da una pista: si no ponemos argumentos adicionales, simplemente saltamos para done una limpieza. Esto podemos sobrevivir, pero no el manejo de ningún argumento de formato. Entonces, veamos dónde terminaría %s . Esto se hace como uno esperaría, hay un gran switch(ch) ... En s dice:

case ''s'': cp = GET_ARG (N, ap, char_ptr_t); sign = ''/0''; if (prec >= 0) { /* * can''t use strlen; can only look for the * NUL in the first `prec'' characters, and * strlen () will go further. */ char *p = memchr (cp, 0, prec); if (p != NULL) { size = p - cp; if (size > prec) size = prec; } else size = prec; } else size = strlen (cp); break;

(Ahora, he asumido que no tiene soporte para cadenas multibyte MB_CAPABLE activado en su newlib . Si lo tiene, la cosa se volvió mucho más complicada). El resto parece fácil de depurar ( strlen y memchr ), pero la macro GET_ARG puede ser complicado, de nuevo dependiendo de su configuración de compilación (si tiene _NO_POS_ARGS , es mucho más simple).

Después del cambio, el caso simple (sin relleno en la cadena de formato) es:

PRINT (cp, size);

que es la macro de impresión. Al menos si el puntero cp está mal, entonces cosas extrañas sucederán.

La macro en sí misma no puede ser espantosamente loca, ya que podemos imprimir el caso simple; solo los argumentos causan problemas.

Me temo que esto es un poco complicado de depurar, pero los síntomas apuntan a algo que se corrompe en la memoria. Una cosa que debes verificar es el valor de retorno de tu printf . Debería devolver la cantidad de caracteres impresos. Si el valor de retorno es sensato o no ayuda a depurar el resto.


Lamento llegar tarde para resolver este problema. Soy el autor de los tutoriales de bare metal de valvers.com. La causa del bloqueo se debe a algo de lo que tenía conocimiento, pero que no había tenido tiempo de resolver. En realidad, no sabía que sería la solución a tu problema.

En resumen, el problema es que le decimos a la cadena de herramientas que el procesador es un ARM1176 y, lo que es más importante, que la unidad de punto flotante es VFP, y debemos usar el ABI de flotación dura.

Usar VFP es una opción importante, significa que seleccionamos la biblioteca C que también se compiló con esta opción. En general, las instrucciones de VFP no se utilizan y, por lo tanto, no nos molestan. Claramente, las porciones de printf usan instrucciones de VFP.

La razón por la que esto nos dispara es porque el ensamblador de inicio que es responsable de generar un buen entorno de tiempo de ejecución C no habilita VFP, por lo que cuando se llega a una instrucción VFP, el procesador salta al vector de excepción de instrucción indefinido.

Así es como descubrí que este era el problema. Simplemente habilité el LED en cualquiera de los vectores de excepción y se iluminó al usar el formato de impresión. Luego fue un caso de eliminar las llamadas LED en los vectores de excepción hasta que ya no se encendió. Esto sucedió en la excepción "Instrucción no definida". Una búsqueda rápida en el sitio ARM revela que el procesador irá aquí si se encuentra una instrucción VFP y el VFP no está habilitado. Por lo tanto, ¡me recordó que resolviera eso!

La solución

Hay algunas cosas que debes hacer. Debe replicar CMAKE_C_FLAGS en CMAKE_ASM_FLAGS en el archivo CMakeLists.txt para que las opciones correctas pasen al ensamblador, ¡y actualmente no lo son! ¡Actualizaré los tutoriales lo antes posible para solucionar esto!

Justo debajo del último comando set( CMAKE_C_FLAGS ... ) en el set( CMAKE_ASM_FLAGS ${CMAKE_C_FLAGS} ) agregar archivos CMakeLists.txt set( CMAKE_ASM_FLAGS ${CMAKE_C_FLAGS} ) que funciona bien porque CMake usa gcc como ensamblador.

A continuación, tenemos que modificar el archivo ensamblador de inicio (en mis tutoriales, armc-nnn-start.S) para habilitar el VFP. Inserta el siguiente código justo arriba bl _cstartup

(Esto está directamente fuera del sitio web de TI )

// Enable VFP/NEON // r1 = Access Control Register MRC p15, #0, r1, c1, c0, #2 // enable full access for p10,11 ORR r1, r1, #(0xf << 20) // ccess Control Register = r1 MCR p15, #0, r1, c1, c0, #2 MOV r1, #0 // flush prefetch buffer because of FMXR below MCR p15, #0, r1, c7, c5, #4 // and CP 10 & 11 were only just enabled // Enable VFP itself MOV r0,#0x40000000 // FPEXC = r0 FMXR FPEXC, r0

Puede encontrar información de ARM sobre esto aquí .

Esos cambios son suficientes para que el formato printf funcione bien (lo he probado en UART). Si tiene más problemas, no dude en preguntar.

Por último, siento haber tenido dolor porque el código de inicio no es correcto. ¡¡Lo último que me gustaría hacer es que le cueste el tiempo a alguien !!