1 | // SPDX-License-Identifier: ISC |
2 | /* |
3 | * Copyright (c) 2015,2017 Qualcomm Atheros, Inc. |
4 | * Copyright (c) 2018-2019, The Linux Foundation. All rights reserved. |
5 | */ |
6 | |
7 | #include "wil6210.h" |
8 | #include <linux/devcoredump.h> |
9 | |
10 | static int wil_fw_get_crash_dump_bounds(struct wil6210_priv *wil, |
11 | u32 *out_dump_size, u32 *out_host_min) |
12 | { |
13 | int i; |
14 | const struct fw_map *map; |
15 | u32 host_min, host_max, tmp_max; |
16 | |
17 | if (!out_dump_size) |
18 | return -EINVAL; |
19 | |
20 | /* calculate the total size of the unpacked crash dump */ |
21 | BUILD_BUG_ON(ARRAY_SIZE(fw_mapping) == 0); |
22 | map = &fw_mapping[0]; |
23 | host_min = map->host; |
24 | host_max = map->host + (map->to - map->from); |
25 | |
26 | for (i = 1; i < ARRAY_SIZE(fw_mapping); i++) { |
27 | map = &fw_mapping[i]; |
28 | |
29 | if (!map->crash_dump) |
30 | continue; |
31 | |
32 | if (map->host < host_min) |
33 | host_min = map->host; |
34 | |
35 | tmp_max = map->host + (map->to - map->from); |
36 | if (tmp_max > host_max) |
37 | host_max = tmp_max; |
38 | } |
39 | |
40 | *out_dump_size = host_max - host_min; |
41 | if (out_host_min) |
42 | *out_host_min = host_min; |
43 | |
44 | return 0; |
45 | } |
46 | |
47 | int wil_fw_copy_crash_dump(struct wil6210_priv *wil, void *dest, u32 size) |
48 | { |
49 | int i; |
50 | const struct fw_map *map; |
51 | void *data; |
52 | u32 host_min, dump_size, offset, len; |
53 | |
54 | if (wil_fw_get_crash_dump_bounds(wil, out_dump_size: &dump_size, out_host_min: &host_min)) { |
55 | wil_err(wil, "fail to obtain crash dump size\n" ); |
56 | return -EINVAL; |
57 | } |
58 | |
59 | if (dump_size > size) { |
60 | wil_err(wil, "not enough space for dump. Need %d have %d\n" , |
61 | dump_size, size); |
62 | return -EINVAL; |
63 | } |
64 | |
65 | down_write(sem: &wil->mem_lock); |
66 | |
67 | if (test_bit(wil_status_suspending, wil->status) || |
68 | test_bit(wil_status_suspended, wil->status)) { |
69 | wil_err(wil, |
70 | "suspend/resume in progress. cannot copy crash dump\n" ); |
71 | up_write(sem: &wil->mem_lock); |
72 | return -EBUSY; |
73 | } |
74 | |
75 | /* copy to crash dump area */ |
76 | for (i = 0; i < ARRAY_SIZE(fw_mapping); i++) { |
77 | map = &fw_mapping[i]; |
78 | |
79 | if (!map->crash_dump) |
80 | continue; |
81 | |
82 | data = (void * __force)wil->csr + HOSTADDR(map->host); |
83 | len = map->to - map->from; |
84 | offset = map->host - host_min; |
85 | |
86 | wil_dbg_misc(wil, |
87 | "fw_copy_crash_dump: - dump %s, size %d, offset %d\n" , |
88 | fw_mapping[i].name, len, offset); |
89 | |
90 | wil_memcpy_fromio_32(dst: (void * __force)(dest + offset), |
91 | src: (const void __iomem * __force)data, count: len); |
92 | } |
93 | |
94 | up_write(sem: &wil->mem_lock); |
95 | |
96 | return 0; |
97 | } |
98 | |
99 | void wil_fw_core_dump(struct wil6210_priv *wil) |
100 | { |
101 | void *fw_dump_data; |
102 | u32 fw_dump_size; |
103 | |
104 | if (wil_fw_get_crash_dump_bounds(wil, out_dump_size: &fw_dump_size, NULL)) { |
105 | wil_err(wil, "fail to get fw dump size\n" ); |
106 | return; |
107 | } |
108 | |
109 | fw_dump_data = vzalloc(size: fw_dump_size); |
110 | if (!fw_dump_data) |
111 | return; |
112 | |
113 | if (wil_fw_copy_crash_dump(wil, dest: fw_dump_data, size: fw_dump_size)) { |
114 | vfree(addr: fw_dump_data); |
115 | return; |
116 | } |
117 | /* fw_dump_data will be free in device coredump release function |
118 | * after 5 min |
119 | */ |
120 | dev_coredumpv(wil_to_dev(wil), data: fw_dump_data, datalen: fw_dump_size, GFP_KERNEL); |
121 | wil_info(wil, "fw core dumped, size %d bytes\n" , fw_dump_size); |
122 | } |
123 | |