如何在 Qemu (raspi3b) 中打印 miniUART 中的内容?

问题描述 投票:0回答:1

我为 Qemu (raspi3b) 编写了一个简单的裸机应用程序:

装载机.s

.global _start

_start:
    BL      run
    BL      .

stdio.h

#ifndef __STDIO_H__
#define __STDIO_H__

#define AUXENB 0x7e215004
#define AUX_MU_CNTL_REG 0x7e215060
#define AUX_MU_IER_REG 0x7e215044
#define AUX_MU_LCR_REG 0x7e21504c
#define AUX_MU_MCR_REG 0x7e215050
#define AUX_MU_BAUD 0x7e215068
#define AUX_MU_IIR_REG 0x7e215048
#define AUX_MU_CNTL_REG 0x7e215060
#define AUX_MU_IO_REG 0x7e215040

#endif //__STDIO_H__

内核.c

#include "stdio.h"

void enable_mini_uart(void) {
    // Set AUXENB register to enable mini UART. Then mini UART register can be accessed.
    unsigned long * auxenb = (unsigned long *)AUXENB;
    *auxenb |= 0x1;
    // Set AUX_MU_CNTL_REG to 0. Disable transmitter and receiver during configuration.
    unsigned long * aux_mu_cntl_reg = (unsigned long *)AUX_MU_CNTL_REG;
    *aux_mu_cntl_reg &= 0xfffffffe;
    // Set AUX_MU_IER_REG to 0. Disable interrupt because currently you don’t need interrupt.
    unsigned long * aux_mu_ier_reg = (unsigned long *)AUX_MU_IER_REG;
    *aux_mu_ier_reg &= 0xfffffffe;
    // Set AUX_MU_LCR_REG to 3. Set the data size to 8 bit.
    unsigned long * aux_mu_lcr_reg = (unsigned long *)AUX_MU_LCR_REG;
    *aux_mu_lcr_reg |= 0x3;
    // Set AUX_MU_MCR_REG to 0. Don’t need auto flow control.
    unsigned long * aux_mu_mcr_reg = (unsigned long *)AUX_MU_MCR_REG;
    *aux_mu_mcr_reg &= 0xfffffffd;
    // Set AUX_MU_BAUD to 270. Set baud rate to 115200
    unsigned long * aux_mu_baud = (unsigned long *)AUX_MU_BAUD;
    *aux_mu_baud = 0x10e;
    // Set AUX_MU_IIR_REG to 6. No FIFO.
    unsigned long * aux_mu_iir_reg = (unsigned long *)AUX_MU_IIR_REG;
    *aux_mu_iir_reg |= 0x6;
    // Set AUX_MU_CNTL_REG to 3. Enable the transmitter and receiver.
    *aux_mu_cntl_reg |= 0x3;
}

void run(void) {
    enable_mini_uart();

    unsigned long * aux_mu_io_reg = (unsigned long *)AUX_MU_IO_REG;
    const char * str = "Hello!";
    for (int i = 0; i < 7; ++i) {
        *aux_mu_io_reg = *(str + i);
    }
}

链接器.ld

ENTRY(_start)
SECTIONS {
    . = 0x80000;
    .text : { *(.text) }
}

生成文件

# Build directory path
BUILD_DIR=build
# Toolchain path
BINTOOLS_PATH=/opt/homebrew/Cellar/aarch64-elf-binutils/2.41/bin
# LLVM path
LLVM_PATH=/opt/homebrew/opt/llvm/bin
# Qemu path
QEMU_PATH=/opt/homebrew/Cellar/qemu/8.1.1
# Tools aliases
OBJCOPY=$(LLVM_PATH)/llvm-objcopy
OBJDUMP=$(LLVM_PATH)/llvm-objdump
HEXDUMP=hexdump
CC=$(LLVM_PATH)/clang
AS=$(BINTOOLS_PATH)/aarch64-elf-as
LD=$(BINTOOLS_PATH)/aarch64-elf-ld
# Headers and sources paths
HEADER_PATH=include
SRC_PATH=src
# C sources
C_SRCS := $(wildcard $(SRC_PATH)/*.c)
# ASM sources
ASM_SRCS := $(wildcard $(SRC_PATH)/*.s)
# C objects
C_OBJS := $(C_SRCS:$(SRC_PATH)/%.c=$(BUILD_DIR)/%.o)
# ASM objects
ASM_OBJS := $(ASM_SRCS:$(SRC_PATH)/%.s=$(BUILD_DIR)/%.o)
# C targets
C_TARGETS := $(C_SRCS:$(SRC_PATH)/%.c=%)
# ASM targets
ASM_TARGETS := $(ASM_SRCS:$(SRC_PATH)/%.s=%)

# Default target
all: mkdir kernel_img

# Target to build a binary kernel
kernel_img: kernel_elf
    $(OBJCOPY) $(BUILD_DIR)/kernel.elf -O binary $(BUILD_DIR)/kernel.img

# Target to build an ELF kernel
kernel_elf: $(ASM_TARGETS) $(C_TARGETS)
    $(LD) -m aarch64elf -nostdlib -T linker.ld $(ASM_OBJS) $(C_OBJS) -o $(BUILD_DIR)/kernel.elf

# Target to compile loader.s
$(ASM_TARGETS): %: $(SRC_PATH)/%.s
    $(AS) $< -o $(BUILD_DIR)/[email protected]

# Targets to compile C sources
$(C_TARGETS): %: $(SRC_PATH)/%.c
    $(CC) -c \
        --target=aarch64-none-linux \
        -Wall \
        -O2 \
        -fomit-frame-pointer \
        -fno-exceptions \
        -Wno-incompatible-library-redeclaration \
        -fno-asynchronous-unwind-tables \
        -fno-unwind-tables \
        -I$(HEADER_PATH) \
        -o $(BUILD_DIR)/[email protected] $<

# Target to ensure if the build directory was created
mkdir:
    mkdir -p $(BUILD_DIR)

# Target to run Qemu
run:
    qemu-system-aarch64 -M raspi3b \
        -display none \
        -serial null \
        -serial stdio \
        -kernel $(BUILD_DIR)/kernel.img

但是,当我在 Qemu 中启动它时:

qemu-system-aarch64 -M raspi3b \
    -display none \
    -serial null \
    -serial stdio \
    -kernel $(BUILD_DIR)/kernel.img

尽管寄存器已更改并且 Qemu 已执行代码,但我的应用程序什么也没打印。

我通过在互联网上找到的手动设置迷你UART端口,所以它可能是一个不起作用的代码。然而,我仍然不知道那一定是怎样的。

P.S. 如果重要的话,我的主机是MacBook Pro(macOS、M1 Pro)。

c assembly raspberry-pi3 qemu bare-metal
1个回答
0
投票

终于发现问题了...

  1. 地址

    0x7exxxxxx
    是视频核心的地址,MMU将它们映射到物理内存中的
    0x3fxxxxxx
    ,因此
    stdio.h
    将如下所示:

    #ifndef __STDIO_H__
    #define __STDIO_H__
    
    #define AUXENB 0x3f215004
    #define AUX_MU_CNTL_REG 0x3f215060
    #define AUX_MU_IER_REG 0x3f215044
    #define AUX_MU_LCR_REG 0x3f21504c
    #define AUX_MU_MCR_REG 0x3f215050
    #define AUX_MU_BAUD 0x3f215068
    #define AUX_MU_IIR_REG 0x3f215048
    #define AUX_MU_CNTL_REG 0x3f215060
    #define AUX_MU_IO_REG 0x3f215040
    
    #endif //__STDIO_H__
    
  2. Clang 作为任何编译器都可以重新排序指令以优化执行速度。一般来说,它可以正常工作。因此,clang 不知道上面的地址属于特殊内存,并且某些位是在输出后设置的。解决此类问题的方法很少,就我而言,我需要使用

    volatile
    关键字:

    typedef unsigned long long ulong;
    
    void enable_mini_uart(void) {
        // Set AUXENB register to enable mini UART. Then mini UART register can be accessed.
        volatile ulong * auxenb = (ulong *)AUXENB;
        *auxenb |= 0x1;
        // Set AUX_MU_CNTL_REG to 0. Disable transmitter and receiver during configuration.
        volatile ulong * aux_mu_cntl_reg = (ulong *)AUX_MU_CNTL_REG;
        *aux_mu_cntl_reg &= 0xfffffffe;
        // Set AUX_MU_IER_REG to 0. Disable interrupt because currently you don’t need interrupt.
        volatile ulong * aux_mu_ier_reg = (ulong *)AUX_MU_IER_REG;
        *aux_mu_ier_reg &= 0xfffffffe;
        // Set AUX_MU_LCR_REG to 3. Set the data size to 8 bit.
        volatile ulong * aux_mu_lcr_reg = (ulong *)AUX_MU_LCR_REG;
        *aux_mu_lcr_reg |= 0x3;
        // Set AUX_MU_MCR_REG to 0. Don’t need auto flow control.
        volatile ulong * aux_mu_mcr_reg = (ulong *)AUX_MU_MCR_REG;
        *aux_mu_mcr_reg &= 0xfffffffd;
        // Set AUX_MU_BAUD to 270. Set baud rate to 115200
        volatile ulong * aux_mu_baud = (ulong *)AUX_MU_BAUD;
        *aux_mu_baud = 0x10e;
        // Set AUX_MU_IIR_REG to 6. No FIFO.
        volatile ulong * aux_mu_iir_reg = (ulong *)AUX_MU_IIR_REG;
        *aux_mu_iir_reg |= 0x6;
        // Set AUX_MU_CNTL_REG to 3. Enable the transmitter and receiver.
        *aux_mu_cntl_reg |= 0x3;
    }
    
    void run(void) {
        enable_mini_uart();
    
        volatile ulong * aux_mu_io_reg = (ulong *)AUX_MU_IO_REG;
        *aux_mu_io_reg = 'V';
    }
    

现在我可以看到输出了,但仍然存在一个错误,不幸的是,上面的代码打印的是

VVVV
而不是
V

© www.soinside.com 2019 - 2024. All rights reserved.