diff options
| author | Petr Mladek <[email protected]> | 2021-08-30 14:56:06 +0200 | 
|---|---|---|
| committer | Petr Mladek <[email protected]> | 2021-08-30 14:56:06 +0200 | 
| commit | 71af75b6929458d85f63c0649dc26d6f4c19729e (patch) | |
| tree | c05c57903424d8270f6b6f3ec3493791fdba4e5c /drivers/net/wwan/iosm/iosm_ipc_protocol_ops.c | |
| parent | fe8e3ee0d588566c1f44f28a555042ef50eba491 (diff) | |
| parent | bc17bed5fd73ef1a9aed39f3b0ea26936dad60b8 (diff) | |
Merge branch 'for-5.15-printk-index' into for-linus
Diffstat (limited to 'drivers/net/wwan/iosm/iosm_ipc_protocol_ops.c')
| -rw-r--r-- | drivers/net/wwan/iosm/iosm_ipc_protocol_ops.c | 552 | 
1 files changed, 552 insertions, 0 deletions
diff --git a/drivers/net/wwan/iosm/iosm_ipc_protocol_ops.c b/drivers/net/wwan/iosm/iosm_ipc_protocol_ops.c new file mode 100644 index 000000000000..91109e27efd3 --- /dev/null +++ b/drivers/net/wwan/iosm/iosm_ipc_protocol_ops.c @@ -0,0 +1,552 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2020-21 Intel Corporation. + */ + +#include "iosm_ipc_protocol.h" +#include "iosm_ipc_protocol_ops.h" + +/* Get the next free message element.*/ +static union ipc_mem_msg_entry * +ipc_protocol_free_msg_get(struct iosm_protocol *ipc_protocol, int *index) +{ +	u32 head = le32_to_cpu(ipc_protocol->p_ap_shm->msg_head); +	u32 new_head = (head + 1) % IPC_MEM_MSG_ENTRIES; +	union ipc_mem_msg_entry *msg; + +	if (new_head == le32_to_cpu(ipc_protocol->p_ap_shm->msg_tail)) { +		dev_err(ipc_protocol->dev, "message ring is full"); +		return NULL; +	} + +	/* Get the pointer to the next free message element, +	 * reset the fields and mark is as invalid. +	 */ +	msg = &ipc_protocol->p_ap_shm->msg_ring[head]; +	memset(msg, 0, sizeof(*msg)); + +	/* return index in message ring */ +	*index = head; + +	return msg; +} + +/* Updates the message ring Head pointer */ +void ipc_protocol_msg_hp_update(struct iosm_imem *ipc_imem) +{ +	struct iosm_protocol *ipc_protocol = ipc_imem->ipc_protocol; +	u32 head = le32_to_cpu(ipc_protocol->p_ap_shm->msg_head); +	u32 new_head = (head + 1) % IPC_MEM_MSG_ENTRIES; + +	/* Update head pointer and fire doorbell. */ +	ipc_protocol->p_ap_shm->msg_head = cpu_to_le32(new_head); +	ipc_protocol->old_msg_tail = +		le32_to_cpu(ipc_protocol->p_ap_shm->msg_tail); + +	ipc_pm_signal_hpda_doorbell(&ipc_protocol->pm, IPC_HP_MR, false); +} + +/* Allocate and prepare a OPEN_PIPE message. + * This also allocates the memory for the new TDR structure and + * updates the pipe structure referenced in the preparation arguments. + */ +static int ipc_protocol_msg_prepipe_open(struct iosm_protocol *ipc_protocol, +					 union ipc_msg_prep_args *args) +{ +	int index; +	union ipc_mem_msg_entry *msg = +		ipc_protocol_free_msg_get(ipc_protocol, &index); +	struct ipc_pipe *pipe = args->pipe_open.pipe; +	struct ipc_protocol_td *tdr; +	struct sk_buff **skbr; + +	if (!msg) { +		dev_err(ipc_protocol->dev, "failed to get free message"); +		return -EIO; +	} + +	/* Allocate the skbuf elements for the skbuf which are on the way. +	 * SKB ring is internal memory allocation for driver. No need to +	 * re-calculate the start and end addresses. +	 */ +	skbr = kcalloc(pipe->nr_of_entries, sizeof(*skbr), GFP_ATOMIC); +	if (!skbr) +		return -ENOMEM; + +	/* Allocate the transfer descriptors for the pipe. */ +	tdr = pci_alloc_consistent(ipc_protocol->pcie->pci, +				   pipe->nr_of_entries * sizeof(*tdr), +				   &pipe->phy_tdr_start); +	if (!tdr) { +		kfree(skbr); +		dev_err(ipc_protocol->dev, "tdr alloc error"); +		return -ENOMEM; +	} + +	pipe->max_nr_of_queued_entries = pipe->nr_of_entries - 1; +	pipe->nr_of_queued_entries = 0; +	pipe->tdr_start = tdr; +	pipe->skbr_start = skbr; +	pipe->old_tail = 0; + +	ipc_protocol->p_ap_shm->head_array[pipe->pipe_nr] = 0; + +	msg->open_pipe.type_of_message = IPC_MEM_MSG_OPEN_PIPE; +	msg->open_pipe.pipe_nr = pipe->pipe_nr; +	msg->open_pipe.tdr_addr = cpu_to_le64(pipe->phy_tdr_start); +	msg->open_pipe.tdr_entries = cpu_to_le16(pipe->nr_of_entries); +	msg->open_pipe.accumulation_backoff = +				cpu_to_le32(pipe->accumulation_backoff); +	msg->open_pipe.irq_vector = cpu_to_le32(pipe->irq); + +	return index; +} + +static int ipc_protocol_msg_prepipe_close(struct iosm_protocol *ipc_protocol, +					  union ipc_msg_prep_args *args) +{ +	int index = -1; +	union ipc_mem_msg_entry *msg = +		ipc_protocol_free_msg_get(ipc_protocol, &index); +	struct ipc_pipe *pipe = args->pipe_close.pipe; + +	if (!msg) +		return -EIO; + +	msg->close_pipe.type_of_message = IPC_MEM_MSG_CLOSE_PIPE; +	msg->close_pipe.pipe_nr = pipe->pipe_nr; + +	dev_dbg(ipc_protocol->dev, "IPC_MEM_MSG_CLOSE_PIPE(pipe_nr=%d)", +		msg->close_pipe.pipe_nr); + +	return index; +} + +static int ipc_protocol_msg_prep_sleep(struct iosm_protocol *ipc_protocol, +				       union ipc_msg_prep_args *args) +{ +	int index = -1; +	union ipc_mem_msg_entry *msg = +		ipc_protocol_free_msg_get(ipc_protocol, &index); + +	if (!msg) { +		dev_err(ipc_protocol->dev, "failed to get free message"); +		return -EIO; +	} + +	/* Prepare and send the host sleep message to CP to enter or exit D3. */ +	msg->host_sleep.type_of_message = IPC_MEM_MSG_SLEEP; +	msg->host_sleep.target = args->sleep.target; /* 0=host, 1=device */ + +	/* state; 0=enter, 1=exit 2=enter w/o protocol */ +	msg->host_sleep.state = args->sleep.state; + +	dev_dbg(ipc_protocol->dev, "IPC_MEM_MSG_SLEEP(target=%d; state=%d)", +		msg->host_sleep.target, msg->host_sleep.state); + +	return index; +} + +static int ipc_protocol_msg_prep_feature_set(struct iosm_protocol *ipc_protocol, +					     union ipc_msg_prep_args *args) +{ +	int index = -1; +	union ipc_mem_msg_entry *msg = +		ipc_protocol_free_msg_get(ipc_protocol, &index); + +	if (!msg) { +		dev_err(ipc_protocol->dev, "failed to get free message"); +		return -EIO; +	} + +	msg->feature_set.type_of_message = IPC_MEM_MSG_FEATURE_SET; +	msg->feature_set.reset_enable = args->feature_set.reset_enable << +					RESET_BIT; + +	dev_dbg(ipc_protocol->dev, "IPC_MEM_MSG_FEATURE_SET(reset_enable=%d)", +		msg->feature_set.reset_enable >> RESET_BIT); + +	return index; +} + +/* Processes the message consumed by CP. */ +bool ipc_protocol_msg_process(struct iosm_imem *ipc_imem, int irq) +{ +	struct iosm_protocol *ipc_protocol = ipc_imem->ipc_protocol; +	struct ipc_rsp **rsp_ring = ipc_protocol->rsp_ring; +	bool msg_processed = false; +	u32 i; + +	if (le32_to_cpu(ipc_protocol->p_ap_shm->msg_tail) >= +			IPC_MEM_MSG_ENTRIES) { +		dev_err(ipc_protocol->dev, "msg_tail out of range: %d", +			le32_to_cpu(ipc_protocol->p_ap_shm->msg_tail)); +		return msg_processed; +	} + +	if (irq != IMEM_IRQ_DONT_CARE && +	    irq != ipc_protocol->p_ap_shm->ci.msg_irq_vector) +		return msg_processed; + +	for (i = ipc_protocol->old_msg_tail; +	     i != le32_to_cpu(ipc_protocol->p_ap_shm->msg_tail); +	     i = (i + 1) % IPC_MEM_MSG_ENTRIES) { +		union ipc_mem_msg_entry *msg = +			&ipc_protocol->p_ap_shm->msg_ring[i]; + +		dev_dbg(ipc_protocol->dev, "msg[%d]: type=%u status=%d", i, +			msg->common.type_of_message, +			msg->common.completion_status); + +		/* Update response with status and wake up waiting requestor */ +		if (rsp_ring[i]) { +			rsp_ring[i]->status = +				le32_to_cpu(msg->common.completion_status); +			complete(&rsp_ring[i]->completion); +			rsp_ring[i] = NULL; +		} +		msg_processed = true; +	} + +	ipc_protocol->old_msg_tail = i; +	return msg_processed; +} + +/* Sends data from UL list to CP for the provided pipe by updating the Head + * pointer of given pipe. + */ +bool ipc_protocol_ul_td_send(struct iosm_protocol *ipc_protocol, +			     struct ipc_pipe *pipe, +			     struct sk_buff_head *p_ul_list) +{ +	struct ipc_protocol_td *td; +	bool hpda_pending = false; +	struct sk_buff *skb; +	s32 free_elements; +	u32 head; +	u32 tail; + +	if (!ipc_protocol->p_ap_shm) { +		dev_err(ipc_protocol->dev, "driver is not initialized"); +		return false; +	} + +	/* Get head and tail of the td list and calculate +	 * the number of free elements. +	 */ +	head = le32_to_cpu(ipc_protocol->p_ap_shm->head_array[pipe->pipe_nr]); +	tail = pipe->old_tail; + +	while (!skb_queue_empty(p_ul_list)) { +		if (head < tail) +			free_elements = tail - head - 1; +		else +			free_elements = +				pipe->nr_of_entries - head + ((s32)tail - 1); + +		if (free_elements <= 0) { +			dev_dbg(ipc_protocol->dev, +				"no free td elements for UL pipe %d", +				pipe->pipe_nr); +			break; +		} + +		/* Get the td address. */ +		td = &pipe->tdr_start[head]; + +		/* Take the first element of the uplink list and add it +		 * to the td list. +		 */ +		skb = skb_dequeue(p_ul_list); +		if (WARN_ON(!skb)) +			break; + +		/* Save the reference to the uplink skbuf. */ +		pipe->skbr_start[head] = skb; + +		td->buffer.address = IPC_CB(skb)->mapping; +		td->scs = cpu_to_le32(skb->len) & cpu_to_le32(SIZE_MASK); +		td->next = 0; + +		pipe->nr_of_queued_entries++; + +		/* Calculate the new head and save it. */ +		head++; +		if (head >= pipe->nr_of_entries) +			head = 0; + +		ipc_protocol->p_ap_shm->head_array[pipe->pipe_nr] = +			cpu_to_le32(head); +	} + +	if (pipe->old_head != head) { +		dev_dbg(ipc_protocol->dev, "New UL TDs Pipe:%d", pipe->pipe_nr); + +		pipe->old_head = head; +		/* Trigger doorbell because of pending UL packets. */ +		hpda_pending = true; +	} + +	return hpda_pending; +} + +/* Checks for Tail pointer update from CP and returns the data as SKB. */ +struct sk_buff *ipc_protocol_ul_td_process(struct iosm_protocol *ipc_protocol, +					   struct ipc_pipe *pipe) +{ +	struct ipc_protocol_td *p_td = &pipe->tdr_start[pipe->old_tail]; +	struct sk_buff *skb = pipe->skbr_start[pipe->old_tail]; + +	pipe->nr_of_queued_entries--; +	pipe->old_tail++; +	if (pipe->old_tail >= pipe->nr_of_entries) +		pipe->old_tail = 0; + +	if (!p_td->buffer.address) { +		dev_err(ipc_protocol->dev, "Td buffer address is NULL"); +		return NULL; +	} + +	if (p_td->buffer.address != IPC_CB(skb)->mapping) { +		dev_err(ipc_protocol->dev, +			"pipe %d: invalid buf_addr or skb_data", +			pipe->pipe_nr); +		return NULL; +	} + +	return skb; +} + +/* Allocates an SKB for CP to send data and updates the Head Pointer + * of the given Pipe#. + */ +bool ipc_protocol_dl_td_prepare(struct iosm_protocol *ipc_protocol, +				struct ipc_pipe *pipe) +{ +	struct ipc_protocol_td *td; +	dma_addr_t mapping = 0; +	u32 head, new_head; +	struct sk_buff *skb; +	u32 tail; + +	/* Get head and tail of the td list and calculate +	 * the number of free elements. +	 */ +	head = le32_to_cpu(ipc_protocol->p_ap_shm->head_array[pipe->pipe_nr]); +	tail = le32_to_cpu(ipc_protocol->p_ap_shm->tail_array[pipe->pipe_nr]); + +	new_head = head + 1; +	if (new_head >= pipe->nr_of_entries) +		new_head = 0; + +	if (new_head == tail) +		return false; + +	/* Get the td address. */ +	td = &pipe->tdr_start[head]; + +	/* Allocate the skbuf for the descriptor. */ +	skb = ipc_pcie_alloc_skb(ipc_protocol->pcie, pipe->buf_size, GFP_ATOMIC, +				 &mapping, DMA_FROM_DEVICE, +				 IPC_MEM_DL_ETH_OFFSET); +	if (!skb) +		return false; + +	td->buffer.address = mapping; +	td->scs = cpu_to_le32(pipe->buf_size) & cpu_to_le32(SIZE_MASK); +	td->next = 0; + +	/* store the new head value. */ +	ipc_protocol->p_ap_shm->head_array[pipe->pipe_nr] = +		cpu_to_le32(new_head); + +	/* Save the reference to the skbuf. */ +	pipe->skbr_start[head] = skb; + +	pipe->nr_of_queued_entries++; + +	return true; +} + +/* Processes DL TD's */ +struct sk_buff *ipc_protocol_dl_td_process(struct iosm_protocol *ipc_protocol, +					   struct ipc_pipe *pipe) +{ +	u32 tail = +		le32_to_cpu(ipc_protocol->p_ap_shm->tail_array[pipe->pipe_nr]); +	struct ipc_protocol_td *p_td; +	struct sk_buff *skb; + +	if (!pipe->tdr_start) +		return NULL; + +	/* Copy the reference to the downlink buffer. */ +	p_td = &pipe->tdr_start[pipe->old_tail]; +	skb = pipe->skbr_start[pipe->old_tail]; + +	/* Reset the ring elements. */ +	pipe->skbr_start[pipe->old_tail] = NULL; + +	pipe->nr_of_queued_entries--; + +	pipe->old_tail++; +	if (pipe->old_tail >= pipe->nr_of_entries) +		pipe->old_tail = 0; + +	if (!skb) { +		dev_err(ipc_protocol->dev, "skb is null"); +		goto ret; +	} else if (!p_td->buffer.address) { +		dev_err(ipc_protocol->dev, "td/buffer address is null"); +		ipc_pcie_kfree_skb(ipc_protocol->pcie, skb); +		skb = NULL; +		goto ret; +	} + +	if (!IPC_CB(skb)) { +		dev_err(ipc_protocol->dev, "pipe# %d, tail: %d skb_cb is NULL", +			pipe->pipe_nr, tail); +		ipc_pcie_kfree_skb(ipc_protocol->pcie, skb); +		skb = NULL; +		goto ret; +	} + +	if (p_td->buffer.address != IPC_CB(skb)->mapping) { +		dev_err(ipc_protocol->dev, "invalid buf=%p or skb=%p", +			(void *)p_td->buffer.address, skb->data); +		ipc_pcie_kfree_skb(ipc_protocol->pcie, skb); +		skb = NULL; +		goto ret; +	} else if ((le32_to_cpu(p_td->scs) & SIZE_MASK) > pipe->buf_size) { +		dev_err(ipc_protocol->dev, "invalid buffer size %d > %d", +			le32_to_cpu(p_td->scs) & SIZE_MASK, +			pipe->buf_size); +		ipc_pcie_kfree_skb(ipc_protocol->pcie, skb); +		skb = NULL; +		goto ret; +	} else if (le32_to_cpu(p_td->scs) >> COMPLETION_STATUS == +		  IPC_MEM_TD_CS_ABORT) { +		/* Discard aborted buffers. */ +		dev_dbg(ipc_protocol->dev, "discard 'aborted' buffers"); +		ipc_pcie_kfree_skb(ipc_protocol->pcie, skb); +		skb = NULL; +		goto ret; +	} + +	/* Set the length field in skbuf. */ +	skb_put(skb, le32_to_cpu(p_td->scs) & SIZE_MASK); + +ret: +	return skb; +} + +void ipc_protocol_get_head_tail_index(struct iosm_protocol *ipc_protocol, +				      struct ipc_pipe *pipe, u32 *head, +				      u32 *tail) +{ +	struct ipc_protocol_ap_shm *ipc_ap_shm = ipc_protocol->p_ap_shm; + +	if (head) +		*head = le32_to_cpu(ipc_ap_shm->head_array[pipe->pipe_nr]); + +	if (tail) +		*tail = le32_to_cpu(ipc_ap_shm->tail_array[pipe->pipe_nr]); +} + +/* Frees the TDs given to CP.  */ +void ipc_protocol_pipe_cleanup(struct iosm_protocol *ipc_protocol, +			       struct ipc_pipe *pipe) +{ +	struct sk_buff *skb; +	u32 head; +	u32 tail; + +	/* Get the start and the end of the buffer list. */ +	head = le32_to_cpu(ipc_protocol->p_ap_shm->head_array[pipe->pipe_nr]); +	tail = pipe->old_tail; + +	/* Reset tail and head to 0. */ +	ipc_protocol->p_ap_shm->tail_array[pipe->pipe_nr] = 0; +	ipc_protocol->p_ap_shm->head_array[pipe->pipe_nr] = 0; + +	/* Free pending uplink and downlink buffers. */ +	if (pipe->skbr_start) { +		while (head != tail) { +			/* Get the reference to the skbuf, +			 * which is on the way and free it. +			 */ +			skb = pipe->skbr_start[tail]; +			if (skb) +				ipc_pcie_kfree_skb(ipc_protocol->pcie, skb); + +			tail++; +			if (tail >= pipe->nr_of_entries) +				tail = 0; +		} + +		kfree(pipe->skbr_start); +		pipe->skbr_start = NULL; +	} + +	pipe->old_tail = 0; + +	/* Free and reset the td and skbuf circular buffers. kfree is save! */ +	if (pipe->tdr_start) { +		pci_free_consistent(ipc_protocol->pcie->pci, +				    sizeof(*pipe->tdr_start) * +					    pipe->nr_of_entries, +				    pipe->tdr_start, pipe->phy_tdr_start); + +		pipe->tdr_start = NULL; +	} +} + +enum ipc_mem_device_ipc_state ipc_protocol_get_ipc_status(struct iosm_protocol +							  *ipc_protocol) +{ +	return (enum ipc_mem_device_ipc_state) +		le32_to_cpu(ipc_protocol->p_ap_shm->device_info.ipc_status); +} + +enum ipc_mem_exec_stage +ipc_protocol_get_ap_exec_stage(struct iosm_protocol *ipc_protocol) +{ +	return le32_to_cpu(ipc_protocol->p_ap_shm->device_info.execution_stage); +} + +int ipc_protocol_msg_prep(struct iosm_imem *ipc_imem, +			  enum ipc_msg_prep_type msg_type, +			  union ipc_msg_prep_args *args) +{ +	struct iosm_protocol *ipc_protocol = ipc_imem->ipc_protocol; + +	switch (msg_type) { +	case IPC_MSG_PREP_SLEEP: +		return ipc_protocol_msg_prep_sleep(ipc_protocol, args); + +	case IPC_MSG_PREP_PIPE_OPEN: +		return ipc_protocol_msg_prepipe_open(ipc_protocol, args); + +	case IPC_MSG_PREP_PIPE_CLOSE: +		return ipc_protocol_msg_prepipe_close(ipc_protocol, args); + +	case IPC_MSG_PREP_FEATURE_SET: +		return ipc_protocol_msg_prep_feature_set(ipc_protocol, args); + +		/* Unsupported messages in protocol */ +	case IPC_MSG_PREP_MAP: +	case IPC_MSG_PREP_UNMAP: +	default: +		dev_err(ipc_protocol->dev, +			"unsupported message type: %d in protocol", msg_type); +		return -EINVAL; +	} +} + +u32 +ipc_protocol_pm_dev_get_sleep_notification(struct iosm_protocol *ipc_protocol) +{ +	struct ipc_protocol_ap_shm *ipc_ap_shm = ipc_protocol->p_ap_shm; + +	return le32_to_cpu(ipc_ap_shm->device_info.device_sleep_notification); +}  |