【Linux学习之驱动:字符设备驱动(一)】

重要结构体

字符设备主要就是通过以下重要的结构体进行实现

struct cdev {
	struct kobject kobj; //内核对象
	struct module *owner;
	const struct file_operations *ops;//设备对应的操作方法
	struct list_head list;//设备链表
	dev_t dev;//设备号
	unsigned int count;
};
struct file_operations {
	struct module *owner;
	loff_t (*llseek) (struct file *, loff_t, int);
	ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
	ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *)	
	..........	
	..........
};

字符设备驱动几个要点

1.注册接口
负责驱动的注册工作
2.卸载接口
负责驱动的卸载工作
3.file_operation
提供设备驱动的操作方法,常见的write、read、iocrtl等接口

驱动初始化

static int __init xxx_chrdev_init(void)
{
	...
}

主要做的工作如下

1.第一步:申请主、次设备号(可以自己定义也可以动态申请)

静态申请设备号并注册:

dev_id = MKDEV(MYMAJOR, 0);//获取主次设备号
retval = register_chrdev_region (dev_id, MYCNT, MYDEVNAME);//注册MYCNT个设备,主次设备号信息存储在dev_id中,设备名为MYDEVNAME="testchar"
if (retval < 0)
{
	printk(KERN_ERR "register_chrdev fail\n");
	return -EINVAL;
}
printk(KERN_INFO "register_chrdev success....\n");

动态申请设备号并注册:

//动态自动获取设备号
retval = alloc_chrdev_region (&dev_id,0, MYCNT, MYDEVNAME);//注册MYCNT个设备,主次设备号信息存储在dev_id中,设备名为MYDEVNAME="testchar"
if (retval < 0)
{
	printk(KERN_ERR "alloc_chrdev_region fail\n");
	goto flag1;
}

2.第二步:注册字符设备驱动

cdev_init(&test_cdev, &test_fops);//void cdev_init(struct cdev *cdev, const struct file_operations *fops)
retval = cdev_add(&test_cdev, dev_id, MYCNT);//int cdev_add(struct cdev *p, dev_t dev, unsigned count)
if (retval) 
{
	printk(KERN_ERR "Unable to cdev_add\n");
	goto flag2;
}

设备驱动安装

我们为了方便调试,编译的时候是用的module的方式编译,最后生成.ko文件copy到开发板中再进行安装

几个命令需要介绍:

lsmod #显示当前安装的module
insmod xxx.ko #安装module
rmmod xxx.ko #卸载module
mknod /dev/xxx c 设备号 次设备号 #c表示char设备  此操作可以根据主次设备号在/dev目录下创建对应的设备文件,后续用户可以通过访问该设备文件对设备进行操作


通过以下命令可以查看当前注册的设备

cat /proc/devices

在这里插入图片描述

实验

1.编译驱动和应用测试程序(后面附带完整程序和脚本)
2.把生成的.ko文件和app copy到开发板中
3.安装模块
在这里插入图片描述
4.生成设备节点
在这里插入图片描述
5.执行程序应用程序
在这里插入图片描述

驱动代码led_test.c

#include <linux/module.h>		// module_init  module_exit
#include <linux/init.h>			// __init   __exit
#include <linux/fs.h>			// __init   __exit
#include <asm/uaccess.h>
#include <mach/regs-gpio.h>
#include <mach/gpio-bank.h>	// arch/arm/mach-s5pv210/include/mach/gpio-bank.h
#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/cdev.h>

#define MYDEVNAME		"testchar"
#define MYMAJOR			200
#define MYCNT		1

#define GPJ0CON		S5PV210_GPJ0CON
#define GPJ0DAT		S5PV210_GPJ0DAT

#define rGPJ0CON	*((volatile unsigned int *)GPJ0CON)
#define rGPJ0DAT	*((volatile unsigned int *)GPJ0DAT)


#define GPJ0CON_PA		 0xE0200240
#define GPJ0DAT_PA		 0xE0200244
unsigned int * pGPJ0CON;
unsigned int * pGPJ0DAT;

char kbuf[100];   //内核空间的buf

dev_t	dev_id;
static struct cdev test_cdev;


static int test_chrdev_open(struct inode *inode, struct file *file)
{
	// 这个函数中真正应该放置的是打开这个设备的硬件操作代码部分
	// 但是现在暂时我们写不了这么多,所以用一个printk打印个信息来做代表。
	printk(KERN_INFO "test_chrdev_open\n");	
	/*
	*pGPJ0CON = 0x11111111;
	*pGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));		// 亮
	*/
	return 0;
}

static int test_chrdev_release(struct inode *inode, struct file *file)
{
	printk(KERN_INFO "test_chrdev_release\n");
/*	
	*pGPJ0CON = 0x11111111;
	*pGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));		// 灭
	
	iounmap(pGPJ0CON);//解绑
	release_mem_region(GPJ0CON_PA, 4);//释放
	iounmap(pGPJ0DAT);//解绑
	release_mem_region(GPJ0DAT_PA, 4);//释放
*/	
	return 0;
}

static ssize_t test_chrdev_write(struct file *file, const char __user *ubuf, size_t count, loff_t *ppos)
{	
	int ret = -1;
	printk(KERN_INFO "test_chrdev_write\n");
	
	//调用函数static inline unsigned long __must_check copy_from_user(void *to, const void __user *from, unsigned long n) 把内容从应用层复制到内核空间
	ret = copy_from_user(kbuf,ubuf,count);
	
	if(ret)//说明没有完全拷贝完
	{		
		printk(KERN_INFO "copy_from_user  fail\n");
		return -EINVAL;
	}
	printk(KERN_INFO "copy_from_user  success\n");
			
	return 0;
}

ssize_t test_chrdev_read(struct file *file, char __user *ubuf, size_t count, loff_t *ppos)
{
	int ret = -1;
	printk(KERN_INFO "test_chrdev_read\n");
	
	//调用函数static inline long copy_to_user(void __user *to,const void *from, unsigned long n)  把内容从内核复制到应用层
	ret = copy_to_user(ubuf,kbuf,count);
	
	if(ret)//说明没有完全拷贝完
	{		
		printk(KERN_INFO "copy_to_user  fail\n");
		return -EINVAL;
	}
	printk(KERN_INFO "copy_to_user  success\n");
		
	return 0;
}

const struct file_operations test_fops = {
	.owner		= THIS_MODULE,
	.open       = test_chrdev_open,
	.write	 	= test_chrdev_write,
	.read 		= test_chrdev_read,
	.release	= test_chrdev_release,
};

// 模块安装函数
/*
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
			const char *name)
*/

static int __init chrdev_init(void)
{	
	int	retval;
	
	printk(KERN_INFO "chrdev_init helloworld init\n");

	//注册/分配设备号
	/*
	dev_id = MKDEV(MYMAJOR, 0);//获取主次设备号
	retval = register_chrdev_region (dev_id, MYCNT, MYDEVNAME);//注册MYCNT个设备,主次设备号信息存储在dev_id中,设备名为MYDEVNAME="testchar"
	if (retval < 0)
	{
		printk(KERN_ERR "register_chrdev fail\n");
		return -EINVAL;
	}
	printk(KERN_INFO "register_chrdev success....\n");
	*/
	//动态自动获取设备号
	retval = alloc_chrdev_region (&dev_id,0, MYCNT, MYDEVNAME);//注册MYCNT个设备,主次设备号信息存储在dev_id中,设备名为MYDEVNAME="testchar"
	if (retval < 0)
	{
		printk(KERN_ERR "alloc_chrdev_region fail\n");
		goto flag1;
	}
	printk(KERN_INFO "alloc_chrdev_region success....\n");
	printk(KERN_INFO "major = %d, minor = %d.\n", MAJOR(dev_id), MINOR(dev_id));//使用宏获取设备号信息
	
	

	// 第2步:注册字符设备驱动
	cdev_init(&test_cdev, &test_fops);//void cdev_init(struct cdev *cdev, const struct file_operations *fops)
	retval = cdev_add(&test_cdev, dev_id, MYCNT);//int cdev_add(struct cdev *p, dev_t dev, unsigned count)
	if (retval) {
		printk(KERN_ERR "Unable to cdev_add\n");
		goto flag2;
	}
	printk(KERN_INFO "cdev_add success\n");

	

	// 使用动态映射的方式来操作寄存器
	if (!request_mem_region(GPJ0CON_PA, 4, "GPJ0CON"))
		goto flag2;
	if (!request_mem_region(GPJ0DAT_PA, 4, "GPJ0CON"))
		goto flag2;
	
	pGPJ0CON = ioremap(GPJ0CON_PA, 4);
	pGPJ0DAT = ioremap(GPJ0DAT_PA, 4);
	
	*pGPJ0CON = 0x11111111;
	*pGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));		// 亮
	
	return 0;
	
	// 如果第1步才出错跳转到这里来
flag4:	
	release_mem_region(GPJ0CON_PA, 4);
	release_mem_region(GPJ0DAT_PA, 4);
	// 如果第2步才出错跳转到这里来
flag3:	
	cdev_del(&test_cdev);
	// 如果第3步才出错跳转到这里来
flag2:	
	unregister_chrdev_region(dev_id, MYCNT);
	// 如果第4步才出错跳转到这里来
flag1:	
	return -EINVAL;
}

// 模块卸载函数
static void __exit chrdev_exit(void)
{
	printk(KERN_INFO "chrdev_exit helloworld exit\n");
	
	*pGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));	//灭
	
	// 解除映射
	iounmap(pGPJ0CON);
	iounmap(pGPJ0DAT);
	release_mem_region(GPJ0CON_PA, 4);
	release_mem_region(GPJ0DAT_PA, 4);
	
	//卸载
	// 使用新的接口来注销字符设备驱动
	// 注销分2步:
	// 第一步真正注销字符设备驱动用cdev_del
	cdev_del(&test_cdev);
	// 第二步去注销申请的主次设备号
	unregister_chrdev_region(dev_id,1);//void unregister_chrdev_region(dev_t from, unsigned count)
	
}


module_init(chrdev_init);
module_exit(chrdev_exit);

// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL");				// 描述模块的许可证
MODULE_AUTHOR("zcc");				// 描述模块的作者
MODULE_DESCRIPTION("module test");	// 描述模块的介绍信息
MODULE_ALIAS("alias xxx");			// 描述模块的别名信息

应用测试程序

#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>

#define FILEPNAME "/dev/led_test"

char buf[100];

int main(void)
{
	
	int fd = -1;
	char write_buf[50] = "--zcc led test--";
	
	fd = open(FILEPNAME,O_RDWR);
	if(fd < 0)
	{
		printf("open fail %s\n",FILEPNAME);
		return -1;
	}
	printf("open %s success \n",FILEPNAME);
	
	//读写文件
	printf("write %s to kernel buf \n",write_buf);
	write(fd, write_buf, strlen(write_buf));
	read(fd, buf, 100);
	
	printf("read from kernel is %s \n",buf);
	//sleep(1);
	
	close(fd);
	return 0;
}

Makefile 脚本

#ubuntu的内核源码树,如果要编译在ubuntu中安装的模块就打开这2个
#KERN_VER = $(shell uname -r)
#KERN_DIR = /lib/modules/$(KERN_VER)/build	

		
# 开发板的linux内核的源码树目录
#KERN_DIR = /root/driver/kernel
# ubuntu 20_04 中的源码树路径
KERN_DIR = /home/zcc/x210/driver/kernel
obj-m	+= led_test.o

all:
	make -C $(KERN_DIR) M=`pwd` modules 
	arm-linux-gcc app.c -o led_test.app

cp:
	#cp *.ko /root/porting_x210/rootfs/rootfs/driver_test
	#cp app /root/porting_x210/rootfs/rootfs/driver_test

	 cp *.ko /home/zcc/x210/porting_x210/rootfs/rootfs/driver_test
	 cp *.app /home/zcc/x210/porting_x210/rootfs/rootfs/driver_test

.PHONY: clean	
clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm -rf *.app
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值