Linux字符设备驱动编写流程-----附源码实例

字符设备驱动模型

-------- 创建普通字符设备驱动模型 ---------
①定义一个字符设备------>struct cdev
②定义并初始化一个文件操作集------>struct file_operations
③为字符设备申请设备号
③初始化字符设备------>cdev_init(struct cdev*, struct file_operations*);
④把字符设备加入Linux内核------>add_cdev(struct cdev*, 设备号, 次设备的数量);

-------- 自动创建设备文件 ---------
⑥创建class------>class_create(THIS_MODULE, 类名);
⑦创建device

-------- 得到虚拟地址,通过虚拟地址访问寄存器 ------
⑧申请物理内存区------>request_mem_region(内存区起始物理地址, 内存区大小, 内存区名);
⑨IO内存动态映射------>ioremap(映射内存区起始物理地址, 映射内存区大小);
//led_drv.c(GEC6818开发板-->led灯驱动例子)
//测试程序:test.c (在最下面)
//头文件来源于linux内核源码
#include /* Needed for KERN_INFO */
#include /* Needed by all modules */
#include /*Needed for cdev*/
#include /*Needed for file_operations*/
#include /*Needed for create device file*/
#include /*Needed for error node*/
#include /*Needed for copy from user or copy to user*/
#include /*Needed for ioremap*/

//1.定义一个字符设备
static struct cdev led_cdev;

//2.给字符设备申请设备号(在gec6818_led_init()中申请)
static unsigned int led_major; //主设备号
static unsigned int led_minor; //次设备号
static dev_t led_dev_num;  //设备号
/*(设备号的前12位是主设备号,后20位是次设备号)*/

//定义类、设备、内存资源指针
static struct class *led_class;
static struct device *led_device;
static struct resource *led_res;

//定义寄存器指针
static void __iomem *GPIOC_BASE;//让编译器compiler忽略对本指针的检测
static void __iomem *GPIOCOUT;
static void __iomem *GPIOCOUTENB;
static void __iomem *GPIOCALTFN0;
static void __iomem *GPIOCALTFN1;

static void __iomem *GPIOE_BASE;
static void __iomem *GPIOEOUT;
static void __iomem *GPIOEOUTENB;
static void __iomem *GPIOEALTFN0;

//定义IOCTL命令-->使用方法:ioctl(int fd, unsigned int cmd, unsigned int led_number);
#define GEC6818_LED_MAGIC 'L'
#define GEC6818_LED_ON  _IOW(GEC6818_LED_MAGIC, 1, unsigned int)
#define GEC6818_LED_OFF     _IOW(GEC6818_LED_MAGIC, 2, unsigned int)

static int gec6818_led_open(struct inode *inode, struct file *filp)
{
    printk("led driver is openning\n");
    return 0;
}

static int gec6818_led_release(struct inode *inode, struct file *filp)
{
    *(unsigned int *)GPIOCOUT |= ((1<<17) | (1<<8) | (1<<7) | (1<<12));
    *(unsigned int *)GPIOEOUT |= (1<<13);
    printk("led driver is closing\n");
    return 0;
}

static long gec6818_led_unlocked_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    if(_IOC_TYPE(cmd) != GEC6818_LED_MAGIC) return -EINVAL;
    if(_IOC_NR(cmd) != 2 && _IOC_NR(cmd) != 1) return -ENOIOCTLCMD;

    switch(cmd)
    {
        case GEC6818_LED_OFF:
            switch(arg)
            {
                case 7  : *(unsigned int *)GPIOEOUT |= (1<<13);break;
                case 8  : *(unsigned int *)GPIOCOUT |= (1<<17);break;
                case 9  : *(unsigned int *)GPIOCOUT |= (1<<8);  break;
                case 10: *(unsigned int *)GPIOCOUT |= (1<<7);   break;
                case 11: *(unsigned int *)GPIOCOUT |= (1<<12);break;
                default : return -EINVAL;
            }
            break;

        case GEC6818_LED_ON:
            switch(arg)
            {
                case 7  : *(unsigned int *)GPIOEOUT &= ~(1<<13);   break;
                case 8  : *(unsigned int *)GPIOCOUT &= ~(1<<17);   break;
                case 9  : *(unsigned int *)GPIOCOUT &= ~(1<<8);    break;
                case 10: *(unsigned int *)GPIOCOUT &= ~(1<<7); break;
                case 11: *(unsigned int *)GPIOCOUT &= ~(1<<12);    break;
                default : return -EINVAL;
            }
    }
    return 0;
}

//3.定义一个文件操作集并初始化
static const struct file_operations gec6818_led_fops = {
    .owner = THIS_MODULE,
    // .write = gec6818_led_write,
    .unlocked_ioctl = gec6818_led_unlocked_ioctl,
    .open = gec6818_led_open,
    .release = gec6818_led_release,
};


//驱动的安装函数
static int __init gec6818_led_init(void)
{

    //2.申请设备号(如果主设备号设置为0 则由系统动态分配)
    int ret;
    led_major = 0;
    led_minor = 0;
    if (led_major == 0)
    {
        ret = alloc_chrdev_region(&led_dev_num, led_minor, 1, "led_driver");
    }
    else
    {
        led_dev_num = MKDEV(led_major, led_minor); //把主次设备号转换成设备号
        ret = register_chrdev_region(led_dev_num, 1, "led_driver");
    }
    if (ret < 0) //出错时,跳转到出错处理
    {
        printk("can not get led device number\n");
        goto err_dev_num;
    }


    //4.初始化字符设备cdev(把文件操作集与字符设备关联起来)
    cdev_init(&led_cdev, &gec6818_led_fops);


    //5.把初始化好的字符设备cdev加入内核
    ret = cdev_add(&led_cdev, led_dev_num, 1);
    if (ret < 0)
    {
        printk("cdev add error\n");
        goto err_cdev_add;//出错时,跳转到出错处理
    }

    //6.驱动程序自动创建设备文件(操作系统必须有mdev或udev)

        //6.1 创建class
    led_class = class_create(THIS_MODULE, "led_class");
    if (IS_ERR(led_class)) //判断指针是否有效
    {
        ret = PTR_ERR(led_class); //用指针求得错误码,并赋值给ret
        printk("led driver class create error\n");
        goto err_create_class;
    }

        //6.2 创建device
    led_device = device_create(led_class, NULL, led_dev_num, NULL, "led_drv");
    if (IS_ERR(led_device)) //判断指针是否有效
    {
        ret = PTR_ERR(led_device);//用指针求得错误码,并赋值给ret
        printk("led driver device create error\n");
        goto err_create_device;
    }


    //7.申请物理内存区(用于实现内存资源的互斥访问,不申请亦可)
    led_res = request_mem_region(0xC001C000, 0x1000, "GPIOC_MEM");
    if (led_res == NULL)
    {
        printk("request memory error\n");
        ret = -EBUSY;
        goto err_request_mem;
    }

/************************************************************************************/

    //8.IO内存的动态映射
    GPIOC_BASE = ioremap(0xC001C000, 0x1000);
    if (GPIOC_BASE == NULL)
    {
        printk("ioremap GPIOC_BASE error\n");
        ret = -EFAULT;
        goto err_ioremap_C;
    }

    GPIOE_BASE = ioremap(0xC001E000, 0x1000);
    if (GPIOE_BASE == NULL)
    {
        printk("ioremap GPIOE_BASE error\n");
        ret = -EFAULT;
        goto err_ioremap_E;
    }


    //通过映射的基地址计算各寄存器的地址
    GPIOCOUT        = GPIOC_BASE + 0X00;
    GPIOCOUTENB = GPIOC_BASE + 0X04;
    GPIOCALTFN0 = GPIOC_BASE + 0X20;
    GPIOCALTFN1     = GPIOC_BASE + 0X24;

    GPIOEOUT        = GPIOE_BASE + 0X00;
    GPIOEOUTENB = GPIOE_BASE + 0X04;
    GPIOEALTFN0 = GPIOE_BASE + 0X20;

    //LED的初始化并关闭全部LED
    *(unsigned int *)GPIOCOUTENB |= ((1<<17) | (1<<8) | (1<<7) | (1<<12));
    *(unsigned int *)GPIOCALTFN0    &= ~((3<<14) | (3<<16) | (3<<24));
    *(unsigned int *)GPIOCALTFN0    |= ((1<<14) | (1<<16) | (1<<24));
    *(unsigned int *)GPIOCALTFN1    &= ~(3<<2);
    *(unsigned int *)GPIOCALTFN1    |= (1<<2);
    *(unsigned int *)GPIOCOUT       |= ((1<<17) | (1<<8) | (1<<7) | (1<<12));

    *(unsigned int *)GPIOEOUTENB |= (1<<13);
    *(unsigned int *)GPIOEALTFN0 &= ~(3<<26);
    *(unsigned int *)GPIOEOUT |= (1<<13);

    //驱动安装完成,并输出相关信息
    printk(KERN_INFO "gec6818_led_init\n");
    printk(KERN_INFO "device_name : led_drv\n");
    printk(KERN_INFO "led_major : %d\n", MAJOR(led_dev_num));
    printk(KERN_INFO "led_minor : %d\n", MINOR(led_dev_num));
    // printk("gec6818_led_init\n"); //缺省优先级的printk将使用系统默认优先级

    return 0;


    //出错处理
    err_ioremap_E:
        iounmap(GPIOC_BASE);
    err_ioremap_C:
        release_mem_region(0xC001C000, 0x1000);//释放已经申请的物理内存区
    err_request_mem:
        device_destroy(led_class, led_dev_num);//删除已经创建的设备文件
    err_create_device:
        class_destroy(led_class);//删除已经创建的类led_class
    err_create_class:
        cdev_del(&led_cdev);//从内核删除已经成功添加的字符设备
    err_cdev_add:
        unregister_chrdev_region(led_dev_num, 1); //释放已经申请的设备号
    err_dev_num:
        return ret;//出错时返回错误码
}

//驱动的卸载函数
static void __exit gec6818_led_exit(void)
{
    iounmap(GPIOE_BASE);
    iounmap(GPIOC_BASE);//解除内存地址映射
    release_mem_region(0xC001C000, 0x1000);
    device_destroy(led_class, led_dev_num);
    class_destroy(led_class);
    cdev_del(&led_cdev);
    unregister_chrdev_region(led_dev_num, 1);

    printk(KERN_INFO "gec6818_led_exit\n"); 
}


//module的入口和出口函数
//#insmod led_drv.ko --->module_init()--->驱动程序的安装函数gec6818_led_init()
module_init(gec6818_led_init);//入口函数
//#rmmod led_drv.ko --->module_exit()--->驱动程序的卸载函数gec6818_led_exit()
module_exit(gec6818_led_exit);//出口函数


//module的描述,不是必需的。#modinfo led_drv.ko
MODULE_AUTHOR("[email protected]");
MODULE_DESCRIPTION("led driver for GEC6818");
MODULE_LICENSE("GPL"); //符合GPL协议
MODULE_VERSION("V1.0");

分步详解:

①定义字符设备管理结构体变量(具体的结构体定义在内核头文件

Linux字符设备驱动编写流程-----附源码实例_第1张图片

②定义并初始化一个文件操作集(用于驱动与应用程序的交互)
    1)文件操作集结构体定义在内核头文件

Linux字符设备驱动编写流程-----附源码实例_第2张图片

    2)选择有需要的接口函数进行初始化

Linux字符设备驱动编写流程-----附源码实例_第3张图片

    3)定义文件操作集中初始化的接口函数

Linux字符设备驱动编写流程-----附源码实例_第4张图片
【函数具体实现见:led_drv.c】

③为字符设备申请设备号(包括主设备号、次设备号和设备号)

Linux字符设备驱动编写流程-----附源码实例_第5张图片

※ 申请设备号的两种方法:动态申请(由系统自动分配)和 手动指定设备号。

Linux字符设备驱动编写流程-----附源码实例_第6张图片

④初始化字符设备(把cdev与文件操作集关联起来)

这里写图片描述

⑤把初始化好的字符设备加入Linux内核

Linux字符设备驱动编写流程-----附源码实例_第7张图片

⑥创建设备文件 \ 设备节点
    1)手动创建设备文件节点
~:mknod 设备类型 设备文件名 主设备号 次设备号
例:[ root@GEC6818 / ]#mknod /dev/led_drv c 100 0
※ 可以使用#cat /proc/devices  查看创建是否成功

2)自动创建设备文件节点(系统中必须有medv或udev工具)
mdev是udev的简化版,这个工具可以根据驱动的class和device来创建设备文件节点。

Linux字符设备驱动编写流程-----附源码实例_第8张图片

⑦申请物理内存区(类似于互斥锁功能)

Linux字符设备驱动编写流程-----附源码实例_第9张图片

⑧IO内存动态映射(把物理地址映射到操作系统可操作的虚拟地址)

Linux字符设备驱动编写流程-----附源码实例_第10张图片

⑨按照实际要求配置寄存器的值
        【具体参考:led_drv.c】

⑩出错处理(用goto语句实现)
每一部都设置出错处理,出错时释放已经申请的资源。
Linux字符设备驱动编写流程-----附源码实例_第11张图片

【做到这里,驱动安装步骤结束】

XI 定义驱动的卸载函数(按顺序释放驱动安装时申请的资源)
Linux字符设备驱动编写流程-----附源码实例_第12张图片

XII 驱动程序的说明与描述(不是必须的)
Linux字符设备驱动编写流程-----附源码实例_第13张图片

※ 如果MODULE_LICENSE 不写,内核会报警告【信息:污染内核】。

//test.c --> led_drv.ko 适用的 led灯测试程序
#include 
#include 

#include 
#include 
#include 
 #include 
#include 

#define GEC6818_LED_MAGIC 'L'
#define GEC6818_LED_ON  _IOW(GEC6818_LED_MAGIC, 1, unsigned int)
#define GEC6818_LED_OFF     _IOW(GEC6818_LED_MAGIC, 2, unsigned int)


int main(void)
{
    int fd;
    long ret;
    unsigned int num = 7;
    fd = open("/dev/led_drv", O_WRONLY);
    if(fd < 0)
    {
        perror("open led driver");
        return -1;      
    }
    while(1)
    {
        ret = ioctl(fd, GEC6818_LED_ON, num);
        sleep(1);

        printf("num = %d\n", num);
        num += 1;
        if (num > 11)
        {
            num = 7;
            break;
        }
    }

    while(1)
    {
        ret = ioctl(fd, GEC6818_LED_OFF, num);
        sleep(1);

        printf("num down = %d\n", num);
        num += 1;
        if (num > 11)
        {
            break;
        }
    }   

    close(fd);

    return 0;
}

你可能感兴趣的:(Linux字符设备驱动编写流程-----附源码实例)