xillybus_0:xillybus@50000000 {
compatible = "xlnx,xillybus-1.00.a";
reg = < 0x50000000 0x1000 >;
interrupts = < 0 59 1 >;
interrupt-parent = <&gic>;
xlnx,max-burst-len = <0x10>;
xlnx,native-data-width = <0x20>;
xlnx,slv-awidth = <0x20>;
xlnx,slv-dwidth = <0x20>;
xlnx,use-wstrb = <0x1>;
} ;
} ;
這里只列出原始DTS文件中的兩個(gè)設(shè)備。
第一個(gè)條目:Zynq處理器的中斷控制器。這個(gè)條目確保中斷控制器被加載。注意它的標(biāo)簽是“gic"。這個(gè)標(biāo)簽被每個(gè)使用中斷的設(shè)備引用。
終于可以講述最有趣的部分了:以上說的這些如何與內(nèi)核代碼配合工作。
關(guān)于內(nèi)核驅(qū)動(dòng)
設(shè)備驅(qū)動(dòng)加載和卸載時(shí)有四件事情會(huì)發(fā)生:
. 硬件存在時(shí)(比如在設(shè)備樹中聲明),內(nèi)核代碼加載相應(yīng)驅(qū)動(dòng)
. 驅(qū)動(dòng)需要了解設(shè)備的物理地址
. 驅(qū)動(dòng)需要了解設(shè)備觸發(fā)的中斷號(hào),用來注冊(cè)中斷處理函數(shù)。
. 一些特殊信息需要被獲取
內(nèi)核中有直接訪問設(shè)備樹的API,但是設(shè)備驅(qū)動(dòng)使用專用接口更方便,這些專用接口受PCI/PCIe驅(qū)動(dòng)的API影響。來看下xillybus_0條目,這是一個(gè)掛載于AXI總線上的典型邏輯設(shè)備。
標(biāo)簽和節(jié)點(diǎn)名
首先,標(biāo)簽("xillybus")和條目名()。標(biāo)簽可以省略,條目節(jié)點(diǎn)名的格式為(),最后在/sys下產(chǎn)生一個(gè)標(biāo)準(zhǔn)的條目(/sys/devices/axi.0/50000000.xillybus/)。,不過內(nèi)核肯定不是從這里訪問設(shè)備樹的。
驅(qū)動(dòng)自動(dòng)加載
節(jié)點(diǎn)中的第一個(gè)賦值語句compatible = “xlnx,xillybus-1.00.a”是最重要的一句:它連接硬件和驅(qū)動(dòng)。當(dāng)內(nèi)核在總線上掃描設(shè)備時(shí)(設(shè)備節(jié)點(diǎn)在設(shè)備樹里掛在一個(gè)總線節(jié)點(diǎn)下),內(nèi)核檢索"compatible"字段,然后將其字符串與一些已知的字符串比較。這個(gè)過程會(huì)在啟動(dòng)時(shí)自動(dòng)發(fā)生兩次:
. 內(nèi)核啟動(dòng)時(shí),編譯進(jìn)內(nèi)核的驅(qū)動(dòng)與設(shè)備樹中某個(gè)"compatible"條目匹配
. 之后加載內(nèi)核模塊時(shí),再觸發(fā)一次匹配操作
內(nèi)核驅(qū)動(dòng)和"compatible"條目的連接由驅(qū)動(dòng)代碼中的一小段完成:
static struct of_device_id xillybus_of_match[] __devinitdata = {
{ .compatible = "xlnx,xillybus-1.00.a", },
{}
};
MODULE_DEVICE_TABLE(of,xillybus_of_match);
這段代碼使得驅(qū)動(dòng)與某一個(gè)"compatible"條目匹配。注意上面的id表中有一個(gè)空結(jié)構(gòu),用這個(gè)空意緒標(biāo)志id表的結(jié)束。
在上段代碼之后,一定有類似如下的一段代碼:
static struct platform_driver xillybus_platform_driver = {
.probe = xilly_drv_probe,
.remove = xilly_drv_remove,
.driver = {
.name = "xillybus",
.owner = THIS_MODULE,
.of_match_table = xillybus_of_match,
},
};
platform_driver_register(&xillybus_platform_driver)在模塊初始化里被調(diào)用。這個(gè)結(jié)構(gòu)告訴內(nèi)核,當(dāng)驅(qū)動(dòng)與某個(gè)硬件匹配時(shí),xilly_drv_probe 被調(diào)用。
對(duì)內(nèi)核來說,"compatible"字串需要與某個(gè)驅(qū)動(dòng)名相同?!眡lnx"前綴用于防止名字沖突。
另外,一個(gè)設(shè)備可以有多個(gè)"compatible"。因?yàn)橐粋€(gè)設(shè)備可以有多個(gè)模塊對(duì)應(yīng)多個(gè)驅(qū)動(dòng)。
可能會(huì)需要匹配硬件的名字和類型,但這不常用。
寫內(nèi)核模塊時(shí)需要特別注意,自動(dòng)加載機(jī)制依賴于/lib/modules/{kernel version}/modules.ofmap文件中的"compatible"字串,其他定義文件也在這個(gè)目錄下。正確的方式是把*.ko文件復(fù)制到/lib/modules/{kernelversion}/kernel/drivers/下的相關(guān)目錄中,然后:
depmod -a
獲取資源信息
內(nèi)核模塊驅(qū)動(dòng)加載之后,就開始把硬件資源管理起來,如讀寫寄存器、接收中斷。
來看看設(shè)備樹里的一條:
xillybus_0: xillybus@50000000 {
compatible = "xlnx,xillybus-1.00.a";
reg = < 0x50000000 0x1000 >;
interrupts = < 0 59 1 >;
interrupt-parent = <&gic>;
xlnx,max-burst-len =<0x10>;
xlnx,native-data-width = <0x20>;
xlnx,slv-awidth = <0x20>;
xlnx,slv-dwidth = <0x20>;
xlnx,use-wstrb = <0x1>;
} ;
驅(qū)動(dòng)一般在探測(cè)函數(shù)里就取得了硬件內(nèi)存段的所有權(quán)(探測(cè)函數(shù)就是probe指針指向的函數(shù))。
來看看一個(gè)典型探測(cè)函數(shù)的框架:
static int __devinit xilly_drv_probe(struct platform_device *op)
{
const struct of_device_id *match;
match =of_match_device(xillybus_of_match, &op->dev);
if (!match)
return -EINVAL;
第一個(gè)操作就是檢查probe是否作用在相關(guān)硬件上。
訪問寄存器
下一步,分配一段內(nèi)存并映射到虛擬內(nèi)存中。
int rc = 0;
struct resource res;
void *registers;
rc = of_address_to_resource(&op->dev.of_node,0, &res);
if (rc) {
/* Fail */
}
if(!request_mem_region(res.start, resource_size(&res), "xillybus")){
/* Fail */
}
registers =of_iomap(op->dev.of_node, 0);
if (!registers) {
/* Fail */
}
of_address_to_resource() 在設(shè)備樹中找到第一個(gè)"reg",并將解析到的信息填充在"res"結(jié)構(gòu)體里。這個(gè)例子里"reg = <0x50000000 0x1000 >”, 指的是分配一塊起始物理地址是0x50000000,長度為0x1000字節(jié)的空間。of_address_to_resource()會(huì)設(shè)置res.start =0x50000000, res.end = 0x50000fff。
調(diào)用request_mem_region()是為了注冊(cè)特殊的內(nèi)存段。目的是避免兩個(gè)驅(qū)動(dòng)訪問同一段寄存器空間而造成的沖突。resource_size()是個(gè)內(nèi)聯(lián)函數(shù),返回segment的大小(此處是0x1000)。
of_iomap()函數(shù)是of_address_to_resource()和ioremap()的組合,本質(zhì)上等效于ioremap(re.start, resource_size(&res)).確保物理段已經(jīng)映射到虛擬內(nèi)存中,函數(shù)返回內(nèi)存段的虛擬地址空間起始地址。
顯然,當(dāng)模塊卸載或某個(gè)錯(cuò)誤發(fā)生時(shí),這些操作都需要有恢復(fù)動(dòng)作。
訪問硬件寄存器請(qǐng)使用iowrite32(),ioread32()以及其他的函數(shù)和宏,而不要直接使用上面的"register"指針。
中斷處理
這部分的驅(qū)動(dòng)很簡單,類似如下:
irq = irq_of_parse_and_map(op->dev.of_node, 0);
rc = request_irq(irq,xillybus_isr, 0, "xillybus", op->dev);
irq_of_parse_and_map()在設(shè)備樹里查找中斷的描述項(xiàng),然后返回中斷號(hào),request_irq()將使用這個(gè)中斷號(hào)來注冊(cè)。第二個(gè)參數(shù)是0,表示使用設(shè)備樹中的第一個(gè)中斷。
設(shè)備樹里面描述是:
interrupts = < 0 59 1 >;
interrupt-parent = <&gic>;
那么使用了這三個(gè)數(shù)據(jù)中的哪一個(gè)呢?
第一個(gè)0是一個(gè)標(biāo)志,用于指示中斷是否是SPI(共享中斷,shared peripheral interrupt)。非0值表示它是SPI。事實(shí)上在Zynq硬件上,這些中斷都是共享的,這里是為了方便才寫0, 軟件上認(rèn)為它不共享。
第二個(gè)數(shù)據(jù)表示中斷號(hào)。
第三個(gè)數(shù)字是中斷類型,可以有如下值:
0 - 內(nèi)核不改變它,開機(jī)或uboot設(shè)置它是什么樣就什么樣。
1 - 上升沿觸發(fā)
4 - 電平觸發(fā),高電平表示來中斷。
不允許有其他值,下降沿觸發(fā)和低電平中斷目前不支持,因?yàn)橛布恢С帜切┠J?。如果需要這樣的觸發(fā)方式,就得在硬件上加一個(gè)非門。
值得注意的是第三個(gè)數(shù)字在設(shè)備樹里通常都是0, 所以Linux內(nèi)核不去改變中斷模式。這通常意味著高電平觸發(fā)。這也讓驅(qū)動(dòng)依賴于bootloader里的設(shè)置。
interrupt-parent 這一句,必須指向中斷控制器&gic。如果反編譯一個(gè)DTB文件,這里的&gic會(huì)被一個(gè)數(shù)字代替,通常是0x1。
Application-specific data
之前提過,設(shè)備樹中是一些特殊信息,這樣一個(gè)驅(qū)動(dòng)可以管理數(shù)片類似的硬件。例如,一個(gè)LCD顯示驅(qū)動(dòng),分辨率信息和物理尺寸可能出現(xiàn)在設(shè)備樹中。串口信息要告訴驅(qū)動(dòng)當(dāng)前的時(shí)鐘頻率。
最簡單的,最常用的形式,這個(gè)信息由一條賦值語句組成:
xlnx,slv-awidth = <0x20>;
"xlnx"前綴可以防止命名沖突。名字可以任意取,但最好能望文知意。這里的"xlnx"是使用軟件自動(dòng)生成設(shè)備樹時(shí)加上的前綴。
為了抓取到這一條信息,代碼可以這樣寫:
void *ptr;
ptr = of_get_property(op->dev.of_node, "xlnx,slv-awidth", NULL);
if (!ptr) {
/* Couldn't find the entry */
}
第三個(gè)參數(shù)NULL,是一個(gè)長度指針,可以返回?cái)?shù)據(jù)的長度。
這條語句的值是一個(gè)數(shù)字:
int value;
value = be32_to_cpup(ptr);
be32_to_cpup讀“ptr”指向的數(shù)據(jù),從大端轉(zhuǎn)到處理器的小端,然后就得到想要的數(shù)字了。
drivers/of/base.c中有大量讀取這些信息的API。
總結(jié)
為一個(gè)外置寫一個(gè)設(shè)備樹entry很簡單:
. 為"compatible"賦一個(gè)字符串"magicstring",自動(dòng)生成工具的生成格式一般是:名字+版本。
. 在數(shù)據(jù)手冊(cè)里查看總線上設(shè)備的地址分配信息, 寫一條 "reg=" 語句。
. "interrupt-parent=<&gic>"
. 中斷號(hào) "interrupt="
. 最后加上一些設(shè)備的自定義參數(shù)
評(píng)論
查看更多