一、简介

在上一篇文章《字符设备》中,我们学习了驱动开发的基础框架。掌握了这个框架后,我们只需要添加对应的代码就可以开发驱动。不过,这种方式存在一个问题:如果我们将硬件的信息都写进驱动代码中,那么每次硬件的引脚接口发生变化,驱动代码就需要重新修改,这显然是不合理的。



那么,有没有合适的解决方案呢?答案是肯定的,Linux引入了设备驱动模型分层的概念, 将我们编写的驱动代码分成了两块:设备与驱动。

  • 设备负责提供硬件资源;
  • 驱动代码负责去使用这些设备提供的硬件资;
  • 总线将它们联系起来;

这样子就构成以下图形中的关系。



针对Linux下的这种分层理念,我们在学习驱动模型之前要先了解以下几个概念:

  • 设备(device):挂载在某个总线上的物理设备;
  • 驱动(driver):与特定设备相关的软件,负责初始化该设备以及提供一些操作该设备的操作方式;
  • 总线(bus):负责管理挂载对应总线的设备以及驱动;
  • 类(class):对于具有相同功能的设备,归结到一种类别,进行分类管理;

二、驱动模型

2.1、总线

通过上面的图我们也可以看出来,在Linux的设备驱动模型中,总线是整个模型的核心,负责将设备和驱动联系起来。

驱动模型中的总线可以是真实存在的物理总线(USB总线、I2C总线等),也可以是为了驱动模型架构设计出的虚拟总线(Platform总线)。为此Linux设备驱动模型都将围绕“总线-设备-驱动”来展开,因为符合Linux设备驱动模型的设备与驱动都是必须挂载在一个总线上的。





每条总线管理着两条链表---设备链表和驱动链表。设备链表用于管理该总线上所有的设备实例,驱动链表用于管理该总线上所有已注册的驱动程序。





当我们向系统的某条总线上注册一个驱动时,它会向Linux内核里该总线的驱动链表中插入我们的新驱动;同样,当我们向系统总线上接入一个新设备时,也会向该总线的设备链表中插入我们的新设备。在插入的同时该总线的核心层会执行bus_type结构体中的match()方法对新插入的设备/驱动进行匹配,如USB总线通过PID(产品ID)和VID(厂商ID)来匹配,而Platform总线则通过compatible字符串进行匹配。



这样,在Linux内核中无论是设备先出现还是驱动先出现,总能彼此找到对方。当Linux内核的总线核心层在匹配成功后,将会调用device_driver结构体中probe函数,并且在移出设备或驱动时,会调用device_driver结构体中remove函数,分别完成驱动的注册和移除工作。





最后,我们再来看一下总线在内核中的表现方式---bus_type结构体(linux/device.h)

struct bus_type {
const char *name;
const struct attribute_group **bus_groups; // 为bus目录创建属性
const struct attribute_group **dev_groups; // 为device目录创建属性
const struct attribute_group **drv_groups; // 为driver目录创建属性

int (*match)(struct device *dev, struct device_driver *drv); // 匹配函数
int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
int (*probe)(struct device *dev);
int (*remove)(struct device *dev);

int (*suspend)(struct device *dev, pm_message_t state);
int (*resume)(struct device *dev);

const struct dev_pm_ops *pm;

struct subsys_private *p; // 私有数据
};
  • name :指定总线的名称,当新注册一种总线类型时,会在/sys/bus目录创建一个新的目录,目录名就是该参数的值;
  • drv_groups、dev_groups、bus_groups :分别表示驱动、设备以及总线的属性;
  • match :当向总线注册一个新的设备或者是新的驱动时,会调用该回调函数。该回调函数主要负责判断是否有注册了的驱动适合新的设备,或者新的驱动能否驱动总线上已注册但没有驱动匹配的设备;
  • uevent :总线上的设备发生添加、移除或者其它动作时,就会调用该函数,来通知驱动做出相应的对策;
  • probe :当总线将设备以及驱动相匹配之后,执行该回调函数,最终会调用驱动提供的probe函数;
  • remove :当设备从总线移除时,调用该回调函数;
  • p :该结构体用于存放特定的私有数据,其成员klist_devices和klist_drivers记录了挂载在该总线的设备和驱动;

在实际编写linux驱动模块时,Linux内核已经为我们写好了大部分总线驱动,正常情况下我们一般不会去注册一个新的总线, 内核中提供了bus_register函数来注册总线,以及bus_unregister函数来注销总线,其函数原型如下:

int bus_register(struct bus_type *bus);  //成功返回0,失败返回负数
void bus_unregister(struct bus_type *bus);

当我们成功注册总线时,会在/sys/bus/目录下创建一个新目录,目录名为我们新注册的总线名。bus目录中包含了当前系统中已经注册了的所有总线,例如i2c,spi,platform等。我们看到每个总线目录都拥有两个子目录devices和drivers, 分别记录着挂载在该总线的所有设备以及驱动。

2.2、设备

在Linux中,一切都是以文件的形式存在, 设备也不例外。/sys/devices目录记录了系统中所有设备,实际上在sys目录下所有设备文件最终都会指向该目录对应的设备文件。

然后我们再来看一下设备在内核中的表现方式---device结构体(linux/device.h)

struct device {
const char *init_name;
struct device *parent;
struct kobject kobj;
struct bus_type *bus;
struct device_driver *driver;
void *platform_data;
void *driver_data;
struct device_node *of_node;
dev_t devt;
struct class *class;
void (*release)(struct device *dev);
const struct attribute_group **groups; /* optional groups */
struct device_private *p;
};
  • init_name:指定该设备的名称,总线匹配时,一般会根据比较名字来进行配对;
  • parent:表示该设备的父对象;
  • bus:表示该设备依赖于哪个总线,当我们注册设备时,内核便会将该设备注册到对应的总线;
  • of_node :存放设备树中匹配的设备节点。当内核使能设备树,总线负责将驱动的of_match_table以及设备树的compatible属性进行比较之后,将匹配的节点保存到该变量;
  • driver_data:驱动层可通过dev_set/get_drvdata函数来获取该成员;
  • class :指向了该设备对应类,我们可以在/sys/class目录下对应的类找到该设备,如input、leds、pwm等目录;
  • release:回调函数,当设备被注销时,会调用该函数。如果我们没定义该函数时,移除设备时,会提示“Device ‘xxxx’ does not have a release() function, it is broken and must be fixed”的错误;
  • group:指向struct attribute_group类型的指针,指定该设备的属性;

内核也提供相关的API来注册和注销设备,如下所示:

int device_register(struct device *dev);  //成功返回0,失败返回负数
void device_unregister(struct device *dev);

2.3、驱动

设备能否正常工作,取决于驱动。驱动需要告诉内核, 自己可以驱动哪些设备,如何初始化设备。接下来,我们再来看一下驱动在内核中的表示方式---device_driver结构体(linux/device.h)

struct device_driver {
const char *name;
struct bus_type *bus; struct module *owner;
const char *mod_name; /* used for built-in modules */ bool suppress_bind_attrs; /* disables bind/unbind via sysfs */ const struct of_device_id *of_match_table;
const struct acpi_device_id *acpi_match_table; int (*probe) (struct device *dev);
int (*remove) (struct device *dev); const struct attribute_group **groups;
struct driver_private *p; };
  • name :指定驱动名称,总线进行匹配时,利用该成员与设备名进行比较;
  • bus :表示该驱动依赖于哪个总线,内核需要保证在驱动执行之前,对应的总线能够正常工作;
  • owner :表示该驱动的拥有者,一般设置为THIS_MODULE;
  • suppress_bind_attrs:布尔量,用于指定是否通过sysfs导出bind与unbind文件,bind与unbind文件是驱动用于绑定/解绑关联的设备;
  • of_match_table :指定该驱动支持的设备类型。当内核使能设备树时,会利用该成员与设备树中的compatible属性进行比较;
  • remove :当设备从操作系统中拔出或者是系统重启时,会调用该回调函数;
  • probe :当驱动以及设备匹配后,会执行该回调函数,对设备进行初始化。通常的代码,都是以main函数开始执行的,但是在内核的驱动代码,都是从probe函数开始的;
  • group :指向struct attribute_group类型的指针,指定该驱动的属性;

内核提供了函数来注册/注销驱动,成功注册的驱动会记录在/sys/bus//drivers目录, 函数原型如下所示:

int driver_register(struct device_driver *drv);  //成功返回0,失败返回负数
void driver_unregister(struct device_driver *drv);

三、设备树

3.1、设备树简介

上面我们一直在强调的就是设备驱动分层,所以为了将设备和驱动剥离开来,社区引入了设备树(Device Tree)这种数据结构。它用来描述硬件设备的信息,特别是硬件设备与操作系统如何交互的方式。设备树的作用有:

  • 描述硬件布局:设备树可以描述CPU、内存、外设、GPIO等硬件资源的位置和属性;
  • 硬件与驱动分离:设备树将硬件信息从内核代码中分离出去,使得内核能够更加通用,不依赖特定硬件配置;
  • 动态配置:设备树支持热插拔设备和动态硬件配置,内核可以在启动时解析设备树来获取硬件信息;

设备树通常由设备树源文件(.dts文件)和编译后的设备树二进制文件(.dtb文件)组成,它在描述硬件资源时有两个特点:

  • 树的主干就是系统总线,在设备树里面称为“根节点”。IIC控制器、GPIO控制器、SPI控制器等都是接到系统主线上的分支,在设备树里称为“根节点的子节点”;
  • 设备树可以像头文件(.h文件)那样,一个设备树文件引用另一个设备树文件,这样可以实现“代码”的重用。例如多个硬件平台都使用I.MX6ULL作为主控芯片,那么我们可以将I.MX6ULL芯片的硬件资源写到一个单独的设备树文件里面(一般使用.dtsi后缀),其他设备树文件直接使用#include xxx引用即可;

3.2、设备树格式

我们已经了解了设备树是什么,接下来,我们再来一起看一看设备树长什么样。在Linux内核源码中,设备树文件存放在arch/arm/boot/dts路径中,以我使用的NXP IMX6ULL开发板为例,查看对应的igbboard-imx6ull.dts文件,内容如下:

点击查看代码
#include "imx6ull.dtsi"     /*头文件*/

/*设备树根节点*/
/ {
model = "LingYun IoT System Studio IoT Gateway Kits Board Based on i.MX6ULL"; /*model属性,用于指定设备的制造商和型号*/
compatible = "lingyun,igkboard-imx6ull", "fsl,imx6ull"; /*compatible属性,系统用来决定绑定到设备驱动的关键,用来查找节点的方法之一*/ /*根节点的子节点*/
chosen {
stdout-path = &uart1;
}; /*根节点的子节点*/
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x20000000>;
}; /*根节点的子节点*/
reserved-memory {
#address-cells = <1>;
#size-cells = <1>;
ranges;
linux,cma {
compatible = "shared-dma-pool";
reusable;
size = <0xa000000>;
linux,cma-default;
};
}; /*根节点的子节点*/
leds {
compatible = "gpio-leds";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_gpio_leds>;
status = "okay"; sysled {
lable = "sysled";
gpios = <&gpio4 16 GPIO_ACTIVE_HIGH>;
linux,default-trigger = "heartbeat";
default-state = "off";
};
};
/*-------------以下内容省略-------------*/ }; /*设备树节点追加内容*/
/*+--------------+
| Misc Modules |
+--------------+*/
/*而是向原有节点追加内容*/
&uart1 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_uart1>;
status = "okay";
}; &pwm1 { /* backlight */
#pwm-cells = <2>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_pwm1>;
status = "okay";
};
... ...

总体上,设备树的源码分为三部分:

  • 头文件。设备树是可以像C语言那样使用“#include”引用“.h”后缀的头文件,也可以引用设备树“.dtsi”后缀的头文件。
#include "imx6ull.dtsi"     /*头文件*/
  • 设备树节点。设备树给我们最直观的感受是它由一些嵌套的大括号“{}”组成, 每一个“{}”都是一个“节点”。“/ {…};”表示“根节点”,每一个设备树只有一个根节点。在根节点内部的chosen{...}、memory{...}等字符,都是根节点的子节点。如果我们想要添加一个自定义的节点(如leds),需要添加到根节点里。
/*设备树根节点*/
/ {
model = "LingYun IoT System Studio IoT Gateway Kits Board Based on i.MX6ULL"; /*model属性,用于指定设备的制造商和型号*/
compatible = "lingyun,igkboard-imx6ull", "fsl,imx6ull"; /*compatible属性,系统用来决定绑定到设备驱动的关键,用来查找节点的方法之一*/ /*根节点的子节点*/
chosen {
stdout-path = &uart1;
}; /*根节点的子节点*/
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x20000000>;
}; /*根节点的子节点*/
reserved-memory {
#address-cells = <1>;
#size-cells = <1>;
ranges;
linux,cma {
compatible = "shared-dma-pool";
reusable;
size = <0xa000000>;
linux,cma-default;
};
}; /*根节点的子节点*/
leds {
compatible = "gpio-leds";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_gpio_leds>;
status = "okay"; sysled {
lable = "sysled";
gpios = <&gpio4 16 GPIO_ACTIVE_HIGH>;
linux,default-trigger = "heartbeat";
default-state = "off";
};
};
/*-------------以下内容省略-------------*/ };
  • 设备树节点追加内容。在官方的设备树头文件中,已经定义了绝大部分的设备节点,如uart、i2c、spi、pwm控制器等。很多情况,我们需要在这些节点里添加、删除或者修改一些内容,此时我们可以使用&来引用前面已经定义好的节点。
&pwm1 { /* backlight */
#pwm-cells = <2>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_pwm1>;
status = "okay";
};

其中,pwm1的定义如下:

pwm1: pwm@2080000 { /*节点标签:节点名称@单元地址*/
compatible = "fsl,imx6ul-pwm", "fsl,imx27-pwm"; /*model属性用于指定设备的制造商和型号*/
reg = <0x02080000 0x4000>; /*reg属性描述设备资源在其父总线定义的地址空间内的地址*/
interrupts = <GIC_SPI 83 IRQ_TYPE_LEVEL_HIGH>; /*描述中断相关的信息*/
clocks = <&clks IMX6UL_CLK_PWM1>, /*初始化GPIO外设时钟信息*/
<&clks IMX6UL_CLK_PWM1>;
clock-names = "ipg", "per";
#pwm-cells = <3>; /*表示有多少个cells来描述pwm引脚*/
status = "disabled"; /*状态属性用于指示设备的“操作状态”*/
};

3.3、节点格式

我们知道设备树由一个根节点和众多子节点组成,子节点也可以继续包含其他节点,也就是子节点的子节点。设备树的组成很简单,下面我们来看一看节点的基本格式吧。

node-name@unit-address{
属性1 = …
属性2 = …
属性3 = …
子节点…
}
  • node-name:节点名称,用于指定节点的名称,如上面的pwm。它的长度为1至31个字符,只能由“数字、大小字母、英文逗号句号、下划线和加减号”组成,节点名应当使用大写或小写字母开头并且能够描述设备类别。
  • @unit-address:其中的符号@可以理解为是一个分割符,“unit-address”用于指定“单元地址”,它的值要和节点“reg”属性的第一个地址一致,如上面的@2080000。如果节点没有“reg”属性值,可以直接忽略“@unit-address”。 同级别的子节点的节点名可以相同,但是要求“单元地址”不同,node-name@unit-address 的整体要求同级唯一。
  • 节点标签:节点名的简写,当其他位置需要引用时可以使用节点标签来向该结点中追加内容,如上面节点名pwm前面多了个pwm1,这个pwm1就是我们所说的节点标签。
  • 节点路径:通过指定从根节点到所需节点的完整路径,可以唯一标识设备树中的节点。不同层次的设备树节点名字可以相同,同层次的设备树节点要唯一。
  • 节点属性:节点的“{}”中包含的内容是节点属性,通常情况下一个节点包含多个属性信息, 这些属性信息就是要传递到内核的“板级硬件描述信息”,驱动中会通过一些API函数获取这些信息。

3.4、节点属性

设备树最主要的内容是编写节点的节点属性,通常情况下一个节点代表一个设备,下面我整理了一些节点的常用属性:



(1)compatible属性

compatible属性值由一个或多个字符串组成,有多个字符串时使用“,”分隔开。设备树中的每一个设备的节点都要有一个compatible属性,它用来指定该设备所使用的驱动。如 leds 节点中的 compatible = "gpio-leds"; 就指定了该设备使用 Linux 内核源码中自带的通用 Led 驱动。在该驱动文件中也会声明其 compatible = "gpio-leds"; ,通过这个 compatbile 标识符,Linux 内核的 platform 总线就可以帮设备找到驱动,驱动找到设备了;



(2)model属性

用于指定设备的制造商和型号,推荐使用“制造商, 型号”的格式,当然也可以自定义;



(3)status属性

状态属性用于指示设备的“操作状态”,通过status可以去禁止设备或者启用设备,比如okay是使能设备、disabled是禁用设备;



(4)#address-cells 和 #size-cells

#address-cells,用于指定子节点reg属性“地址字段”所占的长度(单元格cells的个数)。 #size-cells,用于指定子节点reg属性“大小字段”所占的长度(单元格cells的个数)。例如#address-cells=2,#size-cells=1,则reg内的数据含义为reg = <address address size address address size>,因为每个cells是一个32位宽的数字,例如需要表示一个64位宽的地址时,就要使用两个address单元来表示;



(5)reg属性

reg属性描述设备资源在其父总线定义的地址空间内的地址。通常情况下用于表示一块寄存器的起始地址(偏移地址)和长度, 在特定情况下也有不同的含义。例如#address-cells = <1>,#size-cells = <1>,reg = <0x9000000 x4000>, 其中0x9000000表示的是地址,0x4000表示的是地址长度,这里的reg属性指定了起始地址为0x9000000,长度为0x4000的一块地址空间;



(6)ranges属性

该属性提供了子节点地址空间和父地址空间的映射(转换)方法,常见格式是 <子地址、父地址、地址长度>。如果父地址空间和子地址空间相同则无需转换。比如对于#address-cells和#size-cells都为1的话,以ranges=<0x0 0x10 0x20>为例,表示将子地址的从0x0~(0x0 + 0x20)的地址空间映射到父地址的0x10~(0x10 + 0x20);

四、设备树API函数

在Linux内核采用设备树之后,驱动程序需要获取设备树的属性。Linux内核为驱动提供了一系列API函数,用于获取设备树的属性值。这些函数都是以“_of_”开头,我们称为OF操作函数,接下来整理一些常见的OF操作函数。

4.1获取设备节点

在内核中,设备以节点的形式附加到设备树上,因此要获得设备信息,必须先获取设备节点。Linux内核使用device_node结构体来描述一个设备节点,此结构体定义在文件 include/linux/of.h 中,代码如下:

点击查看代码
struct device_node {
const char *name; /*节点的名字*/
phandle phandle;
const char *full_name; /*节点的全名,node-name[@unit-address]*/
struct fwnode_handle fwnode; struct property *properties; /*节点的属性*/
struct property *deadprops; /* removed properties */
struct device_node *parent; /*父节点*/
struct device_node *child; /*子节点*/
struct device_node *sibling; /*节点的兄弟,即同级节点*/
#if defined(CONFIG_OF_KOBJ)
struct kobject kobj;
#endif
unsigned long _flags;
void *data;
#if defined(CONFIG_SPARC)
unsigned int unique_id;
struct of_irq_controller *irq_trans;
#endif
};

上述数据结构是设备节点结构。让我们来看一下获取设备节点的几个常见函数。

(1)通过设备节点的名字获取设备节点

struct device_node *of_find_node_by_name(struct device_node *from, const char *name);
//from:指定要搜索设备节点的起始位置。若为NULL,则从根节点开始搜索;
//name:要查找的设备节点的名称;
//成功返回设备节点结构,失败时返回NULL;

(2)通过设备节点类型获取设备节点

struct device_node *of_find_node_by_type(struct device_node *from, const char *type);
//from:指定要搜索设备节点的起始位置。若为NULL,则从根节点开始搜索;
//type:要查找的设备节点的类型,device_type属性值;
//成功返回设备节点结构,失败返回NULL;

(3)通过节点的compatible属性和type获取设备节点

struct device_node *of_find_compatible_node(struct device_node *from, const char *type, const char *compat);
//from参数:指定要搜索设备节点的起始位置。若为NULL,则从根节点开始搜索;
//type参数:要查找的设备节点类型。如果为NULL,则忽略类型限制;
//compatible参数:要查找的设备节点的compatible属性名称;
//成功返回设备节点结构,失败时返回NULL;

(4)通过设备节点路径名获取设备节点

static inline struct device_node *of_find_node_by_path(const char *path)
{
return of_find_node_opts_by_path(path, NULL);
}
//path参数:设备节点的路径名;
//成功返回设备节点结构,失败时返回NULL;

(5)通过 of_device_id 匹配表来查找指定的节点

struct device_node *of_find_matching_node_and_match(struct device_node *from,
const struct of_device_id *matches,
const struct of_device_id **match)
//from: 指定要搜索设备节点的起始位置。若为NULL,则从根节点开始搜索;
//matches: of_device_id 匹配表,也就是在此匹配表里面查找节点;
//match: 找到的匹配的 of_device_id;
//成功返回找到的节点,失败时返回NULL;

4.2、获取父子设备节点API

(1)用于获取某一节点的父节点

struct device_node *of_get_parent(const struct device_node *node);
//node参数:要查找父节点的节点;
//功返回父节点的设备节点结构,失败时返回NULL;

(2)遍历某一节点的子节点

device_node *of_get_next_child(const struct device_node *node,struct device_node *prev);
//node参数:父节点;
//prev参数:上一个找到的子节点,即从哪个子节点开始搜索。如果为NULL,表示从第一个子节点开始搜索;
//返回值是找到的下一个子节点的设备节点结构;

4.3、platform_device相关

(1)根据device_node分配platform_device

struct platform_device *of_device_alloc(struct device_node *np,const char *bus_id,struct device *parent);
//np: 设备节点在设备树中对应的节点;
//bus_id: 设备所属的总线类型(如 "spi"、"i2c" 等),通常此参数被设为 NULL,表示依赖设备属性(device properties)标识符自动匹配设备总线类型;
//parent: 设备的父设备,通常设置为 NULL;
//该函数返回值是新分配的platform_device结构体指针,如果分配失败则返回 NULL;

(2)查找与给定设备节点(np)相对应的平台设备

struct platform_device *of_find_device_by_node(struct device_node *np);
//np: 设备节点在设备树中对应的节点;
//如果找到了匹配的平台设备,则返回这个平台设备的指针;否则,返回 NULL;

该函数常用于在设备树驱动程序中查找一个已知的平台设备,以便对其进行进一步的操作或数据访问。在实际编写设备树驱动程序中,通常需要通过 of_find_device_by_node 函数查找与设备节点关联的设备,并使用dev_set_drvdata函数将自定义的设备数据结构(如设备驱动结构体)与平台设备绑定。



(3)基于设备树节点节点创建一个平台设备

struct platform_device *of_platform_device_create(struct device_node *np,const char *bus_id,struct device *parent);
//np: 设备节点在设备树中对应的节点;
//bus_id: 设备所属的总线类型(如 "spi"、"i2c" 等),通常此参数被设为NULL,表示依赖设备属性(device properties)标识符自动匹配设备总线类型;
//parent: 设备的父设备,通常设置为NULL;
//该函数返回值是新创建的平台设备(platform_device)结构体指针。如果创建失败,则返回 NULL

在函数内部,of_platform_device_create 会使用 devm_kzalloc函数为设备结构体分配内存,并调用of_device_add函数将该设备注册到系统总线中。然后,将设备节点、总线类型和父设备作为参数传递给这个平台设备结构体,并返回新创建的平台设备结构体指针。



在这里,我仅仅整理了我目前接触到的一些主要OF操作函数,如果还需要更详细的,可以看这篇文章,我觉得还挺详细的。

Linux驱动---设备驱动模型的更多相关文章

  1. 深入理解Linux字符设备驱动

    文章从上层应用访问字符设备驱动开始,一步步地深入分析Linux字符设备的软件层次.组成框架和交互.如何编写驱动.设备文件的创建和mdev原理,对Linux字符设备驱动有全面的讲解.本文整合之前发表的& ...

  2. Linux字符设备驱动结构(一)--cdev结构体、设备号相关知识机械【转】

    本文转载自:http://blog.csdn.net/zqixiao_09/article/details/50839042 一.字符设备基础知识 1.设备驱动分类 linux系统将设备分为3类:字符 ...

  3. linux lcd设备驱动剖析四

    在"linux lcd设备驱动剖析二"文章中,我们详细分析了s3c24xxfb_probe函数. 文章链接:http://blog.csdn.net/lwj103862095/ar ...

  4. Linux混杂设备驱动

    1. Linux混杂设备驱动模型 ① 在Linux系统中,存在一类字符设备,它们拥有相同的主设备号(10),但次设备号不同,我们称这类设备为混杂设备(miscdevice).所有混杂设备形成一个链表, ...

  5. 【VS开发】【DSP开发】浅谈Linux PCI设备驱动(一)

    要弄清楚Linux PCI设备驱动,首先要明白,所谓的Linux PCI设备驱动实际包括Linux PCI设备驱动和设备本身驱动两部分.不知道读者理不理解这句话,本人觉得这句话很重要,对于PCI.US ...

  6. (57)Linux驱动开发之三Linux字符设备驱动

    1.一般情况下,对每一种设备驱动都会定义一个软件模块,这个工程模块包含.h和.c文件,前者定义该设备驱动的数据结构并声明外部函数,后者进行设备驱动的具体实现. 2.典型的无操作系统下的逻辑开发程序是: ...

  7. linux块设备驱动之实例

    1.注册:向内核注册个块设备驱动,其实就是用主设备号告诉内核这个代表块设备驱动 sbull_major  =  register_blkdev(sbull_major, "sbull&quo ...

  8. Linux 视频设备驱动V4L2最常用的控制命令

    http://blog.csdn.net/shaolyh/article/details/6583226 Linux 视频设备驱动V4L2最常用的控制命令使用说明(1.02) 命令 功能 VIDIOC ...

  9. Smart210学习记录----beep linux字符设备驱动

    今天搞定了beep linux字符设备驱动,心里还是很开心的,哈哈...但在完成的过程中却遇到了一个非常棘手的问题,花费了我大量的时间,,,, 还是把问题描述一下吧,好像这个问题很普遍的,网上许多解决 ...

  10. Linux块设备驱动详解

    <机械硬盘> a:磁盘结构 -----传统的机械硬盘一般为3.5英寸硬盘,并由多个圆形蝶片组成,每个蝶片拥有独立的机械臂和磁头,每个堞片的圆形平面被划分了不同的同心圆,每一个同心圆称为一个 ...

随机推荐

  1. 一款 IDEA 必备的 JSON 处理工具插件 — Json Assistant

    Json Assistant 是基于 IntelliJ IDEs 的 JSON 工具插件,让 JSON 处理变得更轻松! 主要功能 完全支持 JSON5 JSON 窗口(多选项卡) 选项卡更名 移动至 ...

  2. ORCA避障源码笔记

    参考资料 https://gamma.cs.unc.edu/ORCA/publications/ORCA.pdf https://gamma.cs.unc.edu/RVO2/ 数学知识 1.向量的点乘 ...

  3. YashanDB V23.3重磅发布,持续深化1:1替代产品力

    11月14日,YashanDB在"2024国产数据库创新生态大会"上正式发布YashanDB V23.3版本,定位为面向企业核心的通用数据库,具备1:1替代Oracle的能力. 当 ...

  4. PL/SQL中文乱码修正

    我根据需求,,需要修改 数据库的部分表格的部分字段,然而在Update的时候,出现了中文乱码(Type字段). 此时,我用的是客户端,服务器没有安装,在另一台机器上,所以,我需要做的是修改客户端编码: ...

  5. 不求甚解--详解ansible-playbook中roles的用法(二)

    前言 本文将详细介绍ansible-playbook中roles的各种用法 环境准备 组件 版本 操作系统 Ubuntu 22.04.4 LTS ansible 2.17.6 基本用法 文件结构 . ...

  6. 巧用mask属性创建一个纯CSS图标库

    说明 mask 是CSS中的一个属性,它允许开发者在元素上设置图像作为遮罩层.这个属性的强大之处,在于它可以接受多种类型的值,包括关键字值.图像值.渐变色,甚至可以设置多个属性值. SVG(Scala ...

  7. 如何调整Gitlab-Runner最大并发数?

    概述: 我们在使用gitlab-runner做cicd时,如果安装之后没有配置gitlab-runner的最大并发数,在使用时候可能会碰到job的警告(job日志超过字节限制):job's log e ...

  8. Mac安装thrift出现的问题总结

    https://www.cnblogs.com/fingerboy/p/6424248.html刚上手thrift,安装上面花了时间,我在上面的链接中照着安装的.下面记录发生的问题:当我正确安装到bi ...

  9. 冒泡排序------python实现

    if __name__ == '__main__': ''' 算法描述 1.比较相邻的元素,更具大小交互位置 2.对每一对相邻元素作同样的工作,从开始第一队到结尾的最后一对,即可选出最大的数 3.所有 ...

  10. Qt编写的项目作品23-推流综合应用示例

    一.功能特点 支持各种本地音视频文件和网络音视频文件,格式包括mp3.aac.wav.wma.mp4.mkv.rmvb.wmv.mpg.flv.asf等. 支持各种网络音视频流,网络摄像头,协议包括r ...