| /* |
| * Copyright (c) 2018 MediaTek Inc. |
| * |
| * Permission is hereby granted, free of charge, to any person obtaining |
| * a copy of this software and associated documentation files |
| * (the "Software"), to deal in the Software without restriction, |
| * including without limitation the rights to use, copy, modify, merge, |
| * publish, distribute, sublicense, and/or sell copies of the Software, |
| * and to permit persons to whom the Software is furnished to do so, |
| * subject to the following conditions: |
| * |
| * The above copyright notice and this permission notice shall be |
| * included in all copies or substantial portions of the Software. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
| * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
| * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. |
| * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY |
| * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, |
| * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE |
| * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
| */ |
| |
| #if !defined(__KDUMP_USB_C__) |
| #define __KDUMP_USB_C__ |
| |
| #include <mrdump.h> |
| #include <malloc.h> |
| #include <printf.h> |
| #include <string.h> |
| #include <sys/types.h> |
| #include "aee.h" |
| #include "kdump.h" |
| |
| /* |
| * USB Connectivity |
| * Note: for usb transmission |
| * QMU mode : MAX packet length: 63x1024 = 64512 byte. -> GPD_BUF_SIZE_ALIGN |
| * ZLP issue: EXSPACE should not be multiple size of 512 byte. |
| */ |
| #define EXSPACE 64256 |
| #define MAX_RSP_SIZE 64 |
| #define CON_TIMEOUT 5000 |
| |
| /* Flow control */ |
| #define USB_RTS "_RTS" |
| #define USB_CTS "_CTS" |
| #define USB_FIN "_FIN" |
| #define USBDONE "DONE" |
| |
| /* |
| * Need to align 64 bytes to meet cache line length |
| * 1. cmd[MAX_RSP_SIZE] |
| * 2. data[EXSPACE] |
| */ |
| struct mrdump_usb_handle { |
| char cmd[MAX_RSP_SIZE]; |
| uint8_t data[EXSPACE]; |
| unsigned int zipsize; |
| int idx; |
| }; |
| |
| /* flow control of usb connection */ |
| static bool usb_data_transfer(struct mrdump_usb_handle *handle, int length) |
| { |
| int len; |
| |
| /* send RTS */ |
| memset(handle->cmd, 0, MAX_RSP_SIZE); |
| len = snprintf(handle->cmd, MAX_RSP_SIZE, "%s", USB_RTS); |
| len = usb_write_with_timeout(handle->cmd, strlen(handle->cmd), CON_TIMEOUT); |
| if (len > 0) { |
| |
| /* receive CTS */ |
| memset(handle->cmd, 0, MAX_RSP_SIZE); |
| len = usb_read_with_timeout(handle->cmd, MAX_RSP_SIZE, CON_TIMEOUT); |
| if ((len == (int)strlen(USB_CTS))&& |
| (!strncmp(handle->cmd, USB_CTS, strlen(USB_CTS)))) { |
| |
| /* send DATA */ |
| len = usb_write_with_timeout(handle->data, length, CON_TIMEOUT); |
| if (len > 0) { |
| |
| /* get FIN */ |
| memset(handle->cmd, 0, sizeof(handle->cmd)); |
| len = usb_read_with_timeout(handle->cmd, sizeof(handle->cmd), CON_TIMEOUT); |
| if ((len == (int)strlen(USB_FIN)) && |
| (!strncmp(handle->cmd, USB_FIN, strlen(USB_FIN)))) { |
| return true; |
| } else { |
| voprintf_error("%s: failed to get FIN.cmd<%p,%d><%x,%x,%x,%x>\n", __func__, |
| handle->cmd, len, handle->cmd[0], handle->cmd[1], handle->cmd[2], handle->cmd[3]); |
| } |
| |
| } else { |
| voprintf_error("%s: send DATA error.\n", __func__); |
| } |
| } else { |
| voprintf_error("%s: Not CTS after RTS.cmd<%p,%d><%x,%x,%x,%x>\n", __func__, |
| handle->cmd, len, handle->cmd[0], handle->cmd[1], handle->cmd[2], handle->cmd[3]); |
| } |
| } else { |
| voprintf_error("%s: send RTS error.\n", __func__); |
| } |
| return false; |
| } |
| |
| /* store data in pool (EXSPACE) and write when pool is full */ |
| static int do_store_or_write(struct mrdump_usb_handle *handle, uint8_t *buf, uint32_t length) |
| { |
| int total; |
| unsigned int leftspace, mylen, reval; |
| |
| |
| /* count for leftspace */ |
| total = EXSPACE; |
| leftspace = total - handle->idx; |
| |
| /* check length */ |
| if (length > leftspace) { |
| mylen = leftspace; |
| reval = length - leftspace; |
| } else { |
| mylen = length; |
| reval = 0; |
| } |
| |
| /* store */ |
| while (mylen > 0) { |
| handle->data[handle->idx] = *buf; |
| handle->idx++; |
| buf++; |
| mylen--; |
| } |
| |
| /* write */ |
| if (handle->idx == total) { |
| if (!usb_data_transfer(handle, handle->idx)) { |
| voprintf_error("%s: connection failed.(error idx: %d)\n", |
| __func__, handle->idx); |
| return -1; |
| } |
| handle->idx = 0; |
| } |
| |
| return reval; |
| } |
| |
| static int usb_write_cb(void *opaque_handle, void *buf, int size) |
| { |
| unsigned int len, moves; |
| int ret = 0; |
| uint8_t *ptr; |
| |
| struct mrdump_usb_handle *handle = opaque_handle; |
| |
| handle->zipsize += size; |
| |
| /* EOF, write the left Data in handle data buffer... */ |
| if ((buf == NULL) && (size == 0)) { |
| |
| /* MUST: a delay for the last transmission */ |
| mdelay(10); |
| |
| if (!usb_data_transfer(handle, handle->idx)) { |
| voprintf_error("%s: connection failed.(error idx: %d)\n", |
| __func__, handle->idx); |
| return -1; |
| } |
| |
| /* send "MRDUMP ZLP" */ |
| memset((void *)handle->data, 0, sizeof(handle->data)); |
| size = snprintf((char *)handle->data, sizeof(handle->data), "%s_%s", |
| MRDUMP_GO_DUMP, USBDONE); |
| if (0 > usb_write_with_timeout(handle->data, strlen((char *)handle->data), |
| CON_TIMEOUT)) { |
| voprintf_error(" USB Dump: Write MRDUMP ZLP failed.\n"); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| /* buf should not be NULL if not EOF */ |
| if (buf == NULL) |
| return -1; |
| |
| /* process of Store and write */ |
| len = size; |
| ptr = (uint8_t *)buf; |
| while (1) { |
| ret = do_store_or_write(handle, ptr, len); |
| if (ret < 0) { |
| voprintf_error(" USB Dump: store and write failed.\n"); |
| return -1; |
| } else if (ret == 0) { |
| break; |
| } else { |
| moves = len - ret; |
| ptr += moves; |
| len = ret; |
| } |
| } |
| |
| return size; |
| } |
| |
| int kdump_usb_output(const struct mrdump_control_block *mrdump_cb, |
| const struct kzip_addlist *memlist) |
| { |
| int i; |
| struct kzip_addlist expdb_file[2]; |
| char *expdb_filename; |
| unsigned int expdb_offset, expdb_filesize; |
| voprintf_info("Output by USB\n"); |
| |
| struct mrdump_usb_handle *handle = memalign(64, sizeof(struct mrdump_usb_handle)); |
| if (handle == NULL) { |
| voprintf_error("No enough memory."); |
| return -1; |
| } |
| memset(handle, 0, sizeof(struct mrdump_usb_handle)); |
| |
| mdelay(100); |
| bool ok = true; |
| mtk_wdt_restart(); |
| struct kzip_file *zf = kzip_open(handle, usb_write_cb); |
| if (zf != NULL) { |
| /* add SYS_COREDUMP */ |
| if (!kzip_add_file(zf, memlist, "SYS_COREDUMP")) |
| ok = false; |
| |
| /* add file on expdb */ |
| for (i = 0; i < IPANIC_NR_SECTIONS; i++) { |
| if (kedump_get_data_info(i, &expdb_filename, &expdb_offset, &expdb_filesize) == 0) { |
| expdb_file[0].address = expdb_offset; |
| expdb_file[0].size = expdb_filesize; |
| expdb_file[0].type = EXPDB_FILE; |
| expdb_file[1].address = 0; |
| expdb_file[1].size = 0; |
| expdb_file[1].type = MEM_NO_MAP; |
| if (!kzip_add_file(zf, expdb_file, expdb_filename)) |
| ok = false; |
| } |
| } |
| |
| /* close zipfile */ |
| kzip_close(zf); |
| usb_write_cb(handle, NULL, 0); /* really write the last part */ |
| zf = NULL; |
| } else { |
| ok = false; |
| } |
| free(handle); |
| |
| mtk_wdt_restart(); |
| if (ok) { |
| mrdump_status_ok("OUTPUT:%s\nMODE:%s\n", "USB DUMP", |
| mrdump_mode2string(mrdump_cb->crash_record.reboot_mode)); |
| } |
| |
| return ok ? 0 : -1; |
| } |
| |
| #endif |