Linux驅動

1 驅動分類node

  常規分類:字符設備、塊設備、網絡設備linux

    字符設備:一種按字節來訪問的設備,字符驅動負責驅動字符設備,這樣的驅動一般實現open、close、read和write系統調用。如串口、LED、按鍵;數組

    塊設備:以塊(通常爲512字節)爲最小傳輸單位的設備,塊設備不能按字節處理數據。在Linux系統中運行塊設備傳輸任意數目的字節。塊設備與字符設備的區別是驅動與內核的接口不一樣。如硬盤、flash、SD卡。網絡

    網絡設備:能夠是一個硬件設備,如網卡;也能夠是一個純粹的軟件設備,如迴環接口(lo)。一個網絡接口負責發送和接收數據報文。函數

  總線分類:USB設備、PCI設備、平臺總線設備ui

2 硬件訪問編碼

  

  驅動程序要控制設備是經過設備內寄存器控制的。spa

  硬件訪問步驟:->地址映射->寄存器讀寫.net

  在Linux系統中,不管是內核程序仍是應用程序,都只能使用虛擬地址,而芯片手冊中給出的寄存器地址或者RAM地址則是物理地址,沒法直接使用。所以,讀寫寄存器的第1步就是將它的物理地址映射爲虛擬地址。3d

  地址映射包括動態映射和物理映射;

  動態映射:在驅動程序中採用ioremap()函數將物理地址映射爲虛擬地址:

    函數原型: void *ioremap(physaddr,size)

         physaddr:待映射的物理地址

         size:映射的區域長度

          返回值:映射後的虛擬地址

    靜態映射:根據用戶事先指定的映射關係,在內核啓動時,自動將物理地址映射爲虛擬地址。

    用戶是經過map_desc結構體來指明物理地址與虛擬地址的映射關係

      struct map_desc{
               unsigned long virtual; /* 映射後的虛擬地址 */
               unsigned long pfn; /* 物理地址所在的頁幀號 */
               unsigned long length; /* 映射長度 */
               unsigned int type; /* 映射的設備類型 */
             };
      pfn: 利用__phys_to_pfn(物理地址)能夠計算出物理地址所在的物理頁幀號

  內核寄存器讀寫函數:

unsigned ioread8(void *addr0)
unsigned ioread16(void *addr0)
unsigned ioread32(void *addr0)

unsigned readb(address)
unsigned readw(address)
unsigned readl(address)

void iowrite8(u8 value,void *addr)
void iowrite16(u16 value,void *addr)
void iowrite32(u32 value,void *addr)

void writeb(unsigned value,address)
void writew(unsigned value,address)
void writel(unsigned value,address)

3 字符設備文件

  字符設備驅動程序是經過字符設備文件被用戶調用。

  

  經過字符設備文件,應用程序可使用相應的字符設備驅動程序來控制字符設備。

  應用程序首先經過文件名找到字符設備文件,假如要從設備中讀出或者寫入數據都是從字符設備文件展開的,字符設備文件是設備驅動程序和應用程序的一個媒介,應用程序對設備的操做是經過字符設備文件來完成的。

  建立字符設備文件:

    mknod命令

    mknod /dev/文件名 c 主設備號 次設備號          //c表示char

   例:mknod /dev/memdev0 c 253 0

    字符設備文件與驅動程序經過主設備號創建起聯繫,字符設備文件對應一個主設備號,驅動程序對應一個主設備號,若是兩個號相等說明兩種之間是一一對應的關係。

    當用戶去操做設備的時候內核就會找到相應的驅動程序。

  經過cat /proc/devices打印主設備號

  次設備號0~255

  ls /dev/memdev0   //查看

  顯示:/dev/memdev0

  有了字符設備文件,也有了設備驅動程序,就要編寫應用程序。就是經過字符設備文件訪問設備驅動程序。

  要訪問硬件,其實就是訪問硬件裏的寄存器,假如定義一個數組,數組裏頭有5個整型的元素,每個整型元素就能夠模擬一個寄存器,要去操做硬件,最後能夠變爲操做數組,好比要把數據寫入寄存器,實際上就變成往數組裏頭寫入數據。經過驅動程序往數組裏頭寫入數據。若是要從設備裏頭讀出數據,經過驅動程序從數組裏頭讀出數據。 

4 字符設備驅動實例

  驅動程序一般採用內核模塊的程序結構來進行編碼。所以,編譯/安裝一個驅動程序,其實質就是編譯/安裝一個內核模塊。

  驅動程序文件包含:memdev.c、Makefile;應用程序文件包含:write_mem.c、read_mem.c;

  1 經過命令make,編譯驅動程序獲得文件memdev.ko,並將其拷貝到nfs開發板掛載的目錄,而後安裝驅動程序

    insmod memdev.ko       安裝memdev.ko

    lsmod         查看驅動

  

  2 使用命令查看設備號:cat /proc/devices

  3 建立字符設備文件:

    mknod /dev/memdev0 c 253 0           //名字不能跟已有的重複

  

  4 查看建立的設備字符文件

    ls /dev/memdev0

    顯示: /dev/memdev0

  5 編譯應用程序write_mem.c

    arm-linux-gcc write_mem.c -o write_mem

  運行:./write_mem

  報錯:-/bin/sh: ./write_mem:not found (應用程序依賴的庫找不到)

  經過:arm-linux-readlef -d write_mem 查詢應用程序所依賴的動態連接庫

  

  經過ls命令查看,開發板中沒有這個庫,因此報錯;

  解決辦法:

    1 直接將libc.so.6複製到開發板中

    2 採樣靜態編譯的方法

      arm-linux-gcc -static write_mem.c -o write_mem

  採用靜態編譯後運行:./write_mem

  6 編譯應用程序read_mem.c

    arm-linux-gcc -static read_mem.c -o read_mem

  運行:./read_mem

  結果:dst is 2013

5 字符設備驅動程序模板

  

  1 設備描述結構

  在任何一種驅動模型中,設備都會用內核中的一種結構來描述。咱們的符設備在內核中使用struct cdev來描述。

    struct cdev {
            struct kobject kobj;
            struct module *owner;
            const struct file_operations *ops; //設備操做集
            struct list_head list;
            dev_t dev; //設備號
            unsigned int count; //設備數
            };

  查看設備號ls –l dev下都是設備文件

   eg:crw-r----- 1 root root 10, 223 12月 15:00.10 uinput

    10:主設備號   223:次設備號

  主設備號反映設備類型,次設備號區分同類型設備

  

  設備號操做 

    Linux內核中使用dev_t類型來定義設備號,dev_t這種類型其實質爲32位的unsigned int,其中高12位爲主設備號,低20位爲次設備號.

    問1:若是知道主設備號,次設備號,怎麼組合成dev_t類型
    答:dev_t dev = MKDEV(主設備號,次設備號)
    問2: 如何從dev_t中分解出主設備號?
    答: 主設備號 = MAJOR(dev_t dev)
    問3: 如何從dev_t中分解出次設備號?
    答: 次設備號=MINOR(dev_t dev)

  設備號分配:靜態申請和動態分配

  靜態申請:

    開發者本身選擇一個數字做爲主設備號,而後經過函數register_chrdev_region向內核申請使用。缺點:若是申請使用的設備號已經被內核中的其餘驅動使用了,則申請失敗。

  動態分配:

    使用alloc_chrdev_region由內核分配一個可用的主設備號。優勢:由於內核知道哪些號已經被使用了,因此不會致使分配到已經被使用的號。

  設備號註銷

    不論使用何種方法分配設備號,都應該在驅動退出時,使用unregister_chrdev_region函數釋放這些設備號。

  2 操做函數集

  

  Struct file_operations是一個函數指針的集合,定義能在設備上進行的操做。結構中的函數指針指向驅動中的函數, 這些函數實現一個針對設備的操做, 對於不支持的操做則設置函數指針爲 NULL。例如:

  https://blog.csdn.net/littlelee111/article/details/10133759

    struct file_operations dev_fops ={
                      .llseek = NULL,
                      .read = dev_read,
                      .write = dev_write,
                      .ioctl = dev_ioctl,
                      .open = dev_open,
                      .release = dev_release,
                     };

  3 字符設備初始化

  分配cdev:能夠採用靜態和動態兩種辦法

  靜態分配:
    struct cdev mdev;
  動態分配:
    struct cdev *pdev = cdev_alloc();

  初始化cdev

      structcdev的初始化使用cdev_init函數來完成。
    cdev_init(struct cdev *cdev, const struct file_operations *fops)
    參數:
      cdev: 待初始化的cdev結構
      fops: 設備對應的操做函數集

  註冊cdev:

         註冊使用cdev_add函數來完成。

    cdev_add(structcdev *p, dev_t dev, unsigned count)
    參數:
      p: 待添加到內核的符設備結構
      dev: 設備號
      count: 該類設備的設備個數

  4 設備操做原型

    int (*open)(struct inode *, struct file *) 打開設備,響應open系統

    int (*release)(struct inode *, struct file *);關閉設備,響應close系統調用

    loff_t (*llseek)(struct file *, loff_t, int);重定位讀寫指針,響應lseek系統調用

    ssize_t (*read)(struct file *,char __user *,size_t,loff_t *)從設備讀取數據,響應read系統調用

    ssize_t(*write)(struct file*,const char __user*,size_t,loff_t*)向設備寫入數據,響應write系統調用

  struct file

    在Linux系統中,每個打開的文件,在內核中都會關聯一個struct file,它由內核在打開文件時建立, 在文件關閉後釋放。

    重要成員:
      loff_t f_pos /*文件讀寫指針*/
      struct file_operations *f_op /*該文件所對應的操做*/

  struct inode

  每個存在於文件系統裏面的文件都會關聯一個inode 結構,該結構主要用來記錄文件物理上的信息。所以, 它和表明打開文件的file結構是不一樣的。一個文件沒有被打開時不會關聯file結構,可是卻會關聯一個inode 結構。

  重要成員:
    dev_t i_rdev:設備號

    設備操做open

  open設備方法是驅動程序用來爲之後的操做完成初始化準備工做的。在大部分驅動程序中,open完成以下工做:

  標明次設備號
  啓動設備

  設備操做release

  release方法的做用正好與open相反。這個設備方法有時也稱爲close,它應該:關閉設備。

  設備操做read

  read設備方法一般完成2件事情:
    從設備中讀取數據(屬於硬件訪問類操做)
    將讀取到的數據返回給應用程序
  ssize_t (*read) (struct file *filp, char __user *buff, size_t count, loff_t *offp)
  參數分析:
    filp:與符設備文件關聯的file結構指針, 由內核建立。
    buff : 從設備讀取到的數據,須要保存到的位置。由read系統調用提供該參數。
    count: 請求傳輸的數據量,由read系統調用提供該參數。
    offp: 文件的讀寫位置,由內核從file結構中取出後,傳遞進來

    buff參數是來源於用戶空間的指針,這類指針都不能被內核代碼直接引用,必須使用專門的函數

      int copy_from_user(void *to, const void __user *from, int n)
      int copy_to_user(void __user *to, const void*from, intn)

  設備操做write

  write設備方法一般完成2件事情:
    從應用程序提供的地址中取出數據
    將數據寫入設備(屬於硬件訪問類操做)
  ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *)

  5 驅動註銷

  當咱們從內核中卸載驅動程序的時候,須要使用cdev_del函數來完成符設備的註銷。

相關文章
相關標籤/搜索