嵌入式Linux驱动框架的搭建

334 阅读1分钟

前言

本文主要介绍了基于JZ2440开发板的LED灯驱动程序的介绍和驱动的安装和测试。

驱动程序的介绍

驱动程序需要的函数主要由以下几个:

first_drv_open函数

这个函数与测试程序的open函数对应,当测试程序调用open函数时,会进入内核寻找first_drv_open函数,本例中的函数内容如下:

static int first_drv_open(struct inode *inode, struct file *file)
{
	*gpfcon &= ~((0x3<<(4*2)) | (0x3<<(5*2)) | (0x3<<(6*2)));
	*gpfcon |= ((0x1<<(4*2)) | (0x1<<(5*2)) | (0x1<<(6*2)));
	return 0;
}

本例中,first_drv_open函数中完成的功能是,对LED灯对应的寄存器位置写1,所以当测试程序调用open函数后,对就对LED灯进行了一个初始化的操作。

first_drv_write函数

这个函数与测试程序的write函数对应,当测试程序调用write函数时,会进入内核寻找first_drv_write函数,本例中的函数内容如下:

static ssize_t first_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
	int val;
	copy_from_user(&val, buf, count); //copy_to_user();
	if (val == 1)
	{
		// 点灯
		*gpfdat &= ~((1<<4) | (1<<5) | (1<<6));
	}
	else
	{
		// 灭灯
		*gpfdat |= (1<<4) | (1<<5) | (1<<6);
	}
	return 0;
}

该函数通过形参const char __user *buf进行测试程序和驱动程序的数据传递,由于该函数是测试程序向驱动程序里写数据,所以需要调用copy_from_user(&val, buf, count);进行数据的拷贝。通过判断测试程序对驱动程序的写0和写1,控制LED灯的亮灭。

first_drv_init函数

这个函数是装载驱动的函数,该函数的目的是把相应的驱动函数信息告诉内核。写这个函数之前,需要首先定义一些信息。

static struct class *firstdrv_class;   //#1
static struct class_device	*firstdrv_class_dev;  //#2
static struct file_operations first_drv_fops = {
    .owner  =   THIS_MODULE,    /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
    .open   =   first_drv_open,     
	.write	=	first_drv_write,	   
};

结构体file_operations存储了驱动的所要用到的函数,将其打包成一个结构体。下面是first_drv_init函数内容:

int major;
static int first_drv_init(void)
{
	major = register_chrdev(0, "first_drv", &first_drv_fops); // #1

	firstdrv_class = class_create(THIS_MODULE, "firstdrv"); //#2

	firstdrv_class_dev = class_device_create(firstdrv_class, NULL, MKDEV(major, 0), NULL, "xyz"); /* #3 /dev/xyz */

	gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16);  //#4
	gpfdat = gpfcon + 1;  //#5

	return 0;
}
  • #1: 将结构体file_operations存储的信息告诉内核,并让内核自动为其分配主设备号,存放至major中。
  • #2和#3:在装载驱动后,根据驱动信息自动生成设备结点,即/dev/xyz文件夹。若没有这两句,在装载完驱动后,需要根据设备信息手动创建设备结点和文件夹,即mknod /dev/xyz c 111(主设备号) 0(副设备号)
  • #4和#5:对LED等的配置寄存器和数据寄存器进行定义。

first_drv_exit()函数

该函数在卸载驱动时调用,函数内容如下:

static void first_drv_exit(void)
{
	unregister_chrdev(major, "first_drv"); // 卸载
	class_device_unregister(firstdrv_class_dev);
	class_destroy(firstdrv_class);
	iounmap(gpfcon);
}

该函数主要是将first_drv_init函数注册进内核的信息给删除掉。 有了first_drv_init函数和first_drv_exit()函数后,需要调用以下语句才能成功:

module_init(first_drv_init);
module_exit(first_drv_exit);

测试程序及测试结果

int main(int argc, char **argv)
{
	int fd;
	int val = 1;
	fd = open("/dev/xyz", O_RDWR); //#1
	if (fd < 0)
	{
		printf("can't open!\n");
	}
	if (argc != 2)
	{
		printf("Usage :\n");
		printf("%s <on|off>\n", argv[0]);
		return 0;
	}

	if (strcmp(argv[1], "on") == 0)
	{
		val  = 1;
	}
	else
	{
		val = 0;
	}
	
	write(fd, &val, 4); //#2
	return 0;
}
  • #1: 通过open函数进入驱动的first_drv_open函数,并返回fd作为该调用该驱动的标识。
  • #4:通过write函数进入驱动first_drv_write函数,将参数命令传给驱动,通过传入的命令控制LED灯的亮灭。

测试过程及结果

  • 输入指令:insmod first_drv.ko 然后输入:cat /proc/devices 可以看到在字符设备列表里有我们刚写的驱动first_drv,以及自动为它分配的主设备号252。
  • 输入指令:./firstdrvtest on 可观察到板子上的LED灯亮起;输入指令:./firstdrvtest off 可观察到板子上的LED灯灭掉。
  • 输入指令:rmmod first_drv.ko 可卸载驱动。