DMA驱动程序解析

1DMA的具体工作过程:

1)外设向DMA发出请求

2DMA通过HOLDCPU发出总线请求

3CPU响应释放三总线,并且发应答HLDA

4DMA向外设发DMA应答

5DMA发出地址、控制信号,为外设传输数据

6)传送完规定的数据后,DMA撤销HOLD信号,CPU也撤销HOLD信号,并且恢复对三总线的控制

2、驱动程序编写

  在本驱动程序中,我们打算在内存中开辟两个空间,分别作为源和目的。我们用两个方法将源中的数据写到目的中,一种方法是让cpu去做,另外一种发放是让DMA去做!好的,闲话少说,直接分析代码:


#include<linux/module.h>
#include<linux/kernel.h>
#include<linux/fs.h>
#include<linux/init.h>
#include<linux/delay.h>
#include<linux/irq.h>
#include<linux/poll.h>
#include<linux/dma-mapping.h>
#include<asm/uaccess.h>
#include<asm/irq.h>
#include<asm/io.h>
#include<asm/arch/regs-gpio.h>
#include<asm/hardware.h>


#define MEM_CPY_NO_DMA 0
#define MEM_CPY_DMA 1
#define BUF_SIZE (512*1024)

#define DMA0_BASE_ADDR 0x4B000000
#define DMA1_BASE_ADDR 0x4B000040
#define DMA2_BASE_ADDR 0x4B000080
#define DMA3_BASE_ADDR 0x4B0000C0

struct s3c_dma_regs {
unsigned long disrc;
unsigned long disrcc;
unsigned long didst;
unsigned long didstc;
unsigned long dcon;
unsigned long dstat;
unsigned long dcsrc;
unsigned long dcdst;
unsigned long dmasktrig;
};

static int major = 0;

/*物理地址和虚拟地址*/
static char *src;
static u32 src_phys;
static char *dst;
static u32 dst_phys;

static struct class * cls;
static volatile struct s3c_dma_regs *dma_regs;
static DECLARE_WAIT_QUEUE_HEAD(dma_waitq);

/* 中断标志 */
static volatile int ev_dma = 0;

static int s3c_dma_ioctl(struct inode *inode, struct file *file, unsigned int cmd,unsigned long arg)
{
    int i;
    memset(src, 0xAA, BUF_SIZE);/*1010*/
    memset(dst, 0x55, BUF_SIZE);/*0101*/

    switch (cmd)
    {
    
        case MEM_CPY_NO_DMA ://这是非DMA模式
        {
            for (i = 0; i < BUF_SIZE; i++)
              dst[i] = src[i]; //CPU直接将源拷贝到目的
              if (memcmp(src, dst, BUF_SIZE) == 0)//这个函数见注释2
            {
                  printk("MEM_CPY_NO_DMA OK\n");
            }
            else
            {
                  printk("MEM_CPY_NO_DMA ERROR\n");
            }
              break;
        }

        case MEM_CPY_DMA ://这是DMA模式
        {
            ev_dma = 0;
            /* 把源,目的,长度告诉DMA */
            /* 关于下面寄存器的具体情况,我们在注释3里面来详细讲一下 */
            dma_regs->disrc =src_phys; /* 源的物理地址 */
            dma_regs->disrcc =(0<<1) | (0<<0); /* 源位于AHB总线, 源地址递增 */
            dma_regs->didst =dst_phys; /* 目的的物理地址 */
            dma_regs->didstc =(0<<2) | (0<<1) | (0<<0); /* 目的位于AHB总线, 目的地址递增 */
            dma_regs->dcon = =(1<<30)|(1<<29)|(0<<28)|(1<<27)|(0<<23)|(0<<20)|(BUF_SIZE<<0); /* 使能中断,单个传输,软件触发, */

            /* 启动DMA */
            dma_regs->dmasktrig = (1<<1) |(1<<0);

            /* 让进程休眠,CPU执行其他操作 
              * DMA继续传输数据直到传输完毕触发中断
              *中断处理函数唤醒该进程,打印相关信息
             */
            wait_event_interruptible(dma_waitq, ev_dma);
            if (memcmp(src, dst, BUF_SIZE) == 0)
            {
                  printk("MEM_CPY_DMA OK\n");
            }
            else
            {
                  printk("MEM_CPY_DMA ERROR\n");
            }
            break;
        }

     }

     return 0;
}

static struct file_operations dma_fops = {
  .owner = THIS_MODULE,
  .ioctl = s3c_dma_ioctl,
};

static irqreturn_t s3c_dma_irq(int irq, void *dev_id)
{
    /* 唤醒 */
    ev_dma = 1;
    wake_up_interruptible(&dma_waitq); /* 唤醒休眠的进程 */
    return IRQ_HANDLED;
}

static int s3c_dma_init(void)
{
    /*注册中断 */
    if (request_irq(IRQ_DMA3, s3c_dma_irq, 0,"s3c_dma", 1))
    {
        printk("can't request_irq for DMA\n");
        return -EBUSY;
    }

    /* 分配SRC, DST对应的缓冲区,关于此函数详见注释1*/
    src = dma_alloc_writecombine(NULL, BUF_SIZE,&src_phys, GFP_KERNEL);

    if (src==NULL)
    {
        printk("can't alloc buffer for DMA src\n");
        free_irq(IRQ_DMA3, 1);
        return -ENOMEM;
    }

    dst = dma_alloc_writecombine(NULL, BUF_SIZE,&dst_phys, GFP_KERNEL);

    if (dst==NULL)
    {
    free_irq(IRQ_DMA3, 1);
    dma_free_writecombine(NULL, BUF_SIZE, src,src_phys);
    printk("can't alloc buffer for DMA dst\n");
    return -ENOMEM;
    }

    //注册字符设备
     major =register_chrdev(0, "s3c_dma", &dma_fops);
    /* 自动创建设备节点 */
    cls = class_create(THIS_MODULE,"s3c_dma");
    device_create(cls, NULL, MKDEV(major,0), NULL, "dma"); 

    dma_regs = ioremap(DMA3_BASE_ADDR,sizeof(struct s3c_dma_regs));/*将物理地址映射为虚拟地址*/
    return 0;
}

static void s3c_dma_exit(void)
{
    iounmap(dma_regs);

    class_device_destroy(cls, MKDEV(major, 0));
    class_destroy(cls);

    unregister_chrdev(major, "s3c_dma");

    dma_free_writecombine(NULL, BUF_SIZE, src,src_phys);
    dma_free_writecombine(NULL, BUF_SIZE, dst,dst_phys);

    free_irq(IRQ_DMA3, 1);
}

module_init(s3c_dma_init);
module_exit(s3c_dma_exit);
MODULE_LICENSE("GPL");



注释1

之前我们知道在内核中开辟空间可以用kmalloc函数,这里却用了dma_alloc_writecombine,这是为什么呢?这是因为kmalloc开辟的空间其逻辑地址虽然是连续的,但是其实际的物理地址可能不是连续的。而DMA传输数据时,要求物理地址是连续的,dma_alloc_writecombine就满足这一点,这个函数的原型是:

dma_alloc_writecombine(struct device *dev, size_tsize,dma_addr_t *handle, gfp_t gfp)

其中size代表开辟的空间的大小,handle代表开辟的空间的物理地址,返回值是开辟的空间的逻辑地址。

注释2

int memcmp(const void*cs, const void *ct, size_tcount)

{

const unsigned char *su1, *su2;

int res = 0;

for (su1 = cs, su2 = ct; 0 < count; ++su1,++su2,count--)

 if ((res = *su1 -*su2) != 0)

  break;

return res;

}

我们看到这个函数的作用就是将第一个参数和第二个参数一位一位地比较,一旦不相等就返回,此时返回值为非零。比较的位数为第三个参数,如果前两个参数的前count为都是相等的,那么就会返回0

注释3

我们先来解析一下上面几个寄存器:

DISRCn

bit

Description

Initial State

S_ADDR

[30:0]

源起始地址

0x00000000

DISRCCn

bit

Description

Initial State

LOC

[1]

用于选择源的位置

0:源在系统总线上

1:源在外设总线上

0

INC

[0]

用于选择地址是否自动增加

0:地址自动增加

1:地址固定不变(此时即便是burst模式下,传输过程中地址自动增加,但是一旦传输完这一次数据,地址又变为初值)

0

 

DIDSTn

bit

Description

Initial State

D_ADDR

[30:0]

目的起始地址

0x00000000

DIDSTCn

Bit

Description

Initial State

CHK_INT

[2]

当设置为自动加载时,用来选择中断发生的时间

0TC0是产生中断

1:自动加载完成的时候产生中断

0

LOC

[1]

用于选择目的设备的位置

0:目的设备在系统总线上

1:目的设备在外设总线上

0

INC

[0]

用于选择地址是否自动增加

0:地址自动增加

1:地址固定不变(此时即便是burst模式下,传输过程中地址自动增加,但是一旦传输完这一次数据,地址又重新变为初值)

0

 

DCONn

Bit

Description

Initial State

DMD_HS

[31]

选择为Demand模式或者是握手模式

0:选择为Demand模式

1:选择为握手模式

这 两种模式下都是当发生请求时,DMA控制器开始传输数据并且发出 应 答信号,不同点是握手模式下,当DMA控制器收到请求撤销信号,并且自 身发出应答撤销信号之后才能接收下一次请求。而在Demand模式下,并不需要等待请求撤销信号,他只需要撤销自身的应答信号,然后等待下一 次的请求。

0

SYNC

[30]

选择DREQ/DACK的同步

0DREQ and DACKPCLK同步

1DREQ and DACKHCLK同步

因此当设备在AHB系统总线时,这一位必须为1,而当设备在APB系统 时,它应该被设为0。当设备位于外部系统时,应根据具体情况而定。

0

INT

[29]

是否使能中断

0:禁止中断,用户需要查看状态寄存器来判断传输是否完成

1:使能中断,所有的传输完成之后产生中断信号

0

TSZ

[28]

选择一个原子传输的大小

0:单元传输(一次传输一个单元)

1:突发传输(一次传输四个单元)

0

SERVMODE

[27]

选择是单服务模式还是整体服务模式

0:单服务模式,当一次原子传输完成后需要等待下一个DMA请求

1:整体服务模式,进行多次原子传输,知道传输计数值到达0

0

HWSRCSEL

[26:24]

为每一个DMA选择DMA请求源

具体参见芯片手册

000

SWHW_SEL

[23]

选择DMA源为软件请求模式还是硬件请求模式

0:软件请求模式,需要将寄存器DMASKTRIGSW_TRIG置位

1:硬件请求模式

0

RELOAD

[22]

是否自动重新装载

0:自动重装,当目前的传输计数值变为0时,自动重装

1:不自动重装

RELOAD[1]被设置为0以防无意识地进行新的DMA传输

0

DSZ

[21:20]

要被传输的数据的大小

00 = Byte 01 = Half word

10 = Word 11 = reserved

00

TC

[19:0]

初始化传输计数

0000

这里我们需要注意了,里面有三个东东要分清楚:

DSZ :代表数据的大小

TSZ :一次传输多少个数据

TC :一共传输多少次

所以实际传输的数据的大小为:DSZ * TSZ * TC

我们本程序里面由于设置为数据大小为1个字节,一次传输1个数据,所以传输次数直接就是实际数据的大小了。

DSTATn

Bit

Description

Initial State

STAT

[21:20]

DMA控制器的状态

00DMA控制器已经准备好接收下一个DMA请求

01DMA控制器正在处理DMA请求

00

CURR_TC

[19:0]

传输计数的当前值

每个原子传输减1

 

DCSRCn

Bit

Description

Initial State

CURR_SRC

[30:0]

当前的源地址

0x00000000

 

DCDSTn

Bit

Description

Initial State

CURR_DST

[30:0]

当前的目的地址

0x00000000

DMASKTRIGn

Bit

Description

Initial State

STOP

[2]

停止DMA操作

1:当前的原子传输完成之后,就停止DMA操作。如果当前没有原子传输正在进行,就立即结束。

 

ON_OFF

[1]

DMA通道的开/

0DMA通道关闭

1DMA通道打开,并且处理DMA请求

 

SW_TRIG

[0]

1:在软件请求模式时触发DMA通道

 

OK!寄存器分析完毕,具体设置就不在写出来了!

在此我们在来总结一下DMA的操作流程:

我们首先设置DMA的工作方式,然后打开DMA通道,紧接着我们使进程休眠,进入等待队列。与此同时,在DMA控制器的作用下,从源向目的拷贝数据。一旦数据拷贝完成,就会触发中断,在中断函数里面,唤醒进程,从而程序继续运行,打印相关信息。

3、应用程序编写(测试用)

<span style="font-size:18px;">#include <stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<sys/ioctl.h>
#include<string.h>

#define MEM_CPY_NO_DMA 0
#define MEM_CPY_DMA 1
void print_usage(char*name)
{
printf("Usage:\n");
printf("%s <nodma | dma>\n",name);
}
int main(int argc,char **argv)
{
 int fd;
 if (argc != 2)
 {
print_usage(argv[0]);
return -1;
 }
fd = open("/dev/dma", O_RDWR);
if (fd < 0)
{
printf("can't open /dev/dma\n");
return -1;
}
if (strcmp(argv[1], "nodma") == 0)
{
while (1)
{
  ioctl(fd,MEM_CPY_NO_DMA);
}
}
else if (strcmp(argv[1], "dma") ==0)
{
while (1)
{
  ioctl(fd,MEM_CPY_DMA);
}
}
else
{
print_usage(argv[0]);
return -1;
}
return 0;
}
</span>

4、测试

# insmod dma.ko //加载驱动

# cat /proc/interrupts //查看中断

CPU0

30: 52318 s3c S3C2410 Timer Tick

33: 0 s3c s3c-mci

34: 0 s3c I2SSDI

35: 0 s3c I2SSDO

36: 0 s3c s3c_dma

37: 12 s3c s3c-mci

42: 0 s3c ohci_hcd:usb1

43: 0 s3c s3c2440-i2c

51: 2725 s3c-ext eth0

60: 0 s3c-ext s3c-mci

70: 97 s3c-uart0 s3c2440-uart

71: 100 s3c-uart0 s3c2440-uart

83: 0 - s3c2410-wdt

Err: 0

 

# ls /dev/dma //查看设备

/dev/dma

 

# ./dmatest //如此就会打印用法

Usage:

./dmatest <nodma | dma>

 

# ./dmatest dma //DMA方式拷贝,CPU可以做其他事情

MEM_CPY_DMA OK

MEM_CPY_DMA OK

MEM_CPY_DMA OK

MEM_CPY_DMA OK

MEM_CPY_DMA OK

MEM_CPY_DMA OK

MEM_CPY_DMA OK

 

# ./dmatest nodma //CPU拷贝,各种竞争CPU

MEM_CPY_DMA OK

MEM_CPY_DMA OK

MEM_CPY_DMA OK

MEM_CPY_DMA OK


  • 2
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
LINUX设备驱动第三版_ 前言 第一章 设备驱动程序简介 设备驱动程序的作用 内核功能划分 设备和模块的分类 安全问题 版本编号 许可证条款 加入内核开发社团 本书概要 第二章 构造和运行模块 设置测试系统 Hello World模块 核心模块与应用程序的对比 编译和装载 内核符号表 预备知识 初始化和关闭 模块参数 在用户空间编写驱动程序 快速参考 第三章 字符设备驱动程序 scull的设计 主设备号和次设备号 一些重要的数据结构 字符设备的注册 open和release scull的内存使用 read和write 试试新设备 快速参考 第四章 调试技术 内核中的调试支持 通过打印调试 通过查询调试 通过监视调试 调试系统故障 调试器和相关工具 第五章 并发和竞态 scull的缺陷 并发及其管理 信号量和互斥体 completion 自旋锁 锁陷阱 除了锁之外的办法 快速参考 第六章 高级字符驱动程序操作 ioctl 阻塞型I/O poll和select 异步通知 定位设备 设备文件的访问控制 快速参考 第七章 时间、延迟及延缓操作 度量时间差 获取当前时间 延迟执行 内核定时器 tasklet 工作队列 快速参考 第八章 分配内存 kmalloc函数的内幕 后备高速缓存 get_free_page和相关函数 vmalloc及其辅助函数 per-CPU变量 获取大的缓冲区 快速参考 第九章 与硬件通信 I/O端口和I/O内存 使用I/O端口 I/O端口示例 使用I/O内存 快速参考 第十章 中断处理 准备并口 安装中断处理例程 实现中断处理例程 顶半部和底半部 中断共享 中断驱动的I/O 快速参考 第十一章 内核的数据类型 使用标准C语言类型 为数据项分配确定的空间大小 接口特定的类型 其他有关移植性的问题 链表 快速参考 第十二章 PCI驱动程序 PCI接口 ISA回顾 PC/104和PC/104+ 其他的PC总线 SBus NuBus 外部总线 快速参考 第十三章 USB驱动程序 USB设备基础 USB和Sysfs USB urb 编写USB驱动程序 不使用urb的USB传输 快速参考 第十四章 Linux设备模型 kobject、kset和子系统 低层sysfs操作 热插拔事件的产生 总线、设备和驱动程序 类 各环节的整合 热插拔 处理固件 快速索引 第十五章 内存映射和DMA Linux的内存管理 mmap设备操作 执行直接I/O访问 直接内存访问 快速参考 第十六章 块设备驱动程序 注册 块设备操作 请求处理 其他一些细节 快速参考 第十七章 网络驱动程序 snull设计 连接到内核 net_device结构细节 打开和关闭 数据包传输 数据包的接收 中断处理例程 不使用接收中断 链路状态的改变 套接字缓冲区 MAC 地址解析 定制 ioctl 命令 统计信息 组播 其他知识点详解 快速参考 第十八章 TTY驱动程序 小型TTY驱动程序 tty_driver函数指针 TTY线路设置 ioctls proc和sysfs对TTY设备的处理 tty_driver结构详解 tty_operations结构详解 tty_struct结构详解 快速参考 参考书目 9112405-1_o.jpg (85.53 KB, 下载次数: 50)
RK3399是一款由Rockchip公司生产的64位六核处理器,它集成了双千兆以太网MAC控制器。在RK3399的Linux内核中,提供了对该以太网控制器的stmmac驱动程序支持。以下是对RK3399 stmmac驱动程序解析: 1. 设备树绑定 RK3399的设备树文件中需要进行stmmac驱动程序的绑定。在设备树中需要指定MAC地址、PHY地址、中断控制器、中断号等信息。以下是设备树中stmmac驱动程序的绑定示例: ``` &mac { status = "okay"; pinctrl-names = "default"; pinctrl-0 = <&mac_rgmii_pins>, <&mac_clk_rx_pins>, <&mac_clk_tx_pins>; phy-mode = "rgmii"; phy-handle = <&phy0>; phy0: ethernet-phy@0 { reg = <0>; compatible = "ethernet-phy-ieee802.3-c22"; }; interrupts = <GIC_SPI 123 IRQ_TYPE_LEVEL_HIGH>; interrupt-names = "macirq"; local-mac-address = [XX XX XX XX XX XX]; }; ``` 2. 驱动程序初始化 在驱动程序初始化时,需要进行一些基本的设置,如设置MAC地址、中断处理程序、DMA通道等。以下是RK3399 stmmac驱动程序初始化的代码示例: ``` static int rk3399_stmmac_probe(struct platform_device *pdev) { struct rk_priv_data *bsp_priv; struct stmmac_resources stmmac_res; int ret; /* Allocate bsp_priv */ bsp_priv = devm_kzalloc(&pdev->dev, sizeof(struct rk_priv_data), GFP_KERNEL); if (!bsp_priv) return -ENOMEM; /* Get GPIO reset pin */ bsp_priv->reset_gpio = of_get_named_gpio(pdev->dev.of_node, "reset-gpios", 0); if (gpio_is_valid(bsp_priv->reset_gpio)) { ret = devm_gpio_request_one(&pdev->dev, bsp_priv->reset_gpio, GPIOF_OUT_INIT_LOW, "stmmac_reset"); if (ret) { dev_err(&pdev->dev, "Failed to request GPIO reset pin %d: %d\n", bsp_priv->reset_gpio, ret); goto err_out; } } /* Set MAC address */ stmmac_res.mac_addr = bsp_priv->mac_addr; /* Set IRQ handler */ stmmac_res.irq = platform_get_irq(pdev, 0); stmmac_res.irqflags = IRQF_TRIGGER_LOW; stmmac_res.irqhandler = rk3399_stmmac_isr; /* Set DMA channel */ stmmac_res.dma_rx_channel = dma_request_slave_channel(&pdev->dev, "rx"); stmmac_res.dma_tx_channel = dma_request_slave_channel(&pdev->dev, "tx"); /* Initialize stmmac */ ret = stmmac_dvr_probe(&pdev->dev, &stmmac_res, &rk3399_pdata, &bsp_priv->plat_dat); if (ret) goto err_out; return 0; err_out: return ret; } ``` 3. DMA传输 RK3399 stmmac驱动程序使用DMA引擎来实现高速数据传输。在DMA传输开始前,需要配置DMA通道和缓冲区。以下是RK3399 stmmac驱动程序DMA传输的代码示例: ``` static int rk3399_stmmac_start_xmit(struct sk_buff *skb, struct net_device *ndev) { struct rk_priv_data *bsp_priv = netdev_priv(ndev); struct stmmac_xgmac_dma_ops *dma_ops = &bsp_priv->plat_dat->dma_ops; struct stmmac_packet_attrs attrs; dma_addr_t dma_addr; int ret; /* Configure DMA buffer */ skb->ip_summed = CHECKSUM_NONE; skb_push(skb, 2); skb->data[0] = 0; skb->data[1] = 0; /* Configure DMA attributes */ attrs.tci = 0; attrs.proto = 0; attrs.dma_desc = NULL; attrs.tx_csum = 0; attrs.tx_timestamp = 0; attrs.tx_vlan = 0; attrs.tx_vlan_cfi = 0; /* Map DMA buffer */ dma_addr = dma_map_single(&pdev->dev, skb->data, skb->len, DMA_TO_DEVICE); if (dma_mapping_error(&pdev->dev, dma_addr)) { dev_err(&pdev->dev, "Failed to map DMA buffer\n"); goto err_out; } /* Configure DMA descriptor */ dma_ops->tx_dma_desc_init(ndev, &attrs, dma_addr, skb->len, 0); /* Start DMA transfer */ ret = dma_ops->tx_dma(ndev, &attrs); if (ret) { dev_err(&pdev->dev, "Failed to start DMA transfer\n"); dma_unmap_single(&pdev->dev, dma_addr, skb->len, DMA_TO_DEVICE); goto err_out; } return NETDEV_TX_OK; err_out: dev_kfree_skb(skb); return NETDEV_TX_OK; } ``` 总的来说,RK3399 stmmac驱动程序是一个功能完善、性能优越的以太网驱动程序,它使用了DMA引擎来实现高速数据传输。通过对设备树进行绑定和执行初始化,可以使该驱动程序在RK3399平台上正常工作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值