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