系列文章目錄
文章目錄
- 系列文章目錄
- 總結
- 介紹
- 字符設備驅動
- 工作原理
- 驅動框架
- 加載卸載
- 注冊注銷
- 設備號詳解
- 打開關閉等操作
- 實例分析
- led驅動編寫
- 地址映射
- LED驅動
- 改進驅動方式
- 總結
- 自動注冊注銷設備號
- 自動創建設備節點
- 設備樹
- 設備樹LED驅動實驗
- pinctrl和gpio
- 并發和競爭
- 原子操作
- 自旋鎖
- 塊設備驅動
- 網絡設備驅動
- V4L2驅動框架
- 二、
總結
字符設備驅動:加載卸載、注冊注銷(設備號)、操作函數、許可注冊
函數指針的用法、主設備號、從設備號、地址映射MMU
虛擬地址和物理地址的重新映射ioremap,解映射,iounmap,這里的物理地址不是DDR,是獨立的物理地址空間。
設備樹修改,里面加哪些內容
pinctrl
臨界區保護和原子操作
介紹
三大類驅動:
字符設備:字節流進行輸入輸出的設備:點燈、IIC、SPI
塊設備:復雜,廠家會寫好,以存儲塊為基礎,存儲器設備驅動,EMMC、NAND、SD卡、U盤
網絡設備:不管是有線還是無線的網絡,USB WIFI也算(USB接口是字符設備)。
Linux內核使用的是4.1.15,支持設備樹,后面要看內核!!!
字符設備驅動
工作原理
頂層應用程序通過調用系統庫,進入內核,操作底層的驅動程序來控制硬件
應用程序在用戶空間,驅動程序算內核部分,其中的橋梁就是open這些c庫函數
驅動程序中有對應的open函數等,對應著這些系統調用的函數
Linux內核中的include/linux/fs.h.文件中有驅動操作函數集合,file_operations結構體
這里補充個函數指針的語法
定義成函數指針,這樣后面驅動程序里面編寫的時候,就可以將自己編寫的open函數,比如里面添加一些自己的功能,直接賦值給函數指針,內核調用的時候,調用統一的接口就行了
注意參數類型和順序和個數其實都是一樣的,只是對自定義結構體內部函數重新賦值,方便
// 示例:一個簡單的字符設備驅動
static int my_open(struct inode *inode, struct file *filp) { /* ... */ }
static ssize_t my_read(struct file *filp, char __user *buf, size_t count, loff_t *fpos) { /* ... */ }// 定義設備的操作集合
struct file_operations my_fops = {.owner = THIS_MODULE,.open = my_open, // 指向驅動自定義的函數.read = my_read, // 指向驅動自定義的函數.write = NULL, // 不支持寫入操作
};
當用戶程序調用read(fd, buf, size)時:
內核通過文件描述符fd找到對應的file_operations結構體。
檢查read指針是否有效,若有效則調用它,否則返回錯誤(如-EINVAL)。
常用函數:
第 1589 行,owner 擁有該結構體的模塊的指針,一般設置為 THIS_MODULE。
第 1590 行,llseek 函數用于修改文件當前的讀寫位置。
第 1591 行,read 函數用于讀取設備文件。
第 1592 行,write 函數用于向設備文件寫入(發送)數據。
第 1596 行,poll 是個輪詢函數,用于查詢設備是否可以進行非阻塞的讀寫。
第 1597 行,unlocked_ioctl 函數提供對于設備的控制功能,與應用程序中的 ioctl 函數對應。
第 1598 行,compat_ioctl 函數與 unlocked_ioctl 函數功能一樣,區別在于在 64 位系統上,
32 位的應用程序調用將會使用此函數。在 32 位的系統上運行 32 位的應用程序調用的是
unlocked_ioctl。
第 1599 行,mmap 函數用于將設備的內存映射到進程空間中(也就是用戶空間),一般幀緩
沖設備會使用此函數,比如 LCD 驅動的顯存,將幀緩沖(LCD 顯存)映射到用戶空間中以后應用
程序就可以直接操作顯存了,這樣就不用在用戶空間和內核空間之間來回復制。
第 1601 行,open 函數用于打開設備文件。
第 1603 行,release 函數用于釋放(關閉)設備文件,與應用程序中的 close 函數對應。
第 1604 行,fasync 函數用于刷新待處理的數據,用于將緩沖區中的數據刷新到磁盤中。
第 1605 行,aio_fsync 函數與 fasync 函數的功能類似,只是 aio_fsync 是異步刷新待處理的
數據。
驅動框架
加載卸載
一般不編譯進內核里面,修改負責,搞成一個模塊(.ko文件)
注冊這兩種操作函數,參數 xxx_init 就是需要注冊的具體函數名,當使用“insmod”、“rmmod”就會調用xxx_init和xxx_exit
原名叫insert module 、remove module
static int __init xxx_init(void)
{/* 入口函數具體內容 */return 0;}/* 驅動出口函數 */static void __exit xxx_exit(void){
/* 出口函數具體內容 */
}/* 將上面兩個函數指定為驅動的入口和出口函數 */
module_init(xxx_init); //注冊模塊加載函數
module_exit(xxx_exit); //注冊模塊卸載函數
modprobe:module probe,模型探索,會智能提供模塊依賴
比如 drv.ko 依賴 first.ko 這個模塊,就必須先使用insmod 命令加載 first.ko 這個模塊,然后再加載 drv.ko 這個模塊。modprobe就不需要。
insmod drv.ko
modprobe drv.kormmod drv.ko
modprobe -r drv.ko
注冊注銷
注冊放在init函數里面,注銷放在exit里面,多用static保證安全性
static struct file_operations test_fops;static int __init xxx_init(void)
{/* 注冊字符設備驅動 */int retvalue = 0;retvalue = register_chrdev(200, "chrtest", &test_fops);
if(retvalue < 0){
/* 字符設備注冊失敗,自行處理 */}return 0;}/* 驅動出口函數 */static void __exit xxx_exit(void){
/* 出口函數具體內容 */unregister_chrdev(200, "chrtest");
}/* 將上面兩個函數指定為驅動的入口和出口函數 */
module_init(xxx_init); //注冊模塊加載函數
module_exit(xxx_exit); //注冊模塊卸載函數
下面解釋一下函數意思
register_chrdev 函數用于注冊字符設備,設備號,設備類型,設備的操作函數結構體
major:主設備號,Linux 下每個設備都有一個設備號,設備號分為主設備號和次設備號兩部分,關于設備號后面會詳細講解。
name:設備名字,指向一串字符串。
fops:結構體 file_operations 類型指針,指向設備的操作函數集合變量。
unregister_chrdev 函數用戶注銷字符設備,設備號和設備名
major:要注銷的設備對應的主設備號。
name:要注銷的設備對應的設備名。
命令“cat /proc/devices”可查看使用了的設備號
設備號詳解
// include/linux/types.h
typedef __u32 __kernel_dev_t;
//include/uapi/asm-generic/int-ll64.h
typedef unsigned int __u32;
這里名字加了__,是為了區分用戶空間和內核空間
dev_t 其實就是 unsigned int 類型,是一個 32 位的數據類型,高 12 位為主設備號,低 20 位為次設備號
所以主設備號范圍0-4095
次設備號是主設備號下的,比如我們led設備有多個,這樣就用一個主,多個次,這樣就能擴充數百萬的設備
靜態分配:有一些主設備號,Linux內核開發者分配掉了,用“cat /proc/devices”查看的包括了這一部分
推薦動態分配:注冊前向系統申請,不用自己找一個沒用過的
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
dev:保存申請到的設備號。
baseminor:次設備號起始地址,alloc_chrdev_region 可以申請一段連續的多個設備號,這些設備號的主設備號一樣,但是次設備號不同,次設備號以 baseminor 為起始地址地址開始遞
增。一般 baseminor 為 0,也就是說次設備號從 0 開始。
count:要申請的設備號數量。
name:設備名字。
注銷字符設備要釋放:
void unregister_chrdev_region(dev_t from, unsigned count)
from:要釋放的設備號。
count:表示從 from 開始,要釋放的設備號數量。
打開關閉等操作
這里里面就不寫具體內容了,展示一下結構框架,最后要添加LICENSE,作者信息,LICENSE采用GPL協議
/* 打開設備 */
static int chrtest_open(struct inode *inode, struct file *filp)
{/* 用戶實現具體功能 */return 0;
}/* 從設備讀取 */
static ssize_t chrtest_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{/* 用戶實現具體功能 */return 0;
}/* 向設備寫數據 */
static ssize_t chrtest_write(struct file *filp,const char __user *buf,size_t cnt, loff_t *offt)
{/* 用戶實現具體功能 */return 0;
}static int chrtest_release(struct inode *inode, struct file *filp)
{/* 用戶實現具體功能 */return 0;
}static struct file_operations test_fops = {.owner = THIS_MODULE, .open = chrtest_open,.read = chrtest_read,.write = chrtest_write,.release = chrtest_release,
};/* 驅動入口函數 */
static int __init xxx_init(void)
{/* 入口函數具體內容 */int retvalue = 0;/* 注冊字符設備驅動 */retvalue = register_chrdev(200, "chrtest", &test_fops);if(retvalue < 0){/* 字符設備注冊失敗,自行處理 */}return 0;
}/* 驅動出口函數 */
static void __exit xxx_exit(void)
{/* 注銷字符設備驅動 */unregister_chrdev(200, "chrtest");
}/* 將上面兩個函數指定為驅動的入口和出口函數 */
module_init(xxx_init);
module_exit(xxx_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("xxx");
LINUX內部采用GPL協議,因為其是開源協議,任何鏈接到內核的代碼,必須要遵循GPL協議,并公開源碼,其實就是標注我接受開源
實例分析
待實驗
Linux首先只有printk函數,運行在內核態,printf是運行在用戶態,驅動程序在內核態
printk可以根據日志級別對消息分類,在文件 include/linux/kern_levels.h 里(這個文件在linux源碼里面)
不顯示調用消息級別,會默認4,只有優先級高于 7 的消息才能顯示在控制臺上
#define KERN_SOH "\001"
#define KERN_EMERG KERN_SOH "0" /* 緊急事件,一般是內核崩潰 */
#define KERN_ALERT KERN_SOH "1" /* 必須立即采取行動 */
#define KERN_CRIT KERN_SOH "2" /* 臨界條件,比如嚴重的軟件或硬件錯誤*/
#define KERN_ERR KERN_SOH "3" /* 錯誤狀態,一般設備驅動程序中使用KERN_ERR 報告硬件錯誤 */
#define KERN_WARNING KERN_SOH "4" /* 警告信息,不會對系統造成嚴重影響 */
#define KERN_NOTICE KERN_SOH "5" /* 有必要進行提示的一些信息 */
#define KERN_INFO KERN_SOH "6" /* 提示性的信息 */
#define KERN_DEBUG KERN_SOH "7" /* 調試信息 *///例子:
printk(KERN_EMERG "gsmi: Log Shutdown Reason\n");
參數 offt 是相對于文件首地址的偏移,kerneldata 里面保存著用戶空間要讀取的數據,先將 kerneldata 數組中的數據拷貝到讀緩沖區 readbuf 中,通過函數 copy_to_user 將readbuf 中的數據復制到參數 buf 中。因為內核空間不能直接操作用戶空間的內存
static char readbuf[100]; /* 讀緩沖區 */static ssize_t chrdevbase_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{int retvalue = 0;/* 向用戶空間發送數據 */memcpy(readbuf, kerneldata, sizeof(kerneldata));retvalue = copy_to_user(buf, readbuf, cnt);if(retvalue == 0){printk("kernel senddata ok!\r\n");}else{printk("kernel senddata failed!\r\n");}//printk("chrdevbase read!\r\n");return 0;
}
這里的__user就是提醒我們注意這是用戶空間的,要用函數拷貝,同理用戶空間也不能直接訪問內核空間的內存
static inline long copy_to_user(void __user *to, const void *from, unsigned long n)
copy_from_user函數同理
led驅動編寫
地址映射
MMU:內存管理單元,memory manage unit,老版本的linux必須有,新版本的支持無mmu的
完成虛擬地址到物理空間的映射,即地址映射
內存保護,設置存儲器的訪問權限,設置虛擬空間的緩沖特性
虛擬地址(VA,Virtual Address)、物理地址(PA,PhyscicalAddress)。
對于 32 位的處理器來說,虛擬地址范圍是 2^32=4GB,我們的開發板上有 512MB 的 DDR3,這 512MB 的內存就是物理內存,經過 MMU 可以將其映射到整個 4GB 的虛擬空間
虛擬地址比物理地址大,那么
malloc申請的是虛擬空間,若物理空間不足,但虛擬空間還夠,就能申請,但是標記未訪問,實際訪問的時候,若物理空間+swap不滿足會觸發錯誤,終止進程(自然malloc返回的是虛擬地址)
Swap(交換空間) 是 Linux 系統中用于擴展可用內存的磁盤空間,當物理內存(RAM)不足時,內核會將不活躍的內存頁(Pages)臨時轉移到磁盤上的 Swap 區域,從而騰出物理內存供其他進程使用。
ioremap 函數用于獲取指定物理地址空間對應的虛擬地址空間 ,定 義 在arch/arm/include/asm/io.h 文件中
#define ioremap(cookie,size) __arm_ioremap((cookie), (size), MT_DEVICE)void __iomem * __arm_ioremap(phys_addr_t phys_addr, size_t size, unsigned int mtype)
{return arch_ioremap_caller(phys_addr, size, mtype,__builtin_return_address(0));
}
是個宏,phys_addr:要映射的物理起始地址。size:要映射的內存空間大小。mtype:ioremap 的類型,可以選擇 MT_DEVICE、MT_DEVICE_NONSHARED、MT_DEVICE_CACHED 和 MT_DEVICE_WC,ioremap 函數選擇 MT_DEVICE。
返回值:__iomem 類型的指針,指向映射后的虛擬空間首地址。
iounmap是釋放映射
void iounmap (volatile void __iomem *addr)
對映射后的內存進行讀寫操作,分別是不同位數的讀寫操作
u8 readb(const volatile void __iomem *addr)
u16 readw(const volatile void __iomem *addr)
u32 readl(const volatile void __iomem *addr)void writeb(u8 value, volatile void __iomem *addr)
void writew(u16 value, volatile void __iomem *addr)
void writel(u32 value, volatile void __iomem *addr)
LED驅動
這里就放一些前面沒了解的代碼
/* 寄存器物理地址 */
#define CCM_CCGR1_BASE (0X020C406C)
#define SW_MUX_GPIO1_IO03_BASE (0X020E0068)
#define SW_PAD_GPIO1_IO03_BASE (0X020E02F4)
#define GPIO1_DR_BASE (0X0209C000)
#define GPIO1_GDIR_BASE (0X0209C004)/* 映射后的寄存器虛擬地址指針 */
static void __iomem *IMX6U_CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;/** @description : LED打開/關閉* @param - sta : LEDON(0) 打開LED,LEDOFF(1) 關閉LED* @return : 無*/
void led_switch(u8 sta)
{u32 val = 0;if(sta == LEDON) {val = readl(GPIO1_DR);val &= ~(1 << 3); writel(val, GPIO1_DR);}else if(sta == LEDOFF) {val = readl(GPIO1_DR);val|= (1 << 3); writel(val, GPIO1_DR);}
}/** @description : 驅動出口函數*/
static int __init led_init(void)
{int retvalue = 0;u32 val = 0;/* 初始化LED *//* 1、寄存器地址映射 */IMX6U_CCM_CCGR1 = ioremap(CCM_CCGR1_BASE, 4);SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE, 4);SW_PAD_GPIO1_IO03 = ioremap(SW_PAD_GPIO1_IO03_BASE, 4);GPIO1_DR = ioremap(GPIO1_DR_BASE, 4);GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE, 4);/* 2、使能GPIO1時鐘 */val = readl(IMX6U_CCM_CCGR1);val &= ~(3 << 26); /* 清楚以前的設置 */val |= (3 << 26); /* 設置新值 */writel(val, IMX6U_CCM_CCGR1);/* 3、設置GPIO1_IO03的復用功能,將其復用為* GPIO1_IO03,最后設置IO屬性。*/writel(5, SW_MUX_GPIO1_IO03);/*寄存器SW_PAD_GPIO1_IO03設置IO屬性*bit 16:0 HYS關閉*bit [15:14]: 00 默認下拉*bit [13]: 0 kepper功能*bit [12]: 1 pull/keeper使能*bit [11]: 0 關閉開路輸出*bit [7:6]: 10 速度100Mhz*bit [5:3]: 110 R0/6驅動能力*bit [0]: 0 低轉換率*/writel(0x10B0, SW_PAD_GPIO1_IO03);/* 4、設置GPIO1_IO03為輸出功能 */val = readl(GPIO1_GDIR);val &= ~(1 << 3); /* 清除以前的設置 */val |= (1 << 3); /* 設置為輸出 */writel(val, GPIO1_GDIR);/* 5、默認關閉LED */val = readl(GPIO1_DR);val |= (1 << 3); writel(val, GPIO1_DR);/* 6、注冊字符設備驅動 */retvalue = register_chrdev(LED_MAJOR, LED_NAME, &led_fops);if(retvalue < 0){printk("register chrdev failed!\r\n");return -EIO;}return 0;
}//驅動出口函數
static void __exit led_exit(void)
{/* 取消映射 */iounmap(IMX6U_CCM_CCGR1);iounmap(SW_MUX_GPIO1_IO03);iounmap(SW_PAD_GPIO1_IO03);iounmap(GPIO1_DR);iounmap(GPIO1_GDIR);/* 注銷字符設備驅動 */unregister_chrdev(LED_MAJOR, LED_NAME);
}
補充一點,當運行./program foo bar,argc是3,文件路徑是一個元素,foo是一個元素 bar是一個元素
./ledApp /dev/led 0,所以應用程序里面寫的是三個元素
argc:Argument Count 參數數量
argv:argument Vector 參數向量
if(argc != 3){
printf("Error Usage!\r\n");
return -1;}filename = argv[1];
databuf[0] = atoi(argv[2]); /* 要執行的操作:打開或關閉 */
改進驅動方式
總結
規范自定義設備結構體,并設置為私有設備;自動創建設備號;初始化設備,并向內核加入設備;自動創建設備節點
自動注冊注銷設備號
注冊和注銷函數register_chrdev 和 unregister_chrdev
原來的register_chrdev 需要知道哪個設備號沒用,而且會把所有的子設備號分走
采用自動申請:
//沒有指定,自動申請
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
//指定主次設備號
int register_chrdev_region(dev_t from, unsigned count, const char *name)
//統一釋放注冊函數
void unregister_chrdev_region(dev_t from, unsigned count)
注冊注銷代碼:
/* 注冊字符設備驅動 *//* 1、創建設備號 */if (newchrled.major) { /* 定義了設備號 */newchrled.devid = MKDEV(newchrled.major, 0);register_chrdev_region(newchrled.devid, NEWCHRLED_CNT, NEWCHRLED_NAME);} else { /* 沒有定義設備號 */alloc_chrdev_region(&newchrled.devid, 0, NEWCHRLED_CNT, NEWCHRLED_NAME); /* 申請設備號 */newchrled.major = MAJOR(newchrled.devid); /* 獲取分配號的主設備號 */newchrled.minor = MINOR(newchrled.devid); /* 獲取分配號的次設備號 */}printk("newcheled major=%d,minor=%d\r\n",newchrled.major, newchrled.minor); /* 注銷字符設備驅動 */cdev_del(&newchrled.cdev);/* 刪除cdev */unregister_chrdev_region(newchrled.devid, NEWCHRLED_CNT); /* 注銷設備號 */
為了規范化,采用字符設備結構體cdev,cdev 結構體在 include/linux/cdev.h 文件
struct cdev {struct kobject kobj;struct module *owner;const struct file_operations *ops;struct list_head list;dev_t dev;unsigned int count;
};
ops 和 dev,這兩個就是字符設備文件操作函數集合file_operations 以及設備號 dev_t。
相關函數
//初始化
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
//向Linux添加字符設備
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
//卸載驅動從內核刪除字符設備
void cdev_del(struct cdev *p)
設備號,唯一標識一個設備,供內核識別和管理。
設備節點,用戶空間訪問設備的接口,本質是文件系統中的特殊文件。通常位于 /dev 目錄下(如 /dev/sda、/dev/ttyUSB0)
自動創建設備節點
之前的代碼還要命令窗modprobe 加載驅動,mknod手動創建設備節點
驅動中實現自動創建設備節點后,使用 modprobe 加載驅動模塊成功的話就會自動在/dev 目錄下創建對應的設備文件。
mdev實現自動功能,而且熱插拔事件也由它管理
創建類,參數 owner 一般為 THIS_MODULE,
struct class *class_create (struct module *owner, const char *name)
void class_destroy(struct class *cls);
創建設備,刪除設備
struct device *device_create(struct class *class, struct device *parent,dev_t devt, void *drvdata, const char *fmt, ...)
void device_destroy(struct class *class, dev_t devt)
所以最后代碼,創建個字符設備結構體,規范
/* newchrled設備結構體 */
struct newchrled_dev{dev_t devid; /* 設備號 */struct cdev cdev; /* cdev */struct class *class; /* 類 */struct device *device; /* 設備 */int major; /* 主設備號 */int minor; /* 次設備號 */
};
struct newchrled_dev newchrled; /* led設備 *//** @description : 打開設備* @param - inode : 傳遞給驅動的inode* @param - filp : 設備文件,file結構體有個叫做private_data的成員變量* 一般在open的時候將private_data指向設備結構體。* @return : 0 成功;其他 失敗*/
static int led_open(struct inode *inode, struct file *filp)
{filp->private_data = &newchrled; /* 設置私有數據 */return 0;
}
filp 是 struct file 類型的指針,代表一個打開的文件對象。每當用戶空間程序通過 open() 打開設備文件(如 /dev/mydevice)時,內核會創建一個 struct file 實例
private_data 是 struct file 中的一個 void* 類型成員,專門用于驅動存儲設備私有數據。它的生命周期與文件對象綁定:當用戶調用 open() 時初始化,在 close() 時釋放。在后續的 read、write、ioctl 等操作中,通過 filp->private_data 快速獲取設備數據,無需每次重新查找。
設備樹
設備樹在DTS(Decive Tree Source)文件中
樹的主干就是系統總線,IIC 控制器、GPIO 控制器、SPI 控制器等都是接到系統主線上的分支。IIC 控制器有分為 IIC1 和 IIC2 兩種,其中 IIC1 上接了 FT5206 和 AT24C02這兩個 IIC 設備,IIC2 上只接了 MPU6050 這個設備。DTS就是這樣的。
.dts文件,這樣不同的開發板直接用這一個文件,然后配置就行了,不然每個開發板都有一個信息文件。
一般.dts 描述板級信息(也就是開發板上有哪些 IIC 設備、SPI 設備等),.dtsi 描述 SOC 級信息(也就是 SOC 有幾個 CPU、主頻是多少、各個外設控制器信息等)。
DTS 是設備樹源碼文件,DTB 是將DTS 編譯以后得到的二進制文件。使用DTB文件編譯
詳細的就先跳過,直接看實戰怎么改
設備樹LED驅動實驗
打開 imx6ull-alientek-emmc.dts 文件,在根節點“/”下創建一個名為“alphaled”的子節點,在根節點“/”最后面輸入如下所示內容
alphaled {#address-cells = <1>;#size-cells = <1>;compatible = "atkalpha-led";
status = "okay";reg = < 0X020C406C 0X04 /* CCM_CCGR1_BASE */0X020E0068 0X04 /* SW_MUX_GPIO1_IO03_BASE */0X020E02F4 0X04 /* SW_PAD_GPIO1_IO03_BASE */0X0209C000 0X04 /* GPIO1_DR_BASE */0X0209C004 0X04 >; /* GPIO1_GDIR_BASE */};
屬性#address-cells 和#size-cells 都為 1,表示reg屬性中起始地址一個字長,地址長度也是一個字長,這里是五個寄存器,每個寄存器都是4字節,32位,一個字,所以是1。
#address-cells = <2>;#size-cells = <1>;reg = <0x00000000 0x40000000 0x1000>; // 64位地址0x40000000,長度0x1000
reg 屬性,非常重要!reg 屬性設置了驅動里面所要使用的寄存器物理地址,比如第 6 行的“0X020C406C 0X04”表示 I.MX6ULL 的 CCM_CCGR1 寄存器,其中寄存器首地址為 0X020C406C,長度為 4 個字節。
設備樹中創建節點,增加屬性值
在驅動程序中,讀取節點屬性值,其他的操作不變
pinctrl和gpio
前面直接操作寄存器太繁瑣了,容易出問題,上pinctrl(pin control)系統
1.獲取設備樹中 pin 信息。
2.根據獲取到的 pin 信息來設置 pin 的復用功能
3.根據獲取到的 pin 信息來設置 pin 的電氣特性,比如上/下拉、速度、驅動能力等。
打開 imx6ull-alientek-emmc.dts,開始了,上輔助配置,hog1熱插拔相關
并發和競爭
在多個任務共同操作同一段內存或者設備的情況,甚至中斷都能訪問的資源叫做共享資源
并發就是多個“用戶”同時訪問同一個共享資源
原因:多線程并發訪問;搶占式并發訪問;中斷程序并發訪問;SMP(多核)核間并發訪問
原子操作
linux提供了原子操作的變量和函數
atomic_t 的結構體來完成整型數據的原子操作
typedef struct {int counter;
} atomic_t;atomic_t a;
自旋鎖
原子操作只能對整形變量或者位進行保護,但是,在實際的使用環境中怎么可能只有整形變量或位這么簡單的臨界區。
如果自旋鎖正在被線程 A 持有,線程 B 想要獲取自旋鎖,那么線程 B 就會處于忙循環-旋轉-等待狀態,線程 B 不會進入休眠狀態或者說去做其他的處理
自旋的意思就是原地打轉
那就等待自旋鎖的線程會一直處于自旋狀態,這樣會浪費處理器時間,降低系統性能,所以自旋鎖的持有時間不能太長。所以自旋鎖適用于短時期的輕量級加鎖
spinlock_t lock; //定義自旋鎖
注意會死鎖,如果睡眠或阻塞
中斷里面可以用自旋鎖,在獲取鎖之前一定要先禁止本地中斷(也就是本 CPU 中斷,對于多核 SOC來說會有多個 CPU 核),否則可能導致鎖死現象的發生,關閉本地中斷
還有讀寫自旋鎖,一次只能允許一個寫操作,也就是只能一個線程持有寫鎖,而且不能進行讀操作。但是當沒有寫操作的時候允許一個或多個線程持有讀鎖
順序鎖:以允許在寫的時候進行讀操作,也就是實現同時讀寫,但是不允許同時進行并發的寫操作,如果在讀的過程中發生了寫操作,最好重新進行讀取,保證數據完整性
自旋鎖使用事項:
因為在等待自旋鎖的時候處于“自旋”狀態,因此鎖的持有時間不能太長,一定要短,否則的話會降低系統性能。如果臨界區比較大,運行時間比較長的話要選擇其他的并發處理方式,比如稍后要講的信號量和互斥體。
自旋鎖保護的臨界區內不能調用任何可能導致線程休眠的 API 函數,否則的話可能導致死鎖。
不能遞歸申請自旋鎖,因為一旦通過遞歸的方式申請一個你正在持有的鎖,那么你就必須“自旋”,等待鎖被釋放,然而你正處于“自旋”狀態,根本沒法釋放鎖。結果就是自己把自己鎖死了!
在編寫驅動程序的時候我們必須考慮到驅動的可移植性,因此不管你用的是單核的還是多核的 SOC,都將其當做多核 SOC 來編寫驅動程序。
塊設備驅動
網絡設備驅動
現在不需要網卡了,集成到一個芯片里面了,
SOC內部有網絡外設MAC,之后還要配一個PHY芯片
如果沒有,會有外置MAC芯片,SRAM接口
內部的 MAC 外設會通過 MII 或者 RMII 接口來連接外部的 PHY 芯片,MII/RMII 接口用來
傳輸網絡數據。
配置或讀取 PHY 芯片,讀寫 PHY 的內部寄存器,叫做 MIDO,MDIO 很類似 IIC,也是兩根線,一根數據線叫做 MDIO,一根時鐘線叫做 MDC。
V4L2驅動框架
- 首先是打開攝像頭設備;
- 查詢設備的屬性或功能;
- 設置設備的參數,譬如像素格式、幀大小、幀率;
- 申請幀緩沖、內存映射;
- 幀緩沖入隊;
- 開啟視頻采集;
- 幀緩沖出隊、對采集的數據進行處理;
- 處理完后,再次將幀緩沖入隊,往復;
- 結束采集。