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

- by 宋宝华(Barry Song)
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;

5

6 /* 设置模式和时钟 */

7 int (*setup)(struct spi_device *spi);

8

9 /* 双向数据传输 */

10 int (*transfer)(struct spi_device *spi,

11 struct spi_message *mesg);

12

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

5

6 dma_addr_t tx_dma;

7 dma_addr_t rx_dma;

8

9 unsigned cs_change:1;

10 u8 bits_per_word;

11 u16 delay_usecs;

12 u32 speed_hz;

13

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;

3

4 struct spi_device *spi;

5

6 unsigned is_dma_mapped:1;

7

8 /* 完成被一个callback报告 */

9 void (*complete)(void *context);

10 void *context;

11 unsigned actual_length;

12 int status;

13

14 struct list_head queue;

15 void *state;

16 };

通过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() API

1 static inline int

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

9

10 spi_message_init(&m);

11 spi_message_add_tail(&t, &m);

12 return spi_sync(spi, &m);

13 }

14

15 static inline int

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

23

24 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()等成员函数。

SPI外设驱动遍布于内核的drivers、sound的各个子目录之下,SPI只是一种总线,spi_driver的作用只是将SPI外设挂接在该总线上,因此在spi_driver的probe()成员函数中,将注册SPI外设本身所属设备驱动的类型。

和platform_driver对应着一个platform_device一样,spi_driver也对应着一个spi_device;platform_device需要在BSP的板文件中添加板信息数据,而spi_device也同样需要。spi_device的板信息用spi_board_info结构体描述,该结构体记录SPI外设使用的主机控制器序号、片选序号、数据比特率、SPI传输模式(即CPOL、CPHA)等。如诺基亚770上2个SPI设备的板信息数据如代码清单12.17,位于板文件arch/arm/mach-omap1/board-nokia770.c。

代码清单12.17诺基亚770板文件中的spi_board_info

1 static struct spi_board_info nokia770_spi_board_info[] __initdata = {

2 [0] = {

3 .modalias = "lcd_mipid",

4 .bus_num = 2, /* 用到的SPI主机控制器序号 */

5 .chip_select = 3, /* 使用哪一号片选 */

6 .max_speed_hz = 12000000, /* SPI数据传输比特率 */

7 .platform_data = &nokia770_mipid_platform_data,

8 },

9 [1] = {

10 .modalias = "ads7846",

11 .bus_num = 2,

12 .chip_select = 0,

13 .max_speed_hz = 2500000,

14 .irq = OMAP_GPIO_IRQ(15),

15 .platform_data = &nokia770_ads7846_platform_data,

16 },

17 };

在Linux启动过程中,在机器的init_machine()函数中,会通过如下语句注册这些spi_board_info:

spi_register_board_info(nokia770_spi_board_info,

ARRAY_SIZE(nokia770_spi_board_info));

这一点和启动时通过platform_add_devices()添加platform_device非常相似。


本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/21cnbao/archive/2010/06/15/5672888.aspx

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Linux外设驱动是一个非常重要的话题,因为它涉及到Linux操作系统与外设之间的交互。在这个视频中,我们会详细讲解有关Linux外设驱动的方方面面。 首先,我们会介绍Linux操作系统和外设之间的基本交互方式。这包括输入/输出(IO)操作、设备文件、设备树等知识点。通过这些基本概念的介绍,你可以理解Linux如何与外设之间进行通讯并实现数据的传输。 其次,我们会重点讲解如何编写Linux外设驱动。这里我们涉及到的主要内容包括Linux模块编程、设备驱动框架、外设控制器驱动等。你将学习到如何编写一个简单的外设驱动,如何添加一个新的设备到Linux驱动中等等。 同时,这个视频还会介绍Linux外设驱动的一些高级话题。比如中断处理、大页内存管理、DMA数据传输等等。这些内容不仅是深入理解Linux外设驱动的关键,也是能够编写高性能、高可靠性驱动的必要知识。 最后,我们还会提供一些实际的案例和应用场景,让你更深入了解如何在实际项目中应用Linux外设驱动。 总的来说,这个视频将提供全面、深入、平易近人的介绍Linux外设驱动的知识。如果你想更好地理解Linux操作系统和外设之间的交互,或者想深入研究嵌入式系统的开发,这个视频绝对是你不容错过的。 ### 回答2: Linux操作系统是一种自由和开放的操作系统,可以轻松安装在任何计算机上。与其他操作系统不同,Linux支持许多不同的外设,并且需要相应的驱动程序来支持这些设备。因此,外设驱动程序是Linux操作系统的重要组成部分。 一个外设驱动程序是一个在操作系统内核中运行的模块,它提供特定设备的功能和控制。每种外设都有自己的驱动程序。例如,磁盘驱动程序控制磁盘硬件,网络驱动程序控制网络接口卡,USB驱动程序控制连接到计算机的USB设备。这些驱动程序允许操作系统通过统一的接口使用硬件设备,使用户可以轻松地与电脑进行互动。 在Linux中,外设驱动程序分为两类:内核驱动程序和用户态驱动程序。内核驱动程序运行在操作系统内核中,可用于控制硬件。用户态驱动程序在用户空间中运行,可用于控制硬件设备。用户态驱动程序通常被称为应用程序程序,因为它们是为用户编写的。 在安装Linux时,通常会自动为系统中的硬件设备安装对应的驱动程序。但是,有时候硬件设备并不是Linux系统默认支持的,所以需要手动安装外设驱动程序以支持设备的工作。为此,开发者可以使用C语言或脚本语言,编写自己的驱动程序,以此支持适配新硬件设备。 总而言之,Linux操作系统的灵活性和自由性来自于它对外设驱动程序的支持。这种支持根据硬件设备的不同,可以的扩展支持面。因此,学习如何操作外设驱动程序对于任何Linux狂热爱好者和开发人员都是非常重要的。 ### 回答3: 在Linux系统中,外设驱动是连接硬件设备和操作系统之间的桥梁。在本视频中,我们将为您介绍Linux下的外部设备驱动程序,并说明如何编写和安装这些驱动程序。 首先,我们将讨论Linux内核和外部设备之间的交互方式。内核中有专门的设备驱动框架来协调设备的访问。为了使外部设备正常工作,需要编写一个特定于设备类型的驱动程序,并与内核中的框架进行交互。一旦驱动程序被编写并安装,它将能够与设备通信,从而使设备正常工作。 在本视频中,我们还将讨论Linux设备驱动程序中的编程概念和技术。首先,我们将介绍Linux设备树,并说明如何使用设备树绑定技术来连接硬件和内核。随后,我们将讨论一些基本的驱动程序概念,如中断处理程序、设备文件和字符设备驱动程序等。我们还将介绍Linux驱动程序模块的概念,这使得驱动程序可以动态地添加和删除。 最后,我们将向您展示如何安装驱动程序并使外部设备正常工作。我们将使用一个示例驱动程序来演示每个步骤,并向您展示如何编写、编译和安装该驱动程序。在这个过程中,我们将以实际的编程方式来介绍Linux外设驱动程序的基本知识和技术。 总之,本视频为您介绍了Linux外设驱动程序的基本知识和技术,并向您展示了如何编写和安装这些驱动程序。我相信本视频会给您带来非常有用的技能和知识,使您能够更好地理解和掌握Linux操作系统。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值