[Linux Device Driver] 看门狗

0. 背景&&原理

我们假设有一个芯片,它自己能不间断的产生脉冲信号(隔一段时间产生,时间由硬件决定),它理所当然有自己的信号时序;比如说,它在每次脉冲前面都要有一个别的信号来通知它,

“嘿,老兄,一切正常,请继续保持工作”,
[Linux Device Driver] 看门狗_第1张图片
基于这样的约定,有人通知它,它就发脉冲;那万一在约定的时间内没人通知呢,那它就不产生脉冲了,直接“躺尸”,在这个芯片产生“躺尸”想法的时候,他会给外界发送一个信号。很常见的就是高低电平,比如本来高电平,芯片凉了,那就把一个信号线拉低变成低电平。
[Linux Device Driver] 看门狗_第2张图片

大家比较喜欢把这种行为,称为“喂狗”,你要是不喂了,狗就死给你看! 那我们怎么利用这种行为呢,不然有毛病啊,设计这种芯片干啥的。

很简单的例子是设备死机了,现代的软件实在是太庞大了,难保就遇到稳定性问题,如果我们能够利用这种支持电平变化的芯片,我们可以把这个芯片的reset脚(触发高低电平变化的信号线),连接到硬重启电路中。三极管、MOS管,这些能够把这种电平变化转换成功能。

整个思路就是这样: 某个芯片一直被喂狗,万一系统死机了,就没人喂狗; 狗凉了,引起电平变化;这个电平变化被硬件识别,触发整个设备重启;然后设备活了,继续坚强的生存下去。。。。。。

1. 代码分享

然后我就写了这样的代码,一个gpio被配置为中断,另一个gpio是通知它的,每次中断触发,调用工作队列,会给一个脉冲。

vendor/qcom/proprietary/devicetree-4.19/qcom/lagoon-mtp.dtsi
tpl5010 {
	compatible = "qcom,tpl5010";
	interrupt-parent = <&tlmm>;
	interrupts = <8 0x2>;
	qcom,gpio-tpl5010-int = <&tlmm 8 0x2002>;
	qcom,gpio-tpl5010-done = <&tlmm 7 0x2002>;
};
kernel/msm-4.19/drivers/tpl5010/tpl5010.c
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

static int tpl5010_gpio = 0;

//static int tpl5010_enable = 1;
//extern int tpl5010_flag = 0;

struct tpl5010_data {
	struct platform_device  *pdev;
	struct miscdevice miscdev;
	int gpio;
	int gpio_done;
	int tpl5010_det_irq;
	struct workqueue_struct *workqueue;
	struct delayed_work work;

};

static ssize_t tpl5010_enable_show(struct device *dev,
                                 struct device_attribute *attr, char *buf)
{
	int val;
	val =  gpio_get_value(tpl5010_gpio);
	return sprintf(buf, "%d\n", val);
}

static ssize_t tpl5010_enable_store(struct device *dev,
                                      struct device_attribute *attr,
                                      const char *buf, size_t size)
{
	ssize_t ret;
	unsigned int state;
	pr_err("#tpl5010#,debug1,fun=%s,tpl5010_gpio=%d\n",__func__,tpl5010_gpio);

	ret = kstrtouint(buf, 10, &state);
	if (state) {
		gpio_direction_output(tpl5010_gpio, 1);
		printk("#fun=%s#,enter\n",__func__);
	}else{
		gpio_direction_output(tpl5010_gpio, 0);
		printk("#fun=%s#,enter\n",__func__);
	}

	return size;
}

static DEVICE_ATTR(tpl5010_enable, S_IRUGO | S_IWUSR, tpl5010_enable_show, tpl5010_enable_store);

static const struct file_operations tpl5010_fops = {
	.owner = THIS_MODULE,
};

static void tpl5010_schedwork(struct work_struct *work)
{
	struct tpl5010_data *tpl5010 = container_of(to_delayed_work(work),struct tpl5010_data ,work);

	gpio_direction_output(tpl5010->gpio_done, 1);
	mdelay(10);
	gpio_direction_output(tpl5010->gpio_done, 0);
	enable_irq(tpl5010->tpl5010_det_irq);
	printk("#fun=%s#,enter\n",__func__);
	return;
}

static irqreturn_t gpio_tpl5010_detect_irq(int irq, void *data)
{
	struct tpl5010_data *tpl5010 = (struct tpl5010_data  *)data;
	disable_irq_nosync(tpl5010->tpl5010_det_irq);
	schedule_delayed_work(&tpl5010->work,msecs_to_jiffies(20));
	printk("#fun=%s#,enter\n",__func__);
	return IRQ_HANDLED;
}

static int qpnp_tpl5010_probe(struct platform_device *pdev)
{
	int rc = 0;
	//struct tpl5010_data *tpl5010 = platform_get_drvdata(pdev);
	struct tpl5010_data *tpl5010;
	struct device *this_device;
	const char *tpl5010_enable_gpio = "qcom,gpio-tpl5010-int";
	const char *tpl5010_enable_gpio_done = "qcom,gpio-tpl5010-done";
	tpl5010 = devm_kzalloc(&pdev->dev, sizeof(*tpl5010), GFP_KERNEL);

	if(!tpl5010) {
		dev_err(&tpl5010->pdev->dev,"fail to alloc tpl5010 mem\n");
		return -ENOMEM;
	}

	tpl5010->pdev = pdev;
	tpl5010->miscdev.minor = MISC_DYNAMIC_MINOR;
	tpl5010->miscdev.name = "ir_tpl5010";
	tpl5010->miscdev.fops = &tpl5010_fops;

//creat work queue and gpio wake
	tpl5010->workqueue = create_singlethread_workqueue("tpl5010_wq");
	INIT_DELAYED_WORK(&tpl5010->work, tpl5010_schedwork);

	if (!tpl5010->workqueue) {
		dev_err(&pdev->dev,
		"%s: Create WorkQueue Fail...\n", __func__);
		return -ENOMEM;
	}

	tpl5010->gpio= of_get_named_gpio(pdev->dev.of_node, tpl5010_enable_gpio, 0);

	if (tpl5010->gpio < 0) {
		dev_err(&pdev->dev, "Failed to get gpio: %d\n", tpl5010->gpio);
		rc = tpl5010->gpio;
		goto out_destory_workqueue;
	}

	rc = gpio_request(tpl5010->gpio, "msm-tpl5010-enable-gpio");
	if (rc < 0) {
		dev_err(&pdev->dev, "Failed to request gpio: %d\n", rc);
		goto out_destory_workqueue;
	}
	rc = gpio_direction_input(tpl5010->gpio);
	pr_err("#tpl5010#,debug,fun=%s,tpl5010->gpio=%d\n",__func__,tpl5010->gpio);

	if (rc < 0) {
		dev_err(&pdev->dev,
			"%s: Set GPIO %d as Input Fail (%d)\n", __func__,
					tpl5010->gpio, rc);
		goto out_free_irq;
	}

	tpl5010->tpl5010_det_irq = gpio_to_irq(tpl5010->gpio);

	if (tpl5010->tpl5010_det_irq < 0) {
		dev_err(&pdev->dev, "get tpl5010_det_irq failed\n");
		rc = tpl5010->tpl5010_det_irq;
		goto out_free_irq;
	}

	rc = request_irq(tpl5010->tpl5010_det_irq, gpio_tpl5010_detect_irq ,IRQF_TRIGGER_HIGH,
				"tpl5010-irq", tpl5010);

	if (rc < 0) {
		dev_err(&pdev->dev, "request for tpl5010_det_irq failed: %d\n",rc);
		goto out_free_irq;
	}

//#define IRQF_TRIGGER_RISING 0x00000001/*指定中断触发类型:上升沿有效*/
//#define IRQF_TRIGGER_FALLING 0x00000002/*中断触发类型:下降沿有效*/
//#define IRQF_TRIGGER_HIGH   0x00000004/*指定中断触发类型:高电平有效*/
//#define IRQF_TRIGGER_LOW 0x00000008/*指定中断触发类型:低电平有效*/

//gpio done
	tpl5010->gpio_done= of_get_named_gpio(pdev->dev.of_node, tpl5010_enable_gpio_done, 0);

	if (tpl5010->gpio_done < 0) {
		dev_err(&pdev->dev, "Failed to get gpio: %d\n", tpl5010->gpio_done);
		rc = tpl5010->gpio_done;
		goto out_free_irq;
	}

	rc = gpio_request(tpl5010->gpio_done, "msm-tpl5010-enable-gpio-done");

	if (rc < 0) {
		dev_err(&pdev->dev,
			"%s: Set GPIO %d as Output Fail (%d)\n", __func__,
					tpl5010->gpio_done, rc);
		goto out_free_irq;
	}
	pr_err("#tpl5010#,debug,fun=%s,tpl5010->gpio_done=%d\n",__func__,tpl5010->gpio_done);
	gpio_direction_output(tpl5010->gpio_done, 1);
	mdelay(10);
	gpio_direction_output(tpl5010->gpio_done, 0);

	rc = device_create_file(&pdev->dev, &dev_attr_tpl5010_enable);
	if (rc < 0) {
		dev_err(&pdev->dev, "failed to create WLED enable sysfs node rc:%d\n",
			rc);
	}

	this_device = tpl5010->miscdev.this_device;
	enable_irq_wake(tpl5010->tpl5010_det_irq);
	pr_err("#tpl5010#,fun=%s, probe success\n",__func__);
	return rc;

out_free_irq:
	if (gpio_is_valid(tpl5010->gpio))
		gpio_free(tpl5010->gpio);

out_destory_workqueue:
	destroy_workqueue(tpl5010 ->workqueue);

	return rc;
}

static int qpnp_tpl5010_remove(struct platform_device *pdev)
{
	struct tpl5010_data *tpl5010 = platform_get_drvdata(pdev);
	destroy_workqueue(tpl5010 ->workqueue);
	return 0;
}

const struct of_device_id qpnp_tpl5010_match_table[] = {
	{ .compatible = "qcom,tpl5010",},
	{ },
};

static struct platform_driver tpl5010_driver = {
	.driver		= {
		.name = "qcom,tpl5010",
		.of_match_table = qpnp_tpl5010_match_table,
	},
	.probe		= qpnp_tpl5010_probe,
	.remove		= qpnp_tpl5010_remove,
};

static int tpl5010_init(void)
{
	return platform_driver_register(&tpl5010_driver);
}
static void tpl5010_exit(void)
{
	platform_driver_unregister(&tpl5010_driver);
	return;
}
module_init(tpl5010_init);
module_exit(tpl5010_exit);
MODULE_LICENSE("GPL");

2. 重要思考

这个代码其实很简单,就一个gpio配置中断,然后中断触发会调用配置队列, 工作队列里面enable中断之前,会让done信号给个脉冲,这样写的原因是根据芯片的时序图来的。 还有一个关键点是probe函数里面,先给了一个脉冲,这个也是芯片手册中的资料,其他没啥特别的。

3. 效果如下

[Linux Device Driver] 看门狗_第3张图片

[Linux Device Driver] 看门狗_第4张图片

用以下命令手动触发设备死机之后,蓝线(reset)会拉低,这个变化被硬件电路识别到之后,会触发设备重启。

echo c > /proc/sysrq-trigger

你可能感兴趣的:(Linux,Device,Driver)