字符设备驱动总结
作者:JCY
此文章是学习了一段时间的linux字符设备驱动所写,在文中如有错误之处请指正,将不胜感激。
写了裸机那么长时间了,终于接触到了linux 驱动程序的设计,当然最简单的驱动程序没有涉及到与设备相关的知识,知识了解一下模块的加载。说到模块的加载,就想写C语言和C++接触到的一个程序hello一样,我们第一个接触到的最简单的模块的加载也是hello。
我写了一个helloworld的程序,以此来说说模块。程序如下
#include <linux/init.h>
#include <linux/module.h>
static void __init helloword_init(void)
{
printk("load module : hello word 11111 !");
//return 0;
}
static void __exit helloword_exit(void)
{
printk("unload module : hello word 11111!");
//return 0;
}
module_init(helloword_init);
module_exit(helloword_exit);
MODULE_LICENSE("GPL");
前面两行是编写模块驱动程序最重要的头文件,每一个驱动程序都会包括这两个头文件。
模块加载函数(module_init(函数名))和释放函数(module_exit(函数名))的宏定义就包含在init.h当中。helloword_init和helloword_exit函数是我自定义的函数,函数定义和声明的格式基本固定,可加一些对函数的一下修饰,如程序当中的static,在函数只实现了在终端打印处信息,仅此而已。由于这是在内核当中的程序所以不能用我们在写应用程序时的printf函数。
至于最后一行是对本模块许可权限的声明,GPL指示本模块为通用公共许可证,如果一个模块不包含任何的许可权限,那么当你加载内核时,内核会提示非标内核的警告。
如果你要编译该模块还有一个Makefile文件需要编写,如果没有此文件,我们管理和编译会很麻烦,当然你不用这个Makefile文件也是可的。我把我写的Makefile文件当中的内容显示如下:
#makefile for module
obj-m := hellomodule.o
KDIR = /home/jcy/SoftEmbed/linux-2.6.30.9
all:
make -C $(KDIR) SUBDIRS=$(shell pwd) modules
第一行为注释,第二行意思是将hellomodule.o和编译过程中生成hellomodule_mod.c编译成hellomodule.ko模块,如果你要编译其他的模块只需要更改hellomodule.o的为要编译C文件的目标文件名就行了。KDIR 是变量名被赋值为内核源文件的路径(此文件要和所正在使用的内核是一样的,否则在加载内核时会出现一些问题)all:为一个目标。最后一行为真正所执行的命令。
看我们将模块下载到板子上后所要执行的命令,
insmod :模块的加载命令。insmod hellomodule.ko
当执行此命令时,内核就会调用helloword_init函数,便会去实行此函数中的语句了,在此模块中只打印了一行“load module : hello word 11111 !”。
lsmod :显示所有加载的模块的信息
rmmod :卸载模块
字符型设备函数调用的流程
Open的函数中有一个文件名参数,当在应用程序中执行了open函数时,因为每一个字符设备文件都对应这一个节点,同过该节点的主设备号,找到在内核当中的一个存放已注册设备的struct dev *的数组。一个字符设备结构体当中有struct file_operations *成员,该指针成员中存放着所有的对此字符设备的操作函数,就是通过这些函数就可以对硬件设备进行操作。例如open函数指针,ioctl函数指针等等!只要对struct file_operations的相应的成员复制就可以在应用程序中调用相关函数来访问内核模块中的函数了,从而实现了控制外设和采集数据的目的。
现在将LED设备驱动拷贝如下
my_led.h
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <mach/regs-gpio.h>
#include <asm/io.h>
#define MAIN_DEV_NUM 108
#define MINOR_DEV_NUM 0
struct cdev my_led_cdev;
int my_led_open(struct inode *inode, struct file *file);
int my_led_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg);
int my_led_close(struct inode *inode, struct file *file);
struct file_operations my_led_fops =
{
.open = my_led_open,
.ioctl = my_led_ioctl,
.release = my_led_close,
};
int my_led_open(struct inode *inode, struct file *file)
{
printk("dev_t = %ld", inode.i_rdev);
unsigned int value = 0 ;
value |= (1<<10)|(1<<12)|(1<<16)|(1<<20);
writel(value,S3C2410_GPBCON);
value = 0;
writel(value,S3C2410_GPBUP);
return 0;
}
#define MY_LED_ON 1
#define MY_LED_OFF 0
#define LED1 1
#define LED2 2
#define LED3 3
#define LED4 4
int my_led_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{
//writel(0,S3C2410_GPBDAT);
printk("enter into my_led_inctl!\n\r");
unsigned int value = 0;
value = readl(S3C2410_GPBDAT);
switch(arg)
{
case LED1 :
if(MY_LED_ON == cmd)
{
writel(value&(~(1<<5)) ,S3C2410_GPBDAT);
}
else if(MY_LED_OFF == cmd)
{
writel((value|(1<<5)),S3C2410_GPBDAT);
}
break;
case LED2 :
if(MY_LED_ON == cmd)
{
writel(value&(~(1<<6)),S3C2410_GPBDAT);
}
else if(MY_LED_OFF == cmd)
{
writel((value|(1<<6)),S3C2410_GPBDAT);
}
break;
case LED3 :
if(MY_LED_ON == cmd)
{
writel(value&(~(1<<8)),S3C2410_GPBDAT);
}
else if(MY_LED_OFF == cmd)
{
writel((value|(1<<8)),S3C2410_GPBDAT);
}
break;
case LED4 :
if(MY_LED_ON == cmd)
{
writel(value&(~(1<<10)),S3C2410_GPBDAT);
}
else if(MY_LED_OFF == cmd)
{
writel((value|(1<<10)),S3C2410_GPBDAT);
}
break;
default :
writel(value&(~((1<<5)|(1<<6)|(1<<8)|(1<<10))),S3C2410_GPBDAT);
break;
}
return 0;
}
int my_led_close(struct inode *inode, struct file *file)
{
printk("close the led dev!\n\r");
return 0;
}
my_led.c
#include <inc/my_led.h>
unsigned long int my_led_no = 0;
static int __init my_led_init(void)
{
my_led_no = MKDEV(MAIN_DEV_NUM, MINOR_DEV_NUM);//内核源码的定义为#define MKDEV(ma,mi) ((ma)<<8 | (mi))
//register_chrdev_region(my_led_no,1,"/dev/led");
cdev_init(&my_led_cdev,&my_led_fops);//也可以对my_led_cdev中的ops变量直接赋值,此函数就是对cdev中变量进行初始化
my_led_cdev.owner = THIS_MODULE;//((struct module *)0)
cdev_add(&my_led_cdev, my_led_no, 1);//第二个的类型是dev_t类型(即unsigned long型)存储主设备和次设备号
#if DEBUG
printk("load my my_led module !\n\r");
#endif //end DEBUG
return 0;
}
static void __exit my_led_exit(void)
{
cdev_del(&my_led_cdev);
//unregister_chrdev_region(my_led_no, 1);
printk("unload my my_led module !\n\r");
}
module_init(my_led_init);
module_exit(my_led_exit);
MODULE_LICENSE("GPL");
设备号包括主备号和次设备号,主设备号最大值为255,次设备也为255.MKDEV宏就是来生成设备号,它的参数有主设备号和次设备号,其宏定义为#define MKDEV(ma,mi) ((ma)<<8 | (mi))。从宏定义我们能知道次设备号的最大值为255。
编写此字符设备的流程为
首先建立一个struct file_operations的变量,然后对变量中的所需成员复制,用到那个函数就在前面声明一下,然后再变量复制之后就对所定义的函数进行实现。然后再建立dev的结构体,然后再对结构体中必要的成员复制,可以直接复制,也可以通过内核提供的函数复制,那种都不会错,最后通过调用cdev_add函数将指定设备号的设备加入到内核。
将对应的应用程序列处如下:
/*
将对应的驱动加载以后,创造一个设备结点(与驱动当中设置的结点是一样,命令:mknod /dev/my_led c 108 0)
然后执行应用程序(如./led ON 2),那么第二个二极管就会点亮,数字参数标示对第几个二极管进行操作 。OFF 标示关闭二极管
*/
#define MY_LED_ON 1
#define MY_LED_OFF 0
#define LED1 1
#define LED2 2
#define LED3 3
#define LED4 4
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[])
{
int fd = 0;
int cmd = 0;
int arg = 0;
unsigned int led_num = 0;
if(argc <= 2)
{
printf("warning(main) :MY_LED_ON/MY_LED_OFF, 1/2/3/4\n\r");
printf("warning(main) :the program will exit !\n\r");
return -1;
}
printf("argc = %d\n\r" , argc);
printf("argv[0] = %s\n\r argv[1] = %s\n\r argv[2] = %s\n\r",argv[0],argv[1],argv[2]);
if((argv[2][0]<='4')&&(argv[2][0]>='1'))
{
arg = argv[2][0] - 0x30;
printf("the variable arg = %d\n\r",arg);
}
else
{
printf("warning(main) : the argument is error !\n\r");
}
if(strcmp(argv[1],"ON") == 0)
{
printf("the second argument is ON\n\r");
cmd = MY_LED_ON;
}else if(strcmp(argv[1],"OFF") == 0)
{
printf("the second argument is OFF\n\r");
cmd = MY_LED_OFF;
}else
{
printf("warning(main) :the argument is error argv[1]!\n\r");
}
printf("cmd and arg is two argument send to driver \n\r");
printf("cmd= %d\n\r",cmd);
printf("arg= %d\n\r",arg);
fd = open("/dev/my_led",0);
ioctl(fd,cmd,arg);
close(fd);
}