物聯網之內核及驅動開發初級三(字符設備驅動開發)

Linux驅動開發之字符設備驅動

1,字符設備驅動框架
    作爲字符設備驅動要素:
        1,必須有一個設備號,用在衆多的設備驅動中進行區分
        2,用戶必須知道設備驅動對應的設備節點(設備文件)
            linux把所有的設備都看成文件

            crw-r----- 1 root root 13, 64 Mar 28 20:14 event0
            crw-r----- 1 root root 13, 65 Mar 28 20:14 event1
            crw-r----- 1 root root 13, 66 Mar 28 20:14 event2
        3,對設備操作其實就是對文件操作,應用空間操作open,read,write的時候
            實際在驅動代碼有對應到open, read,write
    

2,作爲驅動必須有一個主設備號--向系統申請
    
    int register_chrdev(unsigned int major, const char * name, const struct file_operations * fops)//申請設備號
    參數1:主設備號
            設備號(32bit--dev_t)==主設備號(12bit) + 次設備號(20bit)
                主設備號:表示一類設備--camera
                次設備號: 表示一類設備中某一個:前置,後置

            給定的方式有兩種:
                1,動態--參數1直接填0
                2,靜態--指定一個整數,250


    參數2: 描述一個設備信息,可以自定義
            /proc/devices列舉出所有到已經註冊的設備


    參數3: 文件操作對象--提供open, read,write
    返回值: 正確返回0,錯誤返回負數

    void unregister_chrdev(unsigned int major, const char * name)//刪除設備號
    參數1:主設備號
    參數2: 描述一個設備信息,可以自定義


3,創建設備節點:
    1,手動創建--缺點/dev/目錄中文件都是在內存中,斷電後/dev/文件就會消失
        
        mknod /dev/設備名  類型  主設備號 次設備號
        比如:
            mknod  /dev/chr0  c  250 0


        [root@farsight drv_module]# ls /dev/chr0 -l
        crw-r--r--    1 0        0         250,   0 Jan  1 00:33 /dev/chr0

    2,自動創建(通過udev/mdev機制)
        struct class *class_create(owner, name)//創建一個類
            參數1: THIS_MODULE
            參數2: 字符串名字,自定義
            返回一個class指針

        //創建一個設備文件
        struct device *device_create(struct class * class, struct device * parent, dev_t devt, 
                            void * drvdata, const char * fmt,...)
        參數1: class結構體,class_create調用之後的返回值
        參數2:表示父親,一般直接填NULL
        參數3: 設備號類型 dev_t
                dev_t devt
                    #define MAJOR(dev)    ((unsigned int) ((dev) >> MINORBITS))
                    #define MINOR(dev)    ((unsigned int) ((dev) & MINORMASK))
                    #define MKDEV(ma,mi)    (((ma) << MINORBITS) | (mi))
        參數4:私有數據,一般直接填NULL
        參數5和6:表示可變參數,字符串,表示設備節點名字(應用程序中調用字符設備時使用該節點的名字,如:
                                                                                                     fd = open("/dev/chr2", O_RDWR);

static unsigned int dev_major = 250;//主設備號
devcls = class_create(THIS_MODULE, "chr_cls");
dev = device_create(devcls, NULL, MKDEV(dev_major,0), NULL, "chr2");

        銷燬動作:
            void device_destroy(devcls,  MKDEV(dev_major, 0));
            參數1: class結構體,class_create調用之後到返回值
            參數2: 設備號類型 dev_t

            void class_destroy(devcls);
            參數1: class結構體,class_create調用之後到返回值

        
4,在驅動中實現文件io的接口,應用程序可以調用文件io
    a,驅動中實現文件io操作接口:struct file_operations
        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 (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
        ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
        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 (*open) (struct inode *, struct file *);
        int (*flush) (struct file *, fl_owner_t id);
        int (*release) (struct inode *, struct file *);
        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 **);
        long (*fallocate)(struct file *file, int mode, loff_t offset,
                  loff_t len);
        int (*show_fdinfo)(struct seq_file *m, struct file *f);
        }; //函數指針的集合,其實就是接口,我們寫驅動到時候需要去實現

        const struct file_operations my_fops = {
                .open = chr_drv_open,
                .read = chr_drv_read,
                .write = chr_drv_write,
                .release = chr_drv_close,
        };


    b,應用程序如何去調用文件io去控制驅動--open,read,...
        
            fd = open("/dev/chr2", O_RDWR);
            if(fd < 0)
            {
                perror("open");
                exit(1);
            }

            read(fd, &value, 4);

            write(fd, &value, 4);


            close(fd);


5, 應用程序需要傳遞數據給驅動
    int copy_to_user(void __user * to, const void * from, unsigned long n)
    //將數據從內核空間拷貝到用戶空間,一般是在驅動中chr_drv_read()用
    參數1:應用驅動中的一個buffer
    參數2:內核空間的一個buffer
    參數3:個數
    返回值:大於0,表示出錯,剩下多少個沒有拷貝成功
            等於0,表示正確


    int copy_from_user(void * to, const void __user * from, unsigned long n)
    //將數據從用戶空間拷貝到內核空間,一般是在驅動中chr_drv_write()用
    參數1:內核驅動中的一個buffer
    參數2:應用空間到一個buffer
    參數3:個數

6, 控制外設,其實就是控制地址,內核驅動中是通過虛擬地址操作
    void *ioremap(cookie, size)
    參數1: 物理地址
    參數2: 長度
    返回值: 虛擬地址

    去映射--解除映射
        void iounmap(void __iomem *addr)
        參數1: 映射之後到虛擬地址

7, 通過驅動控制led燈:
        led--- GPX2_7 --- GPX2CON ==0x11000C40
                          GPX2DAT ==0x11000C44

        將0x11000C40映射成虛擬地址
        對虛擬地址中的[32:28] = 0x1

8, 應用程序和驅動扮演的是什麼角色

    用戶態:應用程序
            玩策略: 怎麼去做
                    1, 一閃一閃
                    2,10s閃一次,也可以1s閃一次
                    3,一直亮
                    4,跑馬燈
            控制權是在應用程序(程序員)
    --------------------------------------
    內核態:驅動
            玩機制: 能做什麼 
                    led:亮 和 滅


9,編寫字符設備驅動到步驟和規範
    步驟:
        1,實現模塊加載和卸載入口函數
                module_init(chr_dev_init);
                module_exit(chr_dev_exit);
        
        2,在模塊加載入口函數中
            a, 申請主設備號  (內核中用於區分和管理不同字符設備)
                     register_chrdev(dev_major, "chr_dev_test", &my_fops);

            b,創建設備節點文件 (爲用戶提供一個可操作的文件接口--open())
                    struct  class *class_create(THIS_MODULE, "chr_cls");
                    struct  device *device_create(devcls, NULL, MKDEV(dev_major, 0), NULL, "chr2");

            c, 硬件的初始化
                   1,地址的映射
                         #define GPX1_CON 0x11000c20
                         #define GPX1_SIZE 8    //8個字節,表示0x11000c20後面連續的8個字節

                        gpx2conf = ioremap(GPX2_CON, GPX2_SIZE);
                   2,中斷的申請
                   3,實現硬件的寄存器的初始化
                        // 需要配置gpio功能爲輸出
                        *gpx2conf &= ~(0xf<<28);
                        *gpx2conf |= (0x1<<28);
            e,實現file_operations
                    const struct file_operations my_fops = {
                            .open = chr_drv_open,
                            .read = chr_drv_read,
                            .write = chr_drv_write,
                            .release = chr_drv_close,
                    };


    規範:
        1,面向對象編程思想
            用一個結構體來表示一個對象

            //設計一個類型,描述一個設備的信息
            struct led_desc{
                unsigned int dev_major; //設備號
                struct class *cls;
                struct device *dev; //創建設備文件
                void *reg_virt_base;
            };

            struct led_desc *led_dev;//表示一個全局的設備對象

            
            // 0, 實例化全局的設備對象--分配空間
            //  GFP_KERNEL 如果當前內存不夠用到時候,該函數會一直阻塞(休眠)
            //  #include <linux/slab.h>
            led_dev = kmalloc(sizeof(struct led_desc), GFP_KERNEL);
            if(led_dev == NULL)
            {
                printk(KERN_ERR "malloc error\n");
                return -ENOMEM;
            }

            led_dev->dev_major = 250;
    
        2,做出錯處理
                在某個位置出錯了,要將之前申請的資源進行釋放
        
            led_dev = kmalloc(sizeof(struct led_desc), GFP_KERNEL);    
        
            led_dev->dev_major = register_chrdev(0, "led_dev_test", &my_fops);
            if(led_dev->dev_major < 0)
            {
                printk(KERN_ERR "register_chrdev error\n");
                ret = -ENODEV;
                goto err_0;
            }


            err_0:
                kfree(led_dev);
                return ret;


10, 操作寄存器地址的方式:
    1, volatile unsigned long *gpxcon;
        *gpxcon &= ~(0xf<<28);
    
    2, readl/writel();
        u32 readl(const volatile void __iomem *addr)//從地址中讀取地址空間的值

        void writel(unsigned long value , const volatile void __iomem *add)
            // 將value的值寫入到addr地址
    
        例子:
            // gpio的輸出功能的配置
            u32 value = readl(led_dev->reg_virt_base);
            value &= ~(0xf<<28);
            value |= (0x1<<28)
            writel(value, led_dev->reg_virt_bas);    

        或者:
                *gpx2dat |= (1<<7);
            替換成:
                writel( readl(led_dev->reg_virt_base + 4) | (1<<7),   led_dev->reg_virt_base + 4 );

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章