Linux驱动开发2 - 内核定时器驱动

背景

所有驱动开发都是基于全志T507(Android 10)进行开发,用于记录驱动开发过程。

简介

定时器是比较常用的一个功能,用来执行周期性任务。一般不太精确的定时可以用系统提供的延时函数进行。如果需要进行较为精确的延时,就要考虑使用系统内核提供的定时器中断。

在linux中ktime可以实现ms级定时,如果需要us、ns级就没法使用这个了(这个我们后续在讨论)。

驱动实现

首先在Linux驱动开发1 - Platform设备中已经实现了Platform设备驱动的编写,本文是基于这个驱动进行的定时器功能开发。

步骤1、配置时钟频率

首先在内核中可以配置定时器中断周期,T507中默认配置为300HZ,即最大精度为3.33ms。如上图所示,timer最大支持的精度为1ms,可以通过启用CONFIG_HZ_1000进行设置。本文就以默认的300HZ频率为例。

也可以用make menuconfig在图形化界面进行配置。

配置文件路径为/kernel/linux-4.9/arch/arm64/configs/xxx

步骤2、实现platform_driver 结构体。

在longan/kernel/linux-4.9/drivers目录下创建自己的文件夹,定义一个timer_led_drv.c文件。

这里可以不用修改,只修改了驱动名称

/* platform驱动结构体 */
static struct platform_driver led_timer_driver = {
	.driver		= {
		.name	= "devledtimer",			        	// 无设备树时,用于设备和驱动间的匹配
		.of_match_table	= led_timer_of_match,     // 有设备树后,利用设备树匹配表
	},
	.probe		= led_timer_probe,
	.remove		= led_timer_remove,
};

在设备数创建设备节点。这里以PI0为例

    led_timer {
		compatible = "devledtimer";
		pinctrl-names = "default";
		status = "okay";
		//pinctrl-0 = <&timer_led_pin_a>;

		ledtimer {
			gpios = <&pio PI 0 1 2 0 1>;
			state = "on";
		};
	};

在PIO节点下定义pinctrl_led_test节点,主要用于配置IO口。

    soc@03000000 {
		pio: pinctrl@0300b000 {
 
			...
 
			timer_led_pin_a: led_timer_grp@0 {
				allwinner,pins = "PI0";
				allwinner,function = "led_timer_grp";
				allwinner,muxsel = <0x01>;
				allwinner,drive = <0x00>;
				allwinner,pull = <0x01>;
				allwinner,data = <0x01>;
			};
            
            ...
        }
    }

步骤3、定义设备节点

主要定义一下led_timer_priv,用来存放节点信息,用于生成/dev/xxx节点。

这里用到了定时器,因此增加了timer_list和timeperiod用来启用定时器及控制定时器周期。

另外还用到了自旋锁,作用自己查资料,这里就不详述了。

/* 存放led信息的结构体 */
struct led_timer_data 
{
    char                    name[16];       // 设备名字
    int                     pin;            // gpio编号
    int                     active;         // 控制亮灭的标志
    int                     enable;         // 定时器使能标志位
};

/* 存放led的私有属性 */
struct led_timer_priv 
{
    struct cdev             cdev;           // cdev结构体
    struct class            *dev_class;     // 自动创建设备节点的类
    int                     num_leds;       // led的数量
    struct led_timer_data   led;            // 存放led信息的结构体数组
    int                     timeperiod;     // 定时周期
    struct timer_list       timer;          // 定义一个定时器
    spinlock_t              lock;           // 定义一个自旋锁
    
};

步骤4、初始化设备

定义一个初始化方法gpio_led_probe,并绑定到步骤2中的.probe

static int led_timer_probe(struct platform_device *pdev)

这个函数和 Linux驱动开发1 - Platform设备中gpio_led_probe一样。

parser_init_led_timer用于获取设备树中的节点信息,并进行初始化。

步骤5、移除设备

常规操作,将所有设备依次注销回收。

增加del_timer_sync(&priv->timer);用于在移除设备时删除定时器。

static int led_timer_remove(struct platform_device *pdev)
{
    struct led_timer_priv *priv = platform_get_drvdata(pdev);
    int i;
    dev_t devno = MKDEV(dev_major, 0);

    /* 注销设备结构体,class结构体和cdev结构体 */
    for(i=0; i<priv->num_leds; i++)
    {
        devno = MKDEV(dev_major, i);
        device_destroy(priv->dev_class, devno);
    }
    class_destroy(priv->dev_class);
    
    cdev_del(&priv->cdev); 
    unregister_chrdev_region(MKDEV(dev_major, 0), priv->num_leds);
    
    /* 将led的状态设置为灭 */
    for (i = 0; i < priv->num_leds; i++) 
    {
        gpio_set_value(priv->led.pin, 0);
    }   

    del_timer_sync(&priv->timer);
    
    printk("led_timer_ioctl success to remove driver[major=%d]!\n", dev_major);
    return 0;
} 

步骤6、字符设备描述file_operations


static struct file_operations led_fops = 
{
    .owner = THIS_MODULE,
    .open = led_open,
    .release = led_release,
    .unlocked_ioctl = led_ioctl,
    .compat_ioctl = led_timer_ioctl,
};

实现led_open、led_release、led_ioctl。

在led_open中初始化自旋锁及初始化定时器timer。修改ioctl逻辑,增加定时器控制命令。

static int led_open(struct inode *inode, struct file *file)
{
    struct led_timer_priv *priv;

    priv = container_of(inode->i_cdev, struct led_timer_priv, cdev);
    file->private_data = priv;
    priv->timeperiod = 1000;

    /* 初始化自旋锁 */
    spin_lock_init(&priv->lock);
    /* 初始化timer */
    setup_timer(&priv->timer, timer_do_in_time, &priv->timer);
    return 0;
}

static int led_release(struct inode *inode, struct file *file)
{
    return 0;
}

static long led_timer_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
    struct led_timer_priv *priv;
    priv = file->private_data;
    unsigned int flags;

    printk(">>>>>  led_timer_ioctl command=%d arg=%ld\n", cmd, arg);

    switch (cmd)
    {
        case 10:/* 关闭定时器 */
            del_timer_sync(&priv->timer);
            priv->led.enable = 0;
            gpio_set_value(priv->led.pin, 0);
            priv->led.active = 0;
            printk(">>>>>  led_timer_ioctl del_timer_sync\n");
            break;

        case 11:/* 打开定时器 */
            del_timer_sync(&priv->timer);
            mod_timer(&priv->timer, jiffies + msecs_to_jiffies(priv->timeperiod));
            priv->led.enable = 1;

            printk(">>>>>  led_timer_ioctl mod_timer = %ld\n", jiffies);
            break;

        case 12:/* 设置定时器周期 */
            spin_lock_irqsave(&priv->lock, flags);
            priv->timeperiod = arg;
            spin_unlock_irqrestore(&priv->lock, flags);

            if (priv->led.enable == 0)
            {
                del_timer_sync(&priv->timer);
                mod_timer(&priv->timer, jiffies + msecs_to_jiffies(priv->timeperiod));
                priv->led.enable = 1;
            }
            printk(">>>>>  led_timer_ioctl period = %d\n", priv->timeperiod);
            break;
    
        default:
            printk("Ioctl command=%d can't be supported\n", cmd);
            break;
    }
    
    return 0;

}

实现定时器中断处理函数

void timer_do_in_time(unsigned long arg)
{
    struct timer_list *mytimer = (struct timer_list *)arg;
	struct led_timer_priv *priv = container_of(mytimer, struct led_timer_priv, timer);

	// TODO 在这里处理需要做的事情
	
	// 根据标志位处理是否重启定时器
    if (priv->led.enable == 1)
    {
        mod_timer(&priv->timer, jiffies + msecs_to_jiffies(priv->timeperiod)); 
    }
}

步骤7、编译环境MakeFile

在自定义目录下创建Makefile文件。这里默认加载设备。

obj-y	+= timer_led_drv.o

遇到的问题

问题1、中断函数中获取led_timer_priv结构体信息。

主要用container_of获取到内存中struct led_timer_priv *priv类型指针,然后使用priv中的led_timer_data或者timer进行操作。

源码

#include <linux/init.h>                  
#include <linux/module.h>          
#include <linux/errno.h>             
#include <linux/types.h>                  
#include <linux/kernel.h>   
#include <linux/timer.h>
#include <linux/jiffies.h>      
#include <linux/fs.h>                  
#include <linux/cdev.h>           
#include <linux/device.h>               
#include <linux/of_gpio.h>             
#include <linux/platform_device.h>    
#include <linux/gpio.h>
#include <linux/of_device.h>
#include <linux/slab.h>


#define LED_OFF                     0       
#define LED_ON                      1

static int dev_major = 0;

/* 存放led信息的结构体 */
struct led_timer_data 
{
    char                    name[16];       // 设备名字
    int                     pin;            // gpio编号
    int                     active;         // 控制亮灭的标志
    int                     enable;         // 定时器使能标志位
};

/* 存放led的私有属性 */
struct led_timer_priv 
{
    struct cdev             cdev;           // cdev结构体
    struct class            *dev_class;     // 自动创建设备节点的类
    int                     num_leds;       // led的数量
    struct led_timer_data   led;            // 存放led信息的结构体数组
    int                     timeperiod;     // 定时周期
    struct timer_list       timer;          // 定义一个定时器
    spinlock_t              lock;           // 定义一个自旋锁
    
};

void timer_do_in_time(unsigned long arg)
{
    struct timer_list *mytimer = (struct timer_list *)arg;
	struct led_timer_priv *priv = container_of(mytimer, struct led_timer_priv, timer);

    /* 每次都取反,实现LED灯反转 */
	priv->led.active = !priv->led.active;		
    gpio_set_value(priv->led.pin, priv->led.active);
	
	/* 重启定时器 */
    if (priv->led.enable == 1)
    {
        mod_timer(&priv->timer, jiffies + msecs_to_jiffies(priv->timeperiod)); 
    }
}

static inline int sizeof_led_timers_priv(int num_leds)
{
    return sizeof(struct led_timer_priv) + (sizeof(struct led_timer_priv) * num_leds);
}

int parser_init_led_timer(struct platform_device *pdev)
{
    struct device_node *np = pdev->dev.of_node;     // 当前设备节点
    struct device_node *child;                      // 当前设备节点的子节点
    struct led_timer_priv *priv;                    // 存放私有属性
    int num_leds, gpio;                             // led数量和gpio编号                

    num_leds = 1;
    if(num_leds <= 0) 
    {
        dev_err(&pdev->dev, "fail to find child node\n");
        return -EINVAL;
    }

    priv = devm_kzalloc(&pdev->dev, sizeof_led_timers_priv(num_leds), GFP_KERNEL);
    if (!priv)
    {
        return -ENOMEM;
    }
    
    priv->num_leds = 1;
    
    /* 找到子节点并传给child */
    child = of_get_child_by_name(np, "ledtimer");

    /* 解析dts并且获取gpio口,函数返回值就得到gpio号,并且读取gpio现在的标志 */
    gpio = of_get_named_gpio(child, "gpios", 0);
    
    /* 将子节点的名字,传给私有属性结构体中的led信息结构体中的name属性 */
    strncpy(priv->led.name, child->name, sizeof(priv->led.name)); 

    printk(">>>>>  led_timer_ioctl gpio=%d name=%s\n", gpio, priv->led.name);
    
    /* 将gpio编号和控制亮灭的标志传给结构体
     * active属性,1代表亮,0代表灭,初始属性为亮
     */
    priv->led.active = 1; 
    priv->led.pin = gpio; 
    
    /* 申请gpio口,相较于gpio_request增加了gpio资源获取与释放功能 */
    gpio_request(priv->led.pin, "ledtimer");
    
    /* 设置gpio为输出模式,并设置初始状态 */
    gpio_direction_output(priv->led.pin, 1);
    
    /* 将led的私有属性放入platform_device结构体的device结构体中的私有数据中 */
    platform_set_drvdata(pdev, priv);
    
    return 0;

}

static int led_open(struct inode *inode, struct file *file)
{
    struct led_timer_priv *priv;

    priv = container_of(inode->i_cdev, struct led_timer_priv, cdev);
    file->private_data = priv;
    priv->timeperiod = 1000;

    /* 初始化自旋锁 */
    spin_lock_init(&priv->lock);
    /* 初始化timer */
    setup_timer(&priv->timer, timer_do_in_time, &priv->timer);
    return 0;
}

static int led_release(struct inode *inode, struct file *file)
{
    return 0;
}

static long led_timer_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
    struct led_timer_priv *priv;
    priv = file->private_data;
    unsigned int flags;

    switch (cmd)
    {
        case 10:/* 关闭定时器 */
            del_timer_sync(&priv->timer);
            priv->led.enable = 0;
            gpio_set_value(priv->led.pin, 0);
            priv->led.active = 0;
            break;

        case 11:/* 打开定时器 */
            del_timer_sync(&priv->timer);
            mod_timer(&priv->timer, jiffies + msecs_to_jiffies(priv->timeperiod));
            priv->led.enable = 1;
            break;

        case 12:/* 设置定时器周期 */
            spin_lock_irqsave(&priv->lock, flags);
            priv->timeperiod = arg;
            spin_unlock_irqrestore(&priv->lock, flags);

            if (priv->led.enable == 0)
            {
                del_timer_sync(&priv->timer);
                mod_timer(&priv->timer, jiffies + msecs_to_jiffies(priv->timeperiod));
                priv->led.enable = 1;
            }
            break;
    
        default:
            printk("Ioctl command=%d can't be supported\n", cmd);
            break;
    }
    
    return 0;

}

static struct file_operations led_fops = 
{
    .owner = THIS_MODULE,
    .open = led_open,
    .release = led_release,
    .unlocked_ioctl = led_timer_ioctl,
    .compat_ioctl = led_timer_ioctl,
};

static int led_timer_probe(struct platform_device *pdev)
{
    struct led_timer_priv   *priv;          // 临时存放私有属性的结构体
    struct device           *dev;           // 设备结构体
    dev_t                   devno;          // 设备的主次设备号
    int                     i, rv = 0;    

    /* 1)解析设备树并初始化led状态 */
    rv = parser_init_led_timer(pdev);
    if( rv < 0 )
            return rv;
    
    /* 将之前存入的私有属性,放入临时的结构体中 */
    priv = platform_get_drvdata(pdev);
    
    /* 2)分配主次设备号 */
    if (0 != dev_major) 
    {   
        /* 静态分配主次设备号 */
        devno = MKDEV(dev_major, 0); 
        rv = register_chrdev_region(devno, priv->num_leds, "devledtimer"); 
    }   
    else 
    {   
        /* 动态分配主次设备号 */
        rv = alloc_chrdev_region(&devno, 0, priv->num_leds, "devledtimer"); 
        dev_major = MAJOR(devno); 
    }   
    if (rv < 0) 
    {   
        dev_err(&pdev->dev, "major can't be allocated\n"); 
        return rv; 
    }   
    
    /* 3)分配cdev结构体 */
    cdev_init(&priv->cdev, &led_fops);
    priv->cdev.owner  = THIS_MODULE;
    
    rv = cdev_add (&priv->cdev, devno , priv->num_leds); 
    if( rv < 0) 
    {
        dev_err(&pdev->dev, "struture cdev can't be allocated\n");
        goto undo_major;
    }
    
    /* 4)创建类,实现自动创建设备节点 */
    priv->dev_class = class_create(THIS_MODULE, "ledtimer");
    if( IS_ERR(priv->dev_class) ) 
    {
        dev_err(&pdev->dev, "fail to create class\n");
        rv = -ENOMEM;
        goto undo_cdev;
    }
    
    /* 5)创建设备 */
    for(i=0; i<priv->num_leds; i++)
    {
        devno = MKDEV(dev_major, i);
        dev = device_create(priv->dev_class, NULL, devno, NULL, "ledtimer");
        if( IS_ERR(dev) ) 
        {
            dev_err(&pdev->dev, "fail to create device\n");
            rv = -ENOMEM;
            goto undo_class;
        }
    }

    printk("led_timer_ioctl success to install driver[major=%d]!\n", dev_major);
    
    return 0;

undo_class:
    class_destroy(priv->dev_class);

undo_cdev:
    cdev_del(&priv->cdev);

undo_major:
    unregister_chrdev_region(devno, priv->num_leds);

    return rv;

}

static int led_timer_remove(struct platform_device *pdev)
{
    struct led_timer_priv *priv = platform_get_drvdata(pdev);
    int i;
    dev_t devno = MKDEV(dev_major, 0);

    /* 注销设备结构体,class结构体和cdev结构体 */
    for(i=0; i<priv->num_leds; i++)
    {
        devno = MKDEV(dev_major, i);
        device_destroy(priv->dev_class, devno);
    }
    class_destroy(priv->dev_class);
    
    cdev_del(&priv->cdev); 
    unregister_chrdev_region(MKDEV(dev_major, 0), priv->num_leds);
    
    /* 将led的状态设置为灭 */
    for (i = 0; i < priv->num_leds; i++) 
    {
        gpio_set_value(priv->led.pin, 0);
    }   

    del_timer_sync(&priv->timer);
    
    printk("led_timer_ioctl success to remove driver[major=%d]!\n", dev_major);
    return 0;
} 

/* 匹配列表 */
static const struct of_device_id led_timer_of_match[] = {
	{ .compatible = "devledtimer" },
	{}
};

MODULE_DEVICE_TABLE(of, led_timer_of_match);

/* platform驱动结构体 */
static struct platform_driver led_timer_driver = {
	.driver		= {
		.name	= "devledtimer",			        	// 无设备树时,用于设备和驱动间的匹配
		.of_match_table	= led_timer_of_match,     // 有设备树后,利用设备树匹配表
	},
	.probe		= led_timer_probe,
	.remove		= led_timer_remove,
};

module_platform_driver(led_timer_driver);

MODULE_LICENSE("GPL");

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值