详情参考从零开始学Linux设备驱动–Linux内存管理与DMA(万字长文)
内核需要操作页表来建立映射。相关的函数或宏如下:
void *page_address(const struct page *page);
/*
只用于非高端内存的虚拟地址的获取,参数page是分配页得到的struct page 对象地址,返回该物理页对应的内核空间虚拟地址。
*/
void *kmap(struct page *page);
/*
用于返回分配的高端或者非高端内存的虚拟地址,如果不是高端内存,则内部调用的其实是page_adress,也叫作永久映射。但是内核中用于永久映射的区域非常小,所以在不使用的时候,应该尽快排出映射。该函数可能会引起休眠。
*/
void *kmap_atomic(struct page *page);
/*
和kmap功能类似,但操作是原子性的,也叫临时映射。
*/
void kunmap(struct page *page);
/*
用于解除前面提到的映射
*/
这些内存分配API的典型用法如下:
struct page *p;
void *kva;
void *hva;
p=alloc_pages(GFP_KERNEL,2);
if(!p)
return -ENOMEM;
kva=page_address(p);
......
_free_pages(p, 2);
p=alloc_pages(_GFP_HIGHMEM,2);
if(!p)
return -ENOMEM;
hva=kmap(p);
……
kunmap(p);
_free_pages (p, 2);
一般我们分配内存分为两步:
这可步骤可能略显繁琐,首先我们需要分配物理内存页,然后在将其映射到虚拟地址,最后才能得到我们所需要的可以使用的内存。所以内核又提供了另外一组将上面两个步骤合二为一的函数,常见的函数如下:
unsigned long __get_free_pages(gfp_t gfp_mask,unsigned int order);
//分配2的order次方的页
unsigned long __get_free_page(gfp_t gfp_mask);
//分配单独的一页物理内存
unsigned long get_zeroed_page(gfp_t gfp_mask);
//获取清零页
void free_pages(unsigned long addr,unsigned int order);
//释放2的order次方页
void free_page(unsigned long addr);
//释放一页
@gfp_mask :和前文提到的掩码一样
@addr :分配得到的内核虚拟地址
常见的用法
void *kva;
kva = (void *)__get_free_pages(GFP_KERNEL,2);
...
free_pages((unsigned long)kva,2);
在学习了动态内存分配的知识后,我们可以把虚拟串口驱动中的全局变量尽可能地用动态内存来替代,这在一个驱动支持多个设备的时候是比较常见的,特别是在设备是动态添加的情况下,不过在本例中,并没有充分体现动态内存的优点。涉及的主要修改代码如下:
struct vser_dev {
struct kfifo fifo;
wait_queue_head_t rwqh;
struct fasync_struct *fapp;
atomic_t available;
unsigned int baud;
struct option opt;
struct cdev cdev;
};
static struct vser_dev *vsdev;
static int __init vser_init(void)
{
int ret;
dev_t dev;
dev = MKDEV(VSER_MAJOR, VSER_MINOR);
ret = register_chrdev_region(dev, VSER_DEV_CNT, VSER_DEV_NAME);
if (ret)
goto reg_err;
vsdev = kzalloc(sizeof(struct vser_dev), GFP_KERNEL);
if (!vsdev) {
ret = -ENOMEM;
goto mem_err;
}
ret = kfifo_alloc(&vsdev->fifo, VSER_FIFO_SIZE, GFP_KERNEL);
if (ret)
goto fifo_err;
.....
}
static void __exit vser_exit(void)
{
dev_t dev;
dev = MKDEV(VSER_MAJOR, VSER_MINOR);
free_irq(167, &vsdev);
cdev_del(&vsdev->cdev);
unregister_chrdev_region(dev, VSER_DEV_CNT);
kfifo_free(&vsdev->fifo);
kfree(vsdev);
}
代码
struct kfifo fifo;
将以前的全局vsfifo的定义放到struct vser_dev结构中
代码
static struct vser_dev *vsdev;
将以前的vsdev对象改成了对象指针。
代码
vsdev = kzalloc(sizeof(struct vser_dev), GFP_KERNEL);
使用kzalloc
来动态分配struct vser_dev
结构对象,因为对内存的分配没有特殊的要求,所以内存分配掩码为GFP_KERNEL
。
代码
ret = kfifo_alloc(&vsdev->fifo, VSER_FIFO_SIZE, GFP_KERNEL);
动态分配FIFO需要的内存空间,大小由VSER_FIFO_SIZE
指定。内存掩码同样是GFP_KERNEL
。
在模块清除函数中,代码
kfifo_free(&vsdev->fifo);
kfree(vsdev);
分别释放了FIFO的内存
和struct vser_dev对象的内存
相关灯的原理图如下:
z
从上面知道,fs4412目标板上的4个LED灯LED2、LED3、LED4、LED5分别接到了Exynos4412CPU的GPX2.7、GPX1.0、GPF3.4和GPF3.5管脚上,并且是高电平点亮,低电平熄灭。
接下来查看Exynos4412的芯片手册,获取对应的SFR信息,如图
如上图,GPX2.7管脚对应的配置寄存器为GPX2CON,其地址为0x11000C40
要配置GPX2.7管脚为输出模式,则 bit3 1:bit28位应设置为0x1。
GPX2.7管脚对应的数据寄存器为GPX2DAT,其地址为0x11000C44
要使管脚输出高电平,则 bit7应设置为1,要使管脚输出低电平,则 bit7应设置为0。
When you configure port as input port then corresponding bit is pin state. When configuring as output port then pin state should be same as corresponding bit. When the port is configured as functional pin, the undefined value will be read.
当您将端口配置为输入端口时,相应的位是引脚状态。当配置为输出端口时,引脚状态应与相应位相同。当端口配置为功能引脚时,将读取未定义的值。
要使管脚输出高电平,则 bit7应设置为1,要使管脚输出低电平,则 bit7应设置为0。
其他的LED灯,都可以按照该方式进行类似的操作,在此不再赘述。
根据上面分析,代码如下:
/* fsled.h */
#ifndef _FSLED_H
#define _FSLED_H
#define FSLED_MAGIC 'f'
#define FSLED_ON _IOW(FSLED_MAGIC, 0, unsigned int)
#define FSLED_OFF _IOW(FSLED_MAGIC, 1, unsigned int)
#define LED2 0
#define LED3 1
#define LED4 2
#define LED5 3
#endif
在“fsled.h” 文件中,代码第6行和第7行定义了两个用于点灯和灭灯的命令FSLED_ON和FSLED_OFF
接下来定义了每个LED灯的编号LED2、LED3、LED4和LED5,作为ioctl命令的参数。
/* fsled.c */
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/ioctl.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include "fsled.h"
#define FSLED_MAJOR 256
#define FSLED_MINOR 0
#define FSLED_DEV_CNT 1
#define FSLED_DEV_NAME "fsled"
#define GPX2_BASE 0x11000C40
#define GPX1_BASE 0x11000C20
#define GPF3_BASE 0x114001E0
struct fsled_dev {
unsigned int __iomem *gpx2con;
unsigned int __iomem *gpx2dat;
unsigned int __iomem *gpx1con;
unsigned int __iomem *gpx1dat;
unsigned int __iomem *gpf3con;
unsigned int __iomem *gpf3dat;
atomic_t available;
struct cdev cdev;
};
static struct fsled_dev fsled;
static int fsled_open(struct inode *inode, struct file *filp)
{
if (atomic_dec_and_test(&fsled.available))
return 0;
else {
atomic_inc(&fsled.available);
return -EBUSY;
}
}
static int fsled_release(struct inode *inode, struct file *filp)
{
writel(readl(fsled.gpx2dat) & ~(0x1 << 7), fsled.gpx2dat);
writel(readl(fsled.gpx1dat) & ~(0x1 << 0), fsled.gpx1dat);
writel(readl(fsled.gpf3dat) & ~(0x3 << 4), fsled.gpf3dat);
atomic_inc(&fsled.available);
return 0;
}
static long fsled_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
if (_IOC_TYPE(cmd) != FSLED_MAGIC)
return -ENOTTY;
switch (cmd) {
case FSLED_ON:
switch(arg) {
case LED2:
writel(readl(fsled.gpx2dat) | (0x1 << 7), fsled.gpx2dat);
break;
case LED3:
writel(readl(fsled.gpx1dat) | (0x1 << 0), fsled.gpx1dat);
break;
case LED4:
writel(readl(fsled.gpf3dat) | (0x1 << 4), fsled.gpf3dat);
break;
case LED5:
writel(readl(fsled.gpf3dat) | (0x1 << 5), fsled.gpf3dat);
break;
default:
return -ENOTTY;
}
break;
case FSLED_OFF:
switch(arg) {
case LED2:
writel(readl(fsled.gpx2dat) & ~(0x1 << 7), fsled.gpx2dat);
break;
case LED3:
writel(readl(fsled.gpx1dat) & ~(0x1 << 0), fsled.gpx1dat);
break;
case LED4:
writel(readl(fsled.gpf3dat) & ~(0x1 << 4), fsled.gpf3dat);
break;
case LED5:
writel(readl(fsled.gpf3dat) & ~(0x1 << 5), fsled.gpf3dat);
break;
default:
return -ENOTTY;
}
break;
default:
return -ENOTTY;
}
return 0;
}
static struct file_operations fsled_ops = {
.owner = THIS_MODULE,
.open = fsled_open,
.release = fsled_release,
.unlocked_ioctl = fsled_ioctl,
};
static int __init fsled_init(void)
{
int ret;
dev_t dev;
dev = MKDEV(FSLED_MAJOR, FSLED_MINOR);
ret = register_chrdev_region(dev, FSLED_DEV_CNT, FSLED_DEV_NAME);
if (ret)
goto reg_err;
memset(&fsled, 0, sizeof(fsled));
atomic_set(&fsled.available, 1);
cdev_init(&fsled.cdev, &fsled_ops);
fsled.cdev.owner = THIS_MODULE;
ret = cdev_add(&fsled.cdev, dev, FSLED_DEV_CNT);
if (ret)
goto add_err;
fsled.gpx2con = ioremap(GPX2_BASE, 8);
fsled.gpx1con = ioremap(GPX1_BASE, 8);
fsled.gpf3con = ioremap(GPF3_BASE, 8);
if (!fsled.gpx2con || !fsled.gpx1con || !fsled.gpf3con) {
ret = -EBUSY;
goto map_err;
}
fsled.gpx2dat = fsled.gpx2con + 1;
fsled.gpx1dat = fsled.gpx1con + 1;
fsled.gpf3dat = fsled.gpf3con + 1;
writel((readl(fsled.gpx2con) & ~(0xF << 28)) | (0x1 << 28), fsled.gpx2con);
writel((readl(fsled.gpx1con) & ~(0xF << 0)) | (0x1 << 0), fsled.gpx1con);
writel((readl(fsled.gpf3con) & ~(0xFF << 16)) | (0x11 << 16), fsled.gpf3con);
writel(readl(fsled.gpx2dat) & ~(0x1 << 7), fsled.gpx2dat);
writel(readl(fsled.gpx1dat) & ~(0x1 << 0), fsled.gpx1dat);
writel(readl(fsled.gpf3dat) & ~(0x3 << 4), fsled.gpf3dat);
return 0;
map_err:
if (fsled.gpf3con)
iounmap(fsled.gpf3con);
if (fsled.gpx1con)
iounmap(fsled.gpx1con);
if (fsled.gpx2con)
iounmap(fsled.gpx2con);
add_err:
unregister_chrdev_region(dev, FSLED_DEV_CNT);
reg_err:
return ret;
}
static void __exit fsled_exit(void)
{
dev_t dev;
dev = MKDEV(FSLED_MAJOR, FSLED_MINOR);
iounmap(fsled.gpf3con);
iounmap(fsled.gpx1con);
iounmap(fsled.gpx2con);
cdev_del(&fsled.cdev);
unregister_chrdev_region(dev, FSLED_DEV_CNT);
}
module_init(fsled_init);
module_exit(fsled_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Kevin Jiang <jiangxg@farsight.com.cn>");
MODULE_DESCRIPTION("A simple character device driver for LEDs on FS4412 board");
在“fsled.c"文件中,代码第21行至第23行定义了SFR 寄存器的基地址
#define GPX2_BASE 0x11000C40
#define GPX1_BASE 0x11000C20
#define GPF3_BASE 0x114001E0
这些地址都是通过查手册得到的。就是咱们上面算出来的地址
代码第26行至第31行
struct fsled_dev {
unsigned int __iomem *gpx2con;
unsigned int __iomem *gpx2dat;
unsigned int __iomem *gpx1con;
unsigned int __iomem *gpx1dat;
unsigned int __iomem *gpf3con;
unsigned int __iomem *gpf3dat;
atomic_t available;
struct cdev cdev;
};
定义了分别用来保存6个SFR映射后的虚拟地址的成员。
代码第133行至第140行是SFR的映射操作
fsled.gpx2con = ioremap(GPX2_BASE, 8);
fsled.gpx1con = ioremap(GPX1_BASE, 8);
fsled.gpf3con = ioremap(GPF3_BASE, 8);
if (!fsled.gpx2con || !fsled.gpx1con || !fsled.gpf3con) {
ret = -EBUSY;
goto map_err;
}
因为CON寄存器和DAT寄存器是连续的
,所以这里每次连续映射了8个字节
(注意,这里在映射之前并没有调用request_mem_region
,是因为内核中的其他驱动申请了这部分I/O内存空间)。
代码第142行至第144行
fsled.gpx2dat = fsled.gpx2con + 1;
fsled.gpx1dat = fsled.gpx1con + 1;
fsled.gpf3dat = fsled.gpf3con + 1;
是对应的DAT寄存器的虚拟地址计算,这里利用了指向unsigned int
类型的指针加1刚好地址值加4
的特点。
代码第146行至第152行
writel((readl(fsled.gpx2con) & ~(0xF << 28)) | (0x1 << 28), fsled.gpx2con);
writel((readl(fsled.gpx1con) & ~(0xF << 0)) | (0x1 << 0), fsled.gpx1con);
writel((readl(fsled.gpf3con) & ~(0xFF << 16)) | (0x11 << 16), fsled.gpf3con);
writel(readl(fsled.gpx2dat) & ~(0x1 << 7), fsled.gpx2dat);
writel(readl(fsled.gpx1dat) & ~(0x1 << 0), fsled.gpx1dat);
writel(readl(fsled.gpf3dat) & ~(0x3 << 4), fsled.gpf3dat);
将对应的管脚配置为输出,并且输出低电平。
这里使用了较多的位操作,主要思想就是先将原来寄存器的内容读出来,然后清除相应的位,接着设置相应的位,最后再写回寄存器中。
代码第175行至第177行
iounmap(fsled.gpf3con);
iounmap(fsled.gpx1con);
iounmap(fsled.gpx2con);
在模块卸载时解除映射。
/
代码第58行至第105行是点灯和灭灯的具体实现,首先判断了命令是否合法,然后根据是点灯还是灭灯来对单个的LED进行具体的操作,其中arg是要操作的LED灯的编号。
static long fsled_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
if (_IOC_TYPE(cmd) != FSLED_MAGIC)
return -ENOTTY;
switch (cmd) {
case FSLED_ON:
switch(arg) {
case LED2:
writel(readl(fsled.gpx2dat) | (0x1 << 7), fsled.gpx2dat);
break;
case LED3:
writel(readl(fsled.gpx1dat) | (0x1 << 0), fsled.gpx1dat);
break;
case LED4:
writel(readl(fsled.gpf3dat) | (0x1 << 4), fsled.gpf3dat);
break;
case LED5:
writel(readl(fsled.gpf3dat) | (0x1 << 5), fsled.gpf3dat);
break;
default:
return -ENOTTY;
}
break;
case FSLED_OFF:
switch(arg) {
case LED2:
writel(readl(fsled.gpx2dat) & ~(0x1 << 7), fsled.gpx2dat);
break;
case LED3:
writel(readl(fsled.gpx1dat) & ~(0x1 << 0), fsled.gpx1dat);
break;
case LED4:
writel(readl(fsled.gpf3dat) & ~(0x1 << 4), fsled.gpf3dat);
break;
case LED5:
writel(readl(fsled.gpf3dat) & ~(0x1 << 5), fsled.gpf3dat);
break;
default:
return -ENOTTY;
}
break;
default:
return -ENOTTY;
}
return 0;
}
代码第50行至第52行在关闭设备文件时将所有LED灯熄灭。
static int fsled_release(struct inode *inode, struct file *filp)
{
writel(readl(fsled.gpx2dat) & ~(0x1 << 7), fsled.gpx2dat);
writel(readl(fsled.gpx1dat) & ~(0x1 << 0), fsled.gpx1dat);
writel(readl(fsled.gpf3dat) & ~(0x3 << 4), fsled.gpf3dat);
atomic_inc(&fsled.available);
return 0;
}
/
/* test.c */
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <errno.h>
#include "fsled.h"
int main(int argc, char *argv[])
{
int fd;
int ret;
int num = LED2;
fd = open("/dev/led", O_RDWR);
if (fd == -1)
goto fail;
while (1) {
ret = ioctl(fd, FSLED_ON, num);
if (ret == -1)
goto fail;
usleep(500000);
ret = ioctl(fd, FSLED_OFF, num);
if (ret == -1)
goto fail;
usleep(500000);
num = (num + 1) % 4;
}
fail:
perror("led test");
exit(EXIT_FAILURE);
}
在“test.c"文件中,首先打开了LED设备,然后在while循环中先点一个LED灯,延时0.5秒后又熄灭这个LED灯,再延时0.5秒,最后调整操作LED灯的编号。如此循环往复,LED灯被轮流点亮、熄灭。
下面以一个内存到内存的DMA传输实例来具体说明dmaengine的编程方法
/* memcpy.c */
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/dmaengine.h>
#include <linux/dma-mapping.h>
#include <linux/slab.h>
struct dma_chan *chan;
unsigned int *txbuf;
unsigned int *rxbuf;
dma_addr_t txaddr;
dma_addr_t rxaddr;
static void dma_callback(void *data)
{
int i;
unsigned int *p = rxbuf;
printk("dma complete\n");
for (i = 0; i < PAGE_SIZE / sizeof(unsigned int); i++)
printk("%d ", *p++);
printk("\n");
}
static bool filter(struct dma_chan *chan, void *filter_param)
{
printk("%s\n", dma_chan_name(chan));
return strcmp(dma_chan_name(chan), filter_param) == 0;
}
static int __init memcpy_init(void)
{
int i;
dma_cap_mask_t mask;
struct dma_async_tx_descriptor *desc;
char name[] = "dma2chan0";
unsigned int *p;
dma_cap_zero(mask);
dma_cap_set(DMA_MEMCPY, mask);
chan = dma_request_channel(mask, filter, name);
if (!chan) {
printk("dma_request_channel failure\n");
return -ENODEV;
}
txbuf = dma_alloc_coherent(chan->device->dev, PAGE_SIZE, &txaddr, GFP_KERNEL);
if (!txbuf) {
printk("dma_alloc_coherent failure\n");
dma_release_channel(chan);
return -ENOMEM;
}
rxbuf = dma_alloc_coherent(chan->device->dev, PAGE_SIZE, &rxaddr, GFP_KERNEL);
if (!rxbuf) {
printk("dma_alloc_coherent failure\n");
dma_free_coherent(chan->device->dev, PAGE_SIZE, txbuf, txaddr);
dma_release_channel(chan);
return -ENOMEM;
}
for (i = 0, p = txbuf; i < PAGE_SIZE / sizeof(unsigned int); i++)
*p++ = i;
for (i = 0, p = txbuf; i < PAGE_SIZE / sizeof(unsigned int); i++)
printk("%d ", *p++);
printk("\n");
memset(rxbuf, 0, PAGE_SIZE);
for (i = 0, p = rxbuf; i < PAGE_SIZE / sizeof(unsigned int); i++)
printk("%d ", *p++);
printk("\n");
desc = chan->device->device_prep_dma_memcpy(chan, rxaddr, txaddr, PAGE_SIZE, DMA_CTRL_ACK | DMA_PREP_INTERRUPT);
desc->callback = dma_callback;
desc->callback_param = NULL;
dmaengine_submit(desc);
dma_async_issue_pending(chan);
return 0;
}
static void __exit memcpy_exit(void)
{
dma_free_coherent(chan->device->dev, PAGE_SIZE, txbuf, txaddr);
dma_free_coherent(chan->device->dev, PAGE_SIZE, rxbuf, rxaddr);
dma_release_channel(chan);
}
module_init(memcpy_init);
module_exit(memcpy_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Kevin Jiang <jiangxg@farsight.com.cn>");
MODULE_DESCRIPTION("simple driver using dmaengine");
代码第40行和第41行
dma_cap_zero(mask);
dma_cap_set(DMA_MEMCPY, mask);
先将掩码清零,然后设置掩码为DMA_MEMCPY
,表示要获得一个能完成内存到内存传输的DMA通道。
代码第42行
chan = dma_request_channel(mask, filter, name);
调用dma_request_channel
来获取一个DMA通道,filter
是通道过滤函数,name
是通道的名字。
在 filter
函数中,通过对比通道的名字来确定一个通道,驱动指定要获取dma2chan0这个通道。因为Exynos4412总共有3个 DMA,其中 dma2专门用于内存到内存的传输,如图7.8所示。
代码第48行至第61行
txbuf = dma_alloc_coherent(chan->device->dev, PAGE_SIZE, &txaddr, GFP_KERNEL);
if (!txbuf) {
printk("dma_alloc_coherent failure\n");
dma_release_channel(chan);
return -ENOMEM;
}
rxbuf = dma_alloc_coherent(chan->device->dev, PAGE_SIZE, &rxaddr, GFP_KERNEL);
if (!rxbuf) {
printk("dma_alloc_coherent failure\n");
dma_free_coherent(chan->device->dev, PAGE_SIZE, txbuf, txaddr);
dma_release_channel(chan);
return -ENOMEM;
}
使用dma_alloc_coherent
建立了两个 DMA 缓冲区的一致性映射,每个缓冲区为一页。txbuf、rxbuf用于保存虚拟地址
,txaddr、rxaddr用于保存总线地址
。
代码第63行至第73行
for (i = 0, p = txbuf; i < PAGE_SIZE / sizeof(unsigned int); i++)
*p++ = i;
for (i = 0, p = txbuf; i < PAGE_SIZE / sizeof(unsigned int); i++)
printk("%d ", *p++);
printk("\n");
memset(rxbuf, 0, PAGE_SIZE);
for (i = 0, p = rxbuf; i < PAGE_SIZE / sizeof(unsigned int); i++)
printk("%d ", *p++);
printk("\n");
则是初始化这两片内存,用于传输完成后的验证。
代码第74行至第76行
desc = chan->device->device_prep_dma_memcpy(chan, rxaddr, txaddr, PAGE_SIZE, DMA_CTRL_ACK | DMA_PREP_INTERRUPT);
desc->callback = dma_callback;
desc->callback_param = NULL;
创建了一个内存到内存的传输描述符,指定了目的地址、源地址(都是总线地址)、传输大小和一些标志,并制定了回调函数为dma_callback
,在传输完成后自动被调用。
代码第78行和第79行
dmaengine_submit(desc);
dma_async_issue_pending(chan);
分别是提交传输并发起传输。dma_callback
在传输结束后被调用,打印了目的内存里面的内容,如果传输成功,那么目的内存的内容和源内存的内容一样。
在这个例子中并没有进行传输参数设置,因为是内存到内存传输,这些参数是可以自动得到的。编译和测试的命令如下:
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
原文链接 : https://blog.csdn.net/qq_35629971/article/details/125097472
内容来源于网络,如有侵权,请联系作者删除!