设备驱动模型之driver-bus-device与probe

1. 概述

Linux设备模型的目的:为内核建立一个统一的设备模型,从而又一个对系统结构的一般性抽象描述。

换句话说,Linux设备模型提取了设备操作的共同属性,进行抽象,并将这部分共同的属性在内核中实现,而为需要新添加设备或驱动提供一般性的统一接口,这使得驱动程序的开发变得更简单了,而程序员只需要去学习接口就行了。

在内核里,有各种各样的总线,如 usb_bus_typespi_bus_typepci_bus_typeplatform_bus_typei2c_bus_type 等,内核通过总线将设备与驱动分离。

设备模型是层次的结构,层次的每一个节点都是通过kobject实现的,在文件上则体现在sysfs文件系统。
在这里插入图片描述

简单来说,bus 负责维护 注册进来的devciedriver ,每注册进来一个device 或者 driver 都会调用 Bus->match 函数 将devicedriver 进行配对,并将它们加入链表。

如果配对成功,调用Bus->probe或者driver->probe函数, 调用kobject_uevent函数设置环境变量(通知用户空间),mdev进行创建设备节点等操作。
对于整个 设备总线驱动模型 的样子,大概如下图:
在这里插入图片描述

接下来从Busdriverdevice三个部分进行详细的分析。

1.1 总线:

总线(bus)是linux发展过程中抽象出来的一种设备模型,为了统一管理所有的设备,内核中每个设备都会被挂载在总线上,这个bus可以是对应硬件的bus(i2c busspi bus)、可以是虚拟bus(platform bus)。

bus将所有挂在上面的具体设备抽象成两部分,device_driverdevice

1.2 driver与device

driver实现了同类型设备的驱动程序实现,而device则向系统注册具体的设备需要的资源,每当添加一个新的driver(device)到bus中时,都将调用busmatch函数,试图寻找匹配的device(driver)。

如果匹配成功,就调用probe函数,在probe函数中实现设备的初始化、各种配置以及生成用户空间的文件接口。

probe函数是总线在匹配成功时调用的函数,bus->probedrv->probe中只会有一个起效,同时存在时使用bus->probe

1.3 bus, driver, device 框架

linux的外围设备驱动,都是通过 bus + driver + device来管理的,其实也好理解 ,外设都是通过总线来与cpu通讯的。kernel会实现各种总线的规范以及设备管理(设备检测,驱动绑定等),驱动程序只需要注册自己的驱动,实现对设备的读写控制即可。

这类驱动通常是2个层次:总线子系统 + 驱动模块,它的流程大概是:

  1. bus_register(xx) : kernel里面的各bus子系统(如:serio, usb, pci, …)会使用该函数来注册自己。
  2. driver_register(xx)
    驱动模块使用它来向总线系统注册自己,这样驱动模块只需要关注相应driver接口的实现。通常,bus子系统会对 driver_register来进行封装,如:
    1. serio 提供serio_register_driver()
    2. usb 提供usb_register_driver()
    3. pci提供 pci_register_driver()
  3. registe_device(xx)
    各总线除了管理driver外,还管理device,通常会提供一支API来添加设备,如: input_register_device, serio_add_port.实现上都是通过一个链表对设备进行管理,通常是在初始化或者probe的时候, 添加设备。

设备(device)指的是具体实现总线协议的物理设备,如对serio总线而言,i8042就是它的一个设备,而该总线连接的设备(鼠标,键盘)则是一个serio driver

2. 初始化

2.1 driver_init

所有的bus都是在buses_init,kernel启动以后,进行初始化,最终执行到:

// init/main.c
kernel_init();
    kernel_init_freeable();
        do_basic_setup();
            driver_init(); // 注意这个
            do_initcalls();
/**
 * driver_init - initialize driver model.
 *
 * Call the driver model init functions to initialize their
 * subsystems. Called early from init/main.c.
 */
void __init driver_init(void)
{
	/* These are the core pieces */
	devtmpfs_init();
	devices_init();// 初始化device
	buses_init();// 初始化bus
	classes_init();
	firmware_init();
	hypervisor_init();

	/* These are also core pieces, but must come after the
	 * core core pieces.
	 */
	of_core_init();
	platform_bus_init();
	auxiliary_bus_init();
	cpu_dev_init();
	memory_dev_init();
	node_dev_init();
	container_dev_init();
}

注意devices_initbuses_init这两个函数会创建一些对应的对象,我们能够在sysfs中看到这些对应的对象,在后续中会用到。

2.2 devices_init

// driversbase/base.h
struct kset *devices_kset;
extern struct kset *devices_kset;

// drivers/base/core.c
int __init devices_init(void)
{
	// 创建 /sys/devices
	devices_kset = kset_create_and_add("devices", &device_uevent_ops, NULL);
	if (!devices_kset)
		return -ENOMEM;
	// 创建 /sys/dev
	dev_kobj = kobject_create_and_add("dev", NULL);
	if (!dev_kobj)
		goto dev_kobj_err;
	 // 创建 /sys/dev/block
	sysfs_dev_block_kobj = kobject_create_and_add("block", dev_kobj);
	if (!sysfs_dev_block_kobj)
		goto block_kobj_err;
	// 创建 /sys/dev/char
	sysfs_dev_char_kobj = kobject_create_and_add("char", dev_kobj);
	if (!sysfs_dev_char_kobj)
		goto char_kobj_err;

	return 0;

 char_kobj_err:
	kobject_put(sysfs_dev_block_kobj);
 block_kobj_err:
	kobject_put(dev_kobj);
 dev_kobj_err:
	kset_unregister(devices_kset);
	return -ENOMEM;
}

2.3 buses_init

// drivers/base/bus.c
static struct kset *system_kset;
static struct kset *bus_kset;

int __init buses_init(void)
{
    // 创建 /sys/bus
    bus_kset = kset_create_and_add("bus", &bus_uevent_ops, NULL);
    if (!bus_kset)
        return -ENOMEM;

    // 创建 /sys/devices/system
    system_kset = kset_create_and_add("system", NULL, &devices_kset->kobj);
    if (!system_kset)
        return -ENOMEM;

    return 0;
}

同样是调用了kset_create_and_add,这里涉及到了ksetkobject这些概念。

2.4 kset_create_and_add

kobject_create_and_add这个函数首先会调用kobject_create来分配并初始化一个kobject对象,然后调用kobject_add函数在sysfs文件系统中为新生成的kobject对象建立一个新的目录。

/*lib/kobject.c*/
/**
 * kset_create_and_add() - Create a struct kset dynamically and add it to sysfs.
 *
 * @name: the name for the kset
 * @uevent_ops: a struct kset_uevent_ops for the kset
 * @parent_kobj: the parent kobject of this kset, if any.
 *
 * This function creates a kset structure dynamically and registers it
 * with sysfs.  When you are finished with this structure, call
 * kset_unregister() and the structure will be dynamically freed when it
 * is no longer being used.
 *
 * If the kset was not able to be created, NULL will be returned.
 */
struct kset *kset_create_and_add(const char *name,
				 const struct kset_uevent_ops *uevent_ops,
				 struct kobject *parent_kobj)
{
	struct kset *kset;
	int error;

	kset = kset_create(name, uevent_ops, parent_kobj);
	if (!kset)
		return NULL;
	error = kset_register(kset);
	if (error) {
		kfree(kset);
		return NULL;
	}
	return kset;
}
EXPORT_SYMBOL_GPL(kset_create_and_add);

此后,其他bus通过 bus_register 进行注册,实际上会注册到 bus_kest中。

3. bus

3.1 bus_type原型

/*./include/linux/device/bus.h*/
/**
 * struct bus_type - The bus type of the device
 *
 * @name:	The name of the bus.
 * @dev_name:	Used for subsystems to enumerate devices like ("foo%u", dev->id).
 * @dev_root:	Default device to use as the parent.
 * @bus_groups:	Default attributes of the bus.
 * @dev_groups:	Default attributes of the devices on the bus.
 * @drv_groups: Default attributes of the device drivers on the bus.
 * @match:	Called, perhaps multiple times, whenever a new device or driver
 *		is added for this bus. It should return a positive value if the
 *		given device can be handled by the given driver and zero
 *		otherwise. It may also return error code if determining that
 *		the driver supports the device is not possible. In case of
 *		-EPROBE_DEFER it will queue the device for deferred probing.
 * @uevent:	Called when a device is added, removed, or a few other things
 *		that generate uevents to add the environment variables.
 * @probe:	Called when a new device or driver add to this bus, and callback
 *		the specific driver's probe to initial the matched device.
 * @sync_state:	Called to sync device state to software state after all the
 *		state tracking consumers linked to this device (present at
 *		the time of late_initcall) have successfully bound to a
 *		driver. If the device has no consumers, this function will
 *		be called at late_initcall_sync level. If the device has
 *		consumers that are never bound to a driver, this function
 *		will never get called until they do.
 * @remove:	Called when a device removed from this bus.
 * @shutdown:	Called at shut-down time to quiesce the device.
 *
 * @online:	Called to put the device back online (after offlining it).
 * @offline:	Called to put the device offline for hot-removal. May fail.
 *
 * @suspend:	Called when a device on this bus wants to go to sleep mode.
 * @resume:	Called to bring a device on this bus out of sleep mode.
 * @num_vf:	Called to find out how many virtual functions a device on this
 *		bus supports.
 * @dma_configure:	Called to setup DMA configuration on a device on
 *			this bus.
 * @dma_cleanup:	Called to cleanup DMA configuration on a device on
 *			this bus.
 * @pm:		Power management operations of this bus, callback the specific
 *		device driver's pm-ops.
 * @iommu_ops:  IOMMU specific operations for this bus, used to attach IOMMU
 *              driver implementations to a bus and allow the driver to do
 *              bus-specific setup
 * @p:		The private data of the driver core, only the driver core can
 *		touch this.
 * @lock_key:	Lock class key for use by the lock validator
 * @need_parent_lock:	When probing or removing a device on this bus, the
 *			device core should lock the device's parent.
 *
 * A bus is a channel between the processor and one or more devices. For the
 * purposes of the device model, all devices are connected via a bus, even if
 * it is an internal, virtual, "platform" bus. Buses can plug into each other.
 * A USB controller is usually a PCI device, for example. The device model
 * represents the actual connections between buses and the devices they control.
 * A bus is represented by the bus_type structure. It contains the name, the
 * default attributes, the bus' methods, PM operations, and the driver core's
 * private data.
 */
struct bus_type {
	const char		*name;
	const char		*dev_name;
	struct device		*dev_root;
	const struct attribute_group **bus_groups;
	const struct attribute_group **dev_groups;
	const struct attribute_group **drv_groups;

	int (*match)(struct device *dev, struct device_driver *drv);
	int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
	int (*probe)(struct device *dev);
	void (*sync_state)(struct device *dev);
	void (*remove)(struct device *dev);
	void (*shutdown)(struct device *dev);

	int (*online)(struct device *dev);
	int (*offline)(struct device *dev);

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

	int (*num_vf)(struct device *dev);

	int (*dma_configure)(struct device *dev);
	void (*dma_cleanup)(struct device *dev);

	const struct dev_pm_ops *pm;

	const struct iommu_ops *iommu_ops;

	struct subsys_private *p;
	struct lock_class_key lock_key;

	bool need_parent_lock;
};

3.2 注册bus:bus_register

bus.c driver.c 分别对 bus,driverdevice进行管理,提供注册bus, driver和查找 device 功能。

bus_register(*bus) 这个函数会生成两个list,用来保存设备和驱动。

/*drivers/base/bus.c*/
/**
 * bus_register - register a driver-core subsystem
 * @bus: bus to register
 *
 * Once we have that, we register the bus with the kobject
 * infrastructure, then register the children subsystems it has:
 * the devices and drivers that belong to the subsystem.
 */
int bus_register(struct bus_type *bus)
{
	int retval;
	struct subsys_private *priv;
	struct lock_class_key *key = &bus->lock_key;

	priv = kzalloc(sizeof(struct subsys_private), GFP_KERNEL);
	/* 1. bus 与 prv 相互建立联系 */
	if (!priv)
		return -ENOMEM;

	// 私有数据 .bus ->  bus 本身
	priv->bus = bus;
	// bus->p 指向 priv
	bus->p = priv;
	// 内核通知链
	BLOCKING_INIT_NOTIFIER_HEAD(&priv->bus_notifier);
	
	/* 2. 设置 bus->prv->subsys->kobj */
	
	// 设置 priv->subsys.kobj.name = bus->name  对应于/sys/ 目录下的目录名
	retval = kobject_set_name(&priv->subsys.kobj, "%s", bus->name);
	if (retval)
		goto out;
	// 所有的 priv->subsys.kobj.kset 指向 bus_kse 对应于图中④与六的关系
	priv->subsys.kobj.kset = bus_kset;
	// 所有的priv->subsys.kobj.ktype 等于 bus_ktype
	priv->subsys.kobj.ktype = &bus_ktype;
	priv->drivers_autoprobe = 1;

	/* 3. 注册 kset (bus->prv->subsys priv->devices_kset priv->drivers_kset) */

	// 注册 priv->subsys ,由于 priv->subsys.kobj.kset = bus_kset,所以会在 /sys/bus/目录下创建 目录 如/sys/bus/plateform
	retval = kset_register(&priv->subsys);
	if (retval)
		goto out;

	retval = bus_create_file(bus, &bus_attr_uevent);
	if (retval)
		goto bus_uevent_fail;

	// 由于 priv->subsys.kobj.kset = bus_kset ,因此会创建 /sys/bus/XXX/devices 目录 如 /sys/bus/plateform/devices
	priv->devices_kset = kset_create_and_add("devices", NULL,
						 &priv->subsys.kobj);
	if (!priv->devices_kset) {
		retval = -ENOMEM;
		goto bus_devices_fail;
	}

	// 同理 创建 /sys/bus/XXX/devices 目录 如 /sys/bus/plateform/drivers
	priv->drivers_kset = kset_create_and_add("drivers", NULL,
						 &priv->subsys.kobj);
	if (!priv->drivers_kset) {
		retval = -ENOMEM;
		goto bus_drivers_fail;
	}

	INIT_LIST_HEAD(&priv->interfaces);
	__mutex_init(&priv->mutex, "subsys mutex", key);
	// 初始化 klist_devices 并设置get put 函数  初始化 klist_drivers 不知为何没有get put ?
	klist_init(&priv->klist_devices, klist_devices_get, klist_devices_put);
	klist_init(&priv->klist_drivers, NULL, NULL);

	retval = add_probe_files(bus);
	if (retval)
		goto bus_probe_files_fail;

	// 添加 bus->attrs 属性文件
	retval = bus_add_groups(bus, bus->bus_groups);
	if (retval)
		goto bus_groups_fail;

	pr_debug("bus: '%s': registered\n", bus->name);
	return 0;

bus_groups_fail:
	remove_probe_files(bus);
bus_probe_files_fail:
	kset_unregister(bus->p->drivers_kset);
bus_drivers_fail:
	kset_unregister(bus->p->devices_kset);
bus_devices_fail:
	bus_remove_file(bus, &bus_attr_uevent);
bus_uevent_fail:
	kset_unregister(&bus->p->subsys);
out:
	kfree(bus->p);
	bus->p = NULL;
	return retval;
}
EXPORT_SYMBOL_GPL(bus_register);

目前,能通过 bus_register 函数处理的工作有:

  1. Buspriv 相互建立联系,用于处理私有数据
  2. 注册通知链BLOCKING_INIT_NOTIFIER_HEAD(&priv->bus_notifier);
  3. 设置bus->priv->subsys(kset).kobj的名字为 bus->name
  4. 设置bus->priv->subsys(kset).kobj.kset 指向 bus_kset
  5. 设置bus->priv->subsys(kset).kobj.ktypebus_ktype ,提供 show store 函数
  6. 设置bus->priv->drivers_autoprobe = 1;
  7. 注册 bus->priv->subsys(kset):对应于图中④与⑥的关系

由于4,且没有指定bus->priv->subsys(kset).kobj.Parent,会将 bus_kest.kobj 设置为 bus->priv->subsys(kset).kobj.Parent
因此,会将bus->priv->subsys(kset).kobj.entry 加入bus_kest链表,且会在/sys/bus目录下创建相应的总线目录/sys/bus/$(bus->name),例如 /sys/bus/platform

  1. 创建 bus_attr_uevent->attr 属性文件
  2. 创建并注册 devices_ksetdevices_kset.kobj.parent = bus->priv->subsys.kobj ,名字为 device ,因此会创建 /sys/bus/$(bus->name)/devices
  3. 创建并注册 drivers_kset drivers_kset.kobj.parent = bus->priv->subsys.kobj,名字为 drivers ,因此会创建 /sys/bus/$(bus->name)/drivers
  4. 初始化 bus->priv->klist_devices 链表
  5. 初始化 bus->priv->klist_drivers 链表
  6. 创建 bus->bus_attrs 属性文件

4. driver

4.1 整体流程

driver_register(drv) [core.c]
  bus_add_driver(drv) [bus.c]
    if (drv->bus->p->drivers_autoprobe)
      driver_attach(dev)[dd.c]
        bus_for_each_dev(dev->bus, NULL, drv,__driver_attach)
        __driver_attach(dev, drv) [dd.c]
          driver_match_device(drv, dev) [base.h]
            drv-bus->match ? drv->bus-amatch(dev, drv) : 1
            if false, return;
          driver_probe_device(drv, dev) [dd.c]
            really_probe(dev, drv) [dd.c]
              dev-driver = drv;
              if (dev-bus->probe)
                dev->bus->probe(dev);
              else if (drv->probe)
                drv-aprobe(dev);
              probe_failed:
                dev->-driver = NULL;

4.2 device_driver原型

/*./include/linux/device/driver.h*/
/**
 * struct device_driver - The basic device driver structure
 * @name:	Name of the device driver.
 * @bus:	The bus which the device of this driver belongs to.
 * @owner:	The module owner.
 * @mod_name:	Used for built-in modules.
 * @suppress_bind_attrs: Disables bind/unbind via sysfs.
 * @probe_type:	Type of the probe (synchronous or asynchronous) to use.
 * @of_match_table: The open firmware table.
 * @acpi_match_table: The ACPI match table.
 * @probe:	Called to query the existence of a specific device,
 *		whether this driver can work with it, and bind the driver
 *		to a specific device.
 * @sync_state:	Called to sync device state to software state after all the
 *		state tracking consumers linked to this device (present at
 *		the time of late_initcall) have successfully bound to a
 *		driver. If the device has no consumers, this function will
 *		be called at late_initcall_sync level. If the device has
 *		consumers that are never bound to a driver, this function
 *		will never get called until they do.
 * @remove:	Called when the device is removed from the system to
 *		unbind a device from this driver.
 * @shutdown:	Called at shut-down time to quiesce the device.
 * @suspend:	Called to put the device to sleep mode. Usually to a
 *		low power state.
 * @resume:	Called to bring a device from sleep mode.
 * @groups:	Default attributes that get created by the driver core
 *		automatically.
 * @dev_groups:	Additional attributes attached to device instance once
 *		it is bound to the driver.
 * @pm:		Power management operations of the device which matched
 *		this driver.
 * @coredump:	Called when sysfs entry is written to. The device driver
 *		is expected to call the dev_coredump API resulting in a
 *		uevent.
 * @p:		Driver core's private data, no one other than the driver
 *		core can touch this.
 *
 * The device driver-model tracks all of the drivers known to the system.
 * The main reason for this tracking is to enable the driver core to match
 * up drivers with new devices. Once drivers are known objects within the
 * system, however, a number of other things become possible. Device drivers
 * can export information and configuration variables that are independent
 * of any specific device.
 */
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 */
	enum probe_type probe_type;

	const struct of_device_id	*of_match_table;
	const struct acpi_device_id	*acpi_match_table;

	int (*probe) (struct device *dev);
	void (*sync_state)(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 attribute_group **groups;
	const struct attribute_group **dev_groups;

	const struct dev_pm_ops *pm;
	void (*coredump) (struct device *dev);

	struct driver_private *p;
};

4.3 注册驱动并匹配:driver_register

/*drivers/base/driver.c*/
/**
 * driver_register - register driver with bus
 * @drv: driver to register
 *
 * We pass off most of the work to the bus_add_driver() call,
 * since most of the things we have to do deal with the bus
 * structures.
 */
int driver_register(struct device_driver *drv)
{
	int ret;
	struct device_driver *other;

	if (!drv->bus->p) {
		pr_err("Driver '%s' was unable to register with bus_type '%s' because the bus was not initialized.\n",
			   drv->name, drv->bus->name);
		return -EINVAL;
	}

	if ((drv->bus->probe && drv->probe) ||
	    (drv->bus->remove && drv->remove) ||
	    (drv->bus->shutdown && drv->shutdown))
		pr_warn("Driver '%s' needs updating - please use "
			"bus_type methods\n", drv->name);

	// 判断是否被注册过了。
	other = driver_find(drv->name, drv->bus);
	if (other) {
		pr_err("Error: Driver '%s' is already registered, "
			"aborting...\n", drv->name);
		return -EBUSY;
	}

	// 1、添加驱动到bus中
	ret = bus_add_driver(drv);
	if (ret)
		return ret;
	ret = driver_add_groups(drv, drv->groups);
	if (ret) {
		bus_remove_driver(drv);
		return ret;
	}
	kobject_uevent(&drv->p->kobj, KOBJ_ADD);

	return ret;
}
EXPORT_SYMBOL_GPL(driver_register);

driver_register做了这几件事情:

  1. 判断driver是否被注册过:通过名字查找总线中是否已经存在同名的对象
  2. 把驱动添加进bus中,
  3. 进行通知到用户空间。

4.4 在bus_add_driver中注册

// drivers/base/base.h
struct driver_private {
	struct kobject kobj;
	struct klist klist_devices;
	struct klist_node knode_bus;
	struct module_kobject *mkobj;
	struct device_driver *driver;
};

// drivers/base/bus.c
/**
 * 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;
	// 在/sys/bus/xxx/drivers 目录下创建目录
	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;

	// 将 driver 加入 Bus 的 drivers 链表中
	klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);
	// 1、匹配 dev
	if (drv->bus->p->drivers_autoprobe) {
		error = driver_attach(drv);
		if (error)
			goto out_unregister;
	}
	// 如果设置了drv->mod_name 根据名字寻找模块
	module_add_driver(drv->owner, drv);

	// 在/sys/bus/xxx/drivers/创建属性文件
	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_groups(drv, bus->drv_groups);
	if (error) {
		/* How the hell do we get out of this pickle? Give up */
		printk(KERN_ERR "%s: driver_add_groups(%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);
		}
	}

	return 0;

out_unregister:
	kobject_put(&priv->kobj);
	/* drv->p is freed in driver_release()  */
	drv->p = NULL;
out_put_bus:
	bus_put(bus);
	return error;
}

在向Bus注册一个driver时,会调用到 driver_attch来寻找与之配对的 deivice

4.4.1 driver_attach

从逻辑上来说,一个驱动可以支持多个设备;一个设备只能绑定一个驱动。

因此,driver_attach最终一一遍历目前所有的驱动和设备,并绑定对应的设备。

/*drivers/base/dd.c*/
/**
 * 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);

4.4.2 遍历bus_for_each_dev
/*drivers/base/bus.c*/
/**
 * 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 *))
{
	// 迭代器,在这里用于遍历device
	struct klist_iter i;
	struct device *dev;
	int error = 0;

	if (!bus || !bus->p)
		return -EINVAL;

	// 设置迭代器的起点为 链表的头部
	klist_iter_init_node(&bus->p->klist_devices, &i,
			     (start ? &start->p->knode_bus : NULL));
	while (!error && (dev = next_device(&i)))
		error = fn(dev, data);
	klist_iter_exit(&i);
	return error;
}
EXPORT_SYMBOL_GPL(bus_for_each_dev);

根据名字我们应该能猜测出来,调用Bus的每一个 devdriver 进行__driver_attach

具体的做法是通过初始化一个迭代器指向链表的头部(在这里是bus->p->klist_devices),然后通过next_device进行遍历,并逐一执行fn方法。

/*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 = NULL;
	if (n && kref_get_unless_zero(&n->n_ref))
		i->i_cur = n;
}
EXPORT_SYMBOL_GPL(klist_iter_init_node);

// drivers/base/core.c
static struct device *next_device(struct klist_iter *i)
{
	struct klist_node *n = klist_next(i);
	struct device *dev = NULL;
	struct device_private *p;

	if (n) {
		p = to_device_private_parent(n);
		dev = p->device;
	}
	return dev;
}

// drivers/base/base.h
#define to_device_private_parent(obj)   \
    container_of(obj, struct device_private, knode_parent)

我们看看fn,在这里它执行的是__driver_attach

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

	/*
	 * 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.
	 */
	//当设备和驱动的名字不匹配的时候返回的是0,然后就会调用下面的return 0;
	ret = driver_match_device(drv, dev);//驱动和设备是否匹配,不匹配将退出,bus_for_each_dev函数继续下一个设备来匹配
	if (ret == 0) {
		/* no match */
		return 0;
	} else if (ret == -EPROBE_DEFER) {
		dev_dbg(dev, "Device match requests probe deferral\n");
		driver_deferred_probe_add(dev);
	} else if (ret < 0) {
		dev_dbg(dev, "Bus failed to match device: %d", ret);
		return ret;
	} /* ret > 0 means positive match */

	if (dev->parent && dev->bus->need_parent_lock)
		device_lock(dev->parent);
	device_lock(dev);
	// 2、从这里开始probe
	if (!dev->p->dead && !dev->driver)
		driver_probe_device(drv, dev);//调用探测函数进行探测,并且调用platform_driver中的probe函数
	device_unlock(dev);
	if (dev->parent && dev->bus->need_parent_lock)
		device_unlock(dev->parent);

	return 0;
}

__driver_attach 中,首先会调用到 driver_match_device 函数(return drv->bus->match ? drv->bus->match(dev, drv) : 1;)进行匹配:

/*drivers/base/base.h*/
static inline int driver_match_device(struct device_driver *drv, struct device *dev)
{
	return drv->bus->match ? drv->bus->match(dev, drv) : 1;//调用match函数,如果没用则默认返回1
}

match方法存在时,进行匹配,返回0代表成功。否则代表失败。

如果匹配成功,则继续调用 driver_probe_device(drv, dev)

/*drivers/base/dd.c*/

/**
 * driver_probe_device - attempt to bind device & driver together
 * @drv: driver to bind a device to
 * @dev: device to try to bind to the driver
 *
 * This function returns -ENODEV if the device is not registered, -EBUSY if it
 * already has a driver, 0 if the device is bound successfully and a positive
 * (inverted) error code for failures from the ->probe method.
 *
 * This function must be called with @dev lock held.  When called for a
 * USB interface, @dev->parent lock must be held as well.
 *
 * If the device has a parent, runtime-resume the parent before driver probing.
 */
static int driver_probe_device(struct device_driver *drv, struct device *dev)
{
	int trigger_count = atomic_read(&deferred_trigger_count);
	int ret;

	atomic_inc(&probe_count);
	ret = __driver_probe_device(drv, dev);
	if (ret == -EPROBE_DEFER || ret == EPROBE_DEFER) {
		driver_deferred_probe_add(dev);

		/*
		 * Did a trigger occur while probing? Need to re-trigger if yes
		 */
		if (trigger_count != atomic_read(&deferred_trigger_count) &&
		    !defer_all_probes)
			driver_deferred_probe_trigger();
	}
	atomic_dec(&probe_count);
	wake_up_all(&probe_waitqueue);
	return ret;
}

static int __driver_probe_device(struct device_driver *drv, struct device *dev)
{
	int ret = 0;

	if (dev->p->dead || !device_is_registered(dev))
		return -ENODEV;
	if (dev->driver)
		return -EBUSY;

	dev->can_match = true;
	pr_debug("bus: '%s': %s: matched device %s with driver %s\n",
		 drv->bus->name, __func__, dev_name(dev), drv->name);

	pm_runtime_get_suppliers(dev);
	if (dev->parent)
		pm_runtime_get_sync(dev->parent);

	pm_runtime_barrier(dev);
	if (initcall_debug)
		ret = really_probe_debug(dev, drv);
	else
		ret = really_probe(dev, drv);
	pm_request_idle(dev);

	if (dev->parent)
		pm_runtime_put(dev->parent);

	pm_runtime_put_suppliers(dev);
	return ret;
}

4.5 在really_probe中probe

4.5.1 device原型
/*include/linux/device.h*/
/**
 * struct device - The basic device structure
 * @parent:	The device's "parent" device, the device to which it is attached.
 * 		In most cases, a parent device is some sort of bus or host
 * 		controller. If parent is NULL, the device, is a top-level device,
 * 		which is not usually what you want.
 * @p:		Holds the private data of the driver core portions of the device.
 * 		See the comment of the struct device_private for detail.
 * @kobj:	A top-level, abstract class from which other classes are derived.
 * @init_name:	Initial name of the device.
 * @type:	The type of device.
 * 		This identifies the device type and carries type-specific
 * 		information.
 * @mutex:	Mutex to synchronize calls to its driver.
 * @bus:	Type of bus device is on.
 * @driver:	Which driver has allocated this
 * @platform_data: Platform data specific to the device.
 * 		Example: For devices on custom boards, as typical of embedded
 * 		and SOC based hardware, Linux often uses platform_data to point
 * 		to board-specific structures describing devices and how they
 * 		are wired.  That can include what ports are available, chip
 * 		variants, which GPIO pins act in what additional roles, and so
 * 		on.  This shrinks the "Board Support Packages" (BSPs) and
 * 		minimizes board-specific #ifdefs in drivers.
 * @driver_data: Private pointer for driver specific info.
 * @links:	Links to suppliers and consumers of this device.
 * @power:	For device power management.
 *		See Documentation/driver-api/pm/devices.rst for details.
 * @pm_domain:	Provide callbacks that are executed during system suspend,
 * 		hibernation, system resume and during runtime PM transitions
 * 		along with subsystem-level and driver-level callbacks.
 * @pins:	For device pin management.
 *		See Documentation/driver-api/pinctl.rst for details.
 * @msi_list:	Hosts MSI descriptors
 * @msi_domain: The generic MSI domain this device is using.
 * @numa_node:	NUMA node this device is close to.
 * @dma_ops:    DMA mapping operations for this device.
 * @dma_mask:	Dma mask (if dma'ble device).
 * @coherent_dma_mask: Like dma_mask, but for alloc_coherent mapping as not all
 * 		hardware supports 64-bit addresses for consistent allocations
 * 		such descriptors.
 * @bus_dma_mask: Mask of an upstream bridge or bus which imposes a smaller DMA
 *		limit than the device itself supports.
 * @dma_pfn_offset: offset of DMA memory range relatively of RAM
 * @dma_parms:	A low level driver may set these to teach IOMMU code about
 * 		segment limitations.
 * @dma_pools:	Dma pools (if dma'ble device).
 * @dma_mem:	Internal for coherent mem override.
 * @cma_area:	Contiguous memory area for dma allocations
 * @archdata:	For arch-specific additions.
 * @of_node:	Associated device tree node.
 * @fwnode:	Associated device node supplied by platform firmware.
 * @devt:	For creating the sysfs "dev".
 * @id:		device instance
 * @devres_lock: Spinlock to protect the resource of the device.
 * @devres_head: The resources list of the device.
 * @knode_class: The node used to add the device to the class list.
 * @class:	The class of the device.
 * @groups:	Optional attribute groups.
 * @release:	Callback to free the device after all references have
 * 		gone away. This should be set by the allocator of the
 * 		device (i.e. the bus driver that discovered the device).
 * @iommu_group: IOMMU group the device belongs to.
 * @iommu_fwspec: IOMMU-specific properties supplied by firmware.
 *
 * @offline_disabled: If set, the device is permanently online.
 * @offline:	Set after successful invocation of bus type's .offline().
 * @of_node_reused: Set if the device-tree node is shared with an ancestor
 *              device.
 *
 * At the lowest level, every device in a Linux system is represented by an
 * instance of struct device. The device structure contains the information
 * that the device model core needs to model the system. Most subsystems,
 * however, track additional information about the devices they host. As a
 * result, it is rare for devices to be represented by bare device structures;
 * instead, that structure, like kobject structures, is usually embedded within
 * a higher-level representation of the device.
 */
struct device {
	struct device		*parent;

	struct device_private	*p;

	struct kobject kobj;
	const char		*init_name; /* initial name of the device */
	const struct device_type *type;

	struct mutex		mutex;	/* mutex to synchronize calls to
					 * its driver.
					 */

	struct bus_type	*bus;		/* type of bus device is on */
	struct device_driver *driver;	/* which driver has allocated this
					   device */
	void		*platform_data;	/* Platform specific data, device
					   core doesn't touch it */
	void		*driver_data;	/* Driver data, set and get with
					   dev_set/get_drvdata */
	struct dev_links_info	links;
	struct dev_pm_info	power;
	struct dev_pm_domain	*pm_domain;

#ifdef CONFIG_GENERIC_MSI_IRQ_DOMAIN
	struct irq_domain	*msi_domain;
#endif
#ifdef CONFIG_PINCTRL
	struct dev_pin_info	*pins;
#endif
#ifdef CONFIG_GENERIC_MSI_IRQ
	struct list_head	msi_list;
#endif

#ifdef CONFIG_NUMA
	int		numa_node;	/* NUMA node this device is close to */
#endif
	const struct dma_map_ops *dma_ops;
	u64		*dma_mask;	/* dma mask (if dma'able device) */
	u64		coherent_dma_mask;/* Like dma_mask, but for
					     alloc_coherent mappings as
					     not all hardware supports
					     64 bit addresses for consistent
					     allocations such descriptors. */
	u64		bus_dma_mask;	/* upstream dma_mask constraint */
	unsigned long	dma_pfn_offset;

	struct device_dma_parameters *dma_parms;

	struct list_head	dma_pools;	/* dma pools (if dma'ble) */

	struct dma_coherent_mem	*dma_mem; /* internal for coherent mem
					     override */
#ifdef CONFIG_DMA_CMA
	struct cma *cma_area;		/* contiguous memory area for dma
					   allocations */
#endif
	/* arch specific additions */
	struct dev_archdata	archdata;

	struct device_node	*of_node; /* associated device tree node */
	struct fwnode_handle	*fwnode; /* firmware device node */

	dev_t			devt;	/* dev_t, creates the sysfs "dev" */
	u32			id;	/* device instance */

	spinlock_t		devres_lock;
	struct list_head	devres_head;

	struct klist_node	knode_class;
	struct class		*class;
	const struct attribute_group **groups;	/* optional groups */

	void	(*release)(struct device *dev);
	struct iommu_group	*iommu_group;
	struct iommu_fwspec	*iommu_fwspec;

	bool			offline_disabled:1;
	bool			offline:1;
	bool			of_node_reused:1;
};
4.5.2 device和driver绑定

当增加新device的时候,bus 会轮循它的驱动列表来找到一个匹配的驱动,它们是通过device iddriverid_table来进行 ”匹配”的,主要是在 driver_match_device()[drivers/base/base.h] 通过 bus->match() 这个callback来让驱动判断是否支持该设备,一旦匹配成功,devicedriver字段会被设置成相应的driver指针 :

/*drivers/base/dd.c*/
static int really_probe(struct device *dev, struct device_driver *drv)
{
	bool test_remove = IS_ENABLED(CONFIG_DEBUG_TEST_DRIVER_REMOVE) &&
			   !drv->suppress_bind_attrs;
	int ret;

	if (defer_all_probes) {
		/*
		 * Value of defer_all_probes can be set only by
		 * device_block_probing() which, in turn, will call
		 * wait_for_device_probe() right after that to avoid any races.
		 */
		dev_dbg(dev, "Driver %s force probe deferral\n", drv->name);
		return -EPROBE_DEFER;
	}

	ret = device_links_check_suppliers(dev);
	if (ret)
		return ret;

	pr_debug("bus: '%s': %s: probing driver %s with device %s\n",
		 drv->bus->name, __func__, drv->name, dev_name(dev));
	if (!list_empty(&dev->devres_head)) {
		dev_crit(dev, "Resources present before probing\n");
		ret = -EBUSY;
		goto done;
	}

re_probe:
	// 1、关联 dev 与 drv
	dev->driver = drv;

	/* If using pinctrl, bind pins now before probing */
	ret = pinctrl_bind_pins(dev);
	if (ret)
		goto pinctrl_bind_failed;

	if (dev->bus->dma_configure) {
		ret = dev->bus->dma_configure(dev);
		if (ret)
			goto pinctrl_bind_failed;
	}

	// 2、更新 sysfs
	ret = driver_sysfs_add(dev);
	if (ret) {
		pr_err("%s: driver_sysfs_add(%s) failed\n",
		       __func__, dev_name(dev));
		goto sysfs_failed;
	}

	if (dev->pm_domain && dev->pm_domain->activate) {
		ret = dev->pm_domain->activate(dev);
		if (ret)
			goto probe_failed;
	}
	// 3、执行真正的probe
	ret = call_driver_probe(dev, drv);
	if (ret) {
		/*
		 * Return probe errors as positive values so that the callers
		 * can distinguish them from other errors.
		 */
		ret = -ret;
		goto probe_failed;
	}

	ret = device_add_groups(dev, drv->dev_groups);
	if (ret) {
		dev_err(dev, "device_add_groups() failed\n");
		goto dev_groups_failed;
	}

	if (dev_has_sync_state(dev)) {
		ret = device_create_file(dev, &dev_attr_state_synced);
		if (ret) {
			dev_err(dev, "state_synced sysfs add failed\n");
			goto dev_sysfs_state_synced_failed;
		}
	}

	if (test_remove) {
		test_remove = false;

		device_remove(dev);
		driver_sysfs_remove(dev);
		device_unbind_cleanup(dev);

		goto re_probe;
	}

	pinctrl_init_done(dev);

	if (dev->pm_domain && dev->pm_domain->sync)
		dev->pm_domain->sync(dev);
	// 4、绑定
	driver_bound(dev);
	pr_debug("bus: '%s': %s: bound device %s to driver %s\n",
		 drv->bus->name, __func__, dev_name(dev), drv->name);
	goto done;

dev_sysfs_state_synced_failed:
dev_groups_failed:
	device_remove(dev);
probe_failed:
	driver_sysfs_remove(dev);
sysfs_failed:
	if (dev->bus)
		blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
					     BUS_NOTIFY_DRIVER_NOT_BOUND, dev);
	if (dev->bus && dev->bus->dma_cleanup)
		dev->bus->dma_cleanup(dev);
pinctrl_bind_failed:
	device_links_no_driver(dev);
	device_unbind_cleanup(dev);
done:
	return ret;
}

然后 callback driverprobe 或者 connect 函数,进行一些初始化操作。

同理,当增加新的driver时,bus也会执行相同的动作,为驱动查找设备。因此,绑定发生在两个阶段:

  1. 驱动找设备,发生在driverbus系统注册自己时候,函数调用链是:
    driver_register --> bus_add_driver --> driver_attach() [dd.c] -- 将轮循device链表,查找匹配的device。
    
  2. 设备查找驱动,发生在设备增加到总线的的时候,函数调用链是:
    device_add --> bus_probe_device --> device_initial_probe --> device_attach -- 将轮循driver链表,查找匹配的driver。
    

匹配成功后,系统继续调用 driver_probe_device()callback drv->probe(dev)' 或者 bus->probe(dev) -->drv->connect(),在probe或者connect函数里面,驱动开始实际的初始化操作。因此,probe() 或者 connect() 是真正的驱动入口。

really_probe 中干了四件大事。

4.5.2.1 关联dev与drv

dev 中记录 driver

dev->driver = drv;

已经match上了配对成功了嘛,所以可以将该devicedriver关联起来:dev -> drv

然而device_driver中并没有device成员,因此并没有drv ->dev

4.5.2.2 通知bus、更新sysfs
driver_sysfs_add(dev);
  1. 通知总线绑定了设备和驱动
  2. 创建两个symlink,更新sysfs
    • sysfs中该 dev.kobj 目录下创建与之匹配的driver的符号连接,名字为driver
    • sysfs中该 driver.kobj 目录下创建与之匹配的device的符号连接,名字为 kobject_name(&dev->kobj)
/*drivers/base/dd.c*/

static int driver_sysfs_add(struct device *dev)
{
	int ret;
	/ 通知总线绑定了设备和驱动
	if (dev->bus)
		blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
					     BUS_NOTIFY_BIND_DRIVER, dev);
	/* 例如,
       在/sys/bus/XXX/drivers/XXX 目录下建立symlink,链接名为 kobj->name, 
       链接指向 /sys/devices/platform/XXX 
    */
	ret = sysfs_create_link(&dev->driver->p->kobj, &dev->kobj,
				kobject_name(&dev->kobj));
	if (ret)
		goto fail;
	/*  例如,
            在/sys/devices/platform/XXX/下建立symlink,链接名为driver, 
            指向/sys/bus/xxx/drivers目录下的某个目录
     */
	ret = sysfs_create_link(&dev->kobj, &dev->driver->p->kobj,
				"driver");
	if (ret)
		goto rm_dev;

	if (!IS_ENABLED(CONFIG_DEV_COREDUMP) || !dev->driver->coredump)
		return 0;

	ret = device_create_file(dev, &dev_attr_coredump);
	if (!ret)
		return 0;

	sysfs_remove_link(&dev->kobj, "driver");

rm_dev:
	sysfs_remove_link(&dev->driver->p->kobj,
			  kobject_name(&dev->kobj));

fail:
	return ret;
}
4.5.2.3 执行真正的 probe 方法
    if (dev->bus->probe) {
        ret = dev->bus->probe(dev);
        if (ret)
            goto probe_failed;
    } else if (drv->probe) {
        ret = drv->probe(dev);
        if (ret)
            goto probe_failed;
    }

probe的规则是:如果BUS上实现了probe就用BUS的probe;否则才会用driverprobe

4.5.2.4 绑定
driver_bound(dev);

device 放入 driver 链表中。

看来一个device只能有一个driver,但是driver可以支持多个device

/*drivers/base/dd.c*/
static void driver_bound(struct device *dev)
{
	// 判断是否绑定过
	if (device_is_bound(dev)) {
		pr_warn("%s: device %s already bound\n",
			__func__, kobject_name(&dev->kobj));
		return;
	}

	pr_debug("driver: '%s': %s: bound to device '%s'\n", dev->driver->name,
		 __func__, dev_name(dev));
	// 将 device 放入 driver 链表中。
	klist_add_tail(&dev->p->knode_driver, &dev->driver->p->klist_devices);
	device_links_driver_bound(dev);

	device_pm_check_callbacks(dev);

	/*
	 * Make sure the device is no longer in one of the deferred lists and
	 * kick off retrying all pending devices
	 */
	driver_deferred_probe_del(dev);
	driver_deferred_probe_trigger();

	if (dev->bus)
		blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
					     BUS_NOTIFY_BOUND_DRIVER, dev);

	kobject_uevent(&dev->kobj, KOBJ_BIND);
}


//lib/klist.c
/**
 * klist_node_attached - Say whether a node is bound to a list or not.
 * @n: Node that we're testing.
 */
int klist_node_attached(struct klist_node *n)
{
	return (n->n_klist != NULL);
}
EXPORT_SYMBOL_GPL(klist_node_attached);


4.5.3 kobject_uevent通知用户空间

主要是在/sys/devices/.../中添加devuevent属性文件。

5. device

5.1 整体流程

device_register(dev)[core.c]
  device_initialize()
  device_add(dev) [core.c]
    bus_add_device(dev)
    bus_probe_device(dev) [bus.c]
      if (dev->bus && dev->bus-op->drivers_autoprobe)
        device_attach(dev) [dd.c]
          if (dev->driver)
            device_bind_driver(dev)
          else // 从这里开始,与 driver一样
            bus_for_each_dev(dev->bus, NULL, drv,__driver_attach)
              __driver_attach(dev, drv) [dd.c]
                driver_match_device(drv, dev) [base.h]
                  drv-bus->match ? drv->bus-amatch(dev, drv) : 1
                  if false, return;
                driver_probe_device(drv, dev) [dd.c]
                  really_probe(dev, drv) [dd.c]
                    dev-driver = drv;
                    if (dev-bus->probe)
                      dev->bus->probe(dev);
                    else if (drv->probe)
                      drv-aprobe(dev);
                    probe_failed:
                      dev->-driver = NULL;

5.2 device原型

上面已经贴出来了源码了,这里不重复占用空间了。

/**
 * driver_register - register driver with bus
 * @drv: driver to register
 *
 * We pass off most of the work to the bus_add_driver() call,
 * since most of the things we have to do deal with the bus
 * structures.
 */
int driver_register(struct device_driver *drv)
{
	int ret;
	struct device_driver *other;

	if (!drv->bus->p) {
		pr_err("Driver '%s' was unable to register with bus_type '%s' because the bus was not initialized.\n",
			   drv->name, drv->bus->name);
		return -EINVAL;
	}
	
	//检测总线的操作函数和驱动的操作函数是否同时存在
	//三种可能都会被考虑进行注册的动作,
	 //drv的总线probe和drv的probe都已定义
	 //drv的总线remove和drv的remove都已经定义
	 //drv的总线shutdown和drv的shutdown都已经进行定义
	if ((drv->bus->probe && drv->probe) ||
	    (drv->bus->remove && drv->remove) ||
	    (drv->bus->shutdown && drv->shutdown))
		printk(KERN_WARNING "Driver '%s' needs updating - please use "
			"bus_type methods\n", drv->name);
			
	//去已经注册的driver链表上查看,该driver是否已经被注册,防止重复注册同一个driver
	other = driver_find(drv->name, drv->bus);//这里查找drv->name="hub"驱动是否已经在drv->bus总线上注册,并增加引用计数,若已经注册,则返回提示信息
	if (other) {
		printk(KERN_ERR "Error: Driver '%s' is already registered, "
			"aborting...\n", drv->name);
		return -EBUSY;
	}

	/把这个driver添加到该总线维护的driver链表中
	ret = bus_add_driver(drv);//如果之前没注册,就在此注册
	if (ret)
		return ret;
	ret = driver_add_groups(drv, drv->groups);
	if (ret) {
		bus_remove_driver(drv);
		return ret;
	}
	kobject_uevent(&drv->p->kobj, KOBJ_ADD);

	return ret;
}
EXPORT_SYMBOL_GPL(driver_register);

5.3 device_add

// drviers/base/core.c
/**
 * device_add - add device to device hierarchy.
 * @dev: device.
 *
 * This is part 2 of device_register(), though may be called
 * separately _iff_ device_initialize() has been called separately.
 *
 * This adds @dev to the kobject hierarchy via kobject_add(), adds it
 * to the global and sibling lists for the device, then
 * adds it to the other relevant subsystems of the driver model.
 *
 * Do not call this routine or device_register() more than once for
 * any device structure.  The driver model core is not designed to work
 * with devices that get unregistered and then spring back to life.
 * (Among other things, it's very hard to guarantee that all references
 * to the previous incarnation of @dev have been dropped.)  Allocate
 * and register a fresh new struct device instead.
 *
 * NOTE: _Never_ directly free @dev after calling this function, even
 * if it returned an error! Always use put_device() to give up your
 * reference instead.
 */
int device_add(struct device *dev)
{
    struct device *parent = NULL;
    struct kobject *kobj;
    struct class_interface *class_intf;
    int error = -EINVAL;

    dev = get_device(dev);
    if (!dev)
        goto done;

    // 初始化 device的 私有数据
    if (!dev->p) {
        error = device_private_init(dev);
        if (error)
            goto done;
    }

    /*
     * for statically allocated devices, which should all be converted
     * some day, we need to initialize the name. We prevent reading back
     * the name, and force the use of dev_name()
     */
    /* 初始化设备内部的kobject的名字 */
    // 如果初始名字(init_name)存在,则设为名字 为 init_name
    if (dev->init_name) {
        dev_set_name(dev, "%s", dev->init_name);
        dev->init_name = NULL;
    }

    /* subsystems can specify simple device enumeration */
    // 如果检查发现没有名字,但bus设置了设备名前缀,则以 类似foo%u的方式来设置设备的名字
    // 例如 tty0
    if (!dev_name(dev) && dev->bus && dev->bus->dev_name)
        dev_set_name(dev, "%s%u", dev->bus->dev_name, dev->id);

    pr_debug("device: '%s': %s\n", dev_name(dev), __func__);

    // 增加设备父设备并增加父设备引用计数,例如:csid的设备节点节v4l-subdev4的父设备是fd8c0000.qcom,msm-cam
    parent = get_device(dev->parent);
    // 获取v4l-subdev4设备目录的父目录是video4linux,video4linux的父目录是fd8c0000.qcom,msm-cam
    kobj = get_device_parent(dev, parent);
    // 在kobject层实现设备父子关系
    if (kobj)
        dev->kobj.parent = kobj;


    /* first, register with generic layer. */
    /* we require the name to be set before, and pass NULL */
    /* 
       把内嵌的kobject注册到设备模型中将设备加入到kobject模型中,
       创建sys相关目录 ,目录名字为kobj->name
    */
    error = kobject_add(&dev->kobj, dev->kobj.parent, NULL);
    if (error)
        goto Error;

    /* notify platform of device entry */
    if (platform_notify)
        platform_notify(dev);

    // 创建sys目录下设备的uevent属性文件,通过它可以查看设备的uevent事件
    error = device_create_file(dev, &dev_attr_uevent);
    if (error)
        goto attrError;

    // 如果有主次设备号 创建dev 属性文件
    if (MAJOR(dev->devt)) {
        // 在 /sys/devices中创建设备节点
        error = device_create_file(dev, &dev_attr_dev);

        /* 在/sys/dev/char/或者/sys/dev/block/创建devt的属性的连接文件,
        形如10:45,由主设备号和次设备号构成,指向/sys/devices/.../的具体设备目录,
        该链接文件只具备读属性,显示主设备号:次设备号,如10:45,用户空间udev相应uevent事件时,将根据设备号在/dev下创建节点文件
        */
        error = device_create_sys_dev_entry(dev);


        devtmpfs_create_node(dev);
    }

    // 创建类符号链接,相互创建dev和class之间的链接文件
    error = device_add_class_symlinks(dev);
    // 创建sys目录下设备其他属性文件
    error = device_add_attrs(dev);
    // 将设备加入到管理它的bus总线的设备链表上
    // 创建subsystem链接文件,链接class下的具体的子系统文件夹
    error = bus_add_device(dev);

    error = dpm_sysfs_add(dev);

    device_pm_add(dev);

    /* Notify clients of device addition.  This call must come
     * after dpm_sysfs_add() and before kobject_uevent().
     */
    // 通知 添加设备 事件
    if (dev->bus)
        blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
                         BUS_NOTIFY_ADD_DEVICE, dev);

    kobject_uevent(&dev->kobj, KOBJ_ADD);
    bus_probe_device(dev);
    if (parent)
        klist_add_tail(&dev->p->knode_parent,
                   &parent->p->klist_children);

    if (dev->class) {
        mutex_lock(&dev->class->p->mutex);
        /* tie the class to the device */
        klist_add_tail(&dev->knode_class,
                   &dev->class->p->klist_devices);

        /* notify any interfaces that the device is here */
        list_for_each_entry(class_intf,
                    &dev->class->p->interfaces, node)
            if (class_intf->add_dev)
                class_intf->add_dev(dev, class_intf);
        mutex_unlock(&dev->class->p->mutex);
    }
done:
    put_device(dev);
    return error;

}
EXPORT_SYMBOL_GPL(device_add);

5.4 bus_add_device

/**
 * bus_add_device - add device to bus
 * @dev: device being added
 *
 * - Add device's bus attributes.
 * - Create links to device's bus.
 * - Add the device to its bus's list of devices.
 */
int bus_add_device(struct device *dev)
{
    /* 引用计数加一 */
    struct bus_type *bus = bus_get(dev->bus);
    int error = 0;

    if (bus) {
        pr_debug("bus: '%s': add device %s\n", bus->name, dev_name(dev));
        /* 创建相应的属性文件 */
        error = device_add_attrs(bus, dev);


        // 添加属性组。
        error = device_add_groups(dev, bus->dev_groups);

        // 创建 /sys/bus/$(bus->name)/devices/$(dev->name) 到 /sys/devices/$(dev->name) 的软连接
        error = sysfs_create_link(&bus->p->devices_kset->kobj,
                                  &dev->kobj, dev_name(dev));

        // 创建 /sys/devices/$(dev->name)/subsystem 到 /sys/bus/$(bus->name)/devices/$(dev->name) 的软连接
        error = sysfs_create_link(&dev->kobj,
                                  &dev->bus->p->subsys.kobj, "subsystem");
        if (error)
            goto out_subsys;
        // 将 dev 加入 bus 所管理的 devices 链表
        klist_add_tail(&dev->p->knode_bus, &bus->p->klist_devices);
    }
    return 0;

}

接下来就和我之前的文章介绍的Linux设备驱动和设备匹配过程文章中一样了,总共分为两种匹配方式。

refer to

  1. Linux 内核:设备驱动模型(2)driver-bus-device与probe
  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
pcf8563_i2c1_r8_ruoge_ov2640通过给RTC驱动增加设备节点读取秒钟成功+直接读取I2C1获取秒钟值20160626_2201.7z http://blog.csdn.net/21cnbao/article/details/7919055 在Android源码树中添加userspace I2C读写工具(i2c-util) 本文使用的开发板是:杭州若格科技有限公司的全志R8。CPU:CPUARM Cortex-A8 更多芯片资料请参见全志官网: http://www.allwinnertech.com/clq/r/R8.html 通过/dev/i2c-n节点,用户可以在userspace直接访问板上的i2c外设寄存器,主要是透过I2C_RDWR这个IO控制命令将i2c_msg数组传递给kernel去执行。 开发板的/dev/i2c-1总线下挂有一片I2C的RTC:pcf8563。 root@android:/dev # cd /sys/class/i2c-adapter/ root@android:/sys/class/i2c-adapter # ll lrwxrwxrwx root root 1970-01-02 08:31 i2c-0 -> ../../devices/platform/sun5i-i2c.0/i2c-0 lrwxrwxrwx root root 1970-01-02 08:31 i2c-1 -> ../../devices/platform/sun5i-i2c.1/i2c-1 lrwxrwxrwx root root 1970-01-02 08:31 i2c-2 -> ../../devices/platform/sun5i-i2c.2/i2c-2 root@android:/sys/class/i2c-adapter # cd i2c-1 root@android:/sys/class/i2c-adapter/i2c-1 # ll drwxr-xr-x root root 1970-01-02 08:31 1-0051 --w------- root root 4096 1970-01-02 08:31 delete_device lrwxrwxrwx root root 1970-01-02 08:31 device -> ../../sun5i-i2c.1 drwxr-xr-x root root 1970-01-01 08:00 i2c-dev -r--r--r-- root root 4096 1970-01-02 08:31 name --w------- root root 4096 1970-01-02 08:31 new_device drwxr-xr-x root root 1970-01-01 08:00 power lrwxrwxrwx root root 1970-01-02 08:31 subsystem -> ../../../../bus/i2c -rw-r--r-- root root 4096 1970-01-01 08:00 uevent root@android:/sys/class/i2c-adapter/i2c-1 # root@android:/sys/class/i2c-adapter/i2c-1 # cd 1-0051/ root@android:/sys/class/i2c-adapter/i2c-1/1-0051 # ll lrwxrwxrwx root root 1970-01-02 10:18 driver -> ../../../../../bus/i2c/drivers/pcf8563 -r--r--r-- root root 4096 1970-01-02 10:18 modalias -r--r--r-- root root 4096 1970-01-02 10:18 name drwxr-xr-x root root 1970-01-02 10:18 power drwxr-xr-x root root 1970-01-02 10:18 rtc lrwxrwxrwx root root 1970-01-02 10:18 subsystem -> ../../../../../bus/i2c -rw-r--r-- root root 4096 1970-01-02 10:18 uevent root@android:/sys/class/i2c-adapter/i2c-1/1-0051 # cat name pcf8563 root@android:/sys/class/i2c-adapter/i2c-1/1-0051 # 注释:1-0051 1 表示 i2c-1这条I2C1总线上挂载的设备,如果是I2C2总线上挂载的设备,路径就是2-00XX了。 0051 一般的I2C设备的从机地址都是一个字节的,因为前两位为0x00(16进制的),后两位为pcf8563移位自后的I2C从机地址0x51(也是16进制的) 压缩包中的PCF8563-CN.pdf,datasheet告诉我们:I2C总线从地址:读,0A3H;写,0A2H。右移一位之后正好是0x51。 下面的代码可以完成这个功能: #include #include #include #include #include #include #include #include #include #include #include /* This is the structure as used in the I2C_RDWR ioctl call */ struct i2c_rdwr_ioctl_data { struct i2c_msg __user *msgs; /* pointers to i2c_msgs */ __u32 nmsgs; /* number of i2c_msgs */ }; int i2c_read_reg(char *dev, unsigned char *buf, unsigned slave_address, unsigned reg_address, int len) { struct i2c_rdwr_ioctl_data work_queue; unsigned char w_val = reg_address; int ret; int fd = open(dev, O_RDWR); if (!fd) { printf("Error on opening the device file\n"); return 0; } work_queue.nmsgs = 2; work_queue.msgs = (struct i2c_msg*)malloc(work_queue.nmsgs *sizeof(struct i2c_msg)); if (!work_queue.msgs) { printf("Memory alloc error\n"); close(fd); return 0; } ioctl(fd, I2C_TIMEOUT, 2); ioctl(fd, I2C_RETRIES, 1); (work_queue.msgs[0]).len = 1; (work_queue.msgs[0]).addr = slave_address; (work_queue.msgs[0]).buf = &w_val; (work_queue.msgs[1]).len = len; (work_queue.msgs[1]).flags = I2C_M_RD; (work_queue.msgs[1]).addr = slave_address; (work_queue.msgs[1]).buf = buf; ret = ioctl(fd, I2C_RDWR, (unsigned long) &work_queue); if (ret < 0) { printf("Error during I2C_RDWR ioctl with error code: %d\n", ret); close(fd); free(work_queue.msgs); return 0; } else { printf("read salve:x reg:x\n", slave_address, reg_address); close(fd); free(work_queue.msgs); return len; } } int i2c_write_reg(char *dev, unsigned char *buf, unsigned slave_address, unsigned reg_address, int len) { struct i2c_rdwr_ioctl_data work_queue; unsigned char w_val = reg_address; unsigned char w_buf[len+1]; int ret; w_buf[0] = reg_address; int fd = open(dev, O_RDWR); if (!fd) { printf("Error on opening the device file\n"); return 0; } work_queue.nmsgs = 1; work_queue.msgs = (struct i2c_msg*)malloc(work_queue.nmsgs *sizeof(struct i2c_msg)); if (!work_queue.msgs) { printf("Memory alloc error\n"); close(fd); return 0; } ioctl(fd, I2C_TIMEOUT, 2); ioctl(fd, I2C_RETRIES, 1); (work_queue.msgs[0]).len = 1 + len; (work_queue.msgs[0]).addr = slave_address; (work_queue.msgs[0]).buf = w_buf; memcpy(w_buf + 1, buf, len); ret = ioctl(fd, I2C_RDWR, (unsigned long) &work_queue); if (ret < 0) { printf("Error during I2C_RDWR ioctl with error code: %d\n", ret); close(fd); free(work_queue.msgs); return 0; } else { printf("write salve:x reg:x\n", slave_address, reg_address); close(fd); free(work_queue.msgs); return len; } } int main(int argc, char **argv) { unsigned int fd; unsigned int slave_address, reg_address; unsigned r_w; unsigned w_val; unsigned char rw_val; if (argc < 5) { printf("Usage:\n%s /dev/i2c-x start_addr reg_addr rw[0|1] [write_val]\n", argv[0]); return 0; } fd = open(argv[1], O_RDWR); if (!fd) { printf("Error on opening the device file %s\n", argv[1]); return 0; } sscanf(argv[2], "%x", &slave_address); sscanf(argv[3], "%x", &reg_address); sscanf(argv[4], "%d", &r_w); if (r_w == 0) { i2c_read_reg(argv[1], &rw_val, slave_address, reg_address, 1); printf("Read %s-%x reg %x, read value:%x\n", argv[1], slave_address, reg_address, rw_val); } else { if (argc < 6) { printf("Usage:\n%s /dev/i2c-x start_addr reg_addr r|w[0|1] [write_val]\n", argv[0]); return 0; } sscanf(argv[5], "%d", &w_val); if ((w_val & ~0xff) != 0) printf("Error on written value %s\n", argv[5]); rw_val = (unsigned char)w_val; i2c_write_reg(argv[1], &rw_val, slave_address, reg_address, 1); } return 0; } 在android/external/新建i2c-util目录,上述源代码存入android/external/i2c-util/i2c-util.c, R:\wyb\pcf8563_i2c1_r8_ruoge_ov2640\android\i2c-util\i2c-util.c 编写对应的Android.mk: LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE_TAGS := optional LOCAL_MODULE = i2c-util # LOCAL_SRC_FILES := $(call all-subdir-c-files) LOCAL_SRC_FILES := i2c-util.c include $(BUILD_EXECUTABLE) 编译Android后,上述工具会位于/system/bin目录。在电路板上使用它: R:\wyb\pcf8563_i2c1_r8_ruoge_ov2640\android\out\target\product\nuclear-evb\system\bin\i2c-util 如果android已经编译了,只需要执行: rootroot@rootroot-E400:~/wyb/pcf8563_i2c1_r8_ruoge_ov2640/android$ source build/envsetup.sh rootroot@rootroot-E400:~/wyb/pcf8563_i2c1_r8_ruoge_ov2640/android$ lunch 选择:18. nuclear_evb-eng (注意:不同的ubuntu电脑,序号可能不同,但是只需要选择nuclear_evb-eng编译选项前面的序号既可!!!!) rootroot@rootroot-E400:~/wyb/pcf8563_i2c1_r8_ruoge_ov2640/android$ rootroot@rootroot-E400:~/wyb/pcf8563_i2c1_r8_ruoge_ov2640/android$ cd i2c-util/ rootroot@rootroot-E400:~/wyb/pcf8563_i2c1_r8_ruoge_ov2640/android/i2c-util$ rootroot@rootroot-E400:~/wyb/pcf8563_i2c1_r8_ruoge_ov2640/android/i2c-util$ rootroot@rootroot-E400:~/wyb/pcf8563_i2c1_r8_ruoge_ov2640/android/i2c-util$ mm 详细的编译步骤: Connecting to 192.168.1.103:22... Connection established. To escape to local shell, press 'Ctrl+Alt+]'. Welcome to Ubuntu 14.04 LTS (GNU/Linux 3.13.0-24-generic x86_64) * Documentation: https://help.ubuntu.com/ Last login: Sun Jun 26 19:23:30 2016 from 192.168.1.101 rootroot@rootroot-E400:~$ cd wyb/pcf8563_i2c1_r8_ruoge_ov2640/ rootroot@rootroot-E400:~/wyb/pcf8563_i2c1_r8_ruoge_ov2640$ rootroot@rootroot-E400:~/wyb/pcf8563_i2c1_r8_ruoge_ov2640$ rootroot@rootroot-E400:~/wyb/pcf8563_i2c1_r8_ruoge_ov2640$ ll total 8606460 drwxr-xr-x 4 rootroot rootroot 4096 Jun 26 20:46 ./ drwxr-xr-x 19 rootroot rootroot 4096 Jun 25 06:50 ../ drwxrwxr-x 29 rootroot rootroot 4096 Jun 26 19:51 android/ drwxrwxr-x 8 rootroot rootroot 4096 Jun 26 16:35 lichee/ rootroot@rootroot-E400:~/wyb/pcf8563_i2c1_r8_ruoge_ov2640$ cd android/ rootroot@rootroot-E400:~/wyb/pcf8563_i2c1_r8_ruoge_ov2640/android$ rootroot@rootroot-E400:~/wyb/pcf8563_i2c1_r8_ruoge_ov2640/android$ rootroot@rootroot-E400:~/wyb/pcf8563_i2c1_r8_ruoge_ov2640/android$ source build/envsetup.sh including device/asus/grouper/vendorsetup.sh including device/asus/tilapia/vendorsetup.sh including device/generic/armv7-a-neon/vendorsetup.sh including device/generic/armv7-a/vendorsetup.sh including device/generic/mips/vendorsetup.sh including device/generic/x86/vendorsetup.sh including device/samsung/maguro/vendorsetup.sh including device/samsung/manta/vendorsetup.sh including device/samsung/toroplus/vendorsetup.sh including device/samsung/toro/vendorsetup.sh including device/softwinner/common/vendorsetup.sh including device/softwinner/crane-evb/vendorsetup.sh including device/softwinner/nuclear-256m/vendorsetup.sh including device/softwinner/nuclear-evb/vendorsetup.sh including device/softwinner/nuclear-r8m-evb/vendorsetup.sh including device/ti/panda/vendorsetup.sh including sdk/bash_completion/adb.bash rootroot@rootroot-E400:~/wyb/pcf8563_i2c1_r8_ruoge_ov2640/android$ rootroot@rootroot-E400:~/wyb/pcf8563_i2c1_r8_ruoge_ov2640/android$ rootroot@rootroot-E400:~/wyb/pcf8563_i2c1_r8_ruoge_ov2640/android$ lunch You're building on Linux Lunch menu... pick a combo: 1. full-eng 2. full_x86-eng 3. vbox_x86-eng 4. full_mips-eng 5. full_grouper-userdebug 6. full_tilapia-userdebug 7. mini_armv7a_neon-userdebug 8. mini_armv7a-userdebug 9. mini_mips-userdebug 10. mini_x86-userdebug 11. full_maguro-userdebug 12. full_manta-userdebug 13. full_toroplus-userdebug 14. full_toro-userdebug 15. crane_evb-eng 16. nuclear_256m-user 17. nuclear_256m-eng 18. nuclear_evb-eng 19. nuclear_r8m_evb-eng 20. full_panda-userdebug Which would you like? [full-eng] 18 ============================================ PLATFORM_VERSION_CODENAME=REL PLATFORM_VERSION=4.2.2 TARGET_PRODUCT=nuclear_evb TARGET_BUILD_VARIANT=eng TARGET_BUILD_TYPE=release TARGET_BUILD_APPS= TARGET_ARCH=arm TARGET_ARCH_VARIANT=armv7-a-neon HOST_ARCH=x86 HOST_OS=linux HOST_OS_EXTRA=Linux-3.13.0-24-generic-x86_64-with-Ubuntu-14.04-trusty HOST_BUILD_TYPE=release BUILD_ID=JDQ39 OUT_DIR=out ============================================ rootroot@rootroot-E400:~/wyb/pcf8563_i2c1_r8_ruoge_ov2640/android$ rootroot@rootroot-E400:~/wyb/pcf8563_i2c1_r8_ruoge_ov2640/android$ rootroot@rootroot-E400:~/wyb/pcf8563_i2c1_r8_ruoge_ov2640/android$ ll total 156 drwxrwxr-x 29 rootroot rootroot 4096 Jun 26 19:51 ./ drwxr-xr-x 4 rootroot rootroot 4096 Jun 26 20:46 ../ drwxrwxr-x 3 rootroot rootroot 4096 Jun 13 11:38 abi/ drwxrwxr-x 10 rootroot rootroot 4096 Jun 13 11:38 bionic/ drwxrwxr-x 5 rootroot rootroot 4096 Jun 13 11:38 bootable/ drwxrwxr-x 7 rootroot rootroot 4096 Jun 13 11:38 build/ drwxrwxr-x 11 rootroot rootroot 4096 Jun 13 11:38 cts/ drwxrwxr-x 18 rootroot rootroot 4096 Jun 13 11:38 dalvik/ drwxrwxr-x 18 rootroot rootroot 4096 Jun 13 11:38 development/ drwxrwxr-x 10 rootroot rootroot 4096 Jun 13 11:38 device/ drwxrwxr-x 3 rootroot rootroot 4096 Jun 13 11:38 docs/ drwxrwxr-x 159 rootroot rootroot 4096 Jun 13 11:39 external/ drwxrwxr-x 14 rootroot rootroot 4096 Jun 13 11:40 frameworks/ drwxrwxr-x 10 rootroot rootroot 4096 Jun 13 11:40 gdk/ drwxrwxr-x 10 rootroot rootroot 4096 Jun 13 11:40 hardware/ drwxr-xr-x 2 rootroot rootroot 4096 Jun 26 19:37 i2cscan/ drwxr-xr-x 2 rootroot rootroot 4096 Jun 26 19:52 i2c-util/ drwxrwxr-x 11 rootroot rootroot 4096 Jun 13 11:40 libcore/ drwxrwxr-x 4 rootroot rootroot 4096 Jun 13 11:40 libnativehelper/ -r--r--r-- 1 rootroot rootroot 87 Jun 13 11:38 Makefile drwxrwxr-x 8 rootroot rootroot 4096 Jun 13 11:40 ndk/ drwxrwxr-x 4 rootroot rootroot 4096 Jun 26 16:43 out/ drwxrwxr-x 8 rootroot rootroot 4096 Jun 13 11:40 packages/ drwxrwxr-x 5 rootroot rootroot 4096 Jun 13 11:40 pdk/ drwxrwxr-x 10 rootroot rootroot 4096 Jun 13 11:41 prebuilts/ drwxr-xr-x 2 rootroot rootroot 4096 Jun 25 13:01 read_pcf8563/ drwxrwxr-x 6 rootroot rootroot 4096 Jun 13 11:38 .repo/ drwxrwxr-x 51 rootroot rootroot 4096 Jun 13 11:41 sdk/ drwxrwxr-x 9 rootroot rootroot 4096 Jun 13 11:41 system/ drwxrwxr-x 4 rootroot rootroot 4096 Jun 13 11:41 tools/ rootroot@rootroot-E400:~/wyb/pcf8563_i2c1_r8_ruoge_ov2640/android$ rootroot@rootroot-E400:~/wyb/pcf8563_i2c1_r8_ruoge_ov2640/android$ rootroot@rootroot-E400:~/wyb/pcf8563_i2c1_r8_ruoge_ov2640/android$ cd i2c-util/ rootroot@rootroot-E400:~/wyb/pcf8563_i2c1_r8_ruoge_ov2640/android/i2c-util$ rootroot@rootroot-E400:~/wyb/pcf8563_i2c1_r8_ruoge_ov2640/android/i2c-util$ rootroot@rootroot-E400:~/wyb/pcf8563_i2c1_r8_ruoge_ov2640/android/i2c-util$ mm ============================================ PLATFORM_VERSION_CODENAME=REL PLATFORM_VERSION=4.2.2 TARGET_PRODUCT=nuclear_evb TARGET_BUILD_VARIANT=eng TARGET_BUILD_TYPE=release TARGET_BUILD_APPS= TARGET_ARCH=arm TARGET_ARCH_VARIANT=armv7-a-neon HOST_ARCH=x86 HOST_OS=linux HOST_OS_EXTRA=Linux-3.13.0-24-generic-x86_64-with-Ubuntu-14.04-trusty HOST_BUILD_TYPE=release BUILD_ID=JDQ39 OUT_DIR=out ============================================ PRODUCT_COPY_FILES frameworks/base/data/sounds/effects/ogg/Effect_Tick.ogg:system/media/audio/ui/Effect_Tick.ogg ignored. PRODUCT_COPY_FILES frameworks/base/data/sounds/effects/ogg/KeypressStandard.ogg:system/media/audio/ui/KeypressStandard.ogg ignored. PRODUCT_COPY_FILES frameworks/base/data/sounds/effects/ogg/KeypressSpacebar.ogg:system/media/audio/ui/KeypressSpacebar.ogg ignored. PRODUCT_COPY_FILES frameworks/base/data/sounds/effects/ogg/KeypressDelete.ogg:system/media/audio/ui/KeypressDelete.ogg ignored. PRODUCT_COPY_FILES frameworks/base/data/sounds/effects/ogg/KeypressReturn.ogg:system/media/audio/ui/KeypressReturn.ogg ignored. PRODUCT_COPY_FILES frameworks/base/data/sounds/effects/ogg/VideoRecord.ogg:system/media/audio/ui/VideoRecord.ogg ignored. PRODUCT_COPY_FILES frameworks/base/data/sounds/effects/ogg/camera_click.ogg:system/media/audio/ui/camera_click.ogg ignored. PRODUCT_COPY_FILES frameworks/base/data/sounds/effects/ogg/LowBattery.ogg:system/media/audio/ui/LowBattery.ogg ignored. PRODUCT_COPY_FILES frameworks/base/data/sounds/effects/ogg/Dock.ogg:system/media/audio/ui/Dock.ogg ignored. PRODUCT_COPY_FILES frameworks/base/data/sounds/effects/ogg/Undock.ogg:system/media/audio/ui/Undock.ogg ignored. PRODUCT_COPY_FILES frameworks/base/data/sounds/effects/ogg/Lock.ogg:system/media/audio/ui/Lock.ogg ignored. PRODUCT_COPY_FILES frameworks/base/data/sounds/effects/ogg/Unlock.ogg:system/media/audio/ui/Unlock.ogg ignored. PRODUCT_COPY_FILES frameworks/base/data/sounds/ringtones/ogg/Sceptrum.ogg:system/media/audio/ringtones/Sceptrum.ogg ignored. PRODUCT_COPY_FILES frameworks/base/data/sounds/effects/ogg/KeypressStandard_120.ogg:system/media/audio/ui/KeypressStandard.ogg ignored. PRODUCT_COPY_FILES frameworks/base/data/sounds/effects/ogg/KeypressSpacebar_120.ogg:system/media/audio/ui/KeypressSpacebar.ogg ignored. PRODUCT_COPY_FILES frameworks/base/data/sounds/effects/ogg/KeypressDelete_120.ogg:system/media/audio/ui/KeypressDelete.ogg ignored. PRODUCT_COPY_FILES frameworks/base/data/sounds/effects/ogg/KeypressReturn_120.ogg:system/media/audio/ui/KeypressReturn.ogg ignored. PRODUCT_COPY_FILES frameworks/base/data/sounds/notifications/ogg/Capella.ogg:system/media/audio/notifications/Capella.ogg ignored. PRODUCT_COPY_FILES frameworks/base/data/sounds/notifications/ogg/CetiAlpha.ogg:system/media/audio/notifications/CetiAlpha.ogg ignored. PRODUCT_COPY_FILES frameworks/base/data/sounds/notifications/ogg/Polaris.ogg:system/media/audio/notifications/Polaris.ogg ignored. PRODUCT_COPY_FILES frameworks/base/data/sounds/notifications/ogg/Pollux.ogg:system/media/audio/notifications/Pollux.ogg ignored. PRODUCT_COPY_FILES frameworks/base/data/sounds/notifications/ogg/Procyon.ogg:system/media/audio/notifications/Procyon.ogg ignored. PRODUCT_COPY_FILES frameworks/base/data/sounds/ringtones/ogg/Aquila.ogg:system/media/audio/ringtones/Aquila.ogg ignored. PRODUCT_COPY_FILES frameworks/base/data/sounds/ringtones/ogg/ArgoNavis.ogg:system/media/audio/ringtones/ArgoNavis.ogg ignored. PRODUCT_COPY_FILES frameworks/base/data/sounds/ringtones/ogg/Carina.ogg:system/media/audio/ringtones/Carina.ogg ignored. PRODUCT_COPY_FILES frameworks/base/data/sounds/ringtones/ogg/Centaurus.ogg:system/media/audio/ringtones/Centaurus.ogg ignored. PRODUCT_COPY_FILES frameworks/base/data/sounds/ringtones/ogg/Cygnus.ogg:system/media/audio/ringtones/Cygnus.ogg ignored. PRODUCT_COPY_FILES frameworks/base/data/sounds/ringtones/ogg/Draco.ogg:system/media/audio/ringtones/Draco.ogg ignored. PRODUCT_COPY_FILES frameworks/base/data/sounds/ringtones/ogg/Machina.ogg:system/media/audio/ringtones/Machina.ogg ignored. PRODUCT_COPY_FILES frameworks/base/data/sounds/ringtones/ogg/Orion.ogg:system/media/audio/ringtones/Orion.ogg ignored. PRODUCT_COPY_FILES frameworks/base/data/sounds/ringtones/ogg/Pegasus.ogg:system/media/audio/ringtones/Pegasus.ogg ignored. PRODUCT_COPY_FILES frameworks/base/data/sounds/ringtones/ogg/Pyxis.ogg:system/media/audio/ringtones/Pyxis.ogg ignored. PRODUCT_COPY_FILES frameworks/base/data/sounds/ringtones/ogg/Rigel.ogg:system/media/audio/ringtones/Rigel.ogg ignored. PRODUCT_COPY_FILES frameworks/base/data/sounds/ringtones/ogg/Scarabaeus.ogg:system/media/audio/ringtones/Scarabaeus.ogg ignored. PRODUCT_COPY_FILES frameworks/base/data/sounds/ringtones/ogg/Solarium.ogg:system/media/audio/ringtones/Solarium.ogg ignored. PRODUCT_COPY_FILES device/softwinner/nuclear-common/googleservice/gapps-jb-20121130-signed/system/tts/lang_pico/es-ES_zl0_sg.bin:system/tts/lang_pico/es-ES_zl0_sg.bin ignored. PRODUCT_COPY_FILES device/softwinner/nuclear-common/googleservice/gapps-jb-20121130-signed/system/tts/lang_pico/es-ES_ta.bin:system/tts/lang_pico/es-ES_ta.bin ignored. PRODUCT_COPY_FILES device/softwinner/nuclear-common/googleservice/gapps-jb-20121130-signed/system/tts/lang_pico/fr-FR_nk0_sg.bin:system/tts/lang_pico/fr-FR_nk0_sg.bin ignored. PRODUCT_COPY_FILES device/softwinner/nuclear-common/googleservice/gapps-jb-20121130-signed/system/tts/lang_pico/fr-FR_ta.bin:system/tts/lang_pico/fr-FR_ta.bin ignored. PRODUCT_COPY_FILES device/softwinner/nuclear-common/googleservice/gapps-jb-20121130-signed/system/tts/lang_pico/de-DE_gl0_sg.bin:system/tts/lang_pico/de-DE_gl0_sg.bin ignored. PRODUCT_COPY_FILES device/softwinner/nuclear-common/googleservice/gapps-jb-20121130-signed/system/tts/lang_pico/it-IT_cm0_sg.bin:system/tts/lang_pico/it-IT_cm0_sg.bin ignored. PRODUCT_COPY_FILES device/softwinner/nuclear-common/googleservice/gapps-jb-20121130-signed/system/tts/lang_pico/it-IT_ta.bin:system/tts/lang_pico/it-IT_ta.bin ignored. PRODUCT_COPY_FILES device/softwinner/nuclear-common/googleservice/gapps-jb-20121130-signed/system/tts/lang_pico/de-DE_ta.bin:system/tts/lang_pico/de-DE_ta.bin ignored. No private recovery resources for TARGET_DEVICE nuclear-evb make: Entering directory `/home/rootroot/wyb/pcf8563_i2c1_r8_ruoge_ov2640/android' target thumb C: i2c-util cd R:\wyb\pcf8563_i2c1_r8_ruoge_ov2640\android\out\target\product\nuclear-evb\system\bin C:\Users\Administrator.USER-20150913SZ>r: R:\wyb\pcf8563_i2c1_r8_ruoge_ov2640\android\out\target\product\nuclear-evb\system\bin>adb remount remount succeeded R:\wyb\pcf8563_i2c1_r8_ruoge_ov2640\android\out\target\product\nuclear-evb\system\bin> R:\wyb\pcf8563_i2c1_r8_ruoge_ov2640\android\out\target\product\nuclear-evb\system\bin>dir i2c* 驱动器 R 中的卷是 rootroot 卷的序列号是 1A1C-E71D R:\wyb\pcf8563_i2c1_r8_ruoge_ov2640\android\out\target\product\nuclear-evb\system\bin 的目录 2016/06/26 21:10 5,388 i2c-util 1 个文件 5,388 字节 0 个目录 268,337,782,784 可用字节 R:\wyb\pcf8563_i2c1_r8_ruoge_ov2640\android\out\target\product\nuclear-evb\system\bin> R:\wyb\pcf8563_i2c1_r8_ruoge_ov2640\android\out\target\product\nuclear-evb\system\bin>adb push i2c-util /data/ 29 KB/s (5388 bytes in 0.180s) R:\wyb\pcf8563_i2c1_r8_ruoge_ov2640\android\out\target\product\nuclear-evb\system\bin> 将R8的串口连接到windows电脑上。通过串口工具:比如Xshell5(有些人可能喜欢使用SecureCRT)读取R8的串口打印 (ubuntu下请使用minicom,使用方法请自行搜索了) 波特率选择:115200 N 8 1 等R8的android启动完成之后(LCD进android主界面),输入:su [ 37.990060] init: process 'ril-daemon', pid 950 exited [ 38.010049] init: process 'ril-daemon' killing any children in process group su root@android:/ # root@android:/ # root@android:/ # cd /data root@android:/data # root@android:/data # ll drwxrwxr-x system system 1970-01-11 08:58 anr drwxrwx--x system system 1970-01-02 08:01 app drwx------ root root 1980-10-01 11:03 app-asec drwxrwx--x system system 1970-01-02 08:01 app-lib drwxrwx--x system system 1980-10-01 11:03 app-private drwx------ system system 1980-10-01 11:04 backup drwxrwx--x system system 1970-01-02 08:00 dalvik-cache drwxrwx--x system system 2016-06-21 10:11 data drwxr-x--- root log 1980-10-01 11:03 dontpanic drwxrwx--- drm drm 1980-10-01 11:04 drm -rw-rw-rw- root root 5388 2016-06-26 21:10 i2c-util drwxr-x--x root root 1980-10-01 11:03 local drwxrwx--- root root 1970-01-01 08:00 lost+found drwxrwx--- media_rw media_rw 1980-10-01 11:03 media drwxrwx--t system misc 1980-10-01 11:03 misc -rw------- system system 154 1970-01-02 08:02 pointercal drwx------ root root 1970-01-02 08:00 property -rwxrwxrwx root root 5392 2016-06-25 13:01 read_pcf8563 drwxrwx--x system system 1980-10-01 11:03 resource-cache drwxr-x--- root shell 1980-10-01 11:03 ssh drwxrwxr-x system system 1970-01-02 08:00 system drwx------ system system 1970-01-02 08:03 tombstones drwx--x--x system system 1980-10-01 11:03 user root@android:/data # (让i2c-util具有可执行权限:) root@android:/data # chmod 777 i2c-util root@android:/data # root@android:/data # ll i2c* -rwxrwxrwx root root 5388 2016-06-26 21:10 i2c-util root@android:/data # root@android:/data # root@android:/data # (可选执行) root@android:/data # sync root@android:/data # 注意:串口打印会打印很多log信息。上面的步骤中的状态信息已经被过滤了。 如果你的串口打印过量的log信息,属于正常现象! 如果不想要这么多的状态信息,可以考虑使用adb shell。 不过windows命令行中的adb shell不能够按TAB键自动补充,ubuntu的可以。 也许我们可以把windows的命令行特别设计一下(给它修正一下),让它也可以通过按TAB键来自动补全!^_ 读取pcf8563的第2个寄存器(秒钟值): 表 5:BCD 格式寄存器概况 标明“-”的位无效 地址 寄存器名称 Bit7 Bit6 Bit5 Bit4 Bit3 Bit2 Bit1 Bit0 02h 秒 VL 00~59BCD 码格式数 root@android:/data # ./i2c-util /dev/i2c-1 0x51 0x02 0 read salve:51 reg:02 Read /dev/i2c-1-51 reg 2, read value:11 root@android:/data # ./i2c-util /dev/i2c-1 0x51 0x02 0 read salve:51 reg:02 Read /dev/i2c-1-51 reg 2, read value:12 root@android:/data # ./i2c-util /dev/i2c-1 0x51 0x02 0 read salve:51 reg:02 Read /dev/i2c-1-51 reg 2, read value:12 root@android:/data # ./i2c-util /dev/i2c-1 0x51 0x02 0 read salve:51 reg:02 Read /dev/i2c-1-51 reg 2, read value:13 root@android:/data # ./i2c-util /dev/i2c-1 0x51 0x02 0 read salve:51 reg:02 Read /dev/i2c-1-51 reg 2, read value:14 root@android:/data # ./i2c-util /dev/i2c-1 0x51 0x02 0 read salve:51 reg:02 Read /dev/i2c-1-51 reg 2, read value:14 root@android:/data # ./i2c-util /dev/i2c-1 0x51 0x02 0 read salve:51 reg:02 Read /dev/i2c-1-51 reg 2, read value:15 可以知道秒钟的值是变化的,基本上是一秒钟递增一次,I2C读取成功。 如果感兴趣的话,在执行读取命令的时候通过协议分析仪或者示波器来抓取I2C1的SCL/SDA的波形,来进行更加详尽的分析!^_ 方法二: 给出了一种复杂的解决办法(步骤从简,更多请参考开头的方法): R:\wyb\pcf8563_i2c1_r8_ruoge_ov2640\lichee\linux-3.4\drivers\rtc\rtc-sun5i.c 给这个驱动文件增加设备节点:/dev/a20_r8_pcf8563 增加设备节点的方法请参考: http://blog.csdn.net/mirkerson/article/details/8844997 android驱动学习---led实验 然后在这个驱动文件的pcf8563_probe函数处截获它的client指针(struct i2c_client *client) client2 = client; 用户可以通过在userspace直接访问设备节点:/dev/a20_r8_pcf8563(open) 然后调用ioctl:ioctl(fd, 0x00000001, 0x02); 来获取第2个寄存器:秒钟的值了。 #include #include #include #include #include #include #include #include #include #include #include int fd; int main(int argc, char **argv) { fd = open("/dev/a20_r8_pcf8563", O_RDWR); ioctl(fd, 0x00000001, 0x02); close(fd); return 0; } 执行过程(直接从内核打印秒钟值了,也可以看到秒钟值大概也是一秒钟递增一次!): root@android:/data # root@android:/data # ./read_pcf8563 [ 1397.060015] ****wyb drivers/rtc/rtc-sun5i.c:1169/r8_pcf8563_open()! open init.... [ 1397.071009] ****wyb drivers/rtc/rtc-sun5i.c:1130/r8_pcf8563_ioctl()! cmd=0x00000001 [ 1397.079076] ****wyb drivers/rtc/rtc-sun5i.c:1160/r8_pcf8563_ioctl()! cmd=0x00000001, value=0x00000046 [ 1397.088342] ****wyb drivers/rtc/rtc-sun5i.c:1177/r8_pcf8563_close()! close init root@android:/data # root@android:/data # ./read_pcf8563 [ 1398.409888] ****wyb drivers/rtc/rtc-sun5i.c:1169/r8_pcf8563_open()! open init.... [ 1398.411203] ****wyb drivers/rtc/rtc-sun5i.c:1130/r8_pcf8563_ioctl()! cmd=0x00000001 [ 1398.419273] ****wyb drivers/rtc/rtc-sun5i.c:1160/r8_pcf8563_ioctl()! cmd=0x00000001, value=0x00000047 [ 1398.428546] ****wyb drivers/rtc/rtc-sun5i.c:1177/r8_pcf8563_close()! close init root@android:/data # root@android:/data # root@android:/data # ./read_pcf8563 [ 1399.668173] ****wyb drivers/rtc/rtc-sun5i.c:1169/r8_pcf8563_open()! open init.... [ 1399.670939] ****wyb drivers/rtc/rtc-sun5i.c:1130/r8_pcf8563_ioctl()! cmd=0x00000001 [ 1399.679022] ****wyb drivers/rtc/rtc-sun5i.c:1160/r8_pcf8563_ioctl()! cmd=0x00000001, value=0x00000048 [ 1399.688299] ****wyb drivers/rtc/rtc-sun5i.c:1177/r8_pcf8563_close()! close init root@android:/data #
r40_tinav2.1_最终验证通过_使用CB-S来验证OV5640有横条纹fpscamera+SPI2.0成功_20171114_1443没有外层目录.7z 开发板:CB-S 1、(可选修改/调试技巧:) 除了ov5640.c之外,其它的驱动都不编译,节省编译时间! W:\ov5640_spi20_r40t\lichee\linux-3.10\drivers\media\platform\sunxi-vfe\device\Makefile obj-m += ov5640.o #obj-m += ov2640.o #obj-m += ov7736.o #obj-m += s5k4ec.o #obj-m += s5k4ec_mipi.o #obj-m += gc2035.o #obj-m += gt2005.o #obj-m += gc0307.o #obj-m += gc0308.o #obj-m += gc0328.o #obj-m += gc0328c.o #obj-m += gc2145.o #obj-m += gc0329.o #obj-m += gc0311.o #obj-m += hi253.o #obj-m += sp2518.o #obj-m += sp2519.o #obj-m += sp0718.o #obj-m += sp0838.o #obj-m += ov16825.o #obj-m += ov5650.o #obj-m += ov5647.o #obj-m += ov5647_mipi.o #obj-m += t8et5.o #obj-m += s5k4e1.o #obj-m += s5k4e1_mipi.o #obj-m += sp2518.o #obj-m += sp0718.o #obj-m += gc5004.o #obj-m += gc5004_mipi.o #obj-m += ov5648.o #obj-m += ar0330.o #obj-m += ov5648.o #obj-m += sp5408.o #obj-m += ov12830.o #obj-m += ov8825.o #obj-m += ov8850.o #obj-m += gc2155.o #obj-m += ov8858.o #obj-m += ov13850.o #obj-m += imx214.o #obj-m += ov8858_4lane.o #obj-m += sp5409.o #obj-m += s5k5e2yx.o #obj-m += ov2710_mipi.o #obj-m += ov2686.o (这里是看ov5640的驱动probe执行是否正确?设备ID是否读取成功!) W:\ov5640_spi20_r40t\lichee\linux-3.10\drivers\media\platform\sunxi-vfe\device\ov5640.c static int sensor_detect(struct v4l2_subdev *sd) { data_type rdval; printk("****wyb %s:%d/%s()! \n", __FILE__, __LINE__, __func__); LOG_ERR_RET(sensor_read(sd, 0x300a, &rdval;)) printk("****wyb %s:%d/%s()! 0x300a rdval=0xx\n", __FILE__, __LINE__, __func__, rdval); if(rdval != 0x56) return -ENODEV; LOG_ERR_RET(sensor_read(sd, 0x300b, &rdval;)) printk("****wyb %s:%d/%s()! 0x300b rdval=0xx\n", __FILE__, __LINE__, __func__, rdval); if(rdval != 0x40) return -ENODEV; return 0; } (在全志R16平台改过这个文件,让摄像头不要休眠,但是全志R40平台的tina v2.1系统下不需要修改!) W:\ov5640_spi20_r40t\lichee\linux-3.10\drivers\media\platform\sunxi-vfe\vfe.c 2、(可
全志R40平台的tinav2.1系统下打开SPI2接口 1、(可选修改) Q:\r40_tinav2.1\spi20_r40_tinav2.1\lichee\brandy\build.sh build_uboot() { if [ "x${PLATFORM}" = "xsun50iw1p1" ] || \ [ "x${PLATFORM}" = "xsun50iw2p1" ] || \ [ "x${PLATFORM}" = "xsun50iw6p1" ] || \ [ "x${PLATFORM}" = "xsun50iw3p1" ] || \ [ "x${PLATFORM}" = "xsun8iw12p1" ] || \ [ "x${PLATFORM}" = "xsun8iw10p1" ] || \ [ "x${PLATFORM}" = "xsun8iw11p1" ]; then cd u-boot-2014.07/ else cd u-boot-2011.09/ fi make distclean if [ "x$MODE" = "xota_test" ] ; then export "SUNXI_MODE=ota_test" fi make ${PLATFORM}_config make -j16 #make spl #make fes if [ ${PLATFORM} = "sun8iw11p1" ]; then make distclean make ${PLATFORM}_nor_config make -j16 #make spl #make fes fi cd - 1>/dev/null } 2、 Q:\r40_tinav2.1\spi20_r40_tinav2.1\lichee\linux-3.10\drivers\spi\spidev.c static struct spi_driver spidev_spi_driver = { .driver = { .name = "spidev", .owner = THIS_MODULE, .of_match_table = of_match_ptr(spidev_dt_ids), }, .probe = spidev_probe, .remove = spidev_remove, /* NOTE: suspend/resume methods are not necessary here. * We don't do anything except pass the requests to/from * the underlying controller. The refrigerator handles * most issues; the controller driver handles the rest. */ }; 3、验证APP程序: Q:\r40_tinav2.1\spi20_r40_tinav2.1\package\allwinner\spidev20_test\Makefile ############################################## # OpenWrt Makefile for helloworld program # # # Most of the variables used here are defined in # the include directives below. We just need to # specify a basic description of the package, # where to build our program, where to find # the source files, and where to install the # compiled program on the router. # # Be very careful of spacing in this file. # Indents should be tabs, not spaces, and # there should be no trailing whitespace in # lines that are not commented. # ############################################## include $(TOPDIR)/rules.mk # Name and release number of this package PKG_NAME:=spidev20_test PKG_VERSION:=0.0.1 PKG_RELEASE:=1 # This specifies the directory where we're going to build the program. # The root build directory, $(BUILD_DIR), is by default the build_mipsel # directory in your OpenWrt SDK directory PKG_BUILD_DIR := $(COMPILE_DIR)/$(PKG_NAME) include $(BUILD_DIR)/package.mk # Specify package information for this program. # The variables defined here should be self explanatory. # If you are running Kamikaze, delete the DESCRIPTION # variable below and uncomment the Kamikaze define # directive for the description below define Package/spidev20_test SECTION:=utils CATEGORY:=Allwinner TITLE:=spidev20_test just test the SPI2 interface DEPENDS:=+libpthread endef # Uncomment portion below for Kamikaze and delete DESCRIPTION variable above define Package/spidev20_test/description If you can't figure out what this program does, you're probably brain-dead and need immediate medical attention. endef # Specify what needs to be done to prepare for building the package. # In our case, we need to copy the source files to the build directory. # This is NOT the default. The default uses the PKG_SOURCE_URL and the # PKG_SOURCE which is not defined here to download the source from the web. # In order to just build a simple program that we have just written, it is # much easier to do it this way. define Build/Prepare mkdir -p $(PKG_BUILD_DIR) $(CP) ./src/* $(PKG_BUILD_DIR)/ endef define Build/Configure endef define Build/Compile $(MAKE) -C $(PKG_BUILD_DIR) \ CC="$(TARGET_CC)" \ CFLAGS="$(TARGET_CFLAGS)" -Wall \ LDFLAGS="$(TARGET_LDFLAGS)" \ LIBS="-lpthread" \ all endef # We do not need to define Build/Configure or Build/Compile directives # The defaults are appropriate for compiling a simple program such as this one # Specify where and how to install the program. Since we only have one file, # the helloworld executable, install it by copying it to the /bin directory on # the router. The $(1) variable represents the root directory on the router running # OpenWrt. The $(INSTALL_DIR) variable contains a command to prepare the install # directory if it does not already exist. Likewise $(INSTALL_BIN) contains the # command to copy the binary file from its current location (in our case the build # directory) to the install directory. define Package/spidev20_test/install $(INSTALL_DIR) $(1)/bin $(INSTALL_BIN) $(PKG_BUILD_DIR)/spidev20_test $(1)/bin/ endef # This line executes the necessary commands to compile our program. # The above define directives specify all the information needed, but this # line calls BuildPackage which in turn actually uses this information to # build a package. $(eval $(call BuildPackage,spidev20_test)) Q:\r40_tinav2.1\spi20_r40_tinav2.1\package\allwinner\spidev20_test\src\Makefile TARGET = spidev20_test INCLUDES += -I. -Icommon/ LIBS += -lpthread -lm -lrt SRCS = spidev20_test.c OBJS = $(SRCS:.c=.o) %.o: %.c $(CC) $(CFLAGS) $(INCLUDES) -c -o $@ $< $(TARGET): $(OBJS) $(CC) -o $@ $(OBJS) $(LIBS) $(LDFLAGS) all:$(TARGET) clean: rm -rf $(TARGET) *.o *.a *~ cd common && rm -f *.o *.a *.bak *~ .depend Q:\r40_tinav2.1\spi20_r40_tinav2.1\package\allwinner\spidev20_test\src\spidev20_test.c /* * SPI testing utility (using spidev driver) * * Copyright (c) 2007 MontaVista Software, Inc. * Copyright (c) 2007 Anton Vorontsov <[email protected]> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License. * * Cross-compile with cross-gcc -I/path/to/cross-kernel/include */ #include <stdint.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <getopt.h> #include <fcntl.h> #include <sys/ioctl.h> #include <linux/types.h> #include <linux/spi/spidev.h> #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) static void pabort(const char *s) { perror(s); abort(); } //static const char *device = "/dev/spidev0.0"; //static const char *device = "/dev/spidev0.1"; static const char *device = "/dev/spidev2.0"; static uint8_t mode; static uint8_t bits = 8; static uint32_t speed = 500000; static uint16_t delay; static void transfer(int fd) { int ret; //uint8_t tx[] = { // 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x40, 0x00, 0x00, 0x00, 0x00, 0x95, // 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xDE, 0xAD, 0xBE, 0xEF, 0xBA, 0xAD, // 0xF0, 0x0D, //}; // CityBrand WelCome U! uint8_t tx[] = { 0x43, 0x69, 0x74, 0x79, 0x42, 0x72, 0x61, 0x6E, 0x64, 0x20, 0x57, 0x65, 0x6C, 0x43, 0x6F, 0x6D, 0x65, 0x20, 0x55, 0x21, 0x43, 0x69, 0x74, 0x79, 0x42, 0x72, 0x61, 0x6E, 0x64, 0x20, 0x57, 0x65, 0x6C, 0x43, 0x6F, 0x6D, 0x65, 0x20, 0x55, 0x21, 0x43, 0x69, 0x74, 0x79, 0x42, 0x72, 0x61, 0x6E, 0x64, 0x20, 0x57, 0x65, 0x6C, 0x43, 0x6F, 0x6D, 0x65, 0x20, 0x55, 0x21, }; uint8_t rx[ARRAY_SIZE(tx)] = {0, }; struct spi_ioc_transfer tr = { .tx_buf = (unsigned long)tx, .rx_buf = (unsigned long)rx, .len = ARRAY_SIZE(tx), .delay_usecs = delay, .speed_hz = speed, .bits_per_word = bits, }; ret = ioctl(fd, SPI_IOC_MESSAGE(1), &tr;); if (ret < 1) pabort("can't send spi message"); for (ret = 0; ret < ARRAY_SIZE(tx); ret++) { //if (!(ret % 6)) // puts(" "); //printf("%.2X ", rx[ret]); if (!(ret % 20)){ puts("\n"); } printf("%c", rx[ret]); } //puts(""); puts("\n"); } static void print_usage(const char *prog) { printf("Usage: %s [-DsbdlHOLC3]\n", prog); puts(" -D --device device to use (default /dev/spidev1.1)\n" " -s --speed max speed (Hz)\n" " -d --delay delay (usec)\n" " -b --bpw bits per word \n" " -l --loop loopback\n" " -H --cpha clock phase\n" " -O --cpol clock polarity\n" " -L --lsb least significant bit first\n" " -C --cs-high chip select active high\n" " -3 --3wire SI/SO signals shared\n"); exit(1); } static void parse_opts(int argc, char *argv[]) { while (1) { static const struct option lopts[] = { { "device", 1, 0, 'D' }, { "speed", 1, 0, 's' }, { "delay", 1, 0, 'd' }, { "bpw", 1, 0, 'b' }, { "loop", 0, 0, 'l' }, { "cpha", 0, 0, 'H' }, { "cpol", 0, 0, 'O' }, { "lsb", 0, 0, 'L' }, { "cs-high", 0, 0, 'C' }, { "3wire", 0, 0, '3' }, { "no-cs", 0, 0, 'N' }, { "ready", 0, 0, 'R' }, { NULL, 0, 0, 0 }, }; int c; c = getopt_long(argc, argv, "D:s:d:b:lHOLC3NR", lopts, NULL); if (c == -1) break; switch (c) { case 'D': device = optarg; break; case 's': speed = atoi(optarg); break; case 'd': delay = atoi(optarg); break; case 'b': bits = atoi(optarg); break; case 'l': mode |= SPI_LOOP; break; case 'H': mode |= SPI_CPHA; break; case 'O': mode |= SPI_CPOL; break; case 'L': mode |= SPI_LSB_FIRST; break; case 'C': mode |= SPI_CS_HIGH; break; case '3': mode |= SPI_3WIRE; break; case 'N': mode |= SPI_NO_CS; break; case 'R': mode |= SPI_READY; break; default: print_usage(argv[0]); break; } } } int main(int argc, char *argv[]) { int ret = 0; int fd; parse_opts(argc, argv); fd = open(device, O_RDWR); if (fd < 0) pabort("can't open device"); /* * spi mode */ ret = ioctl(fd, SPI_IOC_WR_MODE, &mode;); if (ret == -1) pabort("can't set spi mode"); ret = ioctl(fd, SPI_IOC_RD_MODE, &mode;); if (ret == -1) pabort("can't get spi mode"); /* * bits per word */ ret = ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &bits;); if (ret == -1) pabort("can't set bits per word"); ret = ioctl(fd, SPI_IOC_RD_BITS_PER_WORD, &bits;); if (ret == -1) pabort("can't get bits per word"); /* * max speed hz */ ret = ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed;); if (ret == -1) pabort("can't set max speed hz"); ret = ioctl(fd, SPI_IOC_RD_MAX_SPEED_HZ, &speed;); if (ret == -1) pabort("can't get max speed hz"); printf("spi mode: %d\n", mode); printf("bits per word: %d\n", bits); printf("max speed: %d Hz (%d KHz)\n", speed, speed/1000); transfer(fd); close(fd); return ret; } 4、修改配置文件: Q:\r40_tinav2.1\spi20_r40_tinav2.1\target\allwinner\azalea-m2ultra\configs\sys_config.fex [jtag_para] jtag_enable = 1 jtag_ms = port:PB14<3><default><default><default> jtag_ck = port:PB15<3><default><default><default> jtag_do = port:PB16<3><default><default><default> jtag_di = port:PB17<3><default><default><default> 修改为: [jtag_para] jtag_enable = 0 ;jtag_ms = port:PB14<3><default><default><default> ;jtag_ck = port:PB15<3><default><default><default> ;jtag_do = port:PB16<3><default><default><default> ;jtag_di = port:PB17<3><default><default><default> ;---------------------------------------------------------------------------------- ;SPI controller configuration ;---------------------------------------------------------------------------------- [spi0] spi0_used = 0 spi0_cs_number = 2 spi0_cs_bitmap = 3 spi0_cs0 = port:PC23<3><1><default><default> spi0_cs1 = port:PI14<2><1><default><default> spi0_sclk = port:PC2<3><default><default><default> spi0_mosi = port:PC0<3><default><default><default> spi0_miso = port:PC1<3><default><default><default> [spi1] spi1_used = 0 spi1_cs_number = 2 spi1_cs_bitmap = 3 spi1_cs0 = port:PA0<3><1><default><default> spi1_cs1 = port:PA4<3><1><default><default> spi1_sclk = port:PA1<3><default><default><default> spi1_mosi = port:PA2<3><default><default><default> spi1_miso = port:PA3<3><default><default><default> [spi2] spi2_used = 0 spi2_cs_number = 2 spi2_cs_bitmap = 3 spi2_cs0 = port:PB14<2><1><default><default> spi2_cs1 = port:PB13<2><1><default><default> spi2_sclk = port:PB15<2><default><default><default> spi2_mosi = port:PB16<2><default><default><default> spi2_miso = port:PB17<2><default><default><default> [spi3] spi3_used = 0 spi3_cs_number = 2 spi3_cs_bitmap = 3 spi3_cs0 = port:PA5<3><1><default><default> spi3_cs1 = port:PA9<3><1><default><default> spi3_sclk = port:PA6<3><default><default><default> spi3_mosi = port:PA7<3><default><default><default> spi3_miso = port:PA8<3><default><default><default> ;---------------------------------------------------------------------------------- ;SPI device configuration ;compatible --- device name ;spi-max-frequency --- work frequency ;reg --- chip select ;optional properties: spi-cpha, spi-cpol, spi-cs-high ;---------------------------------------------------------------------------------- ;[spi0/spi_board0] ;compatible = ;spi-max-frequency = ;reg = ;spi-cpha ;spi-cpol ;spi-cs-high 修改为: ;---------------------------------------------------------------------------------- ;SPI controller configuration ;---------------------------------------------------------------------------------- ;spi0有接SPI器件(和PC引脚和NAND冲突),但是空贴! [spi0] spi0_used = 0 spi0_cs_number = 2 spi0_cs_bitmap = 3 spi0_cs0 = port:PC23<3><1><default><default> spi0_cs1 = port:PI14<2><1><default><default> spi0_sclk = port:PC2<3><default><default><default> spi0_mosi = port:PC0<3><default><default><default> spi0_miso = port:PC1<3><default><default><default> ;和gmac0复用冲突了! [spi1] spi1_used = 0 spi1_cs_number = 2 spi1_cs_bitmap = 3 spi1_cs0 = port:PA0<3><1><default><default> spi1_cs1 = port:PA4<3><1><default><default> spi1_sclk = port:PA1<3><default><default><default> spi1_mosi = port:PA2<3><default><default><default> spi1_miso = port:PA3<3><default><default><default> ;大排针J9引出来了! [spi2] spi2_used = 1 spi2_cs_number = 2 spi2_cs_bitmap = 3 spi2_cs0 = port:PB14<2><1><default><default> spi2_cs1 = port:PB13<2><1><default><default> spi2_sclk = port:PB15<2><default><default><default> spi2_mosi = port:PB16<2><default><default><default> spi2_miso = port:PB17<2><default><default><default> ;和gmac0复用冲突了! [spi3] spi3_used = 0 spi3_cs_number = 2 spi3_cs_bitmap = 3 spi3_cs0 = port:PA5<3><1><default><default> spi3_cs1 = port:PA9<3><1><default><default> spi3_sclk = port:PA6<3><default><default><default> spi3_mosi = port:PA7<3><default><default><default> spi3_miso = port:PA8<3><default><default><default> ;---------------------------------------------------------------------------------- ;SPI device configuration ;compatible --- device name ;spi-max-frequency --- work frequency ;reg --- chip select ;optional properties: spi-cpha, spi-cpol, spi-cs-high ;---------------------------------------------------------------------------------- ;[spi0/spi_board0] ;compatible = ;spi-max-frequency = ;reg = ;spi-cpha ;spi-cpol ;spi-cs-high [spi2/spi_board2] compatible = "spidev" spi-max-frequency = 50000000 reg = 0 ;spi-cpha = 0 ;spi-cpol = 0 ;spi-cs-high = 0 5、刷机之后: root@TinaLinux:/# root@TinaLinux:/# find . -name spi* ./bin/spidev20_test ./dev/spidev2.0 ./proc/irq/44/spi2 ./rom/bin/spidev20_test ./rom/usr/lib/opkg/info/spidev20_test.control ./rom/usr/lib/opkg/info/spidev20_test.list ./sys/bus/spi ./sys/bus/spi/devices/spi2.0 ./sys/bus/spi/drivers/spidev ./sys/bus/spi/drivers/spidev/spi2.0 ./sys/bus/platform/devices/spi2 ./sys/bus/platform/drivers/spi ./sys/bus/platform/drivers/spi/spi2 ./sys/devices/soc/spi2 ./sys/devices/soc/spi2/spi_master ./sys/devices/soc/spi2/spi_master/spi2 ./sys/devices/soc/spi2/spi_master/spi2/spi2.0 ./sys/devices/soc/spi2/spi_master/spi2/spi2.0/spidev ./sys/devices/soc/spi2/spi_master/spi2/spi2.0/spidev/spidev2.0 ./sys/class/spi_master ./sys/class/spi_master/spi2 ./sys/class/spidev ./sys/class/spidev/spidev2.0 ./sys/kernel/debug/clk/hosc/pll_periph0/spi2 ./sys/kernel/debug/clk/hosc/spi0 ./sys/kernel/debug/clk/hosc/spi1 ./sys/kernel/debug/clk/hosc/spi3 ./sys/module/spidev ./usr/lib/opkg/info/spidev20_test.control ./usr/lib/opkg/info/spidev20_test.list root@TinaLinux:/# root@TinaLinux:/#
内核版本:2.6.31.6 首先在S3C2440平台的初始化函数中,主要是将开发平台的设备注册进了系统,也就是将device注册到了platform虚拟的总线上,并进行了一些初始化的工作,这里我们只关注I2C的部分。 static void __init smdk2440_machine_init(void) { s3c24xx_fb_set_platdata(&smdk2440_fb_info); s3c_i2c0_set_platdata(NULL); platform_add_devices(smdk2440_devices, ARRAY_SIZE(smdk2440_devices)); smdk_machine_init(); } s3c_i2c0_set_platdata()函数将S3C2440上的I2C控制器进行了一些初始化,但是并没有写入硬件寄存器,仅仅是保存在了s3c2410_platform_i2c结构体中。 void __init s3c_i2c0_set_platdata(struct s3c2410_platform_i2c *pd) { struct s3c2410_platform_i2c *npd; if (!pd) pd = &default_i2c_data0; npd = kmemdup(pd, sizeof(struct s3c2410_platform_i2c), GFP_KERNEL); if (!npd) printk(KERN_ERR "%s: no memory for platform data\n", __func__); else if (!npd->cfg_gpio) npd->cfg_gpio = s3c_i2c0_cfg_gpio; /* s3c_i2c0_cfg_gpio为 配置I2C控制器GPIO函数指针 */ s3c_device_i2c0.dev.platform_data = npd; /*最后将struct device 中的platform_data指针直指向了初始化后的 s3c2410_platform_i2c结构体 */ } 函数s3c_i2c0_cfg_gpio()很简单,实际上就是配置GPIO为I2C的工作模式 void s3c_i2c0_cfg_gpio(struct platform_device *dev) { s3c2410_gpio_cfgpin(S3C2410_GPE(15), S3C2410_GPE15_IICSDA); s3c2410_gpio_cfgpin(S3C2410_GPE(14), S3C2410_GPE14_IICSCL); } s3c_i2c0_set_platdata(struct s3c2410_platform_i2c *pd)函数实际上就是把初始化数据段中的default_i2c_data0结构体复制过来,然后对GPIO进行配置的函数指针进行了初始化。default_i2c_data0结构体如下: static struct s3c2410_platform_i2c default_i2c_data0 __initdata = { .flags = 0, .slave_addr = 0x10, .frequency = 100*1000, .sda_delay = 100, }; s3c2410_platform_i2c结构体原型如下,根据英文注释即可大致理解其意思 /** * struct s3c2410_platform_i2c - Platform data for s3c I2C. * @bus_num: The bus number to use (if possible). * @flags: Any flags for the I2C bus (E.g. S3C_IICFLK_FILTER). * @slave_addr: The I2C address for the slave device (if enabled). * @frequency: The desired frequency in Hz of the bus. This is * guaranteed to not be exceeded. If the caller does * not care, use zero and the driver will select a * useful default. * @sda_delay: The delay (in ns) applied to SDA edges. * @cfg_gpio: A callback to configure the pins for I2C operation. */ struct s3c2410_platform_i2c { int bus_num; unsigned int flags; unsigned int slave_addr; unsigned long frequency; unsigned int sda_delay; void (*cfg_gpio)(struct platform_device *dev); }; 在函数smdk2440_machine_init(void)中,调用了 platform_add_devices(smdk2440_devices, ARRAY_SIZE(smdk2440_devices)); 即将smdk2440_devices结构体数组中platform_device添加到了系统中,也就是添加到了platform总线上。smdk2440_devices的具体内容如下: static struct platform_device *smdk2440_devices[] __initdata = { &s3c_device_usb, &s3c_device_lcd, &s3c_device_wdt, &s3c_device_i2c0, &s3c_device_iis, &s3c_device_dm9000, &s3c_device_rtc, }; 其中s3c_device_i2c0保存了S3C2440中的I2C控制器的一些内部资源等信息,具体内容如下: struct platform_device s3c_device_i2c0 = { .name = "s3c2410-i2c", /*设备名,platform总线的match函数中会用设备名和驱动名的比较来绑定设备驱动程序*/ #ifdef CONFIG_S3C_DEV_I2C1 .id = 0, #else .id = -1, #endif .num_resources = ARRAY_SIZE(s3c_i2c_resource), .resource = s3c_i2c_resource, }; 其中s3c_i2c_resource结构体保存了S3C2440中I2C控制器寄存器的物理地址和中断号等具体的硬件信息。 static struct resource s3c_i2c_resource[] = { [0] = { .start = S3C_PA_IIC, .end = S3C_PA_IIC + SZ_4K - 1, .flags = IORESOURCE_MEM, }, [1] = { .start = IRQ_IIC, .end = IRQ_IIC, .flags = IORESOURCE_IRQ, }, }; 在后面注册具体设备驱动时也会添加到paltform总线上,platform总线会将具体的设备驱动进行绑定,这样驱动就可以操作具体的设备了。platform实际上是一个虚拟的总线,本质上也是一个设备。 好了,上面是一些板级的硬件设备资源向系统的注册,没有设计到具体的硬件操作,在加载驱动程序时,驱动程序会根据已经注册到系统的具体设备的硬件资源进行初始化,也就是进行一些硬件操作,控制硬件设备的正常工作,下面来分析驱动程序的加载过程。 S3C2440平台上的I2C的驱动程序在linux/drivers/i2c/busses/i2c-s3c2410.c文件中, 在驱动的加载程序中,将platform_driver类型的s3c24xx_i2c_driver注册到了系统中。 static int __init i2c_adap_s3c_init(void) { return platform_driver_register(&s3c24xx_i2c_driver); } 分析platform_driver_register(&s3c24xx_i2c_driver);的源代码可知,实际上是将s3c24xx_i2c_driver注册到了platform总线上。 int platform_driver_register(struct platform_driver *drv) { drv->driver.bus = &platform_bus_type; /*将device_driver中的probe,remove,shutdown函数指针指向platform_driver中的函数,后面进行驱动设备绑定后会调用probe函数 */ 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); } 下图即为Linux 2.6中引入的设备驱动模型的结构图(只是个总体框架,并不是指这的platform总线,设备驱动)。 总线上包括设备驱动的集合,总线上所有设备组成双向循环链表,包含在platform_device设备集合中,总线上所有驱动组成双向循环链表,包含在platform_dirver的驱动集合中。 platform_driver_register(struct platform_driver *drv)函数实际上是对driver_register(struct device_driver *drv)函数的一个简单封装。driver_register()函数的调用关系如下 driver_register() —>bus_add_driver(drv); —> driver_attach(drv); —> bus_for_each_dev(drv->bus, NULL, drv, __driver_attach); bus_for_each_dev(drv->bus, NULL, drv, __driver_attach)函数会遍历总线上所有的设备,并调用__driver_attach函数,判断驱动是否和设备匹配,若匹配则将struct device中的 struct device_driver *driver指向此驱动,也就是进行了驱动设备的绑定,若不匹配,则继续遍历下一个设备。事实上,在向总线注册设备时,同样会进行类似的操作,遍历总线上所有驱动程序,找到则进行设备驱动程序的绑定。 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. */ /*调用platform总线的match()函数,即platform_match函数,判断设备驱动是否匹配,若匹配则返真,找到对应的设备,继续执行后面的程序,若没有找到,则返回假,函数执行结束 。这里我们的I2C驱动找到了可以驱动设备,所以会继续执行*/ if (!driver_match_device(drv, dev)) return 0; if (dev->parent) /* Needed for USB */ down(&dev->parent->sem); down(&dev->sem); /*设备是否已经找到驱动?显然,这里没有找到驱动,因为设备在向系统中platform总线注册时还没有驱动注册到platform总线上,所以dev->drive = NULL */ if (!dev->driver) driver_probe_device(drv, dev); up(&dev->sem); if (dev->parent) up(&dev->parent->sem); return 0; } driver_probe_device(drv, dev)函数进行驱动设备的绑定。 /** * driver_probe_device - attempt to bind device & driver together * @drv: driver to bind a device to * @dev: device to try to bind to the driver * * This function returns -ENODEV if the device is not registered, * 1 if the device is bound sucessfully and 0 otherwise. * * This function must be called with @dev->sem held. When called for a * USB interface, @dev->parent->sem must be held as well. */ int driver_probe_device(struct device_driver *drv, struct device *dev) { int ret = 0; if (!device_is_registered(dev)) //判断设备是否已经注册 return -ENODEV; pr_debug("bus: '%s': %s: matched device %s with driver %s\n", drv->bus->name, __func__, dev_name(dev), drv->name); ret = really_probe(dev, drv); return ret; } really_probe函数中 进行devicedriver的绑定,并调用用户在device_driver 中注册的probe()例程。 static int really_probe(struct device *dev, struct device_driver *drv) { int ret = 0; atomic_inc(&probe_count); pr_debug("bus: '%s': %s: probing driver %s with device %s\n", drv->bus->name, __func__, drv->name, dev_name(dev)); WARN_ON(!list_empty(&dev->devres_head)); /*将device中的device_driver指针指向了这个driver,即完成devicedriver的绑定*/ dev->driver = drv; f (driver_sysfs_add(dev)) { printk(KERN_ERR "%s: driver_sysfs_add(%s) failed\n", __func__, dev_name(dev)); goto probe_failed; } /*若总线设置了probe函数,则调用总线的probe函数,然而platform总线并没有设置 */ if (dev->bus->probe) { ret = dev->bus->probe(dev); if (ret) goto probe_failed; } /* 否则,调用驱动注册在device_driver里的probe,这个函数中一般进行获得硬件资源,初始化硬件等操作,这里实际调用了s3c24xx_i2c_probe函数*/ else if (drv->probe) { ret = drv->probe(dev); if (ret) goto probe_failed; } /*将设备添加到driver所支持的设备列表中(因为一个驱动可以支持多个设备),并通知bus上的设备,表明BUS_NOTIFY_BOUND_DRIVER */ driver_bound(dev); ret = 1; pr_debug("bus: '%s': %s: bound device %s to driver %s\n", drv->bus->name, __func__, dev_name(dev), drv->name); goto done; probe_failed: devres_release_all(dev); driver_sysfs_remove(dev); dev->driver = NULL; if (ret != -ENODEV && ret != -ENXIO) { /* driver matched but the probe failed */ printk(KERN_WARNING "%s: probe of %s failed with error %d\n", drv->name, dev_name(dev), ret); } /* * Ignore errors returned by ->probe so that the next driver can try * its luck. */ ret = 0; done: atomic_dec(&probe_count); wake_up(&probe_waitqueue); return ret; } 到这里,I2C设备软件层次上的驱动模型已经建立好了,接着会执行s3c24xx_i2c_probe函数,获取系统开始注册的一些硬件资源信息,进行硬件上的一些操作,以及真正的涉及到数据传输驱动程序的注册等操作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值