学习指南

本次实习项目共有三个阶段,前两个阶段各包含五个实验内容(实验开始前,请先阅读每阶段的相关知识),第三阶段为驱动开发环节

  • 第一阶段主要了解树莓派以及如何在树莓派上运行ArceOS

    • 实验一为编译环境,消耗时间较久,大概需要一个小时左右

    • 实验二、三为在模拟环境运行ArceOS,每个实验大概需要一刻钟左右

    • 实验四、五为在树莓派主板上运行ArceOS,每个实验大概需要一刻钟左右(没有树莓派主板可以继续在qemu上运行)

  • 第二阶段主要用Rust写树莓派串口驱动,共包含五个实验内容

    • 实验一为驱动 UART0 串口,大概需要一个小时左右(没有树莓派可以在qemu上运行)

    • 实验二为用串口驱动小车,大概需要一个小时左右(没有小车,可以通过查看输出结果验证代码是否可以跑通)

    • 实验三为驱动另一串口,大概需要一个小时左右(没有树莓派可以在qemu上运行)

    • 实验四与实验三类似

    • 实验五为通过初始串口发出指令,由另一串口驱动小车,大概需要一个小时(选做)

  • 第三阶段为用Rust写树莓派USB转串口驱动,可以分为以下几步

    • PCIe总线初始化,可以读取USB设备ID

    • 为USB设备分配内存空间

    • xhci主机控制器的初始化

    • 枚举检测设备,为设备分配地址

    • 解析设备配置,加载相应的驱动程序

    • USB转串口的驱动实现

    • 完成技术需求,撰写技术总结文档

第一阶段:认识树莓派及ArceOS编译

本阶段主要以认识树莓派为主,学会如何编译支持树莓派4b的 Qemu 模拟器、如何在树莓派4b上运行 ArceOS。

ArceOS:

ArceOS实验环境配置

克隆这个仓库:

https://github.com/chenlongos/arceos

生成ArceOS代码仓库。

前置了解:树莓派相关知识

树莓派新手入门手册

树莓派4B(Raspberry Pi 4 Model B)是一款功能强大的单板计算机,由Raspberry Pi基金会推出。它提供了丰富的特性和扩展性,适用于各种项目和应用。以下是与树莓派4B相关的一些知识:

  1. 规格和硬件:树莓派4B采用了Broadcom BCM2711 SoC处理器,具有四个ARM Cortex-A72 CPU核心、VideoCore VI GPU和1GB、2GB或4GB LPDDR4内存选项。它还配备了多个USB 3.0和USB 2.0接口、Gigabit以太网端口、HDMI输出、MicroSD卡槽等。
  2. 操作系统支持:树莓派4B可以运行各种操作系统,包括Raspberry Pi官方的Raspberry Pi OS(以前称为Raspbian),以及其他基于Linux的发行版如Ubuntu、Fedora等。还可以安装其它的操作系统。
  3. GPIO引脚:树莓派4B具有40个GPIO(通用输入/输出)引脚,可以用于连接和控制各种外部设备,如传感器、LED、电机等。这些引脚还可以通过编程语言进行访问和控制。
  4. 外设接口:除了GPIO引脚,树莓派4B还提供了丰富的外设接口。它具有多个USB端口(包括USB 3.0和USB 2.0)、以太网端口、HDMI接口(支持4K分辨率输出)、音频/视频接口、摄像头接口、显示器接口等。
  5. 储存和扩展:树莓派4B使用MicroSD卡作为主要的存储介质,可以通过插入不同容量的MicroSD卡来扩展存储空间。此外,它还具有两个Micro HDMI端口和一个CSI摄像头接口,可用于连接外部显示器和摄像头模块。

树莓派主板如下图所示:

小车如下图所示(最上方是一块树莓派主板):

树莓派跑ArceOS通过串口控制小车运动:

实验一:支持树莓派4b的 Qemu 环境搭建

  1. 克隆这个仓库来生成新的 qemu 用来支持树莓派4b

    git clone https://github.com/0xMirasio/qemu-patch-raspberry4.git
    
    root@uBuntu:~/Github/Chenlong# git clone https://github.com/0xMirasio/qemu-patch-raspberry4
    Cloning into 'qemu-patch-raspberry4'...
    remote: Enumerating objects: 605399, done.
    remote: Counting objects: 100% (1/1), done.
    remote: Total 605399 (delta 0), reused 0 (delta 0), pack-reused 605398
    Receiving objects: 100% (605399/605399), 360.98 MiB | 11.84 MiB/s, done.
    Resolving deltas: 100% (489320/489320), done.
    root@uBuntu:~/Github/Chenlong# cd qemu-patch-raspberry4/
    root@uBuntu:~/Github/Chenlong/qemu-patch-raspberry4# ls
    accel           common-user    dtc                   io             memory_ldst.c.inc  page-vary-common.c    qemu-keymap.c    scripts         trace
    audio           configs        dump                  iothread.c     meson              pc-bios               qemu-nbd.c       scsi            trace-events
    authz           configure      ebpf                  job.c          meson.build        plugins               qemu.nsi         semihosting     ui
    backends        contrib        fpu                   job-qmp.c      meson_options.txt  po                    qemu-options.hx  slirp           util
    block           COPYING        fsdev                 Kconfig        migration          python                qemu.sasl        softmmu         VERSION
    block.c         COPYING.LIB    gdbstub.c             Kconfig.host   module-common.c    qapi                  qga              storage-daemon  version.rc
    blockdev.c      cpu.c          gdb-xml               libdecnumber   monitor            qemu-bridge-helper.c  qobject          stubs
    blockdev-nbd.c  cpus-common.c  gitdm.config          LICENSE        nbd                qemu-edid.c           qom              subprojects
    blockjob.c      crypto         hmp-commands.hx       linux-headers  net                qemu-img.c            README.rst       target
    bsd-user        disas          hmp-commands-info.hx  linux-user     os-posix.c         qemu-img-cmds.hx      replay           tcg
    capstone        disas.c        hw                    MAINTAINERS    os-win32.c         qemu-io.c             replication.c    tests
    chardev         docs           include               Makefile       page-vary.c        qemu-io-cmds.c        roms             tools
    
  2. 然后,执行以下操作进行编译:

    mkdir build
    cd build/
    ../configure
    make
    
    root@uBuntu:~/Github/Chenlong/qemu-patch-raspberry4# mkdir build
    root@uBuntu:~/Github/Chenlong/qemu-patch-raspberry4# cd build/
    root@uBuntu:~/Github/Chenlong/qemu-patch-raspberry4/build# ../configure
    root@uBuntu:~/Github/Chenlong/qemu-patch-raspberry4/build# make
    
  3. 编译时间较长,完成后生成的可执行文件就在当前 qemu 项目的 build 目录下,版本号为 QEMU emulator version 6.2.50 :

    root@uBuntu:~/Github/Chenlong/qemu-patch-raspberry4/build# ls qemu-system-*
    qemu-system-aarch64  qemu-system-cris  qemu-system-microblaze    qemu-system-mips64el  qemu-system-ppc      qemu-system-rx     qemu-system-sparc    qemu-system-xtensa
    qemu-system-alpha    qemu-system-hppa  qemu-system-microblazeel  qemu-system-mipsel    qemu-system-ppc64    qemu-system-s390x  qemu-system-sparc64  qemu-system-xtensaeb
    qemu-system-arm      qemu-system-i386  qemu-system-mips          qemu-system-nios2     qemu-system-riscv32  qemu-system-sh4    qemu-system-tricore
    qemu-system-avr      qemu-system-m68k  qemu-system-mips64        qemu-system-or1k      qemu-system-riscv64  qemu-system-sh4eb  qemu-system-x86_64
    
    root@uBuntu:~/Github/Chenlong/qemu-patch-raspberry4/build# ./qemu-system-aarch64 --version
    QEMU emulator version 6.2.50 (v6.2.0-1433-g6213f46ca3)
    Copyright (c) 2003-2022 Fabrice Bellard and the QEMU Project developers
    
  4. 最后,执行

    qemu-system-aarch64 -M help | grep ras
    

    可以看到 qemu 已经支持 树莓派4b 了

    root@iZ2ze7ajbjlxlzqf7yrk1zZ:~/Github/Chenlong/qemu-patch-raspberry4/build# ./qemu-system-aarch64 -M help | grep ras
    raspi0               Raspberry Pi Zero (revision 1.2)
    raspi1ap             Raspberry Pi A+ (revision 1.1)
    raspi2b              Raspberry Pi 2B (revision 1.1)
    raspi3ap             Raspberry Pi 3A+ (revision 1.0)
    raspi3b              Raspberry Pi 3B (revision 1.2)
    raspi4b1g            Raspberry Pi 4B (revision 1.1)
    raspi4b2g            Raspberry Pi 4B (revision 1.2)
    

至此,实验一结束,最终提交实验过程记录(包含出现的各类问题及解决办法)以及执行qemu-system-aarch64 -M help | grep ras命令后生成的结果,要求可以看到已经支持树莓派4b了。

实验二:Qemu 模拟器启动 ArceOS,打印Hello,world

  1. 在ArceOS目录下,输入:

    make A=apps/helloworld ARCH=aarch64 PLATFORM=aarch64-raspi4 SMP=4 
    

    (如果4核运行不了,可以改为单核)

    编译出ArceOS在raspi4 上的一个镜像。

  2. 在qemu模拟器上运行该镜像:

    ./qemu-system-aarch64 -m 2G -smp 4 -cpu cortex-a72 -machine raspi4b2g -nographic -kernel {yourpath}/helloworld_aarch64-raspi4.bin
    
  3. 最后,看到ArceOS在qemu模拟器中成功运行:

    root@DESKTOP-KO8A4KB:~/qemu-patch-raspberry4/build/arceos# make A=apps/helloworld ARCH=aarch64 PLATFORM=aarch64-raspi4 SMP=4
        Building App: helloworld, Arch: aarch64, Platform: aarch64-raspi4, App type: rust
    cargo build --target aarch64-unknown-none-softfloat --target-dir /root/qemu-patch-raspberry4/build/arceos/target --release  --manifest-path 
    apps/helloworld/Cargo.toml --features "axstd/log-level-warn axstd/smp"
        Finished release [optimized] target(s) in 0.09s
    rust-objcopy --binary-architecture=aarch64 apps/helloworld/helloworld_aarch64-raspi4.elf --strip-all -O binary apps/helloworld/helloworld_aarch64-raspi4.bin
    root@DESKTOP-KO8A4KB:~/qemu-patch-raspberry4/build/arceos# cd ..
    root@DESKTOP-KO8A4KB:~/qemu-patch-raspberry4/build# ./qemu-system-aarch64 -m 2G -smp 4 -cpu cortex-a72 -machine raspi4b2g -nographic -kernel 
    arceos/apps/helloworld/helloworld_aarch64-raspi4.bin
    
           d8888                            .d88888b.   .d8888b.
          d88888                           d88P" "Y88b d88P  Y88b
         d88P888                           888     888 Y88b.
        d88P 888 888d888  .d8888b  .d88b.  888     888  "Y888b.
       d88P  888 888P"   d88P"    d8P  Y8b 888     888     "Y88b.
      d88P   888 888     888      88888888 888     888       "888
     d8888888888 888     Y88b.    Y8b.     Y88b. .d88P Y88b  d88P
    d88P     888 888      "Y8888P  "Y8888   "Y88888P"   "Y8888P"
    
    arch = aarch64
    platform = aarch64-raspi4
    target = aarch64-unknown-none-softfloat
    smp = 4
    build_mode = release
    log_level = warn
    
    Hello, world!
    

至此,实验二结束,最终提交实验过程记录(包含出现的各类问题及解决办法)以及ArceOS成功运行,打印Hello,world的结果。

实验三:Qemu 模拟器启动 ArceOS,执行shell命令

  1. 在ArceOS目录下,输入:

    make A=apps/fs/shell ARCH=aarch64 PLATFORM=aarch64-raspi4 SMP=4 BLK=y FEATURES=driver-ramdisk
    

    编译出ArceOS在raspi4 上的镜像。

  2. 在qemu模拟器上运行该镜像:

    ./qemu-system-aarch64 -m 2G -smp 4 -cpu cortex-a72 -machine raspi4b2g  -nographic -kernel arceos/apps/fs/shell/shell_aarch64-raspi4.bin
    
  3. 看到ArceOS在qemu模拟器中成功运行:

    
           d8888                            .d88888b.   .d8888b.
          d88888                           d88P" "Y88b d88P  Y88b
         d88P888                           888     888 Y88b.
        d88P 888 888d888  .d8888b  .d88b.  888     888  "Y888b.
       d88P  888 888P"   d88P"    d8P  Y8b 888     888     "Y88b.
      d88P   888 888     888      88888888 888     888       "888
     d8888888888 888     Y88b.    Y8b.     Y88b. .d88P Y88b  d88P
    d88P     888 888      "Y8888P  "Y8888   "Y88888P"   "Y8888P"
    
    arch = aarch64
    platform = aarch64-raspi4
    target = aarch64-unknown-none-softfloat
    smp = 4
    build_mode = release
    log_level = warn
    
    [  0.032734 fatfs::boot_sector:615] Invalid FAT type
    [  0.055230 fatfs::dir:140] Is a directory
    [  0.059208 fatfs::dir:140] Is a directory
    [  0.064018 fatfs::dir:140] Is a directory
    [  0.066387 fatfs::dir:140] Is a directory
    Available commands:
      cat
      cd
      echo
      exit
      help
      ls
      mkdir
      pwd
      rm
      uname
      ldr
      str
    arceos:/$
    
  4. 尝试运行ldr和str命令:

    • ldr命令是用来读取地址所存储的值,例如输入ldr ffff0000fe201000,就会读出在地址为ffff0000fe201000中存储的值。
    arceos# ldr ffff0000fe201000
    ldr
    Value at address 0xffff0000fe201000: 0x66
    
    • str命令是用来往地址中写入值的,例如输入str ffff0000400fe000 123,就会往地址为ffff0000400fe000中写入123,输入234,就会写入234。
    arceos# str ffff0000400fe000 123
    arceos# ldr ffff0000400fe000
    ldr
    Value at address 0xffff0000400fe000: 0x123
    arceos# str ffff0000400fe000 234
    arceos# ldr ffff0000400fe000
    ldr
    Value at address 0xffff0000400fe000: 0x234
    
  5. 尝试输出字母A:

    尝试向ffff0000fe201000中写入41,使其输出字母A:

    arceos:/$ str ffff0000fe201000 41
    AWrite value at address ffff0000fe201000: 0x41
    

    可以在最后一行的开头看到输出了一个字母A。

至此,实验三结束,最终提交实验过程记录(包含出现的各类问题及解决办法)以及关于ArceOS成功运行,并且尝试执行ldr、str命令,最后再成功输出字母A的结果。(相关代码位置:arceos/apps/cli/src/)

实验四:树莓派启动 ArceOS,打印Hello,world

前置知识

SD卡的配置:

  • 创建一个名为bootFAT32分区
  • 在SD卡上生成一个名为config.txt的文件,并将以下内容写入其中:
```
arm_64bit=1
init_uart_clock=48000000
```
  • Raspberry Pi firmware repo中将以下文件复制到SD卡上:

  • 将通过编译生成的kernel8.img复制到SD卡上 kernel8.img的生成

    • 首先,克隆这个仓库:
    git clone https://github.com/chenlongos/rust-raspberrypi-OS-tutorials.git
    
    • 然后,在06_uart_chainloader目录下,执行:
    BSP=rpi4 make
    

    便可以看到生成了一个kernel8.img文件。

树莓派上电启动启动流程:

  • 硬件初始化: 树莓派4上电后,硬件会被初始化,包括CPU、内存、外设等。
  • GPU加载启动代码(bootcode.bin): GPU(图形处理单元)是树莓派启动的主要控制器。在启动时,GPU会从SD卡的boot分区加载一个文件,通常是 bootcode.bin。这个文件包含了GPU的启动代码,负责初始化系统硬件,设置内存分配和加载下一阶段的启动代码。
  • 加载启动配置文件(config.txt): GPU加载 config.txt 文件,该文件包含了系统的配置信息,比如时钟频率、内存分配等。
  • 加载启动文件(start4.elf): GPU加载 start4.elf 文件,它是一个二进制文件,包含了树莓派系统的启动代码,它负责初始化硬件和启动ARM处理器。
  • 加载设备树文件(bcm2711-rpi-4-b.dtb):start4.elf文件应该也会去读取设备树文件,然后设置一些基本的参数。
  • 加载操作系统内核(kernel8.img): start4.elf 文件会加载操作系统内核,是一个名为 kernel8.img 的文件。这个内核文件是一个裸机可执行文件,包含了操作系统的核心功能。
  • 初始化和启动操作系统: 内核文件被加载到内存后,GPU将控制权交给ARM处理器,操作系统开始初始化并启动,完成系统的启动过程。

树莓派通过串口与主机连接

将三根串口连接线分别了解到编号为6,8,10的三个引脚处。(6号对应的是地线GND,8号对应的是TXD,10号对应的是RXD)

再将一根USB串口转换线与连接到树莓派的线相连,其中TXD对应RXD,RXD对应TXD,GND对应GND。(默认情况下,USB串口转换线中黑色代表GND,白色代表RXD,绿色代表TXD,红色不连接)

连接软件的使用

以putty为例:

连接类型选择Serial,再将Serial line改为COM3(具体需要自己查看),Speed(波特率)设为115200,最后点击Open即可。

实验内容

  1. 在ArceOS目录下,输入:

    make A=apps/helloworld ARCH=aarch64 PLATFORM=aarch64-raspi4  LOG=debug 
    

    编译出ArceOS在raspi4 上的一个镜像。

  2. 编译生成一个kernel8.img文件:

    BSP=rpi4 make
    
  3. 把 kernel8.img 和 helloworld_aarch64-raspi4.bin 通过 cat 命令拼接到一个 bin 文件中,仍然取名为 kernel8.img

    cat ../rust-raspberrypi-OS-tutorials/06_uart_chainloader/kernel8.img apps/helloworld/helloworld_aarch64-raspi4.bin > kernel8.img
    

若已有树莓派开发板则进行以下操作

  1. 把新生成的 kernel8.img 拷贝到 sd 卡上。

  2. 将树莓派与PC相连,打开连接软件。

  3. 启动树莓派板子,可以看到上电后输出 miniload 并进入到 arceos helloworld 里

    
           d8888                            .d88888b.   .d8888b.
          d88888                           d88P" "Y88b d88P  Y88b
         d88P888                           888     888 Y88b.
        d88P 888 888d888  .d8888b  .d88b.  888     888  "Y888b.
       d88P  888 888P"   d88P"    d8P  Y8b 888     888     "Y88b.
      d88P   888 888     888      88888888 888     888       "888
     d8888888888 888     Y88b.    Y8b.     Y88b. .d88P Y88b  d88P
    d88P     888 888      "Y8888P  "Y8888   "Y88888P"   "Y8888P"
    
    arch = aarch64
    platform = aarch64-raspi4
    target = aarch64-unknown-none-softfloat
    smp = 1
    build_mode = release
    log_level = debug
    
    [  0.108847 0 axruntime:126] Logging is enabled.
    [  0.114576 0 axruntime:127] Primary CPU 0 started, dtb = 0x0.
    [  0.121522 0 axruntime:129] Found physcial memory regions:
    [  0.128209 0 axruntime:131]   [PA:0x80000, PA:0x86000) .text (READ | EXECUTE | RESERVED)
    [  0.137498 0 axruntime:131]   [PA:0x86000, PA:0x88000) .rodata (READ | RESERVED)
    [  0.146093 0 axruntime:131]   [PA:0x88000, PA:0x8c000) .data .tdata .tbss .percpu (READ | WRITE | RESERVED)
    [  0.157033 0 axruntime:131]   [PA:0x8c000, PA:0xcc000) boot stack (READ | WRITE | RESERVED)
    [  0.166583 0 axruntime:131]   [PA:0xcc000, PA:0xcd000) .bss (READ | WRITE | RESERVED)
    [  0.175613 0 axruntime:131]   [PA:0x0, PA:0x1000) spintable (READ | WRITE | RESERVED)
    [  0.184642 0 axruntime:131]   [PA:0xcd000, PA:0xfc000000) free memory (READ | WRITE | FREE)
    [  0.194193 0 axruntime:131]   [PA:0xfe201000, PA:0xfe202000) mmio (READ | WRITE | DEVICE | RESERVED)
    [  0.204525 0 axruntime:131]   [PA:0xff841000, PA:0xff849000) mmio (READ | WRITE | DEVICE | RESERVED)
    [  0.214857 0 axruntime:149] Initialize platform devices...
    [  0.221542 0 axruntime:185] Primary CPU 0 init OK.
    Hello, world! 
    [  9.556116 0 axruntime:198] main task exited: exit_code=0
    [  9.560792 0 axhal::platform::aarch64_raspi::misc:21] Shutting down...
    

若没有开发板则在qemu模拟器中进行

  1. 在qemu中运行kernel8.img:

    ./qemu-system-aarch64 -m 2G -smp 4 -cpu cortex-a72 -machine raspi4b2g -nographic -kernel arceos/kernel8.img
    
  2. 可以看到输出:

    
           d8888                            .d88888b.   .d8888b.
          d88888                           d88P" "Y88b d88P  Y88b
         d88P888                           888     888 Y88b.
        d88P 888 888d888  .d8888b  .d88b.  888     888  "Y888b.
       d88P  888 888P"   d88P"    d8P  Y8b 888     888     "Y88b.
      d88P   888 888     888      88888888 888     888       "888
     d8888888888 888     Y88b.    Y8b.     Y88b. .d88P Y88b  d88P
    d88P     888 888      "Y8888P  "Y8888   "Y88888P"   "Y8888P"
    
    arch = aarch64
    platform = aarch64-raspi4
    target = aarch64-unknown-none-softfloat
    smp = 1
    build_mode = release
    log_level = debug 
    
    [  0.018980 0 axruntime:126] Logging is enabled.
    [  0.021262 0 axruntime:127] Primary CPU 0 started, dtb = 0x0.
    [  0.022619 0 axruntime:129] Found physcial memory regions:
    [  0.023704 0 axruntime:131]   [PA:0x80000, PA:0x86000) .text (READ | EXECUTE | RESERVED)
    [  0.025339 0 axruntime:131]   [PA:0x86000, PA:0x88000) .rodata (READ | RESERVED)
    [  0.026509 0 axruntime:131]   [PA:0x88000, PA:0x8c000) .data .tdata .tbss .percpu (READ | WRITE | RESERVED)
    [  0.028264 0 axruntime:131]   [PA:0x8c000, PA:0xcc000) boot stack (READ | WRITE | RESERVED)
    [  0.029420 0 axruntime:131]   [PA:0xcc000, PA:0xcd000) .bss (READ | WRITE | RESERVED)
    [  0.030306 0 axruntime:131]   [PA:0x0, PA:0x1000) spintable (READ | WRITE | RESERVED)
    [  0.032056 0 axruntime:131]   [PA:0xcd000, PA:0xfc000000) free memory (READ | WRITE | FREE)
    [  0.032680 0 axruntime:131]   [PA:0xfe201000, PA:0xfe202000) mmio (READ | WRITE | DEVICE | RESERVED)
    [  0.033333 0 axruntime:131]   [PA:0xff841000, PA:0xff849000) mmio (READ | WRITE | DEVICE | RESERVED)
    [  0.034284 0 axruntime:149] Initialize platform devices...
    [  0.035041 0 axruntime:185] Primary CPU 0 init OK.
    Hello, world!
    [  0.036391 0 axruntime:198] main task exited: exit_code=0
    [  0.037152 0 axhal::platform::aarch64_raspi::misc:21] Shutting down...
    

    (ArceOS上面的内容miniload是原先的kernel8.img里的)

至此,实验四结束,最终提交实验过程记录(包含出现的各类问题及解决办法)以及ArceOS成功运行,打印Hello,world的结果。

实验五:树莓派启动 ArceOS,执行shell命令

  1. 在ArceOS目录下,输入:

    make A=apps/cli ARCH=aarch64 PLATFORM=aarch64-raspi4 LOG=debug
    

    编译出ArceOS在raspi4 上的镜像。

  2. 生成kernel8.img文件

    BSP=rpi4 make
    
  3. 把 kernel8.img 和 cli_aarch64-raspi4.bin 通过 cat 命令拼接到一个 bin 文件中,仍然取名为 kernel8.img:

    cat ../rust-raspberrypi-OS-tutorials/06_uart_chainloader/kernel8.img apps/cli/cli_aarch64-raspi4.bin > kernel8.img
    

若已有树莓派开发板则进行以下操作

  1. 把新生成的 kernel8.img 拷贝到 sd 卡上。

  2. 将树莓派与PC相连,打开连接软件。

  3. 启动树莓派板子,可以看到上电后输出 miniload 并进入到 shell 命令里:

           d8888                            .d88888b.   .d8888b.
          d88888                           d88P" "Y88b d88P  Y88b
         d88P888                           888     888 Y88b.
        d88P 888 888d888  .d8888b  .d88b.  888     888  "Y888b.
       d88P  888 888P"   d88P"    d8P  Y8b 888     888     "Y88b.
      d88P   888 888     888      88888888 888     888       "888
     d8888888888 888     Y88b.    Y8b.     Y88b. .d88P Y88b  d88P
    d88P     888 888      "Y8888P  "Y8888   "Y88888P"   "Y8888P"
    
    arch = aarch64
    platform = aarch64-raspi4
    target = aarch64-unknown-none-softfloat
    smp = 1
    build_mode = release
    log_level = debug
    
    [  0.108866 0 axruntime:126] Logging is enabled.
    [  0.114596 0 axruntime:127] Primary CPU 0 started, dtb = 0x0.
    [  0.121542 0 axruntime:129] Found physcial memory regions:
    [  0.128229 0 axruntime:131]   [PA:0x80000, PA:0x8a000) .text (READ | EXECUTE | RESERVED)
    [  0.137517 0 axruntime:131]   [PA:0x8a000, PA:0x8d000) .rodata (READ | RESERVED)
    [  0.146113 0 axruntime:131]   [PA:0x8d000, PA:0x91000) .data .tdata .tbss .percpu (READ | WRITE | RESERVED)
    [  0.157053 0 axruntime:131]   [PA:0x91000, PA:0xd1000) boot stack (READ | WRITE | RESERVED)
    [  0.166603 0 axruntime:131]   [PA:0xd1000, PA:0xd2000) .bss (READ | WRITE | RESERVED)
    [  0.175633 0 axruntime:131]   [PA:0x0, PA:0x1000) spintable (READ | WRITE | RESERVED)
    [  0.184662 0 axruntime:131]   [PA:0xd2000, PA:0xfc000000) free memory (READ | WRITE | FREE)
    [  0.194213 0 axruntime:131]   [PA:0xfe201000, PA:0xfe202000) mmio (READ | WRITE | DEVICE | RESERVED)
    [  0.204545 0 axruntime:131]   [PA:0xff841000, PA:0xff849000) mmio (READ | WRITE | DEVICE | RESERVED)
    [  0.214877 0 axruntime:149] Initialize platform devices...
    [  0.221562 0 axruntime:185] Primary CPU 0 init OK.
    Available commands:
      exit
      help
      uname
      ldr
      str
    arceos#
    
  4. 尝试修改ldr命令相关代码(位置arceos/apps/cli/src/cmd.rs):

    • 目前的ldr只能以8个字节为间隔来读取地址的值,要求改为以4个字节为间隔来读取地址的值(地址都是以16进制表示的),例如:

      arceos# ldr ffff0000fe201000
      ldr
      Value at address 0xffff0000fe201000: 0x31
      
      arceos# ldr ffff0000fe201004   //修改前如果输入ldr ffff0000fe201004 会直接崩溃
      ldr
      Value at address 0xffff0000fe201000: 0x0
      
    • 目前的ldr只能将完整的地址输入进去才能输出,要求改为输入一个地址加一个数字,可以直接输出一排相邻地址的值(起始的值为输入的地址的值,输出的值的个数取决于输入的数字,不输入默认为1),例如:

      arceos# ldr ffff0000fe201000 5 //修改前只能输入ldr ffff0000fe201000 ffff0000fe201008
      ldr
      Value at address 0xffff0000fe201000: 0x31
      Value at address 0xffff0000fe201004: 0x0
      Value at address 0xffff0000fe201008: 0x0
      Value at address 0xffff0000fe20100c: 0x0
      Value at address 0xffff0000fe201010: 0x0
      
  5. 尝试修改str命令相关代码

    目前的str只能以8个字节为间隔来写入地址的值,要求改为以4个字节为间隔来写入地址的值(地址都是以16进制表示的),例如:

    arceos# str ffff0000fe201000 123
    
    arceos# str ffff0000fe201004 123  //修改前如果输入ldr ffff0000fe201004 会直接崩溃
    
    
  6. 尝试输出字母A:

    尝试向ffff0000fe201000中写入41,使其输出字母A:

    arceos# str ffff0000fe201000 41
    Aarceos# 
    

    可以看到输出了一个字母A。(如果没有输出A,可能原因是提供的str代码执行后会打印出很多东西,导致A的结果被顶掉了,所以可以把一些打印语句删除再进行尝试)

若没有开发板则在qemu模拟器中运行

  1. 在qemu中运行kernel8.img:

    ./qemu-system-aarch64 -m 2G -smp 4 -cpu cortex-a72 -machine raspi4b2g -nographic -kernel arceos/kernel8.img
    
  2. 可以看到输出:

           d8888                            .d88888b.   .d8888b.
          d88888                           d88P" "Y88b d88P  Y88b
         d88P888                           888     888 Y88b.
        d88P 888 888d888  .d8888b  .d88b.  888     888  "Y888b.
       d88P  888 888P"   d88P"    d8P  Y8b 888     888     "Y88b.
      d88P   888 888     888      88888888 888     888       "888
     d8888888888 888     Y88b.    Y8b.     Y88b. .d88P Y88b  d88P
    d88P     888 888      "Y8888P  "Y8888   "Y88888P"   "Y8888P"
    
    arch = aarch64
    platform = aarch64-raspi4
    target = aarch64-unknown-none-softfloat
    smp = 1
    build_mode = release
    log_level = debug
    
    [  0.108866 0 axruntime:126] Logging is enabled.
    [  0.114596 0 axruntime:127] Primary CPU 0 started, dtb = 0x0.
    [  0.121542 0 axruntime:129] Found physcial memory regions:
    [  0.128229 0 axruntime:131]   [PA:0x80000, PA:0x8a000) .text (READ | EXECUTE | RESERVED)
    [  0.137517 0 axruntime:131]   [PA:0x8a000, PA:0x8d000) .rodata (READ | RESERVED)
    [  0.146113 0 axruntime:131]   [PA:0x8d000, PA:0x91000) .data .tdata .tbss .percpu (READ | WRITE | RESERVED)
    [  0.157053 0 axruntime:131]   [PA:0x91000, PA:0xd1000) boot stack (READ | WRITE | RESERVED)
    [  0.166603 0 axruntime:131]   [PA:0xd1000, PA:0xd2000) .bss (READ | WRITE | RESERVED)
    [  0.175633 0 axruntime:131]   [PA:0x0, PA:0x1000) spintable (READ | WRITE | RESERVED)
    [  0.184662 0 axruntime:131]   [PA:0xd2000, PA:0xfc000000) free memory (READ | WRITE | FREE)
    [  0.194213 0 axruntime:131]   [PA:0xfe201000, PA:0xfe202000) mmio (READ | WRITE | DEVICE | RESERVED)
    [  0.204545 0 axruntime:131]   [PA:0xff841000, PA:0xff849000) mmio (READ | WRITE | DEVICE | RESERVED)
    [  0.214877 0 axruntime:149] Initialize platform devices...
    [  0.221562 0 axruntime:185] Primary CPU 0 init OK.
    Available commands:
      exit
      help
      uname
      ldr
      str
    arceos#
    
  3. 同上面的第7步

  4. 同上面的第8步

  5. 同上面的第9步

至此,实验五结束,最终提交实验过程记录(包含出现的各类问题及解决办法)以及关于ArceOS成功运行,并且执行ldr一次输出20个值的结果(以ffff0000fe201000为起始地址),最后再成功输出字母A的结果。

第二阶段:Rust编写树莓派串口驱动

此阶段主要关于树莓派如何启用串口。

前置知识一:树莓派 GPIO 功能

引脚介绍

  • 下图为树莓派40个引脚的介绍。

  • 根据官方文档,可知各种 UART 信号如何(包括迷你 UART)映射到通用 I/O (GPIO) 上。
PullALT0ALT1ALT2ALT3ALT4ALT5
GPIO0HighTXD2
GPIO1HighRXD2
GPIO2HighCTS2
GPIO3HighRTS2
GPIO4HighTXD3
GPIO5HighRXD3
GPIO6HighCTS3
GPIO7HighRTS3
GPIO8HighTXD4
GPIO9HighRXD4
GPIO10HighCTS4
GPIO11HighRTS4
GPIO12HighTXD5
GPIO13HighRXD5
GPIO14LowTXD0CTS5
GPIO15LowRXD0RTS5
GPIO16LowCTS0
GPIO17LowRTS0

相关寄存器

GPIO 寄存器基地址:0xfe200000

寄存器简介

  • 根据官方文档,以下表格是部分寄存器的介绍。
OffsetNameDescription
0x00GPFSEL0GPIO Function Select 0
0x04GPFSEL1GPIO Function Select 1
0x08GPFSEL2GPIO Function Select 2
0x0cGPFSEL3GPIO Function Select 3
0x10GPFSEL4GPIO Function Select 4
0x14GPFSEL5GPIO Function Select 5
0xe4GPIO_PUP_PDN_CNTRL_REG0GPIO Pull-up / Pull-down Register 0
0xe8GPIO_PUP_PDN_CNTRL_REG1GPIO Pull-up / Pull-down Register 1
0xecGPIO_PUP_PDN_CNTRL_REG2GPIO Pull-up / Pull-down Register 2
0xf0GPIO_PUP_PDN_CNTRL_REG3GPIO Pull-up / Pull-down Register 3
  • GPFSEL寄存器,主要是控制GPIOxx--GPIOxx的功能选择。

    GPFSEL0控制GPIO0--GPIO9的功能选择;

    GPFSEL1控制GPIO10--GPIO19的功能选择;

    GPFSEL2控制GPIO20--GPIO29的功能选择;

    GPFSEL3控制GPIO30--GPIO39的功能选择;

    GPFSEL4控制GPIO40--GPIO49的功能选择;

    GPFSEL5控制GPIO50--GPIO57的功能选择。

    该寄存器存储32位的二进制数,每3位代表了一个GPIO引脚选择的功能,最后两位保留,其中,每个值的对应关系为

    000 = GPIO Pin ~ is an input
    001 = GPIO Pin ~ is an output
    100 = GPIO Pin ~ takes alternate function 0
    101 = GPIO Pin ~ takes alternate function 1
    110 = GPIO Pin ~ takes alternate function 2
    111 = GPIO Pin ~ takes alternate function 3
    011 = GPIO Pin ~ takes alternate function 4
    010 = GPIO Pin ~ takes alternate function 5
    
  • GPIO_PUP_PDN_CNTRL_REG寄存器,主要用于对GPIOxx--GPIOxx配置GPIO引脚的上拉(Pull-Up)和下拉(Pull-Down)电阻(上拉电阻会将引脚连接到正电源,从而将引脚拉高到高电平;下拉电阻会将引脚连接到地(GND),从而将引脚拉低到低电平)。

    GPIO_PUP_PDN_CNTRL_REG0用于对GPIO0--GPIO15配置GPIO引脚的上拉(Pull-Up)和下拉(Pull-Down)电阻;

    GPIO_PUP_PDN_CNTRL_REG1用于对GPIO16--GPIO31配置GPIO引脚的上拉(Pull-Up)和下拉(Pull-Down)电阻;

    GPIO_PUP_PDN_CNTRL_REG2用于对GPIO32--GPIO47配置GPIO引脚的上拉(Pull-Up)和下拉(Pull-Down)电阻;

    GPIO_PUP_PDN_CNTRL_REG3用于对GPIO48--GPIO57配置GPIO引脚的上拉(Pull-Up)和下拉(Pull-Down)电阻。

    该寄存器存储32位的二进制数,每2位代表了一个GPIO引脚上拉/下拉电阻选择的可能配置,其中,每个值的对应关系为:

    00 = No resistor is selected
    01 = Pull up resistor is selected
    10 = Pull down resistor is selected
    11 = Reserved
    

关于寄存器的代码介绍

位置:rust-raspberrypi-OS-tutorials/06_uart_chainloader/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs

#![allow(unused)]
fn main() {
register_bitfields! {
    u32,

    /// GPIO Function Select 1
    GPFSEL1 [
        /// Pin 15
        FSEL15 OFFSET(15) NUMBITS(3) [
            Input = 0b000,
            Output = 0b001,
            AltFunc0 = 0b100  // PL011 UART RX
        ],
        /// Pin 14
        FSEL14 OFFSET(12) NUMBITS(3) [
            Input = 0b000,
            Output = 0b001,
            AltFunc0 = 0b100  // PL011 UART TX
        ],
    ],
    /// GPIO Pull-up/down Register
    /// BCM2837 only.
    GPPUD [
        /// Controls the actuation of the internal pull-up/down control line to ALL the GPIO pins.
        PUD OFFSET(0) NUMBITS(2) [
            Off = 0b00,
            PullDown = 0b01,
            PullUp = 0b10
        ]
    ],
    /// GPIO Pull-up/down Clock Register 0
    /// BCM2837 only.
    GPPUDCLK0 [
        /// Pin 15
        PUDCLK15 OFFSET(15) NUMBITS(1) [
            NoEffect = 0,
            AssertClock = 1
        ],
        /// Pin 14
        PUDCLK14 OFFSET(14) NUMBITS(1) [
            NoEffect = 0,
            AssertClock = 1
        ],
    ],

    /// GPIO Pull-up / Pull-down Register 0
    /// BCM2711 only.
    GPIO_PUP_PDN_CNTRL_REG0 [
        /// Pin 15
        GPIO_PUP_PDN_CNTRL15 OFFSET(30) NUMBITS(2) [
            NoResistor = 0b00,
            PullUp = 0b01
        ],
        /// Pin 14
        GPIO_PUP_PDN_CNTRL14 OFFSET(28) NUMBITS(2) [
            NoResistor = 0b00,
            PullUp = 0b01
        ],
    ]
}

register_structs! {
    #[allow(non_snake_case)]
    RegisterBlock {
        (0x00 => _reserved1),
        (0x04 => GPFSEL1: ReadWrite<u32, GPFSEL1::Register>),
        (0x08 => _reserved2),
        (0x94 => GPPUD: ReadWrite<u32, GPPUD::Register>),
        (0x98 => GPPUDCLK0: ReadWrite<u32, GPPUDCLK0::Register>),
        (0x9C => _reserved3),
        (0xE4 => GPIO_PUP_PDN_CNTRL_REG0: ReadWrite<u32, GPIO_PUP_PDN_CNTRL_REG0::Register>),
        (0xE8 => @END),
    }
}
}

根据上述内容,可以知道GPIO14、15要实现串口通信,需要启用ALT0功能。而这对应着关于GPIO的GPFSEL1寄存器,

所以,在代码中,在第12-14位和第15-17位设置为0b100:

#![allow(unused)]
fn main() {
 GPFSEL1 [
        /// Pin 15
        FSEL15 OFFSET(15) NUMBITS(3) [
            Input = 0b000,
            Output = 0b001,
            AltFunc0 = 0b100  // PL011 UART RX
        ],

        /// Pin 14
        FSEL14 OFFSET(12) NUMBITS(3) [
            Input = 0b000,
            Output = 0b001,
            AltFunc0 = 0b100  // PL011 UART TX
        ],
 ]

}

还要设置上拉/下拉电阻,在第28、29位和第30、31位设置为0b01:

#![allow(unused)]
fn main() {
 GPIO_PUP_PDN_CNTRL_REG0 [
        /// Pin 15
        GPIO_PUP_PDN_CNTRL15 OFFSET(30) NUMBITS(2) [
            NoResistor = 0b00,
            PullUp = 0b01
        ],

        /// Pin 14
        GPIO_PUP_PDN_CNTRL14 OFFSET(28) NUMBITS(2) [
            NoResistor = 0b00,
            PullUp = 0b01
        ]
  ]
}

前置知识二:UART 通信

UART 是一种通用的串行、异步通信总线,该总线有两条数据线,TXD 用于发送数据,RXD用于接收数据,在嵌入式系统中常用于主机与辅助设备之间的通信。

UART连接方式如图:

基本特性和工作原理:

  1. 异步性: UART是异步通信协议,发送和接收设备之间不需要共享时钟信号。相反,它使用起始位(Start Bit)和停止位(Stop Bit)来标识每个数据字节的开始和结束,以及一个或多个数据位组成的数据字节。

  2. 数据帧: UART通信的数据传输以数据帧为单位。每个数据帧通常包括一个起始位、8位数据(通常是8位,但也可以是其他位数)、一个可选的奇偶校验位和一个或多个停止位。起始位和停止位提供了数据的边界,确保接收端能够准确地识别每个字节。

  3. 波特率: 波特率是UART通信中的数据传输速度,通常以每秒位数(bps,bits per second)为单位。发送和接收设备必须使用相同的波特率进行通信,以确保数据的正确传输。 波特率计算公式:波特率=时钟频率/16*(IBRD+(FBRD)/64)

  4. 起始位和停止位: 起始位指示数据传输的开始,停止位标志着数据传输的结束。当接收端检测到起始位时,它开始接收数据位,然后是奇偶校验位(如果启用),最后是停止位。接收端通过检测停止位来确定整个数据帧的结束。

  5. 奇偶校验: UART通信可以使用奇偶校验位来验证数据的准确性。奇偶校验可以选择奇数或偶数校验。发送端计算数据位中1的数量,如果选择奇校验,发送端会确保总位数(包括校验位)为奇数;如果选择偶校验,发送端会确保总位数为偶数。

相关寄存器

寄存器简介

根据官方文档,以下是关于部分寄存器的介绍。

OffsetNameDescription
0x00DRData Register
0x04RSRECR
0x18FRFlag register
0x24IBRDInteger Baud rate divisor
0x28FBRDFractional Baud rate divisor
0x2cLCRHLine Control register
0x30CRControl register
0x44ICRInterrupt Clear Register
0x48DMACRDMA Control Register
  • DR寄存器,数据寄存器,用于存储将要发送的数据或接收到的数据。

  • FR寄存器,标志寄存器。

  • IBRD寄存器,整数波特率除数寄存器。

  • FBRD寄存器,分数波特率除数寄存器。

IBRD和FBRD寄存器决定了波特率的数值。

  • LCR_H寄存器,线控制寄存器,用于配置 UART 的数据格式,包括数据位数、停止位数、奇偶校验设置等。

  • CR寄存器,控制寄存器,控制UART的启用,控制发送和接收数据的启用。

  • ICR寄存器,中断清除寄存器,用于清除 UART 中断标志,以便在处理中断后复位相应的中断状态。具体来说,当某个中断发生时,相应的标志位会被设置。通过写入 ICR 寄存器,可以清除这些标志位。

关于寄存器的代码部分

位置:rust-raspberrypi-OS-tutorials/06_uart_chainloader/src/bsp/device_driver/bcm/bcm2xxx_pl011_uart.rs

#![allow(unused)]
fn main() {
register_bitfields! {
    u32,

    /// Flag Register.
    FR [
        /// Transmit FIFO empty. The meaning of this bit depends on the state of the FEN bit in the
        /// Line Control Register, LCR_H.
        ///
        /// - If the FIFO is disabled, this bit is set when the transmit holding register is empty.
        /// - If the FIFO is enabled, the TXFE bit is set when the transmit FIFO is empty.
        /// - This bit does not indicate if there is data in the transmit shift register.
        TXFE OFFSET(7) NUMBITS(1) [],

        /// Transmit FIFO full. The meaning of this bit depends on the state of the FEN bit in the
        /// LCR_H Register.
        ///
        /// - If the FIFO is disabled, this bit is set when the transmit holding register is full.
        /// - If the FIFO is enabled, the TXFF bit is set when the transmit FIFO is full.
        TXFF OFFSET(5) NUMBITS(1) [],

        /// Receive FIFO empty. The meaning of this bit depends on the state of the FEN bit in the
        /// LCR_H Register.
        ///
        /// - If the FIFO is disabled, this bit is set when the receive holding register is empty.
        /// - If the FIFO is enabled, the RXFE bit is set when the receive FIFO is empty.
        RXFE OFFSET(4) NUMBITS(1) [],

        /// UART busy. If this bit is set to 1, the UART is busy transmitting data. This bit remains
        /// set until the complete byte, including all the stop bits, has been sent from the shift
        /// register.
        ///
        /// This bit is set as soon as the transmit FIFO becomes non-empty, regardless of whether
        /// the UART is enabled or not.
        BUSY OFFSET(3) NUMBITS(1) []
    ],

    /// Integer Baud Rate Divisor.
    IBRD [
        /// The integer baud rate divisor.
        BAUD_DIVINT OFFSET(0) NUMBITS(16) []
    ],

    /// Fractional Baud Rate Divisor.
    FBRD [
        ///  The fractional baud rate divisor.
        BAUD_DIVFRAC OFFSET(0) NUMBITS(6) []
    ],

    /// Line Control Register.
    LCR_H [
        /// Word length. These bits indicate the number of data bits transmitted or received in a
        /// frame.
        #[allow(clippy::enum_variant_names)]
        WLEN OFFSET(5) NUMBITS(2) [
            FiveBit = 0b00,
            SixBit = 0b01,
            SevenBit = 0b10,
            EightBit = 0b11
        ],

        /// Enable FIFOs:
        ///
        /// 0 = FIFOs are disabled (character mode) that is, the FIFOs become 1-byte-deep holding
        /// registers.
        ///
        /// 1 = Transmit and receive FIFO buffers are enabled (FIFO mode).
        FEN  OFFSET(4) NUMBITS(1) [
            FifosDisabled = 0,
            FifosEnabled = 1
        ]
    ],

    /// Control Register.
    CR [
        /// Receive enable. If this bit is set to 1, the receive section of the UART is enabled.
        /// Data reception occurs for either UART signals or SIR signals depending on the setting of
        /// the SIREN bit. When the UART is disabled in the middle of reception, it completes the
        /// current character before stopping.
        RXE OFFSET(9) NUMBITS(1) [
            Disabled = 0,
            Enabled = 1
        ],

        /// Transmit enable. If this bit is set to 1, the transmit section of the UART is enabled.
        /// Data transmission occurs for either UART signals, or SIR signals depending on the
        /// setting of the SIREN bit. When the UART is disabled in the middle of transmission, it
        /// completes the current character before stopping.
        TXE OFFSET(8) NUMBITS(1) [
            Disabled = 0,
            Enabled = 1
        ],

        /// UART enable:
        ///
        /// 0 = UART is disabled. If the UART is disabled in the middle of transmission or
        /// reception, it completes the current character before stopping.
        ///
        /// 1 = The UART is enabled. Data transmission and reception occurs for either UART signals
        /// or SIR signals depending on the setting of the SIREN bit
        UARTEN OFFSET(0) NUMBITS(1) [
            /// If the UART is disabled in the middle of transmission or reception, it completes the
            /// current character before stopping.
            Disabled = 0,
            Enabled = 1
        ]
    ],

    /// Interrupt Clear Register.
    ICR [
        /// Meta field for all pending interrupts.
        ALL OFFSET(0) NUMBITS(11) []
    ]
}

register_structs! {
    #[allow(non_snake_case)]
    pub RegisterBlock {
        (0x00 => DR: ReadWrite<u32>),
        (0x04 => _reserved1),
        (0x18 => FR: ReadOnly<u32, FR::Register>),
        (0x1c => _reserved2),
        (0x24 => IBRD: WriteOnly<u32, IBRD::Register>),
        (0x28 => FBRD: WriteOnly<u32, FBRD::Register>),
        (0x2c => LCR_H: WriteOnly<u32, LCR_H::Register>),
        (0x30 => CR: WriteOnly<u32, CR::Register>),
        (0x34 => _reserved3),
        (0x44 => ICR: WriteOnly<u32, ICR::Register>),
        (0x48 => @END),
    }
}

impl PL011UartInner {
    
    ... ...

    pub fn init(&mut self) {
        
        self.flush();

        // Turn the UART off temporarily.
        self.registers.CR.set(0);

        // Clear all pending interrupts.
        self.registers.ICR.write(ICR::ALL::CLEAR);

        // Set new baud rate = 115200
        // (48_000_000 / 16) / 115200 = 26.0416667
        // INTEGER((0.0416667 * 64) + 0.5) = 3.16666688
        self.registers.IBRD.write(IBRD::BAUD_DIVINT.val(26));
        self.registers.FBRD.write(FBRD::BAUD_DIVFRAC.val(3));

        self.registers
            .LCR_H
            .write(LCR_H::WLEN::EightBit + LCR_H::FEN::FifosEnabled);

        // Turn the UART on.
        self.registers
            .CR
            .write(CR::UARTEN::Enabled + CR::TXE::Enabled + CR::RXE::Enabled);
    }

    ... ...
}
}

要将波特率设置为115200,所以,在代码中:

self.registers.IBRD.write(IBRD::BAUD_DIVINT.val(26));
self.registers.FBRD.write(FBRD::BAUD_DIVFRAC.val(3));

要启用FIFO,并且将发送或接收的数据位数设置为8位,所以:

self.registers
            .LCR_H
            .write(LCR_H::WLEN::EightBit + LCR_H::FEN::FifosEnabled);

还要启用表示UART启用,同时启用发送和接收数据,所以:

self.registers
    .CR
    .write(CR::UARTEN::Enabled + CR::TXE::Enabled + CR::RXE::Enabled);

实验一:UART0 的启用

  1. 在rust-raspberrypi-OS-tutorials/05_drivers_gpio_uart中输入:

    BSP=rpi4 make
    

    编译生成一个kernel8.img文件。

  2. 把新生成的 kernel8.img 拷贝到 sd 卡上,并将SD卡插入RPi。

  3. 运行miniterm target,在主机上打开UART设备:

    make miniterm
    
  4. 将USB串口连接到主机PC,然后打开树莓派电源开关。

  5. 会得到以下输出:

    Miniterm 1.0
    
    [MT] ⏳ Waiting for /dev/ttyUSB0
    [MT] ✅ Serial connected
    [0] mingo version 0.5.0
    [1] Booting on: Raspberry Pi 4
    [2] Drivers loaded:
         1. BCM PL011 UART
         2. BCM GPIO
    [3] Chars written: 117
    [4] Echoing input now
    

如果没有树莓派主板,可以在qemu上运行生成的kernel8.img。

./qemu-system-aarch64 -m 2G -smp 4 -cpu cortex-a72 -machine raspi4b2g -nographic -kernel rust-raspberrypi-OS-tutorials/05_drivers_gpio_uart/kernel8.img

至此,实验一结束,最终提交实验过程记录(包含出现的各类问题及解决办法)以及正确的输出结果。

实验二:通过 UART0 驱动小车

补充知识:树莓派如何与小车(STM32)相连

参考:https://github.com/orgs/chenlongos/discussions/14

实验内容

驱动小车的代码位置:arceos/apps/boards/raspi4/src/main.rs

  1. 在ArceOS目录下,输入:

    make A=apps/boards/raspi4 ARCH=aarch64 PLATFORM=aarch64-raspi4 LOG=debug SMP=4
    

    编译出ArceOS在raspi4 上的一个镜像raspi4_aarch64-raspi4.bin。

  2. 在rust-raspberrypi-OS-tutorials/06_uart_chainloader中输入:

    BSP=rpi4 make
    

    编译生成一个kernel8.img文件。

  3. 把 kernel8.img 和 raspi4_aarch64-raspi4.bin 通过 cat 命令拼接到一个 bin 文件中,仍然取名为 kernel8.img:

    cat ../rust-raspberrypi-OS-tutorials/06_uart_chainloader/kernel8.img apps/boards/raspi4/raspi4_aarch64-raspi4.bin > kernel8.img
    
  4. 把新生成的 kernel8.img 拷贝到 sd 卡上,并且将树莓派串口连线与小车相连。

  5. 打开树莓派电源,可以看到小车会走出一个方形的轨迹。

  6. 修改代码,改变小车的运动轨迹。(尝试使小车跑出不同的轨迹形状,如三角形等)

    可以参考以下资料:

    https://github.com/orgs/chenlongos/discussions/13#discussion-5604815

若没有小车,可以在qemu模拟器上运行,

./qemu-system-aarch64 -m 2G -smp 4 -cpu cortex-a72 -machine raspi4b2g -nographic -kernel arceos/kernel8.img

尝试看到代码的逻辑是可以跑通的。

至此,实验二结束,最终提交实验过程记录(包含出现的各类问题及解决办法)以及相关代码。

实验三:通过 UART0 启用 UART5

若已有树莓派开发板则进行以下操作:

  1. GPIO改为ALT4功能启用UART5

    根据前置知识,已经知道GPIO12和GPIO13的ALT4功能可以用来进行串口通信,为UART5。其中,GPIO12对应着TXD/发送、GPIO13对应着RXD/接收。

    • 在原先启用GPIO14、15的ALT0的基础上,需要在偏移地址为0x04的GPFSEL1寄存器的第6-8位和第9-11位将值设置为0b011,也就是将值变为0b0010 0100 0110 1100 0000即0x246c0。

      let str_addr1 = "ffff0000fe200004 246c0";
      do_str(str_addr1);
      
    • 在偏移地址为0xe4的GPIO_PUP_PDN_CNTRL_REG0寄存器中将值变为0b0101_0101_0000_0000_0000_0000_0000_0000即0x5500_0000。

      let str_addr2 = "ffff0000fe2000e4 55000000";
      do_str(str_addr2);
      

    这样GPIO12、13的功能就变为了ALT4。

  2. 配置UART相关寄存器

    通过bcm2711的数据手册,可以知道UART5的起始地址为0xfe201a00

    • 波特率设置为115200,将偏移地址为0x24的IBRD寄存器的值变为26,将偏移地址为0x28的FBRD寄存器的值变为3。

      let str_addr3 = "ffff0000fe201a24 1A";
      let str_addr4 = "ffff0000fe201a28 3";
      do_str(str_addr3);
      do_str(str_addr4);
      
    • 启用FIFO缓冲区,设置数据位数为8位,即将偏移地址为0x2c的寄存器的值变为70。

      let str_addr5 = "ffff0000fe201a2c 70";
      do_str(str_addr5);
      
    • 启用UART,启用发送接收,即将偏移地址为0x30的寄存器的值变为301。

      let str_addr6 = "ffff0000fe201a30 301";
      do_str(str_addr6);
      

      所以,在shell命令的源代码中,添加一个命令 UART ,使得输入 UART 5 便可以启用 UART 5:

      #![allow(unused)]
      fn main() {
      fn do_UART(args: &str) {
          match args {
          "5" =>{
              let str_addr0 = "ffff0000fe200000 1B";
              let str_addr1 = "ffff0000fe200004 246c0";
              let str_addr2 = "ffff0000fe2000e4 55000000";
              let str_addr3 = "ffff0000fe201a24 1A";
              let str_addr4 = "ffff0000fe201a28 3";
              let str_addr5 = "ffff0000fe201a2c 70";
              let str_addr6 = "ffff0000fe201a30 301";
              //调用str写入函数
              do_str(str_addr0);
              do_str(str_addr1);
              do_str(str_addr2);
              do_str(str_addr3);
              do_str(str_addr4);
              do_str(str_addr5);
              do_str(str_addr6);
              }
          _ => {}
          } 
      }
      }
      
  3. 将以下测试函数添加到shell命令中:

    #![allow(unused)]
    fn main() {
    fn do_test(args: &str) {
            fn delay(seconds: u64) {
             for i in 1..seconds + 1 {
                 fn fibonacci_recursive(n: u64) -> u64 {
                     if n == 0 {
                         return 0;
                     }
                     if n == 1 {
                         return 1;
                     }
                     return fibonacci_recursive(n - 1) + fibonacci_recursive(n - 2);
                 }
                 fibonacci_recursive(36 + (i % 2));
             }
         }
     if args == "run" {
         loop {
             let arges = "ffff0000fe201a00 41";
             do_str(arges);
             delay(4);
         }
     }
    }
    }
    

    再启用UART 5后,运行此命令 test run ,然后将UART 5的TXD、RXD和GND与PC相连,观察输出。

    如果屏幕中会不断地输出字母A,则表示UART5被成功启用,可以通过UART5来进行数据通信。

若没有开发板则进行以下操作:

  1. 查看rust-raspberrypi-OS-tutorials/06_uart_chainloader关于gpio和uart的代码

    位置:rust-raspberrypi-OS-tutorials/06_uart_chainloader/src/bsp/device_driver/bcm/

    修改bcm2xxx_gpio.rs中的:

    #![allow(unused)]
    fn main() {
    GPFSEL1 [
         /// Pin 15
         FSEL15 OFFSET(15) NUMBITS(3) [
             Input = 0b000,
             Output = 0b001,
             AltFunc0 = 0b100  // PL011 UART RX
    
         ],
    
         /// Pin 14
         FSEL14 OFFSET(12) NUMBITS(3) [
             Input = 0b000,
             Output = 0b001,
             AltFunc0 = 0b100  // PL011 UART TX
         ]
     ],
    }
    

    将FSEL14、15改为12、13,将OFFSET偏移位改为6、9, 由于需要启用的UART5的ALT功能为4,所以需要将AltFunc0 = 0b100变为AltFunc4 = 0b010

    #![allow(unused)]
    fn main() {
    GPIO_PUP_PDN_CNTRL_REG0 [
         /// Pin 15
         GPIO_PUP_PDN_CNTRL15 OFFSET(30) NUMBITS(2) [
             NoResistor = 0b00,
             PullUp = 0b01
         ],
    
         /// Pin 14
         GPIO_PUP_PDN_CNTRL14 OFFSET(28) NUMBITS(2) [
             NoResistor = 0b00,
             PullUp = 0b01
         ],
     ]
    }
    

    将OFFSET偏移位改为24、26

  2. 用UART5的基地址替换掉UART0的基地址:

    位置:rust-raspberrypi-OS-tutorials/06_uart_chainloader/src/bsp/raspberrypi/memory.rs

    #![allow(unused)]
    fn main() {
    pub const UART_OFFSET:         usize = 0x0020_1000;
    ···
    pub const START:            usize =         0xFE00_0000;
    pub const PL011_UART_START: usize = START + UART_OFFSET;
    }
    

    将UART0的基地址fe201000变为UART5的基地址fe201a00

  3. 生成kernel8.img文件,尝试在qemu模拟器中运行kernel8.img

上述步骤是将原先可用的串口UART0变为了串口UART5,所以还是只启用了一个串口,因此

  1. 在rust-raspberrypi-OS-tutorials/06_uart_chainloader中修改并添加代码,使得串口UART0和UART5都被直接启用

至此,实验三结束,最终提交实验过程记录(包含出现的各类问题及解决办法)以及屏幕不断打印字母A的截图。

实验四:通过 UART0 启用 UART2/3/4

根据实验三启用UART5,尝试再启用一个新的串口UART2/3/4。

同实验三,如果没有树莓派,可以查看rust-raspberrypi-OS-tutorials/06_uart_chainloader关于gpio和uart的代码,修改相关代码来启用uart2/3/4。

最终提交实验过程记录(包含出现的各类问题及解决办法)以及修改的代码部分,并且可以成功通过test测试。

实验五:通过 UART0 发送指令,由 UART2/3/4/5 驱动小车

由实验二和实验三,考虑将树莓派通过 UART0 与主机相连,通过 UART5 与小车相连,然后可以在主机端驱动小车运动

因此,本实验要求为:在第一阶段实验五和第二阶段实验二、三、四的基础上,在shell命令相关代码中添加一个 move 命令,使得可以通过主机下达命令从而驱使小车运动

类似步骤为:

  1. 在shell命令中添加move函数,实现驱动小车的功能。

  2. 将新生成的关于shell的镜像与kernel8.img合并,复制到SD卡上。

  3. 树莓派通过 UART0 (引脚6(GND)、8(TXD)、10(RXD))与主机相连,通过 UART5 (引脚34(GND)、32(TXD)、33(RXD))与小车相连。

  4. 打开连接软件,启动树莓派电源。

  5. 在shell命令中输入move相关命令,观察发现小车开始运动(注意:在实验时应该将小车四轮悬空)。

至此,实验五结束,最终提交实验过程记录(包含出现的各类问题及解决办法)以及move部分的代码以及通过主机端控制小车运动的视频。

第三阶段:Rust编写树莓派USB驱动

经过第二阶段的了解、学习,可以发现目前如果想要用树莓派驱动小车需要对STM32板子进行飞线处理,而这需要进行焊接,非常的不方便,而且树莓派本身含有许多的USB接口,因此,考虑编写USB转串口驱动,使得树莓派可以通过USB接口控制小车运动,就像这样:

通过串口控制

背景资料:https://github.com/orgs/chenlongos/discussions/14

参考资料:

  1. https://blog.csdn.net/fudan_abc
  2. VL805相关手册
  3. xhci协议

大概可以分为以下几步:

  1. PCIe总线初始化

  2. PCIe检测设备、分配内存空间

  3. XHCI主机控制器初始化

  4. XHCI检测设备、分配地址空间

  5. 解析设备配置,加载对应驱动

  6. USB转串口的设备驱动实现

  7. 完成技术需求,撰写技术总结文档

任务一:PCIe主桥初始化

何为PCIe

PCIe(Peripheral Component Interconnect Express),是一种高速串行计算机扩展总线标准,用于连接计算机内部的硬件设备。PCIe是PCI的后继者,旨在提供更高的带宽和性能。

关键特点:

  1. 高速串行连接: 与传统的PCI总线使用并行连接不同,PCIe采用高速串行连接。这使得数据能够以更高的速率进行传输,提供更大的带宽。

  2. 多通道架构: PCIe采用多通道(lanes)的结构,每个通道都能独立传输数据。每个通道的带宽是独立分配的,通常以“x1”、“x4”、“x8”和“x16”等倍数来表示。例如,PCIe x16插槽提供比PCIe x1插槽更大的带宽。

  3. 点对点连接: 每个PCIe设备都与主板上的PCIe插槽之间建立一个点对点的连接。这种连接方式与传统PCI总线的共享连接方式相比,更有效地支持高带宽需求。

  4. 热插拔支持: PCIe支持热插拔,允许用户在计算机运行时插入或拔出PCIe设备,而无需重新启动计算机。

  5. 兼容性: PCIe是一种通用的总线标准,广泛应用于各种设备,包括显卡、网卡、固态硬盘、扩展卡等。

初始化实现

参考代码:https://github.com/Axsl666/arceos

运行结果:

       d8888                            .d88888b.   .d8888b.
      d88888                           d88P" "Y88b d88P  Y88b
     d88P888                           888     888 Y88b.
    d88P 888 888d888  .d8888b  .d88b.  888     888  "Y888b.
   d88P  888 888P"   d88P"    d8P  Y8b 888     888     "Y88b.
  d88P   888 888     888      88888888 888     888       "888
 d8888888888 888     Y88b.    Y8b.     Y88b. .d88P Y88b  d88P
d88P     888 888      "Y8888P  "Y8888   "Y88888P"   "Y8888P"

arch = aarch64
platform = aarch64-raspi4
target = aarch64-unknown-none-softfloat
smp = 1
build_mode = release
log_level = debug

[  0.108847 0 axruntime:126] Logging is enabled.
[  0.114577 0 axruntime:127] Primary CPU 0 started, dtb = 0x0.
[  0.121522 0 axruntime:129] Found physcial memory regions:
[  0.128209 0 axruntime:131]   [PA:0x80000, PA:0x89000) .text (READ | EXECUTE | RESERVED)
[  0.137498 0 axruntime:131]   [PA:0x89000, PA:0x8c000) .rodata (READ | RESERVED)
[  0.146093 0 axruntime:131]   [PA:0x8c000, PA:0x90000) .data .tdata .tbss .percpu (READ | WRITE | RESERVED)
[  0.157033 0 axruntime:131]   [PA:0x90000, PA:0xd0000) boot stack (READ | WRITE | RESERVED)
[  0.166584 0 axruntime:131]   [PA:0xd0000, PA:0xd1000) .bss (READ | WRITE | RESERVED)
[  0.175613 0 axruntime:131]   [PA:0x0, PA:0x1000) spintable (READ | WRITE | RESERVED)
[  0.184643 0 axruntime:131]   [PA:0xd1000, PA:0xfc000000) free memory (READ | WRITE | FREE)
[  0.194193 0 axruntime:131]   [PA:0xfe201000, PA:0xfe202000) mmio (READ | WRITE | DEVICE | RESERVED)
[  0.204525 0 axruntime:131]   [PA:0xff841000, PA:0xff849000) mmio (READ | WRITE | DEVICE | RESERVED)
[  0.214857 0 axruntime:131]   [PA:0xfd500000, PA:0xfd509310) mmio (READ | WRITE | DEVICE | RESERVED)
[  0.225189 0 axruntime:149] Initialize platform devices...
[  0.231874 0 axruntime:185] Primary CPU 0 init OK.
Available commands:
  exit
  help
  uname
  ldr
  str
  uart
  test
arceos# [  0.245158 0 brcm_pcie::bcm2711:309] assert bridge reset
[  0.251670 0 brcm_pcie::bcm2711:313] assert fundamental reset
[  0.258618 0 brcm_pcie::bcm2711:319] deassert bridge reset
[  0.265304 0 brcm_pcie::bcm2711:326] enable serdes
[  0.271294 0 brcm_pcie::bcm2711:333] hw_rev772
[  0.276935 0 brcm_pcie::bcm2711:339] disable and clear any pending interrupts
[  0.340273 0 brcm_pcie::bcm2711:406] PCIe link is ready

arceos# ldr ffff0000fd508000
Value at address ffff0000fd508000: 0x34831106

代码分析

#![allow(unused)]
fn main() {
//相关寄存器地址及配置
register_bitfields![
    u32,

    //  Broadcom STB PCIe Register Offsets
    // 0x0188
    RC_CFG_VENDOR_VENDOR_SPECIFIC_REG1 [
        LITTLE_ENDIAN OFFSET(0) NUMBITS(1) [],
        ENDIAN_MODE_BAR2 OFFSET(0xC) NUMBITS(1) [],
    ],

    // 0x043c
    RC_CFG_PRIV1_ID_VAL3 [
        CLASS_ID  OFFSET(0) NUMBITS(24) [
            pcie_pcie_bridge = 0x060400
        ],
    ],

    ...
    
    RGR1_SW_INIT_1 [
        PCIE_RGR1_SW_INTI_1_PERST OFFSET(0) NUMBITS(1) [],
        RGR1_SW_INTI_1_GENERIC OFFSET(1) NUMBITS(1) [],
    ],

];

...

impl BCM2711PCIeHostBridgeRegs {
    // 设置桥接器软初始化标志
    fn bridge_sw_init_set(&self, bit: u32) {
        // 如果 bit 为 1,将 RGR1_SW_INTI_1_GENERIC 置位
        if bit == 1 {
            self.rgr1_sw_init
                .modify(RGR1_SW_INIT_1::RGR1_SW_INTI_1_GENERIC::SET);
        }
        // 如果 bit 为 0,清除 RGR1_SW_INTI_1_GENERIC 位
        if bit == 0 {
            self.rgr1_sw_init
                .modify(RGR1_SW_INIT_1::RGR1_SW_INTI_1_GENERIC::CLEAR);
        }
    }

    // 设置 PERST(复位)标志
    fn perst_set(&self, bit: u32) {
        // 如果 bit 为 1,将 PCIE_RGR1_SW_INTI_1_PERST 置位
        if bit == 1 {
            self.rgr1_sw_init
                .modify(RGR1_SW_INIT_1::PCIE_RGR1_SW_INTI_1_PERST::SET);
        }
        // 如果 bit 为 0,清除 PCIE_RGR1_SW_INTI_1_PERST 位
        if bit == 0 {
            self.rgr1_sw_init
                .modify(RGR1_SW_INIT_1::PCIE_RGR1_SW_INTI_1_PERST::CLEAR);
        }
    }
}

 pub fn setup(&self) {
    let regs = self.regs();

    // 断言桥复位
    // 确保 PCIe 控制器处于已知状态,实际上是将 PCIe 控制器的状态置于一个初始值
    regs.bridge_sw_init_set(1);
    log::debug!("assert bridge reset");

    // 断言基本复位
    // 将整个 PCIe 控制器或者相关模块复位到初始状态,以确保系统在初始化开始时处于一种可控制和已知状态
    regs.perst_set(1);
    log::debug!("assert fundamental reset");

    H::sleep(core::time::Duration::from_micros(2));

    // 解除桥复位
    //标志 PCIe 控制器已经完成了一系列的初始化步骤,并且认为 PCIe 控制器已经准备好正常工作
    regs.bridge_sw_init_set(0);
    log::debug!("deassert bridge reset");

    H::sleep(core::time::Duration::from_micros(2));

    // 启用 SerDes,确保 PCIe 控制器能够正常进行数据传输
    regs.misc_hard_pcie_hard_debug
        .modify(MISC_HARD_PCIE_HARD_DEBUG::SERDES_IDDQ::CLEAR);
    log::debug!("enable serdes");

    H::sleep(core::time::Duration::from_micros(2));

    // 获取硬件版本
    let hw_rev = regs.misc_revision.read(MISC_REVISION::MISC_REVISION) & 0xFFFF;
    log::debug!("hw_rev: {}", hw_rev);

    // 禁用和清除任何挂起的中断,保在 PCIe 控制器初始化的过程中,系统处于一个可控的状态
    regs.msi_intr2_clr.write(MSI_INTR2_CLR::INTR_CLR::SET);
    regs.msi_intr2_mask_set
        .write(MSI_INTR2_MASK_SET::INTR_MASK_SET::SET);
    log::debug!("disable and clear any pending interrupts");

    // 初始化设置 SCB_MAX_BURST_SIZE 0x0, CFG_READ_UR_MODE, SCB_ACCESS_EN
    regs.misc_misc_ctrl
        .modify(MISC_MISC_CTRL::SCB_ACCESS_EN::SET);
    regs.misc_misc_ctrl
        .modify(MISC_MISC_CTRL::CFG_READ_UR_MODE::SET);
    regs.misc_misc_ctrl
        .modify(MISC_MISC_CTRL::MAX_BURST_SIZE::CLEAR);

    // 设置入站内存视图
    regs.misc_rc_bar2_config_lo
        .write(MISC_RC_BAR2_CONFIG_LO::VALUE_LO::init_val);
    regs.misc_rc_bar2_config_hi
        .write(MISC_RC_BAR2_CONFIG_HI::VALUE_HI::init_val);
    regs.misc_misc_ctrl
        .modify(MISC_MISC_CTRL::SCB0_SIZE::init_val);

    // 禁用 PCIe->GISB 内存窗口和 PCIe->SCB 内存窗口
    regs.misc_rc_bar1_config_lo
        .modify(MISC_RC_BAR1_CONFIG_LO::MEM_WIN::CLEAR);
    regs.misc_rc_bar3_config_lo
        .modify(MISC_RC_BAR3_CONFIG_LO::MEM_WIN::CLEAR);

    // 设置 MSIs,清除中断,屏蔽中断
    // CPU::MMIOWrite32(pcieBase + MSI_BAR_CONFIG_LO, (MSI_TARGET_ADDR & 0xFFFFFFFFu) | 1);
    // CPU::MMIOWrite32(pcieBase + MSI_BAR_CONFIG_HI, MSI_TARGET_ADDR >> 32);
    // CPU::MMIOWrite32(pcieBase + MSI_DATA_CONFIG, hwRev >= HW_REV_33 ? 0xffe06540 : 0xFFF86540);
    // TODO: 在此注册 MSI 处理程序

  

    // 解除基本复位
    // 在确认初始化步骤完成后,将 PCIe 控制器从基本复位状态恢复到正常工作状态
    regs.perst_set(0);

    // 等待 [0xfd504068] 的位 4 和 5 被设置,每隔 5000 微秒检查一次
    for _ in 0..20 {
        let val = regs.misc_pcie_status.read(MISC_PCIE_STATUS::CHECK_BITS);
        log::trace!("val: {}", val);
        if val == 0x3 {
            break;
        }
        H::sleep(core::time::Duration::from_micros(5000));
    }

    // 检查链路是否正常
    {
        let val = regs.misc_pcie_status.read(MISC_PCIE_STATUS::CHECK_BITS);
        if val != 0x3 {
            panic!("PCIe link is down");
        }
    }

    // 检查控制器是否运行在根复杂模式。如果位 7 未设置,则发生错误
    {
        let val = regs.misc_pcie_status.read(MISC_PCIE_STATUS::RC_MODE);
        if val != 0x1 {
            panic!("PCIe controller is not running in root complex mode");
        }
    }

    log::debug!("PCIe link is ready");

    // 配置出站内存
    // 定义 PCIe 设备可以访问的一块内存区域,使 PCIe 设备可以读取或写入这个内存区域中的数据
    regs.misc_cpu_2_pcie_mem_win0_lo
        .write(MISC_CPU_2_PCIE_MEM_WIN0_LO::MEM_WIN0_LO::init_val);
    regs.misc_cpu_2_pcie_mem_win0_hi
        .write(MISC_CPU_2_PCIE_MEM_WIN0_HI::MEM_WIN0_HI::init_val);
    regs.misc_cpu_2_pcie_mem_win0_base_limit
        .write(MISC_CPU_2_PCIE_MEM_WIN0_BASE_LIMIT::MEM_WIN0_BASE_LIMIT::init_val);
    regs.misc_cpu_2_pcie_mem_win0_base_hi
        .write(MISC_CPU_2_PCIE_MEM_WIN0_BASE_HI::MEM_WIN0_BASE_HI::init_val);
    regs.misc_cpu_2_pcie_mem_win0_limit_hi
        .write(MISC_CPU_2_PCIE_MEM_WIN0_LIMIT_HI::MEM_WIN0_LIMIT_HI::init_val);

    // 设置正确的 Class ID
    regs.rc_cfg_priv1_id_val3
        .modify(RC_CFG_PRIV1_ID_VAL3::CLASS_ID::pcie_pcie_bridge);

}
}

任务二:PCIe主机桥为PCIe设备分配内存空间

流程:

  1. 枚举PCIe设备:使用操作系统提供的PCIe总线驱动程序,首先枚举(或探测)连接到主机系统上的PCIe设备。这将识别和注册每个设备,并为其分配一个唯一的设备标识符。

  2. 识别设备和配置空间:对于每个已枚举的PCIe设备,读取其配置空间来获取设备的相关信息,例如设备ID、制造商ID、设备类、中断号等。配置空间是设备的一部分,包含用于描述设备和其资源分配的寄存器。

  3. 分配内存空间:根据设备的需求,向设备分配内存空间。可以通过操作系统的内存管理机制进行。

  4. 映射内存空间:将分配的内存空间映射到设备的地址空间中,以便设备可以访问该内存。

参考代码仓库:https://github.com/dbydd/arceos_experiment/tree/task3_usb

运行结果:


       d8888                            .d88888b.   .d8888b.
      d88888                           d88P" "Y88b d88P  Y88b
     d88P888                           888     888 Y88b.
    d88P 888 888d888  .d8888b  .d88b.  888     888  "Y888b.
   d88P  888 888P"   d88P"    d8P  Y8b 888     888     "Y88b.
  d88P   888 888     888      88888888 888     888       "888
 d8888888888 888     Y88b.    Y8b.     Y88b. .d88P Y88b  d88P
d88P     888 888      "Y8888P  "Y8888   "Y88888P"   "Y8888P"

arch = aarch64
platform = aarch64-raspi4
target = aarch64-unknown-none-softfloat
smp = 1
build_mode = release
log_level = debug

[  0.111216 0 axruntime:126] Logging is enabled.
[  0.116945 0 axruntime:127] Primary CPU 0 started, dtb = 0x0.
[  0.123891 0 axruntime:129] Found physcial memory regions:
[  0.130578 0 axruntime:131]   [PA:0x80000, PA:0x91000) .text (READ | EXECUTE | RESERVED)
[  0.139866 0 axruntime:131]   [PA:0x91000, PA:0x96000) .rodata (READ | RESERVED)
[  0.148462 0 axruntime:131]   [PA:0x96000, PA:0x9a000) .data .tdata .tbss .percpu (READ | WRITE | RESERVED)
[  0.159402 0 axruntime:131]   [PA:0x9a000, PA:0xda000) boot stack (READ | WRITE | RESERVED)
[  0.168952 0 axruntime:131]   [PA:0xda000, PA:0xff000) .bss (READ | WRITE | RESERVED)
[  0.177982 0 axruntime:131]   [PA:0x0, PA:0x1000) spintable (READ | WRITE | RESERVED)
[  0.187012 0 axruntime:131]   [PA:0xff000, PA:0xfc000000) free memory (READ | WRITE | FREE)
[  0.196562 0 axruntime:131]   [PA:0xfe201000, PA:0xfe202000) mmio (READ | WRITE | DEVICE | RESERVED)
[  0.206894 0 axruntime:131]   [PA:0xff841000, PA:0xff849000) mmio (READ | WRITE | DEVICE | RESERVED)
[  0.217226 0 axruntime:131]   [PA:0xfd500000, PA:0xfd509310) mmio (READ | WRITE | DEVICE | RESERVED)
[  0.227558 0 axruntime:131]   [PA:0xf8000000, PA:0xfc000000) mmio (READ | WRITE | DEVICE | RESERVED)
[  0.237890 0 axruntime:149] Initialize platform devices...
[  0.244575 0 axruntime:185] Primary CPU 0 init OK.
[  0.250567 0 brcm_pcie::bcm2711:309] assert bridge reset
[  0.257078 0 brcm_pcie::bcm2711:313] assert fundamental reset
[  0.264026 0 brcm_pcie::bcm2711:319] deassert bridge reset
[  0.270711 0 brcm_pcie::bcm2711:326] enable serdes
[  0.276702 0 brcm_pcie::bcm2711:333] hw_rev772
[  0.282343 0 brcm_pcie::bcm2711:339] disable and clear any pending interrupts
[  1.290781 0 brcm_pcie::bcm2711:408] PCIe link is ready
Available commands:
  exit
  help
  uname
  ldr
  str
  uart
  test
  move
  tud
arceos# tud
[  3.236644 0 brcm_pcie::bcm2711:309] assert bridge reset
[  3.240455 0 brcm_pcie::bcm2711:313] assert fundamental reset
[  3.247403 0 brcm_pcie::bcm2711:319] deassert bridge reset
[  3.254088 0 brcm_pcie::bcm2711:326] enable serdes
[  3.260079 0 brcm_pcie::bcm2711:333] hw_rev772
[  3.265720 0 brcm_pcie::bcm2711:339] disable and clear any pending interrupts
[  4.274160 0 brcm_pcie::bcm2711:408] PCIe link is ready
[  4.277714 0 axdriver:158] Initialize device drivers...
[  4.284137 0 axdriver:159]   device model: static
[  4.290041 0 axdriver::bus::pci:144] base_vaddr:ffff0000fd500000
[  4.297248 0 axdriver::bus::pci:155] iter 0
[  4.302666 0 axdriver::bus::pci:160] PCI 00:01.0: 1106:3483 (class 0c.03, rev 01) Standard
[  4.312094 0 axdriver::bus::pci:162] in!
[  4.317250 0 axdriver::bus::pci:37] type 64
[  4.322632 0 axdriver::bus::pci:61]   BAR 0: MEM [0x600000000, 0x600001000) 64bit
[  4.331282 0 axdriver::bus::pci:79] two ents
[  4.336805 0 axdriver::drivers:168] vl805 found! at DeviceFunction { bus: 0, device: 1, function: 0 }
[  4.347203 0 axdriver::drivers:171] Memory space at 0x600000000, size 4096, type Width64, prefetchable false
[  4.358227 0 axdriver::drivers:181] status:10
[  4.363754 0 axdriver::drivers:182] command:7
[  4.369311 0 axdriver::bus::pci:177] registered a new XHCI device at 00:01.0: "xhci-controller"
[  4.379209 0 axdriver:166] number of NICs: 0
[  4.384679 0 axdriver:190] number of xhci devices: 1
[  4.390843 0 axdriver:193]   xhci device 0: "xhci-controller"
arceos#

任务三:xhci 主机控制器初始化

任务四:xhci 检测设备,分配地址空间

任务五:解析设备配置,加载对应驱动

任务六:USB转串口的设备驱动实现

参考资料:

  1. https://blog.csdn.net/mao_hui_fei/article/details/107592985
  2. https://github.com/orgs/chenlongos/discussions
  3. https://github.com/MrRobertYuan/report/blob/main/docs/2023/20230601_How-to-run-ArceOS-on-raspi4.md
  4. https://datasheets.raspberrypi.com/bcm2711/bcm2711-peripherals.pdf
  5. https://elinux.org/RPi_BCM2711_GPIOs#GPIO0
  6. https://juejin.cn/post/6977611730784354334
  7. https://github.com/chenlongos/rust-raspberrypi-OS-tutorials/tree/master/06_uart_chainloader
  8. https://pinout.xyz