ZYNQ Linux驱动开发——第一个字符设备驱动

时间:2023-01-04 11:35:55

硬件平台: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等等。