diff options
author | 2022-02-10 21:04:45 +0530 | |
---|---|---|
committer | 2022-02-13 11:55:28 +0000 | |
commit | 1f52d7b622854b8bd7a1be3de095ca2e1f77098e (patch) | |
tree | ac8432ad3174813c258564f15b7a47f9134de6bc /drivers/net | |
parent | Revert "net: ethernet: cavium: use div64_u64() instead of do_div()" (diff) | |
download | linux-1f52d7b62285.tar.gz linux-1f52d7b62285.tar.bz2 linux-1f52d7b62285.zip |
net: wwan: iosm: Enable M.2 7360 WWAN card support
This patch enables Intel M.2 7360 WWAN card support on
IOSM Driver.
Control path implementation is a reuse whereas data path
implementation it uses a different protocol called as MUX
Aggregation. The major portion of this patch covers the MUX
Aggregation protocol implementation used for IP traffic
communication.
For M.2 7360 WWAN card, driver exposes 2 wwan AT ports for
control communication. The user space application or the
modem manager to use wwan AT port for data path establishment.
During probe, driver reads the mux protocol device capability
register to know the mux protocol version supported by device.
Base on which the right mux protocol is initialized for data
path communication.
An overview of an Aggregation Protocol
1> An IP packet is encapsulated with 16 octet padding header
to form a Datagram & the start offset of the Datagram is
indexed into Datagram Header (DH).
2> Multiple such Datagrams are composed & the start offset of
each DH is indexed into Datagram Table Header (DTH).
3> The Datagram Table (DT) is IP session specific & table_length
item in DTH holds the number of composed datagram pertaining
to that particular IP session.
4> And finally the offset of first DTH is indexed into DBH (Datagram
Block Header).
So in TX/RX flow Datagram Block (Datagram Block Header + Payload)is
exchanged between driver & device.
Signed-off-by: M Chetan Kumar <m.chetan.kumar@linux.intel.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'drivers/net')
-rw-r--r-- | drivers/net/wwan/iosm/iosm_ipc_imem.c | 54 | ||||
-rw-r--r-- | drivers/net/wwan/iosm/iosm_ipc_imem.h | 5 | ||||
-rw-r--r-- | drivers/net/wwan/iosm/iosm_ipc_mmio.c | 6 | ||||
-rw-r--r-- | drivers/net/wwan/iosm/iosm_ipc_mmio.h | 6 | ||||
-rw-r--r-- | drivers/net/wwan/iosm/iosm_ipc_mux.c | 21 | ||||
-rw-r--r-- | drivers/net/wwan/iosm/iosm_ipc_mux.h | 133 | ||||
-rw-r--r-- | drivers/net/wwan/iosm/iosm_ipc_mux_codec.c | 742 | ||||
-rw-r--r-- | drivers/net/wwan/iosm/iosm_ipc_mux_codec.h | 142 | ||||
-rw-r--r-- | drivers/net/wwan/iosm/iosm_ipc_pcie.c | 1 | ||||
-rw-r--r-- | drivers/net/wwan/iosm/iosm_ipc_pcie.h | 1 |
10 files changed, 1033 insertions, 78 deletions
diff --git a/drivers/net/wwan/iosm/iosm_ipc_imem.c b/drivers/net/wwan/iosm/iosm_ipc_imem.c index f9e8e0ee4de3..1e6a47976642 100644 --- a/drivers/net/wwan/iosm/iosm_ipc_imem.c +++ b/drivers/net/wwan/iosm/iosm_ipc_imem.c @@ -114,17 +114,35 @@ ipc_imem_fast_update_timer_cb(struct hrtimer *hr_timer) return HRTIMER_NORESTART; } +static int ipc_imem_tq_adb_timer_cb(struct iosm_imem *ipc_imem, int arg, + void *msg, size_t size) +{ + ipc_mux_ul_adb_finish(ipc_imem->mux); + return 0; +} + +static enum hrtimer_restart +ipc_imem_adb_timer_cb(struct hrtimer *hr_timer) +{ + struct iosm_imem *ipc_imem = + container_of(hr_timer, struct iosm_imem, adb_timer); + + ipc_task_queue_send_task(ipc_imem, ipc_imem_tq_adb_timer_cb, 0, + NULL, 0, false); + return HRTIMER_NORESTART; +} + static int ipc_imem_setup_cp_mux_cap_init(struct iosm_imem *ipc_imem, struct ipc_mux_config *cfg) { ipc_mmio_update_cp_capability(ipc_imem->mmio); - if (!ipc_imem->mmio->has_mux_lite) { + if (ipc_imem->mmio->mux_protocol == MUX_UNKNOWN) { dev_err(ipc_imem->dev, "Failed to get Mux capability."); return -EINVAL; } - cfg->protocol = MUX_LITE; + cfg->protocol = ipc_imem->mmio->mux_protocol; cfg->ul_flow = (ipc_imem->mmio->has_ul_flow_credit == 1) ? MUX_UL_ON_CREDITS : @@ -153,6 +171,10 @@ void ipc_imem_msg_send_feature_set(struct iosm_imem *ipc_imem, IPC_MSG_PREP_FEATURE_SET, &prep_args); } +/** + * ipc_imem_td_update_timer_start - Starts the TD Update Timer if not started. + * @ipc_imem: Pointer to imem data-struct + */ void ipc_imem_td_update_timer_start(struct iosm_imem *ipc_imem) { /* Use the TD update timer only in the runtime phase */ @@ -179,6 +201,21 @@ void ipc_imem_hrtimer_stop(struct hrtimer *hr_timer) hrtimer_cancel(hr_timer); } +/** + * ipc_imem_adb_timer_start - Starts the adb Timer if not starting. + * @ipc_imem: Pointer to imem data-struct + */ +void ipc_imem_adb_timer_start(struct iosm_imem *ipc_imem) +{ + if (!hrtimer_active(&ipc_imem->adb_timer)) { + ipc_imem->hrtimer_period = + ktime_set(0, IOSM_AGGR_MUX_ADB_FINISH_TIMEOUT_NSEC); + hrtimer_start(&ipc_imem->adb_timer, + ipc_imem->hrtimer_period, + HRTIMER_MODE_REL); + } +} + bool ipc_imem_ul_write_td(struct iosm_imem *ipc_imem) { struct ipc_mem_channel *channel; @@ -550,6 +587,11 @@ static void ipc_imem_run_state_worker(struct work_struct *instance) while (ctrl_chl_idx < IPC_MEM_MAX_CHANNELS) { if (!ipc_chnl_cfg_get(&chnl_cfg_port, ctrl_chl_idx)) { ipc_imem->ipc_port[ctrl_chl_idx] = NULL; + if (ipc_imem->pcie->pci->device == INTEL_CP_DEVICE_7360_ID && + chnl_cfg_port.wwan_port_type == WWAN_PORT_MBIM) { + ctrl_chl_idx++; + continue; + } if (chnl_cfg_port.wwan_port_type != WWAN_PORT_UNKNOWN) { ipc_imem_channel_init(ipc_imem, IPC_CTYPE_CTRL, chnl_cfg_port, @@ -680,8 +722,11 @@ static void ipc_imem_handle_irq(struct iosm_imem *ipc_imem, int irq) } /* Try to generate new ADB or ADGH. */ - if (ipc_mux_ul_data_encode(ipc_imem->mux)) + if (ipc_mux_ul_data_encode(ipc_imem->mux)) { ipc_imem_td_update_timer_start(ipc_imem); + if (ipc_imem->mux->protocol == MUX_AGGREGATION) + ipc_imem_adb_timer_start(ipc_imem); + } /* Continue the send procedure with accumulated SIO or NETIF packets. * Reset the debounce flags. @@ -1330,6 +1375,9 @@ struct iosm_imem *ipc_imem_init(struct iosm_pcie *pcie, unsigned int device_id, HRTIMER_MODE_REL); ipc_imem->td_alloc_timer.function = ipc_imem_td_alloc_timer_cb; + hrtimer_init(&ipc_imem->adb_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + ipc_imem->adb_timer.function = ipc_imem_adb_timer_cb; + if (ipc_imem_config(ipc_imem)) { dev_err(ipc_imem->dev, "failed to initialize the imem"); goto imem_config_fail; diff --git a/drivers/net/wwan/iosm/iosm_ipc_imem.h b/drivers/net/wwan/iosm/iosm_ipc_imem.h index 98554e9beb01..5682e8d6be7b 100644 --- a/drivers/net/wwan/iosm/iosm_ipc_imem.h +++ b/drivers/net/wwan/iosm/iosm_ipc_imem.h @@ -317,6 +317,7 @@ enum ipc_phase { * @tdupdate_timer: Delay the TD update doorbell. * @fast_update_timer: forced head pointer update delay timer. * @td_alloc_timer: Timer for DL pipe TD allocation retry + * @adb_timer: Timer for finishing the ADB. * @rom_exit_code: Mapped boot rom exit code. * @enter_runtime: 1 means the transition to runtime phase was * executed. @@ -364,6 +365,7 @@ struct iosm_imem { struct hrtimer tdupdate_timer; struct hrtimer fast_update_timer; struct hrtimer td_alloc_timer; + struct hrtimer adb_timer; enum rom_exit_code rom_exit_code; u32 enter_runtime; struct completion ul_pend_sem; @@ -593,4 +595,7 @@ void ipc_imem_channel_init(struct iosm_imem *ipc_imem, enum ipc_ctype ctype, * Returns: 0 on success, -1 on failure */ int ipc_imem_devlink_trigger_chip_info(struct iosm_imem *ipc_imem); + +void ipc_imem_adb_timer_start(struct iosm_imem *ipc_imem); + #endif diff --git a/drivers/net/wwan/iosm/iosm_ipc_mmio.c b/drivers/net/wwan/iosm/iosm_ipc_mmio.c index f09e5e77a2a5..63eb08c43c05 100644 --- a/drivers/net/wwan/iosm/iosm_ipc_mmio.c +++ b/drivers/net/wwan/iosm/iosm_ipc_mmio.c @@ -10,6 +10,7 @@ #include <linux/slab.h> #include "iosm_ipc_mmio.h" +#include "iosm_ipc_mux.h" /* Definition of MMIO offsets * note that MMIO_CI offsets are relative to end of chip info structure @@ -71,8 +72,9 @@ void ipc_mmio_update_cp_capability(struct iosm_mmio *ipc_mmio) ver = ipc_mmio_get_cp_version(ipc_mmio); cp_cap = ioread32(ipc_mmio->base + ipc_mmio->offset.cp_capability); - ipc_mmio->has_mux_lite = (ver >= IOSM_CP_VERSION) && - !(cp_cap & DL_AGGR) && !(cp_cap & UL_AGGR); + ipc_mmio->mux_protocol = ((ver >= IOSM_CP_VERSION) && (cp_cap & + (UL_AGGR | DL_AGGR))) ? MUX_AGGREGATION + : MUX_LITE; ipc_mmio->has_ul_flow_credit = (ver >= IOSM_CP_VERSION) && (cp_cap & UL_FLOW_CREDIT); diff --git a/drivers/net/wwan/iosm/iosm_ipc_mmio.h b/drivers/net/wwan/iosm/iosm_ipc_mmio.h index f861994a6d90..193d7ba2478a 100644 --- a/drivers/net/wwan/iosm/iosm_ipc_mmio.h +++ b/drivers/net/wwan/iosm/iosm_ipc_mmio.h @@ -72,7 +72,7 @@ struct mmio_offset { * @context_info_addr: Physical base address of context info structure * @chip_info_version: Version of chip info structure * @chip_info_size: Size of chip info structure - * @has_mux_lite: It doesn't support mux aggergation + * @mux_protocol: mux protocol * @has_ul_flow_credit: Ul flow credit support * @has_slp_no_prot: Device sleep no protocol support * @has_mcr_support: Usage of mcr support @@ -84,8 +84,8 @@ struct iosm_mmio { phys_addr_t context_info_addr; unsigned int chip_info_version; unsigned int chip_info_size; - u8 has_mux_lite:1, - has_ul_flow_credit:1, + u32 mux_protocol; + u8 has_ul_flow_credit:1, has_slp_no_prot:1, has_mcr_support:1; }; diff --git a/drivers/net/wwan/iosm/iosm_ipc_mux.c b/drivers/net/wwan/iosm/iosm_ipc_mux.c index 8e66ffe92055..9c7a9a2a1f25 100644 --- a/drivers/net/wwan/iosm/iosm_ipc_mux.c +++ b/drivers/net/wwan/iosm/iosm_ipc_mux.c @@ -279,9 +279,10 @@ struct iosm_mux *ipc_mux_init(struct ipc_mux_config *mux_cfg, struct iosm_imem *imem) { struct iosm_mux *ipc_mux = kzalloc(sizeof(*ipc_mux), GFP_KERNEL); - int i, ul_tds, ul_td_size; + int i, j, ul_tds, ul_td_size; struct sk_buff_head *free_list; struct sk_buff *skb; + int qlt_size; if (!ipc_mux) return NULL; @@ -321,6 +322,24 @@ struct iosm_mux *ipc_mux_init(struct ipc_mux_config *mux_cfg, ipc_mux->channel_id = -1; ipc_mux->channel = NULL; + if (ipc_mux->protocol != MUX_LITE) { + qlt_size = offsetof(struct mux_qlth, ql) + + MUX_QUEUE_LEVEL * sizeof(struct mux_qlth_ql); + + for (i = 0; i < IPC_MEM_MUX_IP_SESSION_ENTRIES; i++) { + ipc_mux->ul_adb.pp_qlt[i] = kzalloc(qlt_size, + GFP_ATOMIC); + if (!ipc_mux->ul_adb.pp_qlt[i]) { + for (j = i - 1; j >= 0; j--) + kfree(ipc_mux->ul_adb.pp_qlt[j]); + return NULL; + } + } + + ul_td_size = IPC_MEM_MAX_UL_ADB_BUF_SIZE; + ul_tds = IPC_MEM_MAX_TDS_MUX_AGGR_UL; + } + /* Allocate the list of UL ADB. */ for (i = 0; i < ul_tds; i++) { dma_addr_t mapping; diff --git a/drivers/net/wwan/iosm/iosm_ipc_mux.h b/drivers/net/wwan/iosm/iosm_ipc_mux.h index 88debaa1ed31..cd9d74cc097f 100644 --- a/drivers/net/wwan/iosm/iosm_ipc_mux.h +++ b/drivers/net/wwan/iosm/iosm_ipc_mux.h @@ -8,9 +8,12 @@ #include "iosm_ipc_protocol.h" -/* Size of the buffer for the IP MUX data buffer. */ -#define IPC_MEM_MAX_DL_MUX_BUF_SIZE (16 * 1024) -#define IPC_MEM_MAX_UL_ADB_BUF_SIZE IPC_MEM_MAX_DL_MUX_BUF_SIZE +#define IPC_MEM_MAX_UL_DG_ENTRIES 100 +#define IPC_MEM_MAX_TDS_MUX_AGGR_UL 60 + +#define IPC_MEM_MAX_ADB_BUF_SIZE (16 * 1024) +#define IPC_MEM_MAX_UL_ADB_BUF_SIZE IPC_MEM_MAX_ADB_BUF_SIZE +#define IPC_MEM_MAX_DL_ADB_BUF_SIZE IPC_MEM_MAX_ADB_BUF_SIZE /* Size of the buffer for the IP MUX Lite data buffer. */ #define IPC_MEM_MAX_DL_MUX_LITE_BUF_SIZE (2 * 1024) @@ -167,6 +170,7 @@ enum mux_state { enum ipc_mux_protocol { MUX_UNKNOWN, MUX_LITE, + MUX_AGGREGATION, }; /* Supported UL data transfer methods. */ @@ -192,24 +196,111 @@ struct mux_session { flush:1; /* flush net interface ? */ }; -/* State of a single UL data block. */ +/** + * struct mux_adth_dg - Structure of the datagram in the Aggregated Datagram + * Table Header. + * @datagram_index : Index (in bytes) to the k-th datagram in the table. + * Index shall count from the start of the block including + * the 16-byte header. This value shall be non-zero. + * @datagram_length: Length of the k-th datagram including the head padding. + * This value shall be non-zero. + * @service_class: Service class identifier for the datagram. + * @reserved: Reserved bytes. Set to zero + */ +struct mux_adth_dg { + __le32 datagram_index; + __le16 datagram_length; + u8 service_class; + u8 reserved; +}; + +/** + * struct mux_qlth_ql - Structure of the queue level in the Aggregated + * Datagram Queue Level Table Header. + * @nr_of_bytes: Number of bytes available to transmit in the queue. + */ +struct mux_qlth_ql { + __le32 nr_of_bytes; +}; + +/** + * struct mux_qlth - Structure of Aggregated Datagram Queue Level Table + * Header. + * @signature: Signature of the Queue Level Table Header + * Value: 0x48544C51 (ASCII characters: 'Q' 'L' 'T' 'H') + * @table_length: Length (in bytes) of the datagram table. This length + * shall include the queue level table header size. + * Minimum value:0x10 + * @if_id: ID of the interface the queue levels in the table + * belong to. + * @reserved: Reserved byte. Set to zero. + * @next_table_index: Index (in bytes) to the next table in the buffer. Index + * shall count from the start of the block including the + * 16-byte header. Value of zero indicates end of the list. + * @reserved2: Reserved bytes. Set to zero + * @ql: Queue level table with variable length + */ +struct mux_qlth { + __le32 signature; + __le16 table_length; + u8 if_id; + u8 reserved; + __le32 next_table_index; + __le32 reserved2; + struct mux_qlth_ql ql; +}; + +/** + * struct mux_adb - Structure of State of a single UL data block. + * @dest_skb: Current UL skb for the data block. + * @buf: ADB memory + * @adgh: ADGH pointer + * @qlth_skb: QLTH pointer + * @next_table_index: Pointer to next table index. + * @free_list: List of alloc. ADB for the UL sess. + * @size: Size of the ADB memory. + * @if_cnt: Statistic counter + * @dg_cnt_total: Datagram count total + * @payload_size: Payload Size + * @dg: Datagram table. + * @pp_qlt: Pointers to hold Queue Level Tables of session + * @adbh: ADBH pointer + * @qlt_updated: Queue level table updated + * @dg_count: Datagram count + */ struct mux_adb { - struct sk_buff *dest_skb; /* Current UL skb for the data block. */ - u8 *buf; /* ADB memory. */ - struct mux_adgh *adgh; /* ADGH pointer */ - struct sk_buff *qlth_skb; /* QLTH pointer */ - u32 *next_table_index; /* Pointer to next table index. */ - struct sk_buff_head free_list; /* List of alloc. ADB for the UL sess.*/ - int size; /* Size of the ADB memory. */ - u32 if_cnt; /* Statistic counter */ + struct sk_buff *dest_skb; + u8 *buf; + struct mux_adgh *adgh; + struct sk_buff *qlth_skb; + u32 *next_table_index; + struct sk_buff_head free_list; + int size; + u32 if_cnt; u32 dg_cnt_total; u32 payload_size; + struct mux_adth_dg + dg[IPC_MEM_MUX_IP_SESSION_ENTRIES][IPC_MEM_MAX_UL_DG_ENTRIES]; + struct mux_qlth *pp_qlt[IPC_MEM_MUX_IP_SESSION_ENTRIES]; + struct mux_adbh *adbh; + u32 qlt_updated[IPC_MEM_MUX_IP_SESSION_ENTRIES]; + u32 dg_count[IPC_MEM_MUX_IP_SESSION_ENTRIES]; }; -/* Temporary ACB state. */ +/** + * struct mux_acb - Structure of Temporary ACB state. + * @skb: Used UL skb. + * @if_id: Session id. + * @buf_p: Command buffer. + * @wanted_response: Wanted Response + * @got_response: Got response + * @cmd: command + * @got_param: Received command/response parameter + */ struct mux_acb { struct sk_buff *skb; /* Used UL skb. */ int if_id; /* Session id. */ + u8 *buf_p; u32 wanted_response; u32 got_response; u32 cmd; @@ -241,6 +332,12 @@ struct mux_acb { * @wwan_q_offset: This will hold the offset of the given instance * Useful while passing or receiving packets from * wwan/imem layer. + * @adb_finish_timer: Timer for forcefully finishing the ADB + * @acb_tx_sequence_nr: Sequence number for the ACB header. + * @params: user configurable parameters + * @adb_tx_sequence_nr: Sequence number for ADB header + * @acc_adb_size: Statistic data for logging + * @acc_payload_size: Statistic data for logging * @initialized: MUX object is initialized * @ev_mux_net_transmit_pending: * 0 means inform the IPC tasklet to pass the @@ -269,10 +366,16 @@ struct iosm_mux { long long ul_data_pend_bytes; struct mux_acb acb; int wwan_q_offset; + struct hrtimer adb_finish_timer; + u16 acb_tx_sequence_nr; + struct ipc_params *params; + u16 adb_tx_sequence_nr; + unsigned long long acc_adb_size; + unsigned long long acc_payload_size; u8 initialized:1, ev_mux_net_transmit_pending:1, - adb_prep_ongoing:1; -}; + adb_prep_ongoing; +} __packed; /* MUX configuration structure */ struct ipc_mux_config { diff --git a/drivers/net/wwan/iosm/iosm_ipc_mux_codec.c b/drivers/net/wwan/iosm/iosm_ipc_mux_codec.c index 40fb54a0513e..d41e373f9c0a 100644 --- a/drivers/net/wwan/iosm/iosm_ipc_mux_codec.c +++ b/drivers/net/wwan/iosm/iosm_ipc_mux_codec.c @@ -54,6 +54,49 @@ static int ipc_mux_acb_send(struct iosm_mux *ipc_mux, bool blocking) 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, @@ -104,7 +147,7 @@ int ipc_mux_dl_acb_send_cmds(struct iosm_mux *ipc_mux, u32 cmd_type, u8 if_id, size_t res_size, bool blocking, bool respond) { struct mux_acb *acb = &ipc_mux->acb; - struct mux_lite_cmdh *ack_lite; + union mux_type_cmdh cmdh; int ret = 0; acb->if_id = if_id; @@ -112,11 +155,23 @@ int ipc_mux_dl_acb_send_cmds(struct iosm_mux *ipc_mux, u32 cmd_type, u8 if_id, 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); + 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; @@ -129,15 +184,17 @@ void ipc_mux_netif_tx_flowctrl(struct mux_session *session, int idx, bool on) } static int ipc_mux_dl_cmdresps_decode_process(struct iosm_mux *ipc_mux, - struct mux_lite_cmdh *cmdh) + 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(cmdh->command_type)) { + 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 = cmdh->param; + acb->got_param = param; break; case MUX_LITE_CMD_FLOW_CTL_ACK: @@ -147,8 +204,16 @@ static int ipc_mux_dl_cmdresps_decode_process(struct iosm_mux *ipc_mux, 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)); + 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: @@ -156,38 +221,39 @@ static int ipc_mux_dl_cmdresps_decode_process(struct iosm_mux *ipc_mux, } acb->wanted_response = MUX_CMD_INVALID; - acb->got_response = le32_to_cpu(cmdh->command_type); + acb->got_response = le32_to_cpu(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) +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) { - union mux_cmd_param *param = &cmdh->param; struct mux_session *session; - int new_size; + struct hrtimer *adb_timer; dev_dbg(ipc_mux->dev, "if_id[%d]: dlcmds decode process %d", - cmdh->if_id, le32_to_cpu(cmdh->command_type)); + if_id, le32_to_cpu(command_type)); - switch (le32_to_cpu(cmdh->command_type)) { + switch (le32_to_cpu(command_type)) { case MUX_LITE_CMD_FLOW_CTL: + case IOSM_AGGR_MUX_CMD_FLOW_CTL_DISABLE: - if (cmdh->if_id >= IPC_MEM_MUX_IP_SESSION_ENTRIES) { + if (if_id >= IPC_MEM_MUX_IP_SESSION_ENTRIES) { dev_err(ipc_mux->dev, "if_id [%d] not valid", - cmdh->if_id); + if_id); return -EINVAL; /* No session interface id. */ } - session = &ipc_mux->session[cmdh->if_id]; + session = &ipc_mux->session[if_id]; + adb_timer = &ipc_mux->imem->adb_timer; - 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)) + if (cmd_len == cpu_to_le16(size)) session->flow_ctl_mask = le32_to_cpu(param->flow_ctl.mask); else @@ -197,6 +263,16 @@ static int ipc_mux_dl_dlcmds_decode_process(struct iosm_mux *ipc_mux, * 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) { @@ -205,8 +281,10 @@ static int ipc_mux_dl_dlcmds_decode_process(struct iosm_mux *ipc_mux, * 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 (cmdh->cmd_len == cpu_to_le16(new_size)) + if (cmd_len == cpu_to_le16(size)) session->flow_ctl_mask = le32_to_cpu(param->flow_ctl.mask); else @@ -217,7 +295,10 @@ static int ipc_mux_dl_dlcmds_decode_process(struct iosm_mux *ipc_mux, break; } - dev_dbg(ipc_mux->dev, "if[%u] FLOW CTRL 0x%08X", cmdh->if_id, + 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; @@ -235,12 +316,20 @@ 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)) { + 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. */ - if (!ipc_mux_dl_dlcmds_decode_process(ipc_mux, cmdh)) { + 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. */ @@ -349,7 +438,7 @@ static void ipc_mux_dl_adgh_decode(struct iosm_mux *ipc_mux, adgh = (struct mux_adgh *)block; - if (adgh->signature != cpu_to_le32(MUX_SIG_ADGH)) { + if (adgh->signature != cpu_to_le32(IOSM_AGGR_MUX_SIG_ADGH)) { dev_err(ipc_mux->dev, "invalid ADGH signature received"); return; } @@ -392,6 +481,192 @@ static void ipc_mux_dl_adgh_decode(struct iosm_mux *ipc_mux, 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; + + 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; + /* 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); + 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) - + sizeof(struct mux_adth_dg))) + 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)) / + 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; @@ -403,14 +678,18 @@ void ipc_mux_dl_decode(struct iosm_mux *ipc_mux, struct sk_buff *skb) signature = le32_to_cpup((__le32 *)skb->data); switch (signature) { - case MUX_SIG_ADGH: + 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; @@ -427,7 +706,10 @@ static int ipc_mux_ul_skb_alloc(struct iosm_mux *ipc_mux, { /* 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. */ @@ -436,7 +718,37 @@ static int ipc_mux_ul_skb_alloc(struct iosm_mux *ipc_mux, IPC_CB(skb)->op_type = (u8)UL_MUX_OP_ADB; switch (type) { - case MUX_SIG_ADGH: + 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; @@ -506,6 +818,94 @@ static void ipc_mux_ul_adgh_finish(struct iosm_mux *ipc_mux) 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, @@ -688,7 +1088,7 @@ static int ipc_mux_ul_adgh_encode(struct iosm_mux *ipc_mux, int session_id, 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)) { + IOSM_AGGR_MUX_SIG_ADGH)) { dev_err(ipc_mux->dev, "no reserved memory for ADGH"); return -ENOMEM; } @@ -720,7 +1120,7 @@ static int ipc_mux_ul_adgh_encode(struct iosm_mux *ipc_mux, int session_id, memcpy(adb->buf + offset + pad_len, src_skb->data, src_skb->len); - adb->adgh->signature = cpu_to_le32(MUX_SIG_ADGH); + 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 + @@ -762,6 +1162,187 @@ static int ipc_mux_ul_adgh_encode(struct iosm_mux *ipc_mux, int session_id, 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)) { + dev_kfree_skb(src_skb); + 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; @@ -802,28 +1383,88 @@ bool ipc_mux_ul_data_encode(struct iosm_mux *ipc_mux) * -> try next session id. */ continue; - - updated = ipc_mux_ul_adgh_encode(ipc_mux, session_id, session, - ul_list, &ipc_mux->ul_adb, - dg_n); + 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; } -void ipc_mux_ul_encoded_process(struct iosm_mux *ipc_mux, struct sk_buff *skb) +/* 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_adgh *adgh; - u16 adgh_len; + 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; + } - adgh = (struct mux_adgh *)skb->data; - adgh_len = le16_to_cpu(adgh->length); + while (next_table_idx != 0) { + /* Get the reference to the table header. */ + adth = (struct mux_adth *)((u8 *)p_adbh + next_table_idx); - 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 (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)) / + 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", @@ -846,10 +1487,13 @@ static int ipc_mux_tq_ul_trigger_encode(struct iosm_imem *ipc_imem, int arg, /* Add session UL data to a ADB and ADGH */ ul_data_pend = ipc_mux_ul_data_encode(ipc_mux); - if (ul_data_pend) + 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; diff --git a/drivers/net/wwan/iosm/iosm_ipc_mux_codec.h b/drivers/net/wwan/iosm/iosm_ipc_mux_codec.h index aae83db5cbb8..5d4e3b89542c 100644 --- a/drivers/net/wwan/iosm/iosm_ipc_mux_codec.h +++ b/drivers/net/wwan/iosm/iosm_ipc_mux_codec.h @@ -13,6 +13,39 @@ */ #define MUX_QUEUE_LEVEL 1 +/* ADB finish timer value */ +#define IOSM_AGGR_MUX_ADB_FINISH_TIMEOUT_NSEC (500 * 1000) + +/* Enables the flow control (Flow is not allowed) */ +#define IOSM_AGGR_MUX_CMD_FLOW_CTL_ENABLE 5 + +/* Disables the flow control (Flow is allowed) */ +#define IOSM_AGGR_MUX_CMD_FLOW_CTL_DISABLE 6 + +/* ACK the flow control command. Shall have the same Transaction ID as the + * matching FLOW_CTL command + */ +#define IOSM_AGGR_MUX_CMD_FLOW_CTL_ACK 7 + +/* Aggregation Protocol Command for report packet indicating link quality + */ +#define IOSM_AGGR_MUX_CMD_LINK_STATUS_REPORT 8 + +/* Response to a report packet */ +#define IOSM_AGGR_MUX_CMD_LINK_STATUS_REPORT_RESP 9 + +/* ACBH: Signature of the Aggregated Command Block Header. */ +#define IOSM_AGGR_MUX_SIG_ACBH 0x48424341 + +/* ADTH: Signature of the Aggregated Datagram Table Header. */ +#define IOSM_AGGR_MUX_SIG_ADTH 0x48544441 + +/* ADBH: Signature of the Aggregated Data Block Header. */ +#define IOSM_AGGR_MUX_SIG_ADBH 0x48424441 + +/* ADGH: Signature of the Datagram Header. */ +#define IOSM_AGGR_MUX_SIG_ADGH 0x48474441 + /* Size of the buffer for the IP MUX commands. */ #define MUX_MAX_UL_ACB_BUF_SIZE 256 @@ -53,6 +86,85 @@ #define IPC_MEM_MUX_UL_FLOWCTRL_HIGH_B (110 * 1024) /** + * struct mux_cmdh - Structure of Command Header. + * @signature: Signature of the Command Header. + * @cmd_len: Length (in bytes) of the Aggregated Command Block. + * @if_id: ID of the interface the commands in the table belong to. + * @reserved: Reserved. Set to zero. + * @next_cmd_index: Index (in bytes) to the next command in the buffer. + * @command_type: Command Enum. See table Session Management chapter for + * details. + * @transaction_id: The Transaction ID shall be unique to the command + * @param: Optional parameters used with the command. + */ +struct mux_cmdh { + __le32 signature; + __le16 cmd_len; + u8 if_id; + u8 reserved; + __le32 next_cmd_index; + __le32 command_type; + __le32 transaction_id; + union mux_cmd_param param; +}; + +/** + * struct mux_acbh - Structure of the Aggregated Command Block Header. + * @signature: Signature of the Aggregated Command Block Header. + * @reserved: Reserved bytes. Set to zero. + * @sequence_nr: Block sequence number. + * @block_length: Length (in bytes) of the Aggregated Command Block. + * @first_cmd_index: Index (in bytes) to the first command in the buffer. + */ +struct mux_acbh { + __le32 signature; + __le16 reserved; + __le16 sequence_nr; + __le32 block_length; + __le32 first_cmd_index; +}; + +/** + * struct mux_adbh - Structure of the Aggregated Data Block Header. + * @signature: Signature of the Aggregated Data Block Header. + * @reserved: Reserved bytes. Set to zero. + * @sequence_nr: Block sequence number. + * @block_length: Length (in bytes) of the Aggregated Data Block. + * @first_table_index: Index (in bytes) to the first Datagram Table in + * the buffer. + */ +struct mux_adbh { + __le32 signature; + __le16 reserved; + __le16 sequence_nr; + __le32 block_length; + __le32 first_table_index; +}; + +/** + * struct mux_adth - Structure of the Aggregated Datagram Table Header. + * @signature: Signature of the Aggregated Datagram Table Header. + * @table_length: Length (in bytes) of the datagram table. + * @if_id: ID of the interface the datagrams in the table + * belong to. + * @opt_ipv4v6: Indicates IPv4(=0)/IPv6(=1) hint. + * @reserved: Reserved bits. Set to zero. + * @next_table_index: Index (in bytes) to the next Datagram Table in + * the buffer. + * @reserved2: Reserved bytes. Set to zero + * @dg: datagramm table with variable length + */ +struct mux_adth { + __le32 signature; + __le16 table_length; + u8 if_id; + u8 opt_ipv4v6; + __le32 next_table_index; + __le32 reserved2; + struct mux_adth_dg dg; +}; + +/** * struct mux_adgh - Aggregated Datagram Header. * @signature: Signature of the Aggregated Datagram Header(0x48474441) * @length: Length (in bytes) of the datagram header. This length @@ -129,11 +241,25 @@ struct ipc_mem_lite_gen_tbl { }; /** - * 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. + * struct mux_type_cmdh - Structure of command header for mux lite and aggr + * @ack_lite: MUX Lite Command Header pointer + * @ack_aggr: Command Header pointer */ +union mux_type_cmdh { + struct mux_lite_cmdh *ack_lite; + struct mux_cmdh *ack_aggr; +}; + +/** + * struct mux_type_header - Structure of mux header type + * @adgh: Aggregated Datagram Header pointer + * @adbh: Aggregated Data Block Header pointer + */ +union mux_type_header { + struct mux_adgh *adgh; + struct mux_adbh *adbh; +}; + void ipc_mux_dl_decode(struct iosm_mux *ipc_mux, struct sk_buff *skb); /** @@ -147,7 +273,7 @@ void ipc_mux_dl_decode(struct iosm_mux *ipc_mux, struct sk_buff *skb); * @blocking: True for blocking send * @respond: If true return transaction ID * - * Returns: 0 in success and failure value on error + * 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, @@ -190,4 +316,10 @@ bool ipc_mux_ul_data_encode(struct iosm_mux *ipc_mux); */ void ipc_mux_ul_encoded_process(struct iosm_mux *ipc_mux, struct sk_buff *skb); +void ipc_mux_ul_adb_finish(struct iosm_mux *ipc_mux); + +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); + #endif diff --git a/drivers/net/wwan/iosm/iosm_ipc_pcie.c b/drivers/net/wwan/iosm/iosm_ipc_pcie.c index d73894e2a84e..31f57b986df2 100644 --- a/drivers/net/wwan/iosm/iosm_ipc_pcie.c +++ b/drivers/net/wwan/iosm/iosm_ipc_pcie.c @@ -320,6 +320,7 @@ ret_fail: static const struct pci_device_id iosm_ipc_ids[] = { { PCI_DEVICE(PCI_VENDOR_ID_INTEL, INTEL_CP_DEVICE_7560_ID) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, INTEL_CP_DEVICE_7360_ID) }, {} }; MODULE_DEVICE_TABLE(pci, iosm_ipc_ids); diff --git a/drivers/net/wwan/iosm/iosm_ipc_pcie.h b/drivers/net/wwan/iosm/iosm_ipc_pcie.h index 7d1f0cd7364c..844cf1fed994 100644 --- a/drivers/net/wwan/iosm/iosm_ipc_pcie.h +++ b/drivers/net/wwan/iosm/iosm_ipc_pcie.h @@ -14,6 +14,7 @@ /* Device ID */ #define INTEL_CP_DEVICE_7560_ID 0x7560 +#define INTEL_CP_DEVICE_7360_ID 0x7360 /* Define for BAR area usage */ #define IPC_DOORBELL_BAR0 0 |