背景
在ZYNQ 平台下,需要對各種需要的底層接口進行初始化。
我依次調試了很多驅動,從最簡單的網口到USB;再到讀寫PL端的寄存器(通過AXI總線,內存映射讀寫物理地址實現),到中斷的時候一直卡着不動。
我調試了很久,最終調完了,回顧自己這1個月的調試,還是有很多的感想。
里程碑
確保PL中斷正常
我通過開發板供應商提供的文檔,完成了在PS-standalone端的裸機中斷驗證。
根據文檔,我也獲取到了對應的硬件中斷號:61(之后因為PL的改動,變成了63,但是關系不大)
同時,我編寫了有關的行動記錄,明確地記錄了每一步做了什么,方便以后復現。
但是,由於缺少管理,導致各種文檔重復的地方很多,難以尋找。(反思:定期整理)
加強調試速度
由於在ZYNQ平台,事先使用了PetaLinux,並將常見的操作整理成了腳本,同時,使用脫機化的構建方式。
這一步是在其他驗證中同時做好的,但是我想,如果沒有這一步,后面的調試會花費我大部分的時間。
同時,我還專門搭建了一個虛擬機,用於直連調試。
因為這樣,我只需要按一下復位按鍵,傳一個文件到tftp服務器的根目錄,就可以完成一次調試。
尋找Linux端驅動實現,失敗
因為Linux的特殊性,一些底層功能的配置需要依賴於Linux的框架。
我在網上找了很多文檔,大致分為3個方向:
1、直接的驅動實現,不依賴設備樹
2、修改設備樹,添加對應的結點
3、在第二種方向的基礎上,使用UIO作為實現。
以及一些有用的信息:
- 如果想要ZYNQ的中斷能夠成功使用:
1、/proc/interrupts
中需要看到對應注冊的中斷。
2、/proc/device-tree/amba_pl/
中需要看到對應的設備樹結點(如果是使用設備樹)。
但是無論是哪個方向,我都試過了,獲取不到我要的中斷。
#錯誤1
type mismatch, failed to map hwirq-61 for /amba/interrupt-controller@f8f01000!
# 錯誤2
insmod: can't insert 'plInt.ko': Invalid argument
root@sd01-usb0-eth0:~# dmesg
genirq: Flags mismatch irq 29. 00000002 (pl-key) vs. 00000084 (mmc1)
can not request irq for pl-key[-16]
此后,根據UIO的有關資料,繼續配置,但是同樣地,沒有結果。
為了不拖累進度,我跑去社區找答案,但是開發板的社區訪問不了,我又去了賽靈思的社區,表達了我遇到的困境,幾天后得到了答復。
有一位專家向我提供了一個驅動模塊(有編程語法錯誤,我還修改了一下),但因為限制於之前經驗的條條框框,我還是以老的文檔作為基點,對設備樹進行改動,並意料之內地失敗了。
翻閱有關資料
我又復習了之前整理的設備樹資料,以及搜索了一些有關的情況。
我根據網友的文檔進行新增的設備樹的屬性應該是沒有問題的,而且我也反復確認過好幾次。
但是我在StackOverflow上看到過關於compatible
屬性可能需要改變。
我曾經懷疑過是否因為PL的配置的問題,即使很清楚standalone下已經確定PL沒問題了;我還是要求同事再配一下電平。
后面依舊沒有結果以后,我就死心了。
為了不被這個難題阻礙開發進度,於是我轉而在“假設中斷能夠正常使用的情況下”,完成了驅動模塊其他方面的設計。
因為被這個難題壓抑了太久,我在另外的設計上用力很猛,也很順利;此后,我又開始面對這個問題。
我開始冷靜思考,重新回顧了我所看過的文檔;基本上這一個月來,網上的文檔我都看過了(包括wiki)。
但是我還是覺得應該再仔細好好看看。
根據何曄: 當ZYNQ遇到Linux Userspace I/O(UIO)文章中提到的:配置內核驅動、對設備樹的修改。
其中將pl.dtsi中的屬性,在system-user.dtsi中重寫。
/include/ "system-conf.dtsi"
/{
usb_phy0: usb_phy@0 {
compatible = "ulpi-phy";
#phy-cells = <0>;
reg = <0xe0002000 0x1000>;
view-port = <0x0170>;
drv-vbus;
};
};
&usb0 {
dr_mode = "host";
usb-phy = <&usb_phy0>;
};
&amba_pl {
// A. 這個改動會覆蓋 pl.dtsi 中的配置
gpio@41300000 {
compatible = "generic-uio","uio"; // 使用UIO配置匹配中斷
interrupts = <0 32 4>; // 從 0-31-4 改成 0-32-4,避免中斷號沖突
};
// B. 新增的節點
uio@0 {
compatible = "generic-uio";
status = "okay";
interrupt-controller;
interrupt-parent=<&intc>;
interrupts = <0 30 4>; // IRQ 31+32 Rising High
reg = <0x0 0x41300000 0x0 0x10000>;
};
uio@1 {
compatible = "generic-uio";
status = "okay";
interrupt-controller;
interrupt-parent=<&intc>;
interrupts = <0 31 4>; // IRQ 32+32 Rising High
reg = <0x0 0x41300000 0x0 0x10000>;
};
};
得到的現象:
1、能夠注冊到一些中斷,但是無法響應。
2、即使是不新增B的節點,使用普通的中斷驅動也可以注冊到中斷了,但是按鍵都要被我按爛了,可中斷死活沒有反應。
於是我繼續思考,應該就是設備樹和中斷驅動之間的問題,中斷號已經正確了,剩下來的事情可能就是在驅動這里了。
回顧賽靈思社區的答復
我根據專家的答案,嘗試性地按我自己的理解配置了一下。
設備樹里只需要把axi_gpio node里的compatible替換為這個module文件里的compatible名字。這個module代碼自動申請中斷資源。
首先,我根據從UIO需要修改設備樹的方法,重寫了gpio中斷的compatible屬性。
cat dts_kernel/system-user.dtsi
/include/ "system-conf.dtsi"
/{
// USB 支持
usb_phy0: usb_phy@0 {
compatible = "ulpi-phy";
#phy-cells = <0>;
reg = <0xe0002000 0x1000>;
view-port = <0x0170>;
drv-vbus;
};
// 重新設置的驅動屬性
amba_pl: amba_pl {
axi_gpio_1: gpio@41300000 {
compatible = "vendor,gpioirq";
};
};
};
// USB 支持
&usb0 {
dr_mode = "host";
usb-phy = <&usb_phy0>;
};
結合之前那位專家提供的驅動模塊:
/* gpioirq.c - The simplest kernel module.
*/
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/io.h>
#include <linux/interrupt.h>
#include <linux/of_address.h>
#include <linux/of_device.h>
#include <linux/of_platform.h>
/* Standard module information, edit as appropriate */
MODULE_LICENSE("GPL");
MODULE_AUTHOR
("Xilinx Inc.");
MODULE_DESCRIPTION
("gpioirq - loadable module template generated by petalinux-create -t modules");
#define DRIVER_NAME "gpioirq"
#define PRINTK_LV "<4>"
#define DATA_0_RO_OFFSET 0x0
#define XGPIO_INTSTS_OFFSET 0x120
#define XGPIO_TRI_OFFSET 0x4
#define XGPIO_GIER_OFFSET 0x11C
#define XGPIO_IER_OFFSET 0x128
/* Simple example of how to receive command line parameters to your module.
Delete if you don't need them */
struct gpioirq_local {
int irq;
unsigned long mem_start;
unsigned long mem_end;
void __iomem *base_addr;
};
static irqreturn_t gpioirq_irq(int irq, void *p)
{
struct gpioirq_local * lp = (struct gpioirq_local *)p;
unsigned int data;
printk(PRINTK_LV "gpioirq interrupt\n");
/*
* Check gpio Value
*/
data = ioread32(lp->base_addr + DATA_0_RO_OFFSET);
data = data;
printk(PRINTK_LV "axigpioirq_isr: Interrupt Occurred ! GPIO input = 0x%08X\n",data);
/*
* Clear Interrupt
*/
data = ioread32(lp->base_addr + XGPIO_INTSTS_OFFSET);
iowrite32(data,
lp->base_addr + XGPIO_INTSTS_OFFSET);
return IRQ_HANDLED;
}
static int gpioirq_probe(struct platform_device *pdev)
{
struct resource *r_irq; /* Interrupt resources */
struct resource *r_mem; /* IO mem resources */
struct device *dev = &pdev->dev;
struct gpioirq_local *lp = NULL;
unsigned int data;
int rc = 0;
printk(PRINTK_LV "Device Tree Probing\n");
/* Get iospace for the device */
r_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!r_mem) {
dev_err(dev, "invalid address\n");
return -ENODEV;
}
lp = (struct gpioirq_local *) kmalloc(sizeof(struct gpioirq_local), GFP_KERNEL);
if (!lp) {
dev_err(dev, "Cound not allocate gpioirq device\n");
return -ENOMEM;
}
dev_set_drvdata(dev, lp);
lp->mem_start = r_mem->start;
lp->mem_end = r_mem->end;
if (!request_mem_region(lp->mem_start,
lp->mem_end - lp->mem_start + 1,
DRIVER_NAME)) {
dev_err(dev, "Couldn't lock memory region at %p\n",
(void *)lp->mem_start);
rc = -EBUSY;
goto error1;
}
lp->base_addr = ioremap(lp->mem_start, lp->mem_end - lp->mem_start + 1);
if (!lp->base_addr) {
dev_err(dev, "gpioirq: Could not allocate iomem\n");
rc = -EIO;
goto error2;
}
/* Get IRQ for the device */
r_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
if (!r_irq) {
printk(PRINTK_LV "no IRQ found\n");
printk(PRINTK_LV "gpioirq at 0x%08x mapped to 0x%08x\n",
(unsigned int __force)lp->mem_start,
(unsigned int __force)lp->base_addr);
return 0;
}
lp->irq = r_irq->start;
rc = request_irq(lp->irq, gpioirq_irq, 0, DRIVER_NAME, lp);
if (rc) {
dev_err(dev, "testmodule: Could not allocate interrupt %d.\n",
lp->irq);
goto error3;
}
printk(PRINTK_LV "gpioirq at 0x%08x mapped to 0x%08x, irq=%d\n",
(unsigned int __force)lp->mem_start,
(unsigned int __force)lp->base_addr,
lp->irq);
/*
* Set gpio direction
*/
iowrite32(0xFFFFFFFF,
lp->base_addr + XGPIO_TRI_OFFSET);
data = ioread32(lp->base_addr + XGPIO_TRI_OFFSET);
printk("axigpioirq_init: gpio channael 0 directions 0x%08X\n",data);
/*
* Enable gpio irq
*/
iowrite32(0x1,
lp->base_addr + XGPIO_IER_OFFSET);
data = ioread32(lp->base_addr + XGPIO_IER_OFFSET);
printk("axigpioirq_init: IER status 0x%08X\n",data);
iowrite32(0x80000000,
lp->base_addr + XGPIO_GIER_OFFSET);
data = ioread32(lp->base_addr + XGPIO_GIER_OFFSET);
printk("axigpioirq_init: GIER status 0x%08X\n",data);
/*
* Set gpio irq type&polarity
*/
data = ioread32(lp->base_addr + XGPIO_INTSTS_OFFSET);
printk("axigpioirq_init: irq status 0x%08X\n",data);
return 0;
error3:
free_irq(lp->irq, lp);
error2:
release_mem_region(lp->mem_start, lp->mem_end - lp->mem_start + 1);
error1:
kfree(lp);
dev_set_drvdata(dev, NULL);
return rc;
}
static int gpioirq_remove(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct gpioirq_local *lp = dev_get_drvdata(dev);
free_irq(lp->irq, lp);
release_mem_region(lp->mem_start, lp->mem_end - lp->mem_start + 1);
kfree(lp);
dev_set_drvdata(dev, NULL);
return 0;
}
#ifdef CONFIG_OF
static struct of_device_id gpioirq_of_match[] = {
/* 這個流程我不熟悉。你可以用附件里的kernel module來試一下。
* 設備樹里只需要把axi_gpio node里的compatible替換為這個module文件里的compatible名字。
* 這個module代碼自動申請中斷資源。在zcu102板子上測試過的,zynq7000上還沒測試過。
* https://forums.xilinx.com/t5/%E5%B5%8C%E5%85%A5%E5%BC%8F-%E7%A1%AC%E4%BB%B6%E7%B3%BB%E7%BB%9F%E5%BC%80%E5%8F%91/%E5%85%B3%E4%BA%8EZYNQ-PL%E4%B8%AD%E6%96%AD%E6%97%B6PS%E7%AB%AF%E6%97%A0%E6%B3%95%E5%93%8D%E5%BA%94%E7%9A%84%E9%97%AE%E9%A2%98/m-p/1135119#M4672
*/
{ .compatible = "vendor,gpioirq", },
{ /* end of list */ },
};
MODULE_DEVICE_TABLE(of, gpioirq_of_match);
#else
# define gpioirq_of_match
#endif
static struct platform_driver gpioirq_driver = {
.driver = {
.name = DRIVER_NAME,
.owner = THIS_MODULE,
.of_match_table = gpioirq_of_match,
},
.probe = gpioirq_probe,
.remove = gpioirq_remove,
};
static int __init gpioirq_init(void)
{
printk("<1>Hello module world.\n");
return platform_driver_register(&gpioirq_driver);
}
static void __exit gpioirq_exit(void)
{
platform_driver_unregister(&gpioirq_driver);
printk(KERN_ALERT "Goodbye module world.\n");
}
module_init(gpioirq_init);
module_exit(gpioirq_exit);
結果馬上就可以了。
以下是log
root@20200827-2s-to-irq-cpu0:/mnt# insmod my_gpio.ko
my_gpio: loading out-of-tree module taints kernel.
<1>Hello module world.
<4>Device Tree Probing
<4>gpioirq at 0x41300000 mapped to 0xf0a30000, irq=48
axigpioirq_init: gpio channael 0 directions 0xFFFFFFFF
axigpioirq_init: IER status 0x00000001
axigpioirq_init: GIER status 0x80000000
axigpioirq_init: irq status 0x00000000
root@20200827-2s-to-irq-cpu0:/mnt# cat /proc/interrupts
CPU0 CPU1
16: 0 0 GIC-0 27 Edge gt
17: 0 0 GIC-0 43 Level ttc_clockevent
18: 23185 24246 GIC-0 29 Edge twd
19: 0 0 GIC-0 37 Level arm-pmu
20: 0 0 GIC-0 38 Level arm-pmu
21: 43 0 GIC-0 39 Level f8007100.adc
23: 0 0 GIC-0 57 Level cdns-i2c
25: 0 0 GIC-0 35 Level f800c000.ocmc
26: 103 0 GIC-0 82 Level xuartps
27: 10 0 GIC-0 51 Level e000d000.spi
28: 34766 0 GIC-0 54 Level eth0
29: 254 0 GIC-0 56 Level mmc0
30: 613 0 GIC-0 79 Level mmc1
31: 0 0 GIC-0 45 Level f8003000.dmac
32: 0 0 GIC-0 46 Level f8003000.dmac
33: 0 0 GIC-0 47 Level f8003000.dmac
34: 0 0 GIC-0 48 Level f8003000.dmac
35: 0 0 GIC-0 49 Level f8003000.dmac
36: 0 0 GIC-0 72 Level f8003000.dmac
37: 0 0 GIC-0 73 Level f8003000.dmac
38: 0 0 GIC-0 74 Level f8003000.dmac
39: 0 0 GIC-0 75 Level f8003000.dmac
40: 0 0 GIC-0 40 Level f8007000.devcfg
46: 30 0 GIC-0 53 Level e0002000.usb
47: 0 0 GIC-0 41 Edge f8005000.watchdog
48: 34 0 GIC-0 63 Level gpioirq
IPI1: 0 0 Timer broadcast interrupts
IPI2: 2339 11847 Rescheduling interrupts
IPI3: 4 0 Function call interrupts
IPI4: 0 0 CPU stop interrupts
IPI5: 32 0 IRQ work interrupts
IPI6: 0 0 completion interrupts
Err: 0
root@20200827-2s-to-irq-cpu0:/mnt# ls /proc/device-tree/amba
amba/ amba_pl/
root@20200827-2s-to-irq-cpu0:/mnt# ls /proc/device-tree/amba_pl/
#address-cells #size-cells compatible gpio@41200000 gpio@41300000 name ranges xadc_wiz@41400000
root@20200827-2s-to-irq-cpu0:/mnt# <4>gpioirq interrupt
<4>axigpioirq_isr: Interrupt Occurred ! GPIO input = 0x00000002
<4>gpioirq interrupt
<4>axigpioirq_isr: Interrupt Occurred ! GPIO input = 0x00000000
<4>gpioirq interrupt
<4>axigpioirq_isr: Interrupt Occurred ! GPIO input = 0x00000002
<4>gpioirq interrupt
<4>axigpioirq_isr: Interrupt Occurred ! GPIO input = 0x00000000
經驗教訓
過於依賴網上的文檔
雖然說網上的文檔在這一塊的資料不夠充足(而且經常有省略),但是因為過於依賴文檔的內容導致了進度緩慢。
不得不說的是,賽靈思的文檔真的是有點零散啊。
基礎知識不夠扎實
設備樹這一塊的知識我在年初就進行了整理,但是這次調試的時候才發現掌握得很差。
驅動框架的內容也是我的一個短板,平時沒有關注到這個領域,學習進度也比較慢。
做得好的
不拖延進度
正如讀書那會,老師會告訴我們,不懂的題目先跳過。
我也是在區分好功能單元的情況下,先假設中斷能夠使用,再開發功能,冷靜以后再分析實際問題。
此外,之前搭建的快速驗證的環境也是幫了我很大的忙。構建好項目以后,我就沒有在PetaLinux的環境下進行構建,節省了大量的時間。