blob: aca766735e007e0eeb6162c649cf07e2131bbb56 [file] [log] [blame]
Trilok Soni1e52e432012-01-13 18:06:14 +05301/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved.
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07002 *
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License version 2 and
5 * only version 2 as published by the Free Software Foundation.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 */
13
14#include <linux/module.h>
15#include <linux/kernel.h>
16#include <linux/init.h>
17#include <linux/io.h>
18#include <linux/delay.h>
19#include <linux/workqueue.h>
20#include <linux/pm.h>
21#include <linux/mfd/pmic8058.h>
22#include <linux/jiffies.h>
23#include <linux/suspend.h>
Trilok Sonieecb28c2011-07-20 16:24:14 +010024#include <linux/percpu.h>
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070025#include <linux/interrupt.h>
Rohit Vaswaniead426f2012-01-05 20:24:52 -080026#include <asm/fiq.h>
27#include <asm/hardware/gic.h>
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070028#include <mach/msm_iomap.h>
29#include <asm/mach-types.h>
Rohit Vaswani9cea7ca2012-07-06 19:54:24 -070030#include <asm/cacheflush.h>
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070031#include <mach/scm.h>
Rohit Vaswani085a9332011-09-28 18:57:24 -070032#include <mach/socinfo.h>
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070033#include "msm_watchdog.h"
Rohit Vaswani085a9332011-09-28 18:57:24 -070034#include "timer.h"
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070035
Jeff Ohlstein7e668552011-10-06 16:17:25 -070036#define MODULE_NAME "msm_watchdog"
37
Rohit Vaswani085a9332011-09-28 18:57:24 -070038#define TCSR_WDT_CFG 0x30
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070039
Rohit Vaswanic77e4a62012-08-09 18:10:28 -070040#define WDT_RST 0x0
41#define WDT_EN 0x8
42#define WDT_STS 0xC
43#define WDT_BARK_TIME 0x14
44#define WDT_BITE_TIME 0x24
Rohit Vaswani085a9332011-09-28 18:57:24 -070045
Jeff Ohlstein7e668552011-10-06 16:17:25 -070046#define WDT_HZ 32768
47
Rohit Vaswaniead426f2012-01-05 20:24:52 -080048struct msm_watchdog_dump msm_dump_cpu_ctx;
49
Rohit Vaswanic77e4a62012-08-09 18:10:28 -070050static void __iomem *msm_wdt_base;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070051
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070052static unsigned long delay_time;
Jeff Ohlstein7e668552011-10-06 16:17:25 -070053static unsigned long bark_time;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070054static unsigned long long last_pet;
Rohit Vaswanic9fdd442012-03-19 14:18:32 -070055static bool has_vic;
Rohit Vaswanic77e4a62012-08-09 18:10:28 -070056static unsigned int msm_wdog_irq;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070057
58/*
59 * On the kernel command line specify
60 * msm_watchdog.enable=1 to enable the watchdog
61 * By default watchdog is turned on
62 */
63static int enable = 1;
64module_param(enable, int, 0);
65
66/*
67 * If the watchdog is enabled at bootup (enable=1),
68 * the runtime_disable sysfs node at
69 * /sys/module/msm_watchdog/runtime_disable
70 * can be used to deactivate the watchdog.
71 * This is a one-time setting. The watchdog
72 * cannot be re-enabled once it is disabled.
73 */
74static int runtime_disable;
75static DEFINE_MUTEX(disable_lock);
76static int wdog_enable_set(const char *val, struct kernel_param *kp);
77module_param_call(runtime_disable, wdog_enable_set, param_get_int,
78 &runtime_disable, 0644);
79
80/*
81 * On the kernel command line specify msm_watchdog.appsbark=1 to handle
82 * watchdog barks in Linux. By default barks are processed by the secure side.
83 */
84static int appsbark;
85module_param(appsbark, int, 0);
86
Rohit Vaswaniead426f2012-01-05 20:24:52 -080087static int appsbark_fiq;
88
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070089/*
90 * Use /sys/module/msm_watchdog/parameters/print_all_stacks
91 * to control whether stacks of all running
92 * processes are printed when a wdog bark is received.
93 */
94static int print_all_stacks = 1;
95module_param(print_all_stacks, int, S_IRUGO | S_IWUSR);
96
97/* Area for context dump in secure mode */
98static void *scm_regsave;
99
Trilok Sonieecb28c2011-07-20 16:24:14 +0100100static struct msm_watchdog_pdata __percpu **percpu_pdata;
101
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700102static void pet_watchdog_work(struct work_struct *work);
103static void init_watchdog_work(struct work_struct *work);
104static DECLARE_DELAYED_WORK(dogwork_struct, pet_watchdog_work);
105static DECLARE_WORK(init_dogwork_struct, init_watchdog_work);
106
Rohit Vaswaniead426f2012-01-05 20:24:52 -0800107/* Called from the FIQ bark handler */
108void msm_wdog_bark_fin(void)
109{
Rohit Vaswani9cea7ca2012-07-06 19:54:24 -0700110 flush_cache_all();
Rohit Vaswaniead426f2012-01-05 20:24:52 -0800111 pr_crit("\nApps Watchdog bark received - Calling Panic\n");
112 panic("Apps Watchdog Bark received\n");
113}
114
Jeff Ohlstein7e668552011-10-06 16:17:25 -0700115static int msm_watchdog_suspend(struct device *dev)
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700116{
Jeff Ohlstein7e668552011-10-06 16:17:25 -0700117 if (!enable)
118 return 0;
119
Rohit Vaswanic77e4a62012-08-09 18:10:28 -0700120 __raw_writel(1, msm_wdt_base + WDT_RST);
121 __raw_writel(0, msm_wdt_base + WDT_EN);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700122 mb();
Jeff Ohlstein7e668552011-10-06 16:17:25 -0700123 return 0;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700124}
125
Jeff Ohlstein7e668552011-10-06 16:17:25 -0700126static int msm_watchdog_resume(struct device *dev)
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700127{
Jeff Ohlstein7e668552011-10-06 16:17:25 -0700128 if (!enable)
129 return 0;
130
Rohit Vaswanic77e4a62012-08-09 18:10:28 -0700131 __raw_writel(1, msm_wdt_base + WDT_EN);
132 __raw_writel(1, msm_wdt_base + WDT_RST);
Jeff Ohlstein7e668552011-10-06 16:17:25 -0700133 mb();
134 return 0;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700135}
136
137static int panic_wdog_handler(struct notifier_block *this,
138 unsigned long event, void *ptr)
139{
140 if (panic_timeout == 0) {
Rohit Vaswanic77e4a62012-08-09 18:10:28 -0700141 __raw_writel(0, msm_wdt_base + WDT_EN);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700142 mb();
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700143 } else {
Jeff Ohlstein7e668552011-10-06 16:17:25 -0700144 __raw_writel(WDT_HZ * (panic_timeout + 4),
Rohit Vaswanic77e4a62012-08-09 18:10:28 -0700145 msm_wdt_base + WDT_BARK_TIME);
Jeff Ohlstein7e668552011-10-06 16:17:25 -0700146 __raw_writel(WDT_HZ * (panic_timeout + 4),
Rohit Vaswanic77e4a62012-08-09 18:10:28 -0700147 msm_wdt_base + WDT_BITE_TIME);
148 __raw_writel(1, msm_wdt_base + WDT_RST);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700149 }
150 return NOTIFY_DONE;
151}
152
153static struct notifier_block panic_blk = {
154 .notifier_call = panic_wdog_handler,
155};
156
Jeff Ohlsteinb1e211e2012-05-01 18:55:05 -0700157struct wdog_disable_work_data {
158 struct work_struct work;
159 struct completion complete;
160};
161
162static void wdog_disable_work(struct work_struct *work)
163{
164 struct wdog_disable_work_data *work_data =
165 container_of(work, struct wdog_disable_work_data, work);
Rohit Vaswanic77e4a62012-08-09 18:10:28 -0700166 __raw_writel(0, msm_wdt_base + WDT_EN);
Jeff Ohlsteinb1e211e2012-05-01 18:55:05 -0700167 mb();
168 if (has_vic) {
Rohit Vaswanic77e4a62012-08-09 18:10:28 -0700169 free_irq(msm_wdog_irq, 0);
Jeff Ohlsteinb1e211e2012-05-01 18:55:05 -0700170 } else {
Rohit Vaswanic77e4a62012-08-09 18:10:28 -0700171 disable_percpu_irq(msm_wdog_irq);
Jeff Ohlsteinb1e211e2012-05-01 18:55:05 -0700172 if (!appsbark_fiq) {
Rohit Vaswanic77e4a62012-08-09 18:10:28 -0700173 free_percpu_irq(msm_wdog_irq,
Jeff Ohlsteinb1e211e2012-05-01 18:55:05 -0700174 percpu_pdata);
175 free_percpu(percpu_pdata);
176 }
177 }
178 enable = 0;
179 atomic_notifier_chain_unregister(&panic_notifier_list, &panic_blk);
180 cancel_delayed_work(&dogwork_struct);
181 /* may be suspended after the first write above */
Rohit Vaswanic77e4a62012-08-09 18:10:28 -0700182 __raw_writel(0, msm_wdt_base + WDT_EN);
Jeff Ohlsteinb1e211e2012-05-01 18:55:05 -0700183 complete(&work_data->complete);
184 pr_info("MSM Watchdog deactivated.\n");
185}
186
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700187static int wdog_enable_set(const char *val, struct kernel_param *kp)
188{
189 int ret = 0;
190 int old_val = runtime_disable;
Jeff Ohlsteinb1e211e2012-05-01 18:55:05 -0700191 struct wdog_disable_work_data work_data;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700192
193 mutex_lock(&disable_lock);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700194 if (!enable) {
195 printk(KERN_INFO "MSM Watchdog is not active.\n");
196 ret = -EINVAL;
197 goto done;
198 }
199
200 ret = param_set_int(val, kp);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700201 if (ret)
202 goto done;
203
Jeff Ohlsteinb1e211e2012-05-01 18:55:05 -0700204 if (runtime_disable == 1) {
205 if (old_val)
206 goto done;
207 init_completion(&work_data.complete);
208 INIT_WORK_ONSTACK(&work_data.work, wdog_disable_work);
209 schedule_work_on(0, &work_data.work);
210 wait_for_completion(&work_data.complete);
211 } else {
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700212 runtime_disable = old_val;
213 ret = -EINVAL;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700214 }
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700215done:
216 mutex_unlock(&disable_lock);
217 return ret;
218}
219
Jeff Ohlstein535280e2011-12-09 19:34:30 -0800220unsigned min_slack_ticks = UINT_MAX;
221unsigned long long min_slack_ns = ULLONG_MAX;
222
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700223void pet_watchdog(void)
224{
Jeff Ohlstein535280e2011-12-09 19:34:30 -0800225 int slack;
226 unsigned long long time_ns;
227 unsigned long long slack_ns;
228 unsigned long long bark_time_ns = bark_time * 1000000ULL;
229
Vikram Mulukutla76f08e02012-05-01 19:15:19 -0700230 if (!enable)
231 return;
232
Rohit Vaswanic77e4a62012-08-09 18:10:28 -0700233 slack = __raw_readl(msm_wdt_base + WDT_STS) >> 3;
Jeff Ohlstein535280e2011-12-09 19:34:30 -0800234 slack = ((bark_time*WDT_HZ)/1000) - slack;
235 if (slack < min_slack_ticks)
236 min_slack_ticks = slack;
Rohit Vaswanic77e4a62012-08-09 18:10:28 -0700237 __raw_writel(1, msm_wdt_base + WDT_RST);
Jeff Ohlstein535280e2011-12-09 19:34:30 -0800238 time_ns = sched_clock();
239 slack_ns = (last_pet + bark_time_ns) - time_ns;
240 if (slack_ns < min_slack_ns)
241 min_slack_ns = slack_ns;
242 last_pet = time_ns;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700243}
244
245static void pet_watchdog_work(struct work_struct *work)
246{
247 pet_watchdog();
248
249 if (enable)
250 schedule_delayed_work_on(0, &dogwork_struct, delay_time);
251}
252
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700253static irqreturn_t wdog_bark_handler(int irq, void *dev_id)
254{
255 unsigned long nanosec_rem;
256 unsigned long long t = sched_clock();
257 struct task_struct *tsk;
258
259 nanosec_rem = do_div(t, 1000000000);
260 printk(KERN_INFO "Watchdog bark! Now = %lu.%06lu\n", (unsigned long) t,
261 nanosec_rem / 1000);
262
263 nanosec_rem = do_div(last_pet, 1000000000);
264 printk(KERN_INFO "Watchdog last pet at %lu.%06lu\n", (unsigned long)
265 last_pet, nanosec_rem / 1000);
266
267 if (print_all_stacks) {
268
269 /* Suspend wdog until all stacks are printed */
Jeff Ohlstein7e668552011-10-06 16:17:25 -0700270 msm_watchdog_suspend(NULL);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700271
272 printk(KERN_INFO "Stack trace dump:\n");
273
274 for_each_process(tsk) {
275 printk(KERN_INFO "\nPID: %d, Name: %s\n",
276 tsk->pid, tsk->comm);
277 show_stack(tsk, NULL);
278 }
279
Jeff Ohlstein7e668552011-10-06 16:17:25 -0700280 msm_watchdog_resume(NULL);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700281 }
282
283 panic("Apps watchdog bark received!");
284 return IRQ_HANDLED;
285}
286
287#define SCM_SET_REGSAVE_CMD 0x2
288
Rohit Vaswani085a9332011-09-28 18:57:24 -0700289static void configure_bark_dump(void)
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700290{
291 int ret;
292 struct {
293 unsigned addr;
294 int len;
295 } cmd_buf;
296
Jeff Ohlstein7e668552011-10-06 16:17:25 -0700297 if (!appsbark) {
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700298 scm_regsave = (void *)__get_free_page(GFP_KERNEL);
299
300 if (scm_regsave) {
301 cmd_buf.addr = __pa(scm_regsave);
302 cmd_buf.len = PAGE_SIZE;
303
304 ret = scm_call(SCM_SVC_UTIL, SCM_SET_REGSAVE_CMD,
305 &cmd_buf, sizeof(cmd_buf), NULL, 0);
306 if (ret)
307 pr_err("Setting register save address failed.\n"
308 "Registers won't be dumped on a dog "
309 "bite\n");
Rohit Vaswani085a9332011-09-28 18:57:24 -0700310 } else {
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700311 pr_err("Allocating register save space failed\n"
312 "Registers won't be dumped on a dog bite\n");
313 /*
314 * No need to bail if allocation fails. Simply don't
315 * send the command, and the secure side will reset
316 * without saving registers.
317 */
Rohit Vaswani085a9332011-09-28 18:57:24 -0700318 }
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700319 }
Rohit Vaswani085a9332011-09-28 18:57:24 -0700320}
321
Jeff Ohlsteinb1e211e2012-05-01 18:55:05 -0700322struct fiq_handler wdog_fh = {
323 .name = MODULE_NAME,
324};
325
Rohit Vaswani085a9332011-09-28 18:57:24 -0700326static void init_watchdog_work(struct work_struct *work)
327{
Jeff Ohlstein7e668552011-10-06 16:17:25 -0700328 u64 timeout = (bark_time * WDT_HZ)/1000;
Jeff Ohlsteinb1e211e2012-05-01 18:55:05 -0700329 void *stack;
330 int ret;
331
332 if (has_vic) {
Rohit Vaswanic77e4a62012-08-09 18:10:28 -0700333 ret = request_irq(msm_wdog_irq, wdog_bark_handler, 0,
Jeff Ohlsteinb1e211e2012-05-01 18:55:05 -0700334 "apps_wdog_bark", NULL);
335 if (ret)
336 return;
337 } else if (appsbark_fiq) {
338 claim_fiq(&wdog_fh);
339 set_fiq_handler(&msm_wdog_fiq_start, msm_wdog_fiq_length);
340 stack = (void *)__get_free_pages(GFP_KERNEL, THREAD_SIZE_ORDER);
341 if (!stack) {
342 pr_info("No free pages available - %s fails\n",
343 __func__);
344 return;
345 }
346
347 msm_wdog_fiq_setup(stack);
Rohit Vaswanic77e4a62012-08-09 18:10:28 -0700348 gic_set_irq_secure(msm_wdog_irq);
Jeff Ohlsteinb1e211e2012-05-01 18:55:05 -0700349 } else {
350 percpu_pdata = alloc_percpu(struct msm_watchdog_pdata *);
351 if (!percpu_pdata) {
352 pr_err("%s: memory allocation failed for percpu data\n",
353 __func__);
354 return;
355 }
356
357 /* Must request irq before sending scm command */
Rohit Vaswanic77e4a62012-08-09 18:10:28 -0700358 ret = request_percpu_irq(msm_wdog_irq,
Jeff Ohlsteinb1e211e2012-05-01 18:55:05 -0700359 wdog_bark_handler, "apps_wdog_bark", percpu_pdata);
360 if (ret) {
361 free_percpu(percpu_pdata);
362 return;
363 }
364 }
Jeff Ohlstein5edb4ae2012-03-06 16:39:50 -0800365
366 configure_bark_dump();
367
Rohit Vaswanic77e4a62012-08-09 18:10:28 -0700368 __raw_writel(timeout, msm_wdt_base + WDT_BARK_TIME);
369 __raw_writel(timeout + 3*WDT_HZ, msm_wdt_base + WDT_BITE_TIME);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700370
371 schedule_delayed_work_on(0, &dogwork_struct, delay_time);
372
373 atomic_notifier_chain_register(&panic_notifier_list,
374 &panic_blk);
375
Rohit Vaswanic77e4a62012-08-09 18:10:28 -0700376 __raw_writel(1, msm_wdt_base + WDT_EN);
377 __raw_writel(1, msm_wdt_base + WDT_RST);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700378 last_pet = sched_clock();
379
Rohit Vaswaniead426f2012-01-05 20:24:52 -0800380 if (!has_vic)
Rohit Vaswanic77e4a62012-08-09 18:10:28 -0700381 enable_percpu_irq(msm_wdog_irq, IRQ_TYPE_EDGE_RISING);
Rohit Vaswaniead426f2012-01-05 20:24:52 -0800382
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700383 printk(KERN_INFO "MSM Watchdog Initialized\n");
384
385 return;
386}
387
Jeff Ohlstein7e668552011-10-06 16:17:25 -0700388static int msm_watchdog_probe(struct platform_device *pdev)
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700389{
Jeff Ohlstein7e668552011-10-06 16:17:25 -0700390 struct msm_watchdog_pdata *pdata = pdev->dev.platform_data;
Jeff Ohlstein7e668552011-10-06 16:17:25 -0700391
392 if (!enable || !pdata || !pdata->pet_time || !pdata->bark_time) {
393 printk(KERN_INFO "MSM Watchdog Not Initialized\n");
394 return -ENODEV;
395 }
396
Jeff Ohlstein7e668552011-10-06 16:17:25 -0700397 bark_time = pdata->bark_time;
Rohit Vaswanic9fdd442012-03-19 14:18:32 -0700398 has_vic = pdata->has_vic;
Rohit Vaswaniead426f2012-01-05 20:24:52 -0800399 if (!pdata->has_secure) {
400 appsbark = 1;
401 appsbark_fiq = pdata->use_kernel_fiq;
402 }
Jeff Ohlstein7e668552011-10-06 16:17:25 -0700403
Rohit Vaswanic77e4a62012-08-09 18:10:28 -0700404 msm_wdt_base = pdata->base;
405 msm_wdog_irq = platform_get_irq(pdev, 0);
Jeff Ohlstein7e668552011-10-06 16:17:25 -0700406
Jeff Ohlstein7e668552011-10-06 16:17:25 -0700407 /*
408 * This is only temporary till SBLs turn on the XPUs
409 * This initialization will be done in SBLs on a later releases
410 */
411 if (cpu_is_msm9615())
412 __raw_writel(0xF, MSM_TCSR_BASE + TCSR_WDT_CFG);
413
Joel Kinge7ca6f72012-02-09 20:51:25 -0800414 if (pdata->needs_expired_enable)
415 __raw_writel(0x1, MSM_CLK_CTL_BASE + 0x3820);
416
Jeff Ohlstein7e668552011-10-06 16:17:25 -0700417 delay_time = msecs_to_jiffies(pdata->pet_time);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700418 schedule_work_on(0, &init_dogwork_struct);
419 return 0;
420}
421
Jeff Ohlstein7e668552011-10-06 16:17:25 -0700422static const struct dev_pm_ops msm_watchdog_dev_pm_ops = {
423 .suspend_noirq = msm_watchdog_suspend,
424 .resume_noirq = msm_watchdog_resume,
425};
426
427static struct platform_driver msm_watchdog_driver = {
428 .probe = msm_watchdog_probe,
Jeff Ohlstein7e668552011-10-06 16:17:25 -0700429 .driver = {
430 .name = MODULE_NAME,
431 .owner = THIS_MODULE,
432 .pm = &msm_watchdog_dev_pm_ops,
433 },
434};
435
436static int init_watchdog(void)
437{
438 return platform_driver_register(&msm_watchdog_driver);
439}
440
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700441late_initcall(init_watchdog);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700442MODULE_DESCRIPTION("MSM Watchdog Driver");
443MODULE_VERSION("1.0");
444MODULE_LICENSE("GPL v2");