xref: /wlan-driver/qcacld-3.0/core/hdd/src/wlan_hdd_memdump.c (revision 5113495b16420b49004c444715d2daae2066e7dc)
1*5113495bSYour Name /*
2*5113495bSYour Name  * Copyright (c) 2016-2021 The Linux Foundation. All rights reserved.
3*5113495bSYour Name  * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
4*5113495bSYour Name  *
5*5113495bSYour Name  * Permission to use, copy, modify, and/or distribute this software for
6*5113495bSYour Name  * any purpose with or without fee is hereby granted, provided that the
7*5113495bSYour Name  * above copyright notice and this permission notice appear in all
8*5113495bSYour Name  * copies.
9*5113495bSYour Name  *
10*5113495bSYour Name  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
11*5113495bSYour Name  * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
12*5113495bSYour Name  * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
13*5113495bSYour Name  * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
14*5113495bSYour Name  * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
15*5113495bSYour Name  * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
16*5113495bSYour Name  * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
17*5113495bSYour Name  * PERFORMANCE OF THIS SOFTWARE.
18*5113495bSYour Name  */
19*5113495bSYour Name 
20*5113495bSYour Name /**
21*5113495bSYour Name  * DOC: wlan_hdd_memdump.c
22*5113495bSYour Name  *
23*5113495bSYour Name  * WLAN Host Device Driver file for dumping firmware memory
24*5113495bSYour Name  *
25*5113495bSYour Name  */
26*5113495bSYour Name 
27*5113495bSYour Name #include <linux/module.h>
28*5113495bSYour Name #include <linux/kernel.h>
29*5113495bSYour Name #include <linux/version.h>
30*5113495bSYour Name #include <linux/proc_fs.h> /* Necessary because we use the proc fs */
31*5113495bSYour Name #include <linux/uaccess.h> /* for copy_to_user */
32*5113495bSYour Name #include "osif_sync.h"
33*5113495bSYour Name #include <sme_api.h>
34*5113495bSYour Name #include <wlan_hdd_includes.h>
35*5113495bSYour Name 
36*5113495bSYour Name #define PROCFS_DRIVER_DUMP_DIR "debugdriver"
37*5113495bSYour Name #define PROCFS_DRIVER_DUMP_NAME "driverdump"
38*5113495bSYour Name #define PROCFS_DRIVER_DUMP_PERM 0444
39*5113495bSYour Name 
40*5113495bSYour Name #if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 17, 0))
41*5113495bSYour Name /*
42*5113495bSYour Name  * Commit 359745d78351 ("proc: remove PDE_DATA() completely")
43*5113495bSYour Name  * Replaced PDE_DATA() with pde_data()
44*5113495bSYour Name  */
45*5113495bSYour Name #define pde_data(inode) PDE_DATA(inode)
46*5113495bSYour Name #endif
47*5113495bSYour Name 
48*5113495bSYour Name static struct proc_dir_entry *proc_file_driver, *proc_dir_driver;
49*5113495bSYour Name 
50*5113495bSYour Name /** memdump_get_file_data() - get data available in proc file
51*5113495bSYour Name  *
52*5113495bSYour Name  * @file - handle for the proc file.
53*5113495bSYour Name  *
54*5113495bSYour Name  * This function is used to retrieve the data passed while
55*5113495bSYour Name  * creating proc file entry.
56*5113495bSYour Name  *
57*5113495bSYour Name  * Return: void pointer to hdd_context
58*5113495bSYour Name  */
memdump_get_file_data(struct file * file)59*5113495bSYour Name static void *memdump_get_file_data(struct file *file)
60*5113495bSYour Name {
61*5113495bSYour Name 	void *hdd_ctx;
62*5113495bSYour Name 
63*5113495bSYour Name 	hdd_ctx = pde_data(file_inode(file));
64*5113495bSYour Name 	return hdd_ctx;
65*5113495bSYour Name }
66*5113495bSYour Name 
hdd_driver_mem_cleanup(void)67*5113495bSYour Name void hdd_driver_mem_cleanup(void)
68*5113495bSYour Name {
69*5113495bSYour Name 	struct hdd_context *hdd_ctx;
70*5113495bSYour Name 
71*5113495bSYour Name 	hdd_ctx = cds_get_context(QDF_MODULE_ID_HDD);
72*5113495bSYour Name 	if (!hdd_ctx)
73*5113495bSYour Name 		return;
74*5113495bSYour Name 
75*5113495bSYour Name 	if (hdd_ctx->driver_dump_mem) {
76*5113495bSYour Name 		qdf_mem_free(hdd_ctx->driver_dump_mem);
77*5113495bSYour Name 		hdd_ctx->driver_dump_mem = NULL;
78*5113495bSYour Name 	}
79*5113495bSYour Name }
80*5113495bSYour Name 
81*5113495bSYour Name 
82*5113495bSYour Name /**
83*5113495bSYour Name  * __hdd_driver_memdump_read() - perform read operation in driver
84*5113495bSYour Name  * memory dump proc file
85*5113495bSYour Name  * @file:  handle for the proc file.
86*5113495bSYour Name  * @buf:   pointer to user space buffer.
87*5113495bSYour Name  * @count: number of bytes to be read.
88*5113495bSYour Name  * @pos:   offset in the from buffer.
89*5113495bSYour Name  *
90*5113495bSYour Name  * This function performs read operation for the driver memory dump proc file.
91*5113495bSYour Name  *
92*5113495bSYour Name  * Return: number of bytes read on success
93*5113495bSYour Name  *         negative error code in case of failure
94*5113495bSYour Name  *         0 in case of no more data
95*5113495bSYour Name  */
__hdd_driver_memdump_read(struct file * file,char __user * buf,size_t count,loff_t * pos)96*5113495bSYour Name static ssize_t __hdd_driver_memdump_read(struct file *file, char __user *buf,
97*5113495bSYour Name 					 size_t count, loff_t *pos)
98*5113495bSYour Name {
99*5113495bSYour Name 	int status;
100*5113495bSYour Name 	QDF_STATUS qdf_status;
101*5113495bSYour Name 	struct hdd_context *hdd_ctx;
102*5113495bSYour Name 	size_t no_of_bytes_read = 0;
103*5113495bSYour Name 
104*5113495bSYour Name 	hdd_ctx = memdump_get_file_data(file);
105*5113495bSYour Name 
106*5113495bSYour Name 	hdd_debug("Read req for size:%zu pos:%llu", count, *pos);
107*5113495bSYour Name 	status = wlan_hdd_validate_context(hdd_ctx);
108*5113495bSYour Name 	if (status != 0)
109*5113495bSYour Name 		return -EINVAL;
110*5113495bSYour Name 
111*5113495bSYour Name 	mutex_lock(&hdd_ctx->memdump_lock);
112*5113495bSYour Name 	if (*pos < 0) {
113*5113495bSYour Name 		hdd_err("Invalid start offset for memdump read");
114*5113495bSYour Name 		mutex_unlock(&hdd_ctx->memdump_lock);
115*5113495bSYour Name 		return -EINVAL;
116*5113495bSYour Name 	}
117*5113495bSYour Name 
118*5113495bSYour Name 	if (!count ||
119*5113495bSYour Name 	    (hdd_ctx->driver_dump_size && *pos >= hdd_ctx->driver_dump_size)) {
120*5113495bSYour Name 		mutex_unlock(&hdd_ctx->memdump_lock);
121*5113495bSYour Name 		hdd_debug("No more data to copy");
122*5113495bSYour Name 		return 0;
123*5113495bSYour Name 	}
124*5113495bSYour Name 
125*5113495bSYour Name 	if (*pos == 0 || !hdd_ctx->driver_dump_mem) {
126*5113495bSYour Name 		/* Allocate memory for Driver memory dump */
127*5113495bSYour Name 		if (!hdd_ctx->driver_dump_mem) {
128*5113495bSYour Name 			hdd_ctx->driver_dump_mem =
129*5113495bSYour Name 				qdf_mem_malloc(DRIVER_MEM_DUMP_SIZE);
130*5113495bSYour Name 			if (!hdd_ctx->driver_dump_mem) {
131*5113495bSYour Name 				mutex_unlock(&hdd_ctx->memdump_lock);
132*5113495bSYour Name 				return -ENOMEM;
133*5113495bSYour Name 			}
134*5113495bSYour Name 		}
135*5113495bSYour Name 
136*5113495bSYour Name 		qdf_status = qdf_state_info_dump_all(hdd_ctx->driver_dump_mem,
137*5113495bSYour Name 						DRIVER_MEM_DUMP_SIZE,
138*5113495bSYour Name 						&hdd_ctx->driver_dump_size);
139*5113495bSYour Name 		/*
140*5113495bSYour Name 		 * If qdf_status is QDF_STATUS_E_NOMEM, then memory allocated is
141*5113495bSYour Name 		 * insufficient to dump driver information. This print can give
142*5113495bSYour Name 		 * information to allocate more memory if more information from
143*5113495bSYour Name 		 * each layer is added in future.
144*5113495bSYour Name 		 */
145*5113495bSYour Name 		if (qdf_status != QDF_STATUS_SUCCESS)
146*5113495bSYour Name 			hdd_err("Error in dump driver information, status %d",
147*5113495bSYour Name 				qdf_status);
148*5113495bSYour Name 		hdd_debug("driver_dump_size: %d", hdd_ctx->driver_dump_size);
149*5113495bSYour Name 	}
150*5113495bSYour Name 
151*5113495bSYour Name 	if (count > hdd_ctx->driver_dump_size - *pos)
152*5113495bSYour Name 		no_of_bytes_read = hdd_ctx->driver_dump_size - *pos;
153*5113495bSYour Name 	else
154*5113495bSYour Name 		no_of_bytes_read = count;
155*5113495bSYour Name 
156*5113495bSYour Name 	if (copy_to_user(buf, hdd_ctx->driver_dump_mem + *pos,
157*5113495bSYour Name 					no_of_bytes_read)) {
158*5113495bSYour Name 		hdd_err("copy to user space failed");
159*5113495bSYour Name 		mutex_unlock(&hdd_ctx->memdump_lock);
160*5113495bSYour Name 		return -EFAULT;
161*5113495bSYour Name 	}
162*5113495bSYour Name 
163*5113495bSYour Name 	/* offset(pos) should be updated here based on the copy done */
164*5113495bSYour Name 	*pos += no_of_bytes_read;
165*5113495bSYour Name 
166*5113495bSYour Name 	/* Entire driver memory dump copy completed */
167*5113495bSYour Name 	if (*pos >= hdd_ctx->driver_dump_size)
168*5113495bSYour Name 		hdd_driver_mem_cleanup();
169*5113495bSYour Name 
170*5113495bSYour Name 	mutex_unlock(&hdd_ctx->memdump_lock);
171*5113495bSYour Name 
172*5113495bSYour Name 	return no_of_bytes_read;
173*5113495bSYour Name }
174*5113495bSYour Name 
175*5113495bSYour Name /**
176*5113495bSYour Name  * hdd_driver_memdump_read() - perform read operation in driver
177*5113495bSYour Name  * memory dump proc file
178*5113495bSYour Name  * @file:  handle for the proc file.
179*5113495bSYour Name  * @buf:   pointer to user space buffer.
180*5113495bSYour Name  * @count: number of bytes to be read.
181*5113495bSYour Name  * @pos:   offset in the from buffer.
182*5113495bSYour Name  *
183*5113495bSYour Name  * This function performs read operation for the driver memory dump proc file.
184*5113495bSYour Name  *
185*5113495bSYour Name  * Return: number of bytes read on success
186*5113495bSYour Name  *         negative error code in case of failure
187*5113495bSYour Name  *         0 in case of no more data
188*5113495bSYour Name  */
hdd_driver_memdump_read(struct file * file,char __user * buf,size_t count,loff_t * pos)189*5113495bSYour Name static ssize_t hdd_driver_memdump_read(struct file *file, char __user *buf,
190*5113495bSYour Name 				       size_t count, loff_t *pos)
191*5113495bSYour Name {
192*5113495bSYour Name 	struct osif_driver_sync *driver_sync;
193*5113495bSYour Name 	ssize_t err_size;
194*5113495bSYour Name 
195*5113495bSYour Name 	err_size = osif_driver_sync_op_start(&driver_sync);
196*5113495bSYour Name 	if (err_size)
197*5113495bSYour Name 		return err_size;
198*5113495bSYour Name 
199*5113495bSYour Name 	err_size = __hdd_driver_memdump_read(file, buf, count, pos);
200*5113495bSYour Name 
201*5113495bSYour Name 	osif_driver_sync_op_stop(driver_sync);
202*5113495bSYour Name 
203*5113495bSYour Name 	return err_size;
204*5113495bSYour Name }
205*5113495bSYour Name 
206*5113495bSYour Name #if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0))
207*5113495bSYour Name static const struct proc_ops driver_dump_fops = {
208*5113495bSYour Name 	.proc_read = hdd_driver_memdump_read,
209*5113495bSYour Name 	.proc_lseek = default_llseek,
210*5113495bSYour Name };
211*5113495bSYour Name #else
212*5113495bSYour Name static const struct file_operations driver_dump_fops = {
213*5113495bSYour Name 	.read = hdd_driver_memdump_read,
214*5113495bSYour Name };
215*5113495bSYour Name #endif
216*5113495bSYour Name 
217*5113495bSYour Name /**
218*5113495bSYour Name  * hdd_driver_memdump_procfs_init() - Initialize procfs for driver memory dump
219*5113495bSYour Name  * @hdd_ctx: Pointer to hdd context
220*5113495bSYour Name  *
221*5113495bSYour Name  * This function create file under proc file system to be used later for
222*5113495bSYour Name  * processing driver memory dump
223*5113495bSYour Name  *
224*5113495bSYour Name  * Return:   0 on success, error code otherwise.
225*5113495bSYour Name  */
hdd_driver_memdump_procfs_init(struct hdd_context * hdd_ctx)226*5113495bSYour Name static int hdd_driver_memdump_procfs_init(struct hdd_context *hdd_ctx)
227*5113495bSYour Name {
228*5113495bSYour Name 	proc_dir_driver = proc_mkdir(PROCFS_DRIVER_DUMP_DIR, NULL);
229*5113495bSYour Name 	if (!proc_dir_driver) {
230*5113495bSYour Name 		pr_debug("Could not initialize /proc/%s\n",
231*5113495bSYour Name 			 PROCFS_DRIVER_DUMP_DIR);
232*5113495bSYour Name 		return -ENOMEM;
233*5113495bSYour Name 	}
234*5113495bSYour Name 
235*5113495bSYour Name 	proc_file_driver = proc_create_data(PROCFS_DRIVER_DUMP_NAME,
236*5113495bSYour Name 				     PROCFS_DRIVER_DUMP_PERM, proc_dir_driver,
237*5113495bSYour Name 				     &driver_dump_fops, hdd_ctx);
238*5113495bSYour Name 	if (!proc_file_driver) {
239*5113495bSYour Name 		remove_proc_entry(PROCFS_DRIVER_DUMP_NAME, proc_dir_driver);
240*5113495bSYour Name 		pr_debug("Could not initialize /proc/%s\n",
241*5113495bSYour Name 			  PROCFS_DRIVER_DUMP_NAME);
242*5113495bSYour Name 		return -ENOMEM;
243*5113495bSYour Name 	}
244*5113495bSYour Name 
245*5113495bSYour Name 	pr_debug("/proc/%s/%s created\n", PROCFS_DRIVER_DUMP_DIR,
246*5113495bSYour Name 		 PROCFS_DRIVER_DUMP_NAME);
247*5113495bSYour Name 	return 0;
248*5113495bSYour Name }
249*5113495bSYour Name 
250*5113495bSYour Name /**
251*5113495bSYour Name  * hdd_driver_memdump_procfs_remove() - Remove file/dir under procfs
252*5113495bSYour Name  * for driver memory dump
253*5113495bSYour Name  *
254*5113495bSYour Name  * This function removes file/dir under proc file system that was
255*5113495bSYour Name  * processing driver memory dump
256*5113495bSYour Name  *
257*5113495bSYour Name  * Return:  None
258*5113495bSYour Name  */
hdd_driver_memdump_procfs_remove(void)259*5113495bSYour Name static void hdd_driver_memdump_procfs_remove(void)
260*5113495bSYour Name {
261*5113495bSYour Name 	if (!proc_file_driver)
262*5113495bSYour Name 		return;
263*5113495bSYour Name 	remove_proc_entry(PROCFS_DRIVER_DUMP_NAME, proc_dir_driver);
264*5113495bSYour Name 	pr_debug("/proc/%s/%s removed\n", PROCFS_DRIVER_DUMP_DIR,
265*5113495bSYour Name 					  PROCFS_DRIVER_DUMP_NAME);
266*5113495bSYour Name 	remove_proc_entry(PROCFS_DRIVER_DUMP_DIR, NULL);
267*5113495bSYour Name 	pr_debug("/proc/%s removed\n", PROCFS_DRIVER_DUMP_DIR);
268*5113495bSYour Name }
269*5113495bSYour Name 
270*5113495bSYour Name /**
271*5113495bSYour Name  * hdd_driver_memdump_init() - Initialization function for driver
272*5113495bSYour Name  * memory dump feature
273*5113495bSYour Name  *
274*5113495bSYour Name  * This function creates proc file for driver memdump feature
275*5113495bSYour Name  *
276*5113495bSYour Name  * Return - 0 on success, error otherwise
277*5113495bSYour Name  */
hdd_driver_memdump_init(void)278*5113495bSYour Name int hdd_driver_memdump_init(void)
279*5113495bSYour Name {
280*5113495bSYour Name 	int status;
281*5113495bSYour Name 	struct hdd_context *hdd_ctx;
282*5113495bSYour Name 
283*5113495bSYour Name 	hdd_ctx = cds_get_context(QDF_MODULE_ID_HDD);
284*5113495bSYour Name 	if (!hdd_ctx)
285*5113495bSYour Name 		return -EINVAL;
286*5113495bSYour Name 
287*5113495bSYour Name 	mutex_init(&hdd_ctx->memdump_lock);
288*5113495bSYour Name 
289*5113495bSYour Name 	status = hdd_driver_memdump_procfs_init(hdd_ctx);
290*5113495bSYour Name 	if (status) {
291*5113495bSYour Name 		hdd_err("Failed to create proc file");
292*5113495bSYour Name 		return status;
293*5113495bSYour Name 	}
294*5113495bSYour Name 
295*5113495bSYour Name 	return 0;
296*5113495bSYour Name }
297*5113495bSYour Name 
298*5113495bSYour Name /**
299*5113495bSYour Name  * hdd_driver_memdump_deinit() - De initialize driver memdump feature
300*5113495bSYour Name  *
301*5113495bSYour Name  * This function removes proc file created for driver memdump feature.
302*5113495bSYour Name  *
303*5113495bSYour Name  * Return: None
304*5113495bSYour Name  */
hdd_driver_memdump_deinit(void)305*5113495bSYour Name void hdd_driver_memdump_deinit(void)
306*5113495bSYour Name {
307*5113495bSYour Name 	hdd_driver_memdump_procfs_remove();
308*5113495bSYour Name }
309