| // 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; |
| } |
| |
| /* Initialize the command header. */ |
| static void ipc_mux_acb_init(struct iosm_mux *ipc_mux) |
| { |
| struct mux_acb *acb = &ipc_mux->acb; |
| struct mux_acbh *header; |
| |
| header = (struct mux_acbh *)(acb->skb)->data; |
| header->block_length = cpu_to_le32(sizeof(struct mux_acbh)); |
| header->first_cmd_index = header->block_length; |
| header->signature = cpu_to_le32(IOSM_AGGR_MUX_SIG_ACBH); |
| header->sequence_nr = cpu_to_le16(ipc_mux->acb_tx_sequence_nr++); |
| } |
| |
| /* Add a command to the ACB. */ |
| static struct mux_cmdh *ipc_mux_acb_add_cmd(struct iosm_mux *ipc_mux, u32 cmd, |
| void *param, u32 param_size) |
| { |
| struct mux_acbh *header; |
| struct mux_cmdh *cmdh; |
| struct mux_acb *acb; |
| |
| acb = &ipc_mux->acb; |
| header = (struct mux_acbh *)(acb->skb)->data; |
| cmdh = (struct mux_cmdh *) |
| ((acb->skb)->data + le32_to_cpu(header->block_length)); |
| |
| 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_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, le32_to_cpu(header->block_length) + |
| le16_to_cpu(cmdh->cmd_len)); |
| |
| return cmdh; |
| } |
| |
| /* 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; |
| union mux_type_cmdh cmdh; |
| int ret = 0; |
| |
| acb->if_id = if_id; |
| ret = ipc_mux_acb_alloc(ipc_mux); |
| if (ret) |
| return ret; |
| |
| if (ipc_mux->protocol == MUX_LITE) { |
| cmdh.ack_lite = ipc_mux_lite_add_cmd(ipc_mux, cmd_type, acb, |
| param, res_size); |
| |
| if (respond) |
| cmdh.ack_lite->transaction_id = |
| cpu_to_le32(transaction_id); |
| } else { |
| /* Initialize the ACB header. */ |
| ipc_mux_acb_init(ipc_mux); |
| cmdh.ack_aggr = ipc_mux_acb_add_cmd(ipc_mux, cmd_type, param, |
| res_size); |
| |
| if (respond) |
| cmdh.ack_aggr->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, |
| union mux_cmd_param param, |
| __le32 command_type, u8 if_id, |
| __le32 transaction_id) |
| { |
| struct mux_acb *acb = &ipc_mux->acb; |
| |
| switch (le32_to_cpu(command_type)) { |
| case MUX_CMD_OPEN_SESSION_RESP: |
| case MUX_CMD_CLOSE_SESSION_RESP: |
| /* Resume the control application. */ |
| acb->got_param = 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_id %u FLOW_CTL_ACK %u received", |
| if_id, le32_to_cpu(transaction_id)); |
| break; |
| |
| case IOSM_AGGR_MUX_CMD_FLOW_CTL_ACK: |
| /* This command type is not expected as response for |
| * Lite version of the protocol. So return non-zero. |
| */ |
| if (ipc_mux->protocol == MUX_LITE) |
| return -EINVAL; |
| break; |
| |
| default: |
| return -EINVAL; |
| } |
| |
| acb->wanted_response = MUX_CMD_INVALID; |
| acb->got_response = le32_to_cpu(command_type); |
| complete(&ipc_mux->channel->ul_sem); |
| |
| return 0; |
| } |
| |
| static int ipc_mux_dl_cmds_decode_process(struct iosm_mux *ipc_mux, |
| union mux_cmd_param *param, |
| __le32 command_type, u8 if_id, |
| __le16 cmd_len, int size) |
| { |
| struct mux_session *session; |
| struct hrtimer *adb_timer; |
| |
| dev_dbg(ipc_mux->dev, "if_id[%d]: dlcmds decode process %d", |
| if_id, le32_to_cpu(command_type)); |
| |
| switch (le32_to_cpu(command_type)) { |
| case MUX_LITE_CMD_FLOW_CTL: |
| case IOSM_AGGR_MUX_CMD_FLOW_CTL_DISABLE: |
| |
| if (if_id >= IPC_MEM_MUX_IP_SESSION_ENTRIES) { |
| dev_err(ipc_mux->dev, "if_id [%d] not valid", |
| if_id); |
| return -EINVAL; /* No session interface id. */ |
| } |
| |
| session = &ipc_mux->session[if_id]; |
| adb_timer = &ipc_mux->imem->adb_timer; |
| |
| if (param->flow_ctl.mask == cpu_to_le32(0xFFFFFFFF)) { |
| /* Backward Compatibility */ |
| if (cmd_len == cpu_to_le16(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; |
| |
| /* We have to call Finish ADB here. |
| * Otherwise any already queued data |
| * will be sent to CP when ADB is full |
| * for some other sessions. |
| */ |
| if (ipc_mux->protocol == MUX_AGGREGATION) { |
| ipc_mux_ul_adb_finish(ipc_mux); |
| ipc_imem_hrtimer_stop(adb_timer); |
| } |
| /* 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 |
| */ |
| dev_dbg(ipc_mux->dev, "if_id[%u] flow_ctl mask 0x%08X", |
| if_id, le32_to_cpu(param->flow_ctl.mask)); |
| /* Backward Compatibility */ |
| if (cmd_len == cpu_to_le16(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; |
| } |
| |
| ipc_mux->acc_adb_size = 0; |
| ipc_mux->acc_payload_size = 0; |
| |
| dev_dbg(ipc_mux->dev, "if_id[%u] FLOW CTRL 0x%08X", 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; |
| int size; |
| |
| if (ipc_mux_dl_cmdresps_decode_process(ipc_mux, cmdh->param, |
| cmdh->command_type, cmdh->if_id, |
| cmdh->transaction_id)) { |
| /* Unable to decode command response indicates the cmd_type |
| * may be a command instead of response. So try to decoding it. |
| */ |
| size = offsetof(struct mux_lite_cmdh, param) + |
| sizeof(cmdh->param.flow_ctl); |
| if (!ipc_mux_dl_cmds_decode_process(ipc_mux, &cmdh->param, |
| cmdh->command_type, |
| cmdh->if_id, |
| cmdh->cmd_len, size)) { |
| /* 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, |
| u32 pkt_len) |
| { |
| struct sk_buff *dest_skb = skb_clone(skb, GFP_ATOMIC); |
| |
| if (!dest_skb) |
| return -ENOMEM; |
| |
| skb_pull(dest_skb, offset); |
| skb_trim(dest_skb, pkt_len); |
| /* Pass the packet to the netif layer. */ |
| dest_skb->priority = service_class; |
| |
| return ipc_wwan_receive(wwan, dest_skb, false, if_id); |
| } |
| |
| /* 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_MEM_MUX_IP_SESSION_ENTRIES) { |
| 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_MEM_MUX_IP_SESSION_ENTRIES); |
| wwan = ipc_mux->session[if_id].wwan; |
| if (!wwan) { |
| dev_err(ipc_mux->dev, "session Net ID is NULL"); |
| return; |
| } |
| |
| ul_credits = le32_to_cpu(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, adgh_len; |
| 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(IOSM_AGGR_MUX_SIG_ADGH)) { |
| dev_err(ipc_mux->dev, "invalid ADGH signature received"); |
| return; |
| } |
| |
| if_id = adgh->if_id; |
| if (if_id >= IPC_MEM_MUX_IP_SESSION_ENTRIES) { |
| 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_MEM_MUX_IP_SESSION_ENTRIES); |
| 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; |
| adgh_len = le16_to_cpu(adgh->length); |
| |
| /* Pass the packet to the netif layer */ |
| rc = ipc_mux_net_receive(ipc_mux, if_id, wwan, packet_offset, |
| adgh->service_class, skb, |
| adgh_len - packet_offset); |
| if (rc) { |
| dev_err(ipc_mux->dev, "mux adgh decoding error"); |
| return; |
| } |
| ipc_mux->session[if_id].flush = 1; |
| } |
| |
| static void ipc_mux_dl_acbcmd_decode(struct iosm_mux *ipc_mux, |
| struct mux_cmdh *cmdh, int size) |
| { |
| u32 link_st = IOSM_AGGR_MUX_CMD_LINK_STATUS_REPORT_RESP; |
| u32 fctl_dis = IOSM_AGGR_MUX_CMD_FLOW_CTL_DISABLE; |
| u32 fctl_ena = IOSM_AGGR_MUX_CMD_FLOW_CTL_ENABLE; |
| u32 fctl_ack = IOSM_AGGR_MUX_CMD_FLOW_CTL_ACK; |
| union mux_cmd_param *cmd_p = NULL; |
| u32 cmd = link_st; |
| u32 trans_id; |
| |
| if (!ipc_mux_dl_cmds_decode_process(ipc_mux, &cmdh->param, |
| cmdh->command_type, cmdh->if_id, |
| cmdh->cmd_len, size)) { |
| size = 0; |
| if (cmdh->command_type == cpu_to_le32(link_st)) { |
| cmd_p = &cmdh->param; |
| cmd_p->link_status_resp.response = MUX_CMD_RESP_SUCCESS; |
| } else if ((cmdh->command_type == cpu_to_le32(fctl_ena)) || |
| (cmdh->command_type == cpu_to_le32(fctl_dis))) { |
| cmd = fctl_ack; |
| } else { |
| return; |
| } |
| trans_id = le32_to_cpu(cmdh->transaction_id); |
| ipc_mux_dl_acb_send_cmds(ipc_mux, cmd, cmdh->if_id, |
| trans_id, cmd_p, size, false, true); |
| } |
| } |
| |
| /* Decode an aggregated command block. */ |
| static void ipc_mux_dl_acb_decode(struct iosm_mux *ipc_mux, struct sk_buff *skb) |
| { |
| struct mux_acbh *acbh; |
| struct mux_cmdh *cmdh; |
| u32 next_cmd_index; |
| u8 *block; |
| int size; |
| |
| acbh = (struct mux_acbh *)(skb->data); |
| block = (u8 *)(skb->data); |
| |
| next_cmd_index = le32_to_cpu(acbh->first_cmd_index); |
| next_cmd_index = array_index_nospec(next_cmd_index, |
| sizeof(struct mux_cmdh)); |
| |
| while (next_cmd_index != 0) { |
| cmdh = (struct mux_cmdh *)&block[next_cmd_index]; |
| next_cmd_index = le32_to_cpu(cmdh->next_cmd_index); |
| if (ipc_mux_dl_cmdresps_decode_process(ipc_mux, cmdh->param, |
| cmdh->command_type, |
| cmdh->if_id, |
| cmdh->transaction_id)) { |
| size = offsetof(struct mux_cmdh, param) + |
| sizeof(cmdh->param.flow_ctl); |
| ipc_mux_dl_acbcmd_decode(ipc_mux, cmdh, size); |
| } |
| } |
| } |
| |
| /* process datagram */ |
| static int mux_dl_process_dg(struct iosm_mux *ipc_mux, struct mux_adbh *adbh, |
| struct mux_adth_dg *dg, struct sk_buff *skb, |
| int if_id, int nr_of_dg) |
| { |
| u32 dl_head_pad_len = ipc_mux->session[if_id].dl_head_pad_len; |
| u32 packet_offset, i, rc, dg_len; |
| |
| for (i = 0; i < nr_of_dg; i++, dg++) { |
| if (le32_to_cpu(dg->datagram_index) |
| < sizeof(struct mux_adbh)) |
| goto dg_error; |
| |
| /* Is the packet inside of the ADB */ |
| if (le32_to_cpu(dg->datagram_index) >= |
| le32_to_cpu(adbh->block_length)) { |
| goto dg_error; |
| } else { |
| packet_offset = |
| le32_to_cpu(dg->datagram_index) + |
| dl_head_pad_len; |
| dg_len = le16_to_cpu(dg->datagram_length); |
| /* Pass the packet to the netif layer. */ |
| rc = ipc_mux_net_receive(ipc_mux, if_id, ipc_mux->wwan, |
| packet_offset, |
| dg->service_class, skb, |
| dg_len - dl_head_pad_len); |
| if (rc) |
| goto dg_error; |
| } |
| } |
| return 0; |
| dg_error: |
| return -1; |
| } |
| |
| /* Decode an aggregated data block. */ |
| static void mux_dl_adb_decode(struct iosm_mux *ipc_mux, |
| struct sk_buff *skb) |
| { |
| struct mux_adth_dg *dg; |
| struct iosm_wwan *wwan; |
| struct mux_adbh *adbh; |
| struct mux_adth *adth; |
| int nr_of_dg, if_id; |
| u32 adth_index; |
| u8 *block; |
| |
| block = skb->data; |
| adbh = (struct mux_adbh *)block; |
| |
| /* Process the aggregated datagram tables. */ |
| adth_index = le32_to_cpu(adbh->first_table_index); |
| |
| /* Has CP sent an empty ADB ? */ |
| if (adth_index < 1) { |
| dev_err(ipc_mux->dev, "unexpected empty ADB"); |
| goto adb_decode_err; |
| } |
| |
| /* Loop through mixed session tables. */ |
| while (adth_index) { |
| /* Get the reference to the table header. */ |
| adth = (struct mux_adth *)(block + adth_index); |
| |
| /* Get the interface id and map it to the netif id. */ |
| if_id = adth->if_id; |
| if (if_id >= IPC_MEM_MUX_IP_SESSION_ENTRIES) |
| goto adb_decode_err; |
| |
| if_id = array_index_nospec(if_id, |
| IPC_MEM_MUX_IP_SESSION_ENTRIES); |
| |
| /* Is the session active ? */ |
| wwan = ipc_mux->session[if_id].wwan; |
| if (!wwan) |
| goto adb_decode_err; |
| |
| /* Consistency checks for aggregated datagram table. */ |
| if (adth->signature != cpu_to_le32(IOSM_AGGR_MUX_SIG_ADTH)) |
| goto adb_decode_err; |
| |
| if (le16_to_cpu(adth->table_length) < sizeof(struct mux_adth)) |
| goto adb_decode_err; |
| |
| /* Calculate the number of datagrams. */ |
| nr_of_dg = (le16_to_cpu(adth->table_length) - |
| sizeof(struct mux_adth)) / |
| sizeof(struct mux_adth_dg); |
| |
| /* Is the datagram table empty ? */ |
| if (nr_of_dg < 1) { |
| dev_err(ipc_mux->dev, |
| "adthidx=%u,nr_of_dg=%d,next_tblidx=%u", |
| adth_index, nr_of_dg, |
| le32_to_cpu(adth->next_table_index)); |
| |
| /* Move to the next aggregated datagram table. */ |
| adth_index = le32_to_cpu(adth->next_table_index); |
| continue; |
| } |
| |
| /* New aggregated datagram table. */ |
| dg = adth->dg; |
| if (mux_dl_process_dg(ipc_mux, adbh, dg, skb, if_id, |
| nr_of_dg) < 0) |
| goto adb_decode_err; |
| |
| /* mark session for final flush */ |
| ipc_mux->session[if_id].flush = 1; |
| |
| /* Move to the next aggregated datagram table. */ |
| adth_index = le32_to_cpu(adth->next_table_index); |
| } |
| |
| adb_decode_err: |
| return; |
| } |
| |
| /** |
| * 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) |
| { |
| u32 signature; |
| |
| if (!skb->data) |
| return; |
| |
| /* Decode the MUX header type. */ |
| signature = le32_to_cpup((__le32 *)skb->data); |
| |
| switch (signature) { |
| case IOSM_AGGR_MUX_SIG_ADBH: /* Aggregated Data Block Header */ |
| mux_dl_adb_decode(ipc_mux, skb); |
| break; |
| case IOSM_AGGR_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 IOSM_AGGR_MUX_SIG_ACBH: /* Aggregated Command Block Header */ |
| ipc_mux_dl_acb_decode(ipc_mux, skb); |
| 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); |
| u32 no_if = IPC_MEM_MUX_IP_SESSION_ENTRIES; |
| u32 *next_tb_id; |
| int qlt_size; |
| u32 if_id; |
| |
| 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 IOSM_AGGR_MUX_SIG_ADBH: |
| /* Save the ADB memory settings. */ |
| ul_adb->dest_skb = skb; |
| ul_adb->buf = skb->data; |
| ul_adb->size = IPC_MEM_MAX_ADB_BUF_SIZE; |
| |
| /* reset statistic counter */ |
| ul_adb->if_cnt = 0; |
| ul_adb->payload_size = 0; |
| ul_adb->dg_cnt_total = 0; |
| |
| /* Initialize the ADBH. */ |
| ul_adb->adbh = (struct mux_adbh *)ul_adb->buf; |
| memset(ul_adb->adbh, 0, sizeof(struct mux_adbh)); |
| ul_adb->adbh->signature = cpu_to_le32(IOSM_AGGR_MUX_SIG_ADBH); |
| ul_adb->adbh->block_length = |
| cpu_to_le32(sizeof(struct mux_adbh)); |
| next_tb_id = (unsigned int *)&ul_adb->adbh->first_table_index; |
| ul_adb->next_table_index = next_tb_id; |
| |
| /* Clear the local copy of DGs for new ADB */ |
| memset(ul_adb->dg, 0, sizeof(ul_adb->dg)); |
| |
| /* Clear the DG count and QLT updated status for new ADB */ |
| for (if_id = 0; if_id < no_if; if_id++) { |
| ul_adb->dg_count[if_id] = 0; |
| ul_adb->qlt_updated[if_id] = 0; |
| } |
| break; |
| |
| case IOSM_AGGR_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->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); |
| } |
| |
| static void ipc_mux_ul_encode_adth(struct iosm_mux *ipc_mux, |
| struct mux_adb *ul_adb, int *out_offset) |
| { |
| int i, qlt_size, offset = *out_offset; |
| struct mux_qlth *p_adb_qlt; |
| struct mux_adth_dg *dg; |
| struct mux_adth *adth; |
| u16 adth_dg_size; |
| u32 *next_tb_id; |
| |
| qlt_size = offsetof(struct mux_qlth, ql) + |
| MUX_QUEUE_LEVEL * sizeof(struct mux_qlth_ql); |
| |
| for (i = 0; i < ipc_mux->nr_sessions; i++) { |
| if (ul_adb->dg_count[i] > 0) { |
| adth_dg_size = offsetof(struct mux_adth, dg) + |
| ul_adb->dg_count[i] * sizeof(*dg); |
| |
| *ul_adb->next_table_index = offset; |
| adth = (struct mux_adth *)&ul_adb->buf[offset]; |
| next_tb_id = (unsigned int *)&adth->next_table_index; |
| ul_adb->next_table_index = next_tb_id; |
| offset += adth_dg_size; |
| adth->signature = cpu_to_le32(IOSM_AGGR_MUX_SIG_ADTH); |
| adth->if_id = i; |
| adth->table_length = cpu_to_le16(adth_dg_size); |
| adth_dg_size -= offsetof(struct mux_adth, dg); |
| memcpy(adth->dg, ul_adb->dg[i], adth_dg_size); |
| ul_adb->if_cnt++; |
| } |
| |
| if (ul_adb->qlt_updated[i]) { |
| *ul_adb->next_table_index = offset; |
| p_adb_qlt = (struct mux_qlth *)&ul_adb->buf[offset]; |
| ul_adb->next_table_index = |
| (u32 *)&p_adb_qlt->next_table_index; |
| memcpy(p_adb_qlt, ul_adb->pp_qlt[i], qlt_size); |
| offset += qlt_size; |
| } |
| } |
| *out_offset = offset; |
| } |
| |
| /** |
| * ipc_mux_ul_adb_finish - Add the TD of the aggregated session packets to TDR. |
| * @ipc_mux: Pointer to MUX data-struct. |
| */ |
| void ipc_mux_ul_adb_finish(struct iosm_mux *ipc_mux) |
| { |
| bool ul_data_pend = false; |
| struct mux_adb *ul_adb; |
| unsigned long flags; |
| int offset; |
| |
| ul_adb = &ipc_mux->ul_adb; |
| if (!ul_adb->dest_skb) |
| return; |
| |
| offset = *ul_adb->next_table_index; |
| ipc_mux_ul_encode_adth(ipc_mux, ul_adb, &offset); |
| ul_adb->adbh->block_length = cpu_to_le32(offset); |
| |
| if (le32_to_cpu(ul_adb->adbh->block_length) > ul_adb->size) { |
| ul_adb->dest_skb = NULL; |
| return; |
| } |
| |
| *ul_adb->next_table_index = 0; |
| ul_adb->adbh->sequence_nr = cpu_to_le16(ipc_mux->adb_tx_sequence_nr++); |
| skb_put(ul_adb->dest_skb, le32_to_cpu(ul_adb->adbh->block_length)); |
| |
| spin_lock_irqsave(&(&ipc_mux->channel->ul_list)->lock, flags); |
| __skb_queue_tail(&ipc_mux->channel->ul_list, ul_adb->dest_skb); |
| spin_unlock_irqrestore(&(&ipc_mux->channel->ul_list)->lock, flags); |
| |
| ul_adb->dest_skb = NULL; |
| /* Updates the TDs with ul_list */ |
| ul_data_pend = ipc_imem_ul_write_td(ipc_mux->imem); |
| |
| /* Delay the doorbell irq */ |
| if (ul_data_pend) |
| ipc_imem_td_update_timer_start(ipc_mux->imem); |
| |
| ipc_mux->acc_adb_size += le32_to_cpu(ul_adb->adbh->block_length); |
| ipc_mux->acc_payload_size += ul_adb->payload_size; |
| ipc_mux->ul_data_pend_bytes += ul_adb->payload_size; |
| } |
| |
| /* 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_MEM_MUX_IP_SESSION_ENTRIES; 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_MEM_MUX_IP_SESSION_ENTRIES; 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 = cpu_to_le32(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, |
| IOSM_AGGR_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(IOSM_AGGR_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; |
| } |
| |
| /** |
| * ipc_mux_ul_adb_update_ql - Adds Queue Level Table and Queue Level to ADB |
| * @ipc_mux: pointer to MUX instance data |
| * @p_adb: pointer to UL aggegated data block |
| * @session_id: session id |
| * @qlth_n_ql_size: Length (in bytes) of the datagram table |
| * @ul_list: pointer to skb buffer head |
| */ |
| void ipc_mux_ul_adb_update_ql(struct iosm_mux *ipc_mux, struct mux_adb *p_adb, |
| int session_id, int qlth_n_ql_size, |
| struct sk_buff_head *ul_list) |
| { |
| int qlevel = ul_list->qlen; |
| struct mux_qlth *p_qlt; |
| |
| p_qlt = (struct mux_qlth *)p_adb->pp_qlt[session_id]; |
| |
| /* Initialize QLTH if not been done */ |
| if (p_adb->qlt_updated[session_id] == 0) { |
| p_qlt->signature = cpu_to_le32(MUX_SIG_QLTH); |
| p_qlt->if_id = session_id; |
| p_qlt->table_length = cpu_to_le16(qlth_n_ql_size); |
| p_qlt->reserved = 0; |
| p_qlt->reserved2 = 0; |
| } |
| |
| /* Update Queue Level information always */ |
| p_qlt->ql.nr_of_bytes = cpu_to_le32(qlevel); |
| p_adb->qlt_updated[session_id] = 1; |
| } |
| |
| /* Update the next table index. */ |
| static int mux_ul_dg_update_tbl_index(struct iosm_mux *ipc_mux, |
| int session_id, |
| struct sk_buff_head *ul_list, |
| struct mux_adth_dg *dg, |
| int aligned_size, |
| u32 qlth_n_ql_size, |
| struct mux_adb *adb, |
| struct sk_buff *src_skb) |
| { |
| ipc_mux_ul_adb_update_ql(ipc_mux, adb, session_id, |
| qlth_n_ql_size, ul_list); |
| ipc_mux_ul_adb_finish(ipc_mux); |
| if (ipc_mux_ul_adb_allocate(ipc_mux, adb, &ipc_mux->size_needed, |
| IOSM_AGGR_MUX_SIG_ADBH)) |
| return -ENOMEM; |
| |
| ipc_mux->size_needed = le32_to_cpu(adb->adbh->block_length); |
| |
| ipc_mux->size_needed += offsetof(struct mux_adth, dg); |
| ipc_mux->size_needed += qlth_n_ql_size; |
| ipc_mux->size_needed += sizeof(*dg) + aligned_size; |
| return 0; |
| } |
| |
| /* Process encode session UL data. */ |
| static int mux_ul_dg_encode(struct iosm_mux *ipc_mux, struct mux_adb *adb, |
| struct mux_adth_dg *dg, |
| struct sk_buff_head *ul_list, |
| struct sk_buff *src_skb, int session_id, |
| int pkt_to_send, u32 qlth_n_ql_size, |
| int *out_offset, int head_pad_len) |
| { |
| int aligned_size; |
| int offset = *out_offset; |
| unsigned long flags; |
| int nr_of_skb = 0; |
| |
| while (pkt_to_send > 0) { |
| /* 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", |
| pkt_to_send); |
| return -1; |
| } |
| aligned_size = ALIGN((head_pad_len + src_skb->len), 4); |
| ipc_mux->size_needed += sizeof(*dg) + aligned_size; |
| |
| if (ipc_mux->size_needed > adb->size || |
| ((ipc_mux->size_needed + ipc_mux->ul_data_pend_bytes) >= |
| IPC_MEM_MUX_UL_FLOWCTRL_HIGH_B)) { |
| *adb->next_table_index = offset; |
| if (mux_ul_dg_update_tbl_index(ipc_mux, session_id, |
| ul_list, dg, |
| aligned_size, |
| qlth_n_ql_size, adb, |
| src_skb) < 0) |
| return -ENOMEM; |
| nr_of_skb = 0; |
| offset = le32_to_cpu(adb->adbh->block_length); |
| /* Load pointer to next available datagram entry */ |
| dg = adb->dg[session_id] + adb->dg_count[session_id]; |
| } |
| /* Add buffer without head padding to next pending transfer. */ |
| memcpy(adb->buf + offset + head_pad_len, |
| src_skb->data, src_skb->len); |
| /* Setup datagram entry. */ |
| dg->datagram_index = cpu_to_le32(offset); |
| dg->datagram_length = cpu_to_le16(src_skb->len + head_pad_len); |
| dg->service_class = (((struct sk_buff *)src_skb)->priority); |
| dg->reserved = 0; |
| adb->dg_cnt_total++; |
| adb->payload_size += le16_to_cpu(dg->datagram_length); |
| dg++; |
| adb->dg_count[session_id]++; |
| offset += aligned_size; |
| /* Remove the processed elements and free it. */ |
| spin_lock_irqsave(&ul_list->lock, flags); |
| src_skb = __skb_dequeue(ul_list); |
| spin_unlock_irqrestore(&ul_list->lock, flags); |
| |
| dev_kfree_skb(src_skb); |
| nr_of_skb++; |
| pkt_to_send--; |
| } |
| *out_offset = offset; |
| return nr_of_skb; |
| } |
| |
| /* Process encode session UL data to ADB. */ |
| static int mux_ul_adb_encode(struct iosm_mux *ipc_mux, int session_id, |
| struct mux_session *session, |
| struct sk_buff_head *ul_list, struct mux_adb *adb, |
| int pkt_to_send) |
| { |
| int adb_updated = -EINVAL; |
| int head_pad_len, offset; |
| struct sk_buff *src_skb = NULL; |
| struct mux_adth_dg *dg; |
| u32 qlth_n_ql_size; |
| |
| /* If any of the opened session has set Flow Control ON then limit the |
| * UL data to mux_flow_ctrl_high_thresh_b bytes |
| */ |
| if (ipc_mux->ul_data_pend_bytes >= |
| IPC_MEM_MUX_UL_FLOWCTRL_HIGH_B) { |
| ipc_mux_stop_tx_for_all_sessions(ipc_mux); |
| return adb_updated; |
| } |
| |
| qlth_n_ql_size = offsetof(struct mux_qlth, ql) + |
| MUX_QUEUE_LEVEL * sizeof(struct mux_qlth_ql); |
| head_pad_len = session->ul_head_pad_len; |
| |
| if (session->ul_head_pad_len > IPC_MEM_DL_ETH_OFFSET) |
| head_pad_len = session->ul_head_pad_len - IPC_MEM_DL_ETH_OFFSET; |
| |
| if (ipc_mux_ul_adb_allocate(ipc_mux, adb, &ipc_mux->size_needed, |
| IOSM_AGGR_MUX_SIG_ADBH)) |
| return -ENOMEM; |
| |
| offset = le32_to_cpu(adb->adbh->block_length); |
| |
| if (ipc_mux->size_needed == 0) |
| ipc_mux->size_needed = offset; |
| |
| /* Calculate the size needed for ADTH, QLTH and QL*/ |
| if (adb->dg_count[session_id] == 0) { |
| ipc_mux->size_needed += offsetof(struct mux_adth, dg); |
| ipc_mux->size_needed += qlth_n_ql_size; |
| } |
| |
| dg = adb->dg[session_id] + adb->dg_count[session_id]; |
| |
| if (mux_ul_dg_encode(ipc_mux, adb, dg, ul_list, src_skb, |
| session_id, pkt_to_send, qlth_n_ql_size, &offset, |
| head_pad_len) > 0) { |
| adb_updated = 1; |
| *adb->next_table_index = offset; |
| ipc_mux_ul_adb_update_ql(ipc_mux, adb, session_id, |
| qlth_n_ql_size, ul_list); |
| adb->adbh->block_length = cpu_to_le32(offset); |
| } |
| |
| 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_MEM_MUX_IP_SESSION_ENTRIES; 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_MEM_MUX_IP_SESSION_ENTRIES) |
| 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; |
| if (ipc_mux->protocol == MUX_LITE) |
| updated = ipc_mux_ul_adgh_encode(ipc_mux, session_id, |
| session, ul_list, |
| &ipc_mux->ul_adb, |
| dg_n); |
| else |
| updated = mux_ul_adb_encode(ipc_mux, session_id, |
| session, ul_list, |
| &ipc_mux->ul_adb, |
| dg_n); |
| } |
| |
| ipc_mux->adb_prep_ongoing = false; |
| return updated == 1; |
| } |
| |
| /* Calculates the Payload from any given ADB. */ |
| static int ipc_mux_get_payload_from_adb(struct iosm_mux *ipc_mux, |
| struct mux_adbh *p_adbh) |
| { |
| struct mux_adth_dg *dg; |
| struct mux_adth *adth; |
| u32 payload_size = 0; |
| u32 next_table_idx; |
| int nr_of_dg, i; |
| |
| /* Process the aggregated datagram tables. */ |
| next_table_idx = le32_to_cpu(p_adbh->first_table_index); |
| |
| if (next_table_idx < sizeof(struct mux_adbh)) { |
| dev_err(ipc_mux->dev, "unexpected empty ADB"); |
| return payload_size; |
| } |
| |
| while (next_table_idx != 0) { |
| /* Get the reference to the table header. */ |
| adth = (struct mux_adth *)((u8 *)p_adbh + next_table_idx); |
| |
| if (adth->signature == cpu_to_le32(IOSM_AGGR_MUX_SIG_ADTH)) { |
| nr_of_dg = (le16_to_cpu(adth->table_length) - |
| sizeof(struct mux_adth)) / |
| sizeof(struct mux_adth_dg); |
| |
| if (nr_of_dg <= 0) |
| return payload_size; |
| |
| dg = adth->dg; |
| |
| for (i = 0; i < nr_of_dg; i++, dg++) { |
| if (le32_to_cpu(dg->datagram_index) < |
| sizeof(struct mux_adbh)) { |
| return payload_size; |
| } |
| payload_size += |
| le16_to_cpu(dg->datagram_length); |
| } |
| } |
| next_table_idx = le32_to_cpu(adth->next_table_index); |
| } |
| |
| return payload_size; |
| } |
| |
| void ipc_mux_ul_encoded_process(struct iosm_mux *ipc_mux, struct sk_buff *skb) |
| { |
| union mux_type_header hr; |
| u16 adgh_len; |
| int payload; |
| |
| if (ipc_mux->protocol == MUX_LITE) { |
| hr.adgh = (struct mux_adgh *)skb->data; |
| adgh_len = le16_to_cpu(hr.adgh->length); |
| if (hr.adgh->signature == cpu_to_le32(IOSM_AGGR_MUX_SIG_ADGH) && |
| ipc_mux->ul_flow == MUX_UL) |
| ipc_mux->ul_data_pend_bytes = |
| ipc_mux->ul_data_pend_bytes - adgh_len; |
| } else { |
| hr.adbh = (struct mux_adbh *)(skb->data); |
| payload = ipc_mux_get_payload_from_adb(ipc_mux, hr.adbh); |
| ipc_mux->ul_data_pend_bytes -= payload; |
| } |
| |
| 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_trim(skb, 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) { |
| if (ipc_mux->protocol == MUX_AGGREGATION) |
| ipc_imem_adb_timer_start(ipc_mux->imem); |
| |
| /* 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; |
| } |