概述:
在Linux驱动里面platform bus,直译过来就是平台总线,实际上,是软件里面虚拟出来的总线,俗称虚拟总线。虚拟总线有什么作用呢?以及怎么用linux虚拟总线子框架?这就是本文要阐述的两个问题。
虚拟总线框架:
platform bus也是驱动框架下的一个子系统,是构建在linux驱动框架bus,device,driver这种模型之上的;
platform_bus用来连接platform_device和platform_driver;
platform_device用来存储设备信息;
platform_driver是设备驱动。
虚拟总线、设备、驱动的作用
简单来说,有以下三个作用:
- 加载dts信息到platform_device,即将reg(dts描述:控制器,寄存器地址,memery地址)、interrupts(dts描述的中断信息)信息翻译成struct resource(platform_device结构体里面有这个类型的指针)这样的结构体中存放着,供驱动程序访问,reg,interrupts信息放在dts里面,所以也可以说是解析dts。
- 向驱动程序提供系统功能接口,这主要是指struct platform_driver这里面的函数指针。只要platform_driver注册成功,像shutdown函数指针指向的函数,在系统关机的时候就会被调用到,suspend指向的函数,在系统被挂起的时候会被调用到等。
- 加载驱动作用,这个主要是platform_driver里面的probe函数指针。现在新版linux,都是采取在dts节点里面加入compatible属性,以及status属性,在开机时候会调用of_platform_populate(),这个函数会遍历整个dts数,凡是有compatible属性,status属性(status属性可有可无,如果有,属性值必须为ok或okay)都会为这个dts节点,创建一个struct platform_device实例并连接到platorm_bus里面,当驱动加载时,调用到platform_driver_register(),首先在platform_bus里面查询与之匹配的platform_device,如果匹配成功(具体怎么匹配,后面会描述),就能获取到platform_device。老版linux,没有dts出现的时候,是用户在开机时,自己调用platform_device_register()注册platform_device。
解析dts里面reg,interrupts:
首先看下platform_device结构体:
struct platform_device {
const char *name;
int id;
bool id_auto;
struct device dev;
u32 num_resources;
struct resource *resource;
const struct platform_device_id *id_entry;
/* MFD cell pointer */
struct mfd_cell *mfd_cell;
/* arch specific additions */
struct pdev_archdata archdata;
};
解析的过程,就是将reg,interrupts信息翻译成struct resource格式存放。
启动解析:
linux在开机过程中,一般会调用of_platform_populate()这个函数,这个函数一般会在setup.c文件里面调用,如果是arm体系的,在arch/arm/kernel/setup.c里面:
static int __init customize_machine(void)
{
/*
* customizes platform devices, or adds new ones
* On DT based machines, we fall back to populating the
* machine from the device tree, if no callback is provided,
* otherwise we would always need an init_machine callback.
*/
if (machine_desc->init_machine)
machine_desc->init_machine();
#ifdef CONFIG_OF
else
of_platform_populate(NULL, of_default_bus_match_table,
NULL, NULL);
#endif
return 0;
}
实际,还是要看CPU厂商在哪里调用of_platform_populate()。
总之linux开机时,根据dts节点创建sturct platform_device实例,并且把dts节点reg,interrupts属性翻译成struct resource类型结构体存放。。
要启动dts一个节点reg,interrupts信息解析前,还会判断dts节点的两个属性,其中一个属性是compatible,这个属性一定要存在,否则不会启动解析,调用情况of_platform_populate() –> of_platform_bus_create() –> of_get_property(bus, “compatible”, NULL)
/*
* Find a property with a given name for a given node
* and return the value.
*/
const void *of_get_property(const struct device_node *np, const char *name,
int *lenp)
{
struct property *pp = of_find_property(np, name, lenp);
return pp ? pp->value : NULL;
}
EXPORT_SYMBOL(of_get_property);
另外一个属性,就是status,这个属性不需要一定存在,如果存在,属性值必须为ok或者okay,才能启动解析,或者干脆就没有这个属性,也能启动解析,调用情况,of_platform_populate() –> of_platform_bus_create() –> of_platform_device_create_pdata() –> of_device_is_available()
/**
* of_device_is_available - check if a device is available for use
*
* @device: Node to check for availability
*
* Returns 1 if the status property is absent or set to "okay" or "ok",
* 0 otherwise
*/
int of_device_is_available(const struct device_node *device)
{
unsigned long flags;
int res;
raw_spin_lock_irqsave(&devtree_lock, flags);
res = __of_device_is_available(device);
raw_spin_unlock_irqrestore(&devtree_lock, flags);
return res;
}
EXPORT_SYMBOL(of_device_is_available);
当这两个属性检测通过后,就会启动解析。
解析reg:
在上面的启动解析中,有调用到函数of_platform_device_create_pdata(),调用过程:of_platform_device_create_pdata()–>of_device_alloc()。函数of_device_alloc():
/**
* of_device_alloc - Allocate and initialize an of_device
* @np: device node to assign to device
* @bus_id: Name to assign to the device. May be null to use default name.
* @parent: Parent device.
*/
struct platform_device *of_device_alloc(struct device_node *np,
const char *bus_id,
struct device *parent)
{
struct platform_device *dev;
int rc, i, num_reg = 0, num_irq;
struct resource *res, temp_res;
dev = platform_device_alloc("", -1);
if (!dev)
return NULL;
/* count the io and irq resources */
if (of_can_translate_address(np))
while (of_address_to_resource(np, num_reg, &temp_res) == 0)
num_reg++;
num_irq = of_irq_count(np);
/* Populate the resource table */
if (num_irq || num_reg) {
res = kzalloc(sizeof(*res) * (num_irq + num_reg), GFP_KERNEL);
if (!res) {
platform_device_put(dev);
return NULL;
}
dev->num_resources = num_reg + num_irq;
dev->resource = res;
for (i = 0; i < num_reg; i++, res++) {
rc = of_address_to_resource(np, i, res);
WARN_ON(rc);
}
WARN_ON(of_irq_to_resource_table(np, res, num_irq) != num_irq);
}
dev->dev.of_node = of_node_get(np);
#if defined(CONFIG_MICROBLAZE)
dev->dev.dma_mask = &dev->archdata.dma_mask;
#endif
dev->dev.parent = parent;
if (bus_id)
dev_set_name(&dev->dev, "%s", bus_id);
else
of_device_make_bus_id(&dev->dev);
return dev;
}
EXPORT_SYMBOL(of_device_alloc);
of_address_to_resource()(位于drivers/of/address.c里面)这个函数就会将dts里面的reg属性解析成struct resource结构体存放。在解析过程中会涉及dts关键字有:“reg”,“reg-names”,“ranges”,”#address-cells”,”#size-cells”。这些关键字,各自有什么作用以及相互关系?先看看如下dts例子:
/{
...
...
...
{
compatible = "zimu,testB";
#address-cells = <1>;
#size-cells = <1>;
ranges;
{
compatible = "zimu,testA";
status = "ok";
reg = <0x100 0x200>,<0x400 0x500>,<0xa00 0x100>;
reg-names = "addr0","addr1","addr2";
};
}
};
对于这中ranges属性值为空的情况:
以上例子,解析完后,platform_device成员的resource指针指向三个struct resource大小连续内存空间,如下定义:
sturct platform_device *pd;
pd = kzalloc(sizeof(sturct platform_device) * 3, GFP_KERNEL);
解析完成后:
pd->num_resource等于3
pd->resource[0].name等于”addr0″
pd->resource[0].start 等于0x100
pd->resource[0].end等于0x300
pd->resource[1].name等于”addr1″
pd->resource[1].start 等于0x400
pd->resource[1].end等于0x900
pd->resource[2].name等于”addr2″
pd->resource[2].start 等于0xa00
pd->resource[2].end等于0xb00
需要访问的时候,可以从pd->num_resource知道有几个sturct resource资源。
注意: #address-cells = <1>表示reg地址占用32位内存空间,即4个byte,如果为2表示占用64位内存空间(8个byte),以此类推,它取值范围是1到4;同样#size-cells = <1>,表示size数值占用32空间,如果是2表示64位内存空间,它的取值范围是大于0,无最大限制。在解析过程中,获取了这两个值后,都会调用宏OF_CHECK_COUNTS判断值是否在要求的范围内,这个宏定义在drivers/of/address.c里面,如下代码片段:
/* Max address size we deal with */
#define OF_MAX_ADDR_CELLS 4
#define OF_CHECK_ADDR_COUNT(na) ((na) > 0 && (na) <= OF_MAX_ADDR_CELLS)
#define OF_CHECK_COUNTS(na, ns) (OF_CHECK_ADDR_COUNT(na) && (ns) > 0)
关于ranges属性值不为空的情况,如下dts例子:
/{
...
...
...
compatible = "zimu,testD";
#address-cells = <1>;
#size-cells = <1>;
ranges;
{
compatible = "zimu,testC";
#address-cells = <1>;
#size-cells = <1>;
ranges = <0x10 0x10 0x500>,<0x700 0x700 0x2000>,<0x5000 0x5000 0x20000>;
{
compatible = "zimu,testB";
#address-cells = <1>;
#size-cells = <1>;
ranges = <0x50 0x50 0x400>,<0x800 0x800 0x1000>,<0x9000 0x9000 0x8000>;
{
compatible = "zimu,testA";
status = "ok";
reg = <0x100 0x200>,<0x1000 0x100>,<0x10000 0x1000>;
reg-names = "addr0","addr1","addr2";
};
}
}
};
reg存放格式:
<0x100 0x200>父节点的#address-cells和#size-cells的值都为1,所以第一个32位内存空间存放的是0x100,第二个32位内存地址空间存放的是0x200。
ranges的存放格式:
<0x50 0x50 0x400>所处节点的#address-cells和#size-cells的值都为1,第一个0x50,就是受本节点控制,所以第一个32位内存空间存放的是0x50,父节点#address-cells也是1,它控制第二段内存空间,所以第二个0x50就存放在第二段32位内存空间里面,第三段32位内存空间存放的是size,就是0x400。
<0x100 0x200>,<0x50 0x50 0x400>,<0x10 0x10 0x500>三组是有关系的,其中都受#address-cells和#size-cells控制。在__of_translate_address()–>of_translate_one()这个调用过程解析,需要满足reg的空间范围在父节点ranges的范围内,父节点的ranges范围必须在祖父节点ranges的范围内。如<0x100 0x200>表示起始地址是0x100,大小是0x200,<0x50,0x50,0x400>表示的是起始地址是0x50(第一个0x50),大小是0x400,其中第二个0x50占多少空间受祖父节点的#address-cells控制。其它以此类推。
解析过程:
reg节点的0x100减去父节点ranges的第一个0x50,得到一个偏移0x50, 然后这偏移加上父节点ranges的第二个0x50,得到0x100,这是__of_translate_address()第一轮循环结束后,得到的这个偏移0x100,进入下一轮循环后,以testC节点作为新起点,拿这个偏移减去0x10,然后得到这个新的偏移0x90,然后0x90加上第二个0x10(testD节点的#address-cells为1),最后得到0x100,这个0x100会作为struct resource里面start成员的值,结构体里面的end,如果解析成功直接就是0x200(当然本例子是故意设置的一个巧合,原因是<0x50 0x50 0x400>,<0x10 0x10 0x500>第二个0x50,第二个0x10不清楚其中的作用,但是分析代码是这样解析的,所以设置这个巧合,有这个巧合,表述解析过程不会错误,其中第二个0x50和0x10也都是故意设置的。)这个循环直到根节点或者ranges没有属性值的节点才会停止。在这个例子中,解析<0x100 0x200>得到的一个struct resource实例情况如下:
sturct resource *p;
pd = kzalloc(sizeof(sturct resource) , GFP_KERNEL);
p->name等于”addr0″
p->start 等于0x100
pd->end等于0x300
其他的两组:
<0x1000 0x100>,<0x800 0x800 0x1000>,<0x700 0x700 0x2000>解析完后:
sturct resource *p;
pd = kzalloc(sizeof(sturct resource) , GFP_KERNEL);
p->name等于”addr1″
p->start 等于0x1000
pd->end等于0x100
另外一组就不说明了,按照这个方法,可以自己分析得出来。分析下列,和ranges没有属性值的情况是一样的,如上述,是故意设置的巧合。
实际ranges没有属性值的情况比较多,有属性值的感觉死板不灵活,至于为什么要设置带属性值情况,搞清楚后,再来讲述。
详细的解析过程,最好是去分析如下调用过程:
of_device_alloc()–>__of_address_to_resource–>of_translate_address()–>
__of_translate_address–>of_translate_one().
解析interrupts:
分三种情况描述解析过程:
首先了解下解析过程中函数的调用:
of_device_alloc()–>of_irq_count()–>of_irq_to_resource()–>
irq_of_parse_and_map()–>of_irq_map_one()–>of_irq_map_raw().
这是解析过程中主要的调用函数,这些函数都定义在drivers/of/irq.c里面。
在of_device_alloc()中有两次调用解析interrupts,第一次是探测inteerupts有几个中断资源被描述,获取到有多少个中断资源后,就分配几个strutc resource内存空间,然后第二次调用解析函数of_irq_to_resource_table()将解析的信息存入到分配内存空间中。
/**
* of_irq_to_resource_table - Fill in resource table with node's IRQ info
* @dev: pointer to device tree node
* @res: array of resources to fill in
* @nr_irqs: the number of IRQs (and upper bound for num of @res elements)
*
* Returns the size of the filled in table (up to @nr_irqs).
*/
int of_irq_to_resource_table(struct device_node *dev, struct resource *res,
int nr_irqs)
{
int i;
for (i = 0; i < nr_irqs; i++, res++)
if (!of_irq_to_resource(dev, i, res))
break;
return i;
}
EXPORT_SYMBOL_GPL(of_irq_to_resource_table);
of_irq_to_resource_table()从这个函数的定义,可以看出前后两次解析都调用到of_irq_to_resource()。
另外在看irq_of_parse_and_map()函数:
/**
* irq_of_parse_and_map - Parse and map an interrupt into linux virq space
* @device: Device node of the device whose interrupt is to be mapped
* @index: Index of the interrupt to map
*
* This function is a wrapper that chains of_irq_map_one() and
* irq_create_of_mapping() to make things easier to callers
*/
unsigned int irq_of_parse_and_map(struct device_node *dev, int index)
{
struct of_irq oirq;
if (of_irq_map_one(dev, index, &oirq))
return 0;
return irq_create_of_mapping(oirq.controller, oirq.specifier,
oirq.size);
}
EXPORT_SYMBOL_GPL(irq_of_parse_and_map);
解析dts里面interrupts主要of_irq_map_one()函数做的事情,也是本章要讲述的。而irq_create_of_mapping()主要是将从dts里面解析的interrupts信息struct of_irq转化成irq的编号,这个过程涉及linux中断子系统,后面会在单独的章节讲述,本章不讲述。
第一种情况:
先看如下dts例子:
/{
interrupt-parent = <&interrupt_con>;
zimu-interrupts:zimu-interrupts{
compatible = "zimu,test-interrupt";
interrupts = <0 123 4>,<0 125 8>;
interrupts-names = "int-0","int-1";
};
interrupt_con:interrupt_con{
compatible = "zimu,test-con";
interrupt-controller;
#interrupt-cells = <3>;
};
}
解析节点zimu-interrupts时:
(1)调用of_get_property()读取interrupts信息,存到内存空间。
(2)调用of_irq_find_parent()找到interrupt-parent属性指向的节点interrupts_con;
(3)在interrupts_con里面如果发现interrupt-controller属性,表示成功。
(4)开始将interrupts的信息解析到struct of_irq结构体里面:
/**
* of_irq - container for device_node/irq_specifier pair for an irq controller
* @controller: pointer to interrupt controller device tree node
* @size: size of interrupt specifier
* @specifier: array of cells @size long specifing the specific interrupt
*
* This structure is returned when an interrupt is mapped. The controller
* field needs to be put() after use
*/
#define OF_MAX_IRQ_SPEC 4 /* We handle specifiers of at most 4 cells */
struct of_irq {
struct device_node *controller; /* Interrupt controller node */
u32 size; /* Specifier size */
u32 specifier[OF_MAX_IRQ_SPEC]; /* Specifier copy */
};
controller成员存放的,是指向dts节点interrupt_con;size存放的,是#interrupt-cells属性值,本例为3;specifier存放的是interrupts的属性值,在本例中specifier[0]为0,specifier[1]为123,specifier[2]为4;在interrupts有两组属性值,如下循环到第二次:
while (of_irq_to_resource(dev, nr, NULL))
nr++;
specifier的情况:specifier[0]为0,specifier[1]为125,specifier[2]为8。
关于interrputs属性值的意义,可参考linux驱动-中断
(5)将解析得到struct of_irq送入到irq_create_of_mapping()转化成irq编号,最后将irq存入到struct resorce,并且会获取interrupt-names属性值和interrupts对应。如下of_irq_to_resource()函数定义:
/**
* of_irq_to_resource - Decode a node's IRQ and return it as a resource
* @dev: pointer to device tree node
* @index: zero-based index of the irq
* @r: pointer to resource structure to return result into.
*/
int of_irq_to_resource(struct device_node *dev, int index, struct resource *r)
{
int irq = irq_of_parse_and_map(dev, index);
/* Only dereference the resource if both the
* resource and the irq are valid. */
if (r && irq) {
const char *name = NULL;
memset(r, 0, sizeof(*r));
/*
* Get optional "interrupts-names" property to add a name
* to the resource.
*/
of_property_read_string_index(dev, "interrupt-names", index,
&name);
r->start = r->end = irq;
r->flags = IORESOURCE_IRQ | irqd_get_trigger_type(irq_get_irq_data(irq));
r->name = name ? name : of_node_full_name(dev);
}
return irq;
}
EXPORT_SYMBOL_GPL(of_irq_to_resource);
<0 124 4>对应int0;
<0 125 8>对应int1。
第二种情况:
/{
interrupt-parent = <&interrupt_con>;
zimu-interrupts:zimu-interrupts{
compatible = "zimu,test-interrupt";
interrupts = <0 123 4>,<0 125 8>;
interrupts-names = "int-0","int-1";
reg = <0>;
};
interrupt_con:interrupt_con{
compatible = "zimu,test-con";
#interrupt-cells = <3>;
#address-cells = <1>;
interrupt-map-mask = <0 0 0 0>;
interrupt-map = <0 0 0 &interrupt_a 0 0 0 0>;
};
interrupt_a:interrupt_a{
interrupt-controller;
#interrupt-cells = <3>;
#address-cells = <1>;
};
}
在第一种情况的(3)步时,如果没有发现interrupt-controller属性,进入另外一种匹配方式,找到具有interrupt-controller属性的节点。新匹配方式如下:
(1)用zimu-interrupts这个节点的reg的0,与interrupt-map的第一个0异或,然后再与interrupt-map-mask的第一个0与(&)上,这样计算结果为0表示成功,将进入用interrupt-map的第二,三,四个0分别与interrupts的<0 123 4>三个值异或,得到值,再与interrupt-map-mask的第二,三,四个值与,获得结果,如果为0,表示成功。这样,将查询到interrupt_a节点,然后发现interrupt_a节点有interrupt-controller属性,表示查找到中断控制器,最后将interrupts转换成struct resource,就是第一种情况的(4),(5)。
第三种情况:
/{
interrupt-parent = <&interrupt_con>;
zimu-interrupts:zimu-interrupts{
compatible = "zimu,test-interrupt";
interrupts = <0 123 4>,<0 125 8>;
interrupts-names = "int-0","int-1";
reg = <0>;
};
interrupt_con:interrupt_con{
compatible = "zimu,test-con";
#interrupt-cells = <3>;
#address-cells = <1>;
interrupt-map-mask = <0 0 0 0>;
interrupt-map = <0 0 0 0 &interrupt_a 0 0 0 0>;
};
interrupt_a:interrupt_a{
#interrupt-cells = <3>;
#address-cells = <1>;
interrupt-map-mask = <0 0 0 0>;
interrupt-map = <0 0 0 0 &interrupt_b 0 0 0 0>;
};
interrupt_b:interrupt_b{
interrupt-controller;
#interrupt-cells = <3>;
#address-cells = <1>;
};
}
在第二种情况,在interrupt_a节点没有发现interrupt-controller属性,将会用interrupt_con的interrupt-map的第五个0,同interrupt_a的interrupt-map的第一个0异或,得到的结果,同interrupt_a的interrupt-map-mask的第一个0与上,计算结果为0,就表示成功。将进入,用interrupt_con的interrupt-map的第六、七、八个0,分别同interrupt_a的interrupt-map的第二、三、四个0异或上,三次,每次得到的结果,都将分别与interrupt_a的interrupt-map-mask的第二、三、四个0与上,三次与的结果,都为0,表示成功(三次,其中一次不为0都会失败,将会继续查找interrupt-map的另外一组值,看下面的例子,这种情况,将会查找到interrupt_c节点),将查询到interrupt_b,那么发现interrupt-controller,将会开始把interrupts转换成struct resource。
interrupt-map = <0 0 0 0 &interrupt_b 0 0 0 0>,
<0 0 0 0 &interrupt_c 0 0 0 0>;
interrupt_c:interrupt_c{
interrupt-controller;
#interrupt-cells = <3>;
#address-cells = <1>;
};
interrupt-map-mask属性可以没有,如果没有这个属性值,将会用默认值0xffffffff。另外关于这里涉及的#address-cells也可以没有,如果没有,会用默认值2。
注意,<0 0 0 0 &interrupt_b 0 0 0 0>头部4个0,尾部4个0,受#address-cell和#interrupt-cells属性值影响,#address-cell控制的值排前面,#interrupt-cells控制的值排后面,头部4个0,第一个0是#address-cell=<1>,后面三个0是#interrupt-cells=<3>。
以上三种情况,主要都是在函数of_irq_map_raw()里面完成解析。
虚拟总线、设备、驱动使用方法:
第一:
就是要构建虚拟设备,即platform_device,现在的方法都是在dts里面加入如下类似的信息,也可以自己调用platform_device_register()自己注册(这种方法现在很少用)。
/{
test:test{
compatible = "test,aa";
status = "ok";
reg = <0x0 0x1000 0x200>;
interrupts = <0 100 8>;
};
};
第二:
注册platform_driver,结构体定义如下:
struct platform_driver {
int (*probe)(struct platform_device *);
int (*remove)(struct platform_device *);
void (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device *, pm_message_t state);
int (*resume)(struct platform_device *);
struct device_driver driver;
const struct platform_device_id *id_table;
};
注册例子:
struct test_struct{
int a;
int b;
}test_struct_t;
test_struct_t t0;
struct platform_device_id test_id_table = {
"test.aa", &t0
};
int test_probe(struct platform_device *pd){};
int test_remove(struct platform_device *pd){};
void test_shutdown(struct platform_device *pd){};
int test_suspend(struct platform_device *pd, pm_message_t state){};
int test_resume(struct platform_device *pd){};
struct platform_driver test_drv = {
.probe = test_probe,
.remove = test_remove,
.shutdown = test_shutdown,
suspend = test_suspend,
resume = test_resume,
.driver = {
.name = "test",
.owner = THIS_MODULE
};
.id_table = test_id_table
};
static int __init test_init(void)
{
platform_driver_register(&test_drv);
}
static void __exit test_exit(void)
{
platform_driver_unregister(&test_drv);
}
module_init(test_init);
module_exit(test_exit);
以上只是说明过程的例子,没有实际编译测试过。
注意,id_table的name一定要和dts里面的compatible相同,才能匹配到platform_device。这样的方法,可以采用(test_struct_t)test_drv.id_table->driver_data访问私有数据。
还用如下的办法:
struct of_device_id test_device_id = {
.compatible = "test.aa",
.data = &t0;
};
struct platform_driver test_drv = {
.probe = test_probe,
.remove = test_remove,
.shutdown = test_shutdown,
suspend = test_suspend,
resume = test_resume,
.driver = {
.name = "test",
.owner = THIS_MODULE,
.of_match_table = &test_device_id;
};
};
这样,很明显test_device_id的compatible和dts里面的相同,就可以匹配上。明显可以(test_struct_t)test_drv.driver.of_match_table.data访问私有结构体。
t0是驱动的私有数据,除了以上两种访问私有结构体,像如下定义(当然像上面的定义也是可以):
struct of_device_id test_device_id = {
.compatible = "test.aa",
.data = NULL;
};
struct platform_device_id test_id_table = {
"test.aa", NULL
};
像这样定义后,在prboe函数里面调用platform_set_drvdata()这个函数设置私有结构体t0,在其他地方访问私有结构体调用platform_get_drvdata()就比较方便了。
probe:当匹配成功后,首先,系统会调用这个函数。一般驱动的初始化操作放这个函数里面。
remove:当模块退出时,调用platform_driver_unregister(),这个函数会被调用到。一般卸载驱动时需要操作的放这个函数里面。
shutdown:系统在关机时,会调用到这个函数。关机需要操作的,放这个函数里面。
suspend:系统待机时,会调用到这个函数。设备待机需要操作的放这个函数里面,如设备睡眠。
resume:系统退出待机时,会调用到这个函数。设备,退出待机需要操作的函数放这里面,如退出睡眠。
今天的文章Linux驱动之platform_bus、platform_device、platform_driver分享到此就结束了,感谢您的阅读,如果确实帮到您,您可以动动手指转发给其他人。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/26059.html