aboutsummaryrefslogtreecommitdiff
path: root/drivers/misc/mediatek/mtprof/mt_wq_debug.c
blob: 2bd3b950cb897b9649a64f039dbf7999e61096fa (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/workqueue.h>
#include <linux/mt_wq_debug.h>
#include <linux/xlog.h>

#define WQ_VERBOSE_DEBUG 0

#define WK_REC_NUM 10
#define WK_CBNAME_LEN 32
#define WQ_WORK_ACTIVED 0
#define WQ_WORK_QUEUED 1

struct work_log {
	int req_cpu;
	int addr;
	int activated;
	char cbname[WK_CBNAME_LEN];
};

struct wq_debug {
	int idx;		/* the current array idx to fill in */
	struct work_log wklog[WK_REC_NUM];
};

#ifdef CONFIG_KALLSYMS
extern unsigned long kallsyms_lookup_name(const char *name);
extern int lookup_module_symbol_attrs(unsigned long addr, unsigned long *size,
				      unsigned long *offset, char *modname, char *name);
#endif

DEFINE_PER_CPU(struct wq_debug, wq_debugger);

/* mt_wq_debug_queue_work is invoked while gcwq->lock is held. */
void mt_wq_debug_queue_work(int req_cpu, struct work_struct *work)
{
	struct wq_debug *wqdbg = &__raw_get_cpu_var(wq_debugger);
	int i, len, lastidx, idx = wqdbg->idx, found = 0;
	char func[128];		/* WK_CBNAME_LEN]; */

	/* snprintf(func, WK_CBNAME_LEN, "%pf", work->func); */

	/* if queuing same wk on same cpu, and it hadn't been activated, ignore it due to we have one. */
	if (idx == 0)
		lastidx = WK_REC_NUM - 1;
	else
		lastidx = idx - 1;

	if (wqdbg->wklog[lastidx].addr == (unsigned int)work &&
	    wqdbg->wklog[lastidx].req_cpu == req_cpu &&
	    wqdbg->wklog[lastidx].activated == WQ_WORK_ACTIVED) {
		wqdbg->wklog[lastidx].activated = WQ_WORK_QUEUED;
#if WQ_VERBOSE_DEBUG
		pr_err("ignore work: %x\n", work);
		return;
	} else
		pr_err("queue work idx: %d vs %d, addr: %x, %x, cpu: %d vs %d, %s\n",
		       idx, lastidx,
		       wqdbg->wklog[lastidx].addr,
		       work,
		       wqdbg->wklog[lastidx].req_cpu,
		       req_cpu,
		       wqdbg->wklog[lastidx].activated == WQ_WORK_ACTIVED ? "activated" : "queued");
#else
		return;
	}
#endif


	/* get next activated entry to fill. */
	for (i = idx; i < WK_REC_NUM; i++) {
		if (WQ_WORK_ACTIVED == wqdbg->wklog[i].activated) {
#if WQ_VERBOSE_DEBUG
			if (idx != i)
				pr_err("queue work idx update: %d -> %d\n", idx, i);
#endif
			idx = i;
			found = 1;
			break;
		}
	}
	if (!found) {
		for (i = 0; i < lastidx; i++) {
			if (WQ_WORK_ACTIVED == wqdbg->wklog[i].activated) {
#if WQ_VERBOSE_DEBUG
				if (idx != i)
					pr_err("queue work idx update: %d -> %d\n", idx, i);
#endif
				idx = i;
				break;
			}
		}
	}

	sprintf(func, "%pf", work->func);

#if WQ_VERBOSE_DEBUG
	pr_err("queue work add %d\n", idx);
#endif

	wqdbg->wklog[idx].addr = (unsigned int)work;
	len = strlen(func) > WK_CBNAME_LEN - 1 ? WK_CBNAME_LEN - 1 : strlen(func);
	strncpy(wqdbg->wklog[idx].cbname, func, len);
	wqdbg->wklog[idx].cbname[len] = '\0';
	wqdbg->wklog[idx].req_cpu = req_cpu;
	wqdbg->wklog[idx].activated = WQ_WORK_QUEUED;

	idx++;
	idx %= WK_REC_NUM;

	wqdbg->idx = idx;
}

/* mt_wq_debug_activate_work is invoked while gcwq->lock is held. */
void mt_wq_debug_activate_work(struct work_struct *work)
{
	struct wq_debug *wqdbg = &__raw_get_cpu_var(wq_debugger);
	int i, n_start = wqdbg->idx, n_end;

	/* in general, it's the last one we push to debugger zone */
	if (n_start == 0) {
		n_start = WK_REC_NUM - 1;
		n_end = 0;
	} else {
		n_end = n_start;
		n_start -= 1;
	}

	/* dirty work... */
	for (i = n_start; i >= 0; i--) {
#if WQ_VERBOSE_DEBUG
		pr_err("activate work %d addr:%x vs %x\n", i, wqdbg->wklog[i].addr, work);
#endif
		if (wqdbg->wklog[i].addr == (unsigned int)work)
			wqdbg->wklog[i].activated = WQ_WORK_ACTIVED;
		return;
	}
	for (i = WK_REC_NUM - 1; i >= n_end; i--) {
#if WQ_VERBOSE_DEBUG
		pr_err("activate work %d addr:%x vs %x\n", i, wqdbg->wklog[i].addr, work);
#endif
		if (wqdbg->wklog[i].addr == (unsigned int)work)
			wqdbg->wklog[i].activated = WQ_WORK_ACTIVED;
		return;
	}
	pr_err("activate work addr not found!!! %x\n", (unsigned int)work);
}

int mt_dump_wq_debugger(void)
{
#define WQ_LOG_TAG "wq_debug"
	int dumpRemain = 0, dumpFrom, i, cpu;
	int address = 0, ret = -1;
	char symName[128];

	for_each_possible_cpu(cpu) {
		dumpFrom = per_cpu(wq_debugger, cpu).idx;
		if (dumpFrom > 0)
			dumpRemain = dumpFrom;

#if WQ_VERBOSE_DEBUG
		pr_err("wq debugger: From:%d, Remain:%d\n", dumpFrom, dumpRemain);
#endif
		for (i = dumpFrom; i < WK_REC_NUM; i++) {
#ifdef CONFIG_KALLSYMS
			address = kallsyms_lookup_name(per_cpu(wq_debugger, cpu).wklog[i].cbname);
			ret = lookup_module_symbol_attrs(address, NULL, NULL, &symName[0], NULL);
#endif
			pr_err
			    ("wq:cpu:%d,idx:%d,req_cpu:%d,wkaddr:%x,cb:%s,cbaddr:%x,module:%s,%s\n",
			     cpu, i, per_cpu(wq_debugger, cpu).wklog[i].req_cpu,
			     per_cpu(wq_debugger, cpu).wklog[i].addr, per_cpu(wq_debugger,
									      cpu).wklog[i].cbname,
			     address, ret != 0 ? "null" : symName, per_cpu(wq_debugger,
									   cpu).wklog[i].
			     activated == WQ_WORK_ACTIVED ? "activated" : "queued");
		}
		for (i = 0; i < dumpRemain; i++) {
#ifdef CONFIG_KALLSYMS
			address = kallsyms_lookup_name(per_cpu(wq_debugger, cpu).wklog[i].cbname);
			ret = lookup_module_symbol_attrs(address, NULL, NULL, &symName[0], NULL);
#endif
			pr_err
			    ("wq:cpu:%d,idx:%d,req_cpu:%d,wkaddr:%x,cb:%s,cbaddr:%x,module:%s,%s\n",
			     cpu, i, per_cpu(wq_debugger, cpu).wklog[i].req_cpu,
			     per_cpu(wq_debugger, cpu).wklog[i].addr, per_cpu(wq_debugger,
									      cpu).wklog[i].cbname,
			     address, ret != 0 ? "null" : symName, per_cpu(wq_debugger,
									   cpu).wklog[i].
			     activated == WQ_WORK_ACTIVED ? "activated" : "queued");
		}
	}
	return 0;
}
EXPORT_SYMBOL(mt_dump_wq_debugger);

void mttrace_workqueue_execute_work(struct work_struct *work)
{
	if (unlikely(wq_tracing & WQ_DUMP_EXECUTE_WORK))
		pr_err("execute work=%p\n", (void *)work);
}
EXPORT_SYMBOL(mttrace_workqueue_execute_work);

void mttrace_workqueue_execute_end(struct work_struct *work)
{
	if (unlikely(wq_tracing & WQ_DUMP_EXECUTE_WORK))
		pr_err("execute end work=%p\n", (void *)work);
}
EXPORT_SYMBOL(mttrace_workqueue_execute_end);

void mttrace_workqueue_activate_work(struct work_struct *work)
{
	if (unlikely(wq_tracing & WQ_DUMP_ACTIVE_WORK))
		pr_err("activate work=%p\n", (void *)work);

	if (likely(wq_debugger_enable))
		mt_wq_debug_activate_work(work);
}
EXPORT_SYMBOL(mttrace_workqueue_activate_work);

void mttrace_workqueue_queue_work(unsigned int req_cpu, struct work_struct *work)
{
	if (unlikely(wq_tracing & WQ_DUMP_QUEUE_WORK))
		pr_err("queue work=%p function=%pf req_cpu=%u\n",
		       (void *)work, (void *)work->func, req_cpu);

	if (likely(wq_debugger_enable))
		mt_wq_debug_queue_work(req_cpu, work);
}
EXPORT_SYMBOL(mttrace_workqueue_queue_work);