Linux驅動——學習筆記

一、燒寫Linux系統到inand

1、燒寫u-boot到inand
    tftp 30008000 u-boot.bin
    movi write u-boot 30008000
2、燒寫Linux內核到inand
    tftp 30008000 zImage-qt
    movi write kernal 30008000
3、燒寫文件系統到inand
    開發板中已經有文件系統,無須再燒,設置啓動參數即可。
4、設置啓動環境變量
setenv bootcmd "movi read kernel 30008000;bootm 30008000"
setenv bootargs "console=ttySAC2,115200 root=/dev/mmcblk0p2 rw init=/linuxrc rootfstype=ext3"

二、掛載ubuntu系統的文件夾到開發板

目的:是爲了把編譯好驅動程序下載到共享目下。
1、進入開發板系統 root 123456
2、檢查開板的ip地址
3、在啓動腳本添加以下內容:vi /etc/init.d/rcS
    mount -t nfs -o nolock 192.168.1.66:/nfs /mnt

三、Linux內核下的C語言編程

1、命名習慣
    普通代碼:
        #define PI 3.14159
        int minVal,maxVal;
        void sendData(void);
    內核代碼:
        #define PI 3.14159
        int min_val,max_val;
        void send_data(void);

2、GNU C與ANSI C
    GNU編譯器是專門爲Linux內核所設計的一款編譯器,而且Linux內核中也使用大量只有GNU編譯才支持的C語言語法,因此只有GNU編譯器才能編譯內核代碼和Linux驅動程序。
    a、零長數組
        typedef struct {
            int len;
            char data[0];
        }Data;

        Data* dp = malloc(sizeof(Data)+1024);
        dp->len = 1024;

        send(sockfd,dp,sizeof(dp->len+sizeof(*dp)),0);
    b、case範圍
        switch(n)
        {
            case 1 ... 5: break;
            case 6 ... 9: break;
        }
    c、typeof
        定義一個宏求兩個變量的最大值。
        #define MIN(a,b) ((a)>(b)?(a):(b))
        #define MIN(type,a,b) ({type _a=num1++;type _b=num2++;_a>_b?_a:_b;})

        MIN(int,num1++,num2++) => num1++ > num2++ ? num1++ : num2++;
        #define MIN(a,b) ({typeof(a) _a=a; typeof(b) _b=b; _a>_b?_a:_b;})

        定義一個宏交換兩個變量值。
        #define swap(a,b) ({typeof(a) t=a; a=b; b =t;})
        
    d、可變參長數宏
        int printf(const char *format, ...);
        #define debug(ftm,arg,...) printf(ftm,##arg)
    e、標號元素
        ANSI C
        struct Student stu = {
            name:"hehe",
            sex:'m',
            age:18
        };

        GNU C
        struct Student stu = {
            .name = "hehe",
            .sex = 'm',
            .age = 18
        };

3、do{ } while(0)
    定義一個宏函數,用來釋放堆內存。
    #define FREE_HEAP(p) do{free(p); p = NULL;}while(0)

    int func(void)
    {
        if(NULL != p)
            FREE_HEAP(p)
        else
            return -1;
    }

4、goto語句
    一般的企業代碼中禁止使用goto語句,因爲goto可以跳轉到函數內的任意位置,會打破原有分支、循環結構、或業務邏輯,所以goto是應用層代碼中是一種非常危險的語句,但在內核中卻有它獨到的功能。
    int example(void)
    {
        if(!register_a())
        {
            goto err1;
        }
    
        if(!register_b())
        {
            goto err2:
        }

        if(!register_c())
        {
            goto err3:
        }

        // work

    err3:
        unregister_c();
    err2:
        unregister_b();
    err1:
        unretister_a();
    err:
        return ret;
    }
    在內核中,資源的申請與釋放必須是逆序,而goto語句能夠把錯誤處理的代碼寫的安全、嚴謹而有簡潔。

四、內核模塊

通過分析Linux內核的編譯過程,瞭解到一個實事:Linux內核是由很多模塊組成了。
1、內核以模塊的形式組成的好處
    a、可以靈活的進行裁剪
    b、可以動態加載或卸載
    c、可以儘量控制內核的大小
2、內核模塊的特點
    a、內核模塊代碼最終是要加入內核中的
    b、內核模塊代碼工作在內核態。
    c、內核模塊代碼一旦出錯,可能會導致整個內核(操作)崩潰。
    d、在Linux系統中驅動程序是一個內核模塊,它按照內核模塊的格式進行編寫。
3、內核模塊的結構
    a、模塊加載函數(必須)
        模塊加載內核時自動執行此函數,相當於內核模塊的入口函數。
        此函數一般要做些初始化工作、申請內存、資源,註冊相關信息。
    b、模塊卸載函數(必須)
        模塊被卸載時自動執行。
        此函數一般要做些資源的釋放、銷燬的工作。
    c、模塊聲明許可證(必須)
    d、模塊作者信息聲明(可選)
    e、其它一些自定義函數,可以被模塊加載函數調用。
4、內核模塊的加載與卸載
    insmod hello.ko
    rmmod hello.ko

五、printfk函數

printfk是在內核中運行的向控制檯輸出顯示信息的函數,與printf函數的用法基本一致。
內核中的消息是分等級的,定義在include/linux/kernal.h
    #define	KERN_EMERG	"<0>"	/* system is unusable			*/
    #define	KERN_ALERT	"<1>"	/* action must be taken immediately	*/
    #define	KERN_CRIT	"<2>"	/* critical conditions			*/
    #define	KERN_ERR	"<3>"	/* error conditions			*/
    #define	KERN_WARNING	"<4>"	/* warning conditions			*/
    #define	KERN_NOTICE	"<5>"	/* normal but significant condition	*/
    #define	KERN_INFO	"<6>"	/* informational			*/
    #define	KERN_DEBUG	"<7>"	/* debug-level messages			*/
    printk 默認的等級是 KERN_NOTICE
在操作系統中可以設置顯示消息等級,數字越大,級別超低。
    cat  /proc/sys/kernel/printk 可以看當前系統設置的消息等級
    echo "5" > /proc/sys/kernel/printk

一、模塊參數

在加載模塊時可以像應用程序一樣附加一些參數。
定義變量:用來存儲數據。
註冊變量:讓內核允許加載模塊時變量被賦值。
    module_param(變量名,類型,權限);
    類型:
        byte、short、ushort、int、uint、long、ulong、charp
    權限:
        #define S_IRUSR 00400
        #define S_IWUSR 00200
        #define S_IXUSR 00100
傳遞參數:insmod xxx.ko 變量名=數據
模塊導出符號:
    EXPORT_SYMBOL_GPL(符號);
    導出的符號(變量名、函數名)可以被其他模塊使用,使用前聲明一下即可。

二、驅動概述

什麼是驅動:就是控制硬件工作的軟件。
在Linux系統下,驅動程序工作在內核中,以內核模塊形式存在,它能能夠控制硬件工作,或者提供控制硬件的接口。
驅動程序提供接口歸內核調用,然後內核再統一抽象成文件(設備文件),代應用的程序以操作文件式進行控制硬件。
應用程序工作在用戶態,驅動程序工作在內核態,它們之間不能直接聯繫,需要按照內核提供的接口(open/read/write/close)來傳遞數據。
在Linux系統下一切皆文件。

三、驅動的類型

字符設備:在IO過程中以字符或字節爲單位進行數據傳輸,數據也要按照一定的順序進行讀寫。
    操作這種設備的接口有:open/close/read/write
    常見的字符設備有:鼠標、鍵盤、串口、LED燈、溫度傳感器、溼度傳感器等。
    字符設備由於功能雜亂,設備也混亂,一般都是小廠家生產的沒有統一的接口,所以大多數據需要編寫驅動的都是字符設備。
塊設備:以塊爲單位進行數據的讀寫,用記在讀寫數據時必須最少讀一塊,這種設備一般都帶緩衝區,而且數據量一般都很大,數據在讀寫時一般不會立即到達硬件。
    操作這種設備的接口有:open/close/read/write/sync/fsync/fdatasync。
    常見的塊設備有:硬盤、SD卡、Nand、iNand。
    雖然存儲介質不同,但都有一個控制器不需要外部直接操作,只需要按照控制器的接口來操作即可。
網絡設備:具有網絡通信協議的設備,網絡設備沒有設備文件,必須使用socket進行操作。
    操作這種設備的接口有:send/recv/sendto/recvfrom。
    網絡設備都非常標準的、規範,所以有了萬能的網卡驅動,頂多需要移植一下,也不需要編寫驅動。

四、設備文件

在Linux系統會把硬件抽象成文件來製作(網上除外)。
查看設備文件:ls -l /dev
每個字母表示文件的類型:
    -   普通文件
    d   目錄文件
    l   鏈接文件
    c   字符設備文件
    b   塊設備文件
    p   管道文件
    s   socket文件
設備號:主設備號+從設備號組成
    主設備號:表示硬件的類型,1~254
    從設備號:硬件的編號,0~255
    它是在驅動程序中確定的,可以讓內核自動生成,也可以手動確定,或者手動創建。
    注意:設備不能重複。

五、字符設備驅動

1、字符設備驅動分析
    drivers\char\Cs5535_gpio.c
    a、按照內核模塊格式編寫
    b、創建設備號、初始化、添加
    c、實現文件操作函數
2、設備號
    dev_t dev_id = MKDEV(主設備號,從設備號);
    功能:生成一個設備號
    返回值:主設備號+從設備號的合體
    注意:不一定能使用。

    int register_chrdev_region(dev_t from, unsigned count, const char *name)
    功能:註冊希望使用的設備號,如果已經被佔用則失敗。
    from:希望使用的設備號,MKDEV的返回值
    count:設備的數量
    name:設備文件名
    返回值:成功返回0

    int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name)
    dev:內核分配的設備號,返回值
    baseminor:設備號的起始值
    count:設備的數量
    name:設備名
    返回值:成功返回0

    MAJOR(dev_id) 解析出主設備號
    MINOR(dev_id) 解析出從設備號

    void unregister_chrdev_region(dev_t from, unsigned count)
    功能:釋放設備號
    from:設備號
    count:數量
3、字符設備結構體
    struct cdev {
        struct kobject kobj;  // 內嵌的對象
        struct module *owner; // 模塊名
        const struct file_operations *ops; // 文件操作結構體(函數指針)
        struct list_head list;
        dev_t dev; // 設備號
        unsigned int count; // 設備數量
    };

    void cdev_init(struct cdev *cdev, const struct file_operations *fops);
    功能:初始化字符設備結構體
    cdev:字符設備結構體
    fops:文件操作結構體

    int cdev_add(struct cdev *p, dev_t dev, unsigned count)
    功能:添加字符設備到內核
    p:被初始化過的字符設備結構體
    dev:註冊過的設備號
    count:設備數量

    struct file_operations {
        // 模塊名
        struct module *owner; 
        // 設備文件位置指針
        loff_t (*llseek) (struct file *, loff_t, int);
        // read函數
        ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
        // write
        ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
        // poll
        unsigned int (*poll) (struct file *, struct poll_table_struct *);
        // ioctl
        int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
        // mmap
        int (*mmap) (struct file *, struct vm_area_struct *);
        // open
        int (*open) (struct inode *, struct file 
        // flush
        int (*flush) (struct file *, fl_owner_t id);
        // close
        int (*release) (struct inode *, struct file *);
    }

六、設備文件

1、手動創建
    mknod 創建設備文件的命令。
    cat /proc/devices 查看內核分配的主設備號
    mknod /dev/char_v1 c 250 0

2、自動創建
內核提供一組函數用於自動創建設備文件,設備類對動對應的數據結構:struct class
    class_create->*__class_create
    struct class *__class_create(struct module *owner, const char *name,struct lock_class_key *key)
    功能:在內核中創建設備類 /sys/class/xxx
    owner:模塊名
    key:設備類的名字
    返回值:設備類指針

    struct device *device_create(struct class *class, struct device *parent,dev_t devt, void *drvdata, const char *fmt, ...)
    功能:/sys/class/xxx/在dev目錄下自動創建設備文件
    class:設備類指針,class_create函數的返回值
    parent:設備結構體指針,兼容各種類型的設備,此處填寫字符設備結構指針。
    devt:設備號
    drvdata:創建設備文件時的附加信息
    fmt:設備文件名

void device_destroy(struct class *class, dev_t devt)
功能:刪除設備
class:設備類指針,class_create函數的返回值
devt:設備號

void class_destroy(struct class *cls)
功能:從內核中刪除設備類

一、驅動程序中操作GPIO

在內核中已經把各個GPIO定義爲宏,不需要再找地址了,在頭文件 arch/arm/mach-sv5pv210/include/mach/gpio.h。
int gpio_request(unsigned gpio, const char *label)
功能:申請GPIO資源
gpio:GPIO管腳的編號,在gpio.h頭文件中有定義
label:給GPIO管腳取個名字

void gpio_free(unsigned gpio)
功能:釋放GPIO資源

int gpio_direction_input(unsigned gpio)
功能:設置GPIO管腳爲輸入模式

int gpio_direction_output(unsigned gpio, int value)
功能:設置GPIO管腳爲輸出模式

int gpio_get_value(unsigned gpio)
功能:從GPIO管腳讀取數據

void gpio_set_value(unsigned gpio, int value)
功能:向GPIO管腳寫入數據

二、ioctl

int ioctl(int d, int request, ...);
功能:從應用層來調用內核層的ioctl函數,對設備進行設置。
d:文件描述符,open函數的返回值
request:命令
...:附加參數,存儲在用戶層,以指針的方式交給內核層。
返回值:成功返回0,失敗返回-1。

int (*ioctl) (struct inode *, struct file *, unsigned int cmd, unsigned long arg);
功能:響應應用層的ioctl函數,對硬件進行設置。
cmd:對應應用層request
arg:實際上是一個指針,它第指向用戶層的一段數據。

三、內核的內存管理

unsigned long __must_check copy_from_user(void *to, const void __user *from, unsigned long n)
功能:從用戶層拷貝數據到內核層
to:指向內核層空間的指針
from:指向用戶層空間的指針
n:要拷貝的字節數據
返回值:成功拷貝的字節數

long copy_to_user(void __user *to,const void *from, unsigned long n)
功能:從內核層拷貝數據到用戶層
to:指向用戶層空間的指針
from:指向內核層空間的指針
n:要拷貝的字節數據
返回值:成功拷貝的字節數

void *kmalloc(int size)
功能:與標準C中的用法一致,其實就是調用malloc,kmalloc只是一個宏名。

void kfree(void *where)
功能:釋放內存
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章