资讯详情

Linux内核驱动—内存管理与DMA

参考从零开始学习的细节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
       )
       ; 
      

一般我们分配内存分为两步:

  1. 获取物理内存页
  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对象的内存

I/O内存实例(以fs4412 LED灯为例)

相关灯的原理图如下:

在这里插入图片描述 z 从上面知道,fs4412目标板上的

接下来查看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行

 

标签: hva扩散硅压力变送器

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台