ISA DMA
- 有关ISA DMA的要点有:
- ISA DMA与PCI总线主控DMA不是一回事;
- ISA DMA通道1、2和3可用于8位传输到ISA外围设备;
- ISA DMA通道5、6和7可用于向ISA外围设备进行16位传输;
- 传输不得跨越物理64KB边界,且不得大于64KB;
- 传输必须是物理上连续的,并且只能针对最低的16 MB物理内存;
- ISA DMA速度很慢——理论上是4.77 MB/秒,但由于ISA总线协议,速度更接近400 KB/秒;
- ISA DMA释放了CPU资源,但给内存总线增加了极重的负载;
- 目前还在使用ISA DMA的设备很少 -- 只有内部软盘、一些嵌入式声音芯片、一些并行端口和一些串行端口。
注:
- Sound Blaster和Sound Blaster PRO仅支持8位DMA;
- Sound Blaster16+两者都支持;
- 软盘控制器仅支持8位DMA,并通过硬连线使用DMA通道2。
PC上有多种DMA
现代PCI控制器总是有自己的 “总线控制DMA(Busmastering DMA)”,这远远优于ISA DMA。 甚至USB软驱也通过PCI USB控制器使用PCI Busmastering发送DMA数据。 PCI Busmasters可以使用32位寻址访问内存。 较新的PCI卡开始支持64位寻址 (尽管目前大多数还不支持)。 通常PCI卡使用“scatter-gather”总线管理,其中单页用作数据页的目录。 这几乎完全克服了所有形式的DMA“仅限物理内存”的限制。
ISA DMA背景
与ISA本身一样,ISA DMA(“Industry Standard Architecture Direct Memory Access”)也是现代PC架构的早期产物。 它由内部软盘控制器、ISA声卡、ISA网卡和并行端口(如果它们支持ECP模式)使用。 尽管中断,键盘和计时器接口电路具有相关明显的用途,但ISA DMA控制器及其编程接口仍然基本停留在最初设计的20世纪70年代中。
DMA背后的想法是,你可以设置一个“通道”,地址指向内存,数据传输的长度。 一旦设置好,CPU就可以告诉拥有通道的外设独立去做它应该做的事情(例如,读取扇区)。 然后中央处理器可以去做别的事情。 当CPU不使用内存总线时,DMA芯片在外围设备和内存之间接管并传输数据,而不涉及CPU。 当传输完成时(例如,整个扇区已被发送到软盘驱动器),DMA芯片随后发出完成的信号。 如果数据用完,DMA芯片甚至可以发出信号,从而使系统能够在同一DMA事务上定位要传输的下一个数据块。 DMA可以大大提高系统的速度,它是由Intel(设计DMA控制器芯片的人)从上世纪60年代的旧大型机上借来的,当时哪些大型机的所有设备都有DMA通道(CPU当时非常慢,且不全在一个芯片上)。
当然,所有好的想法都会有不利之处,虽然Intel不能真的为即将被描述的事情而受到指责,但IBM肯定要背这个锅。
一开始有PC,但是PC很慢。 IBM从天堂上往下看,说“装上DMA控制器——这应该会加快速度。” IBM的用心是正确的; 但是DMA的控制大脑总是在状况外,导致DMA控制器一直无法匹配系统需求。 PC/AT标准包含2块Intel 8237A DMA芯片,作为主/从芯片连接。 第二个芯片是Master,它的第一条线路(通道4)由第一个芯片Slave使用。 (这与中断控制器不同,中断控制中第一个芯片是Master。) 8237A是为旧的8080 8位处理器设计的,这可能是造成如此多DMA问题的主要原因。 IBM为其PC选择的8088和8086处理器相对于DMA控制器来说太先进了。
前面提到DMA控制器应该能够发出完成信号,或者甚至能发出请求需要更多信息。 不幸的是,这会使扩展槽太大,所以IBM切断了与DMA芯片的所有连接。 知道传输何时完成的唯一时间是外设发出中断信号。 这意味着所有使用ISA DMA通道的外围设备都被限制为不超过64 KB的传输,因为担心会扰乱DMA控制器。
即使在PC/AT中,IBM也开始绕过PC/XT中使用的ISA DMA,并对硬盘使用ATA PIO Mode。 正是因为上面概述的64KB限制,以及286处理器可以在6 MHz下执行16位事务的事实。 甚至ISA总线也可以以高达12 MHz的速度运行,远远快于DMA控制器运行的4.77 MHz。
扩展卡设计人员也对DMA的功能不足感到不满,尤其是依赖数据传输速度的“硬盘”扩展卡制造商。
为了绕过“板载”DMA控制器的限制,扩展卡制造商开始在他们的扩展卡上安装他们自己的DMA控制器。 它们的功能与 “机上” DMA完全相同,在处理器不看时 “窃取内存总线周期”,从而提高了整个系统的性能。 这些“ISA总线主控器”传输通常仍限于较低的16 MiB内存,但没有4.77 MHz的问题。 这一趋势通过PCI总线的创建继续发展,PCI总线也最终完全取代了PC中的ISA总线。
技术细节
每个8237 DMA芯片有4个DMA通道。 第一个芯片上有DMA0-DMA3,第二个芯片上有DMA4-DMA7。 第一个DMA控制器连接起来进行8位传输,而第二个连接起来进行16位传输。 在一些教程或其他wiki文章中,你有时会看到“第二个”DMA芯片(通道4到7)标记为“主”控制器,第一个(通道0到3)标记为“从”。 这非常令人困惑,这里不会再使用这些术语。
DMA通道0不可用,因为它在短时间内用于DRAM内存刷新,因此保留 (即使现代计算机不使用它)。 DMA通道4不能用于外围设备,因为它用于级联其他DMA控制器。
DMA控制器的内部地址寄存器只有16位。 为了扩展这一点,IBM在每个通道增加了一个 “外部(external)” “页面寄存器(page register)” 字节,允许访问16 MB的内存 (总共24位)。 如果DMA传输跨越64 KB的边界,内部地址寄存器将变为零,外部页面寄存器将“不”递增。 这时DMA控制器将用它在新地址找到的任何数据继续传输。
基于ISA的DMA控制器被指定为以4.77 MHz运行。没有例外。 如果系统的“前端”(内存)总线以133 MHz的频率运行,在传输每个ISA DMA字节/字时,它将被人为地降低到4.77 MHz。 这包括EISA和PS/2 32位控制器,即使这些控制器具有额外的页面寄存器(允许4GiB寻址空间)和执行32位传输的能力。 (这些DMA控制器仅存在于EISA和MCA系统上,这两个系统现已过时,此处不再进一步介绍。)== 编程细节 ==
16位问题
16位通道(5、6和7)具有特殊的寻址方案来处理它们递增地址的方式。 内部寄存器增加1,但每次访问之间需要将内存地址增加2。 解决方案是,CPU存储在DMA控制器中的“起始地址”需要右移1位。 在每次存储器访问时,该内部16位地址值递增1,然后取该值并将其左移一位(清除低位),然后将其用作地址。 然后以正常方式附加外部 “页寄存器” 寻址字节。 重要的是要注意,内部地址的高位在向左移位时是“丢失的”——它不会被或运算到页面寄存器字节中。 因为地址寄存器将有效位绕回到零,而不会递增页面寄存器字节,这会阻止大于64 KB的DMA传输工作,即使它们在技术上对于16位通道是可行的。
物理内存与分页
分页内存映射完全由CPU控制。 DMA的全部意义在于绕过CPU。(译者注:也就不知道MMU分页表的存在) 因此,DMA不可以访问任何虚拟内存地址。 所有DMA始终仅在物理内存地址上完成。 ISA DMA的物理地址限制为16 MB。
由于DMA独立于CPU运行,因此重要的是,OS要为为DMA传输分配一个连续的物理内存块,以防止该内存用于任何其他目的或交换出去,直到DMA传输完成。
注:VM86模式不使用“物理”寻址。 内存地址是“假的”。 在VM86模式下,操作系统必须代表应用程序模拟任何DMA事务。
缓冲区大小
一张典型的1.44MB软盘可以在一次传输中轻松地传输36个扇区的数据。 这只有18 KB。 最大的内部软盘使用最差格式,一次可以传输84个扇区。 这仍然只有42KB。 Soundblaster卡使用64 KB缓冲区可以最佳运行。 从来没有必要尝试将缓冲区ISA DMA传输加倍,因为它们无论如何都非常慢。 最多有6个可用DMA通道,不需要为每个通道分配完整的64KB。 将所有这些放在一起,任何操作系统都应该能够轻松地从256 KB的池中分配所需的所有ISA DMA物理内存,甚至有时只需一半。
Flip-Flop
PC上的许多设备(例如ATA磁盘驱动器)使用8位IO端口来接收16位值。 这是使用flip-flop完成的。 设备首先需要低字节。 一旦接收到一个字节,flip-flop就会改变状态,然后器件就会等待高位字节。 当接收到高字节时,flip-flop再次改变状态,并且设备期望一个新的低字节。 通常,每个16位“寄存器”都有自己的Flip-Flop,但ISA DMA控制器在这方面存在问题。
在8237芯片上,只有一个Flip-Flop。 有8个16位寄存器。 最多可以有三个设备驱动程序同时竞争使用一个触发器。
这就产生了两个严重的问题。 一是 “争用问题”。 另一个问题是,很难确定触发器当前处于什么状态。 处理触发器状态问题的标准解决方案是在你每次想要使用Flip-Flop时将其重置为“低字节”状态,这样你就可以在发送字节之前确定它处于正确的状态。 “争用” 只有两种解决方案: 要么使用 锁,要么只允许存在一个ISA DMA驱动程序,则无法进行争用。
屏蔽DRQ(DMA Request)
设置DMA传输总是需要设置传输的两端。 也就是说,无论哪个外围设备拥有DMA通道,都需要被告知如何通过DMA传输数据块。 DMA控制器需要被告知内存地址、传输长度、传输“模式”和传输方向(读或写)。 因此,外围设备和内存两者中总有一个需要“先”完成--通常是外围设备在准备好传输第一个字节之前有很长的延迟时间。 如果首先设置外围设备,则在设置DMA控制器的过程中,它的第一个DMA请求信号 (通常称为DMA request signal-DRQ) 就可能会到达。
答案是在初始化DMA控制器时屏蔽特定信道的DRQ。 下面会介绍三种临时禁用通道的方式。
传输长度
存储在每个计数寄存器中的值始终是传输长度(字节或字)“-1”。 如果你忘了减去1,你在传输时会出错。
完成时中断
在PC机上实现时,DMA控制器不能发送中断。 希望在传输完成时,无论哪个外设“拥有”的每个DMA通道,都会向CPU发送中断。 但是,如果传输失败并出现错误,某些外围设备可能会 “不” 发送中断。 和其它类似场景一样,有超时策略很重要。
寄存器
主从DMA控制器非常相似,因此 (为了节省空间) 它们都已合并到下表中。 希望你不要为这种排版困惑。 注:有关通道5至7上的地址和计数寄存器,请参见上面的16位问题。
每个8237A都有18个寄存器,通过I/O端口总线寻址:
Channels 0-3 | Channels 4-7 | |||
---|---|---|---|---|
IO Port | IO Port | Size | Read or Write | Function |
0x00 | 0xC0 | Word | W | 起始地址寄存器通道0/4(不可用) |
0x01 | 0xC2 | Word | W | 计数寄存器通道0/4(不可用) |
0x02 | 0xC4 | Word | W | 开始地址寄存器通道1/5 |
0x03 | 0xC6 | Word | W | 计数寄存器通道1/5 |
0x04 | 0xC8 | Word | W | 起始地址寄存器通道2/6 |
0x05 | 0xCA | Word | W | 计数寄存器通道2/6 |
0x06 | 0xCC | Word | W | 起始地址寄存器通道3/7 |
0x07 | 0xCE | Word | W | 计数寄存器通道3/7 |
0x08 | 0xD0 | Byte | R | 状态寄存器 |
0x08 | 0xD0 | Byte | W | 命令寄存器 |
0x09 | 0xD2 | Byte | W | 请求寄存器 |
0x0A | 0xD4 | Byte | W | 单通道屏蔽寄存器 |
0x0B | 0xD6 | Byte | W | 模式寄存器 |
0x0C | 0xD8 | Byte | W | Flip-Flop复位寄存器 |
0x0D | 0xDA | Byte | R | 中间(Intermediate)寄存器 |
0x0D | 0xDA | Byte | W | 主复位寄存器 |
0x0E | 0xDC | Byte | W | 屏蔽复位寄存器 |
0x0F | 0xDE | Byte | RW | 多通道屏蔽寄存器 (文章中没有说明有读取操作,但可以读!) |
每个通道还有一个外部R/W页地址寄存器,还要和24位传输存储器地址的上8位组成数据:
0x87 | 通道0页地址寄存器(不可用) |
0x83 | 通道1页地址寄存器 |
0x81 | 通道2页面地址寄存器 |
0x82 | 通道3页地址寄存器 |
0x8F | 通道 4页面地址寄存器 (不可用) |
0x8B | 通道5页面地址寄存器 |
0x89 | 通道6页地址寄存器 |
0x8A | 通道7页面地址寄存器 |
有用的寄存器说明
- 单通道掩码寄存器0x0A和0xD4(写入)
Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 |
MASK_ON | SEL 1 | SEL 0 |
这些寄存器仅用于在主DMA芯片或从DMA芯片上屏蔽(或取消屏蔽)单个通道的DRQ。
也就是说,如果你不想计算出所有其他通道的屏蔽状态,你可以一次屏蔽/取消屏蔽一个通道的DRQ。
使用SEL 0和1位选择通道,并使用MASK_ON位为其设置或清除屏蔽。
注意,由于级联关系,屏蔽DMA信道4将屏蔽7、6、5和4。
- 多通道掩码寄存器0x0F和0xDE(读和写)
Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 |
MASK3 | MASK2 | MASK1 | MASK0 |
将适当的位设置为0或1允许你取消屏蔽或屏蔽 (分别) 这些通道的DRQ。 使用此寄存器意味着你的驱动程序需要知道此时“所有”通道的所需掩码状态。 有几种方法可以做到这一点,这里可以考虑简单地读取该寄存器。 注意,由于级联,屏蔽DMA通道4将屏蔽7、6、5和4。
- DMA模式寄存器0x0B和0xD6(写入)
Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 |
MOD1 | MOD0 | DOWN | AUTO | TRA1 | TRA0 | SEL1 | SEL0 |
设置该寄存器有点棘手,因为它在很大程度上取决于要为其编程DMA控制器的外设。 但是,外围设备的驱动程序是需要设置此寄存器的主体,它应该知道外围设备需要什么模式。
- SEL0和SEL1选择要更改的通道;
- TRA0和TRA1选择传输类型;
- 0b00 运行控制器的自检;
- 0b01 外围设备正在写入内存;
- 0b10 外设正在从存储器中读取;
- 0b11无效。
- AUTO:设置此位时,在传输完成后,通道将自身重置为编程到其中的地址和计数值。 这对于软盘传输非常有用。 在磁道中读的时候-这些值可以设置好它们,以便立即再次读数据。 对于写操作,你只需要更改传输模式,而不需要更改地址。 某些扩展卡不支持自动初始化DMA,如Sound Blaster 1.x。 如果与auto-init DMA一起使用,这些设备将崩溃。 Sound Blaster 2.0及更高版本确实支持自动初始化DMA。
- DOWN:设置时颠倒数据的内存顺序。 从高地址到低地址访问内存 (每次传输之间地址递减)。
- MOD0和MOD1:基于DMA控制器所连接的外围设备,这可能会出现一些问题。 DMA控制器有几种模式:
- 0b00 = 按需传输;
- 0b01 = 单DMA传输(Single DMA Transfer);
- 0b10 = 块DMA传输(Block DMA Transfer);
- 0b11 = 级联模式 (用于级联另一个DMA控制器)。
单传输模式对外围设备来说是一种好方法,因为它不能同时缓存大量数据。 非82077AA软盘控制器、Sound Blaster和Sound Blaster Pro应使用单传输DMA模式。
块传输模式对于可以缓冲整个信息块的外围设备是很好的。 硬盘控制器板就是一个例子。
按需传输模式适用于间歇性启动和停止的外围设备,如磁带机。 驱动器可以读取整个信息负载,只要它可以,并暂停传输以移动到磁带的另一部分。 较新的软盘控制器也适用于按需传输,因为它们有FIFO缓冲区来存储读取和写入的信息(但需要正确设置FIFO)。 外围设备控制流量,由于信息流不中断,因此可以获得好的性能。 一些后来的计算机中的cpu通常具有缓存,并且可以在需求DMA传输期间继续不间断地工作。 较旧的计算机会在CPU等待内存总线可用时可能会减慢速度。
- Flip-Flop复位寄存器0x0C和0xD8(写入)
- 主复位寄存器0x0D和0xDA (写)
- 屏蔽重置寄存器0x0E和0xDC(写入)
将需要的值发送到复位寄存器以激活它们。 主复位将Flip-Flop设置为低电平,清除状态,并将所有掩码位设置为ON。 屏蔽重置将关闭所有掩码位。
“必须在任何“16位事务之前发送复位触发器命令。(以前的维基文章中的会说明需要在真实硬件上进行验证,因为它很可能在模拟器上是错误的) 在DMA控制器收到第二个字节后,Flip-Flop “不重置”。
其他寄存器
- 状态寄存器0x08和0xD0(读取)
Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 |
REQ3 | REQ2 | REQ1 | REQ0 | TC3 | TC2 | TC1 | TC0 |
- REQ3-0: 设置1时: DMA请求待处理。
- TC3-0:设置1时:传输完成。
- 读取该寄存器将清除TC位。
鉴于8237不能发送IRQ来告诉你它已经完成,这个寄存器不是很重要。 通常不需要轮询该寄存器,因为当事务完成时,外围设备(在DMA的另一端)将发送一个中断。
- 命令寄存器0x08和0xD0(写入)
Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 |
DACKP | DRQP | EXTW | PRIO | COMP | COND | ADHE | MMT |
此寄存器也确实表现出了8237与PC硬件的不兼容程度。
- 让我们从EXTW和COMP开始。 这些使DMA传输速度提高了25%,消除了一个时钟周期。 它能用吗? 不能。
- PRIO。 置零时,这允许DMA优先级轮换,从而允许共享数据总线的所有外设的自由。它能用吗?不能。
- MMT和ADHE。 你是否知道IBM PC可以1981年进行内存到内存(memory to memory)的传输? 没错,一种硬件精灵(hardware sprites)技术,从一个位置到另一个位置的硬件帧缓冲。它能用吗?不能。
- COND。 Hooray是控制寄存器中唯一有用的部分。 设置此位将禁用DMA控制器。 这是在不屏蔽每个通道的情况下设置多个DMA通道的一种方式。
- 请求寄存器0x09和0xD2 (写)
用于内存到内存的传输和设置优先级轮换——绝对没有用。
;“Intermediate”寄存器0x0D和0xDA(读取) 从未在个人电脑上实现。没用。
示例
软盘DMA初始化
你只需要实现1到3个小例程即可执行DMA传输。 这个例子是软盘驱动器控制器(可能是最常见的,其次是SoundBlaster)。
注意:以下代码不是最佳代码,因为存在两次到同一IO端口的输出(在两个位置)。 这会导致IO端口总线上的额外延迟。 真正的代码应该将两个“out 0x4”和“out 0x5”调用与另一个端口的“out”分开。
initialize_floppy_DMA:
; 将DMA通道2设置为在内存中传输来自0x1000-0x33ff的数据
; 分页必须将此 _物理_ 内存映射到其他地方,并且进行从分页到磁盘 _pin_ !
; 将计数器设置为0x23ff,即1.44 MiB软盘上的磁道长度-1(假设是512字节扇区)
; transfer length = counter + 1
out 0x0a, 0x06 ; 屏蔽DMA通道2和0(假设0已屏蔽)
out 0x0c, 0xFF ; 重置主Flip-Flop
out 0x04, 0 ; 地址为0(低字节)
out 0x04, 0x10 ; 地址设为0x10(高字节)
out 0x0c, 0xFF ; 重置主Flip-Flop (再次!)
out 0x05, 0xFF ; 计数为0x23ff(低字节)
out 0x05, 0x23 ; 计数为0x23ff(高字节),
out 0x81, 0 ; 外部页寄存器为0,总地址为00 10 00
out 0x0a, 0x02 ; 解除DMA信道2的屏蔽
ret
一旦你设置了起始地址和传输长度,如果你使用的是autoinit,则不需要再次管它。 一旦选择了读或写,你也不需要改变它。 要“更改”选择读取或写入,请使用模式寄存器。
prepare_for_floppy_DMA_write:
out 0x0a, 0x06 ; 屏蔽DMA通道2和0(假设0已屏蔽)
out 0x0b, 0x5A ; 01011010
; single transfer, address increment, autoinit, write, channel2)
out 0x0a, 0x02 ; 解除DMA通道2的掩码
ret
prepare_for_floppy_DMA_read:
out 0x0a, 0x06 ; 屏蔽DMA通道2和0(假设0已屏蔽)
out 0x0b, 0x56 ; 01010110
; single transfer, address increment, autoinit, read, channel2)
out 0x0a, 0x02 ; 取消屏蔽DMA通道2
ret
有些硬件以及VirtualPC不支持autoinit。 在上述例程中,你可能要将模式寄存器设置为0x4A和0x46。
上述例程使用single transfer模式来实现兼容性,但是在你的软盘驱动程序初始化期间,如果你检测到 “高级” 软盘控制器 (使用Version命令),则应使用 “demand transfer” 来减少开销。