1. 程式人生 > >ZYNQ Linux驅動開發——第一個字元裝置驅動

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等等。