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