1 /*
2 * Copyright (C) 2021 HiSilicon (Shanghai) Technologies CO., LIMITED.
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 */
18
19 #include "watchdog.h"
20 #ifdef __HuaweiLite__
21 #include "pthread.h"
22 #include <sys/prctl.h>
23 #endif
24 #include "hi_osal.h"
25 #include "hi_wtdg_hal.h"
26 #include "securec.h"
27
28 #ifndef NULL
29 #define NULL ((void *)0)
30 #endif
31
32 #define hiwdt_reg(x) (HIWDT_BASE + (x))
33
34 #define HIWDT_LOAD 0x000
35 #define HIWDT_VALUE 0x004
36 #define HIWDT_CTRL 0x008
37 #define HIWDT_INTCLR 0x00C
38 #define HIWDT_RIS 0x010
39 #define HIWDT_MIS 0x014
40 #define HIWDT_LOCK 0xC00
41
42 #define HIWDT_UNLOCK_VAL 0x1ACCE551
43
44 volatile void *g_wtdg_reg_base = NULL;
45
46 #define hiwdt_io_address(x) ((uintptr_t)g_wtdg_reg_base + (x) - HIWDT_BASE)
47
48 #define hiwdt_readl(x) osal_readl(hiwdt_io_address(hiwdt_reg(x)))
49 #define hiwdt_writel(v, x) osal_writel(v, hiwdt_io_address(hiwdt_reg(x)))
50
51 /* debug */
52 #define HIDOG_PFX "HiDog: "
53 #define hidog_dbg(params...) osal_printk(HIDOG_PFX params)
54
55 /* module param */
56 #define HIDOG_TIMER_MARGIN 60
57 int default_margin = HIDOG_TIMER_MARGIN; /* in seconds */
58 #define HIDOG_TIMER_DEMULTIPLY 9
59
60 #define wtdg_check_securec_return(err) \
61 do { \
62 if ((err) != EOK) { \
63 return (-1); \
64 } \
65 } while (0)
66
67 int nodeamon = 0;
68
69 /* watchdog info */
70 static struct watchdog_info g_ident = {
71 .options = WDIOF_SETTIMEOUT |
72 WDIOF_KEEPALIVEPING |
73 WDIOF_MAGICCLOSE,
74 .firmware_version = 0,
75 .identity = "Hisilicon Watchdog",
76 };
77
78 /* local var */
79 static struct osal_spinlock g_hidog_lock;
80 static int g_cur_margin = HIDOG_TIMER_MARGIN;
81 static int g_need_iounmap = 0;
82
83 #ifdef __HuaweiLite__
84 pthread_t g_task_hidog_deamon = 0;
85 #else
86 struct osal_task *g_task_hidog_deamon = NULL;
87 #endif
88
89 #define HIDOG_EXIT 0
90 #define HIDOG_SELFCLR 1
91 #define HIDOG_EXTCLR 2
92
93 static volatile unsigned int g_hidog_state = 0;
94 static osal_atomic_t g_driver_open;
95 static unsigned int g_options = WDIOS_ENABLECARD;
96
97 #ifndef MHZ
98 #define MHZ (1000 * 1000)
99 #endif
100
101 static const unsigned long g_rate = 3 * MHZ; /* 3MHZ */
102
hidog_set_timeout(unsigned int nr)103 static void hidog_set_timeout(unsigned int nr)
104 {
105 unsigned int cnt_0 = ~0x0;
106 unsigned int cnt = cnt_0 / g_rate; /* max cnt */
107 unsigned long flags;
108
109 osal_spin_lock_irqsave(&g_hidog_lock, &flags);
110
111 if ((nr == 0) || (nr > cnt)) {
112 cnt = ~0x0;
113 } else {
114 cnt = nr * g_rate;
115 }
116 /* unlock watchdog registers */
117 hiwdt_writel(HIWDT_UNLOCK_VAL, HIWDT_LOCK);
118 hiwdt_writel(cnt, HIWDT_LOAD);
119 hiwdt_writel(cnt, HIWDT_VALUE);
120 /* lock watchdog registers */
121 hiwdt_writel(0, HIWDT_LOCK);
122 osal_spin_unlock_irqrestore(&g_hidog_lock, &flags);
123 }
124
hidog_feed(void)125 static void hidog_feed(void)
126 {
127 unsigned long flags;
128
129 /* read the RIS state of current wdg */
130 unsigned int v = (unsigned int)hiwdt_readl(HIWDT_RIS);
131 v &= 0x1; /* 0x1: get INT bit [1] */
132 if (v == 0) { /* no INT on current wdg */
133 return;
134 }
135
136 osal_spin_lock_irqsave(&g_hidog_lock, &flags);
137 /* unlock watchdog registers */
138 hiwdt_writel(HIWDT_UNLOCK_VAL, HIWDT_LOCK);
139 /* clear watchdog */
140 hiwdt_writel(0x00, HIWDT_INTCLR);
141 /* lock watchdog registers */
142 hiwdt_writel(0, HIWDT_LOCK);
143 osal_spin_unlock_irqrestore(&g_hidog_lock, &flags);
144 }
145
hidog_set_heartbeat(int t)146 static int hidog_set_heartbeat(int t)
147 {
148 int ret = 0;
149 unsigned int cnt_0 = ~0x0;
150 unsigned int cnt = cnt_0 / g_rate;
151
152 if (t <= 0) {
153 osal_printk("set heartbeat less or equal to 0, heartbeat will not be changed.\n");
154 t = g_cur_margin;
155 ret = -1;
156 } else if ((unsigned int)t > cnt) {
157 osal_printk("set heartbeat range error, t = %d\n", t);
158 osal_printk("force heartbeat to %u\n", cnt);
159 t = cnt;
160 ret = -1;
161 }
162
163 g_cur_margin = t;
164
165 hidog_set_timeout(t);
166 hidog_feed();
167
168 return ret;
169 }
170
hidog_keepalive(void)171 static int hidog_keepalive(void)
172 {
173 hidog_feed();
174 return 0;
175 }
176
hidog_start(void)177 static void hidog_start(void)
178 {
179 unsigned long flags;
180
181 osal_spin_lock_irqsave(&g_hidog_lock, &flags);
182 /* unlock watchdog registers */
183 hiwdt_writel(HIWDT_UNLOCK_VAL, HIWDT_LOCK);
184 hiwdt_writel(0x00, HIWDT_CTRL); /* 0x00: disable watch dog reset signal and interrupt */
185 hiwdt_writel(0x00, HIWDT_INTCLR); /* 0x00: clear interrupt */
186 hiwdt_writel(0x03, HIWDT_CTRL); /* 0x03: enable watch dog reset signal and interrupt */
187 /* lock watchdog registers */
188 hiwdt_writel(0, HIWDT_LOCK);
189 osal_spin_unlock_irqrestore(&g_hidog_lock, &flags);
190
191 g_options = WDIOS_ENABLECARD;
192 }
193
hidog_stop(void)194 static void hidog_stop(void)
195 {
196 unsigned long flags;
197
198 osal_spin_lock_irqsave(&g_hidog_lock, &flags);
199 /* unlock watchdog registers */
200 hiwdt_writel(HIWDT_UNLOCK_VAL, HIWDT_LOCK);
201 /* stop watchdog timer */
202 hiwdt_writel(0x00, HIWDT_CTRL); /* 0x00: disable watch dog reset signal and interrupt */
203 hiwdt_writel(0x00, HIWDT_INTCLR); /* 0x00: clear interrupt */
204 /* lock watchdog registers */
205 hiwdt_writel(0, HIWDT_LOCK);
206 osal_spin_unlock_irqrestore(&g_hidog_lock, &flags);
207
208 hidog_set_timeout(0);
209 g_options = WDIOS_DISABLECARD;
210 }
211
hidog_open(void * private_data)212 static int hidog_open(void *private_data)
213 {
214 int ret = 0;
215 hi_wtdg_unused(private_data);
216
217 if (osal_atomic_dec_return(&g_driver_open) != 0) {
218 ret = osal_atomic_inc_return(&g_driver_open);
219 osal_printk("Error: device already open:%d.\n", ret);
220 return -1;
221 }
222
223 hidog_keepalive();
224
225 return ret;
226 }
227
hidog_release(void * private_data)228 static int hidog_release(void *private_data)
229 {
230 hi_wtdg_unused(private_data);
231
232 if (osal_atomic_inc_return(&g_driver_open) != 1) {
233 osal_atomic_dec_return(&g_driver_open);
234 return 0;
235 }
236
237 g_hidog_state = HIDOG_SELFCLR;
238 hidog_set_heartbeat(g_cur_margin);
239
240 if (g_options == WDIOS_DISABLECARD) {
241 osal_printk("Watchdog is disabled!\n");
242 }
243 return 0;
244 }
245
hidog_ioctl(unsigned int cmd,unsigned long arg,void * private_data)246 static long hidog_ioctl(unsigned int cmd, unsigned long arg, void *private_data)
247 {
248 void *argp = (void *)(uintptr_t)arg;
249 int new_margin;
250 unsigned int new_options;
251 errno_t err;
252 hi_wtdg_unused(private_data);
253
254 switch (cmd) {
255 case WDIOC_GETSUPPORT:
256 err = memcpy_s(argp, sizeof(g_ident), &g_ident, sizeof(g_ident));
257 wtdg_check_securec_return(err);
258 return 0;
259
260 case WDIOC_GETSTATUS:
261 case WDIOC_GETBOOTSTATUS:
262 err = memcpy_s((int *)argp, sizeof(int), &g_options, sizeof(int));
263 wtdg_check_securec_return(err);
264 return 0;
265
266 case WDIOC_KEEPALIVE:
267 hidog_keepalive();
268 return 0;
269
270 case WDIOC_SETTIMEOUT:
271 err = memcpy_s(&new_margin, sizeof(int), (int *)argp, sizeof(int));
272 wtdg_check_securec_return(err);
273
274 if (hidog_set_heartbeat(new_margin)) {
275 return -1;
276 }
277 hidog_keepalive();
278 return 0;
279
280 case WDIOC_GETTIMEOUT:
281 err = memcpy_s((int *)argp, sizeof(int), &g_cur_margin, sizeof(int));
282 wtdg_check_securec_return(err);
283 return 0;
284
285 case WDIOC_SETOPTIONS:
286 err = memcpy_s(&new_options, sizeof(int), (int *)argp, sizeof(int));
287 wtdg_check_securec_return(err);
288
289 if (new_options & WDIOS_ENABLECARD) {
290 hidog_start();
291 hidog_set_heartbeat(g_cur_margin);
292 return 0;
293 } else if (new_options & WDIOS_DISABLECARD) {
294 hidog_stop();
295 return 0;
296 } else {
297 return -WDIOS_UNKNOWN;
298 }
299
300 default:
301 return -1;
302 }
303 }
304
305 /* Kernel Interfaces */
306 static struct osal_fileops g_hidog_fops = {
307 .unlocked_ioctl = hidog_ioctl,
308 .open = hidog_open,
309 .release = hidog_release,
310 };
311
312 static struct osal_dev *g_hidog_miscdev = NULL;
313
hidog_deamon(void * data)314 static int hidog_deamon(void *data)
315 {
316 hi_wtdg_unused(data);
317
318 #ifdef __HuaweiLite__
319 prctl(PR_SET_NAME, "hidog_deamon", 0, 0, 0);
320 #endif
321 while (g_hidog_state != HIDOG_EXIT) {
322 switch (g_hidog_state) {
323 case HIDOG_SELFCLR:
324 if (g_options & WDIOS_ENABLECARD) {
325 hidog_feed();
326 }
327 break;
328 case HIDOG_EXTCLR:
329 break;
330 default:
331 break;
332 }
333 /* sleep; when self feed dog, only use the default margin */
334 osal_msleep(default_margin * 1000 / 2 + 10); /* sleep (60*1000/2 + 10)->30.01s */
335 }
336
337 return 0;
338 }
339
hidog_init(void)340 static int hidog_init(void)
341 {
342 hidog_start();
343
344 g_hidog_state = HIDOG_SELFCLR;
345 if (!nodeamon) {
346 #ifdef __HuaweiLite__
347 if (pthread_create(&g_task_hidog_deamon, NULL, (void *)hidog_deamon, 0) < 0) {
348 osal_printk("create hidog_deamon failed!\n");
349 return -1;
350 }
351 #else
352 struct osal_task *dog = NULL;
353
354 dog = osal_kthread_create(hidog_deamon, NULL, "hidog");
355 if (dog == NULL) {
356 osal_printk("create hidog_deamon failed!\n");
357 return -1;
358 }
359 g_task_hidog_deamon = dog;
360 #endif
361 }
362 return 0;
363 }
364
get_margin(void)365 static int get_margin(void)
366 {
367 int ret = default_margin;
368
369 /* Check that the default_margin value is within it's range ; if not reset to the default */
370 if (hidog_set_heartbeat(default_margin)) {
371 default_margin = HIDOG_TIMER_MARGIN;
372 hidog_set_heartbeat(HIDOG_TIMER_MARGIN);
373 osal_printk("default_margin value must be 0<default_margin<%lu, using %d\n",
374 (~0x0UL) / g_rate, HIDOG_TIMER_MARGIN);
375 }
376
377 return ret;
378 }
379
ptr_ioremap(void)380 static int ptr_ioremap(void)
381 {
382 if (g_wtdg_reg_base == NULL) {
383 g_wtdg_reg_base = (volatile void *)osal_ioremap(HIWDT_BASE, 0x1000); /* 0x1000: watch dog reg length */
384 if (g_wtdg_reg_base == NULL) {
385 osal_printk("osal_ioremap err. \n");
386 osal_spin_lock_destroy(&g_hidog_lock);
387 osal_atomic_destroy(&g_driver_open);
388 return -1;
389 }
390 g_need_iounmap = 1;
391 }
392 return 1;
393 }
394
watchdog_init(void)395 int watchdog_init(void)
396 {
397 if (osal_atomic_init(&g_driver_open) != 0) {
398 osal_printk("Error: init atomic\n");
399 return -1;
400 }
401 osal_atomic_set(&g_driver_open, 1);
402
403 if (osal_spin_lock_init(&g_hidog_lock) != 0) {
404 osal_printk("function %s line %d failed\n", __FUNCTION__, __LINE__);
405 goto spin_lock_init_fail;
406 }
407
408 if (ptr_ioremap() != 1) {
409 return -1;
410 }
411
412 g_cur_margin = get_margin();
413
414 g_hidog_miscdev = osal_createdev("watchdog");
415 if (g_hidog_miscdev == NULL) {
416 osal_printk("fail to create dev\n");
417 goto create_dev_fail;
418 }
419
420 g_hidog_miscdev->minor = WATCHDOG_MINOR;
421 g_hidog_miscdev->fops = &g_hidog_fops;
422 if (osal_registerdevice(g_hidog_miscdev) != 0) {
423 osal_printk("fail to register dev\n");
424 goto register_dev_fail;
425 }
426
427 if (hidog_init() != 0) {
428 goto dog_init_fail;
429 }
430
431 osal_printk("Hisilicon Watchdog Timer: 0.01 initialized. default_margin=%d sec (nodeamon= %d)\n",
432 default_margin, nodeamon);
433 osal_printk("hiwtdg init ok. ver=%s, %s.\n", __DATE__, __TIME__);
434 return 0;
435
436 dog_init_fail:
437 osal_deregisterdevice(g_hidog_miscdev);
438 register_dev_fail:
439 osal_destroydev(g_hidog_miscdev);
440 create_dev_fail:
441 if (g_need_iounmap) {
442 osal_iounmap((void *)g_wtdg_reg_base, 0x1000);
443 g_need_iounmap = 0;
444 g_wtdg_reg_base = NULL;
445 }
446 osal_spin_lock_destroy(&g_hidog_lock);
447 spin_lock_init_fail:
448 osal_atomic_destroy(&g_driver_open);
449 return -1;
450 }
451
hidog_exit(void)452 static void hidog_exit(void)
453 {
454 hidog_set_timeout(0);
455 hidog_stop();
456 g_hidog_state = HIDOG_EXIT;
457 if (!nodeamon) {
458 #ifdef __HuaweiLite__
459 pthread_join(g_task_hidog_deamon, NULL);
460 #else
461 struct osal_task *p_dog = g_task_hidog_deamon;
462 if (p_dog == NULL) {
463 return;
464 }
465
466 osal_kthread_destroy(p_dog, 1);
467 #endif
468 osal_yield();
469 }
470
471 #ifdef __HuaweiLite__
472 g_task_hidog_deamon = 0;
473 #else
474 g_task_hidog_deamon = NULL;
475 #endif
476 }
477
watchdog_exit(void)478 void watchdog_exit(void)
479 {
480 hidog_exit();
481
482 osal_deregisterdevice(g_hidog_miscdev);
483 osal_destroydev(g_hidog_miscdev);
484 if (g_need_iounmap) {
485 osal_iounmap((void *)g_wtdg_reg_base, 0x1000);
486 g_need_iounmap = 0;
487 g_wtdg_reg_base = NULL;
488 }
489 osal_spin_lock_destroy(&g_hidog_lock);
490 osal_atomic_destroy(&g_driver_open);
491 osal_printk("hiwtdg exit ok.\n");
492 }
493