From c272eac91a14c6f17307b32cc919b0913999110a Mon Sep 17 00:00:00 2001 From: Taejin Kim Date: Mon, 14 May 2018 19:35:46 +0900 Subject: [PATCH] [COMMON] drivers: modem_if: Apply modem interface driver files Apply modem interface driver files from modem main branch TAG: ModemIF-4.9-20180509 Change-Id: I190896ee5cb9758e06618f4a6f458a89fa241c70 Signed-off-by: Taejin Kim --- drivers/misc/Kconfig | 1 + drivers/misc/Makefile | 1 + drivers/misc/modem_if/Kconfig | 45 + drivers/misc/modem_if/Makefile | 13 + drivers/misc/modem_if/include/exynos_ipc.h | 296 ++ drivers/misc/modem_if/include/modem_v1.h | 434 +++ drivers/misc/modem_if/modem_boot_device_spi.c | 285 ++ drivers/misc/modem_if/modem_dump.c | 117 + drivers/misc/modem_if/modem_dump.h | 27 + drivers/misc/modem_if/modem_io_device.c | 1672 +++++++++++ .../misc/modem_if/modem_link_device_memory.c | 510 ++++ .../misc/modem_if/modem_link_device_memory.h | 385 +++ .../misc/modem_if/modem_link_device_shmem.c | 2486 +++++++++++++++++ .../misc/modem_if/modem_link_device_shmem.h | 857 ++++++ drivers/misc/modem_if/modem_main.c | 632 +++++ .../modem_if/modem_modemctl_device_sh333ap.c | 789 ++++++ .../modem_if/modem_net_flowcontrol_device.c | 116 + drivers/misc/modem_if/modem_notifier.c | 40 + drivers/misc/modem_if/modem_prj.h | 988 +++++++ drivers/misc/modem_if/modem_utils.c | 1294 +++++++++ drivers/misc/modem_if/modem_utils.h | 419 +++ drivers/misc/modem_if/modem_variation.h | 83 + include/linux/modem_notifier.h | 36 + include/soc/samsung/exynos-modem-ctrl.h | 24 + 24 files changed, 11550 insertions(+) create mode 100644 drivers/misc/modem_if/Kconfig create mode 100644 drivers/misc/modem_if/Makefile create mode 100644 drivers/misc/modem_if/include/exynos_ipc.h create mode 100644 drivers/misc/modem_if/include/modem_v1.h create mode 100644 drivers/misc/modem_if/modem_boot_device_spi.c create mode 100644 drivers/misc/modem_if/modem_dump.c create mode 100644 drivers/misc/modem_if/modem_dump.h create mode 100644 drivers/misc/modem_if/modem_io_device.c create mode 100644 drivers/misc/modem_if/modem_link_device_memory.c create mode 100644 drivers/misc/modem_if/modem_link_device_memory.h create mode 100755 drivers/misc/modem_if/modem_link_device_shmem.c create mode 100644 drivers/misc/modem_if/modem_link_device_shmem.h create mode 100644 drivers/misc/modem_if/modem_main.c create mode 100755 drivers/misc/modem_if/modem_modemctl_device_sh333ap.c create mode 100644 drivers/misc/modem_if/modem_net_flowcontrol_device.c create mode 100644 drivers/misc/modem_if/modem_notifier.c create mode 100644 drivers/misc/modem_if/modem_prj.h create mode 100644 drivers/misc/modem_if/modem_utils.c create mode 100644 drivers/misc/modem_if/modem_utils.h create mode 100644 drivers/misc/modem_if/modem_variation.h create mode 100644 include/linux/modem_notifier.h create mode 100644 include/soc/samsung/exynos-modem-ctrl.h diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index c3ae75a8ecb1..d3ba3917e316 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -540,4 +540,5 @@ source "drivers/misc/genwqe/Kconfig" source "drivers/misc/echo/Kconfig" source "drivers/misc/cxl/Kconfig" source "drivers/misc/mcu_ipc/Kconfig" +source "drivers/misc/modem_if/Kconfig" endmenu diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index c9175204c35f..15c802c065a8 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -59,6 +59,7 @@ obj-$(CONFIG_PCI_ENDPOINT_TEST) += pci_endpoint_test.o obj-$(CONFIG_UID_SYS_STATS) += uid_sys_stats.o obj-$(CONFIG_MCU_IPC) += mcu_ipc/ +obj-$(CONFIG_SEC_MODEM_IF) += modem_if/ obj-$(CONFIG_MEMORY_STATE_TIME) += memory_state_time.o lkdtm-$(CONFIG_LKDTM) += lkdtm_core.o diff --git a/drivers/misc/modem_if/Kconfig b/drivers/misc/modem_if/Kconfig new file mode 100644 index 000000000000..75f470598ffc --- /dev/null +++ b/drivers/misc/modem_if/Kconfig @@ -0,0 +1,45 @@ +menuconfig SEC_MODEM_IF + bool "Samsung Mobile Modem Interface" + default n + ---help--- + Samsung Modem Interface Driver + +if SEC_MODEM_IF + +config MODEM_CHIP_MODAP_S3XXAP + bool "Exynos ModAP S3XXAP" + select MODEMCTRL_MODAP_S3XXAP + select LINK_DEVICE_SHMEM + select MCU_IPC + select SHM_IPC + select CP_PMUCAL + select CP_SECURE_BOOT + default n + +menu "Configurations" +config MODEMCTRL_MODAP_S3XXAP + bool "Control device: Exynos ModAP S3xxAP" + default n + +config LINK_DEVICE_SHMEM + bool "Link device: shared memory" + default n + +config CP_SECURE_BOOT + bool "Support CP Secure Boot" + default n + +config DEBUG_MODEM_IF + bool "Enable debug option" + default n + +config SIM_DETECT + bool "Detect SIM by GPIO" + default n + +config MODEM_IF_ADAPTIVE_QOS + bool "Enable Adaptive QOS for high data performance" + default n + +endmenu +endif diff --git a/drivers/misc/modem_if/Makefile b/drivers/misc/modem_if/Makefile new file mode 100644 index 000000000000..1d82e2768d77 --- /dev/null +++ b/drivers/misc/modem_if/Makefile @@ -0,0 +1,13 @@ +# Makefile of modem_if + +obj-y += modem_main.o \ + modem_io_device.o \ + modem_net_flowcontrol_device.o \ + modem_utils.o \ + modem_dump.o \ + modem_notifier.o + +obj-$(CONFIG_LINK_DEVICE_SHMEM) += modem_link_device_shmem.o \ + modem_link_device_memory.o + +obj-$(CONFIG_MODEMCTRL_MODAP_S3XXAP) += modem_modemctl_device_sh333ap.o diff --git a/drivers/misc/modem_if/include/exynos_ipc.h b/drivers/misc/modem_if/include/exynos_ipc.h new file mode 100644 index 000000000000..2e9e7aa84bfa --- /dev/null +++ b/drivers/misc/modem_if/include/exynos_ipc.h @@ -0,0 +1,296 @@ +/* + * Copyright (C) 2010 Samsung Electronics. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __EXYNOS_IPC_H__ +#define __EXYNOS_IPC_H__ + +#include +#include "modem_v1.h" + +#define EXYNOS_SINGLE_MASK (0x3<<6) +#define EXYNOS_MULTI_START_MASK (0x1<<7) +#define EXYNOS_MULTI_LAST_MASK (0x1<<6) + +#define EXYNOS_START_MASK 0xABCD +#define EXYNOS_START_OFFSET 0 +#define EXYNOS_START_SIZE 2 + +#define EXYNOS_FRAME_SEQ_OFFSET 2 +#define EXYNOS_FRAME_SIZE 2 + +#define EXYNOS_FRAG_CONFIG_OFFSET 4 +#define EXYNOS_FRAG_CONFIG_SIZE 2 + +#define EXYNOS_LEN_OFFSET 6 +#define EXYNOS_LEN_SIZE 2 + +#define EXYNOS_CH_ID_OFFSET 8 +#define EXYNOS_CH_SIZE 1 + +#define EXYNOS_CH_SEQ_OFFSET 9 +#define EXYNOS_CH_SEQ_SIZE 1 + +#define EXYNOS_HEADER_SIZE 12 + +#define EXYNOS_DATA_LOOPBACK_CHANNEL 82 + +#define EXYNOS_FMT_NUM 1 +#define EXYNOS_RFS_NUM 10 + +enum exynos_ch_id { + EXYNOS_CH_ID_MULTIPDP = 0, + + EXYNOS_CH_ID_PDP_0 = 1, /*rmnet0*/ + EXYNOS_CH_ID_PDP_1, + EXYNOS_CH_ID_PDP_2, + EXYNOS_CH_ID_PDP_3, + EXYNOS_CH_ID_PDP_4, + EXYNOS_CH_ID_PDP_5, + EXYNOS_CH_ID_PDP_6, + EXYNOS_CH_ID_PDP_7, + EXYNOS_CH_ID_PDP_8, + EXYNOS_CH_ID_PDP_9, + + EXYNOS_CH_ID_BT_DUN = 21, /*umts_router*/ + + EXYNOS_CH_ID_RFS_0 = 41, /*umts_rfs*/ + EXYNOS_CH_ID_RFS_1, + EXYNOS_CH_ID_RFS_2, + EXYNOS_CH_ID_RFS_3, + EXYNOS_CH_ID_RFS_4, + EXYNOS_CH_ID_RFS_5, + EXYNOS_CH_ID_RFS_6, + EXYNOS_CH_ID_RFS_7, + EXYNOS_CH_ID_RFS_8, + EXYNOS_CH_ID_RFS_9, + + EXYNOS_CH_ID_CPLOG = 81, /*umts_dm0*/ + EXYNOS_CH_ID_LOOPBACK, /*umts_loopback*/ + + EXYNOS_CH_ID_RCS_0 = 91, /*CH_RAW_CS_VT_DATA*/ + EXYNOS_CH_ID_RCS_1 = 92, /*CH_RAW_CIQ_BRIDGE*/ + + EXYNOS_CH_ID_PPT_0 = 93, /*Mission-Critical Push to Talk*/ + EXYNOS_CH_ID_PPT_1 = 94, /*Mission-Critical Push to Talk*/ + + EXYNOS_CH_ID_BOOT = 241, + EXYNOS_CH_ID_DUMP = 242, + + EXYNOS_CH_ID_FMT_0 = 245, /*umts_ipc0*/ + EXYNOS_CH_ID_MAX = 255, + EXYNOS_CH_ID_FLOW_CTRL = 255 +}; + +struct frag_config { + u8 frame_first:1, + frame_last:1, + packet_index:6; + u8 frame_index; +} __attribute__((packed)); + +/* EXYNOS link-layer header */ +struct exynos_link_header { + u16 sync; + u16 seq; + u16 cfg; + u16 len; + u8 ch_id; + u8 ch_seq; +} __attribute__((packed)); + +struct exynos_seq_num { + u16 frame_cnt; + u8 ch_cnt[255]; +} __attribute__((packed)); + +struct exynos_frame_data { + /* Frame length calculated from the length fields */ + unsigned int len; + + /* The length of link layer header */ + unsigned int hdr_len; + + /* The length of received header */ + unsigned int hdr_rcvd; + + /* The length of link layer payload */ + unsigned int pay_len; + + /* The length of received data */ + unsigned int pay_rcvd; + + /* The length of link layer padding */ + unsigned int pad_len; + + /* The length of received padding */ + unsigned int pad_rcvd; + + /* Header buffer */ + u8 hdr[EXYNOS_HEADER_SIZE]; +}; + +static inline bool exynos_start_valid(u8 *frm) +{ + u16 cfg = *(u16 *)(frm + EXYNOS_START_OFFSET); + return cfg == EXYNOS_START_MASK ? true : false; +} + +static inline bool exynos_multi_start_valid(u8 *frm) +{ + u16 cfg = *(u16 *)(frm + EXYNOS_FRAG_CONFIG_OFFSET); + return ((cfg >> 8) & EXYNOS_MULTI_START_MASK) == EXYNOS_MULTI_START_MASK; +} + +static inline bool exynos_multi_last_valid(u8 *frm) +{ + u16 cfg = *(u16 *)(frm + EXYNOS_FRAG_CONFIG_OFFSET); + return ((cfg >> 8) & EXYNOS_MULTI_LAST_MASK) == EXYNOS_MULTI_LAST_MASK; +} + +static inline bool exynos_single_frame(u8 *frm) +{ + u16 cfg = *(u16 *)(frm + EXYNOS_FRAG_CONFIG_OFFSET); + return ((cfg >> 8) & EXYNOS_SINGLE_MASK) == EXYNOS_SINGLE_MASK; +} + +static inline bool exynos_rcs_ch(u8 ch) +{ + return (ch == EXYNOS_CH_ID_RCS_0 || ch == EXYNOS_CH_ID_RCS_1) ? true : false; +} + +static inline bool exynos_ppt_ch(u8 ch) +{ + return (ch == EXYNOS_CH_ID_PPT_0 || ch == EXYNOS_CH_ID_PPT_1) ? true : false; +} + +static inline u8 exynos_get_ch(u8 *frm) +{ + return frm[EXYNOS_CH_ID_OFFSET]; +} + +static inline unsigned int exynos_get_frame_seq(u8 *frm) +{ + u16 cfg = *(u16 *)(frm + EXYNOS_FRAME_SEQ_OFFSET); + return cfg; +} + +static inline unsigned int exynos_get_ch_seq(u8 *frm) +{ + return frm[EXYNOS_CH_SEQ_OFFSET]; +} + +static inline unsigned int exynos_calc_padding_size(unsigned int len) +{ + unsigned int residue = len & 0x7; + return residue ? (8 - residue) : 0; +} + +static inline unsigned int exynos_get_frame_len(u8 *frm) +{ + return (unsigned int)*(u16 *)(frm + EXYNOS_LEN_OFFSET); +} + +static inline bool exynos_fmt_ch(u8 ch) +{ + return (ch == EXYNOS_CH_ID_FMT_0) ? true : false; +} + +static inline bool exynos_rfs_ch(u8 ch) +{ + return (ch >= EXYNOS_CH_ID_RFS_0 && ch <= EXYNOS_CH_ID_RFS_9) ? + true : false; +} + +static inline bool exynos_boot_ch(u8 ch) +{ + return (ch == EXYNOS_CH_ID_BOOT) ? true : false; +} + +static inline bool exynos_dump_ch(u8 ch) +{ + return (ch == EXYNOS_CH_ID_LOOPBACK) ? true : false; +} + +static inline bool exynos_udl_ch(u8 ch) +{ + return (ch == EXYNOS_CH_ID_BOOT || ch == EXYNOS_CH_ID_DUMP) ? + true : false; +} + +static inline bool exynos_ipc_ch(u8 ch) +{ + return (ch > 0 && (ch != EXYNOS_CH_ID_BOOT && ch != EXYNOS_CH_ID_DUMP)) ? + true : false; +} + +static inline bool exynos_ps_ch(u8 ch) +{ + return (ch >= EXYNOS_CH_ID_PDP_0 && ch <= EXYNOS_CH_ID_PDP_9) ? + true : false; +} + +static inline bool exynos_log_ch(u8 ch) +{ + return (ch == EXYNOS_CH_ID_CPLOG) ? true : false; +} + +static inline bool exynos_router_ch(u8 ch) +{ + return (ch == EXYNOS_CH_ID_BT_DUN) ? true : false; +} + +static inline unsigned int exynos_get_total_len(u8 *frm) +{ + unsigned int len; + unsigned int pad; + + len = exynos_get_frame_len(frm); + pad = exynos_calc_padding_size(len) ? exynos_calc_padding_size(len) : 0; + return len + pad; +} + +static inline u16 modify_next_frame(u16 fr_cfg) +{ + u8 frame_index; + + frame_index = fr_cfg & 0x3fff; + + if (!(--frame_index)) { + fr_cfg &= 0x3fff; + fr_cfg |= 0x4000; + } + + return (fr_cfg & 0xff00) | frame_index; +} + +static inline bool exynos_padding_exist(u8 *frm) +{ + return exynos_calc_padding_size(exynos_get_frame_len(frm)) ? true : false; +} + +static inline u32 exynos_multi_packet_index(u16 ctrl) +{ + return ((ctrl >> 8) & 0x3f); +} + +static inline u32 exynos_multi_frame_index(u16 ctrl) +{ + return ctrl & 0xff; +} + +static inline bool exynos_multi_last(u16 ctrl) +{ + return (ctrl >> 8) & EXYNOS_MULTI_LAST_MASK ? true : false; +} +#endif diff --git a/drivers/misc/modem_if/include/modem_v1.h b/drivers/misc/modem_if/include/modem_v1.h new file mode 100644 index 000000000000..3c55be236266 --- /dev/null +++ b/drivers/misc/modem_if/include/modem_v1.h @@ -0,0 +1,434 @@ +/* + * Copyright (C) 2014 Samsung Electronics. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __MODEM_IF_H__ +#define __MODEM_IF_H__ + +#include +#include +#include + +enum modem_t { + SEC_CMC221, + SEC_SS222, + SEC_SH222AP, + SEC_SH310AP, + SEC_SH333AP, + SEC_SH340AP, + DUMMY, + MAX_MODEM_TYPE +}; + +enum dev_format { + IPC_FMT, + IPC_RAW, + IPC_RFS, + IPC_MULTI_RAW, + IPC_BOOT, + IPC_DUMP, + IPC_CMD, + IPC_DEBUG, + MAX_DEV_FORMAT, +}; +#define MAX_IPC_DEV (IPC_RFS + 1) /* FMT, RAW, RFS */ +#define MAX_SIPC5_DEV (IPC_RAW + 1) /* FMT, RAW */ +#define MAX_EXYNOS_DEVICES (IPC_RAW + 1) /* FMT, RAW */ + +enum modem_io { + IODEV_MISC, + IODEV_NET, + IODEV_DUMMY, +}; + +enum modem_link { + LINKDEV_UNDEFINED, + LINKDEV_MIPI, + LINKDEV_USB, + LINKDEV_HSIC, + LINKDEV_DPRAM, + LINKDEV_PLD, + LINKDEV_C2C, + LINKDEV_SHMEM, + LINKDEV_SPI, + LINKDEV_MAX +}; +#define LINKTYPE(modem_link) (1u << (modem_link)) + +enum ap_type { + S5P, + MAX_AP_TYPE +}; + +enum sipc_ver { + NO_SIPC_VER = 0, + SIPC_VER_40 = 40, + SIPC_VER_41 = 41, + SIPC_VER_42 = 42, + SIPC_VER_50 = 50, + MAX_SIPC_VER +}; + +#define STR_CP_FAIL "cp_fail" +#define STR_CP_WDT "cp_wdt" /* CP watchdog timer */ + +enum uart_direction { + AP = 0, + CP = 1, +}; + +enum iodev_attr_bit { + ATTR_SIPC4, + ATTR_SIPC5, + ATTR_CDC_NCM, + ATTR_MULTIFMT, + ATTR_HANDOVER, + ATTR_LEGACY_RFS, + ATTR_RX_FRAGMENT, + ATTR_SBD_IPC, /* IPC using SBD designed from MIPI-LLI */ + ATTR_NO_LINK_HEADER, /* Link-layer header is not needed */ + ATTR_NO_CHECK_MAXQ, /* no need to check rxq overflow condition */ +}; +#define IODEV_ATTR(b) (0x1 << b) + +/** + * struct modem_io_t - declaration for io_device + * @name: device name + * @id: for SIPC4, contains format & channel information + * (id & 11100000b)>>5 = format (eg, 0=FMT, 1=RAW, 2=RFS) + * (id & 00011111b) = channel (valid only if format is RAW) + * for SIPC5, contains only 8-bit channel ID + * @format: device format + * @io_type: type of this io_device + * @links: list of link_devices to use this io_device + * for example, if you want to use DPRAM and USB in an io_device. + * .links = LINKTYPE(LINKDEV_DPRAM) | LINKTYPE(LINKDEV_USB) + * @tx_link: when you use 2+ link_devices, set the link for TX. + * If define multiple link_devices in @links, + * you can receive data from them. But, cannot send data to all. + * TX is only one link_device. + * @app: the name of the application that will use this IO device + * + * This structure is used in board-*-modems.c + */ +struct modem_io_t { + char *name; + int id; + enum dev_format format; + enum modem_io io_type; + enum modem_link links; + enum modem_link tx_link; + u32 attrs; + char *app; + + unsigned int ul_num_buffers; + unsigned int ul_buffer_size; + unsigned int dl_num_buffers; + unsigned int dl_buffer_size; +}; + +struct modemlink_pm_data { + char *name; + /* link power contol 2 types : pin & regulator control */ + int (*link_ldo_enable)(bool); + unsigned int gpio_link_enable; + unsigned int gpio_link_active; + unsigned int gpio_link_hostwake; + unsigned int gpio_link_slavewake; + int (*link_reconnect)(void); + + /* usb hub only */ + int (*port_enable)(int, int); + int (*hub_standby)(void *); + void *hub_pm_data; + bool has_usbhub; + + /* cpu/bus frequency lock */ + atomic_t freqlock; + int (*freq_lock)(struct device *dev); + int (*freq_unlock)(struct device *dev); + + int autosuspend_delay_ms; /* if zero, the default value is used */ + void (*ehci_reg_dump)(struct device *); +}; + +struct modemlink_pm_link_activectl { + int gpio_initialized; + int gpio_request_host_active; +}; + +enum shmem_type { + REAL_SHMEM, + C2C_SHMEM, + MAX_SHMEM_TYPE +}; + +#define STR_SHMEM_BASE "shmem_base" + +#define SHMEM_SIZE_1MB (1 << 20) /* 1 MB */ +#define SHMEM_SIZE_2MB (2 << 20) /* 2 MB */ +#define SHMEM_SIZE_4MB (4 << 20) /* 4 MB */ +#define SHMEM_SIZE_8MB (8 << 20) /* 8 MB */ + + +struct modem_mbox { + unsigned int mbx_ap2cp_msg; + unsigned int mbx_cp2ap_msg; + unsigned int mbx_ap2cp_status; /* AP_STATUS */ + unsigned int mbx_cp2ap_status; /* CP_STATUS */ + unsigned int int_ap2cp_uart_noti; + + int int_ap2cp_msg; + int int_ap2cp_wakeup; + int int_ap2cp_active; + int int_ap2cp_status; + + int irq_cp2ap_msg; + int irq_cp2ap_active; + int irq_cp2ap_status; + int irq_cp2ap_wake_lock; + + /* Performance request */ + unsigned int mbx_ap2cp_perf_req; + unsigned int mbx_cp2ap_perf_req; + unsigned int mbx_cp2ap_perf_req_cpu; + unsigned int mbx_cp2ap_perf_req_mif; + unsigned int mbx_cp2ap_perf_req_int; + unsigned int mbx_cp2ap_pcie_l1ss_disable; + unsigned int mbx_ap2cp_mif_freq; + + unsigned int int_ap2cp_perf_req; + unsigned int irq_cp2ap_perf_req; + unsigned int irq_cp2ap_perf_req_cpu; + unsigned int irq_cp2ap_perf_req_mif; + unsigned int irq_cp2ap_perf_req_int; + unsigned int irq_cp2ap_pcie_l1ss_disable; + + /* Status Bit Info */ + unsigned int sbi_lte_active_mask; + unsigned int sbi_lte_active_pos; + unsigned int sbi_wake_lock_mask; + unsigned int sbi_wake_lock_pos; + unsigned int sbi_cp_status_mask; + unsigned int sbi_cp_status_pos; + + unsigned int sbi_pda_active_mask; + unsigned int sbi_pda_active_pos; + unsigned int sbi_ap_status_mask; + unsigned int sbi_ap_status_pos; + + unsigned int sbi_uart_noti_mask; + unsigned int sbi_uart_noti_pos; + + /* System (H/W) revision */ + unsigned int mbx_ap2cp_sys_rev; + unsigned int mbx_ap2cp_pmic_rev; + unsigned int mbx_ap2cp_pkg_id; + + unsigned int *ap_clk_table; + unsigned int ap_clk_cnt; + + unsigned int *mif_clk_table; + unsigned int mif_clk_cnt; +}; + +struct modem_pmu { + int (*power)(int); + int (*init)(void); + int (*get_pwr_status)(void); + int (*stop)(void); + int (*start)(void); + int (*clear_cp_fail)(void); + int (*clear_cp_wdt)(void); +}; + +/* platform data */ +struct modem_data { + char *name; + + unsigned int gpio_cp_on; + unsigned int gpio_cp_off; + unsigned int gpio_reset_req_n; + unsigned int gpio_cp_reset; + + /* for broadcasting AP's PM state (active or sleep) */ + unsigned int gpio_pda_active; + + /* for checking aliveness of CP */ + unsigned int gpio_phone_active; + int irq_phone_active; + + /* for AP-CP IPC interrupt */ + unsigned int gpio_ipc_int2ap; + int irq_ipc_int2ap; + unsigned long irqf_ipc_int2ap; /* IRQ flags */ + unsigned int gpio_ipc_int2cp; + + /* for AP-CP power management (PM) handshaking */ + unsigned int gpio_ap_wakeup; + int irq_ap_wakeup; + unsigned int gpio_ap_status; + unsigned int gpio_cp_wakeup; + unsigned int gpio_cp_status; + int irq_cp_status; + + /* for USB/HSIC PM */ + unsigned int gpio_host_wakeup; + int irq_host_wakeup; + unsigned int gpio_host_active; + unsigned int gpio_slave_wakeup; + + unsigned int gpio_cp_dump_int; + unsigned int gpio_ap_dump_int; + unsigned int gpio_flm_uart_sel; + unsigned int gpio_cp_warm_reset; + + unsigned int hw_revision; + unsigned int package_id; + unsigned int lock_value; + struct modem_mbox *mbx; + struct modem_pmu *pmu; + + int cp_active; + int cp_wdt_reset; + + /* Switch with 2 links in a modem */ + unsigned int gpio_link_switch; + + /* Modem component */ + enum modem_link link_types; + char *link_name; + + /* the number of real IPC devices -> (IPC_RAW + 1) or (IPC_RFS + 1) */ + int max_ipc_dev; + + /* Information of IO devices */ + unsigned int num_iodevs; + struct modem_io_t *iodevs; + + /* Modem link PM support */ + struct modemlink_pm_data *link_pm_data; + + /* Handover with 2+ modems */ + bool use_handover; + + /* SIM Detect polarity */ + bool sim_polarity; + + /* SHDMEM ADDR */ + u32 shmem_base; + u32 ipcmem_offset; + u32 ipc_size; + u32 dump_offset; + u32 dump_addr; + + u8 __iomem *modem_base; + u8 __iomem *dump_base; + u8 __iomem *ipc_base; + + void (*gpio_revers_bias_clear)(void); + void (*gpio_revers_bias_restore)(void); + + struct resource *syscp_info; +}; + +#define MODEM_BOOT_DEV_SPI "modem_boot_spi" + +struct modem_boot_spi_platform_data { + const char *name; + unsigned int gpio_cp_status; +}; + +struct modem_boot_spi { + struct miscdevice dev; + struct spi_device *spi_dev; + struct mutex lock; + unsigned int gpio_cp_status; +}; + +#define to_modem_boot_spi(misc) container_of(misc, struct modem_boot_spi, dev) + +struct utc_time { + u16 year; + u8 mon:4, + day:4; + u8 hour; + u8 min; + u8 sec; + u16 msec; +} __attribute__((packed)); + +extern void get_utc_time(struct utc_time *utc); + +#ifdef CONFIG_OF +#define mif_dt_read_enum(np, prop, dest) \ + do { \ + u32 val; \ + if (of_property_read_u32(np, prop, &val)) { \ + mif_err("%s is not defined\n", prop); \ + return -EINVAL; \ + } \ + dest = (__typeof__(dest))(val); \ + } while (0) + +#define mif_dt_read_bool(np, prop, dest) \ + do { \ + u32 val; \ + if (of_property_read_u32(np, prop, &val)) { \ + mif_err("%s is not defined\n", prop); \ + return -EINVAL; \ + } \ + dest = val ? true : false; \ + } while (0) + +#define mif_dt_read_string(np, prop, dest) \ + do { \ + if (of_property_read_string(np, prop, \ + (const char **)&dest)) { \ + mif_err("%s is not defined\n", prop); \ + return -EINVAL; \ + } \ + } while (0) + +#define mif_dt_read_u32(np, prop, dest) \ + do { \ + u32 val; \ + if (of_property_read_u32(np, prop, &val)) { \ + mif_err("%s is not defined\n", prop); \ + return -EINVAL; \ + } \ + dest = val; \ + } while (0) + +#define mif_dt_read_u32_noerr(np, prop, dest) \ + do { \ + u32 val; \ + if (!of_property_read_u32(np, prop, &val)) \ + dest = val; \ + } while (0) +#endif + +#define LOG_TAG "mif: " +#define CALLEE (__func__) +#define CALLER (__builtin_return_address(0)) + +#define mif_err_limited(fmt, ...) \ + printk_ratelimited(KERN_ERR "%s: " pr_fmt(fmt), __func__, ##__VA_ARGS__) +#define mif_err(fmt, ...) \ + pr_err(LOG_TAG "%s: " pr_fmt(fmt), __func__, ##__VA_ARGS__) +#define mif_debug(fmt, ...) \ + pr_debug(LOG_TAG "%s: " pr_fmt(fmt), __func__, ##__VA_ARGS__) +#define mif_info(fmt, ...) \ + pr_info(LOG_TAG "%s: " pr_fmt(fmt), __func__, ##__VA_ARGS__) + +#endif diff --git a/drivers/misc/modem_if/modem_boot_device_spi.c b/drivers/misc/modem_if/modem_boot_device_spi.c new file mode 100644 index 000000000000..2aad1b9244de --- /dev/null +++ b/drivers/misc/modem_if/modem_boot_device_spi.c @@ -0,0 +1,285 @@ +/* + * Copyright (C) 2011 Samsung Electronics. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "modem_prj.h" + +#define SPI_XMIT_DELEY 100 + +static int check_cp_status(unsigned int gpio_cp_status, unsigned int count) +{ + int ret = 0; + int cnt = 0; + + while (1) { + if (gpio_get_value(gpio_cp_status) != 0) { + ret = 0; + break; + } + + cnt++; + if (cnt >= count) { + mif_err("ERR! gpio_cp_status == 0 (cnt %d)\n", cnt); + ret = -EFAULT; + break; + } + + msleep(20); + } + + return ret; +} + +static inline int spi_send(struct modem_boot_spi *loader, const char val) +{ + char buff[1]; + int ret; + struct spi_message msg; + struct spi_transfer xfer = { + .len = 1, + .tx_buf = buff, + }; + + buff[0] = val; + + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + + ret = spi_sync(loader->spi_dev, &msg); + if (ret < 0) + mif_err("ERR! spi_sync fail (err %d)\n", ret); + + return ret; +} + +static int spi_boot_write(struct modem_boot_spi *loader, const char *addr, + const long len) +{ + int i; + int ret = 0; + char *buff = NULL; + mif_err("+++\n"); + + buff = kzalloc(len, GFP_KERNEL); + if (!buff) { + mif_err("ERR! kzalloc(%ld) fail\n", len); + ret = -ENOMEM; + goto exit; + } + + ret = copy_from_user(buff, (const void __user *)addr, len); + if (ret) { + mif_err("ERR! copy_from_user fail (err %d)\n", ret); + ret = -EFAULT; + goto exit; + } + + for (i = 0 ; i < len ; i++) { + ret = spi_send(loader, buff[i]); + if (ret < 0) { + mif_err("ERR! spi_send fail (err %d)\n", ret); + goto exit; + } + } + +exit: + if (buff) + kfree(buff); + + mif_err("---\n"); + return ret; +} + +static int spi_boot_open(struct inode *inode, struct file *filp) +{ + struct modem_boot_spi *loader = to_modem_boot_spi(filp->private_data); + filp->private_data = loader; + return 0; +} + +static long spi_boot_ioctl(struct file *filp, unsigned int cmd, + unsigned long arg) +{ + int ret = 0; + struct modem_firmware img; + struct modem_boot_spi *loader = filp->private_data; + + mutex_lock(&loader->lock); + switch (cmd) { + case IOCTL_MODEM_XMIT_BOOT: + ret = copy_from_user(&img, (const void __user *)arg, + sizeof(struct modem_firmware)); + if (ret) { + mif_err("ERR! copy_from_user fail (err %d)\n", ret); + ret = -EFAULT; + goto exit_err; + } + mif_info("IOCTL_MODEM_XMIT_BOOT (size %d)\n", img.size); + + ret = spi_boot_write(loader, img.binary, img.size); + if (ret < 0) { + mif_err("ERR! spi_boot_write fail (err %d)\n", ret); + break; + } + + if (!loader->gpio_cp_status) + break; + + ret = check_cp_status(loader->gpio_cp_status, 100); + if (ret < 0) + mif_err("ERR! check_cp_status fail (err %d)\n", ret); + + break; + + default: + mif_err("ioctl cmd error\n"); + ret = -ENOIOCTLCMD; + + break; + } + +exit_err: + mutex_unlock(&loader->lock); + + return ret; +} + +static const struct file_operations modem_spi_boot_fops = { + .owner = THIS_MODULE, + .open = spi_boot_open, + .unlocked_ioctl = spi_boot_ioctl, +}; + +static int __devinit modem_spi_boot_probe(struct spi_device *spi) +{ + int ret; + struct modem_boot_spi *loader; + struct modem_boot_spi_platform_data *pdata; + mif_err("+++\n"); + + loader = kzalloc(sizeof(*loader), GFP_KERNEL); + if (!loader) { + mif_err("failed to allocate for modem_boot_spi\n"); + ret = -ENOMEM; + goto err_alloc; + } + mutex_init(&loader->lock); + + spi->bits_per_word = 8; + + if (spi_setup(spi)) { + mif_err("ERR! spi_setup fail\n"); + ret = -EINVAL; + goto err_setup; + } + loader->spi_dev = spi; + + if (!spi->dev.platform_data) { + mif_err("ERR! no platform_data\n"); + ret = -EINVAL; + goto err_setup; + } + pdata = (struct modem_boot_spi_platform_data *)spi->dev.platform_data; + + loader->gpio_cp_status = pdata->gpio_cp_status; + + spi_set_drvdata(spi, loader); + + loader->dev.minor = MISC_DYNAMIC_MINOR; + loader->dev.name = "modem_boot_spi"; + loader->dev.fops = &modem_spi_boot_fops; + ret = misc_register(&loader->dev); + if (ret) { + mif_err("ERR! misc_register fail (err %d)\n", ret); + goto err_setup; + } + +#ifdef AIRPLANE_MODE_TEST + lte_airplane_mode = 0; +#endif + mif_err("---\n"); + return 0; + +err_setup: + mutex_destroy(&loader->lock); + kfree(loader); + +err_alloc: + mif_err("xxx\n"); + return ret; +} + +static int __devexit modem_spi_boot_remove(struct spi_device *spi) +{ + struct modem_boot_spi *loader = spi_get_drvdata(spi); + + misc_deregister(&loader->dev); + mutex_destroy(&loader->lock); + kfree(loader); + + return 0; +} + +static struct spi_driver modem_spi_boot_driver = { + .driver = { + .name = "modem_boot_spi", + .owner = THIS_MODULE, + }, + .probe = modem_spi_boot_probe, + .remove = __devexit_p(modem_spi_boot_remove), +}; + +static int __init modem_spi_boot_init(void) +{ + int err; + mif_err("+++\n"); + + err = spi_register_driver(&modem_spi_boot_driver); + if (err) { + mif_err("spi_register_driver fail (err %d)\n", err); + mif_err("xxx\n"); + return err; + } + + mif_err("---\n"); + return 0; +} + +static void __exit modem_spi_boot_exit(void) +{ + spi_unregister_driver(&modem_spi_boot_driver); +} + +module_init(modem_spi_boot_init); +module_exit(modem_spi_boot_exit); + +MODULE_DESCRIPTION("SPI Driver for Downloading Modem Bootloader"); +MODULE_LICENSE("GPL"); diff --git a/drivers/misc/modem_if/modem_dump.c b/drivers/misc/modem_if/modem_dump.c new file mode 100644 index 000000000000..f3a2041b44cd --- /dev/null +++ b/drivers/misc/modem_if/modem_dump.c @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2016 Samsung Electronics. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include "modem_prj.h" +#include "modem_utils.h" +#include "modem_link_device_shmem.h" + +static int save_dump_file(struct link_device *ld, struct io_device *iod, + unsigned long arg, u8 __iomem *dump_base, size_t dump_size) +{ + size_t copied = 0; + struct sk_buff *skb; + unsigned int alloc_size = 0xE00; + int ret; + + if (dump_size == 0 || dump_base == NULL) { + mif_err("ERR! save_dump_file fail!\n"); + return -EFAULT; + } + + ret = copy_to_user((void __user *)arg, &dump_size, sizeof(dump_size)); + if (ret) { + mif_err("ERR! copy_from_user fail!\n"); + return -EFAULT; + } + + while (copied < dump_size) { + if (dump_size - copied < alloc_size) + alloc_size = dump_size - copied; + + skb = alloc_skb(alloc_size, GFP_ATOMIC); + if (!skb) { + skb_queue_purge(&iod->sk_rx_q); + mif_err("ERR! alloc_skb fail, purged skb_rx_q\n"); + return -ENOMEM; + } + + memcpy(skb_put(skb, alloc_size), dump_base + copied, + alloc_size); + copied += alloc_size; + + /* Record the IO device and the link device into the &skb->cb */ + skbpriv(skb)->iod = iod; + skbpriv(skb)->ld = ld; + + skbpriv(skb)->lnk_hdr = false; + skbpriv(skb)->exynos_ch = iod->id; + + ret = iod->recv_skb_single(iod, ld, skb); + if (unlikely(ret < 0)) { + struct modem_ctl *mc = ld->mc; + + mif_err_limited("%s: %s<-%s: %s->recv_skb fail (%d)\n", + ld->name, iod->name, mc->name, iod->name, ret); + dev_kfree_skb_any(skb); + return ret; + } + } + + mif_info("Complete! (%zu bytes)\n", copied); + + return 0; +} + +int save_vss_dump(struct link_device *ld, struct io_device *iod, + unsigned long arg) +{ + struct shmem_link_device *shmd = to_shmem_link_device(ld); + size_t vss_size = shm_get_vss_size(); + + if (vss_size == 0 || shmd->vss_base == NULL) { + mif_err("ERR! save_vss_dump fail!\n"); + return -EFAULT; + } + + return save_dump_file(ld, iod, arg, shmd->vss_base, vss_size); +} + +int save_acpm_dump(struct link_device *ld, struct io_device *iod, + unsigned long arg) +{ + struct shmem_link_device *shmd = to_shmem_link_device(ld); + size_t acpm_size = shm_get_acpm_size(); + + if (acpm_size == 0 || shmd->acpm_base == NULL) { + mif_err("ERR! save_acpm_dump fail!\n"); + return -EFAULT; + } + + return save_dump_file(ld, iod, arg, shmd->acpm_base, acpm_size); +} + +int save_shmem_dump(struct link_device *ld, struct io_device *iod, + unsigned long arg) +{ + struct shmem_link_device *shmd = to_shmem_link_device(ld); + size_t shmem_size = shmd->size; + + if (shmem_size == 0 || shmd->base == NULL) { + mif_err("ERR! save_shmem_dump fail!\n"); + return -EFAULT; + } + + return save_dump_file(ld, iod, arg, (u8 __iomem *)shmd->base, shmem_size); +} diff --git a/drivers/misc/modem_if/modem_dump.h b/drivers/misc/modem_if/modem_dump.h new file mode 100644 index 000000000000..4286fd55d221 --- /dev/null +++ b/drivers/misc/modem_if/modem_dump.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2016 Samsung Electronics. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __MODEM_DUMP_H__ +#define __MODEM_DUMP_H__ + +#include +#include "modem_prj.h" + +int save_shmem_dump(struct link_device *mld, struct io_device *iod, + unsigned long arg); +int save_acpm_dump(struct link_device *mld, struct io_device *iod, + unsigned long arg); +int save_vss_dump(struct link_device *mld, struct io_device *iod, + unsigned long arg); +#endif diff --git a/drivers/misc/modem_if/modem_io_device.c b/drivers/misc/modem_if/modem_io_device.c new file mode 100644 index 000000000000..521a6c31a979 --- /dev/null +++ b/drivers/misc/modem_if/modem_io_device.c @@ -0,0 +1,1672 @@ +/* + * Copyright (C) 2010 Samsung Electronics. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "modem_prj.h" +#include "modem_utils.h" + + +static u16 exynos_build_fr_config(struct io_device *iod, struct link_device *ld, + unsigned int count); + +static void exynos_build_header(struct io_device *iod, struct link_device *ld, + u8 *buff, u16 cfg, u8 ctl, size_t count); + +static ssize_t show_waketime(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned int msec; + char *p = buf; + struct miscdevice *miscdev = dev_get_drvdata(dev); + struct io_device *iod = container_of(miscdev, struct io_device, + miscdev); + + msec = jiffies_to_msecs(iod->waketime); + + p += snprintf(buf, PAGE_SIZE, "raw waketime : %ums\n", msec); + + return p - buf; +} + +static ssize_t store_waketime(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + unsigned long msec; + int ret; + struct miscdevice *miscdev = dev_get_drvdata(dev); + struct io_device *iod = container_of(miscdev, struct io_device, + miscdev); + + ret = kstrtoul(buf, 10, &msec); + if (ret) + return count; + + iod->waketime = msecs_to_jiffies(msec); + + return count; +} + +static struct device_attribute attr_waketime = + __ATTR(waketime, S_IRUGO | S_IWUSR, show_waketime, store_waketime); + +static ssize_t show_loopback(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct miscdevice *miscdev = dev_get_drvdata(dev); + struct modem_shared *msd = + container_of(miscdev, struct io_device, miscdev)->msd; + unsigned char *ip = (unsigned char *)&msd->loopback_ipaddr; + char *p = buf; + + p += snprintf(buf, PAGE_SIZE, "%u.%u.%u.%u\n", + ip[0], ip[1], ip[2], ip[3]); + + return p - buf; +} + +static ssize_t store_loopback(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct miscdevice *miscdev = dev_get_drvdata(dev); + struct modem_shared *msd = + container_of(miscdev, struct io_device, miscdev)->msd; + + msd->loopback_ipaddr = ipv4str_to_be32(buf, count); + + return count; +} + +static struct device_attribute attr_loopback = + __ATTR(loopback, S_IRUGO | S_IWUSR, show_loopback, store_loopback); + +static void iodev_showtxlink(struct io_device *iod, void *args) +{ + char **p = (char **)args; + struct link_device *ld = get_current_link(iod); + + if (iod->io_typ == IODEV_NET && IS_CONNECTED(iod, ld)) + *p += snprintf(*p, PAGE_SIZE, "%s<->%s\n", iod->name, ld->name); +} + +static ssize_t show_txlink(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct miscdevice *miscdev = dev_get_drvdata(dev); + struct modem_shared *msd = + container_of(miscdev, struct io_device, miscdev)->msd; + char *p = buf; + + iodevs_for_each(msd, iodev_showtxlink, &p); + + return p - buf; +} + +static ssize_t store_txlink(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + /* don't change without gpio dynamic switching */ + return -EINVAL; +} + +static struct device_attribute attr_txlink = + __ATTR(txlink, S_IRUGO | S_IWUSR, show_txlink, store_txlink); + +static inline void iodev_lock_wlock(struct io_device *iod) +{ + if (iod->waketime > 0 && !wake_lock_active(&iod->wakelock)) + wake_lock_timeout(&iod->wakelock, iod->waketime); +} +static int netif_flow_ctrl(struct link_device *ld, struct sk_buff *skb) +{ + u8 cmd = skb->data[0]; + + if (cmd == FLOW_CTRL_SUSPEND) { + if (ld->suspend_netif_tx) + goto exit; + ld->suspend_netif_tx = true; + mif_netif_stop(ld); + mif_info("%s: FLOW_CTRL_SUSPEND\n", ld->name); + } else if (cmd == FLOW_CTRL_RESUME) { + if (!ld->suspend_netif_tx) + goto exit; + ld->suspend_netif_tx = false; + mif_netif_wake(ld); + mif_info("%s: FLOW_CTRL_RESUME\n", ld->name); + } else { + mif_info("%s: ERR! invalid command %02X\n", ld->name, cmd); + } + +exit: + dev_kfree_skb_any(skb); + return 0; +} + +static inline int queue_skb_to_iod(struct sk_buff *skb, struct io_device *iod) +{ + struct sk_buff_head *rxq = &iod->sk_rx_q; + + skb_queue_tail(rxq, skb); + + if (iod->format < IPC_MULTI_RAW && rxq->qlen > MAX_IOD_RXQ_LEN) { + mif_err("%s: %s application may be dead (rxq->qlen %d > %d)\n", + iod->name, iod->app ? iod->app : "corresponding", + rxq->qlen, MAX_IOD_RXQ_LEN); + skb_queue_purge(rxq); + return -ENOSPC; + } + + mif_debug("%s: rxq->qlen = %d\n", iod->name, rxq->qlen); + wake_up(&iod->wq); + + return 0; +} + +static int rx_drain(struct sk_buff *skb) +{ + dev_kfree_skb_any(skb); + return 0; +} + +static int rx_loopback(struct sk_buff *skb) +{ + struct io_device *iod = skbpriv(skb)->iod; + struct link_device *ld = skbpriv(skb)->ld; + int ret; + + ret = ld->send(ld, iod, skb); + if (ret < 0) { + mif_err("%s->%s: ERR! ld->send fail (err %d)\n", + iod->name, ld->name, ret); + } + + return ret; +} + +static int gather_multi_frame(struct exynos_link_header *hdr, + struct sk_buff *skb) +{ + u16 ctrl = hdr->cfg; + struct io_device *iod = skbpriv(skb)->iod; + struct modem_ctl *mc = iod->mc; + struct sk_buff_head *multi_q = &iod->sk_multi_q[exynos_multi_packet_index(ctrl)]; + int len = skb->len; + +#ifdef DEBUG_MODEM_IF_LINK_RX + /* If there has been no multiple frame with this ID, ... */ + if (skb_queue_empty(multi_q)) { + mif_err("%s<-%s: start of multi-frame (pkt_index:%d fr_index:%d len:%d)\n", + iod->name, mc->name, exynos_multi_packet_index(ctrl), + exynos_multi_frame_index(ctrl), hdr->len); + } +#endif + skb_queue_tail(multi_q, skb); + + if (!exynos_multi_last(ctrl)) { + /* It is the last frame because the "more" bit is 0. */ + mif_err("%s<-%s: recv of multi-frame (CH_ID:0x%02x rcvd:%d)\n", + iod->name, mc->name, hdr->ch_id, skb->len); + } else { + struct sk_buff_head *rxq = &iod->sk_rx_q; + struct sk_buff *skb_new; + struct sk_buff *skb_tmp; + int total_len = 0; + + /* The last frame has not arrived yet. */ + mif_err("%s<-%s: end multi-frame (CH_ID:0x%02x rcvd:%d)\n", + iod->name, mc->name, hdr->ch_id, skb->len); + + /* check totoal multi packet size */ + skb_tmp = skb_peek(multi_q); + + while (skb_tmp != NULL) { + total_len += skb_tmp->len; + skb_tmp = skb_peek_next(skb_tmp, multi_q); + } + mif_info("Total multi-frame packet size is %d\n", total_len); + + skb_new = alloc_skb(total_len, GFP_KERNEL); + if (unlikely(skb_new == NULL)) { + mif_err("ERR - alloc_skb fail\n"); + skb_queue_purge(multi_q); + return -ENOMEM; + } + + while (!skb_queue_empty(multi_q)) { + skb_tmp = skb_dequeue(multi_q); + memcpy(skb_put(skb_new, skb_tmp->len), skb_tmp->data, skb_tmp->len); + } + + skb_trim(skb_new, skb_new->len); + skb_queue_tail(rxq, skb_new); + + skb_queue_purge(multi_q); + skb_queue_head_init(multi_q); + + wake_up(&iod->wq); + } + + return len; +} + +static inline int rx_frame_with_link_header(struct sk_buff *skb) +{ + struct exynos_link_header *hdr; + bool single_frame; + + /* Remove EXYNOS link header */ + hdr = (struct exynos_link_header *)skb->data; + skb_pull(skb, EXYNOS_HEADER_SIZE); + single_frame = exynos_single_frame((u8 *)hdr); + + if (single_frame) + return queue_skb_to_iod(skb, skbpriv(skb)->iod); + else + return gather_multi_frame(hdr, skb); +} + +static int rx_fmt_ipc(struct sk_buff *skb) +{ + if (skbpriv(skb)->lnk_hdr) + return rx_frame_with_link_header(skb); + else + return queue_skb_to_iod(skb, skbpriv(skb)->iod); +} + +static int rx_raw_misc(struct sk_buff *skb) +{ + struct io_device *iod = skbpriv(skb)->iod; + + if (skbpriv(skb)->lnk_hdr) { + /* Remove the EXYNOS link header */ + skb_pull(skb, EXYNOS_HEADER_SIZE); + } + + queue_skb_to_iod(skb, iod); + + return 0; +} + +static int rx_multi_pdp(struct sk_buff *skb) +{ + struct link_device *ld = skbpriv(skb)->ld; + struct io_device *iod = skbpriv(skb)->iod; + struct net_device *ndev; + struct iphdr *iphdr; + int ret; + + ndev = iod->ndev; + if (!ndev) { + mif_info("%s: ERR! no iod->ndev\n", iod->name); + return -ENODEV; + } + + /* Remove the EXYNOS link header */ + skb_pull(skb, EXYNOS_HEADER_SIZE); + + skb->dev = ndev; + ndev->stats.rx_packets++; + ndev->stats.rx_bytes += skb->len; + + /* check the version of IP */ + iphdr = (struct iphdr *)skb->data; + if (iphdr->version == IP6VERSION) + skb->protocol = htons(ETH_P_IPV6); + else + skb->protocol = htons(ETH_P_IP); + + if (iod->use_handover) { + struct ethhdr *ehdr; + const char source[ETH_ALEN] = SOURCE_MAC_ADDR; + + ehdr = (struct ethhdr *)skb_push(skb, sizeof(struct ethhdr)); + memcpy(ehdr->h_dest, ndev->dev_addr, ETH_ALEN); + memcpy(ehdr->h_source, source, ETH_ALEN); + ehdr->h_proto = skb->protocol; + skb->ip_summed = CHECKSUM_UNNECESSARY; + skb_reset_mac_header(skb); + skb_pull(skb, sizeof(struct ethhdr)); + } + + if (in_interrupt()) + ret = netif_rx(skb); + else + ret = netif_rx_ni(skb); + + if (ret != NET_RX_SUCCESS) { + mif_err("%s->%s: ERR! netif_rx fail (err %d)\n", + ld->name, iod->name, ret); + } + + return ret; +} + +static int rx_demux(struct link_device *ld, struct sk_buff *skb) +{ + struct io_device *iod; + u8 ch = skbpriv(skb)->exynos_ch; + + if (unlikely(ch == 0)) { + mif_err("%s: ERR! invalid ch# %d\n", ld->name, ch); + return -ENODEV; + } + + if (unlikely(ch == EXYNOS_CH_ID_FLOW_CTRL)) + return netif_flow_ctrl(ld, skb); + + /* IP loopback */ + if (ch == DATA_LOOPBACK_CHANNEL && ld->msd->loopback_ipaddr) + ch = EXYNOS_CH_ID_PDP_0; + + iod = link_get_iod_with_channel(ld, ch); + if (unlikely(!iod)) { + mif_err("%s: ERR! no iod with ch# %d\n", ld->name, ch); + return -ENODEV; + } + + skbpriv(skb)->ld = ld; + skbpriv(skb)->iod = iod; + + /* Don't care whether or not DATA_DRAIN_CHANNEL is opened */ + if (iod->id == DATA_DRAIN_CHANNEL) + return rx_drain(skb); + + /* Don't care whether or not DATA_LOOPBACK_CHANNEL is opened */ + if (iod->id == DATA_LOOPBACK_CHANNEL) + return rx_loopback(skb); + + /* Print recieved data from CP */ + log_ipc_pkt(skb, IODEV, RX); + + if (atomic_read(&iod->opened) <= 0) { + mif_err("%s: ERR! %s is not opened\n", ld->name, iod->name); + return -ENODEV; + } + + if (exynos_fmt_ch(ch) || exynos_rcs_ch(ch) || exynos_ppt_ch(ch)) + return rx_fmt_ipc(skb); + else if (exynos_ps_ch(ch)) + return rx_multi_pdp(skb); + else + return rx_raw_misc(skb); +} + +/** + * rx_frame_config + * @iod: pointer to an instance of io_device structure + * @ld: pointer to an instance of link_device structure + * @buff: pointer to a buffer in which incoming data is stored + * @size: size of data in the buffer + * @frm: pointer to an instance of exynos_frame_data structure + * + * 1) Checks a config field + * 2) Calculates the length of link layer header in an incoming frame and stores + * the value to "frm->hdr_len" + * 3) Stores the config field to "frm->hdr" and add the size of config field to + * "frm->hdr_rcvd" + * + * Returns the length of a config field that was copied to "frm" + */ +static int rx_frame_config(struct io_device *iod, struct link_device *ld, + u8 *buff, int size, struct exynos_frame_data *frm) +{ + int rest; + int rcvd; + + if (unlikely(!exynos_start_valid(buff))) { + mif_err("%s->%s: ERR! INVALID config 0x%02x\n", + ld->name, iod->name, buff[0]); + return -EBADMSG; + } + + frm->hdr_len = EXYNOS_HEADER_SIZE; + + /* Calculate the size of a segment that will be copied */ + rest = frm->hdr_len; + rcvd = EXYNOS_FRAG_CONFIG_SIZE; + mif_debug("%s->%s: hdr_len:%d hdr_rcvd:%d rest:%d size:%d rcvd:%d\n", + ld->name, iod->name, frm->hdr_len, frm->hdr_rcvd, rest, size, + rcvd); + + /* Copy the config field of an EXYNOS link header to the header buffer */ + memcpy(frm->hdr, buff, rcvd); + frm->hdr_rcvd += rcvd; + + return rcvd; +} + +/** + * rx_frame_prepare_skb + * @iod: pointer to an instance of io_device structure + * @ld: pointer to an instance of link_device structure + * @frm: pointer to an instance of exynos_frame_data structure + * + * 1) Extracts the length of a link frame from the link header in "frm->hdr" + * 2) Allocates an skb + * 3) Calculates the payload size in the link frame + * 4) Calculates the padding size in the link frame + * + * Returns the pointer to an skb + */ +static struct sk_buff *rx_frame_prepare_skb(struct io_device *iod, + struct link_device *ld, struct exynos_frame_data *frm) +{ + struct sk_buff *skb; + + /* Get the frame length */ + frm->len = exynos_get_frame_len(frm->hdr); + + /* Allocate an skb */ + skb = rx_alloc_skb(frm->len, iod, ld); + if (!skb) { + mif_err("%s->%s: ERR! rx_alloc_skb fail (size %d)\n", + ld->name, iod->name, frm->len); + return NULL; + } + + /* Calculates the payload size */ + frm->pay_len = frm->len - frm->hdr_len; + + /* Calculates the padding size */ + if (exynos_padding_exist(frm->hdr)) + frm->pad_len = exynos_calc_padding_size(frm->len); + + mif_debug("%s->%s: size %d (header:%d payload:%d padding:%d)\n", + ld->name, iod->name, frm->len, frm->hdr_len, frm->pay_len, + frm->pad_len); + + return skb; +} + +/** + * rx_frame_header + * @iod: pointer to an instance of io_device structure + * @ld: pointer to an instance of link_device structure + * @buff: pointer to a buffer in which incoming data is stored + * @size: size of data in the buffer + * @frm: pointer to an instance of exynos_frame_data structure + * + * 1) Stores a link layer header to "frm->hdr" temporarily while "frm->hdr_rcvd" + * is less than "frm->hdr_len" + * 2) Then, + * Allocates an skb + * Copies the link header from "frm" to "skb" + * Register the skb to receive payload + * + * Returns the size of a segment that was copied to "frm" + */ +static int rx_frame_header(struct io_device *iod, struct link_device *ld, + u8 *buff, int size, struct exynos_frame_data *frm) +{ + struct sk_buff *skb; + int rest; + int rcvd; + + /* Calculate the size of a segment that will be copied */ + rest = frm->hdr_len - frm->hdr_rcvd; + rcvd = min(rest, size); + mif_debug("%s->%s: hdr_len:%d hdr_rcvd:%d rest:%d size:%d rcvd:%d\n", + ld->name, iod->name, frm->hdr_len, frm->hdr_rcvd, rest, size, + rcvd); + + /* Copy a segment of an EXYNOS link header to "frm" */ + memcpy((frm->hdr + frm->hdr_rcvd), buff, rcvd); + frm->hdr_rcvd += rcvd; + + if (frm->hdr_rcvd >= frm->hdr_len) { + /* Prepare an skb with the information in {iod, ld, frm} */ + skb = rx_frame_prepare_skb(iod, ld, frm); + if (!skb) { + mif_err("%s->%s: ERR! rx_frame_prepare_skb fail\n", + ld->name, iod->name); + return -ENOMEM; + } + + /* Copy an EXYNOS link header from "frm" to "skb" */ + memcpy(skb_put(skb, frm->hdr_len), frm->hdr, frm->hdr_len); + + /* Register the skb to receive payload */ + fragdata(iod, ld)->skb_recv = skb; + } + + return rcvd; +} + +/** + * rx_frame_payload + * @iod: pointer to an instance of io_device structure + * @ld: pointer to an instance of link_device structure + * @buff: pointer to a buffer in which incoming data is stored + * @size: size of data in the buffer + * @frm: pointer to an instance of exynos_frame_data structure + * + * Stores a link layer payload to "skb" + * + * Returns the size of a segment that was copied to "skb" + */ +static int rx_frame_payload(struct io_device *iod, struct link_device *ld, + u8 *buff, int size, struct exynos_frame_data *frm) +{ + struct sk_buff *skb = fragdata(iod, ld)->skb_recv; + int rest; + int rcvd; + + /* Calculate the size of a segment that will be copied */ + rest = frm->pay_len - frm->pay_rcvd; + rcvd = min(rest, size); + mif_debug("%s->%s: pay_len:%d pay_rcvd:%d rest:%d size:%d rcvd:%d\n", + ld->name, iod->name, frm->pay_len, frm->pay_rcvd, rest, size, + rcvd); + + /* Copy an EXYNOS link payload to "skb" */ + memcpy(skb_put(skb, rcvd), buff, rcvd); + frm->pay_rcvd += rcvd; + + return rcvd; +} + +static int rx_frame_padding(struct io_device *iod, struct link_device *ld, + u8 *buff, int size, struct exynos_frame_data *frm) +{ + struct sk_buff *skb = fragdata(iod, ld)->skb_recv; + int rest; + int rcvd; + + /* Calculate the size of a segment that will be dropped as padding */ + rest = frm->pad_len - frm->pad_rcvd; + rcvd = min(rest, size); + mif_debug("%s->%s: pad_len:%d pad_rcvd:%d rest:%d size:%d rcvd:%d\n", + ld->name, iod->name, frm->pad_len, frm->pad_rcvd, rest, size, + rcvd); + + /* Copy an EXYNOS link padding to "skb" */ + memcpy(skb_put(skb, rcvd), buff, rcvd); + frm->pad_rcvd += rcvd; + + return rcvd; +} + +static int rx_frame_done(struct io_device *iod, struct link_device *ld, + struct sk_buff *skb) +{ + /* Cut off the padding of the current frame */ + skb_trim(skb, exynos_get_frame_len(skb->data)); + mif_debug("%s->%s: frame length = %d\n", ld->name, iod->name, skb->len); + + return rx_demux(ld, skb); +} + +static int recv_frame_from_buff(struct io_device *iod, struct link_device *ld, + const char *data, unsigned int size) +{ + struct exynos_frame_data *frm = &fragdata(iod, ld)->f_data; + struct sk_buff *skb; + u8 *buff = (u8 *)data; + int rest = (int)size; + int done = 0; + int err = 0; + + mif_debug("%s->%s: size %d (RX state = %s)\n", ld->name, iod->name, + size, get_rx_state_str(iod->curr_rx_state)); + + while (rest > 0) { + switch (iod->curr_rx_state) { + case IOD_RX_ON_STANDBY: + fragdata(iod, ld)->skb_recv = NULL; + memset(frm, 0, sizeof(struct exynos_frame_data)); + + done = rx_frame_config(iod, ld, buff, rest, frm); + if (done < 0) { + err = done; + goto err_exit; + } + + iod->next_rx_state = IOD_RX_HEADER; + + break; + + case IOD_RX_HEADER: + done = rx_frame_header(iod, ld, buff, rest, frm); + if (done < 0) { + err = done; + goto err_exit; + } + + if (frm->hdr_rcvd >= frm->hdr_len) + iod->next_rx_state = IOD_RX_PAYLOAD; + else + iod->next_rx_state = IOD_RX_HEADER; + + break; + + case IOD_RX_PAYLOAD: + done = rx_frame_payload(iod, ld, buff, rest, frm); + if (done < 0) { + err = done; + goto err_exit; + } + + if (frm->pay_rcvd >= frm->pay_len) { + if (frm->pad_len > 0) + iod->next_rx_state = IOD_RX_PADDING; + else + iod->next_rx_state = IOD_RX_ON_STANDBY; + } else { + iod->next_rx_state = IOD_RX_PAYLOAD; + } + + break; + + case IOD_RX_PADDING: + done = rx_frame_padding(iod, ld, buff, rest, frm); + if (done < 0) { + err = done; + goto err_exit; + } + + if (frm->pad_rcvd >= frm->pad_len) + iod->next_rx_state = IOD_RX_ON_STANDBY; + else + iod->next_rx_state = IOD_RX_PADDING; + + break; + + default: + mif_err("%s->%s: ERR! INVALID RX state %d\n", + ld->name, iod->name, iod->curr_rx_state); + err = -EINVAL; + goto err_exit; + } + + if (iod->next_rx_state == IOD_RX_ON_STANDBY) { + /* A complete frame is in fragdata(iod, ld)->skb_recv. */ + skb = fragdata(iod, ld)->skb_recv; + err = rx_frame_done(iod, ld, skb); + if (err < 0) + goto err_exit; + } + + buff += done; + rest -= done; + if (rest < 0) + goto err_range; + + iod->curr_rx_state = iod->next_rx_state; + } + + return size; + +err_exit: + if (fragdata(iod, ld)->skb_recv) { + mif_err("%s->%s: ERR! clear frag (size:%d done:%d rest:%d)\n", + ld->name, iod->name, size, done, rest); + dev_kfree_skb_any(fragdata(iod, ld)->skb_recv); + fragdata(iod, ld)->skb_recv = NULL; + } + iod->curr_rx_state = IOD_RX_ON_STANDBY; + return err; + +err_range: + mif_err("%s->%s: ERR! size:%d done:%d rest:%d\n", + ld->name, iod->name, size, done, rest); + iod->curr_rx_state = IOD_RX_ON_STANDBY; + return size; +} + +/* called from link device when a packet arrives for this io device */ +static int io_dev_recv_data_from_link_dev(struct io_device *iod, + struct link_device *ld, const char *data, unsigned int len) +{ + struct sk_buff *skb; + int err; + + switch (iod->format) { + case IPC_FMT: + case IPC_RAW: + case IPC_RFS: + case IPC_MULTI_RAW: + if (iod->waketime) + wake_lock_timeout(&iod->wakelock, iod->waketime); + + err = recv_frame_from_buff(iod, ld, data, len); + if (err < 0) { + mif_err("%s->%s: ERR! recv_frame_from_buff fail(err %d)\n", + ld->name, iod->name, err); + } + + return err; + + default: + mif_debug("%s->%s: len %d\n", ld->name, iod->name, len); + + /* save packet to sk_buff */ + skb = rx_alloc_skb(len, iod, ld); + if (!skb) { + mif_info("%s->%s: ERR! rx_alloc_skb fail\n", + ld->name, iod->name); + return -ENOMEM; + } + + memcpy(skb_put(skb, len), data, len); + + queue_skb_to_iod(skb, iod); + + return len; + } +} + +/* called from link device when a packet arrives for this io device */ +static int io_dev_recv_skb_from_link_dev(struct io_device *iod, + struct link_device *ld, struct sk_buff *skb) +{ + if (iod->waketime) + wake_lock_timeout(&iod->wakelock, iod->waketime); + + /* Cut off the padding of the current frame */ + skb_trim(skb, exynos_get_frame_len(skb->data)); + mif_debug("%s->%s: frame length = %d\n", ld->name, iod->name, skb->len); + + return rx_demux(ld, skb); +} + +/* called from link device when a packet arrives fo this io device */ +static int io_dev_recv_skb_single_from_link_dev(struct io_device *iod, + struct link_device *ld, struct sk_buff *skb) +{ + iodev_lock_wlock(iod); + + if (skbpriv(skb)->lnk_hdr && ld->aligned) + skb_trim(skb, exynos_get_frame_len(skb->data)); + + return rx_demux(ld, skb); +} +/* inform the IO device that the modem is now online or offline or + * crashing or whatever... + */ +static void io_dev_modem_state_changed(struct io_device *iod, + enum modem_state state) +{ + struct modem_ctl *mc = iod->mc; + int old_state = mc->phone_state; + + if (old_state != state) { + mc->phone_state = state; + mif_err("%s state changed (%s -> %s)\n", mc->name, + get_cp_state_str(old_state), get_cp_state_str(state)); + } + + if (state == STATE_CRASH_RESET || state == STATE_CRASH_EXIT || + state == STATE_NV_REBUILDING || state == STATE_CRASH_WATCHDOG) + wake_up(&iod->wq); +} + +/** + * io_dev_sim_state_changed + * @iod: IPC's io_device + * @sim_online: SIM is online? + */ +static void io_dev_sim_state_changed(struct io_device *iod, bool sim_online) +{ + if (atomic_read(&iod->opened) == 0) { + mif_info("%s: ERR! not opened\n", iod->name); + } else if (iod->mc->sim_state.online == sim_online) { + mif_info("%s: SIM state not changed\n", iod->name); + } else { + iod->mc->sim_state.online = sim_online; + iod->mc->sim_state.changed = true; + mif_info("%s: SIM state changed {online %d, changed %d}\n", + iod->name, iod->mc->sim_state.online, + iod->mc->sim_state.changed); + wake_up(&iod->wq); + } +} + +static void iodev_dump_status(struct io_device *iod, void *args) +{ + if (iod->format == IPC_RAW && iod->io_typ == IODEV_NET) { + struct link_device *ld = get_current_link(iod); + mif_com_log(iod->mc->msd, "%s: %s\n", iod->name, ld->name); + } +} + +static int misc_open(struct inode *inode, struct file *filp) +{ + struct io_device *iod = to_io_device(filp->private_data); + struct modem_shared *msd = iod->msd; + struct link_device *ld; + int ref_cnt; + int ret; + filp->private_data = (void *)iod; + + list_for_each_entry(ld, &msd->link_dev_list, list) { + if (IS_CONNECTED(iod, ld) && ld->init_comm) { + ret = ld->init_comm(ld, iod); + if (ret < 0) { + mif_err("%s<->%s: ERR! init_comm fail(%d)\n", + iod->name, ld->name, ret); + return ret; + } + } + } + + ref_cnt = atomic_inc_return(&iod->opened); + + mif_err("%s (opened %d) by %s\n", iod->name, ref_cnt, current->comm); + + return 0; +} + +static int misc_release(struct inode *inode, struct file *filp) +{ + struct io_device *iod = (struct io_device *)filp->private_data; + struct modem_shared *msd = iod->msd; + struct link_device *ld; + int ref_cnt; + int i; + + if (iod->id != EXYNOS_CH_ID_BOOT || strcmp(current->comm, "cbd") == 0) { + skb_queue_purge(&iod->sk_rx_q); + + /* purge multi_frame queue */ + for (i = 0; i < NUM_EXYNOS_MULTI_FRAME_IDS; i++) + skb_queue_purge(&iod->sk_multi_q[i]); + } + + list_for_each_entry(ld, &msd->link_dev_list, list) { + if (IS_CONNECTED(iod, ld) && ld->terminate_comm) + ld->terminate_comm(ld, iod); + } + + ref_cnt = atomic_dec_return(&iod->opened); + + mif_err("%s (opened %d) by %s\n", iod->name, ref_cnt, current->comm); + + return 0; +} + +static unsigned int misc_poll(struct file *filp, struct poll_table_struct *wait) +{ + struct io_device *iod = (struct io_device *)filp->private_data; + struct modem_ctl *mc = iod->mc; + static int cnt; + + poll_wait(filp, &iod->wq, wait); + + if (!skb_queue_empty(&iod->sk_rx_q) && mc->phone_state != STATE_OFFLINE) + return POLLIN | POLLRDNORM; + + if (mc->phone_state == STATE_CRASH_RESET + || mc->phone_state == STATE_CRASH_EXIT + || mc->phone_state == STATE_NV_REBUILDING + || mc->phone_state == STATE_CRASH_WATCHDOG + || mc->sim_state.changed) { + if (iod->format == IPC_RAW) { + msleep(20); + return 0; + } + if (iod->format == IPC_DUMP) + return 0; + + if ((++cnt % 1000) == 0) { + mif_err("Loading CP Dump File...\n"); + cnt = 0; + + return POLLHUP | POLLERR; + } + + return POLLHUP; + } else { + return 0; + } +} + +static long misc_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + struct io_device *iod = (struct io_device *)filp->private_data; + struct link_device *ld = get_current_link(iod); + struct modem_ctl *mc = iod->mc; + int p_state; + char *buff; + void __user *user_buff; + unsigned long size; + + switch (cmd) { + case IOCTL_MODEM_ON: + if (mc->ops.modem_on) { + mif_err("%s: IOCTL_MODEM_ON\n", iod->name); + return mc->ops.modem_on(mc); + } + mif_err("%s: !mc->ops.modem_on\n", iod->name); + return -EINVAL; + + case IOCTL_MODEM_OFF: + if (mc->ops.modem_off) { + mif_err("%s: IOCTL_MODEM_OFF\n", iod->name); + return mc->ops.modem_off(mc); + } + mif_err("%s: !mc->ops.modem_off\n", iod->name); + return -EINVAL; + + case IOCTL_MODEM_RESET: + if (mc->ops.modem_reset) { + mif_err("%s: IOCTL_MODEM_RESET\n", iod->name); + return mc->ops.modem_reset(mc); + } + mif_err("%s: !mc->ops.modem_reset\n", iod->name); + return -EINVAL; + + case IOCTL_MODEM_BOOT_ON: + if (mc->ops.modem_boot_on) { + mif_err("%s: IOCTL_MODEM_BOOT_ON\n", iod->name); + return mc->ops.modem_boot_on(mc); + } + mif_err("%s: !mc->ops.modem_boot_on\n", iod->name); + return -EINVAL; + + case IOCTL_MODEM_BOOT_OFF: + if (mc->ops.modem_boot_off) { + mif_err("%s: IOCTL_MODEM_BOOT_OFF\n", iod->name); + return mc->ops.modem_boot_off(mc); + } + mif_err("%s: !mc->ops.modem_boot_off\n", iod->name); + return -EINVAL; + + case IOCTL_MODEM_BOOT_DONE: + mif_err("%s: IOCTL_MODEM_BOOT_DONE\n", iod->name); + if (mc->ops.modem_boot_done) + return mc->ops.modem_boot_done(mc); + return 0; + + case IOCTL_MODEM_STATUS: + mif_debug("%s: IOCTL_MODEM_STATUS\n", iod->name); + + p_state = mc->phone_state; + + if (p_state != STATE_ONLINE) { + mif_err("%s: IOCTL_MODEM_STATUS (state %s)\n", + iod->name, get_cp_state_str(p_state)); + } + + if (mc->sim_state.changed) { + int s_state = mc->sim_state.online ? + STATE_SIM_ATTACH : STATE_SIM_DETACH; + mc->sim_state.changed = false; + return s_state; + } + + if (p_state == STATE_NV_REBUILDING) + mc->phone_state = STATE_ONLINE; + + return p_state; + + case IOCTL_MODEM_DL_START: + if (ld->dload_start) { + mif_info("%s: IOCTL_MODEM_DL_START\n", iod->name); + return ld->dload_start(ld, iod); + } + mif_err("%s: !ld->dload_start\n", iod->name); + return -EINVAL; + + case IOCTL_MODEM_FW_UPDATE: + if (ld->firm_update) { + mif_info("%s: IOCTL_MODEM_FW_UPDATE\n", iod->name); + return ld->firm_update(ld, iod, arg); + } + mif_err("%s: !ld->firm_update\n", iod->name); + return -EINVAL; + + case IOCTL_MODEM_FORCE_CRASH_EXIT: + if (mc->ops.modem_force_crash_exit) { + mif_err("%s: IOCTL_MODEM_FORCE_CRASH_EXIT\n", + iod->name); + return mc->ops.modem_force_crash_exit(mc); + } + mif_err("%s: !mc->ops.modem_force_crash_exit\n", iod->name); + return -EINVAL; + + case IOCTL_MODEM_DUMP_RESET: + if (mc->ops.modem_dump_reset) { + mif_info("%s: IOCTL_MODEM_DUMP_RESET\n", iod->name); + return mc->ops.modem_dump_reset(mc); + } + mif_err("%s: !mc->ops.modem_dump_reset\n", iod->name); + return -EINVAL; + + case IOCTL_MODEM_DUMP_START: + if (mc->ops.modem_dump_start) { + mif_err("%s: IOCTL_MODEM_DUMP_START\n", iod->name); + return mc->ops.modem_dump_start(mc); + } else if (ld->dump_start) { + mif_err("%s: IOCTL_MODEM_DUMP_START\n", iod->name); + return ld->dump_start(ld, iod); + } + mif_err("%s: !dump_start\n", iod->name); + return -EINVAL; + + case IOCTL_MODEM_RAMDUMP_START: + if (ld->dump_start) { + mif_info("%s: IOCTL_MODEM_RAMDUMP_START\n", iod->name); + return ld->dump_start(ld, iod); + } + mif_err("%s: !ld->dump_start\n", iod->name); + return -EINVAL; + + case IOCTL_MODEM_DUMP_UPDATE: + if (ld->dump_update) { + mif_info("%s: IOCTL_MODEM_DUMP_UPDATE\n", iod->name); + return ld->dump_update(ld, iod, arg); + } + mif_err("%s: !ld->dump_update\n", iod->name); + return -EINVAL; + + case IOCTL_MODEM_RAMDUMP_STOP: + if (ld->dump_finish) { + mif_info("%s: IOCTL_MODEM_RAMDUMP_STOP\n", iod->name); + return ld->dump_finish(ld, iod, arg); + } + mif_err("%s: !ld->dump_finish\n", iod->name); + return -EINVAL; + + case IOCTL_MODEM_CP_UPLOAD: + buff = iod->msd->cp_crash_info + strlen(CP_CRASH_TAG); + user_buff = (void __user *)arg; + + mif_info("%s: IOCTL_MODEM_CP_UPLOAD\n", iod->name); + memmove(iod->msd->cp_crash_info, CP_CRASH_TAG, + strlen(CP_CRASH_TAG)); + if (arg) { + if (copy_from_user(buff, user_buff, MAX_CPINFO_SIZE)) + return -EFAULT; + } + panic(iod->msd->cp_crash_info); + return 0; + + case IOCTL_MODEM_PROTOCOL_SUSPEND: + mif_info("%s: IOCTL_MODEM_PROTOCOL_SUSPEND\n", iod->name); + if (iod->format == IPC_MULTI_RAW) { + iodevs_for_each(iod->msd, iodev_netif_stop, 0); + return 0; + } + return -EINVAL; + + case IOCTL_MODEM_PROTOCOL_RESUME: + mif_info("%s: IOCTL_MODEM_PROTOCOL_RESUME\n", iod->name); + if (iod->format != IPC_MULTI_RAW) { + iodevs_for_each(iod->msd, iodev_netif_wake, 0); + return 0; + } + return -EINVAL; + + case IOCTL_MIF_LOG_DUMP: + user_buff = (void __user *)arg; + + iodevs_for_each(iod->msd, iodev_dump_status, 0); + size = MAX_MIF_BUFF_SIZE; + if (copy_to_user(user_buff, &size, sizeof(unsigned long))) + return -EFAULT; + mif_dump_log(mc->msd, iod); + return 0; + + case IOCTL_SHMEM_FULL_DUMP: + mif_info("%s: IOCTL_SHMEM_FULL_DUMP\n", iod->name); + if (ld->shmem_dump) + return ld->shmem_dump(ld, iod, arg); + else + return -EINVAL; + + case IOCTL_VSS_FULL_DUMP: + mif_info("%s: IOCTL_VSS_FULL_DUMP\n", iod->name); + if (ld->vss_dump) + return ld->vss_dump(ld, iod, arg); + else + return -EINVAL; + + case IOCTL_ACPM_FULL_DUMP: + mif_info("%s: IOCTL_ACPM_FULL_DUMP\n", iod->name); + if (ld->acpm_dump) + return ld->acpm_dump(ld, iod, arg); + else + return -EINVAL; + + case IOCTL_MODEM_GET_SHMEM_INFO: + mif_debug("<%s> IOCTL_MODEM_GET_SHMEM_INFO\n", iod->name); + return 0; + /* Below codes will be used next time. + * return iod->mc->ops.modem_get_meminfo(iod->mc, arg); + */ + + case IOCTL_SEC_CP_INIT: + if (ld->sec_init) { + mif_info("%s: IOCTL_MODEM_SEC_INIT\n", iod->name); + return ld->sec_init(ld, iod, arg); + } + return -EINVAL; + + case IOCTL_CHECK_SECURITY: + if (ld->check_security) { + mif_info("%s: IOCTL_MODEM_CHECK_SECURITY\n", iod->name); + return ld->check_security(ld, iod, arg); + } + mif_err("%s: !ld->check_security\n", iod->name); + return -EINVAL; + + case IOCTL_XMIT_BIN: + if (ld->xmit_bin) + return ld->xmit_bin(ld, iod, arg); + return -EINVAL; + + case IOCTL_MODEM_CRASH_REASON: + if (ld->crash_reason) { + mif_info("%s: IOCTL_MODEM_CRASH_REASON\n", iod->name); + return ld->crash_reason(ld, iod, arg); + } + mif_err("%s: !ld->crash_reason\n", iod->name); + return -EINVAL; + default: + /* If you need to handle the ioctl for specific link device, + * then assign the link ioctl handler to ld->ioctl + * It will be call for specific link ioctl + */ + if (ld->ioctl) + return ld->ioctl(ld, iod, cmd, arg); + + mif_info("%s: ERR! undefined cmd 0x%X\n", iod->name, cmd); + return -EINVAL; + } + + return 0; +} + +static ssize_t misc_write(struct file *filp, const char __user *data, + size_t count, loff_t *fpos) +{ + struct io_device *iod = (struct io_device *)filp->private_data; + struct link_device *ld = get_current_link(iod); + struct modem_ctl *mc = iod->mc; + struct sk_buff *skb; + u8 *buff; + int ret; + u32 copied = 0, tot_frame = 0, copied_frm = 0; + u32 remains, alloc_size; + size_t headroom; + size_t tailroom; + size_t tx_bytes; + u16 fr_cfg; + + if (iod->format <= IPC_RFS && iod->id == 0) + return -EINVAL; + + if (unlikely(!cp_online(mc)) && exynos_ipc_ch(iod->id)) { + mif_debug("%s: ERR! %s->state == %s\n", + iod->name, mc->name, get_cp_state_str(mc->phone_state)); + return -EPERM; + } + + if (iod->link_header) { + fr_cfg = exynos_build_fr_config(iod, ld, count); + headroom = EXYNOS_HEADER_SIZE; + } else { + fr_cfg = 0; + headroom = 0; + tailroom = 0; + } + + while (copied < count) { + remains = count - copied; + tx_bytes = (remains > SZ_2K - headroom) ? SZ_2K - headroom : remains; + + if (iod->format >= IPC_BOOT) + tx_bytes = count; + + if (ld->aligned) + tailroom = exynos_calc_padding_size(headroom + tx_bytes); + else + tailroom = 0; + + alloc_size = headroom + tx_bytes + tailroom; + + skb = alloc_skb(alloc_size, GFP_KERNEL); + if (!skb) { + mif_info("%s: ERR! alloc_skb fail (tx_bytes:%ld)\n", + iod->name, tx_bytes); + return -ENOMEM; + } + + /* Build EXYNOS link header*/ + if (fr_cfg) { + buff = skb_put(skb, headroom); + exynos_build_header(iod, ld, buff, fr_cfg, 0, tx_bytes); + + /* modify next link header for multiframe */ + if (((fr_cfg >> 8) & EXYNOS_SINGLE_MASK) != EXYNOS_SINGLE_MASK) + fr_cfg = modify_next_frame(fr_cfg); + } + + /* Store IPC message */ + buff = skb_put(skb, tx_bytes); + if (copy_from_user(buff, data + copied, tx_bytes)) { + mif_err("%s->%s: ERR! copy_from_user fail (count %ld)\n", + iod->name, ld->name, tx_bytes); + dev_kfree_skb_any(skb); + return -EFAULT; + } + + /* Apply padding */ + if (tailroom) + skb_put(skb, tailroom); + + copied += tx_bytes; + tot_frame += alloc_size; + + /* Store the IO device, the link device, etc. */ + skbpriv(skb)->iod = iod; + skbpriv(skb)->ld = ld; + + skbpriv(skb)->lnk_hdr = iod->link_header; + skbpriv(skb)->exynos_ch = iod->id; + +#ifdef DEBUG_MODEM_IF_IODEV + log_ipc_pkt(skb, APP, TX); +#endif + + ret = ld->send(ld, iod, skb); + if (ret < 0) { + mif_info("%s->%s: ERR! ld->send fail (err %d, tx_bytes %ld)\n", + iod->name, ld->name, ret, tx_bytes); + return ret; + } + copied_frm += ret; + } + + if (copied_frm != tot_frame) { + mif_info("%s->%s: WARNING! ret %d != tx_bytes %d (count %d)\n", + iod->name, ld->name, ret, copied_frm, tot_frame); + } + + return count; +} + +static ssize_t misc_read(struct file *filp, char *buf, size_t count, + loff_t *fpos) +{ + struct io_device *iod = (struct io_device *)filp->private_data; + struct sk_buff_head *rxq = &iod->sk_rx_q; + struct sk_buff *skb; + int copied = 0; + + if (skb_queue_empty(rxq)) { + mif_info("%s: ERR! no data in rxq\n", iod->name); + return 0; + } + + skb = skb_dequeue(rxq); + if (unlikely(!skb)) { + mif_info("%s: No data in RXQ\n", iod->name); + return 0; + } + +#ifdef DEBUG_MODEM_IF_IODEV + log_ipc_pkt(skb, APP, RX); +#endif + + copied = skb->len > count ? count : skb->len; + + if (copy_to_user(buf, skb->data, copied)) { + mif_err("%s: ERR! copy_to_user fail\n", iod->name); + dev_kfree_skb_any(skb); + return -EFAULT; + } + + mif_debug("%s: data:%d copied:%d qlen:%d\n", + iod->name, skb->len, copied, rxq->qlen); + + if (skb->len > count) { + skb_pull(skb, count); + skb_queue_head(rxq, skb); + } else { + dev_kfree_skb_any(skb); + } + + return copied; +} + +static const struct file_operations misc_io_fops = { + .owner = THIS_MODULE, + .open = misc_open, + .release = misc_release, + .poll = misc_poll, + .unlocked_ioctl = misc_ioctl, + .compat_ioctl = misc_ioctl, + .write = misc_write, + .read = misc_read, +}; + +static int vnet_open(struct net_device *ndev) +{ + struct vnet *vnet = netdev_priv(ndev); + + mif_err("%s\n", vnet->iod->name); + + netif_start_queue(ndev); + atomic_inc(&vnet->iod->opened); + return 0; +} + +static int vnet_stop(struct net_device *ndev) +{ + struct vnet *vnet = netdev_priv(ndev); + + mif_err("%s\n", vnet->iod->name); + + atomic_dec(&vnet->iod->opened); + netif_stop_queue(ndev); + skb_queue_purge(&vnet->iod->sk_rx_q); + return 0; +} + +static int vnet_xmit(struct sk_buff *skb, struct net_device *ndev) +{ + struct vnet *vnet = netdev_priv(ndev); + struct io_device *iod = vnet->iod; + struct link_device *ld = get_current_link(iod); + struct sk_buff *skb_new = skb; + struct iphdr *ip_header = (struct iphdr *)skb->data; + struct modem_ctl *mc = iod->mc; + int ret; + unsigned int headroom; + unsigned int tailroom; + size_t count = skb->len; + size_t tx_bytes; + u8 *buff; + u16 fr_cfg; + + if (unlikely(!cp_online(mc))) { + if (!netif_queue_stopped(ndev)) + netif_stop_queue(ndev); + /* just drop the tx packet and return tx_ok */ + dev_kfree_skb_any(skb); + return NETDEV_TX_OK; + } + + /* When use `handover' with Network Bridge, + * user -> bridge device(rmnet0) -> real rmnet(xxxx_rmnet0) -> here. + * bridge device is ethernet device unlike xxxx_rmnet(net device). + * We remove the an ethernet header of skb before using skb->len, + * because bridge device added an ethernet header to skb. + */ + if (iod->use_handover) { + if (iod->id >= EXYNOS_CH_ID_PDP_0 && + iod->id <= EXYNOS_CH_ID_PDP_9) + skb_pull(skb, sizeof(struct ethhdr)); + } + + if (iod->link_header) { + fr_cfg = exynos_build_fr_config(iod, ld, count); + headroom = EXYNOS_HEADER_SIZE; + if (ld->aligned) + tailroom = exynos_calc_padding_size(EXYNOS_HEADER_SIZE + count); + else + tailroom = 0; + } else { + fr_cfg = 0; + headroom = 0; + tailroom = 0; + } + + tx_bytes = headroom + count + tailroom; + + if (skb_headroom(skb) < headroom || skb_tailroom(skb) < tailroom) { + mif_debug("%s: skb_copy_expand needed\n", iod->name); + skb_new = skb_copy_expand(skb, headroom, tailroom, GFP_ATOMIC); + if (!skb_new) { + mif_info("%s: ERR! skb_copy_expand fail\n", iod->name); + return NETDEV_TX_BUSY; + } + dev_kfree_skb_any(skb); + } + + /* Build EXYNOS link header*/ + buff = skb_push(skb_new, headroom); + if (fr_cfg) + exynos_build_header(iod, ld, buff, fr_cfg, 0, count); + + /* IP loop-back */ + ip_header = (struct iphdr *)skb->data; + if (iod->msd->loopback_ipaddr && + ip_header->daddr == iod->msd->loopback_ipaddr) { + swap(ip_header->saddr, ip_header->daddr); + buff[EXYNOS_CH_ID_OFFSET] = DATA_LOOPBACK_CHANNEL; + } + + if (tailroom) + skb_put(skb_new, tailroom); + + skbpriv(skb_new)->iod = iod; + skbpriv(skb_new)->ld = ld; + + skbpriv(skb_new)->lnk_hdr = iod->link_header; + skbpriv(skb_new)->exynos_ch = iod->id; + + ret = ld->send(ld, iod, skb_new); + if (ret < 0) { + netif_stop_queue(ndev); + mif_info("%s->%s: ERR! ld->send fail (err %d, tx_bytes %ld)\n", + iod->name, ld->name, ret, tx_bytes); + return NETDEV_TX_BUSY; + } + + if (ret != tx_bytes) { + mif_info("%s->%s: WARNING! ret %d != tx_bytes %ld (count %ld)\n", + iod->name, ld->name, ret, tx_bytes, count); + } + + ndev->stats.tx_packets++; + ndev->stats.tx_bytes += count; + + return NETDEV_TX_OK; +} + +static const struct net_device_ops vnet_ops = { + .ndo_open = vnet_open, + .ndo_stop = vnet_stop, + .ndo_start_xmit = vnet_xmit, +}; + +static void vnet_setup(struct net_device *ndev) +{ + ndev->netdev_ops = &vnet_ops; + ndev->type = ARPHRD_PPP; + ndev->flags = IFF_POINTOPOINT | IFF_NOARP | IFF_MULTICAST; + ndev->addr_len = 0; + ndev->hard_header_len = 0; + ndev->tx_queue_len = 1000; + ndev->mtu = ETH_DATA_LEN; + ndev->watchdog_timeo = 5 * HZ; +} + +static void vnet_setup_ether(struct net_device *ndev) +{ + ndev->netdev_ops = &vnet_ops; + ndev->type = ARPHRD_ETHER; + ndev->flags = IFF_POINTOPOINT | IFF_NOARP | IFF_MULTICAST | IFF_SLAVE; + ndev->addr_len = ETH_ALEN; + random_ether_addr(ndev->dev_addr); + ndev->hard_header_len = 0; + ndev->tx_queue_len = 1000; + ndev->mtu = ETH_DATA_LEN; + ndev->watchdog_timeo = 5 * HZ; +} + +static u16 exynos_build_fr_config(struct io_device *iod, struct link_device *ld, + unsigned int count) +{ + u16 fr_cfg = 0; + u8 frames = 0; + u8 *packet_index = &iod->packet_index; + + if (iod->format > IPC_DUMP) + return 0; + + if (iod->format >= IPC_BOOT) + return fr_cfg |= (EXYNOS_SINGLE_MASK << 8); + + if ((count + EXYNOS_HEADER_SIZE) <= SZ_2K) { + fr_cfg |= (EXYNOS_SINGLE_MASK << 8); + } else { + frames = count / (SZ_2K - EXYNOS_HEADER_SIZE); + frames = (count % (SZ_2K - EXYNOS_HEADER_SIZE)) ? frames : frames - 1; + + fr_cfg |= ((EXYNOS_MULTI_START_MASK | (0x3f & ++*packet_index)) << 8) | frames; + } + + return fr_cfg; +} + +static void exynos_build_header(struct io_device *iod, struct link_device *ld, + u8 *buff, u16 cfg, u8 ctl, size_t count) +{ + u16 *exynos_header = (u16 *)(buff + EXYNOS_START_OFFSET); + u16 *frame_seq = (u16 *)(buff + EXYNOS_FRAME_SEQ_OFFSET); + u16 *frag_cfg = (u16 *)(buff + EXYNOS_FRAG_CONFIG_OFFSET); + u16 *size = (u16 *)(buff + EXYNOS_LEN_OFFSET); + struct exynos_seq_num *seq_num = &(iod->seq_num); + + *exynos_header = EXYNOS_START_MASK; + *frame_seq = ++seq_num->frame_cnt; + *frag_cfg = cfg; + *size = (u16)(EXYNOS_HEADER_SIZE + count); + buff[EXYNOS_CH_ID_OFFSET] = iod->id; + + if (cfg == EXYNOS_SINGLE_MASK) + *frag_cfg = cfg; + + buff[EXYNOS_CH_SEQ_OFFSET] = ++seq_num->ch_cnt[iod->id]; +} + +int exynos_init_io_device(struct io_device *iod) +{ + int ret = 0; + int i; + struct vnet *vnet; + + if (iod->attrs & IODEV_ATTR(ATTR_NO_LINK_HEADER)) + iod->link_header = false; + else + iod->link_header = true; + + /* Get modem state from modem control device */ + iod->modem_state_changed = io_dev_modem_state_changed; + iod->sim_state_changed = io_dev_sim_state_changed; + + /* Get data from link device */ + mif_debug("%s: init\n", iod->name); + iod->recv = io_dev_recv_data_from_link_dev; + iod->recv_skb = io_dev_recv_skb_from_link_dev; + iod->recv_skb_single = io_dev_recv_skb_single_from_link_dev; + + /* Register misc or net device */ + switch (iod->io_typ) { + case IODEV_MISC: + init_waitqueue_head(&iod->wq); + skb_queue_head_init(&iod->sk_rx_q); + + iod->miscdev.minor = MISC_DYNAMIC_MINOR; + iod->miscdev.name = iod->name; + iod->miscdev.fops = &misc_io_fops; + + ret = misc_register(&iod->miscdev); + if (ret) + mif_info("%s: ERR! misc_register failed - %d\n", iod->name, ret); + + break; + + case IODEV_NET: + skb_queue_head_init(&iod->sk_rx_q); + if (iod->use_handover) + iod->ndev = alloc_netdev(0, iod->name, NET_NAME_UNKNOWN, + vnet_setup_ether); + else + iod->ndev = alloc_netdev(0, iod->name, NET_NAME_UNKNOWN, + vnet_setup); + + if (!iod->ndev) { + mif_info("%s: ERR! alloc_netdev fail\n", iod->name); + return -ENOMEM; + } + + ret = register_netdev(iod->ndev); + if (ret) { + mif_info("%s: ERR! register_netdev fail\n", iod->name); + free_netdev(iod->ndev); + } + + mif_debug("iod 0x%p\n", iod); + vnet = netdev_priv(iod->ndev); + mif_debug("vnet 0x%p\n", vnet); + vnet->iod = iod; + + break; + + case IODEV_DUMMY: + skb_queue_head_init(&iod->sk_rx_q); + + iod->miscdev.minor = MISC_DYNAMIC_MINOR; + iod->miscdev.name = iod->name; + iod->miscdev.fops = &misc_io_fops; + + ret = misc_register(&iod->miscdev); + if (ret) + mif_info("%s: ERR! misc_register fail\n", iod->name); + + ret = device_create_file(iod->miscdev.this_device, + &attr_waketime); + if (ret) + mif_info("%s: ERR! device_create_file fail\n", + iod->name); + + ret = device_create_file(iod->miscdev.this_device, + &attr_loopback); + if (ret) + mif_err("failed to create `loopback file' : %s\n", + iod->name); + + ret = device_create_file(iod->miscdev.this_device, + &attr_txlink); + if (ret) + mif_err("failed to create `txlink file' : %s\n", + iod->name); + break; + + default: + mif_info("%s: ERR! wrong io_type %d\n", iod->name, iod->io_typ); + return -EINVAL; + } + + for (i = 0; i < NUM_EXYNOS_MULTI_FRAME_IDS; i++) + skb_queue_head_init(&iod->sk_multi_q[i]); + + return ret; +} diff --git a/drivers/misc/modem_if/modem_link_device_memory.c b/drivers/misc/modem_if/modem_link_device_memory.c new file mode 100644 index 000000000000..df7da9e95219 --- /dev/null +++ b/drivers/misc/modem_if/modem_link_device_memory.c @@ -0,0 +1,510 @@ +/* + * Copyright (C) 2011 Samsung Electronics. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "modem_prj.h" +#include "modem_utils.h" +#include "modem_link_device_memory.h" + +void msq_reset(struct mem_status_queue *msq) +{ + unsigned long flags; + spin_lock_irqsave(&msq->lock, flags); + msq->out = msq->in; + spin_unlock_irqrestore(&msq->lock, flags); +} + +/** + * msq_get_free_slot + * @trq : pointer to an instance of mem_status_queue structure + * + * Succeeds always by dropping the oldest slot if a "msq" is full. + */ +struct mem_status *msq_get_free_slot(struct mem_status_queue *msq) +{ + int qsize = MAX_MEM_LOG_CNT; + int in; + int out; + unsigned long flags; + struct mem_status *stat; + + spin_lock_irqsave(&msq->lock, flags); + + in = msq->in; + out = msq->out; + + if (circ_get_space(qsize, in, out) < 1) { + /* Make the oldest slot empty */ + out++; + msq->out = (out == qsize) ? 0 : out; + } + + /* Get a free slot */ + stat = &msq->stat[in]; + + /* Make it as "data" slot */ + in++; + msq->in = (in == qsize) ? 0 : in; + + spin_unlock_irqrestore(&msq->lock, flags); + + memset(stat, 0, sizeof(struct mem_status)); + + return stat; +} + +inline int msq_get_size(struct mem_status_queue *msq) +{ + int qsize = MAX_MEM_LOG_CNT; + int in, out; + unsigned long flags; + + spin_lock_irqsave(&msq->lock, flags); + in = msq->in; + out = msq->out; + spin_unlock_irqrestore(&msq->lock, flags); + + return circ_get_space(qsize, in, out); +} + +struct mem_status *msq_get_data_slot(struct mem_status_queue *msq) +{ + int qsize = MAX_MEM_LOG_CNT; + int in; + int out; + unsigned long flags; + struct mem_status *stat; + + spin_lock_irqsave(&msq->lock, flags); + + in = msq->in; + out = msq->out; + + if (in == out) { + stat = NULL; + goto exit; + } + + /* Get a data slot */ + stat = &msq->stat[out]; + + /* Make it "free" slot */ + out++; + msq->out = (out == qsize) ? 0 : out; + +exit: + spin_unlock_irqrestore(&msq->lock, flags); + return stat; +} + +/** + * memcpy16_from_io + * @to: pointer to "real" memory + * @from: pointer to IO memory + * @count: data length in bytes to be copied + * + * Copies data from IO memory space to "real" memory space. + */ +void memcpy16_from_io(const void *to, const void __iomem *from, u32 count) +{ + u16 *d = (u16 *)to; + u16 *s = (u16 *)from; + u32 words = count >> 1; + while (words--) + *d++ = ioread16(s++); +} + +/** + * memcpy16_to_io + * @to: pointer to IO memory + * @from: pointer to "real" memory + * @count: data length in bytes to be copied + * + * Copies data from "real" memory space to IO memory space. + */ +void memcpy16_to_io(const void __iomem *to, const void *from, u32 count) +{ + u16 *d = (u16 *)to; + u16 *s = (u16 *)from; + u32 words = count >> 1; + while (words--) + iowrite16(*s++, d++); +} + +/** + * memcmp16_to_io + * @to: pointer to IO memory + * @from: pointer to "real" memory + * @count: data length in bytes to be compared + * + * Compares data from "real" memory space to IO memory space. + */ +int memcmp16_to_io(const void __iomem *to, const void *from, u32 count) +{ + u16 *d = (u16 *)to; + u16 *s = (u16 *)from; + int words = count >> 1; + int diff = 0; + int i; + u16 d1; + u16 s1; + + for (i = 0; i < words; i++) { + d1 = ioread16(d); + s1 = *s; + if (d1 != s1) { + diff++; + mif_err("ERR! [%d] d:0x%04X != s:0x%04X\n", i, d1, s1); + } + d++; + s++; + } + + return diff; +} + +/** + * circ_read16_from_io + * @dst: start address of the destination buffer + * @src: start address of the buffer in a circular queue + * @qsize: size of the circular queue + * @out: offset to read + * @len: length of data to be read + * + * Should be invoked after checking data length + */ +void circ_read16_from_io(void *dst, void *src, u32 qsize, u32 out, u32 len) +{ + if ((out + len) <= qsize) { + /* ----- (out) (in) ----- */ + /* ----- 7f 00 00 7e ----- */ + memcpy16_from_io(dst, (src + out), len); + } else { + /* (in) ----------- (out) */ + /* 00 7e ----------- 7f 00 */ + unsigned int len1 = qsize - out; + + /* 1) data start (out) ~ buffer end */ + memcpy16_from_io(dst, (src + out), len1); + + /* 2) buffer start ~ data end (in - 1) */ + memcpy16_from_io((dst + len1), src, (len - len1)); + } +} + +/** + * circ_write16_to_io + * @dst: pointer to the start of the circular queue + * @src: pointer to the source + * @qsize: size of the circular queue + * @in: offset to write + * @len: length of data to be written + * + * Should be invoked after checking free space + */ +void circ_write16_to_io(void *dst, void *src, u32 qsize, u32 in, u32 len) +{ + u32 space; + + if ((in + len) < qsize) { + /* (in) ----------- (out) */ + /* 00 7e ----------- 7f 00 */ + memcpy16_to_io((dst + in), src, len); + } else { + /* ----- (out) (in) ----- */ + /* ----- 7f 00 00 7e ----- */ + + /* 1) space start (in) ~ buffer end */ + space = qsize - in; + memcpy16_to_io((dst + in), src, ((len > space) ? space : len)); + + /* 2) buffer start ~ data end */ + if (len > space) + memcpy16_to_io(dst, (src + space), (len - space)); + } +} + +/** + * copy_circ_to_user + * @dst: start address of the destination buffer + * @src: start address of the buffer in a circular queue + * @qsize: size of the circular queue + * @out: offset to read + * @len: length of data to be read + * + * Should be invoked after checking data length + */ +int copy_circ_to_user(void __user *dst, void *src, u32 qsize, u32 out, u32 len) +{ + if ((out + len) <= qsize) { + /* ----- (out) (in) ----- */ + /* ----- 7f 00 00 7e ----- */ + if (copy_to_user(dst, (src + out), len)) { + mif_err("ERR! copy_to_user fail\n", + CALLER); + return -EFAULT; + } + } else { + /* (in) ----------- (out) */ + /* 00 7e ----------- 7f 00 */ + unsigned int len1 = qsize - out; + + /* 1) data start (out) ~ buffer end */ + if (copy_to_user(dst, (src + out), len1)) { + mif_err("ERR! copy_to_user fail\n", + CALLER); + return -EFAULT; + } + + /* 2) buffer start ~ data end (in?) */ + if (copy_to_user((dst + len1), src, (len - len1))) { + mif_err("ERR! copy_to_user fail\n", + CALLER); + return -EFAULT; + } + } + + return 0; +} + +/** + * copy_user_to_circ + * @dst: pointer to the start of the circular queue + * @src: pointer to the source + * @qsize: size of the circular queue + * @in: offset to write + * @len: length of data to be written + * + * Should be invoked after checking free space + */ +int copy_user_to_circ(void *dst, void __user *src, u32 qsize, u32 in, u32 len) +{ + u32 space; + u32 len1; + + if ((in + len) < qsize) { + /* (in) ----------- (out) */ + /* 00 7e ----------- 7f 00 */ + if (copy_from_user((dst + in), src, len)) { + mif_err("ERR! copy_from_user fail\n", + CALLER); + return -EFAULT; + } + } else { + /* ----- (out) (in) ----- */ + /* ----- 7f 00 00 7e ----- */ + + /* 1) space start (in) ~ buffer end */ + space = qsize - in; + len1 = (len > space) ? space : len; + if (copy_from_user((dst + in), src, len1)) { + mif_err("ERR! copy_from_user fail\n", + CALLER); + return -EFAULT; + } + + /* 2) buffer start ~ data end */ + if (len > len1) { + if (copy_from_user(dst, (src + space), (len - len1))) { + mif_err("ERR! copy_from_user fail\n", + CALLER); + return -EFAULT; + } + } + } + + return 0; +} + +/** + * print_mem_status + * @ld: pointer to an instance of link_device structure + * @mst: pointer to an instance of mem_status structure + * + * Prints a snapshot of the status of a SHM. + */ +void print_mem_status(struct link_device *ld, struct mem_status *mst) +{ + struct utc_time utc; + int us = ns2us(mst->ts.tv_nsec); + + ts2utc(&mst->ts, &utc); + pr_info("%s: %s: [%02d:%02d:%02d.%06d] ", + MIF_TAG, ld->name, utc.hour, utc.min, utc.sec, us); + pr_info("[%s] ACC{%X %d} ", get_dir_str(mst->dir), + mst->magic, mst->access); + pr_info("FMT{TI:%u TO:%u RI:%u RO:%u} ", + mst->head[IPC_FMT][TX], mst->tail[IPC_FMT][TX], + mst->head[IPC_FMT][RX], mst->tail[IPC_FMT][RX]); + pr_info("RAW{TI:%u TO:%u RI:%u RO:%u} ", + mst->head[IPC_RAW][TX], mst->tail[IPC_RAW][TX], + mst->head[IPC_RAW][RX], mst->tail[IPC_RAW][RX]); + pr_info("INTR{RX:0x%X TX:0x%X}\n", mst->int2ap, mst->int2cp); +} + +/** + * print_circ_status + * @ld: pointer to an instance of link_device structure + * @dev: IPC device (IPC_FMT, IPC_RAW, etc.) + * @mst: pointer to an instance of mem_status structure + * + * Prints a snapshot of the status of a memory + */ +void print_circ_status(struct link_device *ld, int dev, struct mem_status *mst) +{ + struct utc_time utc; + int us = ns2us(mst->ts.tv_nsec); + + if (dev > IPC_RAW) + return; + + ts2utc(&mst->ts, &utc); + pr_info("%s: %s: [%02d:%02d:%02d.%06d] [%s] %s | ", + MIF_TAG, ld->name, utc.hour, utc.min, utc.sec, us, + get_dir_str(mst->dir), get_dev_name(dev)); + pr_info("TXQ{in:%u out:%u} RXQ{in:%u out:%u} | INTR{0x%02X}\n", + mst->head[dev][TX], mst->tail[dev][TX], + mst->head[dev][RX], mst->tail[dev][RX], mst->int2ap); +} + +/** + * print_ipc_trace + * @ld: pointer to an instance of link_device structure + * @dev: IPC device (IPC_FMT, IPC_RAW, etc.) + * @stat: pointer to an instance of circ_status structure + * @ts: pointer to an instance of timespec structure + * @buff: start address of a buffer into which RX IPC messages were copied + * @rcvd: size of data in the buffer + * + * Prints IPC messages in a local memory buffer to a kernel log. + */ +void print_ipc_trace(struct link_device *ld, int dev, struct circ_status *stat, + struct timespec *ts, u8 *buff, u32 rcvd) +{ + struct utc_time utc; + + ts2utc(ts, &utc); + + pr_info("%s: [%d-%02d-%02d %02d:%02d:%02d.%03d] ", MIF_TAG, utc.year, + utc.mon, utc.day, utc.hour, utc.min, utc.sec, utc.msec); + pr_info("%s %s_RXQ {IN:%u OUT:%u LEN:%d}\n", + ld->name, get_dev_name(dev), stat->in, stat->out, stat->size); + + mif_print_dump(buff, rcvd, 4); +} + +/** + * capture_mem_dump + * @ld: pointer to an instance of link_device structure + * @base: base virtual address to a memory interface medium + * @size: size of the memory interface medium + * + * Captures a dump for a memory interface medium. + * + * Returns the pointer to a memory dump buffer. + */ +u8 *capture_mem_dump(struct link_device *ld, u8 *base, u32 size) +{ + u8 *buff = kzalloc(size, GFP_ATOMIC); + + if (!buff) { + mif_err("%s: ERR! kzalloc(%d) fail\n", ld->name, size); + return NULL; + } + + memcpy16_from_io(buff, base, size); + return buff; +} + +/** + * trq_get_free_slot + * @trq : pointer to an instance of trace_data_queue structure + * + * Succeeds always by dropping the oldest slot if a "trq" is full. + */ +struct trace_data *trq_get_free_slot(struct trace_data_queue *trq) +{ + int qsize = MAX_TRACE_SIZE; + int in; + int out; + unsigned long flags; + struct trace_data *trd; + + spin_lock_irqsave(&trq->lock, flags); + + in = trq->in; + out = trq->out; + + /* The oldest slot can be dropped. */ + if (circ_get_space(qsize, in, out) < 1) { + /* Free the data buffer in the oldest slot */ + trd = &trq->trd[out]; + kfree(trd->data); + + /* Make the oldest slot empty */ + out++; + trq->out = (out == qsize) ? 0 : out; + } + + /* Get a free slot and make it occupied */ + trd = &trq->trd[in++]; + trq->in = (in == qsize) ? 0 : in; + + spin_unlock_irqrestore(&trq->lock, flags); + + memset(trd, 0, sizeof(struct trace_data)); + + return trd; +} + +struct trace_data *trq_get_data_slot(struct trace_data_queue *trq) +{ + int qsize = MAX_TRACE_SIZE; + int in; + int out; + unsigned long flags; + struct trace_data *trd; + + spin_lock_irqsave(&trq->lock, flags); + + in = trq->in; + out = trq->out; + + if (circ_get_usage(qsize, in, out) < 1) { + spin_unlock_irqrestore(&trq->lock, flags); + return NULL; + } + + /* Get a data slot and make it empty */ + trd = &trq->trd[out++]; + trq->out = (out == qsize) ? 0 : out; + + spin_unlock_irqrestore(&trq->lock, flags); + + return trd; +} diff --git a/drivers/misc/modem_if/modem_link_device_memory.h b/drivers/misc/modem_if/modem_link_device_memory.h new file mode 100644 index 000000000000..48f8a6d3cee4 --- /dev/null +++ b/drivers/misc/modem_if/modem_link_device_memory.h @@ -0,0 +1,385 @@ +/* + * Copyright (C) 2010 Samsung Electronics. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __MODEM_LINK_DEVICE_MEMORY_H__ +#define __MODEM_LINK_DEVICE_MEMORY_H__ + +#include +#include +#include +#include +#include +#if defined(CONFIG_HAS_EARLYSUSPEND) +#include +#elif defined(CONFIG_FB) +#include +#endif + +#include "modem_prj.h" + +/* interrupt masks.*/ +#define INT_MASK_VALID 0x0080 +#define INT_MASK_CMD 0x0040 +#define INT_VALID(x) ((x) & INT_MASK_VALID) +#define INT_CMD_VALID(x) ((x) & INT_MASK_CMD) +#define INT_NON_CMD(x) (INT_MASK_VALID | (x)) +#define INT_CMD(x) (INT_MASK_VALID | INT_MASK_CMD | (x)) + +#define EXT_UDL_MASK 0xF000 +#define EXT_UDL_CMD(x) ((x) & EXT_UDL_MASK) +#define EXT_INT_VALID_MASK 0x8000 +#define EXT_CMD_VALID_MASK 0x4000 +#define UDL_CMD_VALID_MASK 0x2000 +#define EXT_INT_VALID(x) ((x) & EXT_INT_VALID_MASK) +#define EXT_CMD_VALID(x) ((x) & EXT_CMD_VALID_MASK) +#define UDL_CMD_VALID(x) ((x) & UDL_CMD_VALID_MASK) +#define INT_EXT_CMD(x) (EXT_INT_VALID_MASK | EXT_CMD_VALID_MASK | (x)) + +#define EXT_CMD_MASK(x) ((x) & 0x0FFF) +#define EXT_CMD_SET_SPEED_LOW 0x0011 +#define EXT_CMD_SET_SPEED_MID 0x0012 +#define EXT_CMD_SET_SPEED_HIGH 0x0013 + +#define UDL_RESULT_SUCCESS 0x1 +#define UDL_RESULT_FAIL 0x2 + +#define UDL_CMD_MASK(x) (((x) >> 8) & 0xF) +#define UDL_CMD_RECV_READY 0x1 +#define UDL_CMD_DL_START_REQ 0x2 +#define UDL_CMD_DL_START_RESP 0x3 +#define UDL_CMD_IMAGE_SEND_REQ 0x4 +#define UDL_CMD_SEND_DONE_RESP 0x5 +#define UDL_CMD_SEND_DONE_REQ 0x6 +#define UDL_CMD_UPDATE_DONE 0x7 +#define UDL_CMD_STATUS_UPDATE 0x8 +#define UDL_CMD_IMAGE_SEND_RESP 0x9 +#define UDL_CMD_EFS_CLEAR_RESP 0xB +#define UDL_CMD_ALARM_BOOT_OK 0xC +#define UDL_CMD_ALARM_BOOT_FAIL 0xD + +#define CMD_DL_READY 0xA100 +#define CMD_DL_START_REQ 0x9200 +#define CMD_DL_START_RESP 0xA301 +#define CMD_DL_SEND_REQ 0x9400 +#define CMD_DL_SEND_RESP 0xA501 +#define CMD_DL_DONE_REQ 0x9600 +#define CMD_DL_DONE_RESP 0xA701 + +#define CMD_UL_RECV_RESP 0x9601 +#define CMD_UL_RECV_DONE_REQ 0xA700 +#define CMD_UL_RECV_DONE_RESP 0x9801 + +/* special interrupt cmd indicating modem boot failure. */ +#define INT_POWERSAFE_FAIL 0xDEAD + +#define INT_MASK_REQ_ACK_F 0x0020 +#define INT_MASK_REQ_ACK_R 0x0010 +#define INT_MASK_RES_ACK_F 0x0008 +#define INT_MASK_RES_ACK_R 0x0004 +#define INT_MASK_SEND_F 0x0002 +#define INT_MASK_SEND_R 0x0001 + +#define INT_MASK_REQ_ACK_RFS 0x0400 /* Request RES_ACK_RFS */ +#define INT_MASK_RES_ACK_RFS 0x0200 /* Response of REQ_ACK_RFS */ +#define INT_MASK_SEND_RFS 0x0100 /* Indicate sending RFS data */ + +#define INT_MASK_REQ_ACK_SET \ + (INT_MASK_REQ_ACK_F | INT_MASK_REQ_ACK_R | INT_MASK_REQ_ACK_RFS) + +#define INT_MASK_RES_ACK_SET \ + (INT_MASK_RES_ACK_F | INT_MASK_RES_ACK_R | INT_MASK_RES_ACK_RFS) + +#define INT_CMD_MASK(x) ((x) & 0xF) +#define INT_CMD_INIT_START 0x1 +#define INT_CMD_INIT_END 0x2 +#define INT_CMD_REQ_ACTIVE 0x3 +#define INT_CMD_RES_ACTIVE 0x4 +#define INT_CMD_REQ_TIME_SYNC 0x5 +#define INT_CMD_KERNEL_PANIC 0x6 +#define INT_CMD_CRASH_RESET 0x7 +#define INT_CMD_PHONE_START 0x8 +#define INT_CMD_ERR_DISPLAY 0x9 +#define INT_CMD_CRASH_EXIT 0x9 +#define INT_CMD_CP_DEEP_SLEEP 0xA +#define INT_CMD_NV_REBUILDING 0xB +#define INT_CMD_EMER_DOWN 0xC +#define INT_CMD_PIF_INIT_DONE 0xD +#define INT_CMD_SILENT_NV_REBUILDING 0xE +#define INT_CMD_NORMAL_POWER_OFF 0xF + +#define UDL_TIMEOUT (50 * HZ) +#define UDL_SEND_TIMEOUT (200 * HZ) +#define FORCE_CRASH_ACK_TIMEOUT (5 * HZ) +#define DUMP_TIMEOUT (30 * HZ) +#define DUMP_START_TIMEOUT (100 * HZ) +#define DUMP_WAIT_TIMEOUT (HZ >> 10) /* 1/1024 second */ + +#define RES_ACK_WAIT_TIMEOUT 100 /* 100 ms */ +#define REQ_ACK_DELAY 10 /* 10 ms */ +#define MAX_TIMEOUT_CNT 1000 + +#ifdef CONFIG_DEBUG_MODEM_IF +#define MAX_RETRY_CNT 1 +#else +#define MAX_RETRY_CNT 3 +#endif + +#define MAX_SKB_TXQ_DEPTH 1024 + +struct memif_boot_map { + u32 __iomem *magic; + u8 __iomem *buff; + u32 __iomem *req; + u32 __iomem *resp; + u32 space; +}; + +struct memif_dload_map { + u32 __iomem *magic; + u8 __iomem *buff; + u32 space; +}; + +struct memif_uload_map { + u32 __iomem *magic; + u8 __iomem *cmd; + u32 cmd_size; + u8 __iomem *buff; + u32 space; +}; + +#define DP_BOOT_BUFF_OFFSET 4 +#define DP_DLOAD_MAGIC_SIZE 4 +#define DP_DLOAD_BUFF_OFFSET 4 +#define DP_ULOAD_MAGIC_SIZE 4 +#define DP_ULOAD_BUFF_OFFSET 4 +#define DP_BOOT_REQ_OFFSET 0 +#define DP_BOOT_RESP_OFFSET 8 +#define DP_MBX_SET_SIZE 4 +#define DP_MAX_PAYLOAD_SIZE 0x2000 + +enum circ_ptr_type { + HEAD, + TAIL, +}; + +static inline bool circ_valid(u32 qsize, u32 in, u32 out) +{ + if (in >= qsize) + return false; + + if (out >= qsize) + return false; + + return true; +} + +static inline u32 circ_get_space(u32 qsize, u32 in, u32 out) +{ + return (in < out) ? (out - in - 1) : (qsize + out - in - 1); +} + +static inline u32 circ_get_usage(u32 qsize, u32 in, u32 out) +{ + return (in >= out) ? (in - out) : (qsize - out + in); +} + +static inline u32 circ_new_pointer(u32 qsize, u32 p, u32 len) +{ + p += len; + return (p < qsize) ? p : (p - qsize); +} + +/** + * circ_read + * @dst: start address of the destination buffer + * @src: start address of the buffer in a circular queue + * @qsize: size of the circular queue + * @out: offset to read + * @len: length of data to be read + * + * Should be invoked after checking data length + */ +static inline void circ_read(void *dst, void *src, u32 qsize, u32 out, u32 len) +{ + unsigned int len1; + + if ((out + len) <= qsize) { + /* ----- (out) (in) ----- */ + /* ----- 7f 00 00 7e ----- */ + memcpy(dst, (src + out), len); + } else { + /* (in) ----------- (out) */ + /* 00 7e ----------- 7f 00 */ + + /* 1) data start (out) ~ buffer end */ + len1 = qsize - out; + memcpy(dst, (src + out), len1); + + /* 2) buffer start ~ data end (in?) */ + memcpy((dst + len1), src, (len - len1)); + } +} + +/** + * circ_write + * @dst: pointer to the start of the circular queue + * @src: pointer to the source + * @qsize: size of the circular queue + * @in: offset to write + * @len: length of data to be written + * + * Should be invoked after checking free space + */ +static inline void circ_write(void *dst, void *src, u32 qsize, u32 in, u32 len) +{ + u32 space; + + if ((in + len) < qsize) { + /* (in) ----------- (out) */ + /* 00 7e ----------- 7f 00 */ + memcpy((dst + in), src, len); + } else { + /* ----- (out) (in) ----- */ + /* ----- 7f 00 00 7e ----- */ + + /* 1) space start (in) ~ buffer end */ + space = qsize - in; + memcpy((dst + in), src, ((len > space) ? space : len)); + + /* 2) buffer start ~ data end */ + if (len > space) + memcpy(dst, (src + space), (len - space)); + } +} + +/** + * circ_dir + * @dir: communication direction (enum direction) + * + * Returns the direction of a circular queue + * + */ +static const inline char *circ_dir(enum direction dir) +{ + if (dir == TX) + return "TXQ"; + else + return "RXQ"; +} + +/** + * circ_ptr + * @ptr: circular queue pointer (enum circ_ptr_type) + * + * Returns the name of a circular queue pointer + * + */ +static const inline char *circ_ptr(enum circ_ptr_type ptr) +{ + if (ptr == HEAD) + return "head"; + else + return "tail"; +} + +/** + * get_dir_str + * @dir: communication direction (enum direction) + * + * Returns the direction of a circular queue + * + */ +static const inline char *get_dir_str(enum direction dir) +{ + if (dir == TX) + return "AP->CP"; + else + return "CP->AP"; +} + +void memcpy16_from_io(const void *to, const void __iomem *from, u32 count); +void memcpy16_to_io(const void __iomem *to, const void *from, u32 count); +int memcmp16_to_io(const void __iomem *to, const void *from, u32 count); +void circ_read16_from_io(void *dst, void *src, u32 qsize, u32 out, u32 len); +void circ_write16_to_io(void *dst, void *src, u32 qsize, u32 in, u32 len); +int copy_circ_to_user(void __user *dst, void *src, u32 qsize, u32 out, u32 len); +int copy_user_to_circ(void *dst, void __user *src, u32 qsize, u32 in, u32 len); + +#define MAX_MEM_LOG_CNT 8192 +#define MAX_TRACE_SIZE 1024 + +struct mem_status { + /* Timestamp */ + struct timespec ts; + + /* Direction (TX or RX) */ + enum direction dir; + + /* The status of memory interface at the time */ + u32 magic; + u32 access; + + u32 head[MAX_IPC_DEV][MAX_DIR]; + u32 tail[MAX_IPC_DEV][MAX_DIR]; + + u16 int2ap; + u16 int2cp; +}; + +struct mem_status_queue { + spinlock_t lock; + u32 in; + u32 out; + struct mem_status stat[MAX_MEM_LOG_CNT]; +}; + +struct circ_status { + u8 *buff; + u32 qsize; /* the size of a circular buffer */ + u32 in; + u32 out; + u32 size; /* the size of free space or received data */ +}; + +struct trace_data { + struct timespec ts; + enum dev_format dev; + struct circ_status circ_stat; + u8 *data; + u32 size; +}; + +struct trace_data_queue { + spinlock_t lock; + u32 in; + u32 out; + struct trace_data trd[MAX_TRACE_SIZE]; +}; + +void msq_reset(struct mem_status_queue *msq); +struct mem_status *msq_get_free_slot(struct mem_status_queue *msq); +struct mem_status *msq_get_data_slot(struct mem_status_queue *msq); +int msq_get_size(struct mem_status_queue *msq); + +void print_mem_status(struct link_device *ld, struct mem_status *mst); +void print_circ_status(struct link_device *ld, int dev, struct mem_status *mst); +void print_ipc_trace(struct link_device *ld, int dev, struct circ_status *stat, + struct timespec *ts, u8 *buff, u32 rcvd); + +u8 *capture_mem_dump(struct link_device *ld, u8 *base, u32 size); +struct trace_data *trq_get_free_slot(struct trace_data_queue *trq); +struct trace_data *trq_get_data_slot(struct trace_data_queue *trq); + +#endif diff --git a/drivers/misc/modem_if/modem_link_device_shmem.c b/drivers/misc/modem_if/modem_link_device_shmem.c new file mode 100755 index 000000000000..956dfda7320d --- /dev/null +++ b/drivers/misc/modem_if/modem_link_device_shmem.c @@ -0,0 +1,2486 @@ +/* + * Copyright (C) 2010 Samsung Electronics. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "modem_prj.h" +#include "modem_utils.h" +#include "modem_link_device_shmem.h" +#include "modem_dump.h" + +#if !defined(CONFIG_CP_SECURE_BOOT) +#define CRC32_XINIT 0xFFFFFFFFL /* initial value */ +#define CRC32_XOROT 0xFFFFFFFFL /* final xor value */ + +static const unsigned long CRC32_TABLE[256] = { + 0x00000000L, 0x77073096L, 0xEE0E612CL, 0x990951BAL, 0x076DC419L, + 0x706AF48FL, 0xE963A535L, 0x9E6495A3L, 0x0EDB8832L, 0x79DCB8A4L, + 0xE0D5E91EL, 0x97D2D988L, 0x09B64C2BL, 0x7EB17CBDL, 0xE7B82D07L, + 0x90BF1D91L, 0x1DB71064L, 0x6AB020F2L, 0xF3B97148L, 0x84BE41DEL, + 0x1ADAD47DL, 0x6DDDE4EBL, 0xF4D4B551L, 0x83D385C7L, 0x136C9856L, + 0x646BA8C0L, 0xFD62F97AL, 0x8A65C9ECL, 0x14015C4FL, 0x63066CD9L, + 0xFA0F3D63L, 0x8D080DF5L, 0x3B6E20C8L, 0x4C69105EL, 0xD56041E4L, + 0xA2677172L, 0x3C03E4D1L, 0x4B04D447L, 0xD20D85FDL, 0xA50AB56BL, + 0x35B5A8FAL, 0x42B2986CL, 0xDBBBC9D6L, 0xACBCF940L, 0x32D86CE3L, + 0x45DF5C75L, 0xDCD60DCFL, 0xABD13D59L, 0x26D930ACL, 0x51DE003AL, + 0xC8D75180L, 0xBFD06116L, 0x21B4F4B5L, 0x56B3C423L, 0xCFBA9599L, + 0xB8BDA50FL, 0x2802B89EL, 0x5F058808L, 0xC60CD9B2L, 0xB10BE924L, + 0x2F6F7C87L, 0x58684C11L, 0xC1611DABL, 0xB6662D3DL, 0x76DC4190L, + 0x01DB7106L, 0x98D220BCL, 0xEFD5102AL, 0x71B18589L, 0x06B6B51FL, + 0x9FBFE4A5L, 0xE8B8D433L, 0x7807C9A2L, 0x0F00F934L, 0x9609A88EL, + 0xE10E9818L, 0x7F6A0DBBL, 0x086D3D2DL, 0x91646C97L, 0xE6635C01L, + 0x6B6B51F4L, 0x1C6C6162L, 0x856530D8L, 0xF262004EL, 0x6C0695EDL, + 0x1B01A57BL, 0x8208F4C1L, 0xF50FC457L, 0x65B0D9C6L, 0x12B7E950L, + 0x8BBEB8EAL, 0xFCB9887CL, 0x62DD1DDFL, 0x15DA2D49L, 0x8CD37CF3L, + 0xFBD44C65L, 0x4DB26158L, 0x3AB551CEL, 0xA3BC0074L, 0xD4BB30E2L, + 0x4ADFA541L, 0x3DD895D7L, 0xA4D1C46DL, 0xD3D6F4FBL, 0x4369E96AL, + 0x346ED9FCL, 0xAD678846L, 0xDA60B8D0L, 0x44042D73L, 0x33031DE5L, + 0xAA0A4C5FL, 0xDD0D7CC9L, 0x5005713CL, 0x270241AAL, 0xBE0B1010L, + 0xC90C2086L, 0x5768B525L, 0x206F85B3L, 0xB966D409L, 0xCE61E49FL, + 0x5EDEF90EL, 0x29D9C998L, 0xB0D09822L, 0xC7D7A8B4L, 0x59B33D17L, + 0x2EB40D81L, 0xB7BD5C3BL, 0xC0BA6CADL, 0xEDB88320L, 0x9ABFB3B6L, + 0x03B6E20CL, 0x74B1D29AL, 0xEAD54739L, 0x9DD277AFL, 0x04DB2615L, + 0x73DC1683L, 0xE3630B12L, 0x94643B84L, 0x0D6D6A3EL, 0x7A6A5AA8L, + 0xE40ECF0BL, 0x9309FF9DL, 0x0A00AE27L, 0x7D079EB1L, 0xF00F9344L, + 0x8708A3D2L, 0x1E01F268L, 0x6906C2FEL, 0xF762575DL, 0x806567CBL, + 0x196C3671L, 0x6E6B06E7L, 0xFED41B76L, 0x89D32BE0L, 0x10DA7A5AL, + 0x67DD4ACCL, 0xF9B9DF6FL, 0x8EBEEFF9L, 0x17B7BE43L, 0x60B08ED5L, + 0xD6D6A3E8L, 0xA1D1937EL, 0x38D8C2C4L, 0x4FDFF252L, 0xD1BB67F1L, + 0xA6BC5767L, 0x3FB506DDL, 0x48B2364BL, 0xD80D2BDAL, 0xAF0A1B4CL, + 0x36034AF6L, 0x41047A60L, 0xDF60EFC3L, 0xA867DF55L, 0x316E8EEFL, + 0x4669BE79L, 0xCB61B38CL, 0xBC66831AL, 0x256FD2A0L, 0x5268E236L, + 0xCC0C7795L, 0xBB0B4703L, 0x220216B9L, 0x5505262FL, 0xC5BA3BBEL, + 0xB2BD0B28L, 0x2BB45A92L, 0x5CB36A04L, 0xC2D7FFA7L, 0xB5D0CF31L, + 0x2CD99E8BL, 0x5BDEAE1DL, 0x9B64C2B0L, 0xEC63F226L, 0x756AA39CL, + 0x026D930AL, 0x9C0906A9L, 0xEB0E363FL, 0x72076785L, 0x05005713L, + 0x95BF4A82L, 0xE2B87A14L, 0x7BB12BAEL, 0x0CB61B38L, 0x92D28E9BL, + 0xE5D5BE0DL, 0x7CDCEFB7L, 0x0BDBDF21L, 0x86D3D2D4L, 0xF1D4E242L, + 0x68DDB3F8L, 0x1FDA836EL, 0x81BE16CDL, 0xF6B9265BL, 0x6FB077E1L, + 0x18B74777L, 0x88085AE6L, 0xFF0F6A70L, 0x66063BCAL, 0x11010B5CL, + 0x8F659EFFL, 0xF862AE69L, 0x616BFFD3L, 0x166CCF45L, 0xA00AE278L, + 0xD70DD2EEL, 0x4E048354L, 0x3903B3C2L, 0xA7672661L, 0xD06016F7L, + 0x4969474DL, 0x3E6E77DBL, 0xAED16A4AL, 0xD9D65ADCL, 0x40DF0B66L, + 0x37D83BF0L, 0xA9BCAE53L, 0xDEBB9EC5L, 0x47B2CF7FL, 0x30B5FFE9L, + 0xBDBDF21CL, 0xCABAC28AL, 0x53B39330L, 0x24B4A3A6L, 0xBAD03605L, + 0xCDD70693L, 0x54DE5729L, 0x23D967BFL, 0xB3667A2EL, 0xC4614AB8L, + 0x5D681B02L, 0x2A6F2B94L, 0xB40BBE37L, 0xC30C8EA1L, 0x5A05DF1BL, + 0x2D02EF8DL +}; +#endif + +#ifdef CONFIG_MODEM_IF_ADAPTIVE_QOS +static struct pm_qos_request pm_qos_req_cpu[2]; +static struct pm_qos_request pm_qos_req_mif; +static struct pm_qos_request pm_qos_req_int; +#endif + +enum smc_error_flag { + CP_NO_ERROR = 0, + CP_NOT_ALIGN_64KB, + CP_MEM_TOO_BIG, + CP_FLAG_OUT_RANGE, + CP_WRONG_TZASC_REGION_NUM, + CP_WRONG_BL_SIZE = 5, + CP_MEM_OUT_OF_RANGE, + CP_NOT_ALIGN_16B, + CP_MEM_IN_SECURE_DRAM, + CP_ASP_ENABLE_FAIL, + CP_ASP_DISABLE_FAIL = 10, + CP_NOT_WORKING, + CP_ALREADY_WORKING, + CP_ALREADY_DUMP_MODE, + CP_NOT_VALID_MAGIC, + CP_SHOULD_BE_DISABLE = 15, + CP_ALREADY_ENABLE_CPMEM_ON, + CP_ALREADY_SET_WND, + CP_FAIL_TO_SET_WND, + CP_INVALID_CP_BASE, + CP_CORRUPTED_CP_MEM_INFO = 20 +}; + +static char *smc_err_string[32] = { + "CP_NO_ERROR", + "CP_NOT_ALIGN_64KB", + "CP_MEM_TOO_BIG", + "CP_FLAG_OUT_RANGE", + "CP_WRONG_TZASC_REGION_NUM", + "CP_WRONG_BL_SIZE", + "CP_MEM_OUT_OF_RANGE", + "CP_NOT_ALIGN_16B", + "CP_MEM_IN_SECURE_DRAM", + "CP_ASP_ENABLE_FAIL", + "CP_ASP_DISABLE_FAIL", + "CP_NOT_WORKING", + "CP_ALREADY_WORKING", + "CP_ALREADY_DUMP_MODE", + "CP_NOT_VALID_MAGIC", + "CP_SHOULD_BE_DISABLE", + "CP_ALREADY_ENABLE_CPMEM_ON", + "CP_ALREADY_SET_WND", + "CP_FAIL_TO_SET_WND", + "CP_INVALID_CP_BASE", + "CP_CORRUPTED_CP_MEM_INFO", +}; + +static unsigned long main_bin_size; +static void *main_bin; +static void trigger_forced_cp_crash(struct shmem_link_device *shmd, + u32 crash_reason_owner, char *crash_reason_string); +static struct shmem_link_device *g_shmd; + +#ifdef CONFIG_DEBUG_MODEM_IF +static void save_mem_dump(struct shmem_link_device *shmd) +{ + struct link_device *ld = &shmd->ld; + char *path = shmd->dump_path; + struct file *fp; + struct utc_time t; + + get_utc_time(&t); + snprintf(path, MIF_MAX_PATH_LEN, "%s/%s_%d%02d%02d_%02d%02d%02d.dump", + MIF_LOG_DIR, ld->name, t.year, t.mon, t.day, t.hour, t.min, + t.sec); + + fp = mif_open_file(path); + if (!fp) { + mif_err("%s: ERR! %s open fail\n", ld->name, path); + return; + } + mif_err("%s: %s opened\n", ld->name, path); + + mif_save_file(fp, (char *)shmd->base, shmd->size); + + mif_close_file(fp); +} + +/** + * mem_dump_work + * @ws: pointer to an instance of work_struct structure + * + * Performs actual file operation for saving a DPRAM dump. + */ +static void mem_dump_work(struct work_struct *ws) +{ + struct shmem_link_device *shmd; + + shmd = container_of(ws, struct shmem_link_device, dump_dwork.work); + if (!shmd) { + mif_err("ERR! no shmd\n"); + return; + } + + save_mem_dump(shmd); +} +#endif + +/** + * recv_int2ap + * @shmd: pointer to an instance of shmem_link_device structure + * + * Returns the value of the CP-to-AP interrupt register. + */ +static inline u16 recv_int2ap(struct shmem_link_device *shmd) +{ + if (shmd->mbx2ap) + return *shmd->mbx2ap; + else + return (u16)mbox_get_value(MCU_CP, shmd->mbx_cp2ap_msg); +} + +/** + * send_int2cp + * @shmd: pointer to an instance of shmem_link_device structure + * @mask: value to be written to the AP-to-CP interrupt register + */ +static inline void send_int2cp(struct shmem_link_device *shmd, u16 mask) +{ + struct link_device *ld = &shmd->ld; + struct modem_ctl *mc = ld->mc; + + if ((ld->mode != LINK_MODE_IPC) && + (mc->phone_state != STATE_CRASH_EXIT)) { + //mif_info("%s: mask 0x%04X\n", ld->name, CALLER, mask); + return; + } + + if (shmd->mbx2cp) { + *shmd->mbx2cp = mask; + } else { + mbox_set_value(MCU_CP, shmd->mbx_ap2cp_msg, mask); + mbox_set_interrupt(MCU_CP, shmd->int_ap2cp_msg); + } +} + +#ifdef CONFIG_MODEM_IF_ADAPTIVE_QOS +static void max_qos_timer_callback(unsigned long data) +{ + struct shmem_link_device *shmd = (struct shmem_link_device *)data; + + schedule_work(&shmd->pm_qos_work_normal); +} + +static void check_rx_shmem_status(struct shmem_link_device *shmd, + struct circ_status *circ) +{ + if (circ->size > SHM_RX_MAX_QOS_TRIGGER_BYTES) { + /* Timer is working */ + if (timer_pending(&shmd->max_qos_timer)) { + /* Expand timer value */ + mod_timer(&shmd->max_qos_timer, get_jiffies_64() + + msecs_to_jiffies(SHM_RX_MIN_QOS_HOLD_MS)); + } else { + /* Set QOS ON */ + schedule_work(&shmd->pm_qos_work_max); + mod_timer(&shmd->max_qos_timer, get_jiffies_64() + + msecs_to_jiffies(SHM_RX_MIN_QOS_HOLD_MS)); + } + } +} +#endif + +/** + * get_shmem_status + * @shmd: pointer to an instance of shmem_link_device structure + * @dir: direction of communication (TX or RX) + * @mst: pointer to an instance of mem_status structure + * + * Takes a snapshot of the current status of a SHMEM. + */ +static void get_shmem_status(struct shmem_link_device *shmd, + enum direction dir, struct mem_status *mst) +{ +#ifdef CONFIG_DEBUG_MODEM_IF + getnstimeofday(&mst->ts); +#endif + + mst->dir = dir; + mst->magic = get_magic(shmd); + mst->access = get_access(shmd); + mst->head[IPC_FMT][TX] = get_txq_head(shmd, IPC_FMT); + mst->tail[IPC_FMT][TX] = get_txq_tail(shmd, IPC_FMT); + mst->head[IPC_FMT][RX] = get_rxq_head(shmd, IPC_FMT); + mst->tail[IPC_FMT][RX] = get_rxq_tail(shmd, IPC_FMT); + mst->head[IPC_RAW][TX] = get_txq_head(shmd, IPC_RAW); + mst->tail[IPC_RAW][TX] = get_txq_tail(shmd, IPC_RAW); + mst->head[IPC_RAW][RX] = get_rxq_head(shmd, IPC_RAW); + mst->tail[IPC_RAW][RX] = get_rxq_tail(shmd, IPC_RAW); + mst->int2ap = recv_int2ap(shmd); + mst->int2cp = read_int2cp(shmd); +} + +static inline void update_rxq_tail_status(struct shmem_link_device *shmd, + int dev, struct mem_status *mst) +{ + mst->tail[dev][RX] = get_rxq_tail(shmd, dev); +} + +/** + * handle_cp_crash + * @shmd: pointer to an instance of shmem_link_device structure + * + * Actual handler for the CRASH_EXIT command from a CP. + */ +static void handle_cp_crash(struct shmem_link_device *shmd) +{ + struct link_device *ld = &shmd->ld; + struct io_device *iod; + struct modem_ctl *mc = ld->mc; + int i; + + if (shmd->forced_cp_crash) + shmd->forced_cp_crash = false; + + ld->mode = LINK_MODE_ULOAD; + + /* Stop network interfaces */ + mif_netif_stop(ld); + + /* Purge the skb_txq in every IPC device (IPC_FMT, IPC_RAW, etc.) */ + for (i = 0; i < MAX_EXYNOS_DEVICES; i++) + skb_queue_purge(ld->skb_txq[i]); + + if (cp_online(mc)) + modem_notify_event(MODEM_EVENT_EXIT); + + /* Change the modem state to STATE_CRASH_EXIT for the FMT IO device */ + iod = link_get_iod_with_format(ld, IPC_FMT); + if (iod) + iod->modem_state_changed(iod, STATE_CRASH_EXIT); + + /* time margin for taking state changes by rild */ + mdelay(100); + + /* Change the modem state to STATE_CRASH_EXIT for the BOOT IO device */ + iod = link_get_iod_with_format(ld, IPC_BOOT); + if (iod) + iod->modem_state_changed(iod, STATE_CRASH_EXIT); +} + +/** + * handle_no_cp_crash_ack + * @arg: pointer to an instance of shmem_link_device structure + * + * Invokes handle_cp_crash() to enter the CRASH_EXIT state if there was no + * CRASH_ACK from a CP in FORCE_CRASH_ACK_TIMEOUT. + */ +static void handle_no_cp_crash_ack(unsigned long arg) +{ + struct shmem_link_device *shmd = (struct shmem_link_device *)arg; + struct link_device *ld = &shmd->ld; + + mif_err("%s: ERR! No CRASH_EXIT ACK from CP\n", ld->name); + + handle_cp_crash(shmd); +} + +/** + * trigger_forced_cp_crash + * @shmd: pointer to an instance of shmem_link_device structure + * + * Triggers an enforced CP crash. + */ +static void trigger_forced_cp_crash(struct shmem_link_device *shmd, + u32 crash_reason_owner, char *crash_reason_string) +{ + struct link_device *ld = &shmd->ld; + struct utc_time t; + + if (shmd->forced_cp_crash) { + mif_err("%s: ALREADY in progress\n", ld->name, CALLER); + return; + } + shmd->forced_cp_crash = true; + + get_utc_time(&t); + mif_err("%s: [%02d:%02d:%02d.%03d] \n", + ld->name, t.hour, t.min, t.sec, t.msec, CALLER); + + shmd->crash_reason.owner = crash_reason_owner; + strncpy(shmd->crash_reason.string, crash_reason_string, + (MEM_CRASH_REASON_SIZE - 1)); + + if (!wake_lock_active(&shmd->wlock)) + wake_lock(&shmd->wlock); + +#ifdef CONFIG_DEBUG_MODEM_IF + if (in_interrupt()) + queue_delayed_work(system_wq, &shmd->dump_dwork, 0); + else + save_mem_dump(shmd); +#endif + + /* Send CRASH_EXIT command to a CP */ + send_int2cp(shmd, INT_CMD(INT_CMD_CRASH_EXIT)); + get_shmem_status(shmd, TX, msq_get_free_slot(&shmd->tx_msq)); + + /* If there is no CRASH_ACK from a CP in FORCE_CRASH_ACK_TIMEOUT, + * handle_no_cp_crash_ack() will be executed. + */ + mif_add_timer(&shmd->crash_ack_timer, FORCE_CRASH_ACK_TIMEOUT, + handle_no_cp_crash_ack, (unsigned long)shmd); +} + +/** + * cmd_crash_reset_handler + * @shmd: pointer to an instance of shmem_link_device structure + * + * Handles the CRASH_RESET command from a CP. + */ +static void cmd_crash_reset_handler(struct shmem_link_device *shmd) +{ + struct link_device *ld = &shmd->ld; + struct io_device *iod = NULL; + int i; + struct utc_time t; + struct modem_ctl *mc = ld->mc; + + ld->mode = LINK_MODE_ULOAD; + + if (!wake_lock_active(&shmd->wlock)) + wake_lock(&shmd->wlock); + + get_utc_time(&t); + mif_err("%s: ERR! [%02d:%02d:%02d.%03d] Recv 0xC7 (CRASH_RESET)\n", + ld->name, t.hour, t.min, t.sec, t.msec); +#ifdef CONFIG_DEBUG_MODEM_IF + queue_delayed_work(system_wq, &shmd->dump_dwork, 0); +#endif + + /* Stop network interfaces */ + mif_netif_stop(ld); + + /* Purge the skb_txq in every IPC device (IPC_FMT, IPC_RAW, etc.) */ + for (i = 0; i < MAX_EXYNOS_DEVICES; i++) + skb_queue_purge(ld->skb_txq[i]); + + mif_err("%s: Recv 0xC7 (CRASH_RESET)\n", ld->name); + + if (cp_online(mc)) + modem_notify_event(MODEM_EVENT_RESET); + + /* Change the modem state to STATE_CRASH_RESET for the FMT IO device */ + iod = link_get_iod_with_format(ld, IPC_FMT); + if (iod) + iod->modem_state_changed(iod, STATE_CRASH_RESET); + + /* time margin for taking state changes by rild */ + mdelay(100); + + /* Change the modem state to STATE_CRASH_RESET for the BOOT IO device */ + iod = link_get_iod_with_format(ld, IPC_BOOT); + if (iod) + iod->modem_state_changed(iod, STATE_CRASH_RESET); +} + +/** + * cmd_crash_exit_handler + * @shmd: pointer to an instance of shmem_link_device structure + * + * Handles the CRASH_EXIT command from a CP. + */ +static void cmd_crash_exit_handler(struct shmem_link_device *shmd) +{ + struct link_device *ld = &shmd->ld; + struct utc_time t; + + ld->mode = LINK_MODE_ULOAD; + + del_timer(&shmd->crash_ack_timer); + + if (!wake_lock_active(&shmd->wlock)) + wake_lock(&shmd->wlock); + + get_utc_time(&t); + mif_err("%s: ERR! [%02d:%02d:%02d.%03d] Recv 0xC9 (CRASH_EXIT)\n", + ld->name, t.hour, t.min, t.sec, t.msec); +#ifdef CONFIG_DEBUG_MODEM_IF + queue_delayed_work(system_wq, &shmd->dump_dwork, 0); +#endif + + handle_cp_crash(shmd); +} + +/** + * cmd_phone_start_handler + * @shmd: pointer to an instance of shmem_link_device structure + * + * Handles the PHONE_START command from a CP. + */ +static void cmd_phone_start_handler(struct shmem_link_device *shmd) +{ + int err; + struct link_device *ld = &shmd->ld; + struct io_device *iod; + + mif_err("%s: Recv 0xC8 (CP_START)\n", ld->name); + + iod = link_get_iod_with_format(ld, IPC_FMT); + if (!iod) { + mif_err("%s: ERR! no FMT iod\n", ld->name); + return; + } + + msq_reset(&shmd->rx_msq); + + err = init_shmem_ipc(shmd); + if (err) + return; + + if (wake_lock_active(&shmd->wlock)) + wake_unlock(&shmd->wlock); + + mif_err("%s: Send 0xC2 (INIT_END)\n", ld->name); + send_int2cp(shmd, INT_CMD(INT_CMD_INIT_END)); + + iod->modem_state_changed(iod, STATE_ONLINE); +} + +/** + * cmd_handler: processes a SHMEM command from a CP + * @shmd: pointer to an instance of shmem_link_device structure + * @cmd: SHMEM command from a CP + */ +static void cmd_handler(struct shmem_link_device *shmd, u16 cmd) +{ + struct link_device *ld = &shmd->ld; + + switch (INT_CMD_MASK(cmd)) { + case INT_CMD_CRASH_RESET: + cmd_crash_reset_handler(shmd); + break; + + case INT_CMD_CRASH_EXIT: + cmd_crash_exit_handler(shmd); + break; + + case INT_CMD_PHONE_START: + cmd_phone_start_handler(shmd); + complete_all(&ld->init_cmpl); + modem_notify_event(MODEM_EVENT_ONLINE); + break; + + default: + mif_err("%s: unknown command 0x%04X\n", ld->name, cmd); + } +} + +int modem_send_panic_noti_ext(void) +{ + if (!g_shmd) { + mif_err("g_shmd is null\n"); + return -1; + } + + mif_info("send INT_CMD_KERNEL_PANIC message to CP\n"); + send_int2cp(g_shmd, INT_CMD(INT_CMD_KERNEL_PANIC)); + + return 0; +} +EXPORT_SYMBOL(modem_send_panic_noti_ext); + +/** + * ipc_rx_work + * @ws: pointer to an instance of work_struct structure + * + * Invokes the recv method in the io_device instance to perform receiving IPC + * messages from each skb. + */ +static void msg_rx_work(struct work_struct *ws) +{ + struct shmem_link_device *shmd; + struct link_device *ld; + struct io_device *iod; + struct sk_buff *skb; + int i, ret; + int queue_len; + + shmd = container_of(ws, struct shmem_link_device, msg_rx_dwork.work); + ld = &shmd->ld; + + for (i = 0; i < MAX_EXYNOS_DEVICES; i++) { + iod = shmd->iod[i]; + queue_len = skb_queue_len(ld->skb_rxq[i]); + + while (queue_len-- > 0) { + skb = skb_dequeue(ld->skb_rxq[i]); + if (!skb) + break; + + ret = iod->recv_skb(iod, ld, skb); + if (unlikely(ret < 0)) { + struct modem_ctl *mc = ld->mc; + + mif_err_limited("%s: %s<-%s: %s->recv_skb fail (%d)\n", + ld->name, iod->name, mc->name, iod->name, ret); + dev_kfree_skb_any(skb); + } + } + } +} + +/** + * rx_ipc_frames + * @shmd: pointer to an instance of shmem_link_device structure + * @dev: IPC device (IPC_FMT, IPC_RAW, etc.) + * @mst: pointer to an instance of mem_status structure + * + * Returns + * ret < 0 : error + * ret == 0 : ILLEGAL status + * ret > 0 : valid data + * + * Must be invoked only when there is data in the corresponding RXQ. + * + * Requires a recv_skb method in the io_device instance, so this function must + * be used for only EXYNOS. + */ +static int rx_ipc_frames(struct shmem_link_device *shmd, int dev, + struct circ_status *circ) +{ + struct link_device *ld = &shmd->ld; + struct io_device *iod; + struct sk_buff_head *rxq = ld->skb_rxq[dev]; + struct sk_buff *skb; + /** + * variables for the status of the circular queue + */ + u8 *src; + u8 hdr[EXYNOS_HEADER_SIZE]; + /** + * variables for RX processing + */ + int qsize; /* size of the queue */ + int rcvd; /* size of data in the RXQ or error */ + int rest; /* size of the rest data */ + int out; /* index to the start of current frame */ + int tot; /* total length including padding data */ + + src = circ->buff; + qsize = circ->qsize; + out = circ->out; + rcvd = circ->size; + + rest = circ->size; + tot = 0; + + while (rest > 0) { + u8 ch; + + /* Copy the header in the frame to the header buffer */ + circ_read(hdr, src, qsize, out, EXYNOS_HEADER_SIZE); + + /* Check the config field in the header */ + if (unlikely(!exynos_start_valid(hdr))) { + mif_err("%s: ERR! %s INVALID config 0x%02X (rcvd %d, rest %d)\n", + ld->name, get_dev_name(dev), hdr[0], + rcvd, rest); + goto bad_msg; + } + + /* Verify the total length of the frame (data + padding) */ + tot = exynos_get_total_len(hdr); + if (unlikely(tot > rest)) { + mif_err("%s: ERR! %s tot %d > rest %d (rcvd %d)\n", + ld->name, get_dev_name(dev), tot, rest, rcvd); + goto bad_msg; + } + + /* Allocate an skb */ + skb = dev_alloc_skb(tot); + if (!skb) { + mif_err("%s: ERR! %s dev_alloc_skb(%d) fail\n", + ld->name, get_dev_name(dev), tot); + goto no_mem; + } + + /* Set the attribute of the skb as "single frame" */ + skbpriv(skb)->single_frame = true; + + /* Read the frame from the RXQ */ + circ_read(skb_put(skb, tot), src, qsize, out, tot); + + ch = exynos_get_ch(skb->data); + iod = link_get_iod_with_channel(ld, ch); + if (!iod) { + mif_err("%s: ERR! no IPC_BOOT iod\n", ld->name); + break; + } + + skbpriv(skb)->lnk_hdr = iod->link_header; + skbpriv(skb)->exynos_ch = ch; + + /* Store the skb to the corresponding skb_rxq */ + skb_queue_tail(rxq, skb); + + /* Calculate new out value */ + rest -= tot; + out += tot; + if (unlikely(out >= qsize)) + out -= qsize; + } + + /* Update tail (out) pointer to empty out the RXQ */ + set_rxq_tail(shmd, dev, circ->in); + return rcvd; + +no_mem: + /* Update tail (out) pointer to the frame to be read in the future */ + set_rxq_tail(shmd, dev, out); + rcvd -= rest; + return rcvd; + +bad_msg: +#ifdef CONFIG_DEBUG_MODEM_IF + mif_err("%s: ERR! rcvd:%d tot:%d rest:%d\n", ld->name, rcvd, tot, rest); + pr_ipc(1, "shmem: ERR! CP2MIF", (src + out), (rest > 20) ? 20 : rest); +#endif + return -EBADMSG; +} + +static inline void done_req_ack(struct shmem_link_device *shmd, int dev) +{ + u16 mask; +#ifdef CONFIG_DEBUG_MODEM_IF + struct mem_status mst; +#endif + + if (unlikely(shmd->dev[dev]->req_ack_rcvd < 0)) + shmd->dev[dev]->req_ack_rcvd = 0; + + if (likely(shmd->dev[dev]->req_ack_rcvd == 0)) + return; + + mask = get_mask_res_ack(shmd, dev); + send_int2cp(shmd, INT_NON_CMD(mask)); + shmd->dev[dev]->req_ack_rcvd -= 1; + +#ifdef CONFIG_DEBUG_MODEM_IF + get_shmem_status(shmd, TX, &mst); + print_res_ack(shmd, dev, &mst); +#endif +} + +static inline void recv_res_ack(struct shmem_link_device *shmd, + struct mem_status *mst) +{ + u16 intr = mst->int2ap; + + if (intr & get_mask_res_ack(shmd, IPC_FMT)) { +#ifdef CONFIG_DEBUG_MODEM_IF + mif_info("%s: recv FMT RES_ACK\n", shmd->ld.name); +#endif + complete(&shmd->req_ack_cmpl[IPC_FMT]); + } + + if (intr & get_mask_res_ack(shmd, IPC_RAW)) { +#ifdef CONFIG_DEBUG_MODEM_IF + mif_info("%s: recv RAW RES_ACK\n", shmd->ld.name); +#endif + complete(&shmd->req_ack_cmpl[IPC_RAW]); + } +} + +static inline void recv_req_ack(struct shmem_link_device *shmd, + struct mem_status *mst) +{ + u16 intr = mst->int2ap; + + if (intr & get_mask_req_ack(shmd, IPC_FMT)) { + shmd->dev[IPC_FMT]->req_ack_rcvd += 1; +#ifdef CONFIG_DEBUG_MODEM_IF + print_req_ack(shmd, IPC_FMT, mst); +#endif + } + + if (intr & get_mask_req_ack(shmd, IPC_RAW)) { + shmd->dev[IPC_RAW]->req_ack_rcvd += 1; +#ifdef CONFIG_DEBUG_MODEM_IF + print_req_ack(shmd, IPC_RAW, mst); +#endif + } +} + +/** + * msg_handler: receives IPC messages from every RXQ + * @shmd: pointer to an instance of shmem_link_device structure + * @mst: pointer to an instance of mem_status structure + * + * 1) Receives all IPC message frames currently in every IPC RXQ. + * 2) Sends RES_ACK responses if there are REQ_ACK requests from a CP. + * 3) Completes all threads waiting for the corresponding RES_ACK from a CP if + * there is any RES_ACK response. + */ +static void msg_handler(struct shmem_link_device *shmd, struct mem_status *mst) +{ + struct link_device *ld = &shmd->ld; + struct circ_status circ; + int i = 0; + int ret = 0; + + if (!ipc_active(shmd)) { + mif_err("%s: ERR! IPC is NOT ACTIVE!!!\n", ld->name); + trigger_forced_cp_crash(shmd, MEM_CRASH_REASON_AP, + "ERR! IPC not active in msg_handler()"); + return; + } + + for (i = 0; i < MAX_EXYNOS_DEVICES; i++) { + /* Skip RX processing if there is no data in the RXQ */ + if (mst->head[i][RX] == mst->tail[i][RX]) { + done_req_ack(shmd, i); + continue; + } + + /* Get the size of data in the RXQ */ + ret = get_rxq_rcvd(shmd, i, mst, &circ); + if (unlikely(ret < 0)) { + mif_err("%s: ERR! get_rxq_rcvd fail (err %d)\n", + ld->name, ret); + trigger_forced_cp_crash(shmd, MEM_CRASH_REASON_CP, + "ERR! invalid RXQ head/tail value in msg_handler()"); + return; + } + +#ifdef CONFIG_MODEM_IF_ADAPTIVE_QOS + check_rx_shmem_status(shmd, &circ); +#endif + + /* Read data in the RXQ */ + ret = rx_ipc_frames(shmd, i, &circ); + if (unlikely(ret < 0)) { + trigger_forced_cp_crash(shmd, MEM_CRASH_REASON_CP, + "ERR! invalid RX frame in msg_handler()"); + return; + } + + if (ret < circ.size) + break; + + /* Process REQ_ACK (At this point, the RXQ may be empty.) */ + done_req_ack(shmd, i); + } +} + +/** + * ipc_rx_task: processes a SHMEM command or receives IPC messages + * @shmd: pointer to an instance of shmem_link_device structure + * @mst: pointer to an instance of mem_status structure + * + * Invokes cmd_handler for commands or msg_handler for IPC messages. + */ +static void ipc_rx_task(unsigned long data) +{ + struct shmem_link_device *shmd = (struct shmem_link_device *)data; + int qlen = msq_get_size(&shmd->rx_msq); + + while (qlen-- > 0) { + struct mem_status *mst; + int i; + u16 intr; + + mst = msq_get_data_slot(&shmd->rx_msq); + if (!mst) + break; + + intr = mst->int2ap; + + /* Process a SHMEM command */ + if (unlikely(INT_CMD_VALID(intr))) { + cmd_handler(shmd, intr); + continue; + } + + /* Update tail variables with the current tail pointers */ + for (i = 0; i < MAX_EXYNOS_DEVICES; i++) + update_rxq_tail_status(shmd, i, mst); + + /* Check and receive RES_ACK from CP */ + if (unlikely(intr & INT_MASK_RES_ACK_SET)) + recv_res_ack(shmd, mst); + + /* Check and receive REQ_ACK from CP */ + if (unlikely(intr & INT_MASK_REQ_ACK_SET)) + recv_req_ack(shmd, mst); + + msg_handler(shmd, mst); + + queue_delayed_work_on(3, system_long_wq, &shmd->msg_rx_dwork, 0); + } +} + +/** + * rx_udl_frames + * @shmd: pointer to an instance of shmem_link_device structure + * @dev: IPC device (IPC_FMT, IPC_RAW, etc.) + * @mst: pointer to an instance of mem_status structure + * + * Returns + * ret < 0 : error + * ret == 0 : ILLEGAL status + * ret > 0 : valid data + * + * Must be invoked only when there is data in the corresponding RXQ. + * + * Requires a recv_skb method in the io_device instance, so this function must + * be used for only EXYNOS. + */ +static int rx_udl_frames(struct shmem_link_device *shmd, int dev, + struct circ_status *circ) +{ + struct link_device *ld = &shmd->ld; + struct io_device *iod; + struct sk_buff *skb; + int ret; + /** + * variables for the status of the circular queue + */ + u8 *src; + u8 hdr[EXYNOS_HEADER_SIZE]; + /** + * variables for RX processing + */ + u32 qsize; /* size of the queue */ + u32 rcvd; /* size of data in the RXQ or error */ + u32 rest; /* size of the rest data */ + u32 out; /* index to the start of current frame */ + unsigned int tot; /* total length including padding data */ + + src = circ->buff; + qsize = circ->qsize; + out = circ->out; + rcvd = circ->size; + rest = circ->size; + tot = 0; + while (rest > 0) { + u8 ch; + + /* Copy the header in the frame to the header buffer */ + circ_read(hdr, src, qsize, out, EXYNOS_HEADER_SIZE); + + /* Check the config field in the header */ + if (unlikely(!exynos_start_valid(hdr))) { + mif_err("%s: ERR! %s INVALID config 0x%02X (rest %d, rcvd %d)\n", + ld->name, get_dev_name(dev), + hdr[0], rest, rcvd); + pr_ipc(1, "UDL", (src + out), (rest > 20) ? 20 : rest); + ret = -EBADMSG; + goto exit; + } + + /* Verify the total length of the frame (data + padding) */ + tot = exynos_get_total_len(hdr); + if (unlikely(tot > rest)) { + mif_err("%s: ERR! %s tot %d > rest %d (rcvd %d)\n", + ld->name, get_dev_name(dev), tot, rest, rcvd); + ret = -ENODATA; + goto exit; + } + + /* Allocate an skb */ + skb = alloc_skb(tot + NET_SKB_PAD, GFP_KERNEL); + if (!skb) { + mif_err("%s: ERR! %s alloc_skb fail\n", + ld->name, get_dev_name(dev)); + ret = -ENOMEM; + goto free_skb; + } + skb_reserve(skb, NET_SKB_PAD); + + /* Set the attribute of the skb as "single frame" */ + skbpriv(skb)->single_frame = true; + + /* Read the frame from the RXQ */ + circ_read(skb_put(skb, tot), src, qsize, out, tot); + + /* Pass the skb to an iod */ + ch = exynos_get_ch(skb->data); + iod = link_get_iod_with_channel(ld, ch); + if (!iod) { + mif_err("%s: ERR! no IPC_BOOT iod\n", ld->name); + ret = -EBADMSG; + goto free_skb; + } + + skbpriv(skb)->lnk_hdr = iod->link_header; + skbpriv(skb)->exynos_ch = ch; + + if (!std_udl_with_payload(std_udl_get_cmd(skb->data))) { + if (ld->mode == LINK_MODE_DLOAD) { + pr_ipc(0, "[CP->AP] DL CMD", skb->data, + (skb->len > 20 ? 20 : skb->len)); + + if (skb->data[12] == 0x20 && skb->data[13] == 0xc1) + log_info.debug_log = false; + } else { + pr_ipc(0, "[CP->AP] UL CMD", skb->data, + (skb->len > 20 ? 20 : skb->len)); + } + } + + iod->recv_skb(iod, ld, skb); + + /* Calculate new out value */ + rest -= tot; + out += tot; + if (unlikely(out >= qsize)) + out -= qsize; + } + + /* Update tail (out) pointer to empty out the RXQ */ + set_rxq_tail(shmd, dev, circ->in); + + return rcvd; + +free_skb: + set_rxq_tail(shmd, dev, out); + kfree_skb(skb); +exit: + return ret; +} + +/** + * udl_rx_work + * @ws: pointer to an instance of the work_struct structure + * + * Invokes the recv method in the io_device instance to perform receiving IPC + * messages from each skb. + */ +static void udl_rx_work(struct work_struct *ws) +{ + struct shmem_link_device *shmd; + struct link_device *ld; + struct sk_buff_head *rxq; + + shmd = container_of(ws, struct shmem_link_device, udl_rx_dwork.work); + ld = &shmd->ld; + rxq = ld->skb_rxq[IPC_RAW]; + + while (1) { + struct mem_status *mst; + struct circ_status circ; + + mst = msq_get_data_slot(&shmd->rx_msq); + if (!mst) + break; + update_rxq_tail_status(shmd, IPC_RAW, mst); + mst->head[IPC_RAW][RX] = get_rxq_head(shmd, IPC_RAW); + + /* Exit the loop if there is no more data in the RXQ */ + if (mst->tail[IPC_RAW][RX] == mst->head[IPC_RAW][RX]) + break; + + /* Invoke an RX function only when there is data in the RXQ */ + if (get_rxq_rcvd(shmd, IPC_RAW, mst, &circ) < 0) { + mif_err("%s: ERR! get_rxq_rcvd fail\n", ld->name); +#ifdef CONFIG_DEBUG_MODEM_IF + trigger_forced_cp_crash(shmd, MEM_CRASH_REASON_AP, + "ERR! invalid RX head/tail value in udl_rx_work()"); +#endif + break; + } + + if (rx_udl_frames(shmd, IPC_RAW, &circ) < 0) { + skb_queue_purge(rxq); + break; + } + } +} + +/** + * udl_handler: receives BOOT/DUMP IPC messages from every RXQ + * @shmd: pointer to an instance of shmem_link_device structure + * @mst: pointer to an instance of mem_status structure + * + * 1) Receives all IPC message frames currently in every IPC RXQ. + * 2) Sends RES_ACK responses if there are REQ_ACK requests from a CP. + * 3) Completes all threads waiting for the corresponding RES_ACK from a CP if + * there is any RES_ACK response. + */ +static void udl_handler(struct shmem_link_device *shmd, struct mem_status *mst) +{ + u16 intr = mst->int2ap; + + /* Process a SHMEM command */ + if (unlikely(INT_CMD_VALID(intr))) { + cmd_handler(shmd, intr); + return; + } + + /* Schedule soft IRQ for RX */ + queue_delayed_work(system_wq, &shmd->udl_rx_dwork, 0); + + /* Check and process RES_ACK */ + if (intr & INT_MASK_RES_ACK_SET) { + if (intr & get_mask_res_ack(shmd, IPC_RAW)) { +#ifdef CONFIG_DEBUG_MODEM_IF + struct link_device *ld = &shmd->ld; + mif_info("%s: recv RAW RES_ACK\n", ld->name); + print_circ_status(ld, IPC_RAW, mst); +#endif + complete(&shmd->req_ack_cmpl[IPC_RAW]); + } + } +} + +/** + * shmem_irq_handler: interrupt handler for a MCU_IPC interrupt + * @data: pointer to a data + * + * 1) Reads the interrupt value + * 2) Performs interrupt handling + * + * Flow for normal interrupt handling: + * shmem_irq_handler -> udl_handler + * shmem_irq_handler -> ipc_rx_task -> cmd_handler -> cmd_xxx_handler + * shmem_irq_handler -> ipc_rx_task -> msg_handler -> rx_ipc_frames -> ... + */ +static void shmem_irq_handler(void *data) +{ + struct shmem_link_device *shmd = (struct shmem_link_device *)data; + struct link_device *ld = (struct link_device *)&shmd->ld; + struct mem_status *mst = msq_get_free_slot(&shmd->rx_msq); + u16 intr; + + get_shmem_status(shmd, RX, mst); + intr = mst->int2ap; + + if (unlikely(ld->mode == LINK_MODE_OFFLINE)) { + mif_info("%s: ld->mode == LINK_MODE_OFFLINE\n", ld->name); + return; + } + + if (unlikely(!INT_VALID(intr))) { + mif_info("%s: ERR! invalid intr 0x%X\n", ld->name, intr); + return; + } + + if (ld->mode == LINK_MODE_DLOAD || ld->mode == LINK_MODE_ULOAD) + udl_handler(shmd, mst); + else + tasklet_schedule(&shmd->rx_tsk); +} + +#ifdef CONFIG_MODEM_IF_ADAPTIVE_QOS +static void shmem_qos_work_max(struct work_struct *work) +{ + mif_info("Request MAX QOS\n"); + + pm_qos_update_request(&pm_qos_req_cpu[0], + INT_MAX); + pm_qos_update_request(&pm_qos_req_cpu[1], + INT_MAX); + pm_qos_update_request(&pm_qos_req_mif, + INT_MAX); + pm_qos_update_request(&pm_qos_req_int, + INT_MAX); +} + +static void shmem_qos_work_normal(struct work_struct *work) +{ + mif_info("Request normal QOS\n"); + + pm_qos_update_request(&pm_qos_req_cpu[0], 0); + pm_qos_update_request(&pm_qos_req_cpu[1], 0); + pm_qos_update_request(&pm_qos_req_mif, 0); + pm_qos_update_request(&pm_qos_req_int, 0); +} +#endif + +static void shmem_cp2ap_wakelock_handler(void *data) +{ + struct shmem_link_device *mld = (struct shmem_link_device *)data; + struct link_device *ld = &mld->ld; + struct modem_ctl *mc = ld->mc; + unsigned int req; + mif_err("%s\n", __func__); + + req = mbox_extract_value(MCU_CP, mc->mbx_cp_status, + mc->sbi_wake_lock_mask, mc->sbi_wake_lock_pos); + + if (req == 0) { + if (wake_lock_active(&mld->cp_wlock)) { + wake_unlock(&mld->cp_wlock); + mif_err("cp_wakelock unlocked\n"); + } else { + mif_err("cp_wakelock already unlocked\n"); + } + } else if (req == 1) { + if (wake_lock_active(&mld->cp_wlock)) { + mif_err("cp_wakelock already unlocked\n"); + } else { + wake_lock(&mld->cp_wlock); + mif_err("cp_wakelock locked\n"); + } + } else { + mif_err("unsupported request: cp_wakelock\n"); + } +} + +/** + * write_ipc_to_txq + * @shmd: pointer to an instance of shmem_link_device structure + * @dev: IPC device (IPC_FMT, IPC_RAW, etc.) + * @circ: pointer to an instance of circ_status structure + * @skb: pointer to an instance of sk_buff structure + * + * Must be invoked only when there is enough space in the TXQ. + */ +static void write_ipc_to_txq(struct shmem_link_device *shmd, int dev, + struct circ_status *circ, struct sk_buff *skb) +{ + u32 qsize = circ->qsize; + u32 in = circ->in; + u8 *buff = circ->buff; + u8 *src = skb->data; + u32 len = skb->len; + + /* Print send data to CP */ + log_ipc_pkt(skb, LINK, TX); + + /* Write data to the TXQ */ + circ_write(buff, src, qsize, in, len); + + /* Update new head (in) pointer */ + set_txq_head(shmd, dev, circ_new_pointer(qsize, in, len)); +} + +/** + * xmit_ipc_msg + * @shmd: pointer to an instance of shmem_link_device structure + * @dev: IPC device (IPC_FMT, IPC_RAW, etc.) + * + * Tries to transmit IPC messages in the skb_txq of @dev as many as possible. + * + * Returns total length of IPC messages transmit or an error code. + */ +static int xmit_ipc_msg(struct shmem_link_device *shmd, int dev) +{ + struct link_device *ld = &shmd->ld; + struct sk_buff_head *txq = ld->skb_txq[dev]; + struct sk_buff *skb; + unsigned long flags; + struct circ_status circ; + int space; + int copied = 0; + bool chk_nospc = false; + + /* Acquire the spin lock for a TXQ */ + spin_lock_irqsave(&shmd->tx_lock[dev], flags); + + while (1) { + /* Get the size of free space in the TXQ */ + space = get_txq_space(shmd, dev, &circ); + if (unlikely(space < 0)) { +#ifdef CONFIG_DEBUG_MODEM_IF + /* Trigger a enforced CP crash */ + trigger_forced_cp_crash(shmd, MEM_CRASH_REASON_CP, + "ERR! txq full in xmit_ipc_msg()"); +#endif + /* Empty out the TXQ */ + reset_txq_circ(shmd, dev); + copied = -EIO; + break; + } + + skb = skb_dequeue(txq); + if (unlikely(!skb)) + break; + + /* CAUTION : Uplink size is limited to 16KB and + * this limitation is used ONLY in North America Prj. + * Check the free space size, + * - FMT : comparing with skb->len + * - RAW : check used buffer size + */ +#ifdef CONFIG_MACH_GARDA + if (dev == IPC_FMT) { + if (unlikely(space < skb->len)) + chk_nospc = true; + } else { /* dev == IPC_RAW */ + if (unlikely((SHM_4M_RAW_TX_BUFF_SZ - space) + >= SHM_4M_MAX_UPLINK_SIZE)) + chk_nospc = true; + } +#else + chk_nospc = (space < skb->len) ? true : false; +#endif + if (unlikely(chk_nospc)) { +#ifdef CONFIG_DEBUG_MODEM_IF + struct mem_status mst; +#endif + /* Set res_required flag for the "dev" */ + atomic_set(&shmd->res_required[dev], 1); + + /* Take the skb back to the skb_txq */ + skb_queue_head(txq, skb); + + mif_err("%s: NOSPC in %s_TXQ {qsize:%u in:%u out:%u} free:%u < len:%u\n", + ld->name, CALLER, get_dev_name(dev), + circ.qsize, circ.in, circ.out, space, skb->len); +#ifdef CONFIG_DEBUG_MODEM_IF + get_shmem_status(shmd, TX, &mst); + print_circ_status(ld, dev, &mst); +#endif + copied = -ENOSPC; + break; + } + + /* TX only when there is enough space in the TXQ */ + write_ipc_to_txq(shmd, dev, &circ, skb); + copied += skb->len; + dev_kfree_skb_any(skb); + } + + /* Release the spin lock */ + spin_unlock_irqrestore(&shmd->tx_lock[dev], flags); + + return copied; +} + +/** + * wait_for_res_ack + * @shmd: pointer to an instance of shmem_link_device structure + * @dev: IPC device (IPC_FMT, IPC_RAW, etc.) + * + * 1) Sends an REQ_ACK interrupt for @dev to CP. + * 2) Waits for the corresponding RES_ACK for @dev from CP. + * + * Returns the return value from wait_for_completion_interruptible_timeout(). + */ +static int wait_for_res_ack(struct shmem_link_device *shmd, int dev) +{ + struct link_device *ld = &shmd->ld; + struct completion *cmpl = &shmd->req_ack_cmpl[dev]; + unsigned long timeout = msecs_to_jiffies(RES_ACK_WAIT_TIMEOUT); + int ret; + u16 mask; + +#ifdef CONFIG_DEBUG_MODEM_IF + mif_info("%s: send %s REQ_ACK\n", ld->name, get_dev_name(dev)); +#endif + + mask = get_mask_req_ack(shmd, dev); + send_int2cp(shmd, INT_NON_CMD(mask)); + + /* ret < 0 if interrupted, ret == 0 on timeout */ + reinit_completion(cmpl); + ret = wait_for_completion_interruptible_timeout(cmpl, timeout); + if (ret < 0) { + mif_err("%s: %s: wait_for_completion interrupted! (ret %d)\n", + ld->name, get_dev_name(dev), ret); + goto exit; + } + + if (ret == 0) { + struct mem_status mst; + + memset(&mst, 0, sizeof(struct mem_status)); + get_shmem_status(shmd, TX, &mst); + + mif_err("%s: wait_for_completion TIMEOUT! (no %s_RES_ACK)\n", + ld->name, get_dev_name(dev)); + + /* + * The TXQ must be checked whether or not it is empty, because + * an interrupt mask can be overwritten by the next interrupt. + */ + if (mst.head[dev][TX] == mst.tail[dev][TX]) { + ret = get_txq_buff_size(shmd, dev); +#ifdef CONFIG_DEBUG_MODEM_IF + mif_err("%s: %s_TXQ has been emptied\n", + ld->name, get_dev_name(dev)); + print_circ_status(ld, dev, &mst); +#endif + } + + goto exit; + } + +#ifdef CONFIG_DEBUG_MODEM_IF + mif_info("%s: recv %s RES_ACK\n", ld->name, get_dev_name(dev)); +#endif + +exit: + return ret; +} + +/** + * process_res_ack + * @shmd: pointer to an instance of shmem_link_device structure + * @dev: IPC device (IPC_FMT, IPC_RAW, etc.) + * + * 1) Tries to transmit IPC messages in the skb_txq with xmit_ipc_msg(). + * 2) Sends an interrupt to CP if there is no error from xmit_ipc_msg(). + * 3) Restarts SHMEM flow control if xmit_ipc_msg() returns -ENOSPC. + * + * Returns the return value from xmit_ipc_msg(). + */ +static int process_res_ack(struct shmem_link_device *shmd, int dev) +{ + int ret; + u16 mask; + + ret = xmit_ipc_msg(shmd, dev); + if (ret > 0) { + mask = get_mask_send(shmd, dev); + send_int2cp(shmd, INT_NON_CMD(mask)); + get_shmem_status(shmd, TX, msq_get_free_slot(&shmd->tx_msq)); + } + + if (ret >= 0) + atomic_set(&shmd->res_required[dev], 0); + + return ret; +} + +/** + * fmt_tx_work: performs TX for FMT IPC device under SHMEM flow control + * @ws: pointer to an instance of the work_struct structure + * + * 1) Starts waiting for RES_ACK of FMT IPC device. + * 2) Returns immediately if the wait is interrupted. + * 3) Restarts SHMEM flow control if there is a timeout from the wait. + * 4) Otherwise, it performs processing RES_ACK for FMT IPC device. + */ +static void fmt_tx_work(struct work_struct *ws) +{ + struct link_device *ld; + struct shmem_link_device *shmd; + int ret; + + ld = container_of(ws, struct link_device, fmt_tx_dwork.work); + shmd = to_shmem_link_device(ld); + + ret = wait_for_res_ack(shmd, IPC_FMT); + /* ret < 0 if interrupted */ + if (ret < 0) + return; + + /* ret == 0 on timeout */ + if (ret == 0) { + queue_delayed_work(ld->tx_wq, ld->tx_dwork[IPC_FMT], 0); + return; + } + + ret = process_res_ack(shmd, IPC_FMT); + if (ret >= 0) + return; + + /* At this point, ret < 0 */ + if (ret == -ENOSPC || ret == -EBUSY) { + queue_delayed_work(ld->tx_wq, ld->tx_dwork[IPC_FMT], + msecs_to_jiffies(1)); + } +} + +/** + * raw_tx_work: performs TX for RAW IPC device under SHMEM flow control. + * @ws: pointer to an instance of the work_struct structure + * + * 1) Starts waiting for RES_ACK of RAW IPC device. + * 2) Returns immediately if the wait is interrupted. + * 3) Restarts SHMEM flow control if there is a timeout from the wait. + * 4) Otherwise, it performs processing RES_ACK for RAW IPC device. + */ +static void raw_tx_work(struct work_struct *ws) +{ + struct link_device *ld; + struct shmem_link_device *shmd; + unsigned long delay = usecs_to_jiffies(1000); + int ret; + + ld = container_of(ws, struct link_device, raw_tx_dwork.work); + shmd = to_shmem_link_device(ld); + + ret = wait_for_res_ack(shmd, IPC_RAW); + /* ret < 0 if interrupted */ + if (ret < 0) + return; + + /* ret == 0 on timeout */ + if (ret == 0) { + queue_delayed_work(ld->tx_wq, ld->tx_dwork[IPC_RAW], delay); + return; + } + + ret = process_res_ack(shmd, IPC_RAW); + if (ret >= 0) { + mif_netif_wake(ld); + return; + } + + /* At this point, ret < 0 */ + if (ret == -ENOSPC || ret == -EBUSY) { + queue_delayed_work(ld->tx_wq, ld->tx_dwork[IPC_RAW], + msecs_to_jiffies(1)); + } +} + +/** + * shmem_send_ipc + * @shmd: pointer to an instance of shmem_link_device structure + * @dev: IPC device (IPC_FMT, IPC_RAW, etc.) + * @skb: pointer to an skb that will be transmitted + * + * 1) Tries to transmit IPC messages in the skb_txq with xmit_ipc_msg(). + * 2) Sends an interrupt to CP if there is no error from xmit_ipc_msg(). + * 3) Starts SHMEM flow control if xmit_ipc_msg() returns -ENOSPC. + */ +static int shmem_send_ipc(struct shmem_link_device *shmd, int dev) +{ + struct link_device *ld = &shmd->ld; + int ret; + u16 mask; + + if (atomic_read(&shmd->res_required[dev]) > 0) { + mif_err("%s: %s_TXQ is full\n", ld->name, get_dev_name(dev)); + return 0; + } + + ret = xmit_ipc_msg(shmd, dev); + if (likely(ret > 0)) { + mask = get_mask_send(shmd, dev); + send_int2cp(shmd, INT_NON_CMD(mask)); + get_shmem_status(shmd, TX, msq_get_free_slot(&shmd->tx_msq)); + goto exit; + } + + /* If there was no TX, just exit */ + if (ret == 0) + goto exit; + + /* At this point, ret < 0 */ + if (ret == -ENOSPC || ret == -EBUSY) { + /*----------------------------------------------------*/ + /* shmd->res_required[dev] was set in xmit_ipc_msg(). */ + /*----------------------------------------------------*/ + + if (dev == IPC_RAW) + mif_netif_stop(ld); + + queue_delayed_work(ld->tx_wq, ld->tx_dwork[dev], + msecs_to_jiffies(1)); + } + +exit: + return ret; +} + +/** + * shmem_try_send_ipc + * @shmd: pointer to an instance of shmem_link_device structure + * @dev: IPC device (IPC_FMT, IPC_RAW, etc.) + * @iod: pointer to an instance of the io_device structure + * @skb: pointer to an skb that will be transmitted + * + * 1) Enqueues an skb to the skb_txq for @dev in the link device instance. + * 2) Tries to transmit IPC messages with shmem_send_ipc(). + */ +static void shmem_try_send_ipc(struct shmem_link_device *shmd, int dev, + struct io_device *iod, struct sk_buff *skb) +{ + struct link_device *ld = &shmd->ld; + struct sk_buff_head *txq = ld->skb_txq[dev]; + int ret; + + if (unlikely(txq->qlen >= MAX_SKB_TXQ_DEPTH)) { + mif_err("%s: %s txq->qlen %d >= %d\n", ld->name, + get_dev_name(dev), txq->qlen, MAX_SKB_TXQ_DEPTH); + dev_kfree_skb_any(skb); + return; + } + + skb_queue_tail(txq, skb); + + ret = shmem_send_ipc(shmd, dev); + if (ret < 0) { + mif_err("%s->%s: ERR! shmem_send_ipc fail (err %d)\n", + iod->name, ld->name, ret); + } +} + +static int shmem_send_udl_cmd(struct shmem_link_device *shmd, int dev, + struct io_device *iod, struct sk_buff *skb) +{ + struct link_device *ld = &shmd->ld; + u8 *buff; + u8 *src; + u32 qsize; + u32 in; + int space; + int tx_bytes; + struct circ_status circ; + + if (iod->format == IPC_BOOT) { + pr_ipc(0, "[AP->CP] DL CMD", skb->data, + (skb->len > 20 ? 20 : skb->len)); + + if (skb->data[12] == 0x2d && skb->data[13] == 0xa1) + log_info.debug_log = true; + } else { + pr_ipc(0, "[AP->CP] UL CMD", skb->data, + (skb->len > 20 ? 20 : skb->len)); + } + + /* Get the size of free space in the TXQ */ + space = get_txq_space(shmd, dev, &circ); + if (space < 0) { + reset_txq_circ(shmd, dev); + tx_bytes = -EIO; + goto exit; + } + + /* Get the size of data to be sent */ + tx_bytes = skb->len; + + /* Check the size of free space */ + if (space < tx_bytes) { + mif_err("%s: NOSPC in %s_TXQ {qsize:%u in:%u out:%u} free:%u < tx_bytes:%u\n", + ld->name, get_dev_name(dev), circ.qsize, + circ.in, circ.out, space, tx_bytes); + tx_bytes = -ENOSPC; + goto exit; + } + + /* Write data to the TXQ */ + buff = circ.buff; + src = skb->data; + qsize = circ.qsize; + in = circ.in; + circ_write(buff, src, qsize, in, tx_bytes); + + /* Update new head (in) pointer */ + set_txq_head(shmd, dev, circ_new_pointer(qsize, circ.in, tx_bytes)); + +exit: + dev_kfree_skb_any(skb); + return tx_bytes; +} + +static int shmem_send_udl_data(struct shmem_link_device *shmd, int dev) +{ + struct link_device *ld = &shmd->ld; + struct sk_buff_head *txq = ld->skb_txq[dev]; + struct sk_buff *skb; + u8 *src; + int tx_bytes; + int copied; + u8 *buff; + u32 qsize; + u32 in; + u32 out; + int space; + struct circ_status circ; + + /* Get the size of free space in the TXQ */ + space = get_txq_space(shmd, dev, &circ); + if (space < 0) { +#ifdef CONFIG_DEBUG_MODEM_IF + /* Trigger a enforced CP crash */ + trigger_forced_cp_crash(shmd, MEM_CRASH_REASON_CP, + "ERR! txq full in shmem_send_udl_data()"); +#endif + /* Empty out the TXQ */ + reset_txq_circ(shmd, dev); + return -EFAULT; + } + + buff = circ.buff; + qsize = circ.qsize; + in = circ.in; + out = circ.out; + space = circ.size; + + copied = 0; + while (1) { + skb = skb_dequeue(txq); + if (!skb) + break; + + /* Get the size of data to be sent */ + src = skb->data; + tx_bytes = skb->len; + + /* Check the free space size comparing with skb->len */ + if (space < tx_bytes) { + /* Set res_required flag for the "dev" */ + atomic_set(&shmd->res_required[dev], 1); + + /* Take the skb back to the skb_txq */ + skb_queue_head(txq, skb); + + mif_info("NOSPC in RAW_TXQ {qsize:%u in:%u out:%u} space:%u < tx_bytes:%u\n", + qsize, in, out, space, tx_bytes); + break; + } + + /* TX only when there is enough space in the TXQ */ + circ_write(buff, src, qsize, in, tx_bytes); + + copied += tx_bytes; + in = circ_new_pointer(qsize, in, tx_bytes); + space -= tx_bytes; + + dev_kfree_skb_any(skb); + } + + /* Update new head (in) pointer */ + if (copied > 0) { + in = circ_new_pointer(qsize, circ.in, copied); + set_txq_head(shmd, dev, in); + } + + return copied; +} + +/** + * shmem_send_udl + * @shmd: pointer to an instance of shmem_link_device structure + * @dev: IPC device (IPC_FMT, IPC_RAW, etc.) + * @iod: pointer to an instance of the io_device structure + * @skb: pointer to an skb that will be transmitted + * + * 1) Enqueues an skb to the skb_txq for @dev in the link device instance. + * 2) Tries to transmit IPC messages in the skb_txq by invoking xmit_ipc_msg() + * function. + * 3) Sends an interrupt to CP if there is no error from xmit_ipc_msg(). + * 4) Starts SHMEM flow control if xmit_ipc_msg() returns -ENOSPC. + */ +static void shmem_send_udl(struct shmem_link_device *shmd, int dev, + struct io_device *iod, struct sk_buff *skb) +{ + struct link_device *ld = &shmd->ld; + struct sk_buff_head *txq = ld->skb_txq[dev]; + struct completion *cmpl = &shmd->req_ack_cmpl[dev]; + struct std_dload_info *dl_info = &shmd->dl_info; + struct mem_status mst; + u32 timeout = msecs_to_jiffies(RES_ACK_WAIT_TIMEOUT); + u32 udl_cmd; + int ret; + u16 mask = get_mask_req_ack(shmd, dev) | get_mask_send(shmd, dev); + + memset(&mst, 0, sizeof(struct mem_status)); + + udl_cmd = std_udl_get_cmd(skb->data); + if (iod->format == IPC_DUMP || !std_udl_with_payload(udl_cmd)) { + ret = shmem_send_udl_cmd(shmd, dev, iod, skb); + if (ret > 0) + send_int2cp(shmd, INT_NON_CMD(mask)); + else + mif_err("ERR! shmem_send_udl_cmd fail(err %d)\n", ret); + goto exit; + } + + skb_queue_tail(txq, skb); + if (txq->qlen < dl_info->num_frames) + goto exit; + + while (1) { + ret = shmem_send_udl_data(shmd, dev); + if (ret < 0) { + mif_err("ERR! shmem_send_udl_data fail(err %d)\n", ret); + skb_queue_purge(txq); + break; + } + + if (skb_queue_empty(txq)) { + send_int2cp(shmd, INT_NON_CMD(mask)); + break; + } + + send_int2cp(shmd, INT_NON_CMD(mask)); + + do { + reinit_completion(cmpl); + ret = wait_for_completion_timeout(cmpl, timeout); + get_shmem_status(shmd, TX, &mst); + } while (mst.head[dev][TX] != mst.tail[dev][TX]); + } + +exit: + return; +} + +/** + * shmem_send + * @ld: pointer to an instance of the link_device structure + * @iod: pointer to an instance of the io_device structure + * @skb: pointer to an skb that will be transmitted + * + * Returns the length of data transmitted or an error code. + * + * Normal call flow for an IPC message: + * shmem_try_send_ipc -> shmem_send_ipc -> xmit_ipc_msg -> write_ipc_to_txq + * + * Call flow on congestion in a IPC TXQ: + * shmem_try_send_ipc -> shmem_send_ipc -> xmit_ipc_msg ,,, queue_delayed_work + * => xxx_tx_work -> wait_for_res_ack + * => msg_handler + * => process_res_ack -> xmit_ipc_msg (,,, queue_delayed_work ...) + */ +static int shmem_send(struct link_device *ld, struct io_device *iod, + struct sk_buff *skb) +{ + struct shmem_link_device *shmd = to_shmem_link_device(ld); + struct modem_ctl *mc = ld->mc; + int dev = iod->format; + int len = skb->len; + + switch (dev) { + case IPC_FMT: + case IPC_RAW: + if (likely(ld->mode == LINK_MODE_IPC)) { + if (unlikely(shmd->forced_cp_crash)) { + mif_err("%s:%s->%s: ERR! Forced CP Crash ...\n", + ld->name, iod->name, mc->name); + dev_kfree_skb_any(skb); + } else { + shmem_try_send_ipc(shmd, dev, iod, skb); + } + } else { + mif_err("%s:%s->%s: ERR! ld->mode != LINK_MODE_IPC\n", + ld->name, iod->name, mc->name); + dev_kfree_skb_any(skb); + } + break; + + case IPC_BOOT: + case IPC_DUMP: + shmem_send_udl(shmd, IPC_RAW, iod, skb); + break; + + default: + mif_err("%s:%s->%s: ERR! Invalid IOD (format %d)\n", + ld->name, iod->name, mc->name, dev); + dev_kfree_skb_any(skb); + len = -ENODEV; + break; + } + + return len; +} + +static int shmem_dload_start(struct link_device *ld, struct io_device *iod) +{ + struct shmem_link_device *shmd = to_shmem_link_device(ld); + u32 magic; + + ld->mode = LINK_MODE_DLOAD; + + clear_shmem_map(shmd); + msq_reset(&shmd->rx_msq); + + set_magic(shmd, SHM_BOOT_MAGIC); + magic = get_magic(shmd); + if (magic != SHM_BOOT_MAGIC) { + mif_err("%s: ERR! magic 0x%08X != SHM_BOOT_MAGIC 0x%08X\n", + ld->name, magic, SHM_BOOT_MAGIC); + return -EFAULT; + } + + return 0; +} + +/** + * shmem_set_dload_info + * @ld: pointer to an instance of link_device structure + * @iod: pointer to an instance of io_device structure + * @arg: pointer to an instance of std_dload_info structure in "user" memory + * + */ +static int shmem_set_dload_info(struct link_device *ld, struct io_device *iod, + unsigned long arg) +{ + struct shmem_link_device *shmd = to_shmem_link_device(ld); + struct std_dload_info *dl_info = &shmd->dl_info; + int ret; + + ret = copy_from_user(dl_info, (void __user *)arg, + sizeof(struct std_dload_info)); + if (ret) { + mif_err("ERR! copy_from_user fail!\n"); + return -EFAULT; + } + + return 0; +} + +static int shmem_force_dump(struct link_device *ld, struct io_device *iod) +{ + struct shmem_link_device *shmd = to_shmem_link_device(ld); + mif_err("+++\n"); + trigger_forced_cp_crash(shmd, MEM_CRASH_REASON_AP, + "ERR! shmem_force_dump() was called"); + mif_err("---\n"); + return 0; +} + +static inline void shmem_send_thermal(struct link_device *ld, + struct io_device *iod, int arg) +{ + struct shmem_link_device *shmd = to_shmem_link_device(ld); + + if (ld->mode != LINK_MODE_IPC) { + mif_info("%s: mask 0x%04X\n", ld->name, CALLER, arg); + return; + } + + mbox_set_value(MCU_CP, shmd->mbx_ap2cp_tmu_msg, arg); + mbox_set_interrupt(MCU_CP, shmd->int_ap2cp_tmu_msg); +} + +static int shmem_dump_start(struct link_device *ld, struct io_device *iod) +{ + struct shmem_link_device *shmd = to_shmem_link_device(ld); + + ld->mode = LINK_MODE_ULOAD; + + clear_shmem_map(shmd); + msq_reset(&shmd->rx_msq); + + mif_err("%s: magic = 0x%08X\n", ld->name, SHM_DUMP_MAGIC); + set_magic(shmd, SHM_DUMP_MAGIC); + + return 0; +} + +static void shmem_remap_4mb_ipc_region(struct shmem_link_device *shmd) +{ + struct shmem_4mb_phys_map *map; + struct shmem_ipc_device *dev; + + map = (struct shmem_4mb_phys_map *)shmd->base; + + /* Magic code and access enable fields */ + shmd->ipc_map.magic = (u32 __iomem *)&map->magic; + shmd->ipc_map.access = (u32 __iomem *)&map->access; + + /* FMT */ + dev = &shmd->ipc_map.dev[IPC_FMT]; + + memmove(dev->name, "FMT", strlen("FMT")); + dev->id = IPC_FMT; + + dev->txq.head = (u32 __iomem *)&map->fmt_tx_head; + dev->txq.tail = (u32 __iomem *)&map->fmt_tx_tail; + dev->txq.buff = (u8 __iomem *)&map->fmt_tx_buff[0]; + dev->txq.size = SHM_4M_FMT_TX_BUFF_SZ; + + dev->rxq.head = (u32 __iomem *)&map->fmt_rx_head; + dev->rxq.tail = (u32 __iomem *)&map->fmt_rx_tail; + dev->rxq.buff = (u8 __iomem *)&map->fmt_rx_buff[0]; + dev->rxq.size = SHM_4M_FMT_RX_BUFF_SZ; + + dev->mask_req_ack = INT_MASK_REQ_ACK_F; + dev->mask_res_ack = INT_MASK_RES_ACK_F; + dev->mask_send = INT_MASK_SEND_F; + + /* RAW */ + dev = &shmd->ipc_map.dev[IPC_RAW]; + + memmove(dev->name, "RAW", strlen("RAW")); + dev->id = IPC_RAW; + + dev->txq.head = (u32 __iomem *)&map->raw_tx_head; + dev->txq.tail = (u32 __iomem *)&map->raw_tx_tail; + dev->txq.buff = (u8 __iomem *)&map->raw_tx_buff[0]; + dev->txq.size = SHM_4M_RAW_TX_BUFF_SZ; + + dev->rxq.head = (u32 __iomem *)&map->raw_rx_head; + dev->rxq.tail = (u32 __iomem *)&map->raw_rx_tail; + dev->rxq.buff = (u8 __iomem *)&map->raw_rx_buff[0]; + dev->rxq.size = SHM_4M_RAW_RX_BUFF_SZ; + + dev->mask_req_ack = INT_MASK_REQ_ACK_R; + dev->mask_res_ack = INT_MASK_RES_ACK_R; + dev->mask_send = INT_MASK_SEND_R; + + /* interrupt ports */ + shmd->ipc_map.mbx2ap = NULL; + shmd->ipc_map.mbx2cp = NULL; +} + +static int shmem_init_ipc_map(struct shmem_link_device *shmd) +{ + if (shmd->size >= SZ_4M) + shmem_remap_4mb_ipc_region(shmd); + else + return -EINVAL; + + memset(shmd->base, 0, shmd->size); + + shmd->magic = shmd->ipc_map.magic; + shmd->access = shmd->ipc_map.access; + + shmd->dev[IPC_FMT] = &shmd->ipc_map.dev[IPC_FMT]; + shmd->dev[IPC_RAW] = &shmd->ipc_map.dev[IPC_RAW]; + + shmd->mbx2ap = shmd->ipc_map.mbx2ap; + shmd->mbx2cp = shmd->ipc_map.mbx2cp; + + return 0; +} + +static void shmem_link_terminate(struct link_device *ld, struct io_device *iod) +{ + if (iod->format == IPC_FMT && ld->mode == LINK_MODE_IPC) { + if (!atomic_read(&iod->opened)) { + ld->mode = LINK_MODE_OFFLINE; + mif_err("%s: %s: link mode changed: IPC -> OFFLINE\n", + iod->name, ld->name); + } + } +} + +static inline bool valid_bin_range(struct modem_data *modem, struct data_info *di) +{ + u32 resmem_start, resmem_end; + u32 bin_start, bin_end; + + resmem_start = modem->shmem_base; + resmem_end = resmem_start + modem->ipcmem_offset; + + bin_start = resmem_start + di->m_offset; + bin_end = bin_start + di->len; + + if (resmem_end < bin_end) { + mif_err("bin image offset:0x%X, size:0x%X [0x%X..0x%X] overruns reserved mem [0x%X..0x%X]\n", + di->m_offset, di->len, bin_start, bin_end, + resmem_start, resmem_end); + return false; + } + + return true; +} + +static int shmem_xmit_bin(struct link_device *ld, struct io_device *iod, + unsigned long arg) +{ + struct data_info di; + struct modem_data *modem = ld->mdm_data; + struct modem_ctl *mc = ld->mc; + u32 size = 0; + u8 __iomem *mem_base; + int err; + u64 buff_64; + + memset(&di, 0, sizeof(struct data_info)); + + err = copy_from_user(&di, (const void __user *)arg, + sizeof(struct data_info)); + if (err) { + mif_err("%s: ERR! INFO copy_from_user fail:%d\n", ld->name, err); + err = -EFAULT; + goto exit; + } + + if (di.len > di.total_size) { + mif_err("Unexpected cp binary size : 0x%x\n", di.len); + goto exit; + } + + + size = di.len; + if (di.stage == BOOT) { + mif_info("Get cp bootloader(size : %d bytes)\n", di.total_size); + + if (di.total_size > SZ_16K) { + mif_err("Unexpected cp bootloader size : 0x%x\n", + di.total_size); + goto exit; + } + + if (IS_ENABLED(CONFIG_CP_SECURE_BOOT) && + (mc->phone_state == STATE_CRASH_EXIT)) { + mem_base = modem->dump_base + di.m_offset; + } else { + if (!valid_bin_range(modem, &di)) { + mif_err("stage:%d invalid BOOT image\n", di.stage); + BUG(); + } + mem_base = modem->modem_base + di.m_offset; + } + + buff_64 = (u64)di.buff; + + err = copy_from_user(mem_base, (void __user *)buff_64, size); + if (err) { + mif_err("%s: ERR! BOOT copy_from_user fail\n", + ld->name); + err = -EFAULT; + goto exit; + } + + /* change magic code for boot */ + shmem_dload_start(ld, iod); + } else { + if (!valid_bin_range(modem, &di) || di.m_offset == 0) { + mif_err("stage:%d invalid image\n", di.stage); + BUG(); + } + buff_64 = (u64)di.buff; + + if ((main_bin_size == 0) && (di.stage == MAIN)) { + main_bin_size = di.total_size; + main_bin = (void *)(modem->modem_base + di.m_offset); + } + err = copy_from_user(modem->modem_base + di.m_offset, + (void __user *)buff_64, size); + + if (err) { + mif_err("%s: ERR! copy_from_user fail\n", ld->name); + err = -EFAULT; + goto exit; + } + } + +exit: + return 0; +} + +static int shmem_sec_init(struct link_device *ld, struct io_device *iod, + unsigned long arg) +{ + enum cp_boot_mode mode = (enum cp_boot_mode)arg; + int err = 0; + + mif_info("%s\n", __func__); + + /* How to use exynos_smc + * exynos_smc(unsigned long cmd, unsigned long arg1, unsigned long arg2, unsigned long arg3) + * x0: smc_cmd_id + * x1: mode(0x0: boot, 0x1: dump 0x2: re_init) + * x2: size(binary size) + * x3: addr(only dump mode) + */ + err = exynos_smc(SMC_ID, mode, 0, 0); + mif_info("%s:smc call return value: %d\n", __func__, err); + + return err; +} + +#if !defined(CONFIG_CP_SECURE_BOOT) +unsigned long shmem_calculate_CRC32(const unsigned char *buf, unsigned long len) +{ + unsigned long ul_crc; + + if (!buf) + return 0L; + + ul_crc = CRC32_XINIT; + while (len--) + ul_crc = CRC32_TABLE[(ul_crc ^ *buf++) & 0xFF] ^ (ul_crc >> 8); + + ul_crc ^= CRC32_XOROT; + + return ul_crc; +} + +void shmem_check_modem_binary_crc(void) +{ + unsigned long CRC; + + if (main_bin == NULL || main_bin_size == 0) + return; + + CRC = shmem_calculate_CRC32((unsigned char *)main_bin, main_bin_size); + + mif_info("Modem Main Binary CRC: %08X\n", (unsigned int)CRC); +} +#endif + +static int cp_init_done; +static int shmem_security_check(struct link_device *ld, struct io_device *iod, + unsigned long arg) +{ + struct modem_ctl __maybe_unused *mc = ld->mc; + struct modem_data __maybe_unused *modem = ld->mdm_data; + struct sec_info info; + int err = 0; + + err = copy_from_user(&info, (const void __user *)arg, + sizeof(struct sec_info)); + if (err) { + mif_err("%s: ERR! security_check copy_from_user fail\n", + ld->name); + err = -EFAULT; + goto exit; + } + + mif_err("%s: call requeset_security_check(mode: %d, boot_size: %d, main_size: %d\n", + ld->name, info.mode, info.boot_size, info.main_size); + +#if !defined(CONFIG_CP_SECURE_BOOT) + if (info.mode == 0) + shmem_check_modem_binary_crc(); +#else + + exynos_smc(SMC_ID_CLK, SSS_CLK_ENABLE, 0, 0); + + if (mc->ap2cp_cfg_ioaddr && !cp_init_done) { + if (cal_cp_status() == 0) { + mif_err("CP first Init!\n"); + cal_cp_init(); + } + cp_init_done = 1; + } + + if (mc->phone_state == STATE_CRASH_EXIT) { + err = exynos_smc(SMC_ID, info.mode, info.boot_size, + modem->dump_addr); + } else { + if (shm_get_use_cp_memory_map_flag() && info.mode == 0) + info.mode |= (unsigned int)shm_get_phys_base(); + err = exynos_smc(SMC_ID, info.mode, info.boot_size, + info.main_size); + } + + exynos_smc(SMC_ID_CLK, SSS_CLK_DISABLE, 0, 0); + mif_info("%s: smc call return_value=0x%08x(%s)\n", ld->name, err, + err < sizeof(smc_err_string) ? smc_err_string[err] : "NULL"); +#endif +exit: + + return err; +} + +static int shmem_crash_reason(struct link_device *ld, struct io_device *iod, + unsigned long arg) +{ + struct shmem_link_device *shmd = to_shmem_link_device(ld); + int ret; + + ret = copy_to_user((void __user *)arg, &shmd->crash_reason, + sizeof(struct crash_reason)); + if (ret) { + mif_err("ERR! copy_to_user fail!\n"); + return -EFAULT; + } + + return 0; +} + +struct link_device *shmem_create_link_device(struct platform_device *pdev) +{ + struct shmem_link_device *shmd = NULL; + struct link_device *ld = NULL; + struct modem_data *modem = NULL; + struct device *dev = &pdev->dev; + int err = 0; + int i = 0; + mif_info("+++\n"); + + /* Get the modem (platform) data */ + modem = (struct modem_data *)dev->platform_data; + if (!modem) { + mif_err("ERR! modem == NULL\n"); + return NULL; + } + mif_err("%s: %s\n", modem->link_name, modem->name); + + if (!modem->mbx) { + mif_err("%s: ERR! %s->mbx == NULL\n", + modem->link_name, modem->name); + return NULL; + } + + /* Alloc an instance of shmem_link_device structure */ + shmd = devm_kzalloc(dev, sizeof(struct shmem_link_device), GFP_KERNEL); + if (!shmd) { + mif_err("%s: ERR! shmd kzalloc fail\n", modem->link_name); + goto error; + } + ld = &shmd->ld; + g_shmd = shmd; + + /* Retrieve modem data and SHMEM control data from the modem data */ + ld->mdm_data = modem; + ld->name = modem->link_name; + ld->aligned = 1; + ld->max_ipc_dev = MAX_EXYNOS_DEVICES; + + /* Set attributes as a link device */ + ld->terminate_comm = shmem_link_terminate; + ld->send = shmem_send; + ld->dload_start = shmem_dload_start; + ld->firm_update = shmem_set_dload_info; + ld->force_dump = shmem_force_dump; + ld->dump_start = shmem_dump_start; + ld->send_tmu = shmem_send_thermal; + + ld->xmit_bin = shmem_xmit_bin; + ld->check_security = shmem_security_check; + ld->sec_init = shmem_sec_init; + + ld->shmem_dump = save_shmem_dump; + ld->vss_dump = save_vss_dump; + ld->acpm_dump = save_acpm_dump; + + ld->crash_reason = shmem_crash_reason; + + INIT_LIST_HEAD(&ld->list); + + skb_queue_head_init(&ld->sk_fmt_tx_q); + skb_queue_head_init(&ld->sk_raw_tx_q); + ld->skb_txq[IPC_FMT] = &ld->sk_fmt_tx_q; + ld->skb_txq[IPC_RAW] = &ld->sk_raw_tx_q; + + skb_queue_head_init(&ld->sk_fmt_rx_q); + skb_queue_head_init(&ld->sk_raw_rx_q); + ld->skb_rxq[IPC_FMT] = &ld->sk_fmt_rx_q; + ld->skb_rxq[IPC_RAW] = &ld->sk_raw_rx_q; + + init_completion(&ld->init_cmpl); + + /* Retrieve SHMEM resource */ + if (modem->link_types & LINKTYPE(LINKDEV_SHMEM)) { + shmd->type = REAL_SHMEM; + mif_debug("%s: shmd->type = REAL_SHMEM\n", ld->name); + } else { + mif_err("%s: ERR! invalid type\n", ld->name); + goto error; + } + + /* Initialize CP Reserved mem */ + modem->modem_base = shm_request_region(modem->shmem_base, + modem->ipcmem_offset); + if (!modem->modem_base) { + mif_err("%s: ERR! cp_reserved_region fail\n", ld->name); + goto error; + } + mif_err("%s: cp phys_addr:0x%x virt_addr:0x%p size: 0x%x\n", ld->name, + modem->shmem_base, modem->modem_base, modem->ipcmem_offset); + + shmd->start = modem->shmem_base + modem->ipcmem_offset; + shmd->size = modem->ipc_size; + shmd->base = shm_request_region(shmd->start, shmd->size); + if (!shmd->base) { + mif_err("%s: ERR! shm_request_region fail\n", ld->name); + goto error; + } + mif_err("%s: phys_addr:0x%x virt_addr:0x%p size:0x%x\n", + ld->name, shmd->start, shmd->base, shmd->size); + + shmd->vss_base = shm_get_vss_region(); + if (!shmd->vss_base) + mif_err("Failed to vmap vss_region\n"); + + mif_err("vss_base=0x%p\n", shmd->vss_base); + + shmd->acpm_base = shm_get_acpm_region(); + shmd->acpm_size = shm_get_acpm_size(); + if (!shmd->acpm_base) + mif_err("Failed to vmap acpm_region\n"); + else + mif_err("acpm_base=0x%p acpm_size:0x%x\n", shmd->acpm_base, + shmd->acpm_size); + + modem->ipc_base = (u8 __iomem *)shmd->base; + modem->dump_addr = shmd->start + modem->dump_offset; + modem->dump_base = (u8 __iomem *)shmd->base + modem->dump_offset; + + /* Initialize SHMEM maps (physical map -> logical map) */ + err = shmem_init_ipc_map(shmd); + if (err < 0) { + mif_err("%s: ERR! shmem_init_ipc_map fail (err %d)\n", + ld->name, err); + goto error; + } + + /* Initialize locks, completions, and bottom halves */ + snprintf(shmd->wlock_name, MIF_MAX_NAME_LEN, "%s_wlock", ld->name); + wake_lock_init(&shmd->wlock, WAKE_LOCK_SUSPEND, shmd->wlock_name); + + init_completion(&shmd->udl_cmpl); + for (i = 0; i < MAX_EXYNOS_DEVICES; i++) + init_completion(&shmd->req_ack_cmpl[i]); + + tasklet_init(&shmd->rx_tsk, ipc_rx_task, (unsigned long)shmd); + INIT_DELAYED_WORK(&shmd->msg_rx_dwork, msg_rx_work); + INIT_DELAYED_WORK(&shmd->udl_rx_dwork, udl_rx_work); + + for (i = 0; i < MAX_EXYNOS_DEVICES; i++) { + spin_lock_init(&shmd->tx_lock[i]); + atomic_set(&shmd->res_required[i], 0); + } + + ld->tx_wq = create_singlethread_workqueue("shmem_tx_wq"); + if (!ld->tx_wq) { + mif_err("%s: ERR! fail to create tx_wq\n", ld->name); + goto error; + } + INIT_DELAYED_WORK(&ld->fmt_tx_dwork, fmt_tx_work); + INIT_DELAYED_WORK(&ld->raw_tx_dwork, raw_tx_work); + ld->tx_dwork[IPC_FMT] = &ld->fmt_tx_dwork; + ld->tx_dwork[IPC_RAW] = &ld->raw_tx_dwork; + + spin_lock_init(&shmd->tx_msq.lock); + spin_lock_init(&shmd->rx_msq.lock); + +#ifdef CONFIG_DEBUG_MODEM_IF + spin_lock_init(&shmd->trace_list.lock); + INIT_DELAYED_WORK(&shmd->dump_dwork, mem_dump_work); +#endif + + /* Retrieve SHMEM MBOX#, IRQ#, etc. */ + shmd->int_ap2cp_msg = modem->mbx->int_ap2cp_msg; + shmd->mbx_ap2cp_msg = modem->mbx->mbx_ap2cp_msg; + + shmd->irq_cp2ap_msg = modem->mbx->irq_cp2ap_msg; + shmd->mbx_cp2ap_msg = modem->mbx->mbx_cp2ap_msg; + + shmd->int_ap2cp_wakeup = modem->mbx->int_ap2cp_wakeup; + + shmd->mbx_mif_freq = modem->mbx->mbx_ap2cp_mif_freq; + shmd->mbx_perf_req_cpu = modem->mbx->mbx_cp2ap_perf_req_cpu; + shmd->mbx_perf_req_mif = modem->mbx->mbx_cp2ap_perf_req_mif; + shmd->mbx_perf_req_int = modem->mbx->mbx_cp2ap_perf_req_int; + shmd->irq_perf_req_cpu = modem->mbx->irq_cp2ap_perf_req_cpu; + shmd->irq_perf_req_mif = modem->mbx->irq_cp2ap_perf_req_mif; + shmd->irq_perf_req_int = modem->mbx->irq_cp2ap_perf_req_int; + + shmd->ap_clk_table = modem->mbx->ap_clk_table; + shmd->ap_clk_cnt = modem->mbx->ap_clk_cnt; + + shmd->mif_clk_table = modem->mbx->mif_clk_table; + shmd->mif_clk_cnt = modem->mbx->mif_clk_cnt; + + shmd->irq_cp2ap_wakelock = modem->mbx->irq_cp2ap_wake_lock; + +#ifdef CONFIG_MODEM_IF_ADAPTIVE_QOS + pm_qos_add_request(&pm_qos_req_cpu[0], PM_QOS_CLUSTER0_FREQ_MIN, 0); + pm_qos_add_request(&pm_qos_req_cpu[1], PM_QOS_CLUSTER1_FREQ_MIN, 0); + pm_qos_add_request(&pm_qos_req_mif, PM_QOS_BUS_THROUGHPUT, 0); + pm_qos_add_request(&pm_qos_req_int, PM_QOS_DEVICE_THROUGHPUT, 0); + + INIT_WORK(&shmd->pm_qos_work_max, shmem_qos_work_max); + INIT_WORK(&shmd->pm_qos_work_normal, shmem_qos_work_normal); + + init_timer(&shmd->max_qos_timer); + shmd->max_qos_timer.function = max_qos_timer_callback; + shmd->max_qos_timer.data = (unsigned long)shmd; +#endif + + /** + * Retrieve SHMEM MBOX# and IRQ# for wakelock + */ + wake_lock_init(&shmd->cp_wlock, WAKE_LOCK_SUSPEND, ld->name); + + err = mbox_request_irq(MCU_CP, shmd->irq_cp2ap_wakelock, shmem_cp2ap_wakelock_handler, shmd); + if (err) { + mif_err("%s: ERR! mbox_request_irq(%u) fail (%d)\n", + ld->name, shmd->irq_cp2ap_wakelock, err); + goto error; + } + mif_info("%s: mbox_request_irq(%u) - irq_cp2ap_wakelock\n", ld->name, shmd->irq_cp2ap_wakelock); + + /* Register interrupt handlers */ + err = mbox_request_irq(MCU_CP, shmd->irq_cp2ap_msg, shmem_irq_handler, shmd); + if (err) { + mif_err("%s: ERR! mbox_request_irq fail (err %d)\n", + ld->name, err); + goto error; + } + mif_info("%s: mbox_request_irq(%u) - irq\n", ld->name, shmd->irq_cp2ap_msg); + + modem->syscp_info = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + mif_info("---\n"); + return ld; + +error: + mif_err("xxx\n"); + kfree(shmd); + return NULL; +} diff --git a/drivers/misc/modem_if/modem_link_device_shmem.h b/drivers/misc/modem_if/modem_link_device_shmem.h new file mode 100644 index 000000000000..cc62cdc34d09 --- /dev/null +++ b/drivers/misc/modem_if/modem_link_device_shmem.h @@ -0,0 +1,857 @@ +/* + * Copyright (C) 2010 Samsung Electronics. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __MODEM_LINK_DEVICE_SHMEM_H__ +#define __MODEM_LINK_DEVICE_SHMEM_H__ + +#include "modem_utils.h" +#include "modem_link_device_memory.h" + +#define SHM_BOOT_MAGIC 0xBDBD +#define SHM_DUMP_MAGIC 0xBDBD +#define SHM_IPC_MAGIC 0xAA +#define SHM_PM_MAGIC 0x5F + +/* for ckecking modem infomation */ +#define SHM_CPINFO_DEBUG 0xF80 +#define SHM_MEM_CAL_OFFSET 0x1000 + +#define MODEM_VER_SIZE 0x64 +#define MODEM_VER_ID_OFFSET 0x1002C +#define MODEM_VER_ID_VALUE 0x44415445 +#define MODEM_VER_ADDR_OFFSET 0x10030 + +#define SHM_4M_RESERVED_SZ 4056 +#define SHM_4M_FMT_TX_BUFF_SZ 4096 +#define SHM_4M_FMT_RX_BUFF_SZ 4096 +#define SHM_4M_RAW_TX_BUFF_SZ 2084864 +#define SHM_4M_RAW_RX_BUFF_SZ 2097152 +#define SHM_4M_MAX_UPLINK_SIZE (16*1024) + +#define CP_WAKEUP_HOLD_TIME 500 /* 500 ms */ + +#define MEM_CRASH_REASON_CP 0 +#define MEM_CRASH_REASON_AP 1 +#define MEM_CRASH_REASON_RIL 2 +#define MEM_CRASH_REASON_SIZE 512 + +#define SHM_RX_MAX_QOS_TRIGGER_BYTES 4000 /* 32 Mbps */ +#define SHM_RX_MIN_QOS_HOLD_MS 500 /* 500ms */ + +struct crash_reason { + u32 owner; + char string[MEM_CRASH_REASON_SIZE]; +}; + +struct shmem_4mb_phys_map { + u32 magic; + u32 access; + + u32 fmt_tx_head; + u32 fmt_tx_tail; + + u32 fmt_rx_head; + u32 fmt_rx_tail; + + u32 raw_tx_head; + u32 raw_tx_tail; + + u32 raw_rx_head; + u32 raw_rx_tail; + + u8 reserved[SHM_4M_RESERVED_SZ]; + + u8 fmt_tx_buff[SHM_4M_FMT_TX_BUFF_SZ]; + u8 fmt_rx_buff[SHM_4M_FMT_RX_BUFF_SZ]; + + u8 raw_tx_buff[SHM_4M_RAW_TX_BUFF_SZ]; + u8 raw_rx_buff[SHM_4M_RAW_RX_BUFF_SZ]; +} __packed; + +struct shmem_circ { + u32 __iomem *head; + u32 __iomem *tail; + u8 __iomem *buff; + u32 size; +}; + +struct shmem_ipc_device { + char name[16]; + int id; + + struct shmem_circ txq; + struct shmem_circ rxq; + + u16 mask_req_ack; + u16 mask_res_ack; + u16 mask_send; + + int req_ack_rcvd; +}; + +struct shmem_ipc_map { + u32 __iomem *magic; + u32 __iomem *access; + + struct shmem_ipc_device dev[MAX_SIPC5_DEV]; + + u32 __iomem *mbx2ap; + u32 __iomem *mbx2cp; +}; + +struct shmem_link_device { + struct link_device ld; + + enum shmem_type type; + + struct modem_mbox *mbx; + + /* SHMEM (SHARED MEMORY) address, size, IRQ# */ + u32 start; /* physical "start" address of SHMEM */ + u32 size; /* size of SHMEM */ + u32 __iomem *base; /* virtual address to the "IPC" region */ + + u8 __iomem *vss_base; + u8 __iomem *acpm_base; + int acpm_size; + + /* MBOX number & IRQ */ + unsigned int mbx_cp2ap_msg; + unsigned int irq_cp2ap_msg; + + unsigned int mbx_ap2cp_msg; + unsigned int int_ap2cp_msg; + unsigned int int_ap2cp_wakeup; + + unsigned int mbx_mif_freq; + + unsigned int mbx_perf_req_cpu; + unsigned int mbx_perf_req_mif; + unsigned int mbx_perf_req_int; + + unsigned int irq_perf_req_cpu; + unsigned int irq_perf_req_mif; + unsigned int irq_perf_req_int; + + struct work_struct pm_qos_work_max; + struct work_struct pm_qos_work_normal; + + unsigned int mbx_cp2ap_wakelock; /* MBOX# for wakelock */ + unsigned int irq_cp2ap_wakelock; /* INTR# for wakelock */ + + unsigned int *mif_clk_table; + unsigned int mif_clk_cnt; + + unsigned int *ap_clk_table; + unsigned int ap_clk_cnt; + + /* SHMEM GPIO & IRQ */ + unsigned int gpio_ap_wakeup; + int irq_ap_wakeup; + unsigned int gpio_ap_status; + + unsigned int gpio_cp_wakeup; + unsigned int gpio_cp_status; + int irq_cp_status; + + /* MBOX for TMU */ + unsigned int mbx_ap2cp_tmu_msg; + int int_ap2cp_tmu_msg; + + /* IPC device map */ + struct shmem_ipc_map ipc_map; + + /* Pointers (aliases) to IPC device map */ + u32 __iomem *magic; + u32 __iomem *access; + struct shmem_ipc_device *dev[MAX_SIPC5_DEV]; + u32 __iomem *mbx2ap; + u32 __iomem *mbx2cp; + + /* Wakelock for SHMEM device */ + struct wake_lock wlock; + char wlock_name[MIF_MAX_NAME_LEN]; + + struct wake_lock ap_wlock; + char ap_wlock_name[MIF_MAX_NAME_LEN]; + + struct wake_lock cp_wlock; + char cp_wlock_name[MIF_MAX_NAME_LEN]; + + /* for UDL */ + struct completion udl_cmpl; + struct std_dload_info dl_info; + + /* for CP crash dump */ + bool forced_cp_crash; + struct timer_list crash_ack_timer; + + /* for locking TX process */ + spinlock_t tx_lock[MAX_SIPC5_DEV]; + + /* for retransmission under SHMEM flow control after TXQ full state */ + atomic_t res_required[MAX_SIPC5_DEV]; + struct completion req_ack_cmpl[MAX_SIPC5_DEV]; + + /* for efficient RX process */ + struct tasklet_struct rx_tsk; + struct delayed_work msg_rx_dwork; + struct delayed_work udl_rx_dwork; + struct io_device *iod[MAX_SIPC5_DEV]; + + /* for logging SHMEM status */ + struct mem_status_queue tx_msq; + struct mem_status_queue rx_msq; + + /* for logging SHMEM dump */ + struct trace_data_queue trace_list; +#ifdef CONFIG_DEBUG_MODEM_IF + struct delayed_work dump_dwork; + char dump_path[MIF_MAX_PATH_LEN]; +#endif + + /* to hold/release "cp_wakeup" for PM (power-management) */ + struct delayed_work cp_sleep_dwork; + atomic_t ref_cnt; + spinlock_t pm_lock; + + struct crash_reason crash_reason; + + struct timer_list max_qos_timer; +}; + +/* converts from struct link_device* to struct xxx_link_device* */ +#define to_shmem_link_device(linkdev) \ + container_of(linkdev, struct shmem_link_device, ld) + +#if 1 +#endif + +/** + * get_magic + * @shmd: pointer to an instance of shmem_link_device structure + * + * Returns the value of the "magic code" field. + */ +static inline u32 get_magic(struct shmem_link_device *shmd) +{ + return ioread32(shmd->magic); +} + +/** + * get_access + * @shmd: pointer to an instance of shmem_link_device structure + * + * Returns the value of the "access enable" field. + */ +static inline u32 get_access(struct shmem_link_device *shmd) +{ + return ioread32(shmd->access); +} + +/** + * set_magic + * @shmd: pointer to an instance of shmem_link_device structure + * @val: value to be written to the "magic code" field + */ +static inline void set_magic(struct shmem_link_device *shmd, u32 val) +{ + iowrite32(val, shmd->magic); +} + +/** + * set_access + * @shmd: pointer to an instance of shmem_link_device structure + * @val: value to be written to the "access enable" field + */ +static inline void set_access(struct shmem_link_device *shmd, u32 val) +{ + iowrite32(val, shmd->access); +} + +/** + * get_txq_head + * @shmd: pointer to an instance of shmem_link_device structure + * @id: IPC device (IPC_FMT, IPC_RAW, etc.) + * + * Returns the value of a head (in) pointer in a TX queue. + */ +static inline u32 get_txq_head(struct shmem_link_device *shmd, int id) +{ + return ioread32(shmd->dev[id]->txq.head); +} + +/** + * get_txq_tail + * @shmd: pointer to an instance of shmem_link_device structure + * @id: IPC device (IPC_FMT, IPC_RAW, etc.) + * + * Returns the value of a tail (out) pointer in a TX queue. + * + * It is useless for an AP to read a tail pointer in a TX queue twice to verify + * whether or not the value in the pointer is valid, because it can already have + * been updated by a CP after the first access from the AP. + */ +static inline u32 get_txq_tail(struct shmem_link_device *shmd, int id) +{ + return ioread32(shmd->dev[id]->txq.tail); +} + +/** + * get_txq_buff + * @shmd: pointer to an instance of shmem_link_device structure + * @id: IPC device (IPC_FMT, IPC_RAW, etc.) + * + * Returns the start address of the buffer in a TXQ. + */ +static inline u8 *get_txq_buff(struct shmem_link_device *shmd, int id) +{ + return shmd->dev[id]->txq.buff; +} + +/** + * get_txq_buff_size + * @shmd: pointer to an instance of shmem_link_device structure + * @id: IPC device (IPC_FMT, IPC_RAW, etc.) + * + * Returns the size of the buffer in a TXQ. + */ +static inline u32 get_txq_buff_size(struct shmem_link_device *shmd, int id) +{ + return shmd->dev[id]->txq.size; +} + +/** + * get_rxq_head + * @shmd: pointer to an instance of shmem_link_device structure + * @id: IPC device (IPC_FMT, IPC_RAW, etc.) + * + * Returns the value of a head (in) pointer in an RX queue. + * + * It is useless for an AP to read a head pointer in an RX queue twice to verify + * whether or not the value in the pointer is valid, because it can already have + * been updated by a CP after the first access from the AP. + */ +static inline u32 get_rxq_head(struct shmem_link_device *shmd, int id) +{ + return ioread32(shmd->dev[id]->rxq.head); +} + +/** + * get_rxq_tail + * @shmd: pointer to an instance of shmem_link_device structure + * @id: IPC device (IPC_FMT, IPC_RAW, etc.) + * + * Returns the value of a tail (in) pointer in an RX queue. + */ +static inline u32 get_rxq_tail(struct shmem_link_device *shmd, int id) +{ + return ioread32(shmd->dev[id]->rxq.tail); +} + +/** + * get_rxq_buff + * @shmd: pointer to an instance of shmem_link_device structure + * @id: IPC device (IPC_FMT, IPC_RAW, etc.) + * + * Returns the start address of the buffer in an RXQ. + */ +static inline u8 *get_rxq_buff(struct shmem_link_device *shmd, int id) +{ + return shmd->dev[id]->rxq.buff; +} + +/** + * get_rxq_buff_size + * @shmd: pointer to an instance of shmem_link_device structure + * @id: IPC device (IPC_FMT, IPC_RAW, etc.) + * + * Returns the size of the buffer in an RXQ. + */ +static inline u32 get_rxq_buff_size(struct shmem_link_device *shmd, int id) +{ + return shmd->dev[id]->rxq.size; +} + +/** + * set_txq_head + * @shmd: pointer to an instance of shmem_link_device structure + * @id: IPC device (IPC_FMT, IPC_RAW, etc.) + * @in: value to be written to the head pointer in a TXQ + */ +static inline void set_txq_head(struct shmem_link_device *shmd, int id, u32 in) +{ + iowrite32(in, shmd->dev[id]->txq.head); +} + +/** + * set_txq_tail + * @shmd: pointer to an instance of shmem_link_device structure + * @id: IPC device (IPC_FMT, IPC_RAW, etc.) + * @out: value to be written to the tail pointer in a TXQ + */ +static inline void set_txq_tail(struct shmem_link_device *shmd, int id, u32 out) +{ + iowrite32(out, shmd->dev[id]->txq.tail); +} + +/** + * set_rxq_head + * @shmd: pointer to an instance of shmem_link_device structure + * @id: IPC device (IPC_FMT, IPC_RAW, etc.) + * @in: value to be written to the head pointer in an RXQ + */ +static inline void set_rxq_head(struct shmem_link_device *shmd, int id, u32 in) +{ + iowrite32(in, shmd->dev[id]->rxq.head); +} + +/** + * set_rxq_tail + * @shmd: pointer to an instance of shmem_link_device structure + * @id: IPC device (IPC_FMT, IPC_RAW, etc.) + * @out: value to be written to the tail pointer in an RXQ + */ +static inline void set_rxq_tail(struct shmem_link_device *shmd, int id, u32 out) +{ + iowrite32(out, shmd->dev[id]->rxq.tail); +} + +/** + * get_mask_req_ack + * @shmd: pointer to an instance of shmem_link_device structure + * @id: IPC device (IPC_FMT, IPC_RAW, etc.) + * + * Returns the REQ_ACK mask value for the IPC device. + */ +static inline u16 get_mask_req_ack(struct shmem_link_device *shmd, int id) +{ + return shmd->dev[id]->mask_req_ack; +} + +/** + * get_mask_res_ack + * @shmd: pointer to an instance of shmem_link_device structure + * @id: IPC device (IPC_FMT, IPC_RAW, etc.) + * + * Returns the RES_ACK mask value for the IPC device. + */ +static inline u16 get_mask_res_ack(struct shmem_link_device *shmd, int id) +{ + return shmd->dev[id]->mask_res_ack; +} + +/** + * get_mask_send + * @shmd: pointer to an instance of shmem_link_device structure + * @id: IPC device (IPC_FMT, IPC_RAW, etc.) + * + * Returns the SEND mask value for the IPC device. + */ +static inline u16 get_mask_send(struct shmem_link_device *shmd, int id) +{ + return shmd->dev[id]->mask_send; +} + +#ifndef CONFIG_LINK_DEVICE_C2C +/** + * read_int2cp + * @shmd: pointer to an instance of shmem_link_device structure + * + * Returns the value of the AP-to-CP interrupt register. + */ +static inline u16 read_int2cp(struct shmem_link_device *shmd) +{ + if (shmd->mbx2cp) + return ioread16(shmd->mbx2cp); + else + return 0; +} +#endif + +/** + * reset_txq_circ + * @shmd: pointer to an instance of shmem_link_device structure + * @dev: IPC device (IPC_FMT, IPC_RAW, etc.) + * + * Empties a TXQ by resetting the head (in) pointer with the value in the tail + * (out) pointer. + */ +static inline void reset_txq_circ(struct shmem_link_device *shmd, int dev) +{ + struct link_device *ld = &shmd->ld; + u32 head = get_txq_head(shmd, dev); + u32 tail = get_txq_tail(shmd, dev); + + mif_err("%s: %s_TXQ: HEAD[%u] <== TAIL[%u]\n", + ld->name, get_dev_name(dev), head, tail); + + set_txq_head(shmd, dev, tail); +} + +/** + * reset_rxq_circ + * @shmd: pointer to an instance of shmem_link_device structure + * @dev: IPC device (IPC_FMT, IPC_RAW, etc.) + * + * Empties an RXQ by resetting the tail (out) pointer with the value in the head + * (in) pointer. + */ +static inline void reset_rxq_circ(struct shmem_link_device *shmd, int dev) +{ + struct link_device *ld = &shmd->ld; + u32 head = get_rxq_head(shmd, dev); + u32 tail = get_rxq_tail(shmd, dev); + + mif_err("%s: %s_RXQ: TAIL[%u] <== HEAD[%u]\n", + ld->name, get_dev_name(dev), tail, head); + + set_rxq_tail(shmd, dev, head); +} + +/** + * ipc_active + * @shmd: pointer to an instance of shmem_link_device structure + * + * Returns whether or not IPC via the shmem_link_device instance is possible. + */ +static inline bool ipc_active(struct shmem_link_device *shmd) +{ + struct link_device *ld = &shmd->ld; + u32 magic = get_magic(shmd); + u32 access = get_access(shmd); + + /* Check link mode */ + if (unlikely(ld->mode != LINK_MODE_IPC)) { + mif_err("%s: ERR! ld->mode != LINK_MODE_IPC\n", + ld->name, CALLER); + return false; + } + + /* Check "magic code" and "access enable" values */ + if (unlikely(magic != SHM_IPC_MAGIC || access != 1)) { + mif_err("%s: ERR! magic:0x%X access:%d\n", + ld->name, CALLER, magic, access); + return false; + } + + return true; +} + +static inline bool all_rxq_empty(struct shmem_link_device *shmd, + struct mem_status *mst) +{ + return ((mst->head[IPC_FMT][RX] == mst->tail[IPC_FMT][RX]) + && (mst->head[IPC_RAW][RX] == mst->tail[IPC_RAW][RX])); +} + +/** + * get_rxq_rcvd + * @shmd: pointer to an instance of shmem_link_device structure + * @dev: IPC device (IPC_FMT, IPC_RAW, etc.) + * @mst: pointer to an instance of mem_status structure + * OUT @circ: pointer to an instance of circ_status structure + * + * Stores {start address of the buffer in a RXQ, size of the buffer, in & out + * pointer values, size of received data} into the 'circ' instance. + * + * Returns an error code. + */ +static inline int get_rxq_rcvd(struct shmem_link_device *shmd, int dev, + struct mem_status *mst, struct circ_status *circ) +{ + struct link_device *ld = &shmd->ld; + + circ->buff = get_rxq_buff(shmd, dev); + circ->qsize = get_rxq_buff_size(shmd, dev); + circ->in = mst->head[dev][RX]; + circ->out = mst->tail[dev][RX]; + circ->size = circ_get_usage(circ->qsize, circ->in, circ->out); + + if (circ_valid(circ->qsize, circ->in, circ->out)) { + return 0; + } + + mif_err("%s: ERR! %s_RXQ invalid (qsize[%d] in[%d] out[%d])\n", + ld->name, get_dev_name(dev), circ->qsize, circ->in, circ->out); + + return -EIO; +} + +/** + * get_txq_space + * @shmd: pointer to an instance of shmem_link_device structure + * @dev: IPC device (IPC_FMT, IPC_RAW, etc.) + * OUT @circ: pointer to an instance of circ_status structure + * + * Stores {start address of the buffer in a TXQ, size of the buffer, in & out + * pointer values, size of free space} into the 'circ' instance. + * + * Returns the size of free space in the buffer or an error code. + */ +static inline int get_txq_space(struct shmem_link_device *shmd, int dev, + struct circ_status *circ) +{ + struct link_device *ld = &shmd->ld; + int cnt = 0; + u32 qsize; + u32 head; + u32 tail; + int space; + + while (1) { + qsize = get_txq_buff_size(shmd, dev); + head = get_txq_head(shmd, dev); + tail = get_txq_tail(shmd, dev); + space = circ_get_space(qsize, head, tail); + + mif_debug("%s: %s_TXQ{qsize:%u in:%u out:%u space:%u}\n", + ld->name, get_dev_name(dev), qsize, head, tail, space); + + if (circ_valid(qsize, head, tail)) + break; + + cnt++; + mif_err("%s: ERR! invalid %s_TXQ{qsize:%d in:%d out:%d space:%d}, count %d\n", + ld->name, get_dev_name(dev), qsize, head, tail, + space, cnt); + if (cnt >= MAX_RETRY_CNT) { + space = -EIO; + break; + } + + udelay(100); + } + + circ->buff = get_txq_buff(shmd, dev); + circ->qsize = qsize; + circ->in = head; + circ->out = tail; + circ->size = space; + + return space; +} + +/** + * get_txq_saved + * @shmd: pointer to an instance of shmem_link_device structure + * @dev: IPC device (IPC_FMT, IPC_RAW, etc.) + * @mst: pointer to an instance of mem_status structure + * OUT @circ: pointer to an instance of circ_status structure + * + * Stores {start address of the buffer in a TXQ, size of the buffer, in & out + * pointer values, size of stored data} into the 'circ' instance. + * + * Returns an error code. + */ +static inline int get_txq_saved(struct shmem_link_device *shmd, int dev, + struct circ_status *circ) +{ + struct link_device *ld = &shmd->ld; + int cnt = 0; + u32 qsize; + u32 head; + u32 tail; + int saved; + + while (1) { + qsize = get_txq_buff_size(shmd, dev); + head = get_txq_head(shmd, dev); + tail = get_txq_tail(shmd, dev); + saved = circ_get_usage(qsize, head, tail); + + mif_debug("%s: %s_TXQ{qsize:%u in:%u out:%u saved:%u}\n", + ld->name, get_dev_name(dev), qsize, head, tail, saved); + + if (circ_valid(qsize, head, tail)) + break; + + cnt++; + mif_err("%s: ERR! invalid %s_TXQ{qsize:%d in:%d out:%d saved:%d}, count %d\n", + ld->name, get_dev_name(dev), qsize, head, tail, + saved, cnt); + if (cnt >= MAX_RETRY_CNT) { + saved = -EIO; + break; + } + + udelay(100); + } + + circ->buff = get_txq_buff(shmd, dev); + circ->qsize = qsize; + circ->in = head; + circ->out = tail; + circ->size = saved; + + return saved; +} + +/** + * clear_shmem_map + * @shmd: pointer to an instance of shmem_link_device structure + * + * Clears all pointers in every circular queue. + */ +static inline void clear_shmem_map(struct shmem_link_device *shmd) +{ + set_txq_head(shmd, IPC_FMT, 0); + set_txq_tail(shmd, IPC_FMT, 0); + set_rxq_head(shmd, IPC_FMT, 0); + set_rxq_tail(shmd, IPC_FMT, 0); + + set_txq_head(shmd, IPC_RAW, 0); + set_txq_tail(shmd, IPC_RAW, 0); + set_rxq_head(shmd, IPC_RAW, 0); + set_rxq_tail(shmd, IPC_RAW, 0); +} + +/** + * reset_shmem_ipc + * @shmd: pointer to an instance of shmem_link_device structure + * + * Reset SHMEM with IPC map. + */ +static inline void reset_shmem_ipc(struct shmem_link_device *shmd) +{ + set_access(shmd, 0); + + clear_shmem_map(shmd); + + atomic_set(&shmd->res_required[IPC_FMT], 0); + atomic_set(&shmd->res_required[IPC_RAW], 0); + + atomic_set(&shmd->ref_cnt, 0); + + set_magic(shmd, SHM_IPC_MAGIC); + set_access(shmd, 1); +} + +/** + * init_shmem_ipc + * @shmd: pointer to an instance of shmem_link_device structure + * + * Initializes IPC via SHMEM. + */ +static inline int init_shmem_ipc(struct shmem_link_device *shmd) +{ + struct link_device *ld = &shmd->ld; + + if (ld->mode == LINK_MODE_IPC && + get_magic(shmd) == SHM_IPC_MAGIC && + get_access(shmd) == 1) { + mif_err("%s: IPC already initialized\n", ld->name); + return 0; + } + + /* Initialize variables for efficient TX/RX processing */ + shmd->iod[IPC_FMT] = link_get_iod_with_format(ld, IPC_FMT); + shmd->iod[IPC_RAW] = link_get_iod_with_format(ld, IPC_MULTI_RAW); + + reset_shmem_ipc(shmd); + + if (get_magic(shmd) != SHM_IPC_MAGIC || get_access(shmd) != 1) + return -EACCES; + + ld->mode = LINK_MODE_IPC; + + return 0; +} + +/** + * print_req_ack + * @shmd: pointer to an instance of shmem_link_device structure + * @dev: IPC device (IPC_FMT, IPC_RAW, etc.) + * @mst: pointer to an instance of mem_status structure + * + * Prints a snapshot of the status of an RXQ when receiving REQ_ACK + */ +static inline void print_req_ack(struct shmem_link_device *shmd, int dev, + struct mem_status *mst) +{ + struct link_device *ld; + struct modem_ctl *mc; + int req_ack_rcvd; + int us; + u32 qsize; + u32 in; + u32 out; + u32 rcvd; + u32 space; + struct utc_time utc; + + if (dev > IPC_RAW) + return; + + ld = &shmd->ld; + mc = ld->mc; + + req_ack_rcvd = shmd->dev[dev]->req_ack_rcvd; + us = ns2us(mst->ts.tv_nsec); + qsize = get_rxq_buff_size(shmd, dev); + in = mst->head[dev][RX]; + out = mst->tail[dev][RX]; + rcvd = circ_get_usage(qsize, in, out); + space = circ_get_space(qsize, in, out); + ts2utc(&mst->ts, &utc); + + pr_info("%s: [%02d:%02d:%02d.%06d] REQ_ACK: %s->%s: %s.%d {in:%d out:%d rcvd:%d space:%d}\n", + MIF_TAG, utc.hour, utc.min, utc.sec, us, mc->name, ld->name, + get_dev_name(dev), req_ack_rcvd, in, out, rcvd, space); +} + +/** + * print_res_ack + * @shmd: pointer to an instance of shmem_link_device structure + * @dev: IPC device (IPC_FMT, IPC_RAW, etc.) + * @mst: pointer to an instance of mem_status structure + * + * Prints a snapshot of the status of an RXQ when sending RES_ACK + */ +static inline void print_res_ack(struct shmem_link_device *shmd, int dev, + struct mem_status *mst) +{ + struct link_device *ld; + struct modem_ctl *mc; + int req_ack_rcvd; + int us; + u32 qsize; + u32 in; + u32 out; + u32 rcvd; + u32 space; + struct utc_time utc; + + if (dev > IPC_RAW) + return; + + ld = &shmd->ld; + mc = ld->mc; + + req_ack_rcvd = shmd->dev[dev]->req_ack_rcvd; + us = ns2us(mst->ts.tv_nsec); + qsize = get_rxq_buff_size(shmd, dev); + in = mst->head[dev][RX]; + out = mst->tail[dev][RX]; + rcvd = circ_get_usage(qsize, in, out); + space = circ_get_space(qsize, in, out); + ts2utc(&mst->ts, &utc); + + pr_info("%s: [%02d:%02d:%02d.%06d] RES_ACK: %s->%s: %s.%d {in:%d out:%d rcvd:%d space:%d}\n", + MIF_TAG, utc.hour, utc.min, utc.sec, us, ld->name, mc->name, + get_dev_name(dev), req_ack_rcvd, in, out, rcvd, space); +} + +#endif + diff --git a/drivers/misc/modem_if/modem_main.c b/drivers/misc/modem_if/modem_main.c new file mode 100644 index 000000000000..5e2ce8e84172 --- /dev/null +++ b/drivers/misc/modem_if/modem_main.c @@ -0,0 +1,632 @@ +/* linux/drivers/modem/modem.c + * + * Copyright (C) 2010 Google, Inc. + * Copyright (C) 2010 Samsung Electronics. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef CONFIG_OF +#include +#include +#endif +#include + +#include "modem_prj.h" +#include "modem_variation.h" +#include "modem_utils.h" + +#define FMT_WAKE_TIME (HZ/2) +#define RAW_WAKE_TIME (HZ*6) + +struct mif_log log_info; + +static int set_log_info(char *str) +{ + log_info.debug_log = true; + log_info.fmt_msg = strstr(str, "fmt") ? 1 : 0; + log_info.boot_msg = strstr(str, "boot") ? 1 : 0; + log_info.dump_msg = strstr(str, "dump") ? 1 : 0; + log_info.rfs_msg = strstr(str, "rfs") ? 1 : 0; + log_info.log_msg = strstr(str, "log") ? 1 : 0; + log_info.ps_msg = strstr(str, "ps") ? 1 : 0; + log_info.router_msg = strstr(str, "router") ? 1 : 0; + log_info.rcs_msg = strstr(str, "rcs") ? 1 : 0; + log_info.ppt_msg = strstr(str, "ppt") ? 1 : 0; + + mif_err("modemIF log info: %s\n", str); + + return 0; +} +__setup("log_info=", set_log_info); + +static struct modem_shared *create_modem_shared_data( + struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct modem_shared *msd; + int size = MAX_MIF_BUFF_SIZE; + + msd = devm_kzalloc(dev, sizeof(struct modem_shared), GFP_KERNEL); + if (msd == NULL) + return NULL; + + /* initialize link device list */ + INIT_LIST_HEAD(&msd->link_dev_list); + + /* initialize tree of io devices */ + msd->iodevs_tree_chan = RB_ROOT; + msd->iodevs_tree_fmt = RB_ROOT; + + msd->storage.cnt = 0; + msd->storage.addr = devm_kzalloc(dev, MAX_MIF_BUFF_SIZE + + (MAX_MIF_SEPA_SIZE * 2), GFP_KERNEL); + if (msd->storage.addr == NULL) { + mif_err("IPC logger buff alloc failed!!\n"); + return NULL; + } + memset(msd->storage.addr, 0, MAX_MIF_BUFF_SIZE + + (MAX_MIF_SEPA_SIZE * 2)); + memcpy(msd->storage.addr, MIF_SEPARATOR, strlen(MIF_SEPARATOR)); + msd->storage.addr += MAX_MIF_SEPA_SIZE; + memcpy(msd->storage.addr, &size, sizeof(int)); + msd->storage.addr += MAX_MIF_SEPA_SIZE; + spin_lock_init(&msd->lock); + + return msd; +} + +static struct modem_ctl *create_modemctl_device(struct platform_device *pdev, + struct modem_shared *msd) +{ + struct device *dev = &pdev->dev; + struct modem_data *pdata = pdev->dev.platform_data; + struct modem_ctl *modemctl; + int ret; + + /* create modem control device */ + modemctl = devm_kzalloc(dev, sizeof(struct modem_ctl), GFP_KERNEL); + if (!modemctl) { + mif_err("%s: modemctl devm_kzalloc fail\n", pdata->name); + mif_err("%s: xxx\n", pdata->name); + return NULL; + } + + modemctl->msd = msd; + modemctl->dev = dev; + modemctl->phone_state = STATE_OFFLINE; + + modemctl->mdm_data = pdata; + modemctl->name = pdata->name; + + /* init modemctl device for getting modemctl operations */ + + ret = init_modemctl_device(modemctl, pdata); + if (ret) { + mif_err("%s: init_modemctl_device fail (err %d)\n", + pdata->name, ret); + mif_err("%s: xxx\n", pdata->name); + kfree(modemctl); + return NULL; + } + + mif_info("%s is created!!!\n", pdata->name); + + return modemctl; +} + +static struct io_device *create_io_device(struct platform_device *pdev, + struct modem_io_t *io_t, struct modem_shared *msd, + struct modem_ctl *modemctl, struct modem_data *pdata) +{ + int ret; + struct device *dev = &pdev->dev; + struct io_device *iod; + + iod = devm_kzalloc(dev, sizeof(struct io_device), GFP_KERNEL); + if (!iod) { + mif_err("iod == NULL\n"); + return NULL; + } + + RB_CLEAR_NODE(&iod->node_chan); + RB_CLEAR_NODE(&iod->node_fmt); + + iod->name = io_t->name; + iod->id = io_t->id; + iod->format = io_t->format; + iod->io_typ = io_t->io_type; + iod->link_types = io_t->links; + iod->attrs = io_t->attrs; + iod->app = io_t->app; + iod->use_handover = pdata->use_handover; + atomic_set(&iod->opened, 0); + + /* link between io device and modem control */ + iod->mc = modemctl; + + if (iod->format == IPC_FMT) + modemctl->iod = iod; + + if (iod->format == IPC_BOOT) { + modemctl->bootd = iod; + mif_err("BOOT device = %s\n", iod->name); + } + + /* link between io device and modem shared */ + iod->msd = msd; + + /* add iod to rb_tree */ + if (iod->format != IPC_RAW) + insert_iod_with_format(msd, iod->format, iod); + + if (exynos_is_not_reserved_channel(iod->id)) + insert_iod_with_channel(msd, iod->id, iod); + + /* register misc device or net device */ + ret = exynos_init_io_device(iod); + if (ret) { + kfree(iod); + mif_err("exynos_init_io_device fail (%d)\n", ret); + return NULL; + } + + mif_info("%s created\n", iod->name); + return iod; +} + +static int attach_devices(struct io_device *iod, enum modem_link tx_link) +{ + struct modem_shared *msd = iod->msd; + struct link_device *ld; + + /* find link type for this io device */ + list_for_each_entry(ld, &msd->link_dev_list, list) { + if (IS_CONNECTED(iod, ld)) { + /* The count 1 bits of iod->link_types is count + * of link devices of this iod. + * If use one link device, + * or, 2+ link devices and this link is tx_link, + * set iod's link device with ld + */ + + if ((countbits(iod->link_types) <= 1) || + (tx_link == ld->link_type)) { + mif_debug("set %s->%s\n", iod->name, ld->name); + set_current_link(iod, ld); + } + } + } + + /* if use rx dynamic switch, set tx_link at modem_io_t of + * board-*-modems.c + */ + if (!get_current_link(iod)) { + mif_err("%s->link == NULL\n", iod->name); + BUG(); + } + + switch (iod->format) { + case IPC_FMT: + wake_lock_init(&iod->wakelock, WAKE_LOCK_SUSPEND, iod->name); + iod->waketime = FMT_WAKE_TIME; + break; + + case IPC_RAW: + wake_lock_init(&iod->wakelock, WAKE_LOCK_SUSPEND, iod->name); + iod->waketime = RAW_WAKE_TIME; + break; + + case IPC_RFS: + wake_lock_init(&iod->wakelock, WAKE_LOCK_SUSPEND, iod->name); + iod->waketime = RAW_WAKE_TIME; + break; + + case IPC_MULTI_RAW: + wake_lock_init(&iod->wakelock, WAKE_LOCK_SUSPEND, iod->name); + iod->waketime = RAW_WAKE_TIME; + break; + + case IPC_BOOT: + wake_lock_init(&iod->wakelock, WAKE_LOCK_SUSPEND, iod->name); + iod->waketime = RAW_WAKE_TIME; + break; + + default: + break; + } + + return 0; +} + +#ifdef CONFIG_OF +static int parse_dt_common_pdata(struct device_node *np, + struct modem_data *pdata) +{ + mif_dt_read_string(np, "mif,name", pdata->name); + mif_dt_read_bool(np, "mif,use_handover", pdata->use_handover); + mif_dt_read_u32(np, "mif,link_types", pdata->link_types); + mif_dt_read_string(np, "mif,link_name", pdata->link_name); + mif_dt_read_u32(np, "mif,num_iodevs", pdata->num_iodevs); + + mif_dt_read_u32(np, "shmem,dump_offset", pdata->dump_offset); + return 0; +} + +static int parse_dt_mbox_pdata(struct device *dev, struct device_node *np, + struct modem_data *pdata) +{ + struct modem_mbox *mbox = pdata->mbx; + + mbox = devm_kzalloc(dev, sizeof(struct modem_mbox), GFP_KERNEL); + if (!mbox) { + mif_err("mbox: failed to alloc memory\n"); + return -ENOMEM; + } + pdata->mbx = mbox; + +#ifdef CONFIG_PCI_EXYNOS + mif_dt_read_u32 (np, "mif,irq_cp2ap_pcie_l1ss_disable", + mbox->irq_cp2ap_pcie_l1ss_disable); + mif_dt_read_u32 (np, "mbx_cp2ap_pcie_l1ss_disable", + mbox->mbx_cp2ap_pcie_l1ss_disable); +#endif + + mif_dt_read_u32 (np, "mbx_ap2cp_msg", mbox->mbx_ap2cp_msg); + mif_dt_read_u32 (np, "mbx_cp2ap_msg", mbox->mbx_cp2ap_msg); + mif_dt_read_u32 (np, "mbx_ap2cp_united_status", + mbox->mbx_ap2cp_status); + mif_dt_read_u32 (np, "mbx_cp2ap_united_status", + mbox->mbx_cp2ap_status); + + mif_dt_read_u32 (np, "mif,int_ap2cp_msg", mbox->int_ap2cp_msg); + mif_dt_read_u32 (np, "mif,int_ap2cp_wakeup", mbox->int_ap2cp_wakeup); + mif_dt_read_u32 (np, "mif,int_ap2cp_status", mbox->int_ap2cp_status); + mif_dt_read_u32 (np, "mif,int_ap2cp_active", mbox->int_ap2cp_active); + + mif_dt_read_u32 (np, "mif,irq_cp2ap_msg", mbox->irq_cp2ap_msg); + mif_dt_read_u32 (np, "mif,irq_cp2ap_status", mbox->irq_cp2ap_status); + mif_dt_read_u32 (np, "mif,irq_cp2ap_active", mbox->irq_cp2ap_active); + mif_dt_read_u32 (np, "mif,irq_cp2ap_wake_lock", + mbox->irq_cp2ap_wake_lock); + + /* Value for Performance Request */ + mif_dt_read_u32 (np, "mbx_cp2ap_dvfsreq", mbox->mbx_cp2ap_perf_req); + mif_dt_read_u32 (np, "mbx_cp2ap_dvfsreq_cpu", + mbox->mbx_cp2ap_perf_req_cpu); + mif_dt_read_u32 (np, "mbx_cp2ap_dvfsreq_mif", + mbox->mbx_cp2ap_perf_req_mif); + mif_dt_read_u32 (np, "mbx_cp2ap_dvfsreq_int", + mbox->mbx_cp2ap_perf_req_int); + + mif_dt_read_u32 (np, "mif,irq_cp2ap_perf_req_cpu", + mbox->irq_cp2ap_perf_req_cpu); + mif_dt_read_u32 (np, "mif,irq_cp2ap_perf_req_mif", + mbox->irq_cp2ap_perf_req_mif); + mif_dt_read_u32 (np, "mif,irq_cp2ap_perf_req_int", + mbox->irq_cp2ap_perf_req_int); + + /* Status Bit Info */ + mif_dt_read_u32 (np, "sbi_lte_active_mask", mbox->sbi_lte_active_mask); + mif_dt_read_u32 (np, "sbi_lte_active_pos", mbox->sbi_lte_active_pos); + mif_dt_read_u32 (np, "sbi_wake_lock_mask", mbox->sbi_wake_lock_mask); + mif_dt_read_u32 (np, "sbi_wake_lock_pos", mbox->sbi_wake_lock_pos); + mif_dt_read_u32 (np, "sbi_cp_status_mask", mbox->sbi_cp_status_mask); + mif_dt_read_u32 (np, "sbi_cp_status_pos", mbox->sbi_cp_status_pos); + mif_dt_read_u32 (np, "sbi_pda_active_mask", mbox->sbi_pda_active_mask); + mif_dt_read_u32 (np, "sbi_pda_active_pos", mbox->sbi_pda_active_pos); + mif_dt_read_u32 (np, "sbi_ap_status_mask", mbox->sbi_ap_status_mask); + mif_dt_read_u32 (np, "sbi_ap_status_pos", mbox->sbi_ap_status_pos); + + return 0; +} + +static int parse_dt_iodevs_pdata(struct device *dev, struct device_node *np, + struct modem_data *pdata) +{ + struct device_node *child = NULL; + struct modem_io_t *iod = NULL; + size_t size = sizeof(struct modem_io_t) * pdata->num_iodevs; + int i = 0; + + pdata->iodevs = devm_kzalloc(dev, size, GFP_KERNEL); + if (!pdata->iodevs) { + mif_err("iodevs: failed to alloc memory\n"); + return -ENOMEM; + } + + for_each_child_of_node(np, child) { + iod = &pdata->iodevs[i]; + mif_dt_read_string(child, "iod,name", iod->name); + mif_dt_read_u32(child, "iod,id", iod->id); + mif_dt_read_enum(child, "iod,format", iod->format); + mif_dt_read_enum(child, "iod,io_type", iod->io_type); + mif_dt_read_u32(child, "iod,links", iod->links); + if (countbits(iod->links) > 1) + mif_dt_read_enum(child, "iod,tx_link", iod->tx_link); + mif_dt_read_u32(child, "iod,attrs", iod->attrs); + mif_dt_read_string(child, "iod,app", iod->app); + + i++; + } + return 0; +} + +static struct modem_data *modem_if_parse_dt_pdata(struct device *dev) +{ + struct modem_data *pdata; + struct device_node *iodevs = NULL; + + pdata = devm_kzalloc(dev, sizeof(struct modem_data), GFP_KERNEL); + + if (!pdata) { + mif_err("modem_data: alloc fail\n"); + return ERR_PTR(-ENOMEM); + } + + if (parse_dt_common_pdata(dev->of_node, pdata)) { + mif_err("DT error: failed to parse common\n"); + goto error; + } + + if (parse_dt_mbox_pdata(dev, dev->of_node, pdata)) { + mif_err("DT error: failed to parse mbox\n"); + goto error; + } + + iodevs = of_get_child_by_name(dev->of_node, "iodevs"); + if (!iodevs) { + mif_err("DT error: failed to get child node\n"); + goto error; + } + + if (parse_dt_iodevs_pdata(dev, iodevs, pdata)) { + mif_err("DT error: failed to parse iodevs\n"); + goto error; + } + + dev->platform_data = pdata; + mif_info("DT parse complete!\n"); + return pdata; + +error: + if (pdata) { + if (pdata->iodevs) + devm_kfree(dev, pdata->iodevs); + devm_kfree(dev, pdata); + } + + return ERR_PTR(-EINVAL); +} + +static const struct of_device_id sec_modem_match[] = { + { .compatible = "sec_modem,modem_pdata", }, + {}, +}; +MODULE_DEVICE_TABLE(of, sec_modem_match); +#else +static struct modem_data *modem_if_parse_dt_pdata(struct device *dev) +{ + return ERR_PTR(-ENODEV); +} +#endif + +static void modem_get_shmem_base(struct modem_data *pdata) +{ + pdata->shmem_base = shm_get_phys_base(); + pdata->ipcmem_offset = shm_get_ipc_rgn_offset(); + pdata->ipc_size = shm_get_ipc_rgn_size(); + mif_err("shmem_base: 0x%x ipcmem_offset: 0x%x, ipc_size: 0x%x\n", + pdata->shmem_base, pdata->ipcmem_offset, pdata->ipc_size); +} + +struct io_device *iod_test; + +static ssize_t do_cp_crash_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct modem_ctl *mc = dev_get_drvdata(dev); + unsigned int val = 0; + + if (kstrtouint(buf, 10, &val)) + return -EINVAL; + + if (mc->bootd) + mc->bootd->modem_state_changed(mc->bootd, val); + return count; +} + +static ssize_t modem_state_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct modem_ctl *mc = dev_get_drvdata(dev); + return sprintf(buf, "%s\n", cp_state_str[mc->phone_state]); +} +static DEVICE_ATTR_WO(do_cp_crash); +static DEVICE_ATTR_RO(modem_state); + +static struct attribute *modem_attrs[] = { + &dev_attr_do_cp_crash.attr, + &dev_attr_modem_state.attr, + NULL, +}; +ATTRIBUTE_GROUPS(modem); + +static int modem_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct modem_data *pdata = dev->platform_data; + struct modem_shared *msd; + struct modem_ctl *modemctl; + struct io_device **iod; + struct link_device *ld; + unsigned int size; + int i; + + mif_err("%s: +++\n", pdev->name); + + if (dev->of_node) { + pdata = modem_if_parse_dt_pdata(dev); + if (IS_ERR(pdata)) { + mif_err("MIF DT pasrse error!\n"); + return PTR_ERR(pdata); + } + } + modem_get_shmem_base(pdata); + + msd = create_modem_shared_data(pdev); + if (!msd) { + mif_err("%s: msd == NULL\n", pdata->name); + return -ENOMEM; + } + + modemctl = create_modemctl_device(pdev, msd); + if (!modemctl) { + mif_err("%s: modemctl == NULL\n", pdata->name); + kfree(msd); + return -ENOMEM; + } + + /* create link device */ + /* support multi-link device */ + for (i = 0; i < LINKDEV_MAX; i++) { + /* find matching link type */ + if (pdata->link_types & LINKTYPE(i)) { + ld = call_link_init_func(pdev, i); + if (!ld) + goto free_mc; + + mif_err("%s: %s link created\n", pdata->name, ld->name); + ld->link_type = i; + ld->mc = modemctl; + ld->msd = msd; + list_add(&ld->list, &msd->link_dev_list); + } + } + + /* create io deivces and connect to modemctl device */ + size = sizeof(struct io_device *) * pdata->num_iodevs; + iod = (struct io_device **)devm_kzalloc(dev, size, GFP_KERNEL); + for (i = 0; i < pdata->num_iodevs; i++) { + iod[i] = create_io_device(pdev, &pdata->iodevs[i], msd, + modemctl, pdata); + if (!iod[i]) { + mif_err("%s: iod[%d] == NULL\n", pdata->name, i); + goto free_iod; + } + + attach_devices(iod[i], pdata->iodevs[i].tx_link); + } + + platform_set_drvdata(pdev, modemctl); + + if (sysfs_create_groups(&dev->kobj, modem_groups)) + mif_err("failed to create modem groups node\n"); + + mif_err("%s: ---\n", pdata->name); + + return 0; + +free_iod: + for (i = 0; i < pdata->num_iodevs; i++) + kfree(iod[i]); + +free_mc: + kfree(modemctl); + kfree(msd); + + mif_err("%s: xxx\n", pdata->name); + + return -ENOMEM; +} + +static void modem_shutdown(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct modem_ctl *mc = dev_get_drvdata(dev); + struct utc_time utc; + + mc->phone_state = STATE_OFFLINE; + + get_utc_time(&utc); + mif_info("%s: at [%02d:%02d:%02d.%03d]\n", + mc->name, utc.hour, utc.min, utc.sec, utc.msec); +} + +#ifdef CONFIG_OF +static int modem_suspend(struct device *pdev) +{ + struct modem_ctl *mc = dev_get_drvdata(pdev); + + if (mc->ops.suspend_modem_ctrl != NULL) { + mif_err("%s: pd_active:0\n", mc->name); + mc->ops.suspend_modem_ctrl(mc); + } + + return 0; +} + +static int modem_resume(struct device *pdev) +{ + struct modem_ctl *mc = dev_get_drvdata(pdev); + + if (mc->ops.suspend_modem_ctrl != NULL) { + mif_err("%s: pd_active:1\n", mc->name); + mc->ops.resume_modem_ctrl(mc); + } + + return 0; +} +#else +#define modem_suspend NULL +#define modem_resume NULL +#endif + +static const struct dev_pm_ops modem_pm_ops = { + .suspend = modem_suspend, + .resume = modem_resume, +}; + +static struct platform_driver modem_driver = { + .probe = modem_probe, + .shutdown = modem_shutdown, + .driver = { + .name = "mif_exynos", + .owner = THIS_MODULE, + .pm = &modem_pm_ops, + +#ifdef CONFIG_OF + .of_match_table = of_match_ptr(sec_modem_match), +#endif + }, +}; + +module_platform_driver(modem_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Samsung Modem Interface Driver"); diff --git a/drivers/misc/modem_if/modem_modemctl_device_sh333ap.c b/drivers/misc/modem_if/modem_modemctl_device_sh333ap.c new file mode 100755 index 000000000000..3cb374206e51 --- /dev/null +++ b/drivers/misc/modem_if/modem_modemctl_device_sh333ap.c @@ -0,0 +1,789 @@ +/* + * Copyright (C) 2010 Samsung Electronics. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef CONFIG_PCI_EXYNOS +#include +#endif +#include +#include "modem_prj.h" +#include "modem_utils.h" +#include "modem_link_device_shmem.h" + +#define MIF_INIT_TIMEOUT (300 * HZ) +#define MBREG_MAX_NUM 64 + +static struct modem_ctl *g_mc; + +static ssize_t modem_ctrl_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int ret = 0; + struct modem_ctl *mc = dev_get_drvdata(dev); + u32 reg; + + ret = snprintf(buf, PAGE_SIZE, "Check Mailbox Registers\n"); + + reg = mbox_extract_value(MCU_CP, mc->mbx_ap_status, + mc->sbi_ap_status_mask, mc->sbi_ap_status_pos); + ret += snprintf(&buf[ret], PAGE_SIZE - ret, "AP2CP_STATUS : %d\n", reg); + + reg = mbox_extract_value(MCU_CP, mc->mbx_cp_status, + mc->sbi_cp_status_mask, mc->sbi_cp_status_pos); + ret += snprintf(&buf[ret], PAGE_SIZE - ret, "CP2AP_STATUS : %d\n", reg); + + reg = mbox_extract_value(MCU_CP, mc->mbx_ap_status, + mc->sbi_pda_active_mask, mc->sbi_pda_active_pos); + ret += snprintf(&buf[ret], PAGE_SIZE - ret, "PDA_ACTIVE : %d\n", reg); + + reg = mbox_extract_value(MCU_CP, mc->mbx_cp_status, + mc->sbi_lte_active_mask, mc->sbi_lte_active_pos); + ret += snprintf(&buf[ret], PAGE_SIZE - ret, "PHONE_ACTIVE : %d\n", reg); + + ret += snprintf(&buf[ret], PAGE_SIZE - ret, "CP2AP_DVFS_REQ : %d\n", + mbox_get_value(MCU_CP, mc->mbx_perf_req)); + + return ret; +} + +static ssize_t modem_ctrl_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct modem_ctl *mc = dev_get_drvdata(dev); + long ops_num; + int ret; + + ret = kstrtol(buf, 10, &ops_num); + if (ret == 0) + return count; + + switch (ops_num) { + case 1: + mif_info("Reset CP (Stop)!!!\n"); + if (cal_cp_status() > 0) { + mif_err("CP aleady Power on, try reset\n"); + mbox_set_interrupt(MCU_CP, mc->int_cp_wakeup); + cal_cp_enable_dump_pc_no_pg(); + cal_cp_reset_assert(); + mbox_sw_reset(MCU_CP); + } + break; + + case 2: + mif_info("Reset CP - Release (Start)!!!\n"); + cal_cp_reset_release(); + break; + + case 3: + mif_info("force_crash_exit!!!\n"); + mc->ops.modem_force_crash_exit(mc); + break; + case 4: + mif_info("Modem Power OFF!!! - TBD\n"); + break; + + default: + mif_info("Wrong operation number\n"); + mif_info("1. Modem Reset - (Stop)\n"); + mif_info("2. Modem Reset - (Start)\n"); + mif_info("3. Modem force crash\n"); + mif_info("4. Modem Power OFF\n"); + } + + return count; +} + +static DEVICE_ATTR(modem_ctrl, 0644, modem_ctrl_show, modem_ctrl_store); + +static irqreturn_t cp_wdt_handler(int irq, void *arg) +{ + struct modem_ctl *mc = (struct modem_ctl *)arg; + struct link_device *ld = get_current_link(mc->iod); + int new_state; + + mif_disable_irq(&mc->irq_cp_wdt); + mif_err("%s: ERR! CP_WDOG occurred\n", mc->name); + + if (cp_online(mc)) + modem_notify_event(MODEM_EVENT_WATCHDOG); + + cal_cp_reset_req_clear(); + + new_state = STATE_CRASH_EXIT; + ld->mode = LINK_MODE_ULOAD; + + mif_err("new_state = %s\n", get_cp_state_str(new_state)); + mc->bootd->modem_state_changed(mc->bootd, new_state); + mc->iod->modem_state_changed(mc->iod, new_state); + + return IRQ_HANDLED; +} + +static void cp_active_handler(void *arg) +{ + struct modem_ctl *mc = (struct modem_ctl *)arg; + struct link_device *ld = get_current_link(mc->iod); + int cp_on = cal_cp_status(); + int cp_active = mbox_extract_value(MCU_CP, mc->mbx_cp_status, + mc->sbi_lte_active_mask, mc->sbi_lte_active_pos); + int old_state = mc->phone_state; + int new_state = mc->phone_state; + + mif_err("old_state:%s cp_on:%d cp_active:%d\n", + get_cp_state_str(old_state), cp_on, cp_active); + + if (!cp_active) { + if (cp_on) { + new_state = STATE_OFFLINE; + ld->mode = LINK_MODE_OFFLINE; + complete_all(&mc->off_cmpl); + } else { + mif_err("don't care!!!\n"); + } + } + + if (old_state != new_state) { + /* corner case */ + if (old_state == STATE_ONLINE) + modem_notify_event(MODEM_EVENT_EXIT); + mif_err("new_state = %s\n", get_cp_state_str(new_state)); + mc->bootd->modem_state_changed(mc->bootd, new_state); + mc->iod->modem_state_changed(mc->iod, new_state); + } +} + +static void dvfs_req_handler(void *arg) +{ + struct modem_ctl *mc = (struct modem_ctl *)arg; + mif_info("CP reqest to change DVFS level!!!!\n"); + + schedule_work(&mc->pm_qos_work); +} + +static void dvfs_req_handler_cpu(void *arg) +{ + struct modem_ctl *mc = (struct modem_ctl *)arg; + mif_info("CP reqest to change DVFS level!!!!\n"); + + schedule_work(&mc->pm_qos_work_cpu); +} + +static void dvfs_req_handler_mif(void *arg) +{ + struct modem_ctl *mc = (struct modem_ctl *)arg; + mif_info("CP reqest to change DVFS level!!!!\n"); + + schedule_work(&mc->pm_qos_work_mif); +} + +static void dvfs_req_handler_int(void *arg) +{ + struct modem_ctl *mc = (struct modem_ctl *)arg; + mif_info("CP reqest to change DVFS level!!!!\n"); + + schedule_work(&mc->pm_qos_work_int); +} + +#ifdef CONFIG_PCI_EXYNOS +static void pcie_l1ss_disable_handler(void *arg) +{ + struct modem_mbox *mbx = (struct modem_mbox *)arg; + unsigned int req; + + req = mbox_get_value(MCU_CP, mbx->mbx_cp2ap_pcie_l1ss_disable); + + if (req == 1) { + exynos_pcie_l1ss_ctrl(0, PCIE_L1SS_CTRL_MODEM_IF); + mif_err("cp requests pcie l1ss disable\n"); + } else if (req == 0) { + exynos_pcie_l1ss_ctrl(1, PCIE_L1SS_CTRL_MODEM_IF); + mif_err("cp requests pcie l1ss enable\n"); + } else { + mif_err("unsupported request: pcie_l1ss_disable\n"); + } +} +#else +static void pcie_l1ss_disable_handler(void *arg) +{ + mif_err("CONFIG_PCI_EXYNOS is not set!\n"); +} +#endif + +static int get_hw_rev(struct device_node *np) +{ + int value, cnt, gpio_cnt; + unsigned int gpio_hw_rev, hw_rev = 0; + + gpio_cnt = of_gpio_count(np); + if (gpio_cnt < 0) { + mif_err("failed to get gpio_count from DT(%d)\n", gpio_cnt); + return gpio_cnt; + } + + for (cnt = 0; cnt < gpio_cnt; cnt++) { + gpio_hw_rev = of_get_gpio(np, cnt); + if (!gpio_is_valid(gpio_hw_rev)) { + mif_err("gpio_hw_rev%d: Invalied gpio\n", cnt); + return -EINVAL; + } + + value = gpio_get_value(gpio_hw_rev); + hw_rev |= (value & 0x1) << cnt; + } + + return hw_rev; +} + +#ifdef CONFIG_SIM_DETECT +static unsigned int get_sim_socket_detection(struct device_node *np) +{ + unsigned int gpio_ds_det; + + gpio_ds_det = of_get_named_gpio(np, "mif,gpio_ds_det", 0); + if (!gpio_is_valid(gpio_ds_det)) { + mif_err("gpio_ds_det: Invalid gpio\n"); + return 0; + } + + return gpio_get_value(gpio_ds_det); +} +#else +static unsigned int get_sim_socket_detection(struct device_node *np) +{ + unsigned int sim_socket_num = 1; + mif_dt_read_u32(np, "mif,sim_socket_num", sim_socket_num); + mif_err("mif,sim_socket_num : %d", sim_socket_num); + + return sim_socket_num - 1; +} +#endif + +static int sh333ap_on(struct modem_ctl *mc) +{ + struct platform_device *pdev = to_platform_device(mc->dev); + struct device_node *np = pdev->dev.of_node; + struct link_device *ld = get_current_link(mc->iod); + int cp_active = mbox_extract_value(MCU_CP, mc->mbx_cp_status, + mc->sbi_lte_active_mask, mc->sbi_lte_active_pos); + int cp_status = mbox_extract_value(MCU_CP, mc->mbx_cp_status, + mc->sbi_cp_status_mask, mc->sbi_cp_status_pos); + int ret; + int i; + unsigned long int flags; + int sys_rev, ds_det; + unsigned int mbx_ap_status; + unsigned int sbi_ds_det_mask, sbi_ds_det_pos; + unsigned int sbi_sys_rev_mask, sbi_sys_rev_pos; + + mif_info("+++\n"); + mif_info("cp_active:%d cp_status:%d\n", cp_active, cp_status); + + if (!wake_lock_active(&mc->mc_wake_lock)) + wake_lock(&mc->mc_wake_lock); + + mc->phone_state = STATE_OFFLINE; + ld->mode = LINK_MODE_OFFLINE; + + for (i = 0; i < MBREG_MAX_NUM; i++) + mbox_set_value(MCU_CP, i, 0); + + mif_dt_read_u32(np, "mbx_ap2cp_united_status", mbx_ap_status); + mif_dt_read_u32(np, "sbi_sys_rev_mask", sbi_sys_rev_mask); + mif_dt_read_u32(np, "sbi_sys_rev_pos", sbi_sys_rev_pos); + mif_dt_read_u32(np, "sbi_ds_det_mask", sbi_ds_det_mask); + mif_dt_read_u32(np, "sbi_ds_det_pos", sbi_ds_det_pos); + + spin_lock_irqsave(&mc->ap_status_lock, flags); + + sys_rev = get_hw_rev(np); + if (sys_rev >= 0) { + mbox_update_value(MCU_CP, mbx_ap_status, sys_rev, + sbi_sys_rev_mask, sbi_sys_rev_pos); + } else { + mif_err("get_hw_rev() ERROR\n"); + } + + ds_det = get_sim_socket_detection(np); + if (ds_det >= 0) { + mbox_update_value(MCU_CP, mbx_ap_status, ds_det, + sbi_ds_det_mask, sbi_ds_det_pos); + } else { + mif_err("get_sim_socket_detection() ERROR\n"); + } + + mif_err("System Revision %d\n", sys_rev); + mif_err("SIM Socket Detection %d\n", ds_det); + + spin_unlock_irqrestore(&mc->ap_status_lock, flags); + + spin_lock_irqsave(&mc->ap_status_lock, flags); + mbox_update_value(MCU_CP, mc->mbx_ap_status, 1, + mc->sbi_ap_status_mask, mc->sbi_ap_status_pos); + spin_unlock_irqrestore(&mc->ap_status_lock, flags); + + if (mc->ap2cp_cfg_ioaddr) { + mif_info("Before setting AP2CP_CFG:0x%08x\n", + __raw_readl(mc->ap2cp_cfg_ioaddr)); + __raw_writel(1, mc->ap2cp_cfg_ioaddr); + ret = __raw_readl(mc->ap2cp_cfg_ioaddr); + if (ret != 1) { + mif_err("AP2CP_CFG setting is not correct:%d\n", ret); + return -1; + } + mif_info("AP2CP_CFG is ok:0x%08x\n", ret); + } else { + ret = cal_cp_status(); + if (ret) { + mif_err("CP aleady Init, Just reset release!\n"); + cal_cp_reset_release(); + } else { + mif_err("CP first Init!\n"); + cal_cp_init(); + } + } + + mif_info("---\n"); + return 0; +} + +static int sh333ap_off(struct modem_ctl *mc) +{ + struct link_device *ld = get_current_link(mc->iod); + int cp_on = cal_cp_status(); + unsigned long timeout = msecs_to_jiffies(3000); + unsigned long remain; + mif_info("+++\n"); + + if (mc->phone_state == STATE_OFFLINE || cp_on == 0) + goto exit; + + reinit_completion(&mc->off_cmpl); + remain = wait_for_completion_timeout(&mc->off_cmpl, timeout); + if (remain == 0) { + mif_err("T-I-M-E-O-U-T\n"); + mc->phone_state = STATE_OFFLINE; + ld->mode = LINK_MODE_OFFLINE; + mc->bootd->modem_state_changed(mc->iod, STATE_OFFLINE); + mc->iod->modem_state_changed(mc->iod, STATE_OFFLINE); + } + +exit: + if (cal_cp_status() > 0) { + mif_err("CP aleady Power on, try reset\n"); + mbox_set_interrupt(MCU_CP, mc->int_cp_wakeup); + cal_cp_enable_dump_pc_no_pg(); + cal_cp_reset_assert(); + cal_cp_reset_release(); + mbox_sw_reset(MCU_CP); + } + + mif_info("---\n"); + return 0; +} + +static int sh333ap_reset(struct modem_ctl *mc) +{ + struct link_device *ld = get_current_link(mc->iod); + + if (*(unsigned int *)(mc->mdm_data->ipc_base + SHM_CPINFO_DEBUG) + == 0xDEB) + return 0; + + mif_err("+++\n"); + + if (cp_online(mc)) + modem_notify_event(MODEM_EVENT_RESET); + + mc->phone_state = STATE_OFFLINE; + ld->mode = LINK_MODE_OFFLINE; + + if (cal_cp_status() > 0) { + mif_err("CP aleady Power on, try reset\n"); + mbox_set_interrupt(MCU_CP, mc->int_cp_wakeup); + cal_cp_enable_dump_pc_no_pg(); + cal_cp_reset_assert(); + cal_cp_reset_release(); + mbox_sw_reset(MCU_CP); + } + + usleep_range(10000, 11000); + + mif_err("---\n"); + return 0; +} + +static int sh333ap_boot_on(struct modem_ctl *mc) +{ + struct link_device *ld = get_current_link(mc->bootd); + int cnt = 100; + unsigned long int flags; + mif_info("+++\n"); + + ld->mode = LINK_MODE_BOOT; + + mc->bootd->modem_state_changed(mc->bootd, STATE_BOOTING); + mc->iod->modem_state_changed(mc->iod, STATE_BOOTING); + + while (mbox_extract_value(MCU_CP, mc->mbx_cp_status, + mc->sbi_cp_status_mask, mc->sbi_cp_status_pos) == 0) { + if (--cnt > 0) + usleep_range(10000, 20000); + else { + mif_err("mbx_cp_status == 0, return -EACCES !!!!!!\n"); + return -EACCES; + } + } + + mif_err("mbx_cp_status == 1 ==> CP booting in Progress...\n"); + + mif_disable_irq(&mc->irq_cp_wdt); + + spin_lock_irqsave(&mc->ap_status_lock, flags); + mbox_update_value(MCU_CP, mc->mbx_ap_status, 1, + mc->sbi_ap_status_mask, mc->sbi_ap_status_pos); + spin_unlock_irqrestore(&mc->ap_status_lock, flags); + + mif_info("---\n"); + return 0; +} + +static int sh333ap_boot_off(struct modem_ctl *mc) +{ + struct link_device *ld = get_current_link(mc->bootd); + unsigned long remain; + int err = 0; + mif_info("+++\n"); + + ld->mode = LINK_MODE_IPC; + + cal_cp_disable_dump_pc_no_pg(); + + reinit_completion(&ld->init_cmpl); + remain = wait_for_completion_timeout(&ld->init_cmpl, MIF_INIT_TIMEOUT); + if (remain == 0) { + mif_err("T-I-M-E-O-U-T\n"); + err = -EAGAIN; + goto exit; + } + + mif_err("Got INT_CMD_PHONE_START from CP. CP booting completion!!!\n"); + + mif_enable_irq(&mc->irq_cp_wdt); + + mif_info("---\n"); + +exit: + return err; +} + +static int sh333ap_boot_done(struct modem_ctl *mc) +{ + mif_info("+++\n"); + + if (wake_lock_active(&mc->mc_wake_lock)) + wake_unlock(&mc->mc_wake_lock); + + mif_info("---\n"); + return 0; +} + +static int sh333ap_force_crash_exit(struct modem_ctl *mc) +{ + struct link_device *ld = get_current_link(mc->bootd); + mif_err("+++\n"); + + /* Make DUMP start */ + ld->force_dump(ld, mc->bootd); + + mif_err("---\n"); + return 0; +} + +int modem_force_crash_exit_ext(void) +{ + if (!g_mc) { + mif_err("g_mc is null\n"); + return -1; + } + + mif_info("Make forced crash exit\n"); + sh333ap_force_crash_exit(g_mc); + + return 0; +} +EXPORT_SYMBOL(modem_force_crash_exit_ext); + +static int sh333ap_dump_reset(struct modem_ctl *mc) +{ + mif_err("+++\n"); + + if (!wake_lock_active(&mc->mc_wake_lock)) + wake_lock(&mc->mc_wake_lock); + + if (cal_cp_status() > 0) { + mif_err("CP aleady Power on, try reset\n"); + mbox_set_interrupt(MCU_CP, mc->int_cp_wakeup); + cal_cp_enable_dump_pc_no_pg(); + cal_cp_reset_assert(); + cal_cp_reset_release(); + mbox_sw_reset(MCU_CP); + } + + mif_err("---\n"); + return 0; +} + +static int sh333ap_dump_start(struct modem_ctl *mc) +{ + int err, ret; + struct link_device *ld = get_current_link(mc->bootd); + unsigned long int flags; + mif_err("+++\n"); + + if (!ld->dump_start) { + mif_err("ERR! %s->dump_start not exist\n", ld->name); + return -EFAULT; + } + + err = ld->dump_start(ld, mc->bootd); + if (err) + return err; + + if (mc->ap2cp_cfg_ioaddr) { + mif_info("Before setting AP2CP_CFG:0x%08x\n", + __raw_readl(mc->ap2cp_cfg_ioaddr)); + __raw_writel(1, mc->ap2cp_cfg_ioaddr); + ret = __raw_readl(mc->ap2cp_cfg_ioaddr); + if (ret != 1) { + mif_err("AP2CP_CFG setting is not correct:%d\n", ret); + return -1; + } + mif_info("AP2CP_CFG is ok:0x%08x\n", ret); + } else { + cal_cp_reset_release(); + } + + spin_lock_irqsave(&mc->ap_status_lock, flags); + mbox_update_value(MCU_CP, mc->mbx_ap_status, 1, + mc->sbi_ap_status_mask, mc->sbi_ap_status_pos); + spin_unlock_irqrestore(&mc->ap_status_lock, flags); + + mif_err("---\n"); + return err; +} + +static int sh333ap_get_meminfo(struct modem_ctl *mc, unsigned long arg) +{ + struct meminfo mem_info; + struct modem_data *modem = mc->mdm_data; + + mem_info.base_addr = modem->shmem_base; + mem_info.size = modem->ipcmem_offset; + + if (copy_to_user((void __user *)arg, &mem_info, sizeof(struct meminfo))) + return -EFAULT; + + return 0; +} + +static int sh333ap_suspend_modemctl(struct modem_ctl *mc) +{ + unsigned long int flags; + + spin_lock_irqsave(&mc->ap_status_lock, flags); + mbox_update_value(MCU_CP, mc->mbx_ap_status, 0, + mc->sbi_pda_active_mask, mc->sbi_pda_active_pos); + spin_unlock_irqrestore(&mc->ap_status_lock, flags); + + mbox_set_interrupt(MCU_CP, mc->int_pda_active); + + return 0; +} + +static int sh333ap_resume_modemctl(struct modem_ctl *mc) +{ + unsigned long int flags; + + spin_lock_irqsave(&mc->ap_status_lock, flags); + mbox_update_value(MCU_CP, mc->mbx_ap_status, 1, + mc->sbi_pda_active_mask, mc->sbi_pda_active_pos); + spin_unlock_irqrestore(&mc->ap_status_lock, flags); + + mbox_set_interrupt(MCU_CP, mc->int_pda_active); + + return 0; +} + +static void sh333ap_get_ops(struct modem_ctl *mc) +{ + mc->ops.modem_on = sh333ap_on; + mc->ops.modem_off = sh333ap_off; + mc->ops.modem_reset = sh333ap_reset; + mc->ops.modem_boot_on = sh333ap_boot_on; + mc->ops.modem_boot_off = sh333ap_boot_off; + mc->ops.modem_boot_done = sh333ap_boot_done; + mc->ops.modem_force_crash_exit = sh333ap_force_crash_exit; + mc->ops.modem_dump_reset = sh333ap_dump_reset; + mc->ops.modem_dump_start = sh333ap_dump_start; + mc->ops.modem_get_meminfo = sh333ap_get_meminfo; + mc->ops.suspend_modem_ctrl = sh333ap_suspend_modemctl; + mc->ops.resume_modem_ctrl = sh333ap_resume_modemctl; +} + +static void sh333ap_get_pdata(struct modem_ctl *mc, struct modem_data *modem) +{ + struct modem_mbox *mbx = modem->mbx; + + mc->int_pda_active = mbx->int_ap2cp_active; + mc->int_cp_wakeup = mbx->int_ap2cp_wakeup; + + mc->irq_phone_active = mbx->irq_cp2ap_active; + + mc->mbx_ap_status = mbx->mbx_ap2cp_status; + mc->mbx_cp_status = mbx->mbx_cp2ap_status; + + mc->mbx_perf_req = mbx->mbx_cp2ap_perf_req; + mc->irq_perf_req = mbx->irq_cp2ap_perf_req; + mc->mbx_perf_req_cpu = mbx->mbx_cp2ap_perf_req_cpu; + mc->irq_perf_req_cpu = mbx->irq_cp2ap_perf_req_cpu; + mc->mbx_perf_req_mif = mbx->mbx_cp2ap_perf_req_mif; + mc->irq_perf_req_mif = mbx->irq_cp2ap_perf_req_mif; + mc->mbx_perf_req_int = mbx->mbx_cp2ap_perf_req_int; + mc->irq_perf_req_int = mbx->irq_cp2ap_perf_req_int; + mc->irq_cp_wakelock = mbx->irq_cp2ap_wake_lock; + + mc->mbx_sys_rev = mbx->mbx_ap2cp_sys_rev; + mc->mbx_pmic_rev = mbx->mbx_ap2cp_pmic_rev; + mc->mbx_pkg_id = mbx->mbx_ap2cp_pkg_id; + + mc->hw_revision = modem->hw_revision; + mc->package_id = modem->package_id; + mc->lock_value = modem->lock_value; + + mc->int_uart_noti = mbx->int_ap2cp_uart_noti; + + mc->sbi_wake_lock_mask = mbx->sbi_wake_lock_mask; + mc->sbi_wake_lock_pos = mbx->sbi_wake_lock_pos; + mc->sbi_lte_active_mask = mbx->sbi_lte_active_mask; + mc->sbi_lte_active_pos = mbx->sbi_lte_active_pos; + mc->sbi_cp_status_mask = mbx->sbi_cp_status_mask; + mc->sbi_cp_status_pos = mbx->sbi_cp_status_pos; + + mc->sbi_pda_active_mask = mbx->sbi_pda_active_mask; + mc->sbi_pda_active_pos = mbx->sbi_pda_active_pos; + mc->sbi_ap_status_mask = mbx->sbi_ap_status_mask; + mc->sbi_ap_status_pos = mbx->sbi_ap_status_pos; + + mc->sbi_uart_noti_mask = mbx->sbi_uart_noti_mask; + mc->sbi_uart_noti_pos = mbx->sbi_uart_noti_pos; + +} + +int init_modemctl_device(struct modem_ctl *mc, struct modem_data *pdata) +{ + struct platform_device *pdev = to_platform_device(mc->dev); + int ret = 0; + int irq_num; + struct device_node *np = pdev->dev.of_node; + unsigned long flags = IRQF_NO_SUSPEND | IRQF_NO_THREAD; + struct modem_mbox *mbx; + + mif_err("+++\n"); + + g_mc = mc; + + sh333ap_get_ops(mc); + sh333ap_get_pdata(mc, pdata); + dev_set_drvdata(mc->dev, mc); + mbx = pdata->mbx; + + /* Init spin_lock for ap2cp status mbx */ + spin_lock_init(&mc->ap_status_lock); + + wake_lock_init(&mc->mc_wake_lock, WAKE_LOCK_SUSPEND, "umts_wake_lock"); + + /* Register CP_WDT interrupt handler */ + irq_num = platform_get_irq(pdev, 0); + mif_init_irq(&mc->irq_cp_wdt, irq_num, "cp_wdt", flags); + + ret = mif_request_irq(&mc->irq_cp_wdt, cp_wdt_handler, mc); + if (ret) + return ret; + + irq_set_irq_wake(irq_num, 1); + + /* CP_WDT interrupt must be enabled only after CP booting */ + mc->irq_cp_wdt.active = true; + mif_disable_irq(&mc->irq_cp_wdt); + + /* Register DVFS_REQ MBOX interrupt handler */ + mbox_request_irq(MCU_CP, mc->irq_perf_req, dvfs_req_handler, mc); + mif_err("dvfs_req_handler registered\n"); + + mbox_request_irq(MCU_CP, mc->irq_perf_req_cpu, + dvfs_req_handler_cpu, mc); + mif_err("dvfs_req_handler_cpu registered\n"); + + mbox_request_irq(MCU_CP, mc->irq_perf_req_mif, + dvfs_req_handler_mif, mc); + mif_err("dvfs_req_handler_mif registered\n"); + + mbox_request_irq(MCU_CP, mc->irq_perf_req_int, + dvfs_req_handler_int, mc); + mif_err("dvfs_req_handler_int registered\n"); + + /* Register PCIE_CTRL MBOX interrupt handler */ + mbox_request_irq(MCU_CP, mbx->irq_cp2ap_pcie_l1ss_disable, + pcie_l1ss_disable_handler, mbx); + mif_err("pcie_l1ss_disable_handler registered\n"); + + /* Register LTE_ACTIVE MBOX interrupt handler */ + mbox_request_irq(MCU_CP, mc->irq_phone_active, cp_active_handler, mc); + mif_err("cp_active_handler registered\n"); + + /* Register global value for mif_freq */ + g_mc = mc; + + init_completion(&mc->off_cmpl); + + ret = device_create_file(mc->dev, &dev_attr_modem_ctrl); + if (ret) + mif_err("can't create modem_ctrl!!!\n"); + + /* AP2CP_CFG */ + mif_dt_read_u32_noerr(np, "ap2cp_cfg_addr", mc->ap2cp_cfg_addr); + if (mc->ap2cp_cfg_addr) { + mif_info("AP2CP_CFG:0x%08x\n", mc->ap2cp_cfg_addr); + mc->ap2cp_cfg_ioaddr = devm_ioremap(mc->dev, mc->ap2cp_cfg_addr, SZ_64); + if (mc->ap2cp_cfg_ioaddr == NULL) { + mif_err("%s: AP2CP_CFG ioremap failed.\n", __func__); + return -EACCES; + } + } + + mif_err("---\n"); + return 0; +} diff --git a/drivers/misc/modem_if/modem_net_flowcontrol_device.c b/drivers/misc/modem_if/modem_net_flowcontrol_device.c new file mode 100644 index 000000000000..5dcf1a22bf24 --- /dev/null +++ b/drivers/misc/modem_if/modem_net_flowcontrol_device.c @@ -0,0 +1,116 @@ +/* /linux/drivers/misc/modem_if/modem_net_flowcontrol_device.c + * + * Copyright (C) 2011 Google, Inc. + * Copyright (C) 2011 Samsung Electronics. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "modem_prj.h" + + +#define NET_FLOWCONTROL_DEV_NAME_LEN 8 + +static int modem_net_flowcontrol_device_open( + struct inode *inode, struct file *filp) +{ + return 0; +} + +static int modem_net_flowcontrol_device_release( + struct inode *inode, struct file *filp) +{ + return 0; +} + +static long modem_net_flowcontrol_device_ioctl( + struct file *filp, unsigned int cmd, unsigned long arg) +{ + struct net *this_net; + struct net_device *ndev; + char dev_name[NET_FLOWCONTROL_DEV_NAME_LEN]; + u8 chan; + + if (copy_from_user(&chan, (void __user *)arg, sizeof(char))) + return -EFAULT; + + if (chan > 15) + return -ENODEV; + + snprintf(dev_name, NET_FLOWCONTROL_DEV_NAME_LEN, "rmnet%d", (int)chan); + this_net = get_net_ns_by_pid(current->pid); + ndev = __dev_get_by_name(this_net, dev_name); + if (ndev == NULL) { + mif_err("device = %s not exist\n", dev_name); + return -ENODEV; + } + + switch (cmd) { + case IOCTL_MODEM_NET_SUSPEND: + netif_stop_queue(ndev); + mif_info("NET SUSPEND(%s)\n", dev_name); + break; + case IOCTL_MODEM_NET_RESUME: + netif_wake_queue(ndev); + mif_info("NET RESUME(%s)\n", dev_name); + break; + default: + return -EINVAL; + } + return 0; +} + +static const struct file_operations modem_net_flowcontrol_device_fops = { + .owner = THIS_MODULE, + .open = modem_net_flowcontrol_device_open, + .release = modem_net_flowcontrol_device_release, + .unlocked_ioctl = modem_net_flowcontrol_device_ioctl, +}; + +static int __init modem_net_flowcontrol_device_init(void) +{ + int ret = 0; + struct io_device *net_flowcontrol_dev; + + net_flowcontrol_dev = kzalloc(sizeof(struct io_device), GFP_KERNEL); + if (!net_flowcontrol_dev) { + mif_err("net_flowcontrol_dev io device memory alloc fail\n"); + return -ENOMEM; + } + + net_flowcontrol_dev->miscdev.minor = MISC_DYNAMIC_MINOR; + net_flowcontrol_dev->miscdev.name = "modem_br"; + net_flowcontrol_dev->miscdev.fops = &modem_net_flowcontrol_device_fops; + + ret = misc_register(&net_flowcontrol_dev->miscdev); + if (ret) { + mif_err("failed to register misc br device : %s\n", + net_flowcontrol_dev->miscdev.name); + kfree(net_flowcontrol_dev); + } + + return ret; +} + +module_init(modem_net_flowcontrol_device_init); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Samsung Modem IF Net Flowcontrol Driver"); diff --git a/drivers/misc/modem_if/modem_notifier.c b/drivers/misc/modem_if/modem_notifier.c new file mode 100644 index 000000000000..0c2d68a5500d --- /dev/null +++ b/drivers/misc/modem_if/modem_notifier.c @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2015 Samsung Electronics. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include + +#include "modem_prj.h" +#include "modem_utils.h" + +static struct raw_notifier_head modem_event_notifier; + +int register_modem_event_notifier(struct notifier_block *nb) +{ + if (!nb) + return -ENOENT; + + return raw_notifier_chain_register(&modem_event_notifier, nb); +} + +void modem_notify_event(enum modem_event evt) +{ + mif_err("event notify (%d) ++\n", evt); + raw_notifier_call_chain(&modem_event_notifier, evt, NULL); + mif_err("event notify (%d) --\n", evt); +} diff --git a/drivers/misc/modem_if/modem_prj.h b/drivers/misc/modem_if/modem_prj.h new file mode 100644 index 000000000000..c1315d68ae64 --- /dev/null +++ b/drivers/misc/modem_if/modem_prj.h @@ -0,0 +1,988 @@ +/* + * Copyright (C) 2010 Samsung Electronics. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __MODEM_PRJ_H__ +#define __MODEM_PRJ_H__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "include/modem_v1.h" +#include "include/exynos_ipc.h" + +#define CALLER (__builtin_return_address(0)) + +#define MAX_CPINFO_SIZE 512 + +#define MAX_LINK_DEVTYPE 3 + +#define MAX_FMT_DEVS 10 +#define MAX_RAW_DEVS 32 +#define MAX_RFS_DEVS 10 +#define MAX_BOOT_DEVS 10 +#define MAX_DUMP_DEVS 10 + +#define MAX_IOD_RXQ_LEN 2048 + +#define IOCTL_MODEM_ON _IO('o', 0x19) +#define IOCTL_MODEM_OFF _IO('o', 0x20) +#define IOCTL_MODEM_RESET _IO('o', 0x21) +#define IOCTL_MODEM_BOOT_ON _IO('o', 0x22) +#define IOCTL_MODEM_BOOT_OFF _IO('o', 0x23) +#define IOCTL_MODEM_BOOT_DONE _IO('o', 0x24) + +#define IOCTL_MODEM_PROTOCOL_SUSPEND _IO('o', 0x25) +#define IOCTL_MODEM_PROTOCOL_RESUME _IO('o', 0x26) + +#define IOCTL_MODEM_STATUS _IO('o', 0x27) +#define IOCTL_MODEM_DL_START _IO('o', 0x28) +#define IOCTL_MODEM_FW_UPDATE _IO('o', 0x29) + +#define IOCTL_MODEM_NET_SUSPEND _IO('o', 0x30) +#define IOCTL_MODEM_NET_RESUME _IO('o', 0x31) + +#define IOCTL_MODEM_DUMP_START _IO('o', 0x32) +#define IOCTL_MODEM_DUMP_UPDATE _IO('o', 0x33) +#define IOCTL_MODEM_FORCE_CRASH_EXIT _IO('o', 0x34) +#define IOCTL_MODEM_CP_UPLOAD _IO('o', 0x35) +#define IOCTL_MODEM_DUMP_RESET _IO('o', 0x36) + +#if defined(CONFIG_SEC_DUAL_MODEM_MODE) +#define IOCTL_MODEM_SWITCH_MODEM _IO('o', 0x37) +#endif + +#define IOCTL_MODEM_RAMDUMP_START _IO('o', 0xCE) +#define IOCTL_MODEM_RAMDUMP_STOP _IO('o', 0xCF) + +#define IOCTL_MODEM_XMIT_BOOT _IO('o', 0x40) +#define IOCTL_MODEM_GET_SHMEM_INFO _IO('o', 0x41) + +/* ioctl command for IPC Logger */ +#define IOCTL_MIF_LOG_DUMP _IO('o', 0x51) + +#define IOCTL_SHMEM_FULL_DUMP _IO('o', 0x54) /* For shmem dump */ +#define IOCTL_VSS_FULL_DUMP _IO('o', 0x57) /* For vss dump */ +#define IOCTL_ACPM_FULL_DUMP _IO('o', 0x58) /* For acpm memory dump */ + +/* ioctcl command for fast CP Boot */ +#define IOCTL_SEC_CP_INIT _IO('o', 0x61) +#define IOCTL_CHECK_SECURITY _IO('o', 0x62) +#define IOCTL_XMIT_BIN _IO('o', 0x63) +#define IOCTL_MODEM_CRASH_REASON _IO('o', 0x64) /* Get Crash Reason */ + +#define CPBOOT_DIR_MASK 0xF000 +#define CPBOOT_STAGE_MASK 0x0F00 +#define CPBOOT_CMD_MASK 0x000F +#define CPBOOT_REQ_RESP_MASK 0x0FFF + +#define CPBOOT_DIR_AP2CP 0x9000 +#define CPBOOT_DIR_CP2AP 0xA000 + +#define CPBOOT_STAGE_SHIFT 8 + +#define CPBOOT_STAGE_START 0x0000 +#define CPBOOT_CRC_SEND 0x000C +#define CPBOOT_STAGE_DONE 0x000D +#define CPBOOT_STAGE_FAIL 0x000F + +/* modem status */ +#define MODEM_OFF 0 +#define MODEM_CRASHED 1 +#define MODEM_RAMDUMP 2 +#define MODEM_POWER_ON 3 +#define MODEM_BOOTING_NORMAL 4 +#define MODEM_BOOTING_RAMDUMP 5 +#define MODEM_DUMPING 6 +#define MODEM_RUNNING 7 + +#define HDLC_HEADER_MAX_SIZE 6 /* fmt 3, raw 6, rfs 6 */ + +#define PSD_DATA_CHID_BEGIN 0x2A +#define PSD_DATA_CHID_END 0x38 + +#define PS_DATA_CH_0 10 +#define PS_DATA_CH_LAST 24 +#define RMNET0_CH_ID PS_DATA_CH_0 + +#define IP6VERSION 6 + +#define SOURCE_MAC_ADDR {0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC} + +/* IP loopback */ +#define DATA_DRAIN_CHANNEL 30 /* Drain channel to drop RX packets */ +#define DATA_LOOPBACK_CHANNEL 31 + +/* Debugging features */ +#define MIF_LOG_DIR "/data/exynos/log/cbd" +#define MIF_MAX_PATH_LEN 256 +#define MIF_MAX_NAME_LEN 64 +#define MIF_MAX_STR_LEN 32 + +#define MAX_HEX_LEN 16 +#define MAX_NAME_LEN 64 +#define MAX_PREFIX_LEN 128 +#define MAX_STR_LEN 256 + +#define CP_CRASH_TAG "CP Crash " + +#define NO_WAKEUP_LOCK +#define FREQ_MAX_LV (40) + +static const char * const dev_format_str[] = { + [IPC_FMT] = "FMT", + [IPC_RAW] = "RAW", + [IPC_RFS] = "RFS", + [IPC_MULTI_RAW] = "MULTI_RAW", + [IPC_BOOT] = "BOOT", + [IPC_DUMP] = "RAMDUMP", + [IPC_CMD] = "CMD", + [IPC_DEBUG] = "DEBUG", +}; + +/** + * get_dev_name + * @dev: IPC device (enum dev_format) + * + * Returns IPC device name as a string. + */ +static const inline char *get_dev_name(unsigned int dev) +{ + if (unlikely(dev >= MAX_DEV_FORMAT)) + return "INVALID"; + else + return dev_format_str[dev]; +} + +/* Does modem ctl structure will use state ? or status defined below ?*/ +enum modem_state { + STATE_OFFLINE, + STATE_CRASH_RESET, /* silent reset */ + STATE_CRASH_EXIT, /* cp ramdump */ + STATE_BOOTING, + STATE_ONLINE, + STATE_NV_REBUILDING, /* <= rebuilding start */ + STATE_LOADER_DONE, + STATE_SIM_ATTACH, + STATE_SIM_DETACH, +#if defined(CONFIG_SEC_DUAL_MODEM_MODE) + STATE_MODEM_SWITCH, +#endif + STATE_CRASH_WATCHDOG, +}; + +static const char * const cp_state_str[] = { + [STATE_OFFLINE] = "OFFLINE", + [STATE_CRASH_RESET] = "CRASH_RESET", + [STATE_CRASH_EXIT] = "CRASH_EXIT", + [STATE_BOOTING] = "BOOTING", + [STATE_ONLINE] = "ONLINE", + [STATE_NV_REBUILDING] = "NV_REBUILDING", + [STATE_LOADER_DONE] = "LOADER_DONE", + [STATE_SIM_ATTACH] = "SIM_ATTACH", + [STATE_SIM_DETACH] = "SIM_DETACH", +#if defined(CONFIG_SEC_DUAL_MODEM_MODE) + [STATE_MODEM_SWITCH] = "MODEM_SWITCH", +#endif + [STATE_CRASH_WATCHDOG] = "CRASH_WATCHDOG", +}; + +enum direction { + TX = 0, + UL = 0, + AP2CP = 0, + RX = 1, + DL = 1, + CP2AP = 1, + TXRX = 2, + RXTX = 2, + MAX_DIR = 2 +}; + +static const inline char *get_cp_state_str(int state) +{ + return cp_state_str[state]; +} + +enum com_state { + COM_NONE, + COM_ONLINE, + COM_HANDSHAKE, + COM_BOOT, + COM_CRASH, +}; + +enum link_mode { + LINK_MODE_OFFLINE = 0, + LINK_MODE_BOOT, + LINK_MODE_IPC, + LINK_MODE_DLOAD, + LINK_MODE_ULOAD, +}; + +struct sim_state { + bool online; /* SIM is online? */ + bool changed; /* online is changed? */ +}; + +enum cp_boot_mode { + CP_BOOT_MODE_NORMAL, + CP_BOOT_MODE_DUMP, + CP_BOOT_RE_INIT, + MAX_CP_BOOT_MODE +}; + +struct modem_firmware { + char *binary; + u32 size; +}; + +enum cp_copy_stage { + BOOT = 0, + MAIN = 1, + VSS = 2, + NV = 3, + NV_PROT = 4 +}; +struct sec_info { + enum cp_boot_mode mode; + u32 boot_size; + u32 main_size; +}; + +struct data_info { + u32 stage; + u32 m_offset; + u8 *buff; + u32 total_size; + u32 offset; + u32 len; +} __attribute__((packed)); + +enum mem_info { + MAGIC_CODE = 16, + CP_MEM, + SHM_MEM +}; + +#define SMC_ID 0x82000700 +#define SMC_ID_CLK 0xC2001011 + +#define SSS_CLK_ENABLE 0 +#define SSS_CLK_DISABLE 1 + +#define HDLC_START 0x7F +#define HDLC_END 0x7E +#define SIZE_OF_HDLC_START 1 +#define SIZE_OF_HDLC_END 1 +#define MAX_LINK_PADDING_SIZE 3 + +#define EXYNOS_MULTI_FRAME_ID_BITS 7 +#define NUM_EXYNOS_MULTI_FRAME_IDS (1 << EXYNOS_MULTI_FRAME_ID_BITS) +#define MAX_EXYNOS_MULTI_FRAME_ID (NUM_EXYNOS_MULTI_FRAME_IDS - 1) + +struct header_data { + char hdr[HDLC_HEADER_MAX_SIZE]; + u32 len; + u32 frag_len; + char start; /*hdlc start header 0x7F*/ +}; + +struct fmt_hdr { + u16 len; + u8 control; +} __attribute__((packed)); + +struct raw_hdr { + u32 len; + u8 channel; + u8 control; +} __attribute__((packed)); + +struct rfs_hdr { + u32 len; + u8 cmd; + u8 id; +} __attribute__((packed)); + +struct sipc_fmt_hdr { + u16 len; + u8 msg_seq; + u8 ack_seq; + u8 main_cmd; + u8 sub_cmd; + u8 cmd_type; +} __attribute__((packed)); + +#define FLOW_CTRL_SUSPEND ((u8)(0xCA)) +#define FLOW_CTRL_RESUME ((u8)(0xCB)) + +/* If iod->id is 0, do not need to store to `iodevs_tree_fmt' in SIPC4 */ +#define sipc4_is_not_reserved_channel(ch) ((ch) != 0) + +/* Channel 0, 5, 6, 27, 255 are reserved in SIPC5. + * see SIPC5 spec: 2.2.2 Channel Identification (Ch ID) Field. + * They do not need to store in `iodevs_tree_fmt' + */ +#define exynos_is_not_reserved_channel(ch) \ + ((ch) != 0 && (ch) != 27 && (ch) != 255) + +struct vnet { + struct io_device *iod; +}; + +/* for fragmented data from link devices */ +struct fragmented_data { + struct sk_buff *skb_recv; + struct header_data h_data; + struct exynos_frame_data f_data; + /* page alloc fail retry*/ + unsigned int realloc_offset; +}; +#define fragdata(iod, ld) (&(iod)->fragments[(ld)->link_type]) + +/** struct skbuff_priv - private data of struct sk_buff + * this is matched to char cb[48] of struct sk_buff + */ +struct skbuff_private { + struct io_device *iod; + struct link_device *ld; + struct io_device *real_iod; /* for rx multipdp */ + + /* for time-stamping */ + struct timespec ts; + + u32 lnk_hdr:1, + reserved:15, + exynos_ch:8, + frm_ctrl:8; + + /* for indicating that thers is only one IPC frame in an skb */ + bool single_frame; +} __packed; + +static inline struct skbuff_private *skbpriv(struct sk_buff *skb) +{ + BUILD_BUG_ON(sizeof(struct skbuff_private) > sizeof(skb->cb)); + return (struct skbuff_private *)&skb->cb; +} + +enum iod_rx_state { + IOD_RX_ON_STANDBY = 0, + IOD_RX_HEADER, + IOD_RX_PAYLOAD, + IOD_RX_PADDING, + MAX_IOD_RX_STATE +}; + +static const char * const rx_state_str[] = { + [IOD_RX_ON_STANDBY] = "RX_ON_STANDBY", + [IOD_RX_HEADER] = "RX_HEADER", + [IOD_RX_PAYLOAD] = "RX_PAYLOAD", + [IOD_RX_PADDING] = "RX_PADDING", +}; + +/** + * get_dev_name + * @dev: IPC device (enum dev_format) + * + * Returns IPC device name as a string. + */ +static const inline char *get_rx_state_str(unsigned int state) +{ + if (unlikely(state >= MAX_IOD_RX_STATE)) + return "INVALID_STATE"; + else + return rx_state_str[state]; +} + +struct meminfo { + unsigned int base_addr; + unsigned int size; +}; + + +struct io_device { + /* rb_tree node for an io device */ + struct rb_node node_chan; + struct rb_node node_fmt; + + /* Name of the IO device */ + char *name; + + /* Reference count */ + atomic_t opened; + + /* Wait queue for the IO device */ + wait_queue_head_t wq; + + /* Misc and net device structures for the IO device */ + struct miscdevice miscdev; + struct net_device *ndev; + + /* ID and Format for channel on the link */ + unsigned int id; + enum modem_link link_types; + enum dev_format format; + enum modem_io io_typ; + + /* Attributes of an IO device */ + u32 attrs; + + /* The name of the application that will use this IO device */ + char *app; + + /* Whether or not handover among 2+ link devices */ + bool use_handover; + + bool link_header; + + /* Rx queue of sk_buff */ + struct sk_buff_head sk_rx_q; + + /* RX state used in RX FSM */ + enum iod_rx_state curr_rx_state; + enum iod_rx_state next_rx_state; + + /* + * work for each io device, when delayed work needed + * use this for private io device rx action + */ + struct delayed_work rx_work; + + struct fragmented_data fragments[LINKDEV_MAX]; + + /* for keeping multi-frame packets temporarily */ + struct sk_buff_head sk_multi_q[NUM_EXYNOS_MULTI_FRAME_IDS]; + struct sk_buff *skb[128]; + + /* called from linkdevice when a packet arrives for this iodevice */ + int (*recv)(struct io_device *iod, struct link_device *ld, + const char *data, unsigned int len); + int (*recv_skb)(struct io_device *iod, struct link_device *ld, + struct sk_buff *skb); + + int (*recv_skb_single)(struct io_device *iod, struct link_device *ld, + struct sk_buff *skb); + + /* inform the IO device that the modem is now online or offline or + * crashing or whatever... + */ + void (*modem_state_changed)(struct io_device *iod, enum modem_state); + + /* inform the IO device that the SIM is not inserting or removing */ + void (*sim_state_changed)(struct io_device *iod, bool sim_online); + + struct modem_ctl *mc; + struct modem_shared *msd; + + struct wake_lock wakelock; + long waketime; + + struct exynos_seq_num seq_num; + u8 packet_index; + + /* DO NOT use __current_link directly + * you MUST use skbpriv(skb)->ld in mc, link, etc.. + */ + struct link_device *__current_link; +}; +#define to_io_device(misc) container_of(misc, struct io_device, miscdev) + +/* get_current_link, set_current_link don't need to use locks. + * In ARM, set_current_link and get_current_link are compiled to + * each one instruction (str, ldr) as atomic_set, atomic_read. + * And, the order of set_current_link and get_current_link is not important. + */ +#define get_current_link(iod) ((iod)->__current_link) +#define set_current_link(iod, ld) ((iod)->__current_link = (ld)) + +struct link_device { + struct list_head list; + char *name; + + enum modem_link link_type; + unsigned int aligned; + + /* Maximum IPC device = the last IPC device (e.g. IPC_RFS) + 1 */ + int max_ipc_dev; + + /* Modem data */ + struct modem_data *mdm_data; + + /* Modem control */ + struct modem_ctl *mc; + + /* Modem shared data */ + struct modem_shared *msd; + + /* Operation mode of the link device */ + enum link_mode mode; + + /* completion for waiting for link initialization */ + struct completion init_cmpl; + + /* completion for waiting for PIF initialization in a CP */ + struct completion pif_cmpl; + + struct io_device *fmt_iods[4]; + + /* TX queue of socket buffers */ + struct sk_buff_head sk_fmt_tx_q; + struct sk_buff_head sk_raw_tx_q; + struct sk_buff_head sk_rfs_tx_q; + + struct sk_buff_head *skb_txq[MAX_IPC_DEV]; + + /* RX queue of socket buffers */ + struct sk_buff_head sk_fmt_rx_q; + struct sk_buff_head sk_raw_rx_q; + struct sk_buff_head sk_rfs_rx_q; + + struct sk_buff_head *skb_rxq[MAX_IPC_DEV]; + + bool raw_tx_suspended; /* for misc dev */ + struct completion raw_tx_resumed_by_cp; + + /** + * This flag is for TX flow control on network interface. + * This must be set and clear only by a flow control command from CP. + */ + bool suspend_netif_tx; + + struct workqueue_struct *tx_wq; + struct work_struct tx_work; + struct delayed_work tx_delayed_work; + + struct delayed_work *tx_dwork[MAX_IPC_DEV]; + struct delayed_work fmt_tx_dwork; + struct delayed_work raw_tx_dwork; + struct delayed_work rfs_tx_dwork; + + struct workqueue_struct *rx_wq; + struct work_struct rx_work; + struct delayed_work rx_delayed_work; + + enum com_state com_state; + + /* init communication - setting link driver */ + int (*init_comm)(struct link_device *ld, struct io_device *iod); + + /* terminate communication */ + void (*terminate_comm)(struct link_device *ld, struct io_device *iod); + + /* called by an io_device when it has a packet to send over link + * - the io device is passed so the link device can look at id and + * format fields to determine how to route/format the packet + */ + int (*send)(struct link_device *ld, struct io_device *iod, + struct sk_buff *skb); + + /* method for CP fast booting */ + int (*xmit_bin)(struct link_device *ld, struct io_device *iod, + unsigned long arg); + + /* methods for CP firmware upgrade */ + int (*dload_start)(struct link_device *ld, struct io_device *iod); + int (*firm_update)(struct link_device *ld, struct io_device *iod, + unsigned long arg); + + /* methods for CP crash dump */ + int (*force_dump)(struct link_device *ld, struct io_device *iod); + int (*dump_start)(struct link_device *ld, struct io_device *iod); + int (*dump_update)(struct link_device *ld, struct io_device *iod, + unsigned long arg); + int (*dump_finish)(struct link_device *ld, struct io_device *iod, + unsigned long arg); + + int (*shmem_dump)(struct link_device *ld, struct io_device *iod, + unsigned long arg); + int (*vss_dump)(struct link_device *ld, struct io_device *iod, + unsigned long arg); + int (*acpm_dump)(struct link_device *ld, struct io_device *iod, + unsigned long arg); + + /* IOCTL extension */ + int (*ioctl)(struct link_device *ld, struct io_device *iod, + unsigned int cmd, unsigned long arg); + + /* Send thermal for CP TMU */ + void (*send_tmu)(struct link_device *ld, struct io_device *iod, + int arg); + + /* for cp security */ + int (*check_security)(struct link_device *ld, struct io_device *iod, + unsigned long arg); + int (*sec_init)(struct link_device *ld, struct io_device *iod, + unsigned long arg); + + /* for recording crash_reason */ + int (*crash_reason)(struct link_device *ld, struct io_device *iod, + unsigned long arg); +}; + +/** rx_alloc_skb - allocate an skbuff and set skb's iod, ld + * @length: length to allocate + * @iod: struct io_device * + * @ld: struct link_device * + * + * %NULL is returned if there is no free memory. + */ +static inline struct sk_buff *rx_alloc_skb(unsigned int length, + struct io_device *iod, struct link_device *ld) +{ + struct sk_buff *skb; + + if (iod->format == IPC_MULTI_RAW || iod->format == IPC_RAW) + skb = dev_alloc_skb(length); + else + skb = alloc_skb(length, GFP_ATOMIC); + + if (likely(skb)) { + skbpriv(skb)->iod = iod; + skbpriv(skb)->ld = ld; + } + return skb; +} + +struct modemctl_ops { + int (*modem_on)(struct modem_ctl *); + int (*modem_off)(struct modem_ctl *); + int (*modem_reset)(struct modem_ctl *); + int (*modem_boot_on)(struct modem_ctl *); + int (*modem_boot_off)(struct modem_ctl *); + int (*modem_boot_done)(struct modem_ctl *); + int (*modem_force_crash_exit)(struct modem_ctl *); + int (*modem_dump_reset)(struct modem_ctl *); + int (*modem_dump_start)(struct modem_ctl *); + int (*suspend_modem_ctrl)(struct modem_ctl *); + int (*resume_modem_ctrl)(struct modem_ctl *); + int (*modem_get_meminfo)(struct modem_ctl *, unsigned long arg); +}; + +/* for IPC Logger */ +struct mif_storage { + char *addr; + unsigned int cnt; +}; + +/* modem_shared - shared data for all io/link devices and a modem ctl + * msd : mc : iod : ld = 1 : 1 : M : N + */ +struct modem_shared { + /* list of link devices */ + struct list_head link_dev_list; + + /* rb_tree root of io devices. */ + struct rb_root iodevs_tree_chan; /* group by channel */ + struct rb_root iodevs_tree_fmt; /* group by dev_format */ + + /* for IPC Logger */ + struct mif_storage storage; + spinlock_t lock; + + /* CP crash information */ + char cp_crash_info[530]; + + /* loopbacked IP address + * default is 0.0.0.0 (disabled) + * after you setted this, you can use IP packet loopback using this IP. + * exam: echo 1.2.3.4 > /sys/devices/virtual/misc/umts_multipdp/loopback + */ + __be32 loopback_ipaddr; +}; + +struct modem_irq { + spinlock_t lock; + int num; + char name[MIF_MAX_NAME_LEN]; + unsigned long flags; + bool active; +}; + +struct freq_table { + int num_of_table; + u32 freq[FREQ_MAX_LV]; +}; + +struct modem_ctl { + struct device *dev; + char *name; + struct modem_data *mdm_data; + + struct modem_shared *msd; + void __iomem *sysram_alive; + + enum modem_state phone_state; + struct sim_state sim_state; + + unsigned int gpio_cp_on; + unsigned int gpio_cp_off; + unsigned int gpio_reset_req_n; + unsigned int gpio_cp_reset; + + /* for broadcasting AP's PM state (active or sleep) */ + unsigned int gpio_pda_active; + unsigned int mbx_pda_active; + int int_pda_active; + + /* for checking aliveness of CP */ + unsigned int gpio_phone_active; + unsigned int mbx_phone_active; + int irq_phone_active; + struct modem_irq irq_cp_wdt; /* watchdog timer */ + struct modem_irq irq_cp_fail; + + /* CP binary info */ + char info_cp_build[30]; + char info_cp_carr[30]; + + /* for AP-CP power management (PM) handshaking */ + unsigned int gpio_ap_wakeup; + unsigned int mbx_ap_wakeup; + int irq_ap_wakeup; + + unsigned int gpio_ap_status; + unsigned int mbx_ap_status; + int int_ap_status; + spinlock_t ap_status_lock; + + unsigned int gpio_cp_wakeup; + unsigned int mbx_cp_wakeup; + int int_cp_wakeup; + + unsigned int gpio_cp_status; + unsigned int mbx_cp_status; + int irq_cp_status; + + /* for performance tuning */ + unsigned int gpio_perf_req; + unsigned int mbx_perf_req; + int irq_perf_req; + + unsigned int mbx_perf_req_cpu; + int irq_perf_req_cpu; + + unsigned int mbx_perf_req_mif; + int irq_perf_req_mif; + + unsigned int mbx_perf_req_int; + int irq_perf_req_int; + int irq_cp_wakelock; + + unsigned int mbx_ap_mif_freq; + + /* for tmu request */ + unsigned int gpio_tmu_req; + unsigned int mbx_tmu_req; + int irq_tmu_req; + + /* for system revision information */ + unsigned int mbx_sys_rev; + unsigned int mbx_pmic_rev; + unsigned int mbx_pkg_id; + unsigned int mbx_lock_value; + + /* for USB/HSIC PM */ + unsigned int gpio_host_wakeup; + int irq_host_wakeup; + unsigned int gpio_host_active; + unsigned int gpio_slave_wakeup; + + unsigned int gpio_cp_dump_int; + unsigned int gpio_ap_dump_int; + unsigned int gpio_flm_uart_sel; + unsigned int gpio_cp_warm_reset; +#if defined(CONFIG_MACH_M0_CTC) + unsigned int gpio_flm_uart_sel_rev06; +#endif + + unsigned int gpio_sim_detect; + int irq_sim_detect; + +#ifdef CONFIG_LINK_DEVICE_SHMEM + struct modem_pmu *pmu; + + /* Status Bit Info */ + unsigned int sbi_wake_lock_mask; + unsigned int sbi_wake_lock_pos; + unsigned int sbi_lte_active_mask; + unsigned int sbi_lte_active_pos; + unsigned int sbi_cp_status_mask; + unsigned int sbi_cp_status_pos; + + unsigned int sbi_pda_active_mask; + unsigned int sbi_pda_active_pos; + unsigned int sbi_ap_status_mask; + unsigned int sbi_ap_status_pos; + + unsigned int sbi_uart_noti_mask; + unsigned int sbi_uart_noti_pos; + + unsigned int ap2cp_cfg_addr; + void __iomem *ap2cp_cfg_ioaddr; +#endif + +#ifdef CONFIG_LINK_DEVICE_PLD + unsigned int gpio_fpga_cs_n; +#endif + +#if defined(CONFIG_MACH_U1_KOR_LGT) + unsigned int gpio_cp_reset_msm; + unsigned int gpio_boot_sw_sel; + void (*vbus_on)(void); + void (*vbus_off)(void); + bool usb_boot; +#endif + +#ifdef CONFIG_TDSCDMA_MODEM_SPRD8803 + unsigned int gpio_ap_cp_int1; + unsigned int gpio_ap_cp_int2; +#endif + + unsigned int hw_revision; + unsigned int package_id; + unsigned int lock_value; + +#ifdef CONFIG_SEC_DUAL_MODEM_MODE + unsigned int gpio_sim_io_sel; + unsigned int gpio_cp_ctrl1; + unsigned int gpio_cp_ctrl2; +#endif + + struct work_struct pm_qos_work; + struct work_struct pm_qos_work_cpu; + struct work_struct pm_qos_work_mif; + struct work_struct pm_qos_work_int; + + struct work_struct pm_tmu_work; + + /* completion for waiting for CP power-off */ + struct completion off_cmpl; + + /* Switch with 2 links in a modem */ + unsigned int gpio_link_switch; + + const struct attribute_group *group; + + struct delayed_work dwork; + struct work_struct work; + + struct modemctl_ops ops; + struct io_device *iod; + struct io_device *bootd; + + /* Wakelock for modem_ctl */ + struct wake_lock mc_wake_lock; + + void (*gpio_revers_bias_clear)(void); + void (*gpio_revers_bias_restore)(void); + + bool need_switch_to_usb; + bool sim_polarity; + + bool is_dualsim; + u32 mif_minlock; + + bool sim_shutdown_req; + void (*modem_complete)(struct modem_ctl *mc); + + /* for notify uart connection with direction*/ + unsigned int mbx_uart_noti; + unsigned int int_uart_noti; + +#if defined(CONFIG_MUIC_NOTIFIER) + struct notifier_block uart_notifier; +#endif + bool uart_connect; + bool uart_dir; + + struct freq_table int_table; + struct freq_table mif_table; + struct freq_table cpu_table; + + struct clk *qch_cp; +}; + +static inline bool cp_offline(struct modem_ctl *mc) +{ + if (!mc) + return true; + return (mc->phone_state == STATE_OFFLINE); +} + +static inline bool cp_online(struct modem_ctl *mc) +{ + if (!mc) + return false; + return (mc->phone_state == STATE_ONLINE); +} + +static inline bool cp_booting(struct modem_ctl *mc) +{ + if (!mc) + return false; + return (mc->phone_state == STATE_BOOTING); +} + +static inline bool cp_crashed(struct modem_ctl *mc) +{ + if (!mc) + return false; + return (mc->phone_state == STATE_CRASH_EXIT); +} + +static inline bool rx_possible(struct modem_ctl *mc) +{ + if (likely(cp_online(mc))) + return true; + + if (cp_booting(mc) || cp_crashed(mc)) + return true; + + return false; +} + +int exynos_init_io_device(struct io_device *iod); + +#ifdef TEST_WITHOUT_CP +ssize_t misc_write(struct file *filp, const char __user *data, + size_t count, loff_t *fpos); +#endif + +#if defined(CONFIG_TDSCDMA_MODEM_SPRD8803) && defined(CONFIG_LINK_DEVICE_SPI) +extern int spi_sema_init(void); +extern int sprd_boot_done; +#endif + +#define STD_UDL_STEP_MASK 0x0000000F +#define STD_UDL_SEND 0x1 +#define STD_UDL_CRC 0xC + +struct std_dload_info { + u32 size; + u32 mtu; + u32 num_frames; +} __packed; + +u32 std_udl_get_cmd(u8 *frm); +bool std_udl_with_payload(u32 cmd); + +#endif diff --git a/drivers/misc/modem_if/modem_utils.c b/drivers/misc/modem_if/modem_utils.c new file mode 100644 index 000000000000..ed4f040acbb3 --- /dev/null +++ b/drivers/misc/modem_if/modem_utils.c @@ -0,0 +1,1294 @@ +/* + * Copyright (C) 2011 Samsung Electronics. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "modem_prj.h" +#include "modem_utils.h" + +#define CMD_SUSPEND ((unsigned short)(0x00CA)) +#define CMD_RESUME ((unsigned short)(0x00CB)) + +#define TX_SEPARATOR "mif: >>>>>>>>>> Outgoing packet " +#define RX_SEPARATOR "mif: Incoming packet <<<<<<<<<<" +#define LINE_SEPARATOR \ + "mif: ------------------------------------------------------------" +#define LINE_BUFF_SIZE 80 + +static const char *hex = "0123456789abcdef"; + +void ts2utc(struct timespec *ts, struct utc_time *utc) +{ + struct tm tm; + + time_to_tm((ts->tv_sec - (sys_tz.tz_minuteswest * 60)), 0, &tm); + utc->year = 1900 + tm.tm_year; + utc->mon = 1 + tm.tm_mon; + utc->day = tm.tm_mday; + utc->hour = tm.tm_hour; + utc->min = tm.tm_min; + utc->sec = tm.tm_sec; + utc->msec = ns2ms(ts->tv_nsec); +} + +void get_utc_time(struct utc_time *utc) +{ + struct timespec ts; + getnstimeofday(&ts); + ts2utc(&ts, utc); +} +EXPORT_SYMBOL(get_utc_time); + +int mif_dump_log(struct modem_shared *msd, struct io_device *iod) +{ + struct sk_buff *skb; + unsigned long read_len = 0; + unsigned long int flags; + + spin_lock_irqsave(&msd->lock, flags); + while (read_len < MAX_MIF_BUFF_SIZE) { + skb = alloc_skb(MAX_IPC_SKB_SIZE, GFP_ATOMIC); + if (!skb) { + mif_err("ERR! alloc_skb fail\n"); + spin_unlock_irqrestore(&msd->lock, flags); + return -ENOMEM; + } + memcpy(skb_put(skb, MAX_IPC_SKB_SIZE), + msd->storage.addr + read_len, MAX_IPC_SKB_SIZE); + skb_queue_tail(&iod->sk_rx_q, skb); + read_len += MAX_IPC_SKB_SIZE; + wake_up(&iod->wq); + } + spin_unlock_irqrestore(&msd->lock, flags); + return 0; +} + +static unsigned long long get_kernel_time(void) +{ + int this_cpu; + unsigned long flags; + unsigned long long time; + + preempt_disable(); + raw_local_irq_save(flags); + + this_cpu = smp_processor_id(); + time = cpu_clock(this_cpu); + + preempt_enable(); + raw_local_irq_restore(flags); + + return time; +} + +void mif_ipc_log(enum mif_log_id id, + struct modem_shared *msd, const char *data, size_t len) +{ + struct mif_ipc_block *block; + unsigned long int flags; + + spin_lock_irqsave(&msd->lock, flags); + + block = (struct mif_ipc_block *) + (msd->storage.addr + (MAX_LOG_SIZE * msd->storage.cnt)); + msd->storage.cnt = ((msd->storage.cnt + 1) < MAX_LOG_CNT) ? + msd->storage.cnt + 1 : 0; + + spin_unlock_irqrestore(&msd->lock, flags); + + block->id = id; + block->time = get_kernel_time(); + block->len = (len > MAX_IPC_LOG_SIZE) ? MAX_IPC_LOG_SIZE : len; + memcpy(block->buff, data, block->len); +} + +void _mif_irq_log(enum mif_log_id id, struct modem_shared *msd, + struct mif_irq_map map, const char *data, size_t len) +{ + struct mif_irq_block *block; + unsigned long int flags; + + spin_lock_irqsave(&msd->lock, flags); + + block = (struct mif_irq_block *) + (msd->storage.addr + (MAX_LOG_SIZE * msd->storage.cnt)); + msd->storage.cnt = ((msd->storage.cnt + 1) < MAX_LOG_CNT) ? + msd->storage.cnt + 1 : 0; + + spin_unlock_irqrestore(&msd->lock, flags); + + block->id = id; + block->time = get_kernel_time(); + memcpy(&(block->map), &map, sizeof(struct mif_irq_map)); + if (data) + memcpy(block->buff, data, + (len > MAX_IRQ_LOG_SIZE) ? MAX_IRQ_LOG_SIZE : len); +} + +void _mif_com_log(enum mif_log_id id, + struct modem_shared *msd, const char *format, ...) +{ + struct mif_common_block *block; + unsigned long int flags; + va_list args; + + spin_lock_irqsave(&msd->lock, flags); + + block = (struct mif_common_block *) + (msd->storage.addr + (MAX_LOG_SIZE * msd->storage.cnt)); + msd->storage.cnt = ((msd->storage.cnt + 1) < MAX_LOG_CNT) ? + msd->storage.cnt + 1 : 0; + + spin_unlock_irqrestore(&msd->lock, flags); + + block->id = id; + block->time = get_kernel_time(); + + va_start(args, format); + vsnprintf(block->buff, MAX_COM_LOG_SIZE, format, args); + va_end(args); +} + +void _mif_time_log(enum mif_log_id id, struct modem_shared *msd, + struct timespec epoch, const char *data, size_t len) +{ + struct mif_time_block *block; + unsigned long int flags; + + spin_lock_irqsave(&msd->lock, flags); + + block = (struct mif_time_block *) + (msd->storage.addr + (MAX_LOG_SIZE * msd->storage.cnt)); + msd->storage.cnt = ((msd->storage.cnt + 1) < MAX_LOG_CNT) ? + msd->storage.cnt + 1 : 0; + + spin_unlock_irqrestore(&msd->lock, flags); + + block->id = id; + block->time = get_kernel_time(); + memcpy(&block->epoch, &epoch, sizeof(struct timespec)); + + if (data) + memcpy(block->buff, data, + (len > MAX_IRQ_LOG_SIZE) ? MAX_IRQ_LOG_SIZE : len); +} + +/* dump2hex + * dump data to hex as fast as possible. + * the length of @buff must be greater than "@len * 3" + * it need 3 bytes per one data byte to print. + */ +static inline int dump2hex(char *buff, const char *data, size_t len) +{ + char *dest = buff; + int i; + + for (i = 0; i < len; i++) { + *dest++ = hex[(data[i] >> 4) & 0xf]; + *dest++ = hex[data[i] & 0xf]; + *dest++ = ' '; + } + if (likely(len > 0)) + dest--; /* last space will be overwrited with null */ + + *dest = '\0'; + + return dest - buff; +} + +static inline void pr_ipc_msg(int level, u8 ch, struct timespec *ts, + const char *prefix, const u8 *msg, + unsigned int len) +{ + size_t offset; + struct utc_time utc; + char str[MAX_STR_LEN] = {0, }; + + /* If @ch is for BOOT or DUMP, only UDL command without any payload + * should be printed. + */ + if (exynos_udl_ch(ch)) + if (len > 16) + return; + + ts2utc(ts, &utc); + + if (prefix) + snprintf(str, MAX_STR_LEN, "%s", prefix); + + offset = strlen(str); + dump2hex((str + offset), msg, (len > MAX_HEX_LEN ? MAX_HEX_LEN : len)); + + if (level > 0) { + pr_err("%s: " HMSU_FMT " %s\n", MIF_TAG, + utc.hour, utc.min, utc.sec, utc.msec, str); + } +} + +void log_ipc_pkt(struct sk_buff *skb, enum ipc_layer layer, enum direction dir) +{ + struct io_device *iod; + struct link_device *ld; + char prefix[MAX_PREFIX_LEN] = {0, }; + struct timespec *ts; + unsigned int hdr_len; + unsigned int msg_len; + u8 *msg; + u8 *hdr; + u8 ch; + + if (!log_info.debug_log) + return; + + iod = skbpriv(skb)->iod; + ld = skbpriv(skb)->ld; + ch = skbpriv(skb)->exynos_ch; + + getnstimeofday(&skbpriv(skb)->ts); + /** + * Make a string of the route + */ + snprintf(prefix, MAX_PREFIX_LEN, "%s %s: %s: %s%s%s: ", + layer_str(layer), dir_str(dir), ld->name, + iod->name, arrow(dir), iod->mc->name); + + hdr = skbpriv(skb)->lnk_hdr ? skb->data : NULL; + hdr_len = hdr ? EXYNOS_HEADER_SIZE : 0; + if (hdr_len > 0) { + char *separation = " | "; + size_t offset = strlen(prefix); + + dump2hex((prefix + offset), hdr, hdr_len); + strncat(prefix, separation, strlen(separation)); + } + + /* Print an IPC message with the prefix */ + ts = &skbpriv(skb)->ts; + msg = skb->data + hdr_len; + msg_len = (skb->len - hdr_len); + + if (exynos_fmt_ch(ch)) + pr_ipc_msg(log_info.fmt_msg, ch, ts, prefix, msg, msg_len); + else if (exynos_boot_ch(ch)) + pr_ipc_msg(log_info.boot_msg, ch, ts, prefix, msg, msg_len); + else if (exynos_dump_ch(ch)) + pr_ipc_msg(log_info.dump_msg, ch, ts, prefix, msg, msg_len); + else if (exynos_rfs_ch(ch)) + pr_ipc_msg(log_info.rfs_msg, ch, ts, prefix, msg, msg_len); + else if (exynos_log_ch(ch)) + pr_ipc_msg(log_info.log_msg, ch, ts, prefix, msg, msg_len); + else if (exynos_ps_ch(ch)) + pr_ipc_msg(log_info.ps_msg, ch, ts, prefix, msg, msg_len); + else if (exynos_router_ch(ch)) + pr_ipc_msg(log_info.router_msg, ch, ts, prefix, msg, msg_len); + else if (exynos_rcs_ch(ch)) + pr_ipc_msg(log_info.rcs_msg, ch, ts, prefix, msg, msg_len); + else if (exynos_ppt_ch(ch)) + pr_ipc_msg(log_info.ppt_msg, ch, ts, prefix, msg, msg_len); + else { + mif_err("unknown ipc pkt (channel=%d)\n", ch); + pr_ipc_msg(1, ch, ts, prefix, msg, msg_len); + } +} + +void pr_ipc(int level, const char *tag, const char *data, size_t len) +{ + struct utc_time utc; + unsigned char str[128]; + + if (level < 0) + return; + + get_utc_time(&utc); + dump2hex(str, data, (len > 32 ? 32 : len)); + if (level > 0) { + pr_err("%s: [%02d:%02d:%02d.%03d] %s: %s\n", MIF_TAG, + utc.hour, utc.min, utc.sec, utc.msec, tag, str); + } else { + pr_info("%s: [%02d:%02d:%02d.%03d] %s: %s\n", MIF_TAG, + utc.hour, utc.min, utc.sec, utc.msec, tag, str); + } +} + +/* print buffer as hex string */ +int pr_buffer(const char *tag, const char *data, size_t data_len, + size_t max_len) +{ + size_t len = min(data_len, max_len); + unsigned char str[len ? len * 3 : 1]; /* 1 <= sizeof <= max_len*3 */ + + dump2hex(str, data, len); + + /* don't change this printk to mif_debug for print this as level7 */ + return pr_info("%s: %s(%lu): %s%s\n", MIF_TAG, tag, data_len, + str, (len == data_len) ? "" : " ..."); +} + +/* flow control CM from CP, it use in serial devices */ +int link_rx_flowctl_cmd(struct link_device *ld, const char *data, size_t len) +{ + struct modem_shared *msd = ld->msd; + unsigned short *cmd, *end = (unsigned short *)(data + len); + + mif_debug("flow control cmd: size=%ld\n", len); + + for (cmd = (unsigned short *)data; cmd < end; cmd++) { + switch (*cmd) { + case CMD_SUSPEND: + iodevs_for_each(msd, iodev_netif_stop, 0); + ld->raw_tx_suspended = true; + mif_info("flowctl CMD_SUSPEND(%04X)\n", *cmd); + break; + + case CMD_RESUME: + iodevs_for_each(msd, iodev_netif_wake, 0); + ld->raw_tx_suspended = false; + complete_all(&ld->raw_tx_resumed_by_cp); + mif_info("flowctl CMD_RESUME(%04X)\n", *cmd); + break; + + default: + mif_err("flowctl BAD CMD: %04X\n", *cmd); + break; + } + } + + return 0; +} + +struct io_device *get_iod_with_channel(struct modem_shared *msd, + unsigned int channel) +{ + struct rb_node *n = msd->iodevs_tree_chan.rb_node; + struct io_device *iodev; + while (n) { + iodev = rb_entry(n, struct io_device, node_chan); + if (channel < iodev->id) + n = n->rb_left; + else if (channel > iodev->id) + n = n->rb_right; + else + return iodev; + } + return NULL; +} + +struct io_device *get_iod_with_format(struct modem_shared *msd, + enum dev_format format) +{ + struct rb_node *n = msd->iodevs_tree_fmt.rb_node; + struct io_device *iodev; + while (n) { + iodev = rb_entry(n, struct io_device, node_fmt); + if (format < iodev->format) + n = n->rb_left; + else if (format > iodev->format) + n = n->rb_right; + else + return iodev; + } + return NULL; +} + +struct io_device *insert_iod_with_channel(struct modem_shared *msd, + unsigned int channel, struct io_device *iod) +{ + struct rb_node **p = &msd->iodevs_tree_chan.rb_node; + struct rb_node *parent = NULL; + struct io_device *iodev; + while (*p) { + parent = *p; + iodev = rb_entry(parent, struct io_device, node_chan); + if (channel < iodev->id) + p = &(*p)->rb_left; + else if (channel > iodev->id) + p = &(*p)->rb_right; + else + return iodev; + } + rb_link_node(&iod->node_chan, parent, p); + rb_insert_color(&iod->node_chan, &msd->iodevs_tree_chan); + return NULL; +} + +struct io_device *insert_iod_with_format(struct modem_shared *msd, + enum dev_format format, struct io_device *iod) +{ + struct rb_node **p = &msd->iodevs_tree_fmt.rb_node; + struct rb_node *parent = NULL; + struct io_device *iodev; + while (*p) { + parent = *p; + iodev = rb_entry(parent, struct io_device, node_fmt); + if (format < iodev->format) + p = &(*p)->rb_left; + else if (format > iodev->format) + p = &(*p)->rb_right; + else + return iodev; + } + rb_link_node(&iod->node_fmt, parent, p); + rb_insert_color(&iod->node_fmt, &msd->iodevs_tree_fmt); + return NULL; +} + +void iodevs_for_each(struct modem_shared *msd, action_fn action, void *args) +{ + struct io_device *iod; + struct rb_node *node = rb_first(&msd->iodevs_tree_chan); + for (; node; node = rb_next(node)) { + iod = rb_entry(node, struct io_device, node_chan); + action(iod, args); + } +} + +void iodev_netif_wake(struct io_device *iod, void *args) +{ + if (iod->io_typ == IODEV_NET && iod->ndev) { + netif_wake_queue(iod->ndev); + mif_info("%s\n", iod->name); + } +} + +void iodev_netif_stop(struct io_device *iod, void *args) +{ + if (iod->io_typ == IODEV_NET && iod->ndev) { + netif_stop_queue(iod->ndev); + mif_info("%s\n", iod->name); + } +} + +static void iodev_set_tx_link(struct io_device *iod, void *args) +{ + struct link_device *ld = (struct link_device *)args; + if (iod->format == IPC_RAW && IS_CONNECTED(iod, ld)) { + set_current_link(iod, ld); + mif_err("%s -> %s\n", iod->name, ld->name); + } +} + +void rawdevs_set_tx_link(struct modem_shared *msd, enum modem_link link_type) +{ + struct link_device *ld = find_linkdev(msd, link_type); + if (ld) + iodevs_for_each(msd, iodev_set_tx_link, ld); +} + +void mif_netif_stop(struct link_device *ld) +{ + struct io_device *iod; + + iod = link_get_iod_with_channel(ld, RMNET0_CH_ID); + if (iod) + iodevs_for_each(iod->msd, iodev_netif_stop, 0); +} + +void mif_netif_wake(struct link_device *ld) +{ + struct io_device *iod; + + /** + * If ld->suspend_netif_tx is true, this means that there was a SUSPEND + * flow control command from CP so MIF must wait for a RESUME command + * from CP. + */ + if (ld->suspend_netif_tx) { + mif_info("%s: waiting for FLOW_CTRL_RESUME\n", ld->name); + return; + } + + iod = link_get_iod_with_channel(ld, RMNET0_CH_ID); + if (iod) + iodevs_for_each(iod->msd, iodev_netif_wake, 0); +} + +/** + * ipv4str_to_be32 - ipv4 string to be32 (big endian 32bits integer) + * @return: return zero when errors occurred + */ +__be32 ipv4str_to_be32(const char *ipv4str, size_t count) +{ + unsigned char ip[4]; + char ipstr[16]; /* == strlen("xxx.xxx.xxx.xxx") + 1 */ + char *next = ipstr; + char *p; + int i; + + memmove(ipstr, ipv4str, ARRAY_SIZE(ipstr)); + + for (i = 0; i < 4; i++) { + p = strsep(&next, "."); + if (kstrtou8(p, 10, &ip[i]) < 0) + return 0; /* == 0.0.0.0 */ + } + + return *((__be32 *)ip); +} + +void mif_add_timer(struct timer_list *timer, unsigned long expire, + void (*function)(unsigned long), unsigned long data) +{ + if (timer_pending(timer)) + return; + + init_timer(timer); + timer->expires = get_jiffies_64() + expire; + timer->function = function; + timer->data = data; + add_timer(timer); +} + +/** + * std_udl_get_cmd + * @frm: pointer to an SIPC5 link frame + * + * Returns the standard BOOT/DUMP (STD_UDL) command in an SIPC5 BOOT/DUMP frame. + */ +u32 std_udl_get_cmd(u8 *frm) +{ + u8 *cmd = frm + EXYNOS_HEADER_SIZE; + return *((u32 *)cmd); +} + +/** + * std_udl_with_payload + * @cmd: standard BOOT/DUMP command + * + * Returns true if the STD_UDL command has a payload. + */ +bool std_udl_with_payload(u32 cmd) +{ + u32 mask = cmd & STD_UDL_STEP_MASK; + return (mask && mask < STD_UDL_CRC) ? true : false; +} +void mif_print_data(const char *buff, int len) +{ + int words = len >> 4; + int residue = len - (words << 4); + int i; + char *b; + char last[80]; + char tb[8]; + + /* Make the last line, if ((len % 16) > 0) */ + if (residue > 0) { + snprintf(last, ARRAY_SIZE(last), "%04X: ", (words << 4)); + b = (char *)buff + (words << 4); + for (i = 0; i < residue; i++) { + snprintf(tb, ARRAY_SIZE(tb), "%02x ", b[i]); + strncat(last, tb, strlen(tb)); + if ((i & 0x3) == 0x3) { + snprintf(tb, ARRAY_SIZE(tb), " "); + strncat(last, tb, ARRAY_SIZE(tb)); + } + } + } + + for (i = 0; i < words; i++) { + b = (char *)buff + (i << 4); + mif_err("%04X: %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n", + (i << 4), + b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7], + b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15]); + } + + /* Print the last line */ + if (residue > 0) + mif_err("%s\n", last); +} + +void mif_dump2format16(const char *data, int len, char *buff, char *tag) +{ + char *d; + int i; + int words = len >> 4; + int residue = len - (words << 4); + char line[LINE_BUFF_SIZE]; + char tb[8]; + + for (i = 0; i < words; i++) { + memset(line, 0, LINE_BUFF_SIZE); + d = (char *)data + (i << 4); + + if (tag) + snprintf(line, LINE_BUFF_SIZE, + "%s%04X| %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n", + tag, (i << 4), + d[0], d[1], d[2], d[3], + d[4], d[5], d[6], d[7], + d[8], d[9], d[10], d[11], + d[12], d[13], d[14], d[15]); + else + snprintf(line, LINE_BUFF_SIZE, + "%04X| %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n", + (i << 4), + d[0], d[1], d[2], d[3], + d[4], d[5], d[6], d[7], + d[8], d[9], d[10], d[11], + d[12], d[13], d[14], d[15]); + + strncat(buff, line, strlen(line)); + } + + /* Make the last line, if (len % 16) > 0 */ + if (residue > 0) { + memset(line, 0, LINE_BUFF_SIZE); + memset(tb, 0, sizeof(tb)); + d = (char *)data + (words << 4); + + if (tag) + snprintf(line, LINE_BUFF_SIZE, "%s%04X|", tag, + (words << 4)); + else + snprintf(line, LINE_BUFF_SIZE, "%04X|", (words << 4)); + + for (i = 0; i < residue; i++) { + snprintf(tb, ARRAY_SIZE(tb), " %02x", d[i]); + strncat(line, tb, strlen(tb)); + if ((i & 0x3) == 0x3) { + snprintf(tb, ARRAY_SIZE(tb), " "); + strncat(line, tb, strlen(tb)); + } + } + strncat(line, "\n", strlen("\n")); + + strncat(buff, line, strlen(line)); + } +} + +void mif_dump2format4(const char *data, int len, char *buff, char *tag) +{ + char *d; + int i; + int words = len >> 2; + int residue = len - (words << 2); + char line[LINE_BUFF_SIZE]; + char tb[8]; + + for (i = 0; i < words; i++) { + memset(line, 0, LINE_BUFF_SIZE); + d = (char *)data + (i << 2); + + if (tag) + snprintf(line, LINE_BUFF_SIZE, + "%s%04X| %02x %02x %02x %02x\n", + tag, (i << 2), d[0], d[1], d[2], d[3]); + else + snprintf(line, LINE_BUFF_SIZE, + "%04X| %02x %02x %02x %02x\n", + (i << 2), d[0], d[1], d[2], d[3]); + + strncat(buff, line, strlen(line)); + } + + /* Make the last line, if (len % 4) > 0 */ + if (residue > 0) { + memset(line, 0, LINE_BUFF_SIZE); + memset(tb, 0, sizeof(tb)); + d = (char *)data + (words << 2); + + if (tag) + snprintf(line, LINE_BUFF_SIZE, "%s%04X|", tag, + (words << 2)); + else + snprintf(line, LINE_BUFF_SIZE, "%04X|", (words << 2)); + + for (i = 0; i < residue; i++) { + snprintf(tb, ARRAY_SIZE(tb), " %02x", d[i]); + strncat(line, tb, strlen(tb)); + } + strncat(line, "\n", strlen("\n")); + + strncat(buff, line, strlen(line)); + } +} + +void mif_print_dump(const char *data, int len, int width) +{ + char *buff; + + buff = kzalloc(len << 3, GFP_ATOMIC); + if (!buff) { + mif_err("ERR! kzalloc fail\n"); + return; + } + + if (width == 16) + mif_dump2format16(data, len, buff, LOG_TAG); + else + mif_dump2format4(data, len, buff, LOG_TAG); + + pr_info("%s", buff); + + kfree(buff); +} + +static void strcat_tcp_header(char *buff, u8 *pkt) +{ + struct tcphdr *tcph = (struct tcphdr *)pkt; + int eol; + char line[LINE_BUFF_SIZE] = {0, }; + char flag_str[32] = {0, }; + +/*------------------------------------------------------------------------- + * + * TCP Header Format + * + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Source Port | Destination Port | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Sequence Number | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Acknowledgment Number | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Data | |C|E|U|A|P|R|S|F| | + * | Offset| Rsvd |W|C|R|C|S|S|Y|I| Window | + * | | |R|E|G|K|H|T|N|N| | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Checksum | Urgent Pointer | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Options | Padding | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | data | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + *------------------------------------------------------------------------- + */ + + snprintf(line, LINE_BUFF_SIZE, + "%s: TCP:: Src.Port %u, Dst.Port %u\n", + MIF_TAG, ntohs(tcph->source), ntohs(tcph->dest)); + strncat(buff, line, strlen(line)); + + snprintf(line, LINE_BUFF_SIZE, + "%s: TCP:: SEQ 0x%08X(%u), ACK 0x%08X(%u)\n", + MIF_TAG, ntohs(tcph->seq), ntohs(tcph->seq), + ntohs(tcph->ack_seq), ntohs(tcph->ack_seq)); + strncat(buff, line, strlen(line)); + + if (tcph->cwr) + strncat(flag_str, "CWR ", strlen("CWR ")); + if (tcph->ece) + strncat(flag_str, "ECE", strlen("ECE")); + if (tcph->urg) + strncat(flag_str, "URG ", strlen("URG ")); + if (tcph->ack) + strncat(flag_str, "ACK ", strlen("ACK ")); + if (tcph->psh) + strncat(flag_str, "PSH ", strlen("PSH ")); + if (tcph->rst) + strncat(flag_str, "RST ", strlen("RST ")); + if (tcph->syn) + strncat(flag_str, "SYN ", strlen("SYN ")); + if (tcph->fin) + strncat(flag_str, "FIN ", strlen("FIN ")); + eol = strlen(flag_str) - 1; + if (eol > 0) + flag_str[eol] = 0; + snprintf(line, LINE_BUFF_SIZE, "%s: TCP:: Flags {%s}\n", + MIF_TAG, flag_str); + strncat(buff, line, strlen(line)); + + snprintf(line, LINE_BUFF_SIZE, + "%s: TCP:: Window %u, Checksum 0x%04X, Urgent %u\n", MIF_TAG, + ntohs(tcph->window), ntohs(tcph->check), ntohs(tcph->urg_ptr)); + strncat(buff, line, strlen(line)); +} + +static void strcat_udp_header(char *buff, u8 *pkt) +{ + struct udphdr *udph = (struct udphdr *)pkt; + char line[LINE_BUFF_SIZE] = {0, }; + +/*------------------------------------------------------------------------- + * + * UDP Header Format + * + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Source Port | Destination Port | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Length | Checksum | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | data | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + *------------------------------------------------------------------------- + */ + + snprintf(line, LINE_BUFF_SIZE, + "%s: UDP:: Src.Port %u, Dst.Port %u\n", + MIF_TAG, ntohs(udph->source), ntohs(udph->dest)); + strncat(buff, line, strlen(line)); + + snprintf(line, LINE_BUFF_SIZE, + "%s: UDP:: Length %u, Checksum 0x%04X\n", + MIF_TAG, ntohs(udph->len), ntohs(udph->check)); + strncat(buff, line, strlen(line)); + + if (ntohs(udph->dest) == 53) { + snprintf(line, LINE_BUFF_SIZE, "%s: UDP:: DNS query!!!\n", + MIF_TAG); + strncat(buff, line, strlen(line)); + } + + if (ntohs(udph->source) == 53) { + snprintf(line, LINE_BUFF_SIZE, "%s: UDP:: DNS response!!!\n", + MIF_TAG); + strncat(buff, line, strlen(line)); + } +} + +void print_ip4_packet(const u8 *ip_pkt, bool tx) +{ + char *buff; + struct iphdr *iph = (struct iphdr *)ip_pkt; + u8 *pkt = (u8 *)ip_pkt + (iph->ihl << 2); + u16 flags = (ntohs(iph->frag_off) & 0xE000); + u16 frag_off = (ntohs(iph->frag_off) & 0x1FFF); + int eol; + char line[LINE_BUFF_SIZE] = {0, }; + char flag_str[16] = {0, }; + +/*--------------------------------------------------------------------------- + * IPv4 Header Format + * + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * |Version| IHL |Type of Service| Total Length | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Identification |C|D|M| Fragment Offset | + * | |E|F|F| | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Time to Live | Protocol | Header Checksum | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Source Address | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Destination Address | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Options | Padding | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * IHL - Header Length + * Flags - Consist of 3 bits + * The 1st bit is "Congestion" bit. + * The 2nd bit is "Dont Fragment" bit. + * The 3rd bit is "More Fragments" bit. + * + *--------------------------------------------------------------------------- + */ + + if (iph->version != 4) + return; + + buff = kzalloc(4096, GFP_ATOMIC); + if (!buff) + return; + + if (tx) + snprintf(line, LINE_BUFF_SIZE, "\n%s\n", TX_SEPARATOR); + else + snprintf(line, LINE_BUFF_SIZE, "\n%s\n", RX_SEPARATOR); + strncat(buff, line, strlen(line)); + + snprintf(line, LINE_BUFF_SIZE, "%s\n", LINE_SEPARATOR); + strncat(buff, line, strlen(line)); + + snprintf(line, LINE_BUFF_SIZE, + "%s: IP4:: Version %u, Header Length %u, TOS %u, Length %u\n", + MIF_TAG, iph->version, (iph->ihl << 2), iph->tos, + ntohs(iph->tot_len)); + strncat(buff, line, strlen(line)); + + snprintf(line, LINE_BUFF_SIZE, "%s: IP4:: ID %u, Fragment Offset %u\n", + MIF_TAG, ntohs(iph->id), frag_off); + strncat(buff, line, strlen(line)); + + if (flags & IP_CE) + strncat(flag_str, "CE ", strlen("CE ")); + if (flags & IP_DF) + strncat(flag_str, "DF ", strlen("DF ")); + if (flags & IP_MF) + strncat(flag_str, "MF ", strlen("MF ")); + eol = strlen(flag_str) - 1; + if (eol > 0) + flag_str[eol] = 0; + snprintf(line, LINE_BUFF_SIZE, "%s: IP4:: Flags {%s}\n", + MIF_TAG, flag_str); + strncat(buff, line, strlen(line)); + + snprintf(line, LINE_BUFF_SIZE, + "%s: IP4:: TTL %u, Protocol %u, Header Checksum 0x%04X\n", + MIF_TAG, iph->ttl, iph->protocol, ntohs(iph->check)); + strncat(buff, line, strlen(line)); + + snprintf(line, LINE_BUFF_SIZE, + "%s: IP4:: Src.IP %u.%u.%u.%u, Dst.IP %u.%u.%u.%u\n", + MIF_TAG, ip_pkt[12], ip_pkt[13], ip_pkt[14], ip_pkt[15], + ip_pkt[16], ip_pkt[17], ip_pkt[18], ip_pkt[19]); + strncat(buff, line, strlen(line)); + + switch (iph->protocol) { + case 6: /* TCP */ + strcat_tcp_header(buff, pkt); + break; + + case 17: /* UDP */ + strcat_udp_header(buff, pkt); + break; + + default: + break; + } + + snprintf(line, LINE_BUFF_SIZE, "%s\n", LINE_SEPARATOR); + strncat(buff, line, strlen(line)); + + pr_info("%s", buff); + + kfree(buff); +} + +bool is_dns_packet(const u8 *ip_pkt) +{ + struct iphdr *iph = (struct iphdr *)ip_pkt; + struct udphdr *udph = (struct udphdr *)(ip_pkt + (iph->ihl << 2)); + + /* If this packet is not a UDP packet, return here. */ + if (iph->protocol != 17) + return false; + + if (ntohs(udph->dest) == 53 || ntohs(udph->source) == 53) + return true; + else + return false; +} + +bool is_syn_packet(const u8 *ip_pkt) +{ + struct iphdr *iph = (struct iphdr *)ip_pkt; + struct tcphdr *tcph = (struct tcphdr *)(ip_pkt + (iph->ihl << 2)); + + /* If this packet is not a TCP packet, return here. */ + if (iph->protocol != 6) + return false; + + if (tcph->syn || tcph->fin) + return true; + else + return false; +} + +void mif_init_irq(struct modem_irq *irq, unsigned int num, const char *name, + unsigned long flags) +{ + spin_lock_init(&irq->lock); + irq->num = num; + memmove(irq->name, name, MIF_MAX_NAME_LEN); + irq->flags = flags; + mif_err("name:%s num:%d flags:0x%08lX\n", name, num, flags); +} + +int mif_request_irq(struct modem_irq *irq, irq_handler_t isr, void *data) +{ + int ret; + + ret = request_irq(irq->num, isr, irq->flags, irq->name, data); + if (ret) { + mif_err("%s: ERR! request_irq fail (%d)\n", irq->name, ret); + return ret; + } + + enable_irq_wake(irq->num); + + irq->active = true; + + mif_err("%s(#%d) handler registered\n", irq->name, irq->num); + + return 0; +} + +void mif_enable_irq(struct modem_irq *irq) +{ + unsigned long flags; + + spin_lock_irqsave(&irq->lock, flags); + + if (irq->active) + goto exit; + + enable_irq(irq->num); + irq->active = true; + +exit: + spin_unlock_irqrestore(&irq->lock, flags); +} + +void mif_disable_irq(struct modem_irq *irq) +{ + unsigned long flags; + + spin_lock_irqsave(&irq->lock, flags); + + if (!irq->active) + goto exit; + + disable_irq_nosync(irq->num); + irq->active = false; + +exit: + spin_unlock_irqrestore(&irq->lock, flags); +} + +int mif_test_dpram(char *dp_name, void __iomem *start, u16 bytes) +{ + u16 i; + u16 words = bytes >> 1; + u16 __iomem *dst = (u16 __iomem *)start; + u16 val; + int err_cnt = 0; + + mif_err("%s: start 0x%p, bytes %d\n", dp_name, start, bytes); + + mif_err("%s: 0/6 stage ...\n", dp_name); + for (i = 1; i <= 100; i++) { + iowrite16(0x1234, dst); + val = ioread16(dst); + if (val != 0x1234) { + mif_err("%s: [0x0000] read 0x%04X != written 0x1234 (try# %d)\n", + dp_name, val, i); + err_cnt++; + } + } + + if (err_cnt > 0) { + mif_err("%s: FAIL!!!\n", dp_name); + return -EINVAL; + } + + mif_err("%s: 1/6 stage ...\n", dp_name); + dst = (u16 __iomem *)start; + for (i = 0; i < words; i++) { + iowrite16(0, dst); + dst++; + } + + dst = (u16 __iomem *)start; + for (i = 0; i < words; i++) { + val = ioread16(dst); + if (val != 0x0000) { + mif_err("%s: ERR! [0x%04X] read 0x%04X != written 0x0000\n", + dp_name, i, val); + err_cnt++; + } + dst++; + } + + if (err_cnt > 0) { + mif_err("%s: FAIL!!!\n", dp_name); + return -EINVAL; + } + + mif_err("%s: 2/6 stage ...\n", dp_name); + dst = (u16 __iomem *)start; + for (i = 0; i < words; i++) { + iowrite16(0xFFFF, dst); + dst++; + } + + dst = (u16 __iomem *)start; + for (i = 0; i < words; i++) { + val = ioread16(dst); + if (val != 0xFFFF) { + mif_err("%s: ERR! [0x%04X] read 0x%04X != written 0xFFFF\n", + dp_name, i, val); + err_cnt++; + } + dst++; + } + + if (err_cnt > 0) { + mif_err("%s: FAIL!!!\n", dp_name); + return -EINVAL; + } + + mif_err("%s: 3/6 stage ...\n", dp_name); + dst = (u16 __iomem *)start; + for (i = 0; i < words; i++) { + iowrite16(0x00FF, dst); + dst++; + } + + dst = (u16 __iomem *)start; + for (i = 0; i < words; i++) { + val = ioread16(dst); + if (val != 0x00FF) { + mif_err("%s: ERR! [0x%04X] read 0x%04X != written 0x00FF\n", + dp_name, i, val); + err_cnt++; + } + dst++; + } + + if (err_cnt > 0) { + mif_err("%s: FAIL!!!\n", dp_name); + return -EINVAL; + } + + mif_err("%s: 4/6 stage ...\n", dp_name); + dst = (u16 __iomem *)start; + for (i = 0; i < words; i++) { + iowrite16(0x0FF0, dst); + dst++; + } + + dst = (u16 __iomem *)start; + for (i = 0; i < words; i++) { + val = ioread16(dst); + if (val != 0x0FF0) { + mif_err("%s: ERR! [0x%04X] read 0x%04X != written 0x0FF0\n", + dp_name, i, val); + err_cnt++; + } + dst++; + } + + if (err_cnt > 0) { + mif_err("%s: FAIL!!!\n", dp_name); + return -EINVAL; + } + + mif_err("%s: 5/6 stage ...\n", dp_name); + dst = (u16 __iomem *)start; + for (i = 0; i < words; i++) { + iowrite16(0xFF00, dst); + dst++; + } + + dst = (u16 __iomem *)start; + for (i = 0; i < words; i++) { + val = ioread16(dst); + if (val != 0xFF00) { + mif_err("%s: ERR! [0x%04X] read 0x%04X != written 0xFF00\n", + dp_name, i, val); + err_cnt++; + } + dst++; + } + + mif_err("%s: 6/6 stage ...\n", dp_name); + dst = (u16 __iomem *)start; + for (i = 0; i < words; i++) { + iowrite16((i & 0xFFFF), dst); + dst++; + } + + dst = (u16 __iomem *)start; + for (i = 0; i < words; i++) { + val = ioread16(dst); + if (val != (i & 0xFFFF)) { + mif_err("%s: ERR! [0x%04X] read 0x%04X != written 0x%04X\n", + dp_name, i, val, (i & 0xFFFF)); + err_cnt++; + } + dst++; + } + + if (err_cnt > 0) { + mif_err("%s: FAIL!!!\n", dp_name); + return -EINVAL; + } + + mif_err("%s: PASS!!!\n", dp_name); + + dst = (u16 __iomem *)start; + for (i = 0; i < words; i++) { + iowrite16(0, dst); + dst++; + } + + return 0; +} + +struct file *mif_open_file(const char *path) +{ + struct file *fp; + mm_segment_t old_fs; + + old_fs = get_fs(); + set_fs(get_ds()); + + fp = filp_open(path, O_RDWR|O_CREAT|O_APPEND, 0666); + + set_fs(old_fs); + + if (IS_ERR(fp)) + return NULL; + + return fp; +} + +void mif_save_file(struct file *fp, const char *buff, size_t size) +{ + int ret; + mm_segment_t old_fs; + + old_fs = get_fs(); + set_fs(get_ds()); + + ret = fp->f_op->write(fp, buff, size, &fp->f_pos); + if (ret < 0) + mif_err("ERR! write fail\n"); + + set_fs(old_fs); +} + +void mif_close_file(struct file *fp) +{ + mm_segment_t old_fs; + + old_fs = get_fs(); + set_fs(get_ds()); + + filp_close(fp, NULL); + + set_fs(old_fs); +} + diff --git a/drivers/misc/modem_if/modem_utils.h b/drivers/misc/modem_if/modem_utils.h new file mode 100644 index 000000000000..d9ca2f452ce7 --- /dev/null +++ b/drivers/misc/modem_if/modem_utils.h @@ -0,0 +1,419 @@ +/* + * Copyright (C) 2011 Samsung Electronics. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __MODEM_UTILS_H__ +#define __MODEM_UTILS_H__ + +#include +#include "modem_prj.h" + +#define IS_CONNECTED(iod, ld) ((iod)->link_types & LINKTYPE((ld)->link_type)) + +#define MAX_MIF_BUFF_SIZE 0x80000 /* 512kb */ +#define MAX_MIF_SEPA_SIZE 32 +#define MIF_SEPARATOR "IPC_LOGGER(VER1.1)" +#define MIF_SEPARATOR_DPRAM "DPRAM_LOGGER(VER1.1)" +#define MAX_IPC_SKB_SIZE 4096 +#define MAX_LOG_SIZE 64 + +#define MIF_TAG "mif" + +#define MAX_LOG_CNT (MAX_MIF_BUFF_SIZE / MAX_LOG_SIZE) +#define MIF_ID_SIZE sizeof(enum mif_log_id) + +#define MAX_IPC_LOG_SIZE \ + (MAX_LOG_SIZE - sizeof(enum mif_log_id) \ + - sizeof(unsigned long long) - sizeof(size_t)) +#define MAX_IRQ_LOG_SIZE \ + (MAX_LOG_SIZE - sizeof(enum mif_log_id) \ + - sizeof(unsigned long long) - sizeof(struct mif_irq_map)) +#define MAX_COM_LOG_SIZE \ + (MAX_LOG_SIZE - sizeof(enum mif_log_id) \ + - sizeof(unsigned long long)) +#define MAX_TIM_LOG_SIZE \ + (MAX_LOG_SIZE - sizeof(enum mif_log_id) \ + - sizeof(unsigned long long) - sizeof(struct timespec)) + +enum mif_log_id { + MIF_IPC_RL2AP = 1, + MIF_IPC_AP2CP, + MIF_IPC_CP2AP, + MIF_IPC_AP2RL, + MIF_IRQ, + MIF_COM, + MIF_TIME +}; + +enum ipc_layer { + LINK, + IODEV, + APP, + MAX_EXYNOS_LAYER +}; + +struct mif_irq_map { + u16 magic; + u16 access; + + u16 fmt_tx_in; + u16 fmt_tx_out; + u16 fmt_rx_in; + u16 fmt_rx_out; + + u16 raw_tx_in; + u16 raw_tx_out; + u16 raw_rx_in; + u16 raw_rx_out; + + u16 cp2ap; +}; + +struct mif_ipc_block { + enum mif_log_id id; + unsigned long long time; + size_t len; + char buff[MAX_IPC_LOG_SIZE]; +}; + +struct mif_irq_block { + enum mif_log_id id; + unsigned long long time; + struct mif_irq_map map; + char buff[MAX_IRQ_LOG_SIZE]; +}; + +struct mif_common_block { + enum mif_log_id id; + unsigned long long time; + char buff[MAX_COM_LOG_SIZE]; +}; + +struct mif_time_block { + enum mif_log_id id; + unsigned long long time; + struct timespec epoch; + char buff[MAX_TIM_LOG_SIZE]; +}; + +struct mif_log { + u8 fmt_msg; + u8 boot_msg; + u8 dump_msg; + u8 rfs_msg; + u8 log_msg; + u8 ps_msg; + u8 router_msg; + u8 rcs_msg; + u8 ppt_msg; + u8 debug_log; +} __attribute__((packed)); + +extern struct mif_log log_info; + +static inline int ns2us(long ns) +{ + return (ns > 0) ? (ns / 1000) : 0; +} + +static inline int ns2ms(long ns) +{ + return (ns > 0) ? (ns / 1000000) : 0; +} + +static const char * const exynos_layer_string[] = { + [LINK] = "LNK", + [IODEV] = "IOD", + [APP] = "APP", + [MAX_EXYNOS_LAYER] = "INVALID" +}; + +static const inline char *layer_str(enum ipc_layer layer) +{ + if (unlikely(layer >= MAX_EXYNOS_LAYER)) + return "INVALID"; + else + return exynos_layer_string[layer]; +} + +static const inline char *dev_str(enum dev_format dev) +{ + if (unlikely(dev >= MAX_DEV_FORMAT)) + return "INVALID"; + else + return dev_format_str[dev]; +} + +static inline enum direction direction_opposite(enum direction dir) +{ + return (dir == TX) ? RX : TX; +} + +static const char * const direction_string[] = { + [TX] = "TX", + [RX] = "RX" +}; + +static const inline char *dir_str(enum direction dir) +{ + if (unlikely(dir >= MAX_DIR)) + return "INVALID"; + else + return direction_string[dir]; +} + +static const char * const ipc_direction_string[] = { + [TX] = "AP->CP", + [RX] = "AP<-CP" +}; + +static const inline char *ipc_dir(enum direction dir) +{ + if (unlikely(dir >= MAX_DIR)) + return "INVALID"; + else + return ipc_direction_string[dir]; +} + +static const char * const arrow_direction[] = { + [TX] = "->", + [RX] = "<-" +}; + +static const inline char *arrow(enum direction dir) +{ + if (unlikely(dir >= MAX_DIR)) + return "><"; + else + return arrow_direction[dir]; +} + +/* {Hour, Minute, Second, U(micro)-second} format */ +#define HMSU_FMT "[%02d:%02d:%02d.%06d]" + +void ts2utc(struct timespec *ts, struct utc_time *utc); +void get_utc_time(struct utc_time *utc); + +int mif_dump_log(struct modem_shared *msd, struct io_device *iod); + +#define mif_irq_log(msd, map, data, len) \ + _mif_irq_log(MIF_IRQ, msd, map, data, len) +#define mif_com_log(msd, format, ...) \ + _mif_com_log(MIF_COM, msd, pr_fmt(format), ##__VA_ARGS__) +#define mif_time_log(msd, epoch, data, len) \ + _mif_time_log(MIF_TIME, msd, epoch, data, len) + +void mif_ipc_log(enum mif_log_id id, + struct modem_shared *msd, const char *data, size_t len); +void _mif_irq_log(enum mif_log_id id, struct modem_shared *msd, + struct mif_irq_map map, const char *data, size_t len); +void _mif_com_log(enum mif_log_id id, + struct modem_shared *msd, const char *format, ...); +void _mif_time_log(enum mif_log_id id, struct modem_shared *msd, + struct timespec epoch, const char *data, size_t len); + +/** find_linkdev - find a link device + * @msd: struct modem_shared * + */ +static inline struct link_device *find_linkdev(struct modem_shared *msd, + enum modem_link link_type) +{ + struct link_device *ld; + list_for_each_entry(ld, &msd->link_dev_list, list) { + if (ld->link_type == link_type) + return ld; + } + return NULL; +} + +/** countbits - count number of 1 bits as fastest way + * @n: number + */ +static inline unsigned int countbits(unsigned int n) +{ + unsigned int i; + for (i = 0; n != 0; i++) + n &= (n - 1); + return i; +} + +/* print IPC message packet */ +void log_ipc_pkt(struct sk_buff *skb, enum ipc_layer layer, enum direction dir); + +/* print IPC message as hex string with UTC time */ +void pr_ipc(int level, const char *tag, const char *data, size_t len); + +/* print buffer as hex string */ +int pr_buffer(const char *tag, const char *data, size_t data_len, + size_t max_len); + +/* print a sk_buff as hex string */ +#define pr_skb(tag, skb) \ + pr_buffer(tag, (char *)((skb)->data), (size_t)((skb)->len), (size_t)16) + +/* print a urb as hex string */ +#define pr_urb(tag, urb) \ + pr_buffer(tag, (char *)((urb)->transfer_buffer), \ + (size_t)((urb)->actual_length), (size_t)16) + +/* Stop/wake all TX queues in network interfaces */ +void mif_netif_stop(struct link_device *ld); +void mif_netif_wake(struct link_device *ld); + +/* flow control CMD from CP, it use in serial devices */ +int link_rx_flowctl_cmd(struct link_device *ld, const char *data, size_t len); + +/* get iod from tree functions */ + +struct io_device *get_iod_with_format(struct modem_shared *msd, + enum dev_format format); +struct io_device *get_iod_with_channel(struct modem_shared *msd, + unsigned int channel); + +static inline struct io_device *link_get_iod_with_format( + struct link_device *ld, enum dev_format format) +{ + struct io_device *iod = get_iod_with_format(ld->msd, format); + return (iod && IS_CONNECTED(iod, ld)) ? iod : NULL; +} + +static inline struct io_device *link_get_iod_with_channel( + struct link_device *ld, unsigned int channel) +{ + struct io_device *iod = get_iod_with_channel(ld->msd, channel); + return (iod && IS_CONNECTED(iod, ld)) ? iod : NULL; +} + +/* insert iod to tree functions */ +struct io_device *insert_iod_with_format(struct modem_shared *msd, + enum dev_format format, struct io_device *iod); +struct io_device *insert_iod_with_channel(struct modem_shared *msd, + unsigned int channel, struct io_device *iod); + +/* iodev for each */ +typedef void (*action_fn)(struct io_device *iod, void *args); +void iodevs_for_each(struct modem_shared *msd, action_fn action, void *args); + +/* netif wake/stop queue of iod */ +void iodev_netif_wake(struct io_device *iod, void *args); +void iodev_netif_stop(struct io_device *iod, void *args); + +/* change tx_link of raw devices */ +void rawdevs_set_tx_link(struct modem_shared *msd, enum modem_link link_type); + +__be32 ipv4str_to_be32(const char *ipv4str, size_t count); + +void mif_add_timer(struct timer_list *timer, unsigned long expire, + void (*function)(unsigned long), unsigned long data); + +/* debug helper functions for sipc4, sipc5 */ +void mif_print_data(const char *buff, int len); +void mif_dump2format16(const char *data, int len, char *buff, char *tag); +void mif_dump2format4(const char *data, int len, char *buff, char *tag); +void mif_print_dump(const char *data, int len, int width); + +/*--------------------------------------------------------------------------- + * + * IPv4 Header Format + * + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * |Version| IHL |Type of Service| Total Length | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Identification |C|D|M| Fragment Offset | + * | |E|F|F| | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Time to Live | Protocol | Header Checksum | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Source Address | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Destination Address | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Options | Padding | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * IHL - Header Length + * Flags - Consist of 3 bits + * The 1st bit is "Congestion" bit. + * The 2nd bit is "Dont Fragment" bit. + * The 3rd bit is "More Fragments" bit. + * + *--------------------------------------------------------------------------- + */ +#define IPV4_HDR_SIZE 20 + +/*------------------------------------------------------------------------- + * + * TCP Header Format + * + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Source Port | Destination Port | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Sequence Number | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Acknowledgment Number | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Data | |C|E|U|A|P|R|S|F| | + * | Offset| Rsvd |W|C|R|C|S|S|Y|I| Window | + * | | |R|E|G|K|H|T|N|N| | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Checksum | Urgent Pointer | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Options | Padding | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | data | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + *------------------------------------------------------------------------- + */ +#define TCP_HDR_SIZE 20 + +/*------------------------------------------------------------------------- + * + * UDP Header Format + * + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Source Port | Destination Port | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Length | Checksum | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | data | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + *------------------------------------------------------------------------- + */ +#define UDP_HDR_SIZE 8 + +void print_ip4_packet(const u8 *ip_pkt, bool tx); +bool is_dns_packet(const u8 *ip_pkt); +bool is_syn_packet(const u8 *ip_pkt); + +int mif_register_isr(unsigned int irq, irq_handler_t isr, unsigned long flags, + const char *name, void *data); +void mif_init_irq(struct modem_irq *irq, unsigned int num, const char *name, + unsigned long flags); +int mif_request_irq(struct modem_irq *irq, irq_handler_t isr, void *data); +void mif_enable_irq(struct modem_irq *irq); +void mif_disable_irq(struct modem_irq *irq); + +int mif_test_dpram(char *dp_name, void __iomem *start, u16 bytes); + +struct file *mif_open_file(const char *path); +void mif_save_file(struct file *fp, const char *buff, size_t size); +void mif_close_file(struct file *fp); + +#endif/*__MODEM_UTILS_H__*/ + diff --git a/drivers/misc/modem_if/modem_variation.h b/drivers/misc/modem_if/modem_variation.h new file mode 100644 index 000000000000..bf034e565eb9 --- /dev/null +++ b/drivers/misc/modem_if/modem_variation.h @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2010 Samsung Electronics. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __MODEM_VARIATION_H__ +#define __MODEM_VARIATION_H__ + +#include "include/modem_v1.h" + +#define DECLARE_LINK_INIT(type) \ + struct link_device *type ## _create_link_device( \ + struct platform_device *pdev) + +#define DECLARE_LINK_INIT_DUMMY(type) \ + static DECLARE_LINK_INIT(type) { return NULL; } + +#define LINK_INIT_CALL(type) type ## _create_link_device + +/* add declaration of modem link type */ +/* link device support */ +DECLARE_LINK_INIT_DUMMY(undefined) + +#ifdef CONFIG_LINK_DEVICE_MIPI +DECLARE_LINK_INIT(mipi); +#else +DECLARE_LINK_INIT_DUMMY(mipi) +#endif + +#ifdef CONFIG_LINK_DEVICE_HSIC +DECLARE_LINK_INIT(hsic); +#else +DECLARE_LINK_INIT_DUMMY(hsic) +#endif + +#ifdef CONFIG_LINK_DEVICE_DPRAM +DECLARE_LINK_INIT(dpram); +#else +DECLARE_LINK_INIT_DUMMY(dpram) +#endif + +#ifdef CONFIG_LINK_DEVICE_SHMEM +DECLARE_LINK_INIT(shmem); +#else +DECLARE_LINK_INIT_DUMMY(shmem) +#endif + +#ifdef CONFIG_LINK_DEVICE_SPI +DECLARE_LINK_INIT(spi); +#else +DECLARE_LINK_INIT_DUMMY(spi) +#endif + +typedef struct link_device *(*link_init_call)(struct platform_device *); +static link_init_call link_init_func[LINKDEV_MAX] = { + [LINKDEV_UNDEFINED] = LINK_INIT_CALL(undefined), + [LINKDEV_MIPI] = LINK_INIT_CALL(mipi), + [LINKDEV_HSIC] = LINK_INIT_CALL(hsic), + [LINKDEV_DPRAM] = LINK_INIT_CALL(dpram), + [LINKDEV_SHMEM] = LINK_INIT_CALL(shmem), + [LINKDEV_SPI] = LINK_INIT_CALL(spi), +}; + +static struct link_device *call_link_init_func(struct platform_device *pdev, + enum modem_link link_type) +{ + if (link_init_func[link_type]) + return link_init_func[link_type](pdev); + else + return NULL; +} + +int init_modemctl_device(struct modem_ctl *mc, struct modem_data *pdata); +#endif diff --git a/include/linux/modem_notifier.h b/include/linux/modem_notifier.h new file mode 100644 index 000000000000..cfe997155aa8 --- /dev/null +++ b/include/linux/modem_notifier.h @@ -0,0 +1,36 @@ +/* Copyright (C) 2015 Samsung Electronics. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __MODEM_NOTIFIER_H__ +#define __MODEM_NOTIFIER_H__ + +/* refer to enum modem_state */ +enum modem_event { + MODEM_EVENT_RESET = 1, + MODEM_EVENT_EXIT, + MODEM_EVENT_ONLINE = 4, + MODEM_EVENT_WATCHDOG = 9, +}; + +#if IS_ENABLED(CONFIG_SHM_IPC) +extern int register_modem_event_notifier(struct notifier_block *nb); +extern void modem_notify_event(enum modem_event evt); +#else +static inline int register_modem_event_notifier(struct notifier_block *nb) +{ + return 0; +} +static inline void modem_notify_event(enum modem_event evt) {} +#endif + +#endif/*__MODEM_NOTIFIER_H__*/ diff --git a/include/soc/samsung/exynos-modem-ctrl.h b/include/soc/samsung/exynos-modem-ctrl.h new file mode 100644 index 000000000000..1c1761493c76 --- /dev/null +++ b/include/soc/samsung/exynos-modem-ctrl.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2015 Samsung Electronics Co.Ltd + * http://www.samsung.com + * + * EXYNOS MODEM CONTROL driver + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#ifndef __EXYNOS_MODEM_CTRL_H +#define __EXYNOS_MODEM_CTRL_H + +#if defined(CONFIG_SEC_MODEM_IF) +extern int modem_force_crash_exit_ext(void); +extern int modem_send_panic_noti_ext(void); +#else /* CONFIG_SEC_MODEM_IF */ +static inline int modem_force_crash_exit_ext(void) { return 0; } +static inline int modem_send_panic_noti_ext(void) { return 0; } +#endif /* CONFIG_SEC_MODEM_IF */ + +#endif -- 2.20.1