diff --git a/articles/20220315-porting-linux-to-a-new-processor-architecture-part-2-the-early-code.md b/articles/20220315-porting-linux-to-a-new-processor-architecture-part-2-the-early-code.md
index 41dea8c134cdfa31fa1b35f9048466baad2c0517..0eca3d003005c0880979f016ba05fcea0b025e81 100644
--- a/articles/20220315-porting-linux-to-a-new-processor-architecture-part-2-the-early-code.md
+++ b/articles/20220315-porting-linux-to-a-new-processor-architecture-part-2-the-early-code.md
@@ -1,9 +1,11 @@
+> Corrector: [TinyCorrect](https://gitee.com/tinylab/tinycorrect) v0.1-rc1 - [pangu]
> Title: [Porting Linux to a new processor architecture, part 2: The early code](https://lwn.net/Articles/656286/)
> Author: Joël Porquet@**September 2, 2015**
> Translator: 通天塔 <985400330@qq.com>
-> Revisor: Falcon
+> Date: 20220315
+> Revisor: Falcon
> Project: [RISC-V Linux 内核剖析](https://gitee.com/tinylab/riscv-linux)
-> Sponsor: PLCT Lab, ISCAS
+> Sponsor: PLCT Lab, ISCAS
# 将 Linux 移植到新的处理器架构,第 2 部分:早期代码
@@ -11,9 +13,7 @@
> In [part 1](https://lwn.net/Articles/654783/) of this series, we laid the groundwork for porting Linux to a new processor architecture by explaining the (non-code-related) preliminary steps. This article continues from there to delve into the boot code. This includes what code needs to be written in order to get from the early assembly boot code to the creation of the first kernel thread.
-在本系列文章的 [第一部分](https://tinylab.org/lwn-654783/),我们通过讲述初步的步骤(与代码无关的)来给我们移植 Linux 到新的处理器架构中做了准备。本文将继续深入研究启动代码。这包括从“早期汇编启动代码到创建第一个内核线程”所需要编写的那些代码。
-
-
+在本系列文章的 [第一部分][009],我们通过讲述初步的步骤(与代码无关的)来给我们移植 Linux 到新的处理器架构中做了准备。本文将继续深入研究启动代码。这包括从“早期汇编启动代码到创建第一个内核线程”所需要编写的那些代码。
## 头文件集合(The header files)
@@ -25,9 +25,9 @@
这些头文件的第一部分(子目录 `asm/`)是内核接口的一部分,在内核源代码内部使用。第二部分(`uapi/asm/`)是用户接口的一部分,旨在导出到用户空间,尽管各种标准 C 库倾向于重新实现头文件,而不是包含导出的那些。这些接口不光是给内核设计的,因为很多 `asm` 头文件(指 `uapi` 部分)会被用户空间使用。
-> Both interfaces are typically more than a hundred header files altogether, which is why headers represent one of the biggest tasks in porting Linux to a new processor architecture. Fortunately, over the past few years, developers noticed that many processor architectures were sharing similar code (because they often exhibited the same behaviors), so the majority of this code has been aggregated into a [generic layer of header files](https://lwn.net/Articles/333569/) (in `linux/include/asm-generic/` and `linux/include/uapi/asm-generic/`).
+> Both interfaces are typically more than a hundred header files altogether, which is why headers represent one of the biggest tasks in porting Linux to a new processor architecture. Fortunately, over the past few years, developers noticed that many processor architectures were sharing similar code (because they often exhibited the same behaviors), so the majority of this code has been aggregated into a [generic layer of header files][007] (in `linux/include/asm-generic/` and `linux/include/uapi/asm-generic/`).
-这两个接口总共约 100 多个头文件,这就是为什么编写这些头文件是移植 Linux 到新架构的最大任务之一,幸运的是在过去几年中,开发者们发现很多处理器架构共享一些相似的代码(因为它们常常表现出相同的行为)。所以这些代码的大部分被整合到 [头文件的通用层](https://lwn.net/Articles/333569/)(在 `linux/include/asm-generic/` 和 `linux/include/uapi/asm-generic/`)。
+这两个接口总共约 100 多个头文件,这就是为什么编写这些头文件是移植 Linux 到新架构的最大任务之一,幸运的是在过去几年中,开发者们发现很多处理器架构共享一些相似的代码(因为它们常常表现出相同的行为)。所以这些代码的大部分被整合到 [头文件的通用层][007](在 `linux/include/asm-generic/` 和 `linux/include/uapi/asm-generic/`)。
> The real benefit is that it is possible to refer to these generic header files, instead of providing custom versions, by simply writing appropriate `Kbuild` files. For example, the few first lines of a typical `include/asm/Kbuild` looks like:
>
@@ -100,8 +100,6 @@
cpu_startup_entry
```
-
-
## 早期汇编代码(Early assembly boot code)
> The early assembly boot code has this special aura that scared me at first (as I'm sure it did many other programmers), since it is often considered one of the most complex pieces of code in a port. But even though writing assembly code is usually not an easy ride, this early boot code is not magic. It is merely a trampoline to the first architecture-independent C function and, to this end, only needs to perform a short and defined list of tasks.
@@ -116,13 +114,13 @@
类似地,对内存状态的了解也不多。特别地,不能保证内存中存放内核 `bss` 段(包含未初始化数据)的区域被重置为零。这就是为什么这个部分必须被清楚明确的清除。
-> Often Linux receives arguments from the bootloader (in the same way that a program receives arguments when it is launched). For example, this could be the memory address of a [flattened device tree](http://www.devicetree.org/) (on ARM, MicroBlaze, openRISC, etc.) or some other architecture-specific structure. Often such arguments are passed using registers and need to be saved into proper kernel variables.
+> Often Linux receives arguments from the bootloader (in the same way that a program receives arguments when it is launched). For example, this could be the memory address of a [flattened device tree][013] (on ARM, MicroBlaze, openRISC, etc.) or some other architecture-specific structure. Often such arguments are passed using registers and need to be saved into proper kernel variables.
-Linux 经常从 bootloader 接收参数(就像程序在启动时接收参数一样)。例如,这可能是 [扁平设备树](http://www.devicetree.org/) 的内存地址(在 ARM、MicroBlaze、openRISC 等上)或一些其他架构相关的结构体。通常,这些参数使用寄存器传递,并且需要保存到适当的内核变量中。
+Linux 经常从 bootloader 接收参数(就像程序在启动时接收参数一样)。例如,这可能是 [扁平设备树][013] 的内存地址(在 ARM、MicroBlaze、openRISC 等上)或一些其他架构相关的结构体。通常,这些参数使用寄存器传递,并且需要保存到适当的内核变量中。
-> At this point, virtual memory has not been activated and it is interesting to note that kernel symbols, which are all defined in the kernel's virtual address space, have to be accessed through a special macro: [pa()](http://lxr.free-electrons.com/source/arch/x86/kernel/head_32.S?v=4.2#L28) in x86, [tophys()](http://lxr.free-electrons.com/source/arch/openrisc/kernel/head.S?v=4.2#L32) in OpenRISC, etc. Such a macro translates the virtual memory address for symbols into their corresponding physical memory address, thus acting as a temporary software-based translation mechanism.
+> At this point, virtual memory has not been activated and it is interesting to note that kernel symbols, which are all defined in the kernel's virtual address space, have to be accessed through a special macro: [pa()][002] in x86, [tophys()][001] in OpenRISC, etc. Such a macro translates the virtual memory address for symbols into their corresponding physical memory address, thus acting as a temporary software-based translation mechanism.
-在这个时间点上,虚拟内存还没有被激活,需要注意到特别有趣的是,内核符号都定义在内核的虚拟地址空间中,必须通过一个特殊的宏来访问:x86 中的 [pa()](http://lxr.free-electrons.com/source/arch/x86/kernel/head_32.S?v=4.2#L28),OpenRISC 中的 [tophys()](http://lxr.free-electrons.com/source/arch/openrisc/kernel/head.S?v=4.2#L32),等等。这样的宏将符号的虚拟内存地址转换为它们对应的物理内存地址,从而充当一个临时的基于软件的转换机制。
+在这个时间点上,虚拟内存还没有被激活,需要注意到特别有趣的是,内核符号都定义在内核的虚拟地址空间中,必须通过一个特殊的宏来访问:x86 中的 [pa()][002],OpenRISC 中的 [tophys()][001],等等。这样的宏将符号的虚拟内存地址转换为它们对应的物理内存地址,从而充当一个临时的基于软件的转换机制。
> Now, in order to enable virtual memory, a page table structure must be set up from scratch. This structure usually exists as a static variable in the kernel image, since at this stage it is nearly impossible to allocate memory. For the same reason, only the kernel image can be mapped by the page table at first, using huge pages if possible. According to convention, this initial page table structure is called `swapper_pg_dir` and is thereafter used as the reference page table structure throughout the execution of the system.
@@ -146,11 +144,11 @@ Linux 经常从 bootloader 接收参数(就像程序在启动时接收参数
## 启动第一个内核线程的路径(En route to the first kernel thread)
-> [start_kernel()](http://lxr.free-electrons.com/source/init/main.c?v=4.2#L497) is where many subsystems are initialized, from the various virtual filesystem (VFS) caches and the security framework to time management, the console layer, and so on. Here, we will look at the main architecture-specific functions that `start_kernel()` calls during boot before it finally calls `rest_init()`, which creates the first two kernel threads and morphs into the boot idle thread.
+> [start_kernel()][005] is where many subsystems are initialized, from the various virtual filesystem (VFS) caches and the security framework to time management, the console layer, and so on. Here, we will look at the main architecture-specific functions that `start_kernel()` calls during boot before it finally calls `rest_init()`, which creates the first two kernel threads and morphs into the boot idle thread.
- [start_kernel()](http://lxr.free-electrons.com/source/init/main.c?v=4.2#L497) 是许多子系统初始化的地方,从各种虚拟文件系统(VFS)缓存和安全框架到时间管理、控制台层(console layer)等等。在这里,我们将看看在引导过程中 `start_kernel()` 在最终调用 `rest_init()` 之前调用的主要架构特定函数,它创建了前两个内核线程并转变为引导空闲线程(boot idle thread)。
+[start_kernel()][005] 是许多子系统初始化的地方,从各种虚拟文件系统(VFS)缓存和安全框架到时间管理、控制台层(console layer)等等。在这里,我们将看看在引导过程中 `start_kernel()` 在最终调用 `rest_init()` 之前调用的主要架构特定函数,它创建了前两个内核线程并转变为引导空闲线程(boot idle thread)。
-#### `setup_arch()`
+#### setup_arch()
> While it has a rather generic name, `setup_arch()` can actually do quite a bit, depending on the architecture. Yet examining the code for different ports reveals that it generally performs the same tasks, albeit never in the same order nor the same way. For a simple port (with device tree support), there is a simple skeleton that `setup_arch()` can follow.
@@ -164,13 +162,13 @@ Linux 经常从 bootloader 接收参数(就像程序在启动时接收参数
接下来,memblock 层需要一些更多的配置,才能映射低内存区域,从而能够分配内存。首先,内核映像和设备树所占用的内存区域被设置为*保留的*,以便将它们从空闲内存池中删除,该内存池稍后将被释放给伙伴分配器。需要确定低内存和高内存之间的边界(即物理内存的哪一部分应该包含在直接映射区域中)。最后,可以清理页表结构(通过删除早期引导代码创建的标识映射)和低内存映射。
-> The last step of the memory initialization is to configure the memory zones. Physical memory pages can be associated with different zones: `ZONE_DMA` for pages compatible with the old ISA 24-bit DMA address limitation, and `ZONE_NORMAL` and `ZONE_HIGHMEM` for low- and high-memory pages, respectively. Further reading on memory allocation in Linux can be found in [*Linux Device Drivers* [PDF]](https://lwn.net/images/pdf/LDD3/ch08.pdf).
+> The last step of the memory initialization is to configure the memory zones. Physical memory pages can be associated with different zones: `ZONE_DMA` for pages compatible with the old ISA 24-bit DMA address limitation, and `ZONE_NORMAL` and `ZONE_HIGHMEM` for low- and high-memory pages, respectively. Further reading on memory allocation in Linux can be found in [*Linux Device Drivers* [PDF]][008].
-内存初始化的最后一步是配置内存分区。物理内存页面可以与不同的区域相关联:`ZONE_DMA` 用于兼容旧 ISA 24 位 DMA 地址限制的页面,`ZONE_NORMAL` 和`ZONE_HIGHMEM` 用于低内存和高内存页面。更多关于 Linux 内存分配的阅读可以在 [*Linux Device Drivers* [PDF]](https://lwn.net/images/pdf/LDD3/ch08.pdf) 中找到。
+内存初始化的最后一步是配置内存分区。物理内存页面可以与不同的区域相关联:`ZONE_DMA` 用于兼容旧 ISA 24 位 DMA 地址限制的页面,`ZONE_NORMAL` 和 `ZONE_HIGHMEM` 用于低内存和高内存页面。更多关于 Linux 内存分配的阅读可以在 [*Linux Device Drivers* [PDF]][008] 中找到。
-> Finally, the kernel memory segments are registered using the resource API and a tree of [struct device_node](http://lxr.free-electrons.com/source/include/linux/of.h?v=4.2#L49) entries is created from the flattened device tree.
+> Finally, the kernel memory segments are registered using the resource API and a tree of [struct device_node][004] entries is created from the flattened device tree.
-最后,使用资源 API 注册内核内存段,并从扁平设备树创建一个 [struct device_node](http://lxr.free-electrons.com/source/include/linux/of.h?v=4.2#L49) 树节点入口。
+最后,使用资源 API 注册内核内存段,并从扁平设备树创建一个 [struct device_node][004] 树节点入口。
> If `early_printk()` is enabled, here is an example of what appears on the terminal at this stage:
>
@@ -182,7 +180,7 @@ Linux 经常从 bootloader 接收参数(就像程序在启动时接收参数
> Built 1 zonelists in Zone order, mobility grouping on. Total pages: 65024
> Kernel command line: console=tty0 console=ttyVTTY0 earlyprintk
>```
->
+>
如果启用了 `early_printk()`,这里有个例子,展示了该阶段打印在控制台的信息:
@@ -195,7 +193,7 @@ Linux 经常从 bootloader 接收参数(就像程序在启动时接收参数
Kernel command line: console=tty0 console=ttyVTTY0 earlyprintk
```
-#### `trap_init()`
+#### trap_init()
> The role of `trap_init()` is to configure the hardware and software architecture-specific parts involved in the interrupt/exception infrastructure. Up to this point, an exception would either cause the system to crash immediately or it would be caught by a handler that the bootloader might have set up (which would eventually result in a crash as well, but perhaps with more information).
@@ -204,7 +202,7 @@ Linux 经常从 bootloader 接收参数(就像程序在启动时接收参数
> Behind (the actually simple) `trap_init()` hides another of the more complex pieces of code in a Linux port: the interrupt/exception handling manager. A big part of it has to be written in assembly code because, as with the early boot code, it deals with specifics that are unique to the targeted processor architecture. On a typical processor, a possible overview of what happens on an interrupt is as follows:
>
> - The processor automatically switches to kernel mode, disables interrupts, and its execution flow is diverted to a special address that leads to the main interrupt handler.
->- This main handler retrieves the exact cause of the interrupt and usually jumps to a sub-handler specialized for this cause. Often an interrupt vector table is used to associate an interrupt sub-handler with a specific cause, and on some architectures there is no need for a main interrupt handler, as the routing between the actual interrupt event and the interrupt vector is done transparently by hardware.
+> - This main handler retrieves the exact cause of the interrupt and usually jumps to a sub-handler specialized for this cause. Often an interrupt vector table is used to associate an interrupt sub-handler with a specific cause, and on some architectures there is no need for a main interrupt handler, as the routing between the actual interrupt event and the interrupt vector is done transparently by hardware.
> - The sub-handler saves the current context, which is the state of the processor that can later be restored in order to resume exactly where it stopped. It may also re-enable the interrupts (thus making Linux re-entrant) and usually jumps to a C function that is better able to handle the cause of the exception. For example, such a C function can, in the case of an access to an illegal memory address, terminate the faulty user program with a `SIGBUS` signal.
>
@@ -218,11 +216,11 @@ Linux 经常从 bootloader 接收参数(就像程序在启动时接收参数
一旦所有的中断机制就绪,`trap_init()` 只会初始化中断向量表,并通过它的一个系统寄存器来配置处理器,以反映主中断处理器的地址(或者直接反映中断向量表的地址)。
-#### `mem_init()`
+#### mem_init()
-> The main role of `mem_init()` is to release the free memory from the memblock layer to the buddy allocator (aka the [page allocator](https://lwn.net/Articles/320556/)). This represents the last memory-related task before the slab allocator (i.e. the cache of commonly used objects, accessible via `kmalloc()`) and the vmalloc infrastructure can be started, as both are based on the buddy allocator.
+> The main role of `mem_init()` is to release the free memory from the memblock layer to the buddy allocator (aka the [page allocator][006]). This represents the last memory-related task before the slab allocator (i.e. the cache of commonly used objects, accessible via `kmalloc()`) and the vmalloc infrastructure can be started, as both are based on the buddy allocator.
-`mem_init()` 的主要作用是将内存块层(memblock layer)的空闲内存释放给伙伴分配器(又名 [页分配器](https://lwn.net/Articles/320556/))。这表示,作为最后一个内存相关的任务,在 slab 分配器(即常用对象的缓存,通过 `kmalloc()` 访问) 和 vmalloc 功能能够被启用之前,伙伴分配器必须就绪,因为两者都依赖它。
+`mem_init()` 的主要作用是将内存块层(memblock layer)的空闲内存释放给伙伴分配器(又名 [页分配器][006])。这表示,作为最后一个内存相关的任务,在 slab 分配器(即常用对象的缓存,通过 `kmalloc()` 访问) 和 vmalloc 功能能够被启用之前,伙伴分配器必须就绪,因为两者都依赖它。
> Often `mem_init()` also prints some information about the memory system:
>
@@ -236,9 +234,9 @@ Linux 经常从 bootloader 接收参数(就像程序在启动时接收参数
> .data : 0xc01621f8 - 0xc01a4fe0 ( 267 kB)
> .text : 0xc00010c0 - 0xc01621f8 (1412 kB)
>```
->
+>
-通常 `mem_init()` 也会打印一些关于内存系统的信息:
+通常 `mem_init()` 也会打印一些关于内存系统的信息:
```
Memory: 257916k/262144k available (1412k kernel code, \
@@ -251,9 +249,7 @@ Linux 经常从 bootloader 接收参数(就像程序在启动时接收参数
.text : 0xc00010c0 - 0xc01621f8 (1412 kB)
```
-
-
-#### `init_IRQ()`
+#### init_IRQ()
> Interrupt networks can be of very different sizes and complexities. In a simple system, the interrupt lines of a few hardware devices are directly connected to the interrupt inputs of the processor. In complex systems, the numerous hardware devices are connected to multiple programmable interrupt controllers (PICs) and these PICs are often cascaded to each other, forming a multilayer interrupt network. The device tree helps a great deal by easily describing such networks (and especially the routing) instead of having to specify them directly in the source code.
@@ -263,31 +259,31 @@ Linux 经常从 bootloader 接收参数(就像程序在启动时接收参数
在 `init_IRQ()` 中,主要任务是调用 `irqchip_init()`,以便扫描设备树并找到所有标识为中断控制器的节点(例如 PICs)。然后,它为每个节点找到相关的驱动程序并初始化它。除非目标系统使用已经支持的中断控制器,否则这通常意味着需要编写第一个设备驱动程序。
-> Such a driver contains a few major functions: an initialization function that maps the device in the kernel address space and maps the controller-local interrupt lines to the Linux IRQ number space (through the [irq_domain](https://www.kernel.org/doc/Documentation/IRQ-domain.txt) mapping library); a mask/unmask function that can configure the controller in order to mask or unmask the specified Linux IRQ number; and, finally, a controller-specific interrupt handler that can find out which of its inputs is active and call the interrupt handler registered with this input (for example, this is how the interrupt handler of a block device connected to a PIC ends up being called after the device has raised an interrupt).
+> Such a driver contains a few major functions: an initialization function that maps the device in the kernel address space and maps the controller-local interrupt lines to the Linux IRQ number space (through the [irq_domain][011] mapping library); a mask/unmask function that can configure the controller in order to mask or unmask the specified Linux IRQ number; and, finally, a controller-specific interrupt handler that can find out which of its inputs is active and call the interrupt handler registered with this input (for example, this is how the interrupt handler of a block device connected to a PIC ends up being called after the device has raised an interrupt).
这样一个驱动包含几个主要函数:
-* 一个初始化函数负责在内核空间映射设备并负责映射本地控制器中断线到 Linux IRQ 编号空间(通过 [irq_domain](https://www.kernel.org/doc/Documentation/IRQ-domain.txt) 映射库);
+* 一个初始化函数负责在内核空间映射设备并负责映射本地控制器中断线到 Linux IRQ 编号空间(通过 [irq_domain][011] 映射库);
* 一组 mask/unmask 函数用于配置控制器以便 mask 或者 unmask 特定的中断 IRQ 编号;
* 最后,控制器特定的中断处理函数能找出它的哪个输入是活动的并调用注册到该输入上的中断处理子函数(例如,当设备触发一个中断后,连接到 PIC 的块设备中断处理函数就是这样被调用的)。
-#### `time_init()`
+#### time_init()
> The purpose of `time_init()` is to initialize the architecture-specific aspects of the timekeeping infrastructure. A minimal version of this function, which relies on the use of a device tree, only involves two function calls.
`time_init()` 的目的是初始化特定架构的计时机制。这个函数的最小版本依赖于设备树的使用,它只涉及两个函数调用。
-> First, `of_clk_init()` will scan the device tree and find all the nodes identified as clock providers in order to initialize the [clock framework](https://www.kernel.org/doc/Documentation/clk.txt). A very simple clock-provider node only has to define a fixed frequency directly specified as one of its properties.
+> First, `of_clk_init()` will scan the device tree and find all the nodes identified as clock providers in order to initialize the [clock framework][010]. A very simple clock-provider node only has to define a fixed frequency directly specified as one of its properties.
-首先,`of_clk_init()` 将扫描设备树并找到所有标识为时钟提供者的节点,以便初始化 [时钟框架](https://www.kernel.org/doc/Documentation/clk.txt) 。在设备树中,一个非常简单的时钟提供者节点只需要定义一个固定频率,直接指定为它的属性之一。
+首先,`of_clk_init()` 将扫描设备树并找到所有标识为时钟提供者的节点,以便初始化 [时钟框架][010] 。在设备树中,一个非常简单的时钟提供者节点只需要定义一个固定频率,直接指定为它的属性之一。
-> Then, `clocksource_of_init()` will parse the clock-source nodes of the device tree and initialize their associated driver. As described in the [kernel documentation](https://www.kernel.org/doc/Documentation/timers/timekeeping.txt), Linux actually needs two types of timekeeping abstraction (which are actually often both provided by the same device): a clock-source device provides the basic timeline by monotonically counting (for example it can count system cycles), and a clock-event device raises interrupts on certain points on this timeline, typically by being programmed to count periods of time. Combined with the clock provider, it allows for precise timekeeping.
+> Then, `clocksource_of_init()` will parse the clock-source nodes of the device tree and initialize their associated driver. As described in the [kernel documentation][012], Linux actually needs two types of timekeeping abstraction (which are actually often both provided by the same device): a clock-source device provides the basic timeline by monotonically counting (for example it can count system cycles), and a clock-event device raises interrupts on certain points on this timeline, typically by being programmed to count periods of time. Combined with the clock provider, it allows for precise timekeeping.
-然后,`clocksource_of_init()` 将解析设备树的时钟源节点,并初始化它们的相关驱动程序。正如在 [kernel 文档](https://www.kernel.org/doc/Documentation/timers/timekeeping.txt) 中所描述的,Linux 实际上需要两种类型的计时抽象(实际上这两种抽象都是由同一台设备提供的):时钟源设备通过单调计数(例如,它可以计数系统周期)来提供基本的时间线,而时钟事件设备在这个时间线上的某些点上引发中断,通常是通过编程来计数时间周期。与时钟提供程序相结合,它允许精确计时。
+然后,`clocksource_of_init()` 将解析设备树的时钟源节点,并初始化它们的相关驱动程序。正如在 [kernel 文档][012] 中所描述的,Linux 实际上需要两种类型的计时抽象(实际上这两种抽象都是由同一台设备提供的):时钟源设备通过单调计数(例如,它可以计数系统周期)来提供基本的时间线,而时钟事件设备在这个时间线上的某些点上引发中断,通常是通过编程来计数时间周期。与时钟提供程序相结合,它允许精确计时。
-> The driver of a clock-source device can be extremely simple, especially for a memory-mapped device for which the [generic MMIO clock-source driver](http://lxr.free-electrons.com/source/drivers/clocksource/mmio.c) only needs to know the address of the device register containing the counter. For the clock event, it is slightly more complicated as the driver needs to define how to program a period and how to acknowledge it when it is over, as well as provide an interrupt handler for when a timer interrupt is raised.
+> The driver of a clock-source device can be extremely simple, especially for a memory-mapped device for which the [generic MMIO clock-source driver][003] only needs to know the address of the device register containing the counter. For the clock event, it is slightly more complicated as the driver needs to define how to program a period and how to acknowledge it when it is over, as well as provide an interrupt handler for when a timer interrupt is raised.
-时钟源设备的驱动程序可能非常简单,特别地,对于一个内存映射设备,它采用 [通用 MMIO 时钟源驱动](http://lxr.free-electrons.com/source/drivers/clocksource/mmio.c),仅需要知道一个包含计数器的设备寄存器地址。对于时钟事件,它稍微复杂一些,因为驱动程序需要定义如何编程一个周期,以及如何在它结束时应答它,以及在计时器中断被引发时提供一个中断处理函数。
+时钟源设备的驱动程序可能非常简单,特别地,对于一个内存映射设备,它采用 [通用 MMIO 时钟源驱动][003],仅需要知道一个包含计数器的设备寄存器地址。对于时钟事件,它稍微复杂一些,因为驱动程序需要定义如何编程一个周期,以及如何在它结束时应答它,以及在计时器中断被引发时提供一个中断处理函数。
## 结论(Conclusion)
@@ -299,3 +295,16 @@ Linux 经常从 bootloader 接收参数(就像程序在启动时接收参数
在下一篇文章中,我们将介绍移植的最后一部分:从创建第一个内核线程到运行 `init` 进程。
+[001]: http://lxr.free-electrons.com/source/arch/openrisc/kernel/head.S?v=4.2#L32
+[002]: http://lxr.free-electrons.com/source/arch/x86/kernel/head_32.S?v=4.2#L28
+[003]: http://lxr.free-electrons.com/source/drivers/clocksource/mmio.c
+[004]: http://lxr.free-electrons.com/source/include/linux/of.h?v=4.2#L49
+[005]: http://lxr.free-electrons.com/source/init/main.c?v=4.2#L497
+[006]: https://lwn.net/Articles/320556/
+[007]: https://lwn.net/Articles/333569/
+[008]: https://lwn.net/images/pdf/LDD3/ch08.pdf
+[009]: https://tinylab.org/lwn-654783/
+[010]: https://www.kernel.org/doc/Documentation/clk.txt
+[011]: https://www.kernel.org/doc/Documentation/IRQ-domain.txt
+[012]: https://www.kernel.org/doc/Documentation/timers/timekeeping.txt
+[013]: http://www.devicetree.org/