linux中dma的实现方法,在Linux操作系统中对ISA总线DMA的实现方法二

Emperor 于 2006-11-30 09:50:28发表:

(4)为DMA通道设置DMA缓冲区的起始物理地址和大小

由于8237中的DMA通道是通过一个8位的Page Register和一个16位的Address Register来寻址位于系统RAM中的DMA缓冲区,因此8237 DMAC最大只能寻址系统RAM中物理地址在0x000000~0xffffff范围内的DMA缓冲区,也即只能寻址物理内存的低16MB(24位物理地址)。反过来讲,Slave/Master 8237 DMAC又是如何寻址低16MB中的物理内存单元的呢?

首先来看Slave 8237 DMAC(即第一个8237 DMAC)。由于Slave 8237 DMAC是一个8位的DMAC,因此DMA通道0~3在一次DMA传输操作(一个DMA传输事务又多次DMA传输操作组成)中只能传输8位数据,即一个字节。Slave 8237 DMAC将低16MB物理内存分成256个64K大小的页(Page),然后用Page Register来表示内存单元物理地址的高8位(bit[23:16]),也即页号;用Address Register来表示内存单元物理地址在一个Page(64KB大小)内的页内偏移量,也即24位物理地址中的低16位(bit[15:0])。由于这种寻址机制,因此DMA通道0~3的DMA缓冲区必须在一个Page之内,也即DMA缓冲区不能跨越64KB页边界。

再来看看Master 8237 DMAC(即第二个8237 DMAC)。这是一个16位宽的DMAC,因此DMA通道5~7在一次DMA传输操作时可以传输16位数据,也即一个字word。此时DMA通道的Count Register(16位宽)表示以字计的待传输数据块大小,因此数据块最大可达128KB(64K个字),也即系统RAM中的DMA缓冲区最大可达128KB。由于一次可传输一个字,因此Master 8237 DMAC所寻址的内存单元的物理地址肯定是偶数,也即物理地址的bit[0]肯定为0。此时物理内存的低16MB被化分成128个128KB大小的page,Page Register中的bit[7:1]用来表示页号,也即对应内存单元物理地址的bit[23:17],而Page Register的bit[0]总是被设置为0。Address Register用来表示内存单元在128KB大小的Page中的页内偏移,也即对应内存单元物理地址的bit[16:1](由于此时物理地址的bit[0]总是为0,因此不需要表示)。由于Master 8237 DMAC的这种寻址机制,因此DMA通道5~7的DMA缓冲区不能跨越128KB的页边界。

下面我们来看看Linux是如何实现为各DMA通道设置其Page寄存器的。NOTE!DMA通道5~7的Page Register中的bit[0]总是为0。如下所示:引用:

static __inline__ void set_dma_page(unsigned int dmanr, char pagenr)

{

switch(dmanr) {

case 0:

dma_outb(pagenr, DMA_PAGE_0);

break;

case 1:

dma_outb(pagenr, DMA_PAGE_1);

break;

case 2:

dma_outb(pagenr, DMA_PAGE_2);

break;

case 3:

dma_outb(pagenr, DMA_PAGE_3);

break;

case 5:

dma_outb(pagenr & 0xfe, DMA_PAGE_5);

break;

case 6:

dma_outb(pagenr & 0xfe, DMA_PAGE_6);

break;

case 7:

dma_outb(pagenr & 0xfe, DMA_PAGE_7);

break;

}

}

在上述函数的基础上,函数set_dma_addr()用来为特定DMA通道设置DMA缓冲区的基地址,传输dmanr指定DMA通道号,传输a指定位于系统RAM中的DMA缓冲区起始位置的物理地址。如下:

引用:

/* Set transfer address & page bits for specific DMA channel.

* Assumes dma flipflop is clear.

*/

static __inline__ void set_dma_addr(unsigned int dmanr, unsigned int a)

{

set_dma_page(dmanr, a>>16);

if (dmanr <= 3) {

dma_outb( a & 0xff, ((dmanr&3)<<1) + IO_DMA1_BASE );

dma_outb( (a>>8) & 0xff, ((dmanr&3)<<1) + IO_DMA1_BASE );

} else {

dma_outb( (a>>1) & 0xff, ((dmanr&3)<<2) + IO_DMA2_BASE );

dma_outb( (a>>9) & 0xff, ((dmanr&3)<<2) + IO_DMA2_BASE );

}

}

函数set_dma_count()为特定DMA通道设置其Count Register的值。传输dmanr指定DMA通道,传输count指定待传输的数据块大小(以字节计),实际写到Count Register中的值应该是count-1。如下所示:

引用:

static __inline__ void set_dma_count(unsigned int dmanr, unsigned int count)

{

count--;

if (dmanr <= 3) {

dma_outb( count & 0xff, ((dmanr&3)<<1) + 1 + IO_DMA1_BASE );

dma_outb( (count>>8) & 0xff, ((dmanr&3)<<1) + 1 + IO_DMA1_BASE );

} else {

dma_outb( (count>>1) & 0xff, ((dmanr&3)<<2) + 2 + IO_DMA2_BASE );

dma_outb( (count>>9) & 0xff, ((dmanr&3)<<2) + 2 + IO_DMA2_BASE );

}

}

函数get_dma_residue()获取某个DMA通道上当前DMA传输事务的未传输剩余数据块的大小(以字节计)。DMA通道的Count Register的值在当前DMA传输事务进行期间会不断地自动将减小,直到当前DMA传输事务完成,Count Register的值减小为0。如下:

引用:

static __inline__ int get_dma_residue(unsigned int dmanr)

{

unsigned int io_port = (dmanr<=3)? ((dmanr&3)<<1) + 1 + IO_DMA1_BASE

: ((dmanr&3)<<2) + 2 + IO_DMA2_BASE;

/* using short to get 16-bit wrap around */

unsigned short count;

count = 1 + dma_inb(io_port);

count += dma_inb(io_port) << 8;

return (dmanr<=3)? count : (count<<1);

}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值