3901 lines
97 KiB
C
Executable File
3901 lines
97 KiB
C
Executable File
/*
|
|
* 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 <linux/irq.h>
|
|
#include <linux/time.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/timer.h>
|
|
#include <linux/wakelock.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/wait.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/vmalloc.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/kallsyms.h>
|
|
#include <linux/suspend.h>
|
|
#include <linux/pm_qos.h>
|
|
#include <linux/reboot.h>
|
|
#include <linux/smc.h>
|
|
#include <linux/shm_ipc.h>
|
|
#include <linux/mcu_ipc.h>
|
|
#if defined(CONFIG_PCI_EXYNOS)
|
|
#include <linux/exynos-pci-ctrl.h>
|
|
#endif
|
|
#if defined(CONFIG_ECT)
|
|
#include <soc/samsung/ect_parser.h>
|
|
#endif
|
|
|
|
#ifdef CONFIG_CP_PMUCAL
|
|
#include <soc/samsung/cal-if.h>
|
|
#endif
|
|
|
|
#include "modem_prj.h"
|
|
#include "modem_utils.h"
|
|
#include "link_device_memory.h"
|
|
#include "link_ctrlmsg_iosm.h"
|
|
#include "modem_dump.h"
|
|
#include <linux/modem_notifier.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
|
|
|
|
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",
|
|
};
|
|
|
|
#ifdef GROUP_MEM_LINK_COMMAND
|
|
|
|
static inline void reset_ipc_map(struct mem_link_device *mld)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < MAX_SIPC_MAP; i++) {
|
|
struct mem_ipc_device *dev = mld->dev[i];
|
|
|
|
set_txq_head(dev, 0);
|
|
set_txq_tail(dev, 0);
|
|
set_rxq_head(dev, 0);
|
|
set_rxq_tail(dev, 0);
|
|
}
|
|
}
|
|
|
|
static int shmem_reset_ipc_link(struct mem_link_device *mld)
|
|
{
|
|
struct link_device *ld = &mld->link_dev;
|
|
unsigned int magic;
|
|
unsigned int access;
|
|
int i;
|
|
|
|
set_access(mld, 0);
|
|
set_magic(mld, 0);
|
|
|
|
reset_ipc_map(mld);
|
|
|
|
for (i = 0; i < MAX_SIPC_MAP; i++) {
|
|
struct mem_ipc_device *dev = mld->dev[i];
|
|
|
|
skb_queue_purge(dev->skb_txq);
|
|
atomic_set(&dev->txq.busy, 0);
|
|
dev->req_ack_cnt[TX] = 0;
|
|
|
|
skb_queue_purge(dev->skb_rxq);
|
|
atomic_set(&dev->rxq.busy, 0);
|
|
dev->req_ack_cnt[RX] = 0;
|
|
}
|
|
|
|
atomic_set(&ld->netif_stopped, 0);
|
|
|
|
set_magic(mld, MEM_IPC_MAGIC);
|
|
set_access(mld, 1);
|
|
|
|
magic = get_magic(mld);
|
|
access = get_access(mld);
|
|
if (magic != MEM_IPC_MAGIC || access != 1)
|
|
return -EACCES;
|
|
|
|
return 0;
|
|
}
|
|
|
|
#endif
|
|
|
|
#ifdef GROUP_MEM_LINK_DEVICE
|
|
|
|
static inline bool ipc_active(struct mem_link_device *mld)
|
|
{
|
|
struct link_device *ld = &mld->link_dev;
|
|
struct modem_ctl *mc = ld->mc;
|
|
|
|
if (unlikely(!cp_online(mc))) {
|
|
mif_err("%s<->%s: %s.state %s != ONLINE <%pf>\n",
|
|
ld->name, mc->name, mc->name, mc_state(mc), CALLER);
|
|
return false;
|
|
}
|
|
|
|
if (mld->dpram_magic) {
|
|
unsigned int magic = get_magic(mld);
|
|
unsigned int access = get_access(mld);
|
|
if (magic != MEM_IPC_MAGIC || access != 1) {
|
|
mif_err("%s<->%s: ERR! magic:0x%X access:%d <%pf>\n",
|
|
ld->name, mc->name, magic, access, CALLER);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (atomic_read(&mld->forced_cp_crash)) {
|
|
mif_err("%s<->%s: ERR! forced_cp_crash:%d <%pf>\n",
|
|
ld->name, mc->name, atomic_read(&mld->forced_cp_crash),
|
|
CALLER);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static inline void purge_txq(struct mem_link_device *mld)
|
|
{
|
|
struct link_device *ld = &mld->link_dev;
|
|
int i;
|
|
|
|
/* Purge the skb_txq in every rb */
|
|
if (ld->sbd_ipc) {
|
|
struct sbd_link_device *sl = &mld->sbd_link_dev;
|
|
|
|
for (i = 0; i < sl->num_channels; i++) {
|
|
struct sbd_ring_buffer *rb = sbd_id2rb(sl, i, TX);
|
|
skb_queue_purge(&rb->skb_q);
|
|
}
|
|
}
|
|
|
|
/* Purge the skb_txq in every IPC device
|
|
* (IPC_MAP_FMT, IPC_MAP_NORM_RAW, etc.)
|
|
*/
|
|
for (i = 0; i < MAX_SIPC_MAP; i++) {
|
|
struct mem_ipc_device *dev = mld->dev[i];
|
|
skb_queue_purge(dev->skb_txq);
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
#ifdef GROUP_MEM_CP_CRASH
|
|
|
|
static void set_modem_state(struct mem_link_device *mld, enum modem_state state)
|
|
{
|
|
struct link_device *ld = &mld->link_dev;
|
|
struct modem_ctl *mc = ld->mc;
|
|
unsigned long flags;
|
|
struct io_device *iod;
|
|
|
|
spin_lock_irqsave(&mc->lock, flags);
|
|
list_for_each_entry(iod, &mc->modem_state_notify_list, list)
|
|
iod->modem_state_changed(iod, state);
|
|
spin_unlock_irqrestore(&mc->lock, flags);
|
|
}
|
|
|
|
static void shmem_handle_cp_crash(struct mem_link_device *mld,
|
|
enum modem_state state)
|
|
{
|
|
struct link_device *ld = &mld->link_dev;
|
|
struct modem_ctl *mc = ld->mc;
|
|
|
|
#ifdef CONFIG_LINK_POWER_MANAGEMENT
|
|
if (mld->stop_pm)
|
|
mld->stop_pm(mld);
|
|
#endif
|
|
|
|
/* Disable normal IPC */
|
|
set_magic(mld, MEM_CRASH_MAGIC);
|
|
set_access(mld, 0);
|
|
|
|
stop_net_ifaces(ld);
|
|
purge_txq(mld);
|
|
|
|
if (cp_online(mc)) {
|
|
switch (state) {
|
|
case STATE_CRASH_RESET:
|
|
modem_notify_event(MODEM_EVENT_RESET);
|
|
break;
|
|
case STATE_CRASH_EXIT:
|
|
modem_notify_event(MODEM_EVENT_EXIT);
|
|
break;
|
|
case STATE_CRASH_WATCHDOG:
|
|
modem_notify_event(MODEM_EVENT_WATCHDOG);
|
|
break;
|
|
default:
|
|
mif_err("Invalid state to notify\n");
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (cp_online(mc) || cp_booting(mc))
|
|
set_modem_state(mld, state);
|
|
|
|
atomic_set(&mld->forced_cp_crash, 0);
|
|
|
|
clean_vss_magic_code();
|
|
}
|
|
|
|
static void handle_no_cp_crash_ack(unsigned long arg)
|
|
{
|
|
struct mem_link_device *mld = (struct mem_link_device *)arg;
|
|
struct link_device *ld = &mld->link_dev;
|
|
struct modem_ctl *mc = ld->mc;
|
|
|
|
if (cp_crashed(mc)) {
|
|
mif_debug("%s: STATE_CRASH_EXIT without CRASH_ACK\n",
|
|
ld->name);
|
|
} else {
|
|
mif_err("%s: ERR! No CRASH_ACK from CP\n", ld->name);
|
|
shmem_handle_cp_crash(mld, STATE_CRASH_EXIT);
|
|
}
|
|
}
|
|
|
|
static void shmem_forced_cp_crash(struct mem_link_device *mld,
|
|
u32 crash_reason_owner, char *crash_reason_string)
|
|
{
|
|
struct link_device *ld = &mld->link_dev;
|
|
struct modem_ctl *mc = ld->mc;
|
|
unsigned int ap_status = mc->mbx_ap_status;
|
|
|
|
/* Disable normal IPC */
|
|
set_magic(mld, MEM_CRASH_MAGIC);
|
|
set_access(mld, 0);
|
|
|
|
if (atomic_inc_return(&mld->forced_cp_crash) > 1) {
|
|
mif_err("%s: ALREADY in progress <%pf>\n",
|
|
ld->name, CALLER);
|
|
return;
|
|
}
|
|
|
|
if (!cp_online(mc) && !cp_booting(mc)) {
|
|
mif_err("%s: %s.state %s != ONLINE <%pf>\n",
|
|
ld->name, mc->name, mc_state(mc), CALLER);
|
|
return;
|
|
}
|
|
|
|
/* Disable debug Snapshot */
|
|
mif_set_snapshot(false);
|
|
|
|
mld->crash_reason.owner = crash_reason_owner;
|
|
strlcpy(mld->crash_reason.string, crash_reason_string,
|
|
CRASH_REASON_SIZE);
|
|
|
|
stop_net_ifaces(ld);
|
|
|
|
if (mld->debug_info)
|
|
mld->debug_info();
|
|
|
|
/**
|
|
* If there is no CRASH_ACK from CP in a timeout,
|
|
* handle_no_cp_crash_ack() will be executed.
|
|
*/
|
|
mif_add_timer(&mld->crash_ack_timer, FORCE_CRASH_ACK_TIMEOUT,
|
|
handle_no_cp_crash_ack, (unsigned long)mld);
|
|
|
|
/* Update crash type to msg box */
|
|
mbox_update_value(MCU_CP, ap_status, mld->crash_reason.owner,
|
|
mld->sbi_crash_type_mask, mld->sbi_crash_type_pos);
|
|
|
|
/* Send CRASH_EXIT command to a CP */
|
|
send_ipc_irq(mld, cmd2int(CMD_CRASH_EXIT));
|
|
|
|
clean_vss_magic_code();
|
|
|
|
mif_err("%s->%s: CP_CRASH_REQ by %d, %s <%pf>\n",
|
|
ld->name, mc->name,
|
|
crash_reason_owner, crash_reason_string,
|
|
CALLER);
|
|
}
|
|
|
|
#endif
|
|
|
|
static bool rild_ready(struct link_device *ld)
|
|
{
|
|
struct io_device *fmt_iod;
|
|
struct io_device *rfs_iod;
|
|
int fmt_opened;
|
|
int rfs_opened;
|
|
|
|
fmt_iod = link_get_iod_with_channel(ld, SIPC5_CH_ID_FMT_0);
|
|
if (!fmt_iod) {
|
|
mif_err("%s: No FMT io_device\n", ld->name);
|
|
return false;
|
|
}
|
|
|
|
rfs_iod = link_get_iod_with_channel(ld, SIPC5_CH_ID_RFS_0);
|
|
if (!rfs_iod) {
|
|
mif_err("%s: No RFS io_device\n", ld->name);
|
|
return false;
|
|
}
|
|
|
|
fmt_opened = atomic_read(&fmt_iod->opened);
|
|
rfs_opened = atomic_read(&rfs_iod->opened);
|
|
mif_err_limited("%s: %s.opened=%d, %s.opened=%d\n", ld->name,
|
|
fmt_iod->name, fmt_opened, rfs_iod->name, rfs_opened);
|
|
if (fmt_opened > 0 && rfs_opened > 0)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
static void cmd_init_start_handler(struct mem_link_device *mld)
|
|
{
|
|
struct link_device *ld = &mld->link_dev;
|
|
struct modem_ctl *mc = ld->mc;
|
|
int err;
|
|
|
|
mif_err("%s: INIT_START <- %s (%s.state:%s cp_boot_done:%d)\n",
|
|
ld->name, mc->name, mc->name, mc_state(mc),
|
|
atomic_read(&mld->cp_boot_done));
|
|
|
|
if (!ld->sbd_ipc) {
|
|
mif_err("%s: LINK_ATTR_SBD_IPC is NOT set\n", ld->name);
|
|
return;
|
|
}
|
|
|
|
err = init_sbd_link(&mld->sbd_link_dev);
|
|
if (err < 0) {
|
|
mif_err("%s: init_sbd_link fail(%d)\n", ld->name, err);
|
|
return;
|
|
}
|
|
|
|
if (mld->attrs & LINK_ATTR(LINK_ATTR_IPC_ALIGNED))
|
|
ld->aligned = true;
|
|
else
|
|
ld->aligned = false;
|
|
|
|
sbd_activate(&mld->sbd_link_dev);
|
|
send_ipc_irq(mld, cmd2int(CMD_PIF_INIT_DONE));
|
|
|
|
mif_err("%s: PIF_INIT_DONE -> %s\n", ld->name, mc->name);
|
|
}
|
|
|
|
#ifdef CONFIG_CP_ETDAC_OTP_WA
|
|
struct etdac_otp {
|
|
u16 magic;
|
|
u16 reserved;
|
|
u32 data;
|
|
} __packed;
|
|
|
|
static void write_cp_etdac_otp_to_shmem(struct mem_link_device *mld)
|
|
{
|
|
struct link_device *ld = &mld->link_dev;
|
|
struct modem_ctl *mc = ld->mc;
|
|
struct sbd_link_device *sl = &mld->sbd_link_dev;
|
|
struct etdac_otp *etdac;
|
|
void __iomem *ioaddr;
|
|
u32 reg_val[2];
|
|
|
|
if (mld->clk_table == NULL) {
|
|
mif_err("clk_table is not defined. skip to set etdac otp\n");
|
|
return;
|
|
}
|
|
ioaddr = devm_ioremap(mc->dev, 0x10000000, SZ_64K);
|
|
if (ioaddr == NULL) {
|
|
mif_err("devm_ioremap() error. skip to set etdac otp\n");
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* ETDAC0[447:443] 6bit : [31:26]
|
|
* ETDAC1[471:448] 24bit : [23:0]
|
|
*/
|
|
reg_val[0] = __raw_readl(ioaddr + 0xA034);
|
|
reg_val[1] = __raw_readl(ioaddr + 0xA038);
|
|
mif_info("etdac raw 0x%08x 0x%08x\n", reg_val[1], reg_val[0]);
|
|
|
|
devm_iounmap(mc->dev, ioaddr);
|
|
|
|
etdac = (struct etdac_otp *)((u8 *)sl->shmem + DESC_RGN_OFFSET - 8);
|
|
etdac->magic = 0xEDAC;
|
|
etdac->reserved = 0;
|
|
etdac->data = ((reg_val[0] >> 26) & 0x3F) +
|
|
((reg_val[1] << 6) & 0x3FFFFFC0);
|
|
|
|
mif_info("etdac 0x%04x 0x%08x\n", etdac->magic, etdac->data);
|
|
}
|
|
#endif
|
|
|
|
static void write_clk_table_to_shmem(struct mem_link_device *mld)
|
|
{
|
|
struct clock_table *clk_tb;
|
|
u32 *clk_data;
|
|
int i, j;
|
|
|
|
if (mld->clk_table == NULL) {
|
|
mif_err("clk_table is not defined\n");
|
|
return;
|
|
}
|
|
|
|
clk_tb = (struct clock_table *)mld->clk_table;
|
|
|
|
strcpy(clk_tb->parser_version, "CT0");
|
|
clk_tb->total_table_count = 4;
|
|
|
|
strcpy(clk_tb->table_info[0].table_name, "CL0");
|
|
clk_tb->table_info[0].table_count = mld->cl0_table.num_of_table;
|
|
|
|
strcpy(clk_tb->table_info[1].table_name, "CL1");
|
|
clk_tb->table_info[1].table_count = mld->cl1_table.num_of_table;
|
|
|
|
strcpy(clk_tb->table_info[2].table_name, "MIF");
|
|
clk_tb->table_info[2].table_count = mld->mif_table.num_of_table;
|
|
|
|
strcpy(clk_tb->table_info[3].table_name, "INT");
|
|
clk_tb->table_info[3].table_count = mld->int_table.num_of_table;
|
|
|
|
clk_data = (u32 *)&(clk_tb->table_info[clk_tb->total_table_count]);
|
|
|
|
/* CL0 */
|
|
for (i = 0; i < mld->cl0_table.num_of_table; i++) {
|
|
*clk_data = mld->cl0_table.freq[i];
|
|
clk_data++;
|
|
}
|
|
|
|
/* CL1 */
|
|
for (i = 0; i < mld->cl1_table.num_of_table; i++) {
|
|
*clk_data = mld->cl1_table.freq[i];
|
|
clk_data++;
|
|
}
|
|
|
|
/* MIF */
|
|
for (i = 0; i < mld->mif_table.num_of_table; i++) {
|
|
*clk_data = mld->mif_table.freq[i];
|
|
clk_data++;
|
|
}
|
|
|
|
/* INT */
|
|
for (i = 0; i < mld->int_table.num_of_table; i++) {
|
|
*clk_data = mld->int_table.freq[i];
|
|
clk_data++;
|
|
}
|
|
|
|
mif_info("PARSER_VERSION: %s\n", clk_tb->parser_version);
|
|
mif_info("TOTAL_TABLE_COUNT: %d\n", clk_tb->total_table_count);
|
|
|
|
for (i = 0; i < clk_tb->total_table_count; i++) {
|
|
mif_info("TABLE_NAME[%d] : %s\n", i+1,
|
|
clk_tb->table_info[i].table_name);
|
|
mif_info("TABLE_COUNT[%d]: %d\n", i+1,
|
|
clk_tb->table_info[i].table_count);
|
|
}
|
|
|
|
clk_data = (u32 *)&(clk_tb->table_info[clk_tb->total_table_count]);
|
|
|
|
for (i = 0; i < clk_tb->total_table_count; i++) {
|
|
for (j = 0; j < clk_tb->table_info[i].table_count; j++)
|
|
mif_info("CLOCK_TABLE[%d][%d] : %d\n",
|
|
i+1, j+1, *clk_data++);
|
|
}
|
|
}
|
|
|
|
static void cmd_phone_start_handler(struct mem_link_device *mld)
|
|
{
|
|
struct link_device *ld = &mld->link_dev;
|
|
struct modem_ctl *mc = ld->mc;
|
|
unsigned long flags;
|
|
int err;
|
|
|
|
mif_err_limited("%s: CP_START <- %s (%s.state:%s cp_boot_done:%d)\n",
|
|
ld->name, mc->name, mc->name, mc_state(mc),
|
|
atomic_read(&mld->cp_boot_done));
|
|
|
|
#ifdef CONFIG_LINK_POWER_MANAGEMENT
|
|
if (mld->start_pm)
|
|
mld->start_pm(mld);
|
|
#endif
|
|
|
|
spin_lock_irqsave(&mld->state_lock, flags);
|
|
|
|
if (mld->state == LINK_STATE_IPC) {
|
|
/*
|
|
If there is no INIT_END command from AP, CP sends a CP_START
|
|
command to AP periodically until it receives INIT_END from AP
|
|
even though it has already been in ONLINE state.
|
|
*/
|
|
if (rild_ready(ld)) {
|
|
mif_err("%s: INIT_END(ONLINE) -> %s\n", ld->name, mc->name);
|
|
send_ipc_irq(mld, cmd2int(CMD_INIT_END));
|
|
}
|
|
goto exit;
|
|
}
|
|
|
|
err = shmem_reset_ipc_link(mld);
|
|
if (err) {
|
|
mif_err("%s: shmem_reset_ipc_link fail(%d)\n", ld->name, err);
|
|
goto exit;
|
|
}
|
|
|
|
if (rild_ready(ld)) {
|
|
mif_err("%s: INIT_END -> %s\n", ld->name, mc->name);
|
|
send_ipc_irq(mld, cmd2int(CMD_INIT_END));
|
|
atomic_set(&mld->cp_boot_done, 1);
|
|
}
|
|
|
|
mld->state = LINK_STATE_IPC;
|
|
complete_all(&mc->init_cmpl);
|
|
modem_notify_event(MODEM_EVENT_ONLINE);
|
|
|
|
exit:
|
|
spin_unlock_irqrestore(&mld->state_lock, flags);
|
|
|
|
#ifdef CONFIG_FREE_CP_RSVD_MEMORY
|
|
send_ipc_irq(mld, cmd2int(CMD_INIT_END));
|
|
//mcu_ipc_unregister_handler(MCU_CP, mld->irq_cp2ap_msg, shmem_irq_handler);
|
|
cancel_delayed_work_sync(&mld->udl_rx_dwork);
|
|
#endif
|
|
}
|
|
|
|
static void cmd_crash_reset_handler(struct mem_link_device *mld)
|
|
{
|
|
struct link_device *ld = &mld->link_dev;
|
|
struct modem_ctl *mc = ld->mc;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&mld->state_lock, flags);
|
|
mld->state = LINK_STATE_OFFLINE;
|
|
spin_unlock_irqrestore(&mld->state_lock, flags);
|
|
|
|
mif_err("%s<-%s: ERR! CP_CRASH_RESET\n", ld->name, mc->name);
|
|
|
|
shmem_handle_cp_crash(mld, STATE_CRASH_RESET);
|
|
}
|
|
|
|
static void cmd_crash_exit_handler(struct mem_link_device *mld)
|
|
{
|
|
struct link_device *ld = &mld->link_dev;
|
|
struct modem_ctl *mc = ld->mc;
|
|
unsigned long flags;
|
|
|
|
/* Disable debug Snapshot */
|
|
mif_set_snapshot(false);
|
|
|
|
spin_lock_irqsave(&mld->state_lock, flags);
|
|
mld->state = LINK_STATE_CP_CRASH;
|
|
spin_unlock_irqrestore(&mld->state_lock, flags);
|
|
|
|
if (timer_pending(&mld->crash_ack_timer))
|
|
del_timer(&mld->crash_ack_timer);
|
|
|
|
if (atomic_read(&mld->forced_cp_crash))
|
|
mif_err("%s<-%s: CP_CRASH_ACK\n", ld->name, mc->name);
|
|
else
|
|
mif_err("%s<-%s: ERR! CP_CRASH_EXIT\n", ld->name, mc->name);
|
|
|
|
shmem_handle_cp_crash(mld, STATE_CRASH_EXIT);
|
|
}
|
|
|
|
static void shmem_cmd_handler(struct mem_link_device *mld, u16 cmd)
|
|
{
|
|
struct link_device *ld = &mld->link_dev;
|
|
|
|
switch (cmd) {
|
|
case CMD_INIT_START:
|
|
cmd_init_start_handler(mld);
|
|
break;
|
|
|
|
case CMD_PHONE_START:
|
|
cmd_phone_start_handler(mld);
|
|
break;
|
|
|
|
case CMD_CRASH_RESET:
|
|
cmd_crash_reset_handler(mld);
|
|
break;
|
|
|
|
case CMD_CRASH_EXIT:
|
|
cmd_crash_exit_handler(mld);
|
|
break;
|
|
|
|
default:
|
|
mif_err("%s: Unknown command 0x%04X\n", ld->name, cmd);
|
|
break;
|
|
}
|
|
}
|
|
|
|
#ifdef GROUP_MEM_IPC_TX
|
|
|
|
static inline int check_txq_space(struct mem_link_device *mld,
|
|
struct mem_ipc_device *dev,
|
|
unsigned int qsize, unsigned int in,
|
|
unsigned int out, unsigned int count)
|
|
{
|
|
struct link_device *ld = &mld->link_dev;
|
|
struct modem_ctl *mc = ld->mc;
|
|
unsigned int space;
|
|
|
|
if (!circ_valid(qsize, in, out)) {
|
|
mif_err("%s: ERR! Invalid %s_TXQ{qsize:%d in:%d out:%d}\n",
|
|
ld->name, dev->name, qsize, in, out);
|
|
return -EIO;
|
|
}
|
|
|
|
space = circ_get_space(qsize, in, out);
|
|
if (unlikely(space < count)) {
|
|
if (cp_online(mc)) {
|
|
mif_err("%s: NOSPC %s_TX{qsize:%d in:%d out:%d space:%d len:%d}\n",
|
|
ld->name, dev->name, qsize,
|
|
in, out, space, count);
|
|
}
|
|
return -ENOSPC;
|
|
}
|
|
|
|
return space;
|
|
}
|
|
|
|
static int txq_write(struct mem_link_device *mld, struct mem_ipc_device *dev,
|
|
struct sk_buff *skb)
|
|
{
|
|
char *src = skb->data;
|
|
unsigned int count = skb->len;
|
|
char *dst = get_txq_buff(dev);
|
|
unsigned int qsize = get_txq_buff_size(dev);
|
|
unsigned int in = get_txq_head(dev);
|
|
unsigned int out = get_txq_tail(dev);
|
|
int space;
|
|
|
|
space = check_txq_space(mld, dev, qsize, in, out, count);
|
|
if (unlikely(space < 0))
|
|
return space;
|
|
|
|
barrier();
|
|
|
|
circ_write(dst, src, qsize, in, count);
|
|
|
|
barrier();
|
|
|
|
set_txq_head(dev, circ_new_ptr(qsize, in, count));
|
|
|
|
/* Commit the item before incrementing the head */
|
|
smp_mb();
|
|
|
|
return count;
|
|
}
|
|
|
|
static int tx_frames_to_dev(struct mem_link_device *mld,
|
|
struct mem_ipc_device *dev)
|
|
{
|
|
struct sk_buff_head *skb_txq = dev->skb_txq;
|
|
int tx_bytes = 0;
|
|
int ret = 0;
|
|
|
|
while (1) {
|
|
struct sk_buff *skb;
|
|
|
|
skb = skb_dequeue(skb_txq);
|
|
if (unlikely(!skb))
|
|
break;
|
|
|
|
ret = txq_write(mld, dev, skb);
|
|
if (unlikely(ret < 0)) {
|
|
/* Take the skb back to the skb_txq */
|
|
skb_queue_head(skb_txq, skb);
|
|
break;
|
|
}
|
|
pktlog_tx_bottom_skb(mld, skb);
|
|
|
|
tx_bytes += ret;
|
|
|
|
#ifdef DEBUG_MODEM_IF_LINK_TX
|
|
mif_pkt(skbpriv(skb)->sipc_ch, "LNK-TX", skb);
|
|
#endif
|
|
|
|
dev_consume_skb_any(skb);
|
|
}
|
|
|
|
return (ret < 0) ? ret : tx_bytes;
|
|
}
|
|
|
|
static enum hrtimer_restart tx_timer_func(struct hrtimer *timer)
|
|
{
|
|
struct mem_link_device *mld;
|
|
struct link_device *ld;
|
|
struct modem_ctl *mc;
|
|
int i;
|
|
bool need_schedule;
|
|
u16 mask;
|
|
unsigned long flags;
|
|
|
|
mld = container_of(timer, struct mem_link_device, tx_timer);
|
|
ld = &mld->link_dev;
|
|
mc = ld->mc;
|
|
|
|
need_schedule = false;
|
|
mask = 0;
|
|
|
|
spin_lock_irqsave(&mc->lock, flags);
|
|
|
|
if (unlikely(!ipc_active(mld)))
|
|
goto exit;
|
|
|
|
#ifdef CONFIG_LINK_POWER_MANAGEMENT_WITH_FSM
|
|
if (mld->link_active) {
|
|
if (!mld->link_active(mld)) {
|
|
need_schedule = true;
|
|
goto exit;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
for (i = 0; i < MAX_SIPC_MAP; i++) {
|
|
struct mem_ipc_device *dev = mld->dev[i];
|
|
int ret;
|
|
|
|
if (unlikely(under_tx_flow_ctrl(mld, dev))) {
|
|
ret = check_tx_flow_ctrl(mld, dev);
|
|
if (ret < 0) {
|
|
if (ret == -EBUSY || ret == -ETIME) {
|
|
need_schedule = true;
|
|
continue;
|
|
} else {
|
|
shmem_forced_cp_crash(mld, CRASH_REASON_MIF_TX_ERR,
|
|
"check_tx_flow_ctrl error");
|
|
need_schedule = false;
|
|
goto exit;
|
|
}
|
|
}
|
|
}
|
|
|
|
ret = tx_frames_to_dev(mld, dev);
|
|
if (unlikely(ret < 0)) {
|
|
if (ret == -EBUSY || ret == -ENOSPC) {
|
|
need_schedule = true;
|
|
txq_stop(mld, dev);
|
|
/* If txq has 2 or more packet and 2nd packet
|
|
has -ENOSPC return, It request irq to consume
|
|
the TX ring-buffer from CP */
|
|
mask |= msg_mask(dev);
|
|
continue;
|
|
} else {
|
|
shmem_forced_cp_crash(mld, CRASH_REASON_MIF_TX_ERR,
|
|
"tx_frames_to_dev error");
|
|
need_schedule = false;
|
|
goto exit;
|
|
}
|
|
}
|
|
|
|
if (ret > 0)
|
|
mask |= msg_mask(dev);
|
|
|
|
if (!skb_queue_empty(dev->skb_txq))
|
|
need_schedule = true;
|
|
}
|
|
|
|
if (!need_schedule) {
|
|
for (i = 0; i < MAX_SIPC_MAP; i++) {
|
|
if (!txq_empty(mld->dev[i])) {
|
|
need_schedule = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (mask)
|
|
send_ipc_irq(mld, mask2int(mask));
|
|
|
|
exit:
|
|
if (need_schedule) {
|
|
ktime_t ktime = ktime_set(0, mld->tx_period_ms * NSEC_PER_MSEC);
|
|
hrtimer_start(timer, ktime, HRTIMER_MODE_REL);
|
|
}
|
|
|
|
spin_unlock_irqrestore(&mc->lock, flags);
|
|
|
|
return HRTIMER_NORESTART;
|
|
}
|
|
|
|
static inline void start_tx_timer(struct mem_link_device *mld,
|
|
struct hrtimer *timer)
|
|
{
|
|
struct link_device *ld = &mld->link_dev;
|
|
struct modem_ctl *mc = ld->mc;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&mc->lock, flags);
|
|
|
|
if (unlikely(cp_offline(mc)))
|
|
goto exit;
|
|
|
|
if (!hrtimer_is_queued(timer)) {
|
|
ktime_t ktime = ktime_set(0, mld->tx_period_ms * NSEC_PER_MSEC);
|
|
hrtimer_start(timer, ktime, HRTIMER_MODE_REL);
|
|
}
|
|
|
|
exit:
|
|
spin_unlock_irqrestore(&mc->lock, flags);
|
|
}
|
|
|
|
static inline void cancel_tx_timer(struct mem_link_device *mld,
|
|
struct hrtimer *timer)
|
|
{
|
|
struct link_device *ld = &mld->link_dev;
|
|
struct modem_ctl *mc = ld->mc;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&mc->lock, flags);
|
|
|
|
if (hrtimer_active(timer))
|
|
hrtimer_cancel(timer);
|
|
|
|
spin_unlock_irqrestore(&mc->lock, flags);
|
|
}
|
|
|
|
static inline void start_datalloc_timer(struct mem_link_device *mld,
|
|
struct hrtimer *timer)
|
|
{
|
|
struct link_device *ld = &mld->link_dev;
|
|
struct modem_ctl *mc = ld->mc;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&mc->lock, flags);
|
|
|
|
if (unlikely(cp_offline(mc)))
|
|
goto exit;
|
|
|
|
if (!hrtimer_is_queued(timer)) {
|
|
ktime_t ktime = ktime_set(0, ms2ns(DATALLOC_PERIOD_MS));
|
|
hrtimer_start(timer, ktime, HRTIMER_MODE_REL);
|
|
}
|
|
|
|
exit:
|
|
spin_unlock_irqrestore(&mc->lock, flags);
|
|
}
|
|
|
|
static inline void __cancel_datalloc_timer(struct mem_link_device *mld,
|
|
struct hrtimer *timer)
|
|
{
|
|
struct link_device *ld = &mld->link_dev;
|
|
struct modem_ctl *mc = ld->mc;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&mc->lock, flags);
|
|
|
|
if (hrtimer_active(timer))
|
|
hrtimer_cancel(timer);
|
|
|
|
spin_unlock_irqrestore(&mc->lock, flags);
|
|
}
|
|
|
|
static inline void cancel_datalloc_timer(struct mem_link_device *mld)
|
|
{
|
|
struct sbd_link_device *sl = &mld->sbd_link_dev;
|
|
struct sbd_ipc_device *ipc_dev = sl->ipc_dev;
|
|
int i;
|
|
|
|
for (i = 0; i < sl->num_channels; i++) {
|
|
struct sbd_ring_buffer *rb = &ipc_dev[i].rb[DL];
|
|
if (rb->zerocopy) {
|
|
struct zerocopy_adaptor *zdptr = rb->zdptr;
|
|
__cancel_datalloc_timer(mld, &zdptr->datalloc_timer);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
static int tx_frames_to_rb(struct sbd_ring_buffer *rb)
|
|
{
|
|
struct sk_buff_head *skb_txq = &rb->skb_q;
|
|
int tx_bytes = 0;
|
|
int ret = 0;
|
|
|
|
while (1) {
|
|
struct sk_buff *skb;
|
|
|
|
skb = skb_dequeue(skb_txq);
|
|
if (unlikely(!skb))
|
|
break;
|
|
|
|
ret = sbd_pio_tx(rb, skb);
|
|
if (unlikely(ret < 0)) {
|
|
/* Take the skb back to the skb_txq */
|
|
skb_queue_head(skb_txq, skb);
|
|
break;
|
|
}
|
|
|
|
tx_bytes += ret;
|
|
#ifdef DEBUG_MODEM_IF_LINK_TX
|
|
mif_pkt(rb->ch, "LNK-TX", skb);
|
|
#endif
|
|
dev_consume_skb_any(skb);
|
|
}
|
|
|
|
return (ret < 0) ? ret : tx_bytes;
|
|
}
|
|
|
|
static enum hrtimer_restart sbd_tx_timer_func(struct hrtimer *timer)
|
|
{
|
|
struct mem_link_device *mld =
|
|
container_of(timer, struct mem_link_device, sbd_tx_timer);
|
|
struct link_device *ld = &mld->link_dev;
|
|
struct modem_ctl *mc = ld->mc;
|
|
struct sbd_link_device *sl = &mld->sbd_link_dev;
|
|
int i;
|
|
bool need_schedule = false;
|
|
u16 mask = 0;
|
|
unsigned long flags = 0;
|
|
|
|
spin_lock_irqsave(&mc->lock, flags);
|
|
if (unlikely(!ipc_active(mld))) {
|
|
spin_unlock_irqrestore(&mc->lock, flags);
|
|
goto exit;
|
|
}
|
|
spin_unlock_irqrestore(&mc->lock, flags);
|
|
|
|
#ifdef CONFIG_LINK_POWER_MANAGEMENT
|
|
if (mld->link_active) {
|
|
if (!mld->link_active(mld)) {
|
|
need_schedule = true;
|
|
goto exit;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
for (i = 0; i < sl->num_channels; i++) {
|
|
struct sbd_ring_buffer *rb = sbd_id2rb(sl, i, TX);
|
|
int ret;
|
|
|
|
if (unlikely(sbd_under_tx_flow_ctrl(rb))) {
|
|
ret = sbd_check_tx_flow_ctrl(rb);
|
|
if (ret < 0) {
|
|
if (ret == -EBUSY || ret == -ETIME) {
|
|
need_schedule = true;
|
|
continue;
|
|
} else {
|
|
shmem_forced_cp_crash(mld,
|
|
CRASH_REASON_MIF_TX_ERR,
|
|
"check_sbd_tx_flow_ctrl error");
|
|
need_schedule = false;
|
|
goto exit;
|
|
}
|
|
}
|
|
}
|
|
|
|
ret = tx_frames_to_rb(rb);
|
|
if (unlikely(ret < 0)) {
|
|
if (ret == -EBUSY || ret == -ENOSPC) {
|
|
need_schedule = true;
|
|
sbd_txq_stop(rb);
|
|
mask = MASK_SEND_DATA;
|
|
continue;
|
|
} else {
|
|
shmem_forced_cp_crash(mld, CRASH_REASON_MIF_TX_ERR,
|
|
"tx_frames_to_rb error");
|
|
need_schedule = false;
|
|
goto exit;
|
|
}
|
|
}
|
|
|
|
if (ret > 0)
|
|
mask = MASK_SEND_DATA;
|
|
|
|
if (!skb_queue_empty(&rb->skb_q))
|
|
need_schedule = true;
|
|
}
|
|
|
|
if (!need_schedule) {
|
|
for (i = 0; i < sl->num_channels; i++) {
|
|
struct sbd_ring_buffer *rb;
|
|
|
|
rb = sbd_id2rb(sl, i, TX);
|
|
if (!rb_empty(rb)) {
|
|
need_schedule = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (mask) {
|
|
spin_lock_irqsave(&mc->lock, flags);
|
|
if (unlikely(!ipc_active(mld))) {
|
|
spin_unlock_irqrestore(&mc->lock, flags);
|
|
need_schedule = false;
|
|
goto exit;
|
|
}
|
|
send_ipc_irq(mld, mask2int(mask));
|
|
spin_unlock_irqrestore(&mc->lock, flags);
|
|
}
|
|
|
|
exit:
|
|
if (need_schedule) {
|
|
ktime_t ktime = ktime_set(0, mld->tx_period_ms * NSEC_PER_MSEC);
|
|
hrtimer_start(timer, ktime, HRTIMER_MODE_REL);
|
|
}
|
|
|
|
return HRTIMER_NORESTART;
|
|
}
|
|
|
|
enum hrtimer_restart datalloc_timer_func(struct hrtimer *timer)
|
|
{
|
|
struct zerocopy_adaptor *zdptr =
|
|
container_of(timer, struct zerocopy_adaptor, datalloc_timer);
|
|
struct sbd_ring_buffer *rb = zdptr->rb;
|
|
struct link_device *ld = rb->ld;
|
|
struct mem_link_device *mld = ld_to_mem_link_device(ld);
|
|
struct modem_ctl *mc = ld->mc;
|
|
bool need_schedule = false;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&mc->lock, flags);
|
|
if (unlikely(!ipc_active(mld))) {
|
|
spin_unlock_irqrestore(&mc->lock, flags);
|
|
goto exit;
|
|
}
|
|
spin_unlock_irqrestore(&mc->lock, flags);
|
|
|
|
if (-ENOMEM == allocate_data_in_advance(zdptr)) {
|
|
need_schedule = true;
|
|
}
|
|
|
|
if (need_schedule) {
|
|
ktime_t ktime = ktime_set(0, ms2ns(DATALLOC_PERIOD_MS));
|
|
hrtimer_start(timer, ktime, HRTIMER_MODE_REL);
|
|
}
|
|
exit:
|
|
|
|
return HRTIMER_NORESTART;
|
|
}
|
|
|
|
static int xmit_ipc_to_rb(struct mem_link_device *mld, enum sipc_ch_id ch,
|
|
struct sk_buff *skb)
|
|
{
|
|
int ret;
|
|
struct link_device *ld = &mld->link_dev;
|
|
struct io_device *iod = skbpriv(skb)->iod;
|
|
struct modem_ctl *mc = ld->mc;
|
|
struct sbd_ring_buffer *rb = sbd_ch2rb_with_skb(&mld->sbd_link_dev, ch, TX, skb);
|
|
struct sk_buff_head *skb_txq;
|
|
unsigned long flags;
|
|
|
|
if (!rb) {
|
|
mif_err("%s: %s->%s: ERR! NO SBD RB {ch:%d}\n",
|
|
ld->name, iod->name, mc->name, ch);
|
|
return -ENODEV;
|
|
}
|
|
|
|
skb_txq = &rb->skb_q;
|
|
|
|
#ifdef CONFIG_LINK_POWER_MANAGEMENT
|
|
if (cp_online(mc) && mld->forbid_cp_sleep)
|
|
mld->forbid_cp_sleep(mld);
|
|
#endif
|
|
|
|
spin_lock_irqsave(&rb->lock, flags);
|
|
|
|
if (unlikely(skb_txq->qlen >= MAX_SKB_TXQ_DEPTH)) {
|
|
mif_err_limited("%s: %s->%s: ERR! {ch:%d} "
|
|
"skb_txq.len %d >= limit %d\n",
|
|
ld->name, iod->name, mc->name, ch,
|
|
skb_txq->qlen, MAX_SKB_TXQ_DEPTH);
|
|
ret = -EBUSY;
|
|
} else {
|
|
skb->len = min_t(int, skb->len, rb->buff_size);
|
|
|
|
ret = skb->len;
|
|
skb_queue_tail(skb_txq, skb);
|
|
start_tx_timer(mld, &mld->sbd_tx_timer);
|
|
}
|
|
|
|
spin_unlock_irqrestore(&rb->lock, flags);
|
|
|
|
#ifdef CONFIG_LINK_POWER_MANAGEMENT
|
|
if (cp_online(mc) && mld->permit_cp_sleep)
|
|
mld->permit_cp_sleep(mld);
|
|
#endif
|
|
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
static int xmit_ipc_to_dev(struct mem_link_device *mld, enum sipc_ch_id ch,
|
|
struct sk_buff *skb)
|
|
{
|
|
int ret;
|
|
struct link_device *ld = &mld->link_dev;
|
|
struct io_device *iod = skbpriv(skb)->iod;
|
|
struct modem_ctl *mc = ld->mc;
|
|
struct mem_ipc_device *dev = mld->dev[get_mmap_idx(ch, skb)];
|
|
struct sk_buff_head *skb_txq;
|
|
|
|
if (!dev) {
|
|
mif_err("%s: %s->%s: ERR! NO IPC DEV {ch:%d}\n",
|
|
ld->name, iod->name, mc->name, ch);
|
|
return -ENODEV;
|
|
}
|
|
|
|
skb_txq = dev->skb_txq;
|
|
|
|
#ifdef CONFIG_LINK_POWER_MANAGEMENT
|
|
if (cp_online(mc) && mld->forbid_cp_sleep)
|
|
mld->forbid_cp_sleep(mld);
|
|
#endif
|
|
if (unlikely(skb_txq->qlen >= MAX_SKB_TXQ_DEPTH)) {
|
|
mif_err_limited("%s: %s->%s: ERR! %s TXQ.qlen %d >= limit %d\n",
|
|
ld->name, iod->name, mc->name, dev->name,
|
|
skb_txq->qlen, MAX_SKB_TXQ_DEPTH);
|
|
ret = -EBUSY;
|
|
} else {
|
|
ret = skb->len;
|
|
skb_queue_tail(dev->skb_txq, skb);
|
|
start_tx_timer(mld, &mld->tx_timer);
|
|
}
|
|
|
|
#ifdef CONFIG_LINK_POWER_MANAGEMENT
|
|
if (cp_online(mc) && mld->permit_cp_sleep)
|
|
mld->permit_cp_sleep(mld);
|
|
#endif
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int xmit_ipc(struct mem_link_device *mld, struct io_device *iod,
|
|
enum sipc_ch_id ch, struct sk_buff *skb)
|
|
{
|
|
struct link_device *ld = &mld->link_dev;
|
|
|
|
if (unlikely(!ipc_active(mld)))
|
|
return -EIO;
|
|
|
|
if (ld->sbd_ipc && iod->sbd_ipc) {
|
|
if (likely(sbd_active(&mld->sbd_link_dev)))
|
|
return xmit_ipc_to_rb(mld, ch, skb);
|
|
else
|
|
return -ENODEV;
|
|
} else {
|
|
return xmit_ipc_to_dev(mld, ch, skb);
|
|
}
|
|
}
|
|
|
|
static inline int check_udl_space(struct mem_link_device *mld,
|
|
struct mem_ipc_device *dev,
|
|
unsigned int qsize, unsigned int in,
|
|
unsigned int out, unsigned int count)
|
|
{
|
|
struct link_device *ld = &mld->link_dev;
|
|
unsigned int space;
|
|
|
|
if (!circ_valid(qsize, in, out)) {
|
|
mif_err("%s: ERR! Invalid %s_TXQ{qsize:%d in:%d out:%d}\n",
|
|
ld->name, dev->name, qsize, in, out);
|
|
return -EIO;
|
|
}
|
|
|
|
space = circ_get_space(qsize, in, out);
|
|
if (unlikely(space < count)) {
|
|
mif_err("%s: NOSPC %s_TX{qsize:%d in:%d out:%d free:%d len:%d}\n",
|
|
ld->name, dev->name, qsize, in, out, space, count);
|
|
return -ENOSPC;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline int udl_write(struct mem_link_device *mld,
|
|
struct mem_ipc_device *dev, struct sk_buff *skb)
|
|
{
|
|
unsigned int count = skb->len;
|
|
char *src = skb->data;
|
|
char *dst = get_txq_buff(dev);
|
|
unsigned int qsize = get_txq_buff_size(dev);
|
|
unsigned int in = get_txq_head(dev);
|
|
unsigned int out = get_txq_tail(dev);
|
|
int space;
|
|
|
|
space = check_udl_space(mld, dev, qsize, in, out, count);
|
|
if (unlikely(space < 0))
|
|
return space;
|
|
|
|
barrier();
|
|
|
|
circ_write(dst, src, qsize, in, count);
|
|
|
|
barrier();
|
|
|
|
set_txq_head(dev, circ_new_ptr(qsize, in, count));
|
|
|
|
/* Commit the item before incrementing the head */
|
|
smp_mb();
|
|
|
|
return count;
|
|
}
|
|
|
|
static int xmit_udl(struct mem_link_device *mld, struct io_device *iod,
|
|
enum sipc_ch_id ch, struct sk_buff *skb)
|
|
{
|
|
int ret;
|
|
struct mem_ipc_device *dev = mld->dev[IPC_MAP_NORM_RAW];
|
|
int count = skb->len;
|
|
int tried = 0;
|
|
|
|
while (1) {
|
|
ret = udl_write(mld, dev, skb);
|
|
if (ret == count)
|
|
break;
|
|
|
|
if (ret != -ENOSPC)
|
|
goto exit;
|
|
|
|
tried++;
|
|
if (tried >= 20)
|
|
goto exit;
|
|
|
|
if (in_interrupt())
|
|
mdelay(50);
|
|
else
|
|
msleep(50);
|
|
}
|
|
|
|
#ifdef DEBUG_MODEM_IF_LINK_TX
|
|
mif_pkt(ch, "LNK-TX", skb);
|
|
#endif
|
|
|
|
dev_consume_skb_any(skb);
|
|
|
|
exit:
|
|
return ret;
|
|
}
|
|
|
|
/*============================================================================*/
|
|
|
|
#ifdef GROUP_MEM_IPC_RX
|
|
|
|
static void pass_skb_to_demux(struct mem_link_device *mld, struct sk_buff *skb)
|
|
{
|
|
struct link_device *ld = &mld->link_dev;
|
|
struct io_device *iod = skbpriv(skb)->iod;
|
|
int ret;
|
|
u8 ch = skbpriv(skb)->sipc_ch;
|
|
|
|
if (unlikely(!iod)) {
|
|
mif_err("%s: ERR! No IOD for CH.%d\n", ld->name, ch);
|
|
dev_kfree_skb_any(skb);
|
|
shmem_forced_cp_crash(mld, CRASH_REASON_MIF_RIL_BAD_CH,
|
|
"ERR! No IOD for CH.XX in pass_skb_to_demux()");
|
|
return;
|
|
}
|
|
|
|
#ifdef DEBUG_MODEM_IF_LINK_RX
|
|
mif_pkt(ch, "LNK-RX", skb);
|
|
#endif
|
|
|
|
ret = iod->recv_skb_single(iod, ld, skb);
|
|
if (unlikely(ret < 0)) {
|
|
struct modem_ctl *mc = ld->mc;
|
|
mif_err_limited("%s: %s<-%s: ERR! %s->recv_skb fail (%d)\n",
|
|
ld->name, iod->name, mc->name, iod->name, ret);
|
|
dev_kfree_skb_any(skb);
|
|
}
|
|
}
|
|
|
|
static inline void link_to_demux(struct mem_link_device *mld)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < MAX_SIPC_MAP; i++) {
|
|
struct mem_ipc_device *dev = mld->dev[i];
|
|
struct sk_buff_head *skb_rxq = dev->skb_rxq;
|
|
|
|
while (1) {
|
|
struct sk_buff *skb;
|
|
|
|
skb = skb_dequeue(skb_rxq);
|
|
if (!skb)
|
|
break;
|
|
|
|
pass_skb_to_demux(mld, skb);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void link_to_demux_work(struct work_struct *ws)
|
|
{
|
|
struct link_device *ld;
|
|
struct mem_link_device *mld;
|
|
|
|
ld = container_of(ws, struct link_device, rx_delayed_work.work);
|
|
mld = to_mem_link_device(ld);
|
|
|
|
link_to_demux(mld);
|
|
}
|
|
|
|
static inline void schedule_link_to_demux(struct mem_link_device *mld)
|
|
{
|
|
struct link_device *ld = &mld->link_dev;
|
|
struct delayed_work *dwork = &ld->rx_delayed_work;
|
|
|
|
/*queue_delayed_work(ld->rx_wq, dwork, 0);*/
|
|
queue_work_on(7, ld->rx_wq, &dwork->work);
|
|
}
|
|
|
|
static struct sk_buff *rxq_read(struct mem_link_device *mld,
|
|
struct mem_ipc_device *dev,
|
|
unsigned int in)
|
|
{
|
|
struct link_device *ld = &mld->link_dev;
|
|
struct sk_buff *skb;
|
|
char *src = get_rxq_buff(dev);
|
|
unsigned int qsize = get_rxq_buff_size(dev);
|
|
unsigned int out = get_rxq_tail(dev);
|
|
unsigned int rest = circ_get_usage(qsize, in, out);
|
|
unsigned int len;
|
|
char hdr[SIPC5_MIN_HEADER_SIZE];
|
|
|
|
/* Copy the header in a frame to the header buffer */
|
|
circ_read(hdr, src, qsize, out, SIPC5_MIN_HEADER_SIZE);
|
|
|
|
/* Check the config field in the header */
|
|
if (unlikely(!sipc5_start_valid(hdr))) {
|
|
mif_err("%s: ERR! %s BAD CFG 0x%02X (in:%d out:%d rest:%d)\n",
|
|
ld->name, dev->name, hdr[SIPC5_CONFIG_OFFSET],
|
|
in, out, rest);
|
|
goto bad_msg;
|
|
}
|
|
|
|
/* Verify the length of the frame (data + padding) */
|
|
len = sipc5_get_total_len(hdr);
|
|
if (unlikely(len > rest)) {
|
|
mif_err("%s: ERR! %s BAD LEN %d > rest %d\n",
|
|
ld->name, dev->name, len, rest);
|
|
goto bad_msg;
|
|
}
|
|
|
|
/* Allocate an skb */
|
|
skb = mem_alloc_skb(len);
|
|
if (!skb) {
|
|
mif_err("%s: ERR! %s mem_alloc_skb(%d) fail\n",
|
|
ld->name, dev->name, len);
|
|
goto no_mem;
|
|
}
|
|
|
|
/* Read the frame from the RXQ */
|
|
circ_read(skb_put(skb, len), src, qsize, out, len);
|
|
|
|
/* Update tail (out) pointer to the frame to be read in the future */
|
|
set_rxq_tail(dev, circ_new_ptr(qsize, out, len));
|
|
|
|
/* Finish reading data before incrementing tail */
|
|
smp_mb();
|
|
|
|
#ifdef DEBUG_MODEM_IF
|
|
/* Record the time-stamp */
|
|
getnstimeofday(&skbpriv(skb)->ts);
|
|
#endif
|
|
|
|
return skb;
|
|
|
|
bad_msg:
|
|
mif_err("%s%s%s: ERR! BAD MSG: %02x %02x %02x %02x\n",
|
|
ld->name, arrow(RX), ld->mc->name,
|
|
hdr[0], hdr[1], hdr[2], hdr[3]);
|
|
set_rxq_tail(dev, in); /* Reset tail (out) pointer */
|
|
shmem_forced_cp_crash(mld, CRASH_REASON_MIF_RX_BAD_DATA,
|
|
"ERR! BAD MSG from CP in rxq_read()");
|
|
|
|
no_mem:
|
|
return NULL;
|
|
}
|
|
|
|
static int rx_frames_from_dev(struct mem_link_device *mld,
|
|
struct mem_ipc_device *dev)
|
|
{
|
|
struct link_device *ld = &mld->link_dev;
|
|
unsigned int qsize = get_rxq_buff_size(dev);
|
|
unsigned int in = get_rxq_head(dev);
|
|
unsigned int out = get_rxq_tail(dev);
|
|
unsigned int size = circ_get_usage(qsize, in, out);
|
|
int rcvd = 0;
|
|
|
|
if (unlikely(circ_empty(in, out)))
|
|
return 0;
|
|
|
|
while (rcvd < size) {
|
|
struct sk_buff *skb;
|
|
u8 ch;
|
|
struct io_device *iod;
|
|
|
|
skb = rxq_read(mld, dev, in);
|
|
if (!skb)
|
|
return -ENOMEM;
|
|
|
|
pktlog_rx_bottom_skb(mld, skb);
|
|
|
|
ch = sipc5_get_ch(skb->data);
|
|
iod = link_get_iod_with_channel(ld, ch);
|
|
if (!iod) {
|
|
mif_err("%s: ERR! [%s]No IOD for CH.%d(out:%u)\n",
|
|
ld->name, dev->name, ch, get_rxq_tail(dev));
|
|
pr_skb("CRASH", skb);
|
|
dev_kfree_skb_any(skb);
|
|
shmem_forced_cp_crash(mld, CRASH_REASON_MIF_RX_BAD_DATA,
|
|
"ERR! No IOD from CP in rx_frames_from_dev()");
|
|
break;
|
|
}
|
|
|
|
/* Record the IO device and the link device into the &skb->cb */
|
|
skbpriv(skb)->iod = iod;
|
|
skbpriv(skb)->ld = ld;
|
|
|
|
skbpriv(skb)->lnk_hdr = iod->link_header;
|
|
skbpriv(skb)->sipc_ch = ch;
|
|
|
|
/* The $rcvd must be accumulated here, because $skb can be freed
|
|
in pass_skb_to_demux(). */
|
|
rcvd += skb->len;
|
|
pass_skb_to_demux(mld, skb);
|
|
}
|
|
|
|
if (rcvd < size) {
|
|
struct link_device *ld = &mld->link_dev;
|
|
mif_err("%s: WARN! rcvd %d < size %d\n", ld->name, rcvd, size);
|
|
}
|
|
|
|
return rcvd;
|
|
}
|
|
|
|
static int recv_ipc_frames(struct mem_link_device *mld,
|
|
struct mem_snapshot *mst)
|
|
{
|
|
int i;
|
|
u16 intr = mst->int2ap;
|
|
|
|
for (i = 0; i < MAX_SIPC_MAP; i++) {
|
|
struct mem_ipc_device *dev = mld->dev[i];
|
|
int rcvd;
|
|
|
|
#if 0
|
|
print_dev_snapshot(mld, mst, dev);
|
|
#endif
|
|
|
|
if (req_ack_valid(dev, intr))
|
|
recv_req_ack(mld, dev, mst);
|
|
|
|
rcvd = rx_frames_from_dev(mld, dev);
|
|
if (rcvd < 0)
|
|
return rcvd;
|
|
|
|
if (req_ack_valid(dev, intr))
|
|
send_res_ack(mld, dev);
|
|
|
|
if (res_ack_valid(dev, intr))
|
|
recv_res_ack(mld, dev, mst);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void pass_skb_to_net(struct mem_link_device *mld, struct sk_buff *skb)
|
|
{
|
|
struct link_device *ld = &mld->link_dev;
|
|
struct skbuff_private *priv;
|
|
struct io_device *iod;
|
|
struct modem_ctl *mc = ld->mc;
|
|
int ret;
|
|
|
|
if (unlikely(!cp_online(mc))) {
|
|
mif_err_limited("ERR! CP not online!, skb:%pK\n", skb);
|
|
dev_kfree_skb_any(skb);
|
|
return;
|
|
}
|
|
|
|
priv = skbpriv(skb);
|
|
if (unlikely(!priv)) {
|
|
mif_err("%s: ERR! No PRIV in skb@%pK\n", ld->name, skb);
|
|
dev_kfree_skb_any(skb);
|
|
shmem_forced_cp_crash(mld, CRASH_REASON_MIF_RX_BAD_DATA,
|
|
"ERR! No PRIV in pass_skb_to_net()");
|
|
return;
|
|
}
|
|
|
|
iod = priv->iod;
|
|
if (unlikely(!iod)) {
|
|
mif_err("%s: ERR! No IOD in skb@%pK\n", ld->name, skb);
|
|
dev_kfree_skb_any(skb);
|
|
shmem_forced_cp_crash(mld, CRASH_REASON_MIF_RX_BAD_DATA,
|
|
"ERR! No IOD in pass_skb_to_net()");
|
|
return;
|
|
}
|
|
|
|
#ifdef DEBUG_MODEM_IF_LINK_RX
|
|
mif_pkt(iod->id, "LNK-RX", skb);
|
|
#endif
|
|
|
|
ret = iod->recv_net_skb(iod, ld, skb);
|
|
if (unlikely(ret < 0)) {
|
|
struct modem_ctl *mc = ld->mc;
|
|
mif_err_limited("%s: %s<-%s: ERR! %s->recv_net_skb fail (%d)\n",
|
|
ld->name, iod->name, mc->name, iod->name, ret);
|
|
dev_kfree_skb_any(skb);
|
|
}
|
|
}
|
|
|
|
#define FREE_RB_BUF_COUNT 200
|
|
static int rx_net_frames_from_zerocopy_adaptor(struct sbd_ring_buffer *rb,
|
|
int budget, int *work_done)
|
|
{
|
|
int rcvd = 0;
|
|
struct link_device *ld = rb->ld;
|
|
struct mem_link_device *mld = ld_to_mem_link_device(ld);
|
|
struct zerocopy_adaptor *zdptr = rb->zdptr;
|
|
unsigned int num_frames;
|
|
int use_memcpy = 0;
|
|
|
|
#ifdef CONFIG_LINK_DEVICE_NAPI
|
|
num_frames = min_t(unsigned int, rb_usage(rb), budget);
|
|
#else /* !CONFIG_LINK_DEVICE_NAPI */
|
|
num_frames = rb_usage(rb);
|
|
#endif /* CONFIG_LINK_DEVICE_NAPI */
|
|
|
|
if (mld->force_use_memcpy || (num_frames > ld->mif_buff_mng->free_cell_count)
|
|
|| (FREE_RB_BUF_COUNT > circ_get_space(zdptr->len, *(zdptr->rp), *(zdptr->wp)))) {
|
|
use_memcpy = 1;
|
|
mld->memcpy_packet_count++;
|
|
} else {
|
|
use_memcpy = 0;
|
|
mld->zeromemcpy_packet_count++;
|
|
}
|
|
|
|
while (rcvd < num_frames) {
|
|
struct sk_buff *skb;
|
|
|
|
skb = sbd_pio_rx_zerocopy_adaptor(rb, use_memcpy);
|
|
if (!skb)
|
|
break;
|
|
|
|
/* The $rcvd must be accumulated here, because $skb can be freed
|
|
in pass_skb_to_net(). */
|
|
rcvd++;
|
|
|
|
pass_skb_to_net(mld, skb);
|
|
}
|
|
|
|
if (rcvd < num_frames) {
|
|
struct io_device *iod = rb->iod;
|
|
struct link_device *ld = rb->ld;
|
|
struct modem_ctl *mc = ld->mc;
|
|
mif_err("%s: %s<-%s: WARN! rcvd %d < num_frames %d\n",
|
|
ld->name, iod->name, mc->name, rcvd, num_frames);
|
|
}
|
|
|
|
#ifdef CONFIG_LINK_DEVICE_NAPI
|
|
*work_done = rcvd;
|
|
allocate_data_in_advance(zdptr);
|
|
#else /* !CONFIG_LINK_DEVICE_NAPI */
|
|
start_datalloc_timer(mld, &zdptr->datalloc_timer);
|
|
#endif /* CONFIG_LINK_DEVICE_NAPI */
|
|
|
|
return rcvd;
|
|
}
|
|
|
|
static int rx_net_frames_from_rb(struct sbd_ring_buffer *rb, int budget,
|
|
int *work_done)
|
|
{
|
|
int rcvd = 0;
|
|
struct link_device *ld = rb->ld;
|
|
struct mem_link_device *mld = ld_to_mem_link_device(ld);
|
|
unsigned int num_frames;
|
|
|
|
#ifdef CONFIG_LINK_DEVICE_NAPI
|
|
num_frames = min_t(unsigned int, rb_usage(rb), budget);
|
|
#else /* !CONFIG_LINK_DEVICE_NAPI */
|
|
num_frames = rb_usage(rb);
|
|
#endif /* CONFIG_LINK_DEVICE_NAPI */
|
|
|
|
while (rcvd < num_frames) {
|
|
struct sk_buff *skb;
|
|
|
|
skb = sbd_pio_rx(rb);
|
|
if (!skb)
|
|
return -ENOMEM;
|
|
|
|
/* The $rcvd must be accumulated here, because $skb can be freed
|
|
in pass_skb_to_net(). */
|
|
rcvd++;
|
|
|
|
pass_skb_to_net(mld, skb);
|
|
}
|
|
|
|
if (rcvd < num_frames) {
|
|
struct io_device *iod = rb->iod;
|
|
struct link_device *ld = rb->ld;
|
|
struct modem_ctl *mc = ld->mc;
|
|
mif_err("%s: %s<-%s: WARN! rcvd %d < num_frames %d\n",
|
|
ld->name, iod->name, mc->name, rcvd, num_frames);
|
|
}
|
|
|
|
#ifdef CONFIG_LINK_DEVICE_NAPI
|
|
*work_done = rcvd;
|
|
#endif /* CONFIG_LINK_DEVICE_NAPI */
|
|
|
|
return rcvd;
|
|
}
|
|
|
|
static int rx_ipc_frames_from_zerocopy_adaptor(struct sbd_ring_buffer *rb)
|
|
{
|
|
int rcvd = 0;
|
|
struct link_device *ld = rb->ld;
|
|
struct mem_link_device *mld = ld_to_mem_link_device(ld);
|
|
struct zerocopy_adaptor *zdptr = rb->zdptr;
|
|
unsigned int num_frames = zerocopy_adaptor_usage(zdptr);
|
|
|
|
while (rcvd < num_frames) {
|
|
struct sk_buff *skb;
|
|
|
|
skb = sbd_pio_rx_zerocopy_adaptor(rb, 0);
|
|
if (!skb) {
|
|
#ifdef DEBUG_MODEM_IF
|
|
panic("skb alloc failed.");
|
|
#else
|
|
shmem_forced_cp_crash(mld, CRASH_REASON_MIF_ZMC,
|
|
"ERR! ZMC alloc failed");
|
|
#endif
|
|
break;
|
|
}
|
|
|
|
/* The $rcvd must be accumulated here, because $skb can be freed
|
|
in pass_skb_to_demux(). */
|
|
rcvd++;
|
|
|
|
if (skbpriv(skb)->lnk_hdr) {
|
|
u8 ch = rb->ch;
|
|
u8 fch = sipc5_get_ch(skb->data);
|
|
if (fch != ch) {
|
|
mif_err("frm.ch:%d != rb.ch:%d\n", fch, ch);
|
|
pr_skb("CRASH", skb);
|
|
dev_kfree_skb_any(skb);
|
|
shmem_forced_cp_crash(mld, CRASH_REASON_MIF_RX_BAD_DATA,
|
|
"frm.ch is not same with rb.ch");
|
|
continue;
|
|
}
|
|
}
|
|
|
|
pass_skb_to_demux(mld, skb);
|
|
}
|
|
|
|
if (rcvd < num_frames) {
|
|
struct io_device *iod = rb->iod;
|
|
struct modem_ctl *mc = ld->mc;
|
|
mif_err("%s: %s<-%s: WARN! rcvd %d < num_frames %d\n",
|
|
ld->name, iod->name, mc->name, rcvd, num_frames);
|
|
}
|
|
|
|
start_datalloc_timer(mld, &zdptr->datalloc_timer);
|
|
|
|
return rcvd;
|
|
}
|
|
|
|
static int rx_ipc_frames_from_rb(struct sbd_ring_buffer *rb)
|
|
{
|
|
int rcvd = 0;
|
|
struct link_device *ld = rb->ld;
|
|
struct mem_link_device *mld = ld_to_mem_link_device(ld);
|
|
unsigned int qlen = rb->len;
|
|
unsigned int in = *rb->wp;
|
|
unsigned int out = *rb->rp;
|
|
unsigned int num_frames = circ_get_usage(qlen, in, out);
|
|
|
|
while (rcvd < num_frames) {
|
|
struct sk_buff *skb;
|
|
|
|
skb = sbd_pio_rx(rb);
|
|
if (!skb)
|
|
return -ENOMEM;
|
|
|
|
/* The $rcvd must be accumulated here, because $skb can be freed
|
|
in pass_skb_to_demux(). */
|
|
rcvd++;
|
|
|
|
if (skbpriv(skb)->lnk_hdr) {
|
|
u8 ch = rb->ch;
|
|
u8 fch = sipc5_get_ch(skb->data);
|
|
if (fch != ch) {
|
|
mif_err("frm.ch:%d != rb.ch:%d\n", fch, ch);
|
|
pr_skb("CRASH", skb);
|
|
dev_kfree_skb_any(skb);
|
|
shmem_forced_cp_crash(mld, CRASH_REASON_MIF_RX_BAD_DATA,
|
|
"frm.ch is not same with rb.ch");
|
|
continue;
|
|
}
|
|
}
|
|
|
|
pass_skb_to_demux(mld, skb);
|
|
}
|
|
|
|
if (rcvd < num_frames) {
|
|
struct io_device *iod = rb->iod;
|
|
struct modem_ctl *mc = ld->mc;
|
|
mif_err("%s: %s<-%s: WARN! rcvd %d < num_frames %d\n",
|
|
ld->name, iod->name, mc->name, rcvd, num_frames);
|
|
}
|
|
return rcvd;
|
|
}
|
|
|
|
#ifdef CONFIG_LINK_DEVICE_NAPI
|
|
static int shmem_poll_recv_on_iod(struct link_device *ld, struct io_device *iod,
|
|
int budget)
|
|
{
|
|
struct mem_link_device *mld = to_mem_link_device(ld);
|
|
struct sbd_ring_buffer *rb = sbd_ch2rb(&mld->sbd_link_dev, iod->id, RX);
|
|
int rcvd = 0;
|
|
int ret;
|
|
|
|
if (rb->zerocopy)
|
|
ret = rx_net_frames_from_zerocopy_adaptor(rb, budget, &rcvd);
|
|
else
|
|
ret = rx_net_frames_from_rb(rb, budget, &rcvd);
|
|
|
|
if (IS_ERR_VALUE((unsigned long)ret))
|
|
mif_err_limited("RX error (%d)\n", ret);
|
|
|
|
return rcvd;
|
|
}
|
|
|
|
static ktime_t rx_int_enable_time;
|
|
static ktime_t rx_int_disable_time;
|
|
|
|
static int shmem_enable_rx_int(struct link_device *ld)
|
|
{
|
|
struct mem_link_device *mld = to_mem_link_device(ld);
|
|
mld->rx_int_enable = 1;
|
|
if (rx_int_disable_time) {
|
|
rx_int_enable_time = ktime_get();
|
|
mld->rx_int_disabled_time += ktime_to_us(ktime_sub(rx_int_enable_time, rx_int_disable_time));
|
|
rx_int_enable_time = 0;
|
|
rx_int_disable_time = 0;
|
|
}
|
|
return mbox_enable_irq(MCU_CP, mld->irq_cp2ap_msg);
|
|
}
|
|
|
|
static int shmem_disable_rx_int(struct link_device *ld)
|
|
{
|
|
struct mem_link_device *mld = to_mem_link_device(ld);
|
|
mld->rx_int_enable = 0;
|
|
rx_int_disable_time = ktime_get();
|
|
return mbox_disable_irq(MCU_CP, mld->irq_cp2ap_msg);
|
|
}
|
|
#endif /* CONFIG_LINK_DEVICE_NAPI */
|
|
|
|
static int recv_sbd_ipc_frames(struct mem_link_device *mld,
|
|
struct mem_snapshot *mst, int budget)
|
|
{
|
|
struct sbd_link_device *sl = &mld->sbd_link_dev;
|
|
int i;
|
|
int total_ps_rcvd = 0;
|
|
int total_non_ps_rcvd = 0;
|
|
|
|
for (i = 0; i < sl->num_channels; i++) {
|
|
struct sbd_ring_buffer *rb = sbd_id2rb(sl, i, RX);
|
|
int rcvd = 0;
|
|
|
|
if (unlikely(rb_empty(rb)))
|
|
continue;
|
|
|
|
if (likely(sipc_ps_ch(rb->ch))) {
|
|
if (rb->zerocopy)
|
|
rcvd = rx_net_frames_from_zerocopy_adaptor(rb, budget, &rcvd);
|
|
else
|
|
rcvd = rx_net_frames_from_rb(rb, budget, &rcvd);
|
|
|
|
budget -= rcvd;
|
|
total_ps_rcvd += rcvd;
|
|
} else {
|
|
if (rb->zerocopy)
|
|
rcvd = rx_ipc_frames_from_zerocopy_adaptor(rb);
|
|
else
|
|
rcvd = rx_ipc_frames_from_rb(rb);
|
|
|
|
total_non_ps_rcvd += rcvd;
|
|
}
|
|
|
|
if (rcvd < 0)
|
|
return rcvd;
|
|
}
|
|
return total_ps_rcvd;
|
|
}
|
|
|
|
static void shmem_oom_handler_work(struct work_struct *ws)
|
|
{
|
|
struct mem_link_device *mld =
|
|
container_of(ws, struct mem_link_device, page_reclaim_work);
|
|
struct sk_buff *skb;
|
|
|
|
/* try to page reclaim with GFP_KERNEL */
|
|
skb = alloc_skb(PAGE_SIZE - 512, GFP_KERNEL);
|
|
if (skb)
|
|
dev_consume_skb_any(skb);
|
|
|
|
/* need to disable the RX irq ?? */
|
|
msleep(200);
|
|
|
|
mif_info("trigger the rx task again\n");
|
|
tasklet_schedule(&mld->rx_tsk);
|
|
}
|
|
|
|
static int ipc_rx_func(struct mem_link_device *mld, int budget)
|
|
{
|
|
u32 qlen = mld->msb_rxq.qlen;
|
|
int total_ps_rcvd = 0;
|
|
int ps_rcvd = 0;
|
|
|
|
while (qlen-- > 0) {
|
|
struct mst_buff *msb;
|
|
u16 intr;
|
|
int ret = 0;
|
|
|
|
msb = msb_dequeue(&mld->msb_rxq);
|
|
if (!msb)
|
|
break;
|
|
|
|
intr = msb->snapshot.int2ap;
|
|
|
|
if (cmd_valid(intr))
|
|
mld->cmd_handler(mld, int2cmd(intr));
|
|
|
|
if (sbd_active(&mld->sbd_link_dev)) {
|
|
ps_rcvd = recv_sbd_ipc_frames(mld, &msb->snapshot, budget);
|
|
if (ps_rcvd >= 0)
|
|
total_ps_rcvd += ps_rcvd;
|
|
else
|
|
ret = ps_rcvd;
|
|
} else
|
|
ret = recv_ipc_frames(mld, &msb->snapshot);
|
|
|
|
if (ret == -ENOMEM) {
|
|
msb_queue_head(&mld->msb_rxq, msb);
|
|
if (!work_pending(&mld->page_reclaim_work)) {
|
|
struct link_device *ld = &mld->link_dev;
|
|
mif_err_limited("Rx ENOMEM, try reclaim work");
|
|
queue_work(ld->rx_wq,
|
|
&mld->page_reclaim_work);
|
|
}
|
|
return 0;
|
|
}
|
|
msb_free(msb);
|
|
}
|
|
|
|
return total_ps_rcvd;
|
|
}
|
|
|
|
static void udl_rx_work(struct work_struct *ws)
|
|
{
|
|
struct mem_link_device *mld;
|
|
|
|
mld = container_of(ws, struct mem_link_device, udl_rx_dwork.work);
|
|
|
|
ipc_rx_func(mld, 0);
|
|
}
|
|
|
|
static void shmem_rx_task(unsigned long data)
|
|
{
|
|
struct mem_link_device *mld = (struct mem_link_device *)data;
|
|
struct link_device *ld = &mld->link_dev;
|
|
struct modem_ctl *mc = ld->mc;
|
|
|
|
if (likely(cp_online(mc)))
|
|
ipc_rx_func(mld, 0);
|
|
else
|
|
queue_delayed_work(ld->rx_wq, &mld->udl_rx_dwork, 0);
|
|
}
|
|
|
|
#endif
|
|
|
|
/*============================================================================*/
|
|
|
|
#ifdef GROUP_MEM_LINK_METHOD
|
|
|
|
static int shmem_init_comm(struct link_device *ld, struct io_device *iod)
|
|
{
|
|
struct mem_link_device *mld = to_mem_link_device(ld);
|
|
struct modem_ctl *mc = ld->mc;
|
|
struct io_device *check_iod;
|
|
int id = iod->id;
|
|
int fmt2rfs = (SIPC5_CH_ID_RFS_0 - SIPC5_CH_ID_FMT_0);
|
|
int rfs2fmt = (SIPC5_CH_ID_FMT_0 - SIPC5_CH_ID_RFS_0);
|
|
|
|
if (atomic_read(&mld->cp_boot_done))
|
|
return 0;
|
|
|
|
#ifdef CONFIG_LINK_CONTROL_MSG_IOSM
|
|
if (mld->iosm) {
|
|
struct sbd_link_device *sl = &mld->sbd_link_dev;
|
|
struct sbd_ipc_device *sid = sbd_ch2dev(sl, iod->id);
|
|
|
|
if (atomic_read(&sid->config_done)) {
|
|
tx_iosm_message(mld, IOSM_A2C_OPEN_CH, (u32 *)&id);
|
|
return 0;
|
|
} else {
|
|
mif_err("%s isn't configured channel\n", iod->name);
|
|
return -ENODEV;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
switch (id) {
|
|
case SIPC5_CH_ID_FMT_0 ... SIPC5_CH_ID_FMT_9:
|
|
check_iod = link_get_iod_with_channel(ld, (id + fmt2rfs));
|
|
if (check_iod ? atomic_read(&check_iod->opened) : true) {
|
|
write_clk_table_to_shmem(mld);
|
|
#ifdef CONFIG_CP_ETDAC_OTP_WA
|
|
write_cp_etdac_otp_to_shmem(mld);
|
|
#endif
|
|
mif_err("%s: %s->INIT_END->%s\n",
|
|
ld->name, iod->name, mc->name);
|
|
send_ipc_irq(mld, cmd2int(CMD_INIT_END));
|
|
atomic_set(&mld->cp_boot_done, 1);
|
|
} else {
|
|
mif_err("%s is not opened yet\n", check_iod->name);
|
|
}
|
|
break;
|
|
|
|
case SIPC5_CH_ID_RFS_0 ... SIPC5_CH_ID_RFS_9:
|
|
check_iod = link_get_iod_with_channel(ld, (id + rfs2fmt));
|
|
if (check_iod) {
|
|
if (atomic_read(&check_iod->opened)) {
|
|
write_clk_table_to_shmem(mld);
|
|
#ifdef CONFIG_CP_ETDAC_OTP_WA
|
|
write_cp_etdac_otp_to_shmem(mld);
|
|
#endif
|
|
mif_err("%s: %s->INIT_END->%s\n",
|
|
ld->name, iod->name, mc->name);
|
|
send_ipc_irq(mld, cmd2int(CMD_INIT_END));
|
|
atomic_set(&mld->cp_boot_done, 1);
|
|
} else {
|
|
mif_err("%s not opened yet\n", check_iod->name);
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void shmem_terminate_comm(struct link_device *ld, struct io_device *iod)
|
|
{
|
|
#ifdef CONFIG_LINK_CONTROL_MSG_IOSM
|
|
struct mem_link_device *mld = to_mem_link_device(ld);
|
|
|
|
if (mld->iosm)
|
|
tx_iosm_message(mld, IOSM_A2C_CLOSE_CH, (u32 *)&iod->id);
|
|
#endif
|
|
}
|
|
|
|
static int shmem_send(struct link_device *ld, struct io_device *iod,
|
|
struct sk_buff *skb)
|
|
{
|
|
struct mem_link_device *mld = to_mem_link_device(ld);
|
|
struct modem_ctl *mc = ld->mc;
|
|
enum dev_format id = iod->format;
|
|
u8 ch = iod->id;
|
|
|
|
switch (id) {
|
|
case IPC_RAW:
|
|
if (sipc_ps_ch(iod->id)) {
|
|
if (unlikely(atomic_read(&ld->netif_stopped) > 0)) {
|
|
if (skb->queue_mapping != 1) {
|
|
if (in_interrupt()) {
|
|
mif_err("raw tx is suspended, drop size=%d\n",
|
|
skb->len);
|
|
return -EBUSY;
|
|
}
|
|
|
|
mif_err("wait TX RESUME CMD...\n");
|
|
reinit_completion(&ld->raw_tx_resumed);
|
|
wait_for_completion_timeout(&ld->raw_tx_resumed,
|
|
msecs_to_jiffies(3000));
|
|
mif_err("TX resumed done.\n");
|
|
} else {
|
|
mif_err_limited("Tx_flowctrl, but received ack from ch %d\n", ch);
|
|
}
|
|
}
|
|
}
|
|
|
|
case IPC_RFS:
|
|
case IPC_FMT:
|
|
if (likely(sipc5_ipc_ch(ch)))
|
|
return xmit_ipc(mld, iod, ch, skb);
|
|
else
|
|
return xmit_udl(mld, iod, ch, skb);
|
|
|
|
case IPC_BOOT:
|
|
case IPC_DUMP:
|
|
if (sipc5_udl_ch(ch))
|
|
return xmit_udl(mld, iod, ch, skb);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
mif_err("%s:%s->%s: ERR! Invalid IO device (format:%s id:%d)\n",
|
|
ld->name, iod->name, mc->name, dev_str(id), ch);
|
|
|
|
return -ENODEV;
|
|
}
|
|
|
|
static void shmem_boot_on(struct link_device *ld, struct io_device *iod)
|
|
{
|
|
struct mem_link_device *mld = to_mem_link_device(ld);
|
|
unsigned long flags;
|
|
|
|
atomic_set(&mld->cp_boot_done, 0);
|
|
|
|
spin_lock_irqsave(&mld->state_lock, flags);
|
|
mld->state = LINK_STATE_OFFLINE;
|
|
spin_unlock_irqrestore(&mld->state_lock, flags);
|
|
|
|
cancel_tx_timer(mld, &mld->tx_timer);
|
|
|
|
if (ld->sbd_ipc) {
|
|
#ifdef CONFIG_LTE_MODEM_XMM7260
|
|
sbd_deactivate(&mld->sbd_link_dev);
|
|
#endif
|
|
cancel_tx_timer(mld, &mld->sbd_tx_timer);
|
|
cancel_datalloc_timer(mld);
|
|
|
|
if (mld->iosm) {
|
|
memset(mld->base + CMD_RGN_OFFSET, 0, CMD_RGN_SIZE);
|
|
mif_info("Control message region has been initialized\n");
|
|
}
|
|
}
|
|
|
|
purge_txq(mld);
|
|
}
|
|
|
|
static int shmem_xmit_boot(struct link_device *ld, struct io_device *iod,
|
|
unsigned long arg)
|
|
{
|
|
struct mem_link_device *mld = to_mem_link_device(ld);
|
|
struct resource *cpmem_info = mld->syscp_info;
|
|
void __iomem *dst;
|
|
void __user *src;
|
|
int err;
|
|
struct modem_firmware mf;
|
|
void __iomem *v_base;
|
|
size_t valid_space;
|
|
|
|
/**
|
|
* Get the information about the boot image
|
|
*/
|
|
memset(&mf, 0, sizeof(struct modem_firmware));
|
|
|
|
err = copy_from_user(&mf, (const void __user *)arg, sizeof(mf));
|
|
if (err) {
|
|
mif_err("%s: ERR! INFO copy_from_user fail\n", ld->name);
|
|
return -EFAULT;
|
|
}
|
|
|
|
/* Calculate size of valid space which BL will download */
|
|
valid_space = (mf.mode) ? mld->size : mld->boot_size;
|
|
/* Calculate base address (0: BOOT_MODE, 1: DUMP_MODE) */
|
|
v_base = (mf.mode) ? mld->base : mld->boot_base;
|
|
|
|
/**
|
|
* Check the size of the boot image
|
|
* fix the integer overflow of "mf.m_offset + mf.len" from Jose Duart
|
|
*/
|
|
if (mf.size > valid_space || mf.len > valid_space
|
|
|| mf.m_offset > valid_space - mf.len) {
|
|
mif_err("%s: ERR! Invalid args: size %x, offset %x, len %x\n",
|
|
ld->name, mf.size, mf.m_offset, mf.len);
|
|
return -EINVAL;
|
|
}
|
|
|
|
dst = (void __iomem *)(v_base + mf.m_offset);
|
|
src = (void __user *)((unsigned long)mf.binary);
|
|
|
|
if (mf.m_offset == (u32)cpmem_info->start)
|
|
{
|
|
mld->cp_binary_size = mf.size;
|
|
}
|
|
|
|
err = copy_from_user(dst, src, mf.len);
|
|
if (err) {
|
|
mif_err("%s: ERR! BOOT copy_from_user fail\n", ld->name);
|
|
return err;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#if !defined(CONFIG_CP_SECURE_BOOT)
|
|
unsigned long shmem_calculate_CRC32(const unsigned char *buf, unsigned long len)
|
|
{
|
|
unsigned long ul_crc;
|
|
|
|
if (0 == 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(struct link_device *ld)
|
|
{
|
|
struct mem_link_device *mld = to_mem_link_device(ld);
|
|
struct resource *cpmem_info = mld->syscp_info;
|
|
unsigned char * data;
|
|
unsigned long CRC;
|
|
|
|
data = (unsigned char *)mld->boot_base + (u32)cpmem_info->start;
|
|
|
|
CRC = shmem_calculate_CRC32(data, mld->cp_binary_size);
|
|
|
|
mif_info("Modem Main Binary CRC: %08X\n", (unsigned int)CRC);
|
|
|
|
}
|
|
#endif
|
|
|
|
static int cp_init_done;
|
|
|
|
static int shmem_security_request(struct link_device *ld, struct io_device *iod,
|
|
unsigned long arg)
|
|
{
|
|
unsigned long param2, param3;
|
|
int err = 0;
|
|
struct modem_sec_req msr;
|
|
|
|
err = copy_from_user(&msr, (const void __user *)arg, sizeof(msr));
|
|
if (err) {
|
|
mif_err("%s: ERR! copy_from_user fail\n", ld->name);
|
|
err = -EFAULT;
|
|
goto exit;
|
|
}
|
|
|
|
param2 = shm_get_security_param2(msr.mode, msr.param2);
|
|
param3 = shm_get_security_param3(msr.mode, msr.param3);
|
|
|
|
#if !defined(CONFIG_CP_SECURE_BOOT)
|
|
if (msr.mode == 0)
|
|
shmem_check_modem_binary_crc(ld);
|
|
#else
|
|
exynos_smc(SMC_ID_CLK, SSS_CLK_ENABLE, 0, 0);
|
|
|
|
if (shm_get_use_cp_memory_map_flag() && msr.mode == 0)
|
|
msr.mode |= (unsigned int)shm_get_phys_base();
|
|
|
|
if ((IS_ENABLED(CONFIG_SOC_EXYNOS9810) ||
|
|
IS_ENABLED(CONFIG_SOC_EXYNOS9110) ||
|
|
IS_ENABLED(CONFIG_SOC_EXYNOS9610)) && !cp_init_done) {
|
|
if (cal_cp_status() == 0) {
|
|
mif_err("CP first Init!\n");
|
|
cal_cp_init();
|
|
}
|
|
cp_init_done = 1;
|
|
}
|
|
|
|
mif_err("mode=%u, size=0x%lx, addr=0x%lx, cp_base_addr=0x%lx\n",
|
|
msr.mode, param2, param3, shm_get_phys_base());
|
|
|
|
err = exynos_smc(SMC_ID, msr.mode, param2, param3);
|
|
|
|
exynos_smc(SMC_ID_CLK, SSS_CLK_DISABLE, 0, 0);
|
|
mif_info("%s: return_value=0x%08x(%s)\n", ld->name, err,
|
|
err < sizeof(smc_err_string) ? smc_err_string[err] : "NULL");
|
|
#endif
|
|
|
|
exit:
|
|
return err;
|
|
}
|
|
|
|
#ifdef CONFIG_MODEM_IF_NET_GRO
|
|
long gro_flush_time = 0;
|
|
module_param(gro_flush_time, long, 0644);
|
|
|
|
static void gro_flush_timer(struct link_device *ld)
|
|
{
|
|
struct mem_link_device *mld = to_mem_link_device(ld);
|
|
struct timespec curr, diff;
|
|
|
|
if (!gro_flush_time) {
|
|
napi_gro_flush(&mld->mld_napi, false);
|
|
return;
|
|
}
|
|
|
|
if (unlikely(mld->flush_time.tv_sec == 0)) {
|
|
getnstimeofday(&mld->flush_time);
|
|
} else {
|
|
getnstimeofday(&(curr));
|
|
diff = timespec_sub(curr, mld->flush_time);
|
|
if ((diff.tv_sec > 0) || (diff.tv_nsec > gro_flush_time)) {
|
|
napi_gro_flush(&mld->mld_napi, false);
|
|
getnstimeofday(&mld->flush_time);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef CONFIG_LINK_DEVICE_NAPI
|
|
static int shmem_enqueue_snapshot(struct mem_link_device *mld);
|
|
|
|
/*
|
|
* mld_rx_int_poll
|
|
*
|
|
* This NAPI poll function does not handle reception of any network frames.
|
|
* It is used for servicing CP2AP commands and FMT RX frames while the RX
|
|
* mailbox interrupt is masked. When the mailbox interrupt is masked, CP can
|
|
* set the interrupt but the AP will not react. However, the interrupt status
|
|
* bit will still be set, so we can poll the status bit to handle new RX
|
|
* interrupts.
|
|
* If the RAW NAPI functions are no longer scheduled at the end of this poll
|
|
* function, we can enable the mailbox interrupt and stop polling.
|
|
*/
|
|
static int mld_rx_int_poll(struct napi_struct *napi, int budget)
|
|
{
|
|
struct mem_link_device *mld = container_of(napi, struct mem_link_device,
|
|
mld_napi);
|
|
struct link_device *ld = &mld->link_dev;
|
|
struct modem_ctl *mc = ld->mc;
|
|
struct sbd_link_device *sl = &mld->sbd_link_dev;
|
|
int total_ps_rcvd = 0;
|
|
int ps_rcvd = 0;
|
|
int i;
|
|
int ret;
|
|
int total_budget;
|
|
|
|
ret = mbox_check_irq(MCU_CP, mld->irq_cp2ap_msg);
|
|
if (IS_ERR_VALUE((unsigned long)ret))
|
|
goto dummy_poll_complete;
|
|
|
|
mld->rx_poll_count++;
|
|
|
|
if (ret) {
|
|
/* If there was a new interrupt, do what irq_handler would have done. */
|
|
if (shmem_enqueue_snapshot(mld))
|
|
goto dummy_poll_complete;
|
|
|
|
if (likely(cp_online(mc)))
|
|
total_ps_rcvd = ipc_rx_func(mld, budget);
|
|
else
|
|
queue_delayed_work(ld->rx_wq, &mld->udl_rx_dwork, 0);
|
|
|
|
if (total_ps_rcvd) {
|
|
if (total_ps_rcvd < budget) {
|
|
napi_complete_done(napi, total_ps_rcvd);
|
|
ld->enable_rx_int(ld);
|
|
}
|
|
return total_ps_rcvd;
|
|
}
|
|
goto dummy_poll_complete;
|
|
} else {
|
|
/* Leave interrupt disabled and poll if NET polling is not finished. */
|
|
total_budget = budget;
|
|
|
|
for (i = 0; i < sl->num_channels; i++) {
|
|
struct sbd_ring_buffer *rb = sbd_id2rb(sl, i, RX);
|
|
if (likely(sipc_ps_ch(rb->ch))) {
|
|
ps_rcvd = shmem_poll_recv_on_iod(ld, rb->iod, budget);
|
|
budget -= ps_rcvd;
|
|
total_ps_rcvd += ps_rcvd;
|
|
}
|
|
}
|
|
|
|
if (total_ps_rcvd) {
|
|
if (total_ps_rcvd < total_budget) {
|
|
napi_complete_done(napi, total_ps_rcvd);
|
|
ld->enable_rx_int(ld);
|
|
}
|
|
} else {
|
|
napi_complete(napi);
|
|
ld->enable_rx_int(ld);
|
|
}
|
|
|
|
return total_ps_rcvd;
|
|
}
|
|
|
|
dummy_poll_complete:
|
|
napi_complete(napi);
|
|
ld->enable_rx_int(ld);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void sync_net_dev(struct link_device *ld)
|
|
{
|
|
struct mem_link_device *mld = to_mem_link_device(ld);
|
|
|
|
napi_synchronize(&mld->mld_napi);
|
|
mif_info("%s\n", netdev_name(&mld->dummy_net));
|
|
}
|
|
#endif /* CONFIG_LINK_DEVICE_NAPI */
|
|
|
|
static int shmem_start_download(struct link_device *ld, struct io_device *iod)
|
|
{
|
|
struct mem_link_device *mld = to_mem_link_device(ld);
|
|
|
|
if (ld->sbd_ipc && mld->attrs & LINK_ATTR(LINK_ATTR_MEM_DUMP))
|
|
sbd_deactivate(&mld->sbd_link_dev);
|
|
|
|
#ifdef CONFIG_LINK_DEVICE_NAPI
|
|
sync_net_dev(ld);
|
|
#endif /* CONFIG_LINK_DEVICE_NAPI */
|
|
|
|
reset_ipc_map(mld);
|
|
|
|
if (mld->attrs & LINK_ATTR(LINK_ATTR_BOOT_ALIGNED))
|
|
ld->aligned = true;
|
|
else
|
|
ld->aligned = false;
|
|
|
|
if (mld->dpram_magic) {
|
|
unsigned int magic;
|
|
|
|
set_magic(mld, MEM_BOOT_MAGIC);
|
|
magic = get_magic(mld);
|
|
if (magic != MEM_BOOT_MAGIC) {
|
|
mif_err("%s: ERR! magic 0x%08X != BOOT_MAGIC 0x%08X\n",
|
|
ld->name, magic, MEM_BOOT_MAGIC);
|
|
return -EFAULT;
|
|
}
|
|
mif_err("%s: magic == 0x%08X\n", ld->name, magic);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int shmem_update_firm_info(struct link_device *ld, struct io_device *iod,
|
|
unsigned long arg)
|
|
{
|
|
struct mem_link_device *mld = to_mem_link_device(ld);
|
|
int ret;
|
|
|
|
ret = copy_from_user(&mld->img_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 mem_link_device *mld = to_mem_link_device(ld);
|
|
u32 crash_type = ld->crash_type;
|
|
|
|
mif_err("+++\n");
|
|
shmem_forced_cp_crash(mld, crash_type,
|
|
"forced crash is called by ioctl command");
|
|
mif_err("---\n");
|
|
return 0;
|
|
}
|
|
|
|
static int shmem_start_upload(struct link_device *ld, struct io_device *iod)
|
|
{
|
|
struct mem_link_device *mld = to_mem_link_device(ld);
|
|
|
|
if (ld->sbd_ipc && mld->attrs & LINK_ATTR(LINK_ATTR_MEM_DUMP))
|
|
sbd_deactivate(&mld->sbd_link_dev);
|
|
|
|
#ifdef CONFIG_LINK_DEVICE_NAPI
|
|
sync_net_dev(ld);
|
|
#endif /* CONFIG_LINK_DEVICE_NAPI */
|
|
|
|
reset_ipc_map(mld);
|
|
|
|
if (mld->attrs & LINK_ATTR(LINK_ATTR_DUMP_ALIGNED))
|
|
ld->aligned = true;
|
|
else
|
|
ld->aligned = false;
|
|
|
|
if (mld->dpram_magic) {
|
|
unsigned int magic;
|
|
|
|
set_magic(mld, MEM_DUMP_MAGIC);
|
|
magic = get_magic(mld);
|
|
if (magic != MEM_DUMP_MAGIC) {
|
|
mif_err("%s: ERR! magic 0x%08X != DUMP_MAGIC 0x%08X\n",
|
|
ld->name, magic, MEM_DUMP_MAGIC);
|
|
return -EFAULT;
|
|
}
|
|
mif_err("%s: magic == 0x%08X\n", ld->name, magic);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void shmem_close_tx(struct link_device *ld)
|
|
{
|
|
struct mem_link_device *mld = to_mem_link_device(ld);
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&mld->state_lock, flags);
|
|
mld->state = LINK_STATE_OFFLINE;
|
|
spin_unlock_irqrestore(&mld->state_lock, flags);
|
|
|
|
if (timer_pending(&mld->crash_ack_timer))
|
|
del_timer(&mld->crash_ack_timer);
|
|
|
|
stop_net_ifaces(ld);
|
|
purge_txq(mld);
|
|
}
|
|
|
|
static int shmem_crash_reason(struct link_device *ld, struct io_device *iod,
|
|
unsigned long arg)
|
|
{
|
|
struct mem_link_device *mld = to_mem_link_device(ld);
|
|
int ret;
|
|
|
|
ret = copy_to_user((void __user *)arg, &mld->crash_reason,
|
|
sizeof(struct crash_reason));
|
|
if (ret) {
|
|
mif_err("ERR! copy_to_user fail!\n");
|
|
return -EFAULT;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int shmem_airplane_mode(struct link_device *ld, struct io_device *iod,
|
|
unsigned long arg)
|
|
{
|
|
struct modem_ctl *mc = ld->mc;
|
|
struct mem_link_device *mld = to_mem_link_device(ld);
|
|
|
|
mc->airplane_mode = (unsigned int)arg;
|
|
mif_info("Set airplane mode: %d\n", mc->airplane_mode);
|
|
|
|
if (mc->airplane_mode)
|
|
shmem_unlock_mif_freq(mld);
|
|
else
|
|
shmem_restore_mif_freq(mld);
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
/*============================================================================*/
|
|
|
|
|
|
static u16 recv_cp2ap_irq(struct mem_link_device *mld)
|
|
{
|
|
return (u16)mbox_get_value(MCU_CP, mld->mbx_cp2ap_msg);
|
|
}
|
|
|
|
static u16 recv_cp2ap_status(struct mem_link_device *mld)
|
|
{
|
|
return (u16)mbox_extract_value(MCU_CP, mld->mbx_cp2ap_status,
|
|
mld->sbi_cp_status_mask, mld->sbi_cp_status_pos);
|
|
}
|
|
|
|
static void send_ap2cp_irq(struct mem_link_device *mld, u16 mask)
|
|
{
|
|
#if 0
|
|
struct link_device *ld = &mld->link_dev;
|
|
struct modem_ctl *mc = ld->mc;
|
|
|
|
if (!cp_online(mc))
|
|
mif_err("%s: mask 0x%04X (%s.state == %s)\n", ld->name, mask,
|
|
mc->name, mc_state(mc));
|
|
#endif
|
|
mbox_set_value(MCU_CP, mld->mbx_ap2cp_msg, mask);
|
|
mbox_set_interrupt(MCU_CP, mld->int_ap2cp_msg);
|
|
}
|
|
|
|
static inline u16 read_ap2cp_irq(struct mem_link_device *mld)
|
|
{
|
|
return mbox_get_value(MCU_CP, mld->mbx_ap2cp_msg);
|
|
}
|
|
|
|
struct shmem_srinfo {
|
|
unsigned int size;
|
|
char buf[0];
|
|
};
|
|
|
|
static char *shmem_get_srinfo_address(struct link_device *ld)
|
|
{
|
|
struct mem_link_device *mld = ld_to_mem_link_device(ld);
|
|
unsigned offs = (mld->link_dev.sbd_ipc) ?
|
|
SHMEM_SRINFO_SBD_OFFSET : SHMEM_SRINFO_OFFSET;
|
|
char *base = mld->base + offs;
|
|
|
|
return base;
|
|
}
|
|
|
|
static void shmem_clr_sbdcplog(unsigned long arg)
|
|
{
|
|
#ifdef CONFIG_SBD_BOOTLOG
|
|
struct mem_link_device *mld = ld_to_mem_link_device((struct link_device *)arg);
|
|
u8 __iomem *base = mld->base + mld->size - SHMEM_BOOTSBDLOG_SIZE;
|
|
|
|
memset(base, 0, SHMEM_BOOTSBDLOG_SIZE);
|
|
#endif
|
|
}
|
|
|
|
static void shmem_pr_sbdcplog(unsigned long arg)
|
|
{
|
|
#ifdef CONFIG_SBD_BOOTLOG
|
|
struct mem_link_device *mld = ld_to_mem_link_device((struct link_device *)arg);
|
|
u8 __iomem *base = mld->base + mld->size - SHMEM_BOOTSBDLOG_SIZE;
|
|
u8 __iomem *offset;
|
|
int i;
|
|
|
|
mif_info("dump cp logs: size: 0x%x, base: %p\n", (unsigned int)mld->size, base);
|
|
|
|
for (i = 0; i * 32 < SHMEM_BOOTSBDLOG_SIZE; i++) {
|
|
offset = base + i * 32;
|
|
mif_info("%6X: %*ph\n", (unsigned int)(offset - mld->base), 32, offset);
|
|
}
|
|
|
|
shmem_clr_sbdcplog(arg);
|
|
#endif
|
|
}
|
|
|
|
/* not in use */
|
|
static int shmem_ioctl(struct link_device *ld, struct io_device *iod,
|
|
unsigned int cmd, unsigned long arg)
|
|
{
|
|
mif_err("%s: cmd 0x%08X\n", ld->name, cmd);
|
|
|
|
switch (cmd) {
|
|
case IOCTL_MODEM_GET_SHMEM_SRINFO:
|
|
{
|
|
struct shmem_srinfo __user *sr_arg =
|
|
(struct shmem_srinfo __user *)arg;
|
|
unsigned count, size = SHMEM_SRINFO_SIZE;
|
|
|
|
if (copy_from_user(&count, &sr_arg->size, sizeof(unsigned)))
|
|
return -EFAULT;
|
|
|
|
mif_info("get srinfo:%s, size = %d\n", iod->name, count);
|
|
|
|
size = min(size, count);
|
|
if (copy_to_user(&sr_arg->size, &size, sizeof(unsigned)))
|
|
return -EFAULT;
|
|
|
|
if (copy_to_user(sr_arg->buf, shmem_get_srinfo_address(ld),
|
|
size))
|
|
return -EFAULT;
|
|
break;
|
|
}
|
|
|
|
case IOCTL_MODEM_SET_SHMEM_SRINFO:
|
|
{
|
|
struct shmem_srinfo __user *sr_arg =
|
|
(struct shmem_srinfo __user *)arg;
|
|
unsigned count, size = SHMEM_SRINFO_SIZE;
|
|
|
|
if (copy_from_user(&count, &sr_arg->size, sizeof(unsigned)))
|
|
return -EFAULT;
|
|
|
|
mif_info("set srinfo:%s, size = %d\n", iod->name, count);
|
|
|
|
if (copy_from_user(shmem_get_srinfo_address(ld), sr_arg->buf,
|
|
min(count, size)))
|
|
return -EFAULT;
|
|
break;
|
|
}
|
|
|
|
case IOCTL_MODEM_GET_CP_BOOTLOG:
|
|
{
|
|
#ifndef CONFIG_SBD_BOOTLOG
|
|
struct mem_link_device *mld = ld_to_mem_link_device(ld);
|
|
u8 __iomem *base = mld->base + SHMEM_BOOTLOG_BASE;
|
|
char str[SHMEM_BOOTLOG_BUFF];
|
|
unsigned int size = base[0] + (base[1] << 8)
|
|
+ (base[2] << 16) + (base[3] << 24);
|
|
|
|
if (size <= 0 || size > SHMEM_BOOTLOG_BUFF - SHMEM_BOOTLOG_OFFSET) {
|
|
mif_info("Invalid CP boot log[%d]\n", size);
|
|
return -EINVAL;
|
|
}
|
|
|
|
strncpy(str, base + SHMEM_BOOTLOG_OFFSET, size);
|
|
mif_info("CP boot log[%d] : %s\n", size, str);
|
|
#else
|
|
shmem_pr_sbdcplog((unsigned long)ld);
|
|
#endif
|
|
break;
|
|
}
|
|
|
|
case IOCTL_MODEM_CLR_CP_BOOTLOG:
|
|
{
|
|
#ifndef CONFIG_SBD_BOOTLOG
|
|
struct mem_link_device *mld = ld_to_mem_link_device(ld);
|
|
u8 __iomem *base = mld->base + SHMEM_BOOTLOG_BASE;
|
|
|
|
mif_info("Clear CP boot log\n");
|
|
memset(base, 0, SHMEM_BOOTLOG_BUFF);
|
|
#endif
|
|
break;
|
|
}
|
|
|
|
default:
|
|
mif_err("%s: ERR! invalid cmd 0x%08X\n", ld->name, cmd);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void shmem_tx_state_handler(void *data)
|
|
{
|
|
struct mem_link_device *mld = (struct mem_link_device *)data;
|
|
struct link_device *ld = &mld->link_dev;
|
|
struct modem_ctl *mc = ld->mc;
|
|
u16 int2ap_status;
|
|
|
|
int2ap_status = mld->recv_cp2ap_status(mld);
|
|
|
|
/* Change SHM_FLOWCTL to MASK_TX_FLOWCTRL */
|
|
int2ap_status = (int2ap_status & SHM_FLOWCTL_BIT) << 2;
|
|
|
|
switch (int2ap_status & (SHM_FLOWCTL_BIT << 2)) {
|
|
case MASK_TX_FLOWCTL_SUSPEND:
|
|
if (!chk_same_cmd(mld, int2ap_status))
|
|
tx_flowctrl_suspend(mld);
|
|
break;
|
|
|
|
case MASK_TX_FLOWCTL_RESUME:
|
|
if (!chk_same_cmd(mld, int2ap_status))
|
|
tx_flowctrl_resume(mld);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (unlikely(!rx_possible(mc))) {
|
|
mif_err("%s: ERR! %s.state == %s\n", ld->name, mc->name,
|
|
mc_state(mc));
|
|
return;
|
|
}
|
|
}
|
|
|
|
static int shmem_enqueue_snapshot(struct mem_link_device *mld)
|
|
{
|
|
struct mst_buff *msb;
|
|
struct link_device *ld = &mld->link_dev;
|
|
struct modem_ctl *mc = ld->mc;
|
|
|
|
msb = mem_take_snapshot(mld, RX);
|
|
if (!msb)
|
|
return -ENOMEM;
|
|
|
|
if (unlikely(!int_valid(msb->snapshot.int2ap))) {
|
|
mif_err("%s: ERR! invalid intr 0x%X\n",
|
|
ld->name, msb->snapshot.int2ap);
|
|
msb_free(msb);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (unlikely(!rx_possible(mc))) {
|
|
mif_err("%s: ERR! %s.state == %s\n", ld->name, mc->name,
|
|
mc_state(mc));
|
|
msb_free(msb);
|
|
return -EINVAL;
|
|
}
|
|
|
|
msb_queue_tail(&mld->msb_rxq, msb);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void shmem_irq_handler(void *data)
|
|
{
|
|
struct mem_link_device *mld = (struct mem_link_device *)data;
|
|
|
|
#ifdef CONFIG_LINK_DEVICE_NAPI
|
|
mld->rx_int_count++;
|
|
if (napi_schedule_prep(&mld->mld_napi)) {
|
|
struct link_device *ld = &mld->link_dev;
|
|
|
|
ld->disable_rx_int(ld);
|
|
__napi_schedule(&mld->mld_napi);
|
|
}
|
|
#else /* !CONFIG_LINK_DEVICE_NAPI */
|
|
if (shmem_enqueue_snapshot(mld))
|
|
return;
|
|
|
|
tasklet_schedule(&mld->rx_tsk);
|
|
#endif /* CONFIG_LINK_DEVICE_NAPI */
|
|
}
|
|
|
|
static struct pm_qos_request pm_qos_req_mif;
|
|
static struct pm_qos_request pm_qos_req_cl0;
|
|
static struct pm_qos_request pm_qos_req_cl1;
|
|
static struct pm_qos_request pm_qos_req_int;
|
|
|
|
static void shmem_qos_work_cpu(struct work_struct *work)
|
|
{
|
|
struct mem_link_device *mld =
|
|
container_of(work, struct mem_link_device, pm_qos_work_cpu);
|
|
int qos_val;
|
|
int cpu_val;
|
|
|
|
qos_val = mbox_get_value(MCU_CP, mld->mbx_perf_req_cpu);
|
|
mif_err("pm_qos:0x%x requested\n", qos_val);
|
|
|
|
cpu_val = (qos_val & 0xff);
|
|
if (qos_val & BIT(31)) {
|
|
/* CL0 - Big */
|
|
if (cpu_val > 0 && cpu_val <= mld->cl0_table.num_of_table) {
|
|
mif_err("Lock CL0[%d] : %u\n", cpu_val,
|
|
mld->cl0_table.freq[cpu_val - 1]);
|
|
pm_qos_update_request(&pm_qos_req_cl0,
|
|
mld->cl0_table.freq[cpu_val - 1]);
|
|
} else {
|
|
mif_err("Unlock CL0(req_val : %d)\n", qos_val);
|
|
pm_qos_update_request(&pm_qos_req_cl0, 0);
|
|
}
|
|
} else {
|
|
/* CL1 - Little */
|
|
if (cpu_val > 0 && cpu_val <= mld->cl1_table.num_of_table) {
|
|
mif_err("Lock CL1[%d] : %u\n", cpu_val,
|
|
mld->cl1_table.freq[cpu_val - 1]);
|
|
pm_qos_update_request(&pm_qos_req_cl1,
|
|
mld->cl1_table.freq[cpu_val - 1]);
|
|
} else {
|
|
mif_err("Unlock CL1(req_val : %d)\n", qos_val);
|
|
pm_qos_update_request(&pm_qos_req_cl1, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void shmem_qos_work_mif(struct work_struct *work)
|
|
{
|
|
struct mem_link_device *mld =
|
|
container_of(work, struct mem_link_device, pm_qos_work_mif);
|
|
int qos_val;
|
|
int mif_val;
|
|
|
|
qos_val = mbox_get_value(MCU_CP, mld->mbx_perf_req_mif);
|
|
mif_err("pm_qos:0x%x requested\n", qos_val);
|
|
|
|
mif_val = (qos_val & 0xff);
|
|
if (mif_val > 0 && mif_val <= mld->mif_table.num_of_table) {
|
|
mif_err("Lock MIF[%d] : %u\n", mif_val,
|
|
mld->mif_table.freq[mif_val - 1]);
|
|
pm_qos_update_request(&pm_qos_req_mif, mld->mif_table.freq[mif_val - 1]);
|
|
} else {
|
|
mif_err("Unlock MIF(req_val : %d)\n", qos_val);
|
|
pm_qos_update_request(&pm_qos_req_mif, 0);
|
|
}
|
|
|
|
/* Save current mif_val */
|
|
mld->current_mif_val = mif_val;
|
|
}
|
|
|
|
static void shmem_qos_work_int(struct work_struct *work)
|
|
{
|
|
struct mem_link_device *mld =
|
|
container_of(work, struct mem_link_device, pm_qos_work_int);
|
|
int qos_val;
|
|
int int_val;
|
|
|
|
qos_val = mbox_get_value(MCU_CP, mld->mbx_perf_req_int);
|
|
mif_err("pm_qos:0x%x requested\n", qos_val);
|
|
|
|
int_val = (qos_val & 0xff);
|
|
if (int_val > 0 && int_val <= mld->int_table.num_of_table) {
|
|
mif_err("Lock INT[%d] : %u\n", int_val,
|
|
mld->int_table.freq[int_val - 1]);
|
|
pm_qos_update_request(&pm_qos_req_int, mld->int_table.freq[int_val - 1]);
|
|
} else {
|
|
mif_err("Unlock INT(req_val : %d)\n", qos_val);
|
|
pm_qos_update_request(&pm_qos_req_int, 0);
|
|
}
|
|
}
|
|
|
|
static void shmem_qos_cpu_req_handler(void *data)
|
|
{
|
|
struct mem_link_device *mld = (struct mem_link_device *)data;
|
|
mif_err("%s\n", __func__);
|
|
|
|
schedule_work(&mld->pm_qos_work_cpu);
|
|
}
|
|
|
|
static void shmem_qos_mif_req_handler(void *data)
|
|
{
|
|
struct mem_link_device *mld = (struct mem_link_device *)data;
|
|
mif_err("%s\n", __func__);
|
|
|
|
schedule_work(&mld->pm_qos_work_mif);
|
|
}
|
|
|
|
static void shmem_qos_int_req_handler(void *data)
|
|
{
|
|
struct mem_link_device *mld = (struct mem_link_device *)data;
|
|
mif_err("%s\n", __func__);
|
|
|
|
schedule_work(&mld->pm_qos_work_int);
|
|
}
|
|
|
|
static void shmem_cp2ap_wakelock_handler(void *data)
|
|
{
|
|
struct mem_link_device *mld = (struct mem_link_device *)data;
|
|
unsigned int req;
|
|
mif_err("%s\n", __func__);
|
|
|
|
req = mbox_extract_value(MCU_CP, mld->mbx_cp2ap_status, \
|
|
mld->sbi_cp2ap_wakelock_mask, mld->sbi_cp2ap_wakelock_pos);
|
|
|
|
if (req == 0) {
|
|
if (wake_lock_active(&mld->cp_wakelock)) {
|
|
wake_unlock(&mld->cp_wakelock);
|
|
mif_err("cp_wakelock unlocked\n");
|
|
} else {
|
|
mif_err("cp_wakelock already unlocked\n");
|
|
}
|
|
} else if (req == 1) {
|
|
if (wake_lock_active(&mld->cp_wakelock)) {
|
|
mif_err("cp_wakelock already unlocked\n");
|
|
} else {
|
|
wake_lock(&mld->cp_wakelock);
|
|
mif_err("cp_wakelock locked\n");
|
|
}
|
|
} else {
|
|
mif_err("unsupported request: cp_wakelock\n");
|
|
}
|
|
}
|
|
|
|
#if defined(CONFIG_PCI_EXYNOS)
|
|
static void shmem_cp2ap_rat_mode_handler(void *data)
|
|
{
|
|
struct mem_link_device *mld = (struct mem_link_device *)data;
|
|
unsigned int req;
|
|
|
|
req = mbox_extract_value(MCU_CP, mld->mbx_cp2ap_status, \
|
|
mld->sbi_cp_rat_mode_mask, mld->sbi_cp_rat_mode_pos);
|
|
|
|
mif_err("value: %u\n", req);
|
|
|
|
if (req) {
|
|
exynos_pcie_l1ss_ctrl(0, PCIE_L1SS_CTRL_MODEM_IF);
|
|
mif_err("cp requests pcie l1ss disable\n");
|
|
} else {
|
|
exynos_pcie_l1ss_ctrl(1, PCIE_L1SS_CTRL_MODEM_IF);
|
|
mif_err("cp requests pcie l1ss enable\n");
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void shmem_unlock_mif_freq(struct mem_link_device *mld)
|
|
{
|
|
if (mld) {
|
|
mif_err("Currnet MIF value: %d)\n", mld->current_mif_val);
|
|
mif_err("Unlock MIF\n");
|
|
pm_qos_update_request(&pm_qos_req_mif, 0);
|
|
}
|
|
}
|
|
|
|
void shmem_restore_mif_freq(struct mem_link_device *mld)
|
|
{
|
|
int mif_val;
|
|
|
|
if (mld) {
|
|
mif_val = mld->current_mif_val;
|
|
mif_err("Currnet MIF value: %d)\n", mif_val);
|
|
|
|
if (mif_val > 0 && mif_val <= mld->mif_table.num_of_table) {
|
|
mif_err("Lock MIF[%d] : %u\n", mif_val,
|
|
mld->mif_table.freq[mif_val - 1]);
|
|
pm_qos_update_request(&pm_qos_req_mif, mld->mif_table.freq[mif_val - 1]);
|
|
}
|
|
}
|
|
}
|
|
|
|
#if defined(CONFIG_ECT)
|
|
static int exynos_devfreq_parse_ect(struct mem_link_device *mld, char *dvfs_domain_name)
|
|
{
|
|
int i, counter = 0;
|
|
void *dvfs_block;
|
|
struct ect_dvfs_domain *dvfs_domain;
|
|
|
|
dvfs_block = ect_get_block(BLOCK_DVFS);
|
|
if (dvfs_block == NULL)
|
|
return -ENODEV;
|
|
|
|
dvfs_domain = ect_dvfs_get_domain(dvfs_block, (char *)dvfs_domain_name);
|
|
if (dvfs_domain == NULL)
|
|
return -ENODEV;
|
|
|
|
if (!strcmp(dvfs_domain_name, "dvfs_mif")) {
|
|
mld->mif_table.num_of_table = dvfs_domain->num_of_level;
|
|
for (i = dvfs_domain->num_of_level - 1; i >= 0; i--) {
|
|
mld->mif_table.freq[i] = dvfs_domain->list_level[counter++].level;
|
|
mif_err("MIF_LEV[%d] : %u\n", i + 1, mld->mif_table.freq[i]);
|
|
}
|
|
} else if (!strcmp(dvfs_domain_name, "dvfs_cpucl0")) {
|
|
mld->cl0_table.num_of_table = dvfs_domain->num_of_level;
|
|
for (i = dvfs_domain->num_of_level - 1; i >= 0; i--) {
|
|
mld->cl0_table.freq[i] = dvfs_domain->list_level[counter++].level;
|
|
mif_err("CL0_LEV[%d] : %u\n", i + 1, mld->cl0_table.freq[i]);
|
|
}
|
|
} else if (!strcmp(dvfs_domain_name, "dvfs_cpucl1")) {
|
|
mld->cl1_table.num_of_table = dvfs_domain->num_of_level;
|
|
for (i = dvfs_domain->num_of_level - 1; i >= 0; i--) {
|
|
mld->cl1_table.freq[i] = dvfs_domain->list_level[counter++].level;
|
|
mif_err("CL1_LEV[%d] : %u\n", i + 1, mld->cl1_table.freq[i]);
|
|
}
|
|
} else if (!strcmp(dvfs_domain_name, "dvfs_int")) {
|
|
mld->int_table.num_of_table = dvfs_domain->num_of_level;
|
|
for (i = dvfs_domain->num_of_level - 1; i >= 0; i--) {
|
|
mld->int_table.freq[i] = dvfs_domain->list_level[counter++].level;
|
|
mif_err("INT_LEV[%d] : %u\n", i + 1, mld->int_table.freq[i]);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#else
|
|
static int exynos_devfreq_parse_ect(struct mem_link_device *mld, char *dvfs_domain_name)
|
|
{
|
|
mif_err("ECT is not defined(%s)\n", __func__);
|
|
|
|
mld->cl0_table.num_of_table = 0;
|
|
mld->cl1_table.num_of_table = 0;
|
|
mld->mif_table.num_of_table = 0;
|
|
mld->int_table.num_of_table = 0;
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static void remap_4mb_map_to_ipc_dev(struct mem_link_device *mld)
|
|
{
|
|
struct link_device *ld = &mld->link_dev;
|
|
struct shmem_4mb_phys_map *map;
|
|
struct mem_ipc_device *dev;
|
|
|
|
map = (struct shmem_4mb_phys_map *)mld->base;
|
|
|
|
/* magic code and access enable fields */
|
|
mld->magic = (u32 __iomem *)&map->magic;
|
|
mld->access = (u32 __iomem *)&map->access;
|
|
|
|
/* To share ECT clock table with CP */
|
|
mld->clk_table = (u32 __iomem *)map->reserved;
|
|
|
|
/* IPC_MAP_FMT */
|
|
dev = &mld->ipc_dev[IPC_MAP_FMT];
|
|
|
|
dev->id = IPC_MAP_FMT;
|
|
strcpy(dev->name, "FMT");
|
|
|
|
spin_lock_init(&dev->txq.lock);
|
|
atomic_set(&dev->txq.busy, 0);
|
|
dev->txq.head = &map->fmt_tx_head;
|
|
dev->txq.tail = &map->fmt_tx_tail;
|
|
dev->txq.buff = &map->fmt_tx_buff[0];
|
|
dev->txq.size = SHM_4M_FMT_TX_BUFF_SZ;
|
|
|
|
spin_lock_init(&dev->rxq.lock);
|
|
atomic_set(&dev->rxq.busy, 0);
|
|
dev->rxq.head = &map->fmt_rx_head;
|
|
dev->rxq.tail = &map->fmt_rx_tail;
|
|
dev->rxq.buff = &map->fmt_rx_buff[0];
|
|
dev->rxq.size = SHM_4M_FMT_RX_BUFF_SZ;
|
|
|
|
dev->msg_mask = MASK_SEND_FMT;
|
|
dev->req_ack_mask = MASK_REQ_ACK_FMT;
|
|
dev->res_ack_mask = MASK_RES_ACK_FMT;
|
|
|
|
dev->skb_txq = &ld->skb_txq[IPC_MAP_FMT];
|
|
dev->skb_rxq = &ld->skb_rxq[IPC_MAP_FMT];
|
|
skb_queue_head_init(dev->skb_txq);
|
|
skb_queue_head_init(dev->skb_rxq);
|
|
|
|
dev->req_ack_cnt[TX] = 0;
|
|
dev->req_ack_cnt[RX] = 0;
|
|
|
|
mld->dev[IPC_MAP_FMT] = dev;
|
|
|
|
#ifdef CONFIG_MODEM_IF_LEGACY_QOS
|
|
/* IPC_MAP_HPRIO_RAW */
|
|
dev = &mld->ipc_dev[IPC_MAP_HPRIO_RAW];
|
|
|
|
dev->id = IPC_MAP_HPRIO_RAW;
|
|
strcpy(dev->name, "HPRIO_RAW");
|
|
|
|
spin_lock_init(&dev->txq.lock);
|
|
atomic_set(&dev->txq.busy, 0);
|
|
dev->txq.head = &map->raw_hprio_tx_head;
|
|
dev->txq.tail = &map->raw_hprio_tx_tail;
|
|
dev->txq.buff = &map->raw_hprio_tx_buff[0];
|
|
dev->txq.size = SHM_4M_RAW_HPRIO_TX_BUFF_SZ;
|
|
|
|
spin_lock_init(&dev->rxq.lock);
|
|
atomic_set(&dev->rxq.busy, 0);
|
|
dev->rxq.head = &map->raw_hprio_rx_head;
|
|
dev->rxq.tail = &map->raw_hprio_rx_tail;
|
|
dev->rxq.buff = &map->raw_hprio_rx_buff[0];
|
|
dev->rxq.size = SHM_4M_RAW_HPRIO_RX_BUFF_SZ;
|
|
|
|
dev->msg_mask = MASK_SEND_RAW;
|
|
dev->req_ack_mask = MASK_REQ_ACK_RAW;
|
|
dev->res_ack_mask = MASK_RES_ACK_RAW;
|
|
|
|
dev->skb_txq = &ld->skb_txq[IPC_MAP_HPRIO_RAW];
|
|
dev->skb_rxq = &ld->skb_rxq[IPC_MAP_HPRIO_RAW];
|
|
skb_queue_head_init(dev->skb_txq);
|
|
skb_queue_head_init(dev->skb_rxq);
|
|
|
|
dev->req_ack_cnt[TX] = 0;
|
|
dev->req_ack_cnt[RX] = 0;
|
|
|
|
mld->dev[IPC_MAP_HPRIO_RAW] = dev;
|
|
#endif
|
|
|
|
/* IPC_MAP_NORM_RAW */
|
|
dev = &mld->ipc_dev[IPC_MAP_NORM_RAW];
|
|
|
|
dev->id = IPC_MAP_NORM_RAW;
|
|
strcpy(dev->name, "NORM_RAW");
|
|
|
|
spin_lock_init(&dev->txq.lock);
|
|
atomic_set(&dev->txq.busy, 0);
|
|
dev->txq.head = &map->raw_tx_head;
|
|
dev->txq.tail = &map->raw_tx_tail;
|
|
dev->txq.buff = &map->raw_tx_buff[0];
|
|
dev->txq.size = SHM_4M_RAW_TX_BUFF_SZ;
|
|
|
|
spin_lock_init(&dev->rxq.lock);
|
|
atomic_set(&dev->rxq.busy, 0);
|
|
dev->rxq.head = &map->raw_rx_head;
|
|
dev->rxq.tail = &map->raw_rx_tail;
|
|
dev->rxq.buff = &map->raw_rx_buff[0];
|
|
dev->rxq.size = SHM_4M_RAW_RX_BUFF_SZ;
|
|
|
|
dev->msg_mask = MASK_SEND_RAW;
|
|
dev->req_ack_mask = MASK_REQ_ACK_RAW;
|
|
dev->res_ack_mask = MASK_RES_ACK_RAW;
|
|
|
|
dev->skb_txq = &ld->skb_txq[IPC_MAP_NORM_RAW];
|
|
dev->skb_rxq = &ld->skb_rxq[IPC_MAP_NORM_RAW];
|
|
skb_queue_head_init(dev->skb_txq);
|
|
skb_queue_head_init(dev->skb_rxq);
|
|
|
|
dev->req_ack_cnt[TX] = 0;
|
|
dev->req_ack_cnt[RX] = 0;
|
|
|
|
mld->dev[IPC_MAP_NORM_RAW] = dev;
|
|
}
|
|
|
|
static int shmem_rx_setup(struct link_device *ld)
|
|
{
|
|
ld->rx_wq = alloc_workqueue(
|
|
"mem_rx_work", WQ_HIGHPRI | WQ_CPU_INTENSIVE, 1);
|
|
if (!ld->rx_wq) {
|
|
mif_err("%s: ERR! fail to create rx_wq\n", ld->name);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
INIT_DELAYED_WORK(&ld->rx_delayed_work, link_to_demux_work);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int shm_reboot_notifier(struct notifier_block *nb,
|
|
unsigned long action, void *nb_data)
|
|
{
|
|
mif_err("Set magic code as 0 because of kernel reboot!\n");
|
|
clean_vss_magic_code();
|
|
|
|
return NOTIFY_OK;
|
|
}
|
|
|
|
static ssize_t tx_period_ms_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct modem_data *modem;
|
|
modem = (struct modem_data *)dev->platform_data;
|
|
return sprintf(buf, "%d\n", modem->mld->tx_period_ms);
|
|
}
|
|
|
|
static ssize_t tx_period_ms_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
int ret;
|
|
struct modem_data *modem;
|
|
modem = (struct modem_data *)dev->platform_data;
|
|
|
|
ret = sscanf(buf, "%u", &modem->mld->tx_period_ms);
|
|
if (ret != 1)
|
|
return -EINVAL;
|
|
|
|
ret = count;
|
|
return ret;
|
|
}
|
|
|
|
static int rb_ch_id = 8;
|
|
static ssize_t rb_info_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct modem_data *modem;
|
|
struct sbd_link_device *sl;
|
|
struct sbd_ring_buffer *rb_tx, *rb_rx;
|
|
modem = (struct modem_data *)dev->platform_data;
|
|
sl = &modem->mld->sbd_link_dev;
|
|
rb_tx = sbd_id2rb(sl, rb_ch_id, TX);
|
|
rb_rx = sbd_id2rb(sl, rb_ch_id, RX);
|
|
|
|
if (rb_tx->len && rb_rx->len)
|
|
return sprintf(buf, "rb_ch_id = %d (total: %d)\n"
|
|
"TX(len: %d, rp: %d, wp: %d, space: %d, usage: %d)\n"
|
|
"RX(len: %d, rp: %d, pre_rp: %d,wp: %d, space: %d, usage: %d)\n",
|
|
rb_ch_id, sl->num_channels,
|
|
rb_tx->len, *rb_tx->rp, *rb_tx->wp, rb_space(rb_tx) + 1, rb_usage(rb_tx),
|
|
rb_rx->len, *rb_rx->rp, rb_rx->zerocopy ? rb_rx->zdptr->pre_rp : -1,
|
|
*rb_rx->wp, rb_space(rb_rx) + 1, rb_usage(rb_rx));
|
|
else
|
|
return sprintf(buf, "rb_ch_id = %d(of %d), TX(empty), RX(empty)\n",
|
|
rb_ch_id, sl->num_channels);
|
|
|
|
}
|
|
|
|
static ssize_t rb_info_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
int ret;
|
|
struct modem_data *modem;
|
|
struct sbd_link_device *sl;
|
|
int id = 0;
|
|
|
|
modem = (struct modem_data *)dev->platform_data;
|
|
sl = &modem->mld->sbd_link_dev;
|
|
|
|
ret = sscanf(buf, "%u", &id);
|
|
if ((ret != 1) || (id < 0) || (id >= sl->num_channels))
|
|
return -EINVAL;
|
|
|
|
rb_ch_id = id;
|
|
ret = count;
|
|
return ret;
|
|
}
|
|
|
|
#if defined(CONFIG_CP_ZEROCOPY)
|
|
static ssize_t zmc_count_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct modem_data *modem;
|
|
modem = (struct modem_data *)dev->platform_data;
|
|
|
|
return sprintf(buf, "memcpy_packet(%d)/zeromemcpy_packet(%d)\n",
|
|
modem->mld->memcpy_packet_count, modem->mld->zeromemcpy_packet_count);
|
|
}
|
|
|
|
static ssize_t zmc_count_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct modem_data *modem;
|
|
int val = 0;
|
|
int ret;
|
|
modem = (struct modem_data *)dev->platform_data;
|
|
ret = sscanf(buf, "%u", &val);
|
|
|
|
if (val == 0) {
|
|
modem->mld->memcpy_packet_count = 0;
|
|
modem->mld->zeromemcpy_packet_count = 0;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t mif_buff_mng_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
if (!g_mif_buff_mng)
|
|
return sprintf(buf, "g_mif_buff_mng is NULL\n");
|
|
|
|
return sprintf(buf, "used(%d)/free(%d)/total(%d)\n",
|
|
g_mif_buff_mng->used_cell_count, g_mif_buff_mng->free_cell_count,
|
|
g_mif_buff_mng->cell_count);
|
|
}
|
|
|
|
static ssize_t force_use_memcpy_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct modem_data *modem;
|
|
modem = (struct modem_data *)dev->platform_data;
|
|
return sprintf(buf, "%d\n", modem->mld->force_use_memcpy);
|
|
}
|
|
|
|
static ssize_t force_use_memcpy_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
|
|
struct modem_data *modem;
|
|
int val = 0;
|
|
int ret;
|
|
modem = (struct modem_data *)dev->platform_data;
|
|
ret = sscanf(buf, "%u", &val);
|
|
|
|
if (val == 0)
|
|
modem->mld->force_use_memcpy = 0;
|
|
else if (val == 1)
|
|
modem->mld->force_use_memcpy = 1;
|
|
return count;
|
|
}
|
|
#endif
|
|
|
|
static DEVICE_ATTR_RW(tx_period_ms);
|
|
static DEVICE_ATTR_RW(rb_info);
|
|
#if defined(CONFIG_CP_ZEROCOPY)
|
|
static DEVICE_ATTR_RO(mif_buff_mng);
|
|
static DEVICE_ATTR_RW(zmc_count);
|
|
static DEVICE_ATTR_RW(force_use_memcpy);
|
|
#endif
|
|
|
|
static struct attribute *shmem_attrs[] = {
|
|
&dev_attr_tx_period_ms.attr,
|
|
&dev_attr_rb_info.attr,
|
|
#if defined(CONFIG_CP_ZEROCOPY)
|
|
&dev_attr_mif_buff_mng.attr,
|
|
&dev_attr_zmc_count.attr,
|
|
&dev_attr_force_use_memcpy.attr,
|
|
#endif
|
|
NULL,
|
|
};
|
|
|
|
static const struct attribute_group shmem_group = { \
|
|
.attrs = shmem_attrs, \
|
|
.name = "shmem",
|
|
};
|
|
|
|
#ifdef CONFIG_LINK_DEVICE_NAPI
|
|
static ssize_t rx_napi_list_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct modem_data *modem;
|
|
struct napi_struct *n;
|
|
struct net_device *netdev;
|
|
ssize_t count = 0;
|
|
|
|
modem = (struct modem_data *)dev->platform_data;
|
|
netdev = &modem->mld->dummy_net;
|
|
|
|
count += sprintf(&buf[count], "[%s`s napi_list]\n", netdev_name(netdev));
|
|
list_for_each_entry(n, &netdev->napi_list, dev_list)
|
|
count += sprintf(&buf[count], "state: %s(%ld), weight: %d, poll: 0x%pK\n",
|
|
test_bit(NAPI_STATE_SCHED, &n->state) ? "NAPI_STATE_SCHED" : "NAPI_STATE_COMPLETE",
|
|
n->state, n->weight, (void *)n->poll);
|
|
return count;
|
|
}
|
|
|
|
static ssize_t rx_int_enable_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct modem_data *modem;
|
|
modem = (struct modem_data *)dev->platform_data;
|
|
return sprintf(buf, "%d\n", modem->mld->rx_int_enable);
|
|
}
|
|
|
|
static ssize_t rx_int_count_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct modem_data *modem;
|
|
modem = (struct modem_data *)dev->platform_data;
|
|
return sprintf(buf, "%d\n", modem->mld->rx_int_count);
|
|
}
|
|
|
|
static ssize_t rx_int_count_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct modem_data *modem;
|
|
int val = 0;
|
|
int ret;
|
|
modem = (struct modem_data *)dev->platform_data;
|
|
ret = sscanf(buf, "%u", &val);
|
|
|
|
if (val == 0)
|
|
modem->mld->rx_int_count = 0;
|
|
return count;
|
|
}
|
|
|
|
static ssize_t rx_poll_count_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct modem_data *modem;
|
|
struct mem_link_device *mld;
|
|
ssize_t count = 0;
|
|
|
|
modem = (struct modem_data *)dev->platform_data;
|
|
mld = modem->mld;
|
|
|
|
count += sprintf(&buf[count], "%s: %d\n", netdev_name(&mld->dummy_net), mld->rx_poll_count);
|
|
return count;
|
|
}
|
|
|
|
static ssize_t rx_poll_count_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct modem_data *modem;
|
|
struct mem_link_device *mld;
|
|
|
|
modem = (struct modem_data *)dev->platform_data;
|
|
mld = modem->mld;
|
|
|
|
mld->rx_poll_count = 0;
|
|
return count;
|
|
}
|
|
|
|
static ssize_t rx_int_disabled_time_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct modem_data *modem;
|
|
modem = (struct modem_data *)dev->platform_data;
|
|
return sprintf(buf, "%lld\n", modem->mld->rx_int_disabled_time);
|
|
}
|
|
|
|
static ssize_t rx_int_disabled_time_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct modem_data *modem;
|
|
int val = 0;
|
|
int ret;
|
|
modem = (struct modem_data *)dev->platform_data;
|
|
ret = sscanf(buf, "%u", &val);
|
|
|
|
if (val == 0)
|
|
modem->mld->rx_int_disabled_time = 0;
|
|
return count;
|
|
}
|
|
|
|
static DEVICE_ATTR_RO(rx_napi_list);
|
|
static DEVICE_ATTR_RO(rx_int_enable);
|
|
static DEVICE_ATTR_RW(rx_int_count);
|
|
static DEVICE_ATTR_RW(rx_poll_count);
|
|
static DEVICE_ATTR_RW(rx_int_disabled_time);
|
|
|
|
static struct attribute *napi_attrs[] = {
|
|
&dev_attr_rx_napi_list.attr,
|
|
&dev_attr_rx_int_enable.attr,
|
|
&dev_attr_rx_int_count.attr,
|
|
&dev_attr_rx_poll_count.attr,
|
|
&dev_attr_rx_int_disabled_time.attr,
|
|
NULL,
|
|
};
|
|
|
|
static const struct attribute_group napi_group = { \
|
|
.attrs = napi_attrs, \
|
|
.name = "napi",
|
|
};
|
|
#endif
|
|
|
|
struct link_device *shmem_create_link_device(struct platform_device *pdev)
|
|
{
|
|
struct modem_data *modem;
|
|
struct mem_link_device *mld;
|
|
struct link_device *ld;
|
|
int err;
|
|
mif_err("+++\n");
|
|
|
|
/**
|
|
* Get the modem (platform) data
|
|
*/
|
|
modem = (struct modem_data *)pdev->dev.platform_data;
|
|
if (!modem) {
|
|
mif_err("ERR! modem == NULL\n");
|
|
return NULL;
|
|
}
|
|
|
|
if (!modem->mbx) {
|
|
mif_err("%s: ERR! mbx == NULL\n", modem->link_name);
|
|
return NULL;
|
|
}
|
|
|
|
if (modem->ipc_version < SIPC_VER_50) {
|
|
mif_err("%s<->%s: ERR! IPC version %d < SIPC_VER_50\n",
|
|
modem->link_name, modem->name, modem->ipc_version);
|
|
return NULL;
|
|
}
|
|
|
|
mif_err("MODEM:%s LINK:%s\n", modem->name, modem->link_name);
|
|
|
|
/*
|
|
** Alloc an instance of mem_link_device structure
|
|
*/
|
|
mld = kzalloc(sizeof(struct mem_link_device), GFP_KERNEL);
|
|
if (!mld) {
|
|
mif_err("%s<->%s: ERR! mld kzalloc fail\n",
|
|
modem->link_name, modem->name);
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
** Retrieve modem-specific attributes value
|
|
*/
|
|
mld->attrs = modem->link_attrs;
|
|
|
|
/*====================================================================*\
|
|
Initialize "memory snapshot buffer (MSB)" framework
|
|
\*====================================================================*/
|
|
if (msb_init() < 0) {
|
|
mif_err("%s<->%s: ERR! msb_init() fail\n",
|
|
modem->link_name, modem->name);
|
|
goto error;
|
|
}
|
|
|
|
/*====================================================================*\
|
|
Set attributes as a "link_device"
|
|
\*====================================================================*/
|
|
ld = &mld->link_dev;
|
|
|
|
ld->name = modem->link_name;
|
|
|
|
if (mld->attrs & LINK_ATTR(LINK_ATTR_SBD_IPC)) {
|
|
mif_err("%s<->%s: LINK_ATTR_SBD_IPC\n", ld->name, modem->name);
|
|
ld->sbd_ipc = true;
|
|
}
|
|
|
|
if (mld->attrs & LINK_ATTR(LINK_ATTR_IPC_ALIGNED)) {
|
|
mif_err("%s<->%s: LINK_ATTR_IPC_ALIGNED\n",
|
|
ld->name, modem->name);
|
|
ld->aligned = true;
|
|
}
|
|
|
|
ld->ipc_version = modem->ipc_version;
|
|
|
|
ld->mdm_data = modem;
|
|
|
|
ld->dev = &pdev->dev;
|
|
|
|
/*
|
|
Set up link device methods
|
|
*/
|
|
ld->ioctl = shmem_ioctl;
|
|
|
|
ld->init_comm = shmem_init_comm;
|
|
ld->terminate_comm = shmem_terminate_comm;
|
|
ld->send = shmem_send;
|
|
ld->reset_zerocopy = NULL;
|
|
|
|
ld->boot_on = shmem_boot_on;
|
|
if (mld->attrs & LINK_ATTR(LINK_ATTR_MEM_BOOT)) {
|
|
if (mld->attrs & LINK_ATTR(LINK_ATTR_XMIT_BTDLR))
|
|
ld->xmit_boot = shmem_xmit_boot;
|
|
ld->dload_start = shmem_start_download;
|
|
ld->firm_update = shmem_update_firm_info;
|
|
ld->security_req = shmem_security_request;
|
|
}
|
|
|
|
ld->shmem_dump = save_shmem_dump;
|
|
ld->force_dump = shmem_force_dump;
|
|
ld->vss_dump = save_vss_dump;
|
|
ld->acpm_dump = save_acpm_dump;
|
|
ld->cplog_dump = save_cplog_dump;
|
|
|
|
if (mld->attrs & LINK_ATTR(LINK_ATTR_MEM_DUMP))
|
|
ld->dump_start = shmem_start_upload;
|
|
|
|
ld->close_tx = shmem_close_tx;
|
|
ld->crash_reason = shmem_crash_reason;
|
|
ld->airplane_mode = shmem_airplane_mode;
|
|
ld->pr_cplog = shmem_pr_sbdcplog;
|
|
|
|
#ifdef CONFIG_MODEM_IF_NET_GRO
|
|
ld->gro_flush = gro_flush_timer;
|
|
#endif
|
|
|
|
#ifdef CONFIG_LINK_DEVICE_NAPI
|
|
ld->poll_recv_on_iod = shmem_poll_recv_on_iod;
|
|
|
|
ld->enable_rx_int = shmem_enable_rx_int;
|
|
ld->disable_rx_int = shmem_disable_rx_int;
|
|
|
|
init_dummy_netdev(&mld->dummy_net);
|
|
netif_napi_add(&mld->dummy_net, &mld->mld_napi, mld_rx_int_poll, 64);
|
|
napi_enable(&mld->mld_napi);
|
|
#endif /* CONFIG_LINK_DEVICE_NAPI */
|
|
|
|
INIT_LIST_HEAD(&ld->list);
|
|
|
|
spin_lock_init(&ld->netif_lock);
|
|
atomic_set(&ld->netif_stopped, 0);
|
|
ld->tx_flowctrl_mask = 0;
|
|
init_completion(&ld->raw_tx_resumed);
|
|
|
|
if (shmem_rx_setup(ld) < 0)
|
|
goto error;
|
|
|
|
if (mld->attrs & LINK_ATTR(LINK_ATTR_DPRAM_MAGIC)) {
|
|
mif_err("%s<->%s: LINK_ATTR_DPRAM_MAGIC\n",
|
|
ld->name, modem->name);
|
|
mld->dpram_magic = true;
|
|
}
|
|
#ifdef CONFIG_LINK_CONTROL_MSG_IOSM
|
|
mld->iosm = true;
|
|
mld->cmd_handler = iosm_event_bh;
|
|
INIT_WORK(&mld->iosm_w, iosm_event_work);
|
|
#else
|
|
mld->cmd_handler = shmem_cmd_handler;
|
|
#endif
|
|
|
|
spin_lock_init(&mld->state_lock);
|
|
mld->state = LINK_STATE_OFFLINE;
|
|
|
|
/*
|
|
** Initialize variables for TX & RX
|
|
*/
|
|
msb_queue_head_init(&mld->msb_rxq);
|
|
msb_queue_head_init(&mld->msb_log);
|
|
|
|
tasklet_init(&mld->rx_tsk, shmem_rx_task, (unsigned long)mld);
|
|
|
|
hrtimer_init(&mld->tx_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
|
|
mld->tx_timer.function = tx_timer_func;
|
|
|
|
INIT_WORK(&mld->page_reclaim_work, shmem_oom_handler_work);
|
|
|
|
/*
|
|
** Initialize variables for CP booting and crash dump
|
|
*/
|
|
INIT_DELAYED_WORK(&mld->udl_rx_dwork, udl_rx_work);
|
|
|
|
/**
|
|
* Link local functions to the corresponding function pointers that are
|
|
* mandatory for all memory-type link devices
|
|
*/
|
|
mld->recv_cp2ap_irq = recv_cp2ap_irq;
|
|
mld->send_ap2cp_irq = send_ap2cp_irq;
|
|
mld->recv_cp2ap_status = recv_cp2ap_status;
|
|
|
|
/**
|
|
* Link local functions to the corresponding function pointers that are
|
|
* optional for some memory-type link devices
|
|
*/
|
|
mld->read_ap2cp_irq = read_ap2cp_irq;
|
|
|
|
/**
|
|
* Initialize SHMEM maps for BOOT (physical map -> logical map)
|
|
*/
|
|
mld->shm_size = shm_get_phys_size();
|
|
mld->boot_size = shm_get_boot_size();
|
|
mld->boot_base = shm_get_boot_region();
|
|
if (!mld->boot_base) {
|
|
mif_err("Failed to vmap boot_region\n");
|
|
goto error;
|
|
}
|
|
mif_err("boot_base=%pK, boot_size=%lu\n",
|
|
mld->boot_base, (unsigned long)mld->boot_size);
|
|
|
|
/**
|
|
* Initialize SHMEM maps for IPC (physical map -> logical map)
|
|
*/
|
|
mld->size = shm_get_ipc_rgn_size();
|
|
mld->base = shm_get_ipc_region();
|
|
if (!mld->base) {
|
|
mif_err("Failed to vmap ipc_region\n");
|
|
goto error;
|
|
}
|
|
mif_err("ipc_base=%pK, ipc_size=%lu\n",
|
|
mld->base, (unsigned long)mld->size);
|
|
|
|
/**
|
|
* Initialize SHMEM maps for VSS (physical map -> logical map)
|
|
*/
|
|
mld->vss_base = shm_get_vss_region();
|
|
if (!mld->vss_base) {
|
|
mif_err("Failed to vmap vss_region\n");
|
|
goto error;
|
|
}
|
|
mif_err("vss_base=%pK\n", mld->vss_base);
|
|
|
|
/**
|
|
* Initialize memory maps for ACPM (physical map -> logical map)
|
|
*/
|
|
mld->acpm_base = shm_get_acpm_region();
|
|
mld->acpm_size = shm_get_acpm_size();
|
|
if (!mld->acpm_base) {
|
|
mif_err("Failed to vmap acpm_region\n");
|
|
goto error;
|
|
}
|
|
mif_err("acpm_base=%pK acpm_size:0x%X\n", mld->acpm_base,
|
|
mld->acpm_size);
|
|
|
|
remap_4mb_map_to_ipc_dev(mld);
|
|
|
|
if (ld->sbd_ipc) {
|
|
hrtimer_init(&mld->sbd_tx_timer,
|
|
CLOCK_MONOTONIC, HRTIMER_MODE_REL);
|
|
mld->sbd_tx_timer.function = sbd_tx_timer_func;
|
|
|
|
err = create_sbd_link_device(ld,
|
|
&mld->sbd_link_dev, mld->base, mld->size);
|
|
if (err < 0)
|
|
goto error;
|
|
}
|
|
|
|
/**
|
|
* Retrieve SHMEM MBOX#, IRQ#, etc.
|
|
*/
|
|
mld->mbx_cp2ap_msg = modem->mbx->mbx_cp2ap_msg;
|
|
mld->irq_cp2ap_msg = modem->mbx->irq_cp2ap_msg;
|
|
|
|
mld->mbx_ap2cp_msg = modem->mbx->mbx_ap2cp_msg;
|
|
mld->int_ap2cp_msg = modem->mbx->int_ap2cp_msg;
|
|
|
|
mld->int_ap2cp_msg = modem->mbx->int_ap2cp_msg;
|
|
|
|
mld->sbi_cp_status_mask = modem->mbx->sbi_cp_status_mask;
|
|
mld->sbi_cp_status_pos = modem->mbx->sbi_cp_status_pos;
|
|
mld->sbi_cp2ap_wakelock_mask = modem->mbx->sbi_cp2ap_wakelock_mask;
|
|
mld->sbi_cp2ap_wakelock_pos = modem->mbx->sbi_cp2ap_wakelock_pos;
|
|
mld->sbi_cp_rat_mode_mask = modem->mbx->sbi_cp2ap_rat_mode_mask;
|
|
mld->sbi_cp_rat_mode_pos = modem->mbx->sbi_cp2ap_rat_mode_pos;
|
|
|
|
/**
|
|
* Register interrupt handlers
|
|
*/
|
|
err = mbox_request_irq(MCU_CP, mld->irq_cp2ap_msg, shmem_irq_handler, mld);
|
|
if (err) {
|
|
mif_err("%s: ERR! mbox_request_irq(MCU_CP, %u) fail (%d)\n",
|
|
ld->name, mld->irq_cp2ap_msg, err);
|
|
goto error;
|
|
}
|
|
|
|
mld->mbx_perf_req_cpu = modem->mbx->mbx_cp2ap_perf_req_cpu;
|
|
mld->mbx_perf_req_mif = modem->mbx->mbx_cp2ap_perf_req_mif;
|
|
mld->mbx_perf_req_int = modem->mbx->mbx_cp2ap_perf_req_int;
|
|
mld->irq_perf_req_cpu = modem->mbx->irq_cp2ap_perf_req_cpu;
|
|
mld->irq_perf_req_mif = modem->mbx->irq_cp2ap_perf_req_mif;
|
|
mld->irq_perf_req_int = modem->mbx->irq_cp2ap_perf_req_int;
|
|
|
|
mld->ap_clk_table = modem->mbx->ap_clk_table;
|
|
mld->ap_clk_cnt = modem->mbx->ap_clk_cnt;
|
|
|
|
mld->mif_clk_table = modem->mbx->mif_clk_table;
|
|
mld->mif_clk_cnt = modem->mbx->mif_clk_cnt;
|
|
|
|
mld->int_clk_table = modem->mbx->int_clk_table;
|
|
mld->int_clk_cnt = modem->mbx->int_clk_cnt;
|
|
|
|
pm_qos_add_request(&pm_qos_req_cl0, PM_QOS_CLUSTER0_FREQ_MIN, 0);
|
|
pm_qos_add_request(&pm_qos_req_cl1, 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(&mld->pm_qos_work_cpu, shmem_qos_work_cpu);
|
|
INIT_WORK(&mld->pm_qos_work_mif, shmem_qos_work_mif);
|
|
INIT_WORK(&mld->pm_qos_work_int, shmem_qos_work_int);
|
|
|
|
err = mbox_request_irq(MCU_CP, mld->irq_perf_req_cpu, shmem_qos_cpu_req_handler, mld);
|
|
if (err) {
|
|
mif_err("%s: ERR! mbox_request_irq(MCU_CP, %u) fail (%d)\n",
|
|
ld->name, mld->irq_perf_req_cpu, err);
|
|
goto error;
|
|
}
|
|
|
|
err = mbox_request_irq(MCU_CP, mld->irq_perf_req_mif, shmem_qos_mif_req_handler, mld);
|
|
if (err) {
|
|
mif_err("%s: ERR! mbox_request_irq(MCU_CP, %u) fail (%d)\n",
|
|
ld->name, mld->irq_perf_req_mif, err);
|
|
goto error;
|
|
}
|
|
|
|
err = mbox_request_irq(MCU_CP, mld->irq_perf_req_int, shmem_qos_int_req_handler, mld);
|
|
if (err) {
|
|
mif_err("%s: ERR! mbox_request_irq(MCU_CP, %u) fail (%d)\n",
|
|
ld->name, mld->irq_perf_req_int, err);
|
|
goto error;
|
|
}
|
|
|
|
/**
|
|
* Retrieve SHMEM MBOX# and IRQ# for wakelock
|
|
*/
|
|
mld->irq_cp2ap_wakelock = modem->mbx->irq_cp2ap_wakelock;
|
|
|
|
wake_lock_init(&mld->cp_wakelock, WAKE_LOCK_SUSPEND, ld->name);
|
|
|
|
err = mbox_request_irq(MCU_CP, mld->irq_cp2ap_wakelock,
|
|
shmem_cp2ap_wakelock_handler, mld);
|
|
if (err) {
|
|
mif_err("%s: ERR! mbox_request_irq(MCU_CP, %u) fail (%d)\n",
|
|
ld->name, mld->irq_perf_req_int, err);
|
|
goto error;
|
|
}
|
|
|
|
/**
|
|
* Retrieve SHMEM MBOX# and IRQ# for RAT_MODE
|
|
*/
|
|
#if defined(CONFIG_PCI_EXYNOS)
|
|
mld->irq_cp2ap_rat_mode = modem->mbx->irq_cp2ap_rat_mode;
|
|
|
|
err = mbox_request_irq(MCU_CP, mld->irq_cp2ap_rat_mode,
|
|
shmem_cp2ap_rat_mode_handler, mld);
|
|
if (err) {
|
|
mif_err("%s: ERR! mbox_request_irq(MCU_CP, %u) fail (%d)\n",
|
|
ld->name, mld->irq_cp2ap_rat_mode, err);
|
|
goto error;
|
|
}
|
|
#endif
|
|
|
|
/* Parsing devfreq, cpufreq table from ECT */
|
|
mif_err("Parsing MIF table...\n");
|
|
err = exynos_devfreq_parse_ect(mld, "dvfs_mif");
|
|
if (err < 0)
|
|
mif_err("Can't get MIF table!!!!!\n");
|
|
|
|
mif_err("Parsing CL0 table...\n");
|
|
err = exynos_devfreq_parse_ect(mld, "dvfs_cpucl0");
|
|
if (err < 0)
|
|
mif_err("Can't get CPU table!!!!!\n");
|
|
|
|
mif_err("Parsing CL1 table...\n");
|
|
err = exynos_devfreq_parse_ect(mld, "dvfs_cpucl1");
|
|
if (err < 0)
|
|
mif_err("Can't get CPU table!!!!!\n");
|
|
|
|
mif_err("Parsing INT table...\n");
|
|
err = exynos_devfreq_parse_ect(mld, "dvfs_int");
|
|
if (err < 0)
|
|
mif_err("Can't get INT table!!!!!\n");
|
|
|
|
/**
|
|
* For TX Flow-control command from CP
|
|
*/
|
|
mld->mbx_cp2ap_status = modem->mbx->mbx_cp2ap_status;
|
|
mld->irq_cp2ap_status = modem->mbx->irq_cp2ap_status;
|
|
mld->tx_flowctrl_cmd = 0;
|
|
|
|
err = mbox_request_irq(MCU_CP, mld->irq_cp2ap_status,
|
|
shmem_tx_state_handler, mld);
|
|
if (err) {
|
|
mif_err("%s: ERR! mbox_request_irq(MCU_CP, %u) fail (%d)\n",
|
|
ld->name, mld->irq_cp2ap_status, err);
|
|
goto error;
|
|
}
|
|
|
|
mld->pktlog = create_pktlog("shmem");
|
|
if (!mld->pktlog)
|
|
mif_err("packet log device create fail\n");
|
|
|
|
mld->syscp_info = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
|
|
/* register reboot notifier */
|
|
mld->reboot_nb.notifier_call = shm_reboot_notifier;
|
|
register_reboot_notifier(&mld->reboot_nb);
|
|
|
|
/* Link mem_link_device to modem_data */
|
|
modem->mld = mld;
|
|
|
|
|
|
clean_vss_magic_code();
|
|
|
|
mld->tx_period_ms = TX_PERIOD_MS;
|
|
|
|
if (sysfs_create_group(&pdev->dev.kobj, &shmem_group))
|
|
mif_err("failed to create sysfs node related shmem\n");
|
|
|
|
#ifdef CONFIG_LINK_DEVICE_NAPI
|
|
if (sysfs_create_group(&pdev->dev.kobj, &napi_group))
|
|
mif_err("failed to create sysfs node related napi\n");
|
|
#endif
|
|
|
|
mif_err("---\n");
|
|
return ld;
|
|
|
|
error:
|
|
shm_release_regions();
|
|
|
|
kfree(mld);
|
|
mif_err("xxx\n");
|
|
return NULL;
|
|
}
|