嵌入式内核及驱动开发-04 字符设备驱动编写(readl-writel)

文章目录

  • 驱动和应用程序的设计思想
    • 应用程序和驱动扮演的是什么角色
  • 编写字符设备驱动的步骤和规范
    • 步骤
    • 规范:
  • 操作寄存器地址的方式 readl/writel():
    • 传统的方式
    • 内核提供的方式
  • 例—LED灯闪烁
    • 驱动代码 led_drv.c
    • 应用程序 led_test.c
    • Makefile

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

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

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

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

步骤

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,
		};

规范:

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;

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

传统的方式

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

内核提供的方式

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 );

例—LED灯闪烁

驱动代码 led_drv.c

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

#include 

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

LED_DESC_T *led_dev;    //申明设备对象


//物理地址
#define GPX2_CON 0x11000C40  
#define GPX2_SIZE 8


static int kernel_val = 555;





ssize_t led_drv_read(struct file * filep, char __user * buf, size_t count, loff_t *fops)
{
	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_to_user error\n");
		return -EFAULT;
	}

	//控制GPX2_7 I/O口电平变化
	if(value)
	{
		writel( readl(led_dev->reg_virte_base + 4) | (1<<7),   led_dev->reg_virte_base  + 4 );
	}
	else
	{
		writel( readl(led_dev->reg_virte_base + 4) & ~(1<<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__);
	
	
	//0,  实例化全局的设备对象---分配空间
	led_dev = kmalloc(sizeof( LED_DESC_T), GFP_KERNEL);
	if( NULL == led_dev)
	{ 
		printk(KERN_ERR "malloc error\n");
		return -ENOMEM;
	}

	//1,  一般都是申请设备号资源
	//申请主设备号
	
#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

	//2,创建设备文件
	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;
	}

	// 3, 硬件初始化
	// 对地址进行映射
	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");

应用程序 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;
}

Makefile

ROOTFS_DIR = /nfs/rootfs/

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

ifeq ($(KERNELRELEASE),)

KERNEL_DIR = /home/zzw/share/linux-3.14.79 	
CPU_DIR = $(shell pwd) 

all:
	make -C $(KERNEL_DIR) M=$(CPU_DIR) modules
	$(CC) $(APP_NAME).c -o $(APP_NAME)
	
clean:
	make -C $(KERNEL_DIR) M=$(CPU_DIR) clean
	rm -rf  $(APP_NAME)
	
install:
	sudo cp -raf *.ko $(APP_NAME) $(ROOTFS_DIR)/drv_module
	
else

obj-m += $(MODULE_NAME).o 

endif

你可能感兴趣的:(驱动开发)