blob: 59876107941ff3061052cff514681c8418838719 [file] [log] [blame]
Prakash Dhavali7090c5f2015-11-02 17:55:19 -08001/*
Jeff Johnson68755312017-02-10 11:46:55 -08002 * Copyright (c) 2016-2017 The Linux Foundation. All rights reserved.
Prakash Dhavali7090c5f2015-11-02 17:55:19 -08003 *
4 * Previously licensed under the ISC license by Qualcomm Atheros, Inc.
5 *
6 *
7 * Permission to use, copy, modify, and/or distribute this software for
8 * any purpose with or without fee is hereby granted, provided that the
9 * above copyright notice and this permission notice appear in all
10 * copies.
11 *
12 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
13 * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
14 * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
15 * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
16 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
17 * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
18 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
19 * PERFORMANCE OF THIS SOFTWARE.
20 */
21
22/*
23 * This file was originally distributed by Qualcomm Atheros, Inc.
24 * under proprietary terms before Copyright ownership was assigned
25 * to the Linux Foundation.
26 */
27
28/**
29 * DOC : wlan_hdd_memdump.c
30 *
31 * WLAN Host Device Driver file for dumping firmware memory
32 *
33 */
34
35#include <sme_api.h>
36#include <wlan_hdd_includes.h>
37#include "wlan_hdd_memdump.h"
38#include <linux/module.h>
39#include <linux/kernel.h>
40#include <linux/version.h>
41#include <linux/proc_fs.h> /* Necessary because we use the proc fs */
42#include <linux/uaccess.h> /* for copy_to_user */
43
44/**
45 * hdd_fw_dump_context - hdd firmware memory dump context
46 *
47 * @request_id: userspace assigned firmware memory dump request ID
48 * @response_event: firmware memory dump request wait event
49 */
50struct hdd_fw_dump_context {
51 uint32_t request_id;
52 struct completion response_event;
53};
54static struct hdd_fw_dump_context fw_dump_context;
55
56/**
57 * memdump_cleanup_timer_cb() - Timer callback function for memory dump cleanup.
58 *
59 * @data: Callback data (used to stored HDD context)
60 *
61 * Callback function registered for memory dump cleanup VOS timer.
62 *
63 * Return: none
64 */
65
66static void memdump_cleanup_timer_cb(void *data)
67{
68 int status;
69 hdd_context_t *hdd_ctx = data;
Anurag Chouhan6d760662016-02-20 16:05:43 +053070 qdf_dma_addr_t paddr;
71 qdf_dma_addr_t dma_ctx = 0;
Anurag Chouhandf2b2682016-02-29 14:15:27 +053072 qdf_device_t qdf_ctx;
Prakash Dhavali7090c5f2015-11-02 17:55:19 -080073
74 status = wlan_hdd_validate_context(hdd_ctx);
Abhishek Singh23edd1c2016-05-05 11:56:06 +053075 if (status)
Prakash Dhavali7090c5f2015-11-02 17:55:19 -080076 return;
Abhishek Singh23edd1c2016-05-05 11:56:06 +053077
Prakash Dhavali7090c5f2015-11-02 17:55:19 -080078
79 if (!hdd_ctx->fw_dump_loc) {
Srinivas Girigowda87150ac2017-03-06 17:48:02 -080080 hdd_debug("Memory dump already freed");
Prakash Dhavali7090c5f2015-11-02 17:55:19 -080081 return;
82 }
83
Anurag Chouhandf2b2682016-02-29 14:15:27 +053084 qdf_ctx = cds_get_context(QDF_MODULE_ID_QDF_DEVICE);
85 if (!qdf_ctx) {
Jeff Johnsonca1b32b2016-08-02 15:25:05 -070086 hdd_err("QDF context is NULL");
Prakash Dhavali7090c5f2015-11-02 17:55:19 -080087 return;
88 }
89
90 paddr = hdd_ctx->dump_loc_paddr;
91 mutex_lock(&hdd_ctx->memdump_lock);
Anurag Chouhanf04e84f2016-03-03 10:12:12 +053092 qdf_mem_free_consistent(qdf_ctx, qdf_ctx->dev,
Prakash Dhavali7090c5f2015-11-02 17:55:19 -080093 FW_MEM_DUMP_SIZE, hdd_ctx->fw_dump_loc, paddr, dma_ctx);
94 hdd_ctx->fw_dump_loc = NULL;
95 hdd_ctx->memdump_in_progress = false;
96 mutex_unlock(&hdd_ctx->memdump_lock);
97
98}
99
100/**
101 * wlan_hdd_cfg80211_fw_mem_dump_cb() - Callback to receive FW memory dump
102 * @ctx: pointer to HDD context.
Arun Khandavalli4b55da72016-07-19 19:55:01 +0530103 * @dump_rsp: pointer to fw dump copy complete response
Prakash Dhavali7090c5f2015-11-02 17:55:19 -0800104 *
105 * This is a callback function used to indicate user space about the
106 * availability for firmware memory dump via vendor event.
107 *
108 * Return: None
109 */
Arun Khandavalli4b55da72016-07-19 19:55:01 +0530110void wlan_hdd_cfg80211_fw_mem_dump_cb(void *ctx,
Prakash Dhavali7090c5f2015-11-02 17:55:19 -0800111 struct fw_dump_rsp *dump_rsp)
112{
113 hdd_context_t *hdd_ctx = ctx;
114 struct hdd_fw_dump_context *context;
115 int status;
116
117 status = wlan_hdd_validate_context(hdd_ctx);
Abhishek Singh23edd1c2016-05-05 11:56:06 +0530118 if (status)
Prakash Dhavali7090c5f2015-11-02 17:55:19 -0800119 return;
Abhishek Singh23edd1c2016-05-05 11:56:06 +0530120
Prakash Dhavali7090c5f2015-11-02 17:55:19 -0800121
122 spin_lock(&hdd_context_lock);
123 context = &fw_dump_context;
124 /* validate the response received */
125 if (!dump_rsp->dump_complete ||
126 context->request_id != dump_rsp->request_id) {
127 spin_unlock(&hdd_context_lock);
Srinivas Girigowda87150ac2017-03-06 17:48:02 -0800128 hdd_err("request_id: %d response_id: %d status: %d",
Prakash Dhavali7090c5f2015-11-02 17:55:19 -0800129 context->request_id, dump_rsp->request_id,
130 dump_rsp->dump_complete);
131 return;
Prakash Dhavali7090c5f2015-11-02 17:55:19 -0800132 }
Jeff Johnson68755312017-02-10 11:46:55 -0800133 complete(&context->response_event);
Prakash Dhavali7090c5f2015-11-02 17:55:19 -0800134 spin_unlock(&hdd_context_lock);
135
136 return;
137}
138
139/**
140 * wlan_hdd_send_memdump_rsp - send memory dump response to user space
141 * @hdd_ctx: Pointer to hdd context
142 *
143 * Return: 0 for success; non-zero for failure
144 */
145static int wlan_hdd_send_memdump_rsp(hdd_context_t *hdd_ctx)
146{
147 struct sk_buff *skb;
148 int status;
149
150 status = wlan_hdd_validate_context(hdd_ctx);
Abhishek Singh23edd1c2016-05-05 11:56:06 +0530151 if (status)
Prakash Dhavali7090c5f2015-11-02 17:55:19 -0800152 return status;
Abhishek Singh23edd1c2016-05-05 11:56:06 +0530153
Prakash Dhavali7090c5f2015-11-02 17:55:19 -0800154
155 skb = cfg80211_vendor_cmd_alloc_reply_skb(hdd_ctx->wiphy,
156 NLMSG_HDRLEN + NLA_HDRLEN + sizeof(uint32_t));
157
158 if (!skb) {
Jeff Johnsonca1b32b2016-08-02 15:25:05 -0700159 hdd_err("cfg80211_vendor_cmd_alloc_reply_skb failed");
Prakash Dhavali7090c5f2015-11-02 17:55:19 -0800160 return -ENOMEM;
161 }
162
163 if (nla_put_u32(skb, QCA_WLAN_VENDOR_ATTR_MEMDUMP_SIZE,
164 FW_MEM_DUMP_SIZE)) {
Jeff Johnsonca1b32b2016-08-02 15:25:05 -0700165 hdd_err("nla put fail");
Prakash Dhavali7090c5f2015-11-02 17:55:19 -0800166 goto nla_put_failure;
167 }
168
169 cfg80211_vendor_cmd_reply(skb);
Srinivas Girigowda87150ac2017-03-06 17:48:02 -0800170 hdd_debug("Memdump event sent successfully to user space");
Prakash Dhavali7090c5f2015-11-02 17:55:19 -0800171 return 0;
172
173nla_put_failure:
174 kfree_skb(skb);
175 return -EINVAL;
176}
177
178/**
179 * __wlan_hdd_cfg80211_get_fw_mem_dump() - Get FW memory dump
180 * @wiphy: pointer to wireless wiphy structure.
181 * @wdev: pointer to wireless_dev structure.
182 * @data: Pointer to the NL data.
183 * @data_len:Length of @data
184 *
185 * This is called when wlan driver needs to get the firmware memory dump
186 * via vendor specific command.
187 *
188 * Return: 0 on success, error number otherwise.
189 */
190static int __wlan_hdd_cfg80211_get_fw_mem_dump(struct wiphy *wiphy,
191 struct wireless_dev *wdev,
192 const void *data, int data_len)
193{
194 int status;
Anurag Chouhanfb54ab02016-02-18 18:00:46 +0530195 QDF_STATUS sme_status;
Prakash Dhavali7090c5f2015-11-02 17:55:19 -0800196 hdd_context_t *hdd_ctx = wiphy_priv(wiphy);
197 struct fw_dump_req fw_mem_dump_req;
198 struct fw_dump_seg_req *seg_req;
199 uint8_t loop;
Anurag Chouhan6d760662016-02-20 16:05:43 +0530200 qdf_dma_addr_t paddr;
201 qdf_dma_addr_t dma_ctx = 0;
Anurag Chouhandf2b2682016-02-29 14:15:27 +0530202 qdf_device_t qdf_ctx;
Prakash Dhavali7090c5f2015-11-02 17:55:19 -0800203 unsigned long rc;
204 struct hdd_fw_dump_context *context;
205
Jeff Johnson1f61b612016-02-12 16:28:33 -0800206 ENTER_DEV(wdev->netdev);
207
Prakash Dhavali7090c5f2015-11-02 17:55:19 -0800208 status = wlan_hdd_validate_context(hdd_ctx);
Abhishek Singh23edd1c2016-05-05 11:56:06 +0530209 if (status)
Prakash Dhavali7090c5f2015-11-02 17:55:19 -0800210 return status;
Abhishek Singh23edd1c2016-05-05 11:56:06 +0530211
Anurag Chouhandf2b2682016-02-29 14:15:27 +0530212 qdf_ctx = cds_get_context(QDF_MODULE_ID_QDF_DEVICE);
213 if (!qdf_ctx) {
Jeff Johnsonca1b32b2016-08-02 15:25:05 -0700214 hdd_err("QDF context is NULL");
Prakash Dhavali7090c5f2015-11-02 17:55:19 -0800215 return -EINVAL;
216 }
217
218 if (hdd_ctx->memdump_in_progress) {
Jeff Johnsonca1b32b2016-08-02 15:25:05 -0700219 hdd_err("Already a memdump req in progress.");
Prakash Dhavali7090c5f2015-11-02 17:55:19 -0800220 return -EBUSY;
221 }
222
223 /*
224 * Allocate memory for fw memory dump. Memory allocated should be
225 * contiguous. Physical address of the allocated memory is passed
226 * to the FW for copy
227 *
228 * Reuse the memory if available.
229 */
230 mutex_lock(&hdd_ctx->memdump_lock);
231 if (!hdd_ctx->fw_dump_loc) {
Anurag Chouhan600c3a02016-03-01 10:33:54 +0530232 hdd_ctx->fw_dump_loc = qdf_mem_alloc_consistent(
233 qdf_ctx, qdf_ctx->dev, FW_MEM_DUMP_SIZE, &paddr);
Prakash Dhavali7090c5f2015-11-02 17:55:19 -0800234 if (!hdd_ctx->fw_dump_loc) {
235 mutex_unlock(&hdd_ctx->memdump_lock);
Jeff Johnsonca1b32b2016-08-02 15:25:05 -0700236 hdd_err("qdf_mem_alloc_consistent failed");
Prakash Dhavali7090c5f2015-11-02 17:55:19 -0800237 return -ENOMEM;
238 }
239 hdd_ctx->dump_loc_paddr = paddr;
Manjeet Singh0e617d62016-11-09 16:08:41 +0530240 } else {
241 paddr = hdd_ctx->dump_loc_paddr;
Prakash Dhavali7090c5f2015-11-02 17:55:19 -0800242 }
243 mutex_unlock(&hdd_ctx->memdump_lock);
244
245 /*
246 * Currently request_id and num_seg is assumed to be default(1)
247 * It is assumed that firmware dump requested is for DRAM section
248 * only
249 */
250
251 fw_mem_dump_req.request_id = FW_MEM_DUMP_REQ_ID;
252 fw_mem_dump_req.num_seg = FW_MEM_DUMP_NUM_SEG;
253
Srinivas Girigowda87150ac2017-03-06 17:48:02 -0800254 hdd_debug("request_id:%d num_seg:%d",
Prakash Dhavali7090c5f2015-11-02 17:55:19 -0800255 fw_mem_dump_req.request_id, fw_mem_dump_req.num_seg);
256 seg_req = (struct fw_dump_seg_req *) fw_mem_dump_req.segment;
257 for (loop = 0; loop < fw_mem_dump_req.num_seg; loop++) {
258 seg_req->seg_id = 1;
259 seg_req->seg_start_addr_lo = FW_DRAM_LOCATION;
260 seg_req->seg_start_addr_hi = 0;
261 seg_req->seg_length = FW_MEM_DUMP_SIZE;
262 seg_req->dst_addr_lo = hdd_ctx->dump_loc_paddr;
263 seg_req->dst_addr_hi = 0;
Srinivas Girigowda87150ac2017-03-06 17:48:02 -0800264 hdd_debug("seg_number:%d", loop);
265 hdd_debug("seg_id:%d start_addr_lo:0x%x start_addr_hi:0x%x",
Prakash Dhavali7090c5f2015-11-02 17:55:19 -0800266 seg_req->seg_id, seg_req->seg_start_addr_lo,
267 seg_req->seg_start_addr_hi);
Srinivas Girigowda87150ac2017-03-06 17:48:02 -0800268 hdd_debug("seg_length:%d dst_addr_lo:0x%x dst_addr_hi:0x%x",
Prakash Dhavali7090c5f2015-11-02 17:55:19 -0800269 seg_req->seg_length, seg_req->dst_addr_lo,
270 seg_req->dst_addr_hi);
271 seg_req++;
272 }
273
274 /**
275 * Start the cleanup timer.
276 * Memory allocated for this request will be freed up
277 * once the timer expires. Memory dump request is expected to be
278 * completed by this time.
279 *
280 * User space will not be able to access the dump after this time.
281 * New request should be issued to get the dump again.
282 */
Anurag Chouhan210db072016-02-22 18:42:15 +0530283 qdf_mc_timer_start(&hdd_ctx->memdump_cleanup_timer,
Prakash Dhavali7090c5f2015-11-02 17:55:19 -0800284 MEMDUMP_COMPLETION_TIME_MS);
285 hdd_ctx->memdump_in_progress = true;
286
287 spin_lock(&hdd_context_lock);
288 context = &fw_dump_context;
289 context->request_id = fw_mem_dump_req.request_id;
290 INIT_COMPLETION(context->response_event);
291 spin_unlock(&hdd_context_lock);
292
293 sme_status = sme_fw_mem_dump(hdd_ctx->hHal, &fw_mem_dump_req);
Anurag Chouhanfb54ab02016-02-18 18:00:46 +0530294 if (QDF_STATUS_SUCCESS != sme_status) {
Jeff Johnsonca1b32b2016-08-02 15:25:05 -0700295 hdd_err("sme_fw_mem_dump Failed");
Prakash Dhavali7090c5f2015-11-02 17:55:19 -0800296 mutex_lock(&hdd_ctx->memdump_lock);
Anurag Chouhanf04e84f2016-03-03 10:12:12 +0530297 qdf_mem_free_consistent(qdf_ctx, qdf_ctx->dev,
Prakash Dhavali7090c5f2015-11-02 17:55:19 -0800298 FW_MEM_DUMP_SIZE, hdd_ctx->fw_dump_loc, paddr, dma_ctx);
299 hdd_ctx->fw_dump_loc = NULL;
300 mutex_unlock(&hdd_ctx->memdump_lock);
301 hdd_ctx->memdump_in_progress = false;
Anurag Chouhan210db072016-02-22 18:42:15 +0530302 if (QDF_TIMER_STATE_RUNNING ==
303 qdf_mc_timer_get_current_state(
Prakash Dhavali7090c5f2015-11-02 17:55:19 -0800304 &hdd_ctx->memdump_cleanup_timer)) {
Anurag Chouhan210db072016-02-22 18:42:15 +0530305 qdf_mc_timer_stop(&hdd_ctx->memdump_cleanup_timer);
Prakash Dhavali7090c5f2015-11-02 17:55:19 -0800306 }
307 return -EINVAL;
308 }
309
310 rc = wait_for_completion_timeout(&context->response_event,
311 msecs_to_jiffies(MEMDUMP_COMPLETION_TIME_MS));
312 if (!rc) {
Jeff Johnsonca1b32b2016-08-02 15:25:05 -0700313 hdd_err("Target response timed out for request_id: %d",
Prakash Dhavali7090c5f2015-11-02 17:55:19 -0800314 context->request_id);
315 return -ETIMEDOUT;
316 }
317
318 status = wlan_hdd_send_memdump_rsp(hdd_ctx);
319 if (status)
Jeff Johnsonca1b32b2016-08-02 15:25:05 -0700320 hdd_err("Failed to send FW memory dump rsp to user space");
Prakash Dhavali7090c5f2015-11-02 17:55:19 -0800321
322 return status;
323}
324
325/**
326 * wlan_hdd_cfg80211_get_fw_mem_dump() - Get FW memory dump
327 * @wiphy: pointer to wireless wiphy structure.
328 * @wdev: pointer to wireless_dev structure.
329 * @data: Pointer to the NL data.
330 * @data_len:Length of @data
331 *
332 * This is called when wlan driver needs to get the firmware memory dump
333 * via vendor specific command.
334 *
335 * Return: 0 on success, error number otherwise.
336 */
337int wlan_hdd_cfg80211_get_fw_mem_dump(struct wiphy *wiphy,
338 struct wireless_dev *wdev,
339 const void *data, int data_len)
340{
341 int ret;
342
343 cds_ssr_protect(__func__);
344 ret = __wlan_hdd_cfg80211_get_fw_mem_dump(wiphy, wdev, data, data_len);
345 cds_ssr_unprotect(__func__);
346
347 return ret;
348}
349
350#define PROCFS_MEMDUMP_DIR "debug"
351#define PROCFS_MEMDUMP_NAME "fwdump"
352#define PROCFS_MEMDUMP_PERM 0444
353
354static struct proc_dir_entry *proc_file, *proc_dir;
355
356/** memdump_get_file_data() - get data available in proc file
357 *
358 * @file - handle for the proc file.
359 *
360 * This function is used to retrieve the data passed while
361 * creating proc file entry.
362 *
363 * Return: void pointer to hdd_context
364 */
Prakash Dhavali7090c5f2015-11-02 17:55:19 -0800365static void *memdump_get_file_data(struct file *file)
366{
367 void *hdd_ctx;
368
369 hdd_ctx = PDE_DATA(file_inode(file));
370 return hdd_ctx;
371}
Prakash Dhavali7090c5f2015-11-02 17:55:19 -0800372
373/**
374 * memdump_read() - perform read operation in memory dump proc file
375 *
376 * @file - handle for the proc file.
377 * @buf - pointer to user space buffer.
378 * @count - number of bytes to be read.
379 * @pos - offset in the from buffer.
380 *
381 * This function performs read operation for the memory dump proc file.
382 *
383 * Return: number of bytes read on success, error code otherwise.
384 */
385static ssize_t memdump_read(struct file *file, char __user *buf,
386 size_t count, loff_t *pos)
387{
388 int status;
389 hdd_context_t *hdd_ctx;
Anurag Chouhan6d760662016-02-20 16:05:43 +0530390 qdf_dma_addr_t paddr;
391 qdf_dma_addr_t dma_ctx = 0;
Anurag Chouhandf2b2682016-02-29 14:15:27 +0530392 qdf_device_t qdf_ctx;
Prakash Dhavali7090c5f2015-11-02 17:55:19 -0800393
394 hdd_ctx = memdump_get_file_data(file);
395
Srinivas Girigowda87150ac2017-03-06 17:48:02 -0800396 hdd_debug("Read req for size:%zu pos:%llu", count, *pos);
Prakash Dhavali7090c5f2015-11-02 17:55:19 -0800397 status = wlan_hdd_validate_context(hdd_ctx);
Abhishek Singh23edd1c2016-05-05 11:56:06 +0530398 if (status)
399 return status;
400
Anurag Chouhandf2b2682016-02-29 14:15:27 +0530401 qdf_ctx = cds_get_context(QDF_MODULE_ID_QDF_DEVICE);
402 if (!qdf_ctx) {
Jeff Johnsonca1b32b2016-08-02 15:25:05 -0700403 hdd_err("QDF context is NULL");
Prakash Dhavali7090c5f2015-11-02 17:55:19 -0800404 return -EINVAL;
405 }
406
407 if (!hdd_ctx->memdump_in_progress) {
Jeff Johnsonca1b32b2016-08-02 15:25:05 -0700408 hdd_err("Current mem dump request timed out/failed");
Prakash Dhavali7090c5f2015-11-02 17:55:19 -0800409 return -EINVAL;
410 }
411
412 if (*pos < 0) {
Jeff Johnsonca1b32b2016-08-02 15:25:05 -0700413 hdd_err("Invalid start offset for memdump read");
Prakash Dhavali7090c5f2015-11-02 17:55:19 -0800414 return -EINVAL;
415 } else if (*pos >= FW_MEM_DUMP_SIZE || !count) {
Srinivas Girigowda87150ac2017-03-06 17:48:02 -0800416 hdd_debug("No more data to copy");
Prakash Dhavali7090c5f2015-11-02 17:55:19 -0800417 return 0;
418 } else if (count > FW_MEM_DUMP_SIZE - *pos) {
419 count = FW_MEM_DUMP_SIZE - *pos;
420 }
421
422 if (!hdd_ctx->fw_dump_loc) {
Jeff Johnsonca1b32b2016-08-02 15:25:05 -0700423 hdd_err("Invalid fw mem dump location");
Prakash Dhavali7090c5f2015-11-02 17:55:19 -0800424 return -EINVAL;
425 }
426
427 if (copy_to_user(buf, hdd_ctx->fw_dump_loc + *pos, count)) {
Jeff Johnsonca1b32b2016-08-02 15:25:05 -0700428 hdd_err("copy to user space failed");
Prakash Dhavali7090c5f2015-11-02 17:55:19 -0800429 return -EFAULT;
430 }
431
432 /* offset(pos) should be updated here based on the copy done*/
433 *pos += count;
434
435 /* Entire FW memory dump copy completed */
436 if (*pos >= FW_MEM_DUMP_SIZE) {
437 paddr = hdd_ctx->dump_loc_paddr;
438 mutex_lock(&hdd_ctx->memdump_lock);
Anurag Chouhanf04e84f2016-03-03 10:12:12 +0530439 qdf_mem_free_consistent(qdf_ctx, qdf_ctx->dev,
Prakash Dhavali7090c5f2015-11-02 17:55:19 -0800440 FW_MEM_DUMP_SIZE, hdd_ctx->fw_dump_loc, paddr, dma_ctx);
441 hdd_ctx->fw_dump_loc = NULL;
442 hdd_ctx->memdump_in_progress = false;
Anurag Chouhan210db072016-02-22 18:42:15 +0530443 if (QDF_TIMER_STATE_RUNNING ==
444 qdf_mc_timer_get_current_state(
Prakash Dhavali7090c5f2015-11-02 17:55:19 -0800445 &hdd_ctx->memdump_cleanup_timer)) {
Anurag Chouhan210db072016-02-22 18:42:15 +0530446 qdf_mc_timer_stop(&hdd_ctx->memdump_cleanup_timer);
Prakash Dhavali7090c5f2015-11-02 17:55:19 -0800447 }
448 mutex_unlock(&hdd_ctx->memdump_lock);
449 }
450
451 return count;
452}
453
454/**
455 * struct memdump_fops - file operations for memory dump feature
456 * @read - read function for memory dump operation.
457 *
458 * This structure initialize the file operation handle for memory
459 * dump feature
460 */
461static const struct file_operations memdump_fops = {
462 read: memdump_read
463};
464
465/**
466 * memdump_procfs_init() - Initialize procfs for memory dump
467 *
468 * This function create file under proc file system to be used later for
469 * processing firmware memory dump
470 *
471 * Return: 0 on success, error code otherwise.
472 */
473static int memdump_procfs_init(void)
474{
475 hdd_context_t *hdd_ctx;
476
Anurag Chouhan6d760662016-02-20 16:05:43 +0530477 hdd_ctx = cds_get_context(QDF_MODULE_ID_HDD);
Prakash Dhavali7090c5f2015-11-02 17:55:19 -0800478 if (!hdd_ctx) {
Jeff Johnsonca1b32b2016-08-02 15:25:05 -0700479 hdd_err("Invalid HDD context");
Prakash Dhavali7090c5f2015-11-02 17:55:19 -0800480 return -EINVAL;
481 }
482
483 proc_dir = proc_mkdir(PROCFS_MEMDUMP_DIR, NULL);
484 if (proc_dir == NULL) {
485 remove_proc_entry(PROCFS_MEMDUMP_DIR, NULL);
Srinivas Girigowda87150ac2017-03-06 17:48:02 -0800486 pr_debug("Could not initialize /proc/%s\n",
Prakash Dhavali7090c5f2015-11-02 17:55:19 -0800487 PROCFS_MEMDUMP_DIR);
488 return -ENOMEM;
489 }
490
491 proc_file = proc_create_data(PROCFS_MEMDUMP_NAME,
492 PROCFS_MEMDUMP_PERM, proc_dir,
493 &memdump_fops, hdd_ctx);
494 if (proc_file == NULL) {
495 remove_proc_entry(PROCFS_MEMDUMP_NAME, proc_dir);
Srinivas Girigowda87150ac2017-03-06 17:48:02 -0800496 pr_debug("Could not initialize /proc/%s\n",
Prakash Dhavali7090c5f2015-11-02 17:55:19 -0800497 PROCFS_MEMDUMP_NAME);
498 return -ENOMEM;
499 }
500
501 pr_debug("/proc/%s/%s created\n", PROCFS_MEMDUMP_DIR,
502 PROCFS_MEMDUMP_NAME);
503 return 0;
504}
505
506/**
507 * memdump_procfs_remove() - Remove file/dir under procfs for memory dump
508 *
509 * This function removes file/dir under proc file system that was
510 * processing firmware memory dump
511 *
512 * Return: None
513 */
514static void memdump_procfs_remove(void)
515{
516 remove_proc_entry(PROCFS_MEMDUMP_NAME, proc_dir);
517 pr_debug("/proc/%s/%s removed\n", PROCFS_MEMDUMP_DIR,
518 PROCFS_MEMDUMP_NAME);
519 remove_proc_entry(PROCFS_MEMDUMP_DIR, NULL);
520 pr_debug("/proc/%s removed\n", PROCFS_MEMDUMP_DIR);
521}
522
523/**
524 * memdump_init() - Intialization function for memory dump feature
525 *
526 * This function creates proc file for memdump feature and registers
527 * HDD callback function with SME.
528 *
529 * Return - 0 on success, error otherwise
530 */
531int memdump_init(void)
532{
533 hdd_context_t *hdd_ctx;
534 int status = 0;
Anurag Chouhanfb54ab02016-02-18 18:00:46 +0530535 QDF_STATUS qdf_status;
Prakash Dhavali7090c5f2015-11-02 17:55:19 -0800536
Anurag Chouhan6d760662016-02-20 16:05:43 +0530537 hdd_ctx = cds_get_context(QDF_MODULE_ID_HDD);
Prakash Dhavali7090c5f2015-11-02 17:55:19 -0800538 if (!hdd_ctx) {
Jeff Johnsonca1b32b2016-08-02 15:25:05 -0700539 hdd_err("Invalid HDD context");
Prakash Dhavali7090c5f2015-11-02 17:55:19 -0800540 return -EINVAL;
541 }
542
Anurag Chouhan6d760662016-02-20 16:05:43 +0530543 if (QDF_GLOBAL_FTM_MODE == hdd_get_conparam()) {
Jeff Johnsonca1b32b2016-08-02 15:25:05 -0700544 hdd_err("Not initializing memdump in FTM mode");
Prakash Dhavali7090c5f2015-11-02 17:55:19 -0800545 return -EINVAL;
546 }
547
Prakash Dhavali7090c5f2015-11-02 17:55:19 -0800548 status = memdump_procfs_init();
549 if (status) {
Jeff Johnsonca1b32b2016-08-02 15:25:05 -0700550 hdd_err("Failed to create proc file");
Prakash Dhavali7090c5f2015-11-02 17:55:19 -0800551 return status;
552 }
553
554 init_completion(&fw_dump_context.response_event);
555
Anurag Chouhan210db072016-02-22 18:42:15 +0530556 qdf_status = qdf_mc_timer_init(&hdd_ctx->memdump_cleanup_timer,
Anurag Chouhan6d760662016-02-20 16:05:43 +0530557 QDF_TIMER_TYPE_SW, memdump_cleanup_timer_cb,
Prakash Dhavali7090c5f2015-11-02 17:55:19 -0800558 (void *)hdd_ctx);
Anurag Chouhanfb54ab02016-02-18 18:00:46 +0530559 if (!QDF_IS_STATUS_SUCCESS(qdf_status)) {
Jeff Johnsonca1b32b2016-08-02 15:25:05 -0700560 hdd_err("Failed to init memdump cleanup timer");
Prakash Dhavali7090c5f2015-11-02 17:55:19 -0800561 return -EINVAL;
562 }
563
564 mutex_init(&hdd_ctx->memdump_lock);
Sachin Ahuja02dd2e72016-09-03 16:17:51 +0530565 hdd_ctx->memdump_init_done = true;
Prakash Dhavali7090c5f2015-11-02 17:55:19 -0800566
567 return 0;
568}
569
570/**
571 * memdump_deinit() - De initialize memdump feature
572 *
573 * This function removes proc file created for memdump feature.
574 *
575 * Return: None
576 */
577void memdump_deinit(void)
578{
579 hdd_context_t *hdd_ctx;
Anurag Chouhan6d760662016-02-20 16:05:43 +0530580 qdf_dma_addr_t paddr;
581 qdf_dma_addr_t dma_ctx = 0;
Anurag Chouhandf2b2682016-02-29 14:15:27 +0530582 qdf_device_t qdf_ctx;
Anurag Chouhanfb54ab02016-02-18 18:00:46 +0530583 QDF_STATUS qdf_status;
Prakash Dhavali7090c5f2015-11-02 17:55:19 -0800584
Anurag Chouhan6d760662016-02-20 16:05:43 +0530585 hdd_ctx = cds_get_context(QDF_MODULE_ID_HDD);
Prakash Dhavali7090c5f2015-11-02 17:55:19 -0800586 if (!hdd_ctx) {
Jeff Johnsonca1b32b2016-08-02 15:25:05 -0700587 hdd_err("Invalid HDD context");
Prakash Dhavali7090c5f2015-11-02 17:55:19 -0800588 return;
589 }
590
Anurag Chouhan6d760662016-02-20 16:05:43 +0530591 if (QDF_GLOBAL_FTM_MODE == hdd_get_conparam()) {
Jeff Johnsonca1b32b2016-08-02 15:25:05 -0700592 hdd_err("Not deinitializing memdump in FTM mode");
Prakash Dhavali7090c5f2015-11-02 17:55:19 -0800593 return;
594 }
595
Sachin Ahuja02dd2e72016-09-03 16:17:51 +0530596 if (!hdd_ctx->memdump_init_done) {
Srinivas Girigowda87150ac2017-03-06 17:48:02 -0800597 hdd_warn("MemDump not initialized");
Sachin Ahuja02dd2e72016-09-03 16:17:51 +0530598 return;
599 }
600 hdd_ctx->memdump_init_done = false;
601
Anurag Chouhandf2b2682016-02-29 14:15:27 +0530602 qdf_ctx = cds_get_context(QDF_MODULE_ID_QDF_DEVICE);
603 if (!qdf_ctx) {
Jeff Johnsonca1b32b2016-08-02 15:25:05 -0700604 hdd_err("QDF context is NULL");
Prakash Dhavali7090c5f2015-11-02 17:55:19 -0800605 return;
606 }
607
608 memdump_procfs_remove();
Prakash Dhavali7090c5f2015-11-02 17:55:19 -0800609
610 mutex_lock(&hdd_ctx->memdump_lock);
611 if (hdd_ctx->fw_dump_loc) {
612 paddr = hdd_ctx->dump_loc_paddr;
Anurag Chouhanf04e84f2016-03-03 10:12:12 +0530613 qdf_mem_free_consistent(qdf_ctx, qdf_ctx->dev,
Prakash Dhavali7090c5f2015-11-02 17:55:19 -0800614 FW_MEM_DUMP_SIZE, hdd_ctx->fw_dump_loc, paddr, dma_ctx);
615 hdd_ctx->fw_dump_loc = NULL;
616 hdd_ctx->memdump_in_progress = false;
617 }
618 mutex_unlock(&hdd_ctx->memdump_lock);
Sachin Ahuja02dd2e72016-09-03 16:17:51 +0530619 mutex_destroy(&hdd_ctx->memdump_lock);
Prakash Dhavali7090c5f2015-11-02 17:55:19 -0800620
Anurag Chouhan210db072016-02-22 18:42:15 +0530621 if (QDF_TIMER_STATE_RUNNING ==
622 qdf_mc_timer_get_current_state(&hdd_ctx->memdump_cleanup_timer)) {
623 qdf_mc_timer_stop(&hdd_ctx->memdump_cleanup_timer);
Prakash Dhavali7090c5f2015-11-02 17:55:19 -0800624 }
625
Anurag Chouhan210db072016-02-22 18:42:15 +0530626 qdf_status = qdf_mc_timer_destroy(&hdd_ctx->memdump_cleanup_timer);
Anurag Chouhanfb54ab02016-02-18 18:00:46 +0530627 if (!QDF_IS_STATUS_SUCCESS(qdf_status))
Jeff Johnsonca1b32b2016-08-02 15:25:05 -0700628 hdd_err("Failed to deallocate timer");
Prakash Dhavali7090c5f2015-11-02 17:55:19 -0800629}
Padma, Santhosh Kumar9aba02f2016-08-11 16:30:25 +0530630
631#ifdef MULTI_IF_NAME
632#define PROCFS_DRIVER_DUMP_DIR "debugdriver" MULTI_IF_NAME
633#else
634#define PROCFS_DRIVER_DUMP_DIR "debugdriver"
635#endif
636#define PROCFS_DRIVER_DUMP_NAME "driverdump"
637#define PROCFS_DRIVER_DUMP_PERM 0444
638
639static struct proc_dir_entry *proc_file_driver, *proc_dir_driver;
640
641/**
642 * hdd_driver_mem_cleanup() - Frees memory allocated for
643 * driver dump
644 *
645 * This function unallocates driver dump memory.
646 *
647 * Return: None
648 */
649static void hdd_driver_mem_cleanup(void)
650{
651 hdd_context_t *hdd_ctx;
652
653 hdd_ctx = cds_get_context(QDF_MODULE_ID_HDD);
654 if (!hdd_ctx) {
655 hdd_err("Invalid HDD context");
656 return;
657 }
658
659 if (hdd_ctx->driver_dump_mem) {
660 qdf_mem_free(hdd_ctx->driver_dump_mem);
661 hdd_ctx->driver_dump_mem = NULL;
662 }
663}
664
665
666/**
667 * hdd_driver_memdump_read() - perform read operation in driver
668 * memory dump proc file
669 * @file - handle for the proc file.
670 * @buf - pointer to user space buffer.
671 * @count - number of bytes to be read.
672 * @pos - offset in the from buffer.
673 *
674 * This function performs read operation for the driver memory dump proc file.
675 *
676 * Return: number of bytes read on success, error code otherwise.
677 */
678static ssize_t hdd_driver_memdump_read(struct file *file, char __user *buf,
679 size_t count, loff_t *pos)
680{
681 int status;
682 QDF_STATUS qdf_status;
683 hdd_context_t *hdd_ctx;
684 size_t no_of_bytes_read = 0;
685
686 hdd_ctx = memdump_get_file_data(file);
687
Srinivas Girigowda87150ac2017-03-06 17:48:02 -0800688 hdd_debug("Read req for size:%zu pos:%llu", count, *pos);
Padma, Santhosh Kumar9aba02f2016-08-11 16:30:25 +0530689 status = wlan_hdd_validate_context(hdd_ctx);
690 if (status != 0)
691 return -EINVAL;
692
SaidiReddy Yenugac3a74ba2017-03-08 16:17:55 +0530693 mutex_lock(&hdd_ctx->memdump_lock);
Padma, Santhosh Kumar9aba02f2016-08-11 16:30:25 +0530694 if (*pos < 0) {
695 hdd_err("Invalid start offset for memdump read");
SaidiReddy Yenugac3a74ba2017-03-08 16:17:55 +0530696 mutex_unlock(&hdd_ctx->memdump_lock);
Padma, Santhosh Kumar9aba02f2016-08-11 16:30:25 +0530697 return -EINVAL;
698 } else if (!count || (hdd_ctx->driver_dump_size &&
699 (*pos >= hdd_ctx->driver_dump_size))) {
SaidiReddy Yenugac3a74ba2017-03-08 16:17:55 +0530700 mutex_unlock(&hdd_ctx->memdump_lock);
Srinivas Girigowda87150ac2017-03-06 17:48:02 -0800701 hdd_debug("No more data to copy");
Padma, Santhosh Kumar9aba02f2016-08-11 16:30:25 +0530702 return 0;
703 } else if ((*pos == 0) || (hdd_ctx->driver_dump_mem == NULL)) {
704 /*
705 * Allocate memory for Driver memory dump.
706 */
707 if (!hdd_ctx->driver_dump_mem) {
708 hdd_ctx->driver_dump_mem =
709 qdf_mem_malloc(DRIVER_MEM_DUMP_SIZE);
710 if (!hdd_ctx->driver_dump_mem) {
711 hdd_err("qdf_mem_malloc failed");
SaidiReddy Yenugac3a74ba2017-03-08 16:17:55 +0530712 mutex_unlock(&hdd_ctx->memdump_lock);
Padma, Santhosh Kumar9aba02f2016-08-11 16:30:25 +0530713 return -ENOMEM;
714 }
715 }
716
717 qdf_status = qdf_state_info_dump_all(hdd_ctx->driver_dump_mem,
718 DRIVER_MEM_DUMP_SIZE,
719 &hdd_ctx->driver_dump_size);
720 /*
721 * If qdf_status is QDF_STATUS_E_NOMEM, then memory allocated is
722 * insufficient to dump driver information. This print can give
723 * information to allocate more memory if more information from
724 * each layer is added in future.
725 */
726 if (qdf_status != QDF_STATUS_SUCCESS)
727 hdd_err("Error in dump driver information, status %d",
728 qdf_status);
Srinivas Girigowda87150ac2017-03-06 17:48:02 -0800729 hdd_debug("driver_dump_size: %d",
Padma, Santhosh Kumar9aba02f2016-08-11 16:30:25 +0530730 hdd_ctx->driver_dump_size);
731 }
732
733 if (count > hdd_ctx->driver_dump_size - *pos)
734 no_of_bytes_read = hdd_ctx->driver_dump_size - *pos;
735 else
736 no_of_bytes_read = count;
737
738 if (copy_to_user(buf, hdd_ctx->driver_dump_mem + *pos,
739 no_of_bytes_read)) {
740 hdd_err("copy to user space failed");
SaidiReddy Yenugac3a74ba2017-03-08 16:17:55 +0530741 mutex_unlock(&hdd_ctx->memdump_lock);
Padma, Santhosh Kumar9aba02f2016-08-11 16:30:25 +0530742 return -EFAULT;
743 }
744
745 /* offset(pos) should be updated here based on the copy done */
746 *pos += no_of_bytes_read;
747
748 /* Entire driver memory dump copy completed */
749 if (*pos >= hdd_ctx->driver_dump_size)
750 hdd_driver_mem_cleanup();
751
SaidiReddy Yenugac3a74ba2017-03-08 16:17:55 +0530752 mutex_unlock(&hdd_ctx->memdump_lock);
753
Padma, Santhosh Kumar9aba02f2016-08-11 16:30:25 +0530754 return no_of_bytes_read;
755}
756
757
758/**
759 * struct driver_dump_fops - file operations for driver dump feature
760 * @read - read function for driver dump operation.
761 *
762 * This structure initialize the file operation handle for memory
763 * dump feature
764 */
765static const struct file_operations driver_dump_fops = {
766read: hdd_driver_memdump_read
767};
768
769/**
770 * hdd_driver_memdump_procfs_init() - Initialize procfs for driver memory dump
771 *
772 * This function create file under proc file system to be used later for
773 * processing driver memory dump
774 *
775 * Return: 0 on success, error code otherwise.
776 */
777static int hdd_driver_memdump_procfs_init(void)
778{
779 hdd_context_t *hdd_ctx;
780
781 hdd_ctx = cds_get_context(QDF_MODULE_ID_HDD);
782 if (!hdd_ctx) {
783 hdd_err("Invalid HDD context");
784 return -EINVAL;
785 }
786
787 proc_dir_driver = proc_mkdir(PROCFS_DRIVER_DUMP_DIR, NULL);
788 if (proc_dir_driver == NULL) {
Srinivas Girigowda87150ac2017-03-06 17:48:02 -0800789 pr_debug("Could not initialize /proc/%s\n",
Padma, Santhosh Kumar9aba02f2016-08-11 16:30:25 +0530790 PROCFS_DRIVER_DUMP_DIR);
791 return -ENOMEM;
792 }
793
794 proc_file_driver = proc_create_data(PROCFS_DRIVER_DUMP_NAME,
795 PROCFS_DRIVER_DUMP_PERM, proc_dir_driver,
796 &driver_dump_fops, hdd_ctx);
797 if (proc_file_driver == NULL) {
798 remove_proc_entry(PROCFS_DRIVER_DUMP_NAME, proc_dir_driver);
Srinivas Girigowda87150ac2017-03-06 17:48:02 -0800799 pr_debug("Could not initialize /proc/%s\n",
Padma, Santhosh Kumar9aba02f2016-08-11 16:30:25 +0530800 PROCFS_DRIVER_DUMP_NAME);
801 return -ENOMEM;
802 }
803
804 pr_debug("/proc/%s/%s created\n", PROCFS_DRIVER_DUMP_DIR,
805 PROCFS_DRIVER_DUMP_NAME);
806 return 0;
807}
808
809/**
810 * hdd_driver_memdump_procfs_remove() - Remove file/dir under procfs
811 * for driver memory dump
812 *
813 * This function removes file/dir under proc file system that was
814 * processing driver memory dump
815 *
816 * Return: None
817 */
818static void hdd_driver_memdump_procfs_remove(void)
819{
820 remove_proc_entry(PROCFS_DRIVER_DUMP_NAME, proc_dir_driver);
821 pr_debug("/proc/%s/%s removed\n", PROCFS_DRIVER_DUMP_DIR,
822 PROCFS_DRIVER_DUMP_NAME);
823 remove_proc_entry(PROCFS_DRIVER_DUMP_DIR, NULL);
824 pr_debug("/proc/%s removed\n", PROCFS_DRIVER_DUMP_DIR);
825}
826
827/**
828 * hdd_driver_memdump_init() - Intialization function for driver
829 * memory dump feature
830 *
831 * This function creates proc file for driver memdump feature
832 *
833 * Return - 0 on success, error otherwise
834 */
835int hdd_driver_memdump_init(void)
836{
837 int status;
838
839 if (hdd_get_conparam() == QDF_GLOBAL_FTM_MODE) {
840 hdd_err("Not initializing memdump in FTM mode");
841 return -EINVAL;
842 }
843
844 status = hdd_driver_memdump_procfs_init();
845 if (status) {
846 hdd_err("Failed to create proc file");
847 return status;
848 }
849
850 return 0;
851}
852
853/**
854 * hdd_driver_memdump_deinit() - De initialize driver memdump feature
855 *
856 * This function removes proc file created for driver memdump feature.
857 *
858 * Return: None
859 */
860void hdd_driver_memdump_deinit(void)
861{
862 if (hdd_get_conparam() == QDF_GLOBAL_FTM_MODE) {
863 hdd_err("Not deinitializing memdump in FTM mode");
864 return;
865 }
866
867 hdd_driver_memdump_procfs_remove();
868
869 hdd_driver_mem_cleanup();
870}