歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> 關於Linux >> 設備模型1

設備模型1

日期:2017/3/1 11:42:46   编辑:關於Linux

作為開頭篇,我不想寫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;
intid;
structdevicedev;
u32num_resources;
struct resource* resource;

const structplatform_device_id*id_entry;


structpdev_archdataarchdata;
};

在我們寫的驅動裡,我們常常這麼定義一個設備:platform_devicexxx_device,而不是直接devicedev。

在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 ints3c_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 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 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等等總線以及設備也采取的是大致的流程。


Copyright © Linux教程網 All Rights Reserved