BBB GPIO驱动分析


一、 说明
IO驱动是驱动开发最基础也是很有代表性的,因为IO驱动基本包含了驱动框架中的所有常用函数,例如加载函数、卸载函数、打开函数、关闭函数、IO配置函数,此外还包括设备结构体、存储结构体。具体实现下面分析
二、 IO驱动函数分析
 设备加载函数:
    加载函数主要是实现设备驱动的注册、分配设备号等相关操作,具体实现代码如下:
static int __init omap3gpio_init(void)
{
        int ret;
        gpio_dev_num.count = 1;
        gpio_dev_num.minor_first = 0;
        ret = alloc_chrdev_region(&gpio_dev_num.devNum, gpio_dev_num.minor_first, gpio_dev_num.count, DEVICE_NAME);
        if(ret < 0)        
                return ret;
        gpio_dev_num.major = MAJOR(gpio_dev_num.devNum);
        gpio_dev_num.minor = MINOR(gpio_dev_num.devNum);
        gpio_devp = kzalloc(sizeof(struct gpio_dev),GFP_KERNEL);
        gpio_setup_cdev(gpio_devp, 0);
        printk(KERN_ERR "gpio alloc_chrdev_region success, major = %d\n", gpio_dev_num.major);
        return 0;
}


static void gpio_setup_cdev(struct gpio_dev *dev, int index)
{
        int ret,devno = gpio_dev_num.devNum + index;
        cdev_init(&dev->cdev, &gpio_fops);
        dev->cdev.owner = THIS_MODULE;
        dev->cdev.ops = &gpio_fops;
        ret = cdev_add(&dev->cdev, devno, 1);
        if(ret)
                printk(KERN_ERR "error %d : adding gpioCtl%d",ret,index);
}
函数中库函数说明:
alloc_chrdev_region(&gpio_dev_num.devNum,gpio_dev_num.minor_first,gpio_dev_num.count, DEVICE_NAME);
功能:分配设备号,与register_chrdev_region()不同的是此函数以向系统动态申请方式获得设备号,获得的设备号放入第一个参数dev中,优点是他会自动避开设备号重复的冲突
gpio_dev_num.major = MAJOR(gpio_dev_num.devNum)
功能:获得主设备号
gpio_dev_num.minor = MINOR(gpio_dev_num.devNum);
功能:获得次设备号
gpio_devp = kzalloc(sizeof(struct gpio_dev),GFP_KERNEL);
功能:内核空间内存动态申请。第一个参数为分配块大小,第二个参数为分配标志
cdev_init(&dev->cdev, &gpio_fops)
功能:初始化cdev成员,并建立cdev与file_operation之间的连接。具体使用方法如下结构:
内核中每个字符设备都对应一个 cdev 结构的变量,下面是它的定义:
/include/linux/cdev.h
struct cdev {
   struct kobject kobj;          // 每个 cdev 都是一个 kobject
   struct module *owner;       // 指向实现驱动的模块
   const struct file_operations *ops;   // 操纵这个字符设备文件的方法
   struct list_head list;       // 与 cdev 对应的字符设备文件的 inode->i_devices 的链表头
   dev_t dev;                   // 起始设备编号
   unsigned int count;       // 设备范围号大小
};
一个 cdev 一般它有两种定义初始化方式:静态的和动态的。
静态内存定义初始化:
struct cdev my_cdev;
cdev_init(&my_cdev, &fops);
my_cdev.owner = THIS_MODULE;
动态内存定义初始化:
struct cdev *my_cdev = cdev_alloc();
my_cdev->ops = &fops;
my_cdev->owner = THIS_MODULE;
两种使用方式的功能是一样的,只是使用的内存区不一样,一般视实际的数据结构需求而定。
ret = cdev_add(&dev->cdev, devno, 1)
功能:向系统注册设备。先申请设备号在注册设备
    
加载函数的流程为:
 动态申请设备号  获得主次设备号  分配内存   cdev初始化    设备注册






卸载函数
static void __exit omap3gpio_exit(void)
{
        cdev_del(&gpio_devp->cdev);
        kfree(gpio_devp);
        unregister_chrdev_region(gpio_dev_num.devNum, 1);
        printk(KERN_ERR "gpio unregister_chrdev_region success, major = %d\n", gpio_dev_num.major);
}
卸载函数执行与加载函数相反的操作,具体如下:
cdev_del(&gpio_devp->cdev)
功能:向系统删除一个cdev设备
kfree(gpio_devp)
功能:释放申请的内存
unregister_chrdev_region(gpio_dev_num.devNum, 1)
功能:释放之前申请的设备号




打开函数
static int gpio_open(struct inode *inode, struct file *filp)
{
        struct gpio_dev *dev;
        dev = container_of(inode->i_cdev, struct gpio_dev, cdev);
        filp->private_data = dev;
        dev->dptr = NULL;
        dev->size = 0;
        //printk(KERN_ERR "gpio_open success!\n");
        return 0;
}
分析: 由container_of获得结构体指针inode中结构体cdev的指针,让结构体指针dev指向上述的指针所指的地址,再让file->private指向这一地址


关闭函数
static int gpio_close(struct inode *inode, struct file *filp)
{
        struct gpio_dev *dev = filp->private_data;
        struct gpio_qset *qset, *qsetTmp;
        qsetTmp = (struct gpio_qset *)kzalloc(sizeof(struct gpio_qset), GFP_KERNEL);
        for(qset = dev->dptr; qset->next !=NULL; qset=qsetTmp)
        {
                qsetTmp = qset->next;
                gpio_free(qset->port);        //释放申请的端口
                kfree(qset);                //释放gpio_qset内存
        }
        gpio_free(qsetTmp->port);
        kfree(qsetTmp);
         //printk(KERN_ERR "gpio release!\n");
        return 0;

}


IO控制函数
static long gpio_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
        int ret;
        struct gpio_dev *dev = filp->private_data;
        struct gpio_qset *qset;
        //cmd == 2 设计成为释放arg端口的功能,之后申请内存的任务便不必执行了,所以直接返回
        if(cmd == 2)
        {
                gpio_free(arg);
                return arg;
        }
        dev->size++;
        if(dev->dptr == NULL)
        {
                dev->dptr = (struct gpio_qset *)kzalloc(sizeof(struct gpio_qset), GFP_KERNEL);
                qset = dev->dptr;
        }
        else
        {
                for(qset = dev->dptr; qset->next != NULL; qset = qset->next); //找到链表最后一项
                qset->next = (struct gpio_qset *)kzalloc(sizeof(struct gpio_qset), GFP_KERNEL);
                qset = qset->next;
        }
        /*链表数据*/
        qset->num = dev->size;        //确定自己的编号
        qset->ddr = cmd;        //确定方向
        qset->port = arg;        //确定端口号
        qset->next = NULL;        //最后一项地址清空
        //printk(KERN_ERR "qset->num=%d,qset->ddr=%d,qset->port=%d\n", qset->num, qset->ddr, qset->port);
        sprintf(qset->label, "gpio%ld", qset->port); //确定申请的GPIO使用名称(和端口直接相关)
        ret = gpio_request(qset->port, qset->label);      //申请端口
        /*由于gpio_requset会自己判断成功与否并且退出函数,故注释掉对ret的判断
        if(ret < 0)
        printk(KERN_ERR "%s_requset failled!%d \n", qset->label, ret);
        */
        /*判断GPIO工作方向(输出或输出)*/        
        switch(qset->ddr)
        {
                case 0:  ret = gpio_direction_input(qset->port);
                        if(ret < 0)  printk(KERN_ERR "gpio_direction_input failled!\n");
                        break;
                case 1:   ret = gpio_direction_output(qset->port, 1);
                        if(ret < 0)  printk(KERN_ERR "gpio_direction_output failled!\n");
                        break;
                default:
                        return -EPERM;        /* Operation not permitted */
        }
        return qset->num;
}
分析:功能:申请新的gpio_qset的内存,确定相应的GPIO端口功能
* cmd:实现的功能,
 *     0:读,
 *     1:写,
 *     2:释放GPIO端口
 * arg:执行操作的GPIO端口
 * 描述:用户空间调用ioctl时运行
 *         long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
 * 返回值:成功返回操作的GPIO端口号
 *           错误返回相应的错误码
应用层使用实例:ioctl(fd, 1, GPIO_TO_PIN(1,22));
ret = gpio_request(qset->port, qset->label);      
功能:申请端口
参数:qset->port 对应申请的哪个管脚,这个对应GPIO_TO_PIN(1,22) , 
此引脚含义是GPIO1_22  对应1*32+22=64引脚号
       qset->label 此变量为管脚围棋取的名字。
ret = gpio_direction_input(qset->port);
功能:设定IO口方向 ,参数对应GPIO_TO_PIN(1,22) , 


IO读取函数
static ssize_t gpio_read (struct file *filp, char __user *readBuf, size_t port, loff_t *offp)
{
        long ret;
        struct gpio_dev *dev = filp->private_data;
        struct gpio_qset *qset;
        if(dev->dptr == NULL)        
                return -ENODEV;                /* No such device */
        for(qset = dev->dptr; qset != NULL; qset = qset->next)
        {
                if(qset->port == port)
                        break;
                if(qset->next == NULL)        
                        return -ENODEV;        /* No such device */
        }
        if(qset->ddr != 0)                //判断是否ioctl设置为读操作
    return -EPERM;                /* Operation not permitted */
        qset->value = gpio_get_value(qset->port);
        //printk(KERN_ERR "qset->port:%d, qset->value:%d\n", qset->port, qset->value);
        switch(qset->value)
        {
                case 0:        ret = copy_to_user(readBuf, "0", 1);
                        break;
                case 1:        ret = copy_to_user(readBuf, "1", 1);
                        break;
        }
        return qset->value;
}
此函数主要实现把获取应用层端口号参数,将IO口实际状态传递到用户层显示
gpio_get_value(qset->port);
功能:获取port引脚的状态
ret = copy_to_user(readBuf, "0", 1);
功能:将内核层的数值0传给用户层的变量readbuf,

之所以这步是因为只有用户态数据能传给应用程序,才能被应用


IO写操作函数


static ssize_t gpio_write (struct file *filp, const char __user *writeBuf, size_t port, loff_t *offp)
{
        long ret;
        struct gpio_dev *dev = filp->private_data;
        struct gpio_qset *qset;
        if(dev->dptr == NULL)        
                return -ENODEV;                /* No such device */
        for(qset = dev->dptr; qset != NULL; qset = qset->next)
        {
        //        printk(KERN_ERR "qset->port=%d,port=%d\n", qset->port, port);
                if(qset->port == port)
                        break;
                if(qset->next == NULL)        
                        return -ENODEV;        /* No such device */
        }
        if(qset->ddr != 1)                //判断是否ioctl设置为写操作
                return -EPERM;                /* Operation not permitted */ 
        ret = copy_from_user(&qset->value, writeBuf, 1);
        //printk(KERN_ERR "write:%d\n", qset->value);
        switch(qset->value)
        {
                case '0': qset->value = 0;
             gpio_set_value(qset->port, 0);
                          break;
                default : qset->value = 1;
                          gpio_set_value(qset->port, 1);
                          break;
        }
        return qset->value;
}


此函数用于对IO口赋值
ret = copy_from_user(&qset->value, writeBuf, 1);
功能:将应用程序的参数转为内核态参数,以便下面的对IO口赋值
gpio_set_value(qset->port, 0);
功能:设置IO口port端口的值为0


三、 应用程序测试
  完整测试程序如下:
#include
#include
#include
#include
#include
#include
#include
#include
 
#define GPIO_TO_PIN(bank, gpio) (32 * (bank) + (gpio))
 
int main(int argc, char * argv)
{
        int i, n, fd;
        char num;
        int ret;
        fd = open("/dev/gpioCtl", O_RDWR);                //打开设备
        if (fd < 0)
        {
            printf("can't open /dev/gpioCtl!\n");
            exit(1);
        }
        sleep(1);
        ioctl(fd, 1, GPIO_TO_PIN(1,22));                //设置gpio1-22为输出(user:led3)
        ioctl(fd, 0, GPIO_TO_PIN(2, 1));                //设置gpio2-1 为输入(p8-18)
        while (1) 
        {
                num = 1;
                ret = write(fd,"1",GPIO_TO_PIN(1,22));
printf("on");
                if(ret < 0)
                {
                        perror("write");
                        return -1;
                }
                sleep(1);
                ret = write(fd,"0",GPIO_TO_PIN(1,22));
printf("off");
                if(ret < 0)
                {
                    perror("write");
                    return -1;
}
sleep(1);
}
}


分析:
 程序定义了GPIO1_22为输出,GPIO2_1为输入,通过write函数实现数据写入,即对IO口赋值,通过read函数实现获取此时相应GPIO端口的状态(0或1)。


注:
GPIO驱动加在成功后,会有/dev/gpioCtl这个设备文件产生,驱动是作为一个文件在系统在存在的,如若对设备进行操作,首先打开设备文件,之后进行相应读写等操作
GPIO端口与GPIO号是有对应关系的,在此程序中,例如标注GPIO2_1端口对应的设备号为:2*38+1,
GPIO端口作为中断口时也要将端口号转换为相应的中断号

你可能感兴趣的:(Linux)