code:
.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/doc-guide/sphinx.rst

:译者: 吴想成 Wu XiangCheng <bobwxc@email.cn>

.. _sphinxdoc_zh:

简介
====

Linux内核使用 `Sphinx <http://www.sphinx-doc.org/>`_ 来把 ``Documentation``
下的 `reStructuredText <http://docutils.sourceforge.net/rst.html>`_ 文件转
换成漂亮的文档。使用 ``make htmldocs`` 或 ``make pdfdocs`` 命令即可构建HTML
或PDF格式的文档。生成的文档放在 ``Documentation/output`` 文件夹中。

reStructuredText文件可能包含包含来自源文件的结构化文档注释或kernel-doc注释。
通常它们用于描述代码的功能、类型和设计。kernel-doc注释有一些特殊的结构和
格式，但除此之外，它们还被作为reStructuredText处理。

最后，有成千上万的纯文本文档文件散布在 ``Documentation`` 里。随着时间推移，
其中一些可能会转换为reStructuredText，但其中大部分仍保持纯文本。

.. _sphinx_install_zh:

安装Sphinx
==========

Documentation/ 下的ReST文件现在使用sphinx1.7或更高版本构建。

这有一个脚本可以检查Sphinx的依赖项。更多详细信息见
:ref:`sphinx-pre-install_zh` 。

大多数发行版都附带了Sphinx，但是它的工具链比较脆弱，而且在您的机器上升级它
或其他一些Python包导致文档构建中断的情况并不少见。

避免此情况的一种方法是使用与发行版附带的不同的版本。因此，建议使用
``virtualenv-3`` 或 ``virtualenv`` 在虚拟环境中安装Sphinx，具体取决于发行版
如何打包Python3。

.. note::

   #) html输出建议使用RTD主题。根据Sphinx版本的不同，它应该用
      ``pip install sphinx_rtd_theme`` 单独安装。

   #) 一些ReST页面包含数学表达式。由于Sphinx的工作方式，这些表达式是使用 LaTeX
      编写的。它需要安装amsfonts和amsmath宏包，以便显示。

总之，如您要安装Sphinx 2.4.4版本，应执行::

       $ virtualenv sphinx_2.4.4
       $ . sphinx_2.4.4/bin/activate
       (sphinx_2.4.4) $ pip install -r Documentation/sphinx/requirements.txt

在运行 ``. sphinx_2.4.4/bin/activate`` 之后，提示符将变化，以指示您正在使用新
环境。如果您打开了一个新的shell，那么在构建文档之前，您需要重新运行此命令以再
次进入虚拟环境中。

图片输出
--------

内核文档构建系统包含一个扩展，可以处理GraphViz和SVG格式的图像（参见
:ref:`sphinx_kfigure_zh` ）。

为了让它工作，您需要同时安装GraphViz和ImageMagick包。如果没有安装这些软件包，
构建系统仍将构建文档，但不会在输出中包含任何图像。

PDF和LaTeX构建
--------------

目前只有Sphinx 2.4及更高版本才支持这种构建。

对于PDF和LaTeX输出，还需要 ``XeLaTeX`` 3.14159265版本。（译注：此版本号真实
存在）

根据发行版的不同，您可能还需要安装一系列 ``texlive`` 软件包，这些软件包提供了
``XeLaTeX`` 工作所需的最小功能集。

.. _sphinx-pre-install_zh:

检查Sphinx依赖项
----------------

这有一个脚本可以自动检查Sphinx依赖项。如果它认得您的发行版，还会提示您所用发行
版的安装命令::

	$ ./scripts/sphinx-pre-install
	Checking if the needed tools for Fedora release 26 (Twenty Six) are available
	Warning: better to also install "texlive-luatex85".
	You should run:

		sudo dnf install -y texlive-luatex85
		/usr/bin/virtualenv sphinx_2.4.4
		. sphinx_2.4.4/bin/activate
		pip install -r Documentation/sphinx/requirements.txt

	Can't build as 1 mandatory dependency is missing at ./scripts/sphinx-pre-install line 468.

默认情况下，它会检查html和PDF的所有依赖项，包括图像、数学表达式和LaTeX构建的
需求，并假设将使用虚拟Python环境。html构建所需的依赖项被认为是必需的，其他依
赖项则是可选的。

它支持两个可选参数：

``--no-pdf``

	禁用PDF检查；

``--no-virtualenv``

	使用Sphinx的系统打包，而不是Python虚拟环境。

Sphinx构建
==========

生成文档的常用方法是运行 ``make htmldocs`` 或 ``make pdfdocs`` 。还有其它可用
的格式：请参阅 ``make help`` 的文档部分。生成的文档放在 ``Documentation/output``
下相应格式的子目录中。

要生成文档，显然必须安装Sphinx（ ``sphinx-build`` ）。要让HTML输出更漂亮，可以
使用Read the Docs Sphinx主题（ ``sphinx_rtd_theme`` ）。对于PDF输出，您还需要
``XeLaTeX`` 和来自ImageMagick（https://www.imagemagick.org）的 ``convert(1)`` 。
所有这些软件在大多发行版中都可用或已打包。

要传递额外的选项给Sphinx，可以使用make变量 ``SPHINXOPTS`` 。例如，使用
``make SPHINXOPTS=-v htmldocs`` 获得更详细的输出。

要删除生成的文档，请运行 ``make cleandocs`` 。

编写文档
========

添加新文档很容易，只需：

1. 在 ``Documentation`` 下某处添加一个新的 ``.rst`` 文件。
2. 从 ``Documentation/index.rst`` 中的Sphinx `主目录树`_ 链接到它。

.. _主目录树: http://www.sphinx-doc.org/en/stable/markup/toctree.html

对于简单的文档（比如您现在正在阅读的文档），这通常已经足够好了，但是对于较大
的文档，最好创建一个子目录（或者使用现有的子目录）。例如，图形子系统文档位于
``Documentation/gpu`` 下，拆分为多个 ``.rst`` 文件，并具有从主目录链接来的单
独索引 ``index.rst`` （有自己的目录树 ``toctree`` ）。

请参阅 `Sphinx <http://www.sphinx-doc.org/>`_ 和 `reStructuredText
<http://docutils.sourceforge.net/rst.html>`_ 的文档，以了解如何使用它们。
特别是Sphinx `reStructuredText 基础`_ 这是开始学习使用reStructuredText的
好地方。还有一些 `Sphinx 特殊标记结构`_ 。

.. _reStructuredText 基础: http://www.sphinx-doc.org/en/stable/rest.html
.. _Sphinx 特殊标记结构: http://www.sphinx-doc.org/en/stable/markup/index.html

内核文档的具体指南
------------------

这是一些内核文档的具体指南：

* 请不要过于痴迷转换格式到reStructuredText。保持简单。在大多数情况下，文档
  应该是纯文本，格式应足够一致，以便可以转换为其他格式。

* 将现有文档转换为reStructuredText时，请尽量减少格式更改。

* 在转换文档时，还要更新内容，而不仅仅是格式。

* 请遵循标题修饰符的顺序：

  1. ``=`` 文档标题，要有上线::

       ========
       文档标题
       ========

  2. ``=`` 章::

       章标题
       ======

  3. ``-`` 节::

       节标题
       ------

  4. ``~`` 小节::

       小节标题
       ~~~~~~~~

  尽管RST没有规定具体的顺序（“没有强加一个固定数量和顺序的节标题装饰风格，最终
  按照的顺序将是实际遇到的顺序。”），但是拥有一个通用级别的文档更容易遵循。

* 对于插入固定宽度的文本块（用于代码样例、用例等）： ``::`` 用于语法高亮意义不
  大的内容，尤其是短代码段； ``.. code-block:: <language>`` 用于需要语法高亮的
  较长代码块。对于嵌入到文本中的简短代码片段，请使用 \`\` 。

C域
---

**Sphinx C域（Domain）** （name c）适用于C API文档。例如，函数原型：

.. code-block:: rst

    .. c:function:: int ioctl( int fd, int request )

内核文档的C域有一些附加特性。例如，您可以使用诸如 ``open`` 或 ``ioctl`` 这样的
通用名称重命名函数的引用名称：

.. code-block:: rst

     .. c:function:: int ioctl( int fd, int request )
        :name: VIDIOC_LOG_STATUS

函数名称（例如ioctl）仍保留在输出中，但引用名称从 ``ioctl`` 变为
``VIDIOC_LOG_STATUS`` 。此函数的索引项也变为 ``VIDIOC_LOG_STATUS`` 。

请注意，不需要使用 ``c:func:`` 生成函数文档的交叉引用。由于一些Sphinx扩展的
神奇力量，如果给定函数名的索引项存在，文档构建系统会自动将对 ``function()``
的引用转换为交叉引用。如果在内核文档中看到 ``c:func:`` 的用法，请删除它。

列表
----

我们建议使用 *列式表* 格式。 *列式表* 格式是二级列表。与ASCII艺术相比，它们对
文本文件的读者来说可能没有那么舒适。但其优点是易于创建或修改，而且修改的差异
（diff）更有意义，因为差异仅限于修改的内容。

*平铺表* 也是一个二级列表，类似于 *列式表* ，但具有一些额外特性：

* 列范围：使用 ``cspan`` 修饰，可以通过其他列扩展单元格

* 行范围：使用 ``rspan`` 修饰，可以通过其他行扩展单元格

* 自动将表格行最右边的单元格扩展到该行右侧空缺的单元格上。若使用
  ``:fill-cells:`` 选项，此行为可以从 *自动合并* 更改为 *自动插入* ，自动
  插入（空）单元格，而不是扩展合并到最后一个单元格。

选项：

* ``:header-rows:``   [int] 标题行计数
* ``:stub-columns:``  [int] 标题列计数
* ``:widths:``        [[int] [int] ... ] 列宽
* ``:fill-cells:``    插入缺少的单元格，而不是自动合并缺少的单元格

修饰：

* ``:cspan:`` [int] 扩展列
* ``:rspan:`` [int] 扩展行

下面的例子演示了如何使用这些标记。分级列表的第一级是 *表格行* 。 *表格行* 中
只允许一个标记，即该 *表格行* 中的单元格列表。 *comments* （ ``..`` ）和
*targets* 例外（例如引用 ``:ref:`最后一行 <last row_zh>``` / :ref:`最后一行
<last row_zh>` ）。

.. code-block:: rst

   .. flat-table:: 表格标题
      :widths: 2 1 1 3

      * - 表头 列1
        - 表头 列2
        - 表头 列3
        - 表头 列4

      * - 行1
        - 字段1.1
        - 字段1.2（自动扩展）

      * - 行2
        - 字段2.1
        - :rspan:`1` :cspan:`1` 字段2.2~3.3

      * .. _`last row_zh`:

        - 行3

渲染效果：

   .. flat-table:: 表格标题
      :widths: 2 1 1 3

      * - 表头 列1
        - 表头 列2
        - 表头 列3
        - 表头 列4

      * - 行1
        - 字段1.1
        - 字段1.2（自动扩展）

      * - 行2
        - 字段2.1
        - :rspan:`1` :cspan:`1` 字段2.2~3.3

      * .. _`last row_zh`:

        - 行3

交叉引用
--------

从一页文档到另一页文档的交叉引用可以通过简单地写出文件路径来完成，无特殊格式
要求。路径可以是绝对路径或相对路径。绝对路径从“Documentation/”开始。例如，要
交叉引用此页，以下写法皆可，取决于具体的文档目录（注意 ``.rst`` 扩展名是可选
的）::

    参见 Documentation/doc-guide/sphinx.rst 。此法始终可用。
    请查看 sphinx.rst ，仅在同级目录中有效。
    请阅读 ../sphinx.rst ，上级目录中的文件。

如果要使用相对路径，则需要使用Sphinx的 ``doc`` 修饰。例如，从同一目录引用此页
的操作如下::

    参见 :doc:`sphinx文档的自定义链接文本 <sphinx>`.

对于大多数用例，前者是首选，因为它更干净，更适合阅读源文件的人。如果您遇到一
个没有任何特殊作用的 ``:doc:`` 用法，请将其转换为文档路径。

有关交叉引用kernel-doc函数或类型的信息，请参阅
Documentation/doc-guide/kernel-doc.rst 。

.. _sphinx_kfigure_zh:

图形图片
========

如果要添加图片，应该使用 ``kernel-figure`` 和 ``kernel-image`` 指令。例如，
要插入具有可缩放图像格式的图形，请使用SVG（:ref:`svg_image_example_zh` ）::

    .. kernel-figure::  ../../../doc-guide/svg_image.svg
       :alt:    简易 SVG 图片

       SVG 图片示例

.. _svg_image_example_zh:

.. kernel-figure::  ../../../doc-guide/svg_image.svg
   :alt:    简易 SVG 图片

   SVG 图片示例

内核figure（和image）指令支持 DOT 格式文件，请参阅

* DOT：http://graphviz.org/pdf/dotguide.pdf
* Graphviz：http://www.graphviz.org/content/dot-language

一个简单的例子（:ref:`hello_dot_file_zh` ）::

  .. kernel-figure::  ../../../doc-guide/hello.dot
     :alt:    你好，世界

     DOT 示例

.. _hello_dot_file_zh:

.. kernel-figure::  ../../../doc-guide/hello.dot
   :alt:    你好，世界

   DOT 示例

嵌入的渲染标记（或语言），如Graphviz的 **DOT** 由 ``kernel-render`` 指令提供::

  .. kernel-render:: DOT
     :alt: 有向图
     :caption: 嵌入式 **DOT** (Graphviz) 代码

     digraph foo {
      "五棵松" -> "国贸";
     }

如何渲染取决于安装的工具。如果安装了Graphviz，您将看到一个矢量图像。否则，原始
标记将作为 *文字块* 插入（:ref:`hello_dot_render_zh` ）。

.. _hello_dot_render_zh:

.. kernel-render:: DOT
   :alt: 有向图
   :caption: 嵌入式 **DOT** (Graphviz) 代码

   digraph foo {
      "五棵松" -> "国贸";
   }

*render* 指令包含 *figure* 指令中已知的所有选项，以及选项 ``caption`` 。如果
``caption`` 有值，则插入一个 *figure* 节点，若无，则插入一个 *image* 节点。
如果您想引用它，还需要一个 ``caption`` （:ref:`hello_svg_render_zh` ）。

嵌入式 **SVG**::

  .. kernel-render:: SVG
     :caption: 嵌入式 **SVG** 标记
     :alt: 右上箭头

     <?xml version="1.0" encoding="UTF-8"?>
     <svg xmlns="http://www.w3.org/2000/svg" version="1.1" ...>
        ...
     </svg>

.. _hello_svg_render_zh:

.. kernel-render:: SVG
   :caption: 嵌入式 **SVG** 标记
   :alt: 右上箭头

   <?xml version="1.0" encoding="UTF-8"?>
   <svg xmlns="http://www.w3.org/2000/svg"
     version="1.1" baseProfile="full" width="70px" height="40px" viewBox="0 0 700 400">
   <line x1="180" y1="370" x2="500" y2="50" stroke="black" stroke-width="15px"/>
   <polygon points="585 0 525 25 585 50" transform="rotate(135 525 25)"/>
   </svg>


code:
.. SPDX-License-Identifier: GPL-2.0

.. include:: ../../disclaimer-zh_CN.rst

:Original: Documentation/driver-api/gpio/index.rst

:翻译:

 司延腾 Yanteng Si <siyanteng@loongson.cn>

:校译:

=======================
通用型输入/输出（GPIO）
=======================

目录:

.. toctree::
   :maxdepth: 2

   legacy

Todolist:

*   intro
*   using-gpio
*   driver
*   consumer
*   board
*   drivers-on-gpio
*   bt8xxgpio

核心
====

该API在以下内核代码中:

include/linux/gpio/driver.h

drivers/gpio/gpiolib.c

ACPI支持
========

该API在以下内核代码中:

drivers/gpio/gpiolib-acpi.c

设备树支持
==========

该API在以下内核代码中:

drivers/gpio/gpiolib-of.c

设备管理支持
============

该API在以下内核代码中:

drivers/gpio/gpiolib-devres.c

sysfs帮助（函数）
=================

该API在以下内核代码中:

drivers/gpio/gpiolib-sysfs.c

repo_name: angery/linux


code:
.. SPDX-License-Identifier: GPL-2.0

.. include:: ../../disclaimer-zh_CN.rst

:Original: Documentation/driver-api/gpio/legacy.rst

:翻译:

 傅炜 Fu Wei <tekkamanninja@gmail.com>
 司延腾 Yanteng Si <siyanteng@loongson.cn>

:校译:

传统GPIO接口
============

本文档概述了Linux下的GPIO访问公约。

这些函数以 gpio_* 作为前缀。其他的函数不允许使用这样的前缀或相关的
__gpio_* 前缀。

什么是GPIO？
============
"通用输入/输出口"(GPIO)是一个灵活的由软件控制的数字信号。他们可
由多种芯片提供,且对于从事嵌入式和定制硬件的 Linux 开发者来说是
比较熟悉。每个GPIO 都代表一个连接到特定引脚或球栅阵列(BGA)封装中
“球珠”的一个位。电路板原理图显示了 GPIO 与外部硬件的连接关系。
驱动可以编写成通用代码，以使板级启动代码可传递引脚配置数据给驱动。

片上系统 (SOC) 处理器对 GPIO 有很大的依赖。在某些情况下,每个
非专用引脚都可配置为 GPIO,且大多数芯片都最少有一些 GPIO。
可编程逻辑器件(类似 FPGA) 可以方便地提供 GPIO。像电源管理和
音频编解码器这样的多功能芯片经常留有一些这样的引脚来帮助那些引脚
匮乏的 SOC。同时还有通过 I2C 或 SPI 串行总线连接的“GPIO扩展器”
芯片。大多数 PC 的南桥有一些拥有 GPIO 能力的引脚 (只有BIOS
固件才知道如何使用他们)。

GPIO 的实际功能因系统而异。通常用法有:

  - 输出值可写 (高电平=1，低电平=0)。一些芯片也有如何驱动这些值的选项,
    例如只允许输出一个值、支持“线与”及其他取值类似的模式(值得注意的是
    “开漏”信号)

  - 输入值可读(1、0)。一些芯片支持引脚在配置为“输出”时回读，这对于类似
    “线与”的情况(以支持双向信号)是非常有用的。GPIO 控制器可能有输入
    去毛刺/消抖逻辑,这有时需要软件控制。

  - 输入通常可作为 IRQ 信号,一般是沿触发,但有时是电平触发。这样的 IRQ
    可能配置为系统唤醒事件,以将系统从低功耗状态下唤醒。

  - 通常一个 GPIO 根据不同产品电路板的需求,可以配置为输入或输出,也有仅
    支持单向的。

  - 大部分 GPIO 可以在持有自旋锁时访问,但是通常由串行总线扩展的 GPIO
    不允许持有自旋锁。但某些系统也支持这种类型。

对于给定的电路板,每个 GPIO 都用于某个特定的目的,如监控 MMC/SD 卡的
插入/移除、检测卡的写保护状态、驱动 LED、配置收发器、模拟串行总线、
复位硬件看门狗、感知开关状态等等。

GPIO 公约
=========
注意,这个叫做“公约”，因为这不是强制性的，不遵循这个公约是无伤大雅的，
因为此时可移植性并不重要。GPIO 常用于板级特定的电路逻辑,甚至可能
随着电路板的版本而改变，且不可能在不同走线的电路板上使用。仅有在少数
功能上才具有可移植性，其他功能是平台特定。这也是由于“胶合”的逻辑造成的。

此外，这不需要任何的执行框架，只是一个接口。某个平台可能通过一个简单地
访问芯片寄存器的内联函数来实现它，其他平台可能通过委托一系列不同的GPIO
控制器的抽象函数来实现它。(有一些可选的代码能支持这种策略的实现,本文档
后面会介绍，但作为 GPIO 接口的客户端驱动程序必须与它的实现无关。)

也就是说,如果在他们的平台上支持这个公约，驱动应尽可能的使用它。同时，平台
必须在 Kconfig 中选择 ARCH_REQUIRE_GPIOLIB 或者 ARCH_WANT_OPTIONAL_GPIOLIB
选项。那些调用标准 GPIO 函数的驱动应该在 Kconfig 入口中声明依赖GENERIC_GPIO。
当驱动包含文件:

	#include <linux/gpio.h>

则 GPIO 函数是可用,无论是“真实代码”还是经优化过的语句。如果你遵守
这个公约，当你的代码完成后，对其他的开发者来说会更容易看懂和维护。

注意，这些操作包含所用平台的 I/O 屏障代码，驱动无须显式地调用他们。

标识 GPIO
---------

GPIO 是通过无符号整型来标识的,范围是 0 到 MAX_INT。保留“负”数
用于其他目的,例如标识信号“在这个板子上不可用”或指示错误。未接触底层
硬件的代码会忽略这些整数。

平台会定义这些整数的用法,且通常使用 #define 来定义 GPIO，这样
板级特定的启动代码可以直接关联相应的原理图。相对来说，驱动应该仅使用
启动代码传递过来的 GPIO 编号，使用 platform_data 保存板级特定
引脚配置数据 (同时还有其他须要的板级特定数据)，避免可能出现的问题。

例如一个平台使用编号 32-159 来标识 GPIO,而在另一个平台使用编号0-63
标识一组 GPIO 控制器,64-79标识另一类 GPIO 控制器,且在一个含有
FPGA 的特定板子上使用 80-95。编号不一定要连续,那些平台中，也可以
使用编号2000-2063来标识一个 I2C 接口的 GPIO 扩展器中的 GPIO。

如果你要初始化一个带有无效 GPIO 编号的结构体,可以使用一些负编码
(如"-EINVAL")，那将使其永远不会是有效。来测试这样一个结构体中的编号
是否关联一个 GPIO，你可使用以下断言::

	int gpio_is_valid(int number);

如果编号不存在，则请求和释放 GPIO 的函数将拒绝执行相关操作(见下文)。
其他编号也可能被拒绝,比如一个编号可能存在，但暂时在给定的电路上不可用。

一个平台是否支持多个 GPIO 控制器为平台特定的实现问题，就像是否可以
在 GPIO 编号空间中有“空洞”和是否可以在运行时添加新的控制器一样。
这些问题会影响其他事情，包括相邻的 GPIO 编号是否存在等。

使用 GPIO
---------

对于一个 GPIO，系统应该做的第一件事情就是通过 gpio_request()
函数分配它，见下文。

接下来是设置I/O方向，这通常是在板级启动代码中为所使用的 GPIO 设置
platform_device 时完成::

	/* 设置为输入或输出, 返回 0 或负的错误代码 */
	int gpio_direction_input(unsigned gpio);
	int gpio_direction_output(unsigned gpio, int value);

返回值为零代表成功，否则返回一个负的错误代码。这个返回值需要检查，因为
get/set(获取/设置)函数调用没法返回错误,且有可能是配置错误。通常，
你应该在进程上下文中调用这些函数。然而,对于自旋锁安全的 GPIO，在板子
启动的早期、进程启动前使用他们也是可以的。

对于作为输出的 GPIO，为其提供初始输出值，对于避免在系统启动期间出现
信号毛刺是很有帮助的。

为了与传统的 GPIO 接口兼容, 在设置一个 GPIO 方向时，如果它还未被申请，
则隐含了申请那个 GPIO 的操作(见下文)。这种兼容性正在从可选的 gpiolib
框架中移除。

如果这个 GPIO 编码不存在，或者特定的 GPIO 不能用于那种模式，则方向
设置可能失败。依赖启动固件来正确地设置方向通常是一个坏主意，因为它可能
除了启动Linux，并没有做更多的验证工作。(同理, 板子的启动代码可能需要
将这个复用的引脚设置为 GPIO，并正确地配置上拉/下拉电阻。)

访问自旋锁安全的 GPIO
---------------------

大多数 GPIO 控制器可以通过内存读/写指令来访问。这些指令不会休眠,可以
安全地在硬(非线程)中断例程和类似的上下文中完成。

对于那些用 gpio_cansleep()测试总是返回失败的 GPIO(见下文)，使用
以下的函数访问::

	/* GPIO 输入:返回零或非零 */
	int gpio_get_value(unsigned gpio);

	/* GPIO 输出 */
	void gpio_set_value(unsigned gpio, int value);

GPIO值是布尔值，零表示低电平，非零表示高电平。当读取一个输出引脚的值时，
返回值应该是引脚上的值。这个值不总是和输出值相符，因为存在开漏输出信号和
输出延迟问题。

以上的 get/set 函数无错误返回值，因为之前 gpio_direction_*()应已检查过
其是否为“无效GPIO”。此外，还需要注意的是并不是所有平台都可以从输出引脚
中读取数据，对于不能读取的引脚应总返回零。另外，对那些在原子上下文中无法
安全访问的 GPIO (译者注：因为访问可能导致休眠)使用这些函数是不合适的
(见下文)。

在 GPIO 编号(还有输出、值)为常数的情况下,鼓励通过平台特定的实现来优化
这两个函数来访问 GPIO 值。这种情况(读写一个硬件寄存器)下只需要几条指令
是很正常的,且无须自旋锁。这种优化函数比起那些在子程序上花费许多指令的
函数可以使得模拟接口(译者注:例如 GPIO 模拟 I2C、1-wire 或 SPI)的
应用(在空间和时间上都)更具效率。

访问可能休眠的 GPIO
-------------------

某些 GPIO 控制器必须通过基于总线(如 I2C 或 SPI)的消息访问。读或写这些
GPIO 值的命令需要等待其信息排到队首才发送命令，再获得其反馈。期间需要
休眠，这不能在 IRQ 例程(中断上下文)中执行。

支持此类 GPIO 的平台通过以下函数返回非零值来区分出这种 GPIO。(此函数需要
一个之前通过 gpio_request 分配到的有效 GPIO 编号)::

	int gpio_cansleep(unsigned gpio);

为了访问这种 GPIO,内核定义了一套不同的函数::

	/* GPIO 输入:返回零或非零 ,可能会休眠 */
	int gpio_get_value_cansleep(unsigned gpio);

	/* GPIO 输出,可能会休眠 */
	void gpio_set_value_cansleep(unsigned gpio, int value);

访问这样的 GPIO 需要一个允许休眠的上下文，例如线程 IRQ 处理例程，并用以上的
访问函数替换那些没有 cansleep()后缀的自旋锁安全访问函数。

除了这些访问函数可能休眠，且它们操作的 GPIO 不能在硬件 IRQ 处理例程中访问的
事实，这些处理例程实际上和自旋锁安全的函数是一样的。

** 除此之外 ** 调用设置和配置此类 GPIO 的函数也必须在允许休眠的上下文中，
因为它们可能也需要访问 GPIO 控制器芯片 (这些设置函数通常在板级启动代码或者
驱动探测/断开代码中，所以这是一个容易满足的约束条件。) ::

                gpio_direction_input()
                gpio_direction_output()
                gpio_request()

        ## 	gpio_request_one()
        ##	gpio_request_array()
        ## 	gpio_free_array()

                gpio_free()
                gpio_set_debounce()

声明和释放 GPIO
----------------

为了有助于捕获系统配置错误,定义了两个函数::

	/* 申请 GPIO, 返回 0 或负的错误代码.
	 * 非空标签可能有助于诊断.
	 */
	int gpio_request(unsigned gpio, const char *label);

	/* 释放之前声明的 GPIO */
	void gpio_free(unsigned gpio);

将无效的 GPIO 编码传递给 gpio_request()会导致失败，申请一个已使用这个
函数声明过的 GPIO 也会失败。gpio_request()的返回值必须检查。你应该在
进程上下文中调用这些函数。然而,对于自旋锁安全的 GPIO,在板子启动的早期、
进入进程之前是可以申请的。

这个函数完成两个基本的目标。一是标识那些实际上已作为 GPIO 使用的信号线，
这样便于更好地诊断;系统可能需要服务几百个可用的 GPIO，但是对于任何一个
给定的电路板通常只有一些被使用。另一个目的是捕获冲突，查明错误:如两个或
更多驱动错误地认为他们已经独占了某个信号线,或是错误地认为移除一个管理着
某个已激活信号的驱动是安全的。也就是说，申请 GPIO 的作用类似一种锁机制。

某些平台可能也使用 GPIO 作为电源管理激活信号(例如通过关闭未使用芯片区和
简单地关闭未使用时钟)。

对于 GPIO 使用引脚控制子系统已知的引脚，子系统应该被告知其使用情况；
一个 gpiolib 驱动的 .request()操作应调用 pinctrl_gpio_request()，
而 gpiolib 驱动的 .free()操作应调用 pinctrl_gpio_free()。引脚控制
子系统允许 pinctrl_gpio_request()在某个引脚或引脚组以复用形式“属于”
一个设备时都成功返回。

任何须将 GPIO 信号导向适当引脚的引脚复用硬件的编程应该发生在 GPIO
驱动的 .direction_input()或 .direction_output()函数中，以及
任何输出 GPIO 值的设置之后。这样可使从引脚特殊功能到 GPIO 的转换
不会在引脚产生毛刺波形。有时当用一个 GPIO 实现其信号驱动一个非 GPIO
硬件模块的解决方案时，就需要这种机制。

某些平台允许部分或所有 GPIO 信号使用不同的引脚。类似的，GPIO 或引脚的
其他方面也需要配置，如上拉/下拉。平台软件应该在对这些 GPIO 调用
gpio_request()前将这类细节配置好，例如使用引脚控制子系统的映射表，
使得 GPIO 的用户无须关注这些细节。

还有一个值得注意的是在释放 GPIO 前，你必须停止使用它。

注意:申请一个 GPIO 并没有以任何方式配置它，只不过标识那个 GPIO 处于使用
状态。必须有另外的代码来处理引脚配置(如控制 GPIO 使用的引脚、上拉/下拉)。
考虑到大多数情况下声明 GPIO 之后就会立即配置它们,所以定义了以下三个辅助函数::

	/* 申请一个 GPIO 信号, 同时通过特定的'flags'初始化配置,
	 * 其他和 gpio_request()的参数和返回值相同
	 *
	 */
	int gpio_request_one(unsigned gpio, unsigned long flags, const char *label);

	/* 在单个函数中申请多个 GPIO
	 */
	int gpio_request_array(struct gpio *array, size_t num);

	/* 在单个函数中释放多个 GPIO
	 */
	void gpio_free_array(struct gpio *array, size_t num);

这里 'flags' 当前定义可指定以下属性:

	* GPIOF_DIR_IN		- 配置方向为输入
	* GPIOF_DIR_OUT		- 配置方向为输出

	* GPIOF_INIT_LOW	- 在作为输出时,初始值为低电平
	* GPIOF_INIT_HIGH	- 在作为输出时,初始值为高电平
	* GPIOF_OPEN_DRAIN	- gpio引脚为开漏信号
	* GPIOF_OPEN_SOURCE	- gpio引脚为源极开路信号

	* GPIOF_EXPORT_DIR_FIXED	- 将 gpio 导出到 sysfs，并保持方向
	* GPIOF_EXPORT_DIR_CHANGEABLE	- 同样是导出, 但允许改变方向

因为 GPIOF_INIT_* 仅有在配置为输出的时候才存在,所以有效的组合为:

	* GPIOF_IN		- 配置为输入
	* GPIOF_OUT_INIT_LOW	- 配置为输出,并初始化为低电平
	* GPIOF_OUT_INIT_HIGH	- 配置为输出,并初始化为高电平

当设置 flag 为 GPIOF_OPEN_DRAIN 时，则假设引脚是开漏信号。这样的引脚
将不会在输出模式下置1。这样的引脚需要连接上拉电阻。通过使能这个标志，gpio库
将会在被要求输出模式下置1时将引脚变为输入状态来使引脚置高。引脚在输出模式下
通过置0使其输出低电平。

当设置 flag 为 GPIOF_OPEN_SOURCE 时，则假设引脚为源极开路信号。这样的引脚
将不会在输出模式下置0。这样的引脚需要连接下拉电阻。通过使能这个标志，gpio库
将会在被要求输出模式下置0时将引脚变为输入状态来使引脚置低。引脚在输出模式下
通过置1使其输出高电平。

将来这些标志可能扩展到支持更多的属性。

更进一步,为了更简单地声明/释放多个 GPIO,'struct gpio'被引进来封装所有
这三个领域::

	struct gpio {
		unsigned	gpio;
		unsigned long	flags;
		const char	*label;
	};

一个典型的用例::

	static struct gpio leds_gpios[] = {
		{ 32, GPIOF_OUT_INIT_HIGH, "Power LED" }, /* 默认开启 */
		{ 33, GPIOF_OUT_INIT_LOW,  "Green LED" }, /* 默认关闭 */
		{ 34, GPIOF_OUT_INIT_LOW,  "Red LED"   }, /* 默认关闭 */
		{ 35, GPIOF_OUT_INIT_LOW,  "Blue LED"  }, /* 默认关闭 */
		{ ... },
	};

	err = gpio_request_one(31, GPIOF_IN, "Reset Button");
	if (err)
		...

	err = gpio_request_array(leds_gpios, ARRAY_SIZE(leds_gpios));
	if (err)
		...

	gpio_free_array(leds_gpios, ARRAY_SIZE(leds_gpios));

GPIO 映射到 IRQ
----------------

GPIO 编号是无符号整数;IRQ 编号也是。这些构成了两个逻辑上不同的命名空间
(GPIO 0 不一定使用 IRQ 0)。你可以通过以下函数在它们之间实现映射::

	/* 映射 GPIO 编号到 IRQ 编号 */
	int gpio_to_irq(unsigned gpio);

	/* 映射 IRQ 编号到 GPIO 编号 (尽量避免使用) */
	int irq_to_gpio(unsigned irq);

它们的返回值为对应命名空间的相关编号，或是负的错误代码(如果无法映射)。
(例如,某些 GPIO 无法做为 IRQ 使用。)以下的编号错误是未经检测的:使用一个
未通过 gpio_direction_input()配置为输入的 GPIO 编号，或者使用一个
并非来源于gpio_to_irq()的 IRQ 编号。

这两个映射函数可能会在信号编号的加减计算过程上花些时间。它们不可休眠。

gpio_to_irq()返回的非错误值可以传递给 request_irq()或者 free_irq()。
它们通常通过板级特定的初始化代码存放到平台设备的 IRQ 资源中。注意:IRQ
触发选项是 IRQ 接口的一部分，如 IRQF_TRIGGER_FALLING，系统唤醒能力
也是如此。

irq_to_gpio()返回的非错误值大多数通常可以被 gpio_get_value()所使用，
比如在 IRQ 是沿触发时初始化或更新驱动状态。注意某些平台不支持反映射,所以
你应该尽量避免使用它。

模拟开漏信号
------------

有时在只有低电平信号作为实际驱动结果(译者注:多个输出连接于一点，逻辑电平
结果为所有输出的逻辑与)的时候,共享的信号线需要使用“开漏”信号。(该术语
适用于 CMOS 管；而 TTL 用“集电极开路”。)一个上拉电阻使信号为高电平。这
有时被称为“线与”。实际上，从负逻辑(低电平为真)的角度来看，这是一个“线或”。

一个开漏信号的常见例子是共享的低电平使能 IRQ 信号线。此外,有时双向数据总线
信号也使用漏极开路信号。

某些 GPIO 控制器直接支持开漏输出，还有许多不支持。当你需要开漏信号，但
硬件又不直接支持的时候，一个常用的方法是用任何即可作输入也可作输出的 GPIO
引脚来模拟:

 LOW:	gpio_direction_output(gpio, 0) ... 这代码驱动信号并覆盖
	上拉配置。

 HIGH:	gpio_direction_input(gpio) ... 这代码关闭输出,所以上拉电阻
	(或其他的一些器件)控制了信号。

如果你将信号线“驱动”为高电平，但是 gpio_get_value(gpio)报告了一个
低电平(在适当的上升时间后)，你就可以知道是其他的一些组件将共享信号线拉低了。
这不一定是错误的。一个常见的例子就是 I2C 时钟的延长：一个需要较慢时钟的
从设备延迟 SCK 的上升沿，而 I2C 主设备相应地调整其信号传输速率。

GPIO控制器和引脚控制子系统
--------------------------

SOC上的GPIO控制器可能与引脚控制子系统紧密结合，即引脚可以与可选的gpio功
能一起被其他功能使用。我们已经涵盖了这样的情况，例如一个GPIO控制器需要保
留一个引脚或通过调用以下任何一个引脚来设置其方向::

  pinctrl_gpio_request()
  pinctrl_gpio_free()
  pinctrl_gpio_direction_input()
  pinctrl_gpio_direction_output()

但是，引脚控制子系统是如何将GPIO号码（这是一个全局事项）与某个引脚控制器
上的某个引脚交叉关联的？

这是通过注册引脚的“范围”来实现的，这基本上是交叉参考表。这些描述是在
Documentation/driver-api/pin-control.rst

虽然引脚分配完全由引脚控制子系统管理，但gpio（在gpiolib下）仍由gpio驱动
维护。可能发生的情况是，SoC中的不同引脚范围由不同的gpio驱动器管理。

这使得在调用 "pinctrl_gpio_request" 之前，让gpio驱动向pin ctrl子系
统宣布它们的引脚范围是合理的，以便在使用任何gpio之前要求引脚控制子系统准
备相应的引脚。

为此，gpio控制器可以用引脚控制子系统注册其引脚范围。目前有两种方法：有或
无DT。

关于对DT的支持，请参考 Documentation/devicetree/bindings/gpio/gpio.txt.

对于非DT支持，用户可以用适当的参数调用gpiochip_add_pin_range()，将一
系列的gpio引脚注册到引脚控制驱动上。为此，必须将引脚控制设备的名称字符串
作为参数之一传给这个程序。

这些公约忽略了什么？
====================

这些公约忽略的最大一件事就是引脚复用，因为这属于高度芯片特定的属性且
没有可移植性。某个平台可能不需要明确的复用信息；有的对于任意给定的引脚
可能只有两个功能选项；有的可能每个引脚有八个功能选项；有的可能可以将
几个引脚中的任何一个作为给定的 GPIO。(是的，这些例子都来自于当前运行
Linux 的系统。)

在某些系统中,与引脚复用相关的是配置和使能集成的上、下拉模式。并不是所有
平台都支持这种模式,或者不会以相同的方式来支持这种模式；且任何给定的电路板
可能使用外置的上拉(或下拉)电阻,这时芯片上的就不应该使用。(当一个电路需要
5kOhm 的拉动电阻,芯片上的 100 kOhm 电阻就不能做到。)同样的，驱动能力
(2 mA vs 20 mA)和电压(1.8V vs 3.3V)是平台特定问题,就像模型一样在
可配置引脚和 GPIO 之间(没)有一一对应的关系。

还有其他一些系统特定的机制没有在这里指出，例如上述的输入去毛刺和线与输出
选项。硬件可能支持批量读或写 GPIO，但是那一般是配置相关的：对于处于同一
块区(bank)的GPIO。(GPIO 通常以 16 或 32 个组成一个区块，一个给定的
片上系统一般有几个这样的区块。)某些系统可以通过输出 GPIO 触发 IRQ，
或者从并非以 GPIO 管理的引脚取值。这些机制的相关代码没有必要具有可移植性。

当前，动态定义 GPIO 并不是标准的，例如作为配置一个带有某些 GPIO 扩展器的
附加电路板的副作用。

GPIO 实现者的框架（可选）
=========================

前面提到了，有一个可选的实现框架，让平台使用相同的编程接口，更加简单地支持
不同种类的 GPIO 控制器。这个框架称为"gpiolib"。

作为一个辅助调试功能，如果 debugfs 可用，就会有一个 /sys/kernel/debug/gpio
文件。通过这个框架，它可以列出所有注册的控制器,以及当前正在使用中的 GPIO
的状态。

控制器驱动: gpio_chip
---------------------

在框架中每个 GPIO 控制器都包装为一个 "struct gpio_chip"，他包含了
该类型的每个控制器的常用信息:

	- 设置 GPIO 方向的方法
	- 用于访问 GPIO 值的方法
	- 告知调用其方法是否可能休眠的标志
	- 可选的 debugfs 信息导出方法 (显示类似上拉配置一样的额外状态)
	- 诊断标签

也包含了来自 device.platform_data 的每个实例的数据：它第一个 GPIO 的
编号和它可用的 GPIO 的数量。

实现 gpio_chip 的代码应支持多控制器实例，这可能使用驱动模型。那些代码要
配置每个 gpio_chip，并发起gpiochip_add()。卸载一个 GPIO 控制器很少见，
但在必要的时候可以使用 gpiochip_remove()。

大部分 gpio_chip 是一个实例特定结构体的一部分，而并不将 GPIO 接口单独
暴露出来,比如编址、电源管理等。类似编解码器这样的芯片会有复杂的非 GPIO
状态。

任何一个 debugfs 信息导出方法通常应该忽略还未申请作为 GPIO 的信号线。
他们可以使用 gpiochip_is_requested()测试，当这个 GPIO 已经申请过了
就返回相关的标签，否则返回 NULL。

平台支持
--------

为了支持这个框架，一个平台的 Kconfig 文件将会 "select"(选择)
ARCH_REQUIRE_GPIOLIB 或 ARCH_WANT_OPTIONAL_GPIOLIB，并让它的
<asm/gpio.h> 包含 <asm-generic/gpio.h>，同时定义三个方法:
gpio_get_value()、gpio_set_value()和 gpio_cansleep()。

它也应提供一个 ARCH_NR_GPIOS 的定义值，这样可以更好地反映该平台 GPIO
的实际数量,节省静态表的空间。(这个定义值应该包含片上系统内建 GPIO 和
GPIO 扩展器中的数据。)

ARCH_REQUIRE_GPIOLIB 意味着 gpiolib 核心在这个构架中将总是编译进内核。

ARCH_WANT_OPTIONAL_GPIOLIB 意味着 gpiolib 核心默认关闭,且用户可以
使能它,并将其编译进内核(可选)。

如果这些选项都没被选择,该平台就不通过 GPIO-lib 支持 GPIO,且代码不可以
被用户使能。

以下这些方法的实现可以直接使用框架代码,并总是通过 gpio_chip 调度::

  #define gpio_get_value	__gpio_get_value
  #define gpio_set_value	__gpio_set_value
  #define gpio_cansleep		__gpio_cansleep

这些定义可以用更理想的实现方法替代，那就是使用经过逻辑优化的内联函数来访问
基于特定片上系统的 GPIO。例如,若引用的 GPIO (寄存器位偏移)是常量“12”，
读取或设置它可能只需少则两或三个指令，且不会休眠。当这样的优化无法实现时，
那些函数必须使用框架提供的代码，那就至少要几十条指令才可以实现。对于用 GPIO
模拟的 I/O 接口, 如此精简指令是很有意义的。

对于片上系统，平台特定代码为片上 GPIO 每个区(bank)定义并注册 gpio_chip
实例。那些 GPIO 应该根据芯片厂商的文档进行编码/标签,并直接和电路板原理图
对应。他们应该开始于零并终止于平台特定的限制。这些 GPIO(代码)通常从
arch_initcall()或者更早的地方集成进平台初始化代码，使这些 GPIO 总是可用，
且他们通常可以作为 IRQ 使用。

板级支持
--------

对于外部 GPIO 控制器(例如 I2C 或 SPI 扩展器、专用芯片、多功能器件、FPGA
或 CPLD)，大多数常用板级特定代码都可以注册控制器设备，并保证他们的驱动知道
gpiochip_add()所使用的 GPIO 编号。他们的起始编号通常跟在平台特定的 GPIO
编号之后。

例如板级启动代码应该创建结构体指明芯片公开的 GPIO 范围，并使用 platform_data
将其传递给每个 GPIO 扩展器芯片。然后芯片驱动中的 probe()例程可以将这个
数据传递给 gpiochip_add()。

初始化顺序很重要。例如，如果一个设备依赖基于 I2C 的(扩展)GPIO，那么它的
probe()例程就应该在那个 GPIO 有效以后才可以被调用。这意味着设备应该在
GPIO 可以工作之后才可被注册。解决这类依赖的的一种方法是让这种 gpio_chip
控制器向板级特定代码提供 setup()和 teardown()回调函数。一旦所有必须的
资源可用之后，这些板级特定的回调函数将会注册设备，并可以在这些 GPIO 控制器
设备变成无效时移除它们。

用户空间的 Sysfs 接口（可选）
=============================

使用“gpiolib”实现框架的平台可以选择配置一个 GPIO 的 sysfs 用户接口。
这不同于 debugfs 接口，因为它提供的是对 GPIO方向和值的控制，而不只显示
一个GPIO 的状态摘要。此外,它可以出现在没有调试支持的产品级系统中。

例如，通过适当的系统硬件文档，用户空间可以知道 GIOP #23 控制 Flash
存储器的写保护(用于保护其中 Bootloader 分区)。产品的系统升级可能需要
临时解除这个保护：首先导入一个 GPIO，改变其输出状态，然后在重新使能写保护
前升级代码。通常情况下,GPIO #23 是不会被触及的，并且内核也不需要知道他。

根据适当的硬件文档，某些系统的用户空间 GPIO 可以用于确定系统配置数据，
这些数据是标准内核不知道的。在某些任务中，简单的用户空间 GPIO 驱动可能是
系统真正需要的。

注意：标准内核驱动中已经存在通用的“LED 和按键”GPIO 任务，分别是:
"leds-gpio" 和 "gpio_keys"。请使用这些来替代直接访问 GPIO，因为集成在
内核框架中的这类驱动比你在用户空间的代码更好。

Sysfs 中的路径
--------------

在/sys/class/gpio 中有 3 类入口:

   -	用于在用户空间控制 GPIO 的控制接口;

   -	GPIOs 本身;以及

   -	GPIO 控制器 ("gpio_chip" 实例)。

除了这些标准的文件,还包含“device”符号链接。

控制接口是只写的:

    /sys/class/gpio/

    	"export" ... 用户空间可以通过写其编号到这个文件，要求内核导出
		一个 GPIO 的控制到用户空间。

		例如: 如果内核代码没有申请 GPIO #19,"echo 19 > export"
		将会为 GPIO #19 创建一个 "gpio19" 节点。

    	"unexport" ... 导出到用户空间的逆操作。

		例如: "echo 19 > unexport" 将会移除使用"export"文件导出的
		"gpio19" 节点。

GPIO 信号的路径类似 /sys/class/gpio/gpio42/ (对于 GPIO #42 来说)，
并有如下的读/写属性:

    /sys/class/gpio/gpioN/

	"direction" ... 读取得到 "in" 或 "out"。这个值通常运行写入。
		写入"out" 时,其引脚的默认输出为低电平。为了确保无故障运行，
		"low" 或 "high" 的电平值应该写入 GPIO 的配置，作为初始输出值。

		注意:如果内核不支持改变 GPIO 的方向，或者在导出时内核代码没有
		明确允许用户空间可以重新配置 GPIO 方向，那么这个属性将不存在。

	"value" ... 读取得到 0 (低电平) 或 1 (高电平)。如果 GPIO 配置为
		输出,这个值允许写操作。任何非零值都以高电平看待。

		如果引脚可以配置为中断信号，且如果已经配置了产生中断的模式
		（见"edge"的描述），你可以对这个文件使用轮询操作(poll(2))，
		且轮询操作会在任何中断触发时返回。如果你使用轮询操作(poll(2))，
		请在 events 中设置 POLLPRI 和 POLLERR。如果你使用轮询操作
		(select(2))，请在 exceptfds 设置你期望的文件描述符。在
		轮询操作(poll(2))返回之后，既可以通过 lseek(2)操作读取
		sysfs 文件的开始部分，也可以关闭这个文件并重新打开它来读取数据。

	"edge" ... 读取得到“none”、“rising”、“falling”或者“both”。
		将这些字符串写入这个文件可以选择沿触发模式，会使得轮询操作
		(select(2))在"value"文件中返回。

		这个文件仅有在这个引脚可以配置为可产生中断输入引脚时，才存在。

	"active_low" ... 读取得到 0 (假) 或 1 (真)。写入任何非零值可以
		翻转这个属性的(读写)值。已存在或之后通过"edge"属性设置了"rising"
		和 "falling" 沿触发模式的轮询操作(poll(2))将会遵循这个设置。

GPIO 控制器的路径类似 /sys/class/gpio/gpiochip42/ (对于从#42 GPIO
开始实现控制的控制器),并有着以下只读属性:

    /sys/class/gpio/gpiochipN/

    	"base" ... 与以上的 N 相同,代表此芯片管理的第一个 GPIO 的编号

    	"label" ... 用于诊断 (并不总是只有唯一值)

    	"ngpio" ... 此控制器所管理的 GPIO 数量(而 GPIO 编号从 N 到
    		N + ngpio - 1)

大多数情况下,电路板的文档应当标明每个 GPIO 的使用目的。但是那些编号并不总是
固定的,例如在扩展卡上的 GPIO会根据所使用的主板或所在堆叠架构中其他的板子而
有所不同。在这种情况下,你可能需要使用 gpiochip 节点(尽可能地结合电路图)来
确定给定信号所用的 GPIO 编号。

从内核代码中导出
----------------

内核代码可以明确地管理那些已通过 gpio_request()申请的 GPIO 的导出::

	/* 导出 GPIO 到用户空间 */
	int gpio_export(unsigned gpio, bool direction_may_change);

	/* gpio_export()的逆操作 */
	void gpio_unexport();

	/* 创建一个 sysfs 连接到已导出的 GPIO 节点 */
	int gpio_export_link(struct device *dev, const char *name,
		unsigned gpio)

在一个内核驱动申请一个 GPIO 之后，它可以通过 gpio_export()使其在 sysfs
接口中可见。该驱动可以控制信号方向是否可修改。这有助于防止用户空间代码无意间
破坏重要的系统状态。

这个明确的导出有助于(通过使某些实验更容易来)调试，也可以提供一个始终存在的接口，
与文档配合作为板级支持包的一部分。

在 GPIO 被导出之后，gpio_export_link()允许在 sysfs 文件系统的任何地方
创建一个到这个 GPIO sysfs 节点的符号链接。这样驱动就可以通过一个描述性的
名字，在 sysfs 中他们所拥有的设备下提供一个(到这个 GPIO sysfs 节点的)接口。

API参考
=======

本节中列出的函数已被废弃。在新的代码中应该使用基于GPIO描述符的API。


code:
.. SPDX-License-Identifier: GPL-2.0

.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/driver-api/index.rst

:翻译:

 司延腾 Yanteng Si <siyanteng@loongson.cn>

:校译:

========================
Linux驱动实现者的API指南
========================

内核提供了各种各样的接口来支持设备驱动的开发。这份文档只是对其中一些接口进行了
一定程度的整理——希望随着时间的推移，它能变得更好！可用的小节可以在下面看到。

.. class:: toc-title

	   目录列表：

.. toctree::
   :maxdepth: 2

   gpio/index
   io_ordering

Todolist:

*   driver-model/index
*   basics
*   infrastructure
*   ioctl
*   early-userspace/index
*   pm/index
*   clk
*   device-io
*   dma-buf
*   device_link
*   component
*   message-based
*   infiniband
*   aperture
*   frame-buffer
*   regulator
*   reset
*   iio/index
*   input
*   usb/index
*   firewire
*   pci/index
*   cxl/index
*   spi
*   i2c
*   ipmb
*   ipmi
*   i3c/index
*   interconnect
*   devfreq
*   hsi
*   edac
*   scsi
*   libata
*   target
*   mailbox
*   mtdnand
*   miscellaneous
*   mei/index
*   mtd/index
*   mmc/index
*   nvdimm/index
*   w1
*   rapidio/index
*   s390-drivers
*   vme
*   80211/index
*   uio-howto
*   firmware/index
*   pin-control
*   md/index
*   media/index
*   misc_devices
*   nfc/index
*   dmaengine/index
*   slimbus
*   soundwire/index
*   thermal/index
*   fpga/index
*   acpi/index
*   auxiliary_bus
*   backlight/lp855x-driver.rst
*   connector
*   console
*   dcdbas
*   eisa
*   isa
*   isapnp
*   io-mapping
*   generic-counter
*   memory-devices/index
*   men-chameleon-bus
*   ntb
*   nvmem
*   parport-lowlevel
*   pps
*   ptp
*   phy/index
*   pwm
*   pldmfw/index
*   rfkill
*   serial/index
*   sm501
*   surface_aggregator/index
*   switchtec
*   sync_file
*   tty/index
*   vfio-mediated-device
*   vfio
*   vfio-pci-device-specific-driver-acceptance
*   xilinx/index
*   xillybus
*   zorro
*   hte/index

.. only::  subproject and html

   Indices
   =======

   * :ref:`genindex`


code:
.. SPDX-License-Identifier: GPL-2.0

.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/driver-api/io_ordering.rst

:翻译:

 林永听 Lin Yongting <linyongting@gmail.com>
 司延腾 Yanteng Si <siyanteng@loongson.cn>

:校译:

===========================
对内存映射地址的I/O写入排序
===========================

在某些平台上，所谓的内存映射I/O是弱顺序。在这些平台上，驱动开发者有责任
保证I/O内存映射地址的写操作按程序图意的顺序达到设备。通常读取一个“安全”
设备寄存器或桥寄存器，触发IO芯片清刷未处理的写操作到达设备后才处理读操作，
而达到保证目的。驱动程序通常在spinlock保护的临界区退出之前使用这种技术。
这也可以保证后面的写操作只在前面的写操作之后到达设备（这非常类似于内存
屏障操作，mb()，不过仅适用于I/O）。

假设一个设备驱动程的具体例子::

                ...
        CPU A:  spin_lock_irqsave(&dev_lock, flags)
        CPU A:  val = readl(my_status);
        CPU A:  ...
        CPU A:  writel(newval, ring_ptr);
        CPU A:  spin_unlock_irqrestore(&dev_lock, flags)
                ...
        CPU B:  spin_lock_irqsave(&dev_lock, flags)
        CPU B:  val = readl(my_status);
        CPU B:  ...
        CPU B:  writel(newval2, ring_ptr);
        CPU B:  spin_unlock_irqrestore(&dev_lock, flags)
                ...

上述例子中，设备可能会先接收到newval2的值，然后接收到newval的值，问题就
发生了。不过很容易通过下面方法来修复::

                ...
        CPU A:  spin_lock_irqsave(&dev_lock, flags)
        CPU A:  val = readl(my_status);
        CPU A:  ...
        CPU A:  writel(newval, ring_ptr);
        CPU A:  (void)readl(safe_register); /* 配置寄存器？*/
        CPU A:  spin_unlock_irqrestore(&dev_lock, flags)
                ...
        CPU B:  spin_lock_irqsave(&dev_lock, flags)
        CPU B:  val = readl(my_status);
        CPU B:  ...
        CPU B:  writel(newval2, ring_ptr);
        CPU B:  (void)readl(safe_register); /* 配置寄存器？*/
        CPU B:  spin_unlock_irqrestore(&dev_lock, flags)

在解决方案中，读取safe_register寄存器，触发IO芯片清刷未处理的写操作，
再处理后面的读操作，防止引发数据不一致问题。


code:
.. SPDX-License-Identifier: GPL-2.0

.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/filesystems/debugfs.rst

=======
Debugfs
=======

译者
::

	中文版维护者： 罗楚成 Chucheng Luo <luochucheng@vivo.com>
	中文版翻译者： 罗楚成 Chucheng Luo <luochucheng@vivo.com>
	中文版校译者:  罗楚成 Chucheng Luo <luochucheng@vivo.com>

版权所有2020 罗楚成 <luochucheng@vivo.com>

Debugfs是内核开发人员在用户空间获取信息的简单方法。与/proc不同，proc只提供进程
信息。也不像sysfs,具有严格的“每个文件一个值“的规则。debugfs根本没有规则,开发
人员可以在这里放置他们想要的任何信息。debugfs文件系统也不能用作稳定的ABI接口。
从理论上讲，debugfs导出文件的时候没有任何约束。但是[1]实际情况并不总是那么
简单。即使是debugfs接口，也最好根据需要进行设计,并尽量保持接口不变。

Debugfs通常使用以下命令安装::

    mount -t debugfs none /sys/kernel/debug

（或等效的/etc/fstab行）。
debugfs根目录默认仅可由root用户访问。要更改对文件树的访问，请使用“ uid”，“ gid”
和“ mode”挂载选项。请注意，debugfs API仅按照GPL协议导出到模块。

使用debugfs的代码应包含<linux/debugfs.h>。然后，首先是创建至少一个目录来保存
一组debugfs文件::

    struct dentry *debugfs_create_dir(const char *name, struct dentry *parent);

如果成功，此调用将在指定的父目录下创建一个名为name的目录。如果parent参数为空，
则会在debugfs根目录中创建。创建目录成功时，返回值是一个指向dentry结构体的指针。
该dentry结构体的指针可用于在目录中创建文件（以及最后将其清理干净）。ERR_PTR
（-ERROR）返回值表明出错。如果返回ERR_PTR（-ENODEV），则表明内核是在没有debugfs
支持的情况下构建的，并且下述函数都不会起作用。

在debugfs目录中创建文件的最通用方法是::

    struct dentry *debugfs_create_file(const char *name, umode_t mode,
				       struct dentry *parent, void *data,
				       const struct file_operations *fops);

在这里，name是要创建的文件的名称，mode描述了访问文件应具有的权限，parent指向
应该保存文件的目录，data将存储在产生的inode结构体的i_private字段中，而fops是
一组文件操作函数，这些函数中实现文件操作的具体行为。至少，read（）和/或
write（）操作应提供；其他可以根据需要包括在内。同样的，返回值将是指向创建文件
的dentry指针，错误时返回ERR_PTR（-ERROR），系统不支持debugfs时返回值为ERR_PTR
（-ENODEV）。创建一个初始大小的文件，可以使用以下函数代替::

    struct dentry *debugfs_create_file_size(const char *name, umode_t mode,
				struct dentry *parent, void *data,
				const struct file_operations *fops,
				loff_t file_size);

file_size是初始文件大小。其他参数跟函数debugfs_create_file的相同。

在许多情况下，没必要自己去创建一组文件操作;对于一些简单的情况,debugfs代码提供
了许多帮助函数。包含单个整数值的文件可以使用以下任何一项创建::

    void debugfs_create_u8(const char *name, umode_t mode,
			   struct dentry *parent, u8 *value);
    void debugfs_create_u16(const char *name, umode_t mode,
			    struct dentry *parent, u16 *value);
    struct dentry *debugfs_create_u32(const char *name, umode_t mode,
				      struct dentry *parent, u32 *value);
    void debugfs_create_u64(const char *name, umode_t mode,
			    struct dentry *parent, u64 *value);

这些文件支持读取和写入给定值。如果某个文件不支持写入，只需根据需要设置mode
参数位。这些文件中的值以十进制表示；如果需要使用十六进制，可以使用以下函数
替代::

    void debugfs_create_x8(const char *name, umode_t mode,
			   struct dentry *parent, u8 *value);
    void debugfs_create_x16(const char *name, umode_t mode,
			    struct dentry *parent, u16 *value);
    void debugfs_create_x32(const char *name, umode_t mode,
			    struct dentry *parent, u32 *value);
    void debugfs_create_x64(const char *name, umode_t mode,
			    struct dentry *parent, u64 *value);

这些功能只有在开发人员知道导出值的大小的时候才有用。某些数据类型在不同的架构上
有不同的宽度，这样会使情况变得有些复杂。在这种特殊情况下可以使用以下函数::

    void debugfs_create_size_t(const char *name, umode_t mode,
			       struct dentry *parent, size_t *value);

不出所料，此函数将创建一个debugfs文件来表示类型为size_t的变量。

同样地，也有导出无符号长整型变量的函数，分别以十进制和十六进制表示如下::

    struct dentry *debugfs_create_ulong(const char *name, umode_t mode,
					struct dentry *parent,
					unsigned long *value);
    void debugfs_create_xul(const char *name, umode_t mode,
			    struct dentry *parent, unsigned long *value);

布尔值可以通过以下方式放置在debugfs中::

    struct dentry *debugfs_create_bool(const char *name, umode_t mode,
				       struct dentry *parent, bool *value);

读取结果文件将产生Y（对于非零值）或N，后跟换行符写入的时候，它只接受大写或小写
值或1或0。任何其他输入将被忽略。

同样，atomic_t类型的值也可以放置在debugfs中::

    void debugfs_create_atomic_t(const char *name, umode_t mode,
				 struct dentry *parent, atomic_t *value)

读取此文件将获得atomic_t值，写入此文件将设置atomic_t值。

另一个选择是通过以下结构体和函数导出一个任意二进制数据块::

    struct debugfs_blob_wrapper {
	void *data;
	unsigned long size;
    };

    struct dentry *debugfs_create_blob(const char *name, umode_t mode,
				       struct dentry *parent,
				       struct debugfs_blob_wrapper *blob);

读取此文件将返回由指针指向debugfs_blob_wrapper结构体的数据。一些驱动使用“blobs”
作为一种返回几行（静态）格式化文本的简单方法。这个函数可用于导出二进制信息，但
似乎在主线中没有任何代码这样做。请注意，使用debugfs_create_blob（）命令创建的
所有文件是只读的。

如果您要转储一个寄存器块（在开发过程中经常会这么做，但是这样的调试代码很少上传
到主线中。Debugfs提供两个函数：一个用于创建仅寄存器文件，另一个把一个寄存器块
插入一个顺序文件中::

    struct debugfs_reg32 {
	char *name;
	unsigned long offset;
    };

    struct debugfs_regset32 {
	struct debugfs_reg32 *regs;
	int nregs;
	void __iomem *base;
    };

    struct dentry *debugfs_create_regset32(const char *name, umode_t mode,
				     struct dentry *parent,
				     struct debugfs_regset32 *regset);

    void debugfs_print_regs32(struct seq_file *s, struct debugfs_reg32 *regs,
			 int nregs, void __iomem *base, char *prefix);

“base”参数可能为0，但您可能需要使用__stringify构建reg32数组，实际上有许多寄存器
名称（宏）是寄存器块在基址上的字节偏移量。

如果要在debugfs中转储u32数组，可以使用以下函数创建文件::

     void debugfs_create_u32_array(const char *name, umode_t mode,
			struct dentry *parent,
			u32 *array, u32 elements);

“array”参数提供数据，而“elements”参数为数组中元素的数量。注意：数组创建后，数组
大小无法更改。

有一个函数来创建与设备相关的seq_file::

   struct dentry *debugfs_create_devm_seqfile(struct device *dev,
				const char *name,
				struct dentry *parent,
				int (*read_fn)(struct seq_file *s,
					void *data));

“dev”参数是与此debugfs文件相关的设备，并且“read_fn”是一个函数指针，这个函数在
打印seq_file内容的时候被回调。

还有一些其他的面向目录的函数::

    struct dentry *debugfs_rename(struct dentry *old_dir,
		                  struct dentry *old_dentry,
		                  struct dentry *new_dir,
				  const char *new_name);

    struct dentry *debugfs_create_symlink(const char *name,
                                          struct dentry *parent,
                                          const char *target);

调用debugfs_rename()将为现有的debugfs文件重命名，可能同时切换目录。 new_name
函数调用之前不能存在；返回值为old_dentry，其中包含更新的信息。可以使用
debugfs_create_symlink（）创建符号链接。

所有debugfs用户必须考虑的一件事是：

debugfs不会自动清除在其中创建的任何目录。如果一个模块在不显式删除debugfs目录的
情况下卸载模块，结果将会遗留很多野指针，从而导致系统不稳定。因此，所有debugfs
用户-至少是那些可以作为模块构建的用户-必须做模块卸载的时候准备删除在此创建的
所有文件和目录。一份文件可以通过以下方式删除::

    void debugfs_remove(struct dentry *dentry);

dentry值可以为NULL或错误值，在这种情况下，不会有任何文件被删除。

很久以前，内核开发者使用debugfs时需要记录他们创建的每个dentry指针，以便最后所有
文件都可以被清理掉。但是，现在debugfs用户能调用以下函数递归清除之前创建的文件::

    void debugfs_remove_recursive(struct dentry *dentry);

如果将对应顶层目录的dentry传递给以上函数，则该目录下的整个层次结构将会被删除。

注释：
[1] http://lwn.net/Articles/309298/


code:
.. SPDX-License-Identifier: GPL-2.0

.. include:: ../disclaimer-zh_CN.rst

:Original: :ref:`Documentation/filesystems/index.rst <filesystems_index>`
:Translator: Wang Wenhu <wenhu.wang@vivo.com>

.. _cn_filesystems_index:

========================
Linux Kernel中的文件系统
========================

这份正在开发的手册或许在未来某个辉煌的日子里以易懂的形式将Linux虚拟\
文件系统（VFS）层以及基于其上的各种文件系统如何工作呈现给大家。当前\
可以看到下面的内容。

文件系统
========

文件系统实现文档。

.. toctree::
   :maxdepth: 2

   virtiofs
   debugfs
   tmpfs


code:
Chinese translated version of Documentation/filesystems/sysfs.rst

If you have any comment or update to the content, please contact the
original document maintainer directly.  However, if you have a problem
communicating in English you can also ask the Chinese maintainer for
help.  Contact the Chinese maintainer if this translation is outdated
or if there is a problem with the translation.

Maintainer: Patrick Mochel	<mochel@osdl.org>
		Mike Murphy <mamurph@cs.clemson.edu>
Chinese maintainer: Fu Wei <tekkamanninja@gmail.com>
---------------------------------------------------------------------
Documentation/filesystems/sysfs.rst 的中文翻译

如果想评论或更新本文的内容，请直接联系原文档的维护者。如果你使用英文
交流有困难的话，也可以向中文版维护者求助。如果本翻译更新不及时或者翻
译存在问题，请联系中文版维护者。
英文版维护者： Patrick Mochel	<mochel@osdl.org>
		Mike Murphy <mamurph@cs.clemson.edu>
中文版维护者： 傅炜 Fu Wei <tekkamanninja@gmail.com>
中文版翻译者： 傅炜 Fu Wei <tekkamanninja@gmail.com>
中文版校译者： 傅炜 Fu Wei <tekkamanninja@gmail.com>

以下为正文
---------------------------------------------------------------------
sysfs - 用于导出内核对象(kobject)的文件系统

Patrick Mochel	<mochel@osdl.org>
Mike Murphy <mamurph@cs.clemson.edu>

修订:    16 August 2011
原始版本:   10 January 2003

sysfs 简介:
~~~~~~~~~~

sysfs 是一个最初基于 ramfs 且位于内存的文件系统。它提供导出内核
数据结构及其属性，以及它们之间的关联到用户空间的方法。

sysfs 始终与 kobject 的底层结构紧密相关。请阅读
Documentation/core-api/kobject.rst 文档以获得更多关于 kobject 接口的
信息。

使用 sysfs
~~~~~~~~~~~

只要内核配置中定义了 CONFIG_SYSFS ，sysfs 总是被编译进内核。你可
通过以下命令挂载它:

    mount -t sysfs sysfs /sys

创建目录
~~~~~~~~

任何 kobject 在系统中注册，就会有一个目录在 sysfs 中被创建。这个
目录是作为该 kobject 的父对象所在目录的子目录创建的，以准确地传递
内核的对象层次到用户空间。sysfs 中的顶层目录代表着内核对象层次的
共同祖先；例如：某些对象属于某个子系统。

Sysfs 在与其目录关联的 kernfs_node 对象中内部保存一个指向实现
目录的 kobject 的指针。以前，这个 kobject 指针被 sysfs 直接用于
kobject 文件打开和关闭的引用计数。而现在的 sysfs 实现中，kobject
引用计数只能通过 sysfs_schedule_callback() 函数直接修改。

属性
~~~~

kobject 的属性可在文件系统中以普通文件的形式导出。Sysfs 为属性定义
了面向文件 I/O 操作的方法，以提供对内核属性的读写。

属性应为 ASCII 码文本文件。以一个文件只存储一个属性值为宜。但一个
文件只包含一个属性值可能影响效率，所以一个包含相同数据类型的属性值
数组也被广泛地接受。

混合类型、表达多行数据以及一些怪异的数据格式会遭到强烈反对。这样做是
很丢脸的,而且其代码会在未通知作者的情况下被重写。

一个简单的属性结构定义如下:

struct attribute {
        char                    * name;
        struct module		*owner;
        umode_t                 mode;
};

int sysfs_create_file(struct kobject * kobj, const struct attribute * attr);
void sysfs_remove_file(struct kobject * kobj, const struct attribute * attr);

一个单独的属性结构并不包含读写其属性值的方法。子系统最好为增删特定
对象类型的属性定义自己的属性结构体和封装函数。

例如:驱动程序模型定义的 device_attribute 结构体如下:

struct device_attribute {
	struct attribute	attr;
	ssize_t (*show)(struct device *dev, struct device_attribute *attr,
			char *buf);
	ssize_t (*store)(struct device *dev, struct device_attribute *attr,
			 const char *buf, size_t count);
};

int device_create_file(struct device *, const struct device_attribute *);
void device_remove_file(struct device *, const struct device_attribute *);

为了定义设备属性，同时定义了一下辅助宏:

#define DEVICE_ATTR(_name, _mode, _show, _store) \
struct device_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store)

例如:声明

static DEVICE_ATTR(foo, S_IWUSR | S_IRUGO, show_foo, store_foo);

等同于如下代码：

static struct device_attribute dev_attr_foo = {
       .attr	= {
		.name = "foo",
		.mode = S_IWUSR | S_IRUGO,
		.show = show_foo,
		.store = store_foo,
	},
};

子系统特有的回调函数
~~~~~~~~~~~~~~~~~~~

当一个子系统定义一个新的属性类型时，必须实现一系列的 sysfs 操作，
以帮助读写调用实现属性所有者的显示和储存方法。

struct sysfs_ops {
        ssize_t (*show)(struct kobject *, struct attribute *, char *);
        ssize_t (*store)(struct kobject *, struct attribute *, const char *, size_t);
};

[子系统应已经定义了一个 struct kobj_type 结构体作为这个类型的
描述符，并在此保存 sysfs_ops 的指针。更多的信息参见 kobject 的
文档]

sysfs 会为这个类型调用适当的方法。当一个文件被读写时，这个方法会
将一般的kobject 和 attribute 结构体指针转换为适当的指针类型后
调用相关联的函数。

示例:

#define to_dev_attr(_attr) container_of(_attr, struct device_attribute, attr)

static ssize_t dev_attr_show(struct kobject *kobj, struct attribute *attr,
                             char *buf)
{
        struct device_attribute *dev_attr = to_dev_attr(attr);
        struct device *dev = kobj_to_dev(kobj);
        ssize_t ret = -EIO;

        if (dev_attr->show)
                ret = dev_attr->show(dev, dev_attr, buf);
        if (ret >= (ssize_t)PAGE_SIZE) {
                printk("dev_attr_show: %pS returned bad count\n",
                                dev_attr->show);
        }
        return ret;
}

读写属性数据
~~~~~~~~~~~~

在声明属性时，必须指定 show() 或 store() 方法，以实现属性的
读或写。这些方法的类型应该和以下的设备属性定义一样简单。

ssize_t (*show)(struct device *dev, struct device_attribute *attr, char *buf);
ssize_t (*store)(struct device *dev, struct device_attribute *attr,
                 const char *buf, size_t count);

也就是说,他们应只以一个处理对象、一个属性和一个缓冲指针作为参数。

sysfs 会分配一个大小为 (PAGE_SIZE) 的缓冲区并传递给这个方法。
Sysfs 将会为每次读写操作调用一次这个方法。这使得这些方法在执行时
会出现以下的行为:

- 在读方面（read(2)），show() 方法应该填充整个缓冲区。回想属性
  应只导出了一个属性值或是一个同类型属性值的数组，所以这个代价将
  不会不太高。

  这使得用户空间可以局部地读和任意的向前搜索整个文件。如果用户空间
  向后搜索到零或使用‘0’偏移执行一个pread(2)操作，show()方法将
  再次被调用，以重新填充缓存。

- 在写方面（write(2)），sysfs 希望在第一次写操作时得到整个缓冲区。
  之后 Sysfs 传递整个缓冲区给 store() 方法。

  当要写 sysfs 文件时，用户空间进程应首先读取整个文件，修该想要
  改变的值，然后回写整个缓冲区。

  在读写属性值时，属性方法的执行应操作相同的缓冲区。

注记:

- 写操作导致的 show() 方法重载，会忽略当前文件位置。

- 缓冲区应总是 PAGE_SIZE 大小。对于i386，这个值为4096。

- show() 方法应该返回写入缓冲区的字节数，也就是 scnprintf()的
  返回值。

- show() 方法在将格式化返回值返回用户空间的时候，禁止使用snprintf()。
  如果可以保证不会发生缓冲区溢出，可以使用sprintf()，否则必须使用
  scnprintf()。

- store() 应返回缓冲区的已用字节数。如果整个缓存都已填满，只需返回
  count 参数。

- show() 或 store() 可以返回错误值。当得到一个非法值，必须返回一个
  错误值。

- 一个传递给方法的对象将会通过 sysfs 调用对象内嵌的引用计数固定在
  内存中。尽管如此，对象代表的物理实体(如设备)可能已不存在。如有必要，
  应该实现一个检测机制。

一个简单的(未经实验证实的)设备属性实现如下：

static ssize_t show_name(struct device *dev, struct device_attribute *attr,
                         char *buf)
{
	return scnprintf(buf, PAGE_SIZE, "%s\n", dev->name);
}

static ssize_t store_name(struct device *dev, struct device_attribute *attr,
                          const char *buf, size_t count)
{
        snprintf(dev->name, sizeof(dev->name), "%.*s",
                 (int)min(count, sizeof(dev->name) - 1), buf);
	return count;
}

static DEVICE_ATTR(name, S_IRUGO, show_name, store_name);

（注意：真正的实现不允许用户空间设置设备名。）

顶层目录布局
~~~~~~~~~~~~

sysfs 目录的安排显示了内核数据结构之间的关系。

顶层 sysfs 目录如下:

block/
bus/
class/
dev/
devices/
firmware/
net/
fs/

devices/ 包含了一个设备树的文件系统表示。他直接映射了内部的内核
设备树，反映了设备的层次结构。

bus/ 包含了内核中各种总线类型的平面目录布局。每个总线目录包含两个
子目录:

	devices/
	drivers/

devices/ 包含了系统中出现的每个设备的符号链接，他们指向 root/ 下的
设备目录。

drivers/ 包含了每个已为特定总线上的设备而挂载的驱动程序的目录(这里
假定驱动没有跨越多个总线类型)。

fs/ 包含了一个为文件系统设立的目录。现在每个想要导出属性的文件系统必须
在 fs/ 下创建自己的层次结构(参见Documentation/filesystems/fuse.rst)。

dev/ 包含两个子目录： char/ 和 block/。在这两个子目录中，有以
<major>:<minor> 格式命名的符号链接。这些符号链接指向 sysfs 目录
中相应的设备。/sys/dev 提供一个通过一个 stat(2) 操作结果，查找
设备 sysfs 接口快捷的方法。

更多有关 driver-model 的特性信息可以在 Documentation/driver-api/driver-model/
中找到。

TODO: 完成这一节。

当前接口
~~~~~~~~

以下的接口层普遍存在于当前的sysfs中:

- 设备 (include/linux/device.h)
----------------------------------
结构体:

struct device_attribute {
	struct attribute	attr;
	ssize_t (*show)(struct device *dev, struct device_attribute *attr,
			char *buf);
	ssize_t (*store)(struct device *dev, struct device_attribute *attr,
			 const char *buf, size_t count);
};

声明:

DEVICE_ATTR(_name, _mode, _show, _store);

增/删属性:

int device_create_file(struct device *dev, const struct device_attribute * attr);
void device_remove_file(struct device *dev, const struct device_attribute * attr);

- 总线驱动程序 (include/linux/device.h)
--------------------------------------
结构体:

struct bus_attribute {
        struct attribute        attr;
        ssize_t (*show)(struct bus_type *, char * buf);
        ssize_t (*store)(struct bus_type *, const char * buf, size_t count);
};

声明:

BUS_ATTR(_name, _mode, _show, _store)

增/删属性:

int bus_create_file(struct bus_type *, struct bus_attribute *);
void bus_remove_file(struct bus_type *, struct bus_attribute *);

- 设备驱动程序 (include/linux/device.h)
-----------------------------------------

结构体:

struct driver_attribute {
        struct attribute        attr;
        ssize_t (*show)(struct device_driver *, char * buf);
        ssize_t (*store)(struct device_driver *, const char * buf,
                         size_t count);
};

声明:

DRIVER_ATTR(_name, _mode, _show, _store)

增/删属性：

int driver_create_file(struct device_driver *, const struct driver_attribute *);
void driver_remove_file(struct device_driver *, const struct driver_attribute *);

文档
~~~~

sysfs 目录结构以及其中包含的属性定义了一个内核与用户空间之间的 ABI。
对于任何 ABI，其自身的稳定和适当的文档是非常重要的。所有新的 sysfs
属性必须在 Documentation/ABI 中有文档。详见 Documentation/ABI/README。


code:
.. SPDX-License-Identifier: GPL-2.0

.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/filesystems/tmpfs.rst

translated by Wang Qing<wangqing@vivo.com>

=====
Tmpfs
=====

Tmpfs是一个将所有文件都保存在虚拟内存中的文件系统。

tmpfs中的所有内容都是临时的，也就是说没有任何文件会在硬盘上创建。
如果卸载tmpfs实例，所有保存在其中的文件都会丢失。

tmpfs将所有文件保存在内核缓存中，随着文件内容增长或缩小可以将不需要的
页面swap出去。它具有最大限制，可以通过“mount -o remount ...”调整。

和ramfs（创建tmpfs的模板）相比，tmpfs包含交换和限制检查。和tmpfs相似的另
一个东西是RAM磁盘（/dev/ram*），可以在物理RAM中模拟固定大小的硬盘，并在
此之上创建一个普通的文件系统。Ramdisks无法swap，因此无法调整它们的大小。

由于tmpfs完全保存于页面缓存和swap中，因此所有tmpfs页面将在/proc/meminfo
中显示为“Shmem”，而在free(1)中显示为“Shared”。请注意，这些计数还包括
共享内存(shmem，请参阅ipcs(1))。获得计数的最可靠方法是使用df(1)和du(1)。

tmpfs具有以下用途：

1) 内核总有一个无法看到的内部挂载，用于共享匿名映射和SYSV共享内存。

   挂载不依赖于CONFIG_TMPFS。如果CONFIG_TMPFS未设置，tmpfs对用户不可见。
   但是内部机制始终存在。

2) glibc 2.2及更高版本期望将tmpfs挂载在/dev/shm上以用于POSIX共享内存
   (shm_open，shm_unlink)。添加内容到/etc/fstab应注意如下：

	tmpfs	/dev/shm	tmpfs	defaults	0 0

   使用时需要记住创建挂载tmpfs的目录。

   SYSV共享内存无需挂载，内部已默认支持。(在2.3内核版本中，必须挂载
   tmpfs的前身(shm fs)才能使用SYSV共享内存)

3) 很多人（包括我）都觉的在/tmp和/var/tmp上挂载非常方便，并具有较大的
   swap分区。目前循环挂载tmpfs可以正常工作，所以大多数发布都应当可以
   使用mkinitrd通过/tmp访问/tmp。

4) 也许还有更多我不知道的地方:-)

tmpfs有三个用于调整大小的挂载选项：

=========  ===========================================================
size       tmpfs实例分配的字节数限制。默认值是不swap时物理RAM的一半。
           如果tmpfs实例过大，机器将死锁，因为OOM处理将无法释放该内存。
nr_blocks  与size相同，但以PAGE_SIZE为单位。
nr_inodes  tmpfs实例的最大inode个数。默认值是物理内存页数的一半，或者
           (有高端内存的机器)低端内存RAM的页数，二者以较低者为准。
=========  ===========================================================

这些参数接受后缀k，m或g表示千，兆和千兆字节，可以在remount时更改。
size参数也接受后缀％用来限制tmpfs实例占用物理RAM的百分比：
未指定size或nr_blocks时，默认值为size=50％

如果nr_blocks=0（或size=0），block个数将不受限制；如果nr_inodes=0，
inode个数将不受限制。这样挂载通常是不明智的，因为它允许任何具有写权限的
用户通过访问tmpfs耗尽机器上的所有内存；但同时这样做也会增强在多个CPU的
场景下的访问。

tmpfs具有为所有文件设置NUMA内存分配策略挂载选项(如果启用了CONFIG_NUMA),
可以通过“mount -o remount ...”调整

======================== =========================
mpol=default             采用进程分配策略
                         (请参阅 set_mempolicy(2))
mpol=prefer:Node         倾向从给定的节点分配
mpol=bind:NodeList       只允许从指定的链表分配
mpol=interleave          倾向于依次从每个节点分配
mpol=interleave:NodeList 依次从每个节点分配
mpol=local               优先本地节点分配内存
======================== =========================

NodeList格式是以逗号分隔的十进制数字表示大小和范围，最大和最小范围是用-
分隔符的十进制数来表示。例如，mpol=bind0-3,5,7,9-15

带有有效NodeList的内存策略将按指定格式保存，在创建文件时使用。当任务在该
文件系统上创建文件时，会使用到挂载时的内存策略NodeList选项，如果设置的话，
由调用任务的cpuset[请参见Documentation/admin-guide/cgroup-v1/cpusets.rst]
以及下面列出的可选标志约束。如果NodeLists为设置为空集，则文件的内存策略将
恢复为“默认”策略。

NUMA内存分配策略有可选标志，可以用于模式结合。在挂载tmpfs时指定这些可选
标志可以在NodeList之前生效。
Documentation/admin-guide/mm/numa_memory_policy.rst列出所有可用的内存
分配策略模式标志及其对内存策略。

::

	=static		相当于	MPOL_F_STATIC_NODES
	=relative	相当于	MPOL_F_RELATIVE_NODES

例如，mpol=bind=staticNodeList相当于MPOL_BIND|MPOL_F_STATIC_NODES的分配策略

请注意，如果内核不支持NUMA，那么使用mpol选项挂载tmpfs将会失败；nodelist指定不
在线的节点也会失败。如果您的系统依赖于此，但内核会运行不带NUMA功能(也许是安全
revocery内核)，或者具有较少的节点在线，建议从自动模式中省略mpol选项挂载选项。
可以在以后通过“mount -o remount,mpol=Policy:NodeList MountPoint”添加到挂载点。

要指定初始根目录，可以使用如下挂载选项：

====	====================
模式	权限用八进制数字表示
uid	用户ID
gid	组ID
====	====================

这些选项对remount没有任何影响。您可以通过chmod(1),chown(1)和chgrp(1)的更改
已经挂载的参数。

tmpfs具有选择32位还是64位inode的挂载选项：

=======   =============
inode64   使用64位inode
inode32   使用32位inode
=======   =============

在32位内核上，默认是inode32，挂载时指定inode64会被拒绝。
在64位内核上，默认配置是CONFIG_TMPFS_INODE64。inode64避免了单个设备上可能有多个
具有相同inode编号的文件；比如32位应用程序使用glibc如果长期访问tmpfs，一旦达到33
位inode编号，就有EOVERFLOW失败的危险，无法打开大于2GiB的文件，并返回EINVAL。

所以'mount -t tmpfs -o size=10G,nr_inodes=10k,mode=700 tmpfs /mytmpfs'将在
/mytmpfs上挂载tmpfs实例，分配只能由root用户访问的10GB RAM/SWAP，可以有10240个
inode的实例。

:作者:
   Christoph Rohland <cr@sap.com>, 1.12.01
:更新:
   Hugh Dickins, 4 June 2007
:更新:
   KOSAKI Motohiro, 16 Mar 2010
:更新:
   Chris Down, 13 July 2020


code:
.. SPDX-License-Identifier: GPL-2.0

.. include:: ../disclaimer-zh_CN.rst

:Original: :ref:`Documentation/filesystems/virtiofs.rst <virtiofs_index>`

译者
::

	中文版维护者： 王文虎 Wang Wenhu <wenhu.wang@vivo.com>
	中文版翻译者： 王文虎 Wang Wenhu <wenhu.wang@vivo.com>
	中文版校译者： 王文虎 Wang Wenhu <wenhu.wang@vivo.com>

===========================================
virtiofs: virtio-fs 主机<->客机共享文件系统
===========================================

- Copyright (C) 2020 Vivo Communication Technology Co. Ltd.

介绍
====
Linux的virtiofs文件系统实现了一个半虚拟化VIRTIO类型“virtio-fs”设备的驱动，通过该\
类型设备实现客机<->主机文件系统共享。它允许客机挂载一个已经导出到主机的目录。

客机通常需要访问主机或者远程系统上的文件。使用场景包括：在新客机安装时让文件对其\
可见；从主机上的根文件系统启动；对无状态或临时客机提供持久存储和在客机之间共享目录。

尽管在某些任务可能通过使用已有的网络文件系统完成，但是却需要非常难以自动化的配置\
步骤，且将存储网络暴露给客机。而virtio-fs设备通过提供不经过网络的文件系统访问文件\
的设计方式解决了这些问题。

另外，virto-fs设备发挥了主客机共存的优点提高了性能，并且提供了网络文件系统所不具备
的一些语义功能。

用法
====
以``myfs``标签将文件系统挂载到``/mnt``:

.. code-block:: sh

  guest# mount -t virtiofs myfs /mnt

请查阅 https://virtio-fs.gitlab.io/ 了解配置QEMU和virtiofsd守护程序的详细信息。

内幕
====
由于virtio-fs设备将FUSE协议用于文件系统请求，因此Linux的virtiofs文件系统与FUSE文\
件系统客户端紧密集成在一起。客机充当FUSE客户端而主机充当FUSE服务器，内核与用户空\
间之间的/dev/fuse接口由virtio-fs设备接口代替。

FUSE请求被置于虚拟队列中由主机处理。主机填充缓冲区中的响应部分，而客机处理请求的完成部分。

将/dev/fuse映射到虚拟队列需要解决/dev/fuse和虚拟队列之间语义上的差异。每次读取\
/dev/fuse设备时，FUSE客户端都可以选择要传输的请求，从而可以使某些请求优先于其他\
请求。虚拟队列有其队列语义，无法更改已入队请求的顺序。在虚拟队列已满的情况下尤
其关键，因为此时不可能加入高优先级的请求。为了解决此差异，virtio-fs设备采用“hiprio”\
（高优先级）虚拟队列，专门用于有别于普通请求的高优先级请求。


code:
.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/iio/iio_configfs.rst

:翻译:

 司延腾 Yanteng Si <siyanteng@loongson.cn>

.. _cn_iio_configfs:

=====================
工业 IIO configfs支持
=====================

1. 概述
=======

Configfs是一种内核对象的基于文件系统的管理系统，IIO使用一些可以通过
configfs轻松配置的对象（例如：设备，触发器）。

关于configfs是如何运行的，请查阅Documentation/filesystems/configfs.rst
了解更多信息。

2. 用法
=======
为了使configfs支持IIO，我们需要在编译时选中config的CONFIG_IIO_CONFIGFS
选项。

然后，挂载configfs文件系统(通常在 /config directory目录下）：：

  $ mkdir/config
  $ mount -t configfs none/config

此时，将创建所有默认IIO组，并可以在/ config / iio下对其进行访问。 下一章
将介绍可用的IIO配置对象。

3. 软件触发器
=============

IIO默认configfs组之一是“触发器”组。挂载configfs后可以自动访问它，并且可
以在/config/iio/triggers下找到。

IIO软件触发器为创建多种触发器类型提供了支持。通常在include/linux/iio
/sw_trigger.h：中的接口下将新的触发器类型实现为单独的内核模块：
::

  /*
   * drivers/iio/trigger/iio-trig-sample.c
   * 一种新触发器类型的内核模块实例
   */
  #include <linux/iio/sw_trigger.h>

  static struct iio_sw_trigger *iio_trig_sample_probe(const char *name)
  {
	/*
	 * 这将分配并注册一个IIO触发器以及其他触发器类型特性的初始化。
	 */
  }

  static int iio_trig_sample_remove(struct iio_sw_trigger *swt)
  {
	/*
	 * 这会废弃iio_trig_sample_probe中的操作
	 */
  }

  static const struct iio_sw_trigger_ops iio_trig_sample_ops = {
	.probe		= iio_trig_sample_probe,
	.remove		= iio_trig_sample_remove,
  };

  static struct iio_sw_trigger_type iio_trig_sample = {
	.name = "trig-sample",
	.owner = THIS_MODULE,
	.ops = &iio_trig_sample_ops,
  };

  module_iio_sw_trigger_driver(iio_trig_sample);

每种触发器类型在/config/iio/triggers下都有其自己的目录。加载iio-trig-sample
模块将创建“trig-sample”触发器类型目录/config/iio/triggers/trig-sample.

我们支持以下中断源（触发器类型）

	* hrtimer,使用高分辨率定时器作为中断源

3.1 Hrtimer触发器创建与销毁
---------------------------

加载iio-trig-hrtimer模块将注册hrtimer触发器类型，从而允许用户在
/config/iio/triggers/hrtimer下创建hrtimer触发器。

例如::

  $ mkdir /config/iio/triggers/hrtimer/instance1
  $ rmdir /config/iio/triggers/hrtimer/instance1

每个触发器可以具有一个或多个独特的触发器类型的属性。

3.2 "hrtimer" 触发器类型属性
----------------------------

"hrtimer”触发器类型没有来自/config dir的任何可配置属性。
它确实引入了触发目录的sampling_frequency属性。
该属性以Hz为单位设置轮询频率，精度为mHz。


code:
.. SPDX-License-Identifier: GPL-2.0

.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/iio/index.rst

:翻译:

 司延腾 Yanteng Si <siyanteng@loongson.cn>

.. _cn_iio_index:

========
工业 I/O
========

.. toctree::
   :maxdepth: 1

   iio_configfs

   ep93xx_adc


code:
.. SPDX-License-Identifier: GPL-2.0

.. raw:: latex

	\renewcommand\thesection*
	\renewcommand\thesubsection*
	\kerneldocCJKon
	\kerneldocBeginSC{

.. _linux_doc_zh:

中文翻译
========

.. note::

   **翻译计划:**
   内核中文文档欢迎任何翻译投稿，特别是关于内核用户和管理员指南部分。

这是中文内核文档树的顶级目录。内核文档，就像内核本身一样，在很大程度上是一
项正在进行的工作；当我们努力将许多分散的文件整合成一个连贯的整体时尤其如此。
另外，随时欢迎您对内核文档进行改进；如果您想提供帮助，请加入vger.kernel.org
上的linux-doc邮件列表。

顺便说下，中文文档也需要遵守内核编码风格，风格中中文和英文的主要不同就是中文
的字符标点占用两个英文字符宽度， 所以，当英文要求不要超过每行100个字符时，
中文就不要超过50个字符。另外，也要注意'-'，'=' 等符号与相关标题的对齐。在将
补丁提交到社区之前，一定要进行必要的 ``checkpatch.pl`` 检查和编译测试。

与Linux 内核社区一起工作
------------------------

与内核开发社区进行协作并将工作推向上游的基本指南。

.. toctree::
   :maxdepth: 1

   process/development-process
   process/submitting-patches
   行为准则 <process/code-of-conduct>
   maintainer/index
   完整开发流程文档 <process/index>

内部API文档
-----------

开发人员使用的内核内部交互接口手册。

.. toctree::
   :maxdepth: 1

   core-api/index
   driver-api/index
   内核中的锁 <locking/index>

TODOList:

* subsystem-apis

开发工具和流程
--------------

为所有内核开发人员提供有用信息的各种其他手册。

.. toctree::
   :maxdepth: 1

   process/license-rules
   doc-guide/index
   dev-tools/index
   dev-tools/testing-overview
   kernel-hacking/index
   rust/index

TODOList:

* trace/index
* fault-injection/index
* livepatch/index

面向用户的文档
--------------

下列手册针对
希望内核在给定系统上以最佳方式工作的*用户*，
和查找内核用户空间API信息的程序开发人员。

.. toctree::
   :maxdepth: 1

   admin-guide/index
   admin-guide/reporting-issues.rst
   userspace-api/index

TODOList:

* 内核构建系统 <kbuild/index>
* 用户空间工具 <tools/index>

也可参考独立于内核文档的 `Linux 手册页 <https://www.kernel.org/doc/man-pages/>`_ 。

固件相关文档
------------

下列文档描述了内核需要的平台固件相关信息。

.. toctree::
   :maxdepth: 2

   devicetree/index

TODOList:

* firmware-guide/index

体系结构文档
------------

.. toctree::
   :maxdepth: 2

   arch

其他文档
--------

有几份未分类的文档似乎不适合放在文档的其他部分，或者可能需要进行一些调整和/或
转换为reStructureText格式，也有可能太旧。

.. toctree::
   :maxdepth: 2

   staging/index

索引和表格
----------

* :ref:`genindex`

.. raw:: latex

	}\kerneldocEndSC


code:
.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/infiniband/core_locking.rst

:翻译:

 司延腾 Yanteng Si <siyanteng@loongson.cn>

:校译:

 王普宇 Puyu Wang <realpuyuwang@gmail.com>
 时奎亮 Alex Shi <alexs@kernel.org>

.. _cn_infiniband_core_locking:

==================
infiniband中间层锁
==================

  本指南试图明确infiniband中间层的锁假设。它描述了对位于中间层以下的低
  级驱动程序和使用中间层的上层协议的要求。

睡眠和中断环境
==============

  除了以下异常情况，ib_device结构体中所有方法的低级驱动实现都可以睡眠。
  这些异常情况是列表中的任意的方法:

    - create_ah
    - modify_ah
    - query_ah
    - destroy_ah
    - post_send
    - post_recv
    - poll_cq
    - req_notify_cq

    他们可能不可以睡眠，而且必须可以从任何上下文中调用。

    向上层协议使用者输出的相应函数:

    - rdma_create_ah
    - rdma_modify_ah
    - rdma_query_ah
    - rdma_destroy_ah
    - ib_post_send
    - ib_post_recv
    - ib_req_notify_cq

    因此，在任何情况下都可以安全调用（它们）。

  此外，该函数

    - ib_dispatch_event

  被底层驱动用来通过中间层调度异步事件的“A”，也可以从任何上下文中安全调
  用。

可重入性
--------

  由低级驱动程序导出的ib_device结构体中的所有方法必须是完全可重入的。
  即使使用同一对象的多个函数调用被同时运行，低级驱动程序也需要执行所有
  必要的同步以保持一致性。

  IB中间层不执行任何函数调用的序列化。

  因为低级驱动程序是可重入的，所以不要求上层协议使用者任何顺序执行。然
  而，为了得到合理的结果，可能需要一些顺序。例如，一个使用者可以在多个
  CPU上同时安全地调用ib_poll_cq()。然而，不同的ib_poll_cq()调用之间
  的工作完成信息的顺序没有被定义。

回调
----

  低级驱动程序不得直接从与ib_device方法调用相同的调用链中执行回调。例
  如，低级驱动程序不允许从post_send方法直接调用使用者的完成事件处理程
  序。相反，低级驱动程序应该推迟这个回调，例如，调度一个tasklet来执行
  这个回调。

  低层驱动负责确保同一CQ的多个完成事件处理程序不被同时调用。驱动程序必
  须保证一个给定的CQ的事件处理程序在同一时间只有一个在运行。换句话说，
  以下情况是不允许的::

          CPU1                                    CPU2

    low-level driver ->
      consumer CQ event callback:
        /* ... */
        ib_req_notify_cq(cq, ...);
                                          low-level driver ->
        /* ... */                           consumer CQ event callback:
                                              /* ... */
        return from CQ event handler

  完成事件和异步事件回调的运行环境没有被定义。 根据低级别的驱动程序，它可能是
  进程上下文、softirq上下文或中断上下文。上层协议使用者可能不会在回调中睡眠。

热插拔
------

  当一个低级驱动程序调用ib_register_device()时，它宣布一个设备已经
  准备好供使用者使用，所有的初始化必须在这个调用之前完成。设备必须保
  持可用，直到驱动对ib_unregister_device()的调用返回。

  低级驱动程序必须从进程上下文调用ib_register_device()和
  ib_unregister_device()。如果使用者在这些调用中回调到驱动程序，它
  不能持有任何可能导致死锁的semaphores。

  一旦其结构体ib_client的add方法被调用，上层协议使用者就可以开始使用
  一个IB设备。使用者必须在从移除方法返回之前完成所有的清理工作并释放
  与设备相关的所有资源。

  使用者被允许在其添加和删除方法中睡眠。


code:
.. SPDX-License-Identifier: GPL-2.0

.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/infiniband/index.rst

:翻译:

 司延腾 Yanteng Si <siyanteng@loongson.cn>

:校译:

 王普宇 Puyu Wang <realpuyuwang@gmail.com>
 时奎亮 Alex Shi <alexs@kernel.org>

.. _cn_infiniband_index:

==========
infiniband
==========

.. toctree::
   :maxdepth: 1

   core_locking
   ipoib
   opa_vnic
   sysfs
   tag_matching
   user_mad
   user_verbs

.. only::  subproject and html

   Indices
   =======

   * :ref:`genindex`


code:
.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/infiniband/ipoib.rst

:翻译:

 司延腾 Yanteng Si <siyanteng@loongson.cn>

:校译:

 王普宇 Puyu Wang <realpuyuwang@gmail.com>
 时奎亮 Alex Shi <alexs@kernel.org>

.. _cn_infiniband_ipoib:

=========================
infiniband上的IP（IPoIB）
=========================

  ib_ipoib驱动是IETF ipoib工作组发布的RFC 4391和4392所规定的
  infiniband上IP协议的一个实现。它是一个“本地”实现，即把接口类型设置为
  ARPHRD_INFINIBAND，硬件地址长度为20（早期的专有实现向内核伪装为以太网
  接口）。

分区和P_Keys
============

  当IPoIB驱动被加载时，它会使用索引为0的P_Key给每个端口创建一个接口。要用
  不同的P_Key创建一个接口，将所需的P_Key写入主接口的
  /sys/class/net/<intf name>/create_child文件里面。比如说::

    echo 0x8001 > /sys/class/net/ib0/create_child

  这将用P_Key 0x8001创建一个名为ib0.8001的接口。要删除一个子接口，使用
  ``delete_child`` 文件::

    echo 0x8001 > /sys/class/net/ib0/delete_child

  任何接口的P_Key都由“pkey”文件给出，而子接口的主接口在“parent”中。

  子接口的创建/删除也可以使用IPoIB的rtnl_link_ops来完成，使用两种
  方式创建的子接口的行为是一样的。

数据报与连接模式
================

  IPoIB驱动支持两种操作模式：数据报和连接。模式是通过接口的
  /sys/class/net/<intf name>/mode文件设置和读取的。

  在数据报模式下，使用IB UD（不可靠数据报）传输，因此接口MTU等于IB L2 MTU
  减去IPoIB封装头（4字节）。例如，在一个典型的具有2K MTU的IB结构中，IPoIB
  MTU将是2048 - 4 = 2044字节。

  在连接模式下，使用IB RC（可靠的连接）传输。连接模式利用IB传输的连接特性，
  允许MTU达到最大的IP包大小64K，这减少了处理大型UDP数据包、TCP段等所需的
  IP包数量，提高了大型信息的性能。

  在连接模式下，接口的UD QP仍被用于组播和与不支持连接模式的对等体的通信。
  在这种情况下，ICMP PMTU数据包的RX仿真被用来使网络堆栈对这些邻居使用较
  小的UD MTU。

无状态卸载
==========

  如果IB HW支持IPoIB无状态卸载，IPoIB会向网络堆栈广播TCP/IP校验和/或大量
  传送（LSO）负载转移能力。

  大量传送（LSO）负载转移也已实现，可以使用ethtool调用打开/关闭。目前，LRO
  只支持具有校验和卸载能力的设备。

  无状态卸载只在数据报模式下支持。

中断管理
========

  如果底层IB设备支持CQ事件管理，可以使用ethtool来设置中断缓解参数，从而减少
  处理中断产生的开销。IPoIB的主要代码路径不使用TX完成信号的事件，所以只支持
  RX管理。

调试信息
========

  通过将CONFIG_INFINIBAND_IPOIB_DEBUG设置为“y”来编译IPoIB驱动，跟踪信
  息被编译到驱动中。通过将模块参数debug_level和mcast_debug_level设置为1来
  打开它们。这些参数可以在运行时通过/sys/module/ib_ipoib/的文件来控制。

  CONFIG_INFINIBAND_IPOIB_DEBUG也启用debugfs虚拟文件系统中的文件。通过挂
  载这个文件系统，例如用::

    mount -t debugfs none /sys/kernel/debug

  可以从/sys/kernel/debug/ipoib/ib0_mcg等文件中获得关于多播组的统计数据。

  这个选项对性能的影响可以忽略不计，所以在正常运行时，在debug_level设置为
  0的情况下启用这个选项是安全的。

  CONFIG_INFINIBAND_IPOIB_DEBUG_DATA当data_debug_level设置为1时，可以
  在数据路径中启用更多的调试输出。 然而，即使禁用输出，启用这个配置选项也
  会影响性能，因为它在快速路径中增加了测试。

引用
====

  在InfiniBand上传输IP（IPoIB）（RFC 4391）。
    http://ietf.org/rfc/rfc4391.txt

  infiniband上的IP:上的IP架构（RFC 4392）。
    http://ietf.org/rfc/rfc4392.txt

  infiniband上的IP: 连接模式 (RFC 4755)
    http://ietf.org/rfc/rfc4755.txt


code:
.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/infiniband/opa_vnic.rst

:翻译:

 司延腾 Yanteng Si <siyanteng@loongson.cn>

:校译:

 王普宇 Puyu Wang <realpuyuwang@gmail.com>
 时奎亮 Alex Shi <alexs@kernel.org>

.. _cn_infiniband_opa_vnic:

=============================================
英特尔全路径（OPA）虚拟网络接口控制器（VNIC）
=============================================

英特尔全路径（OPA）虚拟网络接口控制器（VNIC）功能通过封装HFI节点之间的以
太网数据包，支持Omni-Path结构上的以太网功能。

体系结构
========

Omni-Path封装的以太网数据包的交换模式涉及Omni-Path结构拓扑上覆盖的一个或
多个虚拟以太网交换机。Omni-Path结构上的HFI节点的一个子集被允许在特定的虚
拟以太网交换机上交换封装的以太网数据包。虚拟以太网交换机是通过配置结构上的
HFI节点实现的逻辑抽象，用于生成和处理报头。在最简单的配置中，整个结构的所有
HFI节点通过一个虚拟以太网交换机交换封装的以太网数据包。一个虚拟以太网交换机，
实际上是一个独立的以太网网络。该配置由以太网管理器（EM）执行，它是可信的结
构管理器（FM）应用程序的一部分。HFI节点可以有多个VNIC，每个连接到不同的虚
拟以太网交换机。下图介绍了两个虚拟以太网交换机与两个HFI节点的情况::

                               +-------------------+
                               |      子网/        |
                               |     以太网        |
                               |      管理         |
                               +-------------------+
                                  /          /
                                /           /
                              /            /
                            /             /
  +-----------------------------+  +------------------------------+
  |     虚拟以太网切换          |  |      虚拟以太网切换          |
  |  +---------+    +---------+ |  | +---------+    +---------+   |
  |  | VPORT   |    |  VPORT  | |  | |  VPORT  |    |  VPORT  |   |
  +--+---------+----+---------+-+  +-+---------+----+---------+---+
           |                 \        /                 |
           |                   \    /                   |
           |                     \/                     |
           |                    /  \                    |
           |                  /      \                  |
       +-----------+------------+  +-----------+------------+
       |   VNIC    |    VNIC    |  |    VNIC   |    VNIC    |
       +-----------+------------+  +-----------+------------+
       |          HFI           |  |          HFI           |
       +------------------------+  +------------------------+

Omni-Path封装的以太网数据包格式如下所述。

==================== ================================
位                   域
==================== ================================
Quad Word 0:
0-19                 SLID (低20位)
20-30                长度 (以四字为单位)
31                   BECN 位
32-51                DLID (低20位)
52-56                SC (服务级别)
57-59                RC (路由控制)
60                   FECN 位
61-62                L2 (=10, 16B 格式)
63                   LT (=1, 链路传输头 Flit)

Quad Word 1:
0-7                  L4 type (=0x78 ETHERNET)
8-11                 SLID[23:20]
12-15                DLID[23:20]
16-31                PKEY
32-47                熵
48-63                保留

Quad Word 2:
0-15                 保留
16-31                L4 头
32-63                以太网数据包

Quad Words 3 to N-1:
0-63                 以太网数据包 (pad拓展)

Quad Word N (last):
0-23                 以太网数据包 (pad拓展)
24-55                ICRC
56-61                尾
62-63                LT (=01, 链路传输尾 Flit)
==================== ================================

以太网数据包在传输端被填充，以确保VNIC OPA数据包是四字对齐的。“尾”字段
包含填充的字节数。在接收端，“尾”字段被读取，在将数据包向上传递到网络堆
栈之前，填充物被移除（与ICRC、尾和OPA头一起）。

L4头字段包含VNIC端口所属的虚拟以太网交换机ID。在接收端，该字段用于将收
到的VNIC数据包去多路复用到不同的VNIC端口。

驱动设计
========

英特尔OPA VNIC的软件设计如下图所示。OPA VNIC功能有一个依赖于硬件的部分
和一个独立于硬件的部分。

对IB设备分配和释放RDMA netdev设备的支持已经被加入。RDMA netdev支持与
网络堆栈的对接，从而创建标准的网络接口。OPA_VNIC是一个RDMA netdev设备
类型。

依赖于HW的VNIC功能是HFI1驱动的一部分。它实现了分配和释放OPA_VNIC RDMA
netdev的动作。它涉及VNIC功能的HW资源分配/管理。它与网络堆栈接口并实现所
需的net_device_ops功能。它在传输路径中期待Omni-Path封装的以太网数据包，
并提供对它们的HW访问。在将数据包向上传递到网络堆栈之前，它把Omni-Path头
从接收的数据包中剥离。它还实现了RDMA netdev控制操作。

OPA VNIC模块实现了独立于硬件的VNIC功能。它由两部分组成。VNIC以太网管理
代理（VEMA）作为一个IB客户端向IB核心注册，并与IB MAD栈接口。它与以太网
管理器（EM）和VNIC netdev交换管理信息。VNIC netdev部分分配和释放OPA_VNIC
RDMA netdev设备。它在需要时覆盖由依赖HW的VNIC驱动设置的net_device_ops函数，
以适应任何控制操作。它还处理以太网数据包的封装，在传输路径中使用Omni-Path头。
对于每个VNIC接口，封装所需的信息是由EM通过VEMA MAD接口配置的。它还通过调用
RDMA netdev控制操作将任何控制信息传递给依赖于HW的驱动程序::

        +-------------------+ +----------------------+
        |                   | |       Linux          |
        |     IB MAD        | |       网络           |
        |                   | |       栈             |
        +-------------------+ +----------------------+
                 |               |          |
                 |               |          |
        +----------------------------+      |
        |                            |      |
        |      OPA VNIC 模块         |      |
        |  (OPA VNIC RDMA Netdev     |      |
        |     & EMA 函数)            |      |
        |                            |      |
        +----------------------------+      |
                    |                       |
                    |                       |
           +------------------+             |
           |     IB 核心      |             |
           +------------------+             |
                    |                       |
                    |                       |
        +--------------------------------------------+
        |                                            |
        |      HFI1 驱动和 VNIC 支持                 |
        |                                            |
        +--------------------------------------------+


code:
.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/infiniband/sysfs.rst

:翻译:

 司延腾 Yanteng Si <siyanteng@loongson.cn>

:校译:

 王普宇 Puyu Wang <realpuyuwang@gmail.com>
 时奎亮 Alex Shi <alexs@kernel.org>

.. _cn_infiniband_sysfs:

=========
Sysfs文件
=========

sysfs接口已移至
Documentation/ABI/stable/sysfs-class-infiniband.


code:
.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/infiniband/tag_matching.rst

:翻译:

 司延腾 Yanteng Si <siyanteng@loongson.cn>

:校译:

 王普宇 Puyu Wang <realpuyuwang@gmail.com>
 时奎亮 Alex Shi <alexs@kernel.org>

.. _cn_infiniband_tag_matching:

============
标签匹配逻辑
============

MPI标准定义了一套规则，称为标签匹配，用于将源发送操作与目的接收匹配。以下参数必
须与以下源和目的参数相匹配:

*	沟通者
*	用户标签--通配符(wild card)可由接收方指定
*	来源等级--通配符可由接收方指定
*	目的地等级 – wild

排序规则要求，当一对以上的发送和接收消息信封可能匹配时，包括最早发布-发送和最早
发布-接收的一对是必须用来满足匹配操作的一对。然而，这并不意味着标签是按照它们被
创建的顺序消耗的，例如，如果早期的标签不能用来满足匹配规则，那么后来生成的标签
可能被消耗。

当消息从发送方发送到接收方时，通信库可能试图在相应的匹配接收被发布之后或之前处
理该操作。如果匹配的接收被发布，这就是一个预期的消息，否则就被称为一个意外的消
息。实现时经常为这两种不同的匹配实例使用不同的匹配方案。

为了减少MPI库的内存占用，MPI实现通常使用两种不同的协议来实现这一目的:

1.	Eager协议--当发送方处理完发送时，完整的信息就会被发送。在send_cq中会收到
一个完成发送的通知，通知缓冲区可以被重新使用。

2.	Rendezvous协议--发送方在第一次通知接收方时发送标签匹配头，也许还有一部分
数据。当相应的缓冲区被发布时，响应者将使用头中的信息，直接向匹配的缓冲区发起
RDMA读取操作。为了使缓冲区得到重用，需要收到一个fin消息。

标签匹配的实现
==============

使用的匹配对象有两种类型，即发布的接收列表和意外消息列表。应用程序通过调用发布
的接收列表中的MPI接收例程发布接收缓冲区，并使用MPI发送例程发布发送消息。发布的
接收列表的头部可以由硬件来维护，而软件则要对这个列表进行跟踪。

当发送开始并到达接收端时，如果没有为这个到达的消息预先发布接收，它将被传递给软
件并被放在意外（unexpect）消息列表中。否则，将对该匹配进行处理，包括交会处理，
如果合适的话，将数据传送到指定的接收缓冲区。这允许接收方MPI标签匹配与计算重叠。

当一个接收信息被发布时，通信库将首先检查软件的意外信息列表，以寻找一个匹配的接
收信息。如果找到一个匹配的，数据就会被送到用户缓冲区，使用一个软件控制的协议。
UCX的实现根据数据大小，使用急切或交会协议。如果没有找到匹配，整个预置的接收列
表由硬件维护，并且有空间在这个列表中增加一个预置的接收，这个接收被传递给硬件。
软件要对这个列表进行跟踪，以帮助处理MPI取消操作。此外，由于硬件和软件在标签匹
配操作方面预计不会紧密同步，这个影子列表被用来检测预先发布的接收被传递到硬件的
情况，因为匹配的意外消息正在从硬件传递到软件。


code:
.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/infiniband/user_mad.rst

:翻译:

 司延腾 Yanteng Si <siyanteng@loongson.cn>

:校译:

 王普宇 Puyu Wang <realpuyuwang@gmail.com>
 时奎亮 Alex Shi <alexs@kernel.org>

.. _cn_infiniband_user_mad:

===============
用户空间MAD访问
===============

设备文件
========

  每个InfiniBand设备的每个端口都有一个“umad”设备和一个“issm”设备连接。
  例如，一个双端口的HCA将有两个umad设备和两个issm设备，而一个交换机将
  有每个类型的一个设备（对于交换机端口0）。

创建MAD代理
===========

  一个MAD代理可以通过填写一个结构体ib_user_mad_reg_req来创建，然后在
  适当的设备文件的文件描述符上调用IB_USER_MAD_REGISTER_AGENT ioctl。
  如果注册请求成功，结构体中会返回一个32位的ID。比如说::

	struct ib_user_mad_reg_req req = { /* ... */ };
	ret = ioctl(fd, IB_USER_MAD_REGISTER_AGENT, (char *) &req);
        if (!ret)
		my_agent = req.id;
	else
		perror("agent register");

  代理可以通过IB_USER_MAD_UNREGISTER_AGENT ioctl取消注册。另外，所有
  通过文件描述符注册的代理在描述符关闭时将被取消注册。

  2014
       现在提供了一个新的注册IOctl，允许在注册时提供额外的字段。这个注册
       调用的用户隐含了对pkey_index的使用（见下文）。现在提供了一个新的
       注册IOctl，允许在注册时提供额外的字段。这个注册调用的用户隐含了对
       pkey_index的使用（见下文）。

接收MADs
========

  使用read()接收MAD。现在接收端支持RMPP。传给read()的缓冲区必须至少是
  一个struct ib_user_mad + 256字节。比如说:

  如果传递的缓冲区不足以容纳收到的MAD（RMPP），errno被设置为ENOSPC，需
  要的缓冲区长度被设置在mad.length中。

  正常MAD(非RMPP)的读取示例::

	struct ib_user_mad *mad;
	mad = malloc(sizeof *mad + 256);
	ret = read(fd, mad, sizeof *mad + 256);
	if (ret != sizeof mad + 256) {
		perror("read");
		free(mad);
	}

  RMPP读取示例::

	struct ib_user_mad *mad;
	mad = malloc(sizeof *mad + 256);
	ret = read(fd, mad, sizeof *mad + 256);
	if (ret == -ENOSPC)) {
		length = mad.length;
		free(mad);
		mad = malloc(sizeof *mad + length);
		ret = read(fd, mad, sizeof *mad + length);
	}
	if (ret < 0) {
		perror("read");
		free(mad);
	}

  除了实际的MAD内容外，其他结构体ib_user_mad字段将被填入收到的MAD的信
  息。例如，远程LID将在mad.lid中。

  如果发送超时，将产生一个接收，mad.status设置为ETIMEDOUT。否则，当一个
  MAD被成功接收后，mad.status将是0。

  poll()/select()可以用来等待一个MAD可以被读取。

  poll()/select()可以用来等待，直到可以读取一个MAD。

发送MADs
========

  MADs是用write()发送的。发送的代理ID应该填入MAD的id字段，目的地LID应该
  填入lid字段，以此类推。发送端确实支持RMPP，所以可以发送任意长度的MAD。
  比如说::

	struct ib_user_mad *mad;

	mad = malloc(sizeof *mad + mad_length);

	/* fill in mad->data */

	mad->hdr.id  = my_agent;	/* req.id from agent registration */
	mad->hdr.lid = my_dest;		/* in network byte order... */
	/* etc. */

	ret = write(fd, &mad, sizeof *mad + mad_length);
	if (ret != sizeof *mad + mad_length)
		perror("write");

交换IDs
=======

  umad设备的用户可以在发送的MAD中使用交换ID字段的低32位（也就是网络字节顺序中
  最小有效的一半字段）来匹配请求/响应对。上面的32位是保留给内核使用的，在发送
  MAD之前会被改写。

P_Key索引处理
=============

  旧的ib_umad接口不允许为发送的MAD设置P_Key索引，也没有提供获取接收的MAD的
  P_Key索引的方法。一个带有pkey_index成员的struct ib_user_mad_hdr的新布局已
  经被定义；然而，为了保持与旧的应用程序的二进制兼容性，除非在文件描述符被用于
  其他用途之前调用IB_USER_MAD_ENABLE_PKEY或IB_USER_MAD_REGISTER_AGENT2 ioctl
  之一，否则不会使用这种新布局。

  在2008年9月，IB_USER_MAD_ABI_VERSION将被增加到6，默认使用新的ib_user_mad_hdr
  结构布局，并且IB_USER_MAD_ENABLE_PKEY ioctl将被删除。

设置IsSM功能位
==============

  要为一个端口设置IsSM功能位，只需打开相应的issm设备文件。如果IsSM位已经被设置，那
  么打开调用将阻塞，直到该位被清除（或者如果O_NONBLOCK标志被传递给open()，则立即返
  回，errno设置为EAGAIN）。当issm文件被关闭时，IsSM位将被清除。在issm文件上不能进
  行任何读、写或其他操作。

/dev文件
========

为了用 udev自动创建相应的字符设备文件，一个类似::

    KERNEL=="umad*", NAME="infiniband/%k"
    KERNEL=="issm*", NAME="infiniband/%k"

  的规则可以被使用。它将创建节点的名字::

    /dev/infiniband/umad0
    /dev/infiniband/issm0

  为第一个端口，以此类推。与这些设备相关的infiniband设备和端口可以从以下文件中确定::

    /sys/class/infiniband_mad/umad0/ibdev
    /sys/class/infiniband_mad/umad0/port

  和::

    /sys/class/infiniband_mad/issm0/ibdev
    /sys/class/infiniband_mad/issm0/port


code:
.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/infiniband/user_verbs.rst

:翻译:

 司延腾 Yanteng Si <siyanteng@loongson.cn>

:校译:

 王普宇 Puyu Wang <realpuyuwang@gmail.com>
 时奎亮 Alex Shi <alexs@kernel.org>

.. _cn_infiniband_user_verbs:

=================
用户空间verbs访问
=================

  ib_uverbs模块，通过启用CONFIG_INFINIBAND_USER_VERBS构建，使用户空间
  通过“verbs”直接访问IB硬件，如InfiniBand架构规范第11章所述。

  要使用verbs，需要libibverbs库，可从https://github.com/linux-rdma/rdma-core。
  libibverbs包含一个独立于设备的API，用于使用ib_uverbs接口。libibverbs
  还需要为你的InfiniBand硬件提供适当的独立于设备的内核和用户空间驱动。例如，
  要使用Mellanox HCA，你需要安装ib_mthca内核模块和libmthca用户空间驱动。

用户-内核通信
=============

  用户空间通过/dev/infiniband/uverbsN字符设备与内核进行慢速路径、资源管理
  操作的通信。快速路径操作通常是通过直接写入硬件寄存器mmap()到用户空间来完成
  的，没有系统调用或上下文切换到内核。

  命令是通过在这些设备文件上的write()s发送给内核的。ABI在
  drivers/infiniband/include/ib_user_verbs.h中定义。需要内核响应的命令的结
  构包含一个64位字段，用来传递一个指向输出缓冲区的指针。状态作为write()系统调
  用的返回值被返回到用户空间。

资源管理
========

  由于所有IB资源的创建和销毁都是通过文件描述符传递的命令完成的，所以内核可以跟
  踪那些被附加到给定用户空间上下文的资源。ib_uverbs模块维护着idr表，用来在
  内核指针和不透明的用户空间句柄之间进行转换，这样内核指针就不会暴露给用户空间，
  而用户空间也无法欺骗内核去跟踪一个假的指针。

  这也允许内核在一个进程退出时进行清理，并防止一个进程触及另一个进程的资源。

内存固定
========

  直接的用户空间I/O要求与作为潜在I/O目标的内存区域保持在同一物理地址上。ib_uverbs
  模块通过get_user_pages()和put_page()调用来管理内存区域的固定和解除固定。它还核
  算进程的pinned_vm中被固定的内存量，并检查非特权进程是否超过其RLIMIT_MEMLOCK限制。

  被多次固定的页面在每次被固定时都会被计数，所以pinned_vm的值可能会高估一个进程所
  固定的页面数量。

/dev文件
========

  要想用udev自动创建适当的字符设备文件，可以采用如下规则::

    KERNEL=="uverbs*", NAME="infiniband/%k"

  可以使用。 这将创建设备节点，名为::

    /dev/infiniband/uverbs0

  等等。由于InfiniBand的用户空间verbs对于非特权进程来说应该是安全的，因此在udev规
  则中加入适当的MODE或GROUP可能是有用的。


code:
.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/kernel-hacking/hacking.rst

:译者:

 吴想成 Wu XiangCheng <bobwxc@email.cn>

==============
内核骇客指北
==============

:作者: Rusty Russell

引言
=====

欢迎咱优雅的读者们来阅读Rusty的非常不靠谱的Linux内核骇客（Hacking）指南。本文
描述了内核代码的常见例程和一般要求：其目标是引导有经验的C程序员入门Linux内核
开发。我回避了实现细节：这是代码要做的，也忽略了很多有用的例程。

在你读这篇文章之前，请理解我从来没有想过要写这篇文章，因为我的资历太低了；
但我一直想读这样的文章，自己写是唯一的方法。我希望它能成长为一个最佳实践、
通用起点和其他信息的汇编。

玩家
=======

在任何时候，系统中的每个CPU都可以：

-  与任何进程无关，服务于硬件中断；

-  与任何进程无关，服务于软件中断（softirq）或子任务（tasklet）；

-  运行于内核空间中，与进程（用户上下文）相关联；

-  在用户空间中运行进程。

它们之间有优先级顺序。最下面的两个可以互相抢占，但上面为严格的层次结构：
每个层级只能被上方的抢占。例如，当一个软中断在CPU上运行时，没有其他软中断
会抢占它，但是硬件中断可以抢占它。不过，系统中的任何其他CPU都是独立执行的。

我们将会看到许多方法，用户上下文可以阻止中断，从而成为真正的不可抢占。

用户上下文
------------

用户上下文是指当您从系统调用或其他陷阱进入时：就像用户空间一样，您可以被更
重要的任务和中断抢占。您可以通过调用 :c:func:`schedule()` 进行睡眠。

.. note::

    在模块加载和卸载以及块设备层上的操作时，你始终处于用户上下文中。

在用户上下文中，当前 ``current`` 指针（指示我们当前正在执行的任务）是有效的，
且 :c:func:`in_interrupt()` （ ``include/linux/preempt.h`` ）值为非（false）。

.. warning::

    请注意，如果您禁用了抢占或软中断（见下文），:c:func:`in_interrupt()` 会
    返回假阳性。

硬件中断（Hard IRQs）
----------------------

像定时器、网卡和键盘等都是可能在任意时刻产生中断的真实硬件。内核运行中断
处理程序，为硬件提供服务。内核确保处理程序永远不会重入：如果相同的中断到达，
它将被排队（或丢弃）。因为它会关闭中断，所以处理程序必须很快：通常它只是
确认中断，标记一个“软件中断”以执行并退出。

您可以通过 in_hardirq() 返回真来判断您处于硬件中断状态。

.. warning::

    请注意，如果中断被禁用，这将返回假阳性（见下文）。

软件中断上下文：软中断（Softirqs）与子任务（Tasklets）
-------------------------------------------------------

当系统调用即将返回用户空间或硬件中断处理程序退出时，任何标记为挂起（通常通
过硬件中断）的“软件中断”将运行（ ``kernel/softirq.c`` ）。

此处完成了许多真正的中断处理工作。在向SMP过渡的早期，只有“bottom halves下半
部”（BHs）机制，无法利用多个CPU的优势。在从那些一团糟的旧电脑切换过来后不久，
我们放弃了这个限制，转而使用“软中断”。

``include/linux/interrupt.h`` 列出了不同的软中断。定时器软中断是一个非常重要
的软中断（ ``include/linux/timer.h`` ）：您可以注册它以在给定时间后为您调用
函数。

软中断通常是一个很难处理的问题，因为同一个软中断将同时在多个CPU上运行。因此，
子任务（ ``include/linux/interrupt.h`` ）更常用：它们是动态可注册的（意味着
您可以拥有任意数量），并且它们还保证任何子任务都只能在一个CPU上运行，不同的
子任务也可以同时运行。

.. warning::

    “tasklet”这个名字是误导性的：它们与“任务”无关。

你可以使用 :c:func:`in_softirq()` 宏（ ``include/linux/preempt.h`` ）来确认
是否处于软中断（或子任务）中。

.. warning::

    注意，如果持有 :ref:`bottom half lock <local_bh_disable_zh>` 锁，这将返回
    假阳性。

一些基本规则
================

缺少内存保护
    如果你损坏了内存，无论是在用户上下文还是中断上下文中，整个机器都会崩溃。
    你确定你不能在用户空间里做你想做的事吗？

缺少浮点或MMX
    FPU上下文不会被保存；即使在用户上下文中，FPU状态也可能与当前进程不一致：
    您会弄乱某些用户进程的FPU状态。如果真的要这样做，就必须显式地保存/恢复
    完整的FPU状态（并避免上下文切换）。这通常不是个好主意；请优先用定点算法。

严格的堆栈限制
    对于大多数32位体系结构，根据配置选项的不同内核堆栈大约为3K到6K；对于大
    多数64位机器，内核堆栈大约为14K，并且经常与中断共享，因此你无法使用全部。
    应避免深度递归和栈上的巨型本地数组（用动态分配它们来代替）。

Linux内核是可移植的
    就这样吧。您的代码应该是纯64位的，并且不依赖于字节序（endian）。您还应该
    尽量减少CPU特定的东西，例如内联汇编（inline assembly）应该被干净地封装和
    最小化以便于移植。一般来说，它应该局限于内核树中有体系结构依赖的部分。

输入输出控制（ioctls）：避免编写新的系统调用
==============================================

系统调用（system call）通常看起来像这样::

    asmlinkage long sys_mycall(int arg)
    {
            return 0;
    }

首先，在大多数情况下，您无需创建新的系统调用。创建一个字符设备并为其实现适当
的输入输出控制（ioctls）。这比系统调用灵活得多，不必写进每个体系结构的
``include/asm/unistd.h`` 和 ``arch/kernel/entry.S`` 文件里，而且更容易被Linus
接受。

如果您的程序所做的只是读取或写入一些参数，请考虑实现 :c:func:`sysfs()` 接口。

在输入输出控制中，您处于进程的用户上下文。出现错误时，返回一个负的错误参数
（errno，请参阅 ``include/uapi/asm-generic/errno-base.h`` 、
``include/uapi/asm-generic/errno.h`` 和 ``include/linux/errno.h`` ），否则返
回0。

在睡眠之后，您应该检查是否出现了信号：Unix/Linux处理信号的方法是暂时退出系统
调用，并返回 ``-ERESTARTSYS`` 错误。系统调用入口代码将切换回用户上下文，处理
信号处理程序，然后系统调用将重新启动（除非用户禁用了该功能）。因此，您应该准
备好处理重新启动，例如若您处理某些数据结构到一半。

::

    if (signal_pending(current))
            return -ERESTARTSYS;

如果你要做更长时间的计算：优先考虑用户空间。如果你真的想在内核中做这件事，你
应该定期检查你是否需要让出CPU（请记得每个CPU都有协作多任务）。
习惯用法::

    cond_resched(); /* Will sleep */

接口设计的小注释：UNIX系统调用的格言是“提供机制而不是策略
Provide mechanism not policy”。

死锁的“配方”
====================

您不能调用任何可能睡眠的程序，除非：

- 您处于用户上下文中。

- 你未拥有任何自旋锁。

- 您已经启用中断（实际上，Andi Kleen说调度代码将为您启用它们，但这可能不是
  您想要的）。

注意，有些函数可能隐式地睡眠：常见的是用户空间访问函数（\*_user）和没有
``GFP_ATOMIC`` 的内存分配函数。

您应该始终打开  ``CONFIG_DEBUG_ATOMIC_SLEEP``  项来编译内核，如果您违反这些
规则，它将警告您。如果你 **真的** 违反了规则，你最终会锁住你的电脑。

真的会这样。

常用函数/程序
===============

:c:func:`printk()`
------------------

定义于 ``include/linux/printk.h``

:c:func:`printk()` 将内核消息提供给控制台、dmesg和syslog守护进程。它对于调
试和报告错误很有用，并且可以在中断上下文中使用，但是使用时要小心：如果机器
的控制台中充斥着printk消息则会无法使用。它使用与ANSI C printf基本兼容的格式
字符串，并通过C字符串串联为其提供第一个“优先”参数::

    printk(KERN_INFO "i = %u\n", i);

参见 ``include/linux/kern_levels.h`` ；了解其他 ``KERN_`` 值；syslog将这些值
解释为级别。特殊用法：打印IP地址使用::

    __be32 ipaddress;
    printk(KERN_INFO "my ip: %pI4\n", &ipaddress);

:c:func:`printk()` 内部使用的1K缓冲区，不捕获溢出。请确保足够使用。

.. note::

    当您开始在用户程序中将printf打成printk时，就知道自己是真正的内核程序员了
    :)

.. note::

    另一个注释：最初的unix第六版源代码在其printf函数的顶部有一个注释：“printf
    不应该用于叽叽喳喳”。你也应该遵循此建议。

:c:func:`copy_to_user()` / :c:func:`copy_from_user()` / :c:func:`get_user()` / :c:func:`put_user()`
---------------------------------------------------------------------------------------------------

定义于 ``include/linux/uaccess.h`` / ``asm/uaccess.h``

**[睡眠]**

:c:func:`put_user()` 和 :c:func:`get_user()` 用于从用户空间中获取和向用户空
间中传出单个值（如int、char或long）。指向用户空间的指针永远不应该直接取消
引用：应该使用这些程序复制数据。两者都返回 ``-EFAULT`` 或 0。

:c:func:`copy_to_user()` 和 :c:func:`copy_from_user()` 更通用：它们从/向用户
空间复制任意数量的数据。

.. warning::

    与 :c:func:`put_user()` 和 :c:func:`get_user()` 不同，它们返回未复制的
    数据量（即0仍然意味着成功）。

【是的，这个讨厌的接口真心让我尴尬。火爆的口水仗大概每年都会发生。
—— Rusty Russell】

这些函数可以隐式睡眠。它不应该在用户上下文之外调用（没有意义）、调用时禁用中断
或获得自旋锁。

:c:func:`kmalloc()`/:c:func:`kfree()`
-------------------------------------

定义于 ``include/linux/slab.h``

**[可能睡眠：见下]**

这些函数用于动态请求指针对齐的内存块，类似用户空间中的malloc和free，但
:c:func:`kmalloc()` 需要额外的标志词。重要的值：

``GFP_KERNEL``
    可以睡眠和交换以释放内存。只允许在用户上下文中使用，但这是分配内存最可靠
    的方法。

``GFP_ATOMIC``
    不会睡眠。较 ``GFP_KERNEL`` 更不可靠，但可以从中断上下文调用。你 **应该**
    有一个很好的内存不足错误处理策略。

``GFP_DMA``
    分配低于16MB的ISA DMA。如果你不知道那是什么，那你就不需要了。非常不可靠。

如果您看到一个从无效上下文警告消息调用的睡眠的函数，那么您可能在没有
``GFP_ATOMIC`` 的情况下从中断上下文调用了一个睡眠的分配函数。你必须立即修复，
快点！

如果你要分配至少 ``PAGE_SIZE`` （ ``asm/page.h`` 或 ``asm/page_types.h`` ）
字节，请考虑使用 :c:func:`__get_free_pages()` （ ``include/linux/gfp.h`` ）。
它采用顺序参数（0表示页面大小，1表示双页，2表示四页……）和与上述相同的内存
优先级标志字。

如果分配的字节数超过一页，可以使用 :c:func:`vmalloc()` 。它将在内核映射中分
配虚拟内存。此块在物理内存中不是连续的，但是MMU（内存管理单元）使它看起来像
是为您准备好的连续空间（因此它只是看起来对cpu连续，对外部设备驱动程序则不然）。
如果您真的需要为一些奇怪的设备提供大量物理上连续的内存，那么您就会遇到问题：
Linux对此支持很差，因为正在运行的内核中的内存碎片化会使它变得很困难。最好的
方法是在引导过程的早期通过 :c:func:`alloc_bootmem()` 函数分配。

在创建自己的常用对象缓存之前，请考虑使用 ``include/linux/slab.h`` 中的slab
缓存。

:c:macro:`current`
------------------

定义于 ``include/asm/current.h``

此全局变量（其实是宏）包含指向当前任务结构（task structure）的指针，因此仅在
用户上下文中有效。例如，当进程进行系统调用时，这将指向调用进程的任务结构。
在中断上下文中不为空（**not NULL**）。

:c:func:`mdelay()`/:c:func:`udelay()`
-------------------------------------

定义于 ``include/asm/delay.h`` / ``include/linux/delay.h``

:c:func:`udelay()` 和 :c:func:`ndelay()` 函数可被用于小暂停。不要对它们使用
大的值，因为这样会导致溢出——帮助函数 :c:func:`mdelay()` 在这里很有用，或者
考虑 :c:func:`msleep()`。

:c:func:`cpu_to_be32()`/:c:func:`be32_to_cpu()`/:c:func:`cpu_to_le32()`/:c:func:`le32_to_cpu()`
-----------------------------------------------------------------------------------------------

定义于 ``include/asm/byteorder.h``

:c:func:`cpu_to_be32()` 系列函数（其中“32”可以替换为64或16，“be”可以替换为
“le”）是在内核中进行字节序转换的常用方法：它们返回转换后的值。所有的变体也
提供反向转换函数：
:c:func:`be32_to_cpu()` 等。

这些函数有两个主要的变体：指针变体，例如 :c:func:`cpu_to_be32p()` ，它获取
指向给定类型的指针，并返回转换后的值。另一个变体是“in-situ”系列，例如
:c:func:`cpu_to_be32s()` ，它转换指针引用的值，并返回void。

:c:func:`local_irq_save()`/:c:func:`local_irq_restore()`
--------------------------------------------------------

定义于 ``include/linux/irqflags.h``

这些程序禁用本地CPU上的硬中断，并还原它们。它们是可重入的；在其一个
``unsigned long flags`` 参数中保存以前的状态。如果您知道中断已启用，那么可
直接使用 :c:func:`local_irq_disable()` 和 :c:func:`local_irq_enable()`。

.. _local_bh_disable_zh:

:c:func:`local_bh_disable()`/:c:func:`local_bh_enable()`
--------------------------------------------------------

定义于 ``include/linux/bottom_half.h``

这些程序禁用本地CPU上的软中断，并还原它们。它们是可重入的；如果之前禁用了
软中断，那么在调用这对函数之后仍然会禁用它们。它们阻止软中断和子任务在当前
CPU上运行。

:c:func:`smp_processor_id()`
----------------------------

定义于 ``include/linux/smp.h``

:c:func:`get_cpu()` 禁用抢占（这样您就不会突然移动到另一个cpu）并返回当前
处理器号，介于0和 ``NR_CPUS`` 之间。请注意，CPU编号不一定是连续的。完成后，
使用 :c:func:`put_cpu()` 再次返回。

如果您知道您不能被另一个任务抢占（即您处于中断上下文中，或已禁用抢占），您
可以使用 :c:func:`smp_processor_id()`。

``__init``/``__exit``/``__initdata``
------------------------------------

定义于  ``include/linux/init.h``

引导之后，内核释放一个特殊的部分；用 ``__init`` 标记的函数和用 ``__initdata``
标记的数据结构在引导完成后被丢弃：同样地，模块在初始化后丢弃此内存。
``__exit`` 用于声明只在退出时需要的函数：如果此文件未编译为模块，则该函数将
被删除。请参阅头文件以使用。请注意，使用 :c:func:`EXPORT_SYMBOL()` 或
:c:func:`EXPORT_SYMBOL_GPL()` 将标记为 ``__init`` 的函数导出到模块是没有意义
的——这将出问题。

:c:func:`__initcall()`/:c:func:`module_init()`
----------------------------------------------

定义于  ``include/linux/init.h`` / ``include/linux/module.h``

内核的许多部分都作为模块（内核的可动态加载部分）良好服务。使用
:c:func:`module_init()` 和 :c:func:`module_exit()` 宏可以简化代码编写，无需
``#ifdef`` ，即可以作为模块运行或内置在内核中。

:c:func:`module_init()` 宏定义在模块插入时（如果文件编译为模块）或在引导时
调用哪个函数：如果文件未编译为模块，:c:func:`module_init()` 宏将等效于
:c:func:`__initcall()` ，它通过链接器的魔力确保在引导时调用该函数。

该函数可以返回一个错误值，以导致模块加载失败（不幸的是，如果将模块编译到内核
中，则此操作无效）。此函数在启用中断的用户上下文中调用，因此可以睡眠。

:c:func:`module_exit()`
-----------------------

定义于  ``include/linux/module.h``

这个宏定义了在模块删除时要调用的函数（如果是编译到内核中的文件，则无用武之地）。
只有在模块使用计数到零时才会调用它。这个函数也可以睡眠，但不能失败：当它返回
时，所有的东西都必须清理干净。

注意，这个宏是可选的：如果它不存在，您的模块将不可移除（除非 ``rmmod -f`` ）。

:c:func:`try_module_get()`/:c:func:`module_put()`
-------------------------------------------------

定义于 ``include/linux/module.h``

这些函数会操作模块使用计数，以防止删除（如果另一个模块使用其导出的符号之一，
则无法删除模块，参见下文）。在调用模块代码之前，您应该在该模块上调用
:c:func:`try_module_get()` ：若失败，那么该模块将被删除，您应该将其视为不存在。
若成功，您就可以安全地进入模块，并在完成后调用模块 :c:func:`module_put()` 。

大多数可注册结构体都有所有者字段，例如在
:c:type:`struct file_operations <file_operations>` 结构体中，此字段应设置为
宏 ``THIS_MODULE`` 。

等待队列 ``include/linux/wait.h``
====================================

**[睡眠]**

等待队列用于等待某程序在条件为真时唤醒另一程序。必须小心使用，以确保没有竞争
条件。先声明一个 :c:type:`wait_queue_head_t` ，然后对希望等待该条件的进程声明
一个关于它们自己的 :c:type:`wait_queue_entry_t` ，并将其放入队列中。

声明
-----

使用 :c:func:`DECLARE_WAIT_QUEUE_HEAD()` 宏声明一个 ``wait_queue_head_t`` ，
或者在初始化代码中使用 :c:func:`init_waitqueue_head()` 程序。

排队
-----

将自己放在等待队列中相当复杂，因为你必须在检查条件之前将自己放入队列中。有一
个宏可以来执行此操作： :c:func:`wait_event_interruptible()`
（ ``include/linux/wait.h`` ）第一个参数是等待队列头，第二个参数是计算的表达
式；当该表达式为true时宏返回0，或者在接收到信号时返回 ``-ERESTARTSYS`` 。
:c:func:`wait_event()` 版本会忽略信号。

唤醒排队任务
-------------

调用 :c:func:`wake_up()` （ ``include/linux/wait.h`` ），它将唤醒队列中的所有
进程。例外情况：如果有一个进程设置了 ``TASK_EXCLUSIVE`` ，队列的其余部分将不
会被唤醒。这个基本函数的其他变体也可以在同一个头文件中使用。

原子操作
=========

某些操作在所有平台上都有保证。第一类为操作 :c:type:`atomic_t`
（ ``include/asm/atomic.h`` ）的函数；它包含一个有符号整数（至少32位长），
您必须使用这些函数来操作或读取 :c:type:`atomic_t` 变量。
:c:func:`atomic_read()` 和 :c:func:`atomic_set()` 获取并设置计数器，还有
:c:func:`atomic_add()` ，:c:func:`atomic_sub()` ，:c:func:`atomic_inc()` ，
:c:func:`atomic_dec()` 和 :c:func:`atomic_dec_and_test()` （如果递减为零，
则返回true）。

是的。它在原子变量为零时返回true（即!=0）。

请注意，这些函数比普通的算术运算速度慢，因此不应过度使用。

第二类原子操作是在 ``unsigned long`` （ ``include/linux/bitops.h`` ）上的
原子位操作。这些操作通常采用指向位模式（bit pattern）的指针，第0位是最低有效
位。:c:func:`set_bit()`，:c:func:`clear_bit()` 和 :c:func:`change_bit()` 设置、
清除和更改给定位。:c:func:`test_and_set_bit()` ，:c:func:`test_and_clear_bit()`
和 :c:func:`test_and_change_bit()` 执行相同的操作，但如果之前设置了位，则返回
true；这些对于原子设置标志特别有用。

可以使用大于 ``BITS_PER_LONG`` 位的位索引调用这些操作。但结果在大端序平台上
不太正常，所以最好不要这样做。

符号
=====

在内核内部，正常的链接规则仍然适用（即除非用static关键字将符号声明为文件范围，
否则它可以在内核中的任何位置使用）。但是对于模块，会保留一个特殊可导出符号表，
该表将入口点限制为内核内部。模块也可以导出符号。

:c:func:`EXPORT_SYMBOL()`
-------------------------

定义于 ``include/linux/export.h``

这是导出符号的经典方法：动态加载的模块将能够正常使用符号。

:c:func:`EXPORT_SYMBOL_GPL()`
-----------------------------

定义于 ``include/linux/export.h``

类似于 :c:func:`EXPORT_SYMBOL()`，只是 :c:func:`EXPORT_SYMBOL_GPL()` 导出的
符号只能由具有由 :c:func:`MODULE_LICENSE()` 指定GPL兼容许可证的模块看到。这
意味着此函数被认为是一个内部实现问题，而不是一个真正的接口。一些维护人员和
开发人员在添加一些新的API或功能时可能却需要导出 EXPORT_SYMBOL_GPL()。

:c:func:`EXPORT_SYMBOL_NS()`
----------------------------

定义于 ``include/linux/export.h``

这是 ``EXPORT_SYMBOL()`` 的变体，允许指定符号命名空间。符号名称空间记录于
Documentation/core-api/symbol-namespaces.rst 。

:c:func:`EXPORT_SYMBOL_NS_GPL()`
--------------------------------

定义于 ``include/linux/export.h``

这是 ``EXPORT_SYMBOL_GPL()`` 的变体，允许指定符号命名空间。符号名称空间记录于
Documentation/core-api/symbol-namespaces.rst 。

程序与惯例
===========

双向链表 ``include/linux/list.h``
-----------------------------------

内核头文件中曾经有三组链表程序，但这一组是赢家。如果你对一个单链表没有特别迫切的
需求，那么这是一个不错的选择。

通常 :c:func:`list_for_each_entry()` 很有用。

返回值惯例
------------

对于在用户上下文中调用的代码，违背C语言惯例是很常见的，即返回0表示成功，返回
负错误值（例如 ``-EFAULT`` ）表示失败。这在一开始可能是不直观的，但在内核中
相当普遍。

使用 :c:func:`ERR_PTR()` （ ``include/linux/err.h`` ）将负错误值编码到指针中，
然后使用 :c:func:`IS_ERR()` 和 :c:func:`PTR_ERR()` 将其再取出：避免为错误值
使用单独的指针参数。挺讨厌的，但的确是个好方式。

破坏编译
----------

Linus和其他开发人员有时会更改开发内核中的函数或结构体名称；这样做不仅是为了
让每个人都保持警惕，还反映了一个重大的更改（例如，不能再在打开中断的情况下
调用，或者执行额外的检查，或者不执行以前捕获的检查）。通常这会附带发送一个
相当全面的注释到相应的内核邮件列表中；请搜索存档以查看。简单地对文件进行全局
替换通常只会让事情变得 **更糟** 。

初始化结构体成员
------------------

初始化结构体的首选方法是使用指定的初始化器，如ISO C99所述。
例如::

    static struct block_device_operations opt_fops = {
            .open               = opt_open,
            .release            = opt_release,
            .ioctl              = opt_ioctl,
            .check_media_change = opt_media_change,
    };

这使得很容易查找（grep），并且可以清楚地看到设置了哪些结构字段。你应该这样做，
因为它看起来很酷。

GNU 扩展
----------

Linux内核中明确允许GNU扩展。请注意，由于缺乏通用性，一些更复杂的版本并没有
得到很好的支持，但以下内容被认为是标准的（有关更多详细信息，请参阅GCC info页
的“C 扩展”部分——是的，实际上是info页，手册页只是info中内容的简短摘要）。

- 内联函数

- 语句表达式（Statement expressions）（即（{ 和 }）结构）。

- 声明函数/变量/类型的属性（__attribute__）

- typeof

- 零长度数组

- 宏变量

- 空指针运算

- 非常量（Non-Constant）初始化程序

- 汇编程序指令（在 arch/ 和 include/asm/ 之内）

- 字符串函数名（__func__）。

- __builtin_constant_p()

在内核中使用long long时要小心，gcc为其生成的代码非常糟糕：除法和乘法在i386上
不能工作，因为内核环境中缺少用于它的gcc运行时函数。

C++
---

在内核中使用C++通常是个坏主意，因为内核不提供必要的运行时环境，并且不为其
测试包含文件。不过这仍然是可能的，但不建议。如果你真的想这么做，至少别用
异常处理（exceptions）。

#if
---

通常认为，在头文件（或.c文件顶部）中使用宏来抽象函数比在源代码中使用“if”预
处理器语句更干净。

把你的东西放进内核里
======================

为了让你的东西更正式、补丁更整洁，还有一些工作要做：

-  搞清楚你修改的代码属于谁。查看源文件的根目录、 ``MAINTAINERS`` 文件以及
   ``CREDITS`` 文件的最后一部分。你应该和此人协调，确保你没有重新发明轮子，
   或者尝试一些已经被拒绝的东西。

   确保你把你的名字和电子邮件地址放在你创建或修改的任何文件的顶部。当人们发
   现一个缺陷，或者想要做出修改时，这是他们首先会看的地方。

-  通常你需要一个配置选项来支持你的内核编程。在适当的目录中编辑 ``Kconfig`` 。
   配置语言很容易通过剪切和粘贴来使用，在
   Documentation/kbuild/kconfig-language.rst 中有完整的文档。

   在您对选项的描述中，请确保同时照顾到了专家用户和对此功能一无所知的用户。
   在此说明任何不兼容和问题。结尾一定要写上“如有疑问，就选N”（或者是“Y”）；
   这是针对那些看不懂你在说什么的人的。

-  编辑 ``Makefile`` ：配置变量在这里导出，因此通常你只需添加一行
   “obj-$(CONFIG_xxx) += xxx.o”。语法记录在
   Documentation/kbuild/makefiles.rst 。

-  如果你认为自己做了一些有意义的事情，可以把自己放进 ``CREDITS`` ，通常不
   止一个文件（无论如何你的名字都应该在源文件的顶部）。  ``MAINTAINERS``
   意味着您希望在对子系统进行更改时得到询问，并了解缺陷；这意味着对某部分
   代码做出更多承诺。

-  最后，别忘记去阅读 Documentation/process/submitting-patches.rst。

Kernel 仙女棒
===============

浏览源代码时的一些收藏。请随意添加到此列表。

``arch/x86/include/asm/delay.h``::

    #define ndelay(n) (__builtin_constant_p(n) ? \
            ((n) > 20000 ? __bad_ndelay() : __const_udelay((n) * 5ul)) : \
            __ndelay(n))

``include/linux/fs.h``::

    /*
     * Kernel pointers have redundant information, so we can use a
     * scheme where we can return either an error code or a dentry
     * pointer with the same return value.
     *
     * This should be a per-architecture thing, to allow different
     * error and pointer decisions.
     */
     #define ERR_PTR(err)    ((void *)((long)(err)))
     #define PTR_ERR(ptr)    ((long)(ptr))
     #define IS_ERR(ptr)     ((unsigned long)(ptr) > (unsigned long)(-1000))

``arch/x86/include/asm/uaccess_32.h:``::

    #define copy_to_user(to,from,n)                         \
            (__builtin_constant_p(n) ?                      \
             __constant_copy_to_user((to),(from),(n)) :     \
             __generic_copy_to_user((to),(from),(n)))

``arch/sparc/kernel/head.S:``::

    /*
     * Sun people can't spell worth damn. "compatibility" indeed.
     * At least we *know* we can't spell, and use a spell-checker.
     */

    /* Uh, actually Linus it is I who cannot spell. Too much murky
     * Sparc assembly will do this to ya.
     */
    C_LABEL(cputypvar):
            .asciz "compatibility"

    /* Tested on SS-5, SS-10. Probably someone at Sun applied a spell-checker. */
            .align 4
    C_LABEL(cputypvar_sun4m):
            .asciz "compatible"

``arch/sparc/lib/checksum.S:``::

            /* Sun, you just can't beat me, you just can't.  Stop trying,
             * give up.  I'm serious, I am going to kick the living shit
             * out of you, game over, lights out.
             */

致谢
=====

感谢Andi Kleen提出点子，回答我的问题，纠正我的错误，充实内容等帮助。
感谢Philipp Rumpf做了许多拼写和清晰度修复，以及一些优秀的不明显的点。
感谢Werner Almesberger对 :c:func:`disable_irq()` 做了一个很好的总结，
Jes Sorensen和Andrea Arcangeli补充了一些注意事项。
感谢Michael Elizabeth Chastain检查并补充了配置部分。
感谢Telsa Gwynne教我DocBook。


code:
.. _kernel_hacking_zh:

.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/kernel-hacking/index.rst

:译者:

 吴想成 Wu XiangCheng <bobwxc@email.cn>

=============
内核骇客指南
=============

.. toctree::
   :maxdepth: 2

   hacking

TODO

- locking


code:
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/locking/index.rst

:翻译:

  唐艺舟 Tang Yizhou <tangyeechou@gmail.com>

==
锁
==

.. toctree::
    :maxdepth: 1

    mutex-design
    spinlocks

TODOList:

    * locktypes
    * lockdep-design
    * lockstat
    * locktorture
    * rt-mutex-design
    * rt-mutex
    * seqlock
    * ww-mutex-design
    * preempt-locking
    * pi-futex
    * futex-requeue-pi
    * hwspinlock
    * percpu-rw-semaphore
    * robust-futexes
    * robust-futex-ABI

.. only::  subproject and html

   Indices
   =======

   * :ref:`genindex`


code:
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/locking/mutex-design.rst

:翻译:

  唐艺舟 Tang Yizhou <tangyeechou@gmail.com>

================
通用互斥锁子系统
================

:初稿:

  Ingo Molnar <mingo@redhat.com>

:更新:

  Davidlohr Bueso <davidlohr@hp.com>

什么是互斥锁？
--------------

在Linux内核中，互斥锁（mutex）指的是一个特殊的加锁原语，它在共享内存系统上
强制保证序列化，而不仅仅是指在学术界或类似的理论教科书中出现的通用术语“相互
排斥”。互斥锁是一种睡眠锁，它的行为类似于二进制信号量（semaphores），在
2006年被引入时[1]，作为后者的替代品。这种新的数据结构提供了许多优点，包括更
简单的接口，以及在当时更少的代码量（见缺陷）。

[1] https://lwn.net/Articles/164802/

实现
----

互斥锁由“struct mutex”表示，在include/linux/mutex.h中定义，并在
kernel/locking/mutex.c中实现。这些锁使用一个原子变量（->owner）来跟踪
它们生命周期内的锁状态。字段owner实际上包含的是指向当前锁所有者的
`struct task_struct *` 指针，因此如果无人持有锁，则它的值为空（NULL）。
由于task_struct的指针至少按L1_CACHE_BYTES对齐，低位（3）被用来存储额外
的状态（例如，等待者列表非空）。在其最基本的形式中，它还包括一个等待队列和
一个确保对其序列化访问的自旋锁。此外，CONFIG_MUTEX_SPIN_ON_OWNER=y的
系统使用一个自旋MCS锁（->osq，译注：MCS是两个人名的合并缩写），在下文的
（ii）中描述。

准备获得一把自旋锁时，有三种可能经过的路径，取决于锁的状态：

(i) 快速路径：试图通过调用cmpxchg()修改锁的所有者为当前任务，以此原子化地
    获取锁。这只在无竞争的情况下有效（cmpxchg()检查值是否为0，所以3个状态
    比特必须为0）。如果锁处在竞争状态，代码进入下一个可能的路径。

(ii) 中速路径：也就是乐观自旋，当锁的所有者正在运行并且没有其它优先级更高的
     任务（need_resched，需要重新调度）准备运行时，当前任务试图自旋来获得
     锁。原理是，如果锁的所有者正在运行，它很可能不久就会释放锁。互斥锁自旋体
     使用MCS锁排队，这样只有一个自旋体可以竞争互斥锁。

     MCS锁（由Mellor-Crummey和Scott提出）是一个简单的自旋锁，它具有一些
     理想的特性，比如公平，以及每个CPU在试图获得锁时在一个本地变量上自旋。
     它避免了常见的“检测-设置”自旋锁实现导致的（CPU核间）缓存行回弹
     （cacheline bouncing）这种昂贵的开销。一个类MCS锁是为实现睡眠锁的
     乐观自旋而专门定制的。这种定制MCS锁的一个重要特性是，它有一个额外的属性，
     当自旋体需要重新调度时，它们能够退出MCS自旋锁队列。这进一步有助于避免
     以下场景：需要重新调度的MCS自旋体将继续自旋等待自旋体所有者，即将获得
     MCS锁时却直接进入慢速路径。

(iii) 慢速路径：最后的手段，如果仍然无法获得锁，该任务会被添加到等待队列中，
      休眠直到被解锁路径唤醒。在通常情况下，它以TASK_UNINTERRUPTIBLE状态
      阻塞。

虽然从形式上看，内核互斥锁是可睡眠的锁，路径(ii)使它实际上成为混合类型。通过
简单地不中断一个任务并忙着等待几个周期，而不是立即睡眠，这种锁已经被认为显著
改善一些工作负载的性能。注意，这种技术也被用于读写信号量（rw-semaphores）。

语义
----

互斥锁子系统检查并强制执行以下规则:

    - 每次只有一个任务可以持有该互斥锁。
    - 只有锁的所有者可以解锁该互斥锁。
    - 不允许多次解锁。
    - 不允许递归加锁/解锁。
    - 互斥锁只能通过API进行初始化（见下文）。
    - 一个任务不能在持有互斥锁的情况下退出。
    - 持有锁的内存区域不得被释放。
    - 被持有的锁不能被重新初始化。
    - 互斥锁不能用于硬件或软件中断上下文，如小任务（tasklet）和定时器。

当CONFIG DEBUG_MUTEXES被启用时，这些语义将被完全强制执行。此外，互斥锁
调试代码还实现了一些其它特性，使锁的调试更容易、更快速：

    - 当打印到调试输出时，总是使用互斥锁的符号名称。
    - 加锁点跟踪，函数名符号化查找，系统持有的全部锁的列表，打印出它们。
    - 所有者跟踪。
    - 检测自我递归的锁并打印所有相关信息。
    - 检测多任务环形依赖死锁，并打印所有受影响的锁和任务（并且只限于这些任务）。

接口
----
静态定义互斥锁::

   DEFINE_MUTEX(name);

动态初始化互斥锁::

   mutex_init(mutex);

以不可中断方式（uninterruptible）获取互斥锁::

   void mutex_lock(struct mutex *lock);
   void mutex_lock_nested(struct mutex *lock, unsigned int subclass);
   int  mutex_trylock(struct mutex *lock);

以可中断方式（interruptible）获取互斥锁::

   int mutex_lock_interruptible_nested(struct mutex *lock,
				       unsigned int subclass);
   int mutex_lock_interruptible(struct mutex *lock);

当原子变量减为0时，以可中断方式（interruptible）获取互斥锁::

   int atomic_dec_and_mutex_lock(atomic_t *cnt, struct mutex *lock);

释放互斥锁::

   void mutex_unlock(struct mutex *lock);

检测是否已经获取互斥锁::

   int mutex_is_locked(struct mutex *lock);

缺陷
----

与它最初的设计和目的不同，'struct mutex' 是内核中最大的锁之一。例如：在
x86-64上它是32字节，而 'struct semaphore' 是24字节，rw_semaphore是
40字节。更大的结构体大小意味着更多的CPU缓存和内存占用。

何时使用互斥锁
--------------

总是优先选择互斥锁而不是任何其它锁原语，除非互斥锁的严格语义不合适，和/或临界区
阻止锁被共享。


code:
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/locking/spinlocks.rst

:翻译:

  唐艺舟 Tang Yizhou <tangyeechou@gmail.com>

==========
加锁的教训
==========

教训 1：自旋锁
==============

加锁最基本的原语是自旋锁（spinlock）::

  static DEFINE_SPINLOCK(xxx_lock);

	unsigned long flags;

	spin_lock_irqsave(&xxx_lock, flags);
	... 这里是临界区 ..
	spin_unlock_irqrestore(&xxx_lock, flags);

上述代码总是安全的。自旋锁将在 _本地_ 禁用中断，但它本身将保证全局锁定。所以它
将保证在该锁保护的区域内只有一个控制线程。即使在单处理器（UP）下也能很好的工作，
所以代码 _不_ 需要担心UP还是SMP的问题：自旋锁在两种情况下都能正常工作。

   注意！自旋锁对内存的潜在影响由下述文档进一步描述：

     Documentation/memory-barriers.txt

       (5) ACQUIRE operations.

       (6) RELEASE operations.

上述代码通常非常简单（对大部分情况，你通常需要并且只希望有一个自旋锁——使用多个
自旋锁会使事情变得更复杂，甚至更慢，而且通常仅仅在你 **理解的** 序列有被拆分的
需求时才值得这么做：如果你不确定的话，请不惜一切代价避免这样做）。

这是关于自旋锁的唯一真正困难的部分：一旦你开始使用自旋锁，它们往往会扩展到你以前
可能没有注意到的领域，因为你必须确保自旋锁正确地保护共享数据结构 **每一处** 被
使用的地方。自旋锁是最容易被添加到完全独立于其它代码的地方（例如，没有人访问的
内部驱动数据结构）的。

   注意！仅当你在跨CPU核访问时使用 **同一把** 自旋锁，对它的使用才是安全的。
   这意味着所有访问共享变量的代码必须对它们想使用的自旋锁达成一致。

----

教训 2：读-写自旋锁
===================

如果你的数据访问有一个非常自然的模式，倾向于从共享变量中读取数据，读-写自旋锁
（rw_lock）有时是有用的。它们允许多个读者同时出现在同一个临界区，但是如果有人想
改变变量，它必须获得一个独占的写锁。

   注意！读-写自旋锁比原始自旋锁需要更多的原子内存操作。除非读者的临界区很长，
   否则你最好只使用原始自旋锁。

例程看起来和上面一样::

   rwlock_t xxx_lock = __RW_LOCK_UNLOCKED(xxx_lock);

	unsigned long flags;

	read_lock_irqsave(&xxx_lock, flags);
	.. 仅读取信息的临界区 ...
	read_unlock_irqrestore(&xxx_lock, flags);

	write_lock_irqsave(&xxx_lock, flags);
	.. 读取和独占写信息 ...
	write_unlock_irqrestore(&xxx_lock, flags);

上面这种锁对于复杂的数据结构如链表可能会有用，特别是在不改变链表的情况下搜索其中
的条目。读锁允许许多并发的读者。任何希望 **修改** 链表的代码将必须先获取写锁。

   注意！RCU锁更适合遍历链表，但需要仔细注意设计细节（见Documentation/RCU/listRCU.rst）。

另外，你不能把读锁“升级”为写锁，所以如果你在 _任何_ 时候需要做任何修改
（即使你不是每次都这样做），你必须在一开始就获得写锁。

   注意！我们正在努力消除大多数情况下的读-写自旋锁的使用，所以请不要在没有达成
   共识的情况下增加一个新的（相反，请参阅Documentation/RCU/rcu.rst以获得完整
   信息）。

----

教训 3：重新审视自旋锁
======================

上述的自旋锁原语绝不是唯一的。它们是最安全的，在所有情况下都能正常工作，但部分
**因为** 它们是安全的，它们也是相当慢的。它们比原本需要的更慢，因为它们必须要
禁用中断（在X86上只是一条指令，但却是一条昂贵的指令——而在其他体系结构上，情况
可能更糟）。

如果你有必须保护跨CPU访问的数据结构且你想使用自旋锁的场景，你有可能使用代价小的
自旋锁版本。当且仅当你知道某自旋锁永远不会在中断处理程序中使用，你可以使用非中断
的版本::

	spin_lock(&lock);
	...
	spin_unlock(&lock);

（当然，也可以使用相应的读-写锁版本）。这种自旋锁将同样可以保证独占访问，而且
速度会快很多。如果你知道有关的数据只在“进程上下文”中被存取，即，不涉及中断，
这种自旋锁就有用了。

当这些版本的自旋锁涉及中断时，你不能使用的原因是会陷入死锁::

	spin_lock(&lock);
	...
		<- 中断来临：
			spin_lock(&lock);

一个中断试图对一个已经锁定的变量上锁。如果中断发生在另一个CPU上，不会有问题；
但如果中断发生在已经持有自旋锁的同一个CPU上，将 _会_ 有问题，因为该锁显然永远
不会被释放（因为中断正在等待该锁，而锁的持有者被中断打断，并且无法继续执行，
直到中断处理结束）。

（这也是自旋锁的中断版本只需要禁用 _本地_ 中断的原因——在发生于其它CPU的中断中
使用同一把自旋锁是没问题的，因为发生于其它CPU的中断不会打断已经持锁的CPU，所以
锁的持有者可以继续执行并最终释放锁）。

		Linus

----

参考信息
========

对于动态初始化，使用spin_lock_init()或rwlock_init()是合适的::

   spinlock_t xxx_lock;
   rwlock_t xxx_rw_lock;

   static int __init xxx_init(void)
   {
	spin_lock_init(&xxx_lock);
	rwlock_init(&xxx_rw_lock);
	...
   }

   module_init(xxx_init);

对于静态初始化，使用DEFINE_SPINLOCK() / DEFINE_RWLOCK()或
__SPIN_LOCK_UNLOCKED() / __RW_LOCK_UNLOCKED()是合适的。


code:
.. SPDX-License-Identifier: GPL-2.0

.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/loongarch/booting.rst

:翻译:

 司延腾 Yanteng Si <siyanteng@loongson.cn>

====================
启动 Linux/LoongArch
====================

:作者: 司延腾 <siyanteng@loongson.cn>
:日期: 2022年11月18日

BootLoader传递给内核的信息
==========================

LoongArch支持ACPI和FDT启动，需要传递给内核的信息包括memmap、initrd、cmdline、可
选的ACPI/FDT表等。

内核在 `kernel_entry` 入口处被传递以下参数:

      - a0 = efi_boot: `efi_boot` 是一个标志，表示这个启动环境是否完全符合UEFI
        的要求。

      - a1 = cmdline: `cmdline` 是一个指向内核命令行的指针。

      - a2 = systemtable: `systemtable` 指向EFI的系统表，在这个阶段涉及的所有
        指针都是物理地址。

Linux/LoongArch内核镜像文件头
=============================

内核镜像是EFI镜像。作为PE文件，它们有一个64字节的头部结构体，如下所示::

	u32	MZ_MAGIC                /* "MZ", MS-DOS 头 */
	u32	res0 = 0                /* 保留 */
	u64	kernel_entry            /* 内核入口点 */
	u64	_end - _text            /* 内核镜像有效大小 */
	u64	load_offset             /* 加载内核镜像相对内存起始地址的偏移量 */
	u64	res1 = 0                /* 保留 */
	u64	res2 = 0                /* 保留 */
	u64	res3 = 0                /* 保留 */
	u32	LINUX_PE_MAGIC          /* 魔术数 */
	u32	pe_header - _head       /* 到PE头的偏移量 */


code:
.. SPDX-License-Identifier: GPL-2.0

.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/loongarch/features.rst
:Translator: Huacai Chen <chenhuacai@loongson.cn>

.. kernel-feat:: $srctree/Documentation/features loongarch


code:
.. SPDX-License-Identifier: GPL-2.0

.. include:: ../disclaimer-zh_CN.rst

:Original: Documentation/loongarch/index.rst
:Translator: Huacai Chen <chenhuacai@loongson.cn>

=================
LoongArch体系结构
=================

.. toctree::
   :maxdepth: 2
   :numbered:

   introduction
   booting
   irq-chip-model

   features

.. only::  subproject and html

   Indices
   =======

   * :ref:`genindex`