这里不做两种异步工作的实现说明,主要针对工作中实际使用总结:
方式 | 实际使用 |
---|---|
hrtimer | 当定时器时间到,以timeout唤醒,执行回调函数,如果模拟一个波形,高频率的产生中断,系统稳定性不够好;占用系统资源较高,但定时准确性高,优先级高;适合处理短时间的事务 |
kthread | 在线程函数中以delay形式循环执行,delay的形式,可以是释放cpu的,也可以是不释放cpu的。当delay时间比较久,使用释放cpu的较好;占用资源较低,优先级可设置;适用于长时间运行 |
实际使用情况
占用资源方面
同样是wiegand输入和输出,使用线程方式异步处理,接受数据的准确率更高。如果使用线程长时间运行,delay使用不释放cpu的函数,cpu占用率也会很高。
系统稳定性方面
在使用gpio口,按照一定波形拉高拉低电平,模拟pwm中的驱动中,注册一个io的上升沿和下降沿的中断,运行一段时间会产生中断错误,导致kernel挂掉;使用kthread方式不会存在系统挂掉的问题。
总结
对时序要求很高的,使用hrtimer比较好。但是,如果cpu资源并不充足,频繁的hrtimer超时唤醒使用中断,系统稳定性较差。
Demo
#include <linux/delay.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/of_gpio.h>
#include <linux/gpio.h>
#include <linux/of_platform.h>
#include <linux/sys_config.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/io.h>
#include <linux/string.h>
#include <linux/slab.h>
#include <linux/cdev.h>
#include <linux/err.h>
#include <linux/hrtimer.h>
#include <linux/kthread.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/time.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <linux/ioctl.h>
#define LOG_TAG "itech_led:"
#define DBUG_BLOCK 1
#define print_dbg(fmt, ...) printk(KERN_DEBUG LOG_TAG "%s:%d->" fmt, \
__func__, __LINE__, ##__VA_ARGS__)
#define print_err(fmt, ...) printk(KERN_ERR LOG_TAG "%s:%d->" fmt, \
__func__, __LINE__, ##__VA_ARGS__)
#define print_info(fmt, ...) printk(KERN_WARNING LOG_TAG "%s:%d->" fmt, \
__func__, __LINE__, ##__VA_ARGS__)
#define debug_block(__block__) do {if(DBUG_BLOCK) {__block__}} while(0)
#define LED_MAGIC 'T'
#define GET_LED_CTRL_IO_LEVEL _IOR(LED_MAGIC, 0, int)
#define SET_LED_BRIGHTNESS _IOR(LED_MAGIC, 1, int)
#define LED_GPIO_LOW 0
#define LED_GPIO_HIGH 1
#define DEV_NAME_MAX_LEN 19
#define LED_DEV_NUM 2
#define CHECK_VALID_INDEX(__X__) do {if(__X__ < 0) return -1;} while(0)
#define USE_THREAD 1
typedef struct {
unsigned int freq;
unsigned int active_keep;
unsigned int one_cycle_keep;
}st_led_param;
typedef enum {
APPLY_OVER,
RUNNING,
CLOSE_LED,
} enum_change_type;
typedef struct {
#if USE_THREAD
struct task_struct* g_pwm_thread;
#else
struct hrtimer led_timer;
ktime_t led_kt;
#endif
int num;
int led_gpio;
volatile unsigned long led_active; /* activated duty cycle */
volatile unsigned long led_inactive; /* for changing brightness */
volatile bool is_pwm_high; /* sign activated and inactivated status */
volatile enum_change_type change_type; /* timer function flag. */
}led_brightness_param;
typedef struct {
unsigned int dev_num;
unsigned int status;
} led_sensor_status;
typedef struct itech_led_device {
dev_t devno;
struct device *dev;
struct class *cls;
struct cdev c_dev;
char name[DEV_NAME_MAX_LEN + 1];
int ctrl_gpio; // signal pin for control led
int crtl_irq;
bool occur_irq;
int irq_result;
led_brightness_param param;
} st_itech_led_dev;
static DECLARE_WAIT_QUEUE_HEAD(led_irq_in_waitq);
static st_itech_led_dev itech_leds[LED_DEV_NUM];
extern int request_itech_misc_gpio(const char* pin_bank, const char* user);
extern int init_one_gpio(struct platform_device *pdev, char* name,
struct gpio_config* const pconfig);
static void reset_pwm(int gpio)
{
gpio_direction_output(gpio, LED_GPIO_LOW);
}
#if USE_THREAD
static int pwm_thread_func(void *args)
{
led_brightness_param* pLedParam = (led_brightness_param*)args;
while(true) {
if(pLedParam->change_type == CLOSE_LED) {
reset_pwm(pLedParam->led_gpio);
print_info("close led(%d)", pLedParam->num);
pLedParam->change_type = APPLY_OVER;
return 0;
}
if(pLedParam->is_pwm_high) {
gpio_direction_output(pLedParam->led_gpio, LED_GPIO_HIGH);
pLedParam->is_pwm_high = false;
ndelay(pLedParam->led_active);
} else {
gpio_direction_output(pLedParam->led_gpio, LED_GPIO_LOW);
pLedParam->is_pwm_high = true;
ndelay(pLedParam->led_inactive);
}
}
return 0;
}
#else
static enum hrtimer_restart led_one_cycle(struct hrtimer *timer) {
led_brightness_param* pLedParam = (led_brightness_param*)timer;
if(pLedParam->change_type == CLOSE_LED) {
reset_pwm(pLedParam->led_gpio);
print_info("close led(%d)", pLedParam->num);
pLedParam->change_type = APPLY_OVER;
return HRTIMER_NORESTART;
}
if(pLedParam->is_pwm_high) {
gpio_direction_output(pLedParam->led_gpio, LED_GPIO_HIGH);
pLedParam->is_pwm_high = false;
pLedParam->led_kt = ktime_set(0, pLedParam->led_active);
} else {
gpio_direction_output(pLedParam->led_gpio, LED_GPIO_LOW);
pLedParam->is_pwm_high = true;
pLedParam->led_kt = ktime_set(0, pLedParam->led_inactive);
}
hrtimer_forward_now(timer, pLedParam->led_kt);
return HRTIMER_RESTART;
}
#endif
static void updata_led_brightness(int dev_index,st_led_param *led_param) {
unsigned long one_cycle_time = 1000*1000*1000L/led_param->freq; // unit is nano second.
unsigned long led_activited_time = (unsigned long)(led_param->active_keep*one_cycle_time/led_param->one_cycle_keep);
unsigned long led_inactivited_time = one_cycle_time - led_activited_time;
enum_change_type pwmStatus = itech_leds[dev_index].param.change_type;
print_dbg("dev_index = %d\n", dev_index);
print_dbg("led_activited_time = %lu", led_activited_time);
if(led_activited_time != 0) {
if(pwmStatus != CLOSE_LED) {
while(itech_leds[dev_index].param.is_pwm_high) {
ndelay(one_cycle_time/led_param->one_cycle_keep);
}
}
itech_leds[dev_index].param.led_active = led_activited_time;
itech_leds[dev_index].param.led_inactive = led_inactivited_time;
if(pwmStatus == CLOSE_LED) {
itech_leds[dev_index].param.change_type = RUNNING;
itech_leds[dev_index].param.is_pwm_high = false;
#if USE_THREAD
itech_leds[dev_index].param.g_pwm_thread =
kthread_run(pwm_thread_func,&itech_leds[dev_index].param,"pwm_func");
if(IS_ERR(itech_leds[dev_index].param.g_pwm_thread)) {
print_err("pwm_func thread create failed!");
}
#else
itech_leds[dev_index].param.led_kt = ktime_set(0, 10); // 10 nano seconds
hrtimer_start(&itech_leds[dev_index].param.led_timer,
itech_leds[dev_index].param.led_kt, HRTIMER_MODE_REL);
#endif
}
} else {
itech_leds[dev_index].param.change_type = CLOSE_LED;
while(pwmStatus != CLOSE_LED
&& itech_leds[dev_index].param.change_type) {
udelay(1000);
}
pwmStatus = CLOSE_LED;
itech_leds[dev_index].param.change_type = pwmStatus;
}
}
static int find_dev_index(struct file *fp)
{
int index = -1;
struct inode *inode = fp->f_path.dentry->d_inode;
index = MINOR(inode->i_rdev);
if(index < 0 || index >= LED_DEV_NUM ||
itech_leds[index].param.num != index) {
print_err("dev index is invalid.\n");
return -1;
}
return index;
}
irqreturn_t irq_thread_handler(int irq, void *dev_id)
{
st_itech_led_dev* dev = (st_itech_led_dev*)dev_id;
print_dbg("irq = %d, dev->crtl_irq: %d", irq, dev->crtl_irq);
if(irq != dev->crtl_irq) {
return IRQ_NONE;
}
dev->occur_irq = true;
udelay(500);
dev->irq_result = gpio_get_value(dev->ctrl_gpio);
print_dbg("gpio_level: %d", gpio_get_value(dev->ctrl_gpio));
print_dbg("irq_result: %d", dev->irq_result);
wake_up_interruptible(&led_irq_in_waitq);
return IRQ_HANDLED;
}
irqreturn_t sensor_irq_handler(int irq, void *dev_id)
{
return IRQ_WAKE_THREAD;
}
static void init_light_sensor_irq(st_itech_led_dev* dev)
{
int ret = -1;
int irq_tmp = gpio_to_irq(dev->ctrl_gpio);
if (ENXIO == irq_tmp) {
print_err("get irq num failed.\n");
return;
}
dev->crtl_irq = irq_tmp;
print_info("request led(%d) sensor irq(%d)", dev->param.num, irq_tmp);
ret = request_threaded_irq(irq_tmp, sensor_irq_handler, irq_thread_handler,
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "light_sensor_irq_up", dev);
if (ret) {
print_err("request_threaded_irq irq_rising_handler ERROR!!!\n");
return ;
}
}
static int init_led_pwm(struct platform_device *pdev, st_itech_led_dev* pLed)
{
int val;
const char* buf;
struct gpio_config config;
struct device_node *np = pdev->dev.of_node;
if (of_property_read_string(np, "dev_name", &buf)) {
print_err("get resourcce dev_name fail.\n");
return -1;
}
val = strlen(buf);
val = val < DEV_NAME_MAX_LEN?val:DEV_NAME_MAX_LEN;
strncpy(pLed->name, buf, val);
pLed->name[val] = '\0';
print_dbg("name = %s\n", pLed->name);
if (init_one_gpio(pdev, "control_gpio", &config) < 0) {
print_info("get resourcce control_gpio fail.\n");
pLed->ctrl_gpio = -1;
} else {
pLed->ctrl_gpio = config.gpio;
print_dbg("itech_led.ctrl_gpio = %d\n", pLed->ctrl_gpio);
init_light_sensor_irq(pLed);
}
if(init_one_gpio(pdev, "analog_pwm_gpio", &config) < 0) {
print_err("get resourcce analog_pwm_gpio fail.\n");
return -1;
}
pLed->param.led_gpio = config.gpio;
print_dbg("led_pwm_gpio = %d\n", pLed->param.led_gpio);
pLed->param.change_type = CLOSE_LED;
#if !USE_THREAD
hrtimer_init(&pLed->param.led_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
pLed->param.led_timer.function = led_one_cycle;
#endif
return 0;
}
// ------------------------------------
// driver function
// ------------------------------------
static int led_open(struct inode *ip, struct file *fp)
{
print_dbg("enter.\n");
return 0;
}
static unsigned int led_poll(struct file *fp, struct poll_table_struct *wait)
{
unsigned int mask = POLLIN | POLLRDNORM;
int dev_index = find_dev_index(fp);
CHECK_VALID_INDEX(dev_index);
if(!itech_leds[dev_index].occur_irq) {
poll_wait(fp, &led_irq_in_waitq, wait);
mask = 0;
}
itech_leds[dev_index].occur_irq = false;
return mask;
}
static ssize_t led_read (struct file *fp, char __user *buf, size_t length, loff_t *ppos)
{
int err = -1;
int dev_index = find_dev_index(fp);
led_sensor_status stStatus;
CHECK_VALID_INDEX(dev_index);
if(length < sizeof(led_sensor_status)) {
print_err("param is error, buffer size is not enough.");
return -EINVAL;
}
stStatus.dev_num = dev_index;
stStatus.status = itech_leds[dev_index].irq_result;
err = copy_to_user(buf, &stStatus, sizeof(led_sensor_status));
print_dbg("err = %d", err);
return err==0?sizeof(led_sensor_status):-1;
}
static int led_release(struct inode *ip, struct file *fp)
{
print_dbg("enter.\n");
return 0;
}
static long led_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) {
st_led_param led_data; // For led brightness control.
int dev_index = find_dev_index(fp);
CHECK_VALID_INDEX(dev_index);
switch(cmd) {
case GET_LED_CTRL_IO_LEVEL:
{
int ctrl_io_level = 0;
int ret = -1;
if(itech_leds[dev_index].ctrl_gpio < 0) {
print_info("no control io is attach to this led.");
return -1;
}
ctrl_io_level = gpio_get_value(itech_leds[dev_index].ctrl_gpio);
ret = copy_to_user((void *)arg, &ctrl_io_level, sizeof(int));
print_dbg("...pir_level[%d]...ret[%d]\n", ctrl_io_level, ret);
} break;
case SET_LED_BRIGHTNESS:
if(!copy_from_user(&led_data, (st_led_param*)arg, sizeof(st_led_param))) {
updata_led_brightness(dev_index, &led_data);
}
break;
default:
print_err("...invalid cmd!!!\n");
break;
}
return 0;
}
// ------------------------------------
/*
* 2018/12/15 by atom.zhou
* For some history reasons, the "pir" lable not only means PIR, it conatins
* the common IO control now, we have added IO control as below:
* 1. LED brightness control.
*/
struct file_operations itech_led_fops = {
.owner = THIS_MODULE,
.open = led_open,
.poll = led_poll,
.read = led_read,
.release = led_release,
.unlocked_ioctl = led_ioctl,
};
static int itech_led_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
int val;
const char* buf = NULL;
dev_t major;
dev_t minor;
st_itech_led_dev* p_itech_led = NULL;
if (of_property_read_u32(np, "led_num", &val)) {
print_err("get led_num fail.\n");
return -1;
}
if(val >= LED_DEV_NUM || val < 0) {
print_err("%s led_num(%d) is out of valid value(0-%d)\n",
buf, val, LED_DEV_NUM-1);
return -1;
}
p_itech_led = &itech_leds[val];
p_itech_led->param.num = val;
if(init_led_pwm(pdev, p_itech_led) < 0) {
print_err("init led(%d) fail.", val);
return -1;
}
if(alloc_chrdev_region(&(p_itech_led->devno), 0, 1, p_itech_led->name)) {
print_err("alloc_chrdev_region %s fail.\n", p_itech_led->name);
goto out;
}
major = MAJOR(p_itech_led->devno);
minor = p_itech_led->param.num;
print_dbg("major = %d minor = %d\n", major, minor);
p_itech_led->devno = MKDEV(major, minor);
cdev_init(&p_itech_led->c_dev, &itech_led_fops);
p_itech_led->c_dev.owner = THIS_MODULE;
p_itech_led->c_dev.ops = &itech_led_fops;
if(cdev_add(&p_itech_led->c_dev, p_itech_led->devno, 1)) {
print_err("cdev_add %s fail.\n", p_itech_led->name);
goto out_unreg_chrdev;
}
p_itech_led->cls = class_create(THIS_MODULE, p_itech_led->name);
if (IS_ERR(p_itech_led->cls)) {
print_err("class_create %s fail.\n", p_itech_led->name);
goto out_cdev_del;
}
p_itech_led->dev = device_create(p_itech_led->cls, NULL, p_itech_led->devno,
NULL, p_itech_led->name);
if (IS_ERR(p_itech_led->dev)) {
print_err("device_create %s fail.\n", p_itech_led->name);
goto out_class_destroy;
}
return 0;
out_class_destroy:
class_destroy(p_itech_led->cls);
p_itech_led->cls = NULL;
out_cdev_del:
cdev_del(&p_itech_led->c_dev);
out_unreg_chrdev:
unregister_chrdev_region(p_itech_led->devno, 1);
out:
print_err("Initialisation failed\n");
return -1;
}
static int itech_led_remove(struct platform_device *pdev)
{
int i = 0;
for(i = 0;i < LED_DEV_NUM;i++) {
if(itech_leds[i].cls != NULL) {
if(itech_leds[i].dev != NULL) {
cdev_del(&itech_leds[i].c_dev);
device_destroy(itech_leds[i].cls, itech_leds[i].devno);
}
class_destroy(itech_leds[i].cls);
unregister_chrdev_region(itech_leds[i].devno, 1);
}
}
return 0;
}
static const struct of_device_id itech_led_ids[] = {
{ .compatible = "icetech, misc-led" },
{ /* Sentinel */ }
};
static struct platform_driver itech_led_driver = {
.probe = itech_led_probe,
.remove = itech_led_remove,
.driver = {
.owner = THIS_MODULE,
.name = "misc-led",
.of_match_table = itech_led_ids,
},
};
module_platform_driver(itech_led_driver);
MODULE_AUTHOR("binn.chen@icetech-china.com");
MODULE_DESCRIPTION("led config driver");
MODULE_LICENSE("GPL");