Linux设备驱动模型3——平台总线的工作原理

以下内容源于朱有鹏嵌入式课程的学习与整理,如有侵权请告知删除。

一、平台总线的简介

1、平台总线的简介

(1)平台总线属于总线中的一种,相对于usb、pci、i2c等物理总线来说,平台总线是虚拟的、抽象出来的。

(2)CPU与外部通信有两种方式,即地址总线式连接(比如SoC内部集成的各种内部外设与CPU的连接)与专用接口式连接(比如nand和CPU连接)。平台总线对应着地址总线式连接设备。

2、平台总线的意义

平台总线的设计目的,是为了管理上的方便和统一。

二、平台总线的两员大将

平台总线的工作体系都定义在内核源代码drivers/base/platform.c中。

其主要涉及两个结构体:struct platform_device和 struct platform_driver。

1、struct platform_device 结构体

struct platform_device
{const char * name; // 平台总线下设备的名字int id;struct device dev; // 所有设备通用的属性部分u32 num_resources;// 设备使用到的resource(IO或者中断号等)的个数struct resource* resource;// 设备使用到的资源数组的首地址// 设备ID表,很多个类似的同系列的产品,可以用同一个驱动const struct platform_device_id* id_entry;/* arch specific additions */struct pdev_archdata archdata;// 自留地,用来提供扩展性的,表示设备的一些属性
};

2、struct platform_driver 结构体

struct platform_driver 
{int  (*probe)   (struct platform_device *);// 驱动探测函数int  (*remove)  (struct platform_device *);// 去掉一个设备void (*shutdown)(struct platform_device *);// 关闭一个设备int  (*suspend) (struct platform_device *, pm_message_t state);//挂起int  (*resume)  (struct platform_device *);//唤醒struct device_driver driver;// 所有设备共用的一些属性const struct platform_device_id *id_table;// 设备ID表,表示支持哪些设备
};

3、两个接口函数

(1)platform_device_register():在系统启动时用来注册设备。

(2)platform_driver_register():用来注册驱动。

三、平台总线的工作流程

1、工作流程简介

(1)第一步:系统启动时在bus系统中注册platform,使得在/sys/bus/目录有platform目录。

(2)第二步内核移植的人负责提供platform_device,即提供板文件。

比如板文件x210_kernel\arch\arm\mach-s5pv210\mach-x210.c文件中部分内容如下:

#if defined(CONFIG_BACKLIGHT_PWM)
static struct platform_pwm_backlight_data smdk_backlight_data = {.pwm_id  = 0,.max_brightness = 255,.dft_brightness = 255,.pwm_period_ns  = 78770*4,
};static struct platform_device smdk_backlight_device = { //描述设备信息.name      = "pwm-backlight",.id        = -1,.dev        = {.parent = &s3c_device_timer[0].dev,.platform_data = &smdk_backlight_data,},
};static void __init smdk_backlight_register(void)
{int ret;
//省略部分代码ret = platform_device_register(&smdk_backlight_device);//进行设备注册if (ret)printk(KERN_ERR "smdk: failed to register backlight device: %d\n", ret);
}
#endif

(3)第三步写驱动的人负责提供platform_driver,主要是填充结构体并register。

比如x210_kernel\drivers\video\backlight\pwm_bl.c文件部分代码如下:

static struct platform_driver pwm_backlight_driver = {.driver		= {.name	= "pwm-backlight",.owner	= THIS_MODULE,},.probe		= pwm_backlight_probe,.remove		= pwm_backlight_remove,.suspend	= pwm_backlight_suspend,.resume		= pwm_backlight_resume,
};static int __init pwm_backlight_init(void)
{return platform_driver_register(&pwm_backlight_driver);
}
module_init(pwm_backlight_init);

(4)第四步:平台总线的match函数通过driver和device两者的name发现它们匹配,于是调用driver的probe函数来完成驱动的初始化和安装,然后设备就工作起来了。这个是自动的,有别于之前的手动安装。

2、工作流程细述 

第一步:平台总线的注册

(1)平台总线注册函数:platform_bus_init()

int __init platform_bus_init(void)
{int error;early_platform_cleanup();error = device_register(&platform_bus);if (error)return error;error =  bus_register(&platform_bus_type);if (error)device_unregister(&platform_bus);return error;
}

由上可知,该函数调用bus_register()函数来注册平台总线“platform_bus_type”。平台总线“platform_bus_type”的定义如下:

struct bus_type platform_bus_type = {.name		= "platform",.dev_attrs	= platform_dev_attrs,.match		= platform_match,//重点关注一下该函数.uevent		= platform_uevent,.pm		= &platform_dev_pm_ops,
};

它是struct bus_type结构体类型的实例化,struct bus_type结构体内容如下。

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;
};

(2)match函数

每种总线(包括平台总线、usb总线、i2c总线等)都会携带一个match函数,用来对总线下的device和driver进行匹配。理论上每种总线的匹配算法是不同的,但实际上都是看名字的。

由平台总线“platform_bus_type”的定义可知,平台总线的匹配方法是platform_match函数。其内容如下:

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);
}
static const struct platform_device_id *platform_match_id(const struct platform_device_id *id,struct platform_device *pdev)
{while (id->name[0]) {if (strcmp(pdev->name, id->name) == 0) {pdev->id_entry = id;return id;}id++;}return NULL;
}

由此可知:如果驱动有id_table,则说明该驱动可能支持多个设备;这时候要去对比id_table中所有的name,只要找到一个与设备名字相同的就匹配上了不再找了,如果找完id_table都还没找到就说明没有匹配上。如果没有id_table或者没有匹配上,那就直接对比device和driver的name,如果还没匹配上那就匹配失败。

第二步:平台设备的注册

(1)平台设备的注册过程

内容总结

对于linux2.6 arm平台而言,对platform_device的定义通常在bsp的板文件中实现。板文件将众多设备归纳为一个数组,通过platform_add_devices()函数逐个注册。

过程分析

如何寻找在板文件定义的设备信息呢?可以通过搜索名字。以leds-s3c24xx.c这个驱动文件为例进行说明,由驱动名字可知它对应的设备名字叫做“s3c24xx_led”。

static struct platform_driver s3c24xx_led_driver = {.probe		= s3c24xx_led_probe,.remove		= s3c24xx_led_remove,.driver		= {.name		= "s3c24xx_led",//由此可知该驱动对应的设备名字应该也叫s3c24xx_led.owner		= THIS_MODULE,},
};

于是在SI工具里搜索这个内容,得知其出现在x210_kernel\arch\arm\mach-s3c2440\mach-mini2440.c文件中,内容如下:

static struct platform_device mini2440_led1 = {.name		= "s3c24xx_led",.id		= 1,.dev		= {.platform_data	= &mini2440_led1_pdata,},
};static struct platform_device mini2440_led2 = {.name		= "s3c24xx_led",.id		= 2,.dev		= {.platform_data	= &mini2440_led2_pdata,},
};static struct platform_device mini2440_led3 = {.name		= "s3c24xx_led",.id		= 3,.dev		= {.platform_data	= &mini2440_led3_pdata,},
};
……

由上可知其定义了mini2440_led1等设备信息。搜索关键词mini2440_led1,得知其出现在mach-mini2440.c文件中,其内容如下:

static struct platform_device *mini2440_devices[] __initdata = {&s3c_device_ohci,&s3c_device_wdt,&s3c_device_i2c0,&s3c_device_rtc,&s3c_device_usbgadget,&mini2440_device_eth,&mini2440_led1,//这里&mini2440_led2,&mini2440_led3,&mini2440_led4,&mini2440_button_device,&s3c_device_nand,&s3c_device_sdi,&s3c_device_iis,&mini2440_audio,
};

由上可知,mach-mini2440.c文件将众多设备都归纳为一个指针数组mini2440_devices,通过在SI中搜索关键词mini2440_devices,得知其出现在mach-mini2440.c文件中,其内容如下:

static void __init mini2440_init(void)
{struct mini2440_features_t features = { 0 };int i;//省略部分代码//出现在这里platform_add_devices(mini2440_devices, ARRAY_SIZE(mini2440_devices));if (features.count)	/* the optional features */platform_add_devices(features.optional, features.count);}

platform_add_devices()函数内容如下,也就是循环利用platform_device_register函数,对每个平台设备进行注册。从代码可知,每个平台设备的注册都不能出错。

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;
}

 (2)平台设备的platform_data如何使用

platform_data,其实就是设备注册时要提供的与设备有关的一些数据(比如设备对应的gpio、使用到的中断号、设备名称等等),一般定义在板级文件里。比如平台设备mini2440_led1,其硬件信息里有platform_data。

//以下三个结构体的位置并非如下所示,只是为了一目了然而写在同一代码段里。
static struct platform_device mini2440_led1 = {.name		= "s3c24xx_led",.id		= 1,.dev		= {.platform_data	= &mini2440_led1_pdata,//关注这个},
};static struct s3c24xx_led_platdata mini2440_led1_pdata = {.name		= "led1",.gpio		= S3C2410_GPB(5),.flags		= S3C24XX_LEDF_ACTLOW | S3C24XX_LEDF_TRISTATE,.def_trigger	= "heartbeat",
};struct s3c24xx_led_platdata {unsigned int		 gpio;unsigned int		 flags;char			*name;char			*def_trigger;
};

在设备和驱动匹配之后,这些数据会由设备方转给驱动方(驱动的probe函数的第一句代码)。驱动拿到这些数据后,通过这些数据得知设备的具体信息,进而操作设备。如此一来,驱动源码中不携带数据,只负责算法(对硬件的操作方法)。现代驱动设计理念就是算法和数据分离,这样最大程度保持驱动的独立性和适应性。比如leds-s3c24xx.c作为驱动,不会携带设备信息,设备信息写在板文件mach-mini2440.c中。

static int s3c24xx_led_probe(struct platform_device *dev)
{   //在设备和驱动匹配之后,这些数据会由设备方转给驱动方(probe的第一句代码)struct s3c24xx_led_platdata *pdata = dev->dev.platform_data;struct s3c24xx_gpio_led *led;int ret;//省略部分代码//驱动拿到这些数据后,通过这些数据得知设备的具体信息,进而操作设备。if (pdata->flags & S3C24XX_LEDF_TRISTATE) {s3c2410_gpio_setpin(pdata->gpio, 0);s3c2410_gpio_cfgpin(pdata->gpio, S3C2410_GPIO_INPUT);} else {s3c2410_gpio_pullup(pdata->gpio, 0);s3c2410_gpio_setpin(pdata->gpio, 0);s3c2410_gpio_cfgpin(pdata->gpio, S3C2410_GPIO_OUTPUT);}/* register our new led device *///这里的注册设备,与平台设备的注册,有矛盾吗?ret = led_classdev_register(&dev->dev, &led->cdev);if (ret < 0) {dev_err(&dev->dev, "led_classdev_register failed\n");kfree(led);return ret;}return 0;
}

s3c24xx_led_probe()函数中有设备的注册,板级文件间接调用platform_device_register()函数完成设备的注册,它们有什么区别吗?会不会冲突?

第三步:平台驱动的注册

(1)平台驱动的注册过程

以leds-s3c24xx.c这个驱动文件为例进行说明,平台驱动的注册如下: 

static struct platform_driver s3c24xx_led_driver = {.probe		= s3c24xx_led_probe,.remove		= s3c24xx_led_remove,.driver		= {.name		= "s3c24xx_led",//驱动名字,和设备名字应该一致.owner		= THIS_MODULE,},
};static int __init s3c24xx_led_init(void)
{return platform_driver_register(&s3c24xx_led_driver);
}static void __exit s3c24xx_led_exit(void)
{platform_driver_unregister(&s3c24xx_led_driver);
}module_init(s3c24xx_led_init);
module_exit(s3c24xx_led_exit);

(2)probe函数的功能和意义

static int s3c24xx_led_probe(struct platform_device *dev)
{struct s3c24xx_led_platdata *pdata = dev->dev.platform_data;struct s3c24xx_gpio_led *led;int ret;led = kzalloc(sizeof(struct s3c24xx_gpio_led), GFP_KERNEL);if (led == NULL) {dev_err(&dev->dev, "No memory for device\n");return -ENOMEM;}platform_set_drvdata(dev, led);led->cdev.brightness_set = s3c24xx_led_set;led->cdev.default_trigger = pdata->def_trigger;led->cdev.name = pdata->name;led->cdev.flags |= LED_CORE_SUSPENDRESUME;led->pdata = pdata;/* no point in having a pull-up if we are always driving */if (pdata->flags & S3C24XX_LEDF_TRISTATE) {s3c2410_gpio_setpin(pdata->gpio, 0);s3c2410_gpio_cfgpin(pdata->gpio, S3C2410_GPIO_INPUT);} else {s3c2410_gpio_pullup(pdata->gpio, 0);s3c2410_gpio_setpin(pdata->gpio, 0);s3c2410_gpio_cfgpin(pdata->gpio, S3C2410_GPIO_OUTPUT);}/* register our new led device */ret = led_classdev_register(&dev->dev, &led->cdev);if (ret < 0) {dev_err(&dev->dev, "led_classdev_register failed\n");kfree(led);return ret;}return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/461237.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MYSQL中的BlackHole引擎

MYSQL中的BlackHole引擎 http://blog.csdn.net/ylspirit/article/details/7234021 http://blog.chinaunix.net/uid-22646981-id-3271711.html MySQL在5.x系列提供了Blackhole引擎–“黑洞”. 其作用正如其名字一样&#xff1a;任何写入到此引擎的数据均会被丢弃掉&#xff0c;…

mysql备份到带库_RMAN备份恢复——备份到带库的性能

简单测试了一下rac环境通过RMAN备份到带库的功能。数据库&#xff1a;Oracle 10203 RAC for Solaris8带库&#xff1a;Quantum PX502备份软件&#xff1a;Veritas netbackup 6.0由于带库是502&#xff0c;因此包含两个控制器。也就是说&#xff0c;应该可以通过两个控制性并行写…

《DIY四轴飞行器》读书笔记1

内容整理于黄和悦的《DIY四轴飞行器》。 一、四轴飞行器概述 1、四轴飞行器的现状 &#xff08;1&#xff09;研究内容 多级协作&#xff0c;自主飞行倾斜&#xff1b;最优控制理论&#xff0c;飞行器自主飞行和避障&#xff1b;主要是飞控部分。 &#xff08;2&#xff09…

脚本输出当前 “yyyy-MM-dd WeakDay Festval”

ylbtech-JavaScript: 脚本输出当前 “yyyy-MM-dd WeakDay Festval”脚本输出当前 “yyyy-MM-dd WeakDay Festval” 1.A,源代码(Source Code)-脚本输出当前 “yyyy-MM-dd WeakDay Festval”返回顶部 <SCRIPT languagejavascript> <!--calendar new Date();day cal…

SecureCRT密钥远程登录Linux

一&#xff1a;环境SecureCRT版本&#xff1a;SecureCRT_5.1.3linux版本&#xff1a;[rootangelT ~]# cat /etc/redhat-release CentOS release 6.4 (Final)[rootangelT ~]# uname -r2.6.32-358.el6.x86_64linux系统的sshd_config配置文件是默认的&#xff0c;没有任何的修改。…

源码安装mysql数据库_Linux下源码安装mysql数据库

1、 创建mysql安装目录&#xff1a;[rootlocalhost ~]#mkdir –pv /usr/local/mysql/2、 创建数据存放目录&#xff1a;[rootlocalhost ~]#mkdir –pv /data/mysql/3、 创建用户和用户组&#xff0c;并赋予数据存放目录权限&#xff1a;[rootlocalhost ~]#groupadd mysql[ro…

win10禁止数字签名

以下内容源于网络资源的学习与整理&#xff0c;如有侵权请告知删除。 1、点击通知&#xff0c;找到并进入“所有设置”。 2、在所有设置中找到并进入“更新和安全”。 3、找到恢复&#xff0c;点击“高级启动”下的“立即重启”&#xff0c;重启电脑。 4、重启后选择“疑难解…

信息采集-火车采集器

最近一位同事提出要采集alibaba上的公司信息&#xff0c;关键词是工业加湿器。 主管把任务分配给我后&#xff0c;推荐了一款软件&#xff0c;火车采集器&#xff08;真心不是做广告&#xff09;。 研究了两天&#xff0c;还算简单&#xff0c;除了正则表达式似懂非懂&#xff…

在Linux系统安装Nginx及配置https加密访问

2019独角兽企业重金招聘Python工程师标准>>> 1、安装nginx ①、为了确保能在 nginx 中使用正则表达式进行更灵活的配置&#xff0c;安装之前需要确定系统是否安装有 PCRE&#xff08;Perl Compatible Regular Expressions&#xff09;包。您可以到 ftp://ftp.csx.c…

mysql cstmt_MySQL

创建一个以JDBC连接数据库的程序&#xff0c;包含7个步骤&#xff1a;1、加载JDBC驱动程序&#xff1a;在连接数据库之前&#xff0c;首先要加载想要连接的数据库的驱动到JVM(Java虚拟机)&#xff0c;这通过java.lang.Class类的静态方法forName(String className)实现。例如&am…

mkv210_image.c文件详解(为BL1添加校验头)

以下内容源于朱有鹏嵌入式课程的学习与整理&#xff0c;如有侵权请告知删除。 1、mkv210_image.c的使用演示 裸机程序中的Makefile是把程序的编译和链接过程分开的。实际上真正的项目的Makefile也是这样的&#xff0c;只是平时我们用gcc a.c -o exe这种方式编译时&#xff0c;把…

和菜鸟一起学linux之bluez学习记录2

这里主要摘取对于hci&#xff0c;l2cap&#xff0c;sdp和rfcomm的一些应用编程。 关于hci 一、HCI层协议概述 1、HCI Command Packets 详见bluez源码&#xff1a;lib/hci.h /* Link Control */ #define OGF_LINK_CTL 0x01 #define OCF_INQUIRY 0x0001 #define OCF_…

AppDelegate.h

2019独角兽企业重金招聘Python工程师标准>>> #ifndef __APP_DELEGATE_H__ #define __APP_DELEGATE_H__#include "CCApplication.h" //CCApplication.h能根据平台打开对应的平台头文件 /** brief The cocos2d Application.The reason for implement as …

wamp替换mysql_将wamp集成的mysql替换成安装版的

替换原因&#xff1a;wamp集成的mysql错误提示乱码。修改文件&#xff1a;路径C:\wamp下的wampmanager.conf&#xff0c;wampmanager.ini&#xff0c;uninstall_services.bat路径C:\wamp\bin\mysql\mysql5.6.17下的wampserver.conf步骤&#xff1a;1.安装wamp和mysql&#xff0…

关于uboot的简介——uboot的一些常识介绍

以下内容源于朱有鹏嵌入式课程的学习与整理&#xff0c;如有侵权请告知删除。 一、uboot的由来 1、uboot从哪里来的&#xff1f; uboot是SourceForge上的开源项目。uboot项目的作者&#xff1a;一个德国人最早发起的项目。uboot就是由一个人发起&#xff0c;然后由整个网络上所…

有目标

生活就是每一天都有一个目标&#xff0c;不管大或者小&#xff0c;只要很好的完成了&#xff0c;都会觉得很有成就感&#xff01;转载于:https://www.cnblogs.com/jackychua/archive/2013/04/01/2994434.html

虚拟机上网以及互ping问题

以下内容源于网络资源的学习与整理&#xff0c;如有侵权请告知删除。 虚拟机设置静态IP和上网问题 &#xff08;1&#xff09;在“虚拟机——设置——网络适配器”中选择桥接模式&#xff1b; &#xff08;2&#xff09;在“编辑——虚拟网络编辑器”中&#xff0c;选择桥接到有…

php定时执行原理

即使关掉浏览器也能继续执行 ignore_user_abort(); //即使Client断开(如关掉浏览器)&#xff0c;PHP脚本也可以继续执行. set_time_limit(0); // 执行时间为无限制&#xff0c;php默认的执行时间是30秒&#xff0c;通过set_time_limit(0)可以让程序无限制的执行下去 转载于:htt…

php递归无限极分类

递归无限级分类有几种形式&#xff0c;我这里仅仅举例比較经常使用的三种&#xff1a; 第一种&#xff1a;返回有排序的数组&#xff1a; <?php$data array(1 > array(id > 1,pid > 0,user_name > one,),2 > array(id > 2,pid > 1,user_name > tw…

qt 飞扬青云_R语言学习——实例标识符

> patientID> age> diabetes> status> patientdata> #在上述创建的病例数据框中&#xff0c;病人编号(patientID)用于区分数据中的不同个体&#xff0c;在R中实例标识符(case identifier)可以通过数据框操作函数中的rowname选项指定&#xff0c;如下代码&…