Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1020370
  • 博文数量: 244
  • 博客积分: 6820
  • 博客等级: 准将
  • 技术积分: 3020
  • 用 户 组: 普通用户
  • 注册时间: 2008-09-09 21:33
文章分类

全部博文(244)

文章存档

2013年(1)

2012年(16)

2011年(132)

2010年(3)

2009年(12)

2008年(80)

我的朋友

分类: LINUX

2008-09-15 20:43:03

Linux系统对ISA总线DMA的实现(上)

申明:这份文档是按照自由软件开放源代码的精神发布的,任何人可以免费获得、使用和重新发布,但是你没有限制别人重新发布你发布内容的权利。发布本文的目的是希望它能对读者有用,但没有任何担保,甚至没有适合特定目的的隐含的担保。更详细的情况请参阅GNU通用公共许可证(GPL),以及GNU自由文档协议(GFDL)

欢迎各位指出文档中的错误与疑问。

关键词:

LinuxI/OISA总线、设备驱动程序

DMA是一种无需CPU的参与就可以让外设与系统RAM之间进行双向(to device from device)数据传输的硬件机制。使用DMA可以使系统CPU从实际的I/O数据传输过程中摆脱出来,从而大大提高系统的吞吐率(throughput)。

由于DMA是一种硬件机制,因此它通常与硬件体系结构是相关的,尤其是依赖于外设的总线技术。比如:ISA卡的DMA机制就与PCI卡的DMA机制有区别。本站主要讨论ISA总线的DMA技术。

1.DMA概述

DMA是外设与主存之间的一种数据传输机制。一般来说,外设与主存之间存在两种数据传输方法:(1Pragrammed I/OPIO)方法,也即由CPU通过内存读写指令或I/O指令来持续地读写外设的内存单元(8位、16位或32位),直到整个数据传输过程完成。(2DMA,即由DMA控制器(DMA Controller,简称DMAC)来完成整个数据传输过程。在此期间,CPU可以并发地执行其他任务,当DMA结束后,DMAC通过中断通知CPU数据传输已经结束,然后由CPU执行相应的ISR进行后处理。

DMA技术产生时正是ISA总线在PC中流行的时侯。因此,ISA卡的DMA数据传输是通过ISA总线控制芯片组中的两个级联8237 DMAC来实现的。这种DMA机制也称为标准DMA”standard DMA)。标准DMA有时也称为第三方DMA”third-party DMA),这是因为:系统DMAC完成实际的传输过程,所以它相对于传输过程的前两方(传输的发送者和接收者)来说是第三方

标准DMA技术主要有两个缺点:(18237 DMAC的数据传输速度太慢,不能与更高速的总线(如PCI)配合使用。(2)两个8237 DMAC一起只提供了8DMA通道,这也成为了限制系统I/O吞吐率提升的瓶颈。

鉴于上述两个原因,PCI总线体系结构设计一种成为第一方DMA”first-party DMA)的DMA机制,也称为“Bus Mastering”(总线主控)。在这种情况下,进行传输的PCI卡必须取得系统总线的主控权后才能进行数据传输。实际的传输也不借助慢速的ISA DMAC来进行,而是由内嵌在PCI卡中的DMA电路(比传统的ISA DMAC要快)来完成。Bus Mastering方式的DMA可以让PCI外设得到它们想要的传输带宽,因此它比标准DMA功能满足现代高性能外设的要求。

随着计算机外设技术的不断发展,现代能提供更快传输速率的Ultra DMAUDMA)也已经被广泛使用了。本为随后的篇幅只讨论ISA总线的标准DMA技术在Linux中的实现。记住:ISA卡几乎不使用Bus Mastering模式的DMA;而PCI卡只使用Bus Mastering模式的DMA,它从不使用标准DMA

2.Intel 8237 DMAC

最初的 PCXT中只有一个8237 DMAC,它提供了48位的DMA通道(DMA channel 03)。从IBM AT开始,又增加了一个8237 DMAC(提供416位的DMA通道,DMA channel 47)。两个8237 DMAC一起为系统提供8DMA通道。与中断控制器8259的级联方式相反,第一个DMAC被级联到第二个DMAC上,通道4被用于DMAC级联,因此它对外设来说是不可用的。第一个DMAC也称为“slave DAMC”,第二个DMAC也称为“Master DMAC”

下面我们来详细叙述一下Intel 8237这个DMAC的结构。

每个8237 DMAC都提供4DMA通道,每个DMA通道都有各自的寄存器,而8237本身也有一组控制寄存器,用以控制它所提供的所有DMA通道。

21 DMA通道的寄存器

8237 DMAC中的每个DMA通道都有5个寄存器,分别是:当前地址寄存器、当前计数寄存器、地址寄存器(也称为偏移寄存器)、计数寄存器和页寄存器。其中,前两个是8237的内部寄存器,对外部是不可见的。

1)当前地址寄存器(Current Address Register):每个DMA通道都有一个16位的当前地址寄存器,表示一个DMA传输事务(Transfer Transaction)期间当前DMA传输操作的DMA物理内存地址。在每个DMA传输开始前,8237都会自动地用该通道的Address Register中的值来初始化这个寄存器;在传输事务期间的每次DMA传输操作之后该寄存器的值都会被自动地增加或减小。

2)当前计数寄存器(Current Count Register):每个每个DMA通道都有一个16位的当前计数寄存器,表示当前DMA传输事务还剩下多少未传输的数据。在每个DMA传输事务开始之前,8237都会自动地用该通道的Count Register中的值来初始化这个寄存器。在传输事务期间的每次DMA传输操作之后该寄存器的值都会被自动地增加或减小(步长为1)。

3)地址寄存器(Address Register)或偏移寄存器(Offset Register):每个DMA通道都有一个16位的地址寄存器,表示系统RAM中的DMA缓冲区的起始位置在页内的偏移。

4)计数寄存器(Count Register):每个DMA通道都有一个16位的计数寄存器,表示DMA缓冲区的大小。

5)页寄存器(Page Register):该寄存器定义了DMA缓冲区的起始位置所在物理页的基地址,即页号。页寄存器有点类似于PC中的段基址寄存器。

22 8237 DAMC的控制寄存器

1)命令寄存器(Command Register

这个8位的寄存器用来控制8237芯片的操作。其各位的定义如下图所示:

2)模式寄存器(Mode Register

用于控制各DMA通道的传输模式,如下所示:

3)请求寄存器(Request Register

用于向各DMA通道发出DMA请求。各位的定义如下:

4)屏蔽寄存器(Mask Register

用来屏蔽某个DMA通道。当一个DMA通道被屏蔽后,它就不能在服务于DMA请求,直到通道的屏蔽码被清除。各位的定义如下:

上述屏蔽寄存器也称为单通道屏蔽寄存器Single Channel Mask Register),因为它一次只能屏蔽一个通道。此外含有一个屏蔽寄存器,可以实现一次屏蔽所有4DMA通道,如下:

5)状态寄存器(Status Register

一个只读的8位寄存器,表示各DMA通道的当前状态。比如:DMA通道是否正服务于一个DMA请求,或者某个DMA通道上的DMA传输事务已经完成。

23 8237 DMACI/O端口地址

主、从8237 DMAC的各个寄存器都是编址在I/O端口空间的。而且其中有些I/O端口地址对于I/O读、写操作有不同的表示含义。如下表示所示:

Slave DMAC’s I/O port Master DMAC’sI/O port read write

0x000 0x0c0 Channel 0/4 Address Register

0x001 0x0c1 Channel 04Count Register

0x002 0x0c2 Channel 15 Address Register

0x003 0x0c3 Channel 15Count Register

0x004 0x0c4 Channel 26Address Register

0x005 0x0c5 Channel 26Count Register

0x006 0x0c6 Channel 37Address Register

0x007 0x0c7 Channel 37Count Register

0x008 0x0d0 Status Register Command Register

0x009 0x0d2 Request Register

0x00a 0x0d4 Single Channel Mask Register

0x00b 0x0d6 Mode Register

0x00c 0x0d8 Clear Flip-Flop Register

0x00d 0x0da Temporary Register Reset DMA controller

0x00e 0x0dc Reset all channel masks

0x00f 0x0de all-channels Mask Register

DMA通道的Page RegisterI/O端口空间中的地址如下:

DMA channel Page Register’sI/O port address

0 0x087

1 0x083

2 0x081

3 0x082

4 0x08f

5 0x08b

6 0x089

7 0x08a

注意两点:

1. DMA通道的Address Register是一个16位的寄存器,但其对应的I/O端口是8位宽,因此对这个寄存器的读写就需要两次连续的I/O端口读写操作,低8位首先被发送,然后紧接着发送高8位。

2. DMA通道的Count Register:这也是一个16位宽的寄存器(无论对于8DMA还是16DMA),但相对应的I/O端口也是8位宽,因此读写这个寄存器同样需要两次连续的I/O端口读写操作,而且同样是先发送低8位,再发送高8位。往这个寄存器中写入的值应该是实际要传输的数据长度减1后的值。在DMA传输事务期间,这个寄存器中的值在每次DMA传输操作后都会被减1,因此读取这个寄存器所得到的值将是当前DMA事务所剩余的未传输数据长度减1后的值。当DMA传输事务结束时,该寄存器中的值应该被置为0

24 DMA通道的典型使用

在一个典型的PC机中,某些DMA通道通常被固定地用于一些PC机中的标准外设,如下所示:

Channel Size Usage

0 8-bit Memory Refresh

1 8-bit Free

2 8-bit Floppy Disk Controller

3 8-bit Free

4 16-bit Cascading

5 16-bit Free

6 16-bit Free

7 16-bit Free

25 启动一个DMA传输事务的步骤

要启动一个DMA传输事务必须对8237进行编程,其典型步骤如下:

1.通过CLI指令关闭中断。

2.Disable那个将被用于此次DMA传输事务的DMA通道。

3.Flip-Flop寄存器中写入0值,以重置它。

4.设置Mode Register

5.设置Page Register

6.设置Address Register

7.设置Count Register

8.Enable那个将被用于此次DMA传输事务的DMA通道。

9.STI指令开中断。

 

Linux系统对ISA总线DMA的实现(下)

33 DMAC的保护

DMAC是一种全局的共享资源,为了保证设备驱动程序对它的独占访问,Linuxkerneldma.c文件中定义了自旋锁dma_spin_lock来保护它(实际上是保护DMACI/O端口资源)。任何想要访问DMAC的设备驱动程序都首先必须先持有自旋锁dma_spin_lock。如下:

static __inline__ unsigned long claim_dma_lock(void)

{

unsigned long flags;

spin_lock_irqsave(&dma_spin_lock, flags); /* 关中断,加锁*/

return flags;

}

 

static __inline__ void release_dma_lock(unsigned long flags)

{

spin_unlock_irqrestore(&dma_spin_lock, flags);/* 开中断,开锁*/

}

4 LinuxISA DMA通道资源的管理

DMA通道是一种系统全局资源。任何ISA外设想要进行DMA传输,首先都必须取得某个DMA通道资源的使用权,并在传输结束后释放所使用DMA通道资源。从这个角度看,DMA通道资源是一种共享的独占型资源。

Linuxkernel/Dma.c文件中实现了对DMA通道资源的管理。

41 DMA通道资源的描述

Linuxkernel/Dma.c文件中定义了数据结构dma_chan来描述DMA通道资源。该结构类型的定义如下:

struct dma_chan {

int lock;

const char *device_id;

};

其中,如果成员lock!=0则表示DMA通道正被某个设备所使用;否则该DMA通道就处于free状态。而成员device_id就指向使用该DMA通道的设备名字字符串。

基于上述结构类型dma_chanLinux定义了全局数组dma_chan_busy[],以分别描述8DMA通道资源各自的使用状态。如下:

static struct dma_chan dma_chan_busy[MAX_DMA_CHANNELS] = {

{ 0, 0 },

{ 0, 0 },

{ 0, 0 },

{ 0, 0 },

{ 1, ”cascade” },

{ 0, 0 },

{ 0, 0 },

{ 0, 0 }

};

显然,在初始状态时除了DMA通道4外,其余DMA通道皆处于free状态。

42 DMA通道资源的申请

任何ISA卡在使用某个DMA通道进行DMA传输之前,其设备驱动程序都必须向内核提出DMA通道资源的申请。只有申请获得成功后才能使用相应的DMA通道。否则就会发生资源冲突。

函数request_dma()实现DMA通道资源的申请。其源码如下:

int request_dma(unsigned int dmanr, const char * device_id)

{

if (dmanr >= MAX_DMA_CHANNELS)

return -EINVAL;

 

if (xchg(&dma_chan_busy[dmanr].lock, 1) != 0)

return -EBUSY;

 

dma_chan_busy[dmanr].device_id = device_id;

 

/* old flag was 0, now contains 1 to indicate busy */

return 0;

}

上述函数的核心实现就是用原子操作xchg()让成员变量dma_chan_busy[dmanr].lock和值1进行交换操作,xchg()将返回lock成员在交换操作之前的值。因此:如果xchg()返回非0值,这说明dmanr所指定的DMA通道已被其他设备所占用,所以request_dma()函数返回错误值-EBUSY表示指定DMA通道正忙;否则,如果xchg()返回0值,说明dmanr所指定的DMA通道正处于free状态,于是xchg()将其lock成员设置为1,取得资源的使用权。

43 释放DMA通道资源

DMA传输事务完成后,设备驱动程序一定要记得释放所占用的DMA通道资源。否则别的外设将一直无法使用该DMA通道。

函数free_dma()释放指定的DMA通道资源。如下:

void free_dma(unsigned int dmanr)

{

if (dmanr >= MAX_DMA_CHANNELS) {

printk(”Trying to free DMA%d

”, dmanr);

return;

}

 

if (xchg(&dma_chan_busy[dmanr].lock, 0) == 0) {

printk(”Trying to free free DMA%d

”, dmanr);

return;

}

 

} /* free_dma */

显然,上述函数的核心实现就是用原子操作xchg()lock成员清零。

44 /proc/dma文件的实现

文件/proc/dma将列出当前8DMA通道的使用状况。Linuxkernel/Dma.c文件中实现了函数个get_dma_list()函数来至此/proc/dma文件的实现。函数get_dma_list()的实现比较简单。主要就是遍历数组dma_chan_busy[],并将那些lock成员为非零值的数组元素输出到列表中即可。如下:

int get_dma_list(char *buf)

{

int i, len = 0;

 

for (i = 0 ; i < MAX_DMA_CHANNELS ; i++) {

if (dma_chan_busy.lock) {

len += sprintf(buf+len, ”%2d: %s

”,

i,

dma_chan_busy.device_id);

}

}

return len;

} /* get_dma_list */

5 使用DMAISA设备驱动程序

DMA虽然是一种硬件机制,但它离不开软件(尤其是设备驱动程序)的配合。任何使用DMA进行数据传输的ISA设备驱动程序都必须遵循一定的框架。

51 DMA通道资源的申请与释放

I/O端口资源类似,设备驱动程序必须在一开始就调用request_dma()函数来向内核申请DMA通道资源的使用权。而且,最好在设备驱动程序的open()方法中完成这个操作,而不是在模块的初始化例程中调用这个函数。因为这在一定程度上可以让多个设备共享DMA通道资源(只要多个设备不同时使用一个DMA通道)。这种共享有点类似于进程对CPU的分时共享:-)

设备使用完DMA通道后,其驱动程序应该记得调用free_dma()函数来释放所占用的DMA通道资源。通常,最好再驱动程序的release()方法中调用该函数,而不是在模块的卸载例程中进行调用。

还需要注意的一个问题是:资源的申请顺序。为了避免死锁(deadlock),驱动程序一定要在申请了中断号资源后才申请DMA通道资源。释放时则要先释放DMA通道,然后再释放中断号资源。

使用DMAISA设备驱动程序的open()方法的如下:

int xxx_open(struct inode * inode, struct file * filp)

{

if((err = request_irq(irq,xxx_ISR,SA_INTERRUPT,”YourDeviceName”,NULL))

return err;

if((err = request_dma(dmanr, “YourDeviceName”)){

free_irq(irq, NULL);

return err;

}

return 0;

}

 

release()方法的范例代码如下:

 

void xxx_release(struct inode * inode, struct file * filp)

{

free_dma(dmanr);

free_irq(irq,NULL);

}

52 申请DMA缓冲区

由于8237 DMAC只能寻址系统RAM中低16MB物理内存,因此:ISA设备驱动程序在申请DMA缓冲区时,一定要以GFP_DMA标志来调用kmalloc()函数或get_free_pages()函数,以便在系统内存的DMA区中分配物理内存。

53 编程DMAC

设备驱动程序可以在他的read()方法、write()方法或ISR中对DMAC进行编程,以便准备启动一个DMA传输事务。一个DMA传输事务有两种典型的过程:(1)用户请求设备进行DMA传输;(2)硬件异步地将外部数据写道系统中。

用户通过I/O请求触发设备进行DMA传输的步骤如下:

1.用户进程通过系统调用read()write()来调用设备驱动程序的read()方法或write()方法,然后由设备驱动程序readwrite方法负责申请DMA缓冲区,对DMAC进行编程,以准备启动一个DMA传输事务,最后正确地设置设备(setup device),并将用户进程投入睡眠。

2.DMAC负责在DMA缓冲区和I/O外设之间进行数据传输,并在结束后触发一个中断。

3.设备的ISR检查DMA传输事务是否成功地结束,并将数据从DMA缓冲区中拷贝到驱动程序的其他内核缓冲区中(对于I/O device to memory的情况)。然后唤醒睡眠的用户进程。

硬件异步地将外部数据写到系统中的步骤如下:

1.外设触发一个中断通知系统有新数据到达。

2.ISR申请一个DMA缓冲区,并对DMAC进行编程,以准备启动一个DMA传输事务,最后正确地设置好外设。

3.硬件将外部数据写到DMA缓冲区中,DMA传输事务结束后,触发一个中断。

4. ISR检查DMA传输事务是否成功地结束,然后将DMA缓冲区中的数据拷贝驱动程序的其他内核缓冲区中,最后唤醒相关的等待进程。

网卡就是上述过程的一个典型例子。

为准备一个DMA传输事务而对DMAC进行编程的典型代码段如下:

  unsigned long flags;

  flags = claim_dma_lock();

  disable_dma(dmanr);

  clear_dma_ff(dmanr);

  set_dma_mode(dmanr,mode);

  set_dma_addr(dmanr, virt_to_bus(buf));

  set_dma_count(dmanr, count);

  enable_dma(dmanr);

  release_dma_lock(flags);

检查一个DMA传输事务是否成功地结束的代码段如下:

int residue;

unsigned long flags = claim_dma_lock();

residue = get_dma_residue(dmanr);

release_dma_lock(flags);

ASSERT(residue == 0);

注:本节大部分内容来自于ldd2

 

 

阅读(972) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~