Commit | Line | Data |
---|---|---|
533a23a1 TK |
1 | /**************************************************************************** |
2 | * | |
3 | * Copyright (c) 2014 - 2019 Samsung Electronics Co., Ltd. All rights reserved | |
4 | * | |
5 | ****************************************************************************/ | |
6 | ||
7 | #include <linux/module.h> | |
8 | #include <linux/init.h> | |
9 | #include <linux/firmware.h> | |
10 | #include <linux/slab.h> | |
11 | #include <linux/delay.h> | |
12 | #include <linux/version.h> | |
13 | #include <linux/kmod.h> | |
14 | #include <linux/notifier.h> | |
15 | #include "scsc_mx_impl.h" | |
16 | #include "miframman.h" | |
17 | #include "mifmboxman.h" | |
18 | #include "mxman.h" | |
19 | #include "srvman.h" | |
20 | #include "mxmgmt_transport.h" | |
21 | #include "gdb_transport.h" | |
22 | #include "mxconf.h" | |
23 | #include "fwimage.h" | |
24 | #include "fwhdr.h" | |
25 | #include "mxlog.h" | |
26 | #include "mxlogger.h" | |
27 | #include "fw_panic_record.h" | |
28 | #include "panicmon.h" | |
29 | #include "mxproc.h" | |
30 | #include "mxlog_transport.h" | |
31 | #include "mxsyserr.h" | |
32 | #ifdef CONFIG_SCSC_SMAPPER | |
33 | #include "mifsmapper.h" | |
34 | #endif | |
35 | #ifdef CONFIG_SCSC_QOS | |
36 | #include "mifqos.h" | |
37 | #endif | |
38 | #include "mxfwconfig.h" | |
39 | #include <scsc/kic/slsi_kic_lib.h> | |
40 | #include <scsc/scsc_release.h> | |
41 | #include <scsc/scsc_mx.h> | |
42 | #include <linux/fs.h> | |
43 | #ifdef CONFIG_SCSC_LOG_COLLECTION | |
44 | #include <scsc/scsc_log_collector.h> | |
45 | #endif | |
46 | ||
47 | #include <scsc/scsc_logring.h> | |
48 | #ifdef CONFIG_SCSC_WLBTD | |
49 | #include "scsc_wlbtd.h" | |
50 | #define SCSC_SCRIPT_MOREDUMP "moredump" | |
51 | #define SCSC_SCRIPT_LOGGER_DUMP "mx_logger_dump.sh" | |
52 | static struct work_struct wlbtd_work; | |
53 | #endif | |
54 | ||
55 | #include "scsc_lerna.h" | |
56 | ||
57 | #include <asm/page.h> | |
58 | #include <scsc/api/bt_audio.h> | |
59 | ||
60 | #define STRING_BUFFER_MAX_LENGTH 512 | |
61 | #define NUMBER_OF_STRING_ARGS 5 | |
62 | #define MX_DRAM_SIZE (4 * 1024 * 1024) | |
63 | #define MX_DRAM_SIZE_SECTION_1 (8 * 1024 * 1024) | |
64 | #define MX_DRAM_SIZE_SECTION_2 (8 * 1024 * 1024) | |
65 | #define MX_FW_RUNTIME_LENGTH (1024 * 1024) | |
66 | #define WAIT_FOR_FW_TO_START_DELAY_MS 1000 | |
67 | #define MBOX2_MAGIC_NUMBER 0xbcdeedcb | |
68 | #define MBOX_INDEX_0 0 | |
69 | #define MBOX_INDEX_1 1 | |
70 | #define MBOX_INDEX_2 2 | |
71 | #define MBOX_INDEX_3 3 | |
72 | #ifdef CONFIG_SOC_EXYNOS7570 | |
73 | #define MBOX_INDEX_4 4 | |
74 | #define MBOX_INDEX_5 5 | |
75 | #define MBOX_INDEX_6 6 | |
76 | #define MBOX_INDEX_7 7 | |
77 | #endif | |
78 | ||
79 | #define SCSC_PANIC_ORIGIN_FW (0x0 << 15) | |
80 | #define SCSC_PANIC_ORIGIN_HOST (0x1 << 15) | |
81 | ||
82 | #define SCSC_PANIC_TECH_WLAN (0x0 << 13) | |
83 | #define SCSC_PANIC_TECH_CORE (0x1 << 13) | |
84 | #define SCSC_PANIC_TECH_BT (0x2 << 13) | |
85 | #define SCSC_PANIC_TECH_UNSP (0x3 << 13) | |
86 | ||
87 | #define SCSC_PANIC_CODE_MASK 0xFFFF | |
88 | #define SCSC_PANIC_ORIGIN_MASK 0x8000 | |
89 | #define SCSC_PANIC_TECH_MASK 0x6000 | |
90 | #define SCSC_PANIC_SUBCODE_MASK_LEGACY 0x0FFF | |
91 | #define SCSC_PANIC_SUBCODE_MASK 0x7FFF | |
92 | ||
93 | #define SCSC_R4_V2_MINOR_52 52 | |
94 | #define SCSC_R4_V2_MINOR_53 53 | |
95 | ||
96 | #define MM_HALT_RSP_TIMEOUT_MS 100 | |
97 | ||
98 | static char panic_record_dump[PANIC_RECORD_DUMP_BUFFER_SZ]; | |
99 | static BLOCKING_NOTIFIER_HEAD(firmware_chain); | |
100 | ||
101 | /** | |
102 | * This will be returned as fw version ONLY if Maxwell | |
103 | * was never found or was unloaded. | |
104 | */ | |
105 | static char saved_fw_build_id[FW_BUILD_ID_SZ] = "Maxwell WLBT unavailable"; | |
106 | ||
107 | static bool allow_unidentified_firmware; | |
108 | module_param(allow_unidentified_firmware, bool, S_IRUGO | S_IWUSR); | |
109 | MODULE_PARM_DESC(allow_unidentified_firmware, "Allow unidentified firmware"); | |
110 | ||
111 | static bool skip_header; | |
112 | module_param(skip_header, bool, S_IRUGO | S_IWUSR); | |
113 | MODULE_PARM_DESC(skip_header, "Skip header, assuming unidentified firmware"); | |
114 | ||
115 | static bool crc_check_allow_none = true; | |
116 | module_param(crc_check_allow_none, bool, S_IRUGO | S_IWUSR); | |
117 | MODULE_PARM_DESC(crc_check_allow_none, "Allow skipping firmware CRC checks if CRC is not present"); | |
118 | ||
119 | static int crc_check_period_ms = 30000; | |
120 | module_param(crc_check_period_ms, int, S_IRUGO | S_IWUSR); | |
121 | MODULE_PARM_DESC(crc_check_period_ms, "Time period for checking the firmware CRCs"); | |
122 | ||
123 | static ulong mm_completion_timeout_ms = 2000; | |
124 | module_param(mm_completion_timeout_ms, ulong, S_IRUGO | S_IWUSR); | |
125 | MODULE_PARM_DESC(mm_completion_timeout_ms, "Timeout wait_for_mm_msg_start_ind (ms) - default 1000. 0 = infinite"); | |
126 | ||
127 | static bool skip_mbox0_check; | |
128 | module_param(skip_mbox0_check, bool, S_IRUGO | S_IWUSR); | |
129 | MODULE_PARM_DESC(skip_mbox0_check, "Allow skipping firmware mbox0 signature check"); | |
130 | ||
131 | static uint firmware_startup_flags; | |
132 | module_param(firmware_startup_flags, uint, S_IRUGO | S_IWUSR); | |
133 | MODULE_PARM_DESC(firmware_startup_flags, "0 = Proceed as normal (default); Bit 0 = 1 - spin at start of CRT0; Other bits reserved = 0"); | |
134 | ||
135 | #ifdef CONFIG_SCSC_CHV_SUPPORT | |
136 | /* First arg controls chv function */ | |
137 | int chv_run; | |
138 | module_param(chv_run, int, S_IRUGO | S_IWUSR); | |
139 | MODULE_PARM_DESC(chv_run, "Run chv f/w: 0 = feature disabled, 1 = for continuous checking, 2 = 1 shot, anything else, undefined"); | |
140 | ||
141 | /* Optional array of args for firmware to interpret when chv_run = 1 */ | |
142 | static unsigned int chv_argv[32]; | |
143 | static int chv_argc; | |
144 | ||
145 | module_param_array(chv_argv, uint, &chv_argc, S_IRUGO | S_IWUSR); | |
146 | MODULE_PARM_DESC(chv_argv, "Array of up to 32 x u32 args for the CHV firmware when chv_run = 1"); | |
147 | #endif | |
148 | ||
149 | static bool disable_auto_coredump; | |
150 | module_param(disable_auto_coredump, bool, S_IRUGO | S_IWUSR); | |
151 | MODULE_PARM_DESC(disable_auto_coredump, "Disable driver automatic coredump"); | |
152 | ||
153 | static bool disable_error_handling; | |
154 | module_param(disable_error_handling, bool, S_IRUGO | S_IWUSR); | |
155 | MODULE_PARM_DESC(disable_error_handling, "Disable error handling"); | |
156 | ||
157 | int disable_recovery_handling = 1; /* MEMDUMP_FILE_FOR_RECOVERY : for /sys/wifi/memdump */ | |
158 | module_param(disable_recovery_handling, int, S_IRUGO | S_IWUSR); | |
159 | MODULE_PARM_DESC(disable_recovery_handling, "Disable recovery handling"); | |
160 | static bool disable_recovery_from_memdump_file = true; | |
161 | static int memdump = -1; | |
162 | static bool disable_recovery_until_reboot; | |
163 | ||
164 | static uint panic_record_delay = 1; | |
165 | module_param(panic_record_delay, uint, S_IRUGO | S_IWUSR); | |
166 | MODULE_PARM_DESC(panic_record_delay, "Delay in ms before accessing the panic record"); | |
167 | ||
168 | static bool disable_logger = true; | |
169 | module_param(disable_logger, bool, S_IRUGO | S_IWUSR); | |
170 | MODULE_PARM_DESC(disable_logger, "Disable launch of user space logger"); | |
171 | ||
172 | /* | |
173 | * shared between this module and mgt.c as this is the kobject referring to | |
174 | * /sys/wifi directory. Core driver is called 1st we create the directory | |
175 | * here and share the kobject, so in mgt.c wifi driver can create | |
176 | * /sys/wif/mac_addr using sysfs_create_file api using the kobject | |
177 | * | |
178 | * If both modules tried to create the dir we were getting kernel panic | |
179 | * failure due to kobject associated with dir already existed | |
180 | */ | |
181 | static struct kobject *wifi_kobj_ref; | |
182 | static int refcount; | |
183 | static ssize_t sysfs_show_memdump(struct kobject *kobj, struct kobj_attribute *attr, | |
184 | char *buf); | |
185 | static ssize_t sysfs_store_memdump(struct kobject *kobj, struct kobj_attribute *attr, | |
186 | const char *buf, size_t count); | |
187 | static struct kobj_attribute memdump_attr = | |
188 | __ATTR(memdump, 0660, sysfs_show_memdump, sysfs_store_memdump); | |
189 | ||
190 | ||
191 | #ifdef CONFIG_SCSC_LOG_COLLECTION | |
192 | static int mxman_minimoredump_collect(struct scsc_log_collector_client *collect_client, size_t size) | |
193 | { | |
194 | int ret = 0; | |
195 | struct mxman *mxman = (struct mxman *) collect_client->prv; | |
196 | ||
197 | if (!mxman || !mxman->start_dram) | |
198 | return ret; | |
199 | ||
200 | SCSC_TAG_INFO(MXMAN, "Collecting Minimoredump runtime_length %d fw_image_size %d\n", | |
201 | mxman->fwhdr.fw_runtime_length, mxman->fw_image_size); | |
202 | /* collect RAM sections of FW */ | |
203 | ret = scsc_log_collector_write(mxman->start_dram + mxman->fw_image_size, | |
204 | mxman->fwhdr.fw_runtime_length - mxman->fw_image_size, 1); | |
205 | ||
206 | return ret; | |
207 | } | |
208 | ||
209 | struct scsc_log_collector_client mini_moredump_client = { | |
210 | .name = "minimoredump", | |
211 | .type = SCSC_LOG_MINIMOREDUMP, | |
212 | .collect_init = NULL, | |
213 | .collect = mxman_minimoredump_collect, | |
214 | .collect_end = NULL, | |
215 | .prv = NULL, | |
216 | }; | |
217 | #endif | |
218 | ||
219 | /* Retrieve memdump in sysfs global */ | |
220 | static ssize_t sysfs_show_memdump(struct kobject *kobj, | |
221 | struct kobj_attribute *attr, | |
222 | char *buf) | |
223 | { | |
224 | return sprintf(buf, "%d\n", memdump); | |
225 | } | |
226 | ||
227 | /* Update memdump in sysfs global */ | |
228 | static ssize_t sysfs_store_memdump(struct kobject *kobj, | |
229 | struct kobj_attribute *attr, | |
230 | const char *buf, | |
231 | size_t count) | |
232 | { | |
233 | int r; | |
234 | ||
235 | r = kstrtoint(buf, 10, &memdump); | |
236 | if (r < 0) | |
237 | memdump = -1; | |
238 | ||
239 | switch (memdump) { | |
240 | case 0: | |
241 | case 2: | |
242 | disable_recovery_from_memdump_file = false; | |
243 | break; | |
244 | case 3: | |
245 | default: | |
246 | disable_recovery_from_memdump_file = true; | |
247 | break; | |
248 | } | |
249 | ||
250 | SCSC_TAG_INFO(MXMAN, "memdump: %d\n", memdump); | |
251 | ||
252 | return (r == 0) ? count : 0; | |
253 | } | |
254 | ||
255 | struct kobject *mxman_wifi_kobject_ref_get(void) | |
256 | { | |
257 | if (refcount++ == 0) { | |
258 | /* Create sysfs directory /sys/wifi */ | |
259 | wifi_kobj_ref = kobject_create_and_add("wifi", NULL); | |
260 | kobject_get(wifi_kobj_ref); | |
261 | kobject_uevent(wifi_kobj_ref, KOBJ_ADD); | |
262 | SCSC_TAG_INFO(MXMAN, "wifi_kobj_ref: 0x%p\n", wifi_kobj_ref); | |
263 | WARN_ON(refcount == 0); | |
264 | } | |
265 | return wifi_kobj_ref; | |
266 | } | |
267 | EXPORT_SYMBOL(mxman_wifi_kobject_ref_get); | |
268 | ||
269 | void mxman_wifi_kobject_ref_put(void) | |
270 | { | |
271 | if (--refcount == 0) { | |
272 | kobject_put(wifi_kobj_ref); | |
273 | kobject_uevent(wifi_kobj_ref, KOBJ_REMOVE); | |
274 | wifi_kobj_ref = NULL; | |
275 | WARN_ON(refcount < 0); | |
276 | } | |
277 | } | |
278 | EXPORT_SYMBOL(mxman_wifi_kobject_ref_put); | |
279 | ||
280 | /* Register memdump override */ | |
281 | void mxman_create_sysfs_memdump(void) | |
282 | { | |
283 | int r; | |
284 | struct kobject *kobj_ref = mxman_wifi_kobject_ref_get(); | |
285 | ||
286 | SCSC_TAG_INFO(MXMAN, "kobj_ref: 0x%p\n", kobj_ref); | |
287 | ||
288 | if (kobj_ref) { | |
289 | /* Create sysfs file /sys/wifi/memdump */ | |
290 | r = sysfs_create_file(kobj_ref, &memdump_attr.attr); | |
291 | if (r) { | |
292 | /* Failed, so clean up dir */ | |
293 | SCSC_TAG_ERR(MXMAN, "Can't create /sys/wifi/memdump\n"); | |
294 | mxman_wifi_kobject_ref_put(); | |
295 | return; | |
296 | } | |
297 | } else { | |
298 | SCSC_TAG_ERR(MXMAN, "failed to create /sys/wifi directory"); | |
299 | } | |
300 | } | |
301 | ||
302 | /* Unregister memdump override */ | |
303 | void mxman_destroy_sysfs_memdump(void) | |
304 | { | |
305 | if (!wifi_kobj_ref) | |
306 | return; | |
307 | ||
308 | /* Destroy /sys/wifi/memdump file */ | |
309 | sysfs_remove_file(wifi_kobj_ref, &memdump_attr.attr); | |
310 | ||
311 | /* Destroy /sys/wifi virtual dir */ | |
312 | mxman_wifi_kobject_ref_put(); | |
313 | } | |
314 | ||
315 | /* Track when WLBT reset fails to allow debug */ | |
316 | bool reset_failed; | |
317 | static u64 reset_failed_time; | |
318 | ||
319 | /* Status of FM driver request, which persists beyond the lifecyle | |
320 | * of the scsx_mx driver. | |
321 | */ | |
322 | #ifdef CONFIG_SCSC_FM | |
323 | static u32 is_fm_on; | |
324 | #endif | |
325 | ||
326 | static int firmware_runtime_flags; | |
327 | static int syserr_command; | |
328 | /** | |
329 | * This mxman reference is initialized/nullified via mxman_init/deinit | |
330 | * called by scsc_mx_create/destroy on module probe/remove. | |
331 | */ | |
332 | static struct mxman *active_mxman; | |
333 | static bool send_fw_config_to_active_mxman(uint32_t fw_runtime_flags); | |
334 | static bool send_syserr_cmd_to_active_mxman(u32 syserr_cmd); | |
335 | ||
336 | static int fw_runtime_flags_setter(const char *val, const struct kernel_param *kp) | |
337 | { | |
338 | int ret = -EINVAL; | |
339 | uint32_t fw_runtime_flags = 0; | |
340 | ||
341 | if (!val) | |
342 | return ret; | |
343 | ret = kstrtouint(val, 10, &fw_runtime_flags); | |
344 | if (!ret) { | |
345 | if (send_fw_config_to_active_mxman(fw_runtime_flags)) | |
346 | firmware_runtime_flags = fw_runtime_flags; | |
347 | else | |
348 | ret = -EINVAL; | |
349 | } | |
350 | return ret; | |
351 | } | |
352 | ||
353 | /** | |
354 | * We don't bother to keep an updated copy of the runtime flags effectively | |
355 | * currently set into FW...we should add a new message answer handling both in | |
356 | * Kenrel and FW side to be sure and this is just to easy debug at the end. | |
357 | */ | |
358 | static struct kernel_param_ops fw_runtime_kops = { | |
359 | .set = fw_runtime_flags_setter, | |
360 | .get = NULL | |
361 | }; | |
362 | ||
363 | module_param_cb(firmware_runtime_flags, &fw_runtime_kops, NULL, 0200); | |
364 | MODULE_PARM_DESC(firmware_runtime_flags, | |
365 | "0 = Proceed as normal (default); nnn = Provides FW runtime flags bitmask: unknown bits will be ignored."); | |
366 | ||
367 | static int syserr_setter(const char *val, const struct kernel_param *kp) | |
368 | { | |
369 | int ret = -EINVAL; | |
370 | u32 syserr_cmd = 0; | |
371 | ||
372 | if (!val) | |
373 | return ret; | |
374 | ret = kstrtouint(val, 10, &syserr_cmd); | |
375 | if (!ret) { | |
376 | u8 sub_system = (u8)(syserr_cmd / 10); | |
377 | u8 level = (u8)(syserr_cmd % 10); | |
378 | ||
379 | if (((sub_system > 2) && (sub_system < 8)) || (sub_system > 8) || (level > 7)) | |
380 | ret = -EINVAL; | |
381 | else if (send_syserr_cmd_to_active_mxman(syserr_cmd)) | |
382 | syserr_command = syserr_cmd; | |
383 | else | |
384 | ret = -EINVAL; | |
385 | } | |
386 | return ret; | |
387 | } | |
388 | ||
389 | static struct kernel_param_ops syserr_kops = { | |
390 | .set = syserr_setter, | |
391 | .get = NULL | |
392 | }; | |
393 | ||
394 | module_param_cb(syserr_command, &syserr_kops, NULL, 0200); | |
395 | MODULE_PARM_DESC(syserr_command, | |
396 | "Decimal XY - Trigger Type X(0,1,2,8), Level Y(1-7). Some combinations not supported"); | |
397 | ||
398 | /** | |
399 | * Maxwell Agent Management Messages. | |
400 | * | |
401 | * TODO: common defn with firmware, generated. | |
402 | * | |
403 | * The numbers here *must* match the firmware! | |
404 | */ | |
405 | enum { | |
406 | MM_START_IND = 0, | |
407 | MM_HALT_REQ = 1, | |
408 | MM_FORCE_PANIC = 2, | |
409 | MM_HOST_SUSPEND = 3, | |
410 | MM_HOST_RESUME = 4, | |
411 | MM_FW_CONFIG = 5, | |
412 | MM_HALT_RSP = 6, | |
413 | MM_FM_RADIO_CONFIG = 7, | |
414 | MM_LERNA_CONFIG = 8, | |
415 | MM_SYSERR_IND = 9, | |
416 | MM_SYSERR_CMD = 10 | |
417 | } ma_msg; | |
418 | ||
419 | /** | |
420 | * Format of the Maxwell agent messages | |
421 | * on the Maxwell management transport stream. | |
422 | */ | |
423 | struct ma_msg_packet { | |
424 | ||
425 | uint8_t ma_msg; /* Message from ma_msg enum */ | |
426 | uint32_t arg; /* Optional arg set by f/w in some to-host messages */ | |
427 | } __packed; | |
428 | ||
429 | /** | |
430 | * Special case Maxwell management, carrying FM radio configuration structure | |
431 | */ | |
432 | struct ma_msg_packet_fm_radio_config { | |
433 | ||
434 | uint8_t ma_msg; /* Message from ma_msg enum */ | |
435 | struct wlbt_fm_params fm_params; /* FM Radio parameters */ | |
436 | } __packed; | |
437 | ||
438 | static bool send_fw_config_to_active_mxman(uint32_t fw_runtime_flags) | |
439 | { | |
440 | bool ret = false; | |
441 | struct srvman *srvman = NULL; | |
442 | ||
443 | SCSC_TAG_INFO(MXMAN, "\n"); | |
444 | if (!active_mxman) { | |
445 | SCSC_TAG_ERR(MXMAN, "Active MXMAN NOT FOUND...cannot send running FW config.\n"); | |
446 | return ret; | |
447 | } | |
448 | ||
449 | mutex_lock(&active_mxman->mxman_mutex); | |
450 | srvman = scsc_mx_get_srvman(active_mxman->mx); | |
451 | if (srvman && srvman->error) { | |
452 | mutex_unlock(&active_mxman->mxman_mutex); | |
453 | SCSC_TAG_INFO(MXMAN, "Called during error - ignore\n"); | |
454 | return ret; | |
455 | } | |
456 | ||
457 | if (active_mxman->mxman_state == MXMAN_STATE_STARTED) { | |
458 | struct ma_msg_packet message = { .ma_msg = MM_FW_CONFIG, | |
459 | .arg = fw_runtime_flags }; | |
460 | ||
461 | SCSC_TAG_INFO(MXMAN, "MM_FW_CONFIG - firmware_runtime_flags:%d\n", message.arg); | |
462 | mxmgmt_transport_send(scsc_mx_get_mxmgmt_transport(active_mxman->mx), | |
463 | MMTRANS_CHAN_ID_MAXWELL_MANAGEMENT, &message, | |
464 | sizeof(message)); | |
465 | ret = true; | |
466 | } else { | |
467 | SCSC_TAG_INFO(MXMAN, "MXMAN is NOT STARTED...cannot send MM_FW_CONFIG msg.\n"); | |
468 | } | |
469 | mutex_unlock(&active_mxman->mxman_mutex); | |
470 | ||
471 | return ret; | |
472 | } | |
473 | ||
474 | static bool send_syserr_cmd_to_active_mxman(u32 syserr_cmd) | |
475 | { | |
476 | bool ret = false; | |
477 | struct srvman *srvman = NULL; | |
478 | ||
479 | SCSC_TAG_INFO(MXMAN, "\n"); | |
480 | if (!active_mxman) { | |
481 | SCSC_TAG_ERR(MXMAN, "Active MXMAN NOT FOUND...cannot send running FW config.\n"); | |
482 | return ret; | |
483 | } | |
484 | ||
485 | mutex_lock(&active_mxman->mxman_mutex); | |
486 | srvman = scsc_mx_get_srvman(active_mxman->mx); | |
487 | if (srvman && srvman->error) { | |
488 | mutex_unlock(&active_mxman->mxman_mutex); | |
489 | SCSC_TAG_INFO(MXMAN, "Called during error - ignore\n"); | |
490 | return ret; | |
491 | } | |
492 | ||
493 | if (active_mxman->mxman_state == MXMAN_STATE_STARTED) { | |
494 | struct ma_msg_packet message = { .ma_msg = MM_SYSERR_CMD, | |
495 | .arg = syserr_cmd}; | |
496 | ||
497 | SCSC_TAG_INFO(MXMAN, "MM_SYSERR_CMD - Args %02d\n", message.arg); | |
498 | mxmgmt_transport_send(scsc_mx_get_mxmgmt_transport(active_mxman->mx), | |
499 | MMTRANS_CHAN_ID_MAXWELL_MANAGEMENT, &message, | |
500 | sizeof(message)); | |
501 | ret = true; | |
502 | } else { | |
503 | SCSC_TAG_INFO(MXMAN, "MXMAN is NOT STARTED...cannot send MM_SYSERR_CMD msg.\n"); | |
504 | } | |
505 | mutex_unlock(&active_mxman->mxman_mutex); | |
506 | ||
507 | return ret; | |
508 | } | |
509 | ||
510 | #ifdef CONFIG_SCSC_FM | |
511 | static bool send_fm_params_to_active_mxman(struct wlbt_fm_params *params) | |
512 | { | |
513 | bool ret = false; | |
514 | struct srvman *srvman = NULL; | |
515 | ||
516 | SCSC_TAG_INFO(MXMAN, "\n"); | |
517 | if (!active_mxman) { | |
518 | SCSC_TAG_ERR(MXMAN, "Active MXMAN NOT FOUND...cannot send FM params\n"); | |
519 | return false; | |
520 | } | |
521 | ||
522 | mutex_lock(&active_mxman->mxman_mutex); | |
523 | srvman = scsc_mx_get_srvman(active_mxman->mx); | |
524 | if (srvman && srvman->error) { | |
525 | mutex_unlock(&active_mxman->mxman_mutex); | |
526 | SCSC_TAG_INFO(MXMAN, "Called during error - ignore\n"); | |
527 | return false; | |
528 | } | |
529 | ||
530 | if (active_mxman->mxman_state == MXMAN_STATE_STARTED) { | |
531 | struct ma_msg_packet_fm_radio_config message = { .ma_msg = MM_FM_RADIO_CONFIG, | |
532 | .fm_params = *params }; | |
533 | ||
534 | SCSC_TAG_INFO(MXMAN, "MM_FM_RADIO_CONFIG\n"); | |
535 | mxmgmt_transport_send(scsc_mx_get_mxmgmt_transport(active_mxman->mx), | |
536 | MMTRANS_CHAN_ID_MAXWELL_MANAGEMENT, &message, | |
537 | sizeof(message)); | |
538 | ||
539 | ret = true; /* Success */ | |
540 | } else | |
541 | SCSC_TAG_INFO(MXMAN, "MXMAN is NOT STARTED...cannot send MM_FM_RADIO_CONFIG msg.\n"); | |
542 | ||
543 | mutex_unlock(&active_mxman->mxman_mutex); | |
544 | ||
545 | return ret; | |
546 | } | |
547 | #endif | |
548 | ||
549 | static void mxman_stop(struct mxman *mxman); | |
550 | static void print_mailboxes(struct mxman *mxman); | |
551 | #ifdef CONFIG_SCSC_WLBTD | |
552 | static int _mx_exec(char *prog, int wait_exec) __attribute__((unused)); | |
553 | #else | |
554 | static int _mx_exec(char *prog, int wait_exec); | |
555 | #endif | |
556 | static int wait_for_mm_msg(struct mxman *mxman, struct completion *mm_msg_completion, ulong timeout_ms) | |
557 | { | |
558 | int r; | |
559 | ||
560 | (void)mxman; /* unused */ | |
561 | ||
562 | if (timeout_ms == 0) { | |
563 | /* Zero implies infinite wait */ | |
564 | r = wait_for_completion_interruptible(mm_msg_completion); | |
565 | /* r = -ERESTARTSYS if interrupted, 0 if completed */ | |
566 | return r; | |
567 | } | |
568 | r = wait_for_completion_timeout(mm_msg_completion, msecs_to_jiffies(timeout_ms)); | |
569 | if (r == 0) { | |
570 | SCSC_TAG_ERR(MXMAN, "timeout\n"); | |
571 | return -ETIMEDOUT; | |
572 | } | |
573 | ||
574 | return 0; | |
575 | } | |
576 | ||
577 | static int wait_for_mm_msg_start_ind(struct mxman *mxman) | |
578 | { | |
579 | return wait_for_mm_msg(mxman, &mxman->mm_msg_start_ind_completion, mm_completion_timeout_ms); | |
580 | } | |
581 | ||
582 | static int wait_for_mm_msg_halt_rsp(struct mxman *mxman) | |
583 | { | |
584 | int r; | |
585 | (void)mxman; /* unused */ | |
586 | ||
587 | if (MM_HALT_RSP_TIMEOUT_MS == 0) { | |
588 | /* Zero implies infinite wait */ | |
589 | r = wait_for_completion_interruptible(&mxman->mm_msg_halt_rsp_completion); | |
590 | /* r = -ERESTARTSYS if interrupted, 0 if completed */ | |
591 | return r; | |
592 | } | |
593 | ||
594 | r = wait_for_completion_timeout(&mxman->mm_msg_halt_rsp_completion, msecs_to_jiffies(MM_HALT_RSP_TIMEOUT_MS)); | |
595 | if (r) | |
596 | SCSC_TAG_INFO(MXMAN, "Received MM_HALT_RSP from firmware"); | |
597 | ||
598 | return r; | |
599 | } | |
600 | ||
601 | #ifndef CONFIG_SCSC_WLBTD | |
602 | static int coredump_helper(void) | |
603 | { | |
604 | int r; | |
605 | int i; | |
606 | static char mdbin[128]; | |
607 | ||
608 | /* Determine path to moredump helper script */ | |
609 | r = mx140_exe_path(NULL, mdbin, sizeof(mdbin), "moredump"); | |
610 | if (r) { | |
611 | SCSC_TAG_ERR(MXMAN, "moredump path error\n"); | |
612 | return r; | |
613 | } | |
614 | ||
615 | for (i = 0; i < 20; i++) { | |
616 | r = _mx_exec(mdbin, UMH_WAIT_PROC); | |
617 | if (r != -EBUSY) | |
618 | break; | |
619 | /* If the usermode helper fails with -EBUSY, the userspace is | |
620 | * likely still frozen from suspend. Back off and retry. | |
621 | */ | |
622 | SCSC_TAG_INFO(MXMAN, "waiting for userspace to thaw...\n"); | |
623 | msleep(1000); | |
624 | } | |
625 | ||
626 | /* Application return codes are in the MSB */ | |
627 | if (r > 0xffL) | |
628 | SCSC_TAG_INFO(MXMAN, "moredump.bin exit(%ld), check syslog\n", (r & 0xff00L) >> 8); | |
629 | ||
630 | return r; | |
631 | } | |
632 | #endif | |
633 | ||
634 | static int send_mm_msg_stop_blocking(struct mxman *mxman) | |
635 | { | |
636 | int r; | |
637 | #ifdef CONFIG_SCSC_FM | |
638 | struct ma_msg_packet message = { .ma_msg = MM_HALT_REQ, | |
639 | .arg = mxman->on_halt_ldos_on }; | |
640 | #else | |
641 | struct ma_msg_packet message = { .ma_msg = MM_HALT_REQ }; | |
642 | #endif | |
643 | mxmgmt_transport_send(scsc_mx_get_mxmgmt_transport(mxman->mx), MMTRANS_CHAN_ID_MAXWELL_MANAGEMENT, &message, sizeof(message)); | |
644 | ||
645 | r = wait_for_mm_msg_halt_rsp(mxman); | |
646 | if (r) { | |
647 | /* | |
648 | * MM_MSG_HALT_RSP is not implemented in all versions of firmware, so don't treat it's non-arrival | |
649 | * as an error | |
650 | */ | |
651 | SCSC_TAG_INFO(MXMAN, "wait_for_MM_HALT_RSP completed"); | |
652 | } | |
653 | ||
654 | return 0; | |
655 | } | |
656 | ||
657 | static char *chip_version(u32 rf_hw_ver) | |
658 | { | |
659 | switch (rf_hw_ver & 0x00ff) { | |
660 | default: | |
661 | break; | |
662 | case 0x00b0: | |
663 | if ((rf_hw_ver & 0xff00) > 0x1000) | |
664 | return "S610/S611"; | |
665 | else | |
666 | return "S610"; | |
667 | case 0x00b1: | |
668 | return "S612"; | |
669 | case 0x00b2: | |
670 | return "S620"; | |
671 | case 0x0000: | |
672 | #ifndef CONFIG_SOC_EXYNOS9610 | |
673 | return "Error: check if RF chip is present"; | |
674 | #else | |
675 | return "Unknown"; | |
676 | #endif | |
677 | } | |
678 | return "Unknown"; | |
679 | } | |
680 | ||
681 | /* | |
682 | * This function is used in this file and in mxproc.c to generate consistent | |
683 | * RF CHIP VERSION string for logging on console and for storing the same | |
684 | * in proc/drivers/mxman_info/rf_chip_version file. | |
685 | */ | |
686 | int mxman_print_rf_hw_version(struct mxman *mxman, char *buf, const size_t bufsz) | |
687 | { | |
688 | int r; | |
689 | ||
690 | r = snprintf(buf, bufsz, "RF_CHIP_VERSION: 0x%04x: %s (0x%02x), EVT%x.%x\n", | |
691 | mxman->rf_hw_ver, | |
692 | chip_version(mxman->rf_hw_ver), (mxman->rf_hw_ver & 0x00ff), | |
693 | ((mxman->rf_hw_ver >> 12) & 0xfU), ((mxman->rf_hw_ver >> 8) & 0xfU)); | |
694 | ||
695 | return r; | |
696 | } | |
697 | ||
698 | static void mxman_print_versions(struct mxman *mxman) | |
699 | { | |
700 | char buf[80]; | |
701 | ||
702 | memset(buf, '\0', sizeof(buf)); | |
703 | ||
704 | (void)mxman_print_rf_hw_version(mxman, buf, sizeof(buf)); | |
705 | ||
706 | SCSC_TAG_INFO(MXMAN, "%s", buf); | |
707 | SCSC_TAG_INFO(MXMAN, "WLBT FW: %s\n", mxman->fw_build_id); | |
708 | SCSC_TAG_INFO(MXMAN, "WLBT Driver: %d.%d.%d.%d\n", | |
709 | SCSC_RELEASE_PRODUCT, SCSC_RELEASE_ITERATION, SCSC_RELEASE_CANDIDATE, SCSC_RELEASE_POINT); | |
710 | #ifdef CONFIG_SCSC_WLBTD | |
711 | scsc_wlbtd_get_and_print_build_type(); | |
712 | #endif | |
713 | } | |
714 | ||
715 | /** Receive handler for messages from the FW along the maxwell management transport */ | |
716 | static void mxman_message_handler(const void *message, void *data) | |
717 | { | |
718 | struct mxman *mxman = (struct mxman *)data; | |
719 | ||
720 | /* Forward the message to the applicable service to deal with */ | |
721 | const struct ma_msg_packet *msg = message; | |
722 | ||
723 | switch (msg->ma_msg) { | |
724 | case MM_START_IND: | |
725 | /* The arg can be used to determine the WLBT/S610 hardware revision */ | |
726 | SCSC_TAG_INFO(MXMAN, "Received MM_START_IND message from the firmware, arg=0x%04x\n", msg->arg); | |
727 | mxman->rf_hw_ver = msg->arg; | |
728 | mxman_print_versions(mxman); | |
729 | atomic_inc(&mxman->boot_count); | |
730 | complete(&mxman->mm_msg_start_ind_completion); | |
731 | break; | |
732 | case MM_HALT_RSP: | |
733 | complete(&mxman->mm_msg_halt_rsp_completion); | |
734 | SCSC_TAG_INFO(MXMAN, "Received MM_HALT_RSP message from the firmware\n"); | |
735 | break; | |
736 | case MM_LERNA_CONFIG: | |
737 | /* Message response to a firmware configuration query. */ | |
738 | SCSC_TAG_INFO(MXMAN, "Received MM_LERNA_CONFIG message from firmware\n"); | |
739 | scsc_lerna_response(message); | |
740 | break; | |
741 | case MM_SYSERR_IND: | |
742 | /* System Error report from firmware */ | |
743 | SCSC_TAG_INFO(MXMAN, "Received MM_SYSERR_IND message from firmware\n"); | |
744 | mx_syserr_handler(mxman, message); | |
745 | break; | |
746 | default: | |
747 | /* HERE: Unknown message, raise fault */ | |
748 | SCSC_TAG_WARNING(MXMAN, "Received unknown message from the firmware: msg->ma_msg=%d\n", msg->ma_msg); | |
749 | break; | |
750 | } | |
751 | } | |
752 | ||
753 | /* | |
754 | * This function calulates and checks two or three (depending on crc32_over_binary flag) | |
755 | * crc32 values in the firmware header. The function will check crc32 over the firmware binary | |
756 | * (i.e. everything in the file following the header) only if the crc32_over_binary is set to 'true'. | |
757 | * This includes initialised data regions so it can be used to check when loading but will not be | |
758 | * meaningful once execution starts. | |
759 | */ | |
760 | static int do_fw_crc32_checks(char *fw, u32 fw_image_size, struct fwhdr *fwhdr, bool crc32_over_binary) | |
761 | { | |
762 | int r; | |
763 | ||
764 | if ((fwhdr->fw_crc32 == 0 || fwhdr->header_crc32 == 0 || fwhdr->const_crc32 == 0) && crc_check_allow_none == 0) { | |
765 | SCSC_TAG_ERR(MXMAN, "error: CRC is missing fw_crc32=%d header_crc32=%d crc_check_allow_none=%d\n", | |
766 | fwhdr->fw_crc32, fwhdr->header_crc32, crc_check_allow_none); | |
767 | return -EINVAL; | |
768 | } | |
769 | ||
770 | if (fwhdr->header_crc32 == 0 && crc_check_allow_none == 1) { | |
771 | SCSC_TAG_INFO(MXMAN, "Skipping CRC check header_crc32=%d crc_check_allow_none=%d\n", | |
772 | fwhdr->header_crc32, crc_check_allow_none); | |
773 | } else { | |
774 | /* | |
775 | * CRC-32-IEEE of all preceding header fields (including other CRCs). | |
776 | * Always the last word in the header. | |
777 | */ | |
778 | r = fwimage_check_fw_header_crc(fw, fwhdr->hdr_length, fwhdr->header_crc32); | |
779 | if (r) { | |
780 | SCSC_TAG_ERR(MXMAN, "fwimage_check_fw_header_crc() failed\n"); | |
781 | return r; | |
782 | } | |
783 | } | |
784 | ||
785 | if (fwhdr->const_crc32 == 0 && crc_check_allow_none == 1) { | |
786 | SCSC_TAG_INFO(MXMAN, "Skipping CRC check const_crc32=%d crc_check_allow_none=%d\n", | |
787 | fwhdr->const_crc32, crc_check_allow_none); | |
788 | } else { | |
789 | /* | |
790 | * CRC-32-IEEE over the constant sections grouped together at start of firmware binary. | |
791 | * This CRC should remain valid during execution. It can be used by run-time checker on | |
792 | * host to detect firmware corruption (not all memory masters are subject to MPUs). | |
793 | */ | |
794 | r = fwimage_check_fw_const_section_crc(fw, fwhdr->const_crc32, fwhdr->const_fw_length, fwhdr->hdr_length); | |
795 | if (r) { | |
796 | SCSC_TAG_ERR(MXMAN, "fwimage_check_fw_const_section_crc() failed\n"); | |
797 | return r; | |
798 | } | |
799 | } | |
800 | ||
801 | if (crc32_over_binary) { | |
802 | if (fwhdr->fw_crc32 == 0 && crc_check_allow_none == 1) | |
803 | SCSC_TAG_INFO(MXMAN, "Skipping CRC check fw_crc32=%d crc_check_allow_none=%d\n", | |
804 | fwhdr->fw_crc32, crc_check_allow_none); | |
805 | else { | |
806 | /* | |
807 | * CRC-32-IEEE over the firmware binary (i.e. everything | |
808 | * in the file following this header). | |
809 | * This includes initialised data regions so it can be used to | |
810 | * check when loading but will not be meaningful once execution starts. | |
811 | */ | |
812 | r = fwimage_check_fw_crc(fw, fw_image_size, fwhdr->hdr_length, fwhdr->fw_crc32); | |
813 | if (r) { | |
814 | SCSC_TAG_ERR(MXMAN, "fwimage_check_fw_crc() failed\n"); | |
815 | return r; | |
816 | } | |
817 | } | |
818 | } | |
819 | ||
820 | return 0; | |
821 | } | |
822 | ||
823 | ||
824 | static void fw_crc_wq_start(struct mxman *mxman) | |
825 | { | |
826 | if (mxman->check_crc && crc_check_period_ms) | |
827 | queue_delayed_work(mxman->fw_crc_wq, &mxman->fw_crc_work, msecs_to_jiffies(crc_check_period_ms)); | |
828 | } | |
829 | ||
830 | ||
831 | static void fw_crc_work_func(struct work_struct *work) | |
832 | { | |
833 | int r; | |
834 | struct mxman *mxman = container_of((struct delayed_work *)work, struct mxman, fw_crc_work); | |
835 | ||
836 | r = do_fw_crc32_checks(mxman->fw, mxman->fw_image_size, &mxman->fwhdr, false); | |
837 | if (r) { | |
838 | SCSC_TAG_ERR(MXMAN, "do_fw_crc32_checks() failed r=%d\n", r); | |
839 | mxman_fail(mxman, SCSC_PANIC_CODE_HOST << 15, __func__); | |
840 | return; | |
841 | } | |
842 | fw_crc_wq_start(mxman); | |
843 | } | |
844 | ||
845 | ||
846 | static void fw_crc_wq_init(struct mxman *mxman) | |
847 | { | |
848 | mxman->fw_crc_wq = create_singlethread_workqueue("fw_crc_wq"); | |
849 | INIT_DELAYED_WORK(&mxman->fw_crc_work, fw_crc_work_func); | |
850 | } | |
851 | ||
852 | static void fw_crc_wq_stop(struct mxman *mxman) | |
853 | { | |
854 | mxman->check_crc = false; | |
855 | cancel_delayed_work(&mxman->fw_crc_work); | |
856 | flush_workqueue(mxman->fw_crc_wq); | |
857 | } | |
858 | ||
859 | static void fw_crc_wq_deinit(struct mxman *mxman) | |
860 | { | |
861 | fw_crc_wq_stop(mxman); | |
862 | destroy_workqueue(mxman->fw_crc_wq); | |
863 | } | |
864 | ||
865 | static int transports_init(struct mxman *mxman) | |
866 | { | |
867 | struct mxconf *mxconf; | |
868 | int r; | |
869 | struct scsc_mx *mx = mxman->mx; | |
870 | ||
871 | /* Initialise mx management stack */ | |
872 | r = mxmgmt_transport_init(scsc_mx_get_mxmgmt_transport(mx), mx); | |
873 | if (r) { | |
874 | SCSC_TAG_ERR(MXMAN, "mxmgmt_transport_init() failed %d\n", r); | |
875 | return r; | |
876 | } | |
877 | ||
878 | /* Initialise gdb transport for cortex-R4 */ | |
879 | r = gdb_transport_init(scsc_mx_get_gdb_transport_r4(mx), mx, GDB_TRANSPORT_R4); | |
880 | if (r) { | |
881 | SCSC_TAG_ERR(MXMAN, "gdb_transport_init() failed %d\n", r); | |
882 | mxmgmt_transport_release(scsc_mx_get_mxmgmt_transport(mx)); | |
883 | return r; | |
884 | } | |
885 | ||
886 | /* Initialise gdb transport for cortex-M4 */ | |
887 | r = gdb_transport_init(scsc_mx_get_gdb_transport_m4(mx), mx, GDB_TRANSPORT_M4); | |
888 | if (r) { | |
889 | SCSC_TAG_ERR(MXMAN, "gdb_transport_init() failed %d\n", r); | |
890 | gdb_transport_release(scsc_mx_get_gdb_transport_r4(mx)); | |
891 | mxmgmt_transport_release(scsc_mx_get_mxmgmt_transport(mx)); | |
892 | return r; | |
893 | } | |
894 | #ifdef CONFIG_SCSC_MX450_GDB_SUPPORT | |
895 | /* Initialise gdb transport for cortex-M4 */ | |
896 | r = gdb_transport_init(scsc_mx_get_gdb_transport_m4_1(mx), mx, GDB_TRANSPORT_M4_1); | |
897 | if (r) { | |
898 | SCSC_TAG_ERR(MXMAN, "gdb_transport_init() failed %d\n", r); | |
899 | gdb_transport_release(scsc_mx_get_gdb_transport_r4(mx)); | |
900 | mxmgmt_transport_release(scsc_mx_get_mxmgmt_transport(mx)); | |
901 | return r; | |
902 | } | |
903 | #endif | |
904 | ||
905 | /* Initialise mxlog transport */ | |
906 | r = mxlog_transport_init(scsc_mx_get_mxlog_transport(mx), mx); | |
907 | if (r) { | |
908 | SCSC_TAG_ERR(MXMAN, "mxlog_transport_init() failed %d\n", r); | |
909 | gdb_transport_release(scsc_mx_get_gdb_transport_m4(mx)); | |
910 | #ifdef CONFIG_SCSC_MX450_GDB_SUPPORT | |
911 | gdb_transport_release(scsc_mx_get_gdb_transport_m4_1(mx)); | |
912 | #endif | |
913 | gdb_transport_release(scsc_mx_get_gdb_transport_r4(mx)); | |
914 | mxmgmt_transport_release(scsc_mx_get_mxmgmt_transport(mx)); | |
915 | return r; | |
916 | } | |
917 | ||
918 | /* | |
919 | * Allocate & Initialise Infrastructre Config Structure | |
920 | * including the mx management stack config information. | |
921 | */ | |
922 | mxconf = miframman_alloc(scsc_mx_get_ramman(mx), sizeof(struct mxconf), 4, MIFRAMMAN_OWNER_COMMON); | |
923 | if (!mxconf) { | |
924 | SCSC_TAG_ERR(MXMAN, "miframman_alloc() failed\n"); | |
925 | gdb_transport_release(scsc_mx_get_gdb_transport_m4(mx)); | |
926 | #ifdef CONFIG_SCSC_MX450_GDB_SUPPORT | |
927 | gdb_transport_release(scsc_mx_get_gdb_transport_m4_1(mx)); | |
928 | #endif | |
929 | gdb_transport_release(scsc_mx_get_gdb_transport_r4(mx)); | |
930 | mxmgmt_transport_release(scsc_mx_get_mxmgmt_transport(mx)); | |
931 | mxlog_transport_release(scsc_mx_get_mxlog_transport(mx)); | |
932 | return -ENOMEM; | |
933 | } | |
934 | mxman->mxconf = mxconf; | |
935 | mxconf->magic = MXCONF_MAGIC; | |
936 | mxconf->version.major = MXCONF_VERSION_MAJOR; | |
937 | mxconf->version.minor = MXCONF_VERSION_MINOR; | |
938 | ||
939 | /* Pass pre-existing FM status to FW */ | |
940 | mxconf->flags = 0; | |
941 | #ifdef CONFIG_SCSC_FM | |
942 | mxconf->flags |= is_fm_on ? MXCONF_FLAGS_FM_ON : 0; | |
943 | #endif | |
944 | SCSC_TAG_INFO(MXMAN, "mxconf flags 0x%08x\n", mxconf->flags); | |
945 | ||
946 | /* serialise mxmgmt transport */ | |
947 | mxmgmt_transport_config_serialise(scsc_mx_get_mxmgmt_transport(mx), &mxconf->mx_trans_conf); | |
948 | /* serialise Cortex-R4 gdb transport */ | |
949 | gdb_transport_config_serialise(scsc_mx_get_gdb_transport_r4(mx), &mxconf->mx_trans_conf_gdb_r4); | |
950 | /* serialise Cortex-M4 gdb transport */ | |
951 | gdb_transport_config_serialise(scsc_mx_get_gdb_transport_m4(mx), &mxconf->mx_trans_conf_gdb_m4); | |
952 | ||
953 | /* Default to Fleximac M4_1 monitor channel not in use. | |
954 | * Allows CONFIG_SCSC_MX450_GDB_SUPPORT to be turned off in Kconfig even though mxconf | |
955 | * struct v5 defines M4_1 channel | |
956 | */ | |
957 | mxconf->mx_trans_conf_gdb_m4_1.from_ap_stream_conf.buf_conf.buffer_loc = 0; | |
958 | #ifdef CONFIG_SCSC_MX450_GDB_SUPPORT | |
959 | /* serialise Cortex-M4 gdb transport */ | |
960 | gdb_transport_config_serialise(scsc_mx_get_gdb_transport_m4_1(mx), &mxconf->mx_trans_conf_gdb_m4_1); | |
961 | #endif | |
962 | /* serialise mxlog transport */ | |
963 | mxlog_transport_config_serialise(scsc_mx_get_mxlog_transport(mx), &mxconf->mxlogconf); | |
964 | SCSC_TAG_DEBUG(MXMAN, "read_bit_idx=%d write_bit_idx=%d buffer=%p num_packets=%d packet_size=%d read_index=%d write_index=%d\n", | |
965 | scsc_mx_get_mxlog_transport(mx)->mif_stream.read_bit_idx, | |
966 | scsc_mx_get_mxlog_transport(mx)->mif_stream.write_bit_idx, | |
967 | scsc_mx_get_mxlog_transport(mx)->mif_stream.buffer.buffer, | |
968 | scsc_mx_get_mxlog_transport(mx)->mif_stream.buffer.num_packets, | |
969 | scsc_mx_get_mxlog_transport(mx)->mif_stream.buffer.packet_size, | |
970 | *scsc_mx_get_mxlog_transport(mx)->mif_stream.buffer.read_index, | |
971 | *scsc_mx_get_mxlog_transport(mx)->mif_stream.buffer.write_index | |
972 | ); | |
973 | ||
974 | /* Need to initialise fwconfig or else random data can make firmware data abort. */ | |
975 | mxconf->fwconfig.offset = 0; | |
976 | mxconf->fwconfig.size = 0; | |
977 | #ifdef CONFIG_SCSC_COMMON_HCF | |
978 | /* Load Common Config HCF */ | |
979 | mxfwconfig_load(mxman->mx, &mxconf->fwconfig); | |
980 | #endif | |
981 | return 0; | |
982 | } | |
983 | ||
984 | static void transports_release(struct mxman *mxman) | |
985 | { | |
986 | mxlog_transport_release(scsc_mx_get_mxlog_transport(mxman->mx)); | |
987 | mxmgmt_transport_release(scsc_mx_get_mxmgmt_transport(mxman->mx)); | |
988 | gdb_transport_release(scsc_mx_get_gdb_transport_r4(mxman->mx)); | |
989 | gdb_transport_release(scsc_mx_get_gdb_transport_m4(mxman->mx)); | |
990 | #ifdef CONFIG_SCSC_MX450_GDB_SUPPORT | |
991 | gdb_transport_release(scsc_mx_get_gdb_transport_m4_1(mxman->mx)); | |
992 | #endif | |
993 | miframman_free(scsc_mx_get_ramman(mxman->mx), mxman->mxconf); | |
994 | } | |
995 | ||
996 | static void mbox_init(struct mxman *mxman, u32 firmware_entry_point) | |
997 | { | |
998 | u32 *mbox0; | |
999 | u32 *mbox1; | |
1000 | u32 *mbox2; | |
1001 | u32 *mbox3; | |
1002 | scsc_mifram_ref mifram_ref; | |
1003 | struct scsc_mx *mx = mxman->mx; | |
1004 | struct scsc_mif_abs *mif = scsc_mx_get_mif_abs(mxman->mx); | |
1005 | ||
1006 | /* Place firmware entry address in MIF MBOX 0 so R4 ROM knows where to jump to! */ | |
1007 | mbox0 = mifmboxman_get_mbox_ptr(scsc_mx_get_mboxman(mx), mif, MBOX_INDEX_0); | |
1008 | mbox1 = mifmboxman_get_mbox_ptr(scsc_mx_get_mboxman(mx), mif, MBOX_INDEX_1); | |
1009 | ||
1010 | /* Write (and flush) entry point to MailBox 0, config address to MBOX 1 */ | |
1011 | *mbox0 = firmware_entry_point; | |
1012 | mif->get_mifram_ref(mif, mxman->mxconf, &mifram_ref); | |
1013 | *mbox1 = mifram_ref; /* must be R4-relative address here */ | |
1014 | /* CPU memory barrier */ | |
1015 | wmb(); | |
1016 | /* | |
1017 | * write the magic number "0xbcdeedcb" to MIF Mailbox #2 & | |
1018 | * copy the firmware_startup_flags to MIF Mailbox #3 before starting (reset = 0) the R4 | |
1019 | */ | |
1020 | mbox2 = mifmboxman_get_mbox_ptr(scsc_mx_get_mboxman(mx), mif, MBOX_INDEX_2); | |
1021 | *mbox2 = MBOX2_MAGIC_NUMBER; | |
1022 | mbox3 = mifmboxman_get_mbox_ptr(scsc_mx_get_mboxman(mx), mif, MBOX_INDEX_3); | |
1023 | *mbox3 = firmware_startup_flags; | |
1024 | } | |
1025 | ||
1026 | static int fwhdr_init(char *fw, struct fwhdr *fwhdr, bool *fwhdr_parsed_ok, bool *check_crc) | |
1027 | { | |
1028 | /* | |
1029 | * Validate the fw image including checking the firmware header, majic #, version, checksum so on | |
1030 | * then do CRC on the entire image | |
1031 | * | |
1032 | * Derive some values from header - | |
1033 | * | |
1034 | * PORT: assumes little endian | |
1035 | */ | |
1036 | if (skip_header) | |
1037 | *fwhdr_parsed_ok = false; /* Allows the forced start address to be used */ | |
1038 | else | |
1039 | *fwhdr_parsed_ok = fwhdr_parse(fw, fwhdr); | |
1040 | *check_crc = false; | |
1041 | if (*fwhdr_parsed_ok) { | |
1042 | SCSC_TAG_INFO(MXMAN, "FW HEADER version: hdr_major: %d hdr_minor: %d\n", fwhdr->hdr_major, fwhdr->hdr_minor); | |
1043 | switch (fwhdr->hdr_major) { | |
1044 | case 0: | |
1045 | switch (fwhdr->hdr_minor) { | |
1046 | case 2: | |
1047 | *check_crc = true; | |
1048 | break; | |
1049 | default: | |
1050 | SCSC_TAG_ERR(MXMAN, "Unsupported FW HEADER version: hdr_major: %d hdr_minor: %d\n", | |
1051 | fwhdr->hdr_major, fwhdr->hdr_minor); | |
1052 | return -EINVAL; | |
1053 | } | |
1054 | break; | |
1055 | case 1: | |
1056 | *check_crc = true; | |
1057 | break; | |
1058 | default: | |
1059 | SCSC_TAG_ERR(MXMAN, "Unsupported FW HEADER version: hdr_major: %d hdr_minor: %d\n", | |
1060 | fwhdr->hdr_major, fwhdr->hdr_minor); | |
1061 | return -EINVAL; | |
1062 | } | |
1063 | switch (fwhdr->fwapi_major) { | |
1064 | case 0: | |
1065 | switch (fwhdr->fwapi_minor) { | |
1066 | case 2: | |
1067 | SCSC_TAG_INFO(MXMAN, "FWAPI version: fwapi_major: %d fwapi_minor: %d\n", | |
1068 | fwhdr->fwapi_major, fwhdr->fwapi_minor); | |
1069 | break; | |
1070 | default: | |
1071 | SCSC_TAG_ERR(MXMAN, "Unsupported FWAPI version: fwapi_major: %d fwapi_minor: %d\n", | |
1072 | fwhdr->fwapi_major, fwhdr->fwapi_minor); | |
1073 | return -EINVAL; | |
1074 | } | |
1075 | break; | |
1076 | default: | |
1077 | SCSC_TAG_ERR(MXMAN, "Unsupported FWAPI version: fwapi_major: %d fwapi_minor: %d\n", | |
1078 | fwhdr->fwapi_major, fwhdr->fwapi_minor); | |
1079 | return -EINVAL; | |
1080 | } | |
1081 | } else { | |
1082 | /* This is unidetified pre-header firmware - assume it is built to run at 0xb8000000 == 0 for bootrom */ | |
1083 | if (allow_unidentified_firmware) { | |
1084 | SCSC_TAG_INFO(MXMAN, "Unidentified firmware override\n"); | |
1085 | fwhdr->firmware_entry_point = 0; | |
1086 | fwhdr->fw_runtime_length = MX_FW_RUNTIME_LENGTH; | |
1087 | } else { | |
1088 | SCSC_TAG_ERR(MXMAN, "Unidentified firmware is not allowed\n"); | |
1089 | return -EINVAL; | |
1090 | } | |
1091 | } | |
1092 | return 0; | |
1093 | } | |
1094 | ||
1095 | static int fw_init(struct mxman *mxman, void *start_dram, size_t size_dram, bool *fwhdr_parsed_ok) | |
1096 | { | |
1097 | int r; | |
1098 | char *build_id; | |
1099 | char *ttid; | |
1100 | u32 fw_image_size; | |
1101 | struct fwhdr *fwhdr = &mxman->fwhdr; | |
1102 | char *fw = start_dram; | |
1103 | ||
1104 | r = mx140_file_download_fw(mxman->mx, start_dram, size_dram, &fw_image_size); | |
1105 | if (r) { | |
1106 | SCSC_TAG_ERR(MXMAN, "mx140_file_download_fw() failed (%d)\n", r); | |
1107 | return r; | |
1108 | } | |
1109 | ||
1110 | r = fwhdr_init(fw, fwhdr, fwhdr_parsed_ok, &mxman->check_crc); | |
1111 | if (r) { | |
1112 | SCSC_TAG_ERR(MXMAN, "fwhdr_init() failed\n"); | |
1113 | return r; | |
1114 | } | |
1115 | mxman->fw = fw; | |
1116 | mxman->fw_image_size = fw_image_size; | |
1117 | if (mxman->check_crc) { | |
1118 | /* do CRC on the entire image */ | |
1119 | r = do_fw_crc32_checks(fw, fw_image_size, &mxman->fwhdr, true); | |
1120 | if (r) { | |
1121 | SCSC_TAG_ERR(MXMAN, "do_fw_crc32_checks() failed\n"); | |
1122 | return r; | |
1123 | } | |
1124 | fw_crc_wq_start(mxman); | |
1125 | } | |
1126 | ||
1127 | if (*fwhdr_parsed_ok) { | |
1128 | build_id = fwhdr_get_build_id(fw, fwhdr); | |
1129 | if (build_id) { | |
1130 | struct slsi_kic_service_info kic_info; | |
1131 | ||
1132 | (void)snprintf(mxman->fw_build_id, sizeof(mxman->fw_build_id), "%s", build_id); | |
1133 | SCSC_TAG_INFO(MXMAN, "Firmware BUILD_ID: %s\n", mxman->fw_build_id); | |
1134 | memcpy(saved_fw_build_id, mxman->fw_build_id, | |
1135 | sizeof(saved_fw_build_id)); | |
1136 | ||
1137 | (void) snprintf(kic_info.ver_str, | |
1138 | min(sizeof(mxman->fw_build_id), sizeof(kic_info.ver_str)), | |
1139 | "%s", mxman->fw_build_id); | |
1140 | kic_info.fw_api_major = fwhdr->fwapi_major; | |
1141 | kic_info.fw_api_minor = fwhdr->fwapi_minor; | |
1142 | kic_info.release_product = SCSC_RELEASE_PRODUCT; | |
1143 | kic_info.host_release_iteration = SCSC_RELEASE_ITERATION; | |
1144 | kic_info.host_release_candidate = SCSC_RELEASE_CANDIDATE; | |
1145 | ||
1146 | slsi_kic_service_information(slsi_kic_technology_type_common, &kic_info); | |
1147 | } else | |
1148 | SCSC_TAG_ERR(MXMAN, "Failed to get Firmware BUILD_ID\n"); | |
1149 | ||
1150 | ttid = fwhdr_get_ttid(fw, fwhdr); | |
1151 | if (ttid) { | |
1152 | (void)snprintf(mxman->fw_ttid, sizeof(mxman->fw_ttid), "%s", ttid); | |
1153 | SCSC_TAG_INFO(MXMAN, "Firmware ttid: %s\n", mxman->fw_ttid); | |
1154 | } | |
1155 | } | |
1156 | ||
1157 | SCSC_TAG_DEBUG(MXMAN, "firmware_entry_point=0x%x fw_runtime_length=%d\n", fwhdr->firmware_entry_point, fwhdr->fw_runtime_length); | |
1158 | ||
1159 | return 0; | |
1160 | ||
1161 | } | |
1162 | ||
1163 | static int mxman_start(struct mxman *mxman) | |
1164 | { | |
1165 | void *start_dram; | |
1166 | size_t size_dram = MX_DRAM_SIZE; | |
1167 | struct scsc_mif_abs *mif; | |
1168 | struct fwhdr *fwhdr = &mxman->fwhdr; | |
1169 | bool fwhdr_parsed_ok; | |
1170 | void *start_mifram_heap; | |
1171 | u32 length_mifram_heap; | |
1172 | void *start_mifram_heap2; | |
1173 | u32 length_mifram_heap2; | |
1174 | int r; | |
1175 | ||
1176 | if (reset_failed) { | |
1177 | struct timeval tval = ns_to_timeval(reset_failed_time); | |
1178 | ||
1179 | SCSC_TAG_ERR(MXMAN, "previous reset failed at [%6lu.%06ld], ignoring\n", tval.tv_sec, tval.tv_usec); | |
1180 | return -EIO; | |
1181 | } | |
1182 | ||
1183 | (void)snprintf(mxman->fw_build_id, sizeof(mxman->fw_build_id), "unknown"); | |
1184 | ||
1185 | /* If the option is set to skip header, we must allow unidentified f/w */ | |
1186 | if (skip_header) { | |
1187 | SCSC_TAG_INFO(MXMAN, "Ignoring firmware header block\n"); | |
1188 | allow_unidentified_firmware = true; | |
1189 | } | |
1190 | ||
1191 | mif = scsc_mx_get_mif_abs(mxman->mx); | |
1192 | start_dram = mif->map(mif, &size_dram); | |
1193 | ||
1194 | if (!start_dram) { | |
1195 | SCSC_TAG_ERR(MXMAN, "Error allocating dram\n"); | |
1196 | return -ENOMEM; | |
1197 | } | |
1198 | ||
1199 | SCSC_TAG_INFO(MXMAN, "Allocated %zu bytes\n", size_dram); | |
1200 | ||
1201 | #ifdef CONFIG_SCSC_CHV_SUPPORT | |
1202 | if (chv_run) | |
1203 | allow_unidentified_firmware = true; | |
1204 | /* Set up chv arguments. */ | |
1205 | ||
1206 | #endif | |
1207 | ||
1208 | mxman->start_dram = start_dram; | |
1209 | ||
1210 | r = fw_init(mxman, start_dram, size_dram, &fwhdr_parsed_ok); | |
1211 | if (r) { | |
1212 | SCSC_TAG_ERR(MXMAN, "fw_init() failed\n"); | |
1213 | mif->unmap(mif, mxman->start_dram); | |
1214 | return r; | |
1215 | } | |
1216 | ||
1217 | /* set up memory protection (read only) from start_dram to start_dram+fw_length | |
1218 | * rounding up the size if required | |
1219 | */ | |
1220 | start_mifram_heap = (char *)start_dram + fwhdr->fw_runtime_length; | |
1221 | length_mifram_heap = MX_DRAM_SIZE_SECTION_1 - fwhdr->fw_runtime_length; | |
1222 | ||
1223 | ||
1224 | start_mifram_heap2 = (char *)start_dram + MX_DRAM_SIZE_SECTION_2; | |
1225 | ||
1226 | /* ABox reserved at end so adjust length - round to multiple of PAGE_SIZE */ | |
1227 | length_mifram_heap2 = MX_DRAM_SIZE_SECTION_2 - | |
1228 | ((sizeof(struct scsc_bt_audio_abox) + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1)); | |
1229 | ||
1230 | miframman_init(scsc_mx_get_ramman(mxman->mx), start_mifram_heap, length_mifram_heap, start_dram); | |
1231 | miframman_init(scsc_mx_get_ramman2(mxman->mx), start_mifram_heap2, length_mifram_heap2, start_mifram_heap2); | |
1232 | miframabox_init(scsc_mx_get_aboxram(mxman->mx), start_mifram_heap2 + length_mifram_heap2); | |
1233 | mifmboxman_init(scsc_mx_get_mboxman(mxman->mx)); | |
1234 | mifintrbit_init(scsc_mx_get_intrbit(mxman->mx), mif); | |
1235 | mxfwconfig_init(mxman->mx); | |
1236 | ||
1237 | /* Initialise transports */ | |
1238 | r = transports_init(mxman); | |
1239 | if (r) { | |
1240 | SCSC_TAG_ERR(MXMAN, "transports_init() failed\n"); | |
1241 | fw_crc_wq_stop(mxman); | |
1242 | mifintrbit_deinit(scsc_mx_get_intrbit(mxman->mx)); | |
1243 | miframman_deinit(scsc_mx_get_ramman(mxman->mx)); | |
1244 | miframman_deinit(scsc_mx_get_ramman2(mxman->mx)); | |
1245 | miframabox_deinit(scsc_mx_get_aboxram(mxman->mx)); | |
1246 | mifmboxman_deinit(scsc_mx_get_mboxman(mxman->mx)); | |
1247 | /* Release the MIF memory resources */ | |
1248 | mif->unmap(mif, mxman->start_dram); | |
1249 | return r; | |
1250 | } | |
1251 | mbox_init(mxman, fwhdr->firmware_entry_point); | |
1252 | init_completion(&mxman->mm_msg_start_ind_completion); | |
1253 | init_completion(&mxman->mm_msg_halt_rsp_completion); | |
1254 | mxmgmt_transport_register_channel_handler(scsc_mx_get_mxmgmt_transport(mxman->mx), | |
1255 | MMTRANS_CHAN_ID_MAXWELL_MANAGEMENT, | |
1256 | &mxman_message_handler, mxman); | |
1257 | ||
1258 | mxlog_init(scsc_mx_get_mxlog(mxman->mx), mxman->mx, mxman->fw_build_id); | |
1259 | #ifdef CONFIG_SCSC_MXLOGGER | |
1260 | mxlogger_init(mxman->mx, scsc_mx_get_mxlogger(mxman->mx), MXL_POOL_SZ); | |
1261 | ||
1262 | #ifdef CONFIG_SCSC_LOG_COLLECTION | |
1263 | /* Register minimoredump client */ | |
1264 | mini_moredump_client.prv = mxman; | |
1265 | scsc_log_collector_register_client(&mini_moredump_client); | |
1266 | #endif | |
1267 | #endif | |
1268 | #ifdef CONFIG_SCSC_SMAPPER | |
1269 | /* Initialize SMAPPER */ | |
1270 | mifsmapper_init(scsc_mx_get_smapper(mxman->mx), mif); | |
1271 | #endif | |
1272 | #ifdef CONFIG_SCSC_QOS | |
1273 | mifqos_init(scsc_mx_get_qos(mxman->mx), mif); | |
1274 | #endif | |
1275 | ||
1276 | #ifdef CONFIG_SCSC_CHV_SUPPORT | |
1277 | if (chv_run) { | |
1278 | int i; | |
1279 | ||
1280 | u32 *p = (u32 *)((u8 *)start_dram + SCSC_CHV_ARGV_ADDR_OFFSET); | |
1281 | ||
1282 | if (chv_argc == 0) { | |
1283 | /* | |
1284 | * Setup the chv f/w arguments. | |
1285 | * Argument of 0 means run once (driver never set this). | |
1286 | * Argument of 1 means run forever. | |
1287 | */ | |
1288 | SCSC_TAG_INFO(MXMAN, "Setting up CHV arguments: start_dram=%p arg=%p, chv_run=%d\n", start_dram, p, chv_run); | |
1289 | *p++ = 1; /* argc */ | |
1290 | *p++ = chv_run == 1 ? 0 : 1; /* arg */ | |
1291 | } else { | |
1292 | /* Pass separate args */ | |
1293 | *p++ = chv_argc; /* argc */ | |
1294 | SCSC_TAG_INFO(MXMAN, "Setting up additional CHV args: chv_argc = %d\n", chv_argc); | |
1295 | ||
1296 | for (i = 0; i < chv_argc; i++) { | |
1297 | SCSC_TAG_INFO(MXMAN, "Setting up additional CHV args: chv_argv[%d]: *(%p) = 0x%x\n", i, p, (u32)chv_argv[i]); | |
1298 | *p++ = (u32)chv_argv[i]; /* arg */ | |
1299 | } | |
1300 | } | |
1301 | } | |
1302 | #endif | |
1303 | mxproc_create_ctrl_proc_dir(&mxman->mxproc, mxman); | |
1304 | panicmon_init(scsc_mx_get_panicmon(mxman->mx), mxman->mx); | |
1305 | ||
1306 | /* Change state to STARTING to allow coredump as we come out of reset */ | |
1307 | mxman->mxman_state = MXMAN_STATE_STARTING; | |
1308 | ||
1309 | /* release Maxwell from reset */ | |
1310 | r = mif->reset(mif, 0); | |
1311 | if (r) { | |
1312 | reset_failed = true; | |
1313 | SCSC_TAG_INFO(MXMAN, "HW reset deassertion failed\n"); | |
1314 | ||
1315 | /* Save log at point of failure */ | |
1316 | #ifdef CONFIG_SCSC_LOG_COLLECTION | |
1317 | scsc_log_collector_schedule_collection(SCSC_LOG_HOST_COMMON, SCSC_LOG_HOST_COMMON_REASON_START); | |
1318 | #else | |
1319 | mx140_log_dump(); | |
1320 | #endif | |
1321 | } | |
1322 | if (fwhdr_parsed_ok) { | |
1323 | r = wait_for_mm_msg_start_ind(mxman); | |
1324 | if (r) { | |
1325 | SCSC_TAG_ERR(MXMAN, "wait_for_MM_START_IND() failed: r=%d\n", r); | |
1326 | print_mailboxes(mxman); | |
1327 | if (skip_mbox0_check) { | |
1328 | SCSC_TAG_ERR(MXMAN, "timeout ignored in skip_mbox0_check mode\n"); | |
1329 | return 0; | |
1330 | } | |
1331 | mxman_stop(mxman); | |
1332 | return r; | |
1333 | } | |
1334 | #ifdef CONFIG_SCSC_MXLOGGER | |
1335 | mxlogger_start(scsc_mx_get_mxlogger(mxman->mx)); | |
1336 | #endif | |
1337 | } else { | |
1338 | msleep(WAIT_FOR_FW_TO_START_DELAY_MS); | |
1339 | } | |
1340 | ||
1341 | return 0; | |
1342 | } | |
1343 | ||
1344 | static bool is_bug_on_enabled(struct scsc_mx *mx) | |
1345 | { | |
1346 | bool bug_on_enabled; | |
1347 | const struct firmware *firm; | |
1348 | int r; | |
1349 | ||
781f598d | 1350 | if ((memdump == 3) && (disable_recovery_handling == MEMDUMP_FILE_FOR_RECOVERY)) |
533a23a1 TK |
1351 | bug_on_enabled = true; |
1352 | else | |
1353 | bug_on_enabled = false; | |
1354 | #ifdef CONFIG_SCSC_LOG_COLLECTION | |
1355 | (void)firm; /* unused */ | |
1356 | (void)r; /* unused */ | |
1357 | return bug_on_enabled; | |
1358 | #else | |
1359 | /* non SABLE platforms should also follow /sys/wifi/memdump if enabled */ | |
1360 | if (disable_recovery_handling == MEMDUMP_FILE_FOR_RECOVERY) | |
1361 | return bug_on_enabled; | |
1362 | ||
1363 | /* for legacy platforms (including Andorid P) using .memdump.info */ | |
1364 | #if defined(ANDROID_VERSION) && (ANDROID_VERSION >= 90000) | |
1365 | #define MX140_MEMDUMP_INFO_FILE "/data/vendor/conn/.memdump.info" | |
1366 | #else | |
1367 | #define MX140_MEMDUMP_INFO_FILE "/data/misc/conn/.memdump.info" | |
1368 | #endif | |
1369 | ||
1370 | SCSC_TAG_INFO(MX_FILE, "Loading %s file\n", MX140_MEMDUMP_INFO_FILE); | |
1371 | r = mx140_request_file(mx, MX140_MEMDUMP_INFO_FILE, &firm); | |
1372 | if (r) { | |
1373 | SCSC_TAG_WARNING(MX_FILE, "Error Loading %s file %d\n", MX140_MEMDUMP_INFO_FILE, r); | |
1374 | return bug_on_enabled; | |
1375 | } | |
1376 | if (firm->size < sizeof(char)) | |
1377 | SCSC_TAG_WARNING(MX_FILE, "file is too small\n"); | |
1378 | else if (*firm->data == '3') | |
1379 | bug_on_enabled = true; | |
1380 | mx140_release_file(mx, firm); | |
1381 | SCSC_TAG_INFO(MX_FILE, "bug_on_enabled %d\n", bug_on_enabled); | |
1382 | return bug_on_enabled; | |
1383 | #endif //CONFIG_SCSC_LOG_COLLECTION | |
1384 | } | |
1385 | ||
1386 | static void print_panic_code_legacy(u16 code) | |
1387 | { | |
1388 | u16 tech = code & SCSC_PANIC_TECH_MASK; | |
1389 | u16 origin = code & SCSC_PANIC_ORIGIN_MASK; | |
1390 | ||
1391 | SCSC_TAG_INFO(MXMAN, "Decoding panic code=0x%x:\n", code); | |
1392 | switch (origin) { | |
1393 | default: | |
1394 | SCSC_TAG_INFO(MXMAN, "Failed to identify panic origin\n"); | |
1395 | break; | |
1396 | case SCSC_PANIC_ORIGIN_FW: | |
1397 | SCSC_TAG_INFO(MXMAN, "SCSC_PANIC_ORIGIN_FW\n"); | |
1398 | break; | |
1399 | case SCSC_PANIC_ORIGIN_HOST: | |
1400 | SCSC_TAG_INFO(MXMAN, "SCSC_PANIC_ORIGIN_HOST\n"); | |
1401 | break; | |
1402 | } | |
1403 | ||
1404 | switch (tech) { | |
1405 | default: | |
1406 | SCSC_TAG_INFO(MXMAN, "Failed to identify panic technology\n"); | |
1407 | break; | |
1408 | case SCSC_PANIC_TECH_WLAN: | |
1409 | SCSC_TAG_INFO(MXMAN, "SCSC_PANIC_TECH_WLAN\n"); | |
1410 | break; | |
1411 | case SCSC_PANIC_TECH_CORE: | |
1412 | SCSC_TAG_INFO(MXMAN, "SCSC_PANIC_TECH_CORE\n"); | |
1413 | break; | |
1414 | case SCSC_PANIC_TECH_BT: | |
1415 | SCSC_TAG_INFO(MXMAN, "SCSC_PANIC_TECH_BT\n"); | |
1416 | break; | |
1417 | case SCSC_PANIC_TECH_UNSP: | |
1418 | SCSC_TAG_INFO(MXMAN, "PANIC_TECH_UNSP\n"); | |
1419 | break; | |
1420 | } | |
1421 | SCSC_TAG_INFO(MXMAN, "panic subcode=0x%x\n", code & SCSC_PANIC_SUBCODE_MASK_LEGACY); | |
1422 | } | |
1423 | ||
1424 | static void print_panic_code(u16 code) | |
1425 | { | |
1426 | u16 origin = code & SCSC_PANIC_ORIGIN_MASK; /* Panic origin (host/fw) */ | |
1427 | u16 subcode = code & SCSC_PANIC_SUBCODE_MASK; /* The panic code */ | |
1428 | ||
1429 | SCSC_TAG_INFO(MXMAN, "Decoding panic code=0x%x:\n", code); | |
1430 | SCSC_TAG_INFO(MXMAN, "panic subcode=0x%x\n", code & SCSC_PANIC_SUBCODE_MASK); | |
1431 | ||
1432 | switch (origin) { | |
1433 | default: | |
1434 | SCSC_TAG_INFO(MXMAN, "Failed to identify panic origin\n"); | |
1435 | break; | |
1436 | case SCSC_PANIC_ORIGIN_FW: | |
1437 | SCSC_TAG_INFO(MXMAN, "WLBT FW PANIC: 0x%02x\n", subcode); | |
1438 | break; | |
1439 | case SCSC_PANIC_ORIGIN_HOST: | |
1440 | SCSC_TAG_INFO(MXMAN, "WLBT HOST detected FW failure, service:\n"); | |
1441 | switch (subcode >> SCSC_SYSERR_HOST_SERVICE_SHIFT) { | |
1442 | case SCSC_SERVICE_ID_WLAN: | |
1443 | SCSC_TAG_INFO(MXMAN, " WLAN\n"); | |
1444 | break; | |
1445 | case SCSC_SERVICE_ID_BT: | |
1446 | SCSC_TAG_INFO(MXMAN, " BT\n"); | |
1447 | break; | |
1448 | case SCSC_SERVICE_ID_ANT: | |
1449 | SCSC_TAG_INFO(MXMAN, " ANT\n"); | |
1450 | break; | |
1451 | case SCSC_SERVICE_ID_CLK20MHZ: | |
1452 | SCSC_TAG_INFO(MXMAN, " CLK20MHZ\n"); | |
1453 | break; | |
1454 | default: | |
1455 | SCSC_TAG_INFO(MXMAN, " Service 0x%x\n", subcode); | |
1456 | break; | |
1457 | } | |
1458 | break; | |
1459 | } | |
1460 | } | |
1461 | ||
1462 | /** | |
1463 | * Print the last panic record collected to aid in post mortem. | |
1464 | * | |
1465 | * Helps when all we have is kernel log showing WLBT failed some time ago | |
1466 | * | |
1467 | * Only prints the R4 record | |
1468 | */ | |
1469 | void mxman_show_last_panic(struct mxman *mxman) | |
1470 | { | |
1471 | u32 r4_panic_record_length = 0; /* in u32s */ | |
1472 | ||
1473 | /* Any valid panic? */ | |
1474 | if (mxman->scsc_panic_code == 0) | |
1475 | return; | |
1476 | ||
1477 | SCSC_TAG_INFO(MXMAN, "\n\n--- DETAILS OF LAST WLBT FAILURE ---\n\n"); | |
1478 | ||
1479 | switch (mxman->scsc_panic_code & SCSC_PANIC_ORIGIN_MASK) { | |
1480 | case SCSC_PANIC_ORIGIN_HOST: | |
1481 | SCSC_TAG_INFO(MXMAN, "Last panic was host induced:\n"); | |
1482 | break; | |
1483 | ||
1484 | case SCSC_PANIC_ORIGIN_FW: | |
1485 | SCSC_TAG_INFO(MXMAN, "Last panic was FW:\n"); | |
1486 | fw_parse_r4_panic_record(mxman->last_panic_rec_r, &r4_panic_record_length); | |
1487 | break; | |
1488 | ||
1489 | default: | |
1490 | SCSC_TAG_INFO(MXMAN, "Last panic unknown origin %d\n", mxman->scsc_panic_code & SCSC_PANIC_ORIGIN_MASK); | |
1491 | break; | |
1492 | } | |
1493 | ||
1494 | print_panic_code(mxman->scsc_panic_code); | |
1495 | ||
1496 | SCSC_TAG_INFO(MXMAN, "Reason: '%s'\n", mxman->failure_reason[0] ? mxman->failure_reason : "<null>"); | |
1497 | SCSC_TAG_INFO(MXMAN, "Auto-recovery: %s\n", disable_recovery_handling ? "off" : "on"); | |
1498 | ||
1499 | if (mxman_recovery_disabled()) { | |
1500 | /* Labour the point that a reboot is needed when autorecovery is disabled */ | |
1501 | SCSC_TAG_INFO(MXMAN, "\n\n*** HANDSET REBOOT NEEDED TO RESTART WLAN AND BT ***\n\n"); | |
1502 | } | |
1503 | ||
1504 | SCSC_TAG_INFO(MXMAN, "\n\n--- END DETAILS OF LAST WLBT FAILURE ---\n\n"); | |
1505 | } | |
1506 | ||
1507 | static void process_panic_record(struct mxman *mxman) | |
1508 | { | |
1509 | u32 *r4_panic_record = NULL; | |
1510 | u32 *m4_panic_record = NULL; | |
1511 | #ifdef CONFIG_SCSC_MX450_GDB_SUPPORT | |
1512 | u32 *m4_1_panic_record = NULL; | |
1513 | #endif | |
1514 | u32 r4_panic_record_length = 0; /* in u32s */ | |
1515 | u32 m4_panic_record_length = 0; /* in u32s */ | |
1516 | #ifdef CONFIG_SCSC_MX450_GDB_SUPPORT | |
1517 | u32 m4_1_panic_record_length = 0; /* in u32s */ | |
1518 | #endif | |
1519 | u32 full_panic_code = 0; | |
1520 | bool r4_panic_record_ok = false; | |
1521 | bool m4_panic_record_ok = false; | |
1522 | #ifdef CONFIG_SCSC_MX450_GDB_SUPPORT | |
1523 | bool m4_1_panic_record_ok = false; | |
1524 | #endif | |
1525 | bool r4_sympathetic_panic_flag = false; | |
1526 | bool m4_sympathetic_panic_flag = false; | |
1527 | #ifdef CONFIG_SCSC_MX450_GDB_SUPPORT | |
1528 | bool m4_1_sympathetic_panic_flag = false; | |
1529 | #endif | |
1530 | ||
1531 | /* some configurable delay before accessing the panic record */ | |
1532 | msleep(panic_record_delay); | |
1533 | /* | |
1534 | * Check if the panic was trigered by MX and set the subcode if so. | |
1535 | */ | |
1536 | if ((mxman->scsc_panic_code & SCSC_PANIC_ORIGIN_MASK) == SCSC_PANIC_ORIGIN_FW) { | |
1537 | if (mxman->fwhdr.r4_panic_record_offset) { | |
1538 | r4_panic_record = (u32 *)(mxman->fw + mxman->fwhdr.r4_panic_record_offset); | |
1539 | r4_panic_record_ok = fw_parse_r4_panic_record(r4_panic_record, &r4_panic_record_length); | |
1540 | } else { | |
1541 | SCSC_TAG_INFO(MXMAN, "R4 panic record doesn't exist in the firmware header\n"); | |
1542 | } | |
1543 | if (mxman->fwhdr.m4_panic_record_offset) { | |
1544 | m4_panic_record = (u32 *)(mxman->fw + mxman->fwhdr.m4_panic_record_offset); | |
1545 | m4_panic_record_ok = fw_parse_m4_panic_record(m4_panic_record, &m4_panic_record_length); | |
1546 | #ifdef CONFIG_SCSC_MX450_GDB_SUPPORT | |
1547 | } else if (mxman->fwhdr.m4_1_panic_record_offset) { | |
1548 | m4_1_panic_record = (u32 *)(mxman->fw + mxman->fwhdr.m4_1_panic_record_offset); | |
31418e88 | 1549 | m4_1_panic_record_ok = fw_parse_m4_panic_record(m4_1_panic_record, &m4_1_panic_record_length); |
533a23a1 TK |
1550 | #endif |
1551 | } else { | |
1552 | SCSC_TAG_INFO(MXMAN, "M4 panic record doesn't exist in the firmware header\n"); | |
1553 | } | |
1554 | ||
1555 | /* Extract and print the panic code */ | |
1556 | switch (r4_panic_record_length) { | |
1557 | default: | |
1558 | SCSC_TAG_WARNING(MXMAN, "Bad panic record length/subversion\n"); | |
1559 | break; | |
1560 | case SCSC_R4_V2_MINOR_52: | |
1561 | if (r4_panic_record_ok) { | |
1562 | full_panic_code = r4_panic_record[2]; | |
1563 | mxman->scsc_panic_code |= SCSC_PANIC_CODE_MASK & full_panic_code; | |
1564 | } else if (m4_panic_record_ok) | |
1565 | mxman->scsc_panic_code |= SCSC_PANIC_CODE_MASK & m4_panic_record[2]; | |
1566 | #ifdef CONFIG_SCSC_MX450_GDB_SUPPORT | |
1567 | else if (m4_1_panic_record_ok) | |
1568 | mxman->scsc_panic_code |= SCSC_PANIC_CODE_MASK & m4_1_panic_record[2]; | |
1569 | #endif | |
1570 | /* Set unspecified technology for now */ | |
1571 | mxman->scsc_panic_code |= SCSC_PANIC_TECH_UNSP; | |
1572 | print_panic_code_legacy(mxman->scsc_panic_code); | |
1573 | break; | |
1574 | case SCSC_R4_V2_MINOR_53: | |
1575 | if (r4_panic_record_ok) { | |
1576 | /* Save the last R4 panic record for future display */ | |
1577 | BUG_ON(sizeof(mxman->last_panic_rec_r) < SCSC_R4_V2_MINOR_53 * sizeof(u32)); | |
1578 | memcpy((u8 *)mxman->last_panic_rec_r, (u8 *)r4_panic_record, SCSC_R4_V2_MINOR_53 * sizeof(u32)); | |
1579 | mxman->last_panic_rec_sz = r4_panic_record_length; | |
1580 | ||
1581 | r4_sympathetic_panic_flag = fw_parse_get_r4_sympathetic_panic_flag(r4_panic_record); | |
1582 | SCSC_TAG_INFO(MXMAN, "r4_panic_record_ok=%d r4_sympathetic_panic_flag=%d\n", | |
1583 | r4_panic_record_ok, | |
1584 | r4_sympathetic_panic_flag | |
1585 | ); | |
1586 | if (r4_sympathetic_panic_flag == false) { | |
1587 | /* process R4 record */ | |
1588 | SCSC_TAG_INFO(MXMAN, "process R4 record\n"); | |
1589 | full_panic_code = r4_panic_record[3]; | |
1590 | mxman->scsc_panic_code |= SCSC_PANIC_CODE_MASK & full_panic_code; | |
1591 | print_panic_code(mxman->scsc_panic_code); | |
1592 | break; | |
1593 | } | |
1594 | } | |
1595 | if (m4_panic_record_ok) { | |
1596 | m4_sympathetic_panic_flag = fw_parse_get_m4_sympathetic_panic_flag(m4_panic_record); | |
1597 | SCSC_TAG_INFO(MXMAN, "m4_panic_record_ok=%d m4_sympathetic_panic_flag=%d\n", | |
1598 | m4_panic_record_ok, | |
1599 | m4_sympathetic_panic_flag | |
1600 | ); | |
1601 | if (m4_sympathetic_panic_flag == false) { | |
1602 | /* process M4 record */ | |
1603 | SCSC_TAG_INFO(MXMAN, "process M4 record\n"); | |
1604 | mxman->scsc_panic_code |= SCSC_PANIC_CODE_MASK & m4_panic_record[3]; | |
1605 | } else if (r4_panic_record_ok) { | |
1606 | /* process R4 record */ | |
1607 | SCSC_TAG_INFO(MXMAN, "process R4 record\n"); | |
1608 | mxman->scsc_panic_code |= SCSC_PANIC_CODE_MASK & r4_panic_record[3]; | |
1609 | } | |
1610 | print_panic_code(mxman->scsc_panic_code); | |
1611 | } | |
1612 | #ifdef CONFIG_SCSC_MX450_GDB_SUPPORT /* this is wrong but not sure what is "right" */ | |
1613 | /* "sympathetic panics" are not really a thing on the Neus architecture unless */ | |
1614 | /* generated by the host */ | |
1615 | if (m4_1_panic_record_ok) { | |
1616 | m4_1_sympathetic_panic_flag = fw_parse_get_m4_sympathetic_panic_flag(m4_panic_record); | |
1617 | SCSC_TAG_INFO(MXMAN, "m4_1_panic_record_ok=%d m4_1_sympathetic_panic_flag=%d\n", | |
1618 | m4_1_panic_record_ok, | |
1619 | m4_1_sympathetic_panic_flag | |
1620 | ); | |
1621 | if (m4_1_sympathetic_panic_flag == false) { | |
1622 | /* process M4 record */ | |
1623 | SCSC_TAG_INFO(MXMAN, "process M4_1 record\n"); | |
1624 | mxman->scsc_panic_code |= SCSC_PANIC_SUBCODE_MASK & m4_1_panic_record[3]; | |
1625 | } else if (r4_panic_record_ok) { | |
1626 | /* process R4 record */ | |
1627 | SCSC_TAG_INFO(MXMAN, "process R4 record\n"); | |
1628 | mxman->scsc_panic_code |= SCSC_PANIC_SUBCODE_MASK & r4_panic_record[3]; | |
1629 | } | |
1630 | print_panic_code(mxman->scsc_panic_code); | |
1631 | } | |
1632 | #endif | |
1633 | break; | |
1634 | } | |
1635 | } | |
1636 | if (r4_panic_record_ok) { | |
1637 | /* Populate syserr info with panic equivalent */ | |
1638 | mxman->last_syserr.subsys = (u8) ((full_panic_code >> SYSERR_SUB_SYSTEM_POSN) & SYSERR_SUB_SYSTEM_MASK); | |
1639 | mxman->last_syserr.level = MX_SYSERR_LEVEL_7; | |
1640 | mxman->last_syserr.type = (u8) ((full_panic_code >> SYSERR_TYPE_POSN) & SYSERR_TYPE_MASK); | |
1641 | mxman->last_syserr.subcode = (u16) ((full_panic_code >> SYSERR_SUB_CODE_POSN) & SYSERR_SUB_CODE_MASK); | |
1642 | } | |
1643 | } | |
1644 | ||
1645 | #define MAX_UHELP_TMO_MS 20000 | |
1646 | /* | |
1647 | * workqueue thread | |
1648 | */ | |
1649 | static void mxman_failure_work(struct work_struct *work) | |
1650 | { | |
1651 | struct mxman *mxman = container_of(work, struct mxman, failure_work); | |
1652 | struct srvman *srvman; | |
1653 | struct scsc_mx *mx = mxman->mx; | |
1654 | struct scsc_mif_abs *mif = scsc_mx_get_mif_abs(mxman->mx); | |
1655 | int used = 0, r = 0; | |
1656 | ||
1657 | #ifdef CONFIG_ANDROID | |
1658 | wake_lock(&mxman->failure_recovery_wake_lock); | |
1659 | #endif | |
1660 | /* Take mutex shared with syserr recovery */ | |
1661 | mutex_lock(&mxman->mxman_recovery_mutex); | |
1662 | ||
1663 | slsi_kic_system_event(slsi_kic_system_event_category_error, | |
1664 | slsi_kic_system_events_subsystem_crashed, GFP_KERNEL); | |
1665 | ||
1666 | blocking_notifier_call_chain(&firmware_chain, SCSC_FW_EVENT_FAILURE, NULL); | |
1667 | ||
1668 | SCSC_TAG_INFO(MXMAN, "Complete mm_msg_start_ind_completion\n"); | |
1669 | complete(&mxman->mm_msg_start_ind_completion); | |
1670 | mutex_lock(&mxman->mxman_mutex); | |
1671 | srvman = scsc_mx_get_srvman(mxman->mx); | |
1672 | ||
1673 | if (mxman->mxman_state != MXMAN_STATE_STARTED && mxman->mxman_state != MXMAN_STATE_STARTING) { | |
1674 | SCSC_TAG_WARNING(MXMAN, "Not in started state: mxman->mxman_state=%d\n", mxman->mxman_state); | |
1675 | #ifdef CONFIG_ANDROID | |
1676 | wake_unlock(&mxman->failure_recovery_wake_lock); | |
1677 | #endif | |
1678 | mutex_unlock(&mxman->mxman_mutex); | |
1679 | mutex_unlock(&mxman->mxman_recovery_mutex); | |
1680 | return; | |
1681 | } | |
1682 | ||
1683 | /** | |
1684 | * Set error on mxlog and unregister mxlog msg-handlers. | |
1685 | * mxlog ISR and kthread will ignore further messages | |
1686 | * but mxlog_thread is NOT stopped here. | |
1687 | */ | |
1688 | mxlog_transport_set_error(scsc_mx_get_mxlog_transport(mx)); | |
1689 | mxlog_release(scsc_mx_get_mxlog(mx)); | |
1690 | /* unregister channel handler */ | |
1691 | mxmgmt_transport_register_channel_handler(scsc_mx_get_mxmgmt_transport(mx), MMTRANS_CHAN_ID_MAXWELL_MANAGEMENT, | |
1692 | NULL, NULL); | |
1693 | mxmgmt_transport_set_error(scsc_mx_get_mxmgmt_transport(mx)); | |
1694 | srvman_set_error(srvman); | |
1695 | fw_crc_wq_stop(mxman); | |
1696 | ||
1697 | mxman->mxman_state = mxman->mxman_next_state; | |
1698 | ||
1699 | /* Mark any single service recovery as no longer in progress */ | |
1700 | mxman->syserr_recovery_in_progress = false; | |
1701 | mxman->last_syserr_recovery_time = 0; | |
1702 | ||
1703 | if (mxman->mxman_state != MXMAN_STATE_FAILED | |
1704 | && mxman->mxman_state != MXMAN_STATE_FROZEN) { | |
1705 | WARN_ON(mxman->mxman_state != MXMAN_STATE_FAILED | |
1706 | && mxman->mxman_state != MXMAN_STATE_FROZEN); | |
1707 | SCSC_TAG_ERR(MXMAN, "Bad state=%d\n", mxman->mxman_state); | |
1708 | #ifdef CONFIG_ANDROID | |
1709 | wake_unlock(&mxman->failure_recovery_wake_lock); | |
1710 | #endif | |
1711 | mutex_unlock(&mxman->mxman_mutex); | |
1712 | mutex_unlock(&mxman->mxman_recovery_mutex); | |
1713 | return; | |
1714 | } | |
1715 | /* Signal panic to r4 and m4 processors */ | |
1716 | SCSC_TAG_INFO(MXMAN, "Setting MIFINTRBIT_RESERVED_PANIC_R4\n"); | |
1717 | mif->irq_bit_set(mif, MIFINTRBIT_RESERVED_PANIC_R4, SCSC_MIF_ABS_TARGET_R4); /* SCSC_MIFINTR_TARGET_R4 */ | |
1718 | #ifdef CONFIG_SCSC_MX450_GDB_SUPPORT | |
1719 | SCSC_TAG_INFO(MXMAN, "Setting MIFINTRBIT_RESERVED_PANIC_M4\n"); | |
1720 | mif->irq_bit_set(mif, MIFINTRBIT_RESERVED_PANIC_M4, SCSC_MIF_ABS_TARGET_M4); /* SCSC_MIFINTR_TARGET_M4 */ | |
1721 | SCSC_TAG_INFO(MXMAN, "Setting MIFINTRBIT_RESERVED_PANIC_M4_1\n"); | |
1722 | mif->irq_bit_set(mif, MIFINTRBIT_RESERVED_PANIC_M4_1, SCSC_MIF_ABS_TARGET_M4_1); /* SCSC_MIFINTR_TARGET_M4 */ | |
1723 | #else | |
1724 | SCSC_TAG_INFO(MXMAN, "Setting MIFINTRBIT_RESERVED_PANIC_M4\n"); | |
1725 | mif->irq_bit_set(mif, MIFINTRBIT_RESERVED_PANIC_M4, SCSC_MIF_ABS_TARGET_M4); /* SCSC_MIFINTR_TARGET_M4 */ | |
1726 | #endif | |
1727 | srvman_freeze_services(srvman, &mxman->last_syserr); | |
1728 | if (mxman->mxman_state == MXMAN_STATE_FAILED) { | |
1729 | mxman->last_panic_time = local_clock(); | |
1730 | process_panic_record(mxman); | |
1731 | SCSC_TAG_INFO(MXMAN, "Trying to schedule coredump\n"); | |
1732 | SCSC_TAG_INFO(MXMAN, "scsc_release %d.%d.%d.%d\n", | |
1733 | SCSC_RELEASE_PRODUCT, | |
1734 | SCSC_RELEASE_ITERATION, | |
1735 | SCSC_RELEASE_CANDIDATE, | |
1736 | SCSC_RELEASE_POINT); | |
1737 | SCSC_TAG_INFO(MXMAN, "Auto-recovery: %s\n", mxman_recovery_disabled() ? "off" : "on"); | |
1738 | #ifdef CONFIG_SCSC_WLBTD | |
1739 | scsc_wlbtd_get_and_print_build_type(); | |
1740 | #endif | |
1741 | ||
1742 | /* schedule coredump and wait for it to finish */ | |
1743 | if (disable_auto_coredump) { | |
1744 | SCSC_TAG_INFO(MXMAN, "Driver automatic coredump disabled, not launching coredump helper\n"); | |
1745 | } else { | |
1746 | /** | |
1747 | * Releasing mxman_mutex here gives way to any | |
1748 | * eventually running resume process while waiting for | |
1749 | * the usermode helper subsystem to be resurrected, | |
1750 | * since this last will be re-enabled right at the end | |
1751 | * of the resume process itself. | |
1752 | */ | |
1753 | mutex_unlock(&mxman->mxman_mutex); | |
1754 | SCSC_TAG_INFO(MXMAN, | |
1755 | "waiting up to %dms for usermode_helper subsystem.\n", | |
1756 | MAX_UHELP_TMO_MS); | |
1757 | /* Waits for the usermode_helper subsytem to be re-enabled. */ | |
1758 | if (usermodehelper_read_lock_wait(msecs_to_jiffies(MAX_UHELP_TMO_MS))) { | |
1759 | /** | |
1760 | * Release immediately the rwsem on usermode_helper | |
1761 | * enabled since we anyway already hold a wakelock here | |
1762 | */ | |
1763 | usermodehelper_read_unlock(); | |
1764 | /** | |
1765 | * We claim back the mxman_mutex immediately to avoid anyone | |
1766 | * shutting down the chip while we are dumping the coredump. | |
1767 | */ | |
1768 | mutex_lock(&mxman->mxman_mutex); | |
1769 | SCSC_TAG_INFO(MXMAN, "Invoking coredump helper\n"); | |
1770 | slsi_kic_system_event(slsi_kic_system_event_category_recovery, | |
1771 | slsi_kic_system_events_coredump_in_progress, | |
1772 | GFP_KERNEL); | |
1773 | #ifdef CONFIG_SCSC_WLBTD | |
1774 | /* we can safely call call_wlbtd as we are | |
1775 | * in workqueue context | |
1776 | */ | |
1777 | #ifdef CONFIG_SCSC_LOG_COLLECTION | |
1778 | /* Collect mxlogger logs */ | |
1779 | scsc_log_collector_schedule_collection(SCSC_LOG_FW_PANIC, mxman->scsc_panic_code); | |
1780 | #else | |
1781 | r = call_wlbtd(SCSC_SCRIPT_MOREDUMP); | |
1782 | #endif | |
1783 | #else | |
1784 | r = coredump_helper(); | |
1785 | #endif | |
1786 | if (r >= 0) { | |
1787 | slsi_kic_system_event(slsi_kic_system_event_category_recovery, | |
1788 | slsi_kic_system_events_coredump_done, GFP_KERNEL); | |
1789 | } | |
1790 | ||
1791 | used = snprintf(panic_record_dump, | |
1792 | PANIC_RECORD_DUMP_BUFFER_SZ, | |
1793 | "RF HW Ver: 0x%X\n", mxman->rf_hw_ver); | |
1794 | used += snprintf(panic_record_dump + used, | |
1795 | PANIC_RECORD_DUMP_BUFFER_SZ - used, | |
1796 | "SCSC Panic Code:: 0x%X\n", mxman->scsc_panic_code); | |
1797 | used += snprintf(panic_record_dump + used, | |
1798 | PANIC_RECORD_DUMP_BUFFER_SZ - used, | |
1799 | "SCSC Last Panic Time:: %lld\n", mxman->last_panic_time); | |
1800 | panic_record_dump_buffer("r4", mxman->last_panic_rec_r, | |
1801 | mxman->last_panic_rec_sz, | |
1802 | panic_record_dump + used, | |
1803 | PANIC_RECORD_DUMP_BUFFER_SZ - used); | |
1804 | ||
1805 | /* Print the host code/reason again so it's near the FW panic | |
1806 | * record in the kernel log | |
1807 | */ | |
1808 | print_panic_code(mxman->scsc_panic_code); | |
1809 | SCSC_TAG_INFO(MXMAN, "Reason: '%s'\n", mxman->failure_reason[0] ? mxman->failure_reason : "<null>"); | |
1810 | ||
1811 | blocking_notifier_call_chain(&firmware_chain, | |
1812 | SCSC_FW_EVENT_MOREDUMP_COMPLETE, | |
1813 | &panic_record_dump); | |
1814 | } else { | |
1815 | SCSC_TAG_INFO(MXMAN, | |
1816 | "timed out waiting for usermode_helper. Skipping coredump.\n"); | |
1817 | mutex_lock(&mxman->mxman_mutex); | |
1818 | } | |
1819 | } | |
1820 | ||
1821 | if (is_bug_on_enabled(mx)) { | |
1822 | SCSC_TAG_ERR(MX_FILE, "Deliberately panic the kernel due to WLBT firmware failure!\n"); | |
1823 | SCSC_TAG_ERR(MX_FILE, "calling BUG_ON(1)\n"); | |
1824 | BUG_ON(1); | |
1825 | } | |
1826 | /* Clean up the MIF following error handling */ | |
1827 | if (mif->mif_cleanup && mxman_recovery_disabled()) | |
1828 | mif->mif_cleanup(mif); | |
1829 | } else { | |
1830 | /* Populate syserr info with panic equivalent for host induced panic */ | |
1831 | mxman->last_syserr.subsys = SYSERR_SUB_SYSTEM_HOST; | |
1832 | mxman->last_syserr.level = MX_SYSERR_LEVEL_7; | |
1833 | mxman->last_syserr.type = 0; | |
1834 | mxman->last_syserr.subcode = mxman->scsc_panic_code; | |
1835 | } | |
1836 | ||
1837 | SCSC_TAG_INFO(MXMAN, "Auto-recovery: %s\n", | |
1838 | mxman_recovery_disabled() ? "off" : "on"); | |
1839 | ||
1840 | if (!mxman_recovery_disabled()) | |
1841 | srvman_clear_error(srvman); | |
1842 | mutex_unlock(&mxman->mxman_mutex); | |
1843 | if (!mxman_recovery_disabled()) { | |
1844 | SCSC_TAG_INFO(MXMAN, "Calling srvman_unfreeze_services\n"); | |
1845 | srvman_unfreeze_services(srvman, mxman->scsc_panic_code); | |
1846 | if (scsc_mx_module_reset() < 0) | |
1847 | SCSC_TAG_INFO(MXMAN, "failed to call scsc_mx_module_reset\n"); | |
1848 | atomic_inc(&mxman->recovery_count); | |
1849 | } | |
1850 | ||
1851 | /** | |
1852 | * If recovery is disabled and an scsc_mx_service_open has been hold up, | |
1853 | * release it, rather than wait for the recovery_completion to timeout. | |
1854 | */ | |
1855 | if (mxman_recovery_disabled()) | |
1856 | complete(&mxman->recovery_completion); | |
1857 | ||
1858 | /* Safe to allow syserr recovery thread to run */ | |
1859 | mutex_unlock(&mxman->mxman_recovery_mutex); | |
1860 | ||
1861 | #ifdef CONFIG_ANDROID | |
1862 | wake_unlock(&mxman->failure_recovery_wake_lock); | |
1863 | #endif | |
1864 | } | |
1865 | ||
1866 | static void failure_wq_init(struct mxman *mxman) | |
1867 | { | |
1868 | mxman->failure_wq = create_singlethread_workqueue("failure_wq"); | |
1869 | INIT_WORK(&mxman->failure_work, mxman_failure_work); | |
1870 | } | |
1871 | ||
1872 | static void failure_wq_stop(struct mxman *mxman) | |
1873 | { | |
1874 | cancel_work_sync(&mxman->failure_work); | |
1875 | flush_workqueue(mxman->failure_wq); | |
1876 | } | |
1877 | ||
1878 | static void failure_wq_deinit(struct mxman *mxman) | |
1879 | { | |
1880 | failure_wq_stop(mxman); | |
1881 | destroy_workqueue(mxman->failure_wq); | |
1882 | } | |
1883 | ||
1884 | static void failure_wq_start(struct mxman *mxman) | |
1885 | { | |
1886 | if (disable_error_handling) | |
1887 | SCSC_TAG_INFO(MXMAN, "error handling disabled\n"); | |
1888 | else | |
1889 | queue_work(mxman->failure_wq, &mxman->failure_work); | |
1890 | } | |
1891 | ||
1892 | /* | |
1893 | * workqueue thread | |
1894 | */ | |
1895 | static void mxman_syserr_recovery_work(struct work_struct *work) | |
1896 | { | |
1897 | struct mxman *mxman = container_of(work, struct mxman, syserr_recovery_work); | |
1898 | struct srvman *srvman; | |
1899 | ||
1900 | #ifdef CONFIG_ANDROID | |
1901 | wake_lock(&mxman->syserr_recovery_wake_lock); | |
1902 | #endif | |
1903 | if (!mutex_trylock(&mxman->mxman_recovery_mutex)) { | |
1904 | SCSC_TAG_WARNING(MXMAN, "Syserr during full reset - ignored\n"); | |
1905 | #ifdef CONFIG_ANDROID | |
1906 | wake_unlock(&mxman->syserr_recovery_wake_lock); | |
1907 | #endif | |
1908 | return; | |
1909 | } | |
1910 | ||
1911 | mutex_lock(&mxman->mxman_mutex); | |
1912 | ||
1913 | if (mxman->mxman_state != MXMAN_STATE_STARTED && mxman->mxman_state != MXMAN_STATE_STARTING) { | |
1914 | SCSC_TAG_WARNING(MXMAN, "Syserr reset ignored: mxman->mxman_state=%d\n", mxman->mxman_state); | |
1915 | #ifdef CONFIG_ANDROID | |
1916 | wake_unlock(&mxman->syserr_recovery_wake_lock); | |
1917 | #endif | |
1918 | mutex_unlock(&mxman->mxman_mutex); | |
1919 | return; | |
1920 | } | |
1921 | ||
1922 | srvman = scsc_mx_get_srvman(mxman->mx); | |
1923 | ||
1924 | srvman_freeze_sub_system(srvman, &mxman->last_syserr); | |
1925 | ||
1926 | #ifdef CONFIG_SCSC_WLBTD | |
1927 | #ifdef CONFIG_SCSC_LOG_COLLECTION | |
1928 | /* Wait for log generation if not finished */ | |
1929 | SCSC_TAG_INFO(MXMAN, "Wait for syserr sable logging\n"); | |
1930 | scsc_wlbtd_wait_for_sable_logging(); | |
1931 | SCSC_TAG_INFO(MXMAN, "Syserr sable logging complete\n"); | |
1932 | #endif | |
1933 | #endif | |
1934 | ||
1935 | srvman_unfreeze_sub_system(srvman, &mxman->last_syserr); | |
1936 | ||
1937 | #ifdef CONFIG_ANDROID | |
1938 | wake_unlock(&mxman->syserr_recovery_wake_lock); | |
1939 | #endif | |
1940 | mutex_unlock(&mxman->mxman_recovery_mutex); | |
1941 | mutex_unlock(&mxman->mxman_mutex); | |
1942 | } | |
1943 | ||
1944 | static void syserr_recovery_wq_init(struct mxman *mxman) | |
1945 | { | |
1946 | mxman->syserr_recovery_wq = create_singlethread_workqueue("syserr_recovery_wq"); | |
1947 | INIT_WORK(&mxman->syserr_recovery_work, mxman_syserr_recovery_work); | |
1948 | } | |
1949 | ||
1950 | static void syserr_recovery_wq_stop(struct mxman *mxman) | |
1951 | { | |
1952 | cancel_work_sync(&mxman->syserr_recovery_work); | |
1953 | flush_workqueue(mxman->syserr_recovery_wq); | |
1954 | } | |
1955 | ||
1956 | static void syserr_recovery_wq_deinit(struct mxman *mxman) | |
1957 | { | |
1958 | syserr_recovery_wq_stop(mxman); | |
1959 | destroy_workqueue(mxman->syserr_recovery_wq); | |
1960 | } | |
1961 | ||
1962 | static void syserr_recovery_wq_start(struct mxman *mxman) | |
1963 | { | |
1964 | queue_work(mxman->syserr_recovery_wq, &mxman->syserr_recovery_work); | |
1965 | } | |
1966 | ||
1967 | static void print_mailboxes(struct mxman *mxman) | |
1968 | { | |
1969 | struct scsc_mif_abs *mif; | |
1970 | struct mifmboxman *mboxman; | |
1971 | int i; | |
1972 | ||
1973 | mif = scsc_mx_get_mif_abs(mxman->mx); | |
1974 | mboxman = scsc_mx_get_mboxman(mxman->mx); | |
1975 | ||
1976 | SCSC_TAG_INFO(MXMAN, "Printing mailbox values:\n"); | |
1977 | for (i = 0; i < MIFMBOX_NUM; i++) | |
1978 | SCSC_TAG_INFO(MXMAN, "MBOX_%d: 0x%x\n", i, *mifmboxman_get_mbox_ptr(mboxman, mif, i)); | |
1979 | } | |
1980 | #ifdef CONFIG_SCSC_WLBTD | |
1981 | static void wlbtd_work_func(struct work_struct *work) | |
1982 | { | |
1983 | /* require sleep-able workqueue to run successfully */ | |
1984 | #ifdef CONFIG_SCSC_LOG_COLLECTION | |
1985 | /* Collect mxlogger logs */ | |
1986 | /* Extend to scsc_log_collector_collect() if required */ | |
1987 | #else | |
1988 | call_wlbtd(SCSC_SCRIPT_LOGGER_DUMP); | |
1989 | #endif | |
1990 | } | |
1991 | ||
1992 | static void wlbtd_wq_init(struct mxman *mx) | |
1993 | { | |
1994 | INIT_WORK(&wlbtd_work, wlbtd_work_func); | |
1995 | } | |
1996 | ||
1997 | static void wlbtd_wq_deinit(struct mxman *mx) | |
1998 | { | |
1999 | /* flush and block until work is complete */ | |
2000 | flush_work(&wlbtd_work); | |
2001 | } | |
2002 | #endif | |
2003 | /* | |
2004 | * Check for matching f/w and h/w | |
2005 | * | |
2006 | * Returns 0: f/w and h/w match | |
2007 | * 1: f/w and h/w mismatch, try the next config | |
2008 | * -ve fatal error | |
2009 | */ | |
2010 | static int mxman_hw_ver_check(struct mxman *mxman) | |
2011 | { | |
2012 | if (mx140_file_supported_hw(mxman->mx, mxman->rf_hw_ver)) | |
2013 | return 0; | |
2014 | else | |
2015 | return 1; | |
2016 | } | |
2017 | ||
2018 | /* | |
2019 | * Select the f/w version to load next | |
2020 | */ | |
2021 | static int mxman_select_next_fw(struct mxman *mxman) | |
2022 | { | |
2023 | return mx140_file_select_fw(mxman->mx, mxman->rf_hw_ver); | |
2024 | } | |
2025 | ||
2026 | /* Boot MX140 with given f/w */ | |
2027 | static int __mxman_open(struct mxman *mxman) | |
2028 | { | |
2029 | int r; | |
2030 | struct srvman *srvman; | |
2031 | ||
2032 | mx140_basedir_file(mxman->mx); | |
2033 | ||
2034 | mutex_lock(&mxman->mxman_mutex); | |
2035 | if (mxman->scsc_panic_code) { | |
2036 | SCSC_TAG_INFO(MXMAN, "Previously recorded crash panic code: scsc_panic_code=0x%x\n", mxman->scsc_panic_code); | |
2037 | SCSC_TAG_INFO(MXMAN, "Reason: '%s'\n", mxman->failure_reason[0] ? mxman->failure_reason : "<null>"); | |
2038 | print_panic_code(mxman->scsc_panic_code); | |
2039 | } | |
2040 | SCSC_TAG_INFO(MXMAN, "Auto-recovery: %s\n", mxman_recovery_disabled() ? "off" : "on"); | |
2041 | srvman = scsc_mx_get_srvman(mxman->mx); | |
2042 | if (srvman && srvman->error) { | |
2043 | mutex_unlock(&mxman->mxman_mutex); | |
2044 | SCSC_TAG_INFO(MXMAN, "Called during error - ignore\n"); | |
2045 | return -EINVAL; | |
2046 | } | |
2047 | ||
2048 | /* Reset the state after a previous crash during f/w boot */ | |
2049 | if (mxman->mxman_state == MXMAN_STATE_STARTING) | |
2050 | mxman->mxman_state = MXMAN_STATE_STOPPED; | |
2051 | ||
2052 | if (mxman->mxman_state == MXMAN_STATE_STARTED) { | |
2053 | /* if in the STARTED state there MUST already be some users */ | |
2054 | if (WARN_ON(!mxman->users)) { | |
2055 | SCSC_TAG_ERR(MXMAN, "ERROR mxman->mxman_state=%d users=%d\n", mxman->mxman_state, mxman->users); | |
2056 | mutex_unlock(&mxman->mxman_mutex); | |
2057 | return -EINVAL; | |
2058 | } | |
2059 | mxman->users++; | |
2060 | SCSC_TAG_INFO(MXMAN, "Already opened: users=%d\n", mxman->users); | |
2061 | mxman_print_versions(mxman); | |
2062 | mutex_unlock(&mxman->mxman_mutex); | |
2063 | return 0; | |
2064 | } else if (mxman->mxman_state == MXMAN_STATE_STOPPED) { | |
2065 | r = mxman_start(mxman); | |
2066 | if (r) { | |
2067 | SCSC_TAG_ERR(MXMAN, "maxwell_manager_start() failed r=%d users=%d\n", r, mxman->users); | |
2068 | mutex_unlock(&mxman->mxman_mutex); | |
2069 | return r; | |
2070 | } | |
2071 | mxman->users++; | |
2072 | mxman->mxman_state = MXMAN_STATE_STARTED; | |
2073 | mutex_unlock(&mxman->mxman_mutex); | |
2074 | /* Start mxlogger */ | |
2075 | if (!disable_logger) { | |
2076 | static char mxlbin[128]; | |
2077 | ||
2078 | r = mx140_exe_path(NULL, mxlbin, sizeof(mxlbin), "mx_logger.sh"); | |
2079 | if (r) { | |
2080 | /* Not found */ | |
2081 | SCSC_TAG_ERR(MXMAN, "mx_logger.sh path error\n"); | |
2082 | } else { | |
2083 | /* Launch it */ | |
2084 | _mx_exec(mxlbin, UMH_WAIT_EXEC); | |
2085 | } | |
2086 | } | |
2087 | return 0; | |
2088 | } | |
2089 | WARN_ON(mxman->mxman_state != MXMAN_STATE_STARTED && mxman->mxman_state != MXMAN_STATE_STOPPED); | |
2090 | SCSC_TAG_ERR(MXMAN, "Bad state: mxman->mxman_state=%d\n", mxman->mxman_state); | |
2091 | mutex_unlock(&mxman->mxman_mutex); | |
2092 | return -EIO; | |
2093 | } | |
2094 | ||
2095 | int mxman_open(struct mxman *mxman) | |
2096 | { | |
2097 | int r; | |
2098 | int try = 0; | |
2099 | ||
2100 | struct scsc_mif_abs *mif = scsc_mx_get_mif_abs(mxman->mx); | |
2101 | ||
2102 | for (try = 0; try < 2; try++) { | |
2103 | /* Boot WLBT. This will determine the h/w version */ | |
2104 | r = __mxman_open(mxman); | |
2105 | if (r) | |
2106 | return r; | |
2107 | ||
2108 | /* On retries, restore USBPLL owner as WLBT */ | |
2109 | if (try > 0 && mif->mif_restart) | |
2110 | mif->mif_restart(mif); | |
2111 | ||
2112 | /* Check the h/w and f/w versions are compatible */ | |
2113 | r = mxman_hw_ver_check(mxman); | |
2114 | if (r > 0) { | |
2115 | /* Not compatible, so try next f/w */ | |
2116 | SCSC_TAG_INFO(MXMAN, "Incompatible h/w 0x%04x vs f/w, close and try next\n", mxman->rf_hw_ver); | |
2117 | ||
2118 | /* Temporarily return USBPLL owner to AP to keep USB alive */ | |
2119 | if (mif->mif_cleanup) | |
2120 | mif->mif_cleanup(mif); | |
2121 | ||
2122 | /* Stop WLBT */ | |
2123 | mxman_close(mxman); | |
2124 | ||
2125 | /* Select the new f/w for this hw ver */ | |
2126 | mxman_select_next_fw(mxman); | |
2127 | } else | |
2128 | break; /* Running or given up */ | |
2129 | } | |
2130 | ||
2131 | #ifdef CONFIG_SCSC_FM | |
2132 | /* If we have stored FM radio parameters, deliver them to FW now */ | |
2133 | if (r == 0 && mxman->fm_params_pending) { | |
2134 | SCSC_TAG_INFO(MXMAN, "Send pending FM params\n"); | |
2135 | mxman_fm_set_params(&mxman->fm_params); | |
2136 | } | |
2137 | #endif | |
2138 | ||
2139 | return r; | |
2140 | } | |
2141 | ||
2142 | static void mxman_stop(struct mxman *mxman) | |
2143 | { | |
2144 | int r; | |
2145 | struct scsc_mif_abs *mif; | |
2146 | ||
2147 | SCSC_TAG_INFO(MXMAN, "\n"); | |
2148 | ||
2149 | /* If reset is failed, prevent new resets */ | |
2150 | if (reset_failed) { | |
2151 | struct timeval tval = ns_to_timeval(reset_failed_time); | |
2152 | ||
2153 | SCSC_TAG_ERR(MXMAN, "previous reset failed at [%6lu.%06ld], ignoring\n", tval.tv_sec, tval.tv_usec); | |
2154 | return; | |
2155 | } | |
2156 | ||
2157 | (void)snprintf(mxman->fw_build_id, sizeof(mxman->fw_build_id), "unknown"); | |
2158 | ||
2159 | mxproc_remove_ctrl_proc_dir(&mxman->mxproc); | |
2160 | ||
2161 | /* Shutdown the hardware */ | |
2162 | mif = scsc_mx_get_mif_abs(mxman->mx); | |
2163 | r = mif->reset(mif, 1); | |
2164 | if (r) { | |
2165 | reset_failed_time = local_clock(); | |
2166 | SCSC_TAG_INFO(MXMAN, "HW reset failed\n"); | |
2167 | reset_failed = true; | |
2168 | ||
2169 | /* Save log at point of failure */ | |
2170 | #ifdef CONFIG_SCSC_LOG_COLLECTION | |
2171 | scsc_log_collector_schedule_collection(SCSC_LOG_HOST_COMMON, SCSC_LOG_HOST_COMMON_REASON_STOP); | |
2172 | #else | |
2173 | mx140_log_dump(); | |
2174 | #endif | |
2175 | } | |
2176 | ||
2177 | panicmon_deinit(scsc_mx_get_panicmon(mxman->mx)); | |
2178 | transports_release(mxman); | |
2179 | mxfwconfig_unload(mxman->mx); | |
2180 | ||
2181 | mxlog_release(scsc_mx_get_mxlog(mxman->mx)); | |
2182 | /* unregister channel handler */ | |
2183 | mxmgmt_transport_register_channel_handler(scsc_mx_get_mxmgmt_transport(mxman->mx), MMTRANS_CHAN_ID_MAXWELL_MANAGEMENT, | |
2184 | NULL, NULL); | |
2185 | fw_crc_wq_stop(mxman); | |
2186 | ||
2187 | /* Unitialise components (they may perform some checks - e.g. all memory freed) */ | |
2188 | mxfwconfig_deinit(mxman->mx); | |
2189 | mifintrbit_deinit(scsc_mx_get_intrbit(mxman->mx)); | |
2190 | miframman_deinit(scsc_mx_get_ramman(mxman->mx)); | |
2191 | miframman_deinit(scsc_mx_get_ramman2(mxman->mx)); | |
2192 | miframabox_deinit(scsc_mx_get_aboxram(mxman->mx)); | |
2193 | mifmboxman_deinit(scsc_mx_get_mboxman(mxman->mx)); | |
2194 | #ifdef CONFIG_SCSC_SMAPPER | |
2195 | mifsmapper_deinit(scsc_mx_get_smapper(mxman->mx)); | |
2196 | #endif | |
2197 | #ifdef CONFIG_SCSC_QOS | |
2198 | mifqos_deinit(scsc_mx_get_qos(mxman->mx)); | |
2199 | #endif | |
2200 | /* Release the MIF memory resources */ | |
2201 | mif->unmap(mif, mxman->start_dram); | |
2202 | } | |
2203 | ||
2204 | void mxman_close(struct mxman *mxman) | |
2205 | { | |
2206 | int r; | |
2207 | struct srvman *srvman; | |
2208 | ||
2209 | mutex_lock(&mxman->mxman_mutex); | |
2210 | srvman = scsc_mx_get_srvman(mxman->mx); | |
2211 | if (srvman && srvman->error) { | |
2212 | mutex_unlock(&mxman->mxman_mutex); | |
2213 | SCSC_TAG_INFO(MXMAN, "Called during error - ignore\n"); | |
2214 | return; | |
2215 | } | |
2216 | ||
2217 | SCSC_TAG_INFO(MXMAN, "\n"); | |
2218 | ||
2219 | if (mxman->mxman_state == MXMAN_STATE_STARTED) { | |
2220 | if (WARN_ON(!mxman->users)) { | |
2221 | SCSC_TAG_ERR(MXMAN, "ERROR users=%d\n", mxman->users); | |
2222 | mutex_unlock(&mxman->mxman_mutex); | |
2223 | return; | |
2224 | } | |
2225 | mxman->users--; | |
2226 | if (mxman->users) { | |
2227 | SCSC_TAG_INFO(MXMAN, "Current number of users=%d\n", mxman->users); | |
2228 | mutex_unlock(&mxman->mxman_mutex); | |
2229 | return; | |
2230 | } | |
2231 | #ifdef CONFIG_SCSC_MXLOGGER | |
2232 | #ifdef CONFIG_SCSC_LOG_COLLECTION | |
2233 | /* Unregister minimoredump client */ | |
2234 | scsc_log_collector_unregister_client(&mini_moredump_client); | |
2235 | #endif | |
2236 | /** | |
2237 | * Deinit mxlogger on last service stop...BUT before asking for HALT | |
2238 | */ | |
2239 | mxlogger_deinit(mxman->mx, scsc_mx_get_mxlogger(mxman->mx)); | |
2240 | #endif | |
2241 | /* | |
2242 | * Ask the subsystem to stop (MM_STOP_REQ), and wait | |
2243 | * for response (MM_STOP_RSP). | |
2244 | */ | |
2245 | r = send_mm_msg_stop_blocking(mxman); | |
2246 | if (r) | |
2247 | SCSC_TAG_ERR(MXMAN, "send_mm_msg_stop_blocking failed: r=%d\n", r); | |
2248 | ||
2249 | mxman_stop(mxman); | |
2250 | mxman->mxman_state = MXMAN_STATE_STOPPED; | |
2251 | mutex_unlock(&mxman->mxman_mutex); | |
2252 | } else if (mxman->mxman_state == MXMAN_STATE_FAILED) { | |
2253 | if (WARN_ON(!mxman->users)) | |
2254 | SCSC_TAG_ERR(MXMAN, "ERROR users=%d\n", mxman->users); | |
2255 | ||
2256 | mxman->users--; | |
2257 | if (mxman->users) { | |
2258 | SCSC_TAG_INFO(MXMAN, "Current number of users=%d\n", mxman->users); | |
2259 | mutex_unlock(&mxman->mxman_mutex); | |
2260 | return; | |
2261 | } | |
2262 | #ifdef CONFIG_SCSC_MXLOGGER | |
2263 | #ifdef CONFIG_SCSC_LOG_COLLECTION | |
2264 | /* Unregister minimoredump client */ | |
2265 | scsc_log_collector_unregister_client(&mini_moredump_client); | |
2266 | #endif | |
2267 | /** | |
2268 | * Deinit mxlogger on last service stop...BUT before asking for HALT | |
2269 | */ | |
2270 | mxlogger_deinit(mxman->mx, scsc_mx_get_mxlogger(mxman->mx)); | |
2271 | #endif | |
2272 | ||
2273 | mxman_stop(mxman); | |
2274 | mxman->mxman_state = MXMAN_STATE_STOPPED; | |
2275 | mutex_unlock(&mxman->mxman_mutex); | |
2276 | complete(&mxman->recovery_completion); | |
2277 | } else { | |
2278 | WARN_ON(mxman->mxman_state != MXMAN_STATE_STARTED); | |
2279 | SCSC_TAG_ERR(MXMAN, "Bad state: mxman->mxman_state=%d\n", mxman->mxman_state); | |
2280 | mutex_unlock(&mxman->mxman_mutex); | |
2281 | return; | |
2282 | } | |
2283 | } | |
2284 | ||
2285 | void mxman_syserr(struct mxman *mxman, struct mx_syserr_decode *syserr) | |
2286 | { | |
2287 | mxman->syserr_recovery_in_progress = true; | |
2288 | ||
2289 | mxman->last_syserr.subsys = syserr->subsys; | |
2290 | mxman->last_syserr.level = syserr->level; | |
2291 | mxman->last_syserr.type = syserr->type; | |
2292 | mxman->last_syserr.subcode = syserr->subcode; | |
2293 | ||
2294 | syserr_recovery_wq_start(mxman); | |
2295 | } | |
2296 | ||
2297 | void mxman_fail(struct mxman *mxman, u16 scsc_panic_code, const char *reason) | |
2298 | { | |
2299 | SCSC_TAG_WARNING(MXMAN, "WLBT FW failure\n"); | |
2300 | ||
2301 | /* The STARTING state allows a crash during firmware boot to be handled */ | |
2302 | if (mxman->mxman_state == MXMAN_STATE_STARTED || mxman->mxman_state == MXMAN_STATE_STARTING) { | |
2303 | mxman->mxman_next_state = MXMAN_STATE_FAILED; | |
2304 | mxman->scsc_panic_code = scsc_panic_code; | |
2305 | strlcpy(mxman->failure_reason, reason, sizeof(mxman->failure_reason)); | |
2306 | /* If recovery is disabled, don't let it be | |
2307 | * re-enabled from now on. Device must reboot | |
2308 | */ | |
2309 | if (mxman_recovery_disabled()) | |
2310 | disable_recovery_until_reboot = true; | |
2311 | ||
2312 | failure_wq_start(mxman); | |
2313 | } else { | |
2314 | SCSC_TAG_WARNING(MXMAN, "Not in MXMAN_STATE_STARTED state, ignore (state %d)\n", mxman->mxman_state); | |
2315 | } | |
2316 | ||
2317 | /* Populate syserr info with panic equivalent or best we can */ | |
2318 | mxman->last_syserr.subsys = scsc_panic_code >> SYSERR_SUB_SYSTEM_POSN; | |
2319 | mxman->last_syserr.level = MX_SYSERR_LEVEL_7; | |
2320 | mxman->last_syserr.type = scsc_panic_code; | |
2321 | mxman->last_syserr.subcode = scsc_panic_code; | |
2322 | } | |
2323 | ||
2324 | void mxman_freeze(struct mxman *mxman) | |
2325 | { | |
2326 | SCSC_TAG_WARNING(MXMAN, "WLBT FW frozen\n"); | |
2327 | ||
2328 | if (mxman->mxman_state == MXMAN_STATE_STARTED) { | |
2329 | mxman->mxman_next_state = MXMAN_STATE_FROZEN; | |
2330 | failure_wq_start(mxman); | |
2331 | } else { | |
2332 | SCSC_TAG_WARNING(MXMAN, "Not in MXMAN_STATE_STARTED state, ignore (state %d)\n", mxman->mxman_state); | |
2333 | } | |
2334 | } | |
2335 | ||
2336 | void mxman_init(struct mxman *mxman, struct scsc_mx *mx) | |
2337 | { | |
2338 | mxman->mx = mx; | |
2339 | mxman->suspended = 0; | |
2340 | #ifdef CONFIG_SCSC_FM | |
2341 | mxman->on_halt_ldos_on = 0; | |
2342 | mxman->fm_params_pending = 0; | |
2343 | #endif | |
2344 | fw_crc_wq_init(mxman); | |
2345 | failure_wq_init(mxman); | |
2346 | syserr_recovery_wq_init(mxman); | |
2347 | #ifdef CONFIG_SCSC_WLBTD | |
2348 | wlbtd_wq_init(mxman); | |
2349 | #endif | |
2350 | mutex_init(&mxman->mxman_mutex); | |
2351 | mutex_init(&mxman->mxman_recovery_mutex); | |
2352 | init_completion(&mxman->recovery_completion); | |
2353 | #ifdef CONFIG_ANDROID | |
2354 | wake_lock_init(&mxman->failure_recovery_wake_lock, WAKE_LOCK_SUSPEND, "mxman_recovery"); | |
2355 | wake_lock_init(&mxman->syserr_recovery_wake_lock, WAKE_LOCK_SUSPEND, "mxman_syserr_recovery"); | |
2356 | #endif | |
2357 | mxman->syserr_recovery_in_progress = false; | |
2358 | mxman->last_syserr_recovery_time = 0; | |
2359 | ||
2360 | /* set the initial state */ | |
2361 | mxman->mxman_state = MXMAN_STATE_STOPPED; | |
2362 | (void)snprintf(mxman->fw_build_id, sizeof(mxman->fw_build_id), "unknown"); | |
2363 | memcpy(saved_fw_build_id, mxman->fw_build_id, | |
2364 | sizeof(saved_fw_build_id)); | |
2365 | (void)snprintf(mxman->fw_ttid, sizeof(mxman->fw_ttid), "unknown"); | |
2366 | mxproc_create_info_proc_dir(&mxman->mxproc, mxman); | |
2367 | active_mxman = mxman; | |
2368 | ||
2369 | #if defined(ANDROID_VERSION) && ANDROID_VERSION >= 90000 | |
2370 | mxman_create_sysfs_memdump(); | |
2371 | #endif | |
2372 | scsc_lerna_init(); | |
2373 | } | |
2374 | ||
2375 | void mxman_deinit(struct mxman *mxman) | |
2376 | { | |
2377 | scsc_lerna_deinit(); | |
2378 | #if defined(ANDROID_VERSION) && ANDROID_VERSION >= 90000 | |
2379 | mxman_destroy_sysfs_memdump(); | |
2380 | #endif | |
2381 | active_mxman = NULL; | |
2382 | mxproc_remove_info_proc_dir(&mxman->mxproc); | |
2383 | fw_crc_wq_deinit(mxman); | |
2384 | failure_wq_deinit(mxman); | |
2385 | syserr_recovery_wq_deinit(mxman); | |
2386 | #ifdef CONFIG_SCSC_WLBTD | |
2387 | wlbtd_wq_deinit(mxman); | |
2388 | #endif | |
2389 | #ifdef CONFIG_ANDROID | |
2390 | wake_lock_destroy(&mxman->failure_recovery_wake_lock); | |
2391 | wake_lock_destroy(&mxman->syserr_recovery_wake_lock); | |
2392 | #endif | |
2393 | mutex_destroy(&mxman->mxman_recovery_mutex); | |
2394 | mutex_destroy(&mxman->mxman_mutex); | |
2395 | } | |
2396 | ||
2397 | int mxman_force_panic(struct mxman *mxman) | |
2398 | { | |
2399 | struct srvman *srvman; | |
2400 | struct ma_msg_packet message = { .ma_msg = MM_FORCE_PANIC }; | |
2401 | ||
2402 | mutex_lock(&mxman->mxman_mutex); | |
2403 | srvman = scsc_mx_get_srvman(mxman->mx); | |
2404 | if (srvman && srvman->error) { | |
2405 | mutex_unlock(&mxman->mxman_mutex); | |
2406 | SCSC_TAG_INFO(MXMAN, "Called during error - ignore\n"); | |
2407 | return -EINVAL; | |
2408 | } | |
2409 | ||
2410 | if (mxman->mxman_state == MXMAN_STATE_STARTED) { | |
2411 | mxmgmt_transport_send(scsc_mx_get_mxmgmt_transport(mxman->mx), MMTRANS_CHAN_ID_MAXWELL_MANAGEMENT, &message, sizeof(message)); | |
2412 | mutex_unlock(&mxman->mxman_mutex); | |
2413 | return 0; | |
2414 | } | |
2415 | mutex_unlock(&mxman->mxman_mutex); | |
2416 | return -EINVAL; | |
2417 | } | |
2418 | ||
2419 | int mxman_suspend(struct mxman *mxman) | |
2420 | { | |
2421 | struct srvman *srvman; | |
2422 | struct ma_msg_packet message = { .ma_msg = MM_HOST_SUSPEND }; | |
2423 | int ret; | |
2424 | ||
2425 | SCSC_TAG_INFO(MXMAN, "\n"); | |
2426 | ||
2427 | mutex_lock(&mxman->mxman_mutex); | |
2428 | srvman = scsc_mx_get_srvman(mxman->mx); | |
2429 | if (srvman && srvman->error) { | |
2430 | mutex_unlock(&mxman->mxman_mutex); | |
2431 | SCSC_TAG_INFO(MXMAN, "Called during error - ignore\n"); | |
2432 | return 0; | |
2433 | } | |
2434 | ||
2435 | /* Call Service suspend callbacks */ | |
2436 | ret = srvman_suspend_services(srvman); | |
2437 | if (ret) { | |
2438 | mutex_unlock(&mxman->mxman_mutex); | |
2439 | SCSC_TAG_INFO(MXMAN, "Service Suspend canceled - ignore %d\n", ret); | |
2440 | return ret; | |
2441 | } | |
2442 | ||
2443 | if (mxman->mxman_state == MXMAN_STATE_STARTED) { | |
2444 | SCSC_TAG_INFO(MXMAN, "MM_HOST_SUSPEND\n"); | |
2445 | #ifdef CONFIG_SCSC_MXLOGGER | |
2446 | mxlogger_generate_sync_record(scsc_mx_get_mxlogger(mxman->mx), MXLOGGER_SYN_SUSPEND); | |
2447 | #endif | |
2448 | mxmgmt_transport_send(scsc_mx_get_mxmgmt_transport(mxman->mx), MMTRANS_CHAN_ID_MAXWELL_MANAGEMENT, &message, sizeof(message)); | |
2449 | mxman->suspended = 1; | |
2450 | atomic_inc(&mxman->suspend_count); | |
2451 | } | |
2452 | mutex_unlock(&mxman->mxman_mutex); | |
2453 | return 0; | |
2454 | } | |
2455 | ||
2456 | #ifdef CONFIG_SCSC_FM | |
2457 | void mxman_fm_on_halt_ldos_on(void) | |
2458 | { | |
2459 | /* Should always be an active mxman unless module is unloaded */ | |
2460 | if (!active_mxman) { | |
2461 | SCSC_TAG_ERR(MXMAN, "No active MXMAN\n"); | |
2462 | return; | |
2463 | } | |
2464 | ||
2465 | active_mxman->on_halt_ldos_on = 1; | |
2466 | ||
2467 | /* FM status to pass into FW at next FW init, | |
2468 | * by which time driver context is lost. | |
2469 | * This is required, because now WLBT gates | |
2470 | * LDOs with TCXO instead of leaving them | |
2471 | * always on, to save power in deep sleep. | |
2472 | * FM, however, needs them always on. So | |
2473 | * we need to know when to leave the LDOs | |
2474 | * alone at WLBT boot. | |
2475 | */ | |
2476 | is_fm_on = 1; | |
2477 | } | |
2478 | ||
2479 | void mxman_fm_on_halt_ldos_off(void) | |
2480 | { | |
2481 | /* Should always be an active mxman unless module is unloaded */ | |
2482 | if (!active_mxman) { | |
2483 | SCSC_TAG_ERR(MXMAN, "No active MXMAN\n"); | |
2484 | return; | |
2485 | } | |
2486 | ||
2487 | /* Newer FW no longer need set shared LDOs | |
2488 | * always-off at WLBT halt, as TCXO gating | |
2489 | * has the same effect. But pass the "off" | |
2490 | * request for backwards compatibility | |
2491 | * with old FW. | |
2492 | */ | |
2493 | active_mxman->on_halt_ldos_on = 0; | |
2494 | is_fm_on = 0; | |
2495 | } | |
2496 | ||
2497 | /* Update parameters passed to WLBT FM */ | |
2498 | int mxman_fm_set_params(struct wlbt_fm_params *params) | |
2499 | { | |
2500 | /* Should always be an active mxman unless module is unloaded */ | |
2501 | if (!active_mxman) { | |
2502 | SCSC_TAG_ERR(MXMAN, "No active MXMAN\n"); | |
2503 | return -EINVAL; | |
2504 | } | |
2505 | ||
2506 | /* Params are no longer valid (FM stopped) */ | |
2507 | if (!params) { | |
2508 | active_mxman->fm_params_pending = 0; | |
2509 | SCSC_TAG_INFO(MXMAN, "FM params cleared\n"); | |
2510 | return 0; | |
2511 | } | |
2512 | ||
2513 | /* Once set the value needs to be remembered for each time WLBT starts */ | |
2514 | active_mxman->fm_params = *params; | |
2515 | active_mxman->fm_params_pending = 1; | |
2516 | ||
2517 | if (send_fm_params_to_active_mxman(params)) { | |
2518 | SCSC_TAG_INFO(MXMAN, "FM params sent to FW\n"); | |
2519 | return 0; | |
2520 | } | |
2521 | ||
2522 | /* Stored for next time FW is up */ | |
2523 | SCSC_TAG_INFO(MXMAN, "FM params stored\n"); | |
2524 | ||
2525 | return -EAGAIN; | |
2526 | } | |
2527 | #endif | |
2528 | ||
2529 | void mxman_resume(struct mxman *mxman) | |
2530 | { | |
2531 | struct srvman *srvman; | |
2532 | struct ma_msg_packet message = { .ma_msg = MM_HOST_RESUME }; | |
2533 | int ret; | |
2534 | ||
2535 | SCSC_TAG_INFO(MXMAN, "\n"); | |
2536 | ||
2537 | mutex_lock(&mxman->mxman_mutex); | |
2538 | srvman = scsc_mx_get_srvman(mxman->mx); | |
2539 | if (srvman && srvman->error) { | |
2540 | mutex_unlock(&mxman->mxman_mutex); | |
2541 | SCSC_TAG_INFO(MXMAN, "Called during error - ignore\n"); | |
2542 | return; | |
2543 | } | |
2544 | ||
2545 | if (mxman->mxman_state == MXMAN_STATE_STARTED) { | |
2546 | SCSC_TAG_INFO(MXMAN, "MM_HOST_RESUME\n"); | |
2547 | #ifdef CONFIG_SCSC_MXLOGGER | |
2548 | mxlogger_generate_sync_record(scsc_mx_get_mxlogger(mxman->mx), MXLOGGER_SYN_RESUME); | |
2549 | #endif | |
2550 | mxmgmt_transport_send(scsc_mx_get_mxmgmt_transport(mxman->mx), MMTRANS_CHAN_ID_MAXWELL_MANAGEMENT, &message, sizeof(message)); | |
2551 | mxman->suspended = 0; | |
2552 | } | |
2553 | ||
2554 | /* Call Service Resume callbacks */ | |
2555 | ret = srvman_resume_services(srvman); | |
2556 | if (ret) | |
2557 | SCSC_TAG_INFO(MXMAN, "Service Resume error %d\n", ret); | |
2558 | ||
2559 | mutex_unlock(&mxman->mxman_mutex); | |
2560 | } | |
2561 | ||
2562 | static void _mx_exec_cleanup(struct subprocess_info *sp_info) | |
2563 | { | |
2564 | if (!sp_info) { | |
2565 | SCSC_TAG_ERR(MXMAN, "sp_info is null\n"); | |
2566 | return; | |
2567 | } | |
2568 | if (!sp_info->argv) { | |
2569 | SCSC_TAG_ERR(MXMAN, "argv is null\n"); | |
2570 | return; | |
2571 | } | |
2572 | ||
2573 | SCSC_TAG_INFO(MXMAN, "0x%p\n", sp_info->argv); | |
2574 | argv_free(sp_info->argv); | |
2575 | } | |
2576 | ||
2577 | /* prog - full path to programme | |
2578 | * wait_exec - one of UMH_WAIT_EXEC, UMH_WAIT_PROC, UMH_KILLABLE, UMH_NO_WAIT | |
2579 | */ | |
2580 | static int _mx_exec(char *prog, int wait_exec) | |
2581 | { | |
2582 | /** | |
2583 | * ENV vars ANDROID_ROOT and ANDROID_DATA are needed to have | |
2584 | * the UMH spawned process working properly (as an example finding | |
2585 | * Timezones files) | |
2586 | */ | |
2587 | static char const *envp[] = { "HOME=/", "PATH=/sbin:/system/sbin:/system/bin:/system/xbin:/vendor/bin:/vendor/xbin", | |
2588 | "ANDROID_ROOT=/system", "ANDROID_DATA=/data", NULL }; | |
2589 | char **argv; | |
2590 | char argv_str[STRING_BUFFER_MAX_LENGTH]; | |
2591 | int argc, result, len; | |
2592 | struct subprocess_info *sp_info; | |
2593 | ||
2594 | len = snprintf(argv_str, STRING_BUFFER_MAX_LENGTH, "%s", prog); | |
2595 | if (len >= STRING_BUFFER_MAX_LENGTH) { | |
2596 | /* snprintf() returns a value of buffer size of greater if it had to truncate the format string. */ | |
2597 | SCSC_TAG_ERR(MXMAN, | |
2598 | "exec string buffer insufficient (buffer size=%d, actual string=%d)\n", | |
2599 | STRING_BUFFER_MAX_LENGTH, len); | |
2600 | return -E2BIG; | |
2601 | } | |
2602 | ||
2603 | /* Kernel library function argv_split() will allocate memory for argv. */ | |
2604 | argc = 0; | |
2605 | argv = argv_split(GFP_KERNEL, argv_str, &argc); | |
2606 | if (!argv) { | |
2607 | SCSC_TAG_ERR(MXMAN, "failed to allocate argv for userspace helper\n"); | |
2608 | return -ENOMEM; | |
2609 | } | |
2610 | ||
2611 | /* Check the argument count just to avoid future abuse */ | |
2612 | if (argc > NUMBER_OF_STRING_ARGS) { | |
2613 | SCSC_TAG_ERR(MXMAN, | |
2614 | "exec string has the wrong number of arguments (has %d, should be %d)\n", | |
2615 | argc, NUMBER_OF_STRING_ARGS); | |
2616 | argv_free(argv); | |
2617 | return -E2BIG; | |
2618 | } | |
2619 | ||
2620 | /* Allocate sp_info and initialise pointers to argv and envp. */ | |
2621 | sp_info = call_usermodehelper_setup(argv[0], argv, (char **)envp, | |
2622 | GFP_KERNEL, NULL, _mx_exec_cleanup, | |
2623 | NULL); | |
2624 | ||
2625 | if (!sp_info) { | |
2626 | SCSC_TAG_ERR(MXMAN, "call_usermodehelper_setup() failed\n"); | |
2627 | argv_free(argv); | |
2628 | return -EIO; | |
2629 | } | |
2630 | ||
2631 | /* Put sp_info into work queue for processing by khelper. */ | |
2632 | SCSC_TAG_INFO(MXMAN, "Launch %s\n", prog); | |
2633 | ||
2634 | result = call_usermodehelper_exec(sp_info, wait_exec); | |
2635 | ||
2636 | if (result != 0) { | |
2637 | /* | |
2638 | * call_usermodehelper_exec() will free sp_info and call any cleanup function | |
2639 | * whether it succeeds or fails, so do not free argv. | |
2640 | */ | |
2641 | if (result == -ENOENT) | |
2642 | SCSC_TAG_ERR(MXMAN, "call_usermodehelper() failed with %d, Executable not found %s'\n", | |
2643 | result, prog); | |
2644 | else | |
2645 | SCSC_TAG_ERR(MXMAN, "call_usermodehelper_exec() failed with %d\n", result); | |
2646 | } | |
2647 | return result; | |
2648 | } | |
2649 | ||
2650 | #if defined(CONFIG_SCSC_PRINTK) && !defined(CONFIG_SCSC_WLBTD) | |
2651 | static int __stat(const char *file) | |
2652 | { | |
2653 | struct kstat stat; | |
2654 | mm_segment_t fs; | |
2655 | int r; | |
2656 | ||
2657 | fs = get_fs(); | |
2658 | set_fs(get_ds()); | |
2659 | r = vfs_stat(file, &stat); | |
2660 | set_fs(fs); | |
2661 | ||
2662 | return r; | |
2663 | } | |
2664 | #endif | |
2665 | ||
2666 | int mx140_log_dump(void) | |
2667 | { | |
2668 | #ifdef CONFIG_SCSC_PRINTK | |
2669 | int r; | |
2670 | # ifdef CONFIG_SCSC_WLBTD | |
2671 | r = schedule_work(&wlbtd_work); | |
2672 | # else | |
2673 | char mxlbin[128]; | |
2674 | ||
2675 | r = mx140_exe_path(NULL, mxlbin, sizeof(mxlbin), "mx_logger_dump.sh"); | |
2676 | if (r) { | |
2677 | SCSC_TAG_ERR(MXMAN, "mx_logger_dump.sh path error\n"); | |
2678 | } else { | |
2679 | /* | |
2680 | * Test presence of script before invoking, to suppress | |
2681 | * unnecessary error message if not installed. | |
2682 | */ | |
2683 | r = __stat(mxlbin); | |
2684 | if (r) { | |
2685 | SCSC_TAG_DEBUG(MXMAN, "%s not installed\n", mxlbin); | |
2686 | return r; | |
2687 | } | |
2688 | SCSC_TAG_INFO(MXMAN, "Invoking mx_logger_dump.sh UHM\n"); | |
2689 | r = _mx_exec(mxlbin, UMH_WAIT_EXEC); | |
2690 | if (r) | |
2691 | SCSC_TAG_ERR(MXMAN, "mx_logger_dump.sh err:%d\n", r); | |
2692 | } | |
2693 | # endif /* CONFIG_SCSC_WLBTD */ | |
2694 | return r; | |
2695 | #else | |
2696 | return 0; | |
2697 | #endif | |
2698 | } | |
2699 | EXPORT_SYMBOL(mx140_log_dump); | |
2700 | ||
2701 | bool mxman_recovery_disabled(void) | |
2702 | { | |
2703 | #ifdef CONFIG_SCSC_WLBT_AUTORECOVERY_PERMANENT_DISABLE | |
2704 | /* Add option to kill autorecovery, ignoring module parameter | |
2705 | * to work around platform that enables it against our wishes | |
2706 | */ | |
2707 | SCSC_TAG_ERR(MXMAN, "CONFIG_SCSC_WLBT_AUTORECOVERY_PERMANENT_DISABLE is set\n"); | |
2708 | return true; | |
2709 | #endif | |
2710 | /* If FW has panicked when recovery was disabled, don't allow it to | |
2711 | * be enabled. The horse has bolted. | |
2712 | */ | |
2713 | if (disable_recovery_until_reboot) | |
2714 | return true; | |
2715 | ||
2716 | if (disable_recovery_handling == MEMDUMP_FILE_FOR_RECOVERY) | |
2717 | return disable_recovery_from_memdump_file; | |
2718 | else | |
2719 | return disable_recovery_handling ? true : false; | |
2720 | } | |
2721 | EXPORT_SYMBOL(mxman_recovery_disabled); | |
2722 | ||
2723 | /** | |
2724 | * This returns the last known loaded FW build_id | |
2725 | * even when the fw is NOT running at the time of the request. | |
2726 | * | |
2727 | * It could be used anytime by Android Enhanced Logging | |
2728 | * to query for fw version. | |
2729 | */ | |
2730 | void mxman_get_fw_version(char *version, size_t ver_sz) | |
2731 | { | |
2732 | /* unavailable only if chip not probed ! */ | |
2733 | snprintf(version, ver_sz, "%s", saved_fw_build_id); | |
2734 | } | |
2735 | EXPORT_SYMBOL(mxman_get_fw_version); | |
2736 | ||
2737 | void mxman_get_driver_version(char *version, size_t ver_sz) | |
2738 | { | |
2739 | /* IMPORTANT - Do not change the formatting as User space tooling is parsing the string | |
2740 | * to read SAP fapi versions. */ | |
2741 | snprintf(version, ver_sz, "drv_ver: %u.%u.%u.%u", | |
2742 | SCSC_RELEASE_PRODUCT, SCSC_RELEASE_ITERATION, SCSC_RELEASE_CANDIDATE, SCSC_RELEASE_POINT); | |
2743 | #ifdef CONFIG_SCSC_WLBTD | |
2744 | scsc_wlbtd_get_and_print_build_type(); | |
2745 | #endif | |
2746 | } | |
2747 | EXPORT_SYMBOL(mxman_get_driver_version); | |
2748 | ||
2749 | int mxman_register_firmware_notifier(struct notifier_block *nb) | |
2750 | { | |
2751 | return blocking_notifier_chain_register(&firmware_chain, nb); | |
2752 | } | |
2753 | EXPORT_SYMBOL(mxman_register_firmware_notifier); | |
2754 | ||
2755 | int mxman_unregister_firmware_notifier(struct notifier_block *nb) | |
2756 | { | |
2757 | return blocking_notifier_chain_unregister(&firmware_chain, nb); | |
2758 | } | |
2759 | EXPORT_SYMBOL(mxman_unregister_firmware_notifier); | |
2760 | ||
2761 | ||
2762 | int mxman_lerna_send(struct mxman *mxman, void *message, u32 message_size) | |
2763 | { | |
2764 | struct srvman *srvman = NULL; | |
2765 | ||
2766 | /* May be called when WLBT is off, so find the context in this case */ | |
2767 | if (!mxman) | |
2768 | mxman = active_mxman; | |
2769 | ||
2770 | if (!active_mxman) { | |
2771 | SCSC_TAG_ERR(MXMAN, "No active MXMAN\n"); | |
2772 | return -EINVAL; | |
2773 | } | |
2774 | ||
2775 | if (!message || (message_size == 0)) { | |
2776 | SCSC_TAG_INFO(MXMAN, "No lerna request provided.\n"); | |
2777 | return 0; | |
2778 | } | |
2779 | ||
2780 | mutex_lock(&active_mxman->mxman_mutex); | |
2781 | srvman = scsc_mx_get_srvman(active_mxman->mx); | |
2782 | if (srvman && srvman->error) { | |
2783 | mutex_unlock(&active_mxman->mxman_mutex); | |
2784 | SCSC_TAG_INFO(MXMAN, "Lerna configuration called during error - ignore\n"); | |
2785 | return 0; | |
2786 | } | |
2787 | ||
2788 | if (active_mxman->mxman_state == MXMAN_STATE_STARTED) { | |
2789 | SCSC_TAG_INFO(MXMAN, "MM_LERNA_CONFIG\n"); | |
2790 | mxmgmt_transport_send(scsc_mx_get_mxmgmt_transport(active_mxman->mx), | |
2791 | MMTRANS_CHAN_ID_MAXWELL_MANAGEMENT, message, | |
2792 | message_size); | |
2793 | mutex_unlock(&active_mxman->mxman_mutex); | |
2794 | return 0; | |
2795 | } | |
2796 | ||
2797 | SCSC_TAG_INFO(MXMAN, "MXMAN is NOT STARTED...cannot send MM_LERNA_CONFIG msg.\n"); | |
2798 | mutex_unlock(&active_mxman->mxman_mutex); | |
2799 | return -EAGAIN; | |
2800 | } |