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