platform总线--驱动发现设备的过程

  总线时物理存在,Linux系统提供了一种简单的总线flatform。

  platform  不是物理存在的总线,而是逻辑概念。现代PC机提供了一条根总线(PCI 总线)管理设备,但是有些设备没有挂载在PCI总线上,不能由PCI总线管理,于是Linux内核虚拟了platform总线来统一管理这种设备。


1、从驱动发现设备的过程

  platform总线虽然简单,但有总线的通用功能。我们选的例子是 q40kbd, 在driver/input/serio目录里,这是一个键盘驱动,使用了platform总线。

1.1、驱动的初始化

  设备驱动一般从初始化函数进行分析,q40kbd_init作用是把驱动程序注册到系统,代码如下

static int __init q40kbd_init(void)
{
	int error;

	if (!MACH_IS_Q40)
		return -ENODEV;

	//驱动作为platform总线驱动 注册
	error = platform_driver_register(&q40kbd_driver);
	if (error)
		return error;

	//分配一个platform设备
	q40kbd_device = platform_device_alloc("q40kbd", -1);
	if (!q40kbd_device)
		goto err_unregister_driver;

	//platform 设备注册
	error = platform_device_add(q40kbd_device);
	if (error)
		goto err_free_device;

	return 0;

 err_free_device:
	platform_device_put(q40kbd_device);
 err_unregister_driver:
	platform_driver_unregister(&q40kbd_driver);
	return error;
}
  这段代码首先注册一个platform驱动,然后注册一个platform设备。这个过程显示了platform总线的用法,PCI总线可以自动扫描设备,而platform总线时虚拟的总线,物理上并不存在,没有扫描设备的功能,所以platform需要直接注册设备


1.2、注册驱动

  驱动注册调用函数是 platform_driver_register,代码如下:

/**
 * platform_driver_register - register a driver for platform-level devices
 * @drv: platform driver structure
 */
int platform_driver_register(struct platform_driver *drv)
{
	drv->driver.bus = &platform_bus_type;
	if (drv->probe)
		drv->driver.probe = platform_drv_probe;
	if (drv->remove)
		drv->driver.remove = platform_drv_remove;
	if (drv->shutdown)
		drv->driver.shutdown = platform_drv_shutdown;

	return driver_register(&drv->driver);
}
EXPORT_SYMBOL_GPL(platform_driver_register);
    platform_driver_register  函数把驱动总线设置为platform总线,然后依次设置 驱动的各个指针,最后调用driver_register函数注册驱动。 driver_register 函数很简单,初始化之后就调用  bus_add_driver

1.3、为总线增加一个驱动

  bus_add_driver 的作用是为总线增加一个驱动,bus_add_driver代码如下【微差别】:

/**
 * bus_add_driver - Add a driver to the bus.
 * @drv: driver.
 */
int bus_add_driver(struct device_driver *drv)
{
	struct bus_type *bus;
	struct driver_private *priv;
	int error = 0;

	bus = bus_get(drv->bus);
	if (!bus)
		return -EINVAL;

	pr_debug("bus: '%s': add driver %s\n", bus->name, drv->name);

	priv = kzalloc(sizeof(*priv), GFP_KERNEL);
	if (!priv) {
		error = -ENOMEM;
		goto out_put_bus;
	}
	klist_init(&priv->klist_devices, NULL, NULL);
	priv->driver = drv;
	drv->p = priv;
	priv->kobj.kset = bus->p->drivers_kset;
	error = kobject_init_and_add(&priv->kobj, &driver_ktype, NULL,
				     "%s", drv->name);
	if (error)
		goto out_unregister;

	if (drv->bus->p->drivers_autoprobe) {
		error = driver_attach(drv);
		if (error)
			goto out_unregister;
	}
	klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);
	module_add_driver(drv->owner, drv);

	error = driver_create_file(drv, &driver_attr_uevent);
	if (error) {
		printk(KERN_ERR "%s: uevent attr (%s) failed\n",
			__func__, drv->name);
	}
	error = driver_add_attrs(bus, drv);
	if (error) {
		/* How the hell do we get out of this pickle? Give up */
		printk(KERN_ERR "%s: driver_add_attrs(%s) failed\n",
			__func__, drv->name);
	}

	if (!drv->suppress_bind_attrs) {
		error = add_bind_files(drv);
		if (error) {
			/* Ditto */
			printk(KERN_ERR "%s: add_bind_files(%s) failed\n",
				__func__, drv->name);
		}
	}

	kobject_uevent(&priv->kobj, KOBJ_ADD);
	return 0;

out_unregister:
	kobject_put(&priv->kobj);
	kfree(drv->p);
	drv->p = NULL;
out_put_bus:
	bus_put(bus);
	return error;
}

  bus_add_driver函数使用了kobject_register 和 driver_add_attrs等函数为sysfs文件系统创建设备驱动相关的 目录 和 文件。 前面介绍过

1.4、驱动加载

  真正执行驱动加载的是 driver_attach 函数,代码如下:

/**
 * driver_attach - try to bind driver to devices.
 * @drv: driver.
 *
 * Walk the list of devices that the bus has on it and try to
 * match the driver with each one.  If driver_probe_device()
 * returns 0 and the @dev->driver is set, we've found a
 * compatible pair.
 */
int driver_attach(struct device_driver * drv)
{
	return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
}
EXPORT_SYMBOL_GPL(driver_attach);


/**
 * bus_for_each_dev - device iterator.
 * @bus: bus type.
 * @start: device to start iterating from.
 * @data: data for the callback.
 * @fn: function to be called for each device.
 *
 * Iterate over @bus's list of devices, and call @fn for each,
 * passing it @data. If @start is not NULL, we use that device to
 * begin iterating from.
 *
 * We check the return of @fn each time. If it returns anything
 * other than 0, we break out and return that value.
 *
 * NOTE: The device that returns a non-zero value is not retained
 * in any way, nor is its refcount incremented. If the caller needs
 * to retain this data, it should do so, and increment the reference
 * count in the supplied callback.
 */
int bus_for_each_dev(struct bus_type *bus, struct device *start,
		     void *data, int (*fn)(struct device *, void *))
{
	struct klist_iter i;
	struct device *dev;
	int error = 0;

	if (!bus)
		return -EINVAL;

//初始化一个kList,从设备start开始klist_iter_init_node(&bus->p->klist_devices, &i, (start ? &start->p->knode_bus : NULL));while ((dev = next_device(&i)) && !error)error = fn(dev, data);klist_iter_exit(&i);return error;}EXPORT_SYMBOL_GPL(bus_for_each_dev);



初始化一个kList,从设备start开始:



int bus_for_each_dev (struct bus_type * bus, struct device * start, void *data, int (*fn) (struct device *, void *))

{

  struct klist_liter i;

  struct device * dev;

  int error;


  if (!bus)

    return -EINVAL;

  //初始化一个klist,

  klist_iter_init_node( &bus->klist_device, &i, (start ? &start->knode : NULL)) ;

  while (( dev = next_device(&i) && !error )

    error = fn(dev, data);

  klist_iter_exit( &i );

  return error;

}

初始化一个kList,从设备start开始:

/lib/Klist.c

/**
 * klist_iter_init_node - Initialize a klist_iter structure.
 * @k: klist we're iterating.
 * @i: klist_iter we're filling.
 * @n: node to start with.
 *
 * Similar to klist_iter_init(), but starts the action off with @n,
 * instead of with the list head.
 */
void klist_iter_init_node(struct klist *k, struct klist_iter *i,
			  struct klist_node *n)
{
	i->i_klist = k;
	i->i_cur = n;
	if (n)
		kref_get(&n->n_ref);
}

//----include/linux/Klist.h
struct klist_iter {
	struct klist		*i_klist;
	struct klist_node	*i_cur;
};

//----include/linux/Klist.h
struct klist_node;
struct klist {
	spinlock_t		k_lock;
	struct list_head	k_list;
	void			(*get)(struct klist_node *);
	void			(*put)(struct klist_node *);
} __attribute__ ((aligned (sizeof(void *))));

struct klist_node {
	void			*n_klist;	/* never access directly */
	struct list_head	n_node;
	struct kref		n_ref;
};

总线类型的结构体定义在 include/linux/Device.h 文件中:

struct bus_type {
	const char		*name;
	struct bus_attribute	*bus_attrs;
	struct device_attribute	*dev_attrs;
	struct driver_attribute	*drv_attrs;

	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);
	void (*shutdown)(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;
};


/**
 * struct subsys_private - structure to hold the private to the driver core portions of the bus_type/class structure.
 *
 * @subsys - the struct kset that defines this subsystem
 * @devices_kset - the list of devices associated
 *
 * @drivers_kset - the list of drivers associated
 * @klist_devices - the klist to iterate over the @devices_kset
 * @klist_drivers - the klist to iterate over the @drivers_kset
 * @bus_notifier - the bus notifier list for anything that cares about things
 *                 on this bus.
 * @bus - pointer back to the struct bus_type that this structure is associated
 *        with.
 *
 * @class_interfaces - list of class_interfaces associated
 * @glue_dirs - "glue" directory to put in-between the parent device to
 *              avoid namespace conflicts
 * @class_mutex - mutex to protect the children, devices, and interfaces lists.
 * @class - pointer back to the struct class that this structure is associated
 *          with.
 *
 * This structure is the one that is the actual kobject allowing struct
 * bus_type/class to be statically allocated safely.  Nothing outside of the
 * driver core should ever touch these fields.
 */
struct subsys_private {
	struct kset subsys;
	struct kset *devices_kset;

	struct kset *drivers_kset;
	struct klist klist_devices;
	struct klist klist_drivers;
	struct blocking_notifier_head bus_notifier;
	unsigned int drivers_autoprobe:1;
	struct bus_type *bus;

	struct list_head class_interfaces;
	struct kset glue_dirs;
	struct mutex class_mutex;
	struct class *class;
};

struct klist_node;
struct klist {
	spinlock_t		k_lock;
	struct list_head	k_list;
	void			(*get)(struct klist_node *);
	void			(*put)(struct klist_node *);
} __attribute__ ((aligned (sizeof(void *))));



1.5、遍历总线上已挂载的设备

  遍历总线上已挂载的设备,起始位置是初始化klist_iter 结构设置的 start 设备,只遍历这个设备之后挂载的设备。当前场景设置的start设备为空,所以要遍历所有的platform总线设备。 对每个设备调用 fn 函数指针, fn就是传入的函数指针 __driver_attach,它的代码如下:

__driver_attach() ---/drivers/base/Dd.c

static int __driver_attach(struct device *dev, void *data)
{
	struct device_driver *drv = data;

	/*
	 * Lock device and try to bind to it. We drop the error
	 * here and always return 0, because we need to keep trying
	 * to bind to devices and some drivers will return an error
	 * simply if it didn't support the device.
	 *
	 * driver_probe_device() will spit a warning if there
	 * is an error.
	 */

	if (!driver_match_device(drv, dev))
		return 0;

	if (dev->parent)	/* Needed for USB */
		device_lock(dev->parent);
	device_lock(dev);
	if (!dev->driver)
		driver_probe_device(drv, dev);
	device_unlock(dev);
	if (dev->parent)
		device_unlock(dev->parent);

	return 0;
}

static int __driver_attach(struct device *dev, void * data )

{

  struct device_driver * drv = data;

if ( dev->parent ) /* need for usb */

down(&dev->parent->sem);

down(&dev->sem);

if (!dev->driver)

driver_probe_device(drv, dev);

return 0;

}

  __driver_attach获取设备的锁之后,调用driver_probe_device函数,代码如下:

int driver_proe_device(struct device_driver * drv, struct device *dev)

{

int ret =0;

//调用总线配置的match函数

if ( drv->bus->match && !drv->bus->match( dev, drv) )

goto Done;

pr_debug("%s: Matched Device %s with Driver %s \n",  drv->bus->name,  dev->bus_id,  drv->name );

dev->driver = drv;

//总线的match函数通过后,继续调用总线的probe函数

if (dev->bus->probe) {

ret = dev->bus->probe(dev);

if(ret) {

dev->driver = NULL;

goto ProbeFailed;

}

}else if ( drv->probe ) {

// 如果驱动提供了probe函数,则调用驱动的probe 函数

ret = drv->probe(dev);

if (ret) {

dev->driver = NULL;

goto ProbeFailed;

}

}

//设备发现驱动,通过sysfs创建一些文件,和设备做符号链接

device_bind_driver(dev);

driver_probe_device函数分为两个步骤:

--第一步 调用总线提供的match函数; 如果检测通过,说明设备和驱动匹配,设备所指向的驱动指针要赋予为当前驱动

--第二步 探测probe。首先调用总线提供的probe函数,如果驱动有自己的probe函数,还要调用驱动的probe函数

   Probe 目的是 总线或设备的进一步探测。比如硬盘控制器,本身是一个PCI设备,同时又提供硬盘接入的功能。那么它的驱动probe函数就要扫描scsi总线,把所有接入的硬盘都扫描出来。


1.5.1 match函数

  platform总线的match函数就是platform_match


  platform_match 函数很简单,比较驱动的名字和设备的名字是否相同,相同就可以匹配。

1.5.2 probe函数

  platform_drv_probe 是封装的函数,简单调用了 驱动的probe函数,驱动probe函数就是 q40kbd_probe

static int __devinit q40kbd_probe(struct platform_device *dev)
{
	q40kbd_port = kzalloc(sizeof(struct serio), GFP_KERNEL);
	if (!q40kbd_port)
		return -ENOMEM;

	q40kbd_port->id.type	= SERIO_8042;
	q40kbd_port->open	= q40kbd_open;
	q40kbd_port->close	= q40kbd_close;
	q40kbd_port->dev.parent	= &dev->dev;
	strlcpy(q40kbd_port->name, "Q40 Kbd Port", sizeof(q40kbd_port->name));
	strlcpy(q40kbd_port->phys, "Q40", sizeof(q40kbd_port->phys));

	serio_register_port(q40kbd_port);
	printk(KERN_INFO "serio: Q40 kbd registered\n");

	return 0;
}

  q40kbd_probe函数设置了一个serio结构变量,然后注册到系统。






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值