Raspberry Pi

来自osdev
Zhang3讨论 | 贡献2022年3月24日 (四) 07:58的版本 (创建页面,内容为“{{In Progress}} {{FirstPerson}} {{Sole Editor}} 这是关于Raspberry Pi上裸机[OS]开发的教程。 本教程是专门为Raspberry PI Model B Rev2编写的,因为作者没有其他硬件可以测试。 但是到目前为止,从本教程的目的出发,这些型号基本上是相同的 (Rev 1有256MB ram,Model A没有以太网)。 这是作者的第一个ARM系统,我们在写作时学习,没有任何关于ARM的知识。 假定并要求具备Li…”)
(差异) ←上一版本 | 最后版本 (差异) | 下一版本→ (差异)
跳到导航 跳到搜索

这个页面正在建设中! 此页面或部分内容仍在改进中,因此可能还不完整。 其内容可能会在不久的将来更改。

本页面或章节指其读者或编者使用人称 , 我的, 我们 或者 我们的。 用encyclopedic百科全书式的语言风格进行编辑

这篇文章写得好像只有一个作者。这是一个wiki,不是个人网站。您可以通过 编辑 这篇文章来帮助本wiki减少对单作者的依赖。

这是关于Raspberry Pi上裸机[OS]开发的教程。 本教程是专门为Raspberry PI Model B Rev2编写的,因为作者没有其他硬件可以测试。 但是到目前为止,从本教程的目的出发,这些型号基本上是相同的 (Rev 1有256MB ram,Model A没有以太网)。

这是作者的第一个ARM系统,我们在写作时学习,没有任何关于ARM的知识。 假定并要求具备Linux/Unix(非常重要)和C/C++语言(非常非常重要,包括如何使用内联汇编器)的经验。 这不是关于如何构建内核的教程,而是关于如何开始使用RPi的简单介绍。

准备工作

材料

你将需要:

  • Raspberry Pi,简称RPI。
  • 用以引导的Sd卡。
  • 一个SD卡读卡器,这样你就可以从开发系统中写入SD卡了。
  • RPi的串行适配器。
  • 来自外部电源,USB或串行适配器的电源。

串行适配器

RPI有2个串口(UARTS)。 本教程仅涉及UART0,简称UART或串口。 UART1从此被忽略。 基本UART板载使用3.3V TTL,并连接到板上标有 “P1” 的一些GPIO引脚。 x86个人电脑和Mac电脑实际使用5V的TTL,所以需要一些适配器来转换TTL。 我建议使用 USB到TTL串行电缆-用于Raspberry Pi的调试/控制台电缆,每条引线都有单独的连接器,例如 商用RPi串行适配器。 然后连接到RPi像这样。 稍微便宜一点的USBPL2303HX适配器被发现也可以使用,但如果用延长电缆连接到端口似乎不可靠(USB2.0集线器可能会解决这个问题)。

注意: 我使用的串行适配器同时提供0v和5v引线 (黑色和红色),为RPi提供电源。 除此之外,不需要额外的电源。

测试硬件/串口

首先,你要确保所有硬件都能正常工作。 将串行适配器连接到RPi并启动正式的Raspian映像。 引导过程将输出到串口和HDMI,并在串口上启动getty。 设置你的串口,无论你的端口如何工作,并打开minicom。 确保流量控制已关闭。 确保你可以以115200波特率、8N1的速度运行,这是RPi所使用的。

如果你得到提示 “权限被拒绝(Permission Denied)” 不需要Root! 这是不必要的。 相反,你应该:

sudo adduser <user> dialout

这将使您的用户无需root即可使用串行端口。

或者执行ls -l /dev/ttyS*以查找拥有该设备的组,然后将你添加到/etc/group下的组中(通常该组为uucp)

如果仅在RPi启动后才启动minicom,则只需在minicom中按 return,这样getty将输出新的登录提示。 否则,等待启动消息出现。 如果你没有得到任何输出,则将RPi连接到显示器以检查它是否真的启动,检查你的连接和Minicom设置。

构建交叉编译器

像我一样,你可能正在使用一台x86 PC作为主机,并希望在其上编辑和编译源代码,而RPi是一个ARM CPU,所以你绝对需要一个交叉编译器。 但是,即使你在ARM系统上进行开发,构建交叉编译器仍然是一个好主意,以避免意外地将你的开发系统中的东西与你自己的内核混为一谈。 按照 GCC Cross-Compiler 中的步骤构建自己的交叉编译器,但使用:

export TARGET=arm-none-eabi

现在你可以开始了。

教程和示例

  1. 汇编教程(剑桥大学)
  2. C教程
  3. Rust教程
  4. dwelch67收集示例和引导程序
  5. bzt提供的C语言AArch64教程
  6. s-matyukevich所著的Raspberry Pi操作系统教程
  7. 检测Raspberry Pi板

启动内核

当你测试上面的硬件时,SD卡上还有原始的Raspian映像吗? 太棒了。 所以你已经有一个带有引导分区和所需文件的sd卡。 如果没有,则下载一个原始的Raspberry引导映像,并将其复制到SD卡。

现在从SD卡挂载第一个分区并查看它:

bootcode.bin  fixup.dat     kernel.img            start.elf
cmdline.txt   fixup_cd.dat  kernel_cutdown.img    start_cd.elf
config.txt    issue.txt     kernel_emergency.img

当RPi通电时,ARM CPU停止,GPU运行。 GPU从rom加载引导加载程序并执行它。 然后找到SD卡并加载bootcode.bin。 bootcode处理config.txt和cmdline.txt (或者start.elf读那个?),然后运行start.elf。 开始elf加载kernel.img设置为0x00008000,将一些操作码设置为0x00000000,ATAG设置为0x0000100,最后ARM CPU启动。 CPU在0x00000000开始执行,它将初始化R0、R1和R2,并跳转到内核映像启动的0x00008000。

因此,要启动你自己的内核,只需用替换我们自己的kernel.img,umount,sync,将sd卡插入RPi并打开电源即可。

注意:GPU还初始化了视频输出,从监视器(如果是hdmi)或config.txt中检测到正确的分辨率并创建一个2x2像素的帧缓冲区(红色、黄色、蓝色和青色像素),硬件通过颜色插值缩放到全屏。 所以你会得到颜色褪色效果不错的矩形。

从串口启动

RPi直接从SD卡引导内核,并且只能从SD卡引导内核。 没有其他选择。 在开发过程中,由于必须不断地将sd卡从RPi交换到sd卡读卡器,然后再返回,因此变得令人厌烦。 将内核反复写入SD卡也会使SD卡磨损。 另外,SD卡插槽有些易碎;有几个人报告说他们不小心把它弄坏了。 总的来说不是一个理想的解决方案。 那么我们能做些什么呢?

我已经根据上面的C教程编写了一个名为Raspboot的小型引导加载程序,它从串口加载真正的内核。 Raspbootin由作为引导服务器和终端程序的Raspbootcom (相同的存储库) 提供。 使用这两个,我只需要重新启动我的RPi就可以让它启动最新的内核。 这使得硬件的测试更快、更安全。

Raspbootin对于你的内核是完全透明的。 它为自制内核保留了r0、r1和r2寄存器和GPU放入内存的ATAG。 因此,无论你是直接从SD卡引导内核,还是通过串口使用Raspboot引导内核,都不会对你的代码造成影响。

Raspbootin串行协议

除非你想编写自己的启动服务器,否则你不必关心这个问题。

Raspboot的引导协议相当简单。 Raspboot首先通过串行线发送3个中断(\x03),表示它已经准备好接收内核。 然后,它期望内核的大小为 uint32_t,以小字节序( little endian)排列。 如果尺寸可接受,则在尺寸后回复“OK”;如果尺寸太大,则回复“SE”。 在“OK”之后,它需要大小很多字节来表示内核。 就这样。

解析ATAGs

一个很好的ATAGs文档可以在 这里找到。

来自Wayback机器的缓存版本

请注意,以后Raspberry Pi将在R2中向你传递设备树BLOB。 如果禁用设备树 (在这种情况下r2包含0x0),则仍然可以在0x100处找到ATAGs - 可以通过ATAG_CORE (0x54410001) 开头来识别。 相比之下,设备树可能更有用,但更复杂。 设备树以uint32_t 0xd00dfeed(大端字序)。请注意big-endian-这适用于所有值。 ARM默认为小端(little endian),所以您可能想要早点编写一些端序例程(endian routines)! Device Tree Specification

帧缓冲区支持

在引导时,RPi将显示器配置为2x2像素的虚拟分辨率,缩放为全屏。 每个像素都有不同的颜色,并且硬件缩放会对颜色进行插值以显示出不错的颜色褪色。 所以,在你做任何事情之前,首先连接一个监视器,确保你得到一些输出。

对于帧缓冲区,你需要了解如何access mailboxes。 然后,你必须向GPU发送一些 mail。 或者,你可以在这里阅读有关帧缓冲区的内容,

首先从一个简单的查询开始,然后构建更复杂的mails。 如果你可以正常工作,那么我建议你仅更改虚拟大小和颜色深度,并保持物理分辨率不变。 RPi似乎可以很好地检测监视器,用户还可以在SD卡上的引导配置文件中配置分辨率。 最好尊重它的意愿。

中断和异常

默认情况下,ARM上的异常向量表从0x0开始。 你可以这样用,但还有更好的方法。 你可以设置一个标志以在0xffff0000处使用高向量,或者设置异常向量基址寄存器以指向你自己喜欢的任何位置 (32字节对齐) 的表。

注: 中断是电平触发的,因此你必须在从中断返回之前清除中断源或屏蔽它。 RPi中的ARM CPU还支持一些额外的指令,用于将寄存器存储在不同模式,切换模式和从中断返回的堆栈上。 这些扩展在ARM上有很好的描述。

注: 中断期间LR中的返回地址将是0-8字节,这取决于异常的类型,偏移到它应该是什么,并且需要在返回之前进行调整。 再次查看适用于哪个异常的偏移量。

首先实现和测试软件中断是一个好主意,因为你可以以受控的方式触发它。

当配置一些外设发送中断时,在CPSR中禁用中断是一件有用的事情,在3个中断启用寄存器中启用你感兴趣的中断 (或全部),然后在紧密循环中轮询3个挂起的寄存器并输出更改。 这允许你查看外设是否引发中断,以及(如果怀疑)是哪一个。 之后,可以配置和测试实际的中断处理程序。 给你一个很好的中途点来测试你到目前为止的情况。

浮点运算支持

为了能够使用任何浮点操作,例如存储或加载浮点数,你需要在使用FPU之前启用它。 要做到这一点,为了无论谁应该能够使用它,你必须启用对协处理器的访问,你必须启用FPU本身。

# 在协处理器启用寄存器(coprocessor enable register)中启用FPU - 这使每个人都可以访问协处理器的两个位置。
ldr r0, =(0xF << 20)
mcr p15, 0, r0, c1, c0, 2

然后启用FPU本身:

# 在FP异常寄存器(FP exception register)中启用FPU
MOV r3, #0x40000000
#  VMSR FPEXC, r3    # 汇编程序错误
.long 0xeee83a10

第三行是你想要使用的实际指令,但是由于Binutils 2.23中的一个错误,它不能汇编。 它下面的行是它应该汇编到的,并替换了操作码(opcode)。 完成这两项之后,就可以使用FPU了。

USB

一个独立的支持键盘和鼠标的bsd许可的usb驱动程序可以在这里获得:https://github.com/Chadderz121/csud。 这个驱动程序可以保持独立,通过编辑 /source/platform.c 文件来将驱动程序与你的实现 malloc() 和类似的功能接口,或者,你可以将驱动程序与操作系统更紧密地集成在一起。

外部参考

  1. arm_arm.pdf - 通用ARM架构参考手册v6
  2. DDI0301H_arm1176jzfs_r0p7_trm.pdf - 针对RPi的更具体的ARM
  3. dwelch67 examples - 基本工具链 + UART工具
  4. RPi_Hardware - datasheet表 (以及关于Broadcom芯片上外围设备的一份手册)
  5. GitHub Raspberry Pi firmware wiki - 邮箱和视频资料
  6. BCM2835-ARM-Peripherals.pdf - RPI外围设备的Datasheet
  7. Booting ARM Linux - 描述了RPi引导加载程序模拟的Linux的ARM端口的通用引导加载程序接口
  8. RPi Emulator - 预配置的Windows QEMU RPI仿真环境。