| // 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 = dma_alloc_coherent(&ipc_protocol->pcie->pci->dev, |
| pipe->nr_of_entries * sizeof(*tdr), |
| &pipe->phy_tdr_start, GFP_ATOMIC); |
| 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=%llx or skb=%p", |
| (unsigned long long)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) { |
| dma_free_coherent(&ipc_protocol->pcie->pci->dev, |
| 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); |
| } |