定制 Linux:移植指南

将 LinuxPPC 移植到定制 SBC

Shie Erlich



          

Rafi Yanai - 我在移植过程中的伙伴

Avi Rubenbach - 没有他,这一切不可能实现

修订历史
修订版 2.12003-03-08修订者:gjf
根据作者修改了代码示例
修订版 2.02002-06-13修订者:tab
根据作者添加了 GFDL
修订版 1.02002-05-13修订者:SE
初始版本

目录
1. 简介
1.1. 谁需要阅读本文?
1.2. 我需要了解什么(为什么这么多)?
1.3. 工具
1.4. 硬件
1.5. 版权与许可
2. 训练营:如何开始?
2.1. 创建开发环境
2.2. 编译第一个内核
2.3. 启动机器
3. 在黑暗中启动
3.1. 使用 print_str() 进行调试
3.2. 使用编译器标志修改代码
3.3. 使控制台工作
4. Linux 仍然无法启动
4.1. 内存探测、RTC 和递减器
4.2. 大端小端(我们应该知道的)
4.3. 以太网:我们的第一个 PCI 设备
4.4. 一些其他问题
5. Linux 正在启动...现在怎么办?
5.1. 64 位障碍
5.2. 从闪存启动
A. GNU 自由文档许可证
A.1. 序言
A.2. 适用性和定义
A.3. 逐字复制
A.4. 批量复制
A.5. 修改
A.6. 文档合并
A.7. 文档集合
A.8. 与独立作品的聚合
A.9. 翻译
A.10. 终止
A.11. 本许可证的未来修订
A.12. 如何将本许可证用于您的文档
B. 商标

第 1 章。简介

1.1. 谁需要阅读本文?

本指南描述了一个正在进行的工作,即将 Linux 移植到定制的基于 PowerPC 的板卡。这意味着使操作系统在不熟悉的硬件上工作。任何在这条道路上的人都可能从阅读本文中受益,因为它突出了沿途的陷阱和问题点。


1.2. 我需要了解什么(为什么这么多)?

在尝试移植 Linux 之前,至少要了解以下内容:(在可能的情况下,附上了指向适当信息来源的链接)

  • 硬件:了解您拥有的硬件、它的工作原理(如果它工作)以及它是如何初始化的。获取您可以找到的所有硬件手册 - 您可能需要它们。此外,永远不要假设硬件会按照它应该的方式工作!硬件人员会做最糟糕的事情 :-(

  • 对驱动程序以及它们在 Linux 中的工作方式有基本的了解。简单的驱动程序编程知识是一个优势 - 但不是必须的。http://www.tldp.org/HOWTO/Module-HOWTO/index.html

  • 如何使用 Vision-ICE,如何配置它并使用它将二进制内核加载到目标 RAM 中。此外,在开始时,您需要知道如何使用 ICE 在汇编中进行调试。

  • 如何编译和配置 Linux 内核。http://www.tldp.org/HOWTO/Kernel-HOWTO.html

  • Linux 启动过程。http://www.tldp.org/HOWTO/BootPrompt-HOWTO.html

  • C 编程的工作知识是必须的。一些汇编知识肯定会有所帮助。此外,最好了解 Makefiles。它们有时会冒出来。

  • 互联网是您的朋友。您需要的所有信息可能都在网上。您只需要知道如何找到它。Google 是一个很好的开始;邮件列表和新闻组通常保留着真正的黄金。

  • 如何安装 Linux、配置它、管理它以及基本上照顾它需要的一切。本指南不涵盖有关系统管理、设置服务器等任何内容。


1.3. 工具

本节介绍我们在过程中使用的工具。大多数工具都很容易安装和使用。必要时,请查阅相应的网址或手册。

  • HardHat Linux:首先,HHLinux,现在称为 MontaVista Linux,是我们开始使用的发行版。该发行版包含适用于多种板卡配置的 PowerPC 的 LSP(与 BSP 相同)。为了移植到我们的板卡,我们采用了硬件上最接近我们的 Artysyn PMPPC 板卡的 LSP,并从那里开始。

  • LXR:这是杀手锏工具,它使我们能够在很短的时间内移植 Linux。LXR 是一个交叉引用器,这意味着它读取一段代码(例如 Linux 内核),然后允许浏览代码、搜索代码等等。我再怎么强调这个工具的重要性也不为过。要查看最终结果是什么样的,请查看 http://lxr.linux.no/source。LXR 本身可以在 http://lxr.sf.net 下载

  • VisionICE:一个硬件调试器,它能够停止、运行并在 CPU 中直接添加断点。当没有操作系统运行时,VisionIce 非常有用,并且允许在启动过程中单步执行内核。该应用程序还可以用于获取内核的二进制映像,将其加载到目标的 RAM 内存中并运行它 - 当您没有引导加载程序时很有用。

  • CVS:一个版本控制系统,允许您保留代码的多个版本。除了备份代码之外,它还允许在不同版本之间进行差异比较,并在需要时恢复到旧版本。

  • 终端程序,例如 Windows™ 的 HyperTerminalProCOMM,或 Linux 的 minicom


1.4. 硬件

该板卡基于 PPC750 (PowerPC) 处理器。它是 6U VME64 标准。该板卡设计用于容纳两个 PCI Mezzanine 卡 (CCPMC) - 可以连接符合 Std CCPMC1386 的 Mezzanine 卡。

  • COP 连接器。

  • 1 MB 的 L2 缓存。

  • CPC700 系统控制器。

  • 128 MB SDRAM,带 ECC。

  • 闪存,分为启动闪存和用户闪存。

  • NVRAM 内存。

  • I/O 离散量。

  • RS232 通道。

  • 通用寄存器。

  • PCI 2.1 本地总线。

  • 10/100 BaseT 以太网通道。

  • VME64 系统总线。


1.5. 版权与许可

版权所有 (c) 2002 Shie Elrich

根据自由软件基金会发布的 GNU 自由文档许可证 1.1 版或任何更高版本的条款,授予复制、分发和/或修改本文档的许可;没有不变部分,没有封面文本,也没有封底文本。许可证副本包含在附录 A中。


第 2 章。训练营:如何开始?

2.1. 创建开发环境

最低要求显然是一个开发站和一个目标。但是,推荐的工作方式是拥有第三台主机作为服务器。服务器运行多项服务,例如 ftp、telnet、NFS、tftp(如果需要)和 CVS。服务器的主要作用是运行 CVS 并跟踪版本控制,但是一旦您可以从网络启动目标,服务器还将保存目标映像和文件系统,这使得开发变得更加容易。

无论如何,第一步是为您的目标安装工具链(编译器、链接器等)。HardHat Linux 光盘包含所有需要的文件,并且 HardHat Linux 文档中记录了安装顺序。在安装过程中,您必须选择您的 LSP(所选板卡的基本软件),HardHat 将安装一组工具和一个与您的 LSP 匹配的内核源代码树。

我们有一个运行 vxWorks 的板卡,因此我们将目标设置为使用标准 vxWorks 引导加载程序启动。一旦引导加载程序启动,我们使用 visionICE 接管目标(以便 vxWorks 不会加载映像文件)并将 Linux 映像加载到目标中。此时您需要做的是获取 ICE,将其连接到网络和目标 - 通过 JTAG 连接 - 并在您的主机上安装 ICE 软件。

到目前为止应该完成的事情

  • 已安装 Linux 主机和 HardHat 工具链。

  • 工作目标(硬件应功能正常)

  • ICE 已连接到目标和网络,其软件可用。

  • 可选地,运行 CVS、telnetd、NFS 和 FTP 的服务器。


2.2. 编译第一个内核

如果您已安装 HardHat 附带的 Linux 内核,则交叉编译应该已在内核中启用Makefile。如果您的内核不是来自 HardHat CD,您应该在Makefile中通过以下方式定义 CROSS_COMPILE 条目来启用交叉编译:(来自主 Makefile 的代码段)

CROSS_COMPILE 	= /opt/hardhat/devkit/ppc/7xx/bin/ppc_7xx-
AS		= $(CROSS_COMPILE)as
LD		= $(CROSS_COMPILE)ld
CC		= $(CROSS_COMPILE)gcc

Linux 内核是模块化的,允许您配置它并选择哪些“块”应与内核一起编译。为了做到这一点,首先 cd /usr/src/linux(假设您的内核源代码安装在 /usr/src/linux)。进入后,键入 make xconfig。保存您的选项后,您应该 make vmlinux 以创建一个适用于 VisionICE 的内核映像。

我们不会在此处深入探讨更多细节,因为它超出了本文档的范围。有关更多信息,请尝试 http://www.tldp.org/HOWTO/Kernel-HOWTO.html


2.3. 启动机器

首先,配置终端程序,在我们的例子中是 minicom,按以下方式配置:9600 bps,8 位,无奇偶校验,1 个停止位,并且没有任何类型的流量控制。Linux 中的串行端口应该是/dev/ttyS0对于COM1, /dev/ttyS1对于COM2等等。

启动目标。您应该在终端屏幕上看到 vxWorks 引导加载程序,并且应该能够通过按空格键来停止引导序列。

Note

我们无法使用 vxWorks 引导加载程序来加载 Linux 内核,因为它会在 ELF 标头中查找并将映像加载到那里写入的地址。但是,使用虚拟内存的 Linux 内核链接到高内存地址,而 vxWorks 无法处理该地址。

一旦目标停止,运行 VisionICE 软件并执行以下步骤

  • 通过按 Target|Initialize 初始化目标

  • File|Load Executable。将打开一个对话框,要求您选择一个文件。请选择您的内核映像 (vmlinux)。在按 Load 之前,不要忘记在+/- 偏差字段中输入一个值。

    Tip

    偏差字段使 ICE 能够将特定映像加载到与 ELF 二进制文件中声明的地址不同的地址。我们希望将内核加载到地址0x300000,并且由于二进制文件链接到0xC0000000,我们输入了-0xBFD00000.

  • 一旦映像成功加载,您可以按 RunStep 开始执行您的内核。

按下 Run 按钮后,什么也没发生。在那一刻,以及之后的一段时间,似乎什么也没发生,内核卡住了。我们使用 ICE 单步执行内核的初始化代码,并排除了一些潜在的问题,例如虚拟内存错误,最终才发现问题很简单:内核确实正在启动,但是由于控制台 (tty) 驱动程序有问题,我们看不到任何东西!

Caution

VisionICE 不是调试 Linux 的正确工具。ICE 不了解虚拟内存和保护模式(至少我们拥有的版本是这样),并且由于 Linux 内核很早就开启了虚拟内存,因此 ICE 仅对于调试第一个汇编语句有用。在 VM 开启后,ICE 开始崩溃并给出奇怪的结果。


第 3 章。在黑暗中启动

3.1. 使用 print_str() 进行调试

如上一章所述,机器开始启动,但什么也没发生。至少,我们看不到任何东西。屏幕是空白的,并且没有内核消息出现。此时,您必须问自己,它真的在启动吗?

由于控制台无法启动,并且 ICE 很快就死掉了,我们别无选择。我们必须以某种方式进行调试,最古老的方法在这里很好用 - 打印到屏幕。显然,我们不能使用printk(),因此我们编写了一个简短的函数,该函数将字符直接推送到串行端口。我们使用了上一节中显示的启动过程“映射”,并沿途插入了一些打印。这帮助我们了解我们完成的阶段以及我们死在哪里。以下代码片段通过轮询并等待串行端口空闲来将单个字符打印到串行端口。
                                 /* tx holding reg empty or tx    */
#define LSR_THREMPTY 0x20        /* fifo is empty (in fifo mode ) */
#define THR_REG      0x00        /* Transmit holding reg */
#define LSR_REG      0x05        /* Line status reg */
#define COM1_ADDRESS 0xFF600300  /* == replace with your UART address */

void print_char (char ch) {
	volatile  unsigned char status = 0;
	/* wait until txempty */
	while ((status & LSR_THREMPTY ) == 0)
		status = *((volatile unsigned char *)(COM1_ADDRESS + LSR_REG));

	*((volatile unsigned char *)(COM1_ADDRESS + THR_REG)) = ch;
}

Note

有一种更好的代码可以直接打印到串行端口,但是,它有点复杂。您可以在 arch/ppc/boot/common/misc-common.c 中找到它,使用puts()putc().


3.2. 使用编译器标志修改代码

虽然这不是移植问题,但您修改代码的方式很重要。如果您第一次就做对了,它会更容易。Linux 内核使用标准配置标志 CONFIG_XXXX(例如 CONFIG_PPC、CONFIG_ISA 等),这些标志用于标记特定的机器、体系结构或设备。我们为自己定义了一个新标志(我们称之为 CONFIG_TESTMACH),并将我们的新/修改后的代码用这些标志包围起来
....original code....
#ifdef CONFIG_TESTMACH
....modified code....
#else
....original code....
#endif /* CONFIG_TESTMACH */
为了“激活”我们的代码,我们将新标志添加到内核配置文件 -.config- 通过向其中添加 CONFIG_TESTMACH=y。在第一阶段,此解决方案允许您快速找到您更改的代码,但在稍后阶段,您选择的标志将允许您将您的代码添加到内核树和配置程序 (make xconfig) 中。


3.3. 使控制台工作

3.3.1. 强制内核以我们的方式启动

一旦我们发现内核确实正在启动,但控制台没有打印,就该开始了。首先,我们强制内核使用串行端口的指定配置启动,在我们的例子中是9600n1,并且不允许任何命令行选项或启动时间考虑等。

第一个要去的地方是drivers/char/tty_io.c,到console_init()。此函数确定启动时的控制台配置。这是其中的一小部分
memset(, 0, sizeof(struct termios));
memcpy(tty_std_termios.c_cc, INIT_C_CC, NCCS);
tty_std_termios.c_iflag = ICRNL | IGNPAR;
tty_std_termios.c_oflag = OPOST | ONLCR;
tty_std_termios.c_cflag = CLOCAL | B9600 | CS8 | CREAD;
tty_std_termios.c_cflag &= ~(CRTSCTS);
tty_std_termios.c_lflag = ISIG | ICANON | ECHO | ECHOE | ECHOK | ECHOCTL | ECHOKE | IEXTEN;
tty_std_termios.c_iflag = ICRNL | IXON;
tty_std_termios.c_oflag = OPOST | ONLCR;
tty_std_termios.c_cflag = B38400 | CS8 | CREAD | HUPCL;
tty_std_termios.c_lflag = ISIG | ICANON | ECHO | ECHOE | ECHOK | ECHOCTL | ECHOKE | IEXTEN;
我们尝试的第一个(幼稚的)事情是按照我们想要的方式配置控制台。当然,这并没有对我们有太大帮助 ;-)

感到失望但没有气馁,我们记得我们还没有引导加载程序,并且我们真的不知道是否有任何选项传递给内核。“也许内核得到了一些命令行垃圾?”我们(再次,幼稚地)想。因此,我们尝试阻止内核解析命令行选项,并手动插入我们的命令行。这并没有对我们有太大帮助 ;-)


3.3.2. 非标准硬件 - 就说不!

那时,我们没有控制台,但我们有时间。因此,我们更深入地研究了控制台问题。查看drivers/char/serial.c,我们遇到了serial_console_setup()。除了解析命令行选项外,此函数还通过直接写入串行端口来配置串行端口。我们的硬件人员认为现在是时候告诉我们我们的串行端口不是标准的了。用于流量控制的线路未连接。我们决定注释掉以下行,该行将 RTS 和 DTR 线路设置为高电平,因为我们根本没有这些线路。
serial_out(info, UART_MCR, UART_MCR_DTR | UART_MCR_RTS);
当然,这并没有对我们有太大帮助 :-( 这里吸取的教训是检查、检查、检查您的硬件!。定制板卡可能不是标准的,如果您了解这一点,移植将进行得更快。


3.3.3. 让光明出现:计算波特率

最后,我们决定检查波特率。当 Linux 说 9600 时,它是否意味着我们认为它意味着的?可能不是,因为我们不知道它是如何计算该值的。我们注意到文件include/asm-ppc/pmppc_serial.h(将 pmppc 替换为您的板卡名称)包含BAUDBASE的定义,稍后将其用于与串行端口相关的所有内容。它是使用板卡的本地总线频率、总线时钟与系统时钟比率等计算出来的。这似乎是错误的,因此我们检查了我们在板卡上运行的 vxWorks 系统中的基本波特率,并将其更改为
/*
 * system clock = 33Mhz, serial clock = system clock / 4
 * the following must hold: (divisor * BaudRate) == (System clock / 64)
 */
#define BASE_BAUD (33000000 / 4 / 16)
快速编译,并在重启后,我们通过串行端口看到了正在启动的内核。成功!


第 4 章。Linux 仍然无法启动

4.1. 内存探测、RTC 和递减器

现在控制台可以工作了,我们可以看到真正的问题了。系统仍然无法启动。由于我们正在使用 C 代码,我们跟踪了代码,发现一个名为sdram_size()的函数没有正确完成。该函数探测 RAM 大小的寄存器,而我们的板卡没有该寄存器。我们使该函数返回给定的 128MB 值,这是一个丑陋的黑客行为,但我们的板卡无法知道 RAM 的数量。

我们在一堆名为 todc_XXXX 的函数中遇到了同样的问题,主要是todc_get_rtc_time(), todc_set_rtc_time(),以及time_init(),因为我们的板卡上没有 RTC(实时时钟)芯片,而这些函数正在使用它。目前,我们使todc_XXX函数仅设置和获取一个恒定的日期和时间,因为我们的板卡没有 bios 电池,因此在断电时无法保持时间。

完成所有这些后,我们发现了todc_calibrate_descr(),它再次使用 RTC 芯片。我们不得不将该函数替换为我们自己的
void calibrate_decr() {
	int freq, divisor;
	freq = bus_freq();
	divisor = 4;
	tb_ticks_per_jiffy = freq / HZ / divisor;
	tb_to_us = mulhwu_scale_factor(freq / divisor, 1000000);
}


4.2. 大端小端(我们应该知道的)

4.2.1. 探测 CPC700

最后,我们达到了启动过程的 PCI 探测部分,但发现它不起作用。我们尝试使用cpc700_read_local_pci_cfgb()(与 PMPPC 的 LSP 一起提供)与 CPC700 通信,并尝试读取 CPC 的配置寄存器。我们应该得到0x1014,这是供应商 ID,但我们没有得到。我们意识到我们正在以小端模式进行通信,而 CPC 正在以大端模式监听。我们对函数进行了小的修补,以便我们以大端模式与 CPC700 通信。然后我们可以正确读取供应商 ID,但其余部分仍然不起作用。我们不想更改代码,以便一切都以大端模式完成。


4.2.2. 使 CPC700 以小端模式通信

我们发现 CPC700 可以初始化为执行自动字节交换,这可以动态地进行小端到大端转换。似乎我们的板卡被初始化为 именно 这样做的。我们在setup_arch()中添加了一个小的代码段,该代码段检查是否启用了字节交换,如果启用,则禁用它
while (cnt<2) {
	cpc700_read_local_pci_cfgb(0, );
	cpc700_read_local_pci_cfgb(1, );
	if (l == 0 && h == 0) {
		if (cnt == 0) {
			printk("CPC700 byte swaping enabled - trying to disable ... ");
			cpc700_write_pifcfg_be(0x18, 0); /* disable byte-swapping */
		} else {
			printk("FAILED !!\n");
			break;
		}
	} else {cd
		printk("byte swapping disabled.\n");
		break;
	}
	++cnt;
}
稍作编译后,PCI 探测工作了!我们喝了些啤酒并庆祝了一下 ;-)


4.3. 以太网:我们的第一个 PCI 设备

我们的板卡使用 Intel 以太网芯片,称为 i82559er,它有一个名为 eepro100 的模块。编译模块并启动后,我们发现该模块不起作用,尽管找到了以太网设备。我们猜测这是一个 irq 问题,并且设备没有获得它们需要的 IRQ。我们修改了一个名为pmppc_map_irq()的函数来映射我们的以太网设备
XXXX_map_irq(struct pci_dev *dev, unsigned char idsel, unsigned char pin) {
	static char pci_irq_table[][4] =
	/*
	 *      PCI IDSEL/INTPIN->INTLINE
	 *      A       B       C       D
	 */
	{
		{22,    0,      0,      0},/* IDSEL 3 - Ethernet */
		{0,     0,      0,      0},/* IDSEL 4 - unused   */
		{0,     0,      0,      0},/* IDSEL 5 - unused   */
		{0,     0,      0,      0},/* IDSEL 6 - ????     */
		{0,     0,      0,      0},/* IDSEL 7 - unused   */
		{0,     0,      0,      0},/* IDSEL 8 - unused   */
		{0,     0,      0,      0},/* IDSEL 9 - unused   */
	};

	const long min_idsel = 3, max_idsel = 9, irqs_per_slot = 4;
	return PCI_IRQ_TABLE_LOOKUP;
}
该函数根据 IDselects 映射 IRQ,这意味着按照设备在 PCI 总线上的设置顺序。这种结构有点棘手:min_idsel 表示数组的左上角,max_idsel 是左下角。irqs_per_slot 是每行的 IRQ 数量。结构如下
each cell contains (IDSEL, SLOT#, IRQ)
		+----------------------------------------+
		| (3,0,22) | (3,1,0) | (3,2,0) | (3,3,0) |
		+----------------------------------------+
		| (4,0,0)  | (4,1,0) | (4,2,0) | (4,3,0) |
		+----------------------------------------+
							..........
							..........
		+----------------------------------------+
		| (9,0,0)  | (9,1,0) | (9,2,0) | (9,3,0) |
		+----------------------------------------+
正如您所看到的,我们的 i8559er 需要 IRQ 22,并且位于 IDselect 3 中。当然,我们在开始时并不知道这一点,因此我们编写了一小段代码,用于读取所有 IDselects 中的所有供应商 ID。完成后,我们进行了编译,但以太网设备仍然无法工作。

下一个问题是模块无法确定设备的 MAC 地址。MAC 地址应该写在 EEPROM 芯片(连接到设备)上,但我们发现硬件人员认为 i82559 不需要 EEPROM,因此他们将其移除。在将 MAC 地址硬编码到eepro100.c中后,以太网设备终于可以工作了。最终的解决方案是使模块从 NVRAM 内存中读取 MAC 地址,并且如果没有其他选择,则回退到默认 MAC 地址。

Note

下一步是挂载 NFS 根文件系统。有关详细信息,请参阅 Documentation/nfsroot.txt 中的文档


4.4. 一些其他问题

我们遇到了新问题,有些人会说是好问题。我们还没有引导加载程序,但是我们需要在启动时将命令行传递给内核。我们将命令行硬编码到内核内部的parse_options()中。在那之后,我们使console_init()serial_console_setup()以它们应该的方式工作。它们不再忽略命令行,但 RTS 和 DTR 仍然保持低电平。

另一个重要问题是内存映射。文件arch/ppc/mm/init.c包含一个名为MMU_init()的函数。此函数实际上是一个大的 switch 语句,按机器类型划分。每台机器都使用setbat()ioremap()函数映射其内存。BAT 机制是一种将虚拟地址转换为物理地址的方法。因此,setbat()通过指定虚拟地址、物理地址和页面大小来使用。并非所有大小都可以在此处使用;您应该使用有限的一组大小之一,范围从 128KB 到 256MB。我们映射了我们的 IO 内存,以便虚拟地址等于物理地址。

如前所述,还有另一种映射内存的方法 -ioremap(). ioremap()用于将物理地址映射到虚拟地址,使它们可供内核使用。该函数不分配任何内存,只是返回一个虚拟地址,通过该虚拟地址可以访问内存区域。以下是MMU_init():
case _MACH_mymachine:
	setbat(0, LOW_IO_VIRT_BASE, LOW_IO_PHYS_BASE, LOW_IO_SIZE, IO_PAGE);
	ioremap(UNIVERSE_BASE,UNIVERSE_SIZE);      /* Universe VME */
	ioremap(EEPRO100_BASE,EEPRO100_SIZE);      /* Ethernet EEPRO100 */
	break;
中的一个片段ioremap()正如您所看到的,我们没有获取


第 5 章。Linux 正在启动...现在怎么办?

5.1. 64 位障碍

CPC700 有一个“功能”,旨在使某些内存访问使用 64 位宽度。这是一个问题,因为我们板卡上的一些 test-and-set 寄存器可能会被意外设置,因为我们试图读取低 16 位的某些内容。为了解决这种情况,我们将内存控制器设置为 64 位宽度间隔。如果您尝试以另一种方式(8 位或 16 位访问)访问这些区域,CPC700 只会丢弃它们。我们必须能够读取/写入这些区域,因为重要的“离散量”(由 Altera 设备控制)映射在那里。

为了访问这些区域,我们需要一个执行 64 位写入的函数。据我所知,在 PowerPC 上执行 64 位写入有两种方法:使用缓存行和使用浮点寄存器。浮点寄存器是一个 64 位大小的寄存器,因此当我们写入它时,整个 64 位都会被写入。问题是您不能在内核中进行浮点运算。由于内核在上下文切换期间不保存浮点寄存器,因此它不允许 FP,并且如果在内核中完成,则会抛出异常。

在摆弄缓存行之后,我们决定走 FP 方式,并添加了以下函数
void out64(__u32 addr, long long *pVal) {
	__u32 flags, tmp_msr;

	save_flags(flags);
	cli();
	tmp_msr = __get_MSR();
	tmp_msr |= MSR_FP;
	tmp_msr &= ~(MSR_FE0 | MSR_FE1);
	__put_MSR(tmp_msr);

	sysOut64(addr, pVal);
	__put_MSR(flags & ~(MSR_EE));
	restore_flags(flags);
}
该函数将浮点添加到 PowerPC MSR 寄存器,并确保不会因执行 FP 而产生异常。完成后,它使用汇编代码,在下面的sysOut64()中描述,以执行实际的浮点运算。请注意,该函数关闭了中断,但这在这里是可以接受的,因为我们仅在极少数情况下使用该函数。
_GLOBAL(sysOut64)
stwu    r1, -DEPTH(r1)
mflr    r0
stw     r31, FP_LOC(r1)
stw     r0,  LR_LOC(r1)
mr              r31, r1
stfd    fr0, FPR_SAVE(r31)      /* save floating point reg contents */

lfd     fr0,0(r4)
stfd  fr0,0(r3)
eieio

lfd     fr0, FPR_SAVE(r31)      /* restore floating point value */
lwz     r4, 0(r1)               /* now restore the stack frame  */
lwz     r0, 4(r4)
mtlr    r0
lwz     r31, -4(r4)
mr      r1, r4
blr


5.2. 从闪存启动

虽然 Linux 正在使用 NFS 文件系统启动,但这还不够。对于实际的现场产品,我们需要 Linux 从独立设备启动,而完全不需要网络。我们决定创建一种特殊类型的映像,称为 initrd,它基本上是一个带有压缩文件的 Linux 内核。压缩文件包括 Linux 文件系统。文件系统在启动时解压缩到 ramdisk,并挂载为根文件系统。

在启动过程中,引导加载程序将内核映像重定位到地址零 - 这很好,并将 initrd 部分重定位到更高的地址。initrd 重定位到的区域未映射到我们内核的内存中,我们得到的只是内核错误(访问错误区域)。在修改引导加载程序以将 initrd 重定位到不同的地址后,一切正常,Linux 成功启动。

Tip

如果您的板卡有一些 NVRAM 内存,那么将其用于引导加载程序目的是一个好主意。在为我们的 NVRAM 内存编写模块(超出本文档范围)后,我们修改了引导加载程序,以便将内核命令行和 MAC 地址保存在 NVRAM 中。当引导加载程序启动时,它会检查 NVRAM,如果它已初始化(通过某个幻数),则引导加载程序使用那里写入的命令行。否则,引导加载程序将恢复为默认命令行,允许用户编辑它。


附录 A. GNU 自由文档许可证

版本 1.1,2000 年 3 月

版权所有 (C) 2000 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 允许任何人复制和分发本许可证文档的逐字副本,但不允许更改它。


A.1. 序言

本许可证的目的是使手册、教科书或其他书面文档在自由意义上“自由”:确保每个人都拥有复制和再分发它的有效自由,无论是否对其进行修改,无论是商业上还是非商业上。其次,本许可证为作者和出版商保留了一种方式来获得对其作品的认可,同时不被视为对他人所做的修改负责。

本许可证是一种“反版权”,这意味着文档的衍生作品本身必须在相同的意义上是自由的。它补充了 GNU 通用公共许可证,后者是为自由软件设计的反版权许可证。

我们设计本许可证是为了将其用于自由软件的手册,因为自由软件需要自由文档:自由程序应附带提供与软件相同的自由的手册。但是本许可证不限于软件手册;它可以用于任何文本作品,无论主题事项如何,也无论它是否作为印刷书籍出版。我们主要推荐本许可证用于目的为指导或参考的作品。


A.2. 适用性和定义

本许可证适用于任何手册或其他包含版权持有人放置的通知的作品,声明它可以根据本许可证的条款分发。“文档”,以下简称“文档”,是指任何此类手册或作品。任何公众成员都是被许可人,并被称为“您”。

文档的“修改版本”是指包含文档或其一部分的任何作品,无论是逐字复制,还是经过修改和/或翻译成另一种语言。

“二级节”是文档的命名附录或前言部分,专门处理文档的出版商或作者与文档的总体主题(或相关事项)的关系,并且不包含任何可能直接属于该总体主题的内容。(例如,如果文档部分是数学教科书,则二级节可能不解释任何数学。)该关系可能是与主题或相关事项的历史联系,或者是关于它们的法律、商业、哲学、伦理或政治立场。

“不变部分”是某些二级节,其标题在声明文档根据本许可证发布的通知中被指定为不变部分的标题。

“封面文本”是在声明文档根据本许可证发布的通知中列出的某些短文本段落,作为封面文本或封底文本。

文档的“透明”副本是指机器可读副本,以其规范可供公众使用的格式表示,其内容可以使用通用文本编辑器或(对于由像素组成的图像)通用绘画程序或(对于绘图)一些广泛可用的绘图编辑器直接且直接地查看和编辑,并且适合输入到文本格式化程序或自动翻译成各种适合输入到文本格式化程序的格式。以其他透明文件格式制作的副本,其标记旨在阻止或阻止读者随后的修改,则不是透明的。不“透明”的副本称为“不透明”。

透明副本的合适格式示例包括没有标记的纯 ASCII、Texinfo 输入格式、LaTeX 输入格式、使用公开可用的 DTD 的 SGML 或 XML,以及为人工修改而设计的符合标准的简单 HTML。不透明格式包括 PostScript、PDF、专有格式(只能由专有文字处理器读取和编辑)、DTD 和/或处理工具通常不可用的 SGML 或 XML,以及某些文字处理器仅用于输出目的而生成的机器生成的 HTML。

对于印刷书籍,“标题页”是指标题页本身,以及随后的页面,这些页面需要容纳本许可证要求出现在标题页中的材料,并且清晰可辨。“标题页”对于没有标题页格式的作品,是指最突出的作品标题外观附近的文本,位于文本正文的开头之前。


A.3. 逐字复制

您可以在任何媒介复制和分发本“文档”,无论是商业用途还是非商业用途,前提是本“许可证”、版权声明以及声明本“许可证”适用于本“文档”的许可证声明在所有副本中均被复制,并且您不得在本“许可证”的条款之外添加任何其他条件。您不得使用技术措施来阻止或控制您制作或分发的副本的阅读或进一步复制。但是,您可以接受报酬以换取副本。如果您分发足够大量的副本,您还必须遵守第 3 节中的条件。

在上述相同条件下,您也可以出借副本,并且您可以公开展示副本。


A.4. 大量复制

如果您出版的“文档”印刷副本数量超过 100 份,且“文档”的许可证声明要求封面文字,则您必须将副本装在封面上,封面上清晰且易读地印上所有这些封面文字:前封面文字印在前封面上,后封面文字印在后封面上。两个封面还必须清晰且易读地标明您是这些副本的出版商。前封面必须以同等突出和可见的方式呈现完整标题的所有文字。您可以在封面上添加其他材料。对封面的更改仅限于保留“文档”标题并满足这些条件的复制,在其他方面可以视为逐字复制。

如果任一封面的所需文字过多而无法清晰地容纳,您应将列出的前几项(尽可能多地合理容纳)放在实际封面上,并将其余部分延续到相邻的页面上。

如果您出版或分发超过 100 份“文档”的“不透明”副本,您必须在每个“不透明”副本中附带一份机器可读的“透明”副本,或者在每个“不透明”副本中或随附声明一个公众可访问的计算机网络位置,其中包含“文档”的完整“透明”副本,不含附加材料,供普通网络用户公众使用公共标准网络协议免费匿名下载。如果您使用后一种选择,您必须采取合理谨慎的步骤,当您开始大量分发“不透明”副本时,以确保该“透明”副本在所述位置保持可访问状态,至少持续到您向公众分发该版本的最后一个“不透明”副本(直接或通过您的代理商或零售商)后一年。

建议(但非强制要求)您在重新分发大量副本之前充分联系“文档”的作者,让他们有机会向您提供“文档”的更新版本。


A.5. 修改

您可以根据上述第 2 节和第 3 节的条件复制和分发“文档”的“修改版本”,前提是您根据本“许可证”精确发布“修改版本”,“修改版本”在此处充当“文档”的角色,从而向任何拥有其副本的人授予分发和修改“修改版本”的许可。此外,您必须在“修改版本”中执行以下操作:

  1. 在标题页(以及封面,如果有)上使用与“文档”标题以及先前版本标题不同的标题(如果存在先前版本,则应在“文档”的历史记录部分中列出)。如果先前版本的原始出版商给予许可,您可以使用与先前版本相同的标题。

  2. 在标题页上,将对“修改版本”中的修改负责的一个或多个人员或实体列为作者,并列出“文档”的至少五位主要作者(如果“文档”的主要作者少于五位,则列出所有主要作者)。

  3. 在标题页上,将“修改版本”的出版商名称声明为出版商。

  4. 保留“文档”的所有版权声明。

  5. 在其他版权声明旁边添加适用于您的修改的适当版权声明。

  6. 在版权声明之后立即包含许可证声明,授予公众根据本“许可证”条款使用“修改版本”的许可,形式如下文附录所示。

  7. 在该许可证声明中保留“文档”的许可证声明中给出的“不变节”和要求的“封面文字”的完整列表。

  8. 包含本“许可证”的未修改副本。

  9. 保留标题为“历史记录”的章节及其标题,并在其中添加一项,至少说明标题页上给出的“修改版本”的标题、年份、新作者和出版商。如果“文档”中没有标题为“历史记录”的章节,则创建一个章节,说明标题页上给出的“文档”的标题、年份、作者和出版商,然后添加一项描述“修改版本”的项目,如前一句所述。

  10. 保留“文档”中给出的用于公众访问“文档”的“透明”副本的网络位置(如果有),以及“文档”中给出的其所基于的先前版本的网络位置。这些可以放在“历史记录”部分中。您可以省略在“文档”本身发布至少四年前发布的作品的网络位置,或者如果它所指的版本的原始出版商给予许可。

  11. 在任何标题为“致谢”或“献词”的章节中,保留该章节的标题,并在该章节中保留其中给出的每个贡献者致谢和/或献词的全部实质和语气。

  12. 保留“文档”的所有“不变节”,其文本和标题均不得更改。节号或等效内容不被视为节标题的一部分。

  13. 删除任何标题为“背书”的章节。“修改版本”中不得包含此类章节。

  14. 请勿将任何现有章节重新命名为“背书”或与任何“不变节”的标题冲突。

如果“修改版本”包含符合“次要节”条件且不包含从“文档”复制的材料的新前言章节或附录,您可以选择将其中部分或全部章节指定为不变节。为此,请将它们的标题添加到“修改版本”的许可证声明中的“不变节”列表中。这些标题必须与任何其他章节标题不同。

您可以添加一个标题为“背书”的章节,前提是它仅包含各方对您的“修改版本”的背书——例如,同行评审声明或文本已被某个组织批准为标准的权威定义。

您可以在“修改版本”的“封面文字”列表末尾添加最多五个单词的段落作为“前封面文字”,以及最多 25 个单词的段落作为“后封面文字”。任何一个实体(或通过其安排)只能添加一段“前封面文字”和一段“后封面文字”。如果“文档”已经包含由您或由您代表的同一实体安排先前添加的相同封面的封面文字,您不得添加另一个;但是,在获得添加旧封面的先前出版商的明确许可后,您可以替换旧封面文字。

“文档”的作者和出版商未通过本“许可证”授权使用他们的姓名进行宣传或声明或暗示对任何“修改版本”的认可。


A.6. 合并文档

您可以将“文档”与根据本“许可证”发布的其他文档合并,根据上述第 4 节中针对“修改版本”定义的条款,前提是您在合并后的作品中包含所有原始文档的所有“不变节”(未修改),并在其许可证声明中将它们全部列为合并作品的“不变节”。

合并后的作品只需包含本“许可证”的一份副本,并且多个相同的“不变节”可以用单个副本替换。如果存在多个同名但内容不同的“不变节”,请通过在每个此类章节的末尾括号中添加该章节的原始作者或出版商的姓名(如果已知),或者添加唯一的数字,使每个此类章节的标题都是唯一的。对合并作品的许可证声明中的“不变节”列表中的章节标题进行相同的调整。

在合并中,您必须合并各个原始文档中所有标题为“历史记录”的章节,形成一个标题为“历史记录”的章节;同样合并所有标题为“致谢”的章节以及所有标题为“献词”的章节。您必须删除所有标题为“背书”的章节。


A.7. 文档集合

您可以创建一个由“文档”和根据本“许可证”发布的其他文档组成的集合,并将各个文档中的本“许可证”的单独副本替换为集合中包含的单个副本,前提是您在所有其他方面都遵循本“许可证”关于每个文档的逐字复制的规则。

您可以从这样的集合中提取单个文档,并根据本“许可证”单独分发,前提是您在提取的文档中插入本“许可证”的副本,并在关于该文档的逐字复制的所有其他方面都遵循本“许可证”。


A.8. 与独立作品的聚合

在存储或分发介质的卷中或卷上,将“文档”或其衍生作品与其他单独和独立的文档或作品汇编在一起,作为一个整体,不应被视为“文档”的“修改版本”,前提是该汇编未声明汇编版权。这种汇编称为“聚合”,并且本“许可证”不适用于因此与“文档”汇编在一起的其他独立作品,因为它们是如此汇编在一起,如果它们本身不是“文档”的衍生作品。

如果第 3 节的“封面文字”要求适用于这些“文档”副本,那么如果“文档”小于整个“聚合”的四分之一,“文档”的“封面文字”可以放置在仅围绕“聚合”内的“文档”的封面上。否则,它们必须出现在围绕整个“聚合”的封面上。


A.9. 翻译

翻译被视为一种修改,因此您可以根据第 4 节的条款分发“文档”的翻译版本。将“不变节”替换为翻译版本需要其版权持有人的特别许可,但您可以包含一些或所有“不变节”的翻译版本,以及这些“不变节”的原始版本。您可以包含本“许可证”的翻译版本,前提是您也包含本“许可证”的原始英文版本。如果本“许可证”的翻译版本与原始英文版本之间存在歧义,则以原始英文版本为准。


A.10. 终止

除非本“许可证”明确规定,否则您不得复制、修改、再许可或分发“文档”。任何其他复制、修改、再许可或分发“文档”的尝试均无效,并将自动终止您在本“许可证”下的权利。但是,根据本“许可证”从您那里收到副本或权利的各方,只要这些各方完全遵守规定,其许可证就不会终止。


A.11. 本许可证的未来修订

自由软件基金会可能会不时发布 GNU 自由文档许可证的新修订版本。这些新版本在精神上与当前版本相似,但在细节上可能有所不同,以解决新的问题或疑虑。请参阅 https://gnu.ac.cn/copyleft/

每个许可证版本都给出了一个区分版本的版本号。如果“文档”指定本“许可证”的特定编号版本“或任何后续版本”适用于它,您可以选择遵循该指定版本或自由软件基金会已发布的任何后续版本(非草案)的条款和条件。如果“文档”未指定本“许可证”的版本号,您可以选择自由软件基金会曾经发布的任何版本(非草案)。


A.12. 如何将本许可证用于您的文档

要在您编写的文档中使用本“许可证”,请在文档中包含本“许可证”的副本,并在标题页之后立即放置以下版权和许可证声明:

版权所有 (c) 年份 您的姓名。根据 GNU 自由文档许可证 1.1 版或自由软件基金会发布的任何后续版本的条款,授予复制、分发和/或修改本文档的许可;其中“不变节”为 [列出其标题],“前封面文字”为 [列出],“后封面文字”为 [列出]。许可证副本包含在标题为“GNU 自由文档许可证”的章节中。

如果您没有“不变节”,请写“没有不变节”,而不是说哪些是不变的。如果您没有“前封面文字”,请写“没有前封面文字”,而不是“前封面文字为 [列出]”;“后封面文字”也是如此。

如果您的文档包含程序代码的重要示例,我们建议您并行地根据您选择的自由软件许可证(例如 GNU 通用公共许可证)发布这些示例,以允许在自由软件中使用它们。


附录 B. 商标

Linux® 是 Linus Torvalds 的注册商标。

MontaVista™ 是 MontaVista Software Inc. 的商标。

PowerPC® 是 IBM Corporation 的注册商标。

Windows® 是 Microsoft Corporation 的注册商标。

vxWorks™ 和 Vision ICE™ 是 Wind River Systems Inc. 的商标。

ProCOMM™ 是 Symantec Corporation 的商标。