轉自:https://blog.csdn.net/xhoufei2010/article/details/99305356
1 說明
由於開發需要,需要通過memory傳輸數據,所以使用devmem 方式讀寫數據,操作linux 內存數據。devmem的方式是提供給驅動開發人員,在應用層能夠偵測內存地址中的數據變化,以此來檢測驅動中對內存或者相關配置的正確性驗證。
2 開發環境
軟件環境: ubuntu 虛擬機、arm-xilinx 交叉編譯工具鏈
硬件環境: ZYNQ7010
3 內存地址說明
基本上的內存物理地址都可以訪問,但是如果需要ZYNQ的PS 和PL 都能讀寫數據,需要查看芯片的datasheet,確定哪個地址可以互相讀寫數據。
通過《ug585-Zynq-7000-TRM.pdf》 的 29章表格“Table 29‐1: Initial OCM/DDR Address Map” 可以得到地址分配。
表3-1 ZYNQ7010 芯片地址分配
從表格3-1得知,DDR的物理地址對應為 0x0010_0000 - 3FFF_FFFF
4 devmem 工具
工具的原理也比較簡單,就是應用程序通過mmap函數實現對/dev/mem驅動中mmap方法的使用,映射了設備的內存到用戶空間,實現對這些物理地址的讀寫操作。
代碼如下:
/**
* @addtogroup module_devmem
* @{
*/
/**
* @file
* @brief 內存管理工具,仿照標准linux devmem 工具進行改良,可以自由讀寫linux內存數據。
* @details 驅動接口。
* @version 1.1.0
* @author sky.houfei
* @date 2019-8-6
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <fcntl.h>
#include <ctype.h>
#include <termios.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <stdbool.h>
//*****************************************************************************
#define FATAL do { fprintf(stderr, "Error at line %d, file %s (%d) [%s]\n", \
__LINE__, __FILE__, errno, strerror(errno)); exit(1); } while(0)
#define MAP_SIZE 4096UL //映射的內存區大小(一般為一個葉框大小)
#define MAP_MASK (MAP_SIZE - 1) //MAP_MASK = 0XFFF
//*****************************************************************************
/**
* @brief 直接寫入到內存實際的物理地址。
* @details 通過 mmap 映射關系,找到對應的內存實際物理地址對應的虛擬地址,然后寫入數據。
* 寫入長度,每次最低4字節
* @param[in] writeAddr, unsigned long, 需要操作的物理地址。
* @param[in] buf,unsigned long *, 需要寫入的數據。
* @param[in] len,unsigned long, 需要寫入的長度,4字節為單位。
* @return ret, int, 如果發送成功,返回0,發送失敗,返回-1。
*/
static int Devmem_Write(unsigned long writeAddr, unsigned long* buf, unsigned long len)
{
int i = 0;
int ret = 0;
int fd;
void *map_base, *virt_addr;
unsigned long addr = writeAddr;
int offset_len = 0;
if(len == 0)
{
printf("%s %s %d, len = 0\n", __FILE__, __func__, __LINE__);
return -1;
}
if ((fd = open("/dev/mem", O_RDWR | O_SYNC)) == -1)
{
fprintf(stderr, "Error at line %d, file %s (%d) [%s]\n", __LINE__, __FILE__, errno, strerror(errno));
return -1;
}
/* Map one page */ //將內核空間映射到用戶空間
map_base = mmap(0, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, addr & ~MAP_MASK);
if(map_base == (void *) -1)
{
fprintf(stderr, "Error at line %d, file %s (%d) [%s]\n", __LINE__, __FILE__, errno, strerror(errno));
close(fd);
return -1;
}
// 發送實際數據內容
for (i = 0; i < len; i++)
{
// 翻頁處理
if(offset_len >= MAP_MASK)
{
offset_len = 0;
if(munmap(map_base, MAP_SIZE) == -1)
{
fprintf(stderr, "Error at line %d, file %s (%d) [%s]\n", __LINE__, __FILE__, errno, strerror(errno));
close(fd);
return -1;
}
map_base = mmap(0, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, addr & ~MAP_MASK);
if(map_base == (void *) -1)
{
fprintf(stderr, "Error at line %d, file %s (%d) [%s]\n", __LINE__, __FILE__, errno, strerror(errno));
close(fd);
return -1;
}
printf("map_base over 4k = [%p].\n", map_base); // 翻頁打印提示
}
virt_addr = map_base + (addr & MAP_MASK); // 映射地址
*((unsigned long *) virt_addr) = buf[i]; // 寫入數據
addr += 4;
offset_len += 4;
}
if(munmap(map_base, MAP_SIZE) == -1)
{
fprintf(stderr, "Error at line %d, file %s (%d) [%s]\n", __LINE__, __FILE__, errno, strerror(errno));
return -1;
}
close(fd);
return 0;
}
/**
* @brief 從實際物理地址讀取數據。
* @details 通過 mmap 映射關系,找到對應的實際物理地址對應的虛擬地址,然后讀取數據。
* 讀取長度,每次最低4字節。
* @param[in] readAddr, unsigned long, 需要操作的物理地址。
* @param[out] buf,unsigned char *, 讀取數據的buf地址。
* @param[in] bufLen,unsigned long , buf 參數的容量,4字節為單位,如 unsigned long buf[100],那么最大能接收100個4字節。
* 用於避免因為buf容量不足,導致素組越界之類的軟件崩潰問題。
* @return len,unsigned long, 讀取的數據長度,字節為單位。如果讀取出錯,則返回0,如果正確,則返回對應的長度。
*/
static int Devmem_Read(unsigned long readAddr, unsigned long* buf, unsigned long len)
{
int i = 0;
int fd,ret;
int offset_len = 0;
void *map_base, *virt_addr;
off_t addr = readAddr;
unsigned long littleEndianLength = 0;
if ((fd = open("/dev/mem", O_RDWR | O_SYNC)) == -1)
{
fprintf(stderr, "Error at line %d, file %s (%d) [%s]\n", __LINE__, __FILE__, errno, strerror(errno));
return 0;
}
/* Map one page */ //將內核空間映射到用戶空間
map_base = mmap(0, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, addr & ~MAP_MASK);
if(map_base == (void *) -1)
{
fprintf(stderr, "Error at line %d, file %s (%d) [%s]\n", __LINE__, __FILE__, errno, strerror(errno));
close(fd);
return 0;
}
for (i = 0; i < len; i++)
{
// 翻頁處理
if(offset_len >= MAP_MASK)
{
offset_len = 0;
if(munmap(map_base, MAP_SIZE) == -1)
{
fprintf(stderr, "Error at line %d, file %s (%d) [%s]\n", __LINE__, __FILE__, errno, strerror(errno));
close(fd);
return 0;
}
map_base = mmap(0, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, addr & ~MAP_MASK);
if(map_base == (void *) -1)
{
fprintf(stderr, "Error at line %d, file %s (%d) [%s]\n", __LINE__, __FILE__, errno, strerror(errno));
close(fd);
return 0;
}
}
virt_addr = map_base + (addr & MAP_MASK); // 將內核空間映射到用戶空間操作
buf[i] = *((unsigned long *) virt_addr); // 讀取數據
addr += 4;
offset_len += 4;
}
if(munmap(map_base, MAP_SIZE) == -1)
{
fprintf(stderr, "Error at line %d, file %s (%d) [%s]\n", __LINE__, __FILE__, errno, strerror(errno));
close(fd);
return 0;
}
close(fd);
return i;
}
static void Devmem_usage(void)
{
printf("Usage ./devmem_tool read/write 0xfa0000 20\n");
printf("The read/write is the command type, read or write data to memory\n");
printf("The 0xfa2000 is the memory physical address\n");
printf("The 20 is read/write data length, uint is 4 byte, so it is total 20 * 4 = 80 bytes\n");
printf("Usage: ./devmem_tool read 0xfa0000 20\n");
printf("Usage: ./devmem_tool write 0xfa0000 20\n");
}
/**
* @brief 內存工具主函數。
* @details 操作方法,請參考Devmem_usage 函數。
* 讀取內存數據: ./devmem_tool read 0xfa0000 20
* 讀取內存物理地址 0xfa0000作為起始地址,一共讀取20個4字節,共計 20 * 4 = 80 字節。
* 寫入內存數據: ./devmem_tool write 0xfa0000 20
* 寫入內存物理地址 0xfa0000作為起始地址,一共寫入20個4字節,共計 20 * 4 = 80 字節。
*/
int main(int argc, char** argv)
{
unsigned long len = 0;
unsigned long writeData[8192];
unsigned long readData[8192];
unsigned long addr = 0;
unsigned long i = 0;
if (argc != 4)
{
Devmem_usage();
return 0;
}
addr = strtoul(argv[2], 0, 0);
len = strtoul(argv[3], 0, 0);
if (strcmp(argv[1], "read") == 0)
{
printf("read data\n");
memset(readData, 0, len);
Devmem_Read(addr, readData, len); // 讀取數據
for (i = 0; i < len; i++)
{
printf("address = 0x%08x, data = 0x%08x\n", (addr + i * 4), readData[i]);
}
}
else if (strcmp(argv[1], "write") == 0)
{
printf("write data\n");
memset(writeData, 0, len);
for (i = 0; i < len; i++)
{
writeData[i] = i;
}
Devmem_Write(addr, writeData, len); // 寫入數據
}
else
{
printf("error command type\n");
Devmem_usage();
}
}
5 編譯調試
使用交叉編譯工具鏈進行編譯
arm-xilinx-linux-gnueabi-gcc -o devmem_tool devmem.c
- 1
將編譯好的 devmem_tool 下載到開發板,運行測試,本工具一次操作4個字節。
測試命令如下:
* 讀取內存數據: ./devmem_tool read 0xfa0000 20
* 讀取內存物理地址 0xfa0000作為起始地址,一共讀取20個4字節,共計 20 * 4 = 80 字節。
* 寫入內存數據: ./devmem_tool write 0xfa0000 20
* 寫入內存物理地址 0xfa0000作為起始地址,一共寫入20個4字節,共計 20 * 4 = 80 字節。