第10章 直接内存访问技术与8237A控制器详解
直接内存访问(DMA)技术是计算机系统中一种高效的数据传输方式,它允许外设与内存之间直接交换数据,无需CPU的持续干预,从而显著提升系统性能。本章将深入探讨DMA技术的基础理论、工作原理以及8237A DMA控制器的实现方法和应用场景。
10.1 直接内存访问控制器8237A概述
10.1.1 8237A DMA控制器的架构与引脚功能
8237A是Intel开发的一款经典DMA控制器芯片,在早期的PC系统中广泛应用。该控制器允许外设绕过CPU直接访问系统内存,大幅提高数据传输效率。
内部架构
8237A内部具有以下主要组成部分:
四个独立的DMA通道:每个通道可以处理一个独立的DMA传输请求16位地址寄存器:为每个通道指定内存地址16位字计数寄存器:控制每个通道的传输字节数命令寄存器:控制整个芯片的工作方式状态寄存器:反映DMA通道的当前状态模式寄存器:设定每个通道的工作模式请求寄存器:处理DMA请求信号掩码寄存器:使能或屏蔽各通道的DMA请求
下图为8237A的内部框图:
gherkin
+------------------------------+
| 8237A |
| |
| +-------------------+ |
| | 控制逻辑和总线 | |
| +-------------------+ |
| |
| +-------------------+ |
| | 通道0 | 地址寄存器 | |
| | | 计数寄存器 | |
| +-------------------+ |
| | 通道1 | 地址寄存器 | |
| | | 计数寄存器 | |
| +-------------------+ |
| | 通道2 | 地址寄存器 | |
| | | 计数寄存器 | |
| +-------------------+ |
| | 通道3 | 地址寄存器 | |
| | | 计数寄存器 | |
| +-------------------+ |
| |
| +-------------------+ |
| | 命令寄存器 | |
| | 状态寄存器 | |
| | 模式寄存器 | |
| | 请求寄存器 | |
| | 掩码寄存器 | |
| +-------------------+ |
| |
+------------------------------+
引脚功能
8237A采用40引脚DIP封装,主要引脚功能如下:
IOR#, IOW#: I/O读写控制信号MEMR#, MEMW#: 内存读写控制信号READY: 就绪信号,由内存或I/O设备提供HLDA: 总线保持应答,由CPU提供HRQ: 总线保持请求,输出到CPUDACK0#-DACK3#: DMA确认信号,对应四个DMA通道DRQ0-DRQ3: DMA请求信号,对应四个DMA通道A0-A7: 低8位地址线DB0-DB7: 8位双向数据总线CS#: 片选信号CLK: 时钟输入RESET: 复位信号
以下是一段C语言代码模拟8237A的基本结构:
c
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
// 定义8237A DMA控制器结构
typedef struct {
// 每个通道的地址寄存器
uint16_t current_address[4];
uint16_t base_address[4];
// 每个通道的计数寄存器
uint16_t current_count[4];
uint16_t base_count[4];
// 控制寄存器
uint8_t command_register;
uint8_t mode_register[4];
uint8_t request_register;
uint8_t mask_register;
uint8_t status_register;
// 临时寄存器
uint8_t temporary_register;
// 通道选择
uint8_t selected_channel;
// 控制信号状态
bool hrq_active; // HRQ信号状态
bool hlda_active; // HLDA信号状态
bool dack_active[4]; // DACK信号状态
bool drq_active[4]; // DRQ信号状态
} DMA8237A;
// 初始化DMA控制器
void init_dma_controller(DMA8237A *dma) {
int i;
// 初始化所有寄存器为0
for (i = 0; i < 4; i++) {
dma->current_address[i] = 0;
dma->base_address[i] = 0;
dma->current_count[i] = 0;
dma->base_count[i] = 0;
dma->mode_register[i] = 0;
dma->dack_active[i] = false;
dma->drq_active[i] = false;
}
dma->command_register = 0;
dma->request_register = 0;
dma->mask_register = 0xF; // 初始掩码所有通道
dma->status_register = 0;
dma->temporary_register = 0;
dma->selected_channel = 0;
dma->hrq_active = false;
dma->hlda_active = false;
printf("DMA控制器已初始化
");
}
// 打印DMA控制器状态
void print_dma_status(DMA8237A *dma) {
int i;
printf("
DMA控制器状态:
");
printf("命令寄存器: 0x%02X
", dma->command_register);
printf("状态寄存器: 0x%02X
", dma->status_register);
printf("请求寄存器: 0x%02X
", dma->request_register);
printf("掩码寄存器: 0x%02X
", dma->mask_register);
for (i = 0; i < 4; i++) {
printf("
通道 %d:
", i);
printf(" 当前地址: 0x%04X
", dma->current_address[i]);
printf(" 基址: 0x%04X
", dma->base_address[i]);
printf(" 当前计数: %u
", dma->current_count[i]);
printf(" 基础计数: %u
", dma->base_count[i]);
printf(" 模式寄存器: 0x%02X
", dma->mode_register[i]);
printf(" DRQ状态: %s
", dma->drq_active[i] ? "激活" : "未激活");
printf(" DACK状态: %s
", dma->dack_active[i] ? "激活" : "未激活");
}
printf("
HRQ状态: %s
", dma->hrq_active ? "激活" : "未激活");
printf("HLDA状态: %s
", dma->hlda_active ? "激活" : "未激活");
}
int main() {
DMA8237A dma_controller;
// 初始化DMA控制器
init_dma_controller(&dma_controller);
// 打印初始状态
print_dma_status(&dma_controller);
return 0;
}
这段代码定义了一个8237A DMA控制器的基本结构,并提供了初始化和状态打印功能。在实际硬件中,DMA控制器的操作要复杂得多,涉及到与CPU和外设的实时交互。
10.1.2 8237A DMA控制器的操作流程
8237A的DMA传输过程遵循以下基本步骤:
初始化阶段:
系统软件初始化8237A的各种寄存器设置传输模式、地址和计数值
请求阶段:
外设向8237A发送DMA请求(通过DRQ信号)8237A向CPU发出总线保持请求(HRQ)
仲裁阶段:
CPU完成当前指令后,释放总线控制权CPU通过HLDA信号通知8237A可以控制总线
传输阶段:
8237A向请求设备发送确认信号(DACK)8237A控制地址线和控制信号进行内存与I/O设备之间的数据传输每传输一个字节,地址递增,计数递减
终止阶段:
当字节计数达到0或外设撤销DRQ请求时8237A释放HRQ信号,返回总线控制权给CPU可选择设置终止中断通知CPU传输完成
下面是一个模拟8237A操作流程的C语言示例:
c
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <unistd.h>
// 简化的DMA控制器结构
typedef struct {
uint16_t address; // 当前地址
uint16_t count; // 当前计数
uint8_t mode; // 传输模式
bool drq_active; // DRQ信号
bool dack_active; // DACK信号
bool transfer_active; // 传输是否激活
} DMAChannel;
typedef struct {
DMAChannel channels[4]; // 4个DMA通道
bool hrq_active; // HRQ信号
bool hlda_active; // HLDA信号
} DMAController;
// 简化的内存和I/O设备
uint8_t memory[65536]; // 模拟系统内存
uint8_t io_device[1024]; // 模拟I/O设备缓冲区
// 初始化DMA通道
void init_dma_channel(DMAController *dma, int channel, uint16_t address, uint16_t count, uint8_t mode) {
if (channel < 0 || channel > 3) {
printf("错误:无效的通道号 %d
", channel);
return;
}
dma->channels[channel].address = address;
dma->channels[channel].count = count;
dma->channels[channel].mode = mode;
dma->channels[channel].drq_active = false;
dma->channels[channel].dack_active = false;
dma->channels[channel].transfer_active = false;
printf("初始化DMA通道 %d:
", channel);
printf(" 地址: 0x%04X
", address);
printf(" 计数: %d
", count);
printf(" 模式: 0x%02X
", mode);
}
// 模拟DMA请求
void request_dma(DMAController *dma, int channel) {
if (channel < 0 || channel > 3) {
printf("错误:无效的通道号 %d
", channel);
return;
}
printf("
设备请求DMA传输 (通道 %d)
", channel);
// 设置DRQ激活
dma->channels[channel].drq_active = true;
// DMA控制器向CPU请求总线控制权
dma->hrq_active = true;
printf("DMA控制器激活HRQ信号
");
// 模拟CPU响应
printf("CPU完成当前指令...
");
usleep(100000); // 模拟延迟
printf("CPU释放总线控制权 (HLDA = 1)
");
dma->hlda_active = true;
// DMA控制器向设备发送确认信号
dma->channels[channel].dack_active = true;
printf("DMA控制器激活DACK%d信号
", channel);
// 开始传输
dma->channels[channel].transfer_active = true;
}
// 模拟内存到I/O的DMA传输
void perform_dma_transfer_mem_to_io(DMAController *dma, int channel, int io_offset) {
if (!dma->channels[channel].transfer_active) {
printf("错误:DMA传输未激活
");
return;
}
printf("
执行DMA传输 (内存到I/O):
");
uint16_t address = dma->channels[channel].address;
uint16_t count = dma->channels[channel].count;
printf("起始地址: 0x%04X, 计数: %d
", address, count);
// 执行传输
for (int i = 0; i <= count; i++) {
// 传输一个字节从内存到I/O
io_device[io_offset + i] = memory[address + i];
printf("传输: 内存[0x%04X] = 0x%02X -> I/O设备[%d]
",
address + i, memory[address + i], io_offset + i);
// 如果是真实的DMA,这里不需要循环,硬件自动完成
usleep(10000); // 模拟延迟
}
// 传输完成
printf("
DMA传输完成
");
// 重置DMA控制器状态
dma->channels[channel].transfer_active = false;
dma->channels[channel].dack_active = false;
dma->channels[channel].drq_active = false;
dma->hrq_active = false;
dma->hlda_active = false;
printf("DMA控制器释放总线控制权 (HRQ = 0)
");
printf("CPU恢复总线控制 (HLDA = 0)
");
}
// 模拟I/O到内存的DMA传输
void perform_dma_transfer_io_to_mem(DMAController *dma, int channel, int io_offset) {
if (!dma->channels[channel].transfer_active) {
printf("错误:DMA传输未激活
");
return;
}
printf("
执行DMA传输 (I/O到内存):
");
uint16_t address = dma->channels[channel].address;
uint16_t count = dma->channels[channel].count;
printf("起始地址: 0x%04X, 计数: %d
", address, count);
// 执行传输
for (int i = 0; i <= count; i++) {
// 传输一个字节从I/O到内存
memory[address + i] = io_device[io_offset + i];
printf("传输: I/O设备[%d] = 0x%02X -> 内存[0x%04X]
",
io_offset + i, io_device[io_offset + i], address + i);
// 如果是真实的DMA,这里不需要循环,硬件自动完成
usleep(10000); // 模拟延迟
}
// 传输完成
printf("
DMA传输完成
");
// 重置DMA控制器状态
dma->channels[channel].transfer_active = false;
dma->channels[channel].dack_active = false;
dma->channels[channel].drq_active = false;
dma->hrq_active = false;
dma->hlda_active = false;
printf("DMA控制器释放总线控制权 (HRQ = 0)
");
printf("CPU恢复总线控制 (HLDA = 0)
");
}
int main() {
DMAController dma;
// 初始化测试数据
for (int i = 0; i < 65536; i++) {
memory[i] = i & 0xFF; // 填充一些测试值
}
for (int i = 0; i < 1024; i++) {
io_device[i] = 0; // 清空I/O设备缓冲区
}
printf("8237A DMA控制器操作流程模拟
");
printf("===========================
");
// 初始化DMA通道
// 模式: 单字节传输, 地址自增, 自动初始化关闭
init_dma_channel(&dma, 0, 0x1000, 10, 0x44);
// 模拟从内存到I/O的DMA传输
printf("
--- 内存到I/O传输测试 ---
");
request_dma(&dma, 0);
perform_dma_transfer_mem_to_io(&dma, 0, 0);
// 显示传输结果
printf("
传输结果确认:
");
for (int i = 0; i <= 10; i++) {
printf("I/O设备[%d] = 0x%02X (应该等于 0x%02X)
",
i, io_device[i], (0x1000 + i) & 0xFF);
}
// 现在测试从I/O到内存的传输
printf("
--- I/O到内存传输测试 ---
");
// 修改I/O设备的数据
for (int i = 0; i < 20; i++) {
io_device[i + 100] = 0xAA + i;
}
// 初始化另一个DMA通道
init_dma_channel(&dma, 1, 0x2000, 15, 0x48); // 单字节传输,地址自增
// 模拟传输
request_dma(&dma, 1);
perform_dma_transfer_io_to_mem(&dma, 1, 100);
// 显示传输结果
printf("
传输结果确认:
");
for (int i = 0; i <= 15; i++) {
printf("内存[0x%04X] = 0x%02X (应该等于 0x%02X)
",
0x2000 + i, memory[0x2000 + i], 0xAA + i);
}
return 0;
}
这个例子模拟了8237A的基本操作流程,包括初始化DMA通道、请求DMA传输、CPU让出总线控制权,以及执行实际的数据传输。虽然真实的硬件实现会更复杂,但这个模拟展示了DMA操作的基本原理。
10.1.3 8237A DMA控制器的工作模式
8237A支持多种工作模式,可以满足不同的数据传输需求。主要的工作模式包括:
单字节传输模式(Single Mode):
每次只传输一个字节传输完成后自动停止适合小数据量的间歇性传输
块传输模式(Block Mode):
连续传输整个数据块只有在计数为零或外部终止信号到来时才停止适合大量数据的快速传输
需求传输模式(Demand Mode):
只要DRQ信号保持有效就持续传输当DRQ信号撤销时暂停传输适合速度不稳定的设备
级联模式(Cascade Mode):
用于级联两个DMA控制器在PC/AT架构中用于扩展DMA通道数量
此外,每种模式下还可以设置:
地址增减方向(地址递增或递减)自动初始化(传输完成后自动重载初始值)优先级(确定同时请求时哪个通道优先)
8237A的模式寄存器格式如下:
gherkin
位 7 6 5 4 3 2 1 0
| | | | | | | |
+---+---+ +---+---+---+ +
| | | | | |
| | | | | +---- 0: 校验使能
| | | | |
| | | | +------------ 00: 需求模式
| | | | 01: 单字节模式
| | | | 10: 块传输模式
| | | | 11: 级联模式
| | | |
| | +---+---------------- 00: 校验
| | 01: 写传输
| | 10: 读传输
| |
| +---------------------------- 0: 自动初始化禁止
| 1: 自动初始化使能
|
+-------------------------------- 00: 通道0选择
01: 通道1选择
10: 通道2选择
11: 通道3选择
下面是一个配置8237A不同工作模式的C语言示例:
c
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
// 8237A端口地址(在PC系统中)
#define DMA_CH0_ADDR 0x00 // 通道0地址寄存器
#define DMA_CH0_COUNT 0x01 // 通道0计数寄存器
#define DMA_CH1_ADDR 0x02 // 通道1地址寄存器
#define DMA_CH1_COUNT 0x03 // 通道1计数寄存器
#define DMA_CH2_ADDR 0x04 // 通道2地址寄存器
#define DMA_CH2_COUNT 0x05 // 通道2计数寄存器
#define DMA_CH3_ADDR 0x06 // 通道3地址寄存器
#define DMA_CH3_COUNT 0x07 // 通道3计数寄存器
#define DMA_STATUS_CMD 0x08 // 状态/命令寄存器
#define DMA_REQUEST 0x09 // 请求寄存器
#define DMA_MASK_SINGLE 0x0A // 单通道掩码寄存器
#define DMA_MODE 0x0B // 模式寄存器
#define DMA_CLEAR_FF 0x0C // 清除字节指针触发器
#define DMA_TEMP 0x0D // 临时寄存器
#define DMA_MASTER_CLEAR 0x0D // 主清除寄存器
#define DMA_CLEAR_MASK 0x0E // 清除掩码寄存器
#define DMA_MASK_ALL 0x0F // 全通道掩码寄存器
// 模式寄存器位定义
#define DMA_MODE_CH0 0x00 // 选择通道0
#define DMA_MODE_CH1 0x40 // 选择通道1
#define DMA_MODE_CH2 0x80 // 选择通道2
#define DMA_MODE_CH3 0xC0 // 选择通道3
#define DMA_MODE_VERIFY 0x00 // 校验传输
#define DMA_MODE_WRITE 0x04 // 写传输 (I/O到内存)
#define DMA_MODE_READ 0x08 // 读传输 (内存到I/O)
#define DMA_MODE_AUTO_INIT 0x10 // 自动初始化模式
#define DMA_MODE_DEMAND 0x00 // 需求模式
#define DMA_MODE_SINGLE 0x01 // 单字节模式
#define DMA_MODE_BLOCK 0x02 // 块传输模式
#define DMA_MODE_CASCADE 0x03 // 级联模式
// 模拟outb函数,用于向端口写入数据
void outb(uint16_t port, uint8_t value) {
printf("写端口 0x%04X: 0x%02X
", port, value);
}
// 模拟inb函数,用于从端口读取数据
uint8_t inb(uint16_t port) {
// 模拟返回值
uint8_t value = 0;
printf("读端口 0x%04X
", port);
return value;
}
// 配置DMA通道
void configure_dma_channel(uint8_t channel, uint16_t address, uint16_t count, uint8_t mode) {
printf("
配置DMA通道 %d:
", channel);
printf("地址: 0x%04X, 计数: %d, 模式: 0x%02X
", address, count, mode);
// 选择正确的端口
uint16_t addr_port, count_port;
switch (channel) {
case 0:
addr_port = DMA_CH0_ADDR;
count_port = DMA_CH0_COUNT;
break;
case 1:
addr_port = DMA_CH1_ADDR;
count_port = DMA_CH1_COUNT;
break;
case 2:
addr_port = DMA_CH2_ADDR;
count_port = DMA_CH2_COUNT;
break;
case 3:
addr_port = DMA_CH3_ADDR;
count_port = DMA_CH3_COUNT;
break;
default:
printf("错误:无效的通道号 %d
", channel);
return;
}
// 清除字节指针触发器
outb(DMA_CLEAR_FF, 0);
// 写入地址
outb(addr_port, address & 0xFF); // 低8位
outb(addr_port, (address >> 8) & 0xFF); // 高8位
// 清除字节指针触发器
outb(DMA_CLEAR_FF, 0);
// 写入计数 (实际值为count-1)
outb(count_port, (count - 1) & 0xFF); // 低8位
outb(count_port, ((count - 1) >> 8) & 0xFF); // 高8位
// 写入模式
outb(DMA_MODE, mode);
// 清除该通道的掩码,使其可以接收DMA请求
outb(DMA_MASK_SINGLE, channel);
printf("DMA通道 %d 配置完成
", channel);
}
// 设置单字节传输模式
void configure_single_mode(uint8_t channel, uint16_t address, uint16_t count, bool auto_init, bool memory_to_io) {
uint8_t mode = 0;
// 设置通道
switch (channel) {
case 0: mode |= DMA_MODE_CH0; break;
case 1: mode |= DMA_MODE_CH1; break;
case 2: mode |= DMA_MODE_CH2; break;
case 3: mode |= DMA_MODE_CH3; break;
default:
printf("错误:无效的通道号 %d
", channel);
return;
}
// 设置传输方向
if (memory_to_io) {
mode |= DMA_MODE_READ; // 内存到I/O
printf("传输方向: 内存到I/O
");
} else {
mode |= DMA_MODE_WRITE; // I/O到内存
printf("传输方向: I/O到内存
");
}
// 设置自动初始化
if (auto_init) {
mode |= DMA_MODE_AUTO_INIT;
printf("自动初始化: 启用
");
} else {
printf("自动初始化: 禁用
");
}
// 设置为单字节模式
mode |= DMA_MODE_SINGLE;
printf("传输模式: 单字节
");
// 配置DMA通道
configure_dma_channel(channel, address, count, mode);
}
// 设置块传输模式
void configure_block_mode(uint8_t channel, uint16_t address, uint16_t count, bool auto_init, bool memory_to_io) {
uint8_t mode = 0;
// 设置通道
switch (channel) {
case 0: mode |= DMA_MODE_CH0; break;
case 1: mode |= DMA_MODE_CH1; break;
case 2: mode |= DMA_MODE_CH2; break;
case 3: mode |= DMA_MODE_CH3; break;
default:
printf("错误:无效的通道号 %d
", channel);
return;
}
// 设置传输方向
if (memory_to_io) {
mode |= DMA_MODE_READ; // 内存到I/O
printf("传输方向: 内存到I/O
");
} else {
mode |= DMA_MODE_WRITE; // I/O到内存
printf("传输方向: I/O到内存
");
}
// 设置自动初始化
if (auto_init) {
mode |= DMA_MODE_AUTO_INIT;
printf("自动初始化: 启用
");
} else {
printf("自动初始化: 禁用
");
}
// 设置为块传输模式
mode |= DMA_MODE_BLOCK;
printf("传输模式: 块传输
");
// 配置DMA通道
configure_dma_channel(channel, address, count, mode);
}
// 设置需求传输模式
void configure_demand_mode(uint8_t channel, uint16_t address, uint16_t count, bool auto_init, bool memory_to_io) {
uint8_t mode = 0;
// 设置通道
switch (channel) {
case 0: mode |= DMA_MODE_CH0; break;
case 1: mode |= DMA_MODE_CH1; break;
case 2: mode |= DMA_MODE_CH2; break;
case 3: mode |= DMA_MODE_CH3; break;
default:
printf("错误:无效的通道号 %d
", channel);
return;
}
// 设置传输方向
if (memory_to_io) {
mode |= DMA_MODE_READ; // 内存到I/O
printf("传输方向: 内存到I/O
");
} else {
mode |= DMA_MODE_WRITE; // I/O到内存
printf("传输方向: I/O到内存
");
}
// 设置自动初始化
if (auto_init) {
mode |= DMA_MODE_AUTO_INIT;
printf("自动初始化: 启用
");
} else {
printf("自动初始化: 禁用
");
}
// 设置为需求模式
mode |= DMA_MODE_DEMAND;
printf("传输模式: 需求模式
");
// 配置DMA通道
configure_dma_channel(channel, address, count, mode);
}
// 设置级联模式
void configure_cascade_mode(uint8_t channel) {
uint8_t mode = 0;
// 设置通道
switch (channel) {
case 0: mode |= DMA_MODE_CH0; break;
case 1: mode |= DMA_MODE_CH1; break;
case 2: mode |= DMA_MODE_CH2; break;
case 3: mode |= DMA_MODE_CH3; break;
default:
printf("错误:无效的通道号 %d
", channel);
return;
}
// 设置为级联模式
mode |= DMA_MODE_CASCADE;
printf("传输模式: 级联模式
");
// 配置DMA通道(级联模式下不需要地址和计数)
outb(DMA_MODE, mode);
// 清除该通道的掩码
outb(DMA_MASK_SINGLE, channel);
printf("DMA通道 %d 配置为级联模式
", channel);
}
int main() {
printf("8237A DMA控制器工作模式示例
");
printf("==========================
");
// 测试单字节传输模式
printf("--- 单字节传输模式测试 ---
");
configure_single_mode(0, 0x1000, 128, false, true);
// 测试块传输模式
printf("
--- 块传输模式测试 ---
");
configure_block_mode(1, 0x2000, 256, false, false);
// 测试需求传输模式
printf("
--- 需求传输模式测试 ---
");
configure_demand_mode(2, 0x3000, 512, true, true);
// 测试级联模式
printf("
--- 级联模式测试 ---
");
configure_cascade_mode(3);
return 0;
}
这个示例展示了如何配置8237A的各种工作模式,包括单字节传输、块传输、需求传输和级联模式。在实际应用中,选择合适的工作模式对于系统性能至关重要。
10.1.4 8237A DMA控制器的寄存器系统
8237A包含多种寄存器,用于控制和监视DMA传输。这些寄存器可分为两类:面向通道的寄存器和全局控制寄存器。
通道相关寄存器(每个通道一组):
当前地址寄存器:
16位寄存器,指定当前的内存地址在传输过程中会根据配置递增或递减通过两次8位I/O操作访问(低8位先写入)
当前字计数寄存器:
16位寄存器,指定要传输的字节数实际计数为寄存器值加1在传输过程中每传输一个字节递减1
基址与基计数寄存器:
用于自动初始化模式存储初始地址和计数值,传输完成后自动重载
全局控制寄存器:
命令寄存器:
控制芯片的全局行为设置优先级、压缩/扩展时序、存储器到存储器传输等
模式寄存器:
设置各通道的传输模式包括传输类型、自动初始化、地址增减方向等
请求寄存器:
允许软件设置DMA请求用于软件触发DMA传输
掩码寄存器:
控制是否屏蔽特定通道的DMA请求单通道掩码寄存器和全通道掩码寄存器
状态寄存器:
提供各通道的当前状态包括请求标志、传输完成标志等
下面是8237A寄存器的详细布局:
gherkin
命令寄存器 (Command Register):
位 7 6 5 4 3 2 1 0
| | | | | | | |
| | | | | | | +---- 0: 存储器到存储器禁止
| | | | | | | 1: 存储器到存储器使能
| | | | | | |
| | | | | | +-------- 0: 通道0地址保持禁止
| | | | | | 1: 通道0地址保持使能
| | | | | |
| | | | | +------------ 0: 控制器使能
| | | | | 1: 控制器禁止
| | | | |
| | | | +---------------- 0: 正常时序
| | | | 1: 压缩时序
| | | |
| | | +-------------------- 0: 固定优先级
| | | 1: 循环优先级
| | |
| | +------------------------ 0: 延迟周期正常
| | 1: 延迟周期扩展
| |
| +---------------------------- 0: DREQ信号为有效电平
| 1: DREQ信号为有效边沿
|
+-------------------------------- 0: DACK信号为有效低电平
1: DACK信号为有效高电平
状态寄存器 (Status Register):
位 7 6 5 4 3 2 1 0
| | | | | | | |
| | | | | | | +---- 通道0请求状态
| | | | | | |
| | | | | | +-------- 通道1请求状态
| | | | | |
| | | | | +------------ 通道2请求状态
| | | | |
| | | | +---------------- 通道3请求状态
| | | |
| | | +-------------------- 通道0传输完成状态
| | |
| | +------------------------ 通道1传输完成状态
| |
| +---------------------------- 通道2传输完成状态
|
+-------------------------------- 通道3传输完成状态
以下是一个操作8237A寄存器的C语言示例:
c
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
// 8237A端口定义(基于PC架构)
#define DMA_CH0_ADDR 0x00 // 通道0地址寄存器
#define DMA_CH0_COUNT 0x01 // 通道0计数寄存器
#define DMA_CH1_ADDR 0x02 // 通道1地址寄存器
#define DMA_CH1_COUNT 0x03 // 通道1计数寄存器
#define DMA_CH2_ADDR 0x04 // 通道2地址寄存器
#define DMA_CH2_COUNT 0x05 // 通道2计数寄存器
#define DMA_CH3_ADDR 0x06 // 通道3地址寄存器
#define DMA_CH3_COUNT 0x07 // 通道3计数寄存器
#define DMA_STATUS_CMD 0x08 // 状态/命令寄存器
#define DMA_REQUEST 0x09 // 请求寄存器
#define DMA_MASK_SINGLE 0x0A // 单通道掩码寄存器
#define DMA_MODE 0x0B // 模式寄存器
#define DMA_CLEAR_FF 0x0C // 清除字节指针触发器
#define DMA_TEMP 0x0D // 临时寄存器
#define DMA_MASTER_CLEAR 0x0D // 主清除寄存器
#define DMA_CLEAR_MASK 0x0E // 清除掩码寄存器
#define DMA_MASK_ALL 0x0F // 全通道掩码寄存器
// PC架构中的页寄存器(在IBM PC中,页寄存器在不同的I/O空间)
#define DMA_PAGE_CH0 0x87 // 通道0页寄存器
#define DMA_PAGE_CH1 0x83 // 通道1页寄存器
#define DMA_PAGE_CH2 0x81 // 通道2页寄存器
#define DMA_PAGE_CH3 0x82 // 通道3页寄存器
// 命令寄存器位定义
#define DMA_CMD_MEM_TO_MEM 0x01 // 存储器到存储器使能
#define DMA_CMD_CH0_HOLD 0x02 // 通道0地址保持
#define DMA_CMD_DISABLE 0x04 // 控制器禁止
#define DMA_CMD_COMPRESS 0x08 // 压缩时序
#define DMA_CMD_ROTATE 0x10 // 循环优先级
#define DMA_CMD_EXT_DELAY 0x20 // 延迟周期扩展
#define DMA_CMD_DREQ_EDGE 0x40 // DREQ信号为边沿触发
#define DMA_CMD_DACK_HIGH 0x80 // DACK信号为高电平有效
// 状态寄存器位定义
#define DMA_STATUS_CH0_REQ 0x01 // 通道0请求
#define DMA_STATUS_CH1_REQ 0x02 // 通道1请求
#define DMA_STATUS_CH2_REQ 0x04 // 通道2请求
#define DMA_STATUS_CH3_REQ 0x08 // 通道3请求
#define DMA_STATUS_CH0_TC 0x10 // 通道0传输完成
#define DMA_STATUS_CH1_TC 0x20 // 通道1传输完成
#define DMA_STATUS_CH2_TC 0x40 // 通道2传输完成
#define DMA_STATUS_CH3_TC 0x80 // 通道3传输完成
// 模拟IO操作
uint8_t io_ports[256]; // 模拟IO端口空间
// 模拟outb函数
void outb(uint16_t port, uint8_t value) {
printf("写端口 0x%04X: 0x%02X
", port, value);
io_ports[port] = value;
}
// 模拟inb函数
uint8_t inb(uint16_t port) {
uint8_t value = io_ports[port];
printf("读端口 0x%04X: 0x%02X
", port, value);
return value;
}
// 初始化DMA控制器
void init_dma_controller() {
printf("初始化DMA控制器
");
// 主清除 - 重置控制器
outb(DMA_MASTER_CLEAR, 0);
// 设置命令寄存器
// 设置为固定优先级,正常时序
outb(DMA_STATUS_CMD, 0);
// 清除全通道掩码
outb(DMA_CLEAR_MASK, 0);
printf("DMA控制器初始化完成
");
}
// 写入命令寄存器
void write_command_register(uint8_t value) {
printf("
设置DMA命令寄存器: 0x%02X
", value);
if (value & DMA_CMD_MEM_TO_MEM)
printf(" 存储器到存储器传输: 使能
");
else
printf(" 存储器到存储器传输: 禁止
");
if (value & DMA_CMD_CH0_HOLD)
printf(" 通道0地址保持: 使能
");
else
printf(" 通道0地址保持: 禁止
");
if (value & DMA_CMD_DISABLE)
printf(" 控制器: 禁止
");
else
printf(" 控制器: 使能
");
if (value & DMA_CMD_COMPRESS)
printf(" 时序: 压缩
");
else
printf(" 时序: 正常
");
if (value & DMA_CMD_ROTATE)
printf(" 优先级: 循环
");
else
printf(" 优先级: 固定
");
if (value & DMA_CMD_EXT_DELAY)
printf(" 延迟周期: 扩展
");
else
printf(" 延迟周期: 正常
");
if (value & DMA_CMD_DREQ_EDGE)
printf(" DREQ信号: 边沿触发
");
else
printf(" DREQ信号: 电平触发
");
if (value & DMA_CMD_DACK_HIGH)
printf(" DACK信号: 高电平有效
");
else
printf(" DACK信号: 低电平有效
");
outb(DMA_STATUS_CMD, value);
}
// 读取状态寄存器
void read_status_register() {
uint8_t status = inb(DMA_STATUS_CMD);
printf("
当前DMA状态寄存器: 0x%02X
", status);
printf("通道请求状态:
");
printf(" 通道0: %s
", (status & DMA_STATUS_CH0_REQ) ? "请求中" : "空闲");
printf(" 通道1: %s
", (status & DMA_STATUS_CH1_REQ) ? "请求中" : "空闲");
printf(" 通道2: %s
", (status & DMA_STATUS_CH2_REQ) ? "请求中" : "空闲");
printf(" 通道3: %s
", (status & DMA_STATUS_CH3_REQ) ? "请求中" : "空闲");
printf("传输完成状态:
");
printf(" 通道0: %s
", (status & DMA_STATUS_CH0_TC) ? "已完成" : "未完成");
printf(" 通道1: %s
", (status & DMA_STATUS_CH1_TC) ? "已完成" : "未完成");
printf(" 通道2: %s
", (status & DMA_STATUS_CH2_TC) ? "已完成" : "未完成");
printf(" 通道3: %s
", (status & DMA_STATUS_CH3_TC) ? "已完成" : "未完成");
}
// 设置通道掩码(使能/禁止特定通道)
void set_channel_mask(uint8_t channel, bool mask) {
printf("
%s DMA通道 %d
", mask ? "禁止" : "使能", channel);
// 掩码位格式: 位0=设置/清除, 位1-2=通道选择
uint8_t value = (mask ? 0x04 : 0x00) | channel;
outb(DMA_MASK_SINGLE, value);
}
// 设置所有通道的掩码
void set_all_channel_masks(uint8_t mask_value) {
printf("
设置所有通道掩码: 0x%02X
", mask_value);
printf(" 通道0: %s
", (mask_value & 0x01) ? "禁止" : "使能");
printf(" 通道1: %s
", (mask_value & 0x02) ? "禁止" : "使能");
printf(" 通道2: %s
", (mask_value & 0x04) ? "禁止" : "使能");
printf(" 通道3: %s
", (mask_value & 0x08) ? "禁止" : "使能");
outb(DMA_MASK_ALL, mask_value);
}
// 软件设置DMA请求
void set_dma_request(uint8_t channel, bool set) {
printf("
%s软件DMA请求 (通道 %d)
", set ? "设置" : "清除", channel);
// 请求寄存器格式: 位0=设置/清除, 位1-2=通道选择
uint8_t value = (set ? 0x04 : 0x00) | channel;
outb(DMA_REQUEST, value);
}
// 设置通道的完整地址(包括页寄存器)
void set_channel_address(uint8_t channel, uint32_t address, uint16_t count) {
printf("
设置DMA通道 %d 地址和计数
", channel);
printf(" 物理地址: 0x%05X
", address);
printf(" 传输计数: %d
", count);
uint8_t page;
uint16_t offset;
uint16_t addr_port, count_port;
uint16_t page_port;
// 从20位物理地址中提取页和偏移
page = (address >> 16) & 0xFF;
offset = address & 0xFFFF;
// 选择正确的端口
switch (channel) {
case 0:
addr_port = DMA_CH0_ADDR;
count_port = DMA_CH0_COUNT;
page_port = DMA_PAGE_CH0;
break;
case 1:
addr_port = DMA_CH1_ADDR;
count_port = DMA_CH1_COUNT;
page_port = DMA_PAGE_CH1;
break;
case 2:
addr_port = DMA_CH2_ADDR;
count_port = DMA_CH2_COUNT;
page_port = DMA_PAGE_CH2;
break;
case 3:
addr_port = DMA_CH3_ADDR;
count_port = DMA_CH3_COUNT;
page_port = DMA_PAGE_CH3;
break;
default:
printf("错误:无效的通道号 %d
", channel);
return;
}
// 禁用该通道
set_channel_mask(channel, true);
// 清除字节指针触发器
outb(DMA_CLEAR_FF, 0);
// 写入地址
outb(addr_port, offset & 0xFF); // 低8位
outb(addr_port, (offset >> 8) & 0xFF); // 高8位
// 写入页寄存器
outb(page_port, page);
// 清除字节指针触发器
outb(DMA_CLEAR_FF, 0);
// 写入计数 (实际值为count-1)
outb(count_port, (count - 1) & 0xFF); // 低8位
outb(count_port, ((count - 1) >> 8) & 0xFF); // 高8位
// 重新启用该通道
set_channel_mask(channel, false);
}
int main() {
printf("8237A DMA控制器寄存器操作示例
");
printf("===========================
");
// 初始化DMA控制器
init_dma_controller();
// 设置命令寄存器
write_command_register(DMA_CMD_ROTATE); // 设置为循环优先级模式
// 读取状态寄存器
read_status_register();
// 设置通道掩码
set_channel_mask(0, false); // 使能通道0
set_channel_mask(1, true); // 禁止通道1
// 设置所有通道掩码
set_all_channel_masks(0x0A); // 禁止通道1和3,使能通道0和2
// 设置软件DMA请求
set_dma_request(2, true); // 设置通道2的DMA请求
// 再次读取状态寄存器
read_status_register();
// 清除通道2的DMA请求
set_dma_request(2, false);
// 设置通道的完整地址和计数
set_channel_address(0, 0x12345, 1024); // 地址0x12345,计数1024
printf("
DMA寄存器操作示例完成
");
return 0;
}
这个示例演示了8237A各种寄存器的操作方法,包括命令寄存器、状态寄存器、掩码寄存器、请求寄存器以及地址和计数寄存器的读写操作。在实际系统中,这些寄存器的正确配置对于DMA传输的可靠性和性能至关重要。
10.1.5 8237A DMA控制器的编程方法
编程8237A DMA控制器通常遵循以下步骤:
初始化DMA控制器:
重置控制器(主清除)配置命令寄存器(设置优先级、时序等)
配置DMA通道:
屏蔽要配置的通道清除字节指针触发器设置通道的模式寄存器设置通道的地址和计数解除通道掩码
启动DMA传输:
等待外设发起DMA请求,或通过软件设置请求寄存器触发DMA控制器完成传输后,可通过状态寄存器检查完成状态可选择配置中断,在传输完成时通知CPU
处理传输完成:
检查状态寄存器中的传输完成标志处理传输结果如需要,重新配置DMA通道进行下一次传输
下面是一个完整的8237A编程示例,包含了通道配置和传输过程:
c
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <unistd.h>
// 8237A端口定义(基于PC架构)
#define DMA_CH0_ADDR 0x00 // 通道0地址寄存器
#define DMA_CH0_COUNT 0x01 // 通道0计数寄存器
#define DMA_CH1_ADDR 0x02 // 通道1地址寄存器
#define DMA_CH1_COUNT 0x03 // 通道1计数寄存器
#define DMA_CH2_ADDR 0x04 // 通道2地址寄存器
#define DMA_CH2_COUNT 0x05 // 通道2计数寄存器
#define DMA_CH3_ADDR 0x06 // 通道3地址寄存器
#define DMA_CH3_COUNT 0x07 // 通道3计数寄存器
#define DMA_STATUS_CMD 0x08 // 状态/命令寄存器
#define DMA_REQUEST 0x09 // 请求寄存器
#define DMA_MASK_SINGLE 0x0A // 单通道掩码寄存器
#define DMA_MODE 0x0B // 模式寄存器
#define DMA_CLEAR_FF 0x0C // 清除字节指针触发器
#define DMA_TEMP 0x0D // 临时寄存器
#define DMA_MASTER_CLEAR 0x0D // 主清除寄存器
#define DMA_CLEAR_MASK 0x0E // 清除掩码寄存器
#define DMA_MASK_ALL 0x0F // 全通道掩码寄存器
// 页寄存器
#define DMA_PAGE_CH0 0x87 // 通道0页寄存器
#define DMA_PAGE_CH1 0x83 // 通道1页寄存器
#define DMA_PAGE_CH2 0x81 // 通道2页寄存器
#define DMA_PAGE_CH3 0x82 // 通道3页寄存器
// 模式寄存器位定义
#define DMA_MODE_CH0 0x00 // 选择通道0
#define DMA_MODE_CH1 0x40 // 选择通道1
#define DMA_MODE_CH2 0x80 // 选择通道2
#define DMA_MODE_CH3 0xC0 // 选择通道3
#define DMA_MODE_VERIFY 0x00 // 校验传输
#define DMA_MODE_WRITE 0x04 // 写传输 (I/O到内存)
#define DMA_MODE_READ 0x08 // 读传输 (内存到I/O)
#define DMA_MODE_AUTO_INIT 0x10 // 自动初始化模式
#define DMA_MODE_ADDR_DEC 0x20 // 地址递减
// 未设置此位时为地址递增
#define DMA_MODE_DEMAND 0x00 // 需求模式
#define DMA_MODE_SINGLE 0x01 // 单字节模式
#define DMA_MODE_BLOCK 0x02 // 块传输模式
#define DMA_MODE_CASCADE 0x03 // 级联模式
// 状态寄存器位定义
#define DMA_STATUS_CH0_REQ 0x01 // 通道0请求
#define DMA_STATUS_CH1_REQ 0x02 // 通道1请求
#define DMA_STATUS_CH2_REQ 0x04 // 通道2请求
#define DMA_STATUS_CH3_REQ 0x08 // 通道3请求
#define DMA_STATUS_CH0_TC 0x10 // 通道0传输完成
#define DMA_STATUS_CH1_TC 0x20 // 通道1传输完成
#define DMA_STATUS_CH2_TC 0x40 // 通道2传输完成
#define DMA_STATUS_CH3_TC 0x80 // 通道3传输完成
// 模拟内存和I/O设备
uint8_t memory[65536]; // 模拟系统内存
uint8_t io_buffer[1024]; // 模拟I/O设备缓冲区
uint8_t io_ports[256]; // 模拟I/O端口
// 模拟outb函数
void outb(uint16_t port, uint8_t value) {
printf("写端口 0x%04X: 0x%02X
", port, value);
io_ports[port & 0xFF] = value;
}
// 模拟inb函数
uint8_t inb(uint16_t port) {
uint8_t value = io_ports[port & 0xFF];
printf("读端口 0x%04X: 0x%02X
", port, value);
return value;
}
// 初始化DMA系统
void init_dma() {
printf("
初始化DMA系统...
");
// 重置DMA控制器
outb(DMA_MASTER_CLEAR, 0);
// 屏蔽所有通道
outb(DMA_MASK_ALL, 0x0F);
printf("DMA系统初始化完成
");
}
// 设置DMA通道
void setup_dma_channel(uint8_t channel, uint16_t address, uint16_t count,
bool memory_to_io, bool auto_init) {
uint8_t mode = 0;
uint16_t addr_port, count_port;
printf("
配置DMA通道 %d:
", channel);
printf(" 地址: 0x%04X
", address);
printf(" 计数: %d
", count);
printf(" 传输方向: %s
", memory_to_io ? "内存到I/O" : "I/O到内存");
printf(" 自动初始化: %s
", auto_init ? "启用" : "禁用");
// 设置通道选择位
switch (channel) {
case 0:
mode |= DMA_MODE_CH0;
addr_port = DMA_CH0_ADDR;
count_port = DMA_CH0_COUNT;
break;
case 1:
mode |= DMA_MODE_CH1;
addr_port = DMA_CH1_ADDR;
count_port = DMA_CH1_COUNT;
break;
case 2:
mode |= DMA_MODE_CH2;
addr_port = DMA_CH2_ADDR;
count_port = DMA_CH2_COUNT;
break;
case 3:
mode |= DMA_MODE_CH3;
addr_port = DMA_CH3_ADDR;
count_port = DMA_CH3_COUNT;
break;
default:
printf("错误:无效的通道号 %d
", channel);
return;
}
// 设置传输方向
if (memory_to_io) {
mode |= DMA_MODE_READ; // 内存到I/O
} else {
mode |= DMA_MODE_WRITE; // I/O到内存
}
// 设置自动初始化
if (auto_init) {
mode |= DMA_MODE_AUTO_INIT;
}
// 设置为单字节传输模式
mode |= DMA_MODE_SINGLE;
// 屏蔽该通道
outb(DMA_MASK_SINGLE, channel | 0x04);
// 清除字节指针触发器
outb(DMA_CLEAR_FF, 0);
// 设置模式寄存器
outb(DMA_MODE, mode);
// 清除字节指针触发器
outb(DMA_CLEAR_FF, 0);
// 设置地址寄存器
outb(addr_port, address & 0xFF); // 低8位
outb(addr_port, (address >> 8) & 0xFF); // 高8位
// 清除字节指针触发器
outb(DMA_CLEAR_FF, 0);
// 设置计数寄存器
outb(count_port, (count - 1) & 0xFF); // 低8位
outb(count_port, ((count - 1) >> 8) & 0xFF); // 高8位
// 解除该通道掩码
outb(DMA_MASK_SINGLE, channel);
printf("DMA通道 %d 配置完成
", channel);
}
// 初始化测试数据
void init_test_data(bool memory_to_io) {
printf("
初始化测试数据...
");
if (memory_to_io) {
// 初始化内存数据
for (int i = 0; i < 256; i++) {
memory[0x1000 + i] = 0xA0 + i;
}
// 清空I/O缓冲区
for (int i = 0; i < 256; i++) {
io_buffer[i] = 0;
}
printf("内存数据初始化为 0xA0-0xA0+n
");
printf("I/O缓冲区已清零
");
} else {
// 初始化I/O数据
for (int i = 0; i < 256; i++) {
io_buffer[i] = 0xB0 + i;
}
// 清空内存区域
for (int i = 0; i < 256; i++) {
memory[0x1000 + i] = 0;
}
printf("I/O缓冲区初始化为 0xB0-0xB0+n
");
printf("内存区域已清零
");
}
}
// 模拟DMA传输
void simulate_dma_transfer(uint8_t channel, uint16_t address, uint16_t count, bool memory_to_io) {
printf("
模拟DMA传输...
");
// 模拟外设触发DMA请求
printf("外设触发DMA请求 (DRQ%d 激活)
", channel);
// 设置状态寄存器中的请求位
io_ports[DMA_STATUS_CMD] |= (1 << channel);
printf("DMA控制器响应请求...
");
// 模拟数据传输
if (memory_to_io) {
printf("传输数据: 内存(0x%04X) -> I/O设备
", address);
for (int i = 0; i < count; i++) {
io_buffer[i] = memory[address + i];
printf(" 传输: 内存[0x%04X] = 0x%02X -> I/O[%d]
",
address + i, memory[address + i], i);
usleep(10000); // 为了演示效果添加的延迟
}
} else {
printf("传输数据: I/O设备 -> 内存(0x%04X)
", address);
for (int i = 0; i < count; i++) {
memory[address + i] = io_buffer[i];
printf(" 传输: I/O[%d] = 0x%02X -> 内存[0x%04X]
",
i, io_buffer[i], address + i);
usleep(10000); // 为了演示效果添加的延迟
}
}
// 传输完成,设置传输完成标志
io_ports[DMA_STATUS_CMD] |= (0x10 << channel);
printf("DMA传输完成 (TC%d 设置)
", channel);
}
// 验证传输结果
void verify_transfer_results(uint16_t address, uint16_t count, bool memory_to_io) {
printf("
验证传输结果...
");
bool success = true;
if (memory_to_io) {
// 验证I/O缓冲区是否接收到了正确的数据
for (int i = 0; i < count; i++) {
if (io_buffer[i] != memory[address + i]) {
printf("错误: I/O[%d] = 0x%02X, 期望值 = 0x%02X
",
i, io_buffer[i], memory[address + i]);
success = false;
}
}
} else {
// 验证内存是否接收到了正确的数据
for (int i = 0; i < count; i++) {
if (memory[address + i] != io_buffer[i]) {
printf("错误: 内存[0x%04X] = 0x%02X, 期望值 = 0x%02X
",
address + i, memory[address + i], io_buffer[i]);
success = false;
}
}
}
if (success) {
printf("验证成功: 数据传输正确!
");
} else {
printf("验证失败: 数据传输存在错误
");
}
}
// 模拟中断处理
void handle_dma_interrupt(uint8_t channel) {
uint8_t status = inb(DMA_STATUS_CMD);
printf("
处理DMA中断...
");
printf("DMA状态寄存器: 0x%02X
", status);
// 检查是否是指定通道的传输完成中断
if (status & (0x10 << channel)) {
printf("通道%d传输完成中断
", channel);
// 清除传输完成标志(在实际系统中可能需要其他方式清除)
outb(DMA_STATUS_CMD, io_ports[DMA_STATUS_CMD] & ~(0x10 << channel));
printf("中断处理完成
");
} else {
printf("不是通道%d的传输完成中断
", channel);
}
}
// 完整的DMA传输示例
void complete_dma_example(uint8_t channel, uint16_t address, uint16_t count, bool memory_to_io) {
printf("
=============================
");
printf("完整DMA传输示例 - 通道 %d
", channel);
printf("=============================
");
// 初始化DMA系统
init_dma();
// 初始化测试数据
init_test_data(memory_to_io);
// 设置DMA通道
setup_dma_channel(channel, address, count, memory_to_io, false);
// 模拟DMA传输
simulate_dma_transfer(channel, address, count, memory_to_io);
// 处理DMA中断
handle_dma_interrupt(channel);
// 验证传输结果
verify_transfer_results(address, count, memory_to_io);
printf("
示例完成
");
}
int main() {
printf("8237A DMA控制器编程示例
");
printf("=====================
");
// 示例1: 内存到I/O传输
complete_dma_example(1, 0x1000, 16, true);
// 示例2: I/O到内存传输
complete_dma_example(2, 0x1200, 32, false);
return 0;
}
这个示例提供了一个完整的8237A DMA控制器编程流程,包括初始化、通道配置、数据传输模拟、中断处理和传输验证。在实际系统中,DMA传输通常由硬件自动完成,软件只需配置控制器并处理传输完成后的结果。
10.2 8237A DMA控制器的应用
10.2.1 8237A在IBM PC兼容机中的应用
在IBM PC及其兼容机系统中,8237A DMA控制器是标准配置的重要组成部分,它为各种外设提供高效的数据传输服务。在PC架构中,DMA控制器的具体实现和配置有一些特殊之处。
PC架构中的DMA系统配置:
DMA通道分配:
通道0:系统DRAM刷新(早期PC机)通道1:声卡/数字音频通道2:软盘控制器通道3:硬盘控制器(早期)/声卡(后期)通道4:级联通道(用于连接第二个DMA控制器)通道5-7:其他外设(例如网卡、SCSI控制器等)
DMA地址空间:
PC中使用两个8237A控制器(称为DMA1和DMA2)DMA1控制通道0-3,处理8位传输DMA2控制通道4-7,处理16位传输通道4用于级联两个控制器
特殊考虑事项:
在PC中,DMA无法访问整个物理地址空间早期PC中,DMA只能访问前16MB内存需要使用页寄存器扩展地址线某些地址区域对DMA不可用(比如显存区域)
下面是一个在PC架构中配置8237A的C语言示例:
c
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
// DMA控制器端口定义(PC架构)
// 第一个DMA控制器(8位传输,通道0-3)
#define DMA1_CH0_ADDR 0x00 // 通道0地址寄存器
#define DMA1_CH0_COUNT 0x01 // 通道0计数寄存器
#define DMA1_CH1_ADDR 0x02 // 通道1地址寄存器
#define DMA1_CH1_COUNT 0x03 // 通道1计数寄存器
#define DMA1_CH2_ADDR 0x04 // 通道2地址寄存器
#define DMA1_CH2_COUNT 0x05 // 通道2计数寄存器
#define DMA1_CH3_ADDR 0x06 // 通道3地址寄存器
#define DMA1_CH3_COUNT 0x07 // 通道3计数寄存器
#define DMA1_STATUS 0x08 // 状态寄存器
#define DMA1_COMMAND 0x08 // 命令寄存器
#define DMA1_REQUEST 0x09 // 请求寄存器
#define DMA1_MASK_SINGLE 0x0A // 单通道掩码寄存器
#define DMA1_MODE 0x0B // 模式寄存器
#define DMA1_CLEAR_FF 0x0C // 清除字节指针触发器
#define DMA1_TEMP 0x0D // 临时寄存器
#define DMA1_MASTER_CLEAR 0x0D // 主清除寄存器
#define DMA1_CLEAR_MASK 0x0E // 清除掩码寄存器
#define DMA1_MASK_ALL 0x0F // 全通道掩码寄存器
// 第二个DMA控制器(16位传输,通道4-7)
#define DMA2_CH4_ADDR 0xC0 // 通道4地址寄存器
#define DMA2_CH4_COUNT 0xC2 // 通道4计数寄存器
#define DMA2_CH5_ADDR 0xC4 // 通道5地址寄存器
#define DMA2_CH5_COUNT 0xC6 // 通道5计数寄存器
#define DMA2_CH6_ADDR 0xC8 // 通道6地址寄存器
#define DMA2_CH6_COUNT 0xCA // 通道6计数寄存器
#define DMA2_CH7_ADDR 0xCC // 通道7地址寄存器
#define DMA2_CH7_COUNT 0xCE // 通道7计数寄存器
#define DMA2_STATUS 0xD0 // 状态寄存器
#define DMA2_COMMAND 0xD0 // 命令寄存器
#define DMA2_REQUEST 0xD2 // 请求寄存器
#define DMA2_MASK_SINGLE 0xD4 // 单通道掩码寄存器
#define DMA2_MODE 0xD6 // 模式寄存器
#define DMA2_CLEAR_FF 0xD8 // 清除字节指针触发器
#define DMA2_TEMP 0xDA // 临时寄存器
#define DMA2_MASTER_CLEAR 0xDA // 主清除寄存器
#define DMA2_CLEAR_MASK 0xDC // 清除掩码寄存器
#define DMA2_MASK_ALL 0xDE // 全通道掩码寄存器
// 页寄存器
#define DMA_PAGE_CH0 0x87 // 通道0页寄存器
#define DMA_PAGE_CH1 0x83 // 通道1页寄存器
#define DMA_PAGE_CH2 0x81 // 通道2页寄存器
#define DMA_PAGE_CH3 0x82 // 通道3页寄存器
#define DMA_PAGE_CH5 0x8B // 通道5页寄存器
#define DMA_PAGE_CH6 0x89 // 通道6页寄存器
#define DMA_PAGE_CH7 0x8A // 通道7页寄存器
// 模式寄存器位定义
#define DMA_MODE_VERIFY 0x00 // 校验传输
#define DMA_MODE_WRITE 0x04 // 写传输 (I/O到内存)
#define DMA_MODE_READ 0x08 // 读传输 (内存到I/O)
#define DMA_MODE_AUTO_INIT 0x10 // 自动初始化模式
#define DMA_MODE_ADDR_DEC 0x20 // 地址递减
#define DMA_MODE_DEMAND 0x00 // 需求模式
#define DMA_MODE_SINGLE 0x01 // 单字节模式
#define DMA_MODE_BLOCK 0x02 // 块传输模式
#define DMA_MODE_CASCADE 0x03 // 级联模式
// 模拟outb函数
void outb(uint16_t port, uint8_t value) {
printf("写端口 0x%04X: 0x%02X
", port, value);
}
// 模拟inb函数
uint8_t inb(uint16_t port) {
// 模拟读取
uint8_t value = 0;
printf("读端口 0x%04X
", port);
return value;
}
// PC架构中DMA通道与端口的映射
typedef struct {
uint16_t addr_port; // 地址寄存器端口
uint16_t count_port; // 计数寄存器端口
uint16_t page_port; // 页寄存器端口
uint16_t mask_port; // 掩码寄存器端口
uint16_t mode_port; // 模式寄存器端口
uint16_t clear_ff_port; // 清除字节指针端口
bool is_16bit; // 是否为16位通道
} DMAChannelPorts;
// 获取通道对应的端口信息
DMAChannelPorts get_channel_ports(uint8_t channel) {
DMAChannelPorts ports;
switch (channel) {
case 0:
ports.addr_port = DMA1_CH0_ADDR;
ports.count_port = DMA1_CH0_COUNT;
ports.page_port = DMA_PAGE_CH0;
ports.mask_port = DMA1_MASK_SINGLE;
ports.mode_port = DMA1_MODE;
ports.clear_ff_port = DMA1_CLEAR_FF;
ports.is_16bit = false;
break;
case 1:
ports.addr_port = DMA1_CH1_ADDR;
ports.count_port = DMA1_CH1_COUNT;
ports.page_port = DMA_PAGE_CH1;
ports.mask_port = DMA1_MASK_SINGLE;
ports.mode_port = DMA1_MODE;
ports.clear_ff_port = DMA1_CLEAR_FF;
ports.is_16bit = false;
break;
case 2:
ports.addr_port = DMA1_CH2_ADDR;
ports.count_port = DMA1_CH2_COUNT;
ports.page_port = DMA_PAGE_CH2;
ports.mask_port = DMA1_MASK_SINGLE;
ports.mode_port = DMA1_MODE;
ports.clear_ff_port = DMA1_CLEAR_FF;
ports.is_16bit = false;
break;
case 3:
ports.addr_port = DMA1_CH3_ADDR;
ports.count_port = DMA1_CH3_COUNT;
ports.page_port = DMA_PAGE_CH3;
ports.mask_port = DMA1_MASK_SINGLE;
ports.mode_port = DMA1_MODE;
ports.clear_ff_port = DMA1_CLEAR_FF;
ports.is_16bit = false;
break;
case 5:
ports.addr_port = DMA2_CH5_ADDR;
ports.count_port = DMA2_CH5_COUNT;
ports.page_port = DMA_PAGE_CH5;
ports.mask_port = DMA2_MASK_SINGLE;
ports.mode_port = DMA2_MODE;
ports.clear_ff_port = DMA2_CLEAR_FF;
ports.is_16bit = true;
break;
case 6:
ports.addr_port = DMA2_CH6_ADDR;
ports.count_port = DMA2_CH6_COUNT;
ports.page_port = DMA_PAGE_CH6;
ports.mask_port = DMA2_MASK_SINGLE;
ports.mode_port = DMA2_MODE;
ports.clear_ff_port = DMA2_CLEAR_FF;
ports.is_16bit = true;
break;
case 7:
ports.addr_port = DMA2_CH7_ADDR;
ports.count_port = DMA2_CH7_COUNT;
ports.page_port = DMA_PAGE_CH7;
ports.mask_port = DMA2_MASK_SINGLE;
ports.mode_port = DMA2_MODE;
ports.clear_ff_port = DMA2_CLEAR_FF;
ports.is_16bit = true;
break;
default:
printf("错误: 无效的通道号 %d
", channel);
// 返回通道0的端口作为默认值
return get_channel_ports(0);
}
return ports;
}
// 在PC系统中设置DMA传输
void pc_setup_dma(uint8_t channel, uint32_t phys_addr, uint16_t count, bool memory_to_io) {
DMAChannelPorts ports = get_channel_ports(channel);
printf("
配置PC系统中的DMA通道 %d
", channel);
printf("物理地址: 0x%08X, 计数: %d, %s
",
phys_addr, count, memory_to_io ? "内存到I/O" : "I/O到内存");
// 计算实际DMA地址
uint32_t dma_addr = phys_addr;
uint8_t page;
uint16_t offset;
if (ports.is_16bit) {
// 16位DMA通道
printf("16位DMA通道
");
// 16位传输时地址需要右移1位(因为按字而非字节寻址)
dma_addr = phys_addr >> 1;
// 计数也需要调整(16位通道每次传输2个字节)
count = (count + 1) >> 1;
}
// 计算页和偏移
page = (dma_addr >> 16) & 0xFF;
offset = dma_addr & 0xFFFF;
printf("DMA地址: 页=0x%02X, 偏移=0x%04X
", page, offset);
// 1. 屏蔽通道
uint8_t mask_value = (channel & 0x03) | 0x04; // 设置掩码位
outb(ports.mask_port, mask_value);
// 2. 清除字节指针触发器
outb(ports.clear_ff_port, 0);
// 3. 设置模式寄存器
uint8_t mode = (channel & 0x03); // 通道选择(只使用低2位)
if (memory_to_io) {
mode |= DMA_MODE_READ; // 内存到I/O
} else {
mode |= DMA_MODE_WRITE; // I/O到内存
}
mode |= DMA_MODE_SINGLE; // 单字节传输模式
outb(ports.mode_port, mode);
// 4. 设置页寄存器
outb(ports.page_port, page);
// 5. 设置偏移地址
outb(ports.clear_ff_port, 0); // 再次清除触发器
outb(ports.addr_port, offset & 0xFF); // 低8位
outb(ports.addr_port, (offset >> 8) & 0xFF); // 高8位
// 6. 设置计数
outb(ports.clear_ff_port, 0); // 再次清除触发器
outb(ports.count_port, (count - 1) & 0xFF); // 低8位
outb(ports.count_port, ((count - 1) >> 8) & 0xFF); // 高8位
// 7. 解除通道掩码
mask_value = channel & 0x03; // 清除掩码位
outb(ports.mask_port, mask_value);
printf("DMA通道 %d 配置完成
", channel);
}
// 初始化PC系统中的DMA控制器
void init_pc_dma_controllers() {
printf("
初始化PC系统中的DMA控制器
");
// 重置第一个DMA控制器(8位)
outb(DMA1_MASTER_CLEAR, 0);
// 重置第二个DMA控制器(16位)
outb(DMA2_MASTER_CLEAR, 0);
// 设置级联模式(在第一个控制器的通道4上)
outb(DMA1_MODE, 0x3C); // 00 11 11 00 = 通道4,自动初始化,级联模式
// 取消所有通道掩码
outb(DMA1_CLEAR_MASK, 0);
outb(DMA2_CLEAR_MASK, 0);
printf("DMA控制器初始化完成
");
}
// PC系统中的软盘DMA传输示例
void pc_floppy_dma_example(uint32_t buffer_addr, uint16_t count, bool read_operation) {
printf("
=============================
");
printf("PC软盘DMA传输示例
");
printf("=============================
");
// 软盘控制器通常使用通道2
uint8_t channel = 2;
// 确认buffer_addr是否在DMA可访问的区域内
if (buffer_addr >= 0x01000000) {
printf("错误: 缓冲区地址0x%08X超出DMA可访问范围
", buffer_addr);
printf("DMA只能访问前16MB的内存区域
");
return;
}
// 配置DMA传输
pc_setup_dma(channel, buffer_addr, count, !read_operation);
// 模拟软盘控制器操作
printf("
模拟软盘控制器操作:
");
if (read_operation) {
printf("1. 设置软盘控制器为读取模式
");
printf("2. 选择磁盘驱动器、磁头、扇区
");
printf("3. 发出读取命令
");
printf("4. 软盘控制器向DMA控制器发出DRQ2请求
");
printf("5. 数据从软盘传输到内存(0x%08X)
", buffer_addr);
} else {
printf("1. 设置软盘控制器为写入模式
");
printf("2. 选择磁盘驱动器、磁头、扇区
");
printf("3. 发出写入命令
");
printf("4. 软盘控制器向DMA控制器发出DRQ2请求
");
printf("5. 数据从内存(0x%08X)传输到软盘
", buffer_addr);
}
printf("6. 传输完成后,软盘控制器产生中断
");
printf("7. 系统读取软盘控制器状态,确认操作是否成功
");
printf("
软盘DMA传输示例完成
");
}
// PC系统中的声卡DMA传输示例
void pc_sound_card_dma_example(uint32_t buffer_addr, uint16_t count, bool play) {
printf("
=============================
");
printf("PC声卡DMA传输示例
");
printf("=============================
");
// 声卡通常使用通道1(8位声音)或通道5(16位声音)
uint8_t channel = 1; // 这里使用8位声音通道
// 确认buffer_addr是否在DMA可访问的区域内
if (buffer_addr >= 0x01000000) {
printf("错误: 缓冲区地址0x%08X超出DMA可访问范围
", buffer_addr);
printf("DMA只能访问前16MB的内存区域
");
return;
}
// 配置DMA传输
pc_setup_dma(channel, buffer_addr, count, play);
// 模拟声卡操作
printf("
模拟声卡操作:
");
if (play) {
printf("1. 设置声卡为播放模式
");
printf("2. 设置采样率、音频格式等参数
");
printf("3. 使能声卡DMA请求
");
printf("4. 声卡向DMA控制器发出DRQ1请求
");
printf("5. 音频数据从内存(0x%08X)传输到声卡
", buffer_addr);
printf("6. 声卡将数字音频数据转换为模拟信号输出
");
} else {
printf("1. 设置声卡为录音模式
");
printf("2. 设置采样率、音频格式等参数
");
printf("3. 使能声卡DMA请求
");
printf("4. 声卡向DMA控制器发出DRQ1请求
");
printf("5. 声卡将模拟音频信号转换为数字数据
");
printf("6. 音频数据从声卡传输到内存(0x%08X)
", buffer_addr);
}
printf("7. 缓冲区填满后,声卡产生中断
");
printf("8. 系统可以处理已录制/播放的音频数据
");
printf("
声卡DMA传输示例完成
");
}
int main() {
printf("IBM PC系统中的8237A DMA控制器应用示例
");
printf("===================================
");
// 初始化PC系统中的DMA控制器
init_pc_dma_controllers();
// 软盘DMA读写示例
pc_floppy_dma_example(0x00100000, 512, true); // 读取一个扇区
// 声卡DMA示例
pc_sound_card_dma_example(0x00200000, 8192, true); // 播放音频
return 0;
}
这个示例展示了在IBM PC兼容机系统中8237A DMA控制器的应用,特别是软盘控制器和声卡的DMA传输。这些是PC架构中最常见的DMA应用场景,它们利用DMA传输提高了系统性能,减轻了CPU负担。
10.2.2 DMA数据传输技术
DMA数据传输是一种高效的数据移动技术,允许外设与内存之间直接交换数据,无需CPU持续干预。8237A控制器支持多种DMA传输方式,适用于不同的应用场景。
DMA传输的基本类型:
单次传输:
每次传输一个字节或字每次传输都需要DMA请求适合低速或间歇性传输
块传输:
一次传输整个数据块直到计数为零或外部终止信号到来才停止适合高速或大量数据传输
需求传输:
只要DRQ信号保持有效就持续传输当DRQ信号撤销时暂停传输适合不稳定速率的设备
存储器到存储器传输:
8237A的特殊模式在PC架构中很少使用允许内存区域之间的数据移动
DMA传输优势:
减轻CPU负担:CPU不需要参与数据移动过程提高数据吞吐量:数据直接移动,减少总线争用减少中断开销:只在传输开始和结束时需要CPU干预提高实时响应性:外设数据处理无需等待CPU
下面是一个实现不同DMA传输类型的C语言示例:
c
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
// 8237A端口定义(基于PC架构)
#define DMA1_CH0_ADDR 0x00 // 通道0地址寄存器
#define DMA1_CH0_COUNT 0x01 // 通道0计数寄存器
#define DMA1_CH1_ADDR 0x02 // 通道1地址寄存器
#define DMA1_CH1_COUNT 0x03 // 通道1计数寄存器
#define DMA1_CH2_ADDR 0x04 // 通道2地址寄存器
#define DMA1_CH2_COUNT 0x05 // 通道2计数寄存器
#define DMA1_CH3_ADDR 0x06 // 通道3地址寄存器
#define DMA1_CH3_COUNT 0x07 // 通道3计数寄存器
#define DMA1_STATUS 0x08 // 状态寄存器
#define DMA1_COMMAND 0x08 // 命令寄存器
#define DMA1_REQUEST 0x09 // 请求寄存器
#define DMA1_MASK_SINGLE 0x0A // 单通道掩码寄存器
#define DMA1_MODE 0x0B // 模式寄存器
#define DMA1_CLEAR_FF 0x0C // 清除字节指针触发器
#define DMA1_MASTER_CLEAR 0x0D // 主清除寄存器
#define DMA1_CLEAR_MASK 0x0E // 清除掩码寄存器
#define DMA1_MASK_ALL 0x0F // 全通道掩码寄存器
// 页寄存器
#define DMA_PAGE_CH0 0x87 // 通道0页寄存器
#define DMA_PAGE_CH1 0x83 // 通道1页寄存器
#define DMA_PAGE_CH2 0x81 // 通道2页寄存器
#define DMA_PAGE_CH3 0x82 // 通道3页寄存器
// 模式寄存器位定义
#define DMA_MODE_CH0 0x00 // 选择通道0
#define DMA_MODE_CH1 0x01 // 选择通道1
#define DMA_MODE_CH2 0x02 // 选择通道2
#define DMA_MODE_CH3 0x03 // 选择通道3
#define DMA_MODE_VERIFY 0x00 // 校验传输
#define DMA_MODE_WRITE 0x04 // 写传输 (I/O到内存)
#define DMA_MODE_READ 0x08 // 读传输 (内存到I/O)
#define DMA_MODE_AUTO_INIT 0x10 // 自动初始化模式
#define DMA_MODE_ADDR_DEC 0x20 // 地址递减
#define DMA_MODE_DEMAND 0x00 // 需求模式
#define DMA_MODE_SINGLE 0x40 // 单字节模式
#define DMA_MODE_BLOCK 0x80 // 块传输模式
#define DMA_MODE_CASCADE 0xC0 // 级联模式
// 命令寄存器位定义
#define DMA_CMD_MEM_TO_MEM 0x01 // 存储器到存储器使能
#define DMA_CMD_CH0_HOLD 0x02 // 通道0地址保持
// 模拟内存和设备缓冲区
uint8_t memory[65536]; // 模拟系统内存
uint8_t device_buffer[4096]; // 模拟设备缓冲区
uint8_t io_ports[256]; // 模拟I/O端口
// 模拟outb函数
void outb(uint16_t port, uint8_t value) {
printf("写端口 0x%04X: 0x%02X
", port, value);
io_ports[port & 0xFF] = value;
}
// 模拟inb函数
uint8_t inb(uint16_t port) {
uint8_t value = io_ports[port & 0xFF];
printf("读端口 0x%04X: 0x%02X
", port, value);
return value;
}
// 初始化数据
void init_data(uint16_t mem_addr, uint16_t count, uint8_t pattern) {
printf("
初始化内存数据 (地址: 0x%04X, 计数: %d, 模式: 0x%02X)
",
mem_addr, count, pattern);
for (int i = 0; i < count; i++) {
memory[mem_addr + i] = pattern + i;
}
memset(device_buffer, 0, sizeof(device_buffer));
}
// 配置单字节传输模式
void configure_single_transfer(uint8_t channel, uint16_t address, uint16_t count, bool memory_to_io) {
printf("
配置单字节传输 (通道: %d, 地址: 0x%04X, 计数: %d)
",
channel, address, count);
uint8_t mode = channel; // 选择通道
if (memory_to_io) {
mode |= DMA_MODE_READ; // 内存到I/O
printf("传输方向: 内存到I/O
");
} else {
mode |= DMA_MODE_WRITE; // I/O到内存
printf("传输方向: I/O到内存
");
}
mode |= DMA_MODE_SINGLE; // 单字节模式
printf("传输模式: 单字节
");
// 屏蔽通道
outb(DMA1_MASK_SINGLE, channel | 0x04);
// 清除字节指针触发器
outb(DMA1_CLEAR_FF, 0);
// 设置模式
outb(DMA1_MODE, mode);
// 设置地址
outb(DMA1_CLEAR_FF, 0);
outb(DMA1_CH0_ADDR + channel * 2, address & 0xFF);
outb(DMA1_CH0_ADDR + channel * 2, (address >> 8) & 0xFF);
// 设置计数
outb(DMA1_CLEAR_FF, 0);
outb(DMA1_CH0_COUNT + channel * 2, (count - 1) & 0xFF);
outb(DMA1_CH0_COUNT + channel * 2, ((count - 1) >> 8) & 0xFF);
// 解除通道掩码
outb(DMA1_MASK_SINGLE, channel);
}
// 配置块传输模式
void configure_block_transfer(uint8_t channel, uint16_t address, uint16_t count, bool memory_to_io) {
printf("
配置块传输 (通道: %d, 地址: 0x%04X, 计数: %d)
",
channel, address, count);
uint8_t mode = channel; // 选择通道
if (memory_to_io) {
mode |= DMA_MODE_READ; // 内存到I/O
printf("传输方向: 内存到I/O
");
} else {
mode |= DMA_MODE_WRITE; // I/O到内存
printf("传输方向: I/O到内存
");
}
mode |= DMA_MODE_BLOCK; // 块传输模式
printf("传输模式: 块传输
");
// 屏蔽通道
outb(DMA1_MASK_SINGLE, channel | 0x04);
// 清除字节指针触发器
outb(DMA1_CLEAR_FF, 0);
// 设置模式
outb(DMA1_MODE, mode);
// 设置地址
outb(DMA1_CLEAR_FF, 0);
outb(DMA1_CH0_ADDR + channel * 2, address & 0xFF);
outb(DMA1_CH0_ADDR + channel * 2, (address >> 8) & 0xFF);
// 设置计数
outb(DMA1_CLEAR_FF, 0);
outb(DMA1_CH0_COUNT + channel * 2, (count - 1) & 0xFF);
outb(DMA1_CH0_COUNT + channel * 2, ((count - 1) >> 8) & 0xFF);
// 解除通道掩码
outb(DMA1_MASK_SINGLE, channel);
}
// 配置需求传输模式
void configure_demand_transfer(uint8_t channel, uint16_t address, uint16_t count, bool memory_to_io) {
printf("
配置需求传输 (通道: %d, 地址: 0x%04X, 计数: %d)
",
channel, address, count);
uint8_t mode = channel; // 选择通道
if (memory_to_io) {
mode |= DMA_MODE_READ; // 内存到I/O
printf("传输方向: 内存到I/O
");
} else {
mode |= DMA_MODE_WRITE; // I/O到内存
printf("传输方向: I/O到内存
");
}
mode |= DMA_MODE_DEMAND; // 需求模式
printf("传输模式: 需求模式
");
// 屏蔽通道
outb(DMA1_MASK_SINGLE, channel | 0x04);
// 清除字节指针触发器
outb(DMA1_CLEAR_FF, 0);
// 设置模式
outb(DMA1_MODE, mode);
// 设置地址
outb(DMA1_CLEAR_FF, 0);
outb(DMA1_CH0_ADDR + channel * 2, address & 0xFF);
outb(DMA1_CH0_ADDR + channel * 2, (address >> 8) & 0xFF);
// 设置计数
outb(DMA1_CLEAR_FF, 0);
outb(DMA1_CH0_COUNT + channel * 2, (count - 1) & 0xFF);
outb(DMA1_CH0_COUNT + channel * 2, ((count - 1) >> 8) & 0xFF);
// 解除通道掩码
outb(DMA1_MASK_SINGLE, channel);
}
// 配置存储器到存储器传输
void configure_mem_to_mem_transfer(uint16_t src_addr, uint16_t dest_addr, uint16_t count) {
printf("
配置存储器到存储器传输
");
printf("源地址: 0x%04X, 目标地址: 0x%04X, 计数: %d
",
src_addr, dest_addr, count);
// 设置命令寄存器,启用存储器到存储器传输
outb(DMA1_COMMAND, DMA_CMD_MEM_TO_MEM);
// 通道0用于源地址,通道1自动用于目标地址
// 屏蔽通道0
outb(DMA1_MASK_SINGLE, 0 | 0x04);
// 清除字节指针触发器
outb(DMA1_CLEAR_FF, 0);
// 设置通道0地址(源地址)
outb(DMA1_CLEAR_FF, 0);
outb(DMA1_CH0_ADDR, src_addr & 0xFF);
outb(DMA1_CH0_ADDR, (src_addr >> 8) & 0xFF);
// 设置通道1地址(目标地址)
outb(DMA1_CLEAR_FF, 0);
outb(DMA1_CH1_ADDR, dest_addr & 0xFF);
outb(DMA1_CH1_ADDR, (dest_addr >> 8) & 0xFF);
// 设置通道0计数
outb(DMA1_CLEAR_FF, 0);
outb(DMA1_CH0_COUNT, (count - 1) & 0xFF);
outb(DMA1_CH0_COUNT, ((count - 1) >> 8) & 0xFF);
// 解除通道0掩码以启动传输
outb(DMA1_MASK_SINGLE, 0);
printf("存储器到存储器传输启动
");
}
// 模拟DMA传输过程
void simulate_dma_transfer(bool memory_to_io, uint16_t mem_addr, uint16_t count) {
printf("
模拟DMA传输过程...
");
if (memory_to_io) {
printf("传输: 内存 -> 设备缓冲区
");
for (int i = 0; i < count; i++) {
device_buffer[i] = memory[mem_addr + i];
}
} else {
printf("传输: 设备缓冲区 -> 内存
");
for (int i = 0; i < count; i++) {
memory[mem_addr + i] = device_buffer[i];
}
}
printf("DMA传输完成
");
}
// 模拟存储器到存储器传输
void simulate_mem_to_mem_transfer(uint16_t src_addr, uint16_t dest_addr, uint16_t count) {
printf("
模拟存储器到存储器DMA传输...
");
for (int i = 0; i < count; i++) {
memory[dest_addr + i] = memory[src_addr + i];
}
printf("存储器到存储器传输完成
");
}
// 显示内存区域内容
void display_memory(uint16_t address, uint16_t count) {
printf("
内存区域 [0x%04X - 0x%04X] 内容:
", address, address + count - 1);
for (int i = 0; i < count; i++) {
if (i % 16 == 0) {
printf("
0x%04X: ", address + i);
}
printf("%02X ", memory[address + i]);
}
printf("
");
}
// 显示设备缓冲区内容
void display_device_buffer(uint16_t count) {
printf("
设备缓冲区 [0 - %d] 内容:
", count - 1);
for (int i = 0; i < count; i++) {
if (i % 16 == 0) {
printf("
%04d: ", i);
}
printf("%02X ", device_buffer[i]);
}
printf("
");
}
// 测试单字节传输
void test_single_byte_transfer() {
printf("
=============================
");
printf("单字节传输测试
");
printf("=============================
");
// 初始化测试数据
uint16_t mem_addr = 0x1000;
uint16_t count = 32;
init_data(mem_addr, count, 0xA0);
// 配置DMA传输
configure_single_transfer(0, mem_addr, count, true); // 内存到I/O
// 显示初始内存内容
display_memory(mem_addr, count);
// 模拟DMA传输
simulate_dma_transfer(true, mem_addr, count);
// 显示传输结果
display_device_buffer(count);
printf("
单字节传输测试完成
");
}
// 测试块传输
void test_block_transfer() {
printf("
=============================
");
printf("块传输测试
");
printf("=============================
");
// 初始化测试数据
uint16_t mem_addr = 0x2000;
uint16_t count = 64;
init_data(mem_addr, count, 0xB0);
// 配置DMA传输
configure_block_transfer(1, mem_addr, count, true); // 内存到I/O
// 显示初始内存内容
display_memory(mem_addr, count);
// 模拟DMA传输
simulate_dma_transfer(true, mem_addr, count);
// 显示传输结果
display_device_buffer(count);
printf("
块传输测试完成
");
}
// 测试需求传输
void test_demand_transfer() {
printf("
=============================
");
printf("需求传输测试
");
printf("=============================
");
// 初始化测试数据
uint16_t mem_addr = 0x3000;
uint16_t count = 48;
init_data(mem_addr, count, 0xC0);
// 配置DMA传输
configure_demand_transfer(2, mem_addr, count, true); // 内存到I/O
// 显示初始内存内容
display_memory(mem_addr, count);
// 模拟DMA传输
simulate_dma_transfer(true, mem_addr, count);
// 显示传输结果
display_device_buffer(count);
printf("
需求传输测试完成
");
}
// 测试存储器到存储器传输
void test_mem_to_mem_transfer() {
printf("
=============================
");
printf("存储器到存储器传输测试
");
printf("=============================
");
// 初始化测试数据
uint16_t src_addr = 0x4000;
uint16_t dest_addr = 0x5000;
uint16_t count = 128;
init_data(src_addr, count, 0xD0);
// 清空目标区域
memset(memory + dest_addr, 0, count);
// 显示初始内存内容
printf("源内存区域:");
display_memory(src_addr, count);
printf("目标内存区域:");
display_memory(dest_addr, count);
// 配置DMA传输
configure_mem_to_mem_transfer(src_addr, dest_addr, count);
// 模拟DMA传输
simulate_mem_to_mem_transfer(src_addr, dest_addr, count);
// 显示传输结果
printf("传输后目标内存区域:");
display_memory(dest_addr, count);
printf("
存储器到存储器传输测试完成
");
}
int main() {
printf("DMA数据传输技术示例
");
printf("=================
");
// 测试不同的DMA传输类型
test_single_byte_transfer();
test_block_transfer();
test_demand_transfer();
test_mem_to_mem_transfer();
return 0;
}
这个示例演示了8237A支持的各种DMA传输类型,包括单字节传输、块传输、需求传输和存储器到存储器传输。它展示了不同传输类型的配置方法和操作流程,以及每种类型的传输结果。在实际应用中,选择合适的传输类型对于优化系统性能至关重要。
10.2.3 DMA编程子程序设计
在实际应用中,DMA操作通常被封装在系统级子程序中,以提供标准的接口供驱动程序和应用程序使用。这些子程序通常包含初始化DMA控制器、设置传输参数和处理完成事件等功能。
下面是一个完整的DMA编程子程序库示例,包含常用的DMA操作函数:
c
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
// DMA控制器端口定义(PC架构)
// 第一个DMA控制器(8位传输,通道0-3)
#define DMA1_CH0_ADDR 0x00 // 通道0地址寄存器
#define DMA1_CH0_COUNT 0x01 // 通道0计数寄存器
#define DMA1_CH1_ADDR 0x02 // 通道1地址寄存器
#define DMA1_CH1_COUNT 0x03 // 通道1计数寄存器
#define DMA1_CH2_ADDR 0x04 // 通道2地址寄存器
#define DMA1_CH2_COUNT 0x05 // 通道2计数寄存器
#define DMA1_CH3_ADDR 0x06 // 通道3地址寄存器
#define DMA1_CH3_COUNT 0x07 // 通道3计数寄存器
#define DMA1_STATUS 0x08 // 状态寄存器
#define DMA1_COMMAND 0x08 // 命令寄存器
#define DMA1_REQUEST 0x09 // 请求
#define DMA1_MASK_SINGLE 0x0A // 单通道掩码寄存器
#define DMA1_MODE 0x0B // 模式寄存器
#define DMA1_CLEAR_FF 0x0C // 清除字节指针触发器
#define DMA1_MASTER_CLEAR 0x0D // 主清除寄存器
#define DMA1_CLEAR_MASK 0x0E // 清除掩码寄存器
#define DMA1_MASK_ALL 0x0F // 全通道掩码寄存器
// 第二个DMA控制器(16位传输,通道4-7)
#define DMA2_CH4_ADDR 0xC0 // 通道4地址寄存器
#define DMA2_CH4_COUNT 0xC2 // 通道4计数寄存器
#define DMA2_CH5_ADDR 0xC4 // 通道5地址寄存器
#define DMA2_CH5_COUNT 0xC6 // 通道5计数寄存器
#define DMA2_CH6_ADDR 0xC8 // 通道6地址寄存器
#define DMA2_CH6_COUNT 0xCA // 通道6计数寄存器
#define DMA2_CH7_ADDR 0xCC // 通道7地址寄存器
#define DMA2_CH7_COUNT 0xCE // 通道7计数寄存器
#define DMA2_STATUS 0xD0 // 状态寄存器
#define DMA2_COMMAND 0xD0 // 命令寄存器
#define DMA2_REQUEST 0xD2 // 请求寄存器
#define DMA2_MASK_SINGLE 0xD4 // 单通道掩码寄存器
#define DMA2_MODE 0xD6 // 模式寄存器
#define DMA2_CLEAR_FF 0xD8 // 清除字节指针触发器
#define DMA2_MASTER_CLEAR 0xDA // 主清除寄存器
#define DMA2_CLEAR_MASK 0xDC // 清除掩码寄存器
#define DMA2_MASK_ALL 0xDE // 全通道掩码寄存器
// 页寄存器
#define DMA_PAGE_CH0 0x87 // 通道0页寄存器
#define DMA_PAGE_CH1 0x83 // 通道1页寄存器
#define DMA_PAGE_CH2 0x81 // 通道2页寄存器
#define DMA_PAGE_CH3 0x82 // 通道3页寄存器
#define DMA_PAGE_CH5 0x8B // 通道5页寄存器
#define DMA_PAGE_CH6 0x89 // 通道6页寄存器
#define DMA_PAGE_CH7 0x8A // 通道7页寄存器
// 传输类型定义
typedef enum {
DMA_MODE_DEMAND = 0x00, // 需求模式
DMA_MODE_SINGLE = 0x40, // 单字节模式
DMA_MODE_BLOCK = 0x80, // 块传输模式
DMA_MODE_CASCADE = 0xC0 // 级联模式
} DmaTransferMode;
// 传输方向定义
typedef enum {
DMA_VERIFY = 0x00, // 校验传输
DMA_IO_TO_MEM = 0x04, // I/O到内存
DMA_MEM_TO_IO = 0x08 // 内存到I/O
} DmaTransferDirection;
// DMA通道信息结构
typedef struct {
uint8_t channel; // 通道号(0-7)
uint16_t addr_reg; // 地址寄存器端口
uint16_t count_reg; // 计数寄存器端口
uint16_t page_reg; // 页寄存器端口
uint16_t mask_reg; // 掩码寄存器端口
uint16_t mode_reg; // 模式寄存器端口
uint16_t clear_ff_reg; // 清除字节指针端口
bool is_16bit; // 是否为16位通道
} DmaChannelInfo;
// 模拟I/O函数
void outb(uint16_t port, uint8_t value) {
printf("写端口 0x%04X: 0x%02X
", port, value);
// 实际系统中应该使用真正的I/O指令
}
uint8_t inb(uint16_t port) {
// 模拟读取
printf("读端口 0x%04X
", port);
// 实际系统中应该使用真正的I/O指令
return 0;
}
// 获取通道信息
DmaChannelInfo get_dma_channel_info(uint8_t channel) {
DmaChannelInfo info;
info.channel = channel;
switch (channel) {
case 0:
info.addr_reg = DMA1_CH0_ADDR;
info.count_reg = DMA1_CH0_COUNT;
info.page_reg = DMA_PAGE_CH0;
info.mask_reg = DMA1_MASK_SINGLE;
info.mode_reg = DMA1_MODE;
info.clear_ff_reg = DMA1_CLEAR_FF;
info.is_16bit = false;
break;
case 1:
info.addr_reg = DMA1_CH1_ADDR;
info.count_reg = DMA1_CH1_COUNT;
info.page_reg = DMA_PAGE_CH1;
info.mask_reg = DMA1_MASK_SINGLE;
info.mode_reg = DMA1_MODE;
info.clear_ff_reg = DMA1_CLEAR_FF;
info.is_16bit = false;
break;
case 2:
info.addr_reg = DMA1_CH2_ADDR;
info.count_reg = DMA1_CH2_COUNT;
info.page_reg = DMA_PAGE_CH2;
info.mask_reg = DMA1_MASK_SINGLE;
info.mode_reg = DMA1_MODE;
info.clear_ff_reg = DMA1_CLEAR_FF;
info.is_16bit = false;
break;
case 3:
info.addr_reg = DMA1_CH3_ADDR;
info.count_reg = DMA1_CH3_COUNT;
info.page_reg = DMA_PAGE_CH3;
info.mask_reg = DMA1_MASK_SINGLE;
info.mode_reg = DMA1_MODE;
info.clear_ff_reg = DMA1_CLEAR_FF;
info.is_16bit = false;
break;
case 4:
// 通道4通常用于级联,不直接用于传输
fprintf(stderr, "警告: 通道4通常用于级联,不应直接用于传输
");
info.addr_reg = DMA2_CH4_ADDR;
info.count_reg = DMA2_CH4_COUNT;
info.page_reg = 0; // 无页寄存器
info.mask_reg = DMA2_MASK_SINGLE;
info.mode_reg = DMA2_MODE;
info.clear_ff_reg = DMA2_CLEAR_FF;
info.is_16bit = true;
break;
case 5:
info.addr_reg = DMA2_CH5_ADDR;
info.count_reg = DMA2_CH5_COUNT;
info.page_reg = DMA_PAGE_CH5;
info.mask_reg = DMA2_MASK_SINGLE;
info.mode_reg = DMA2_MODE;
info.clear_ff_reg = DMA2_CLEAR_FF;
info.is_16bit = true;
break;
case 6:
info.addr_reg = DMA2_CH6_ADDR;
info.count_reg = DMA2_CH6_COUNT;
info.page_reg = DMA_PAGE_CH6;
info.mask_reg = DMA2_MASK_SINGLE;
info.mode_reg = DMA2_MODE;
info.clear_ff_reg = DMA2_CLEAR_FF;
info.is_16bit = true;
break;
case 7:
info.addr_reg = DMA2_CH7_ADDR;
info.count_reg = DMA2_CH7_COUNT;
info.page_reg = DMA_PAGE_CH7;
info.mask_reg = DMA2_MASK_SINGLE;
info.mode_reg = DMA2_MODE;
info.clear_ff_reg = DMA2_CLEAR_FF;
info.is_16bit = true;
break;
default:
fprintf(stderr, "错误: 无效的通道号 %d
", channel);
// 返回通道0作为默认值
return get_dma_channel_info(0);
}
return info;
}
// 初始化DMA控制器
void dma_init() {
printf("初始化DMA控制器...
");
// 重置第一个DMA控制器(8位)
outb(DMA1_MASTER_CLEAR, 0);
// 重置第二个DMA控制器(16位)
outb(DMA2_MASTER_CLEAR, 0);
// 设置级联模式(在第一个控制器的通道4上)
outb(DMA1_MODE, 0xC0); // 通道0,级联模式
// 清除所有掩码
outb(DMA1_CLEAR_MASK, 0);
outb(DMA2_CLEAR_MASK, 0);
printf("DMA控制器初始化完成
");
}
// 屏蔽DMA通道
void dma_mask_channel(uint8_t channel, bool mask) {
DmaChannelInfo info = get_dma_channel_info(channel);
printf("%s DMA通道 %d
", mask ? "屏蔽" : "解除屏蔽", channel);
uint8_t value;
if (channel < 4) {
// 通道0-3
value = channel & 0x03;
if (mask) value |= 0x04; // 设置掩码位
} else {
// 通道5-7
value = (channel - 4) & 0x03;
if (mask) value |= 0x04; // 设置掩码位
}
outb(info.mask_reg, value);
}
// 设置DMA通道的传输地址
void dma_set_address(uint8_t channel, uint32_t address, uint16_t count,
DmaTransferMode mode, DmaTransferDirection direction,
bool auto_init) {
DmaChannelInfo info = get_dma_channel_info(channel);
uint32_t dma_addr = address;
uint16_t dma_count = count;
printf("
配置DMA通道 %d
", channel);
printf(" 物理地址: 0x%08X
", address);
printf(" 传输计数: %d
", count);
// 检查地址限制
if (address >= 0x01000000) {
fprintf(stderr, "错误: 地址0x%08X超出DMA可访问范围(16MB)
", address);
return;
}
// 16位通道需要特殊处理
if (info.is_16bit) {
// 16位传输的地址需要右移1位,计数需要除以2
dma_addr = address >> 1;
dma_count = (count + 1) >> 1;
printf(" 16位通道: 地址调整为0x%08X,计数调整为%d
", dma_addr, dma_count);
}
// 提取页和偏移
uint8_t page = (dma_addr >> 16) & 0xFF;
uint16_t offset = dma_addr & 0xFFFF;
printf(" DMA地址: 页=0x%02X, 偏移=0x%04X
", page, offset);
// 屏蔽该通道
dma_mask_channel(channel, true);
// 清除字节指针触发器
outb(info.clear_ff_reg, 0);
// 构建模式寄存器值
uint8_t mode_value = 0;
// 设置通道号(仅使用低2位)
if (channel < 4) {
mode_value |= channel & 0x03;
} else {
mode_value |= (channel - 4) & 0x03;
}
// 设置传输方向
mode_value |= (uint8_t)direction;
// 设置自动初始化
if (auto_init) {
mode_value |= 0x10;
}
// 设置传输模式
mode_value |= (uint8_t)mode;
printf(" 模式寄存器: 0x%02X
", mode_value);
// 写入模式寄存器
outb(info.mode_reg, mode_value);
// 写入页寄存器
if (info.page_reg != 0) {
outb(info.page_reg, page);
}
// 写入地址
outb(info.clear_ff_reg, 0); // 再次清除触发器
outb(info.addr_reg, offset & 0xFF); // 低8位
outb(info.addr_reg, (offset >> 8) & 0xFF); // 高8位
// 写入计数
outb(info.clear_ff_reg, 0); // 再次清除触发器
outb(info.count_reg, (dma_count - 1) & 0xFF); // 低8位
outb(info.count_reg, ((dma_count - 1) >> 8) & 0xFF); // 高8位
// 解除通道掩码
dma_mask_channel(channel, false);
printf("DMA通道 %d 配置完成
", channel);
}
// 软件触发DMA请求
void dma_trigger_request(uint8_t channel) {
DmaChannelInfo info = get_dma_channel_info(channel);
printf("软件触发DMA通道 %d 的请求
", channel);
uint8_t value;
if (channel < 4) {
// 通道0-3
value = channel & 0x03;
value |= 0x04; // 设置软件请求位
outb(DMA1_REQUEST, value);
} else {
// 通道5-7
value = (channel - 4) & 0x03;
value |= 0x04; // 设置软件请求位
outb(DMA2_REQUEST, value);
}
}
// 检查DMA传输是否完成
bool dma_is_transfer_complete(uint8_t channel) {
uint8_t status;
if (channel < 4) {
// 通道0-3
status = inb(DMA1_STATUS);
return (status & (1 << (channel + 4))) != 0;
} else {
// 通道5-7
status = inb(DMA2_STATUS);
return (status & (1 << (channel - 4 + 4))) != 0;
}
}
// 设置内存到I/O的DMA传输
void dma_setup_mem_to_io(uint8_t channel, uint32_t address, uint16_t count,
DmaTransferMode mode, bool auto_init) {
printf("
设置内存到I/O的DMA传输 (通道 %d)
", channel);
dma_set_address(channel, address, count, mode, DMA_MEM_TO_IO, auto_init);
}
// 设置I/O到内存的DMA传输
void dma_setup_io_to_mem(uint8_t channel, uint32_t address, uint16_t count,
DmaTransferMode mode, bool auto_init) {
printf("
设置I/O到内存的DMA传输 (通道 %d)
", channel);
dma_set_address(channel, address, count, mode, DMA_IO_TO_MEM, auto_init);
}
// 等待DMA传输完成
bool dma_wait_for_completion(uint8_t channel, int timeout_ms) {
printf("等待DMA通道 %d 传输完成...
", channel);
// 在实际系统中,这里应该使用适当的延时或事件等待机制
// 此示例中简单模拟等待过程
// 模拟传输完成
printf("DMA传输已完成
");
return true; // 假设传输成功完成
}
// 分配DMA可访问的缓冲区
void* dma_allocate_buffer(size_t size, uint32_t* physical_addr) {
printf("分配DMA缓冲区,大小: %zu 字节
", size);
// 在实际系统中,这里应该分配物理连续的内存,并返回物理地址
// 此示例中简单分配普通内存并假设其物理地址
void* buffer = malloc(size);
if (buffer) {
// 假设的物理地址,实际系统中应获取真实物理地址
*physical_addr = 0x00100000; // 假设分配在1MB地址处
memset(buffer, 0, size);
printf("DMA缓冲区分配成功: 虚拟地址=%p, 物理地址=0x%08X
",
buffer, *physical_addr);
} else {
*physical_addr = 0;
printf("DMA缓冲区分配失败
");
}
return buffer;
}
// 释放DMA缓冲区
void dma_free_buffer(void* buffer) {
printf("释放DMA缓冲区: %p
", buffer);
if (buffer) {
free(buffer);
}
}
// 示例: 使用DMA传输数据到声卡
void example_sound_card_dma() {
printf("
=============================
");
printf("声卡DMA传输示例
");
printf("=============================
");
// 初始化DMA控制器
dma_init();
// 分配DMA缓冲区
uint32_t phys_addr;
const size_t buffer_size = 8192; // 8KB音频缓冲区
void* buffer = dma_allocate_buffer(buffer_size, &phys_addr);
if (!buffer) {
fprintf(stderr, "无法分配DMA缓冲区
");
return;
}
// 填充音频数据(实际应用中应包含真实音频)
printf("填充音频数据...
");
for (int i = 0; i < buffer_size; i++) {
((uint8_t*)buffer)[i] = i & 0xFF;
}
// 配置DMA传输
// 使用通道1, 块传输模式, 不使用自动初始化
dma_setup_mem_to_io(1, phys_addr, buffer_size, DMA_MODE_BLOCK, false);
// 模拟声卡设置
printf("
模拟声卡初始化...
");
printf("1. 设置声卡采样率: 44.1KHz
");
printf("2. 设置音频格式: 8位PCM
");
printf("3. 设置DMA通道: 1
");
// 启动播放
printf("
开始音频播放...
");
// 等待DMA传输完成
dma_wait_for_completion(1, 5000); // 最多等待5秒
printf("
音频播放完成
");
// 释放DMA缓冲区
dma_free_buffer(buffer);
}
// 示例: 使用DMA从网卡接收数据
void example_network_card_dma() {
printf("
=============================
");
printf("网卡DMA接收示例
");
printf("=============================
");
// 分配DMA缓冲区
uint32_t phys_addr;
const size_t buffer_size = 1536; // 以太网最大帧大小
void* buffer = dma_allocate_buffer(buffer_size, &phys_addr);
if (!buffer) {
fprintf(stderr, "无法分配DMA缓冲区
");
return;
}
// 配置DMA传输
// 使用通道5, 单字节模式, 使用自动初始化(连续接收)
dma_setup_io_to_mem(5, phys_addr, buffer_size, DMA_MODE_SINGLE, true);
// 模拟网卡设置
printf("
模拟网卡初始化...
");
printf("1. 设置网卡接收模式: 混杂模式
");
printf("2. 设置DMA通道: 5
");
printf("3. 启用网卡接收
");
// 模拟接收数据
printf("
模拟网络数据包接收...
");
// 模拟网卡中断
printf("
网卡触发中断,表示数据包已接收
");
// 模拟处理接收到的数据
printf("处理接收到的数据包:
");
printf(" 数据包大小: 64字节
");
printf(" 源MAC: 00:11:22:33:44:55
");
printf(" 目标MAC: AA:BB:CC:DD:EE:FF
");
printf(" 协议类型: IP (0x0800)
");
// 释放DMA缓冲区
dma_free_buffer(buffer);
}
// 主函数 - 测试DMA子程序
int main() {
printf("DMA编程子程序设计示例
");
printf("=====================
");
// 初始化DMA控制器
dma_init();
// 运行声卡DMA示例
example_sound_card_dma();
// 运行网卡DMA示例
example_network_card_dma();
printf("
DMA编程子程序设计示例完成
");
return 0;
}
这个DMA编程子程序库展示了如何在实际系统中封装DMA操作,提供了一系列实用函数,包括:
初始化函数:初始化DMA控制器配置函数:设置DMA传输的地址、计数和模式控制函数:屏蔽/解除屏蔽通道、触发请求、等待传输完成内存管理函数:分配和释放DMA可访问的缓冲区应用示例:展示声卡和网卡使用DMA传输数据的场景
这些子程序提供了一个抽象层,隐藏了DMA控制器的硬件细节,使应用程序和驱动程序能够以一种统一的方式使用DMA功能。在实际系统中,这种子程序库通常是操作系统内核或驱动程序框架的一部分。
总结
本章详细介绍了直接内存访问(DMA)技术和8237A DMA控制器的工作原理与应用方法。DMA是计算机系统中一种重要的数据传输技术,它允许外设与内存之间直接交换数据,无需CPU的持续干预,从而显著提升系统性能和效率。
主要内容包括:
8237A DMA控制器的内部架构和引脚功能,介绍了其四个独立的DMA通道、16位地址寄存器、字计数寄存器等组成部分。
8237A的基本操作流程,包括初始化、请求、仲裁、传输和终止五个阶段,详细解释了DMA传输的完整过程。
8237A支持的多种工作模式,如单字节传输、块传输、需求传输和级联模式,以及每种模式的特点和适用场景。
8237A的寄存器系统,包括通道相关寄存器(地址寄存器、计数寄存器等)和全局控制寄存器(命令寄存器、模式寄存器等)。
8237A在IBM PC兼容机中的实际应用,特别是软盘控制器、声卡和硬盘控制器等外设的DMA传输。
DMA数据传输的不同类型和编程技巧,以及如何开发DMA编程子程序库,为应用程序和驱动程序提供统一的接口。
通过学习本章内容,读者应该能够理解DMA技术的基本原理,掌握8237A DMA控制器的编程方法,并能够在实际系统中应用DMA技术提高数据传输效率。尽管现代计算机系统中的DMA控制器已经发生了很大变化,但基本原理和概念仍然适用,理解这些基础知识对于学习现代DMA技术有重要意义。
习题10
简述DMA传输的基本工作原理和优势。
8237A DMA控制器有几个通道?分别支持什么功能?
描述DMA传输的完整过程,从请求到传输完成。
比较8237A中的单字节传输模式、块传输模式和需求传输模式的特点和适用场景。
在PC/AT架构中,为什么需要两个8237A控制器?它们分别负责哪些功能?
编写一个程序,使用C语言模拟8237A DMA控制器从内存到I/O设备的数据传输过程。
在IBM PC兼容机中,软盘控制器通常使用哪个DMA通道?说明使用DMA传输对软盘操作的优势。
如何使用8237A实现循环缓冲区功能?这对于哪些应用场景特别有用?
分析DMA传输对系统性能的影响,尤其是对CPU利用率和数据吞吐量的影响。
设计一个完整的DMA子程序库,包含初始化、配置、启动和结束DMA传输的功能,并编写一个简单的驱动程序使用该库。