码迷,mamicode.com
首页 > 其他好文 > 详细

字符设备驱动(二)

时间:2015-04-22 09:40:18      阅读:259      评论:0      收藏:0      [点我收藏+]

标签:

驱动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);就是返回调用该函数的次设备号。

字符设备驱动(二)

标签:

原文地址:http://blog.csdn.net/u014104588/article/details/45177317

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!