/****************************************************************************
 *
 *                  list_head,proc file system,GPIO,ioremap
 *
 *   声明:
 *       1. 本系列文档是在vim下编辑,请尽量是用vim来阅读,在其它编辑器下可能会
 *         不对齐,从而影响阅读.
 *       2. 本文中有些源代码没有全部帖出来,主要是因为篇幅太大的原因;
 *       3. 基于2中的原因,本文借鉴了python中的缩进代码风格进行代码的体现:
 *           1. 有些代码中的"..."代表省略了不影响阅读的代码;
 *           2. 如下代码缩进代表在一个函数内部的代码,至于在什么函数里,不影响阅读:
 *               ... //省略代码
 *               struct test_s {
 *               };
 *               ... //省略代码
 *
 *                   //进入临界区之前加锁     }
 *                   spin_lock(&p->lock);     | 
 *                                            |   |
 *                   /* 有效代码 */           |-->|采用缩进,代表在一个函数内
 *                                            |   |的代码
 *                   //出临界区之后解锁       |
 *                   spin_unlock(&p->lock);   }
 *
 *               ... //省略代码                                
 *               int __init test_init(void)
 *               {   
 *                   ... //省略代码
 *               }  
 *               ... //省略代码
 *
 *                                          2015-3-11 阴 深圳 尚观 Var 曾剑锋
 ****************************************************************************/

                    \\\\\\\\\\\--*目录*--//////////
                    |  一. list_head常用接口:     
                    |  二. proc文件系统相关操作:  
                    |  三. gpio_request相关操作:  
                    |  四. ioremap相关操作:       
                    |  五. LED驱动写法:           
                    |  六. 测试LED驱动:           
                    \\\\\\\\\\\\\\\\///////////////


一. list_head常用接口:
    1. 定义内核链表头,并初始化: 
        LIST_HEAD(test_head);
    2. 两种链表添加方式: 
        1. 头插: list_add();
        2. 尾插: list_add_tail();
    3. 两种特殊的for each循环遍历,内部使用了typeof查询类型,再加上container_of: 
        1. list_for_each_entry();
        2. list_for_each_entry_reverse();
    4. 普通的for each循环遍历: 
        list_for_each();
    5. 内核链表Demo:
        ... // "..."代表省略一些不影响分析代码的代码
        struct test_list {
            int data;
            struct list_head entry; //内核链表
        };
        
        //定义一个内核链表头,并初始化
        LIST_HEAD(test_head);
        
        struct test_list item[NUM];
        int __init test_init(void)
        {
            int i;
            struct test_list *tail;
            struct list_head *p;    
        
            for(i = 0; i < NUM; i++)
            {
                item[i].data = i;
                //头插方式
                /*list_add(&item[i].entry, &test_head);*/
                //尾插方式
                list_add_tal(&item[i].entry, &test_head);
            }
        
            //遍历链表,这两个宏里面都使用了typeof来配合container_of获取结构体指针
            /*list_for_each_entry(tail, &test_head, entry)*/
            list_for_each_entry_reverse(tail, &test_head, entry)
            {
                printk("%d ", tail->data);
            }
            printk("\n");
        
            //没有像上面那样得到结构体指针,需要自己使用container_of获取结构体指针
            list_for_each(p, &test_head)
            {
                tail = container_of(p, struct test_list, entry);
                printk("%d ", tail->data);
            }
            printk("\n");
        
            return 0;
        }
        ...

二. proc文件系统相关操作:  //这里的资料不足,需要另外参考网络资料
    1. 头文件:
        #include <linux/proc_fs.h>
        #include <linux/seq_file.h>
    2. 创建文件:      proc_create();
    3. 创建目录:      proc_mkdir();
    4. 删除文件,目录: remove_proc_entry();
    5. 访问方法,此处的资料不够,需要另外查相关资料:
        1. seq_printf();
        2. single_open();
        3. single_release();
        4. seq_read();
        5. seq_lseek();
    6. proc文件系统访问Demo:
        #include <linux/module.h>
        #include <linux/proc_fs.h>
        #include <linux/seq_file.h>
        
        static int num = 11223344;
        
        static int my_show(struct seq_file *file, void *data)
        {
            return seq_printf(file, "num is %d\n", num);
        }
        
        static int test_open(struct inode *inode, struct file *file)
        {
            return single_open(file, my_show, NULL);
        }
        
        struct file_operations fops = {
            .owner      = THIS_MODULE,
            .open       = test_open,
            .release    = single_release,
            .read       = seq_read,
            .llseek     = seq_lseek,
        };
        
        struct proc_dir_entry *pde;
        int __init test_init(void)
        {
            //在proc文件系统上创建空目录
            pde = proc_mkdir("test_dir", NULL);
            if(!pde)
                return -EFAULT;
        
            //在proc文件系统指定目录上创建文件
            /**
             * 1. "proc_test" : 创建的文件名;
             * 2. 0644        : 八进制,表示创建的文件权限;
             * 3. pde         : 前面创建文件夹的指针;
             * 4. &fops       : 文件操作指针;
             */
            proc_create("proc_test", 0644, pde, &fops);
        
            return 0;
        }
        
        void __exit test_exit(void)
        {
            //先删除proc文件系统上的目录
            remove_proc_entry("proc_test", pde);
            //删除proc文件系统上proc_test目录的文件
            remove_proc_entry("test_dir", NULL);
        }
        
        module_init(test_init);
        module_exit(test_exit);
        
        MODULE_LICENSE("GPL");

三. gpio_request相关操作:
    1. 头文件: #include <linux/gpio.h>
    2. 获取GPIO在系统中的编号: EXYNOS4X12_GPXn(nr),比如:获取GPM4的0号引脚: 
        unsigned int gpio = EXYNOS4X12_GPM4(0);
    3. 向系统申请引脚编号为gpio的引脚,并命名为"name",成功返回0,主要是为了查看引脚是否被占用: 
        int gpio_request(gpio, "name"),
    4. 将系统编号为gpio的引脚设置为输出管脚,并把管脚的值设置为data_value:
        int gpio_direction_output(gpio, data_value);
    5. 将系统编号为gpio的引脚设置为输入管脚: 
        int gpio_direction_input(gpio);
    6. 三星提供的专门设置管脚的接口: 
        int s3c_gpio_cfgpin(gpio, S3C_GPIO_SFN(config_value));
    7. 将系统编号为gpio的引脚值设为data_value,主要是针对输出管脚:
        void gpio_set_value(gpio, data_value);
    8. 获取系统编号为gpio的管脚值: 
        int gpio_get_value(gpio);
    9. 释放系统编号为gpio的管脚:   
        void gpio_free(gpio);
    10. gpio_request操作Demo大致操作:
        ...  // "..."代表省略部分代码,但不影响阅读
        struct test_s {
            struct file_operations fops;
            int major;
            unsigned int led_tbl[LEDNO];
        };
        typedef struct test_s test_t;
        struct test_s test = {
            .fops = {
                .owner      = THIS_MODULE,
                .open       = test_open,
                .release    = test_close,
                .unlocked_ioctl = test_ioctl,
            },
            .major = 0,
            .led_tbl = {
                [0] = EXYNOS4X12_GPM4(0), //申请系统对芯片引脚的gpio编号
                [1] = EXYNOS4X12_GPM4(1),
                [2] = EXYNOS4X12_GPM4(2),
                [3] = EXYNOS4X12_GPM4(3),
            },
        };
        
        ...
        
            for(i = 0; i < ARRAY_SIZE(p->led_tbl); i++)
            {
                ret = gpio_request(p->led_tbl[i], "led"); //主要是为了查看引脚是否被占用
                if(ret)
                {
                    for(--i; i >= 0; i--)  //释放之前成功申请的管脚
                    {
                        gpio_free(p->led_tbl[i]); 
                    }
                    return ret;
                }
            }
            
            //把io口配置成输出功能,输出高电平
            for(i = 0; i < ARRAY_SIZE(p->led_tbl); i++)
                gpio_direction_output(p->led_tbl[i], LED_OFF); //设置输出方向和初始化值
        
        ...
        
        /* 通过ioctl来控制灯的两灭 */
        static long test_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
        {
            int i;
            test_t *p = file->private_data;
        
            if(arg < 0 || arg > 4)
                return -EINVAL;
        
            switch(cmd)
            {
                case LED_ON:
                    if(!arg)
                    {
                        for(i = 0; i < ARRAY_SIZE(p->led_tbl); i++)
                            gpio_set_value(p->led_tbl[i], LED_ON);
                    }
                    else
                        gpio_set_value(p->led_tbl[arg - 1], LED_ON);
                    break;
                case LED_OFF:
                    if(!arg)
                    {
                        for(i = 0; i < ARRAY_SIZE(p->led_tbl); i++)
                            gpio_set_value(p->led_tbl[i], LED_OFF);
                    }
                    else
                        gpio_set_value(p->led_tbl[arg - 1], LED_OFF);
                    break;
                default:
                    return -EINVAL;
            }
        
            return 0;
        }
        ...

四. ioremap相关操作:
    1. 在内核中下面两个缩写代表的意义:
        1. pa = physical addr  物理地址
        2. va = virtual addr   虚拟地址
    2. 建立映射关系,给出实际引脚控制寄存器的物理地址,以及控制寄存器的大小size(占用字节数):
        void *va = ioremap(pa, size);
    3. 访问外设地址:
        1. 向32位寄存器中读取,写入数据:
            data = ioread32(addr);
            iowrite32(data, addr);
        2. 向16位寄存器中读取,写入数据:
            data = ioread16(addr);
            iowrite16(data, addr);
        3. 向8位寄存器中读取,写入数据:
            data = ioread8(addr);
            iowrite8(data, addr);
    4. 取消映射关系:
        iounmap(va);

五. LED驱动写法: 

    #include <linux/module.h>
    #include <linux/fs.h>
    #include <linux/gpio.h>
    #include <linux/uaccess.h>
    #include <linux/io.h>
    
    #define rGPM4CON    0x110002E0
    #define SIZE        SZ_8 //因为GPM4CON和GPM4DAT都是32位寄存器,共8字节
    
    #define GPM4CON     0  //GPM4CON相对rGPM4CON地址的偏移量
    #define GPM4DAT     4  //GPM4CON相对rGPM4CON地址的偏移量
    
    #define LED_ON      0
    #define LED_OFF     1
    
    #define DEV_NAME    "test"
    #define LEDNO       4
    
    struct test_s {
        struct file_operations fops;
        int major;
        void __iomem *reg;
    };
    typedef struct test_s test_t;
    
    static int test_open(struct inode *inode, struct file *file)
    {
        unsigned long val;
        test_t *p;
        p = container_of(file->f_op, test_t, fops);
    
        file->private_data = p;
    
        //映射外设地址,GPM4CON和GPM4DAT都是32位寄存器,共8字节
        p->reg = ioremap(rGPM4CON, SIZE);
        if(!p->reg)  //映射出错
            return -ENOMEM;
    
        //配置IO口为输出功能
        val = ioread32(p->reg + GPM4CON);
        val &= ~0xffff;
        val |= 0x1111;
        iowrite32(val, p->reg + GPM4CON);
    
        return 0;
    }
    
    static int test_close(struct inode *inode, struct file *file)
    {
        test_t *p = file->private_data;
    
        iounmap(p->reg); //取消io映射
    
        return 0;
    }
    
    static ssize_t test_read(struct file *file, char __user *buf, size_t count, loff_t *pos)
    {
        int ret, i;
        unsigned long val;
        char kbuf[LEDNO];
        test_t *p = file->private_data;
    
        if(count <= 0 || count > LEDNO)
            return -EINVAL;
    
        if(count > LEDNO - *pos)   //防止获取数据时超出数组下标范围
            count = LEDNO - *pos;
    
        val = ioread32(p->reg + GPM4DAT);
    
        for(i = 0; i < count; i++)
        {
            if(test_bit(i, &val))   //测试val中对应的位是否是1
                kbuf[i] = LED_OFF;
            else
                kbuf[i] = LED_ON;
        }
    
        ret = copy_to_user(buf, kbuf + *pos, count);
        if(ret)
            return -EFAULT;
    
        *pos += count;
    
        return count;
    }
    
    static ssize_t test_write(struct file *file, const char __user *buf, size_t count, loff_t *pos)
    {
        int ret, i;
        unsigned long val;
        char kbuf[LEDNO];
        test_t *p = file->private_data;
    
        if(count <= 0 || count > LEDNO)
            return -EINVAL;
    
        if(count > LEDNO - *pos)   //和read中的原因一致
            count = LEDNO - *pos;
    
        ret = copy_from_user(kbuf, buf, count);
        if(ret)
            return -EFAULT;
    
        val = ioread32(p->reg + GPM4DAT);
        for(i = 0; i < count; i++)
        {
            if(kbuf[i] == LED_ON)
                val &= ~(1 << (i + *pos));
            else
                val |= (1 << (i + *pos));
        }
        iowrite32(val, p->reg + GPM4DAT);
    
        *pos += count;
    
        return count;
    }
    
    static loff_t test_llseek(struct file *file, loff_t offset, int whence)
    {
        loff_t pos = file->f_pos;
    
        switch(whence)
        {
            case SEEK_SET:
                pos = offset;
                break;
            case SEEK_CUR:
                pos += offset;
                break;
            case SEEK_END:
                pos = LEDNO + offset;
                break;
            default:
                return -EINVAL; //参数非法
        }
    
        file->f_pos = pos;
    
        return pos;
    }
    
    struct test_s test = {
        .fops = {
            .owner      = THIS_MODULE,
            .open       = test_open,
            .release    = test_close,
            .read       = test_read,
            .write      = test_write,
            .llseek     = test_llseek,
        },
        .major = 0,
    };
    
    int __init test_init(void)
    {
        int ret;
    
        ret = register_chrdev(test.major,
                DEV_NAME, &test.fops);
        if(ret > 0)
        {
            test.major = ret;
            printk("major = %d\n", test.major);
            ret = 0;
        }
    
        return ret;
    }
    
    
    void __exit test_exit(void)
    {
        unregister_chrdev(test.major, DEV_NAME);
    }
    
    module_init(test_init);
    module_exit(test_exit);
    
    MODULE_LICENSE("GPL");

六. 测试LED驱动:

    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <sys/ioctl.h>
    
    #include "led.h"
    
    int main(int argc, char **argv)
    {
        int fd, ret, i, j;
        char buf[4];
    
        fd = open(argv[1], O_RDWR);
        if(-1 == fd)
        {
            perror("open");
            exit(1);
        }
    
        while(1)
        {
            lseek(fd, 0, SEEK_SET);
            memset(buf, LED_OFF, sizeof(buf));
            write(fd, buf, sizeof(buf));
            usleep(300 * 1000);
    
            lseek(fd, 0, SEEK_SET);
            for(i = 0; i < 4; i++)
            {
                buf[0] = LED_ON;
                write(fd, buf, 1);
    
                lseek(fd, 0, SEEK_SET);
                ret = read(fd, buf, sizeof(buf));
                if(-1 == ret)
                    perror("read");
    
                for(j = 0; j < sizeof(buf); j++)
                {
                    if(LED_ON == buf[j])
                        printf("led%d is on\n", j + 1);
                    else
                        printf("led%d is off\n", j + 1);
                }
    
                usleep(300 * 1000);
                lseek(fd, i + 1, SEEK_SET);
            }
        }
    
        close(fd);
        return 0;
    }