Linux主机驱动与外设驱动分离思想

合集下载

Linux系统关系族谱图:应用程序、内核、驱动程序、硬件详解

Linux系统关系族谱图:应用程序、内核、驱动程序、硬件详解

Linux系统关系族谱图:应用程序、内核、驱动程序、硬件详解目前,Linux软件工程师大致可分为两个层次:01Linux应用软件工程师(ApplicaTIon Software Engineer):主要利用C库函数和Linux API 进行应用软件的编写;从事这方面的开发工作,主要需要学习:符合linux posix标准的API函数及系统调用,linux 的多任务编程技巧:多进程、多线程、进程间通信、多任务之间的同步互斥等,嵌入式数据库的学习,UI编程:QT、miniGUI等。

02Linux固件工程师(Firmware Engineer):主要进行Bootloader、Linux的移植及Linux设备驱动程序的设计工作。

一般而言,固件工程师的要求要高于应用软件工程师的层次,而其中的Linux设备驱动编程又是Linux程序设计中比较复杂的部分,究其原因,主要包括如下几个方面:1 )设备驱动属于Linux内核的部分,编写Linux设备驱动需要有一定的Linux操作系统内核基础;需要了解部分linux内核的工作机制与系统组成2)编写Linux设备驱动需要对硬件的原理有相当的了解,大多数情况下我们是针对一个特定的嵌入式硬件平台编写驱动的,例如:针对特定的主机平台:可能是三星的2410、2440,也可能是atmel的,或者飞思卡尔的等等3 )Linux设备驱动中广泛涉及到多进程并发的同步、互斥等控制,容易出现bug;因为linux本身是一个多任务的工作环境,不可避免的会出现在同一时刻对同一设备发生并发操作4 )由于属于内核的一部分,Linux设备驱动的调试也相当复杂。

linux设备驱动没有一个很好的IDE环境进行单步、变量查看等调试辅助工具;linux驱动跟linux内核工作在同一层次,一旦发生问题,很容易造成内核的整体崩溃。

在任何一个计算机系统中,大至服务器、PC机、小至手机、mp3/mp4播放器,无论是复杂的大型服务器系统还是一个简单的流水灯单片机系统,都离不开驱动程序的身影,没有硬件的软件是空中楼阁,没有软件的硬件只是一堆废铁,硬件是底层的基础,是所有软件。

Linux下I2C驱动介绍

Linux下I2C驱动介绍

1、I2C概述I2C是philips公司提供的外设总线,I2C有两条数据线,一条是串行数据线SDA、一条是时钟线SCL,使用SDA和SCL实现了数据的交换,便于布线。

I2C总线方便用在EEPROM、实时钟、小型LCD等与CPU外部的接口上。

2、Linux下的驱动思路Linux系统下编写I2c驱动主要有两种方法:一种是把I2C当做普通字符设备来使用;另一种利用Linux下驱动的体系结构来实现。

第一种方法:优点:思路比较直接,不用花费大量时间去了解Linux系统下I2C体系结构缺点:不仅对I2C设备操作要了解,还有了解I2C的适配器操作不仅对I2C设备器和设备操作需要了解,编写的驱动移植性差,内核提供的I2C设备器都没有用上。

第二种方法:第一种的优点就是第二种的缺点,第一种的缺点就是第二种的优点。

3、I2C框架概述Linux的I2C体系结构分为3部分:1)I2C核心I2C核心提供了I2C总线驱动和设备驱动的注册和注销的方法,I2C 通信方法(algorithm)上层,与具体适配器无关的代码,检测设备上层的代码等。

2)I2C总线驱动I2C总线驱动是对I2C硬件体系结构中适配器端的实现,适配器可以直接受CPU来控制。

3)I2C设备驱动I2C设备驱动是对I2C硬件体系结构中设备端的实现,设备端挂在受CPU控制的适配器上,通过I2C适配器与CPU交换数据。

Linux下的I2C体系结构:1)Linux下的I2C体系结构4、I2C设备驱动编写方法首先让我们明白适配器驱动的作用是让我们能够通过它发出标准的I2C时序,在linux内核源代码中driver/I2C/buss包含一些适配器的驱动,例如s3c2410的驱动I2C-s3c2410.c,适配器被加载到内核中,接下的任务就是实现设备驱动的编写。

编写设备驱动的方法主要分为两种方法:第一种:利用设备提供的I2C-dev.c来实现I2C适配器设备文件,然后通过上层应用程序来操作I2C设备器来控制I2C设备。

Linux设备驱动程序原理及框架-内核模块入门篇

Linux设备驱动程序原理及框架-内核模块入门篇

Linux设备驱动程序原理及框架-内核模块入门篇内核模块介绍应用层加载模块操作过程内核如何支持可安装模块内核提供的接口及作用模块实例内核模块内核模块介绍Linux采用的是整体式的内核结构,这种结构采用的是整体式的内核结构,采用的是整体式的内核结构的内核一般不能动态的增加新的功能。

为此,的内核一般不能动态的增加新的功能。

为此,Linux提供了一种全新的机制,叫(可安装) 提供了一种全新的机制,可安装) 提供了一种全新的机制模块” )。

利用这个机制“模块”(module)。

利用这个机制,可以)。

利用这个机制,根据需要,根据需要,在不必对内核重新编译链接的条件将可安装模块动态的插入运行中的内核,下,将可安装模块动态的插入运行中的内核,成为内核的一个有机组成部分;成为内核的一个有机组成部分;或者从内核移走已经安装的模块。

正是这种机制,走已经安装的模块。

正是这种机制,使得内核的内存映像保持最小,的内存映像保持最小,但却具有很大的灵活性和可扩充性。

和可扩充性。

内核模块内核模块介绍可安装模块是可以在系统运行时动态地安装和卸载的内核软件。

严格来说,卸载的内核软件。

严格来说,这种软件的作用并不限于设备驱动,并不限于设备驱动,例如有些文件系统就是以可安装模块的形式实现的。

但是,另一方面,可安装模块的形式实现的。

但是,另一方面,它主要用来实现设备驱动程序或者与设备驱动密切相关的部分(如文件系统等)。

密切相关的部分(如文件系统等)。

课程内容内核模块介绍应用层加载模块操作过程内核如何支持可安装模块内核提供的接口及作用模块实例内核模块应用层加载模块操作过程内核引导的过程中,会识别出所有已经安装的硬件设备,内核引导的过程中,会识别出所有已经安装的硬件设备,并且创建好该系统中的硬件设备的列表树:文件系统。

且创建好该系统中的硬件设备的列表树:/sys 文件系统。

(udev 服务就是通过读取该文件系统内容来创建必要的设备文件的。

)。

Linuxkernel驱动相关抽象概念及其实现之“bus,device,driver”

Linuxkernel驱动相关抽象概念及其实现之“bus,device,driver”

Linuxkernel驱动相关抽象概念及其实现之“bus,device,driver”bus,device,driver三个很重要的概念贯穿Linux内核驱动架构,特转载⼀篇博⽂:内核的开发者将总线,设备,驱动这三者⽤软件思想抽象了出来,巧妙的建⽴了其间的关系,使之更形象化。

结合前⾯所学的知识,总的来说其三者间的关系为bus有两条链表,分别⽤于挂接设备和驱动,指定了其⾃⾝bus的device或者driver最后都会分别连接到对应bus的这两条链表上,⽽总线⼜有其始端,为bus_kset,⼀个driver可以对应于⼏个设备,因此driver同样有其设备链表,⽤于挂接可以操作的设备,其⾃⾝也有bus挂接点,⽤于将⾃⾝挂接到对应bus(每个driver只属于⼀条总线),⽽对于device,⼀个设备只属于⼀条总线,只能有⼀个driver与其对应,因此对于device,都是单⼀的,⼀个driver挂接点,⼀个bus挂接点,device与bus相同的是都有始端,device为devices_kset,因此device的注册同时会出现在对应的bus⽬录和device总⽬录下。

好了,下⾯就以源码为例分别分析⼀下bus,device,driver的注册过程。

⼀、bus的注册bus的注册⽐较简单,⾸先来看⼀下bus的结构:1struct bus_type {2const char *name; //名字3struct bus_attribute *bus_attrs; //bus属性集4struct device_attribute *dev_attrs; //device属性集5struct driver_attribute *drv_attrs; //driver属性集6int (*match)(struct device *dev, struct device_driver *drv);7int (*uevent)(struct device *dev, struct kobj_uevent_env *env);8int (*probe)(struct device *dev);9int (*remove)(struct device *dev);10void (*shutdown)(struct device *dev);11int (*suspend)(struct device *dev, pm_message_t state);12int (*resume)(struct device *dev);13const struct dev_pm_ops *pm;14struct bus_type_private *p; //bus的私有成员15 };16//其中重点看⼀下私有成员结构体:17struct bus_type_private {18struct kset subsys; //bus内嵌的kset,代表其⾃⾝19struct kset *drivers_kset;20struct kset *devices_kset;21struct klist klist_devices; //包含devices链表及其操作函数22struct klist klist_drivers; //driver链表及其操作函数23struct blocking_notifier_head bus_notifier;24 unsigned int drivers_autoprobe:1; //匹配成功⾃动初始化标志25struct bus_type *bus;26 };⽆论是bus,driver,还是device其本⾝特征都放在私有成员⾥,其注册时,都会申请并填充这个结构体,下⾯具体分析⼀下bus的注册流程,从bus_register开始:1int bus_register(struct bus_type *bus)2 {3int retval;4struct bus_type_private *priv;5 priv = kzalloc(sizeof(struct bus_type_private), GFP_KERNEL); //进⼊时bus_type->bus_type_private为NULL6if (!priv) //该函数主要是对其的设置7return -ENOMEM;8 priv->bus = bus; //私有成员的bus回指该bus9 bus->p = priv; //初始化bus->p,即其私有属性10 BLOCKING_INIT_NOTIFIER_HEAD(&priv->bus_notifier);11 retval = kobject_set_name(&priv->subsys.kobj, "%s", bus->name); //设置该bus的名字,bus是kset的封装12if (retval)13goto out;14//bus_kset即为所有bus的总起始端点15//围绕bus内嵌的kset初始化,和kset的初始化时围绕16 priv->subsys.kobj.kset = bus_kset; //kobj相似,没有parent时,就会⽤kset的kobj,此处即是17 priv->subsys.kobj.ktype = &bus_ktype; //属性操作级别统⼀为bus_ktype18 priv->drivers_autoprobe = 1; //设置该标志,当有driver注册时,会⾃动匹配devices19//上的设备并⽤probe初始化,20//当有device注册时也同样找到 driver并会初始化21 retval = kset_register(&priv->subsys); //注册kset,创建⽬录结构,以及层次关系22if (retval)23goto out;24 retval = bus_create_file(bus, &bus_attr_uevent); //当前bus⽬录下⽣成bus_attr_uevent属性⽂件25if (retval)26goto bus_uevent_fail;27 priv->devices_kset = kset_create_and_add("devices", NULL, //初始化bus⽬录下的devices⽬录,⾥⾯级联了该bus下设备,28 &priv->subsys.kobj); //仍然以kset为原型29if (!priv->devices_kset) {30 retval = -ENOMEM;31goto bus_devices_fail;32 }33 priv->drivers_kset = kset_create_and_add("drivers", NULL, //初始化bus⽬录下的drivers⽬录,⾥⾯级联了该bus下设备的driver34 &priv->subsys.kobj);35if (!priv->drivers_kset) {36 retval = -ENOMEM;37goto bus_drivers_fail;38 }39 klist_init(&priv->klist_devices, klist_devices_get, klist_devices_put); //初始化klist_devices⾥的操作函数成员40 klist_init(&priv->klist_drivers, NULL, NULL); //klist_drivers⾥的操作函数置空41 retval = add_probe_files(bus); //增加bus_attr_drivers_probe和bus_attr_drivers_autoprobe42if (retval) //属性⽂件43goto bus_probe_files_fail;44 retval = bus_add_attrs(bus); //增加默认的属性⽂件45if (retval)46goto bus_attrs_fail;47 pr_debug("bus: '%s': registered/n", bus->name);48return0;49 bus_attrs_fail: //以下为错误处理50 remove_probe_files(bus);51 bus_probe_files_fail:52 kset_unregister(bus->p->drivers_kset);53 bus_drivers_fail:54 kset_unregister(bus->p->devices_kset);55 bus_devices_fail:56 bus_remove_file(bus, &bus_attr_uevent);57 bus_uevent_fail:58 kset_unregister(&bus->p->subsys);59out:60 kfree(bus->p);61 bus->p = NULL;62return retval;63 }由此可见,bus⼜是kset的封装,bus_register主要完成了其私有成员bus_type_private的初始化,并初始化了其下的两个⽬录devices和drivers,及其属性⽂件,bus有个⾃⼰的根⽬录也就是bus有个起始端点,是bus_kset,经过此番的注册,bus⽬录下将会出现我们注册的bus,并且其下会有device和driver两个⼦⽬录,代表它下⾯的driver和device链表。

Linux驱动:SPI驱动编写要点

Linux驱动:SPI驱动编写要点

Linux驱动:SPI驱动编写要点题外话:⾯对成功和失败,⼀个⼈有没有“冠军之⼼”,直接影响他的表现。

⼏周前剖析了Linux SPI 驱动框架,算是明⽩个所以然,对于这么⼀个庞⼤的框架,并不是每⼀⾏代码都要⾃⼰去敲,因为前⼈已经把这个框架搭建好了,作为驱动开发者的我们只需要搞清楚哪⼀部分是需要⾃⼰修改或重新编写就OK了。

结合Linux内核⾯向对象的设计思想,SPI总的设计思路⼤概是这样的:第①处:内核中抽象了SPI控制器,让spi_master成为他的象征,他的实例化对象就是与硬⽣⽣的SPI控制器对应的,在Linux内核中习惯将集成到SOC上的控制器⽤假想的platform总线来进⾏管理,于是乎spi_master的实例化就得依靠platform_device和platform_driver来联⼿完成了。

细嚼慢咽:这⾥的platform_device就相当于是spi_master的静态描述:包括⼏号控制器、寄存器地址、引脚配置等等,把这些信息以“资源”的形式挂在platform_device上,等platform_driver找到命中注定的那个他之后就可以获得这个(静态描述)资源,probe中⽤这些资源就⽣出了spi_master实例对象。

这⼀系列流程前辈们都已经做好了,我们要做的就是将这静态描述platform_device修改成和⾃⼰SOC中的spi 控制器⼀致的特性即可。

即:适当修改arch/arm/mach-s5pv210/dev-spi.c中platform_device涉及的成员。

1// SPI0的寄存器地址2static struct resource s5pv210_spi0_resource[] = {3 [0] = {4 .start = S5PV210_PA_SPI0,5 .end = S5PV210_PA_SPI0 + 0x100 - 1,6 .flags = IORESOURCE_MEM,7 },8 [1] = {9 .start = DMACH_SPI0_TX,10 .end = DMACH_SPI0_TX,11 .flags = IORESOURCE_DMA,12 },13 [2] = {14 .start = DMACH_SPI0_RX,15 .end = DMACH_SPI0_RX,16 .flags = IORESOURCE_DMA,17 },18 [3] = {19 .start = IRQ_SPI0,20 .end = IRQ_SPI0,21 .flags = IORESOURCE_IRQ,22 },23 };2425/**26 * struct s3c64xx_spi_info - SPI Controller defining structure27 * @src_clk_nr: Clock source index for the CLK_CFG[SPI_CLKSEL] field.28 * @src_clk_name: Platform name of the corresponding clock.29 * @clk_from_cmu: If the SPI clock/prescalar control block is present30 * by the platform's clock-management-unit and not in SPI controller.31 * @num_cs: Number of CS this controller emulates.32 * @cfg_gpio: Configure pins for this SPI controller.33 * @fifo_lvl_mask: All tx fifo_lvl fields start at offset-634 * @rx_lvl_offset: Depends on tx fifo_lvl field and bus number35 * @high_speed: If the controller supports HIGH_SPEED_EN bit36 * @tx_st_done: Depends on tx fifo_lvl field37*/38static struct s3c64xx_spi_info s5pv210_spi0_pdata = {39 .cfg_gpio = s5pv210_spi_cfg_gpio, //将GPIO配置成SPI0引脚的函数40 .fifo_lvl_mask = 0x1ff,41 .rx_lvl_offset = 15,42 .high_speed = 1, //看s5pv210的使⽤⼿册P901可知:这是⽤来配置CH_CFG寄存器的,主要是210⽤于从设备时......43 .tx_st_done = 25,44 };4546static u64 spi_dmamask = DMA_BIT_MASK(32);4748struct platform_device s5pv210_device_spi0 = {49 .name = "s3c64xx-spi",50 .id = 0,51 .num_resources = ARRAY_SIZE(s5pv210_spi0_resource),52 .resource = s5pv210_spi0_resource,53 .dev = {54 .dma_mask = &spi_dmamask,55 .coherent_dma_mask = DMA_BIT_MASK(32),56 .platform_data = &s5pv210_spi0_pdata,//特殊的spi_master数据57 },58 };platform_device第②处:添加/修改SPI外设“静态描述”的结构。

linux设备驱动程序的设计与实现

linux设备驱动程序的设计与实现

linux设备驱动程序的设计与实现
Linux设备驱动程序的设计与实现是一个涉及底层系统编程和硬件交互的复杂过程。

下面是一个简单的步骤指南,以帮助你开始设计和实现Linux设备驱动程序:
1. 了解硬件:首先,你需要熟悉你要驱动的硬件设备的规格和特性。

这包括硬件的内存空间、I/O端口、中断请求等。

2. 选择驱动程序模型:Linux支持多种设备驱动程序模型,包括字符设备、块设备、网络设备等。

根据你的硬件设备和需求,选择合适的驱动程序模型。

3. 编写Makefile:Makefile是一个文本文件,用于描述如何编译和链接你的设备驱动程序。

它告诉Linux内核构建系统如何找到并编译你的代码。

4. 编写设备驱动程序:在Linux内核源代码树中创建一个新的驱动程序模块,并编写相应的C代码。

这包括设备注册、初始化和卸载函数,以及支持读写和配置硬件的函数。

5. 测试和调试:编译你的设备驱动程序,并将其加载到运行中的Linux内核中。

使用各种测试工具和方法来验证驱动程序的正确性和稳定性。

6. 文档和发布:编写清晰的文档,描述你的设备驱动程序的用途、用法和已知问题。

发布你的代码以供其他人使
用和改进。

linux 开发新驱动步骤

linux 开发新驱动步骤Linux作为一款开源的操作系统,其内核源码也是开放的,因此,许多开发人员在Linux上进行驱动开发。

本文将介绍在Linux上进行新驱动开发的步骤。

第一步:确定驱动类型和接口在进行驱动开发前,需要确定驱动类型和接口。

驱动类型包括字符设备驱动、块设备驱动、网络设备驱动等。

接口包括设备文件、系统调用、ioctl等。

根据驱动类型和接口的不同,驱动开发的流程也有所不同。

第二步:了解Linux内核结构和API驱动开发需要熟悉Linux内核的结构和API。

Linux内核由许多模块组成,每个模块都有自己的功能。

API是应用程序接口,提供了许多函数和数据结构,开发人员可以使用这些函数和数据结构完成驱动开发。

第三步:编写驱动代码在了解了Linux内核结构和API后,就可以编写驱动代码了。

驱动代码需要按照Linux内核的编码规范编写,确保代码风格统一、可读性好、可维护性强等。

在编写代码时,需要使用API提供的函数和数据结构完成相应的功能。

第四步:编译驱动代码和内核模块驱动代码编写完成后,需要编译成内核模块。

编译内核模块需要使用内核源码中的Makefile文件。

编译完成后,会生成一个.ko文件,这个文件就是内核模块。

第五步:加载和卸载内核模块内核模块编译完成后,需要加载到Linux系统中。

可以使用insmod命令加载内核模块,使用rmmod命令卸载内核模块。

在加载和卸载内核模块时,需要注意依赖关系,确保依赖的模块已经加载或卸载。

第六步:调试和测试驱动开发完成后,需要进行调试和测试。

可以使用printk函数输出调试信息,在/var/log/messages文件中查看。

测试时需要模拟各种可能的情况,确保驱动程序的稳定性和可靠性。

Linux驱动开发需要掌握Linux内核结构和API,熟悉驱动类型和接口,按照编码规范编写驱动代码,并进行编译、加载、调试和测试。

只有掌握了这些技能,才能进行高效、稳定和可靠的驱动开发。

linux驱动面试题

linux驱动面试题Linux驱动是指在Linux操作系统中,用于控制与硬件之间的交互和通信的软件模块。

在Linux的工作环境中,驱动程序起着至关重要的作用。

如果你准备参加Linux驱动的面试,以下是一些常见的Linux驱动面试题,希望可以对你有所帮助。

一、简述Linux驱动的作用和功能。

Linux驱动是一种软件模块,用来控制硬件设备与操作系统之间的通信和交互。

它负责将输入/输出请求传递给硬件设备,并处理来自硬件设备的中断和事件。

Linux驱动的功能包括设备初始化和配置、数据传输和处理以及错误处理等。

二、请简要介绍Linux驱动程序的加载过程。

当系统启动时,Linux内核首先会加载核心模块和驱动程序模块。

驱动程序模块是以目标硬件设备为基础的,它们包含了与设备通信所需的函数和数据结构。

一般情况下,系统会根据硬件设备信息自动加载对应的驱动程序模块。

加载驱动程序模块需要通过insmod或modprobe命令进行,这些命令可以在启动时自动执行。

三、请简述Linux驱动程序的实现方式。

Linux驱动程序的实现方式包括内核空间驱动和用户空间驱动。

内核空间驱动是指驱动程序运行在内核空间,直接与硬件设备进行交互。

用户空间驱动是指驱动程序运行在用户空间,通过系统调用和内核模块实现与硬件设备的通信。

内核空间驱动的优势是性能更好,但需要对内核进行编译和加载,而用户空间驱动的优势是开发更加容易,但性能会稍差。

四、请介绍Linux驱动程序中常用的数据结构和函数。

在Linux驱动程序中,常用的数据结构有file结构体、inode结构体和cdev结构体等。

file结构体用于表示一个打开的设备文件,可以通过它传递与设备相关的信息。

inode结构体用于表示一个文件的元数据信息,包括文件的权限、大小和创建时间等。

cdev结构体用于表示一个字符设备,包含了设备文件的操作函数和设备号等信息。

常用的函数包括register_chrdev、unregister_chrdev、request_irq和release_irq等。

linux操作系统的基本体系结构

linux操作系统的基本体系结构一、内核(Kernel)Linux操作系统的核心是内核,它负责管理系统资源、控制硬件设备、调度进程和提供基本的系统服务。

Linux内核采用单内核结构,包含了操作系统的大部分核心功能和驱动程序。

内核是操作系统的核心组件,它提供了操作系统运行所必须的基本功能。

Linux内核具有以下特点:1、多任务处理:Linux内核支持多任务处理,可以同时运行多个程序,并实现多个程序之间的切换和管理。

2、硬件管理:Linux内核负责管理硬件设备,与硬件设备交互,控制硬件设备的工作状态。

3、内存管理:Linux内核负责管理系统的内存,包括内存的分配、释放、映射和交换等操作。

4、文件系统:Linux内核支持多种文件系统,包括ext4、NTFS、FAT等,负责文件的读写、管理和保护。

5、进程管理:Linux内核管理系统进程,包括进程的创建、调度、挂起、唤醒和终止等操作。

6、网络通信:Linux内核支持网络通信功能,包括TCP/IP协议栈、网卡驱动等,实现网络数据传输和通信。

二、ShellShell是Linux操作系统的命令解释器,用户通过Shell与操作系统进行交互。

Shell接受用户的命令,并将其转换为对应的系统调用,最终由内核执行。

Linux系统中常用的Shell有Bash、Zsh等,用户可以根据自己的喜好选择不同的Shell。

Shell具有以下功能:1、命令解释:Shell接受用户输入的命令,并将其翻译为操作系统可以执行的命令。

2、执行程序:Shell可以执行各种程序、脚本和命令,包括系统工具、应用程序等。

3、环境控制:Shell可以设置环境变量、别名和路径等,帮助用户管理系统环境。

4、文件处理:Shell可以处理文件操作,包括创建、删除、复制、移动等。

5、脚本编程:Shell支持脚本编程,用户可以编写Shell脚本来自动执行一系列操作。

三、系统工具Linux操作系统提供了丰富的系统工具,帮助用户管理系统和执行各种任务。

linux下pci的数据读写流程

linux下pci的数据读写流程Linux下PCI的数据读写流程一、引言PCI(Peripheral Component Interconnect,外设互联)是一种计算机总线标准,用于连接计算机的主板与外部硬件设备。

在Linux系统下,PCI设备的数据读写是通过访问设备的寄存器来实现的。

本文将介绍Linux下PCI的数据读写流程。

二、PCI设备的识别和配置在Linux系统启动时,会进行PCI总线的枚举和设备的识别与配置。

Linux内核会扫描PCI总线上的设备,并为每个设备分配唯一的设备标识符,称为PCI设备号。

系统将会为每个PCI设备分配资源,并将设备驱动程序与设备进行匹配。

三、设备驱动程序的加载与初始化在设备识别和配置完成后,系统会加载与该设备匹配的驱动程序。

设备驱动程序是对设备进行管理和控制的软件模块。

当驱动程序被加载时,会进行初始化操作,包括分配内存空间、注册中断处理程序等。

四、设备寄存器的映射设备驱动程序在初始化过程中需要访问设备的寄存器来进行数据的读写。

为了实现对设备寄存器的访问,驱动程序需要将设备寄存器映射到内核空间。

在Linux中,可以通过ioremap函数将设备寄存器的物理地址映射到内核虚拟地址空间。

五、数据读写操作一旦设备寄存器映射完成,驱动程序就可以通过读写内核虚拟地址来实现对设备寄存器的读写操作。

通常,设备寄存器是以字节为单位进行读写的。

驱动程序可以使用readb、readw、readl函数来读取设备寄存器的值,使用writeb、writew、writel函数来向设备寄存器写入数据。

六、同步与互斥在进行数据读写操作时,为了保证数据的正确性和一致性,需要进行同步与互斥操作。

同步操作可以通过在读写操作前后使用memory barrier指令来实现,确保读写操作的顺序性。

互斥操作可以通过自旋锁或信号量机制来实现,防止多个进程同时对设备进行读写操作。

七、数据传输与中断处理在进行数据读写操作的同时,设备可能会触发中断。

  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

Linux主机驱动与外设驱动分离思想1主机、外设驱动分离的意义在Linux设备驱动框架的设计中,除了有分层设计实现以外,还有分隔的思想。

举一个简单的例子,假设我们要通过SPI总线访问某外设,在这个访问过程中,要通过操作CPU XXX 上的SPI控制器的寄存器来达到访问SPI外设YYY的目的,最简单的方法是:return_type xxx_write_spi_yyy(...){xxx_write_spi_host_ctrl_reg(ctrl);xxx_ write_spi_host_data_reg(buf);while(!(xxx_spi_host_status_reg()&SPI_DATA_TRANSFER_DONE));...}如果按照这种方式来设计驱动,结果是对于任何一个SPI外设来讲,它的驱动代码都是CPU 相关的。

也就是说,当然用在CPU XXX上的时候,它访问XXX的SPI主机控制寄存器,当用在XXX1的时候,它访问XXX1的SPI主机控制寄存器:return_type xxx1_write_spi_yyy(...){xxx1_write_spi_host_ctrl_reg(ctrl);xxx1_ write_spi_host_data_reg(buf);while(!(xxx1_spi_host_status_reg()&SPI_DATA_TRANSFER_DONE));...}这显然是不能接受的,因为这意味着外设YYY用在不同的CPU XXX和XXX1上的时候需要不同的驱动。

那么,我们可以用如图12.4的思想对主机控制器驱动和外设驱动进行分离。

这样的结构是,外设a、b、c的驱动与主机控制器A、B、C的驱动不相关,主机控制器驱动不关心外设,而外设驱动也不关心主机,外设只是访问核心层的通用的API进行数据传输,主机和外设之间可以进行任意的组合。

图12.4 Linux设备驱动的主机、外设驱动分离如果我们不进行如图12.4的主机和外设分离,外设a、b、c和主机A、B、C进行组合的时候,需要9个不同的驱动。

设想一共有m个主机控制器,n个外设,分离的结果是需要m+n个驱动,不分离则需要m*n个驱动。

Linux SPI、I2C、USB、ASoC(ALSA SoC)等子系统都典型地利用了这种分离的设计思想,在本章我们先以简单一些的SPI为例,而I2C、USB、ASoC等则在后续章节会进行详细介绍。

2 Linux SPI主机和设备驱动SPI(同步外设接口)是由摩托罗拉公司开发的全双工同步串行总线,其接口由MISO(串行数据输入),MOSI(串行数据输出),SCK(串行移位时钟),SS(从使能信号)四种信号构成,SS决定了唯一的与主设备通信的从设备,主设备通过产生移位时钟来发起通讯。

通讯时,数据由MOSI输出,MISO输入,数据在时钟的上升或下降沿由MOSI输出,在紧接着的下降或上升沿由MISO读入,这样经过8/16次时钟的改变,完成8/16位数据的传输。

SPI模块为了和外设进行数据交换,根据外设工作要求,其输出串行同步时钟极性(CPOL)和相位(CPHA)可以进行配置。

如果CPOL=0,串行同步时钟的空闲状态为低电平;如果CPOL=1,串行同步时钟的空闲状态为高电平。

如果CPHA=0,在串行同步时钟的第一个跳变沿(上升或下降)数据被采样;如果CPHA=1,在串行同步时钟的第二个跳变沿(上升或下降)数据被采样。

SPI接口时序如图12.5所示。

图12.5 SPI总线时序在Linux中,用代码清单12.12的spi_master结构体来描述一个SPI主机控制器驱动,其主要成员是主机控制器的序号(系统中可能存在多个SPI主机控制器)、片选数量、SPI 模式和时钟设置用到的函数、数据传输用到的函数等。

代码清单12.12 spi_master结构体1 struct spi_master {2 struct device dev;3 s16 bus_num;4 u16 num_chipselect;56 /* 设置模式和时钟*/7 int (*setup)(struct spi_device *spi);89 /* 双向数据传输*/10 int (*transfer)(struct spi_device *spi,11 struct spi_message *mesg);1213 void (*cleanup)(struct spi_device *spi);14 };分配、注册和注销SPI主机的API由SPI核心提供:struct spi_master * spi_alloc_master(struct device *host, unsigned size);int spi_register_master(struct spi_master *master);void spi_unregister_master(struct spi_master *master);在Linux中,用代码清单12.13的spi_driver结构体来描述一个SPI外设驱动,可以认为是spi_master的client驱动。

代码清单12.13 spi_driver结构体1 struct spi_driver {2 int (*probe)(struct spi_device *spi);3 int (*remove)(struct spi_device *spi);4 void (*shutdown)(struct spi_device *spi);5 int (*suspend)(struct spi_device *spi, pm_message_t mesg);6 int (*resume)(struct spi_device *spi);7 struct device_driver driver;8 };可以看出,spi_driver结构体和platform_driver结构体有极大的相似性,都有probe()、remove()、suspend()、resume()这样的接口。

是的,这几乎是一切client驱动的习惯模板。

在SPI外设驱动中,当透过SPI总线进行数据传输的时候,使用了一套与CPU无关的统一的接口。

这套接口的第1个关键数据结构就是spi_transfer,它用于描述SPI传输,如代码清单12.14。

代码清单12.14 spi_transfer结构体1 struct spi_transfer {2 const void *tx_buf;3 void *rx_buf;4 unsigned len;56 dma_addr_t tx_dma;7 dma_addr_t rx_dma;89 unsigned cs_change:1;10 u8 bits_per_word;11 u16 delay_usecs;12 u32 speed_hz;14 struct list_head transfer_list;15 };而一次完整的SPI传输流程可能不只包含1次spi_transfer,它可能包含1个或多个spi_transfer,这些spi_transfer最终通过spi_message组织在一起,其定义如代码清单12.15。

代码清单12.15 spi_message结构体1 struct spi_message {2 struct list_head transfers;34 struct spi_device *spi;56 unsigned is_dma_mapped:1;78 /* 完成被一个callback报告*/9 void (*complete)(void *context);10 void *context;11 unsigned actual_length;12 int status;1314 struct list_head queue;15 void *state;通过spi_message_init()可以初始化spi_message,而将spi_transfer添加到spi_message 队列的方法则是:void spi_message_add_tail(struct spi_transfer *t, struct spi_message *m);发起一次spi_message的传输有同步和异步两种方式,使用同步API时,会阻塞等待这个消息被处理完。

同步操作时使用的API是:int spi_sync(struct spi_device *spi, struct spi_message *message);使用异步API时,不会阻塞等待这个消息被处理完,但是可以在spi_message的complete 字段挂接一个回调函数,当消息被处理完成后,该函数会被调用。

异步操作时使用的API 是:int spi_async(struct spi_device *spi, struct spi_message *message);代码清单12.16是非常典型的初始化spi_transfer、spi_message并进行SPI数据传输的例子,同时它们也是SPI核心层的2个通用API,在SPI外设驱动中可以直接调用它们进行写和读操作。

代码清单12.16 SPI传输实例spi_write()、spi_read() API1 static inline int2 spi_write(struct spi_device *spi, const u8 *buf, size_t len)3 {4 struct spi_transfer t = {5 .tx_buf = buf,6 .len = len,7 };8 struct spi_message m;910 spi_message_init(&m);11 spi_message_add_tail(&t, &m);12 return spi_sync(spi, &m);13 }1415 static inline int16 spi_read(struct spi_device *spi, u8 *buf, size_t len)17 {18 struct spi_transfer t = {19 .rx_buf = buf,20 .len = len,21 };22 struct spi_message m;2324 spi_message_init(&m);25 spi_message_add_tail(&t, &m);26 return spi_sync(spi, &m);27 }LDD6410开发板所使用的S3C6410的SPI主机控制器驱动位于drivers/spi/spi_s3c.h和drivers/spi/spi_s3c.c这2个文件,其主体是实现了spi_master的setup()、transfer()等成员函数。

相关文档
最新文档