科技行者

行者学院 转型私董会 科技行者专题报道 网红大战科技行者

知识库

知识库 安全导航

至顶网软件频道Linux系统对ISA总线DMA的实现(中) (3)

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

  • 扫一扫
    分享文章到微信

  • 扫一扫
    关注官方公众号
    至顶头条

由于DMAC的各寄存器是在I/O端口空间中编址的,因此读写8237 DMAC是平台相关的。对于x86平台来说,Linux在include/asm-i386/Dma.h头文件中实现了对两个8237 DMAC的读写操作。

作者:hehyuan 来源:赛迪网技术社区 2007年11月17日

关键字: 操作系统 实现 ISA Linux

  • 评论
  • 分享微博
  • 分享邮件
 

(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);
}
    • 评论
    • 分享微博
    • 分享邮件
    邮件订阅

    如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。

    重磅专题
    往期文章
    最新文章