字符设备驱动

一、针对LED驱动与VirtualDisk驱动的分析

1 LED驱动编写

1.1 头文件及宏定义

LED的驱动编写首先进行将一系列头文件与宏定义进行添加。

#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <asm/io.h>
#include <asm/system.h>
#include <asm/uaccess.h>
#include <linux/gpio.h>

#define GPIO_TO_PIN(bank, gpio) (32 * (bank) + (gpio))

#define DEVICE_NAME "my_first_cdev"
MODULE_AUTHOR("nfk <35b202@gree.com>");
MODULE_DESCRIPTION("HMI_LED_DRIVER");
MODULE_LICENSE("GPL");

1.2 安装与卸载函数

完成以上宏定义之后,开始编写安装驱动程序和卸载驱动程序相应的函数。

static int __init my_first_init(void)
{
	int rc;

	rc = register_chrdev(231, "my_first_cdev",&my_first_fileops);
	if(rc<0)
		{
			printk(DEVICE_NAME "can't register major number \n");
			return rc;
		}
	printk(DEVICE_NAME "initialized \n");
	return rc;
}

static void __exit my_first_cleanup(void)
{
	unregister_chrdev(231,"my_first_cdev");
}
module_init(my_first_init);
module_exit(my_first_cleanup);

关于以上的初始化方式,这里需要进行详细分析,其中注册设备函数:register_chrdev(),该函数是Linux内核的一个老的接口,该接口仅能注册主设备号,副设备号只能注册为“0”,这种接口在早期使用的比较多,到了后期,设备的不断增加,仅靠一个主设备号已经没法满足设备的需求。这个问题将在后边的虚拟磁盘驱动中进行详细分析

1.3 构建file_operations结构体

该结构体是字符设备驱动程序的核心,该结构体给出了一系列的函数指针,将函数指针指向本次驱动实现的函数,就可以通过写好的函数对设备进行相应的操作。
以下是该构建的结构体。

static const struct file_operations my_first_fileops = {
	.owner   = THIS_MODULE,
	.open    = my_first_open,
	.unlocked_ioctl	 = my_first_ioctl,
};

下一步就是填充该结构体中提出的函数,通过实现上述这些函数达到操控设备的目的。

1.4 函数实现

在之前的file_operations结构体中已经将需要的实现的函数进行了指定。

static int my_first_open(struct inode *inode, struct file *file)
{
	gpio_request(GPIO_TO_PIN(0,14),"nfk_led0");
	return 0;
}

static int my_first_ioctl(struct inode *inode, unsigned int cmd,unsigned long arg)
{
	switch(cmd)
		{
			case  1:
				gpio_direction_output(GPIO_TO_PIN(0,14),0);
				break;
			case  0:
				gpio_direction_output(GPIO_TO_PIN(0,14),1);
				break;
			default:
				return -1;
		}
	return 0;
}

2 应用程序测试

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#define FILE	"/dev/test"			// mknod /dev/test c 231 0

char buf[100];

int main(void)
{
	int fd = -1;
	int i = 0;
	
	fd = open(FILE, O_RDWR);
	if (fd < 0)
	{
		printf("open %s error.\n", FILE);
		return -1;
	}
	printf("open %s success..\n", FILE);

	while (1)
	{
		memset(buf, 0 , sizeof(buf));
		printf("please input on | off \n");
		scanf("%s", buf);
		if (!strcmp(buf, "on"))
		{
			ioctl(fd, 1, 1);
		}
		else if (!strcmp(buf, "off"))
		{
			ioctl(fd, 0, 1);
		}
		else if (!strcmp(buf, "flash"))
		{
			for (i=0; i<3; i++)
			{
				ioctl(fd, 1, 1);
				sleep(1);
				ioctl(fd, 0, 1);
				sleep(1);
			}
		}	
		else if (!strcmp(buf, "quit"))
		{
			break;
		}
}
	close(fd);
	return 0;
}

以上为测试驱动的应用程序,应用程序调用内核给出的接口,打开节点之后(对应驱动里的my_first_open()函数),就将节点返回的数据作为参数给ioctl函数使用,传入参数之后,就由之前写出的my_first_ioctl()进行处理。具体关于函数通过何种方式进行对应的,这里暂不深究。

二、VirtualDisk字符设备驱动分析

1 驱动编写

/* linux/drivers/char/scx200_gpio.c

   National Semiconductor SCx200 GPIO driver.  Allows a user space
   process to play with the GPIO pins.

   Copyright (c) 2001,2002 Christer Weinigel <wingel@nano-system.com> */
#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <asm/io.h>
#include <asm/system.h>
#include <asm/uaccess.h>
#include <linux/slab.h>
#include <linux/gpio.h>

#define VIRTUALDISK_SIZE  0X1000
#define MEM_CLEAR  0X1
#define PORT1_SET  0X2
#define PORT2_SET  0X3
#define VIRTUALDISK_MAJOR  200
static int VirtualDisk_major = VIRTUALDISK_MAJOR;


#define DEVICE_NAME "VirtualDisk_nfk"



MODULE_AUTHOR("nfk <35b202@gree.com>");
MODULE_DESCRIPTION("HMI_VirtualDisk");
MODULE_LICENSE("GPL");

以上为一系列头文件及宏定义,这里不做分析。

struct VirtualDisk {
	struct cdev cdev;
	unsigned char mem[VIRTUALDISK_SIZE];
	int port1;
	long port2;
	long count;
	
};
struct VirtualDisk *Virtualdisk_devp;

以上为构建结构体,结构体内包含cdev结构体,用于设备的注册,创建全局指针,用于为设备申请内存,并用来传参。

static const struct file_operations VirtualDisk_fops ={
	.owner   = THIS_MODULE,
	.open    = VirtualDisk_open,
	.write	=VirtualDisk_write,
	.read	=VirtualDisk_read,
	.release	=VirtualDisk_release,
	.llseek	=VirtualDisk_llseek,
	.unlocked_ioctl	 = VirtualDisk_ioctl,
};

这是构建的file_operations结构体,内核通过该结构体选择操作函数。操作函数实现的难度不大,这里不做详细分析。
实现操作函数时,首先要将全局变量的指针传入内部函数,然后根据操作函数的需求写一些逻辑需求。
例如read与write函数,可以利用内核与用户空间的数据交换函数,copy_to_user和copy_from_user进行数据的交换,将数据写入到结构体成员中的mem当中,就就完成了一次写入,读取则是将结构体成员的mem数据读出到用户空间。其中有一个指针变量,用于读取写入时对内存地址的操作,这个指针变量时内核的指针,用户无法直接控制,只能通过驱动中的llseek对指针进行控制移动,达到对任意位置进行读写的目的。

以下内容为对内存的申请,以及对主副设备号的申请,通过以下内容对驱动进行了注册,在此处就看到了之前对设备注册的新接口的用法, 这个用法实际上就是对老接口的一个拆分,单独将副设备号的注册摘了出来,放在了VirtualDisk_setup_dev当中,这一部分在下一小节中有详细分析。

static void VirtualDisk_setup_dev(struct VirtualDisk *dev,int minor)
{
	int err;
	int devno =MKDEV(VirtualDisk_major, minor);
	cdev_init(&dev->cdev,&VirtualDisk_fops);
	dev->cdev.owner=THIS_MODULE;
	dev->cdev.ops=&VirtualDisk_fops;
	err=cdev_add(&dev->cdev,devno,1);
	if(err)
		printk(KERN_NOTICE "Error in cdev_add()\n");
}


static int __init VirtualDisk_init(void)
{
	int result;
	dev_t devno = MKDEV(VirtualDisk_major, 0);
	if(VirtualDisk_major)
		{
			result = register_chrdev_region(devno,1,"VirtualDisk_nfk");
		}
	else
		{
			result=alloc_chrdev_region(&devno,0,1,"VirtualDisk_nfk");
			VirtualDisk_major=MAJOR(devno);
		}
	if(result<0)
		{
			printk(DEVICE_NAME "can't register major number \n");
			return result;
		}
	Virtualdisk_devp = kmalloc(sizeof(struct VirtualDisk), GFP_KERNEL);
	if(!Virtualdisk_devp)
		{
			result = -ENOMEM;
			goto fail_kmalloc;
		}
	memset(Virtualdisk_devp, 0, sizeof(struct VirtualDisk));
	VirtualDisk_setup_dev(Virtualdisk_devp,0);
	printk(DEVICE_NAME " initialized \n");
	return 0;
	fail_kmalloc:
		unregister_chrdev_region(devno, 0);
	return result;
}

static void __exit VirtualDisk_exit(void)
{
	cdev_del(&Virtualdisk_devp-> cdev);
	unregister_chrdev_region(MKDEV(VirtualDisk_major,0),1);
	kfree(Virtualdisk_devp);
}

module_init(VirtualDisk_init);
module_exit(VirtualDisk_exit);

2 与led驱动的区别

2.1 关于内存申请的不同

第一章中的led驱动十分简单,与VirtualDisk存在着许多区别,本LED驱动中没有向内核申请动态的内存存储结构体, 实际上并不是没有申请,而是在我们书写的代码中没有申请, 由于LED驱动在书写时使用了较老的接口register_chrdev(),该接口将注册副设备号和为cdev申请内存都在内部进行了实现,所以我们不必为结构体申请内存。
5
由上图可以看到为cdev申请内存,在该函数中还对cdev进行了一系列的赋值。
下图为VirtualDisk的驱动使用的方式,可以看到在使用register_chrdev_region()之后,有使用VirtualDisk_set_up_dev()进行了一系列操作,这些操作都是在老街口中进行过实现的,很明显,新接口的功能更丰富,可以单独制造更大的结构体,而不仅仅是一个cdev结构体,可以制作包含cdev结构体并且具有其他特性的结构体。
6

2.2 关于操作函数实现的不同

在对操作函数进行实现时,VirtualDisk每次都要将申请到的内存的地址指针传入函数内部,然后由函数内部的指针再针对内存进行操作,而led驱动是要直接针对GPIO进行操作的,并不对内存进行读写,所以不需要将地址指针传入,仅需要完成自己对IO口的操作即可。

3 对于VirtualDisk的应用测试

测试方法就是使用read、write、open、close对内存进行读写。
还有一种方法就是使用fopen、fputs、fgets、fclose、fseek,对设备进行读写。
7
两种方式都使用了一遍,进行测试,测试结果无问题。
有一处bug未能找到原因,在使用ioctl清空内存时,系统会产生oops报警,暂未找到原因。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

塔通天

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值