[英]Linux Kernel Device Driver Write Callback is writing data across the entire allocated memory space for the device driver
我有一個 Xilinx SoC,並通過 verilog 在可編程邏輯上創建了一個簡單的乘法器。 乘法器接受兩個 16 位輸入,將它們相乘並返回一個 32 位 output。 數字設計已通過 AXI-Lite 接口封裝並鏈接到 SoC 內的處理器系統。 Xilinx 工具已為此數字設計自動生成設備樹實體,因此可以創建自定義 linux 設備驅動程序以與數字設計交互(即 PS 會將其視為連接到 ARM 處理器的外部硬件設備) .
生成的設備樹如下所示:
/ {
amba_pl: amba_pl@0 {
#address-cells = <2>;
#size-cells = <2>;
compatible = "simple-bus";
ranges ;
multi2_0: multi2@a0000000 {
clock-names = "s00_axi_aclk";
clocks = <&zynqmp_clk 71>;
compatible = "xlnx,multi2-1.0";
reg = <0x0 0xa0000000 0x0 0x10000>;
xlnx,s00-axi-addr-width = <0x4>;
xlnx,s00-axi-data-width = <0x20>;
};
};
};
所以從設備樹中我們可以看到乘法器(“multi2-1.0”)的物理memory地址為0xa0000000,地址寬度為0x4,數據寬度為32位。
因此,從設備驅動程序的角度來看,特別是在寫回調 function 中,我正在將一個 32 位數字寫入從“ioremap(.)”檢索到的虛擬 memory 地址 ZC1C4245268E67A94D11
進行了完整性檢查以查看虛擬 memory 映射到物理地址,並且它似乎正確完成,沒有錯誤(驅動程序中的一些與內存相關的代碼片段如下所示):
struct simpmod_local {
int irq;
unsigned long mem_start;
unsigned long mem_end;
void __iomem *base_addr;
};
struct simpmod_local *lp = NULL;
……
static int simpmod_probe(struct platform_device *pdev)
{ .....
lp->base_addr = ioremap(lp->mem_start, lp->mem_end - lp->mem_start + 1);
...
dev_info(dev,"simpmod at 0x%08x mapped to 0x%08x, irq=%d\n",
(unsigned int __force)lp->mem_start,
(unsigned int __force)lp->base_addr,
lp->irq);
....
}
寫回調 function(截至目前)只是取一個 32 位數字並將其放入 memory。 但是,即使我從 base_address+0x20_offset 讀取,讀取回調 function 也只是讀取完全相同的數字。 我嘗試更改偏移值,但無論如何,它一直在讀取相同的數字。
我的直覺告訴我,如果從不同的 memory 地址讀取該值應該是垃圾值或零,但它不太可能讀取寫入基地址的相同值。 為什么要在整個分配的 memory 空間中復制寫入的數據?
即使執行 devmem 命令 <devmem 0xa0000000 w 52> 也會在執行 <devmem 0xa0000020 w> 或 <devmem 0xa0000040 w> 或......
寫回調 function 如下所示:
static ssize_t dev_write(struct file *fil, const char *buf, size_t len, loff_t *off){
sscanf (buf,"%d,%d",&operand_1,&operand_2);
ker_buf[len] = 0 ;
iowrite32((unsigned int) operand_1, lp->base_addr);
return len;
}
完整的項目代碼(稍作改動)可在https://forums.xilinx.com/t5/Embedded-Linux/Memory-Replications-during-write-call-back-function-in-Linux/mp/1212405上找到
警告:這與其說是一個解決方案,不如說是一些觀察和嘗試的事情[無特定順序]。
目前,您有多個潛在的錯誤來源:硬件邏輯錯誤、設備驅動程序錯誤。
從鏈接的驅動程序代碼中,大多數return
語句返回錯誤代碼(例如-ENOMEM
),但有些確實return -1
。 這是不一致的。
正如我在評論中提到的,你有很多全局變量。 沒有線程間鎖定。 所以,你可能有競爭條件。
我想你正在啟動petalinux
。 而且,只要您不訪問您的設備,它就可以工作。 這是一件大事[以一種好的方式]。
我假設您通過串行電纜從您的開發系統 [運行(例如) minicom
] 到板載 UART 與它通信。 因此,您會收到登錄提示和/或 shell。
這意味着 UART 驅動程序源 [和相應的 dtb/dts] 可用。 您可以將其用作參考驅動程序。 或者,像 GPIO 之類的其他東西。
我注意到您提到了ZYNQ
[這是一種相當流行的 Xilinx FPGA 芯片]。 我假設您也在使用帶有ZYNQ
芯片的標准 SDK 板。 因此,Vivado 已經了解了電路板互連/布局。
而且,我假設 Vivado 能夠將板定義傳遞給 Xilinx 的 S/W SDK/builder,以便它可以構建兼容的petalinux
kernel。
我從未見過寫入值並正確讀取它,但在整個 memory 中復制了該數據。
這意味着您設備中的地址匹配邏輯不僅響應其分配的地址范圍,而且響應更多不應該的地址。 可能與其他設備重疊,它們可能正在競爭/競賽。
我不是Vivado 專家,但是...
從您的鏈接中,查看 Vivado windows 之一的.png
,它說AXI BASEADDR
是0xFFFFFFFF
而AXI HIGHADDR
是0x00000000
。 兩者都有一個藍色的i
。
這些對我來說非常可疑,因為我認為這些值應該與 DTB 條目中的值匹配。 而且, BASEADDR
值對我來說毫無意義。
我想知道是否可以將 DTB 生成到某個健全的地址,但生成的實際硬件邏輯是不同的。
這很容易導致您看到的所有症狀。
可能有幫助的一件事是將chipscope
添加到硬件設計中,以便您可以調試硬件邏輯和/或觀察對給定端口/地址范圍的任何訪問。
您正在使用copy_to_user
等。 人。 但是,這可能會失敗,並且您沒有檢查錯誤代碼。 我還會對正在傳遞的 arguments 進行printk
。
無法保證傳遞給dev_read/dev_write
的len
值足以包含傳輸大小。 在dev_read
中,您執行ioread32
。 但是,你這樣做: int n = sprintf(ker_buf, "%d\n", read_val);
您沒有檢查n
與len
以確保有足夠的空間。 而且,您不是在檢查/尊重loff_t
這兩個函數都傳遞了一個struct file
指針。 但是,這個值會被忽略,取而代之的是您已經設置的全局變量。 正如我在頂級評論中提到的那樣,使用這些全局變量是有問題的。 您應該使用傳遞的指針來找到適當的struct
指針和 [最終] 您的私有設備 struct simpmod_local
。
您的dev_write
應該將來自用戶空間的值存儲到私有結構中。 dev_read
應該從那里得到它們。
這是一個總的猜測:我見過的大多數設計都使用完整的AXI
而不是AXI
lite。 我對“AXI 線程 ID”的構成一無所知,所以我不知道您的訪問代碼在內核之間彈跳的含義可能是什么 [如果有的話]。
像你一樣使用dev_write/dev_read
不是原子的。 我認為,目前,你有更根本的問題。 但是,從長遠來看,我會用一個帶有struct
的ioctl
調用來替換它,例如:
struct mymult_user {
u32 operand_1;
u32 operand_2;
u32 result;
};
ioctl
調用對此進行了copy_from_user
。 將這些值發送到 H/W,取回結果。 並且,將結果返回給 ioctl 調用者。 或者,它可以在struct
的result
字段上執行copy_to_user
。
總體而言,您更有可能在 Xilinx 的論壇頁面上獲得 [有用的] 響應 [因為一直在做這些事情的人經常光顧它]。
更新:
我注意到了別的東西。
DTB 條目將 AXI 數據寬度指定為 0x20。 這是32字節?? 它是自動生成的,所以它必須是正確的;-) 但是,這對我來說似乎太過分了。 它可能只是與 AXI 數據總線的寬度有關,所以,也許不是問題......
但是,查看驅動程序,基地址的偏移量似乎不匹配。
operand_1
是偏移量0x10, operand_2
是偏移量0x20,結果是偏移量0x30。 那么,偏移量 0x0 是什么?
AXI 總線的寬度和寄存器的寬度可能沒有嚴格的關系。
一種查看方式是偏移量應與總線寬度對齊:0x0、0x20、0x40。
但是,通常情況下,我希望事情會更加緊湊。 (例如)分別偏移 0x0、0x2、0x4。
在調試時只做ioread*
可能不會那么痛苦[減少內存/總線損壞的機會]。 由於您沒有寫入地址空間,因此它不太可能損壞其他 memory 單元,並且系統可能會更長時間地保持 [未損壞]。 這只會給你最初結果 reg 中的任何值。
此外,您可以在ioread32
上編寫操作數和循環以獲取偏移量(例如) printk
並打印這些值。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.