Эмуляция Qemu baremetal - как просмотреть вывод UART?

Вопрос:

Как я могу получить вывод UART из программы «baremetal», запущенной с Qemu?

Фон

Вот вызов командной строки, который я использовал:

qemu-system-arm -M xilinx-zynq-a9 -cpu cortex-a9 -nographic -kernel $BUILD_DIR/mm.elf -m 512M -s -S
  • используя машину xilinx-zynq-a9
  • процессор cortex-a9
  • поскольку это чистый металл, исполняемый файл представляет собой автономный файл ELF.
  • -m 512M означает, что на платформе 512 МБ ОЗУ.
  • -s - это ярлык для -gdb tcp::1234
  • -S означает зависание процессора при запуске

Используемый мной файл ELF (mm.elf) выполняет простую операцию умножения матриц, а затем выводит информацию о том, удалось ли она выполнить или нет, и сколько времени потребовалось для выполнения. ELF был скомпилирован с использованием инструментария Xilinx ARM. Я использую это для программного обеспечения внедрения ошибок. В настоящее время я использую GDB, чтобы запросить значения переменных, которые должны быть напечатаны. Однако, поскольку есть много вещей, которые могут пойти не так с печатью в контексте внедрения неисправности, было бы неплохо увидеть, что на самом деле отправляется через UART.

Связанные ответы:

перенаправить вывод окна QEMU на терминал, на котором запущен qemu

Я пробовал несколько советов, но это неприменимо, потому что вопрос касался получения загрузочных сообщений Linux в окне терминала хоста.

Как запустить программу без операционной системы?

Это не очень связано, потому что все еще предполагает, что у пользователя есть какой-то загрузчик. Хотя технически для запуска приложения должен быть загрузчик, Xilinx предоставляет этот системный код в таких файлах, как boot.S, которые затем компилируются в файл ELF как код, который выполняется до main.

Вещи, которые я пробовал:

Я попытался добавить каждый из них в конец моей текущей команды Qemu. Результаты соответствуют опробованным параметрам.

  • -serial mon:stdio
    • nothing
  • -serial null -serial mon:stdio (because Cortex-A9 has two UARTs)
    • nothing
  • the above two with -semihosting added
    • nothing
  • -serial stdio
    • cannot use stdio by multiple character devices
    • не удалось подключить последовательное устройство к символьному серверу 'stdio'
  • -console=/dev/tty
    • invalid option
  • -curses
    • black screen, no output

Расследование

Я посмотрел на разборку файла ELF и убедился, что адрес, на который записываются сообщения UART, совпадает с ожидаемым настройкой Qemu (info mtree). Базовый адрес 0xe0000000, одинаковый в обоих местах.

Цель

Я хочу иметь возможность фиксировать вывод сообщений, отправленных на UART. Если это делается путем перенаправления на стандартный вывод, ничего страшного. Если он проходит через сокет TCP, это тоже нормально. В настройке внедрения ошибок используется Python, а Qemu работает как подпроцесс, поэтому было бы легко получить выходные данные из любого из этих источников.

Примечание: при запуске в настройке внедрения неисправности вызов Qemu

qemu-system-arm -M xilinx-zynq-a9 -cpu cortex-a9 -nographic -kernel $BUILD_DIR/mm.elf -m 512M -gdb tcp::3345 -S -monitor telnet::3347,server,nowait

Основные отличия заключаются в следующем: 1) номер порта GDB отличается (поэтому несколько экземпляров могут работать одновременно) и 2) Qemu должен управляться с помощью соединения telnet через сокет, поэтому им можно управлять с помощью скрипта Python.


person thatjames    schedule 05.03.2020    source источник
comment
Мой ответ также ссылается на: stackoverflow.com/questions/38914019/, который содержит две настройки UART QEMU. Один RPI запускает идентичный код BTW, только с другим адресом UART, без загрузчика (помимо любых неизбежных вещей, RPI заставляет вас запускать перед полезной нагрузкой).   -  person Ciro Santilli 新疆再教育营六四事件ۍ    schedule 19.08.2020


Ответы (1)


Вам необходимо инициализировать UART, прежде чем пытаться выводить какие-либо символы. Эмуляция UART0 работает нормально, например, при использовании слегка измененной версии this программа:

/opt/qemu-4.2.0/bin/qemu-system-arm -semihosting --semihosting-config enable=on,target=native -nographic -serial mon:stdio -machine xilinx-zynq-a9 -m 768M -cpu cortex-a9 -kernel hello05.elf

Hello number 1

Результат выполнения команды git diff после внесения изменений был следующим:

diff --git a/Hello01/Makefile b/Hello01/Makefile
index 4a1b512..8d6d12a 100644
--- a/Hello01/Makefile
+++ b/Hello01/Makefile
@@ -1,10 +1,10 @@
 ARMGNU ?= arm-linux-gnueabihf
-COPS    =   
+COPS    = -g -O0  
 ARCH    = -mcpu=cortex-a9 -mfpu=vfpv3 

 gcc : hello01.bin

-all : gcc clang
+all : gcc 

 clean :
    rm -f *.o
@@ -15,8 +15,6 @@ clean :
    rm -f *.img
    rm -f *.bc

-clang: hello02.bin
-
 startup.o : startup.s
    $(ARMGNU)-as $(ARCH) startup.s -o startup.o

diff --git a/Hello01/hello01.c b/Hello01/hello01.c
index 20cb4a4..14ed2a0 100644
--- a/Hello01/hello01.c
+++ b/Hello01/hello01.c
@@ -10,16 +10,16 @@
 */


-#define UART1_BASE 0xe0001000
-#define UART1_TxRxFIFO0 ((unsigned int *) (UART1_BASE + 0x30))
+#define UART0_BASE 0xe0000000
+#define UART0_TxRxFIFO0 ((unsigned int *) (UART0_BASE + 0x30))

-volatile unsigned int * const TxRxUART1 = UART1_TxRxFIFO0;
+volatile unsigned int * const TxRxUART0 = UART0_TxRxFIFO0;

 void print_uart1(const char *s) 
 {
     while(*s != '\0') 
     {     /* Loop until end of string */
-    *TxRxUART1 = (unsigned int)(*s); /* Transmit char */
+    *TxRxUART0 = (unsigned int)(*s); /* Transmit char */
     s++; /* Next char */
     }
 }
@@ -28,4 +28,4 @@ void c_entry()
 {
    print_uart1("\r\nHello world!");
    while(1) ; /*dont exit the program*/
-}
\ No newline at end of file
+}
diff --git a/Hello05/Makefile b/Hello05/Makefile
index 9d3ca23..bc9bb61 100644
--- a/Hello05/Makefile
+++ b/Hello05/Makefile
@@ -1,5 +1,5 @@
 ARMGNU ?= arm-linux-gnueabihf
-COPS    =   
+COPS    =  -g -O0
 ARCH    = -mcpu=cortex-a9 -mfpu=vfpv3 

 gcc : hello05.bin
diff --git a/Hello05/hello05.c b/Hello05/hello05.c
index 1b92dde..01ce7ee 100644
--- a/Hello05/hello05.c
+++ b/Hello05/hello05.c
@@ -26,7 +26,7 @@

 void c_entry() 
 {
-   init_uart1_RxTx_115200_8N1();
+   init_uart0_RxTx_115200_8N1();
    printf("\nHello number %d\n",1);
    while(1) ; /*dont exit the program*/
 }
diff --git a/Hello05/xuartps.c b/Hello05/xuartps.c
index bdf7ad1..74f68bd 100644
--- a/Hello05/xuartps.c
+++ b/Hello05/xuartps.c
@@ -16,42 +16,42 @@
 void putc(int *p ,char c);

 /*
-* Initiate UART1  ( /dev/ttyACM0 on host computer )
+* Initiate UART0  ( /dev/ttyACM0 on host computer )
 *   115,200 Baud 8-bit No-Parity 1-stop-bit
 */
-void init_uart1_RxTx_115200_8N1()
+void init_uart0_RxTx_115200_8N1()
 {
   /* Disable the transmitter and receiver before writing to the Baud Rate Generator */
-  UART1->control_reg0=0; 
+  UART0->control_reg0=0; 

   /* Set Baudrate to 115,200 Baud */
-  UART1->baud_rate_divider =XUARTPS_BDIV_CD_115200;
-  UART1->baud_rate_gen=     XUARTPS_BRGR_CD_115200;
+  UART0->baud_rate_divider =XUARTPS_BDIV_CD_115200;
+  UART0->baud_rate_gen=     XUARTPS_BRGR_CD_115200;

   /*Set 8-bit NoParity 1-StopBit*/
-  UART1->mode_reg0   =   XUARTPS_MR_PAR_NONE;  
+  UART0->mode_reg0   =   XUARTPS_MR_PAR_NONE;  

   /*Enable Rx & Tx*/
-  UART1->control_reg0=   XUARTPS_CR_TXEN | XUARTPS_CR_RXEN | XUARTPS_CR_TXRES | XUARTPS_CR_RXRES ;      
+  UART0->control_reg0=   XUARTPS_CR_TXEN | XUARTPS_CR_RXEN | XUARTPS_CR_TXRES | XUARTPS_CR_RXRES ;      


 }

-void sendUART1char(char s)
+void sendUART0char(char s)
 {
   /*Make sure that the uart is ready for new char's before continuing*/
-  while ((( UART1->channel_sts_reg0 ) & UART_STS_TXFULL) > 0) ;
+  while ((( UART0->channel_sts_reg0 ) & UART_STS_TXFULL) > 0) ;

   /* Loop until end of string */
-  UART1->tx_rx_fifo= (unsigned int) s; /* Transmit char */
+  UART0->tx_rx_fifo= (unsigned int) s; /* Transmit char */
 }

 /* "print.h" uses this function for is's printf implementation */
 void putchar(char c)
 {
   if(c=='\n')
-    sendUART1char('\r');
-  sendUART1char(c);
+    sendUART0char('\r');
+  sendUART0char(c);
 }

 /* <stdio.h>'s printf uses puts to send chars
@@ -61,9 +61,9 @@ int puts(const char *s)
     while(*s != '\0') 
     { 
      if(*s=='\n')
-         sendUART1char('\r');
+         sendUART0char('\r');

-      sendUART1char(*s); /*Send char to the UART1*/       
+      sendUART0char(*s); /*Send char to the UART0*/       
       s++; /* Next char */
     }
     return 0;
diff --git a/Hello05/xuartps.h b/Hello05/xuartps.h
index fc5008f..64e3b88 100644
--- a/Hello05/xuartps.h
+++ b/Hello05/xuartps.h
@@ -13,7 +13,7 @@
    #define u32 unsigned int
 #endif

-#define UART1_BASE 0xe0001000
+#define UART0_BASE 0xe0000000
 // Register Description as found in
 //    B.33 UART Controller (UART) p.1626
 struct XUARTPS{
@@ -34,7 +34,7 @@ struct XUARTPS{
         u32 Flow_delay_reg0;            /* Flow Control Delay Register  def=0*/
         u32 Tx_FIFO_trigger_level;};    /* Transmitter FIFO Trigger Level Register */

-static struct XUARTPS *UART1=(struct XUARTPS*) UART1_BASE;        
+static struct XUARTPS *UART0=(struct XUARTPS*) UART0_BASE;        

 /*
     Page 496
@@ -87,11 +87,11 @@ static struct XUARTPS *UART1=(struct XUARTPS*) UART1_BASE;
 #define XUARTPS_MR_CLKS_REF_CLK 0       /*  0: clock source is uart_ref_clk*/

 /*
-* Initiate UART1  ( /dev/ttyACM0 on host computer )
+* Initiate UART0  ( /dev/ttyACM0 on host computer )
 *   115,200 Baud 8-bit No-Parity 1-stop-bit
 */
-void init_uart1_RxTx_115200_8N1();
-void sendUART1char(char s);
+void init_uart0_RxTx_115200_8N1();
+void sendUART0char(char s);
 int puts(const char *s);
 //void putc((void*), char);

Команда, выполняемая из каталога ZedBoard-BareMetal-Examples/Hello05 для построения измененного примера Hello05, была следующей:

make ARMGNU=/opt/arm/9/gcc-arm-9.2-2019.12-x86_64-arm-none-eabi/bin/arm-none-eabi clean all 

При этом последний комментарий из вашего предыдущего сообщения заставило меня подумать, что вы можете просто захотеть увидеть вывод своей программы, но не обязательно с помощью UART0.

В этом случае можно использовать интерфейс Angel / Semihosting - я понимаю, что вы, возможно, пытались пойти этим путем.

Пример:

// hello.c:

#include <stdlib.h>

int main(int argc, char** argv)
{
    printf("Hello, World!\n");
    return EXIT_SUCCESS;
}

команда gcc:

/opt/arm/9/gcc-arm-9.2-2019.12-x86_64-arm-none-eabi/bin/arm-none-eabi-gcc -g -O0 --specs=rdimon.specs -o hello.elf hello.c

команда qemu:

/opt/qemu-4.2.0/bin/qemu-system-arm -semihosting --semihosting-config enable=on,target=native -nographic -serial mon:stdio -machine xilinx-zynq-a9 -m 768M -cpu cortex-a9 -kernel hello.elf

Исход:

Hello, World!

Использование интерфейса полухостинга позволит вам читать / записывать файлы, читать вводимые пользователем данные и использовать некоторые из xUnit фреймворки тестирования, доступные для C или C ++ - например, я успешно использовал CppUnit с QEMU и интерфейсом Semihosting. в нескольких случаях.

Надеюсь, это поможет.

person Frant    schedule 06.03.2020
comment
Полухостинг был бы идеальным способом просмотра печатного вывода программы. По причинам, связанным с моим исследованием, я использую другой процесс сборки, который использует clang для интерфейса. . Если я понимаю ваш ответ, вы говорите, что программа должна быть скомпилирована иначе для поддержки полухостинга. Это верно? - person thatjames; 06.03.2020
comment
@thatjames: это больше, программа должна быть связана с другой версией библиотек, см. содержимое файла /opt/arm/9/gcc-arm-9.2-2019.12-aarch64-arm-none-eabi/arm-none-eabi/lib/rdimon.specs, где приведен список библиотек, с которыми должна быть связана программа с полу-размещенным хостом. Я не знаком с clang, но если вы можете связать с набором newlib/gcc библиотек, перечисленных в rdimon.specs, скорее всего, это сработает. Мне был бы интересен результат. - person Frant; 06.03.2020
comment
@thatjames: И в случае, если вы подумаете, что я правильно ответил на ваш вопрос, пожалуйста, не стесняйтесь принять ответ - я потратил пару часов, работая над ответом на ваш вопрос, в основном потому, что я нашел его интересным / сложным - вещь лязг я полагаю, это не было частью первоначального вопроса. - person Frant; 06.03.2020
comment
@thatjames: Местоположение файла спецификации rdimon явно неверно, правильным будет /opt/arm/9/gcc-arm-9.2-2019.12-x86_64-arm-none-eabi/arm-none-eabi/lib/rdimon.specs, если предположить, что gcc-arm-9.2-2019.12-x86_64-arm-none-eabi набор инструментов, доступный из arm, был извлечен в /opt. - person Frant; 06.03.2020
comment
@thatjames: согласно этот документ, кажется, вы можете использовать clang для создания полу-размещенного приложения. - person Frant; 09.03.2020
comment
Да, думаю, я не совсем понимал, какой набор инструментов я использовал. Я попробовал некоторые из ваших предложений, но без видимого успеха. Вероятно, это потому, что интерфейс полухостинга не вызывается правильно, потому что нет системных вызовов. Я попробую то, что вы сказали об использовании библиотек newlib. - person thatjames; 09.03.2020
comment
@thatjames: Пример hello semihosting должен работать из коробки. UART0 тоже должен, если вы используете команду patch и предоставленный мной .diff файл - я могу попытаться привести более простой пример. - person Frant; 10.03.2020
comment
вы были правы насчет полухостинга; Мне пришлось изменить функции инициализации в моем BSP на основе этот файл, и мне пришлось сделать ссылку на librdimon.a, найденный в libnewlib-arm-none-eabi, используя файл спецификаций. Но теперь это работает, спасибо! - person thatjames; 07.04.2020
comment
@thatjames: спасибо за обновление, я рад, что оно сработало. - person Frant; 07.04.2020