linux 庐山培训,Linux

2023-11-21 16:00
文章标签 linux 培训 庐山

本文主要是介绍linux 庐山培训,Linux,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Clock统是Linux内核中专门管理时钟的子系统.

时钟在嵌入式系统中很重要,它就像人的脉搏一样,驱动器件工作.

任何一个CPU,都需要给它提供一个外部晶振,这个晶振就是用来提供时钟的;任何一个CPU内部的片上外设,也需要工作时钟:例如GPIO控制器,首先得给它提供工作时钟,然后才能访问它的寄存器.

如果你去看一个ARM CPU的芯片手册,你一定能找到一个章节,专门描述系统时钟,一般称之为时钟树(clock tree).

芯片手册从硬件的角度上描述了某个CPU的时钟系统是如何设计的,而Clock子系统从软件的层面来抽象这个设计.

在本章中,我们首先从硬件的角度来看看一个时钟树的的例子,然后自己思考一下软件层面该如何设计,最后看看clock子系统是怎么做的.

2.1时钟树

如今,可运行Linux的主流CPU,都有非常复杂的clock tree,我们随便拿一个处理器的spec,查看clock相关的章节,一定会有一个非常庞大和复杂的树状图.这个图由clock相关的器件,以及这些器件输出的clock组成.

下图是一个简单的示例:

347c6388ea77f188446a676cb4b9e031.png

clock相关的器件包括

用于产生clock的Oscillator(有源振荡器,也称作谐振荡器)或者Crystal(无源振荡器,也称晶振)

用于倍频的PLL(锁相环,Phase Locked Loop)

用于分频的divider

用于多路选择的Mux

用于clock enable控制的与门

使用clock的硬件模块(可称作consumer),例如HW1,它可能是GPIO控制器

器件用于产生具体的clock,例如osc_clk.这些器件的特性如下:

从输入(parent)和输出(children)的角度来看

某些器件没有parent,有一个或多个输出.例如osc_clk

某些器件有一个parent,有一个或多个输出.例如PLL1,它的parent是osc_clk,输出只有一个pll1_clk

某些器件有多个parent,有一个或多个输出.例如MUX,它有多个parent,输出只有一个hw3_clk

从频率的角度上来看

某些clock的频率是可以调整的,我们可以通过设置倍频和分频因子来调整输出频率.

这一类clock最常见.

某些clock的频率是固定的,而且不能开关,比如osc_clk.最常见的是24M或25M.

这一类clock称作fixed_rate_clock

某些clock的频率也是固定的,不过它可以开关.

这一类clock称作gate_clock

某些clock有固定的倍频和分频因子,它的输出频率跟随parent的变化而变化.

这一类clock称作fixed_factor_clock

在上图的时钟树中,有些是clock的提供者,我们可以称之为provider,例如oscillator, PLLs;有些是clock的使用者,我们可以称之为consumer,例如HW1, HW2, HW3.

在ARM CPU的内部,时钟树系统用来provide各种各样的时钟;各片上外设consume这些时钟.例如时钟树系统负责提供时钟给GPIO控制器, GPIO控制器则消费提供给它的工作时钟.

在设备驱动开发的过程中,我们经常会遇到的一个问题是:想要开启某个模块的时钟.

例如开发GPIO的驱动,在驱动的probe函数中,我们需要使能GPIO模块的工作时钟.

从软件层面,我们就是要提供一种机制,让consumer可以方便的获取/使能/配置/关闭一个时钟.

接下来,我们看看软件层面上该如何抽象.

2.2软件抽象

上一节我们介绍了时钟树,并介绍了时钟的provider和consumer.一个CPU芯片内部,会有很多个provider,也会有很多的consumer.软件层面需要做的事情就是管理所有这些provider,并向consumer提供尽量简单的接口使得consumer可以获取/使能/配置/关闭一个时钟.

因此,我们可以设计这样一个池子,所有的provider都可以向池子注册,把自己添加到池子里面.池子里面可以用一个链表把所有的provider都串起来,不同的provider以不同的name区分.当consumer需要获取某个clock的时候,通过name向池子查询即可.

在这个池子里面,每一个provider都可以抽象成一个独立的元素,因此我们最好设计一个数据结构,来表示每一个元素.

大致逻辑就是这样了, Linux内核的clock子系统基本上就是在干这些事情.

2.3clock子系统

Linux内核的clock子系统,按照其职能,可以大致分为3部分:

向下提供注册接口,以便各个clocks能注册进clock子系统

在核心层维护一个池子,管理所有注册进来的clocks.这一部分实现的是通用逻辑,与具体硬件无关.

向上,也就是像各个消费clocks的模块的device driver,提供获取/使能/配置/关闭clock的通用API

clock子系统的结构框图如下,一些细节你现在可能还不能理解.不过没关系,读完后面的章节你就会明白了.

后面的章节,我们也会分为上述3部分来描述,在阅读的过程中,你可以边看边对着下面这张图理解,这样会更加清晰.

88d17521e9d663fd0b28ea67a4d037c7.png

2.clock provider—如何注册Clocks

3.1简介

前文我们介绍了时钟树,本章要阐述的主要问题就是如何把时钟树产生的这些clocks注册进Linux内核的clock子系统.换句话说,就是如何编写clock driver.

在ARM CPU内部,管理时钟树的也是一个单独的模块,一般叫PCM(ProgrammableClock Management),编写clock driver其实就是编写PCM的driver.

PCM也是CPU的一个片上外设,因此它也会借用platform这套机制.因此我们就需要有platform_device来描述设备,同时要有与之对应的platform_driver来控制设备.所谓控制,就写读写PCM的寄存器来使能/关闭时钟,设置时钟频率等等.在platform_driver的probe函数中,还有一项重要功能,就是调用clock子系统提供的API,向clock子系统注册.

下面,我看看编写clockdriver的大致步骤是怎样的.

3.2编写clock driver的大致步骤

由前文可知,首先你得准备一个platform_device,引入device tree的机制后, platform_device被dts替代了,因此我们就需要在dts里面描述时钟树.

编写platform_device(DTS node)

那么这个DTS该怎么写呢?通常有两种方式:

方式一:将时钟树中所有的clock,抽象为一个虚拟的设备,用一个DTS node表示.

1:/* arch/arm/boot/dts/exynos4210.dtsi */

2: theclock:clock–controller@0x10030000{

3:compatible= “samsung,exynos4210-clock”;

4:reg= <0x10030000 0x20000>;

5:#clock–cells= <1>;

6: };

这种方式跟编写一个普通的片上外设的DTS很类似,比如GPIO控制器的DTS,对比一下,是不是很类似.

从这个例子里面,我们可以看出一个clock的DTS node的基本语法:

compatible,决定了与这个node匹配的driver

reg就是用来描述PCM的寄存器

#clock-cells,这个是clock provider node独有的,表明在引用此clock时,需要用几个32位来描述.什么意思呢?

假设这个clock只有一个输出时钟,那么#clock–cells= <0>,我们在引用此clock的时候,只用指明此clock即可.

引用此clock是什么意思?引用指的是clock的consumer端.例如GPIO模块需要工作时钟,那么我们在编写GPIO的DTS node时,需要指明它的工作时钟是多少,这个过程就是引用,写个简单的例子:

gpio :gpio-controller@xxxx {

compatible=“yyyy”;

reg= ;

……

clocks = ;  /*指明/引用某一个clock*/

}

假设这个clock有多个输出时钟,那么#clock–cells= <0>肯定不行,因为我们在引用此clock的时候,需要指明到底用哪一个输出时钟.

这个时候#clock–cells应该为 <1>,在引用此clock,就得这样写:

gpio :gpio-controller@xxxx {

compatible=“yyyy”;

reg= ;

……

clocks = ;  /*指明/引用某一个clock, num是一个32位的整数,表明到底用哪一个输出clock*/

}

theclock,此DTS node的lable,clock consumer可以根据该名称引用clock

方式二:将时钟树中的每一个clock抽象为一个DTS node,并以树形的结构组织.这种方式相较与方式一,能更清晰的抽象出时钟的树形结构,从逻辑上也更合理,因此推荐大家使用这种方式.

举个方式二的例子:

1:/* arch/arm/boot/dts/sun4i-a10.dtsi */

2:clocks{

3:#address–cells= <1>;

4:#size–cells= <1>;

5:ranges;

19:osc24M:osc24M@01c20050{

20:#clock–cells= <0>;

21:compatible= “allwinner,sun4i-osc-clk”;

22:reg= <0x01c20050 0x4>;

23:clock–frequency= <24000000>;

24:};

25:

26:osc32k:osc32k{

27:#clock–cells= <0>;

28:compatible= “fixed-clock”;

29:clock–frequency= <32768>;

30:};

31:

32:pll1:pll1@01c20000{

33:#clock–cells= <0>;

34:compatible= “allwinner,sun4i-pll1-clk”;

35:reg= <0x01c20000 0x4>;

36:clocks= ;

37:};

38:

39:/* dummy is 200M */

40:cpu:cpu@01c20054{

41:#clock–cells= <0>;

42:compatible= “allwinner,sun4i-cpu-clk”;

43:reg= <0x01c20054 0x4>;

44:clocks= , , , ;

45:};

46:

47:axi:axi@01c20054{

48:#clock–cells= <0>;

49:compatible= “allwinner,sun4i-axi-clk”;

50:reg= <0x01c20054 0x4>;

51:clocks= ;

52:};

53:

54:axi_gates:axi_gates@01c2005c{

55:#clock–cells= <1>;

56:compatible= “allwinner,sun4i-axi-gates-clk”;

57:reg= <0x01c2005c 0x4>;

58:clocks= ;

59:clock–output–names= “axi_dram”;

60:};

61:

62:ahb:ahb@01c20054{

63:#clock–cells= <0>;

64:compatible= “allwinner,sun4i-ahb-clk”;

65:reg= <0x01c20054 0x4>;

66:clocks= ;

67:};

68:

69:ahb_gates:ahb_gates@01c20060{

70:#clock–cells= <1>;

71:compatible= “allwinner,sun4i-ahb-gates-clk”;

72:reg= <0x01c20060 0x8>;

73:clocks= ;

74:clock–output–names= “ahb_usb0”, “ahb_ehci0”,

75:“ahb_ohci0”, “ahb_ehci1”, “ahb_ohci1”, “ahb_ss”,

76:“ahb_dma”, “ahb_bist”, “ahb_mmc0”, “ahb_mmc1”,

77:“ahb_mmc2”, “ahb_mmc3”, “ahb_ms”, “ahb_nand”,

78:“ahb_sdram”, “ahb_ace”,“ahb_emac”, “ahb_ts”,

79:“ahb_spi0”, “ahb_spi1”, “ahb_spi2”, “ahb_spi3”,

80:“ahb_pata”, “ahb_sata”, “ahb_gps”, “ahb_ve”,

81:“ahb_tvd”, “ahb_tve0”, “ahb_tve1”, “ahb_lcd0”,

82:“ahb_lcd1”, “ahb_csi0”, “ahb_csi1”, “ahb_hdmi”,

83:“ahb_de_be0”, “ahb_de_be1”, “ahb_de_fe0”,

84:“ahb_de_fe1”, “ahb_mp”, “ahb_mali400”;

85:};

86:

87:apb0:apb0@01c20054{

88:#clock–cells= <0>;

89:compatible= “allwinner,sun4i-apb0-clk”;

90:reg= <0x01c20054 0x4>;

91:clocks= ;

92:};

93:

94:apb0_gates:apb0_gates@01c20068{

95:#clock–cells= <1>;

96:compatible= “allwinner,sun4i-apb0-gates-clk”;

97:reg= <0x01c20068 0x4>;

98:clocks= ;

99:clock–output–names= “apb0_codec”, “apb0_spdif”,

100:“apb0_ac97”, “apb0_iis”, “apb0_pio”, “apb0_ir0”,

101:“apb0_ir1”, “apb0_keypad”;

102:};

103:

104:/* dummy is pll62 */

105:apb1_mux:apb1_mux@01c20058{

106:#clock–cells= <0>;

107:compatible= “allwinner,sun4i-apb1-mux-clk”;

108:reg= <0x01c20058 0x4>;

109:clocks= , , ;

110:};

111:

112:apb1:apb1@01c20058{

113:#clock–cells= <0>;

114:compatible= “allwinner,sun4i-apb1-clk”;

115:reg= <0x01c20058 0x4>;

116:clocks= ;

117:};

118:

119:apb1_gates:apb1_gates@01c2006c{

120:#clock–cells= <1>;

121:compatible= “allwinner,sun4i-apb1-gates-clk”;

122:reg= <0x01c2006c 0x4>;

123:clocks= ;

124:clock–output–names= “apb1_i2c0”, “apb1_i2c1”,

125:“apb1_i2c2”, “apb1_can”, “apb1_scr”,

126:“apb1_ps20”, “apb1_ps21”, “apb1_uart0”,

127:“apb1_uart1”, “apb1_uart2”, “apb1_uart3”,

128:“apb1_uart4”, “apb1_uart5”, “apb1_uart6”,

129:“apb1_uart7”;

130:};

131: };

osc24M 代表24M的晶振

osc32k 代表32k的慢速时钟

pll1,它的父时钟是osc24M,只有一个输出时钟

cpu,它的父时钟有多个, , , ,只有一个输出时钟

ahb_gates,它的父时钟只有一个,是ahb,但是它的输出时钟有很多个

……后面的都类似,不一一说明了

看见了吧,这种方式确实能清晰的描述多个clocks的树形关系.

编写platform_driver

有了platform_device之后,接下来就得编写platform_driver,在driver里面最重要的事情就是向clock子系统注册.

如何注册呢?

clock子系统定义了clock driver需要实现的数据结构,同时提供了注册的函数.我们只需要准备好相关的数据结构,然后调用注册函数进行注册即可.

这些数据结构和接口函数的定义是在:include/linux/clk-provider.h

需要实现的数据结构是 struct clk_hw,需要调用的注册函数是struct clk *clk_register(struct device *dev, struct clk_hw *hw).数据结构和注册函数的细节我们在后文说明.

注册函数会返回给你一个struct clk类型的指针,在Linux的clock子系统中,用一个struct clk代表一个clock.你的CPU的时钟树里有多少个clock,就会有多少个对应的struct clk.

当你拿到返回结果之后,你需要调用另外一个API :of_clk_add_provider,把刚刚拿到的返回结果通过此API丢到池子里面,好让consumer从这个池子里获取某一个clock.

池子的概念我们在前文讲述过,除了上述方法,你还可以用另外一种方法把struct clk添加到池子里面.

如果你要用这种方法,你会用到clock子系统提供给你的另外几个API,这些API的定义在:include/linux/clkdev.h

其中最主要的一个API是int clk_register_clkdev(struct clk *, const char *, const char *, …),你可以在你的clock driver里面调用这个API,把刚刚拿到的返回结果通过此API丢到池子里面.

为什么会存在这两种方式呢?得从consumer的角度来解答这个问题.

我们用GPIO来举个例子, GPIO控制器需要工作时钟,这个时钟假设叫gpio_clk,它是一个provider.你需要把这个provider注册进clock子系统,并把用于描述这个gpio_clk的struct clk添加到池子里面.

在GPIO控制器的driver代码里,我们需要获取到gpio_clk这个时钟并使能它,获取的过程就是向池子查询.

怎么查询?你可以直接给定一个name,然后通过这个name向池子查询;你也可以在GPIO的DTS  node里面用clocks = ;方式指明使用哪一个clock,然后通过这种方式向池子查询.

如果consumer是通过name查询,则对应的添加到池子的API就是clk_register_clkdev

如果consumer是通过DTS查询,则对应的添加到池子的API就是of_clk_add_provider

那么我在我的clock driver里面到底应该用哪个API向池子添加clk呢?

两者你都应该同时使用,这样consumer端不管用哪种查询方式都能工作.

读到这里,建议你回头看看clock子系统的系统框图,结合框图在琢磨琢磨.

接下来,我们就会详细介绍这些数据结构和相关的API了.

3.3主要数据结构

通过前文,我们知道了clock driver需要实现的一个主要的数据结构struct clk_hw.

与之相关的还有另外几个重要数据结构:struct clk_init_data和struct clk_ops.

下面我们看看这几个数据结构.

structclk_hw

头文件: include/linux/clk-provider.h

struct  clk_hw

Comment

struct clk_core *core

clk_core是clock子系统核心层的一个数据结构,由核心层代码创建和维护,一个clk_core对应一个具体的clock.

struct clk *clk

clk也是clock子系统核心层的一个数据结构,同样由核心层代码创建和维护,它也对应一个具体的clock.

clk_core和clk的细节,放在《clock core》一章中描述

const struct clk_init_data *init

clk_init_data是provider需要实现并填充的一个数据结构,编写clock driver,最主要的任务就是实现它

structclk_init_data

头文件: include/linux/clk-provider.h

structclk_init_data

Comment

const char*name

该clock的name,系统中会存在很多个clock,每一个clock都会有一个名称,可以用名称来区分不同的clock,因此不能重名

const struct clk_ops*ops

与clock控制相关的ops,例如enable/disable; set_rate等等.

当consumer端想要操作某个clock时,最终就会调用到该clock的clk_ops

const char**parent_names

该clock的所有parents.通过name,就能找到parent是谁了

u8num_parents

一个clock可能有多个parents, num_parents指明到底有几个

unsigned longflags

标志位,表明该clock的一些特性,核心层代码会根据不同的flags采取不同的动作.可选的值如下:

#defineCLK_SET_RATE_GATEBIT(0) /* must be gated across rate change */

#defineCLK_SET_PARENT_GATEBIT(1) /* must be gated across re-parent */

#defineCLK_SET_RATE_PARENTBIT(2) /* propagate rate change up one level */

#defineCLK_IGNORE_UNUSEDBIT(3) /* do not gate even if unused */

#defineCLK_IS_ROOTBIT(4) /* root clk, has no parent */

#defineCLK_IS_BASICBIT(5) /* Basic clk, can’t do a to_clk_foo() */

#defineCLK_GET_RATE_NOCACHE BIT(6) /* do not use the cached clk rate */

#defineCLK_SET_RATE_NO_REPARENTBIT(7) /* don’t re-parent on rate change */

#defineCLK_GET_ACCURACY_NOCACHEBIT(8) /* do not use the cached clk accuracy */

structclk_ops

头文件: include/linux/clk-provider.h

下述这些ops在.h文件里面都有详细的注释,可以阅读源代码获取更多信息.

structclk_ops

Comment

int(*prepare)(struct clk_hw *hw)

为什么要有prepare接口,直接enable不就行了吗?

从硬件的角度来说,某些clock,如果想使能它,需要等待一段时间.

例如倍频器PLL,当你使能倍频器之后,你需要等待几毫秒让倍频器工作平稳.

因为要等待,软件上就有可能sleep,也就是休眠.但是Linux内核中有很多情况下不能休眠,比如说中断服务器程序.

如果你把所有的操作都放在enable这一个函数里面,那么enable函数就可能休眠,因而中断服务程序里面就不能调用enable函数.

但实际情况是,很多时候,我们都要求在中断服务程序里面开/关某个clock

怎么办呢?

拆分成2个函数, prepare和enable.

prepare负责使能clock之前的准备工作, prepare里面可以休眠,一旦prepare返回,就意味着clock已经完全准备好了,可以直接开/关

enable负责打开clock,它不能休眠,这样在中断服务程序中也可以调用enable了

void(*unprepare)(struct clk_hw *hw)

prepare的反函数, un-do prepare里面做的所有事情

int(*is_prepared)(struct clk_hw *hw)

is_prepared,判断clock是否已经prepared,可以不提供.

clock framework core会维护一个prepare的计数(该计数在clk_prepare调用时加一,在clk_unprepare时减一),并依据该计数判断是否prepared

void(*unprepare_unused)(struct clk_hw *hw)

自动unprepare unused clocks

clock framework core提供一个clk_disable_unused接口,在系统初始化的late_call中调用,用于关闭unused clocks,这个接口会调用相应clock的.unprepare_unused和.disable_unused函数

int(*enable)(struct clk_hw *hw)

使能clock,此函数不能休眠

当此函数返回时,代表consumer端可以收到一个稳定,可用的clock波形了.

void(*disable)(struct clk_hw *hw)

禁止clock,此函数不能休眠

int(*is_enabled)(struct clk_hw *hw)

与is_prepared类似

void(*disable_unused)(struct clk_hw *hw)

与unprepare_unused类似

int(*save_context)(struct clk_hw *hw)

Save the context of the clock in prepration for poweroff

void(*restore_context)(struct clk_hw *hw)

Restore the context of the clock after a restoration of power

unsigned long(*recalc_rate)(struct clk_hw *hw, unsigned long parent_rate)

以parent clock rate为参数,从新计算并返回clock rate

long(*round_rate)(structclk_hw *hw, unsigned long rate, unsigned long *parent_rate)

Given a target rate asinput, returns the closest rate actually supported by the clock

该接口有点特别,在返回rounded rate的同时,会通过一个指针,返回round后parent的rate.这和CLK_SET_RATE_PARENT flag有关

当clock consumer调用clk_round_rate获取一个近似的rate时,如果该clock没有提供.round_rate函数,有两种方法:

在没有设置CLK_SET_RATE_PARENT标志时,直接返回该clock的cache rate

如果设置了CLK_SET_RATE_PARENT标志,则会询问parent,即调用clk_round_rate获取parent clock能提供的、最接近该rate的值.

这是什么意思呢?也就是说,如果parent clock可以得到一个近似的rate值,那么通过改变parent clock,就能得到所需的clock

long(*determine_rate)(struct clk_hw *hw,

unsigned long rate,

unsigned long min_rate,

unsigned long max_rate,

unsigned long *best_parent_rate,

struct clk_hw **best_parent_hw)

与round_rate类似,暂时不清楚它俩有什么区别.

int(*set_parent)(struct clk_hw *hw, u8 index)

Change the input source of this clock

有的clocks可能有多个parents,那到底用哪一个呢?由参数index决定

u8(*get_parent)(struct clk_hw *hw)

Queries the hardware to determine the parent of a clock

返回值是一个u8类型的变量,它是一个index,通过它可以查找到对应的parent.所有的parents都存储在.parent_names or .parents arrays里面

实际上,此函数会读取硬件寄存器,然后把寄存器的值转变为对应的index

int(*set_rate)(struct clk_hw *hw, unsigned long rate,

unsigned long parent_rate)

Change the rate of this clock

The requested rate is specified by the second argument, which should typically be the return of .round_rate call

The third argument gives the parent rate which is likely helpful for most .set_rate implementation

int (*set_rate_and_parent)(struct clk_hw *hw,

unsigned long rate,

unsigned long parent_rate,

u8 index)

Change the rate and the parent of this clock

This callback is optional (and unnecessary) for clocks with 0 or 1 parents as well as for clocks that can tolerate switching the rate and the parent separately via calls to .set_parent and .set_rate

unsigned long (*recalc_accuracy)(struct clk_hw *hw,

unsigned long parent_accuracy)

Recalculate the accuracy of this clock

The clock accuracy is expressed in ppb (parts per billion)

int(*get_phase)(struct clk_hw *hw)

Queries the hardware to get the current phase of a clock

Returned values are 0-359 degrees on success, negative error codes on failure

int(*set_phase)(structclk_hw *hw, int degrees)

Shift the phase this clock signal in degrees specified by the secondargument

Valid values for degrees are 0-359

Return 0 on success, otherwise -EERROR

void(*init)(struct clk_hw *hw)

Perform platform-specific initialization magic

不过从代码注释来看,内核推荐你不要实现这个接口函数,后面可能会遗弃

int(*debug_init)(struct clk_hw *hw, struct dentry *dentry)

debugfs相关,这里不细述

细节可以看代码注释

3.4主要API说明

Linux clock子系统向下提供几个重要的API,下面我挨个看下这些API的细节.

clk_register/devm_clk_register

头文件: include/linux/clk-provider.h

实现文件: drivers/clk/clk.c

/**

* clk_register – allocate a new clock, register it and return an opaque cookie

* @dev: device that is registering this clock

* @hw: link to hardware-specific clock data

*

*clk_registeris the primary interface for populating the clock tree with new

*clocknodes.  It returns a pointer to the newly allocated struct clk which

* cannot be dereferenced by driver code but may be used in conjuction with the

*restof the clock API.  In the event of an error clk_register will return an

*errorcode; drivers must test for an error code after calling clk_register.

*/

structclk*clk_register(structdevice*dev, structclk_hw*hw);

structclk*devm_clk_register(structdevice*dev, structclk_hw*hw);

voidclk_unregister(structclk*clk);

voiddevm_clk_unregister(structdevice*dev, structclk*clk);

clk_register是clock子系统提供的注册clock的最基础的API函数,后文描述的其它APIs都是对它的封装.

devm_clk_register是clk_register的devm版本, devm机制在《设备模型》一文中有详述.

要向系统注册一个clock也很简单,准备好clk_hw结构体,然后调用clk_register接口即可.

不过,clock framework所做的远比这周到,它基于clk_register,又封装了其它接口,在向clock子系统注册时,连struct clk_hw都不需要关心,而是直接使用类似人类语言的方式.

也就是说,实际在编写clock driver的时候,我们不会直接使用clk_register接口,只需要调用下面的某个API即可.

下文我们一一介绍这些API.

clk_register_fixed_rate

clock有不同的类型,有的clock频率是固定的,不可调整的,例如外部晶振,频率就是固定的(24M / 25M).这种类型的clock就是fixed_rate clock.

fixed_rateclock具有固定的频率,不能开关、不能调整频率、不能选择parent、不需要提供任何的clk_ops回调函数,是最简单的一类clock.

如果要注册这种类型的clock,直接调用本API,传递相应的参数给本API即可.  API的细节如下:

头文件: include/linux/clk-provider.h

实现文件: drivers/clk/clk-fixed-rate.c

/*

* DOC: Basic clock implementations common to many platforms

*

* Each basic clock hardware type is comprised of a structure describing the

*clockhardware, implementations of the relevant callbacks in struct clk_ops,

*uniqueflags for that hardware type, a registration function and an

*alternativemacro for static initialization

*/

/**

*structclk_fixed_rate – fixed-rate clock

* @hw:     handle between common and hardware-specific interfaces

* @fixed_rate: constant frequency of clock

*/

structclk_fixed_rate{

structclk_hw hw;

unsigned longfixed_rate;

unsigned longfixed_accuracy;

u8flags;

};

extern const structclk_ops clk_fixed_rate_ops;

structclk*clk_register_fixed_rate(structdevice*dev, const char *name,

const char *parent_name, unsigned longflags,

unsigned longfixed_rate);

structclk*clk_register_fixed_rate_with_accuracy(structdevice*dev,

const char *name, const char *parent_name, unsigned longflags,

unsigned longfixed_rate, unsigned longfixed_accuracy);

voidof_fixed_clk_setup(structdevice_node*np);

若想注册一个fixed rate clock,除了你可以在自己的clock driver里面手动调用clk_register_fixed_rate之外,还有一种更简单的方式,那就是用DTS.

你可以在DTS里面用如下方式描述一个fixed rate clock:

26:osc32k:osc32k{

27:#clock–cells= <0>;

28:compatible = “fixed-clock“;

29:clock–frequency = <32768>;

clock-accuracy= xxxx;

clock-output-names= xxxx;

30:};

关键地方在于compatible一定要是”fixed-clock”

“drivers/clk/clk-fixed-rate.c”中的of_fixed_clk_setup会负责匹配这个compatible,这个C文件是clock子系统实现的.

of_fixed_clk_setup会解析3个参数:clock-frequency,clock-accuracy,clock-output-names

clock-frequency是必须的,另外2个参数可选.

参数解析完毕之后,会调用clk_register_fixed_rate_with_accuracy向系统注册一个clock.

clk_register_gate

这一类clock只可开关(会提供.enable/.disable回调),可使用下面接口注册:

头文件: include/linux/clk-provider.h

实现文件: drivers/clk/clk-gate.c

/**

*structclk_gate – gating clock

*

* @hw:     handle between common and hardware-specific interfaces

* @reg:    register controlling gate

* @bit_idx:    single bit controlling gate

* @flags:  hardware-specific flags

* @lock:   register lock

*

* Clock which can gate its output.  Implements .enable & .disable

*

* Flags:

* CLK_GATE_SET_TO_DISABLE – by default this clock sets the bit at bit_idx to

*  enablethe clock.  Setting this flag does the opposite: setting the bit

*  disablethe clock and clearing it enables the clock

* CLK_GATE_HIWORD_MASK – The gate settings are only inlower16-bit

*  ofthis register, and mask of gate bits are in higher 16-bit of this

*  register.  While setting the gate bits, higher 16-bit should also be

*  updatedto indicate changing gate bits.

*/

structclk_gate{

structclk_hw hw;

void__iomem*reg;

u8      bit_idx;

u8flags;

spinlock_t*lock;

};

#define CLK_GATE_SET_TO_DISABLEBIT(0)

#define CLK_GATE_HIWORD_MASKBIT(1)

extern const structclk_ops clk_gate_ops;

structclk*clk_register_gate(structdevice*dev, const char *name,

const char *parent_name, unsigned longflags,

void__iomem*reg,u8 bit_idx,

u8 clk_gate_flags,spinlock_t*lock);

voidclk_unregister_gate(structclk*clk);

clk_register_gate,它的参数列表如下:

name:clock的名称

parent_name:parent clock的名称,没有的话可留空

flags:参考3.3节 struct clk_hw结构体中对于flags的描述

reg:控制该clock开关的寄存器地址(虚拟地址)

bit_idx:  reg中,第几个bit是控制clock开/关的

clk_gate_flags:  gate clock特有的参数.其中一个可选值是CLK_GATE_SET_TO_DISABLE,它的意思是1表示开还是0表示开,类似于翻转位.

lock:如果clock开关时需要互斥,可提供一个spinlock.

clock子系统并没有定义类似fixed rate clock的DTS处理方式.

如果你想借用DTS,也很简单.如下:

ehrpwm1_tbclk:ehrpwm1_tbclk@44e10664{

#clock-cells = <0>;

compatible = “ti,gate-clock”;

clocks = ;

ti,bit–shift= <1>;

reg = <0x0664>;

};

注意它的compatible,自己实现一个driver,匹配这个compatible.然后在driver里面解析DTS相关参数并调用clk_register_gateAPI即可.

clk_register_divider/clk_register_divider_table

这一类clock可以设置分频值(因而会提供.recalc_rate/.set_rate/.round_rate回调),可通过下面两个接口注册:

头文件: include/linux/clk-provider.h

实现文件: drivers/clk/clk-divider.c

/**

*structclk_divider – adjustable divider clock

*

* @hw:     handle between common and hardware-specific interfaces

* @reg:    register containing the divider

* @shift:  shift to the divider bit field

* @width:  width of the divider bit field

* @table:  array of value/divider pairs, last entry should have div = 0

* @lock:   register lock

*

* Clock with an adjustable divider affecting its output frequency.  Implements

* .recalc_rate, .set_rate and .round_rate

*

* Flags:

* CLK_DIVIDER_ONE_BASED – by default the divisor is the value read from the

*  registerplus one.  If CLK_DIVIDER_ONE_BASED is set then the divider is

*  theraw value read from the register, with the value of zero considered

*  invalid, unless CLK_DIVIDER_ALLOW_ZERO is set.

* CLK_DIVIDER_POWER_OF_TWO – clock divisor is 2 raised to the value read from

*  thehardware register

* CLK_DIVIDER_ALLOW_ZERO – Allow zero divisors.  For dividers which have

*  CLK_DIVIDER_ONE_BASED set, it is possible to end up with a zero divisor.

*  Somehardware implementations gracefully handle this case and allow a

*  zerodivisor by not modifying their input clock

*  (divide by one / bypass).

* CLK_DIVIDER_HIWORD_MASK – The divider settings are only inlower16-bit

*  ofthis register, and mask of divider bits are in higher 16-bit of this

*  register.  While setting the divider bits, higher 16-bit should also be

*  updatedto indicate changing divider bits.

* CLK_DIVIDER_ROUND_CLOSEST – Makes the best calculated divider to be rounded

*  tothe closest integer instead of the up one.

* CLK_DIVIDER_READ_ONLY – The divider settings are preconfigured and should

*  notbe changed by the clock framework.

*/

structclk_divider{

structclk_hw   hw;

void__iomem*reg;

u8shift;

u8width;

u8flags;

const structclk_div_table*table;

spinlock_t*lock;

u32context;

};

#define CLK_DIVIDER_ONE_BASEDBIT(0)

#define CLK_DIVIDER_POWER_OF_TWOBIT(1)

#define CLK_DIVIDER_ALLOW_ZEROBIT(2)

#define CLK_DIVIDER_HIWORD_MASKBIT(3)

#define CLK_DIVIDER_ROUND_CLOSESTBIT(4)

#define CLK_DIVIDER_READ_ONLYBIT(5)

extern const structclk_ops clk_divider_ops;

unsigned longdivider_recalc_rate(structclk_hw*hw, unsigned longparent_rate,

unsigned intval, const structclk_div_table*table,

unsigned longflags);

longdivider_round_rate(structclk_hw*hw, unsigned longrate,

unsigned long *prate, const structclk_div_table*table,

u8width, unsigned longflags);

intdivider_get_val(unsigned longrate, unsigned longparent_rate,

const structclk_div_table*table,u8 width,

unsigned longflags);

structclk*clk_register_divider(structdevice*dev, const char *name,

const char *parent_name, unsigned longflags,

void__iomem*reg,u8 shift,u8 width,

u8 clk_divider_flags,spinlock_t*lock);

structclk*clk_register_divider_table(structdevice*dev, const char *name,

const char *parent_name, unsigned longflags,

void__iomem*reg,u8 shift,u8 width,

u8 clk_divider_flags, const structclk_div_table*table,

spinlock_t*lock);

voidclk_unregister_divider(structclk*clk);

clk_register_divider:该接口用于注册分频比规则的clock

reg:控制clock分频比的寄存器

shift:控制分频比的bit在寄存器中的偏移

width:控制分频比的bit位数,默认情况下,实际的divider值是寄存器值加1.

如果有其它例外,可使用下面的的flag指示。

clk_divider_flags:divider clock特有的flag,包括:

CLK_DIVIDER_ONE_BASED:

实际的divider值就是寄存器值(0是无效的,除非设置CLK_DIVIDER_ALLOW_ZERO flag)

CLK_DIVIDER_POWER_OF_TWO:

实际的divider值是寄存器值得2次方

CLK_DIVIDER_ALLOW_ZERO:

divider值可以为0(不改变,视硬件支持而定)

clk_register_divider_table:该接口用于注册分频比不规则的clock,和上面接口比较,差别在于divider值和寄存器值得对应关系由一个table决定. table的原型如下:

structclk_div_table{

unsigned intval;

unsigned intdiv;

};

val代表寄存器值,  div代表对应的分频值.

同样, clock子系统并没有实现DTS相关接口,不过你可以自己编写driver去解析DTS并调用API注册.

clk_register_mux

这一类clock可以选择多个parent,因为会实现.get_parent/.set_parent/.recalc_rate回调,可通过下面两个接口注册:

头文件: include/linux/clk-provider.h

实现文件: drivers/clk/clk-mux.c

/**

*structclk_mux – multiplexer clock

*

* @hw:     handle between common and hardware-specific interfaces

* @reg:    register controlling multiplexer

* @shift:  shift to multiplexer bit field

* @width:  width of mutliplexer bit field

* @flags:  hardware-specific flags

* @lock:   register lock

*

* Clock with multiple selectable parents.  Implements .get_parent, .set_parent

*and.recalc_rate

*

* Flags:

* CLK_MUX_INDEX_ONE – register index starts at 1, not 0

* CLK_MUX_INDEX_BIT – register index is a single bit (power of two)

* CLK_MUX_HIWORD_MASK – The mux settings are only inlower16-bit of this

*  register, and mask of mux bits are in higher 16-bit of this register.

*  Whilesetting the mux bits, higher 16-bit should also be updated to

*  indicatechanging mux bits.

* CLK_MUX_ROUND_CLOSEST – Use the parent rate that is closest to the desired

*  frequency.

*/

structclk_mux{

structclk_hw   hw;

void__iomem*reg;

u32*table;

u32mask;

u8shift;

u8flags;

spinlock_t*lock;

u8      saved_parent;

};

#define CLK_MUX_INDEX_ONEBIT(0)

#define CLK_MUX_INDEX_BITBIT(1)

#define CLK_MUX_HIWORD_MASKBIT(2)

#define CLK_MUX_READ_ONLYBIT(3)/* mux can’t be changed */

#define CLK_MUX_ROUND_CLOSESTBIT(4)

extern const structclk_ops clk_mux_ops;

extern const structclk_ops clk_mux_ro_ops;

structclk*clk_register_mux(structdevice*dev, const char *name,

const char **parent_names,u8 num_parents, unsigned longflags,

void__iomem*reg,u8 shift,u8 width,

u8 clk_mux_flags,spinlock_t*lock);

structclk*clk_register_mux_table(structdevice*dev, const char *name,

const char **parent_names,u8 num_parents, unsigned longflags,

void__iomem*reg,u8 shift,u32 mask,

u8 clk_mux_flags,u32*table,spinlock_t*lock);

voidclk_unregister_mux(structclk*clk);

clk_register_mux:该接口可注册mux控制比较规则的clock(类似divider clock)

parent_names:一个字符串数组,用于描述所有可能的parent clock

num_parents:parent clock的个数

reg、shift、width:选择parent的寄存器、偏移、宽度

clk_mux_flags:mux clock特有的flag

CLK_MUX_INDEX_ONE:寄存器值不是从0开始,而是从1开始

CLK_MUX_INDEX_BIT:寄存器值为2的幂

clk_register_mux_table:该接口通过一个table,注册mux控制不规则的clock,原理和divider clock类似,不再详细介绍

同样, clock子系统并没有实现DTS相关接口,不过你可以自己编写driver去解析DTS并调用API注册.

clk_register_fixed_factor

这一类clock具有固定的factor(即multiplier和divider),clock的频率是由parent clock的频率,乘以mul,除以div,多用于一些具有固定分频系数的clock.

由于parent clock的频率可以改变,因而fix factor clock也可以改变频率,因此也会提供.recalc_rate/.set_rate/.round_rate等回调.

可通过下面接口注册:

头文件: include/linux/clk-provider.h

实现文件: drivers/clk/clk-fixed-factor.c

void of_fixed_factor_clk_setup(structdevice_node*node);

/**

*structclk_fixed_factor – fixed multiplier and divider clock

*

* @hw:     handle between common and hardware-specific interfaces

* @mult:   multiplier

* @div:    divider

*

* Clock with a fixed multiplier and divider. The output frequency is the

*parentclock rate divided by div and multiplied by mult.

* Implements .recalc_rate, .set_rate and .round_rate

*/

structclk_fixed_factor{

structclk_hw   hw;

unsigned intmult;

unsigned intdiv;

};

extern structclk_ops clk_fixed_factor_ops;

structclk*clk_register_fixed_factor(structdevice*dev, const char *name,

const char *parent_name, unsigned longflags,

unsigned intmult, unsigned intdiv);

API参数比较简单,不多说了.

另外, clock子系统还提供了此种类型clock的DTS接口.

clk-fixed-factor.c中的of_fixed_factor_clk_setup函数会负责解析DTS.关于DTS的匹配规则和相关参数,自己看看源码吧.

clk_register_fractional_divider

这一类和divider clock很像,唯一的不同在于它可支持到更细的粒度 (可用小数表示分频因子) .例如 clk = parent / 1.5

API说明如下:

头文件: include/linux/clk-provider.h

实现文件: drivers/clk/clk-fractional-divider.c

/**

*structclk_fractional_divider – adjustable fractional divider clock

*

* @hw:     handle between common and hardware-specific interfaces

* @reg:    register containing the divider

* @mshift: shift to the numerator bit field

* @mwidth: width of the numerator bit field

* @nshift: shift to the denominator bit field

* @nwidth: width of the denominator bit field

* @lock:   register lock

*

* Clock with adjustable fractional divider affecting its output frequency.

*/

structclk_fractional_divider{

structclk_hw   hw;

void__iomem*reg;

u8mshift;

u32mmask;

u8nshift;

u32nmask;

u8flags;

spinlock_t*lock;

};

extern const structclk_ops clk_fractional_divider_ops;

structclk*clk_register_fractional_divider(structdevice*dev,

const char *name, const char *parent_name, unsigned longflags,

void__iomem*reg,u8 mshift,u8 mwidth,u8 nshift,u8 nwidth,

u8 clk_divider_flags,spinlock_t*lock);

clock子系统没有提供相关的DTS解析函数.

clk_register_composite

顾名思义,就是mux、divider、gate等clock的组合,可通过下面接口注册:

头文件: include/linux/clk-provider.h

实现文件: drivers/clk/clk-composite.c

/***

*structclk_composite – aggregate clock of mux, divider and gate clocks

*

* @hw:     handle between common and hardware-specific interfaces

* @mux_hw: handle between composite and hardware-specific mux clock

* @rate_hw:    handle between composite and hardware-specific rate clock

* @gate_hw:    handle between composite and hardware-specific gate clock

* @mux_ops:    clock ops for mux

* @rate_ops:   clock ops for rate

* @gate_ops:   clock ops for gate

*/

structclk_composite{

structclk_hw   hw;

structclk_ops  ops;

structclk_hw*mux_hw;

structclk_hw*rate_hw;

structclk_hw*gate_hw;

const structclk_ops*mux_ops;

const structclk_ops*rate_ops;

const structclk_ops*gate_ops;

};

structclk*clk_register_composite(structdevice*dev, const char *name,

const char **parent_names, intnum_parents,

structclk_hw*mux_hw, const structclk_ops*mux_ops,

structclk_hw*rate_hw, const structclk_ops*rate_ops,

structclk_hw*gate_hw, const structclk_ops*gate_ops,

unsigned longflags);

看着有点复杂,但理解了上面1~5类clock,这里就只剩下苦力了,耐心一点,就可以了.

另外, clock子系统没有提供相关的DTS解析函数.

clk_register_gpio_gate

把某个gpio当做一个gate clock.也就是说可以通过clock子系统来控制这个gpio开/关,也就是控制这个GPIO输出高/低电平.

某些情况下,有的模块需要通过某个GPIO来控制其使能/禁止,例如蓝牙模块.而且你也不需要向此模块提供时钟,模板本身就有晶振存在,可以自己给自己供时钟.

这个时候我们就可以把该GPIO抽象成gpio  gate  clock,借助clock子系统,来控制模块的enable/disable.

头文件: include/linux/clk-provider.h

实现文件: drivers/clk/clk-gpio-gate.c

/***

*structclk_gpio_gate – gpio gated clock

*

* @hw:     handle between common and hardware-specific interfaces

* @gpiod:  gpio descriptor

*

* Clock with a gpio control for enabling and disabling the parent clock.

* Implements .enable, .disable and .is_enabled

*/

structclk_gpio{

structclk_hw   hw;

structgpio_desc*gpiod;

};

extern const structclk_ops clk_gpio_gate_ops;

structclk*clk_register_gpio_gate(structdevice*dev, const char *name,

const char *parent_name, unsignedgpio, boolactive_low,

unsigned longflags);

void of_gpio_clk_gate_setup(structdevice_node*node);

API参数比较简单,不多说了.

另外, clock子系统还提供了此种类型clock的DTS接口.

xxx_unregister

上述xxx_register函数都有对应的xxx_unregister.

unregister的函数原型在上述代码中都有提及,这里不多说了,有兴趣可以自行阅读源代码.

clk_register_clkdev

头文件: include/linux/clkdev.h

实现文件: drivers/clk/clkdev.c

原型:intclk_register_clkdev(structclk*, const char *, const char *, …);

上述的clk_register_xxx接口会向clock子系统注册一个clock,并返回一个代表该clock的struct clk结构体.

clk_register_clkdev的作用就是把返回的这个struct clk添加到某个池子里面.

这个池子其实就是个链表啦,链表定义在clkdev.c里面.

池子里面主要是用name做为关键字,区分不同的clk.

当consumer端想要查询某个clk的时候,就会尝试从这个链表里面通过clock name去检索.

of_clk_add_provider

头文件: include/linux/clk-provider.h

实现文件: drivers/clk/clk.c

原型:

intof_clk_add_provider(structdevice_node*np,

structclk*(*clk_src_get)(structof_phandle_args*args,

void *data),

void *data);

此API的主要目的也是把得到的struct clk结构体添加到某个池子里面.

这个池子也是个链表,定义在clk.c里面.

与上面那个池子的不同之处在于,这里是用device_node做为关键字来区分不同的clk.

当consumer端想要查询某个clk的时候,会在DTS node里面通过clocks = 来引用某个clock.

通过引用的这个clock的phandle,就能找到对应的device_node.然后通过device_node就能从池子里面检索出需要的clk.

其实,这两种池子对应了我们的consumer端的两种方式:一种是通过name获取clk,不需要DTS;另外一种就是通过DTS.

随着Linux内核大力推行DTS,方式二会逐渐成为主流.

3.clockcore —如何管理Clocks

4.1简介

第3章我们描述了clock子系统的功能之一 :向底层的clock driver提供注册接口.

本章我们描述clock子系统的功能之二 :如何管理这些clocks.

当clock driver向子系统注册某一个clock的时候,子系统内部会创建一个数据结构来表示这个clock. 这个数据结构在前文提过,就是struct clk.

一个struct clk就对应一个clock.到目前为止,我们还没见过这个数据结构的庐山真面目呢!本章会围绕这个数据结构展开,充分理解它,你就里面本章了.

4.2主要数据结构

structclk

结构体定义在一个C文件里面 : drivers/clk/clk.c

struct  clk

Comment

struct clk_core*core

clk_core是clock核心层的一个私有数据结构,下面细述

const char *dev_id

使用该clk的device的name

const char *con_id

connection ID string on device

unsigned long min_rate

最小rate

unsigned long max_rate

最大rate

struct hlist_node clks_node

这个结构体有点颠覆本文的一个观点,那就是:一个struct clk结构体对应一个具体的clock.

仔细阅读源码后发现,原来每当某一个driver尝试获取某个clock的时候, clock子系统就会创建一个struct clk.

例如假设有个clock,名称是pll_clk.  GPIO, SPI, I2C这3个模块都是用此pll_clk做为工作时钟的.

那么clock子系统核心层会有一个clk_core用于描述pll_clk,每当GPIO/SPI/I2C的driver首次尝试获取pll_clk时, clock子系统就会创建一个struct clk结构体,并将创建的这个struct clk返回给对应的driver.该clk结构体的dev_id和con_id都是对应的driver指定的.

不过为了简便起见,我们在本文中还是暂定一个struct clk对应一个clock吧,理解起来不会有什么异常.

structclk_core

前文我们说过,一个struct clk就代表一个clock.一个clk_core与clock也是一一对应的关系.那它俩有什么区别呢?

struct clk更像是对外的接口,例如当provider向clock子系统注册时,它会得到一个struct clk*的返回结果;当consumer想要使用某个clock时,也会首先获取struct clk*这个结构体.

struct clk_core则是clock子系统核心层的一个私有数据结构,在核心层代描述某个具体的clock. provider或consumer不会触碰这个数据结构.

结构体定义在一个C文件里面 : drivers/clk/clk.c

structclk_core

Comment

const char*name

此clock的name,  provider在注册的时候会提供clock的name

const struct clk_ops*ops

操作此clock的ops,  provider在注册的时候会提供clock的ops

struct clk_hw*hw

provider端提供的clk_hw结构体

struct module*owner

struct clk_core*parent

一个clock可能有多个parent,但是同一时刻只能有一个parent有效.

这里指向有效的那个父时钟所对应的clk_core

const char**parent_names

所有可选的父时钟的names

struct clk_core**parents

所有可选的父时钟的clk_core

u8num_parents

有多少个parents

u8new_parent_index

你可以把所有的parents理解成一个数组, index就对应数组的某个元素

unsigned longrate

clock的rate

unsigned longreq_rate

consumer端要求的rate

unsigned longnew_rate

新的rate

struct clk_core*new_parent

新的parent

struct clk_core*new_child

新的child

unsigned longflags

此clock的flags,可选的flag在3.3节《struct clk_init_data》中有介绍

unsigned intenable_count

被使能的次数

unsigned intprepare_count

被prepare的次数

unsigned longaccuracy

此clock当前的accuracy, The clock accuracy is expressed in ppb (parts per billion)

intphase

此clock当前的phase,取值范围 0 – 359

struct hlist_headchildren

链表头,用于挂接本clock所有的children

struct hlist_nodechild_node

链表节点,用于把本clock挂接到对应的parent的children链表下

struct hlist_nodedebug_node

链表节点,与debugfs相关

struct hlist_headclks

一个struct clk_core可能对应多个struct clk,这个链表头挂接所有的clks.

关于struct clk_core和struct clk的关系,参见4.2节《struct clk》的说明

unsigned intnotifier_count

内核通知链机制相关.

当内核其它模块想知道某个clock的变化时 (例如频率等的改变),可以向此clock的通知链注册.这样当clock发生变化时,就会向通知链上的所有模块发送通知

notifier_count代表有多少个模块向本clock注册了

struct dentry*dentry

debugfs相关

struct krefref

引用计数

4.3 关键代码分析

clk_register

我们在3.4节介绍了clock子系统提供给provider端的总多clk_register_xxx函数.这些函数都是对clk_register的封装,基础都是clk_register.

因此我们本章只重点讲解这一个API,其它API有兴趣可以自己阅读源码.

在开始分析之前,结合之前讲解的内容,你能猜到clk_register会做哪些事情吗?

首先,它得创建struct clk和struct clk_core这两个数据结构.

然后,它得维护clk_core的树形关系,就像时钟数的硬件形态那样:

有一个或多个ROOT_CLOCK, ROOT_CLOCK没有parent,下面挂载的是children, children下面在挂载children.

为什么要维护这样的树形结构呢?因为我们在操作某个clk时,往往会跟它的parent有关:例如要使能某个clk,必须保证它的parent也使能;或者parent的rate改变了,那它的children的rate都有可能改变.因此clock子系统必须维护好树形结构,才能方便的处理这些相关性.

下面我们来看看代码:

实现文件: drivers/clk/clk.c

/**

* clk_register – allocate a new clock, register it and return an opaque cookie

* @dev: device that is registering this clock

* @hw: link to hardware-specific clock data

*

*clk_registeris the primary interface for populating the clock tree with new

*clocknodes.  It returns a pointer to the newly allocated struct clk which

* cannot be dereferenced by driver code but may be used in conjuction with the

*restof the clock API.  In the event of an error clk_register will return an

*errorcode; drivers must test for an error code after calling clk_register.

*/

structclk*clk_register(structdevice*dev, structclk_hw*hw)

{

inti,ret;

structclk_core*clk;

clk =kzalloc(sizeof(*clk),GFP_KERNEL);

if (!clk) {

pr_err(“%s: could not allocate clk\n”,__func__);

ret = –ENOMEM;

gotofail_out;

}

clk->name=kstrdup_const(hw->init->name,GFP_KERNEL);

if (!clk->name) {

pr_err(“%s: could not allocate clk->name\n”,__func__);

ret = –ENOMEM;

gotofail_name;

}

clk->ops=hw->init->ops;

if (dev&&dev->driver)

clk->owner=dev->driver->owner;

clk->hw=hw;

clk->flags=hw->init->flags;

clk->num_parents=hw->init->num_parents;

hw->core=clk;

/* allocate local copy in case parent_names is __initdata */

clk->parent_names=kcalloc(clk->num_parents, sizeof(char *),

GFP_KERNEL);

if (!clk->parent_names) {

pr_err(“%s: could not allocate clk->parent_names\n”,__func__);

ret = –ENOMEM;

gotofail_parent_names;

}

/* copy each string name in case parent_names is __initdata */

for (i= 0;inum_parents;i++) {

clk->parent_names[i] =kstrdup_const(hw->init->parent_names[i],

GFP_KERNEL);

if (!clk->parent_names[i]) {

pr_err(“%s: could not copy parent_names\n”,__func__);

ret = –ENOMEM;

gotofail_parent_names_copy;

}

}

INIT_HLIST_HEAD(&clk->clks);

hw->clk= __clk_create_clk(hw, NULL, NULL);

if (IS_ERR(hw->clk)) {

pr_err(“%s: could not allocate per-user clk\n”,__func__);

ret =PTR_ERR(hw->clk);

gotofail_parent_names_copy;

}

ret = __clk_init(dev,hw->clk);

if (!ret)

returnhw->clk;

__clk_free_clk(hw->clk);

hw->clk= NULL;

fail_parent_names_copy:

while (–i>= 0)

kfree_const(clk->parent_names[i]);

kfree(clk->parent_names);

fail_parent_names:

kfree_const(clk->name);

fail_name:

kfree(clk);

fail_out:

returnERR_PTR(ret);

}

EXPORT_SYMBOL_GPL(clk_register);

上面这段代码的逻辑比较简单:

首先,创建了clk_core结构体,然后用provider提供的clk_hw,填充clk_core中的各个字段.

然后,调用 __clk_create_clk,在__clk_create_clk里面会创建struct clk这个结构体并初始化其相关字段. clk_register会返回给provider一个struct clk*的结构体指针,这个clk*就是这里创建的.

下文我们会单独介绍__clk_create_clk这个函数.

最后,会调用__clk_init,在__clk_init里面会处理clk_core之间的树形结构关系.

下文我们会单独介绍__clk_init这个函数.

__clk_create_clk

这个API会创建一个struct clk结构体.

要理解此API的细节,首先得理清楚struct clk和struct clk_core的关系.

我们在4.2节《struct clk_core》中已经初步介绍了clk和clk_core的关系,可以回头看看.

这里我们在做进一步的说明.

还记得我们在《字符设备驱动》一文中介绍过的struct file和struct inode这两个结构体吗? inode在物理上代表一个文件,一个文件对应唯一一个inode; file则代表一个打开的文件,文件被打开几次,就会有几个file.

clk和clk_core的关系与上面很类型, clk_core代表一个硬件上的clock,一个clock对应唯一一个clk_core; 而clk则代表被使用的clock,例如GPIO driver想要获取某个clock,它会得到一个struct clk, SPI driver想要获取同一个clock,它也会得到一个struct clk,另外,当此clock的provider向clock子系统注册时, provider也会得到一个struct clk.

接下来我们看看代码细节吧:

structclk*__clk_create_clk(structclk_hw*hw, const char *dev_id,

const char *con_id)

{

structclk*clk;

/*Thisis to allow this function to be chained to others */

if (!hw||IS_ERR(hw))

return (structclk*)hw;

clk =kzalloc(sizeof(*clk),GFP_KERNEL);

if (!clk)

returnERR_PTR(-ENOMEM);

clk->core=hw->core;

clk->dev_id=dev_id;

clk->con_id=con_id;

clk->max_rate=ULONG_MAX;

clk_prepare_lock();

hlist_add_head(&clk->clks_node, &hw->core->clks);

clk_prepare_unlock();

returnclk;

}

逻辑比较简单:

首先创建struct clk结构体,然后初始化结构体的相关参数,最后把这个struct clk挂载到clk_core的clks链表头下面.

__clk_init

这个API主要是处理clock之间的树形关系.

当任何一个clock调用clk_register接口进行注册时, __clk_init都会负责把这个clock放在树形结构的恰当位置.

代码细节如下:

1: /**

2:  * __clk_init – initialize the data structures in a struct clk

3:  * @dev:    device initializing this clk, placeholder for now

4:  * @clk:    clk being initialized

5:  *

6:  * Initializes the lists in struct clk, queries the hardware for the

7:  * parent and rate and sets them both.

8:  */

9: int__clk_init(structdevice*dev, structclk*clk)

10: {

11:inti,ret= 0;

12:structclk*orphan;

13:structhlist_node*tmp2;

14:

15:if (!clk)

16:return –EINVAL;

17:

18:clk_prepare_lock();

19:

20:/* check to see if a clock with this name is already registered */

21:if (__clk_lookup(clk->name)) {

22:pr_debug(“%s: clk %s already initialized\n”,

23:__func__,clk->name);

24:ret= –EEXIST;

25:gotoout;

26:}

27:

28:/* check that clk_ops are sane.  See Documentation/clk.txt */

29:if (clk->ops->set_rate&&

30:!(clk->ops->round_rate&&clk->ops->recalc_rate)) {

31:pr_warning(“%s: %s must implement .round_rate & .recalc_rate\n”,

32:__func__,clk->name);

33:ret= –EINVAL;

34:gotoout;

35:}

36:

37:if (clk->ops->set_parent&& !clk->ops->get_parent) {

38:pr_warning(“%s: %s must implement .get_parent & .set_parent\n”,

39:__func__,clk->name);

40:ret= –EINVAL;

41:gotoout;

42:}

43:

44:/* throw a WARN if any entries in parent_names are NULL */

45:for (i= 0;inum_parents;i++)

46:WARN(!clk->parent_names[i],

47:“%s: invalid NULL in %s’s .parent_names\n”,

48:__func__,clk->name);

49:

50:/*

51:      * Allocate an array of struct clk *’s to avoid unnecessary string

52:      * look-ups of clk’s possible parents.  This can fail for clocks passed

53:      * in to clk_init during early boot; thus any access to clk->parents[]

54:      * must always check for a NULL pointer and try to populate it if

55:      * necessary.

56:      *

57:      * If clk->parents is not NULL we skip this entire block.  This allows

58:      * for clock drivers to statically initialize clk->parents.

59:      */

60:if (clk->num_parents> 1 && !clk->parents) {

61:clk->parents= kzalloc((sizeof(structclk*) *clk->num_parents),

62:GFP_KERNEL);

63:/*

64:          * __clk_lookup returns NULL for parents that have not been

65:          * clk_init’d; thus any access to clk->parents[] must check

66:          * for a NULL pointer.  We can always perform lazy lookups for

67:          * missing parents later on.

68:          */

69:if (clk->parents)

70:for (i= 0;inum_parents;i++)

71:clk->parents[i] =

72:__clk_lookup(clk->parent_names[i]);

73:}

74:

75:clk->parent=__clk_init_parent(clk);

76:

77:/*

78:      * Populate clk->parent if parent has already been __clk_init’d.  If

79:      * parent has not yet been __clk_init’d then place clk in the orphan

80:      * list.  If clk has set the CLK_IS_ROOT flag then place it in the root

81:      * clk list.

82:      *

83:      * Every time a new clk is clk_init’d then we walk the list of orphan

84:      * clocks and re-parent any that are children of the clock currently

85:      * being clk_init’d.

86:      */

87:if (clk->parent)

88:hlist_add_head(&clk->child_node,

89:&clk->parent->children);

90:else if (clk->flags&CLK_IS_ROOT)

91:hlist_add_head(&clk->child_node, &clk_root_list);

92:else

93:hlist_add_head(&clk->child_node, &clk_orphan_list);

94:

95:/*

96:      * Set clk’s rate.  The preferred method is to use .recalc_rate.  For

97:      * simple clocks and lazy developers the default fallback is to use the

98:      * parent’s rate.  If a clock doesn’t have a parent (or is orphaned)

99:      * then rate is set to zero.

100:      */

101:if (clk->ops->recalc_rate)

102:clk->rate=clk->ops->recalc_rate(clk->hw,

103:__clk_get_rate(clk->parent));

104:else if (clk->parent)

105:clk->rate=clk->parent->rate;

106:else

107:clk->rate= 0;

108:

109:/*

110:      * walk the list of orphan clocks and reparent any that are children of

111:      * this clock

112:      */

113:hlist_for_each_entry_safe(orphan,tmp2, &clk_orphan_list,child_node) {

114:if (orphan->ops->get_parent) {

115:i=orphan->ops->get_parent(orphan->hw);

116:if (!strcmp(clk->name,orphan->parent_names[i]))

117:__clk_reparent(orphan,clk);

118:continue;

119:}

120:

121:for (i= 0;inum_parents;i++)

122:if (!strcmp(clk->name,orphan->parent_names[i])) {

123:__clk_reparent(orphan,clk);

124:break;

125:}

126:}

127:

128:/*

129:      * optional platform-specific magic

130:      *

131:      * The .init callback is not used by any of the basic clock types, but

132:      * exists for weird hardware that must perform initialization magic.

133:      * Please consider other ways of solving initialization problems before

134:      * using this callback, asit’suse is discouraged.

135:      */

136:if (clk->ops->init)

137:clk->ops->init(clk->hw);

138:

139:clk_debug_register(clk);

140:

141:out:

142:clk_prepare_unlock();

143:

144:returnret;

145: }

这一段代码的逻辑很复杂,主要做的事情如下:

20~26行,以clock name为参数,调用__clk_lookup接口,查找是否已有相同name的clock注册,如果有,则返回错误。由此可以看出,clock framework以name唯一识别一个clock,因此不能有同名的clock存在

28~42行,检查clk ops的完整性,例如:如果提供了set_rate接口,就必须提供round_rate和recalc_rate接口;如果提供了set_parent,就必须提供get_parent

50~73行,分配一个struct clk *类型的数组,缓存该clock的parents clock。具体方法是根据parents_name,查找相应的struct clk指针

75行,获取当前的parent clock,并将其保存在parent指针中。具体可参考下面“说明2”

77~93行,根据该clock的特性,将它添加到clk_root_list、clk_orphan_list或者parent->children三个链表中的一个,具体请参考下面“说明1”

95~107行,计算clock的初始rate,具体请参考下面“说明3”

109~126行,尝试reparent当前所有的孤儿(orphan)clock,具体请参考下面“说明4”

128~137行,如果clock ops提供了init接口,执行之(由注释可知,kernel不建议提供init接口)

说明1:clock的管理和查询

clock framework有2条全局的链表:clk_root_list和clk_orphan_list。所有设置了CLK_IS_ROOT属性的clock都会挂在clk_root_list中。其它clock,如果有valid的parent,则会挂到parent的“children”链表中,如果没有valid的parent,则会挂到clk_orphan_list中。

查询时(__clk_lookup接口做的事情),依次搜索:

clk_root_list–>root_clk–>children–>child’s children

clk_orphan_list–>orphan_clk–>children–>child’s children

即可

说明2:当前parent clock的选择(__clk_init_parent)

对于没有parent,或者只有1个parent的clock来说,比较简单,设置为NULL,或者根据parent name获得parent的struct clk指针接。

对于有多个parent的clock,就必须提供.get_parent ops,该ops要根据当前硬件的配置情况,例如寄存器值,返回当前所有使用的parent的index(即第几个parent)。然后根据index,取出对应parent clock的struct clk指针,作为当前的parent。

说明3:clock的初始rate计算

对于提供.recalc_rate ops的clock来说,优先使用该ops获取初始的rate。如果没有提供,退而求其次,直接使用parent clock的rate。最后,如果该clock没有parent,则初始的rate只能选择为0

.recalc_rate ops的功能,是以parent clock的rate为输入参数,根据当前硬件的配置情况,如寄存器值,计算获得自身的rate值

说明4:orphan clocks的reparent

有些情况下,child clock会先于parent clock注册,此时该child就会成为orphan clock,被收养在clk_orphan_list中。

而每当新的clock注册时,kernel都会检查这个clock是否是某个orphan的parent,如果是,就把这个orphan从clk_orphan_list中移除,放到新注册的clock的怀抱。这就是reparent的功能,它的处理逻辑是:

遍历orphan list,如果orphan提供了.get_parent ops,则通过该ops得到当前parent的index,并从parent_names中取出该parent的name,然后和新注册的clock name比较,如果相同,呵呵,找到parent了,执行__clk_reparent,进行后续的操作

如果没有提供.get_parent ops,只能遍历自己的parent_names,检查是否有和新注册clock匹配的,如果有,执行__clk_reparent,进行后续的操作

__clk_reparent会把这个orphan从clk_orphan_list中移除,并挂到新注册的clock上。然后调用__clk_recalc_rates,重新计算自己以及自己所有children的rate。计算过程和上面的clock rate设置类似

4.clockconsumer —如何使用Clocks

5.1简介

clock子系统管理clocks的最终目的,是让device driver可以方便的获取并使用这些clocks.

我们知道clock子系统用一个struct clk结构体来抽象某一个clock.

当device driver要操作某个clock时,它需要做两件事情:

首先,获取clock.也叫clk_get.

然后,操作这个clock.如 clk_prepare/clk_enable/clk_disable/clk_set_rate/…

举个例子,以GPIO控制器为例.从硬件的角度来说, GPIO控制器需要工作时钟.因此,在GPIO控制器的platform_driver的probe函数里面,就需要操作这个时钟.

操作的第一步是获取clock,获取clock有两种方式:

第一种方式是直接通过name来获取,例如假设已知GPIO控制器的工作时钟的name是”gpio_clk”,那么我们就可以在probe函数里面通过clk_get(&device, “gpio_clk”) 这种方式获取.

另外一种方式是说,对于GPIO控制器来讲,我们会有一个platform_device,在里面描述GPIO控制器的资源,也就是寄存器地址,中断号什么的. GPIO控制器的工作时钟也算一种资源了,我们能否在platform_device里面一并描述这个资源呢?

答案是可以. platform_device现在都是在DTS中描述的,因此这个GPIO控制器的DTS可以这样写:

gpio :gpio-controller@xxxx {

compatible=“yyyy”;

reg= ;

……

clocks = ;  /*指明/引用某一个clock*/

}

我们用clocks这个property来描述时钟资源.

在对应的platform_driver的probe函数里面,我们就可以通过clk_get(&device,NULL)这种方式来获取所需的clock.

这种方式下,我们只需要知道platform_device,然后就能通过它找到对应的DTS node,然后就能通过DTS  node的”clocks”property获取到对应的时钟资源.

获取到clk之后,就可以通过clock子系统提供的API来操作clk了.

clock子系统提供给consumer端的APIs都定义在头文件 include/linux/clk.h里面.

接下来的章节,我们会分两部分来介绍这些APIs.

5.2节介绍获取clk相关的APIs.

5.3节介绍操作clk相关的APIs.

5.2APIs– 获取clk

头文件: include/linux/clk.h

实现文件: drivers/clk/clkdev.c

最主要的两个API:

structclk*clk_get(structdevice*dev, const char *id);

structclk*devm_clk_get(structdevice*dev, const char *id);

devm_clk_get是devm版本的clk_get.用于自动释放资源,我们在《设备模型》一文中有过介绍,这里不多说了.

除了这两个用的最多的API之外,还有一些其他的API,如下:

structclk*clk_get_sys(const char *dev_id, const char *con_id)

structclk*of_clk_get(structdevice_node*np, intindex);

structclk*of_clk_get_by_name(structdevice_node*np, const char *name);

structclk*of_clk_get_from_provider(structof_phandle_args*clkspec);

clk_get相当于一个总逻辑,它会根据不同的情况调用上述这些API,在实际代码中,基本上我们只会用到clk_get或者devm_clk_get.

接下来,我们会仔细看看clk_get的内部逻辑.

clk_get / devm_clk_get

头文件: include/linux/clk.h

实现文件: drivers/clk/clkdev.c

原型:structclk*clk_get(structdevice*dev, const char *id);

该API的主要作用就是根据参数,从clock子系统中获取一个clk给到consumer,然后consumer就可以操作该clk了.因此该API的返回值就是用于描述某个clock的数据结构:structclk.

代码细节如下:

structclk*clk_get(structdevice*dev, const char *con_id)

{

const char *dev_id=dev?dev_name(dev) : NULL;

structclk*clk;

if (dev) {

clk =__of_clk_get_by_name(dev->of_node,dev_id,con_id);

if (!IS_ERR(clk) ||PTR_ERR(clk) == –EPROBE_DEFER)

returnclk;

}

returnclk_get_sys(dev_id,con_id);

}

EXPORT_SYMBOL(clk_get);

如果你已经充分理解了2.3节的那个系统框图,那么此处的逻辑就很简单了:

如果dev不为空,那么就以dev->of_node为参数,调用of_XXX那一套,从LIST_HEAD(of_clk_providers)这个池子里面查询某个clk.

如果查询到了,则返回该clk.这种情况其实对应5.1节中描述的DTS node方式.

如果上面没有获取到clk,则调用clk_get_sys,从LIST_HEAD(clocks)这个池子里面查询clk.查询的关键字是con_id,其实就是clock的name.

5.3APIs– 操作clk

头文件: include/linux/clk.h

实现文件: drivers/clk/clk.c

1: intclk_prepare(structclk*clk)

2: voidclk_unprepare(structclk*clk)

3:

4: staticinlineintclk_enable(structclk*clk)

5: staticinlinevoidclk_disable(structclk*clk)

6:

7: staticinlineunsigned longclk_get_rate(structclk*clk)

8: staticinlineintclk_set_rate(structclk*clk, unsigned longrate)

9: staticinlinelongclk_round_rate(structclk*clk, unsigned longrate)

10:

11: staticinlineintclk_set_parent(structclk*clk, structclk*parent)

12: staticinlinestructclk*clk_get_parent(structclk*clk)

13:

14: staticinlineintclk_prepare_enable(structclk*clk)

15: staticinlinevoidclk_disable_unprepare(structclk*clk)

clk_enable/clk_disable:启动/停止clock.不会睡眠

clk_prepare/clk_unprepare:启动clock前的准备工作/停止clock后的善后工作.可能会睡眠

clk_get_rate/clk_set_rate/clk_round_rate:clock频率的获取和设置,其中clk_set_rate可能会不成功(例如没有对应的分频比),此时会返回错误.如果要确保设置成功,则需要先调用clk_round_rate接口,得到和需要设置的rate比较接近的那个值

clk_set_parent/clk_get_parent:获取/选择clock的parent clock

clk_prepare_enable:将clk_prepare和clk_enable组合起来,一起调用

clk_disable_unprepare:将clk_disable和clk_unprepare组合起来,一起调用

prepare/unprepare,enable/disable的说明:

这两套API的本质,是把clock的启动/停止分为atomic和non-atomic两个阶段,以方便实现和调用。

因此上面所说的“不会睡眠/可能会睡眠”,有两个角度的含义:

一是告诉底层的clock driver,请把可能引起睡眠的操作,放到prepare/unprepare中实现,一定不能放到enable/disable中

二是提醒上层使用clock的driver,调用prepare/unprepare接口时可能会睡眠哦,千万不能在atomic上下文(例如中断处理中)调用哦,而调用enable/disable接口则可放心。

另外,clock的开关为什么需要睡眠呢?这里举个例子,例如enable PLL clk,在启动PLL后,需要等待它稳定。而PLL的稳定时间是很长的,这段时间要把CPU交出(进程睡眠),不然就会浪费CPU。

最后,为什么会有合在一起的clk_prepare_enable/clk_disable_unprepare接口呢?如果调用者能确保是在non-atomic上下文中调用,就可以顺序调用prepare/enable、disable/unprepared,为了简单,framework就帮忙封装了这两个接口。

5.4其它APIs

头文件: include/linux/clk.h

实现文件: drivers/clk/clk.c

1: intclk_notifier_register(structclk*clk, structnotifier_block*nb);

2: intclk_notifier_unregister(structclk*clk, structnotifier_block*nb);

这两个API与内核提供的通知链机制有关.

这两个notify接口,用于注册/注销 clock rate改变的通知.

例如某个driver关心某个clock,期望这个clock的rate改变时,通知到自己,就可以注册一个notify.

这篇关于linux 庐山培训,Linux的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/403412

相关文章

linux-基础知识3

打包和压缩 zip 安装zip软件包 yum -y install zip unzip 压缩打包命令: zip -q -r -d -u 压缩包文件名 目录和文件名列表 -q:不显示命令执行过程-r:递归处理,打包各级子目录和文件-u:把文件增加/替换到压缩包中-d:从压缩包中删除指定的文件 解压:unzip 压缩包名 打包文件 把压缩包从服务器下载到本地 把压缩包上传到服务器(zip

Linux 网络编程 --- 应用层

一、自定义协议和序列化反序列化 代码: 序列化反序列化实现网络版本计算器 二、HTTP协议 1、谈两个简单的预备知识 https://www.baidu.com/ --- 域名 --- 域名解析 --- IP地址 http的端口号为80端口,https的端口号为443 url为统一资源定位符。CSDNhttps://mp.csdn.net/mp_blog/creation/editor

【Python编程】Linux创建虚拟环境并配置与notebook相连接

1.创建 使用 venv 创建虚拟环境。例如,在当前目录下创建一个名为 myenv 的虚拟环境: python3 -m venv myenv 2.激活 激活虚拟环境使其成为当前终端会话的活动环境。运行: source myenv/bin/activate 3.与notebook连接 在虚拟环境中,使用 pip 安装 Jupyter 和 ipykernel: pip instal

Linux_kernel驱动开发11

一、改回nfs方式挂载根文件系统         在产品将要上线之前,需要制作不同类型格式的根文件系统         在产品研发阶段,我们还是需要使用nfs的方式挂载根文件系统         优点:可以直接在上位机中修改文件系统内容,延长EMMC的寿命         【1】重启上位机nfs服务         sudo service nfs-kernel-server resta

【Linux 从基础到进阶】Ansible自动化运维工具使用

Ansible自动化运维工具使用 Ansible 是一款开源的自动化运维工具,采用无代理架构(agentless),基于 SSH 连接进行管理,具有简单易用、灵活强大、可扩展性高等特点。它广泛用于服务器管理、应用部署、配置管理等任务。本文将介绍 Ansible 的安装、基本使用方法及一些实际运维场景中的应用,旨在帮助运维人员快速上手并熟练运用 Ansible。 1. Ansible的核心概念

Linux服务器Java启动脚本

Linux服务器Java启动脚本 1、初版2、优化版本3、常用脚本仓库 本文章介绍了如何在Linux服务器上执行Java并启动jar包, 通常我们会使用nohup直接启动,但是还是需要手动停止然后再次启动, 那如何更优雅的在服务器上启动jar包呢,让我们一起探讨一下吧。 1、初版 第一个版本是常用的做法,直接使用nohup后台启动jar包, 并将日志输出到当前文件夹n

[Linux]:进程(下)

✨✨ 欢迎大家来到贝蒂大讲堂✨✨ 🎈🎈养成好习惯,先赞后看哦~🎈🎈 所属专栏:Linux学习 贝蒂的主页:Betty’s blog 1. 进程终止 1.1 进程退出的场景 进程退出只有以下三种情况: 代码运行完毕,结果正确。代码运行完毕,结果不正确。代码异常终止(进程崩溃)。 1.2 进程退出码 在编程中,我们通常认为main函数是代码的入口,但实际上它只是用户级

【Linux】应用层http协议

一、HTTP协议 1.1 简要介绍一下HTTP        我们在网络的应用层中可以自己定义协议,但是,已经有大佬定义了一些现成的,非常好用的应用层协议,供我们直接使用,HTTP(超文本传输协议)就是其中之一。        在互联网世界中,HTTP(超文本传输协议)是一个至关重要的协议,他定义了客户端(如浏览器)与服务器之间如何进行通信,以交换或者传输超文本(比如HTML文档)。

如何编写Linux PCIe设备驱动器 之二

如何编写Linux PCIe设备驱动器 之二 功能(capability)集功能(capability)APIs通过pci_bus_read_config完成功能存取功能APIs参数pos常量值PCI功能结构 PCI功能IDMSI功能电源功率管理功能 功能(capability)集 功能(capability)APIs int pcie_capability_read_wo