ZYNQ Linux驅動開發——第一個字元裝置驅動
硬體平臺:XCZ7020 CLG484-1 完全適配Zedboard
開發環境:Widows下Vivado 2016.2 、 SDK2016.2 、 Linux機器:debin
目的:操作板載的LED燈LD9,受PS部分的MIO7控制
linux裝置驅動大體分三種:字元裝置、塊裝置、網路裝置。字元裝置指可以以位元組為單位訪問記憶體,塊裝置只能以資料塊進行訪問,比如NandFlash等,網路裝置就指乙太網等網絡卡驅動了。
在原始的裝置驅動編寫風格來看,主要是搭建框架,然後填充框架,填充的內容就和裸機的驅動檔案一樣了,所以裝置驅動的核心還是裝置的裸機程式。
目前我用的裝置驅動方案大體框架如下:
#include <linux/init.h>
#include <linux/module.h>
MODULE_LICENSE("Dual BSD/GPL");
static int hello_init(void)
{
printk("Module init complete!\nHello, Linux Driver!\n");
return 0;
}
static void hello_exit(void)
{
printk("Module exit!\nBye, Linux Driver!\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_AUTHOR(" [email protected]");
MODULE_DESCRIPTION("HelloLinuxDriver");
MODULE_ALIAS("It's only a test");
模組剛開始載入的時候執行module_init,從而執行hello_init;模組退出的時候執行module_exit從而執行hello_exit。Linux一切皆檔案,包括對應用程式對驅動的操作也都是讀檔案,寫檔案等等,所以除了模組的初始化和模組的退出,裝置驅動還需要為應用程式提供讀寫檔案的功能,這些介面的提供是通過file_operations結構體來實現的。
static struct file_operations gpio_drv_fops = {
.owner = THIS_MODULE, /* 這是一個巨集,推向編譯模組時自動建立的__this_module變數 */
.open = gpio_open,
.write = gpio_write,
};
Gpio_open和gpio_write就是驅動中具體的實現函式,填充完結構體後,通過對註冊字元裝置將此結構體傳遞給核心,從而構建了系統對驅動的讀寫操作,註冊是在模組的初始化中實現的,除了註冊裝置,為了在目標板中載入模組方便還需自動註冊類與裝置。
除了註冊字元裝置,在init函式中最重要的操作就是記憶體對映。通過MMU,將裝置的實體地址對映為虛擬地址,使用者可以對系統的操作均為虛擬地址。下面給出全部程式碼。
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/ioport.h>
#include <linux/of.h>
#include <linux/fs.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#define DEVICE_NAME "first_gpio"
#define MY_GPIO_BASE_ADDR 0xe000a000 //Modify the address to your peripheral
#define XGPIOPS_DIRM_OFFSET 0x00000204U /* Direction Mode Register, RW */
#define XGPIOPS_DATA_LSW_OFFSET 0x00000000U
#define XGPIOPS_DATA_0_OFFSET 0x00000040U
MODULE_AUTHOR("Xilinx XUP");
MODULE_DESCRIPTION("LED moudle dirver");
MODULE_VERSION("v1.0");
MODULE_LICENSE("GPL");
static int gpio_driver_major;
static struct class* gpio_driver_class = NULL;
static struct device* gpio_driver_device = NULL;
volatile unsigned long *Gpio_DIR = NULL;
volatile unsigned long *Gpio_EN = NULL;
volatile unsigned long *Gpio_DATA = NULL;
//volatile unsigned long *MIO_PIN_7 = NULL;
volatile unsigned long *DATA = NULL;
volatile unsigned long *CLK= NULL;
static int gpio_open(struct inode * inode , struct file * filp)
{
printk("first_drv_open\n");
//13 12 11 10 9 8 7 6 5 4 3 2 1 0
//1 1 0 1 1 0 0 0 0 0 0 0 0 0
//*MIO_PIN_7 = 0x00003600;
//*Gpio_DIR|= ((u32)1 << (u32)7); //output,pin7
//*Gpio_EN|= ((u32)1 << (u32)7);//enable output
iowrite32(0x80,Gpio_DIR);
iowrite32(0x80,Gpio_EN);
printk("GPIO_DIR_ADDR %x DATA %x \n",Gpio_DIR,ioread32(Gpio_DIR));
printk("GPIO_EN_ADDR %x DATA %x \n",Gpio_EN,ioread32(Gpio_EN));
printk("CLK_ADDR %x DATA %x \n",CLK,ioread32(CLK));
return 0;
}
static ssize_t gpio_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
int val;
printk("first_drv_write\n");
copy_from_user(&val, buf, count); // copy_to_user();
if (val == 1)
{
// 點燈
*Gpio_DATA=0xff7f0080; //high 16 is mask low 16 is data ,pin7
//*DATA = 0x00;
printk("GPIO_DATA_ADDR %x DATA %x \n",Gpio_DATA,ioread32(Gpio_DATA));
}
else
{
// 滅燈
*Gpio_DATA=0xff7f0000;
//*DATA = 0xffffffff;
printk("GPIO_DATA_ADDR %x DATA %x \n",Gpio_DATA,ioread32(Gpio_DATA));
}
return 0;
}
static struct file_operations gpio_drv_fops = {
.owner = THIS_MODULE, /* 這是一個巨集,推向編譯模組時自動建立的__this_module變數 */
.open = gpio_open,
.write = gpio_write,
};
int major;
static int __init gpio_drv_init(void)
{
printk("first_drv_init\n");
major = register_chrdev(0, "first_gpio", &gpio_drv_fops); // 註冊, 告訴核心
if (major < 0){
printk("failed to register device.\n");
return -1;
}
gpio_driver_class = class_create(THIS_MODULE, "firstgpio");
if (IS_ERR(gpio_driver_class)){
printk("failed to create pwm moudle class.\n");
unregister_chrdev(major, "first_gpio");
return -1;
}
gpio_driver_device = device_create(gpio_driver_class, NULL, MKDEV(major, 0), NULL, "first_gpio"); /* /dev/first_gpio */;
if (IS_ERR(gpio_driver_device)){
printk("failed to create device .\n");
unregister_chrdev(major, "first_gpio");
return -1;
}
Gpio_DIR = (volatile unsigned long *)ioremap(MY_GPIO_BASE_ADDR+XGPIOPS_DIRM_OFFSET, 16);
Gpio_EN = Gpio_DIR+1;
Gpio_DATA = (volatile unsigned long *)ioremap(MY_GPIO_BASE_ADDR+XGPIOPS_DATA_LSW_OFFSET,4);
CLK = (volatile unsigned long *)ioremap(0XF800012C,4);
//MIO_PIN_7 = (volatile unsigned long *)ioremap(0xF800071C,4);
//DATA = (volatile unsigned long *)ioremap(MY_GPIO_BASE_ADDR+XGPIOPS_DATA_0_OFFSET,4);
iowrite32(0x01ec044d,CLK);//時鐘使能
return 0;
}
static void __exit gpio_drv_exit(void)
{
printk("Exit gpio module.\n");
device_destroy(gpio_driver_class, MKDEV(major, 0));
class_unregister(gpio_driver_class);
class_destroy(gpio_driver_class);
unregister_chrdev(major, "first_gpio");
printk("gpio module exit.\n");
/*
unregister_chrdev(major, "first_gpio"); // 解除安裝
class_device_unregister(gpio_driver_device);
class_destroy(gpio_driver_class);
*/
iounmap(Gpio_DIR);
iounmap(Gpio_DATA);
iounmap(CLK);
//iounmap(MIO_PIN_7);
//iounmap(DATA);
}
module_init(gpio_drv_init);
module_exit(gpio_drv_exit);
程式碼中有除錯過程做的註釋,下面給出測試檔案程式碼
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
/* firstdrvtest on
* firstdrvtest off
*/
int main(int argc, char **argv)
{
int fd;
int val = 1;
fd = open("/dev/xyz", O_RDWR);
if (fd < 0)
{
printf("can't open!\n");
}
if (argc != 2)
{
printf("Usage :\n");
printf("%s <on|off>\n", argv[0]);
return 0;
}
if (strcmp(argv[1], "on") == 0)
{
val = 1;
}
else
{
val = 0;
}
write(fd, &val, 4);
return 0;
}
和韋東山教程中的測試檔案一致,整體的驅動編寫過程也和他的教程一致,可以參考韋東山的教學視訊。
一點感悟:1、裝置驅動不好除錯,可以先將裸機程式除錯好,再進行裝置驅動的包裝。2、在進行某個某塊程式設計的時候如果遇到問題,細緻檢視資料手冊等官方資料,比如這次遇到的問題就是沒有對GPIO時鐘使能,從而無法操作GPIO暫存器。3、在這次裝置驅動開發中,Uboot的操作提供了很大幫助,包括直接檢視某個地址暫存器的值md,以及直接在某個地址暫存器寫值mm等等。