----------原創不易,歡迎點贊收藏。廣交嵌入式開發的朋友,討論技術和產品-------------
在飛騰D2000平臺上,安裝了麒麟linux系統,我寫了個GPIO點燈的程序,在應用層利用mmap函數將內核空間映射到用戶態,然后直接操作GPIO方向和數據寄存器即可控制LED管腳的電壓,實現閃燈功能。
核心代碼如下,利用/dev/mem設備,內核將硬件地址空間映射到用戶態空間
if ((fd_mem = open("/dev/mem", O_RDWR, 0755)) < 0){printf("\r\n open /dev/mem err: %d-%s. \n", errno, strerror(errno));return 0;}mapped_gpio = (volatile unsigned char *)mmap(0, 0x1000, PROT_READ|PROT_WRITE, MAP_SHARED, fd_mem, GPIO1_BASE_ADDR);if (tmp == (void*)-1){ printf("mmap error : %d-%s. \n", errno, strerror(errno));close(fd_mem);return 1;}
然后交叉編譯后,將目標二進制文件上傳到單板上運行,閃燈正常。
于是乎把這源碼和可執行文件發給客戶使用,結果報錯。打印如下信息,提示非法參數
我將mmap的幾個參數都研究了一下:
1、應該不是權限問題,對方用的是root賬戶執行的;
2、查看了單板上內核空間,看是否有GPIO物理地址的定義
#define GPIO1_BASE_ADDR 0x28005000 //PHYTIUM D2000 GPIO
cat /proc/iomem 執行結果如下,有0x28005000的空間
3、PROT_READ|PROT_WRITE這兩個參數應該沒有問題;
4、想到重要的一個因素,mmap必須以頁表大小對齊的起始地址,才能做映射。于是查看當前系統的頁表大小,用如下命令:getconf PAGESIZE。
在麒麟系統查到結果是4KB的小頁面,在centos下查詢得到的是65536,64KB的頁面。這個參數在內核編譯的時候可以設定的。肯定是centos沒有設定小頁面模式。關于MMU和內存頁的知識,本文不敘述。有興趣的朋友自己去研究學習。
于是乎將代碼修改了一下,考慮不管多大頁面都能兼容
int pagesize;u32 mmap_base;u32 mmap_offset;volatile unsigned char *tmp;pagesize = sysconf(_SC_PAGESIZE);if (pagesize == -1) {printf("Error, sysconf for _SC_PAGESIZE \n");return 1;} else {printf("Page size: %d bytes \n", pagesize);}mmap_base = GPIO1_BASE_ADDR & ~(pagesize - 1);mmap_offset = GPIO1_BASE_ADDR & (pagesize - 1);printf("Hardware address = 0x%x, mmap_base = 0x%x, mmap_offset = 0x%x \n", GPIO1_BASE_ADDR, mmap_base, mmap_offset);if ((fd_mem = open("/dev/mem", O_RDWR, 0755)) < 0){printf("\r\n open /dev/mem err: %d-%s. \n", errno, strerror(errno));return 0;}tmp = (volatile unsigned char *)mmap(0, pagesize, PROT_READ|PROT_WRITE, MAP_SHARED, fd_mem, mmap_base);if (tmp == (void*)-1){ printf("mmap error : %d-%s. \n", errno, strerror(errno));close(fd_mem);return 1;}mapped_gpio = (volatile unsigned int *)((unsigned long)tmp + mmap_offset);printf(" mapped_base = %p, mapped_gpio = %p \n", tmp, mapped_gpio);printf("GPIO_SWPORTA_DR = 0x%x \n", *mapped_gpio); //GPIO_SWPORTA_DRprintf("GPIO_SWPORTA_DDR = 0x%x \n\n", *(mapped_gpio+1)); //GPIO_SWPORTA_DDR
重新編譯后在多個版本上驗證可行。當初寫這個功能程序,太匆忙,沒有考慮太細致。不過也很快定位解決了。這也告誡搞研發的朋友,雖然同一套代碼不修改,在不同環境上也不一定成功執行。陷阱無處不在,大家謹慎小心,寫出健壯可靠的代碼。