文章目录
一、针对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申请内存都在内部进行了实现,所以我们不必为结构体申请内存。
由上图可以看到为cdev申请内存,在该函数中还对cdev进行了一系列的赋值。
下图为VirtualDisk的驱动使用的方式,可以看到在使用register_chrdev_region()之后,有使用VirtualDisk_set_up_dev()进行了一系列操作,这些操作都是在老街口中进行过实现的,很明显,新接口的功能更丰富,可以单独制造更大的结构体,而不仅仅是一个cdev结构体,可以制作包含cdev结构体并且具有其他特性的结构体。
2.2 关于操作函数实现的不同
在对操作函数进行实现时,VirtualDisk每次都要将申请到的内存的地址指针传入函数内部,然后由函数内部的指针再针对内存进行操作,而led驱动是要直接针对GPIO进行操作的,并不对内存进行读写,所以不需要将地址指针传入,仅需要完成自己对IO口的操作即可。
3 对于VirtualDisk的应用测试
测试方法就是使用read、write、open、close对内存进行读写。
还有一种方法就是使用fopen、fputs、fgets、fclose、fseek,对设备进行读写。
两种方式都使用了一遍,进行测试,测试结果无问题。
有一处bug未能找到原因,在使用ioctl清空内存时,系统会产生oops报警,暂未找到原因。