字符设备驱动框架和sys文件----led灯驱动(7.5-7.7)

前瞻:

1、驱动电灯与裸机点灯区别:

        1、Linux系统有内存管理(操作虚拟地址映射物理地址)

        2、点灯软件编写必须符合Linux系统架构(字符设备驱动框架)

        3、需要将编写的驱动代码加入已经运行的程序

2、Linux系统五大功能:内存管理、任务管理、进程间通信、文件系统管理(一切皆是文件,比如驱动、鼠标/dev/mice、终端/dev/tty、硬盘/dev/sda)、网络管理

3、驱动分类:字符设备、块设备、网络设备

4、字符设备驱动两种加载:静态(代码加入内核编译)、动态(驱动独立编译生成.ko文件,insmod加载到已经运行的linux系统)

5、字符设备驱动框架:

每个字符设备对应一个cdev结构体,结构体里面有:

        1、设备号(主设备号(设备类型)+次设备号(同设备类型下的设备编号))关联一个文件:Linux系统中,每一个文件都可以用stat函数找到其设备号

        2、file_operation结构体(对设备的系统调用操作):比如led以文件形式存在:/dev/led,可以用文件操作open、read、write、close操作。

6、驱动文件编写

        1、写一个简单驱动文件:led_drv.c

//在linux系统写驱动必备四个头文件
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/cdev.h>

/*第二步:写入口、出口内联函数:类型按照附录3写,一般要加上__init,见附录4,作用就是运行完入口代码后就直接删除,节省内存。
int __init led_init(void)
{
    pr_info("led driver init success!");
    /*在Linux内核打印信息需要用printk(KERN_INFO,""),也就是加上打印级别,见附录5,后来简化为这种。*/
    return 0;
}

void __exit led_exit(void)
{
    pr_info("led driver exit success!");
    return;
}


/*第一步:先写驱动程序入口、出口。不知道函数参数是什么怎么办,因为Linux内核驱动是在内核源码基础上写的,所以直接在内核源码查找见附录1,由于内核源码文件太大,所以需要一个工具ctags,1、ctags -R生成一个ctags目录,用vi -t module_init查找,优先选择include/linux目录下,函数见附录2。可知参数是一个内联函数,可以用ctrl ]查看参数定义,见附录3
*/
module_init(led_init);//入口(动态加载驱动时执行)
module_exit(led_exit);//出口(卸载时执行)

附录1,内核源码文件

附录2,module_init函数

附录3,初始化内联函数

附录4:__init

附录5:printk打印级别

        2、编译验证

我们编写的驱动编译时需要调用内核编译make modules,但是我们的文件不在内核文件路径,所以需要-C$(kerdir)指定内核路径,即make -C$(kerdir) modules,会编译内核所以模块,所以依赖文件就是内核所有模块,所以makefile编写时不需要依赖文件,但是这样就不会编译我们的驱动模块,所以:要在原有模块基础上加上我们的驱动模块,就要在内核调用make modules时加上我们的文件:obj-m表示内核编译模块,obj必须小写,加上我们的模块即可:obj-m+=led_drv.o,由于模块不在内核文件夹,所以要加上我们的驱动源码路径,M=$(drvdir)

编译完成会生成.ko模块驱动文件,用insmod即可动态加入运行的linux系统,完整makefile如下:

#模块名
modulename=led_drv

#内核路径
kerdir=/home/linux/imx6ull/kernel/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek

#当前路径
curpath=$(shell pwd)

#将代码加入模块编译选项中
obj-m+=$(modulename).o 

all:
	make -C $(kerdir) modules M=$(curpath)
	cp $(modulename).ko ~/nfs/rootfs
.PHONY:
clean:
	rm $(modulename).ko

3、驱动程序没有错误就会生成.ko驱动文件,然后通过minicom把驱动文件加载到板子:

       1、 insmod led_dev.ko                加载成功会打印对应信息,同时还会打印一些其他信息,就是让你遵从GNU组织协定,使用了linux内核源码就需要公开你写的相关代码,要想不显示这些信息就可以在最后加上两行代码,表示你遵从这个协定(GPL协定):

MODULE_LICENSE("GPL");

MODULE_AUTOHR("name");name填你的名字

        2、rmmod led_dev.ko                删除模块

7、在上述代码加上驱动框架设备号获取

        1、设备号不能随意分配,因为有些设备号已经被分配了。

cat /proc/devices查看设备信息

        2、设备号申请与释放

EXPORT_SYMBOL表示在其他文件调用该函数只需要声明一些就可以,不需要包含头文件。

我们一般用alloc_chrdev_region随机获得一个设备号,用unregister_chrdev_region释放设备号。

编译验证:

//在linux系统写驱动必备四个头文件
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/errno.h>

dev_t dev;定义全局变量作为设备号

//第二步
int __init led_init(void)
{
    pr_info("led driver init success!");
    
    int ret = 0;

    //第三步
    ret = alloc_chrdev_region(&dev,0,"myled");参数一存放设备号,二是设备号从几i开始,三是申请几个设备,四是设备名字,即是哪一类设备。
    if(ret){
        pr_info("alloc_chrdev_region failed");
        return EAGAIN;内核错误一般不返回-1,而是用错误编号见附录1。
}
    
    pr_info("alloc_chrdev_region success,major:%d minor:%d",MAJOR(dev),MINOR(dev));用两个宏获取主次设备号
    return 0;
}

void __exit led_exit(void)
{
    unregister_chrdev_region(dev,1);释放设备号
    pr_info("led driver exit success!");
    return;
}


//第一步
module_init(led_init);//入口(动态加载驱动时执行)
module_exit(led_exit);//出口(卸载时执行)

MODULE_LICENSE("GPL");
MODULE_AUTOHR("zoushiyu");

附录1;错误编号

    编译后申请成功就会给出对应主设备号和次设备号。

8、在上述驱动代码加上文件操作,构建cdev结构体

前面已经申请了设备号,接下来我们要在自己的驱动代码加上file_operation,这样用户层调用文件操作函数就会在驱动层调用对应的文件操作。

1、内核里面file_operation是一系列函数指针

2、依照内核文件操作结构体写一个相同类型的结构体,只是成员只需要写上我们用得上的即可。

//在linux系统写驱动必备四个头文件
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/errno.h>

//自定义函数变量前加static,让其只能在本文件使用。
static dev_t dev;

static ssize_t led_read(struct file *fp, char __user *puser, size_t, loff_t *off)
{
    return 0;
}

static ssize_t led_write (struct file *fp, const char __user *puser, size_t n, loff_t *off);
{
    return 0;
}

static int led_open (struct inode *node, struct file *fp)
{
    return 0;
}

static int led_release(struct inode *node, struct file *fp)
{
    return 0;
}

//第四步:编写文件操作结构体,类型需要与内核一致,成员是指向自己编写的函数,并且自己编写的函数应该与内核对应的文件操作函数类型一致
static struct file_operations fops = {
    .owner = THIS_MODULE, //计数,表示有几个模块调用文件操作
    .open = led_opem,
    .release = led_release,  
    .read = led_read,
    .write = led_write,       
};

//第二步
static int __init led_init(void)
{
    pr_info("led driver init success!");
    
    int ret = 0;

    //第三步
    ret = alloc_chrdev_region(&dev,0,"myled");
    if(ret){
        pr_info("alloc_chrdev_region failed");
        return EAGAIN;
}
    
    pr_info("alloc_chrdev_region success,major:%d minor:%d",MAJOR(dev),MINOR(dev));
    return 0;
}

static void __exit led_exit(void)
{
    unregister_chrdev_region(dev,1);
    pr_info("led driver exit success!");
    return;
}


//第一步
module_init(led_init);
module_exit(led_exit);

MODULE_LICENSE("GPL");
MODULE_AUTOHR("zoushiyu");

3、构建cdev结构体

我们用cdev_alloc申请cdev结构体,并关联设备号和文件操作,这样后面写用户app,当对自己的设备操作时,就会调用自己写的文件操作函数。

//在linux系统写驱动必备四个头文件
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/errno.h>

//自定义函数变量前加static,让其只能在本文件使用。
static dev_t devno;
static struct cdev *pcdev

//自己定义的文件操作函数,当用户层打开的是我们创建的设备,那么后续文件操作就会调用自己写的read、write,如果不是打开自己创建的设备,就会按照默认驱动文件操作。
static ssize_t led_read(struct file *fp, char __user *puser, size_t, loff_t *off)
{
    pr_info("led read success\n");
    return 0;
}

static ssize_t led_write (struct file *fp, const char __user *puser, size_t n, loff_t *off);
{
    pr_info("led write success\n");
    return 0;
}

static int led_open (struct inode *node, struct file *fp)
{
    pr_info("led open success\n");
    return 0;
}

static int led_release(struct inode *node, struct file *fp)
{
    pr_info("led release success\n");
    return 0;
}

//第四步:
static struct file_operations fops = {
    .owner = THIS_MODULE, //计数,表示有几个模块调用文件操作
    .open = led_open,
    .release = led_release,  
    .read = led_read,
    .write = led_write,       
};

//第二步
static int __init led_init(void)
{
    pr_info("led driver init success!");
    
    int ret = 0;

    //第三步
    ret = alloc_chrdev_region(&devno,0,"myled");
    if(ret){
        pr_info("alloc_chrdev_region failed");
        //return -EFAULT;
        goto err_alloc_chrdev_region;
    }
    pr_info("alloc_chrdev_region success,major:%d minor:%d",MAJOR(devno),MINOR(devno));
    
    //第五步:随机申请1个字符设备(cdev)结构体,跟设备号和file_operations关联,卸载模块时释放
    pcdev = cdev_alloc();
    if(!pcdev){
        pr_info("cdev_alloc failed");
        //unregister_chrdev_region(devno,1);//出错执行return语句就会结束,导致设备号没释放,所以先释放设备号再返回值。
        //return -EFAULT;
        goto err_cdev_alloc;
    }
    pr_info("cdev_alloc success!");
    ret = cdev_add(pcdev,devo,1);//把cdev跟设备号关联
    if(ret){
        pr_info("cdev_add failed");
        //unregister_chrdev_region(devno,1);//可以看到,每次出错都要释放设备号,为了简化,内核一般采用goto语句,我们也用这个,出错就跳转到指定位置
        //return -EFAULT;
        goto err_cdev_add;
    }
    pcdev->ops = &fops;//将cdev结构体和文件操作结构体关联
    return 0;
}

err_cdev_add:
err_cdev_alloc:
    unregister_chrdev_region(devno,1);
err_alloc_chrdev_region:
    return -1;

static void __exit led_exit(void)
{
    cdev_del(pcdev);//释放cdev结构体,后申请的先释放,因为后申请的依赖前面申请的东西。
    unregister_chrdev_region(devno,1);
    pr_info("led driver exit success!");
    return;
}


//第一步
module_init(led_init);
module_exit(led_exit);

MODULE_LICENSE("GPL");
MODULE_AUTOHR("zoushiyu");

附录1:cdev结构体

这样,一个简单的驱动文件就写好了,后续加入外设相关操作就可以驱动外设了。

由于没有创建设备文件,只有设备号与我们的设备关联,cat /proc/devices可以看的我们的设备号248

我们可以自己在板子linux系统运行后,在用户交互界面创建一个文件与设备号关联即可:

mknod /dev/led  c 248 0                创建一个字符设备文件与我们的设备号关联

ls -l /dev/led                                        就可以看的我们的设备led对应设备号248

9、编写用户程序,调用这个驱动来验证一下。

//用户程序就不需要保护内核头文件了,只需要包含需要的c库头文件和系统调用头文件即可
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>

int main(void)
{
    int fd = 0;
    int buf[32] = {0};

    fd = open("/dev/led", O_RDWR);//打开自己的设备文件,权限为可读写
    if (-1 == fd)
    {
        perror("fail to open");
        return -1;
    }
    write(fd, "hello world", 11);
    read(fd, &buf, sizeof(buf));

    close(fd);

    return 0;
}

写一个makefile方便编译:

#目标文件
OBJ=appname

#依赖文件
OBJS+=main.c

#编译器
CC=arm-linux-gnueabihf-gcc

$(appname):$(OBJS)
	$(CC) $^ -o $@
	cp $(appname) ~/nfs/rootfs #把可执行程序挂载到共享文件,就可以在板子的用户交互界面运行
.PHONY:
clean:
	rm $(appname)

10、写一个makefile调用驱动和用户的makefile,这样就不要make两次了

appdir=led_app
drvdir=led_drv

all:
    make -C $(appdir)    #表示调用led_app目录下的makefile
    make -C $(drvdir)
.PHONY:
clean:
    make -C $(appdir) clean    #调用led_app目录的make clean
    make -C $(drvdir) clean

make后,在板子用户交互界面运行:./appname,如果打印了驱动写的四个打印数据,就表示我们用户层调用了驱动层文件操作函数。

前面创建设备节点都是在板子用户交互界面mknod /dev/led c 248 0后创建出设备,然后用户层就可以访问这个设备节点/dev/led,接下来我们在驱动代码创建设备节点,这样就不需要在用户交互界面输入命令创建了

//在linux系统写驱动必备四个头文件
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/errno.h>
include<linux/device.h>//第六步:创建节点要加这个头文件

//自定义函数变量前加static,让其只能在本文件使用。
static dev_t devno;
static struct cdev *pcdev;
static struct class* pclass;
static struct device *pdevice;

static ssize_t led_read(struct file *fp, char __user *puser, size_t, loff_t *off)
{
    pr_info("led read success\n");
    return 0;
}

static ssize_t led_write (struct file *fp, const char __user *puser, size_t n, loff_t *off);
{
    pr_info("led write success\n");
    return 0;
}

static int led_open (struct inode *node, struct file *fp)
{
    pr_info("led open success\n");
    return 0;
}

static int led_release(struct inode *node, struct file *fp)
{
    pr_info("led release success\n");
    return 0;
}

//第四步:
static struct file_operations fops = {
    .owner = THIS_MODULE, 
    .open = led_open,
    .release = led_release,  
    .read = led_read,
    .write = led_write,       
};

//第二步
static int __init led_init(void)
{
    pr_info("led driver init success!");
    
    int ret = 0;

    //第三步
    ret = alloc_chrdev_region(&devno,0,"myled");
    if(ret){
        pr_info("alloc_chrdev_region failed");
        //return -EFAULT;
        goto err_alloc_chrdev_region;
    }
    pr_info("alloc_chrdev_region success,major:%d minor:%d",MAJOR(devno),MINOR(devno));
    
    //第五步
    pcdev = cdev_alloc();
    if(!pcdev){
        pr_info("cdev_alloc failed");
        goto err_cdev_alloc;
    }
    pr_info("cdev_alloc success!");
    ret = cdev_add(pcdev,devo,1);
    if(ret){
        pr_info("cdev_add failed");
        goto err_cdev_add;
    }
    pcdev->ops = &fops;
    
    //第六步:创建设备节点。1、先创建设备类。2、创建具体设备
    pclass = class_creat(THIS_MODULE,"led_class");
    if(!pclass){
        pr_info("class creat failed\n");
        goto err_class_creat;
    }
    pr_info("class creat success\n");
    
    pdevice = device_creat(pclass,NULL,devno,NULL,"led%d",0);
    if(!pdevice){
        pr_info("device creat failed\n");
        goto err_device_creat;
    }
    pr_info("device creat success\n");
    return 0;
}
err_device_creat:
    class_destroy(pclass);
err_class_creat:
    cdev_del(pcdev);
err_cdev_add:
err_cdev_alloc:
    unregister_chrdev_region(devno,1);
err_alloc_chrdev_region:
    return -1;

static void __exit led_exit(void)
{
    device_destroy(pclass,devno);
    cdev_del(pcdev);
    unregister_chrdev_region(devno,1);
    pr_info("led driver exit success!");
    return;
}


//第一步
module_init(led_init);
module_exit(led_exit);

MODULE_LICENSE("GPL");
MODULE_AUTOHR("zoushiyu");

 到此,就完成了后两个点,1、符合linux驱动架构,2、可以把驱动加入到运行的系统,还剩下一条,虚拟地址物理地址映射,这就是利用linux操作系统对外设访问的方法了

11、通过linux操作系统虚拟地址到物理地址的映射,来对寄存器进行读写操作,实现led灯亮灭。

1、前面学裸机开发已经知道要想操作imx6ull开发板的led等外设,需要做到以下几点:

        1、使能GPIO时钟

        2、引脚复用功能设为GPIO

        3、配置引脚电器属性

        4、配置gpio寄存器,方向寄存器设为输出,对数据寄存器写入高低电平来实现led灯亮灭。

从imx6ull查找相关寄存器地址:

从原理图看出led灯低电平点亮。

Linux驱动不能直接访问物理地址,但可以通过内核提供的映射机制间接访问。

2、利用ioremap函数实现地址映射:

//在linux系统写驱动必备四个头文件
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/errno.h>
#include<linux/device.h>
#include<asm/io.h> //第七步:地址映射必备头文件

//自定义函数变量前加static,让其只能在本文件使用。
static dev_t devno;
static struct cdev *pcdev;
static struct class* pclass;
static struct device *pdevice;
static void __iomem *pCCGR1;
static void __iomem *pIOMUX;
static void __iomem *pIOPAD;
static void __iomem *pGPIOGDIR;
static void __iomem *pGPIODR;
static ssize_t led_read(struct file *fp, char __user *puser, size_t, loff_t *off)
{
    pr_info("led read success\n");
    return 0;
}

static ssize_t led_write (struct file *fp, const char __user *puser, size_t n, loff_t *off);
{
    pr_info("led write success\n");
    return 0;
}

static int led_open (struct inode *node, struct file *fp)
{
    pr_info("led open success\n");
    return 0;
}

static int led_release(struct inode *node, struct file *fp)
{
    pr_info("led release success\n");
    return 0;
}

//第四步:
static struct file_operations fops = {
    .owner = THIS_MODULE, 
    .open = led_open,
    .release = led_release,  
    .read = led_read,
    .write = led_write,       
};

//第二步
static int __init led_init(void)
{
    pr_info("led driver init success!");
    
    int ret = 0;

    //第三步
    ret = alloc_chrdev_region(&devno,0,"myled");
    if(ret){
        pr_info("alloc_chrdev_region failed");
        //return -EFAULT;
        goto err_alloc_chrdev_region;
    }
    pr_info("alloc_chrdev_region success,major:%d minor:%d",MAJOR(devno),MINOR(devno));
    
    //第五步
    pcdev = cdev_alloc();
    if(!pcdev){
        pr_info("cdev_alloc failed");
        goto err_cdev_alloc;
    }
    pr_info("cdev_alloc success!");
    ret = cdev_add(pcdev,devo,1);
    if(ret){
        pr_info("cdev_add failed");
        goto err_cdev_add;
    }
    pcdev->ops = &fops;
    
    //第六步
    pclass = class_creat(THIS_MODULE,"led_class");
    if(!pclass){
        pr_info("class creat failed\n");
        goto err_class_creat;
    }
    pr_info("class creat success\n");
    
    pdevice = device_creat(pclass,NULL,devno,NULL,"led%d",0);
    if(!pdevice){
        pr_info("device creat failed\n");
        goto err_device_creat;
    }
    pr_info("device creat success\n");
    
    //第七步:地址映射:
    pCCGR1 = ioremap(0x020c406c,4); //前面是寄存器地址,后面是寄存器字节数,函数返回值就是寄存器虚拟地址,用指定返回值类型指针接住,失败则返回NULL。这个就是CCGR寄存器。
    if(!pCCGR1){
        pr_info("ioremap failed\n");
        goto err_ioremap;
    }
    pIOMUX = ioremap(0x020e0068,4) ;
    if(!pIOMUX){
        pr_info("ioremap failed\n");
        goto err_ioremap;
    }
    pIOPAD = ioremap(0x020e02f4,4) ;
    if(!pIOPAD){
        pr_info("ioremap failed\n");
        goto err_ioremap;
    }
    pGPIOGDIR = ioremap(0x0209c004,4) ;
    if(!pGPIOGDIR){
        pr_info("ioremap failed\n");
        goto err_ioremap;
    }
    pGPIODR = ioremap(0x0209c000,4) ;
    if(!pGPIODR){
        pr_info("ioremap failed\n");
        goto err_ioremap;
    }

    return 0;
}

err_ioremap:
    device_destroy(pclass,devno);//还需要解除已经成功映射的寄存器,十分麻烦,可以使用devm_ioremap函数进行映射,它会跟设备绑定,这样设备释放后,相关地址映射都会释放,具体传参可以vi -t查看,一般不会出错,就先用原来的。
err_device_creat:
    class_destroy(pclass);
err_class_creat:
    cdev_del(pcdev);
err_cdev_add:
err_cdev_alloc:
    unregister_chrdev_region(devno,1);
err_alloc_chrdev_region:
    return -1;

static void __exit led_exit(void)
{
    iounremap(pCCGR1);//解除地址映射
    iounremap(pIOMUX);
    iounremap(pIOPAD);
    iounremap(pGPIOGDIR);
    iounremap(pGPIODR);
    device_destroy(pclass,devno);
    cdev_del(pcdev);
    unregister_chrdev_region(devno,1);
    pr_info("led driver exit success!");
    return;
}


//第一步
module_init(led_init);
module_exit(led_exit);

MODULE_LICENSE("GPL");
MODULE_AUTOHR("zoushiyu");

3、利用readl和writel读写寄存器的值

//在linux系统写驱动必备四个头文件
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/errno.h>
#include<linux/device.h>
#include<asm/io.h> 
#include<asm-generic/io.h>//第八步要用readl和writel函数需要包含

//自定义函数变量前加static,让其只能在本文件使用。
static dev_t devno;
static struct cdev *pcdev;
static struct class* pclass;
static struct device *pdevice;
static void __iomem *pCCGR1;
static void __iomem *pIOMUX;
static void __iomem *pIOPAD;
static void __iomem *pGPIOGDIR;
static void __iomem *pGPIODR;
static ssize_t led_read(struct file *fp, char __user *puser, size_t, loff_t *off)
{
    pr_info("led read success\n");
    return 0;
}

static ssize_t led_write (struct file *fp, const char __user *puser, size_t n, loff_t *off);
{
    unsigned int tmpvalue = 0;
    //置0开灯
    tmpvalue = readl(pGPIODR);
    tmpvalue &= ~(0x01 << 3);
    writel(tmpvalue, pGPIODR);
    //置1关灯
    tmpvalue = readl(pGPIODR);
    tmpvalue |= (0x01 << 3);
    writel(tmpvalue, pGPIODR);

    pr_info("led write success\n");
    return 0;
}

static int led_open (struct inode *node, struct file *fp)
{
    pr_info("led open success\n");
    return 0;
}

static int led_release(struct inode *node, struct file *fp)
{
    pr_info("led release success\n");
    return 0;
}

//第四步:
static struct file_operations fops = {
    .owner = THIS_MODULE, 
    .open = led_open,
    .release = led_release,  
    .read = led_read,
    .write = led_write,       
};

//第二步
static int __init led_init(void)
{
    pr_info("led driver init success!");
    unsigned int tmpvalue;//接收寄存器读到的值
    int ret = 0;
    
    //第三步
    ret = alloc_chrdev_region(&devno,0,"myled");
    if(ret){
        pr_info("alloc_chrdev_region failed");
        //return -EFAULT;
        goto err_alloc_chrdev_region;
    }
    pr_info("alloc_chrdev_region success,major:%d minor:%d",MAJOR(devno),MINOR(devno));
    
    //第五步
    pcdev = cdev_alloc();
    if(!pcdev){
        pr_info("cdev_alloc failed");
        goto err_cdev_alloc;
    }
    pr_info("cdev_alloc success!");
    ret = cdev_add(pcdev,devo,1);
    if(ret){
        pr_info("cdev_add failed");
        goto err_cdev_add;
    }
    pcdev->ops = &fops;
    
    //第六步
    pclass = class_creat(THIS_MODULE,"led_class");
    if(!pclass){
        pr_info("class creat failed\n");
        goto err_class_creat;
    }
    pr_info("class creat success\n");
    
    pdevice = device_creat(pclass,NULL,devno,NULL,"led%d",0);
    if(!pdevice){
        pr_info("device creat failed\n");
        goto err_device_creat;
    }
    pr_info("device creat success\n");
    
    //第七步
    pCCGR1 = ioremap(0x020c406c,4);
    if(!pCCGR1){
        pr_info("ioremap failed\n");
        goto err_ioremap;
    }
    pIOMUX = ioremap(0x020e0068,4) ;
    if(!pIOMUX){
        pr_info("ioremap failed\n");
        goto err_ioremap;
    }
    pIOPAD = ioremap(0x020e02f4,4) ;
    if(!pIOPAD){
        pr_info("ioremap failed\n");
        goto err_ioremap;
    }
    pGPIOGDIR = ioremap(0x0209c004,4) ;
    if(!pGPIOGDIR){
        pr_info("ioremap failed\n");
        goto err_ioremap;
    }
    pGPIODR = ioremap(0x0209c000,4) ;
    if(!pGPIODR){
        pr_info("ioremap failed\n");
        goto err_ioremap;
    }
    
    //第八步:读写寄存器的值,准备工作写在初始化里面,最后改变引脚电平也就是对GPIODR寄存器的读写写出函数操作里面,这样应用层就可以对引脚电平改变了。
    //gpio1对应时钟使能
    tmpvalue = readl(pCCGR1);这里的l就是long,四字节的意思。
    tmpvalue &= ~(0x3 << 26);
    tmpvalue |= (0x3 << 26);
    writel(tmpvalue,pCCGR1);
    //设置为GPIO
    writel(0x5, pIOMUX);

    //设置电器属性
    writel(0x10B0, pIOPAD);

    //GPIO方向
    tmpvalue = readl(pGPIODIR);
    tmpvalue |= 0x1 << 3;
    writel(tmpvalue, pGPIODIR);

    return 0;
}

err_ioremap:
    device_destroy(pclass,devno);
err_device_creat:
    class_destroy(pclass);
err_class_creat:
    cdev_del(pcdev);
err_cdev_add:
err_cdev_alloc:
    unregister_chrdev_region(devno,1);
err_alloc_chrdev_region:
    return -1;

static void __exit led_exit(void)
{
    iounremap(pCCGR1);
    iounremap(pIOMUX);
    iounremap(pIOPAD);
    iounremap(pGPIOGDIR);
    iounremap(pGPIODR);
    device_destroy(pclass,devno);
    cdev_del(pcdev);
    unregister_chrdev_region(devno,1);
    pr_info("led driver exit success!");
    return;
}


//第一步
module_init(led_init);
module_exit(led_exit);

MODULE_LICENSE("GPL");
MODULE_AUTOHR("zoushiyu");

这样我们就完成了驱动间接操作寄存器,但是在驱动的write操作函数中,我们写了开灯关灯操作,很明显不完整,我们的加一个选择,让应用层调write时候,可以根据写入的东西来判断什么时候开,什么时候关,static ssize_t led_write (struct file *fp, const char __user *puser, size_t n, loff_t *off);从函数原型看到const char __user *puser, size_t n这两个参数似乎就是我们写入的字符串和个数,但是我们没法直接使用这两个参数值,因为这些是系统调用的,这就需要再用两个函数来获得用户写的字符串了:copy_from_user和copy_to_user:

//在linux系统写驱动必备四个头文件
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/errno.h>
#include<linux/device.h>
#include<asm/io.h> 
#include<asm-generic/io.h>
#include<asm/uaccess.h>第九部:使用copy_from_user和copy_to_user包含
//自定义函数变量前加static,让其只能在本文件使用。
extern void __iomen *devm_ioremap(struct device *dev,resource_size_t offset,resource_size_t size);内核的符号表里面有,声明一下就可以用。

static int ledstat;//定义为全局变量,在read读出状态
static dev_t devno;
static struct cdev *pcdev;
static struct class* pclass;
static struct device *pdevice;
static void __iomem *pCCGR1;
static void __iomem *pIOMUX;
static void __iomem *pIOPAD;
static void __iomem *pGPIOGDIR;
static void __iomem *pGPIODR;
static ssize_t led_read(struct file *fp, char __user *puser, size_t, loff_t *off)
{
    unsigned long ret = 0;
    ret = copy_to_user(puser,&ledstat,sizeof(ledstat));传递状态信息到用户缓冲区,用户区的fd就是二者的交互接口,返回值是0表示成功,非零表示未拷贝的字节数。
    if(!ret){
        pr_info("copy_to_user failed");
    }
    pr_info("led read success\n");
    return 0;
}

static ssize_t led_write (struct file *fp, const char __user *puser, size_t n, loff_t *off);
{
    第九部:利用安全函数拷贝获取const char __user *puser,即用户写的字符串数据
    unsigned int tmpvalue = 0;
    unsigned int len;
    ledstat = 0;
    len = copy_from_user(&ledstat,puser,sizeof(ledstat));从puser拷贝到ledstat
    if(!len){
        pr_info("copy_from_user failed");
    }
    用户层写1开灯,写0关灯。
    if(ledstat == 1){
        //置0开灯
        tmpvalue = readl(pGPIODR);
        tmpvalue &= ~(0x01 << 3);
        writel(tmpvalue, pGPIODR);
    }
    if(ledstat == 0){
        //置1关灯
        tmpvalue = readl(pGPIODR);
        tmpvalue |= (0x01 << 3);
        writel(tmpvalue, pGPIODR);
    }

    pr_info("led write success\n");
    return 0;
}

static int led_open (struct inode *node, struct file *fp)
{
    pr_info("led open success\n");
    return 0;
}

static int led_release(struct inode *node, struct file *fp)
{
    pr_info("led release success\n");
    return 0;
}

//第四步:
static struct file_operations fops = {
    .owner = THIS_MODULE, 
    .open = led_open,
    .release = led_release,  
    .read = led_read,
    .write = led_write,       
};

//第二步
static int __init led_init(void)
{
    pr_info("led driver init success!");
    unsigned int tmpvalue;
    int ret = 0;
    
    //第三步
    ret = alloc_chrdev_region(&devno,0,"myled");
    if(ret){
        pr_info("alloc_chrdev_region failed");
        //return -EFAULT;
        goto err_alloc_chrdev_region;
    }
    pr_info("alloc_chrdev_region success,major:%d minor:%d",MAJOR(devno),MINOR(devno));
    
    //第五步
    pcdev = cdev_alloc();
    if(!pcdev){
        pr_info("cdev_alloc failed");
        goto err_cdev_alloc;
    }
    pr_info("cdev_alloc success!");
    ret = cdev_add(pcdev,devno,1);
    if(ret){
        pr_info("cdev_add failed");
        goto err_cdev_add;
    }
    pcdev->ops = &fops;
    
    //第六步
    pclass = class_creat(THIS_MODULE,"led_class");
    if(!pclass){
        pr_info("class creat failed\n");
        goto err_class_creat;
    }
    pr_info("class creat success\n");
    
    pdevice = device_creat(pclass,NULL,devno,NULL,"led%d",0);
    if(!pdevice){
        pr_info("device creat failed\n");
        goto err_device_creat;
    }
    pr_info("device creat success\n");
    
    //第七步
    pCCGR1 = devm_ioremap(pdevice,0x020c406c,4);由于ioremap进行地址映射没法跟设备绑定,在后面释放设备就不会回收虚拟地址,还需要手动回收,改成devm_ioremap就可以在回收设备时顺便把地址回收,后面错误处理也简单些,所以都用这个。
    if(!pCCGR1){
        pr_info("ioremap failed\n");
        goto err_ioremap;
    }
    pIOMUX = devm_ioremap(pdevice,0x020e0068,4) ;
    if(!pIOMUX){
        pr_info("ioremap failed\n");
        goto err_ioremap;
    }
    pIOPAD = devm_ioremap(pdevice,0x020e02f4,4) ;
    if(!pIOPAD){
        pr_info("ioremap failed\n");
        goto err_ioremap;
    }
    pGPIOGDIR = devm_ioremap(pdevice,0x0209c004,4) ;
    if(!pGPIOGDIR){
        pr_info("ioremap failed\n");
        goto err_ioremap;
    }
    pGPIODR = devm_ioremap(pdevice,0x0209c000,4) ;
    if(!pGPIODR){
        pr_info("ioremap failed\n");
        goto err_ioremap;
    }
    
    //第八步
    //gpio1对应时钟使能
    tmpvalue = readl(pCCGR1);
    tmpvalue &= ~(0x3 << 26);
    tmpvalue |= (0x3 << 26);
    writel(tmpvalue,pCCGR1);
    //设置为GPIO
    writel(0x5, pIOMUX);
    //设置电器属性
    writel(0x10B0, pIOPAD);
    //GPIO方向
    tmpvalue = readl(pGPIODIR);
    tmpvalue |= 0x1 << 3;
    writel(tmpvalue, pGPIODIR);
    //置1关灯    初始化为关灯状态
    tmpvalue = readl(pGPIODR);
    tmpvalue |= (0x01 << 3);
    writel(tmpvalue, pGPIODR);

    return 0;
}

err_ioremap:
    device_destroy(pclass,devno);
err_device_creat:
    class_destroy(pclass);
err_class_creat:
    cdev_del(pcdev);
err_cdev_add:
err_cdev_alloc:
    unregister_chrdev_region(devno,1);
err_alloc_chrdev_region:
    return -1;

static void __exit led_exit(void)
{
    device_destroy(pclass,devno);
    cdev_del(pcdev);
    unregister_chrdev_region(devno,1);
    pr_info("led driver exit success!");
  /第一步
module_init(led_init);
module_exit(led_exit);

MODULE_LICENSE("GPL");
MODULE_AUTOHR("zoushiyu");

用户层:写入整型值

//用户程序就不需要保护内核头文件了,只需要包含需要的c库头文件和系统调用头文件即可
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>

int main(void)
{
    int fd = 0;
    int ledstat = 0;

    fd = open("/dev/led0", O_RDWR);//打开自己的设备文件,权限为可读写
    if (-1 == fd)
    {
        perror("fail to open");
        return -1;
    }
    ledstat = 1;
    write(fd, &ledstat, sizeof(ledstat));
    read(fd, &ledstat, sizeof(ledstat));
    if(ledstat == 1)
    {
        printf(led on success);
    }
    close(fd);

    return 0;
}

这下,一个简单完整的字符设备驱动程序基本写好了,大致步骤如下:

12、sys文件系统,简单来说就是Linux系统展示内核状态的一个文件系统,如下

比较重要的:

加载完驱动,我们创建的led0就在/sys/class/led_class/led0,

另外,在dev目录下也有:/sys/dev/char/248:0/

可以看到两种路径都进入同一个目录

前瞻结束

一、字符设备驱动框架

1.字符设备流程:

        *字符设备驱动加载到Linux内核方式:静态加载和动态加载。

        *静态加载是在内核编译阶段,编译到内核中, Linux内核启动后直接拥有了该内核模块。

        *动态加载是Linux内核启动完毕后通过insmod加载、 rmmod卸载。

        *驱动开发过程更多采用动态加载。

        *动态加载需要创建一个cdev结构体,包含:dev_t类型的设备号、操作设备的file_operations结构体

        设备号包含:主设备号表示设备类型次设备号表示该类型的第几个设备

        file_operations结构体成员是:各种操作设备对应的函数指针。来实现具体的open、 read、 write、 ioctl等一系列对设备的操作。

        *用户空间通过open打开对应的设备文件设备文件通过VFS文件系统找到对应的设备号通过设备号可以找到字符设备cdev结构体,通过结构体可以找到对设备操作的file_operations结构体,最终通过这个结构体中的函数指针实现对设备具体的操作

2.file_operation结构体:

struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);//常用
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);//常用
ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
int (*iterate) (struct file *, struct dir_context *);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*mremap)(struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);//常用
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *);//常用,相当于close
int (*fsync) (struct file *, loff_t, loff_t, int datasync);
int (*aio_fsync) (struct kiocb *, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *,
int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned
long, unsigned long, unsigned long);
int (*check_flags)(int);
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *,
size_t, unsigned int);
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *,
size_t, unsigned int);
int (*setlease)(struct file *, long, struct file_lock **, void **);
long (*fallocate)(struct file *file, int mode, loff_t offset, loff_t len);
void (*show_fdinfo)(struct seq_file *m, struct file *f);
#ifndef CONFIG_MMU
unsigned (*mmap_capabilities)(struct file *);
#endif
};

3.函数接口:

1.cdev操作函数接口:

函数名功能
cdev_init初始化字符设备结构体
cdev_alloc申请字符设备结构体
cdev_add添加字符设备
cdev_del删除字符设备

函数原型
 

void cdev_init(struct cdev *cdev, const struct file_operations *fops) ;
struct cdev *cdev_alloc(void);
int cdev_add(struct cdev *p, dev_t dev, unsigned count);
void cdev_del(struct cdev *p);

2.字符设备操作接口:

函数名功能
register_chrdev_region静态注册已知设备号范围(需提前确定主设备号)
alloc_chrdev_region动态分配设备号(内核自动分配主设备号)
unregister_chrdev_region释放注册的设备号
register_chrdev注册设备号并关联file_operations结构体(早期内核使用, 2.6版本以后尽量不使用)
unregister_chrdev注册注册的设备号(早期内核使用, 2.6版本以后尽量不使用)

函数原型:

int register_chrdev_region(dev_t from, unsigned count, const char *name);
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const
char *name);
void unregister_chrdev_region(dev_t from, unsigned count);
static inline int register_chrdev(unsigned int major, const char *name, const
struct file_operations *fops);
static inline void unregister_chrdev(unsigned int major, const char *name);

3.设备类创建

函数名功能
class_create创建设备类目录
class_destroy销毁设备类

函数原型:

#define class_create(owner, name)
void class_destroy(struct class *cls);

4.设备创建

函数名功能

device_create创建设备

device_destroy销毁设备

函数原型:

struct device *device_create(struct class *class, struct device *parent, dev_t
devt, void *drvdata, const char *fmt, ...);
void device_destroy(struct class *class, dev_t devt);

二、sys文件系统

        sysfs是Linux内核中一种特殊虚拟文件系统,用来向用户空间提供内核设备和驱动程序的信息。它提供了一种统一的接口,用于查看和操作设备、驱动程序、文件系统等内核对象。 sysfs存放在内存中,挂载在/sys目录下,文件系统中的文件不对应硬盘上任何文件。

1.sys的目录结构

sys文件系统中重点目录的设计思想如下图所示:

目录名功能目录结构
/sys/devices表示系统中的物理设备,每个子目录对应一个设备1.系统设备:如CPU、系统内存等2.总线设备:如PCI、 USB、 SCSI等设备3.虚拟设备:如虚拟网络设备
/sys/bus表示系统总线类型(如PCI、 USB等),每个子目录对应一个总线1.devices:列出所有连接到该总线的设备2.drivers:列出与该总线相关的所有驱动程序3.drivers_autoprobe和drivers_probe:用于自动或手动驱动程序绑定4.uevent:用于触发uevent事件
/sys/class表示系统中的设备类型(如网络设备、块设备等),子目录按类型分类1.net:表示所有网络接口2.block:表示所有块设备3.tty:表示所有终端设备

其余目录还有:

目录名功能
/sys/kernel表示内核参数和信息,如调度器参数、内核模块等
/sys/module表示加载的内核模块,每个子目录对应一个模块,包含模块参数和状态信息
/sys/block表示块设备(如硬盘、 USB存储设备等)

2.sys文件系统使用

sysfs在驱动中的作用通常包括:

1. 设备信息显示:将内核对象(设备、驱动、总线等)的属性以文件形式展示在/sys目录下,例如设备状态、配置参数、硬件拓扑等。用户可通过cat或者echo命令直接读取/修改这些文件,无需编写专用工具。

2. 动态设备管理:实时反映系统设备变化(如热插拔),目录结构随设备增删动态更新支持设备分类

3. 驱动调试与配置:提供寄存器访问接口,允许直接读取/写入硬件寄存器。

函数名功能

kobject_create_and_add创建并初

sysfs_create_file创建一个

kobject_get减少kobje
kobject_put增加kobject的引用计数

sysfs_remove_file删除已创建的sysfs属性文件

__ATTR

函数原型:

struct kobject *kobject_create_and_add(const char *name, struct kobject *parent);
static inline int __must_check sysfs_create_file(struct kobject *kobj, const
struct attribute *attr);
struct kobject *kobject_get(struct kobject *kobj);
void kobject_put(struct kobject *kobj);
static inline void sysfs_remove_file(struct kobject *kobj, const struct attribute
*attr);
#define __ATTR(_name, _mode, _show, _store);

三、LED灯驱动

内存映射接口

函数名功能
ioremap将内核虚拟地址映射到物理地址
iounmap解除映射的虚拟地址
devm_ioremap将物理地址映射到内存虚拟地址空间(驱动卸载时自动解除映射)
devm_iounmap提前解除映射关系
devm_ioremap_resouce整合资源申请并映射,避免硬件冲突,适用设备树场景

函数原型:

void __iomem *ioremap(unsigned long paddr, unsigned long size);
void iounmap(const void __iomem *addr);
void __iomem *devm_ioremap(struct device *dev, resource_size_t offset,
resource_size_t size);
void devm_iounmap(struct device *dev, void __iomem *addr);
void __iomem *devm_ioremap_resource(struct device *dev, struct resource *res)
//led_drv.c
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <asm/io.h>
#include <asm-generic/io.h>
#include <asm/uaccess.h>
#include <linux/of.h>

static ssize_t led_read(struct file *fp, char __user *puser, size_t n, loff_t *off);
static ssize_t led_write(struct file *fp, const char __user *puser, size_t n, loff_t *off);
static int led_open(struct inode *node, struct file *fp);
static int led_release(struct inode *node, struct file *fp);
extern void __iomem *devm_ioremap(struct device *dev, resource_size_t offset, resource_size_t size);
ssize_t led_show(struct device *dev, struct device_attribute *attr, char *buf);
ssize_t led_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count);

static int ledstat;
static struct device *pdevice;
static struct class *pclass;
static struct cdev *pcdev;
static dev_t devno;
static void __iomem *pccgr1;
static void __iomem *imuxrcsw;
static void __iomem *imuxrcpad;
static void __iomem *gpiodir;
static void __iomem *gpiodat;
static struct device_attribute led_attr = __ATTR(ledbright, 0664, led_show, led_store);
static struct file_operations fops = {
    .owner = THIS_MODULE,
    .read = led_read,
    .write = led_write,
    .open = led_open,
    .release = led_release,
};

static ssize_t led_read(struct file *fp, char __user *puser, size_t n, loff_t *off)
{   
    unsigned long ret = 0;

    ret = copy_to_user(puser, &ledstat, sizeof(ledstat));
    if (ret) 
        pr_info("copy_to_user failed\n");
    
    pr_info("led read success!\n");

    return 0;
}

static ssize_t led_write(struct file *fp, const char __user *puser, size_t n, loff_t *off)
{
    unsigned int tmpvalue = 0;
    unsigned long len = 0;

    len = copy_from_user(&ledstat, puser, sizeof(ledstat));
    if (len) {
        pr_info("copy_from_user failed");
    }

    if (1 == ledstat)
    {
        //置0开灯
        tmpvalue = readl(gpiodat);
        tmpvalue &= ~(0x1 << 3);
        writel(tmpvalue, gpiodat);
    }
    else if (0 == ledstat)
    {
        //置1关灯
        tmpvalue = readl(gpiodat);
        tmpvalue |= 0x1 << 3;
        writel(tmpvalue, gpiodat);
    }

    pr_info("led write success!\n");

    return 0;
}

static int led_open(struct inode *node, struct file *fp)
{
    pr_info("led open success!\n");

    return 0;
}

static int led_release(struct inode *node, struct file *fp)
{
    pr_info("led release success!\n");

    return 0;
}

ssize_t led_show(struct device *dev, struct device_attribute *attr, char *buf)
{
    ssize_t len = 0;

    if (1 == ledstat)
    {
        len = sprintf(buf, "LED_ON");
    }
    else if (0 == ledstat)
    {
        len = sprintf(buf, "LED_OFF");
    }

    return len;
}

ssize_t led_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
    unsigned int tmpvalue = 0;
    char tmpbuff[32] = {0};

    sscanf(buf, "%s", tmpbuff);

    if (!strcmp(tmpbuff, "LED_ON"))
    {
        //置0开灯
        tmpvalue = readl(gpiodat);
        tmpvalue &= ~(0x1 << 3);
        writel(tmpvalue, gpiodat);
    }
    else if (!strcmp(tmpbuff, "LED_OFF"))
    {
        //置1关灯
        tmpvalue = readl(gpiodat);
        tmpvalue |= 0x1 << 3;
        writel(tmpvalue, gpiodat);
    }

    return count;
}

static int __init led_init(void)
{   
    int ret = 0;
    unsigned int tmpvalue = 0;
    struct device_node *pdtsdevice = NULL;
    const char *pcompatible = NULL;
    unsigned int regaddr[10] = {0};
    int i = 0;

    //注册设备号
    ret = alloc_chrdev_region(&devno, 0, 1, "myled");
    if (ret) {
        pr_info("alloc_chrdev_region failed\n");
        goto err_alloc_chrdev_region;
    }

    pr_info("major:%d minor:%d\n", MAJOR(devno), MINOR(devno));

    //创建cdev并初始化
    pcdev = cdev_alloc();
    if (!pcdev) {
        pr_info("cdev_alloc failed\n");
        goto err_cdev_alloc;
    }

    //将cdev加入字符设备结构中
    pcdev->ops = &fops;
    ret = cdev_add(pcdev, devno, 1);
    if (ret) {
        pr_info("cdev_alloc failed\n");
        goto err_cdev_add;
    }

    //创建设备类
    pclass = class_create(THIS_MODULE, "led_class");
    if (!pclass) {
        pr_info("class_create failed\n");
        goto err_class_create;
    }  

    //创建具体设备
    pdevice = device_create(pclass, NULL, devno, NULL, "led%d", 0);
    if (!pclass) {
        pr_info("device_create failed\n");
        goto err_device_create;
    }  

    //查找设备树节点
    pdtsdevice = of_find_node_by_path("/puteleds");
    if (!pdtsdevice)
        pr_info("of_find_node_by_path");

    //读取属性中的compatible字符串
    ret = of_property_read_string(pdtsdevice, "compatible", &pcompatible);
    if (ret)
        pr_info("of_property_read_string failed");
    
    pr_info("compatible = %s\n", pcompatible);

    ret = of_property_read_u32_array(pdtsdevice, "reg", regaddr, 10);
    if (ret)
        pr_info("of_property_read_u32_array");

    for (i = 0; i < 10; i+=2)
    {
        pr_info("addr: %#x size:%d\n", regaddr[i], regaddr[i+1]);
    }

    //虚拟地址向物理地址映射
    pccgr1 = devm_ioremap(pdevice, regaddr[0], regaddr[1]);
    if (!pccgr1) {
        pr_info("fail to ioremap");
        goto err_device_create;
    }  
    imuxrcsw = devm_ioremap(pdevice, regaddr[2], regaddr[3]); 
    if (!imuxrcsw) {
        pr_info("fail to ioremap");
        goto err_device_create;
    }  
    imuxrcpad = devm_ioremap(pdevice, regaddr[4], regaddr[5]); 
    if (!imuxrcpad) {
        pr_info("fail to ioremap");
        goto err_device_create;
    }  
    gpiodir = devm_ioremap(pdevice, regaddr[6], regaddr[7]); 
    if (!gpiodir) {
        pr_info("fail to ioremap");
        goto err_device_create;
    }  
    gpiodat = devm_ioremap(pdevice, regaddr[8], regaddr[9]); 
    if (!gpiodat) {
        pr_info("fail to ioremap");
        goto err_device_create;
    }  

    //时钟
    tmpvalue = readl(pccgr1);
    tmpvalue &= ~(0x3 << 26);
    tmpvalue |= (0x3 << 26);
    writel(tmpvalue, pccgr1);

    //设置为GPIO
    writel(0x5, imuxrcsw);

    //设置电器属性
    writel(0x10B0, imuxrcpad);

    //GPIO方向
    tmpvalue = readl(gpiodir);
    tmpvalue |= 0x1 << 3;
    writel(tmpvalue, gpiodir);

    //关灯
    //置1关灯
    tmpvalue = readl(gpiodat);
    tmpvalue |= (0x1 << 3);
    writel(tmpvalue, gpiodat);

    //增加sysfs文件系统中的调试节点
    ret = device_create_file(pdevice, &led_attr);
    if (ret) 
        pr_info("device_create_file failed");

    pr_info("led init success\n");

    return 0;
err_device_create:
    class_destroy(pclass); 
err_class_create:
    cdev_del(pcdev);
err_cdev_add:
err_cdev_alloc:
    unregister_chrdev_region(devno, 1);
err_alloc_chrdev_region:
    return -1;
}

static void __exit led_exit(void)
{
    device_destroy(pclass, devno);
    class_destroy(pclass);
    cdev_del(pcdev);
    unregister_chrdev_region(devno, 1);
    pr_info("led exit success\n");

    return;
}

module_init(led_init);
module_exit(led_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("haiersen");
#led_drv的makefile
#模块名
modulename=led_drv

#内核路径
kerdir=/home/linux/imx6ull/kernel/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek

#当前路径
curpath=$(shell pwd)

#将代码加入模块编译选项中
obj-m+=$(modulename).o 

all:
	make -C $(kerdir) modules M=$(curpath)
	cp $(modulename).ko ~/nfs/rootfs
.PHONY:
clean:
	rm $(modulename).ko
//led_app
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>

int main(void)
{
    int fd = 0;
    int ledstat = 0;

    fd = open("/dev/led0", O_RDWR);
    if (-1 == fd)
    {
        perror("fail to open");
        return -1;
    }

    while (1)
    {
        ledstat = 1;
        write(fd, &ledstat, sizeof(ledstat));
        read(fd, &ledstat, sizeof(ledstat));
        if (1 == ledstat)
        {
            printf("open led success\n");
        }
        sleep(1);

        ledstat = 0;
        write(fd, &ledstat, sizeof(ledstat));
        read(fd, &ledstat, sizeof(ledstat));
        if (0 == ledstat)
        {
            printf("close led success\n");
        }
        sleep(1);
    }


    close(fd);

    return 0;
}
#led_app的makefile
#模块名称
appname=led_app

#编译器
CC=arm-linux-gnueabihf-gcc

$(appname):main.c
	$(CC) main.c -o $@
	cp $(appname) ~/nfs/rootfs 
.PHONY:
clean:
	rm $(appname)
#总makefile
modulename=led

all:
	make -C $(modulename)_app
	make -C $(modulename)_drv

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值