1. 程式人生 > >linux驅動之字元裝置

linux驅動之字元裝置

第一部分:字元裝置工作過程
1、系統呼叫和驅動程式的關聯
關鍵結構體:struct file_operation;file_operation結構體的每一個成員的名字都對應著一個系統呼叫。使用者程序利用系統呼叫在對裝置檔案進行諸如read/write操作時,系統呼叫通過裝置檔案的主裝置號找到相應的裝置驅動程式,然後讀取這個資料結構相應的函式指標,接著把控制權交給該函式。這是linux的裝置驅動程式工作的基本原理。編寫裝置驅動程式的主要工作就是編寫子函式,並填充file_operations的各個域。

 

第二部分:字元裝置驅動
1、註冊字元裝置驅動介面
(1)老介面 register_chrdev
__register_chrdev //函式前面加__,說明函式是由核心維護者寫的
__register_chrdev_region //用來註冊支付裝置的主次裝置號
cdev_alloc()
cdev_add()
(2)新介面 register_chrdev_region(自己指定裝置號)/alloc_chrdev_region(自己不指定裝置號,然系統來分配)僅可用來獲取裝置號,尚未註冊裝置 ;
cdevn,結構體,可以實現字元裝置驅動程式的註冊,相關函式有cdev_alloc(為cdev結構體建立記憶體空間)、cdev_init、cdev_add(用來註冊)、cdev_del
在註冊裝置的時候可以定義cdev結構體,這種方式分配的記憶體在資料段,靈活性不夠;也可通過定義cdev指標變數,然後通過cdev_alloc申請記憶體空間,這種方式比較靈活。
register_chrdev_region
__register_chrdev_region
alloc_chrdev_region
__register_chrdev_region
(3)主次裝置號
MKDEV,由主裝置號和次裝置號算出來一個主次裝置號
MAJOR,從裝置號裡面提取出來主裝置號
MINOR,從裝置號裡面提取出來次裝置號

(4)記憶體
全域性變數 .data資料段,程式在載入的時候去分配,程式結束的時候資料段的內容才會去釋放,資料段裡面的內容在程式執行過程中是永久有效的;缺點:記憶體一直佔用,靈活性不夠;
區域性變數 棧,用的時候申請,不用的時候釋放,靈活性太高
malloc 堆,按需分配,申請和釋放都需要手工進行

字元裝置驅動工作流程:在應用程式開啟/dev/text裝置檔案,在核心中找到這個檔案所對應的主次裝置號,根據主次裝置號可以找到對應的file_operatons結構體

2、自動建立字元裝置驅動的裝置檔案
(1)使用mknod建立裝置檔案的缺點:手工建立,手工刪除,太麻煩
(2)解決方案:udev(嵌入式中用的是mdev)
mdev是一個應用層的軟體,由於裝置檔案在使用者空間,所以是在busybox中實現的一個命令。
核心驅動和應用層udev之間有一套資訊傳輸機制(netlink協議)。
應用層啟動udev(在跟檔案系統的rcS裡面)核心驅動中使用相應的介面,驅動在註冊和登出時資訊會被傳給udev,由udev在應用層進行裝置檔案的建立和刪除。
(3)核心驅動裝置類相關函式
class_create //建立一個類
device_create //建立一個裝置

(4)裝置類程式碼分析
使用class_create建立一個裝置類,類名就是根檔案系統中sys/class目錄下的名字;
uevent 裡面存放的就是核心給udev傳遞資訊的地方;

(5)靜態對映表建立過程分析
對映表就是map-s5p.h裡面的一些巨集定義,這些巨集定義就是虛擬地址和實體地址的對映關係。
對映表建立函式。該函式負責由對映表來建立linux核心的頁表對映關係。在kernel/arch/arm/mach-s5pv210/mach-smdkc110.c中的smdkc110_map_io函式中實現。
s5p_init_io
iotable_init
結論:經過分析,真正的核心移植時給定的靜態對映表在arch/arm/plat-s5p/cpu.c中的s5p_iodesc,本質是一個結構體陣列,陣列中每一個元素就是一個對映,這個對映描述了一段實體地址到虛擬地址之間的對映。這個結構體陣列所記錄的幾個對映關係被iotable_init所使用,該函式負責將這個結構體陣列格式的表建立成MMU所能識別的頁表對映關係,這樣在開機後可以直接使用相對應的虛擬地址來訪問對應的實體地址。
開機時呼叫對映表建立函式:
問題:開機時(kernel啟動時)smdkc110_map_io怎麼被呼叫的?
start_kernel
setup_arch
paging_init
devicemaps_init

if (mdesc->map_io)
mdesc->map_io();


第三部分:驅動框架
1、驅動框架的概念
(1)核心中驅動部分維護者針對每種種類的的驅動設計一套成熟的、標準的、典型的驅動實現,然後把不同廠家的同類硬體驅動中的相同的部分抽出來自己實現好,再把不同部分留出介面給具體的驅動開發工程師來實現,這就叫驅動框架。
(2)核心維護者在核心中設計了一些統一管控系統資源的體系,這些體系讓核心能夠對資源在各個驅動之間的使用統一協調和分配,保證整個核心的穩定健康執行。譬如系統中所有的GPIO就屬於系統資源,每個驅動模組如果要使用某個GPIO就要先呼叫特殊的介面先申請,申請到後使用,使用完後要釋放。又譬如中斷號也是一種資源,驅動在使用前也必須去申請。這也是驅動框架的組成部分。
(3)一些特定的介面函式、一些特定的資料結構,這些是驅動框架的直接表現。
5.4.2.核心驅動框架中LED的基本情況
5.4.2.1、相關檔案
(1)drivers/leds目錄,這個目錄就是驅動框架規定的LED這種硬體的驅動應該待的地方。
(2)led-class.c和led-core.c,這兩個檔案加起來屬於LED驅動框架的第一部分,這兩個檔案是核心開發者提供的,他們描述的是核心中所有廠家的不同LED硬體的相同部分的邏輯。
(3)leds-xxxx.c,這個檔案是LED驅動框架的第2部分,是由不同廠商的驅動工程師編寫新增的,廠商驅動工程師結合自己公司的硬體的不同情況來對LED進行操作,使用第一部分提供的介面來和驅動框架進行互動,最終實現驅動的功能。
5.4.2.2、九鼎移植的核心中led驅動
(1)九鼎實際未使用核心推薦的led驅動框架
(2)drivers/char/led/x210-led.c
5.4.2.3、案例分析驅動框架的使用
(1)以leds-s3c24xx.c為例。leds-s3c24xx.c中通過呼叫led_classdev_register來完成我們的LED驅動的註冊,而led_classdev_register是在drivers/leds/led-class.c中定義的。所以其實SoC廠商的驅動工程師是呼叫核心開發者在驅動框架中提供的介面來實現自己的驅動的。
(2)驅動框架的關鍵點就是:分清楚核心開發者提供了什麼,驅動開發者自己要提供什麼
5.4.2.4、典型的驅動開發行業現狀
(1)核心開發者對驅動框架進行開發和維護、升級,對應led-class.c和led-core.c
(2)SoC廠商的驅動工程師對裝置驅動原始碼進行編寫、除錯,提供參考版本,對應leds-s3c24xx.c
(3)做產品的廠商的驅動工程師以SoC廠商提供的驅動原始碼為基礎,來做移植和除錯


5.4.3_4.初步分析led驅動框架原始碼1_2
5.4.3.1、涉及到的檔案
(1)led-core.c
(2)led-class.c
5.4.3.2、subsys_initcall
(1)經過基本分析,發現LED驅動框架中核心開發者實現的部分主要是led-class.c
(2)我們發現led-class.c就是一個核心模組,對led-class.c分析應該從下往上,遵從對模組的基本分析方法。
(3)為什麼LED驅動框架中核心開發者實現的部分要實現成一個模組?因為核心開發者希望這個驅動框架是可以被裝載/解除安裝的。這樣當我們核心使用者不需要這個驅動框架時可以完全去掉,需要時可以隨時加上。
(4)subsys_initcall是一個巨集,定義在linux/init.h中。經過對這個巨集進行展開,發現這個巨集的功能是:將其宣告的函式放到一個特定的段:.initcall4.init。
subsys_initcall
__define_initcall("4",fn,4)
(5)分析module_init巨集,可以看出它將函式放到了.initcall6.init段中。
module_init
__initcall
device_initcall
__define_initcall("6",fn,6)
(6)核心在啟動過程中需要順序的做很多事,核心如何實現按照先後順序去做很多初始化操作。核心的解決方案就是給核心啟動時要呼叫的所有函式歸類,然後每個類按照一定的次序去呼叫執行。這些分類名就叫.initcalln.init。n的值從1到8。核心開發者在編寫核心程式碼時只要將函式設定合適的級別,這些函式就會被連結的時候放入特定的段,核心啟動時再按照段順序去依次執行各個段即可。
(7)經過分析,可以看出,subsys_initcall和module_init的作用是一樣的,只不過前者所宣告的函式要比後者在核心啟動時的執行順序更早。

2、led_class_attrs
(1)什麼是attribute,對應將來/sys/class/leds/目錄裡的內容,一般是檔案和資料夾。這些檔案其實就是sysfs開放給應用層的一些操作介面(非常類似於/dev/目錄下的那些裝置檔案)
(2)attribute有什麼用,作用就是讓應用程式可以通過/sys/class/leds/目錄下面的屬性檔案來操作驅動進而操作硬體裝置。
(3)attribute其實是另一條驅動實現的路線。有區別於之前講的file_operations那條線。

3、led_classdev_register
led_classdev_register
device_create
(1)分析可知,led_classdev_register這個函式其實就是去建立一個屬於leds這個類的一個裝置。其實就是去註冊一個裝置。所以這個函式其實就是led驅動框架中核心開發者提供給SoC廠家驅動開發者的一個註冊驅動的介面。
(2)當我們使用led驅動框架去編寫驅動的時候,這個led_classdev_register函式的作用類似於我們之前使用file_operations方式去註冊字元裝置驅動時的register_chrdev函式。


4、基於驅動框架寫led驅動
第1:我們寫的驅動確實工作了,被載入了,/sys/class/leds/目錄下確實多出來了一個表示裝置的資料夾。資料夾裡面有相應的操控led硬體的2個屬性brightness和max_brightness
第2:led-class.c中brightness方法有一個show方法和store方法,這兩個方法對應使用者在/sys/class/leds/myled/brightness目錄下直接去讀寫這個檔案時實際執行的程式碼。
當我們show brightness時,實際就會執行led_brightness_show函式
當我們echo 1 > brightness時,實際就會執行led_brightness_store函式
(1)show方法實際要做的就是讀取LED硬體資訊,然後把硬體資訊返回給我們即可。所以show方法和store方法必會去操控硬體。但是led-class.c檔案又屬於驅動框架中的檔案,它本身無法直接讀取具體硬體,因此在show和store方法中使用函式指標的方式呼叫了struct led_classdev結構體中的相應的讀取/寫入硬體資訊的方法。
(2)struct led_classdev結構體中的實際用來讀寫硬體資訊的函式,就是我們自己寫的驅動檔案leds-s5pv210.c中要提供的。

驅動的設計理念:不要對最終需求功能進行假定,而應該只是直接的對硬體的操作。有一個概念就是:機制和策略的問題。在硬體操作上驅動只應該提供機制而不是策略。策略由應用程式來做。

5、gpiolib學習
(1)學習重點:gpiolib的建立過程;
gpiolib的使用方法:申請、使用、釋放;
gpiolib的架構:涉及哪些目錄的哪些檔案

(2)主線1:gpiolib的建立
smdkc110_map_io
s5pv210_gpiolib_init 這個函式就是我們gpiolib初始化的函式
(3)struct s3c_gpio_chip
這個結構體是一個GPIO埠的抽象,這個結構體的一個變數就可以完全的描述一個IO埠。
埠和IO口是兩個概念。S5PV210有很多個IO口(160個左右),這些IO口首先被分成N個埠(port group),然後每個埠中又包含了M個IO口。譬如GPA0是一個埠,裡面包含了8個IO口,我們一般記作:GPA0_0(或GPA0.0)、GPA0_1、
核心中為每個GPIO分配了一個編號,編號是一個數字(譬如一共有160個IO時編號就可以從1到160連續分佈),編號可以讓程式很方便的去識別每一個GPIO。


6、裝置驅動模型簡介
(1)類class、匯流排bus、裝置device、驅動driver
代表4個結構體,比如每一個裝置都可以用一個device結構體表示,這4個結構體分別用來描述這4個東西,將來用這四個結構體來生產對應結構體型別的變數,每一個結構體型別的變數都能代表一個這種型別的具體的例項。
裝置 struct device是硬體裝置在核心驅動框架中的抽象
device_register用於向核心驅動框架註冊一個裝置
通常device不會單獨使用,而是被包含在一個具體裝置結構體中,如struct usb_device
驅動 struct device_driver是驅動程式在核心驅動框架中的抽象
關鍵元素1:name,驅動程式的名字,很重要,經常被用來作為驅動和裝置的匹配依據
關鍵元素2:probe,驅動程式的探測函式,用來檢測一個裝置是否可以被該驅動所管理

(2)Kobject和物件生命週期
kobject是核心裡面一個高度抽象的結構體,用來表示核心裡面的一個物件,就是核心裡面所有物件抽象出來的一個總類
(3)sysfs,虛擬檔案系統,在核心空間和使用者空間之間建立了一個對映關係。
(4)udev,是為了實現核心空間和使用者空間資訊的同步,可以讓使用者空間及時得知核心空間的狀況。
(5)裝置驅動模型負責統一實現和維護一些特性,譬如:電源管理、熱插拔、物件生命週期、使用者空間和驅動空間的互動等基礎設施。裝置驅動模型目的是簡化驅動程式編寫,但是客觀上裝置驅動模型本身設計和實現很複雜。

7、裝置驅動模型的底層架構
(1)kobject ,linux裝置驅動中最原始的,最基本的結構體,這個結構體作為別的結構體中的一個成員的方式存在的,提供一些公用型服務如:物件引用計數、維護物件連結串列、物件上鎖、對使用者空間的表示。