Эмуляция 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
- ничего такого
-serial null -serial mon:stdio
(потому что Cortex-A9 имеет два UART)- ничего такого
- вышеперечисленные два с
-semihosting
добавлено- ничего такого
-serial stdio
- не может использовать stdio на многосимвольных устройствах
- не удалось подключить последовательное устройство к символьному серверу 'stdio'
-console=/dev/tty
- неверный вариант
-curses
- черный экран, нет вывода
изучение
Я посмотрел на разборку файла 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.
1 ответ
Вам необходимо инициализировать UART перед попыткой вывода каких-либо символов. ВUART0
эмуляция работает нормально, например, при использовании слегка измененной версии этой программы:
/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.в нескольких случаях.
Надеюсь, это поможет.