zoukankan      html  css  js  c++  java
  • 转字符驱动实例gpio

    概述: 字符设备驱动程序: 是按照字符设备要求完成的由操作系统调用的代码。
    重点理解以下内容:
     1. 驱动是写给操作系统的代码,它不是直接给用户层程序调用的,而是给系统调用的
     2. 所以驱动要向系统注册。
     3. 注册的时候,要求驱动必须符合一定的规范,否则系统就会不认识。这就是程序架构。
     4. 字符设备驱动对应一个cdev 结构, 需要向系统注册或申请设备号,注册cdev设备,
        完成cdev 设备需要的操作,诸如读,写,ioctl操作等。
     5. 系统下驱动以模块的形式而存在
     6. 用户空间验证,需要先建立设备节点
        例如 mknod /dev/gpio c 126 0  将创建/dev/gpio 节点, 主设备号126,从设备号0
        然后用 echo 'a' >/dev/gpio 查看写入
               cat /dev/gpio       查看读出。

        你也可以书些标准的文件访问来测试 /dev/gpio, 这里从略。

    这个126 如果是系统申请的,则是动态的,你需要用cat /proc/devices 去查询系统给你的驱动分配了什么设备号。

    然后再创建设备结点。

    如果启用了sysfs, 则在 /sys/module/gpio 目录下有相应的属性信息描述。

    补充:

    1。可以用cat /proc/devices | grep <设备名> 查看系统分配(或自己指定)的主设备号。

    2. 当mknod 以后,可以用 ll /dev | grep <设备名> 查看设备的主设备号,从设备号

    --------------------------------------------------------------------------------
     下面给出一个实例:
     gpioadaptor.c 演示字符设备向系统注册的情景。
     gpio.c 是真正的硬件驱动代码。(这里只是用printk 打印了相关信息)
     在centos 3.10 内核上测试通过
    --------------------------------------------------------------------------------

    /*======================================================================
      A gpio driver as an example of char device drivers  
      author: hjjdebug
      date: Fri May  9 17:54:33 CST 2014

      ======================================================================*/

    // gpioadaptor.c

    #include <linux/module.h>        // module 架构及宏定义
    // struct cdev 定义, 字符型设备结构体。标准化结构
    #include <linux/cdev.h>            
    // struct file 定义, 设备操作是按文件来操作的,所以用到文件指针
    #include <linux/fs.h>            
    #include <linux/slab.h>            // kmalloc, kfree 声明, 为设备变量分配缓存
    #include <asm/uaccess.h>        // copy_to_user , copy_from_user 声明, 数据copy
    #include "gpio.h"

    #define GPIO_SIZE    0x4            // 4字节做为gpio 缓存, 你可以定义的更大一些。
    #define GPIO_MAJOR 254            /*预设的gpio的主设备号*/

    // IOCTL 命令定义
    #define ALL_MEM_CLEAR 0x1  /*清0全部内存*/
    #define SET_MEM_ADDR  0x2   // 设置操作的gpio 地址
    #define WRITE_DATA      0x3   // 写io 端口
    #define READ_DATA          0x4   // 读io 端口

    /*gpio设备结构体, 定义自己使用的变量,并要包含一个cdev 成员,与系统字符设备接口*/
    struct gpio_dev                                     
    {                                                        
        struct cdev cdev; /*cdev结构体*/                       
        unsigned char mem[GPIO_SIZE]; /*全局内存*/        
        int addr;        // gpio 地址, 操作哪一个gpio
    };

    // 全局变量定义
    static int gpio_major = GPIO_MAJOR;  // 保留申请的主设备号
    struct gpio_dev *gpio_devp; /*设备结构体指针*/
    /*文件打开函数, 将gpio_devp 传递给file 结构的私有数据*/
    int gpio_open(struct inode *inode, struct file *filp)
    {
        /*将设备结构体指针赋值给文件私有数据指针*/
        filp->private_data = gpio_devp;
        return 0;
    }
    /*文件释放函数*/
    int gpio_release(struct inode *inode, struct file *filp)
    {
        return 0;
    }

    // ioctl设备控制函数
    // 对于简单的gpio. 也许ioctl就已经足够了,而不许要read,write 接口了。
    // 这里为了完整,仍然写了read, write,等,完成批量内存操作
    long gpio_ioctl(struct file *filp, unsigned
            int cmd, unsigned long arg)
    {
        struct gpio_dev *pDev = filp->private_data;/*获得设备结构体指针*/

        switch (cmd)
        {
            case ALL_MEM_CLEAR:
                memset(pDev->mem, 0, GPIO_SIZE);      
                printk(KERN_INFO "all gpio is set to zero ");
                break;
            case SET_MEM_ADDR:
                pDev->addr = arg;
                printk(KERN_INFO "addr is %d ",pDev->addr);
                break;
            case WRITE_DATA:
                pDev->mem[pDev->addr]=arg;
                GPIOSetData(pDev->addr, arg);
                printk(KERN_INFO "Data Write: %d ",(int)arg);
                break;
            case READ_DATA:
                arg=pDev->mem[pDev->addr];
                printk(KERN_INFO "Data Read: %d ",(int)arg);
                break;


            default:
                return  - EINVAL;
        }
        return 0;
    }

    //读函数, 可以一次读多个gpio 的数值,似乎有些多余,但体现read 的能力
    static ssize_t gpio_read(struct file *filp, char __user *buf, size_t size,
            loff_t *ppos)
    {
        unsigned long offset =  *ppos;
        unsigned int count = size;
        int ret = 0;
        struct gpio_dev *pDev = filp->private_data; /*获得设备结构体指针*/

        printk("need size:%ld, offset:%ld ",size,offset);

        /*分析和获取有效的写长度*/
        if (offset > GPIO_SIZE)
        {
            return count ?  - ENXIO: 0;
        }
        else if(offset == GPIO_SIZE)
        {
            return 0;   // 防止测试cat /dev/gpio 时 文件尾出现错误提示
        }
        if (count > GPIO_SIZE - offset)
        {
            count = GPIO_SIZE - offset;
        }

        /*内核空间->用户空间*/
        if (!copy_to_user(buf, (void*)(pDev->mem + offset), count))
        {
            *ppos += count;
            printk(KERN_INFO "read %d bytes(s) from %ld addr ", count, offset);
            ret = count;
        }
        else
        {
            ret =  - EFAULT;
        }

        return ret;
    }

    /*写函数*/
    static ssize_t gpio_write(struct file *filp, const char __user *buf,
            size_t size, loff_t *ppos)
    {
        unsigned long offset =  *ppos;
        unsigned int count = size;
        int ret = 0;
        int i;
        struct gpio_dev *pDev = filp->private_data; /*获得设备结构体指针*/

        /*分析和获取有效的写长度*/
        if (offset >= GPIO_SIZE)
        {
            return count ?  - ENXIO: 0;
        }
        if (count > GPIO_SIZE - offset)
        {
            count = GPIO_SIZE - offset;
        }

        /*用户空间->内核空间*/
        if (!copy_from_user(pDev->mem + offset, buf, count))
        {
            *ppos += count;
            for(i=0; i< count; i++)
            {
                GPIOSetData(offset+i, pDev->mem[offset+i]);
            }    
            printk(KERN_INFO "written %d bytes(s) to %ld addr ", count, offset);
            ret = count;
        }
        else
        {
            ret =  - EFAULT;
        }

        return ret;
    }

    /* seek文件定位函数 */
    static loff_t gpio_llseek(struct file *filp, loff_t offset, int orig)
    {
        loff_t ret = 0;
        switch (orig)
        {
            case 0:   /*相对文件开始位置偏移*/
                if (offset < 0)
                {
                    ret =  - EINVAL;
                    break;
                }
                if ((unsigned int)offset > GPIO_SIZE)
                {
                    ret =  - EINVAL;
                    break;
                }
                filp->f_pos = (unsigned int)offset;
                ret = filp->f_pos;
                break;
            case 1:   /*相对文件当前位置偏移*/
                if ((filp->f_pos + offset) > GPIO_SIZE)
                {
                    ret =  - EINVAL;
                    break;
                }
                if ((filp->f_pos + offset) < 0)
                {
                    ret =  - EINVAL;
                    break;
                }
                filp->f_pos += offset;
                ret = filp->f_pos;
                break;
            default:
                ret =  - EINVAL;
                break;
        }
        return ret;
    }

    /*文件操作结构体*/
    static const struct file_operations gpio_fops =
    {
        .owner = THIS_MODULE,
        .llseek = gpio_llseek,
        .read = gpio_read,
        .write = gpio_write,
        .compat_ioctl = gpio_ioctl,
        .open = gpio_open,
        .release = gpio_release,
    };

    /*向系统注册设备*/
    static void gpio_setup_cdev(struct gpio_dev *pDev, int index)
    {
        int err, devno = MKDEV(gpio_major, index);

        cdev_init(&pDev->cdev, &gpio_fops);
        pDev->cdev.owner = THIS_MODULE;
        pDev->cdev.ops = &gpio_fops;
        err = cdev_add(&pDev->cdev, devno, 1);
        if (err)
            printk(KERN_NOTICE "Error %d adding CDEV%d", err, index);
    }

    /*模块加载函数*/
    int gpio_init(void)
    {
        int result = -1;
        dev_t devno = MKDEV(gpio_major, 0);

        /* 申请设备号*/
        if (gpio_major)
        {
            result = register_chrdev_region(devno, 1, "gpio");
        }
        if (result < 0) // 设备号已被占用等
        {
            /* 动态申请设备号 */
            result = alloc_chrdev_region(&devno, 0, 1, "gpio");
            gpio_major = MAJOR(devno);
        }  
        if (result < 0)
        {
            printk("gpio module register devno failed!, result:%d ",result);
            return result;
        }

        /* 动态申请设备结构体的内存*/
        gpio_devp = kmalloc(sizeof(struct gpio_dev), GFP_KERNEL);
        if (!gpio_devp)    /*申请失败*/
        {
            result =  - ENOMEM;
            goto fail_malloc;
        }
        memset(gpio_devp, 0, sizeof(struct gpio_dev));

        gpio_setup_cdev(gpio_devp, 0);
        // 调用硬件层初始化
        GPIOInit(NULL, GPIO_SIZE);
        printk("gpio module installed! ");
        return 0;

    fail_malloc: unregister_chrdev_region(devno, 1);
                 return result;
    }

    /*模块卸载函数*/
    void gpio_exit(void)
    {
        if(gpio_devp)
        {
            cdev_del(&gpio_devp->cdev);   /*注销cdev*/
            kfree(gpio_devp);     /*释放设备结构体内存*/
            unregister_chrdev_region(MKDEV(gpio_major, 0), 1); /*释放设备号*/
        }
        gpio_devp = 0;
        printk(KERN_INFO "gpio module released! ");
    }


    module_init(gpio_init);
    module_exit(gpio_exit);

    MODULE_AUTHOR("HJJDEBUG");
    MODULE_LICENSE("GPL");
    --------------------------------------------------------------------------------
    // 真正的驱动,虚拟
    #include <linux/kernel.h>
    #include "gpio.h"
    /***************************************************
     * 这里是个虚拟的驱动, 所有的硬件寄存器操作全部忽略。
     ***************************************************/
    int GPIOInit(int *pAddr, int size)
    {
        printk("all gpio has inited ok! ");
        return 0;
    }
    int GPIOSetData(int addr, int data)
    {
        printk("gpio addr:%d, data:%d ", addr, data);
        return 0;
    }

    --------------------------------------------------------------------------------

    Makefile:

    ifneq ($(KERNELRELEASE),)

    #    obj-m := test.o
        obj-m := m_gpio.o
        m_gpio-y:= gpioadaptor.o gpio.o

    else
        PWD=$(shell pwd)
        KVER=$(shell uname -r)
        KDIR=/lib/modules/$(KVER)/build
    all:
        make -C $(KDIR) M=$(PWD)
    clean:
        rm *.o *.ko modules.* Module.symvers *.mod.c
    endif

    --------------------------------------------------------------------------------

    补充一个字符设备测试程序,注意open 的模式, 如果写成0(RD_ONLY), 写两个字符会出错。

    错误号为9, Bad file descriptor. 但写一个字符还是可以的
    [root@hjj]# cat test.c
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <fcntl.h>
    #include <errno.h>

    char *data="ab";
    int main(int argc, char *argv[])
    {
        int fd = open("/dev/udp",O_WRONLY); // 注意文件打开模式
        if(fd== -1)
        {
            printf("error open device. ");
            exit(1);
        }
        printf("fd:%d ",fd);
        ssize_t size=write(fd,data,strlen(data));
        if(size==-1)
        {
            printf("errno:%d string:%s ",errno,strerror(errno));
            perror("reason:");
        }
        else
        {
            printf("size:%d bytes write ",size);
        }
        close(fd);
        return 0;

    }

    一个驱动可以被多次打开,会返回不同的fd, 不同的fd, 会对应不同的filp.从而可以存储各自的数据

    这样依据fd, 就可以操作不同的数据。

  • 相关阅读:
    工作总结06
    工作总结05
    工作总结04
    站立会议01
    团队项目估算
    团队项目计划会议
    团队需求分析视频
    团队介绍
    团队项目计划会议01
    电梯演讲
  • 原文地址:https://www.cnblogs.com/oracleloyal/p/5368255.html
Copyright © 2011-2022 走看看