1 // SPDX-License-Identifier: GPL-2.0
2 /*
3 * Copyright (C) 2022 Huawei Technologies Co., Ltd. All rights reserved.
4 */
5
6 #define pr_fmt(fmt) "zrhung " fmt
7
8 #include <linux/sched/clock.h>
9 #include <linux/sched/debug.h>
10 #include <linux/kernel.h>
11 #include <linux/fb.h>
12 #include <linux/notifier.h>
13 #include <linux/module.h>
14 #include <linux/string.h>
15 #include <linux/spinlock.h>
16 #include <linux/workqueue.h>
17 #include <linux/time.h>
18 #include <linux/input.h>
19 #include <linux/jiffies.h>
20 #include <linux/sched/debug.h>
21 #include <dfx/zrhung.h>
22 #include <dfx/hung_wp_screen.h>
23
24 #define TIME_CONVERT_UNIT 1000
25 #define DEFAULT_TIMEOUT 10
26
27 #define LPRESSEVENT_TIME 5
28 #define POWERKEYEVENT_MAX_COUNT 10
29 #define POWERKEYEVENT_DEFAULT_COUNT 3
30 #define POWERKEYEVENT_DEFAULT_TIMEWINDOW 5
31 #define POWERKEYEVENT_DEFAULT_LIMIT_MS 300
32 #define POWERKEYEVENT_DEFAULT_REPORT_MIN 2
33 #define POWERKEYEVENT_TIME_LEN (POWERKEYEVENT_MAX_COUNT + 2)
34
35 struct hung_wp_screen_data {
36 struct timer_list timer;
37 struct timer_list long_press_timer;
38 struct workqueue_struct *workq;
39 struct work_struct send_work;
40 spinlock_t lock;
41 int fb_blank;
42 int check_id;
43 int tag_id;
44 };
45
46 static bool init_done;
47 static struct hung_wp_screen_data g_hung_data;
48 static unsigned int lastreport_time;
49 static unsigned int lastprkyevt_time;
50 static unsigned int powerkeyevent_time[POWERKEYEVENT_TIME_LEN] = {0};
51 static unsigned int newevt;
52 static unsigned int headevt;
53 static int *check_off_point;
54 struct work_struct powerkeyevent_sendwork;
55 struct work_struct lpressevent_sendwork;
56 static struct notifier_block hung_wp_screen_setblank_ncb;
57
zrhung_lpressevent_send_work(struct work_struct * work)58 static void zrhung_lpressevent_send_work(struct work_struct *work)
59 {
60 pr_info("LONGPRESS_EVENT send to zerohung\n");
61 zrhung_send_event(WP_SCREEN_DOMAIN, WP_SCREEN_LPRESS_NAME, "none");
62 }
63
zrhung_wp_lpress_send(struct timer_list * t)64 static void zrhung_wp_lpress_send(struct timer_list *t)
65 {
66 int *check_off = check_off_point;
67
68 del_timer(&g_hung_data.long_press_timer);
69 *check_off = 0;
70 queue_work(g_hung_data.workq, &lpressevent_sendwork);
71 }
72
zrhung_powerkeyevent_send_work(struct work_struct * work)73 static void zrhung_powerkeyevent_send_work(struct work_struct *work)
74 {
75 pr_info("POWERKEY_EVENT send to zerohung\n");
76 zrhung_send_event(WP_SCREEN_DOMAIN, WP_SCREEN_PWK_NAME, "none");
77 }
78
zrhung_powerkeyevent_report(unsigned int dur,unsigned int end)79 static void zrhung_powerkeyevent_report(unsigned int dur, unsigned int end)
80 {
81 unsigned int send_interval;
82
83 send_interval = end > lastreport_time ?
84 ((end - lastreport_time) / TIME_CONVERT_UNIT) : POWERKEYEVENT_DEFAULT_REPORT_MIN;
85 if (unlikely(lastreport_time == 0)) {
86 lastreport_time = end;
87 } else if (send_interval < POWERKEYEVENT_DEFAULT_REPORT_MIN) {
88 pr_info("powerkeyevent too fast to report: %d\n", end);
89 return;
90 }
91 lastreport_time = end;
92 queue_work(g_hung_data.workq, &powerkeyevent_sendwork);
93 }
94
refresh_prkyevt_index(unsigned int event)95 static unsigned int refresh_prkyevt_index(unsigned int event)
96 {
97 unsigned int evt = event;
98
99 if (evt < POWERKEYEVENT_MAX_COUNT)
100 evt++;
101 else
102 evt = 0;
103 return evt;
104 }
105
zrhung_new_powerkeyevent(unsigned int tmescs)106 static void zrhung_new_powerkeyevent(unsigned int tmescs)
107 {
108 unsigned int prkyevt_interval;
109 unsigned int evt_index;
110 int diff;
111
112 powerkeyevent_time[newevt] = tmescs;
113 evt_index = (newevt >= headevt) ?
114 (newevt - headevt) : (newevt + POWERKEYEVENT_MAX_COUNT + 1 - headevt);
115 if (evt_index < (POWERKEYEVENT_DEFAULT_COUNT - 1)) {
116 pr_info("powerkeyevent not enough-%d\n", POWERKEYEVENT_DEFAULT_COUNT);
117 } else {
118 diff = powerkeyevent_time[newevt] - powerkeyevent_time[headevt];
119 if (diff < 0) {
120 pr_info("powerkeyevent sth wrong in record time\n");
121 return;
122 }
123
124 prkyevt_interval = (unsigned int)(diff / TIME_CONVERT_UNIT);
125 if (prkyevt_interval <= POWERKEYEVENT_DEFAULT_TIMEWINDOW)
126 zrhung_powerkeyevent_report(prkyevt_interval, tmescs);
127 headevt = refresh_prkyevt_index(headevt);
128 }
129 newevt = refresh_prkyevt_index(newevt);
130 }
131
zrhung_powerkeyevent_handler(void)132 static void zrhung_powerkeyevent_handler(void)
133 {
134 unsigned int curtime;
135 unsigned long curjiff;
136
137 pr_info("powerkeyevent check start");
138 curjiff = jiffies;
139 curtime = jiffies_to_msecs(curjiff);
140 if (unlikely(lastprkyevt_time > curtime)) {
141 pr_info("powerkeyevent check but time overflow");
142 lastprkyevt_time = curtime;
143 return;
144 } else if ((curtime - lastprkyevt_time) < POWERKEYEVENT_DEFAULT_LIMIT_MS) {
145 pr_info("powerkeyevent user press powerkey too fast-time:%d", curtime);
146 return;
147 }
148 lastprkyevt_time = curtime;
149 zrhung_new_powerkeyevent(curtime);
150 }
151
hung_wp_screen_setblank(struct notifier_block * self,unsigned long event,void * data)152 static int hung_wp_screen_setblank(struct notifier_block *self, unsigned long event, void *data)
153 {
154 unsigned long flags;
155 struct fb_event *evdata = data;
156 int blank;
157
158 if (!init_done)
159 return 0;
160
161 if (event != FB_EVENT_BLANK)
162 return 0;
163
164 blank = *(int *)evdata->data;
165 spin_lock_irqsave(&(g_hung_data.lock), flags);
166 g_hung_data.fb_blank = blank;
167 if (((g_hung_data.check_id == ZRHUNG_WP_SCREENON) && (blank == 0)) ||
168 ((g_hung_data.check_id == ZRHUNG_WP_SCREENOFF) && (blank != 0))) {
169 pr_info("check_id=%d, blank=%d", g_hung_data.check_id, g_hung_data.fb_blank);
170 del_timer(&g_hung_data.timer);
171 g_hung_data.check_id = ZRHUNG_WP_NONE;
172 }
173 spin_unlock_irqrestore(&(g_hung_data.lock), flags);
174
175 return 0;
176 }
177
hung_wp_screen_send_work(struct work_struct * work)178 static void hung_wp_screen_send_work(struct work_struct *work)
179 {
180 unsigned long flags = 0;
181
182 show_state_filter(TASK_UNINTERRUPTIBLE);
183
184 if (g_hung_data.check_id == 1)
185 zrhung_send_event(WP_SCREEN_DOMAIN, WP_SCREEN_ON_NAME, "none");
186 else
187 zrhung_send_event(WP_SCREEN_DOMAIN, WP_SCREEN_OFF_NAME, "none");
188 pr_info("send event: %d\n", g_hung_data.check_id);
189 spin_lock_irqsave(&(g_hung_data.lock), flags);
190 g_hung_data.check_id = ZRHUNG_WP_NONE;
191 spin_unlock_irqrestore(&(g_hung_data.lock), flags);
192 }
193
hung_wp_screen_send(struct timer_list * t)194 static void hung_wp_screen_send(struct timer_list *t)
195 {
196 del_timer(&g_hung_data.timer);
197 pr_info("hung_wp_screen_%d end\n", g_hung_data.tag_id);
198 queue_work(g_hung_data.workq, &g_hung_data.send_work);
199 }
200
hung_wp_screen_start(int check_id)201 static void hung_wp_screen_start(int check_id)
202 {
203 if (g_hung_data.check_id != ZRHUNG_WP_NONE) {
204 pr_info("already in check_id: %d\n", g_hung_data.check_id);
205 return;
206 }
207
208 g_hung_data.check_id = check_id;
209 if (timer_pending(&g_hung_data.timer))
210 del_timer(&g_hung_data.timer);
211
212 g_hung_data.timer.expires = jiffies + msecs_to_jiffies(DEFAULT_TIMEOUT * TIME_CONVERT_UNIT);
213 add_timer(&g_hung_data.timer);
214 pr_info("going to check ID=%d timeout=%d\n", check_id, DEFAULT_TIMEOUT);
215 }
216
hung_wp_screen_powerkey_ncb(int event)217 void hung_wp_screen_powerkey_ncb(int event)
218 {
219 static int check_off;
220 unsigned long flags = 0;
221
222 if (!init_done)
223 return;
224
225 spin_lock_irqsave(&(g_hung_data.lock), flags);
226 if (event == WP_SCREEN_PWK_PRESS) {
227 pr_info("hung_wp_screen_%d start! fb_blank=%d",
228 ++g_hung_data.tag_id, g_hung_data.fb_blank);
229 check_off = 0;
230 if (g_hung_data.fb_blank != 0) {
231 hung_wp_screen_start(ZRHUNG_WP_SCREENON);
232 } else {
233 check_off = 1;
234 pr_info("start longpress test timer\n");
235 check_off_point = &check_off;
236 g_hung_data.long_press_timer.expires = jiffies +
237 msecs_to_jiffies(LPRESSEVENT_TIME * TIME_CONVERT_UNIT);
238 if (!timer_pending(&g_hung_data.long_press_timer))
239 add_timer(&g_hung_data.long_press_timer);
240 }
241 zrhung_powerkeyevent_handler();
242 } else if (check_off) {
243 check_off = 0;
244 del_timer(&g_hung_data.long_press_timer);
245 if (event == WP_SCREEN_PWK_RELEASE && g_hung_data.fb_blank == 0)
246 hung_wp_screen_start(ZRHUNG_WP_SCREENOFF);
247 }
248 spin_unlock_irqrestore(&(g_hung_data.lock), flags);
249 }
250
hung_wp_screen_init(void)251 static int __init hung_wp_screen_init(void)
252 {
253 init_done = false;
254 pr_info("%s start\n", __func__);
255 g_hung_data.fb_blank = 0;
256 g_hung_data.tag_id = 0;
257 g_hung_data.check_id = ZRHUNG_WP_NONE;
258 spin_lock_init(&(g_hung_data.lock));
259
260 timer_setup(&g_hung_data.timer, hung_wp_screen_send, 0);
261 timer_setup(&g_hung_data.long_press_timer, zrhung_wp_lpress_send, 0);
262
263 g_hung_data.workq = create_workqueue("hung_wp_screen_workq");
264 if (g_hung_data.workq == NULL) {
265 pr_err("create workq failed\n");
266 return -EFAULT;
267 }
268 INIT_WORK(&g_hung_data.send_work, hung_wp_screen_send_work);
269 INIT_WORK(&powerkeyevent_sendwork, zrhung_powerkeyevent_send_work);
270 INIT_WORK(&lpressevent_sendwork, zrhung_lpressevent_send_work);
271
272 hung_wp_screen_setblank_ncb.notifier_call = hung_wp_screen_setblank;
273 fb_register_client(&hung_wp_screen_setblank_ncb);
274
275 init_done = true;
276 pr_info("%s done\n", __func__);
277 return 0;
278 }
279
hung_wp_screen_exit(void)280 static void __exit hung_wp_screen_exit(void)
281 {
282 fb_unregister_client(&hung_wp_screen_setblank_ncb);
283
284 cancel_work_sync(&lpressevent_sendwork);
285 cancel_work_sync(&powerkeyevent_sendwork);
286 cancel_work_sync(&g_hung_data.send_work);
287
288 destroy_workqueue(g_hung_data.workq);
289
290 del_timer_sync(&g_hung_data.timer);
291 del_timer_sync(&g_hung_data.long_press_timer);
292 }
293
294 module_init(hung_wp_screen_init);
295 module_exit(hung_wp_screen_exit);
296
297 MODULE_AUTHOR("OHOS");
298 MODULE_DESCRIPTION("Reporting the frozen screen alarm event");
299 MODULE_LICENSE("GPL");
300