Linux驱动之platform_bus、platform_device、platform_driver

概述:

在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:系统退出待机时,会调用到这个函数。设备,退出待机需要操作的函数放这里面,如退出睡眠。

  • 2
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,以下是platform_bus_type结构体的内容及注释: ```c struct platform_bus_type { const char *name; // 平台总线类型的名称 int (*match)(struct device *dev, struct device_driver *drv); // 匹配设备和驱动程序的函数 int (*probe)(struct platform_device *pdev); // 设备探测函数 int (*remove)(struct platform_device *pdev); // 设备移除函数 void (*shutdown)(struct platform_device *dev); // 设备关机函数 struct bus_attribute *bus_attrs; // 总线属性 struct device_attribute *dev_attrs; // 设备属性 struct driver_attribute *drv_attrs; // 驱动程序属性 const struct attribute_group **bus_groups; // 总线属性组 const struct attribute_group **dev_groups; // 设备属性组 const struct attribute_group **drv_groups; // 驱动程序属性组 void (*uevent)(struct device *dev, struct kobj_uevent_env *env); // 用户空间事件函数 }; ``` 这是一个用于定义平台总线类型的结构体,它包含了许多与该总线类型相关的函数和属性。 其中,`name` 字段用于指定平台总线类型的名称,`match` 函数用于匹配设备和驱动程序,`probe` 函数用于设备探测,`remove` 函数用于设备移除,`shutdown` 函数用于设备关机。 此外,还有 `bus_attrs`、`dev_attrs` 和 `drv_attrs` 三个字段,分别用于指定总线、设备和驱动程序的属性,以及 `bus_groups`、`dev_groups` 和 `drv_groups` 三个字段,分别用于指定总线、设备和驱动程序的属性组。 最后,`uevent` 函数用于处理用户空间的事件。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值