字符设备驱动(二)

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

时间: 2024-10-07 10:30:58

字符设备驱动(二)的相关文章

linux字符设备驱动

一.字符设备.字符设备驱动与用户空间访问该设备的程序三者之间的关系. 如图,在Linux内核中使用cdev结构体来描述字符设备,通过其成员dev_t来定义设备号(分为主.次设备号)以确定字符设备的唯一性.通过其成员file_operations来定义字符设备驱动提供给VFS的接口函数,如常见的open().read().write()等. 在Linux字符设备驱动中,模块加载函数通过register_chrdev_region( ) 或alloc_chrdev_region( )来静态或者动态获

linux 字符设备驱动开发详解

一.设备的分类及特点 1.字符设备 字符设备是面向数据流的设备,没有请求缓冲区,对设备的存取只能按顺序按字节的存取而不能随机访问.    Linux下的大多设备都是字符设备.应用程序是通过字符设备节点来访问字符设备的.通常至少需要实现 open, close, read, 和 write 等系统调用.    设备节点一般都由mknod命令都创建在/dev目录下,包含了设备的类型.主/次设备号以及设备的访问权限控制等,如:crw-rw----  1 root  root 4, 64 Feb 18

linux设备驱动第三篇:写一个简单的字符设备驱动

在linux设备驱动第一篇:设备驱动程序简介中简单介绍了字符驱动,本篇简单介绍如何写一个简单的字符设备驱动.本篇借鉴LDD中的源码,实现一个与硬件设备无关的字符设备驱动,仅仅操作从内核中分配的一些内存. 下面就开始学习如何写一个简单的字符设备驱动.首先我们来分解一下字符设备驱动都有那些结构或者方法组成,也就是说实现一个可以使用的字符设备驱动我们必须做些什么工作. 1.主设备号和次设备号 对于字符设备的访问是通过文件系统中的设备名称进行的.他们通常位于/dev目录下.如下: [plain] vie

Linux字符设备驱动剖析

一.先看看设备应用程序 1.很简单,open设备文件,read.write.ioctl,最后close退出.如下: intmain(int argc ,char *argv[]){ unsigned char val[1] = 1; int fd =open("/dev/LED",O_RDWR);//打开设备 write(fd,val,1);//写入设备,这里代表LED全亮 close(fd);//关闭设备 return 0; } 二./dev目录与文件系统 2./dev是根文件系统下

字符设备驱动、平台设备驱动、设备驱动模型、sysfs的关系

Linux驱动开发的童鞋们来膜拜吧:-)  学习Linux设备驱动开发的过程中自然会遇到字符设备驱动.平台设备驱动.设备驱动模型和sysfs等相关概念和技术.对于初学者来说会非常困惑,甚至对Linux有一定基础的工程师而言,能够较好理解这些相关技术也相对不错了.要深刻理解其中的原理需要非常熟悉设备驱动相关的框架和模型代码.网络上有关这些技术的文章不少,但多是对其中的某一点进行阐述,很难找到对这些技术进行比较和关联的分析.对于开发者而言,能够熟悉某一点并分享出来已很难得,但对于专注传授技术和经验给

Samsung_tiny4412(笔记)--&gt;字符设备驱动基本操作及调用流程

/*********************************************************************************** * * Samsung_tiny4412(笔记)-->字符设备驱动基本操作及调用流程 * * 声明: * 以下所有的shell命令都是在root权限下运行的; * * 2015-3-7 阴 深圳 尚观 Sbin 曾剑锋 *******************************************************

Linux字符设备驱动框架

字符设备是Linux三大设备之一(另外两种是块设备,网络设备),字符设备就是字节流形式通讯的I/O设备,绝大部分设备都是字符设备,常见的字符设备包括鼠标.键盘.显示器.串口等等,当我们执行ls -l /dev的时候,就能看到大量的设备文件,c就是字符设备,b就是块设备,网络设备没有对应的设备文件.编写一个外部模块的字符设备驱动,除了要实现编写一个模块所需要的代码之外,还需要编写作为一个字符设备的代码. 驱动模型 Linux一切皆文件,那么作为一个设备文件,它的操作方法接口封装在struct fi

深入浅出~Linux设备驱动之字符设备驱动

一.linux系统将设备分为3类:字符设备.块设备.网络设备.使用驱动程序: 字符设备:是指只能一个字节一个字节读写的设备,不能随机读取设备内存中的某一数据,读取数据需要按照先后数据.字符设备是面向流的设备,常见的字符设备有鼠标.键盘.串口.控制台和LED设备等. 块设备:是指可以从设备的任意位置读取一定长度数据的设备.块设备包括硬盘.磁盘.U盘和SD卡等. 每一个字符设备或块设备都在/dev目录下对应一个设备文件.linux用户程序通过设备文件(或称设备节点)来使用驱动程序操作字符设备和块设备

linux设备驱动第三篇:如何写一个简单的字符设备驱动?

在linux设备驱动第一篇:设备驱动程序简介中简单介绍了字符驱动,本篇简单介绍如何写一个简单的字符设备驱动.本篇借鉴LDD中的源码,实现一个与硬件设备无关的字符设备驱动,仅仅操作从内核中分配的一些内存. 下面就开始学习如何写一个简单的字符设备驱动.首先我们来分解一下字符设备驱动都有那些结构或者方法组成,也就是说实现一个可以使用的字符设备驱动我们必须做些什么工作. 1.主设备号和次设备号 对于字符设备的访问是通过文件系统中的设备名称进行的.他们通常位于/dev目录下.如下: [email prot

linux设备驱动第三篇:如何实现简单的字符设备驱动

在linux设备驱动第一篇:设备驱动程序简介中简单介绍了字符驱动,本篇简单介绍如何写一个简单的字符设备驱动.本篇借鉴LDD中的源码,实现一个与硬件设备无关的字符设备驱动,仅仅操作从内核中分配的一些内存. 下面就开始学习如何写一个简单的字符设备驱动.首先我们来分解一下字符设备驱动 都有那些结构或者方法组成,也就是说实现一个可以使用的字符设备驱动我们必须做些什么工作. 1.主设备号和次设备号 对于字符设备的访问是通过文件系统中的设备名称进行的.他们通常位于/dev目录下.如下: [email pro