1) Encode UL packet into datagram. 2) Decode DL datagram and route it to network layer. 3) Supports credit based flow control. Signed-off-by: M Chetan Kumar <m.chetan.kumar@xxxxxxxxx> --- v4: no change. v3: Aligned ipc_ prefix for function name to be consistent across file. v2: * Endianness type correction for Host-Device protocol structure. * Function signature documentation correction. * Streamline multiple returns using goto. * Removed vlan tag id & replace with ip link interface id. --- drivers/net/wwan/iosm/iosm_ipc_mux_codec.c | 910 +++++++++++++++++++++ drivers/net/wwan/iosm/iosm_ipc_mux_codec.h | 193 +++++ 2 files changed, 1103 insertions(+) create mode 100644 drivers/net/wwan/iosm/iosm_ipc_mux_codec.c create mode 100644 drivers/net/wwan/iosm/iosm_ipc_mux_codec.h diff --git a/drivers/net/wwan/iosm/iosm_ipc_mux_codec.c b/drivers/net/wwan/iosm/iosm_ipc_mux_codec.c new file mode 100644 index 000000000000..fbf3cab3394c --- /dev/null +++ b/drivers/net/wwan/iosm/iosm_ipc_mux_codec.c @@ -0,0 +1,910 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2020-21 Intel Corporation. + */ + +#include <linux/nospec.h> + +#include "iosm_ipc_imem_ops.h" +#include "iosm_ipc_mux_codec.h" +#include "iosm_ipc_task_queue.h" + +/* Test the link power state and send a MUX command in blocking mode. */ +static int ipc_mux_tq_cmd_send(struct iosm_imem *ipc_imem, int arg, void *msg, + size_t size) +{ + struct iosm_mux *ipc_mux = ipc_imem->mux; + const struct mux_acb *acb = msg; + + skb_queue_tail(&ipc_mux->channel->ul_list, acb->skb); + ipc_imem_ul_send(ipc_mux->imem); + + return 0; +} + +static int ipc_mux_acb_send(struct iosm_mux *ipc_mux, bool blocking) +{ + struct completion *completion = &ipc_mux->channel->ul_sem; + int ret = ipc_task_queue_send_task(ipc_mux->imem, ipc_mux_tq_cmd_send, + 0, &ipc_mux->acb, + sizeof(ipc_mux->acb), false); + if (ret) { + dev_err(ipc_mux->dev, "unable to send mux command"); + return ret; + } + + /* if blocking, suspend the app and wait for irq in the flash or + * crash phase. return false on timeout to indicate failure. + */ + if (blocking) { + u32 wait_time_milliseconds = IPC_MUX_CMD_RUN_DEFAULT_TIMEOUT; + + reinit_completion(completion); + + if (wait_for_completion_interruptible_timeout + (completion, msecs_to_jiffies(wait_time_milliseconds)) == + 0) { + dev_err(ipc_mux->dev, "ch[%d] timeout", + ipc_mux->channel_id); + ipc_uevent_send(ipc_mux->imem->dev, UEVENT_MDM_TIMEOUT); + return -ETIMEDOUT; + } + } + + return 0; +} + +/* Prepare mux Command */ +static struct mux_lite_cmdh *ipc_mux_lite_add_cmd(struct iosm_mux *ipc_mux, + u32 cmd, struct mux_acb *acb, + void *param, u32 param_size) +{ + struct mux_lite_cmdh *cmdh = (struct mux_lite_cmdh *)acb->skb->data; + + cmdh->signature = cpu_to_le32(MUX_SIG_CMDH); + cmdh->command_type = cpu_to_le32(cmd); + cmdh->if_id = acb->if_id; + + acb->cmd = cmd; + + cmdh->cmd_len = cpu_to_le16(offsetof(struct mux_lite_cmdh, param) + + param_size); + cmdh->transaction_id = cpu_to_le32(ipc_mux->tx_transaction_id++); + + if (param) + memcpy(&cmdh->param, param, param_size); + + skb_put(acb->skb, le16_to_cpu(cmdh->cmd_len)); + + return cmdh; +} + +static int ipc_mux_acb_alloc(struct iosm_mux *ipc_mux) +{ + struct mux_acb *acb = &ipc_mux->acb; + struct sk_buff *skb; + dma_addr_t mapping; + + /* Allocate skb memory for the uplink buffer. */ + skb = ipc_pcie_alloc_skb(ipc_mux->pcie, MUX_MAX_UL_ACB_BUF_SIZE, + GFP_ATOMIC, &mapping, DMA_TO_DEVICE, 0); + if (!skb) + return -ENOMEM; + + /* Save the skb address. */ + acb->skb = skb; + + memset(skb->data, 0, MUX_MAX_UL_ACB_BUF_SIZE); + + return 0; +} + +int ipc_mux_dl_acb_send_cmds(struct iosm_mux *ipc_mux, u32 cmd_type, u8 if_id, + u32 transaction_id, union mux_cmd_param *param, + size_t res_size, bool blocking, bool respond) +{ + struct mux_acb *acb = &ipc_mux->acb; + struct mux_lite_cmdh *ack_lite; + int ret = 0; + + acb->if_id = if_id; + ret = ipc_mux_acb_alloc(ipc_mux); + if (ret) + return ret; + + ack_lite = ipc_mux_lite_add_cmd(ipc_mux, cmd_type, acb, param, + res_size); + if (respond) + ack_lite->transaction_id = cpu_to_le32(transaction_id); + + ret = ipc_mux_acb_send(ipc_mux, blocking); + + return ret; +} + +void ipc_mux_netif_tx_flowctrl(struct mux_session *session, int idx, bool on) +{ + /* Inform the network interface to start/stop flow ctrl */ + ipc_wwan_tx_flowctrl(session->wwan, idx, on); +} + +static int ipc_mux_dl_cmdresps_decode_process(struct iosm_mux *ipc_mux, + struct mux_lite_cmdh *cmdh) +{ + struct mux_acb *acb = &ipc_mux->acb; + + switch (le32_to_cpu(cmdh->command_type)) { + case MUX_CMD_OPEN_SESSION_RESP: + case MUX_CMD_CLOSE_SESSION_RESP: + /* Resume the control application. */ + acb->got_param = cmdh->param; + break; + + case MUX_LITE_CMD_FLOW_CTL_ACK: + /* This command type is not expected as response for + * Aggregation version of the protocol. So return non-zero. + */ + if (ipc_mux->protocol != MUX_LITE) + return -EINVAL; + + dev_dbg(ipc_mux->dev, "if %u FLOW_CTL_ACK %u received", + cmdh->if_id, le32_to_cpu(cmdh->transaction_id)); + break; + + default: + return -EINVAL; + } + + acb->wanted_response = MUX_CMD_INVALID; + acb->got_response = le32_to_cpu(cmdh->command_type); + complete(&ipc_mux->channel->ul_sem); + + return 0; +} + +static int ipc_mux_dl_dlcmds_decode_process(struct iosm_mux *ipc_mux, + struct mux_lite_cmdh *cmdh) +{ + union mux_cmd_param *param = &cmdh->param; + struct mux_session *session; + int new_size; + + dev_dbg(ipc_mux->dev, "if_id[%d]: dlcmds decode process %d", + cmdh->if_id, le32_to_cpu(cmdh->command_type)); + + switch (le32_to_cpu(cmdh->command_type)) { + case MUX_LITE_CMD_FLOW_CTL: + + if (cmdh->if_id >= ipc_mux->nr_sessions) { + dev_err(ipc_mux->dev, "if_id [%d] not valid", + cmdh->if_id); + return -EINVAL; /* No session interface id. */ + } + + session = &ipc_mux->session[cmdh->if_id]; + + new_size = offsetof(struct mux_lite_cmdh, param) + + sizeof(param->flow_ctl); + if (param->flow_ctl.mask == cpu_to_le32(0xFFFFFFFF)) { + /* Backward Compatibility */ + if (cmdh->cmd_len == cpu_to_le16(new_size)) + session->flow_ctl_mask = + le32_to_cpu(param->flow_ctl.mask); + else + session->flow_ctl_mask = ~0; + /* if CP asks for FLOW CTRL Enable + * then set our internal flow control Tx flag + * to limit uplink session queueing + */ + session->net_tx_stop = true; + /* Update the stats */ + session->flow_ctl_en_cnt++; + } else if (param->flow_ctl.mask == 0) { + /* Just reset the Flow control mask and let + * mux_flow_ctrl_low_thre_b take control on + * our internal Tx flag and enabling kernel + * flow control + */ + /* Backward Compatibility */ + if (cmdh->cmd_len == cpu_to_le16(new_size)) + session->flow_ctl_mask = + le32_to_cpu(param->flow_ctl.mask); + else + session->flow_ctl_mask = 0; + /* Update the stats */ + session->flow_ctl_dis_cnt++; + } else { + break; + } + + dev_dbg(ipc_mux->dev, "if[%u] FLOW CTRL 0x%08X", cmdh->if_id, + le32_to_cpu(param->flow_ctl.mask)); + break; + + case MUX_LITE_CMD_LINK_STATUS_REPORT: + break; + + default: + return -EINVAL; + } + return 0; +} + +/* Decode and Send appropriate response to a command block. */ +static void ipc_mux_dl_cmd_decode(struct iosm_mux *ipc_mux, struct sk_buff *skb) +{ + struct mux_lite_cmdh *cmdh = (struct mux_lite_cmdh *)skb->data; + __le32 trans_id = cmdh->transaction_id; + + if (ipc_mux_dl_cmdresps_decode_process(ipc_mux, cmdh)) { + /* Unable to decode command response indicates the cmd_type + * may be a command instead of response. So try to decoding it. + */ + if (!ipc_mux_dl_dlcmds_decode_process(ipc_mux, cmdh)) { + /* Decoded command may need a response. Give the + * response according to the command type. + */ + union mux_cmd_param *mux_cmd = NULL; + size_t size = 0; + u32 cmd = MUX_LITE_CMD_LINK_STATUS_REPORT_RESP; + + if (cmdh->command_type == + cpu_to_le32(MUX_LITE_CMD_LINK_STATUS_REPORT)) { + mux_cmd = &cmdh->param; + mux_cmd->link_status_resp.response = + cpu_to_le32(MUX_CMD_RESP_SUCCESS); + /* response field is u32 */ + size = sizeof(u32); + } else if (cmdh->command_type == + cpu_to_le32(MUX_LITE_CMD_FLOW_CTL)) { + cmd = MUX_LITE_CMD_FLOW_CTL_ACK; + } else { + return; + } + + if (ipc_mux_dl_acb_send_cmds(ipc_mux, cmd, cmdh->if_id, + le32_to_cpu(trans_id), + mux_cmd, size, false, + true)) + dev_err(ipc_mux->dev, + "if_id %d: cmd send failed", + cmdh->if_id); + } + } +} + +/* Pass the DL packet to the netif layer. */ +static int ipc_mux_net_receive(struct iosm_mux *ipc_mux, int if_id, + struct iosm_wwan *wwan, u32 offset, + u8 service_class, struct sk_buff *skb) +{ + struct sk_buff *dest_skb = skb_clone(skb, GFP_ATOMIC); + + if (!dest_skb) + return -ENOMEM; + + skb_pull(dest_skb, offset); + skb_set_tail_pointer(dest_skb, dest_skb->len); + /* Pass the packet to the netif layer. */ + dest_skb->priority = service_class; + + return ipc_wwan_receive(wwan, dest_skb, false, if_id + 1); +} + +/* Decode Flow Credit Table in the block */ +static void ipc_mux_dl_fcth_decode(struct iosm_mux *ipc_mux, + unsigned char *block) +{ + struct ipc_mem_lite_gen_tbl *fct = (struct ipc_mem_lite_gen_tbl *)block; + struct iosm_wwan *wwan; + int ul_credits; + int if_id; + + if (fct->vfl_length != sizeof(fct->vfl.nr_of_bytes)) { + dev_err(ipc_mux->dev, "unexpected FCT length: %d", + fct->vfl_length); + return; + } + + if_id = fct->if_id; + if (if_id >= ipc_mux->nr_sessions) { + dev_err(ipc_mux->dev, "not supported if_id: %d", if_id); + return; + } + + /* Is the session active ? */ + if_id = array_index_nospec(if_id, ipc_mux->nr_sessions); + wwan = ipc_mux->session[if_id].wwan; + if (!wwan) { + dev_err(ipc_mux->dev, "session Net ID is NULL"); + return; + } + + ul_credits = fct->vfl.nr_of_bytes; + + dev_dbg(ipc_mux->dev, "Flow_Credit:: if_id[%d] Old: %d Grants: %d", + if_id, ipc_mux->session[if_id].ul_flow_credits, ul_credits); + + /* Update the Flow Credit information from ADB */ + ipc_mux->session[if_id].ul_flow_credits += ul_credits; + + /* Check whether the TX can be started */ + if (ipc_mux->session[if_id].ul_flow_credits > 0) { + ipc_mux->session[if_id].net_tx_stop = false; + ipc_mux_netif_tx_flowctrl(&ipc_mux->session[if_id], + ipc_mux->session[if_id].if_id, false); + } +} + +/* Decode non-aggregated datagram */ +static void ipc_mux_dl_adgh_decode(struct iosm_mux *ipc_mux, + struct sk_buff *skb) +{ + u32 pad_len, packet_offset; + struct iosm_wwan *wwan; + struct mux_adgh *adgh; + u8 *block = skb->data; + int rc = 0; + u8 if_id; + + adgh = (struct mux_adgh *)block; + + if (adgh->signature != cpu_to_le32(MUX_SIG_ADGH)) { + dev_err(ipc_mux->dev, "invalid ADGH signature received"); + return; + } + + if_id = adgh->if_id; + if (if_id >= ipc_mux->nr_sessions) { + dev_err(ipc_mux->dev, "invalid if_id while decoding %d", if_id); + return; + } + + /* Is the session active ? */ + if_id = array_index_nospec(if_id, ipc_mux->nr_sessions); + wwan = ipc_mux->session[if_id].wwan; + if (!wwan) { + dev_err(ipc_mux->dev, "session Net ID is NULL"); + return; + } + + /* Store the pad len for the corresponding session + * Pad bytes as negotiated in the open session less the header size + * (see session management chapter for details). + * If resulting padding is zero or less, the additional head padding is + * omitted. For e.g., if HEAD_PAD_LEN = 16 or less, this field is + * omitted if HEAD_PAD_LEN = 20, then this field will have 4 bytes + * set to zero + */ + pad_len = + ipc_mux->session[if_id].dl_head_pad_len - IPC_MEM_DL_ETH_OFFSET; + packet_offset = sizeof(*adgh) + pad_len; + + if_id += ipc_mux->wwan_q_offset; + + /* Pass the packet to the netif layer */ + rc = ipc_mux_net_receive(ipc_mux, if_id, wwan, packet_offset, + adgh->service_class, skb); + if (rc) { + dev_err(ipc_mux->dev, "mux adgh decoding error"); + return; + } + ipc_mux->session[if_id].flush = 1; +} + +void ipc_mux_dl_decode(struct iosm_mux *ipc_mux, struct sk_buff *skb) +{ + u32 signature; + + if (!skb->data) + return; + + /* Decode the MUX header type. */ + signature = le32_to_cpup((__le32 *)skb->data); + + switch (signature) { + case MUX_SIG_ADGH: + ipc_mux_dl_adgh_decode(ipc_mux, skb); + break; + + case MUX_SIG_FCTH: + ipc_mux_dl_fcth_decode(ipc_mux, skb->data); + break; + + case MUX_SIG_CMDH: + ipc_mux_dl_cmd_decode(ipc_mux, skb); + break; + + default: + dev_err(ipc_mux->dev, "invalid ABH signature"); + } + + ipc_pcie_kfree_skb(ipc_mux->pcie, skb); +} + +static int ipc_mux_ul_skb_alloc(struct iosm_mux *ipc_mux, + struct mux_adb *ul_adb, u32 type) +{ + /* Take the first element of the free list. */ + struct sk_buff *skb = skb_dequeue(&ul_adb->free_list); + int qlt_size; + + if (!skb) + return -EBUSY; /* Wait for a free ADB skb. */ + + /* Mark it as UL ADB to select the right free operation. */ + IPC_CB(skb)->op_type = (u8)UL_MUX_OP_ADB; + + switch (type) { + case MUX_SIG_ADGH: + /* Save the ADB memory settings. */ + ul_adb->dest_skb = skb; + ul_adb->buf = skb->data; + ul_adb->size = IPC_MEM_MAX_DL_MUX_LITE_BUF_SIZE; + /* reset statistic counter */ + ul_adb->if_cnt = 0; + ul_adb->payload_size = 0; + ul_adb->dg_cnt_total = 0; + + ul_adb->adgh = (struct mux_adgh *)skb->data; + memset(ul_adb->adgh, 0, sizeof(struct mux_adgh)); + break; + + case MUX_SIG_QLTH: + qlt_size = offsetof(struct ipc_mem_lite_gen_tbl, vfl) + + (MUX_QUEUE_LEVEL * sizeof(struct mux_lite_vfl)); + + if (qlt_size > IPC_MEM_MAX_DL_MUX_LITE_BUF_SIZE) { + dev_err(ipc_mux->dev, + "can't support. QLT size:%d SKB size: %d", + qlt_size, IPC_MEM_MAX_DL_MUX_LITE_BUF_SIZE); + return -ERANGE; + } + + ul_adb->qlth_skb = skb; + memset((ul_adb->qlth_skb)->data, 0, qlt_size); + skb_put(skb, qlt_size); + break; + } + + return 0; +} + +static void ipc_mux_ul_adgh_finish(struct iosm_mux *ipc_mux) +{ + struct mux_adb *ul_adb = &ipc_mux->ul_adb; + u16 adgh_len; + long long bytes; + char *str; + + if (!ul_adb || !ul_adb->dest_skb) { + dev_err(ipc_mux->dev, "no dest skb"); + return; + } + + adgh_len = le16_to_cpu(ul_adb->adgh->length); + skb_put(ul_adb->dest_skb, adgh_len); + skb_queue_tail(&ipc_mux->channel->ul_list, ul_adb->dest_skb); + ul_adb->dest_skb = NULL; + + if (ipc_mux->ul_flow == MUX_UL_ON_CREDITS) { + struct mux_session *session; + + session = &ipc_mux->session[ul_adb->adgh->if_id]; + str = "available_credits"; + bytes = (long long)session->ul_flow_credits; + + } else { + str = "pend_bytes"; + bytes = ipc_mux->ul_data_pend_bytes; + ipc_mux->ul_data_pend_bytes = ipc_mux->ul_data_pend_bytes + + adgh_len; + } + + dev_dbg(ipc_mux->dev, "UL ADGH: size=%u, if_id=%d, payload=%d, %s=%lld", + adgh_len, ul_adb->adgh->if_id, ul_adb->payload_size, + str, bytes); +} + +/* Allocates an ADB from the free list and initializes it with ADBH */ +static bool ipc_mux_ul_adb_allocate(struct iosm_mux *ipc_mux, + struct mux_adb *adb, int *size_needed, + u32 type) +{ + bool ret_val = false; + int status; + + if (!adb->dest_skb) { + /* Allocate memory for the ADB including of the + * datagram table header. + */ + status = ipc_mux_ul_skb_alloc(ipc_mux, adb, type); + if (status) + /* Is a pending ADB available ? */ + ret_val = true; /* None. */ + + /* Update size need to zero only for new ADB memory */ + *size_needed = 0; + } + + return ret_val; +} + +/* Informs the network stack to stop sending further packets for all opened + * sessions + */ +static void ipc_mux_stop_tx_for_all_sessions(struct iosm_mux *ipc_mux) +{ + struct mux_session *session; + int idx; + + for (idx = 0; idx < ipc_mux->nr_sessions; idx++) { + session = &ipc_mux->session[idx]; + + if (!session->wwan) + continue; + + session->net_tx_stop = true; + } +} + +/* Sends Queue Level Table of all opened sessions */ +static bool ipc_mux_lite_send_qlt(struct iosm_mux *ipc_mux) +{ + struct ipc_mem_lite_gen_tbl *qlt; + struct mux_session *session; + bool qlt_updated = false; + int i; + int qlt_size; + + if (!ipc_mux->initialized || ipc_mux->state != MUX_S_ACTIVE) + return qlt_updated; + + qlt_size = offsetof(struct ipc_mem_lite_gen_tbl, vfl) + + MUX_QUEUE_LEVEL * sizeof(struct mux_lite_vfl); + + for (i = 0; i < ipc_mux->nr_sessions; i++) { + session = &ipc_mux->session[i]; + + if (!session->wwan || session->flow_ctl_mask) + continue; + + if (ipc_mux_ul_skb_alloc(ipc_mux, &ipc_mux->ul_adb, + MUX_SIG_QLTH)) { + dev_err(ipc_mux->dev, + "no reserved mem to send QLT of if_id: %d", i); + break; + } + + /* Prepare QLT */ + qlt = (struct ipc_mem_lite_gen_tbl *)(ipc_mux->ul_adb.qlth_skb) + ->data; + qlt->signature = cpu_to_le32(MUX_SIG_QLTH); + qlt->length = cpu_to_le16(qlt_size); + qlt->if_id = i; + qlt->vfl_length = MUX_QUEUE_LEVEL * sizeof(struct mux_lite_vfl); + qlt->reserved[0] = 0; + qlt->reserved[1] = 0; + + qlt->vfl.nr_of_bytes = session->ul_list.qlen; + + /* Add QLT to the transfer list. */ + skb_queue_tail(&ipc_mux->channel->ul_list, + ipc_mux->ul_adb.qlth_skb); + + qlt_updated = true; + ipc_mux->ul_adb.qlth_skb = NULL; + } + + if (qlt_updated) + /* Updates the TDs with ul_list */ + (void)ipc_imem_ul_write_td(ipc_mux->imem); + + return qlt_updated; +} + +/* Checks the available credits for the specified session and returns + * number of packets for which credits are available. + */ +static int ipc_mux_ul_bytes_credits_check(struct iosm_mux *ipc_mux, + struct mux_session *session, + struct sk_buff_head *ul_list, + int max_nr_of_pkts) +{ + int pkts_to_send = 0; + struct sk_buff *skb; + int credits = 0; + + if (ipc_mux->ul_flow == MUX_UL_ON_CREDITS) { + credits = session->ul_flow_credits; + if (credits <= 0) { + dev_dbg(ipc_mux->dev, + "FC::if_id[%d] Insuff.Credits/Qlen:%d/%u", + session->if_id, session->ul_flow_credits, + session->ul_list.qlen); /* nr_of_bytes */ + return 0; + } + } else { + credits = IPC_MEM_MUX_UL_FLOWCTRL_HIGH_B - + ipc_mux->ul_data_pend_bytes; + if (credits <= 0) { + ipc_mux_stop_tx_for_all_sessions(ipc_mux); + + dev_dbg(ipc_mux->dev, + "if_id[%d] encod. fail Bytes: %llu, thresh: %d", + session->if_id, ipc_mux->ul_data_pend_bytes, + IPC_MEM_MUX_UL_FLOWCTRL_HIGH_B); + return 0; + } + } + + /* Check if there are enough credits/bytes available to send the + * requested max_nr_of_pkts. Otherwise restrict the nr_of_pkts + * depending on available credits. + */ + skb_queue_walk(ul_list, skb) + { + if (!(credits >= skb->len && pkts_to_send < max_nr_of_pkts)) + break; + credits -= skb->len; + pkts_to_send++; + } + + return pkts_to_send; +} + +/* Encode the UL IP packet according to Lite spec. */ +static int ipc_mux_ul_adgh_encode(struct iosm_mux *ipc_mux, int session_id, + struct mux_session *session, + struct sk_buff_head *ul_list, + struct mux_adb *adb, int nr_of_pkts) +{ + int offset = sizeof(struct mux_adgh); + int adb_updated = -EINVAL; + struct sk_buff *src_skb; + int aligned_size = 0; + int nr_of_skb = 0; + u32 pad_len = 0; + + /* Re-calculate the number of packets depending on number of bytes to be + * processed/available credits. + */ + nr_of_pkts = ipc_mux_ul_bytes_credits_check(ipc_mux, session, ul_list, + nr_of_pkts); + + /* If calculated nr_of_pkts from available credits is <= 0 + * then nothing to do. + */ + if (nr_of_pkts <= 0) + return 0; + + /* Read configured UL head_pad_length for session.*/ + if (session->ul_head_pad_len > IPC_MEM_DL_ETH_OFFSET) + pad_len = session->ul_head_pad_len - IPC_MEM_DL_ETH_OFFSET; + + /* Process all pending UL packets for this session + * depending on the allocated datagram table size. + */ + while (nr_of_pkts > 0) { + /* get destination skb allocated */ + if (ipc_mux_ul_adb_allocate(ipc_mux, adb, &ipc_mux->size_needed, + MUX_SIG_ADGH)) { + dev_err(ipc_mux->dev, "no reserved memory for ADGH"); + return -ENOMEM; + } + + /* Peek at the head of the list. */ + src_skb = skb_peek(ul_list); + if (!src_skb) { + dev_err(ipc_mux->dev, + "skb peek return NULL with count : %d", + nr_of_pkts); + break; + } + + /* Calculate the memory value. */ + aligned_size = ALIGN((pad_len + src_skb->len), 4); + + ipc_mux->size_needed = sizeof(struct mux_adgh) + aligned_size; + + if (ipc_mux->size_needed > adb->size) { + dev_dbg(ipc_mux->dev, "size needed %d, adgh size %d", + ipc_mux->size_needed, adb->size); + /* Return 1 if any IP packet is added to the transfer + * list. + */ + return nr_of_skb ? 1 : 0; + } + + /* Add buffer (without head padding to next pending transfer) */ + memcpy(adb->buf + offset + pad_len, src_skb->data, + src_skb->len); + + adb->adgh->signature = cpu_to_le32(MUX_SIG_ADGH); + adb->adgh->if_id = session_id; + adb->adgh->length = + cpu_to_le16(sizeof(struct mux_adgh) + pad_len + + src_skb->len); + adb->adgh->service_class = src_skb->priority; + adb->adgh->next_count = --nr_of_pkts; + adb->dg_cnt_total++; + adb->payload_size += src_skb->len; + + if (ipc_mux->ul_flow == MUX_UL_ON_CREDITS) + /* Decrement the credit value as we are processing the + * datagram from the UL list. + */ + session->ul_flow_credits -= src_skb->len; + + /* Remove the processed elements and free it. */ + src_skb = skb_dequeue(ul_list); + dev_kfree_skb(src_skb); + nr_of_skb++; + + ipc_mux_ul_adgh_finish(ipc_mux); + } + + if (nr_of_skb) { + /* Send QLT info to modem if pending bytes > high watermark + * in case of mux lite + */ + if (ipc_mux->ul_flow == MUX_UL_ON_CREDITS || + ipc_mux->ul_data_pend_bytes >= + IPC_MEM_MUX_UL_FLOWCTRL_LOW_B) + adb_updated = ipc_mux_lite_send_qlt(ipc_mux); + else + adb_updated = 1; + + /* Updates the TDs with ul_list */ + (void)ipc_imem_ul_write_td(ipc_mux->imem); + } + + return adb_updated; +} + +bool ipc_mux_ul_data_encode(struct iosm_mux *ipc_mux) +{ + struct sk_buff_head *ul_list; + struct mux_session *session; + int updated = 0; + int session_id; + int dg_n; + int i; + + if (!ipc_mux || ipc_mux->state != MUX_S_ACTIVE || + ipc_mux->adb_prep_ongoing) + return false; + + ipc_mux->adb_prep_ongoing = true; + + for (i = 0; i < ipc_mux->nr_sessions; i++) { + session_id = ipc_mux->rr_next_session; + session = &ipc_mux->session[session_id]; + + /* Go to next handle rr_next_session overflow */ + ipc_mux->rr_next_session++; + if (ipc_mux->rr_next_session >= ipc_mux->nr_sessions) + ipc_mux->rr_next_session = 0; + + if (!session->wwan || session->flow_ctl_mask || + session->net_tx_stop) + continue; + + ul_list = &session->ul_list; + + /* Is something pending in UL and flow ctrl off */ + dg_n = skb_queue_len(ul_list); + if (dg_n > MUX_MAX_UL_DG_ENTRIES) + dg_n = MUX_MAX_UL_DG_ENTRIES; + + if (dg_n == 0) + /* Nothing to do for ipc_mux session + * -> try next session id. + */ + continue; + + updated = ipc_mux_ul_adgh_encode(ipc_mux, session_id, session, + ul_list, &ipc_mux->ul_adb, + dg_n); + } + + ipc_mux->adb_prep_ongoing = false; + return updated == 1; +} + +void ipc_mux_ul_encoded_process(struct iosm_mux *ipc_mux, struct sk_buff *skb) +{ + struct mux_adgh *adgh; + u16 adgh_len; + + adgh = (struct mux_adgh *)skb->data; + adgh_len = le16_to_cpu(adgh->length); + + if (adgh->signature == cpu_to_le32(MUX_SIG_ADGH) && + ipc_mux->ul_flow == MUX_UL) + ipc_mux->ul_data_pend_bytes = ipc_mux->ul_data_pend_bytes - + adgh_len; + + if (ipc_mux->ul_flow == MUX_UL) + dev_dbg(ipc_mux->dev, "ul_data_pend_bytes: %lld", + ipc_mux->ul_data_pend_bytes); + + /* Reset the skb settings. */ + skb->tail = 0; + skb->len = 0; + + /* Add the consumed ADB to the free list. */ + skb_queue_tail((&ipc_mux->ul_adb.free_list), skb); +} + +/* Start the NETIF uplink send transfer in MUX mode. */ +static int ipc_mux_tq_ul_trigger_encode(struct iosm_imem *ipc_imem, int arg, + void *msg, size_t size) +{ + struct iosm_mux *ipc_mux = ipc_imem->mux; + bool ul_data_pend = false; + + /* Add session UL data to a ADB and ADGH */ + ul_data_pend = ipc_mux_ul_data_encode(ipc_mux); + if (ul_data_pend) + /* Delay the doorbell irq */ + ipc_imem_td_update_timer_start(ipc_mux->imem); + + /* reset the debounce flag */ + ipc_mux->ev_mux_net_transmit_pending = false; + + return 0; +} + +int ipc_mux_ul_trigger_encode(struct iosm_mux *ipc_mux, int if_id, + struct sk_buff *skb) +{ + struct mux_session *session = &ipc_mux->session[if_id]; + int ret = -EINVAL; + + if (ipc_mux->channel && + ipc_mux->channel->state != IMEM_CHANNEL_ACTIVE) { + dev_err(ipc_mux->dev, + "channel state is not IMEM_CHANNEL_ACTIVE"); + goto out; + } + + if (!session->wwan) { + dev_err(ipc_mux->dev, "session net ID is NULL"); + ret = -EFAULT; + goto out; + } + + /* Session is under flow control. + * Check if packet can be queued in session list, if not + * suspend net tx + */ + if (skb_queue_len(&session->ul_list) >= + (session->net_tx_stop ? + IPC_MEM_MUX_UL_SESS_FCON_THRESHOLD : + (IPC_MEM_MUX_UL_SESS_FCON_THRESHOLD * + IPC_MEM_MUX_UL_SESS_FCOFF_THRESHOLD_FACTOR))) { + ipc_mux_netif_tx_flowctrl(session, session->if_id, true); + ret = -EBUSY; + goto out; + } + + /* Add skb to the uplink skb accumulator. */ + skb_queue_tail(&session->ul_list, skb); + + /* Inform the IPC kthread to pass uplink IP packets to CP. */ + if (!ipc_mux->ev_mux_net_transmit_pending) { + ipc_mux->ev_mux_net_transmit_pending = true; + ret = ipc_task_queue_send_task(ipc_mux->imem, + ipc_mux_tq_ul_trigger_encode, 0, + NULL, 0, false); + if (ret) + goto out; + } + dev_dbg(ipc_mux->dev, "mux ul if[%d] qlen=%d/%u, len=%d/%d, prio=%d", + if_id, skb_queue_len(&session->ul_list), session->ul_list.qlen, + skb->len, skb->truesize, skb->priority); + ret = 0; +out: + return ret; +} diff --git a/drivers/net/wwan/iosm/iosm_ipc_mux_codec.h b/drivers/net/wwan/iosm/iosm_ipc_mux_codec.h new file mode 100644 index 000000000000..4a74e3c9457f --- /dev/null +++ b/drivers/net/wwan/iosm/iosm_ipc_mux_codec.h @@ -0,0 +1,193 @@ +/* SPDX-License-Identifier: GPL-2.0-only + * + * Copyright (C) 2020-21 Intel Corporation. + */ + +#ifndef IOSM_IPC_MUX_CODEC_H +#define IOSM_IPC_MUX_CODEC_H + +#include "iosm_ipc_mux.h" + +/* Queue level size and reporting + * >1 is enable, 0 is disable + */ +#define MUX_QUEUE_LEVEL 1 + +/* Size of the buffer for the IP MUX commands. */ +#define MUX_MAX_UL_ACB_BUF_SIZE 256 + +/* Maximum number of packets in a go per session */ +#define MUX_MAX_UL_DG_ENTRIES 100 + +/* ADGH: Signature of the Datagram Header. */ +#define MUX_SIG_ADGH 0x48474441 + +/* CMDH: Signature of the Command Header. */ +#define MUX_SIG_CMDH 0x48444D43 + +/* QLTH: Signature of the Queue Level Table */ +#define MUX_SIG_QLTH 0x48544C51 + +/* FCTH: Signature of the Flow Credit Table */ +#define MUX_SIG_FCTH 0x48544346 + +/* MUX UL session threshold factor */ +#define IPC_MEM_MUX_UL_SESS_FCOFF_THRESHOLD_FACTOR (4) + +/* Size of the buffer for the IP MUX Lite data buffer. */ +#define IPC_MEM_MAX_DL_MUX_LITE_BUF_SIZE (2 * 1024) + +/* MUX UL session threshold in number of packets */ +#define IPC_MEM_MUX_UL_SESS_FCON_THRESHOLD (64) + +/* Default time out for sending IPC session commands like + * open session, close session etc + * unit : milliseconds + */ +#define IPC_MUX_CMD_RUN_DEFAULT_TIMEOUT 1000 /* 1 second */ + +/* MUX UL flow control lower threshold in bytes */ +#define IPC_MEM_MUX_UL_FLOWCTRL_LOW_B 10240 /* 10KB */ + +/* MUX UL flow control higher threshold in bytes (5ms worth of data)*/ +#define IPC_MEM_MUX_UL_FLOWCTRL_HIGH_B (110 * 1024) + +/** + * struct mux_adgh - Aggregated Datagram Header. + * @signature: Signature of the Aggregated Datagram Header(0x48474441) + * @length: Length (in bytes) of the datagram header. This length + * shall include the header size. Min value: 0x10 + * @if_id: ID of the interface the datagrams belong to + * @opt_ipv4v6: Indicates IPv4(=0)/IPv6(=1), It is optional if not + * used set it to zero. + * @reserved: Reserved bits. Set to zero. + * @service_class: Service class identifier for the datagram. + * @next_count: Count of the datagrams that shall be following this + * datagrams for this interface. A count of zero means + * the next datagram may not belong to this interface. + * @reserved1: Reserved bytes, Set to zero + */ +struct mux_adgh { + __le32 signature; + __le16 length; + u8 if_id; + u8 opt_ipv4v6; + u8 service_class; + u8 next_count; + u8 reserved1[6]; +}; + +/** + * struct mux_lite_cmdh - MUX Lite Command Header + * @signature: Signature of the Command Header(0x48444D43) + * @cmd_len: Length (in bytes) of the command. This length shall + * include the header size. Minimum value: 0x10 + * @if_id: ID of the interface the commands in the table belong to. + * @reserved: Reserved Set to zero. + * @command_type: Command Enum. + * @transaction_id: 4 byte value shall be generated and sent along with a + * command Responses and ACKs shall have the same + * Transaction ID as their commands. It shall be unique to + * the command transaction on the given interface. + * @param: Optional parameters used with the command. + */ +struct mux_lite_cmdh { + __le32 signature; + __le16 cmd_len; + u8 if_id; + u8 reserved; + __le32 command_type; + __le32 transaction_id; + union mux_cmd_param param; +}; + +/** + * struct mux_lite_vfl - value field in generic table + * @nr_of_bytes: Number of bytes available to transmit in the queue. + */ +struct mux_lite_vfl { + u32 nr_of_bytes; +}; + +/** + * struct ipc_mem_lite_gen_tbl - Generic table format for Queue Level + * and Flow Credit + * @signature: Signature of the table + * @length: Length of the table + * @if_id: ID of the interface the table belongs to + * @vfl_length: Value field length + * @reserved: Reserved + * @vfl: Value field of variable length + */ +struct ipc_mem_lite_gen_tbl { + __le32 signature; + __le16 length; + u8 if_id; + u8 vfl_length; + u32 reserved[2]; + struct mux_lite_vfl vfl; +}; + +/** + * ipc_mux_dl_decode -Route the DL packet through the IP MUX layer + * depending on Header. + * @ipc_mux: Pointer to MUX data-struct + * @skb: Pointer to ipc_skb. + */ +void ipc_mux_dl_decode(struct iosm_mux *ipc_mux, struct sk_buff *skb); + +/** + * ipc_mux_dl_acb_send_cmds - Respond to the Command blocks. + * @ipc_mux: Pointer to MUX data-struct + * @cmd_type: Command + * @if_id: Session interface id. + * @transaction_id: Command transaction id. + * @param: Pointer to command params. + * @res_size: Response size + * @blocking: True for blocking send + * @respond: If true return transaction ID + * + * Returns: 0 in success and failure value on error + */ +int ipc_mux_dl_acb_send_cmds(struct iosm_mux *ipc_mux, u32 cmd_type, u8 if_id, + u32 transaction_id, union mux_cmd_param *param, + size_t res_size, bool blocking, bool respond); + +/** + * ipc_mux_netif_tx_flowctrl - Enable/Disable TX flow control on MUX sessions. + * @session: Pointer to mux_session struct + * @idx: Session ID + * @on: true for Enable and false for disable flow control + */ +void ipc_mux_netif_tx_flowctrl(struct mux_session *session, int idx, bool on); + +/** + * ipc_mux_ul_trigger_encode - Route the UL packet through the IP MUX layer + * for encoding. + * @ipc_mux: Pointer to MUX data-struct + * @if_id: Session ID. + * @skb: Pointer to ipc_skb. + * + * Returns: 0 if successfully encoded + * failure value on error + * -EBUSY if packet has to be retransmitted. + */ +int ipc_mux_ul_trigger_encode(struct iosm_mux *ipc_mux, int if_id, + struct sk_buff *skb); +/** + * ipc_mux_ul_data_encode - UL encode function for calling from Tasklet context. + * @ipc_mux: Pointer to MUX data-struct + * + * Returns: TRUE if any packet of any session is encoded FALSE otherwise. + */ +bool ipc_mux_ul_data_encode(struct iosm_mux *ipc_mux); + +/** + * ipc_mux_ul_encoded_process - Handles the Modem processed UL data by adding + * the SKB to the UL free list. + * @ipc_mux: Pointer to MUX data-struct + * @skb: Pointer to ipc_skb. + */ +void ipc_mux_ul_encoded_process(struct iosm_mux *ipc_mux, struct sk_buff *skb); + +#endif -- 2.25.1