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函數來完成符設備的註銷。