Linux设备驱动模型

article/2025/11/10 1:17:43

目录

  • 一、linux设备驱动模型简介
    • 1、什么是设备驱动模型
    • 2、为什么需要设备驱动模型
    • 3、驱动开发的2个点
  • 二、设备驱动模型的底层架构
    • 1、kobject
    • 2、kobj_type
    • 3、kset
  • 三、总线式设备驱动组织方式
    • 1、总线
    • 2、设备
    • 3、驱动
    • 4、类
    • 5、总结
  • 四、platform平台总线工作原理
    • 1、何为平台总线
    • 2、平台总线下管理的2员大将
    • 3、平台总线体系的工作流程
    • 4、代码分析:platform本身注册
    • 5、以leds-s3c24xx.c为例来分析platform设备和驱动的注册过程
    • 6、platdata怎么玩
    • 7、match函数的调用轨迹
    • 8、probe函数的功能和意义
  • 五、平台总线实践环节

一、linux设备驱动模型简介

1、什么是设备驱动模型

  是这是一个比较抽象的概念,并无明确的定义,它是linux内核为了管理硬件上的设备和对应的驱动制定的一套软件体系

(1)类class、总线bus、设备device、驱动driver(对应4个结构体来描述)

(2)kobject(结构体,类似于一个基类)和对象生命周期 (kobject实现了一种自我管理的机制)

(3)sysfs(使内核空间 和 用户空间建立了一种映射关系)

(4)udev(实现内核空间 和 用户空间信息的同步)
以上(1)、(2)、(3)、(4)等都属于设备驱动模型的范畴。

2、为什么需要设备驱动模型

(1)早期内核(2.4之前)没有统一的设备驱动模型,但照样可以用

(2)2.6版本中正式引入设备驱动模型,目的是在设备越来越多,功耗要求等新特性要求的情况下让驱动体系更易用、更优秀。

(3)设备驱动模型负责统一实现和维护一些特性,诸如:电源管理、热插拔、对象生命周期、用户空间和驱动空间的交互等基础设施

(4)设备驱动模型目的是简化驱动程序编写,但是客观上设备驱动模型本身设计和实现很复杂。

3、驱动开发的2个点

(1)驱动源码本身编写、调试。重点在于对硬件的了解。

(2)驱动什么时候被安装、驱动中的函数什么时候被调用。跟硬件无关,完全和设备驱动模型有关。

二、设备驱动模型的底层架构

https://www.cnblogs.com/deng-tao/p/6033932.html
https://blog.csdn.net/yong199105140/article/details/8240875

1、kobject

(1)定义在linux/kobject.h中

struct kobject {
const char *name;/*名称*/
struct list_head entry;/*用于链入所属的kset的链表,将不同的对象使用双向链表连接起来*/
struct kobject *parent;/*父object*/
struct kset *kset;/*内部提供了对象上锁的功能*/
struct kobj_type *ktype;/*对用户空间的表示*/
struct sysfs_dirent *sd;/*sysfs中的目录项*/
struct kref kref;/*生命周期(引用计数)管理,*/
unsigned int state_initialized:1;/*标记:初始化*/
unsigned int state_in_sysfs:1;/*标记:在sysfs中*/
unsigned int state_add_uevent_sent:1;/*标记:已发出KOBJ_ADD uevent*/
unsigned int state_remove_uevent_sent:1;/*标记:已发出的KOBJ_REMOVE uevent*/
unsigned int uevent_suppress:1;/*标记:禁止发出uevent*/
};

  Kobject是Linux 2.6引入的新的设备管理机制,在内核中由struct kobject表示。通过这个数据结构使所有设备在底层都具有统一的接口,kobject提供基本的对象管理,它与sysfs文件系统紧密关联,每个在内核中注册的kobject对象都对应于sysfs文件系统中的一个目录。Kobject是组成设备模型的基本结构。

  上层结构例如device,device_driver,bus_type都嵌入了一个kobject,这相当于面向对象程序设计机制中的继承。

  当kobject被嵌入到其他结构体中时,该结构体便拥有了kobject提供的标准功能。更重要的一点是,嵌入kobject结构体可以成为对象层次架构中的一部分。比如cdev结构体就可以通过其父进程指针cdev->kobj->parent和链表 cdev->kobj->entry来插入到对象层次结构中。

(2)各种对象最基本单元,提供一些公用型服务如:对象引用计数、维护对象链表、对象上锁、对用户空间的表示(sys)

(3)设备驱动模型中的各种对象其内部都会包含一个kobject,从而可以使用kobject的功能和服务。

(4)地位相当于面向对象体系架构中的总基类

2、kobj_type

struct kobj_type {
void (*release)(struct kobject *kobj);
const struct sysfs_ops *sysfs_ops;
struct attribute **default_attrs;
const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj);
const void *(*namespace)(struct kobject *kobj);
};release:release成员是一个指向对应于该类kobject的release方法。release指针指向在kobject引用计数减至0时要被调用的析构函数。该函数负责释放所有kobject使用的内存和其它相关清理工作,注意必须得提供release函数。每个kobject必须有一个release函数,并且这个kobject必须在release函数被调用前保持不
变(稳定状态 )。这样,每一个 kobject需要有一个关联的 kobj_type结构,指向这个结构的指针能在 2个不同的地方找到:kobject结构自身包含一个成员(ktype)指向kobj_type;如果这个 kobject 是一个 kset的成员, kset会提供kobj_type指针。sysfs_ops和default_attrs控制如何在sysfs中表示这个类型的对象:
sysfs_ops变量指向sysfs_ops结构体。该结构体描述了sysfs文件读写时的特性default_attrs指向一个attribute结构体数组。default_attrs指针是默认属性的列表,
当创建注册这个ktype的kobject的时候,这些默认属性会被自动的添加。这些结构体定义了该kobject相关的默认属性。属性给定了对象的特征,如果该kobject被导
出到sysfs中,那么这些属性都将相应的作为文件而导出。

(1)很多书中简称为ktype,每一个kobject都需要绑定(通过指针)一个ktype来提供相应功能

(2)关键点1:sysfs_ops,提供该对象在sysfs中的操作方法(show和store)

(3)关键点2:attribute,提供在sysfs中以文件形式存在的属性,其实就是应用接口

3、kset

struct kset {struct list_head list;spinlock_t list_lock;//自旋锁,有其表示可以上锁struct kobject kobj;const struct kset_uevent_ops *uevent_ops;
};kset是kobject对象的集合体。把它看成一个容器,可将所有相关的kobject对象,比如“
全部的块设备”置于同一位置。kset可把kobject集中到一个集合中,而ktype描述相关类型kobject所共有的特性,它
们之间的重要区别在于:具有相同ktype的kobject可以分到不同的kset中(同一个kset中的
kobject是否具有相同的ktype呢)。kobject的kset指针指向相应的kset集合。

(1)kset的主要作用是做顶层kobject的容器类(装kobject)

(2)kset的主要目的是将各个kobject(代表着各个对象)组织出目录层次架构

(3)可以认为kset就是为了在sysfs中弄出目录,从而让设备驱动模型中的多个对象能够有层次有逻辑性的组织在一起

(4)引用计数:kobject的主要功能之一就是为我们提供了一个统一的引用计数系统。初始化后,kobject的引用计数设置为1。只要引用计数不为0(表示有多个对象在引用该对象),那么该对象就会继续保留在内存中,也可以说是被“钉”住了,任何包含对象引用的代码首先要增加该对象的引用计数,当代码结束后则减少它的引用计数。当引用计数减为0时(此时相当于close),对象便可以被销毁,同时相关内存也都被释放。

增加一个引用计数可通过kobject_get()函数完成:
struct kobject * kobject_get(struct kobject *kobj);
该函数正常情况下返回一个指向kobject的指针,如果失败,则返回NULL指针。减少引用计数通过kobject_put()完成:
void kobject_put(struct kobject *kobj);

(5)Kobject的添加:仅仅初始化一个kobject是不能自动将其导出到sysfs中的,想要把kobject导入sysfs,需要用到函数kobject_add():

int kobject_add(struct kobject *kobj, struct kobject *parent, const char *fmt, ...);

  kobject在sysfs中的位置取决于kobject在对象层次结构中的位置。如果kobject的父指针被设置,那么在sysfs中kobject将被映射为其父目录下的子目录,如果parent没有设置,那么kobject将被映射到kset->kobj中的子目录。两者都未设置,映射为 sysfs下的根级目录。
  如果kobject被关联到一个特殊的kset,在调用kobject_add()之前kobj->kset必须被赋值。
  如果一个kset被关联到一个kobject,那么在kobject_add()调用中该kobject的父节点可被设置为NULL(就是kobject_add()的第二个参数设置为NULL),并且,该kobject的父节点就是那个kset本身。

三、总线式设备驱动组织方式

1、总线

(1)物理上的真实总线及其作用(英文bus)
物理上:可指用于通电供电的总线,也可指计算机上的物理总线,例如USB总线分成多个USB接口

(2)驱动框架中的总线式设计:驱动管理方法

  总线分为usb/pci/iic等多种总线,其中的某个总线又可划分为设备和驱动两部分,当添加一个USB设备时,会将其划分给USB总线,寻找相应的驱动。

(3)bus_type结构体,关键是match函数和uevent函数

struct bus_type {const char		*name;struct bus_attribute	*bus_attrs;//bus本身所具有的一些属性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 bus_type_private *p;
};

2、设备

(1)struct device是硬件设备在内核驱动框架中的抽象

(2)device_register用于向内核驱动框架注册一个设备

int __must_check device_register(struct device *dev);

(3)通常device不会单独使用,而是被包含在一个具体设备结构体中,如struct usb_device

struct device {struct device		*parent;struct device_private	*p;struct kobject kobj;const char		*init_name; /* initial name of the device */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 thisdevice */void		*platform_data;	/* Platform specific data, devicecore doesn't touch it */struct dev_pm_info	power;#ifdef CONFIG_NUMAint		numa_node;	/* NUMA node this device is close to */
#endifu64		*dma_mask;	/* dma mask (if dma'able device) */u64		coherent_dma_mask;/* Like dma_mask, but foralloc_coherent mappings asnot all hardware supports64 bit addresses for consistentallocations such descriptors. */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 memoverride *//* arch specific additions */struct dev_archdata	archdata;
#ifdef CONFIG_OFstruct device_node	*of_node;
#endifdev_t			devt;	/* dev_t, creates the sysfs "dev" */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);
};

3、驱动

(1)struct device_driver是驱动程序在内核驱动框架中的抽象

(2)关键元素1:name,驱动程序的名字,很重要,经常被用来作为驱动和设备的匹配依据

(3)关键元素2:probe,驱动程序的探测函数,用来检测一个设备是否可以被该驱动所管理(使用总线框架写驱动才会使用)

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 */#if defined(CONFIG_OF)const struct of_device_id	*of_match_table;
#endifint (*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;
};

4、类

(1)相关结构体:struct class 和 struct class_device

struct class {const char		*name;struct module		*owner;struct class_attribute		*class_attrs;struct device_attribute		*dev_attrs;struct kobject			*dev_kobj;int (*dev_uevent)(struct device *dev, struct kobj_uevent_env *env);char *(*devnode)(struct device *dev, mode_t *mode);void (*class_release)(struct class *class);void (*dev_release)(struct device *dev);int (*suspend)(struct device *dev, pm_message_t state);int (*resume)(struct device *dev);const struct kobj_ns_type_operations *ns_type;const void *(*namespace)(struct device *dev);const struct dev_pm_ops *pm;struct class_private *p;
};

(2)udev的使用离不开class

(3)class的真正意义在于作为同属于一个class的多个设备的容器。也就是说,class是一种人造概念,目的就是为了对各种设备进行分类管理。当然,class在分类的同时还对每个类贴上了一些“标签”,这也是设备驱动模型为我们写驱动提供的基础设施。

  从分类、总线这两个不同的角度都可以找到设备,而且是同一个,其中利用了符号链接,/sys/目录下有很多的符号链接

5、总结

(1)模型思想很重要,其实就是面向对象的思想

(2)全是结构体套结构体,对基本功(语言功底和大脑复杂度)要求很高

四、platform平台总线工作原理

1、何为平台总线

(1)相对于usb、pci、i2c等物理总线来说,platform总线是虚拟的、抽象出来的。

(2)CPU与外部通信的2种方式:地址总线式连接(如DM9000网卡连接方式)和专用接口式(USB、IIC等)连接。平台总线对应地址总线式连接设备,也就是SoC内部集成的各种内部外设。

  CPU的一部分外设内部直连,而且工作方式扩展到CPU的地址空间中(210为4G, 0——2^32-1,寄存器扩展到地址总线空间,每个寄存器都有一个物理地址,而nandflash等与CPU连接通过专用的接口),例如定时器等本身不对应总线,为统一标准,规定所有的设备都有总线,故而原来不是总线的设备归到平台总线(虚拟出来的总线)。

(3)思考:为什么要有平台总线?进一步思考:为什么要有总线的概念?

2、平台总线下管理的2员大将

(1)platform工作体系都定义在drivers/base/platform.c中

(2)两个结构体:platform_device和platform_driver

(3)两个接口函数:platform_device_register和platform_driver_register

struct platform_device {const char	* name;			// 平台总线下设备的名字int		id;//区分多个设备的id例如led1、led2之类的struct device	dev;		// 所有设备通用的属性部分u32		num_resources;		// 设备使用到的resource的个数,资源可以是地址范围,中断号等等struct resource	* resource;	// 设备使用到的资源数组的首地址const struct platform_device_id	*id_entry;	// 设备ID表 ,同一系列不同设备的id/* arch specific additions */struct pdev_archdata	archdata;			// 自留地,用来提供扩展性的
};
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;	// 设备ID表
};

3、平台总线体系的工作流程

(1)第一步:系统启动时在bus系统中注册platform

(2)第二步:内核移植的人负责提供platform_device

(3)第三步:写驱动的人负责提供platform_driver

(4)第四步:platform的match函数发现driver和device匹配后,调用driver的probe函数来完成驱动的初始化和安装,然后设备就工作起来了

4、代码分析:platform本身注册

(1)每种总线(不光是platform,usb、i2c那些也是)都会带一个match方法,match方法用来对总线下的device和driver进行匹配。理论上每种总线的匹配算法是不同的,但是实际上一般都是看name的。

(2)platform_match函数就是平台总线的匹配方法。该函数的工作方法是:如果有id_table就说明驱动可能支持多个设备,所以这时候要去对比id_table中所有的name,只要找到一个相同的就匹配上了不再找了,如果找完id_table都还没找到就说明没有匹配上;如果没有id_table或者没有匹配上,那就直接对比device和driver的name,如果匹配上就匹配上了,如果还没匹配上那就匹配失败。

  有两个重要的链表挂在bus上,一个是设备device链表,一个是驱动driver链表。

static int platform_match(struct device *dev, struct device_driver *drv)
{struct platform_device *pdev = to_platform_device(dev);struct platform_driver *pdrv = to_platform_driver(drv);/* match against the id table first */if (pdrv->id_table)return platform_match_id(pdrv->id_table, pdev) != NULL;/* fall-back to driver name match */return (strcmp(pdev->name, drv->name) == 0);
}

5、以leds-s3c24xx.c为例来分析platform设备和驱动的注册过程

(1)platform_driver_register

static struct platform_driver s3c24xx_led_driver = {.probe		= s3c24xx_led_probe,//检测驱动是否可用,类似安装,初始化驱动.remove		= s3c24xx_led_remove,//用来卸载设备.driver		= {//所有设备共有的一些属性.name		= "s3c24xx_led",.owner		= THIS_MODULE,},
};//可以尝试搜索s3c24xx_led找到它对应的device结构体
static int __init s3c24xx_led_init(void)
{return platform_driver_register(&s3c24xx_led_driver);//注册driver
}
static void __exit s3c24xx_led_exit(void)
{platform_driver_unregister(&s3c24xx_led_driver);
}

(2)platform_device_register

/*** platform_add_devices - add a numbers of platform devices* @devs: array of platform devices to add* @num: number of platform devices in array*/
int platform_add_devices(struct platform_device **devs, int num)
{int i, ret = 0;for (i = 0; i < num; i++) {ret = platform_device_register(devs[i]);if (ret) {while (--i >= 0)//只要出错会将之前注册的注销掉platform_device_unregister(devs[i]);break;}}return ret;
}int platform_device_register(struct platform_device *pdev)
{device_initialize(&pdev->dev);return platform_device_add(pdev);
}int platform_device_add(struct platform_device *pdev)
{int i, ret = 0;if (!pdev)return -EINVAL;if (!pdev->dev.parent)pdev->dev.parent = &platform_bus;pdev->dev.bus = &platform_bus_type;if (pdev->id != -1)dev_set_name(&pdev->dev, "%s.%d", pdev->name,  pdev->id);elsedev_set_name(&pdev->dev, "%s", pdev->name);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)) {printk(KERN_ERR"%s: failed to claim resource %d\n",dev_name(&pdev->dev), 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);if (ret == 0)return ret;failed: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);}return ret;
}
EXPORT_SYMBOL_GPL(platform_device_add);

6、platdata怎么玩

struct platform_device {const char	* name;int		id;struct device	dev;u32		num_resources;struct resource	* resource;const struct platform_device_id	*id_entry;/* arch specific additions */struct pdev_archdata	archdata;
};
struct device {struct device		*parent;struct device_private	*p;struct kobject kobj;const char		*init_name; /* initial name of the device */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 thisdevice */void		*platform_data;	/* Platform specific data, devicecore doesn't touch it */struct dev_pm_info	power;#ifdef CONFIG_NUMAint		numa_node;	/* NUMA node this device is close to */
#endifu64		*dma_mask;	/* dma mask (if dma'able device) */u64		coherent_dma_mask;/* Like dma_mask, but foralloc_coherent mappings asnot all hardware supports64 bit addresses for consistentallocations such descriptors. */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 memoverride *//* arch specific additions */struct dev_archdata	archdata;
#ifdef CONFIG_OFstruct device_node	*of_node;
#endifdev_t			devt;	/* dev_t, creates the sysfs "dev" */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 s3c24xx_led_platdata {unsigned int		 gpio;//使用的GPIO对应的编号unsigned int		 flags;//属性char			*name;//名称char			*def_trigger;/与某个硬件的操作相绑定,例如读取硬盘时led闪烁
};
static int s3c24xx_led_probe(struct platform_device *dev)
{struct s3c24xx_led_platdata *pdata = dev->dev.platform_data;...
}

(1)platdata其实就是设备注册时提供的设备有关的一些数据(譬如设备对应的gpio、使用到的中断号、设备名称····)

(2)这些数据在设备和驱动match之后,会由设备方转给驱动方。驱动拿到这些数据后,通过这些数据得知设备的具体信息,然后来操作设备。

(3)这样做的好处是:驱动源码中不携带数据,只负责算法(对硬件的操作方法)。现代驱动设计理念就是算法和数据分离,这样最大程度保持驱动的独立性和适应性。

7、match函数的调用轨迹

struct bus_type {const char		*name;struct bus_attribute	*bus_attrs;struct device_attribute	*dev_attrs;struct driver_attribute	*drv_attrs;int (*match)(struct device *dev, struct device_driver *drv);int (*uevent)(struct device *dev, struct kobj_uevent_env *env);int (*probe)(struct device *dev);int (*remove)(struct device *dev);void (*shutdown)(struct device *dev);int (*suspend)(struct device *dev, pm_message_t state);int (*resume)(struct device *dev);const struct dev_pm_ops *pm;struct bus_type_private *p;
};
struct bus_type platform_bus_type = {.name		= "platform",.dev_attrs	= platform_dev_attrs,.match		= platform_match,.uevent		= platform_uevent,.pm		= &platform_dev_pm_ops,
};
EXPORT_SYMBOL_GPL(platform_bus_type);
#define to_platform_device(x) container_of((x), struct platform_device, dev)
#define to_platform_driver(drv)	(container_of((drv), struct platform_driver,driver))static int platform_match(struct device *dev, struct device_driver *drv)
{struct platform_device *pdev = to_platform_device(dev);struct platform_driver *pdrv = to_platform_driver(drv);/* match against the id table first */if (pdrv->id_table)return platform_match_id(pdrv->id_table, pdev) != NULL;/* fall-back to driver name match */return (strcmp(pdev->name, drv->name) == 0);
}

在这里插入图片描述

8、probe函数的功能和意义

  probe函数在设备驱动注册进行最后收尾工作,当设备的device 和其对应的driver 在总线上完成配对之后,系统就调用platform设备的probe函数完成驱动注册最后工作。资源、中断调用函数以及其他相关工作。

每当我们向一根bus注册一个驱动driver时,套路是这样的:driver_register(struct device_driver * drv) -> bus_add_driver() ->
driver_attach() ->bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);bus_for_each_dev遍历该总线上所有的device,执行一次__driver_attach(),看能不能将驱动关联(attach)到某个设备上去。__driver_attach()->driver_probe_device()->drv->bus->match(dev, drv), // 调用bus的match函数,看device和driver匹不匹配。如果匹配上,继续执行really_probe()->really_probe()->driver->probe()(如果bus->probe非空,则调用bus->probe)

summary:对于上述所讲的知识如果想深入理解,建议自己去分析一个平台总线驱动的程序,理理思路和代码的流程。

五、平台总线实践环节

1、检查mach-x210.c中是否有led 相关的 platform_device(1726行上下附近都是)
  注意:platform_device中的name要与platform_driver驱动中相同,否则无法匹配。可能三个led都会匹配上同一个驱动,使得probe函数被执行三次。这就使得每次申请使用后要及时释放,否则第二个设备就无法使用platform_driver了,会出现错误。

添加:

static struct s5pv210_led_platdata x210_led0_pdata = {.name		= "led0",.gpio		= S5PV210_GPJ0(3),.flags		= S5PV210_LEDF_ACTLOW | S5PV210_LEDF_TRISTATE,.def_trigger	= "",
};static struct platform_device x210_led0 = {.name		= "s5pv210_led",.id		= 0,.dev		= {.platform_data	= &x210_led0_pdata,},
};static struct s5pv210_led_platdata x210_led1_pdata = {.name		= "led1",.gpio		= S5PV210_GPJ0(4),.flags		= S5PV210_LEDF_ACTLOW | S5PV210_LEDF_TRISTATE,.def_trigger	= "",
};static struct platform_device x210_led1 = {.name		= "s5pv210_led",.id		= 1,.dev		= {.platform_data	= &x210_led1_pdata,},
};

2、参考mach-mini2440.c中添加led的platform_device定义在mach-x210.c文件中

  所以在kernel\arch\arm\mach-s5pv210\include\mach目录下添加leds-gpio.h文件,否则没有类似struct s3c24xx_led_platdata这样的数据结构的定义

/* arch/arm/mach-s5cpv210/include/mach/leds-gpio.h** Copyright (c) 2006 Simtec Electronics*	http://armlinux.simtec.co.uk/*	Ben Dooks <ben@simtec.co.uk>** S5PV210 - LEDs GPIO connector** This program is free software; you can redistribute it and/or modify* it under the terms of the GNU General Public License version 2 as* published by the Free Software Foundation.
*/#ifndef __ASM_ARCH_LEDSGPIO_H
#define __ASM_ARCH_LEDSGPIO_H "leds-gpio.h"#define S5PV210_LEDF_ACTLOW	(1<<0)		/* LED is on when GPIO low */
#define S5PV210_LEDF_TRISTATE	(1<<1)		/* tristate to turn off */struct s5pv210_led_platdata {unsigned int		 gpio;unsigned int		 flags;char			*name;char			*def_trigger;
};#endif /* __ASM_ARCH_LEDSGPIO_H */

   将修改好的文件同步到ubuntu的内核源码目录下,重新编译内核进行烧录

3、驱动程序:led-s5pv210.c

#include <linux/module.h>		// module_init  module_exit
#include <linux/init.h>			// __init   __exit
#include <linux/fs.h>
#include <linux/leds.h>
#include <mach/regs-gpio.h>
#include <mach/gpio-bank.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <mach/gpio.h>
#include <linux/platform_device.h>
#include <mach/leds-gpio.h>
#include <linux/slab.h>#define GPIO_LED1 S5PV210_GPJ0(3)#define x210_LED_OFF 1      //210开发板中led正极接电源,负极接GPIO
#define x210_LED_ON  0      //所以1是灭,0是亮struct led_classdev led_cdev;//定义结构体变量,用于描述led设备类的一个设备struct s5pv210_gpio_led {struct led_classdev		 cdev;struct s5pv210_led_platdata	*pdata;};static inline struct s5pv210_gpio_led *pdev_to_gpio(struct platform_device *dev)
{return platform_get_drvdata(dev);
}static inline struct s5pv210_gpio_led *to_gpio(struct led_classdev *led_cdev)
{return container_of(led_cdev, struct s5pv210_gpio_led, cdev);
}static void s5pv210_led_set(struct led_classdev *led_cdev,enum led_brightness value)
{struct s5pv210_gpio_led *p = to_gpio(led_cdev);printk(KERN_INFO "s5pv210_led_set\n");//在这里根据用户设定的值(value)操作硬件if (value == LED_OFF){gpio_set_value(p->pdata->gpio, x210_LED_OFF);}else{gpio_set_value(p->pdata->gpio, x210_LED_ON);}}static int s5pv210_led_probe(struct platform_device *dev)
{int ret = -1;struct s5pv210_led_platdata *pdata = dev->dev.platform_data;struct s5pv210_gpio_led *led;printk(KERN_INFO "-----s5pv210_led_probe------\n");led = kzalloc(sizeof(struct s5pv210_gpio_led), GFP_KERNEL);if (led == NULL) {dev_err(&dev->dev, "No memory for device\n");return -ENOMEM;}platform_set_drvdata(dev, led);//在这里去申请驱动用到的各种资源,当前驱动中就是GPIO资源if (gpio_request(pdata->gpio, pdata->name)){printk(KERN_INFO "pdata.gpio_request failed.\n");}else{//设置为输出模式,并且默认输出1让led灯灭gpio_direction_output(pdata->gpio, 1);}//填充描述led类设备的属性的结构体变量led->cdev.name = pdata->name;led->cdev.brightness = 0;led->cdev.brightness_set = s5pv210_led_set;led->pdata = pdata;ret = led_classdev_register(&dev->dev, &led->cdev);//来源于led-class.c,创建属于led类的//一个设备if (ret < 0) {								printk(KERN_INFO "led_classdev_register failed\n");return ret;}return 0;}static int s5pv210_led_remove(struct platform_device *dev)
{struct s5pv210_gpio_led *led = pdev_to_gpio(dev);printk(KERN_INFO "-----s5pv210_led_remove------\n");led_classdev_unregister(&led->cdev);kfree(led);gpio_free(led->pdata->gpio);return 0;	}static struct platform_driver s5pv210_led_driver = {.probe		= s5pv210_led_probe,.remove		= s5pv210_led_remove,.driver		= {.name		= "s5pv210_led",.owner		= THIS_MODULE,},
};static int __init s5pv210_led_init(void)
{return platform_driver_register(&s5pv210_led_driver);
}static void __exit s5pv210_led_exit(void)
{platform_driver_unregister(&s5pv210_led_driver);
}module_init(s5pv210_led_init);
module_exit(s5pv210_led_exit);// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL");							// 描述模块的许可证
MODULE_AUTHOR("aston <1264671872@qq.com>");		// 描述模块的作者
MODULE_DESCRIPTION("s5pv210 led driver");		// 描述模块的介绍信息
MODULE_ALIAS("s5pv210_led");					// 描述模块的别名信息

注:本资料大部分由朱老师物联网大讲堂课程笔记整理而来并且引用了部分他人博客的内容,如有侵权,联系删除!水平有限,如有错误,欢迎各位在评论区交流。


http://chatgpt.dhexx.cn/article/Q8lNXK8v.shtml

相关文章

【linux内核分析与应用-陈莉君】字符设备驱动

目录 1.什么是字符设备 2.如何来描述字符设备 3 struct cdev与const struct file_operations之间的关系 4.struct file_operations源码 5.字符设备驱动框架 6.编写字符设备驱动的步骤 7.字符设备结构 8.字符设备驱动程序的注册 9.从系统调用到驱动程序 10.用户空间与内…

微信小程序云数据库使用讲解

第一步&#xff1a;注册开通 单击云开发 第二步&#xff1a;创建数据库 选择数据库&#xff0c;并点击号创建一个集合 输入名字 创建完毕后点击添加记录即可添加数据 数据库获取&#xff1a; 查询&#xff1a; 查询指令&#xff1a;

微信小程序云开发入门-数据库插入数据(包含批量)

一、前言 文章将介绍如何在微信小程序云开发中向云开发数据库插入数据&#xff08;单条或批量&#xff09;。 写法有好几种&#xff0c;文章将会一一进行对比&#xff0c;看看每种写法之间有何优缺点&#xff0c;如何让代码看起来更优雅。 为了更加贴合实际的开发逻辑&#xf…

【微信小程序】如何获取微信小程序云开发数据库的数据并渲染到页面?

前言 上一篇博客我把微信小程序云开发数据库操作&#xff08;增删改查&#xff09;的实现方法都已经分享出来啦&#xff0c;可以戳链接进去阅读哦 【微信小程序】小程序云开发实现数据库增删改查(小白速度Get起来&#xff01;&#xff01;一步步教你如何实现) 基于微信小程序…

开发一个可以查询并显示数据库内容的微信小程序

使用微信开发者工具可以创建云数据库&#xff0c;并通过代码可以查询并在客户端显示数据库的内容。 附&#xff1a;小程序一个功能页面有wxml(客户端呈现)&#xff0c;js(功能函数)&#xff0c;json&#xff0c;wxss(个性化处理)&#xff0c;这些是局部的文件。还有全局的文件如…

微信小程序开发---连接云开发数据库,实现数据获取

之前几篇博客里面都详细交代了如何配置云函数&#xff0c;现在就讲一下关于云函数中数据库的使用&#xff0c;主要是讲如何从云开发平台的数据库中调取数据 我们直接来到需要调用数据库数据的页面的js文件&#xff0c;直接设置全局变量&#xff0c;来便于后续调用数据库 const …

微信小程序查询数据库

微信小程序云开发的官方例子&#xff1a; const db wx.cloud.database() //获取数据库的引用 const _ db.command //获取数据库查询及更新指令 db.collection("china") //获取集合china的引用.where({ //查询的条件指令wheregdp: _.gt(3000) …

微信小程序云数据库操作

微信小程序云数据库操作 1、云数据库简介1.1 数据类型Date地理位置Null 1.2 权限控制 2、云数据库操作2.1 查询数据2.1.1 通过collection.doc获取一条记录2.1.2 通过collection.get获取所有记录的数据2.1.3 通过document.get获取某一条记录的数据2.1.4 通过collection.count获取…

微信小程序取本地数据库数据(实测有图)

测试效果如下&#xff1a; 本实验主要分为如下几个步骤&#xff1a; 一、安装数据库 二、安装PHPApache 三、编辑微信小程序代码 前两项的简单介绍在如下连接&#xff1a; PHPApache 四、本文主要介绍第三项的内容 需要用到的文件如下&#xff1a; 1、新建微信小程序工程 2、…

微信小程序云开发(数据库)

开发者可以使用云开发开发微信小程序、小游戏&#xff0c;无需搭建服务器&#xff0c;即可使用云端能力。 云开发为开发者提供完整的云端支持&#xff0c;弱化后端和运维概念&#xff0c;无需搭建服务器&#xff0c;使用平台提供的 API 进行核心业务开发&#xff0c;即可实现快…

微信小程序云开发(云数据库篇)

微信小程序云开发[云数据库篇] 云数据库关系型数据库和 JSON 数据库对比数据类型数据库操作联表查询事务处理 云数据库 云开发提供了一个 JSON 数据库&#xff0c;顾名思义&#xff0c;数据库中的每条记录都是一个 JSON 格式的对象。 一个数据库可以有多个集合&#xff08;相当…

微信小程序云开发 1 - 数据库

微信小程序云开发最重要的有两点&#xff1a; 1、云数据库&#xff1b; 2、云函数&#xff1b; 学会这两点基本就能够进行微信小程序的云开发&#xff1b; 首先&#xff0c;我们先看微信小程序云数据库的基本操作&#xff1a; 1&#xff09;打开微 信开发者工具&#xff0…

微信小程序笔记 -- 数据库

6.15 学习微信小程序 -- 数据库 数据库1. 初始化2. 数据库操作2.1 数据类型2.2 增删查改2.2.1 增加/插入 数据&#xff08;add方法&#xff09;2.2.2 删除数据&#xff08;remove方法&#xff09;2.2.3 查看数据&#xff08;get&#xff0c;where&#xff09;2.2.4 更新数据&am…

nohup忽略SIGHUP信号

今天遇到一个问题&#xff1a;开启终端启动gunicorn进程后台运行&#xff0c;终端不关闭时&#xff0c;可以导入excel&#xff0c;关闭终端后&#xff0c;不能导入excel。原因是&#xff0c;xlrd模块需要向控制台输出内容&#xff0c;终端关闭后&#xff0c;控制台消失&#xf…

sighup问题

2019独角兽企业重金招聘Python工程师标准>>> SIGHUP会在以下3种情况下被发送给相应的进程&#xff1a; 1、终端关闭时&#xff0c;该信号被发送到session首进程以及作为job提交的进程&#xff08;即用 & 符号提交的进程&#xff09; 2、session首进程退出时&…

Linux——1、守护进程、SIGHUP与nohup

一、守护进程 脱离于终端并且在后台运行的进程&#xff0c;用于长期运行&#xff0c;守护自己的职责&#xff08;如&#xff1a;监听端口、服务等&#xff09;。 1、特点&#xff1a; 不受用户登录、注销影响。大多数Linux下的服务器都是利用守护进程实现的&#xff0c;如My…

Failed to ignore SIGHUP: No error

memcache下载:https://www.runoob.com/memcached/window-install-memcached.html 下载下来需要install一下: c:\memcached\memcached.exe -d install c:\memcached\memcached.exe -d start c:\memcached\memcached.exe -d stop 然后报错: 我下载的版本是1.4.5按照网上说的: 以…

安装Memcached:Failed to ignore SIGHUP: No error 解决方式

Memcached安装1.4.5 版本 管理员运行cmd&#xff0c;出现如下错误&#xff1a; 解决方式&#xff1a; 命令行中输入schtasks /create /sc onstart /tn memcached /tr "c:\memcached\memcached.exe -m 512",回车 注意&#xff1a;c:\memcached\memcached.exe替换自己…

系统信号(SIGHUP ,SIGPIPE,SIGURG)

对于信号的介绍&#xff0c;我再前面的一篇博客中做过专门的总结&#xff0c;感兴趣的可以看看。本文主要介绍在网络编程中几个密切相关的函数&#xff1a;SIGUP&#xff0c;SIGPIPE&#xff0c;SIGURG。 SIGHUP信号 在介绍SIGHUP信号之前&#xff0c;先来了解两个概念&#x…

PostgreSQL参数重载信号SIGHUP的处理

前边讲过&#xff0c;配置文件重载可以使用SIGHUP信号&#xff0c;也可以使用 pg_ctl reload&#xff0c;后者实际上也是发送SIGHUP给postmaster。 1、postmaster 的SIGHUP处理 下边的代码位于 src/backend/postmaster/postmaster.c 中的函数 SIGHUP_handler&#xff1a; erepo…