目录
1. 分配一个platform_driver结构并调用platform_driver_register函数进行注册
2. 调用driver_register函数将驱动添加到总线的drv链表(其中大部分工作通过调用bus_add_driver函数来完成)
4. 调用driver_attach函数,对总线drv链表中的驱动与总线dev链表中的设备进行匹配
5. 调用driver_probe_device函数,将设备与驱动程序进行绑定(调用probe函数)
platform_device的注册过程可以简化为以下过程:
1. 分配一个platform_device结构并调用platform_device_register函数进行注册
2. 调用platform_device_add函数向内核添加一个平台设备
3. 调用device_add函数向内核添加一个device
4. 调用bus_probe_device函数为总线上的设备寻找驱动
5. 调用driver_probe_device将设备与驱动程序进行绑定(调用probe函数)
这是在整理设备树时,发现平台设备还是不够的清楚,这里在重新详细的整理一下
下图是嵌入式系统常见的硬件拓扑的一个示例
platform总线设备模型
1. 平台总线概述
从Linux2.6开始Linux加入了一套驱动管理和注册机制—platform平台总线驱动模型。platform平台总线是一条虚拟总线,platform_device为相应的设备,platform_driver为相应的驱动。与传统的bus/device/driver机制相比,platform由内核统一进行管理,提高了代码的可移植性和安全性。所谓的platform_device并不是与字符设备、块设备和网络设备并列的概念,而是Linux系统提供的一种附加手段。Linux总线设备驱动模型的框架如下图所示:
从图中我们可以很清楚的看出Linux平台总线设备驱动模型的整体架构。在总线设备驱动模型中,需关心总线、设备和驱动这3个实体,总线将设备和驱动绑定。当系统向内核注册每一个驱动程序时,都要通过调用platform_driver_register函数将驱动程序注册到总线,并将其放入所属总线的drv链表中,注册驱动的时候会调用所属总线的match函数寻找该总线上与之匹配的每一个设备,如果找到与之匹配的设备则会调用相应的probe函数将相应的设备和驱动进行绑定;同样的当系统向内核注册每一个设备时,都要通过调用platform_device_register函数将设备注册到总线,并将其放入所属总线的dev链表中,注册设备的时候同样也会调用所属总线的match函数寻找该总线上与之匹配的每一个驱动程序,如果找到与之匹配的驱动程序时会调用相应的probe函数将相应的设备和驱动进行绑定;而这一匹配的过程是由总线自动完成的。
bus
include/device.h
1.1描述结构体
/**
* 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_attrs: Default attributes of the bus.
* @dev_attrs: Default attributes of the devices on the bus.
* @drv_attrs: 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 nonzero value if the
* given device can be handled by the given driver.
* @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.
* @remove: Called when a device removed from this bus.
* @shutdown: Called at shut-down time to quiesce the device.
* @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.
* @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.
*
* 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;
struct bus_attribute *bus_attrs;
struct device_attribute *dev_attrs;
struct driver_attribute *drv_attrs;
//驱动和设备的匹配函数
/*当一个新设备/新驱动被添加到这个总线时,该函数被调用,用于判断指定的驱动程序
是否能处理指定的设备,若可以则返回非零
*/
int (*match)(struct device *dev, struct device_driver *drv);
int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
int (*probe)(struct device *dev);
int (*remove)(struct device *dev);
void (*shutdown)(struct device *dev);
int (*suspend)(struct device *dev, pm_message_t state);
int (*resume)(struct device *dev);
const struct dev_pm_ops *pm;
struct iommu_ops *iommu_ops;
struct subsys_private *p;
struct lock_class_key lock_key;
};
1.2注册
platform_bus_init过程中,platform_bus_type总线和platform_bus设备的注册,以及构建的目录结构
driver/base/platform.c
platform_bus_init//总线初始化
early_platform_cleanup();//进行一些早期的平台清理
//注册设备 (在/sys/devices/目录下建立 platform目录对应的设备对象 /sys/devices/platform/)
error = device_register(&platform_bus);
error = bus_register(&platform_bus_type);//总线注册
bus_register
/**
* 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.
*/
//若成功,新总线将被添加进系统,并可在/sys/bus下看到相应目录
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);
if (!priv)
return -ENOMEM;
priv->bus = bus;
bus->p = priv;
BLOCKING_INIT_NOTIFIER_HEAD(&priv->bus_notifier);
retval = kobject_set_name(&priv->subsys.kobj, "%s", bus->name);
goto out;
priv->subsys.kobj.kset = bus_kset;/* 绑定内核再初始化时就位所有的bus统一申请的kset */
priv->subsys.kobj.ktype = &bus_ktype;/* 绑定ktype */
priv->drivers_autoprobe = 1;
retval = kset_register(&priv->subsys);/* 注册kset,即在bus目录下就出现了对应目录 */
if (retval)
if (retval)
goto out;
retval = bus_create_file(bus, &bus_attr_uevent);
if (retval)
goto bus_uevent_fail;
/* bus目录下创建devices和drivers目录,用来将来存放绑定该总线的驱动 */
priv->devices_kset = kset_create_and_add("devices", NULL,
&priv->subsys.kobj);
if (!priv->devices_kset) {
retval = -ENOMEM;
goto bus_devices_fail;
}
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_init(&priv->klist_devices, klist_devices_get, klist_devices_put);
klist_init(&priv->klist_drivers, NULL, NULL);
/* 总线的最重要的目的是匹配device和driver,用的就是probe机制 */
retval = add_probe_files(bus);
if (retval)
goto bus_probe_files_fail;
/* 总线可以属于其它总线的组里面 */
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;
}
1.4示例
Tbus.c
#include<linux/module.h>
#include<linux/init.h>
#include<linux/kernel.h>
#include<linux/device.h>
int Tmatch(struct device *dev, struct device_driver *drv)
{
return 0;
}
struct bus_type Tbus_type =
{
.name = "Tbus",
.match = Tmatch,
};
int Tbus_init(void)
{
int ret;
ret = bus_register(&Tbus_type);
return ret;
}
void Tbus_exit(void)
{
bus_unregister(&Tbus_type);
}
module_init(Tbus_init);
module_exit(Tbus_exit);
MODULE_LICENSE("GPL");
下面向上面的my_bus总线上挂载一个驱动!
platform驱动的相关数据结构
1. 结构体驱动对应的结构体platform_driver
include/platform_device.h
struct platform_driver {
int (*probe)(struct platform_device *);
int (*remove)(struct platform_device *);
void (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device *, pm_message_t state);
int (*resume)(struct platform_device *);
struct device_driver driver;
const struct platform_device_id *id_table;
};
可以看到platform_driver结构体中包含了probe和remove等相关操作,同时还内嵌了device_driver结构体。
2. device_driver结构体
include/device/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.
* @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.
* @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.
* @pm: Power management operations of the device which matched
* this driver.
* @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 */
const struct of_device_id *of_match_table;
const struct acpi_device_id *acpi_match_table;
int (*probe) (struct device *dev);//驱动挂载时调用
int (*remove) (struct device *dev);//驱动卸载时调用
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 dev_pm_ops *pm;
struct driver_private *p;
};
其中有一个指向driver_private的指针p,一些与其他的组件相关的联系都被移到这个结构变量中。
3. driver_private结构体
struct driver_private {
struct kobject kobj;// 在sysfs中代表目录本身
struct klist klist_devices;// 驱动链表即我们上面所说的drv链表
struct klist_node knode_bus;// 挂载在总线上的驱动链表的节点
struct module_kobject *mkobj;// driver与相关的module之间的联系
struct device_driver *driver;
};
#define to_driver(obj) container_of(obj, struct driver_private, kobj)
4.注册
int driver_register(struct device_driver *drv)
简单示例
Tdevice.c
#include <linux/device.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
extern struct bus_type Tbus_type;
static void Trelease(struct device *dev)
{
}
struct device Tdev =
{
.init_name = "Tdev",//和驱动名字一样
.bus = &Tbus_type,
.release = Trelease,
};
int Tdevice_init(void)
{
int ret;
ret = device_register(&Tdev);
return ret;
}
void Tdevice_exit(void)
{
device_unregister(&Tdev);
}
module_init(Tdevice_init);
module_exit(Tdevice_exit);
MODULE_LICENSE("GPL");
Tdriver.c
#include<linux/module.h>
#include<linux/init.h>
#include<linux/device.h>
#include<linux/kernel.h>
extern struct bus_type Tbus_type;
int Tprobe(struct device *dev)
{
printk(KERN_WARNING"driver found the device!!!\n");
return 0;
}
struct device_driver Tdriver =
{
.name = "Tdev",
.bus = &Tbus_type,
.probe = Tprobe, //当找到这个设备时将调用这个函数
};
int Tdevice_init(void)
{
int ret;
ret = driver_register(&Tdriver);//注册一个驱动
return 0;
}
void Tdevice_exit(void)
{
driver_unregister(&Tdriver);
}
module_init(Tdevice_init);
module_exit(Tdevice_exit);
MODULE_LICENSE("GPL");
注册过程可以简化为如下过程:随便找了个示例
//1.定义平台驱动并初始化
static struct platform_driver serial8250_isa_driver = {
.probe = serial8250_probe,
.remove = serial8250_remove,
.suspend = serial8250_suspend,
.resume = serial8250_resume,
.driver = {
.name = "serial8250",
},
};
platform_driver_register(&serial8250_isa_driver)//2.注册平台驱动
struct bus_type platform_bus_type = {
.name = "platform",
.dev_groups = platform_dev_groups,
.match = platform_match,//驱动和设备匹配,重点
.uevent = platform_uevent,
.dma_configure = platform_dma_configure,
.pm = &platform_dev_pm_ops,
};
int __platform_driver_register(struct platform_driver *drv,
struct module *owner)
{
drv->driver.owner = owner;
drv->driver.bus = &platform_bus_type;// 设置driver的bus的类型为platform_bus_type
drv->driver.probe = platform_drv_probe;// 如果drv含有probe(device_driver类型)则driver上的probe指向总线的probe函数
drv->driver.remove = platform_drv_remove;// 如果drv含有remove(device_driver类型)则driver上的remove指向总线的remove函数
drv->driver.shutdown = platform_drv_shutdown;// 如果drv含有shutdown(device_driver类型)则driver上的shutdown指向总线的shutdown函数
return driver_register(&drv->driver);// 注册平台驱动
}
driver_register(&drv->driver);//将注册的驱动挂接到platform总线上
ret = bus_add_driver(drv);
bus = bus_get(drv->bus);// 获取总线类型
priv = kzalloc(sizeof(*priv), GFP_KERNEL);//分配一个driver_private结构并初始化(也就是drv->p)
error = kobject_init_and_add(&priv->kobj, &driver_ktype, NULL,
"%s", drv->name);//将drv加入sysfs
error = driver_attach(drv);//对总线drv链表中的驱动与总线dev链表中的设备进行匹配
return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
while (!error && (dev = next_device(&i)))// 遍历总线dev链表中的所有设备
error = fn(dev, data);// 判断驱动与设备是否匹配,若匹配则将二者绑定
driver_probe_device(drv, dev);
//调用bus的match函数对设备和驱动进行匹配,若不匹配driver_match_device函数的返回值为1,
//则程序立即返回,若匹配则继续向下执行
ret = driver_match_device(drv, dev);
// 若设备和驱动匹配且设备的驱动程序为空,则将该驱动程序绑定到该设备
//(调用驱动程序的probe函数)
driver_probe_device(drv, dev);
ret = really_probe(dev, drv);
<dev->bus->probe(dev) 或 ->>&drv->driver->probe(dev)>
//注:若调用probe函数时为drv->probe(dev),则实际调用的函数为platform_drv_probe其源码为
if (dev->bus->probe) {// 如果设备本身含有probe函数,那么久调用设备的probe函数
ret = dev->bus->probe(dev);
if (ret)
goto probe_failed;
} else if (drv->probe) {// 如果设备不含有probe函数,那么就调用驱动程序的probe函数
ret = drv->probe(dev);
将此过程展开如下:
1. 分配一个platform_driver结构并调用platform_driver_register函数进行注册
#define platform_driver_register(drv) \
__platform_driver_register(drv, THIS_MODULE)
int __platform_driver_register(struct platform_driver *drv,
struct module *owner)
{
drv->driver.owner = owner;
drv->driver.bus = &platform_bus_type; // 设置driver的bus的类型为platform_bus_type
if (drv->probe) // 如果drv含有probe(device_driver类型)则driver上的probe指向总线的probe函数
drv->driver.probe = platform_drv_probe;
if (drv->remove) // 如果drv含有remove(device_driver类型)则driver上的remove指向总线的remove函数
drv->driver.remove = platform_drv_remove;
if (drv->shutdown) // 如果drv含有shutdown(device_driver类型)则driver上的shutdown指向总线的shutdown函数
drv->driver.shutdown = platform_drv_shutdown;
return driver_register(&drv->driver); // 注册平台驱动
}
2. 调用driver_register函数将驱动添加到总线的drv链表(其中大部分工作通过调用bus_add_driver函数来完成)
int driver_register(struct device_driver *drv)
{
int ret;
struct device_driver *other;
BUG_ON(!drv->bus->p);
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);
other = driver_find(drv->name, drv->bus);
if (other) {
printk(KERN_ERR "Error: Driver '%s' is already registered, "
"aborting...\n", drv->name);
return -EBUSY;
}
ret = bus_add_driver(drv); // 添加驱动到总线驱动链表即我们上面所说的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;
}
3. 调用bus_add_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); // 分配一个driver_private结构并初始化(也就是drv->p)
if (!priv) {
error = -ENOMEM;
goto out_put_bus;
}
klist_init(&priv->klist_devices, NULL, NULL);
priv->driver = drv;
drv->p = priv;
priv->kobj.kset = bus->p->drivers_kset;
error = kobject_init_and_add(&priv->kobj, &driver_ktype, NULL,
"%s", drv->name); //将drv加入sysfs
if (error)
goto out_unregister;
klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers); //将drv挂入到总线的链表中
if (drv->bus->p->drivers_autoprobe) {
error = driver_attach(drv); //如果总线可以自动的probe,就会调用匹配函数
if (error)
goto out_unregister;
}
module_add_driver(drv->owner, drv); //创建driver相关的模块
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_create_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);
kfree(drv->p);
drv->p = NULL;
out_put_bus:
bus_put(bus);
return error;
}
4. 调用driver_attach函数,对总线drv链表中的驱动与总线dev链表中的设备进行匹配
int driver_attach(struct device_driver *drv)
{
return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
}
(1)bus_for_each_dev函数源码:
int bus_for_each_dev(struct bus_type *bus, struct device *start,
void *data, int (*fn)(struct device *, void *))
{
struct klist_iter i;
struct device *dev;
int error = 0;
if (!bus || !bus->p)
return -EINVAL;
klist_iter_init_node(&bus->p->klist_devices, &i,
(start ? &start->p->knode_bus : NULL));
while ((dev = next_device(&i)) && !error) // 遍历总线dev链表中的所有设备
error = fn(dev, data); // 判断驱动与设备是否匹配,若匹配则将二者绑定
klist_iter_exit(&i);
return error;
}
(2)__driver_attach函数源码:
static int __driver_attach(struct device *dev, void *data)
{
struct device_driver *drv = data;
/*
* Lock device and try to bind to it. We drop the error
* here and always return 0, because we need to keep trying
* to bind to devices and some drivers will return an error
* simply if it didn't support the device.
*
* driver_probe_device() will spit a warning if there
* is an error.
*/
if (!driver_match_device(drv, dev)) // 调用bus的match函数对设备和驱动进行匹配,若不匹配driver_match_device函数的返回值为1,则程序立即返回,若匹配则继续向下执行
return 0;
if (dev->parent) /* Needed for USB */
device_lock(dev->parent);
device_lock(dev);
if (!dev->driver)
driver_probe_device(drv, dev); // 若设备和驱动匹配且设备的驱动程序为空,则将该驱动程序绑定到该设备(调用驱动程序的probe函数)
device_unlock(dev);
if (dev->parent)
device_unlock(dev->parent);
return 0;
}
5. 调用driver_probe_device函数,将设备与驱动程序进行绑定(调用probe函数)
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);
pm_runtime_barrier(dev);
ret = really_probe(dev, drv); // 调用probe
pm_request_idle(dev);
return ret;
}
static int really_probe(struct device *dev, struct device_driver *drv)
{
int ret = 0;
int local_trigger_count = atomic_read(&deferred_trigger_count);
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));
dev->driver = drv;
/* If using pinctrl, bind pins now before probing */
ret = pinctrl_bind_pins(dev);
if (ret)
goto probe_failed;
if (driver_sysfs_add(dev)) {
printk(KERN_ERR "%s: driver_sysfs_add(%s) failed\n",
__func__, dev_name(dev));
goto probe_failed;
}
if (dev->bus->probe) { // 如果设备本身含有probe函数,那么久调用设备的probe函数
ret = dev->bus->probe(dev);
if (ret)
goto probe_failed;
} else if (drv->probe) { // 如果设备不含有probe函数,那么就调用驱动程序的probe函数
ret = drv->probe(dev);
if (ret)
goto probe_failed;
}
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;
dev_set_drvdata(dev, NULL);
if (ret == -EPROBE_DEFER) {
/* Driver requested deferred probing */
dev_info(dev, "Driver %s requests probe deferral\n", drv->name);
driver_deferred_probe_add(dev);
/* Did a trigger occur while probing? Need to re-trigger if yes */
if (local_trigger_count != atomic_read(&deferred_trigger_count))
driver_deferred_probe_trigger();
} else 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);
} else {
pr_debug("%s: probe of %s rejects match %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;
}
注:若调用probe函数时为drv->probe(dev),则实际调用的函数为platform_drv_probe其源码为
static int platform_drv_probe(struct device *_dev)
{
struct platform_driver *drv = to_platform_driver(_dev->driver);
struct platform_device *dev = to_platform_device(_dev);
int ret;
if (ACPI_HANDLE(_dev))
acpi_dev_pm_attach(_dev, true);
ret = drv->probe(dev); // 此处间接地调用了platform_driver提供的probe函数
if (ret && ACPI_HANDLE(_dev))
acpi_dev_pm_detach(_dev, true);
if (drv->prevent_deferred_probe && ret == -EPROBE_DEFER) {
dev_warn(_dev, "probe deferral not supported\n");
ret = -ENXIO;
}
return ret;
}
注:platform_driver提供的probe函数即为注册平台驱动时程序员自己编写的probe函数
platform_device的注册过程
platform_device的注册过程可以简化为以下过程:
struct platform_device *pdev;// 定义一个平台设备并初始化
platform_device_register(pdev)// 注册
platform_device_add(pdev)// 向内核添加一个平台设备
device_add(&pdev->dev)// 向内核添加一个device
bus_probe_device(&pdev->dev)// 为总线上的设备寻找驱动
device_attach(&pdev->dev)// 调用device_attach()进行实际的寻找
bus_for_each_drv(&(pdev->dev)->bus, NULL, &pdev->dev, __device_attach)// 遍历bus的drv链表为设备寻找驱动
__device_attach(drv, &pdev->dev)
driver_probe_device(drv, &pdev->dev)
really_probe(&pdev->dev, drv)// 调用probe
<&pdev->dev->bus->probe(dev) 或 drv->probe(dev)>
// 如果设备本身含有probe函数,那么久调用设备的probe函数
将此过程展开如下:
1. 分配一个platform_device结构并调用platform_device_register函数进行注册
int platform_device_register(struct platform_device *pdev)
{
device_initialize(&pdev->dev); // 初始化platform_device的device成员
arch_setup_pdev_archdata(pdev);
return platform_device_add(pdev); // 向内核添加一个平台设备
}
2. 调用platform_device_add函数向内核添加一个平台设备
int platform_device_add(struct platform_device *pdev)
{
int i, ret;
if (!pdev)
return -EINVAL;
if (!pdev->dev.parent) //如果pdev->dev.parent为空则将pdev->dev.parent设置为platform_bus
pdev->dev.parent = &platform_bus;
pdev->dev.bus = &platform_bus_type; // 设置总线类型为platform_bus_type
switch (pdev->id) { // 分配名字
default:
dev_set_name(&pdev->dev, "%s.%d", pdev->name, pdev->id);
break;
case PLATFORM_DEVID_NONE:
dev_set_name(&pdev->dev, "%s", pdev->name);
break;
case PLATFORM_DEVID_AUTO:
/*
* Automatically allocated device ID. We mark it as such so
* that we remember it must be freed, and we append a suffix
* to avoid namespace collision with explicit IDs.
*/
ret = ida_simple_get(&platform_devid_ida, 0, 0, GFP_KERNEL);
if (ret < 0)
goto err_out;
pdev->id = ret;
pdev->id_auto = true;
dev_set_name(&pdev->dev, "%s.%d.auto", pdev->name, pdev->id);
break;
}
for (i = 0; i < pdev->num_resources; i++) { // 获取资源
struct resource *p, *r = &pdev->resource[i];
if (r->name == NULL)
r->name = dev_name(&pdev->dev);
p = r->parent;
if (!p) { // 设置资源类型
if (resource_type(r) == IORESOURCE_MEM)
p = &iomem_resource;
else if (resource_type(r) == IORESOURCE_IO)
p = &ioport_resource;
}
if (p && insert_resource(p, r)) {
dev_err(&pdev->dev, "failed to claim resource %d\n", i);
ret = -EBUSY;
goto failed;
}
}
pr_debug("Registering platform device '%s'. Parent at %s\n",
dev_name(&pdev->dev), dev_name(pdev->dev.parent));
ret = device_add(&pdev->dev); // 向内核添加一个device
if (ret == 0)
return ret;
failed:
if (pdev->id_auto) {
ida_simple_remove(&platform_devid_ida, pdev->id);
pdev->id = PLATFORM_DEVID_AUTO;
}
while (--i >= 0) {
struct resource *r = &pdev->resource[i];
unsigned long type = resource_type(r);
if (type == IORESOURCE_MEM || type == IORESOURCE_IO)
release_resource(r);
}
err_out:
return ret;
}
3. 调用device_add函数向内核添加一个device
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;
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()
*/
if (dev->init_name) {
dev_set_name(dev, "%s", dev->init_name);
dev->init_name = NULL;
}
/* subsystems can specify simple device enumeration */
if (!dev_name(dev) && dev->bus && dev->bus->dev_name)
dev_set_name(dev, "%s%u", dev->bus->dev_name, dev->id);
if (!dev_name(dev)) {
error = -EINVAL;
goto name_error;
}
pr_debug("device: '%s': %s\n", dev_name(dev), __func__);
parent = get_device(dev->parent);
kobj = get_device_parent(dev, parent);
if (kobj)
dev->kobj.parent = kobj;
/* use parent numa_node */
if (parent)
set_dev_node(dev, dev_to_node(parent));
/* first, register with generic layer. */
/* we require the name to be set before, and pass NULL */
error = kobject_add(&dev->kobj, dev->kobj.parent, NULL);
if (error)
goto Error;
/* notify platform of device entry */
if (platform_notify)
platform_notify(dev);
error = device_create_file(dev, &dev_attr_uevent);
if (error)
goto attrError;
if (MAJOR(dev->devt)) {
error = device_create_file(dev, &dev_attr_dev);
if (error)
goto ueventattrError;
error = device_create_sys_dev_entry(dev);
if (error)
goto devtattrError;
devtmpfs_create_node(dev);
}
error = device_add_class_symlinks(dev);
if (error)
goto SymlinkError;
error = device_add_attrs(dev);
if (error)
goto AttrsError;
error = bus_add_device(dev);
if (error)
goto BusError;
error = dpm_sysfs_add(dev);
if (error)
goto DPMError;
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;
DPMError:
bus_remove_device(dev);
BusError:
device_remove_attrs(dev);
AttrsError:
device_remove_class_symlinks(dev);
SymlinkError:
if (MAJOR(dev->devt))
devtmpfs_delete_node(dev);
if (MAJOR(dev->devt))
device_remove_sys_dev_entry(dev);
devtattrError:
if (MAJOR(dev->devt))
device_remove_file(dev, &dev_attr_dev);
ueventattrError:
device_remove_file(dev, &dev_attr_uevent);
attrError:
kobject_uevent(&dev->kobj, KOBJ_REMOVE);
kobject_del(&dev->kobj);
Error:
cleanup_device_parent(dev);
if (parent)
put_device(parent);
name_error:
kfree(dev->p);
dev->p = NULL;
goto done;
}
4. 调用bus_probe_device函数为总线上的设备寻找驱动
void bus_probe_device(struct device *dev)
{
struct bus_type *bus = dev->bus;
struct subsys_interface *sif;
int ret;
if (!bus)
return;
if (bus->p->drivers_autoprobe) {
ret = device_attach(dev); // 调用device_attach()进行实际的寻找
WARN_ON(ret < 0);
}
mutex_lock(&bus->p->mutex);
list_for_each_entry(sif, &bus->p->interfaces, node)
if (sif->add_dev)
sif->add_dev(dev, sif);
mutex_unlock(&bus->p->mutex);
}
int device_attach(struct device *dev)
{
int ret = 0;
device_lock(dev);
if (dev->driver) {
if (klist_node_attached(&dev->p->knode_driver)) {
ret = 1;
goto out_unlock;
}
ret = device_bind_driver(dev);
if (ret == 0)
ret = 1;
else {
dev->driver = NULL;
ret = 0;
}
} else {
ret = bus_for_each_drv(dev->bus, NULL, dev, __device_attach); // 遍历bus的drv链表为设备寻找驱动
pm_request_idle(dev);
}
out_unlock:
device_unlock(dev);
return ret;
}
(1) bus_for_each_drv函数源码:
int bus_for_each_drv(struct bus_type *bus, struct device_driver *start,
void *data, int (*fn)(struct device_driver *, void *))
{
struct klist_iter i;
struct device_driver *drv;
int error = 0;
if (!bus)
return -EINVAL;
klist_iter_init_node(&bus->p->klist_drivers, &i,
start ? &start->p->knode_bus : NULL);
while ((drv = next_driver(&i)) && !error) // 遍历整个drv链表
error = fn(drv, data); // 寻找该设备匹配的驱动程序,若匹配则将二者绑定
klist_iter_exit(&i);
return error;
}
5. 调用driver_probe_device将设备与驱动程序进行绑定(调用probe函数)
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);
pm_runtime_barrier(dev);
ret = really_probe(dev, drv); // 调用probe
pm_request_idle(dev);
return ret;
}
static int really_probe(struct device *dev, struct device_driver *drv)
{
int ret = 0;
int local_trigger_count = atomic_read(&deferred_trigger_count);
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));
dev->driver = drv;
/* If using pinctrl, bind pins now before probing */
ret = pinctrl_bind_pins(dev);
if (ret)
goto probe_failed;
if (driver_sysfs_add(dev)) {
printk(KERN_ERR "%s: driver_sysfs_add(%s) failed\n",
__func__, dev_name(dev));
goto probe_failed;
}
if (dev->bus->probe) { // 如果设备本身含有probe函数,那么久调用设备的probe函数
ret = dev->bus->probe(dev);
if (ret)
goto probe_failed;
} else if (drv->probe) { // 如果设备不含有probe函数,那么就调用驱动程序的probe函数
ret = drv->probe(dev);
if (ret)
goto probe_failed;
}
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;
dev_set_drvdata(dev, NULL);
if (ret == -EPROBE_DEFER) {
/* Driver requested deferred probing */
dev_info(dev, "Driver %s requests probe deferral\n", drv->name);
driver_deferred_probe_add(dev);
/* Did a trigger occur while probing? Need to re-trigger if yes */
if (local_trigger_count != atomic_read(&deferred_trigger_count))
driver_deferred_probe_trigger();
} else 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);
} else {
pr_debug("%s: probe of %s rejects match %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;
}
到此Linux内核的总线设备驱动模型分析完毕。从上面的分析过程可以看出,所谓的platform_device并不是与字符设备、块设备和网络设备并列的概念,而是Linux系统提供的一种附加手段。
测试1
基于tiny4412,4个led灯
设备层编程
1 编程思路
1. 先编写一个模块的模板代码文件
2. 在模块文件中定义 struct platform_device 结构变量
3. 初始化上一步定义的 struct platform_device 结构变量必须的元素,一般在定义时候就初始化了
初始化的核心:
1)分析清楚设备占用的资源情况:分析硬件原理图得到。
2)定义设备占用的资源数组
3)初始化设备结构的 num_resources, resource
4)根据需要决定是否需要传递平台数据: dev.platform_data 成员
4. 在模块加载函数中调用 platform_device_register 函数注册已经初始好的平台设备结构变量
5. 在模块卸载函数中调用 platform_device_unregister 函数注销已经注册的平台设备
2 编写代码
以 LED 这个设备为例子编写设备层代码。
4 个灯占用的 IO 口是 GPM4 这组控制,所以需要使用到 GPM4 相关的寄存器。
第一个寄存器地址:
6.2.3.113 GPM4CON
Base Address: 0x1100_0000
Address = Base Address + 0x02E0, Reset Value = 0x0000_0000 = 0x110002E0
最后一个寄存器地址:
6.2.3.118 GPM4PUDPDN
Base Address: 0x1100_0000
Address = Base Address + 0x02F4, Reset Value = 0x0000
这个寄存器本身还占用 4 字节空间,所以结束地址是 = 0x110002F8-1 = 0x110002F7
占用的内存空间大小 = 0x110002F7 - 0x110002E0 + 1 = 0x18
3.device实现源码:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/platform_device.h>
#include "leds_platform_data.h"
//定义开发板上每个led基本信息
struct led_info myled_info[] = {
[0] = {
.pin_nr = 0,
},
[1] = {
.pin_nr = 1,
},
[2] = {
.pin_nr = 2,
},
[3] = {
.pin_nr = 3,
}
};
//定义led平台数据变量并且初始化
struct leds_data myleds_data = {
.leds_info = myled_info,
.led_num = ARRAY_SIZE(myled_info)
};
//3.1 定义leds设备占用的内存资源
struct resource myleds_res[] = {
[0] = {
.start = 0x110002E0, //GPM4相关寄存器的物理起始地址
.end = 0x110002F7, //GPM4相关寄存器的物理结束地址
.flags = IORESOURCE_MEM, //上面的start,end表示 范围是内存地址
.name = "GPM4", //可选择的
}
};
//写一个空函数,消除卸载模块时候警告
void myleds_release(struct device *dev)
{
;
}
//2.定义一个平台设备变量
struct platform_device myleds_dev = {
//3.2 初始化结构
.name = "myleds",
.id = -1,
.num_resources = ARRAY_SIZE(myleds_res),
.resource = myleds_res,
.dev.release = myleds_release,
.dev.platform_data = &myleds_data,
};
static int leddev_init(void)
{
int ret ;
printk(KERN_EMERG"%s is call\r\n", __FUNCTION__);
//4. 注册平台设备
ret = platform_device_register(&myleds_dev);
if(ret < 0) {
printk("platform_device_register error\r\n");
return ret;
}
return 0;
}
static void leddev_exit(void)
{
//5.注销平台设备
platform_device_unregister(&myleds_dev);
printk(KERN_EMERG"%s is call\r\n", __FUNCTION__);
}
module_init(leddev_init);
module_exit(leddev_exit);
MODULE_LICENSE("GPL");
驱动层编写
目标:把以前写的 leds 普通杂项设备驱动修改为平台设备驱动模型。
1 编程思路
1. 先编写一个模块文件的基本模板
2. 定义一个平台驱动结构变量
3. 初始化上一步定义平台驱动结构变量
4. 在模块加载函数中调用平台驱动注册函数注册上一步初始好的平台驱动结构变量
5. 在模块卸载函数中调用平台驱动注销函数注销平台驱动。
核心:
1)设备名: .driver.name : 保持和设备层中的相同即可
2) id: 如果一对一就是-1
3) probe:
a.探测平台设备资源
b.申请使用平台设备资源
c.使用申请到资源:内存资源—映射成虚拟地址再使用,中断资源—注册中断。
d.注册字符设备(杂项设备):以前应该怎么写还怎么写
4) remove:功能和 probe 相反,所以它所做事情是释放 在 probe 函数占用的资源。
2 代码编写
在上个示例代码基础上修改
#include<linux/kernel.h>
#include<linux/module.h>
#include<linux/init.h>
#include<linux/ioctl.h>
#include<linux/types.h>
#include<linux/cdev.h>
#include<linux/device.h>
#include<linux/fs.h>
#include <asm/io.h> //包含了IO内存的相关操作
#include <linux/platform_device.h>
#include <linux/miscdevice.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include "leds_platform_data.h"
//定义平台数据结构指针,在probe函数中获得指向,在文件操作方法中使用
struct leds_data *pdata_leds;
static unsigned long *base_addr = NULL; //存放物理地址对应的虚拟地址
//按照数据手册定义寄存器指针
//修改平台模型后,设备资源已经不写驱动层代码中了
//#define BASE_ADDR (0x110002E0) //GPM4CON起始 地址
#define GPM4CON (*( volatile unsigned *)(base_addr+0))
#define GPM4DAT (*( volatile unsigned *)(base_addr+1))
//以下是文件操作方法的具体实现代码
static int xxx_open(struct inode *pinode, struct file *pfile )
{
printk(KERN_EMERG"file:%s\r\nline:%d, %s is call\n", __FILE__, __LINE__, __FUNCTION__);
return 0;
}
//用户空间: ssize_t read(int fd, void *buf, size_t count);
//内核空间不能直接使用用户空间传递下来的指针 ,因为会有安全隐患
//要复制数据给用户空间,必须使用内核专用的复制函数 copy_to_user
//这个函数会先检测指针是否合法,合法再进行复制 read( fd, buf, 1);
/*
1.7.1.3 考虑文件指针偏移需要考虑以下情况
1) count如是0,函数返回0.
'驱动程序不做任何操作,直接返回0。
2) 如果文件读写位置已经到末尾,不管count是多少,都不能读,返回0;
'把leds设备看成是只有4个字节的文件,当读写位置已经等于4时候,则不能再进行读操作。
3) 当读写位置还没有到文件最后:
a. 如count 大于于文件大小,修正大小为:文件大小 - *poff
b. 如count 小于文件大小,但是count + *poff大于文件大小,修正大小为:文件大小 - *poff
c. 如count 小于文件大小,但是count + *poff小于或等于于文件大小,则count不需要修正。
*/
static ssize_t xxx_read(struct file *pfile, char __user *buf, size_t count, loff_t *poff)
{
int i;
char kbuf[1000] = {0};
loff_t cur_pos = *poff; //取出当前读写位置值
printk(KERN_EMERG"file:%s\r\nline:%d, %s is call\n", __FILE__, __LINE__, __FUNCTION__);
//进行参数修正:1)count如是0,函数返回0.
if(count == 0) {
return 0;
}
//进行参数修正:2) 如果文件读写位置已经到末尾,不管count是多少,都不能读,返回0;
if(cur_pos >= pdata_leds->led_num) {
return 0;
}
//进行参数修正:3) 判断 count + *poff 是否大于文件大小
if(cur_pos + count > pdata_leds->led_num) {
count = pdata_leds->led_num - cur_pos;
}
#if 0 //原来的固定数量 ,固定位置,连续的IO
//准备数据:读取4个led灯状态
for(i = 0; i < LED_NUM; i++) {
//灭:返回'0',使用正逻辑方式表,也要和write逻辑相同
if(GPM4DAT & 1 << i) {
kbuf[i] = '0' ;
}
//亮:返回'1',使用正逻辑方式表,也要和write逻辑相同
else {
kbuf[i] = '1' ;
}
}
#endif
//根据平台设备的信息初始化对应的IO口
for(i = 0; i < pdata_leds->led_num; i++) {
//灭:返回'0',使用正逻辑方式表,也要和write逻辑相同
if(GPM4DAT & 1 << pdata_leds->leds_info[i].pin_nr) {
kbuf[i] = '0' ;
}
//亮:返回'1',使用正逻辑方式表,也要和write逻辑相同
else {
kbuf[i] = '1' ;
}
}
//复制数据给用户空间
if (copy_to_user(buf, &kbuf[cur_pos], count) ) {
printk(KERN_EMERG" error: copy_to_user\r\n");
return -EFAULT;
}
//更新文件读写位置,不能少,少了得不到正确的结果
*poff += count;
return count;
}
/* write(fd,buf,2);
* buf是用户空间传递下来的指针 ,一般情况不能直接使用;
* 直接使用来获取用户空间的内容会存在安全隐患。
* 要安全的访问用户空间的内容:必须使用内核专用的复制函数(这个函数会先检测地址是否合法,再决定是否复制)
* 对驱动的write接口,就是从用户空间读取数据,内核对应有一个专用函数:copy_from_user 函数
*/
/*
1) count如是0,函数返回0.
'驱动程序不做任何操作。
2) 如果文件读写位置已经到末尾,不管count是多少,都不能写,返回0;
'把leds设备看成是只有4个字节的文件,当读写位置已经等于4时候,则不能再进行写操作。
3) 当读写位置还没有到文件最后:
d. 如count 大于于文件大小,修正大小为:文件大小 - * poff
e. 如count 小于文件大小,但是count + *poff大于文件大小,修正大小为:文件大小 - *poff
f. 如count 小于文件大小,但是count + *poff小于或等于于文件大小,则count不需要修正。
*/
static ssize_t xxx_write(struct file *pfile,
const char __user *buf, size_t count, loff_t *poff)
{
int i = 0;
char kbuf[1000] = {0}; //你可以根据 pdata_leds->led_num动态分配合适的的空间
loff_t cur_pos = *poff; //取出当前读写位置值
//进行参数修正:1)count如是0,函数返回0.
if(count == 0) {
return 0;
}
//进行参数修正:2) 如果文件读写位置已经到末尾,不管count是多少,都不能读,返回0;
if(cur_pos >= pdata_leds->led_num) {
return 0;
}
//进行参数修正:3) 判断 count + *poff 是否大于文件大小
if(cur_pos + count > pdata_leds->led_num) {
count = pdata_leds->led_num - cur_pos;
}
//使用专用的函数进行复制,得到到对应 的元素位置
if (copy_from_user(&kbuf[cur_pos], buf, count) ) {
printk(KERN_EMERG" error: copy_from_user\r\n");
return -EFAULT;
}
//根据用户空间传递下来的内容进行处理
for(i = 0; i < count; i++) {
#if 0
if(kbuf[i + cur_pos] == '0') {
//熄灭第i个灯:通过配置数据寄存器
GPM4DAT |= 1 << (i + cur_pos) ; //把第i个IO口配置为高电平,
} else if(kbuf[i + cur_pos ] == '1') {
//点亮第i个灯:通过配置数据寄存器
GPM4DAT &= ~( 1 << (i + cur_pos)); //把第i个IO口配置为低电平,
}
#endif
if(kbuf[i + cur_pos] == '0') {
//熄灭第i个灯:通过配置数据寄存器
GPM4DAT |= 1 << (pdata_leds->leds_info[cur_pos + i].pin_nr) ; //把第i个IO口配置为高电平,
} else if(kbuf[i + cur_pos ] == '1') {
//点亮第i个灯:通过配置数据寄存器
GPM4DAT &= ~( 1 << (pdata_leds->leds_info[cur_pos + i].pin_nr)); //把第i个IO口配置为低电平,
}
}
//更新文件读写位置,不能少,少了得不到正确的结果
*poff += count;
return count;
}
/*
对应的应用编程API接口说明:
off_t lseek(int fd, off_t offset, int whence);
作用:按照whence指定的方式把 fd文件的读写指针调整 offset偏移,whence有以下三种情况:
SEEK_SET:以文件开头(0)为参照点移动到offset处。
SEEK_CUR:以文件指针 [当前位置 + offset ]做为新的offset 指针位置。
SEEK_END:以文件[末尾位置 + offset] 做为新的offset 指针位置。
offset:需要调整的偏移,值是可正数,可负数
whence:调整方式,只有三种值:SEEK_SEK,SEEK_CUR,SEEK_END
返回值:
成功:移动后文件指针位置(以0为参照点);
失败:-1,并且设置全局变量 errno 值为对应的错误码(来源码驱动)
*/
static loff_t xxx_llseek(struct file *pfile, loff_t off, int whence)
{
loff_t temp;
printk(KERN_EMERG"file:%s\r\nline:%d, %s is call\n", __FILE__, __LINE__, __FUNCTION__);
switch(whence) {
case SEEK_SET:
temp = off;
break;
case SEEK_CUR:
temp = pfile->f_pos + off; //当前位置加上调整值
break;
case SEEK_END:
temp = pdata_leds->led_num + off; //文件大小加上调整值
break;
default:
return -EINVAL; //告诉应用程序具体错误原因是参数无效
}
//检测最后调整结果是否合法
if((temp < 0) || (temp > pdata_leds->led_num) ) {
return -EINVAL;
}
//更新调整后结果到文件读写位置变量中
pfile->f_pos = temp;
//返回调整后的结果
return temp;
}
static int xxx_release (struct inode *pinode, struct file *pfile)
{
printk(KERN_EMERG"file:%s\r\nline:%d, %s is call\n", __FILE__, __LINE__, __FUNCTION__);
return 0;
}
static long xxx_unlocked_ioctl (struct file *pfile,
unsigned int cmd, unsigned long args)
{
printk(KERN_EMERG"file:%s\r\nline:%d, %s is call\n", __FILE__, __LINE__, __FUNCTION__);
return 0;
}
//文件操作方法集合指针
static const struct file_operations mymisc_fops = {
.open = xxx_open,
.read = xxx_read,
.write = xxx_write,
.llseek = xxx_llseek,
.release = xxx_release,
.unlocked_ioctl = xxx_unlocked_ioctl,
};
//定义核心结构
static struct miscdevice misc = {
.minor = 255,
.name = "myleds", ///dev目录下的设备名
.fops = &mymisc_fops,
};
//探测函数
static int led_probe(struct platform_device *pdev)
{
int size;
int ret;
int i = 0;
//定义资源指针 ,在probe函数中获得
static struct resource *res;
//取得平台设备层传递下来的平台数据
pdata_leds = (struct leds_data *)pdev->dev.platform_data;
//指向了 设备层中的 &myleds_res[0]
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
printk("res->name:%s\r\n", res->name);
printk("res->start:0x%x,res->end:0x%x\r\n", res->start, res->end);
//向内核申请资源
size = res->end - res->start + 1;
//res = request_mem_region(res->start, size, "myleds");
res = devm_request_mem_region(&pdev->dev, res->start, size, "myleds");
if(!res) {
printk("1: request_mem_region error\r\n");
return -EBUSY;
}
printk("1: request_mem_region ok\r\n");
//映射资源,使用资源
base_addr = (unsigned long *) ioremap(res->start, res->end - res->start + 1);
#if 0
GPM4CON &= ~0xffff; //清0
GPM4CON |= 0x1111; //配置为输出方向
GPM4DAT |= 0xf ; //配置为输出方向
#endif
//根据平台设备的信息初始化对应的IO口
for(i = 0; i < pdata_leds->led_num; i++) {
GPM4CON &= ~ (0xf << pdata_leds->leds_info[i].pin_nr); //清0
GPM4CON |= (0x1 << pdata_leds->leds_info[i].pin_nr); //设置为1
GPM4DAT |= (0x1 << pdata_leds->leds_info[i].pin_nr); //默认输出高电平
}
//注册核心结构
ret = misc_register(&misc);
if(ret < 0) {
printk(KERN_EMERG"misc_register error\n");
goto err_misc_register;
}
printk(KERN_EMERG"misc_register ok\n");
return 0;
err_misc_register:
//release_mem_region(res->start, size) ;
//如果使用devm_request_mem_region则使用以下函数进行释放资源
devm_release_mem_region(&pdev->dev, res->start, size) ;
return ret;
}
//在删除设备时候会调用
static int led_remove(struct platform_device *pdev)
{
struct resource *res;
int size;
int ret;
iounmap(base_addr); //释放映射后的虚拟地址空间
//注销核心结构
ret = misc_deregister(&misc);
if(ret < 0) {
printk(KERN_EMERG"misc_deregister error\n");
return ret;
}
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
printk(KERN_EMERG"%s\r\n%d,%s is call\r\n", __FILE__, __LINE__, __FUNCTION__); //调度信息
//如果使用request_mem_region则使用以下函数进行释放资源
size = res->end - res->start + 1;
//release_mem_region(res->start, size) ;
//如果使用devm_request_mem_region则使用以下函数进行释放资源
devm_release_mem_region(&pdev->dev, res->start, size) ;
return 0;
}
//平台设备驱动结构
struct platform_driver led_driver = {
.probe = led_probe,
.remove = led_remove,
.driver = {
.owner = THIS_MODULE,
.name = "myleds",
},
};
//驱动初始化函数
static int __init leddrv_init(void)
{
printk(KERN_EMERG"%s is call\r\n", __FUNCTION__);
platform_driver_register(&led_driver);
return 0;
}
//驱动卸载函数
static void __exit leddrv_exit(void)
{
printk(KERN_EMERG"%s is call\r\n", __FUNCTION__);
platform_driver_unregister(&led_driver);
}
module_init(leddrv_init);
module_exit(leddrv_exit);
MODULE_LICENSE("GPL");
app
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h> //lseek
#include <sys/ioctl.h> //ioctl
int fd; //存放文件描述符号
char save_buf[10] = {0}; //存放数据使用
int main(void)
{
int ret;
int i;
//传递‘1’,‘0’表示对灯控制命令,‘1’表示灯亮, ‘0’表示灯灭
char t_buf[5] = {'1', '0', '1', '0', 0}; //要的灯状态是 亮灭亮灭
fd = open("/dev/myleds", O_RDWR); //以读写方式进行打开
if(fd < 0) {
printf("open error\r\n");
return -1;
}
while(1)
{
for(i = 0; i < 4; i++) {
memset(t_buf,'0',4);
t_buf[i] = '1';
lseek(fd, 0, SEEK_SET); //把光标移动到文件的0位置
write(fd, t_buf, 4);
sleep(1);
}
}
//如果接下来再写操作控制灯,也必须先使用 lseek 移动文件读写位置,否则得不到正确结果。
//关闭文件
close(fd);
return 0;
}
测试2
基于3288,只有一个灯,且计算GPIO是新的方式,所以先这样写,当然也可以参考kernel中类似的写
Tpleddev.c
#include <linux/module.h>
#include <linux/version.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/types.h>
#ifdef CONFIG_OF
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/of_platform.h>
#endif
#include <linux/device.h>
static void led_release(struct device * dev)
{
}
struct platform_device led_device =
{
.name = "myled",
.id = -1,
.dev = {
.release = led_release,
},
};
int leddev_init(void)
{
platform_device_register(&led_device);
return 0;
}
void leddev_exit(void)
{
platform_device_unregister(&led_device);
}
module_init(leddev_init);
module_exit(leddev_exit);
MODULE_LICENSE("GPL");
Tpleddrv.c
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/sysctl.h>
#include <linux/proc_fs.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/gpio.h>
#ifdef CONFIG_OF
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/of_platform.h>
#endif
#include <linux/fb.h>
#include <linux/device.h>
#include <linux/miscdevice.h>
static struct class *led_cls;
int led1_gpio = (32*4 + 8*3 + 3);//LED1 BLUE 0有效
int led_gpio_active = 0;
static int led_open(struct inode *inode, struct file *file)
{
printk("carroll led_open ok \n");
return 0;
}
static int led_release(struct inode *inode, struct file *file)
{
printk("carroll led_close \n");
return 0;
}
/* app : ioctl(fd, cmd, arg) */
static long led_ioctl(struct file *filp, unsigned int cmd,
unsigned long arg)
{
/* 根据传入的参数设置GPIO */
/* cmd : 0-1, which led */
/* arg : 0-off, 1-on*/
if(cmd == 0)
{
//if(led1_gpio != INVALID_GPIO)
gpio_set_value(led1_gpio, arg);
printk("cmd == 0 carroll led_ioctl: %d \n", arg);
}
else if(cmd == 1)
{
//if(led2_gpio != INVALID_GPIO)
//gpio_set_value(led2_gpio, !cmd);
printk("cmd == 1 carroll led_ioctl: %d \n", arg);
}
printk("carroll led_ioctl: cmd:%d arg:%d\n", cmd, arg);
return 0;
}
/* File operations struct for character device */
static struct file_operations led_fops = {
.owner = THIS_MODULE,
.open = led_open,
.release = led_release,
.unlocked_ioctl = led_ioctl,
};
static struct miscdevice led_dev =
{
.minor = MISC_DYNAMIC_MINOR,
//.name = "test_led",
.name = "myled",
.fops = &led_fops,
};
static int led_probe(struct platform_device *pdev)
{
int ret = -1;
enum of_gpio_flags flags;
struct device_node *hello_node = pdev->dev.of_node;
printk("%s-%d: carroll \n",__FUNCTION__,__LINE__);
/* register test_led dev file */
ret = misc_register(&led_dev);
if(ret < 0){
printk("misc register error\n");
goto err0;
}
/* led1 init */
ret = gpio_request(led1_gpio, "test_led");
if (ret != 0) {
gpio_free(led1_gpio);
printk("carroll: led1_gpio free\n");
return -EIO;
}
ret = gpio_request(led1_gpio,"led1_gpio"); //申请led1_gpio引脚为GPIO模式
if(ret < 0){
printk("gpio_request led1_gpio error\n");
goto err1;
}
ret = gpio_direction_output(led1_gpio,1); //初始划LED1为熄灭状态
if(ret < 0){
printk("gpio direction output led1_gpio error\n");
//goto err3;
goto err2;
}
err2:
gpio_free(led1_gpio);
err1:
misc_deregister(&led_dev);
err0:
return ret;
}
static int led_remove(struct platform_device *pdev)
{
misc_deregister(&led_dev);
return 0;
}
static struct platform_driver led_driver = {
.probe = led_probe,
.remove = led_remove,
.driver = {
.name = "myled",
.owner = THIS_MODULE,
}
};
/* 分配/设置/注册一个platform_driver */
static int led_drv_init(void)
{
return platform_driver_register(&led_driver);
}
static void led_drv_exit(void)
{
platform_driver_unregister(&led_driver);
}
module_init(led_drv_init);
module_exit(led_drv_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("LWJ");
MODULE_DESCRIPTION("Just for Demo");
Tled.c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
int main(int argc, char **argv)
{
int fd;
int val = 1;
fd = open("/dev/myled", O_RDWR);
if (fd < 0)
{
printf("can't open!\n");
}
if (argc != 3)
{
printf("Usage :\n");
printf("%s ledx <on|off>\n", argv[0]);
return 0;
}
if ((strcmp(argv[1], "led0") == 0) && (strcmp(argv[2], "0") == 0))
{
ioctl(fd, 0, 0);
}
else if ((strcmp(argv[1], "led0") == 0) && (strcmp(argv[2], "1") == 0))
{
ioctl(fd, 0, 1);
}
else
{
printf("no led\n");
}
return 0;
}
Makefile
#export PATH=/usr/local/arm/arm-2009q3/bin:$PATH
obj-m := Tpleddrv.o Tpleddev.o
KDIR := /home/rpdzkj/rk3288_5.1/kernel
all:
make -C $(KDIR) M=$(PWD) modules
arm-none-linux-gnueabi-gcc Tled.c -o Tled -static
clean:
rm -f *.ko *.o *.mod.o *.mod.c *.symvers *.bak *.order
copy:
cp *.ko /mnt/hgfs/MAndroid5.1_source/3288_test/platform/ -rf