嵌入式linux-嵌入式内核及驱动开发,字符设备驱动,驱动和应用程序的设计思想,编写字符设备驱动的步骤和规范,操作寄存器地址的方式 readl/writel()

文章目录

  • 1, 驱动和应用程序的设计思想
    • 1.1,应用程序和驱动扮演的是什么角色
  • 2,编写字符设备驱动的步骤和规范
    • 2.1,步骤:
    • 2.2,规范:
  • 3,操作寄存器地址的方式 readl/writel():
    • 3.1,传统的方式
    • 3.2,内核提供的方式
  • 4,例---LED灯闪烁
    • 4.1,驱动代码 led_drv.c
    • 4.2,应用程序 led_test.c
    • 4.3,Makefile
    • 4.4,串口终端信息

1, 驱动和应用程序的设计思想

1.1,应用程序和驱动扮演的是什么角色

用户态:应用程序
	    玩策略: 怎么去做
				1, 一闪一闪
				2,10s闪一次,也可以1s闪一次
				3,一直亮
				4,跑马灯
		控制权是在应用程序(程序员)
--------------------------------------
内核态:驱动
		玩机制: 能做什么 
				led:亮 和 灭

2,编写字符设备驱动的步骤和规范

2.1,步骤:

	1,实现模块加载和卸载入口函数
			module_init(chr_dev_init);
			module_exit(chr_dev_exit);
	
	2,在模块加载入口函数中
		a,申请主设备号  (内核中用于区分和管理不同字符设备)
				 register_chrdev(dev_major, "chr_dev_test", &my_fops);

		b,创建设备节点文件 (为用户提供一个可操作到文件接口--open())
				struct  class *class_create(THIS_MODULE, "chr_cls");
				struct  device *device_create(devcls, NULL, MKDEV(dev_major, 0), NULL, "chr2");

		c, 硬件的初始化
			   1,地址的映射
					gpx2conf = ioremap(GPX2_CON, GPX2_SIZE);
			   2,中断到申请
			   3,实现硬件的寄存器的初始化
					// 需要配置gpio功能为输出
					*gpx2conf &= ~(0xf<<28);
					*gpx2conf |= (0x1<<28);
		e,实现file_operations
				const struct file_operations my_fops = {
						.open = chr_drv_open,
						.read = chr_drv_read,
						.write = chr_drv_write,
						.release = chr_drv_close,
				};

2.2,规范:

	1,面向对象编程思想
		用一个结构体来表示一个对象

		//设计一个类型,描述一个设备的信息
		struct led_desc{
			unsigned int dev_major; //设备号
			struct class *cls;
			struct device *dev; //创建设备文件
			void *reg_virt_base;
		};

		struct led_desc *led_dev;//表示一个全局的设备对象

		
		// 0(在init中第一步做), 实例化全局的设备对象--分配空间
		//  GFP_KERNEL 如果当前内存不够用到时候,该函数会一直阻塞(休眠)
		//  #include 
		led_dev = kmalloc(sizeof(struct led_desc), GFP_KERNEL);
		if(led_dev == NULL)
		{
			printk(KERN_ERR "malloc error\n");
			return -ENOMEM;
		}

		led_dev->dev_major = 250;

	2,做出错处理
			在某个位置出错了,要将之前申请到资源进行释放
	
		led_dev = kmalloc(sizeof(struct led_desc), GFP_KERNEL);	
	
		led_dev->dev_major = register_chrdev(0, "led_dev_test", &my_fops);
		if(led_dev->dev_major < 0)
		{
			printk(KERN_ERR "register_chrdev error\n");
			ret = -ENODEV;
			goto err_0;
		}


		err_0:
			kfree(led_dev);
			return ret;

3,操作寄存器地址的方式 readl/writel():

3.1,传统的方式

	volatile unsigned long *gpxcon;
	*gpxcon &= ~(0xf<<28);

3.2,内核提供的方式

readl/writel();
	u32 readl(const volatile void __iomem *addr) 	//从地址中读取地址空间的值

	void writel(unsigned long value , const volatile void __iomem *addr)		// 将value的值写入到addr地址

	例子1:
		// gpio的输出功能的配置
		u32 value = readl(led_dev->reg_virt_base);
		value &= ~(0xf<<28);
		value |= (0x1<<28);
		writel(value, led_dev->reg_virt_bas);	

	例子2:
			*gpx2dat |= (1<<7);
		替换成:
			writel( readl(led_dev->reg_virt_base + 4) | (1<<7),   led_dev->reg_virt_base + 4 );

4,例—LED灯闪烁

4.1,驱动代码 led_drv.c

#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include 

//设计一个类型,描述一个设备的信息
struct led_desc{
	unsigned int dev_major; 	//主设备号
	struct class *cls;
	struct device *dev; 	//创建设备文件
	void *reg_virte_base; 	//存放虚拟地址的首地址(寄存器地址的基准值)
};

//物理地址
//#define GPX2_CON 	(*(volatile unsigned int *)0x11000C40)  //此处是裸机驱动开发时的用法,此处不能用
#define GPX2_CON 0x11000C40
//GPX2_DATA就是GPX2_CON + 4
#define GPX2_SIZE 8

struct led_desc *led_dev; 	//申明设备对象

static int kernel_val = 555;

ssize_t led_drv_read(struct file *filep, char __user *buf, size_t count, loff_t *fpos)
{
	int ret;
	
	printk("---------%s-------------\n",__FUNCTION__);

	//从内核空间拷贝数据到用户空间
	ret = copy_to_user(buf, &kernel_val, count);
	if(ret > 0)
	{
		printk("copy_to_user error\n");
		return -EFAULT;			
	}
	
	return 0;
}
ssize_t led_drv_write(struct file *filep, const char __user *buf, size_t count, loff_t *fops)
{
	int ret;
	int value;
	
	printk("---------%s-------------\n",__FUNCTION__);

	//从用户空间拷贝数据到内核空间
	ret = copy_from_user(&value, buf, count);
	if(ret > 0)
	{
		printk("copy_from_user error\n");
		return -EFAULT;			
	}

	//控制GPX2_7 I/O口电平变化
	if(value)
	{
		writel((readl(led_dev->reg_virte_base + 4) | (0x1 << 7)), led_dev->reg_virte_base + 4);
	}
	else
	{
		writel((readl(led_dev->reg_virte_base + 4) & ~(0x1 << 7)), led_dev->reg_virte_base + 4);
	}
	
	return 0;
}
int led_drv_open(struct inode *inode, struct file *filep)
{
	printk("---------%s-------------\n",__FUNCTION__);
	
	return 0;
}
int led_drv_close(struct inode *inode, struct file *filep)
{
	printk("---------%s-------------\n",__FUNCTION__);
	
	return 0;
}

const struct file_operations my_fops = {
	.open = led_drv_open,
	.read = led_drv_read,
	.write = led_drv_write,
	.release = led_drv_close,
};

static int __init led_dev_init(void)
{
	int ret;
	
	printk("---------%s-------------\n",__FUNCTION__);

	//实例化全局的设备对象---分配空间
	led_dev = kmalloc(sizeof(struct led_desc), GFP_KERNEL);
	if(led_dev == NULL)
	{
		printk(KERN_ERR "malloc error\n");
		return -ENOMEM;
	}

	//装载一般都是申请设备号资源
	//申请主设备号

#if 0	//静态申请主设备号
	led_dev->dev_major = 250;
	ret = register_chrdev(led_dev->dev_major, "led_dev_test", &my_fops);
	if(ret == 0){
		printk("register ok\n");
	}
	else{
		printk("register failed\n");
		ret = -EINVAL;
		goto err_0;
	}
#else 	//动态态申请主设备号
	led_dev->dev_major = register_chrdev(0, "led_dev_test", &my_fops);
	if(led_dev->dev_major < 0){
		printk(KERN_ERR "register_chrdev error\n");
		ret = -ENODEV;
		goto err_0;
	}
	else{
		printk("register ok\n");
	}
#endif

	//自动创建设备节点
	led_dev->cls = class_create(THIS_MODULE, "led_cls");
	if(IS_ERR(led_dev->cls))
	{
		printk(KERN_ERR "class_create error\n");
		ret = PTR_ERR(led_dev->cls); 	//将指针出错的具体原因转换成一个出错码
		goto err_1;
	}
	led_dev->dev = device_create(led_dev->cls,NULL,MKDEV(led_dev->dev_major, 0),NULL,"led%d",0); 	// 	/dev/led0
	if(IS_ERR(led_dev->dev))
	{
		printk(KERN_ERR "device_create error\n");
		ret = PTR_ERR(led_dev->dev); 	//将指针出错的具体原因转换成一个出错码
		goto err_2;
	}

	//地址映射
	led_dev->reg_virte_base = ioremap(GPX2_CON, GPX2_SIZE);
	if(led_dev->reg_virte_base == NULL)
	{
		printk(KERN_ERR "ioremap error\n");
		ret = -ENOMEM;
		goto err_3;
	}

	//配置GPIO的功能为输出
#if 0
	u32 value = readl(led_dev->reg_virte_base);
	value = (value & ~(0xf << 28)) | (0x1 << 28);
	writel(value, led_dev->reg_virte_base);
#else
	writel(((readl(led_dev->reg_virte_base) & ~(0xf << 28)) | (0x1 << 28)), led_dev->reg_virte_base);
#endif
	return 0;
	
err_3:
	device_destroy(led_dev->cls,MKDEV(led_dev->dev_major, 0));
err_2:
	class_destroy(led_dev->cls);
err_1:
	unregister_chrdev(led_dev->dev_major,"led_dev_test");	
err_0:
	kfree(led_dev);	
	return ret;
	
}

static void __exit led_dev_exit(void)
{
	printk("---------%s-------------\n",__FUNCTION__);
	
 	//卸载一般都是释放资源

	//去映射
	iounmap(led_dev->reg_virte_base);

	//销毁节点和类
	device_destroy(led_dev->cls,MKDEV(led_dev->dev_major, 0));
	class_destroy(led_dev->cls);

	//释放主设备号
 	unregister_chrdev(led_dev->dev_major,"led_dev_test");	

	//释放动态内存
 	kfree(led_dev);	
}

module_init
(led_dev_init);
module_exit(led_dev_exit);
MODULE_LICENSE("GPL");

4.2,应用程序 led_test.c

#include 
#include 
#include 
#include 

int main(int argc, char *argv[])
{
	//调用驱动
	int fd;
	int value = 0;

	fd = open("/dev/led0", O_RDWR);
	if(fd < 0)
	{
		perror("led_test open");
		exit(1);
	}
	
	read(fd,&value,4);
	printf("__USER__ : value = %d\n",value);

	while(1)
	{
		value = 1;
		write(fd, &value, 4);
		sleep(1);

		
		value = 0;
		write(fd, &value, 4);
		sleep(1);
	}

	close(fd);

	return 0;
}

4.3,Makefile

ROOTFS_DIR = /nfs/rootfs#根文件系统路径

APP_NAME = led_test
MODULE_NAME = led_drv
CROSS_COMPILE = arm-none-linux-gnueabi-
CC = $(CROSS_COMPILE)gcc

ifeq ($(KERNELRELEASE),)

KERNEL_DIR = /home/linux/linux-3.14.79  		#编译过的内核源码的路径
CPU_DIR = $(shell pwd) 	#当前路径

all:
	make -C $(KERNEL_DIR) M=$(CPU_DIR) modules  #把当前路径编成modules
	@#make -C 进入到内核路径
	@#M 指定当前路径
	$(CC) $(APP_NAME).c -o $(APP_NAME)

clean:
	make -C $(KERNEL_DIR) M=$(CPU_DIR) clean
	rm $(APP_NAME)

install:
	sudo cp -raf *.ko $(APP_NAME) $(ROOTFS_DIR)/drv_module 	#把当前的所有.ko文件考到根文件系统的drv_module目录

else

obj-m += $(MODULE_NAME).o  #指定内核要将哪个文件编译成ko

endif

4.4,串口终端信息

嵌入式linux-嵌入式内核及驱动开发,字符设备驱动,驱动和应用程序的设计思想,编写字符设备驱动的步骤和规范,操作寄存器地址的方式 readl/writel()_第1张图片

你可能感兴趣的:(嵌入式linux-嵌入式内核及驱动开发,字符设备驱动,驱动和应用程序的设计思想,编写字符设备驱动的步骤和规范,操作寄存器地址的方式 readl/writel())