root/drivers/net/wwan/iosm/iosm_ipc_protocol_ops.c
// 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 = kzalloc_objs(*skbr, pipe->nr_of_entries, 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)
{
        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 (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);
}