設備模型1

作為開頭篇,我不想寫HELLLOWORLD驅動,甚至字符設備驅動的開發,這樣文章充斥在各大網站上的博客上,隨便搜搜,就可以找到幾百篇。這是最基本的東西,通過這些內容的學習,我們要掌握LINUX驅動的基本要素,比如初始化函數,退初函數,以及去理解簡單的驅動的MAKEFILE的編寫,推薦去看LDD,這方面有比較詳細的敘述。

但是我的理解,即使我們會寫這些東西,對我們的工作也沒有太大的用處,如果你深入去讀LINUX的驅動代碼,就會發現,你還是云里霧里。比如觸摸屏是字符設備,可是怎么我看到的觸摸屏驅動里根本看不到一點字符設備的影子。OHOH...,因此我覺得學習LINUX驅動,要跳出一個圈子,不能把自己局限在某個驅動的編寫上,而應該把重心放在模型或者架構的層次上去掌握它,只有這樣才能更快地深入地理解LINUX驅動的精髓。

因此在看過HELLWORLD或者寫過一個簡單的字符設備驅動之后,應該迅速地去學習LINUX的設備模型,這是我們必須第一個要學習的模型,也是最重要一個模型。如果你不能理解它,你將掉進驅動的泥潭里。

?

學習之前,不要忘記,源碼,源碼!!!在看任何關于LINUX的文章的時候,都要把SOURCEINSIGHT打開,隨時要做好準備去查源碼,源碼是理解任何LINUX驅動的捷徑。

不要忘記這個目錄Documentation,你會有意想不到的發現,這是LINUX開發者留給我們的精華。

?

LINUX設備模型四要素

linux設備模型的抽象是總線、設備、驅動,類。按照這個順序來分析就可以勾勒出linux設備模型。

很多人看設備模型,會選擇直接去學習Kobject、Kset 和Subsystem(在2.6其實也是KSET),進入了個大坑讓人直接對LINUX驅動模型的理解望而生畏。這么復雜的東西,實在太難以理解了。我們是不是可以試著先忽略掉它們,你們是誰呀,我不想知道你們,滾一邊去。

我們先簡單的講一下總線、設備、驅動的關系(類到下一節再來說)。在LINUX驅動的世界里,所有的設備和驅動都是掛在總線上的,也就是總線來管理設備和驅動的,總線知道掛在它上邊的所有驅動和設備的情況,由總線完成驅動和設備的匹配和探測。至于怎么實現,以后再講,現在只要姐的這一點就可以了。讓我們想象以前我們工作碰到的各種總線,I2C,SPI,PCI等等,看來總線也不神秘呀。如果你夠聰明的話,估計你要問,有些設備不是直接連在總線上的呀,LINUX如何管理的呢?比如RTC,那我先告訴你,針對SOC(什么是SOC,自己去BAIDU去)上一些外設,系統已經虛擬了一個PLATFORM總線,更準確的說,是一套PLATFORM總線,設備,驅動, 甚至I2C這些總線也是掛在這個PLARFORM上的。是不是迷糊了? 那就暫時忘記最后半句話。

在我們的腦海中,應該有這個概念,總線上掛著驅動和設備,一個驅動可以管理多個設備,一個設備保存一個對應驅動的信息,一般在初始化的時候,總線先初始化,然后設備先注冊,最后驅動去找設備,完成他們之間的銜接。

我們要不要去寫一個總線驅動呢?可以說99.999999%的人都不需要,系統已經給我們準備好了我們所學要的總線。對于我們來說,就是去學好怎么在系統中添加設備以及相關的驅動就行了。我是沒寫過任何總線的驅動,所以我們看看設備和驅動,回頭再看總線。

?

LINUX設備

在底層,LINUX設備都可以用DEVICE結構的一個實例來表示:

struct device {
?struct device_type?*type;

?structbus_type?*bus;??
?struct device_driver*driver;?
?void??*platform_data;?
?};

在這個節都中還包含著許多其他的結構成員,只是我們現在暫時不考慮,否則又要出現很多個為什么了。我們現在只關心這幾個成員:

1)設備類型

2)設備所掛的總線

3)設備的驅動

4)設備的私有數據

可以看出,描述設備的結構已經把自身和總線以及設備關聯起來,一般情況下,我們也不會這么定義一個設備。為了描述一個設備,常常把設備其他信息和這個結構定義在一起來描述特定的設備。以我們之前提到的虛擬的PLARFORMDEVICE為例:

struct platform_device {
?const char?* name;
?int??id;
?structdevice?dev;
?u32??num_resources;
?struct resource?* resource;

?const structplatform_device_id?*id_entry;

?
?structpdev_archdata?archdata;
};

在我們寫的驅動里,我們常常這么定義一個設備:platform_device?xxx_device,而不是直接device?dev。

在S3C系列中,它所支持的大部分設備都是在common-sdk.c和MACH-SMDK***.C中定義好,在板級初始化的時候通過smdkXXXX_init(void)把設備添加到系統中->調用platform_add_devices。而這個函數platform_add_devices的參數smdk_devs則包含系統了S3C上支持的設備。

//共用的

static struct platform_device __initdata *smdk_devs[] = {
?&s3c_device_nand,
?&smdk_led4,
?&smdk_led5,
?&smdk_led6,
?&smdk_led7,
};

//具體芯片的

static struct platform_device *smdk2410_devices[] __initdata ={
?&s3c_device_ohci,
?&s3c_device_lcd,
?&s3c_device_wdt,
?&s3c_device_i2c0,
?&s3c_device_iis,
};

在MACH-SMDK***.C文件(比如MACH-SMDK2410.C)的最后幾行看看MACHINE_START->smdk2410_init->smdk_machine_init()->platform_add_devices,就這樣完成了板子上主要設備的注冊,掛在PLATFORM總線上了。

關于換個PLATFORMDEVICE,還有一個很關鍵的地方就是該結構一個重要的元素是resource,該元素存入了最為重要的設備資源信息,定義在kernel\include\linux\ioport.h中,
struct resource {
?const char *name;
?unsigned long start, end;
?unsigned long flags;
?struct resource *parent, *sibling, *child;
};
具體可以這么定義:

static struct resource s3c_lcd_resource[] = {
?[0] = {
??.start = S3C24XX_PA_LCD,
??.end??= S3C24XX_PA_LCD + S3C24XX_SZ_LCD - 1,
??.flags = IORESOURCE_MEM,
?},
?[1] = {
??.start = IRQ_LCD,
??.end??= IRQ_LCD,
??.flags = IORESOURCE_IRQ,
?}

};
這里定義了兩組resource,它描述了一個LCD設備的資源,第1組描述了這個LCD設備所占用的
總線地址范圍,也就是DATASHEET上LCD控制器的寄存器地址的范圍IORESOURCE_MEM表示第1組描述的是內存類型的資源信息,第2組描述了這個LCD設備的中斷號,IORESOURCE_IRQ表示第2組描述的是中斷資源信息。設備驅動會根據flags來獲取相應的資源信息。

?

設備驅動

在LINUX中,一個設備驅動是以device_driver這個結構描述的(還包含著許多其他的結構成員,只是我們現在暫時不考慮)。

struct device_driver {
?constchar??*name;
?structbus_type??*bus;

?int (*probe) (struct device *dev);
?struct driver_private *p;
};

driver_register用來把去總掛接到總線上。

和DEVICE類似,在寫驅動的時候,我們也會把device_driver 包裝一下,比如platform_driver。

struct platform_driver {
?int (*probe)(struct platform_device *);
?int (*remove)(struct platform_device *);
?void (*shutdown)(struct platform_device *);
?int (*suspend)(struct platform_device *,pm_message_t state);
?int (*resume)(struct platform_device *);
?struct device_driverdriver;
?const structplatform_device_id *id_table;
};

我們定義一個LCD驅動:

static struct platform_driver s3c_fb_driver = {
?.probe??=s3c_fb_probe,
?.remove??=__devexit_p(s3c_fb_remove),
?.id_table?=s3c_fb_driver_ids,
?.driver??={
??.name?="s3c-fb",
??.owner?=THIS_MODULE,
??.pm?=&s3cfb_pm_ops,
?},
};

在模塊初始化的時候,調用platform_driver_register調用driver_register把注冊platform_driver到PLATFORM總線上,去看看platform_driver_register的實現就什么都清楚了。

int platform_driver_register(struct platform_driver *drv)
{
?drv->driver.bus =&platform_bus_type;
?if(drv->probe)
??drv->driver.probe= platform_drv_probe;
?if (drv->remove)
??drv->driver.remove= platform_drv_remove;
?if (drv->shutdown)
??drv->driver.shutdown= platform_drv_shutdown;

?returndriver_register(&drv->driver);
}

在一個驅動中,最最重要的一個接口就是probe函數,它負責去獲取對應DEVICE的數據信息,然后初始化這個設備。如果完成這個函數,我們就完成了驅動的大部分工作了。暫時先停住,在具體的驅動分析中,我們再來考慮這個問題。

到目前為止,我們還沒有說清楚這個DEVICE和DRIVER是怎么聯系起來的。正如你想到的,我們要去總線那里去看看,它是設備模型中的管理者嘛,因此把驅動和設備聯系起來,就是它主要的工作了。

?

?

總線

我們直接看代碼,看看總線在系統中是如何表述的:

struct bus_type {
?constchar??*name;
?int (*match)(struct device *dev, structdevice_driver *drv);
?int (*probe)(struct device *dev);
};

name 就是總線的名字,比如PLATFORM,PCI,I2C等等。

對于總線,PLATFORM總線倒是沒有封裝,直接采用下邊方法定義:

struct bus_type platform_bus_type = {
?.name??="platform",
?.match??=platform_match,
?};

?

這條總線在系統初始化的時候通過platform_bus_init來初始化,顯然這個動作應該驅動注冊之前完成。

總線是用MATCH方法來完成驅動和設備的聯系的。當總線上的新設備或者新的驅動程序被添加的時候,會來調用這個函數。如果指定的驅動程序能夠處理指定的設備,干函數就返回非0,去執行驅動的PROBE函數.

?

我們結合驅動的注冊來看看這個過程是什么樣子的?

在驅動初始化函數中調用函數platform_driver_register()注冊platform_driver,需要注意的是
platform_device結構中name元素和platform_driver結構中driver.name必須是相同的,這樣
在platform_driver_register()注冊時會對所有已注冊的所有platform_device中的name和當前注
冊的platform_driver的driver.name進行比較,只有找到相同的名稱的platfomr_device才能注冊
成功,當注冊成功時會調用platform_driver結構元素probe函數指針。

?

platform_driver_register

????driver_register

????????bus_add_driver
???????????driver_attach

??????????????__driver_attach

????????????????driver_match_device

?????如果MATCH成功,測開始PROBE.

????????????????driver_probe_device

?????????????really_probe

??????????dev->bus->probe(dev)

???????dev->probe
platform_driver->probe

?

這樣就直接走到我們驅動PROBE函數中,這個過程很負責,但是對于我們來說只要記住亮點:

1)MATCH的標準: NAME 要相同,或者有的驅動和設備支持ID

2)MATCH成功,我們就轉向驅動的PROBE函數。

?

我們來看一個驅動的PROBE函數:

static int?s3c_fb_probe(struct platform_device*pdev)
{


?struct s3c_fb_driverdata *fbdrv;
?struct device *dev =&pdev->dev;
?struct s3c_fb_platdata *pd;
?struct s3c_fb *sfb;
?struct resource *res;
?int win;
?int ret = 0;

//這個數據是在設備定義的時候定義的,就是我們前面看到的內存,IRQ等資源

?fbdrv = (struct s3c_fb_driverdata*)platform_get_device_id(pdev)->driver_data;

?pd =pdev->dev.platform_data;
?sfb = kzalloc(sizeof(struct s3c_fb),GFP_KERNEL);
?dev_dbg(dev, "allocate new framebuffer %p\n",sfb);

?sfb->dev = dev;
?sfb->pdata = pd;
?sfb->variant =fbdrv->variant;

//獲取時鐘,并且ENABLE它

?sfb->bus_clk = clk_get(dev,"lcd");
?clk_enable(sfb->bus_clk);

//處理來自驅動的資源,記著去ioremap,為什么呢?見下邊。

?res = platform_get_resource(pdev,IORESOURCE_MEM, 0);
?sfb->regs_res =request_mem_region(res->start,resource_size(res),
???????dev_name(dev));
?sfb->regs =ioremap(res->start, resource_size(res));
?res = platform_get_resource(pdev, IORESOURCE_IRQ,0);
?sfb->irq_no =res->start;
?ret = request_irq(sfb->irq_no,s3c_fb_irq,
????0, "s3c_fb", sfb);
?platform_set_drvdata(pdev, sfb);

?

//初始化硬件
?

?pd->setup_gpio();

?writel(pd->vidcon1,sfb->regs + VIDCON1);

?

?for (win = 0; win <fbdrv->variant.nr_windows; win++)
??s3c_fb_clear_win(sfb, win);

?
?for (win = 0; win <(fbdrv->variant.nr_windows - 1); win++) {
??void __iomem *regs =sfb->regs + sfb->variant.keycon;

??regs += (win * 8);
??writel(0xffffff, regs +WKEYCON0);
??writel(0xffffff, regs +WKEYCON1);
?}

?

?for (win = 0; win <fbdrv->variant.nr_windows; win++) {
??if(!pd->win[win])
???continue;

??if(!pd->win[win]->win_mode.pixclock)
???s3c_fb_missing_pixclock(&pd->win[win]->win_mode);

??ret = s3c_fb_probe_win(sfb,win, fbdrv->win[win],
??????????&sfb->windows[win]);
??if (ret < 0){
???dev_err(dev,"failed to create window %d\n", win);
???for (; win>= 0; win--)
????s3c_fb_release_win(sfb,sfb->windows[win]);
???gotoerr_irq;
??}
?}

?platform_set_drvdata(pdev, sfb);
?pm_runtime_put_sync(sfb->dev);

?return 0;

//出錯處理

?return ret;
}

這里說明一下如何獲取資源的:

當進入probe函數后,需要獲取設備的資源信息,獲取資源的函數有:
struct resource * platform_get_resource(struct platform_device*dev, unsigned int type, unsigned int num);
根據參數type所指定類型,例如IORESOURCE_MEM,來獲取指定的資源。
struct int platform_get_irq(struct platform_device *dev, unsignedint num);
獲取資源中的中斷號。
struct resource * platform_get_resource_byname(structplatform_device *dev, unsigned int type, char *name);
根據參數name所指定的名稱,來獲取指定的資源。
int platform_get_irq_byname(struct platform_device *dev, char*name);
根據參數name所指定的名稱,來獲取資源中的中斷號。

ioremap是用來把資源中定義的物理地址轉換成內核虛擬地址的,我們的代碼中用到的地址是虛擬地址,必須做這樣的轉換喲。還有一種靜態的轉換方法,我們以后再來看。

在ioremap之后,我們就可以讀寫外設的寄存器了,比如控制寄存器,狀態寄存器,數據寄存器等等,這樣就可以操作外圍設備了。

?

當然,還有一些其他東西沒有提,對應各種注冊,添加函數,同樣存在注銷,移除等函數,基本就是做相反的操作,只要稍微看看就行了。

?

上邊的例子是以PLATFORM 的總線,設備和驅動來講的,其實I2C等等總線以及設備也采取的是大致的流程。


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

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

相關文章

如何使用Android Studio把自己的Android library分享到jCenter和Maven Central

第一部分&#xff1a;在bintray上創建package首先&#xff0c;你需要在bintray上創建一個package。為此&#xff0c;你需要一個bintray賬號&#xff0c;并在網站上創建一個package。第一步&#xff1a;在bintray.com上注冊一個賬號。&#xff08;注冊過程很簡單&#xff0c;自己…

python2編碼_Python2字符編碼

我們通常見到的字符串編碼主要是三種GB2312/GBK、Unicode、UTF-8。GB2312/GBK是多字節(multibytes)編碼的一種&#xff0c;屬于“ASCII的加強版”&#xff0c;與之平行的由Big5、ShiftJIS之類的編碼各自為政&#xff0c;所有這些用兩個字節表示漢字的多字節編碼標準統稱為ANSI編…

angularJs關于指令的一些冷門屬性

我們使用ng的時候&#xff0c;經常會使用到指令&#xff0c;大家所熟知的屬性我在這里就不介紹了&#xff0c;講講大家沒怎么留意的屬性 1.multiElement 這是指定指令作用區間的功能&#xff0c;最常用的就是ng-repeat-start和ng-repeat-end了。 2.priority 指令優先級&#xf…

設備模型2

前言 在上一篇中&#xff0c;我們大致描述了LINUX設備模型&#xff0c;我們先來總結一下三要素的關系。 從圖中可以看出,Linux設備模型就是"總線、設備、驅動、類"這四個概念之前的相互關系;這也是Linux2.6內核抽象出來的用于管理系統中所有設備的模型圖; 簡單地描述…

angular自定義指令詳解

指令&#xff08;directive&#xff09;是angular里面最核心也是最難懂的東西&#xff0c;在慕課網看了下大漠窮秋老濕的視頻&#xff0c;自己百度半天做了一些小test&#xff0c;總算把一切都搞明白了。 先列出學習來源&#xff1a; 指令中controller和link的區別&#xff1a;…

delphi7aes加密解密與java互轉_跨語言(java vs python vs nodejs)的RSA加解密問題探討

多次被問到這樣的問題&#xff1a;java服務端的rsa加密操作已經完成&#xff0c;返回一個16進制的字符串給python平臺&#xff0c;但是在python進行私鑰解密的時候發現行不通。。。。前端python加密&#xff0c;后端用java解密&#xff0c;解不出來還有諸如nodejs從理論上來說&…

類的定義、成員定義修飾符

類的定義 修飾符含義1無或internal 只能在當前項目中訪問類&#xff0c;其它項目引用后也無法訪問2public 可以任何地方訪問類3abstract 不能實例化&#xff0c;只能供繼承之用4sealed 不能供派生之用&#xff0c;只能實例化5internal abstract136public abstract237intern…

設備模型3

在上一篇分析中&#xff0c;多次提到了SYSFS&#xff0c;這是個什么東西&#xff1f;這可是個很大的TOPIC&#xff0c;關于它的講述可以寫本書&#xff0c;但是我們暫時的目標不是要完全啃投它所有的東西&#xff0c;沒時間&#xff0c;沒精力&#xff0c;我們只要掌握我們需要…

浪潮之巔讀后感

這幾天看到一本非常好的書《浪潮之巔》。浪潮&#xff0c;指的是時代的大潮。而浪潮之巔&#xff0c;顧名思義就是指站在時代潮流的巔峰&#xff0c;引領時代潮流&#xff0c;扛起發展大旗。而本書就是在介紹各大處在浪潮之巔的IT公司的歷史及發展。 書中有句話說得好&#xff…

利用dft的定義計算dft的matlab程序_CP2K教程系列之靜態計算(Pymatflow篇)

本系列CP2K教程是《CP2K菜根譚》的升級版&#xff0c;在舊版基礎上添加了如何結合Pymatflow工具簡化計算流程的內容。話不多說&#xff0c;本文將為您帶來CP2K系列教程中的靜態計算部分。靜態計算設置靜態計算是大多數人接觸第一性原理計算后第一次運行的計算類型。很多其它類型…

機器學習:最大似然估計與最大后驗概率估計

在機器學習領域&#xff0c;概率估計是經常用到的一種模型&#xff0c;而概率估計中&#xff0c;我們經常會看到兩種估計模型&#xff0c;一種是最大似然估計&#xff0c;即 Maximum likelihood&#xff0c; 另外一種就是最大后驗概率估計&#xff0c;即 Maximum posterior &am…

ubuntu14.04安裝git

參考教程&#xff1a;git介紹&#xff1a;安裝&#xff0c;使用&#xff0c;創建分支 安裝的方法有兩種&#xff0c;一種直接是通過ubuntu的APT安裝&#xff0c;這種方法最簡便&#xff0c;缺點是版本可能不是最新的。所有還有另一種方法是下載源碼進行安裝&#xff0c;這種能…

微軟是如何戲耍程序員們的

2019獨角獸企業重金招聘Python工程師標準>>> 別用微軟的東西。商業目的性太強&#xff0c;千萬別被微軟牽著鼻子走&#xff0c;血淋淋的教訓。微軟推出的垃圾多了去了。它什么都想做&#xff0c;很多都沒做好&#xff1a; MFC&#xff1a;Win31時代出生&#xff0c;…

3d立體相冊特效html網頁代碼_新聞類網頁正文通用抽取器

項目起源開發這個項目&#xff0c;源自于我在知網發現了一篇關于自動化抽取新聞類網站正文的算法論文——《基于文本及符號密度的網頁正文提取方法》這篇論文中描述的算法看起來簡潔清晰&#xff0c;并且符合邏輯。但由于論文中只講了算法原理&#xff0c;并沒有具體的語言實現…

ubuntu14.04安裝 R16 Tina Linux SDK

第一步&#xff1a;由于是14.04系統所以這樣$sudo -i $cd /etc/apt/sources.list.d $echo "deb http://old-releases.ubuntu.com/ubuntu/ raring main restricted universe multiverse" > ia32-libs-raring.list $apt-get update $apt-get install ia32-libs $r…

分答

用戶在分答上可以自我介紹或描述擅長的領域&#xff0c;設置付費問答的價格&#xff0c;其他用戶感興趣就可以付費向其提問。分答的收入分配機制增加了信息的價值&#xff0c;分答中信息接收者被區分成了兩種身份。首先提問的人是一種付費懸賞模式&#xff0c;當回答完畢內容沉…

字節數組轉換為圖片_每日一課 | Python 3 TypeError:無法將“字節”對象隱式轉換為str...

將Python 2套接字示例轉換為Python 3whois.py import sysimport sockets socket.socket(socket.AF_INET, socket.SOCK_STREAM)s.connect(("whois.arin.net", 43))s.send((sys.argv[1] "\r\n").encode())response ""while True: data s.re…

php版redis插件,SSDB數據庫,增強型的Redis管理api實例

php版redis插件,SSDB數據庫,增強型的Redis管理api實例 SSDB是一套基于LevelDB存儲引擎的非關系型數據庫&#xff08;NOSQL&#xff09;&#xff0c;可用于取代Redis&#xff0c;更適合海量數據的存儲。另外&#xff0c;rocksdb是FB在LevelDB的二次開發版本&#xff0c;因此也存…

加速度計和陀螺儀數據融合

本帖翻譯自 IMU&#xff08;加速度計和陀螺儀設備&#xff09;在嵌入式應用中使用的指南。這篇文章主要介紹加速度計和陀螺儀的數學模型和基本算法&#xff0c;以及如何融合這兩者&#xff0c;側重算法、思想的討論介紹本指南旨在向興趣者介紹慣性MEMS&#xff08;微機電系統&a…

循環嵌套練習題

//BOSS://讓用戶輸入一個奇數&#xff0c;打印菱形&#xff0c;最長的行內容個數為用戶輸入的個數&#xff0c;并且由英文字母拼接而成//比如用戶輸入了7// A// ABA// ABCBA// ABCDCBA// ABCBA// ABA// A//1、接收并判斷用戶輸入的是不是數字 try{#region 解法一…