LINUX驅動篇(二)驅動開發

系列文章目錄


文章目錄

  • 系列文章目錄
  • 總結
  • 介紹
  • 字符設備驅動
    • 工作原理
    • 驅動框架
      • 加載卸載
      • 注冊注銷
        • 設備號詳解
      • 打開關閉等操作
      • 實例分析
      • 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驅動框架

  1. 首先是打開攝像頭設備;
  2. 查詢設備的屬性或功能;
  3. 設置設備的參數,譬如像素格式、幀大小、幀率;
  4. 申請幀緩沖、內存映射;
  5. 幀緩沖入隊;
  6. 開啟視頻采集;
  7. 幀緩沖出隊、對采集的數據進行處理;
  8. 處理完后,再次將幀緩沖入隊,往復;
  9. 結束采集。

二、

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/95434.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/95434.shtml
英文地址,請注明出處:http://en.pswp.cn/web/95434.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【工具】開源大屏設計器 自用整理

【工具】開源大屏設計器 自用整理 GoView低代碼數據可視化 GoView 說明文檔 | 低代碼數據可視化開發平臺 JimuReport積木報表(免費報表工具) https://github.com/jeecgboot/JimuReport 「數據可視化&#xff1a;報表、大屏、數據看板」積木報表是一款類Excel操作風格&#xf…

.NetCore MVC

這個是我自己記得筆記&#xff0c;最好有點基礎看我的。 html 輔助標簽 Html.DropList 分布視圖 使用 RenderPartialAsync 呈現分部視圖。 此方法不返回 IHtmlContent。 它將呈現的輸出直接流式傳輸到響應。 因為該方法不返回結果&#xff0c;所以必須在 Razor 代碼塊內調用它…

@GitLab 介紹部署使用詳細指南

文章目錄**GitLab 介紹&部署&使用詳細指南****1. GitLab 介紹與核心概念****1.1 什么是 GitLab&#xff1f;****1.2 核心特性****1.3 版本區別****2. 部署指南 (以 Ubuntu 22.04 LTS 為例)****2.1 環境準備****2.2 安裝步驟****2.3 重要配置文件****3. 基本使用入門***…

如何通過 AI IDE 集成開發工具快速生成簡易留言板系統

在當今快速迭代的軟件開發環境中&#xff0c;AI 輔助編程工具已經成為開發者提高效率的重要手段。本文將詳細介紹如何利用 AI IDE 集成開發工具快速構建一個功能完整的簡易留言板系統&#xff0c;涵蓋從需求分析到部署上線的全過程&#xff0c;并提供完整代碼、流程圖、Prompt …

機器學習:從技術原理到實踐應用的深度解析

目錄引言一.什么是機器學習&#xff08;ML&#xff09;&#xff1f;——從技術本質到核心目標1.與傳統編程的本質區別&#xff1a;規則的“來源不同”2.核心目標&#xff1a;在“偏差-方差權衡”下優化性能指標二.機器學習的核心分類——基于“數據標簽”與“學習范式”的技術劃…

[muduo網絡庫]-muduo庫TcpServer類解析

本貼用于記錄muduo庫的學習過程&#xff0c;以下是關于TcpServer的個人理解。 TcpServer內含Acceptor、threadpool等類&#xff0c;算是把主線程所有要做的事封裝了起來。 重要成員變量 EventLoop *loop_; // baseloop 用戶自定義的loopconst std::string ipPort_;const std…

工作兩年,最后從css轉向tailwind了!

菜鳥上班已經兩年了&#xff0c;從一個對技術充滿熱情的小伙子&#xff0c;變成了一個職場老鳥了。自以為自己在不停的學習&#xff0c;但是其實就是學一些零碎的知識點&#xff0c;比如&#xff1a;vue中什么東西沒見過、js什么特性沒用過、css新出了個啥 …… 菜鳥感覺自己也…

macOS 更新后找不到鑰匙串訪問工具的解決方案

macOS 更新后找不到鑰匙串訪問工具的解決方案 隨著macOS的不斷更新&#xff0c;一些系統工具的位置可能會發生變化&#xff0c;給用戶帶來不便。鑰匙串訪問&#xff08;Keychain Access&#xff09;是macOS中一個非常重要的工具&#xff0c;用于管理密碼、證書等敏感信息。最近…

深入理解Go 與 PHP 在參數傳遞上的核心區別

$run_return_data []; $ret $this->handleData($event_req_info, $run_return_data); public function handleData($event_req_info, &$run_return_data): array {$run_return_data [ //使用引用變量返回數據shop_id > $shop_id,request_id > $request_…

【Dify智能體】2025 最新版Linux部署Dify教程(Ubuntu)

一、前言 Dify 是一款開源的智能體工作流平臺,可以用來快速構建 AI 應用。相比手動搭建復雜的依賴環境,Docker Compose 部署方式更簡單、更快速、更穩定。本文將一步步帶你在 Ubuntu 22.04 + Docker Compose v2 上安裝 Dify,并給出常見問題與優化方案。 ps:如果還沒有安裝…

基礎思想:動態規劃與貪心算法

一、動態規劃核心思想&#xff1a;將復雜問題分解為相互重疊的子問題&#xff0c;通過保存子問題的解來避免重復計算&#xff08;記憶化&#xff09;。動態規劃需要通過子問題的最優解&#xff0c;推導出最終問題的最優解&#xff0c;因此這種方法特別注重子問題之間的轉移關系…

使用生成對抗網絡增強網絡入侵檢測性能

文章目錄前言一、GAN 模型介紹二、研究方法1.數據集選擇與處理2.IDS 基線模型構建3. GAN 模型設計與樣本生成4.生成樣本質量評估三、實驗評估四、總結前言 網絡入侵檢測系統&#xff08;Network Intrusion Detection System, NIDS&#xff09;在保護關鍵數字基礎設施免受網絡威…

VR森林經營模擬體驗帶動旅游經濟發展

將VR森林經營模擬體驗作為一種獨特的旅游項目&#xff0c;正逐漸成為旅游市場的新熱點。游客們無需長途跋涉前往深山老林&#xff0c;只需在旅游景區的VR體驗中心&#xff0c;戴上VR設備&#xff0c;就能開啟一場奇妙的森林之旅。在虛擬森林中&#xff0c;他們可以盡情探索&…

Vue2存量項目國際化改造踩坑

Vue2存量項目國際化改造踩坑 一、背景 在各類業務場景中&#xff0c;國際化作為非常重要的一部分已經有非常多成熟的方案&#xff0c;但對于一些存量項目則存在非常的改造成本&#xff0c;本文將分享一個的Vue2項目國際化改造方案&#xff0c;通過自定義Webpack插件自動提取中文…

硬件開發(1)—單片機(1)

1.單片機相關概念1.CPU&#xff1a;中央處理器&#xff0c;數據運算、指令處理&#xff0c;CPU性能越高&#xff0c;完成指令處理和數據運算的速度越快核心&#xff1a;指令解碼執行數據運算處理2.MCU&#xff1a;微控制器&#xff0c;集成度比較高&#xff0c;將所有功能集成到…

Elasticsearch面試精講 Day 4:集群發現與節點角色

【Elasticsearch面試精講 Day 4】集群發現與節點角色 在“Elasticsearch面試精講”系列的第四天&#xff0c;我們將深入探討Elasticsearch分布式架構中的核心機制——集群發現&#xff08;Cluster Discovery&#xff09;與節點角色&#xff08;Node Roles&#xff09;。這是構…

微信小程序長按識別圖片二維碼

提示&#xff1a;二維碼圖片才能顯示識別菜單1.第一種方法添加屬性&#xff1a;show-menu-by-longpress添加屬性&#xff1a;show-menu-by-longpress <image src"{{shop.wx_qrcode}}" mode"widthFix" show-menu-by-longpress></image>2.第二種…

智能化數據平臺:AI 與大模型驅動的架構升級

在前面的文章中,我們探討了 存算分離與云原生,以及 流批一體化計算架構 的演進趨勢。這些演進解決了“算力與數據效率”的問題。但在今天,企業在數據平臺上的需求已經從 存儲與計算的統一,逐步走向 智能化與自動化。 尤其是在 AI 與大模型快速發展的背景下,數據平臺正在發…

解鎖 Vue 動畫的終極指南:Vue Bits 實戰進階教程,讓你的Vue動畫比原生動畫還絲滑,以及動畫不生效解決方案。

一條 Splash Cursor 的 10 秒 Demo 視頻曾創下 200 萬 播放量&#xff0c;讓 React Bits 風靡全球。如今&#xff0c;Vue 開發者終于迎來了官方移植版 —— Vue Bits。 在現代 Web 開發中&#xff0c;UI 動效已成為提升用戶體驗的關鍵因素。Vue Bits 作為 React Bits 的官方 Vu…

《微服務協作實戰指南:構建全鏈路穩健性的防御體系》

在微服務架構從“技術嘗鮮”邁向“規模化落地”的進程中&#xff0c;服務間的協作不再是簡單的接口調用&#xff0c;而是涉及超時控制、事務一致性、依賴容錯、配置同步等多維度的復雜博弈。那些潛藏于協作鏈路中的隱性Bug&#xff0c;往往不是單一服務的功能缺陷&#xff0c;而…