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