我为 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)。
终于发现问题了...
地址
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__
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
。