• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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