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("Cuter@ChinaAET");
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等等。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章