字符设备驱动(二)

来源:互联网 发布:统计年鉴数据怎么下载 编辑:程序博客网 时间:2024/06/08 06:38

驱动LED灯
首先加入头文件

#include <linux/module.h>#include <linux/kernel.h>#include <linux/fs.h>#include <linux/init.h>#include <linux/delay.h>#include <asm/uaccess.h>#include <asm/irq.h>#include <asm/io.h>#include <asm/arch/regs-gpio.h>#include <asm/hardware.h>

还要定义几个类

static struct class *leds_class;static struct class_device  *leds_class_devs[4];

定义一个类,然后再在类下定义4个设备,下面会用到。

然后创建open函数myled_open

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

当再测试文件中执行open(“/dev/xxx”,”x”);时,系统就会根据file_operations结构体运行myled_open函数。所以在这个函数中初始化led引脚为输出。该函数参数为 设备节点结构体指针,和文件流指针。

然后创建write函数myled_write

static int myled_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos){    int minor=MINOR(file->f_dentry->d_inode->i_rdev);    char val;    copy_from_user(&val,buf,count);//copy variable from user to kernel    switch(minor){        case 0:        {            if(val==1)*gpfdat &=(0<<4|0<<5|0<<6);//点灯            else    *gpfdat |=(1<<4|1<<5|1<<6);//关灯            break;        }        case 1:        {            *gpfdat |=(1<<4|1<<5|1<<6);//关灯            if(val==1)            *gpfdat &=(~(1<<4));//点灯            break;        }        case 2:        {            *gpfdat |=(1<<4|1<<5|1<<6);//关灯            if(val==1)            *gpfdat &=(~(1<<5));            break;        }        case 3:        {            *gpfdat |=(1<<4|1<<5|1<<6);//关灯            if(val==1)            *gpfdat &=(~(1<<6));            break;        }    }    return 0;}

myled_write函数的参数为文件流指针、传递进来的数据指针、数据大小
MINOR(file->f_dentry->d_inode->i_rdev);是取文件的次设备号,copy_from_user(void *to, const void __user *from, unsigned long n)从用户空间拷贝数据到内核空间,失败返回没有被拷贝的字节数,成功返回0.
当测试程序执行write(fd,&val,1);时,系统就会调用该函数。fd为执行open函数时返回的唯一的文件文件描述符,

然后接下来就是填充file_operations结构体

static struct .file_operations myled_fops = {    .owner  =   THIS_MODULE,    /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */    .open   =   myled_open,                .write  =   myled_write,       };

系统启动后,会自动执行这个结构体,然后将创建的函数与底层open,write函数关联起来。
接下来就是注册函数

static int myled_init(void)//加载{    major=register_chrdev(111,"myled",&myled_fops);//major为自己定义的全局整型变量    if (major < 0) {      printk("myled can't register major number\n");      return major;    }    myled_class=class_create(THIS_MODULE, "myled");    if (IS_ERR(myled_class))        return PTR_ERR(myled_class);        int i;    for(i=0;i<4;i++){        myled_class_devs[i]=class_device_create(myled_class, NULL, MKDEV(111, i), NULL, "led%d",i);//        if (unlikely(IS_ERR(myled_class_devs[i])))//先建立一个类 再建立一个设备,然后自动创建一个xyz设备节点            return PTR_ERR(myled_class_devs[i]);    }    gpfcon=(unsigned long *)ioremap(0x56000050,16);    gpfdat=gpfcon+1;    return 0;}

major=register_chrdev(111,”myled”,&myled_fops);就是将file_operations创建的myled_fops结构题告诉内核,然后给该设备的主设备号赋值为111,设备名字为myled.
myled_class=class_create(THIS_MODULE, “myled”);
myled_class_devs[i]=class_device_create(myled_class, NULL, MKDEV(111, i), NULL, “led%d”,i);
就是给该设备创建一个类,然后再创建的类下,再创建四个设备节点。最终在系统中节点是/dev/led0 /dev/led1 /dev/led2 /dev/led3.应用层序中open函数中的参数就是这些节点。它们的主设备号相同,但是次设备号不同。都归属于myled这个节点。
gpfcon=(unsigned long *)ioremap(0x56000050,16);
gpfdat=gpfcon+1;
ioremap函数就是将一段连续的物理地址映射为虚拟地址。因为指针大小为4字节,所以加一就表示加了4字节。在注册函数中映射地址,那么就得在卸载函数中取消映射地址。

下面是卸载函数

static void myled_exit(void){    int i;    unregister_chrdev(111,"myled");//卸载;    for(i=0;i<4;i++){        class_device_unregister(myled_class_devs[i]);    }    class_destroy(myled_class);    iounmap(gpfcon);}

unregister_chrdev(111,”myled”);是从内核中删除创建的myled_fops结构体,这里只需要主设备号和设备名。
class_device_unregister(myled_class_devs[i]);class_destroy(myled_class);与上面的创建类的函数相对应
iounmap(gpfcon);就是取消虚拟地址映射。

最后还要加上如下几行

module_init(myled_init);module_exit(myled_exit);MODULE_LICENSE("GPL");

module_init(myled_init);创建一个结构体,里面有myled_init函数的地址,当执行insmod myled.ko时,系统会自动找到这个结构体,然后找到里面的函数地址,去执行myled_init。
module_exit(myled_exit);创建一个结构体,里面有first_drv_init函数的地址,当执行rmmod myled.ko时,系统会自动找到这个结构体,然后找到里面的函数地址,去执行myled_exit
MODULE_LICENSE(“GPL”);是将钥匙设置为GPL.

下面是测试函数

加入头文件

#include<sys/types.h>#include<sys/stat.h>#include<fcntl.h>#include<stdio.h>
void print_usage(char *file){    printf("Usage:\n");    printf("%s /dev/led0 <on|off>\n", file);    printf("%s /dev/led1 <on|off>\n", file);    printf("%s /dev/led2 <on|off>\n", file);    printf("%s /dev/led3 <on|off>\n", file);}int main(int argc,char *argv[]){    int fd;    int val=1;    if(argc<3){        print_usage(argv[0]);        return 0;    }    fd=open(argv[1],O_RDWR);    if(fd<0){        printf("error,can't open %s\n",argv[1]);        return 0;    }    if(strcmp(argv[2],"on")==0)        val=1;    else if(strcmp(argv[2],"off")==0)        val=0;    else print_usage(argv[0]);        write(fd,&val,1);    return 0;}

例如执行/myledtest /dev/led0 on
当执行 fd=open(argv[1],O_RDWR);函数时,就会运行/dev/led0对应open函数,返回的是每个次设备号不同的描述符,每个主设备里的次设备的描述符不一样。
然后下面执行write(fd,&val,1);就会运行/dev/led0对应的write函数。
执行/myledtest /dev/led1 on /myledtest /dev/led2 on时也会这样。
是因为在注册函数中将同一主设备号的write,read函数填充在myled_fops结构体中了。然后又将这个结构体和主设备名绑定在一起并告诉内核了,然后又利用主设备名创建一个类,类里又创建四个次设备。所以每次调用次设备时就会执行主设备对应的底层函数,然后在该函数中分别是哪一个次设备调用的。如int minor=MINOR(file->f_dentry->d_inode->i_rdev);就是返回调用该函数的次设备号。

0 0