|  | // SPDX-License-Identifier: GPL-2.0 | 
|  | /* | 
|  | * Copyright 2018-2020 Broadcom. | 
|  | */ | 
|  |  | 
|  | #include <linux/delay.h> | 
|  | #include <linux/fs.h> | 
|  | #include <linux/hash.h> | 
|  | #include <linux/interrupt.h> | 
|  | #include <linux/list.h> | 
|  | #include <linux/module.h> | 
|  | #include <linux/poll.h> | 
|  | #include <linux/sizes.h> | 
|  | #include <linux/spinlock.h> | 
|  | #include <linux/timer.h> | 
|  |  | 
|  | #include "bcm_vk.h" | 
|  | #include "bcm_vk_msg.h" | 
|  | #include "bcm_vk_sg.h" | 
|  |  | 
|  | /* functions to manipulate the transport id in msg block */ | 
|  | #define BCM_VK_MSG_Q_SHIFT	 4 | 
|  | #define BCM_VK_MSG_Q_MASK	 0xF | 
|  | #define BCM_VK_MSG_ID_MASK	 0xFFF | 
|  |  | 
|  | #define BCM_VK_DMA_DRAIN_MAX_MS	  2000 | 
|  |  | 
|  | /* number x q_size will be the max number of msg processed per loop */ | 
|  | #define BCM_VK_MSG_PROC_MAX_LOOP 2 | 
|  |  | 
|  | /* module parameter */ | 
|  | static bool hb_mon = true; | 
|  | module_param(hb_mon, bool, 0444); | 
|  | MODULE_PARM_DESC(hb_mon, "Monitoring heartbeat continuously.\n"); | 
|  | static int batch_log = 1; | 
|  | module_param(batch_log, int, 0444); | 
|  | MODULE_PARM_DESC(batch_log, "Max num of logs per batch operation.\n"); | 
|  |  | 
|  | static bool hb_mon_is_on(void) | 
|  | { | 
|  | return hb_mon; | 
|  | } | 
|  |  | 
|  | static u32 get_q_num(const struct vk_msg_blk *msg) | 
|  | { | 
|  | u32 q_num = msg->trans_id & BCM_VK_MSG_Q_MASK; | 
|  |  | 
|  | if (q_num >= VK_MSGQ_PER_CHAN_MAX) | 
|  | q_num = VK_MSGQ_NUM_DEFAULT; | 
|  | return q_num; | 
|  | } | 
|  |  | 
|  | static void set_q_num(struct vk_msg_blk *msg, u32 q_num) | 
|  | { | 
|  | u32 trans_q; | 
|  |  | 
|  | if (q_num >= VK_MSGQ_PER_CHAN_MAX) | 
|  | trans_q = VK_MSGQ_NUM_DEFAULT; | 
|  | else | 
|  | trans_q = q_num; | 
|  |  | 
|  | msg->trans_id = (msg->trans_id & ~BCM_VK_MSG_Q_MASK) | trans_q; | 
|  | } | 
|  |  | 
|  | static u32 get_msg_id(const struct vk_msg_blk *msg) | 
|  | { | 
|  | return ((msg->trans_id >> BCM_VK_MSG_Q_SHIFT) & BCM_VK_MSG_ID_MASK); | 
|  | } | 
|  |  | 
|  | static void set_msg_id(struct vk_msg_blk *msg, u32 val) | 
|  | { | 
|  | msg->trans_id = (val << BCM_VK_MSG_Q_SHIFT) | get_q_num(msg); | 
|  | } | 
|  |  | 
|  | static u32 msgq_inc(const struct bcm_vk_sync_qinfo *qinfo, u32 idx, u32 inc) | 
|  | { | 
|  | return ((idx + inc) & qinfo->q_mask); | 
|  | } | 
|  |  | 
|  | static | 
|  | struct vk_msg_blk __iomem *msgq_blk_addr(const struct bcm_vk_sync_qinfo *qinfo, | 
|  | u32 idx) | 
|  | { | 
|  | return qinfo->q_start + (VK_MSGQ_BLK_SIZE * idx); | 
|  | } | 
|  |  | 
|  | static u32 msgq_occupied(const struct bcm_vk_msgq __iomem *msgq, | 
|  | const struct bcm_vk_sync_qinfo *qinfo) | 
|  | { | 
|  | u32 wr_idx, rd_idx; | 
|  |  | 
|  | wr_idx = readl_relaxed(&msgq->wr_idx); | 
|  | rd_idx = readl_relaxed(&msgq->rd_idx); | 
|  |  | 
|  | return ((wr_idx - rd_idx) & qinfo->q_mask); | 
|  | } | 
|  |  | 
|  | static | 
|  | u32 msgq_avail_space(const struct bcm_vk_msgq __iomem *msgq, | 
|  | const struct bcm_vk_sync_qinfo *qinfo) | 
|  | { | 
|  | return (qinfo->q_size - msgq_occupied(msgq, qinfo) - 1); | 
|  | } | 
|  |  | 
|  | /* number of retries when enqueue message fails before returning EAGAIN */ | 
|  | #define BCM_VK_H2VK_ENQ_RETRY 10 | 
|  | #define BCM_VK_H2VK_ENQ_RETRY_DELAY_MS 50 | 
|  |  | 
|  | bool bcm_vk_drv_access_ok(struct bcm_vk *vk) | 
|  | { | 
|  | return (!!atomic_read(&vk->msgq_inited)); | 
|  | } | 
|  |  | 
|  | void bcm_vk_set_host_alert(struct bcm_vk *vk, u32 bit_mask) | 
|  | { | 
|  | struct bcm_vk_alert *alert = &vk->host_alert; | 
|  | unsigned long flags; | 
|  |  | 
|  | /* use irqsave version as this maybe called inside timer interrupt */ | 
|  | spin_lock_irqsave(&vk->host_alert_lock, flags); | 
|  | alert->notfs |= bit_mask; | 
|  | spin_unlock_irqrestore(&vk->host_alert_lock, flags); | 
|  |  | 
|  | if (test_and_set_bit(BCM_VK_WQ_NOTF_PEND, vk->wq_offload) == 0) | 
|  | queue_work(vk->wq_thread, &vk->wq_work); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Heartbeat related defines | 
|  | * The heartbeat from host is a last resort.  If stuck condition happens | 
|  | * on the card, firmware is supposed to detect it.  Therefore, the heartbeat | 
|  | * values used will be more relaxed on the driver, which need to be bigger | 
|  | * than the watchdog timeout on the card.  The watchdog timeout on the card | 
|  | * is 20s, with a jitter of 2s => 22s.  We use a value of 27s here. | 
|  | */ | 
|  | #define BCM_VK_HB_TIMER_S 3 | 
|  | #define BCM_VK_HB_TIMER_VALUE (BCM_VK_HB_TIMER_S * HZ) | 
|  | #define BCM_VK_HB_LOST_MAX (27 / BCM_VK_HB_TIMER_S) | 
|  |  | 
|  | static void bcm_vk_hb_poll(struct work_struct *work) | 
|  | { | 
|  | u32 uptime_s; | 
|  | struct bcm_vk_hb_ctrl *hb = container_of(to_delayed_work(work), struct bcm_vk_hb_ctrl, | 
|  | work); | 
|  | struct bcm_vk *vk = container_of(hb, struct bcm_vk, hb_ctrl); | 
|  |  | 
|  | if (bcm_vk_drv_access_ok(vk) && hb_mon_is_on()) { | 
|  | /* read uptime from register and compare */ | 
|  | uptime_s = vkread32(vk, BAR_0, BAR_OS_UPTIME); | 
|  |  | 
|  | if (uptime_s == hb->last_uptime) | 
|  | hb->lost_cnt++; | 
|  | else /* reset to avoid accumulation */ | 
|  | hb->lost_cnt = 0; | 
|  |  | 
|  | dev_dbg(&vk->pdev->dev, "Last uptime %d current %d, lost %d\n", | 
|  | hb->last_uptime, uptime_s, hb->lost_cnt); | 
|  |  | 
|  | /* | 
|  | * if the interface goes down without any activity, a value | 
|  | * of 0xFFFFFFFF will be continuously read, and the detection | 
|  | * will be happened eventually. | 
|  | */ | 
|  | hb->last_uptime = uptime_s; | 
|  | } else { | 
|  | /* reset heart beat lost cnt */ | 
|  | hb->lost_cnt = 0; | 
|  | } | 
|  |  | 
|  | /* next, check if heartbeat exceeds limit */ | 
|  | if (hb->lost_cnt > BCM_VK_HB_LOST_MAX) { | 
|  | dev_err(&vk->pdev->dev, "Heartbeat Misses %d times, %d s!\n", | 
|  | BCM_VK_HB_LOST_MAX, | 
|  | BCM_VK_HB_LOST_MAX * BCM_VK_HB_TIMER_S); | 
|  |  | 
|  | bcm_vk_blk_drv_access(vk); | 
|  | bcm_vk_set_host_alert(vk, ERR_LOG_HOST_HB_FAIL); | 
|  | } | 
|  | /* re-arm timer */ | 
|  | schedule_delayed_work(&hb->work, BCM_VK_HB_TIMER_VALUE); | 
|  | } | 
|  |  | 
|  | void bcm_vk_hb_init(struct bcm_vk *vk) | 
|  | { | 
|  | struct bcm_vk_hb_ctrl *hb = &vk->hb_ctrl; | 
|  |  | 
|  | INIT_DELAYED_WORK(&hb->work, bcm_vk_hb_poll); | 
|  | schedule_delayed_work(&hb->work, BCM_VK_HB_TIMER_VALUE); | 
|  | } | 
|  |  | 
|  | void bcm_vk_hb_deinit(struct bcm_vk *vk) | 
|  | { | 
|  | struct bcm_vk_hb_ctrl *hb = &vk->hb_ctrl; | 
|  |  | 
|  | cancel_delayed_work_sync(&hb->work); | 
|  | } | 
|  |  | 
|  | static void bcm_vk_msgid_bitmap_clear(struct bcm_vk *vk, | 
|  | unsigned int start, | 
|  | unsigned int nbits) | 
|  | { | 
|  | spin_lock(&vk->msg_id_lock); | 
|  | bitmap_clear(vk->bmap, start, nbits); | 
|  | spin_unlock(&vk->msg_id_lock); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * allocate a ctx per file struct | 
|  | */ | 
|  | static struct bcm_vk_ctx *bcm_vk_get_ctx(struct bcm_vk *vk, const pid_t pid) | 
|  | { | 
|  | u32 i; | 
|  | struct bcm_vk_ctx *ctx = NULL; | 
|  | u32 hash_idx = hash_32(pid, VK_PID_HT_SHIFT_BIT); | 
|  |  | 
|  | spin_lock(&vk->ctx_lock); | 
|  |  | 
|  | /* check if it is in reset, if so, don't allow */ | 
|  | if (vk->reset_pid) { | 
|  | dev_err(&vk->pdev->dev, | 
|  | "No context allowed during reset by pid %d\n", | 
|  | vk->reset_pid); | 
|  |  | 
|  | goto in_reset_exit; | 
|  | } | 
|  |  | 
|  | for (i = 0; i < ARRAY_SIZE(vk->ctx); i++) { | 
|  | if (!vk->ctx[i].in_use) { | 
|  | vk->ctx[i].in_use = true; | 
|  | ctx = &vk->ctx[i]; | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (!ctx) { | 
|  | dev_err(&vk->pdev->dev, "All context in use\n"); | 
|  |  | 
|  | goto all_in_use_exit; | 
|  | } | 
|  |  | 
|  | /* set the pid and insert it to hash table */ | 
|  | ctx->pid = pid; | 
|  | ctx->hash_idx = hash_idx; | 
|  | list_add_tail(&ctx->node, &vk->pid_ht[hash_idx].head); | 
|  |  | 
|  | /* increase kref */ | 
|  | kref_get(&vk->kref); | 
|  |  | 
|  | /* clear counter */ | 
|  | atomic_set(&ctx->pend_cnt, 0); | 
|  | atomic_set(&ctx->dma_cnt, 0); | 
|  | init_waitqueue_head(&ctx->rd_wq); | 
|  |  | 
|  | all_in_use_exit: | 
|  | in_reset_exit: | 
|  | spin_unlock(&vk->ctx_lock); | 
|  |  | 
|  | return ctx; | 
|  | } | 
|  |  | 
|  | static u16 bcm_vk_get_msg_id(struct bcm_vk *vk) | 
|  | { | 
|  | u16 rc = VK_MSG_ID_OVERFLOW; | 
|  | u16 test_bit_count = 0; | 
|  |  | 
|  | spin_lock(&vk->msg_id_lock); | 
|  | while (test_bit_count < (VK_MSG_ID_BITMAP_SIZE - 1)) { | 
|  | /* | 
|  | * first time come in this loop, msg_id will be 0 | 
|  | * and the first one tested will be 1.  We skip | 
|  | * VK_SIMPLEX_MSG_ID (0) for one way host2vk | 
|  | * communication | 
|  | */ | 
|  | vk->msg_id++; | 
|  | if (vk->msg_id == VK_MSG_ID_BITMAP_SIZE) | 
|  | vk->msg_id = 1; | 
|  |  | 
|  | if (test_bit(vk->msg_id, vk->bmap)) { | 
|  | test_bit_count++; | 
|  | continue; | 
|  | } | 
|  | rc = vk->msg_id; | 
|  | bitmap_set(vk->bmap, vk->msg_id, 1); | 
|  | break; | 
|  | } | 
|  | spin_unlock(&vk->msg_id_lock); | 
|  |  | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | static int bcm_vk_free_ctx(struct bcm_vk *vk, struct bcm_vk_ctx *ctx) | 
|  | { | 
|  | u32 idx; | 
|  | u32 hash_idx; | 
|  | pid_t pid; | 
|  | struct bcm_vk_ctx *entry; | 
|  | int count = 0; | 
|  |  | 
|  | if (!ctx) { | 
|  | dev_err(&vk->pdev->dev, "NULL context detected\n"); | 
|  | return -EINVAL; | 
|  | } | 
|  | idx = ctx->idx; | 
|  | pid = ctx->pid; | 
|  |  | 
|  | spin_lock(&vk->ctx_lock); | 
|  |  | 
|  | if (!vk->ctx[idx].in_use) { | 
|  | dev_err(&vk->pdev->dev, "context[%d] not in use!\n", idx); | 
|  | } else { | 
|  | vk->ctx[idx].in_use = false; | 
|  | vk->ctx[idx].miscdev = NULL; | 
|  |  | 
|  | /* Remove it from hash list and see if it is the last one. */ | 
|  | list_del(&ctx->node); | 
|  | hash_idx = ctx->hash_idx; | 
|  | list_for_each_entry(entry, &vk->pid_ht[hash_idx].head, node) { | 
|  | if (entry->pid == pid) | 
|  | count++; | 
|  | } | 
|  | } | 
|  |  | 
|  | spin_unlock(&vk->ctx_lock); | 
|  |  | 
|  | return count; | 
|  | } | 
|  |  | 
|  | static void bcm_vk_free_wkent(struct device *dev, struct bcm_vk_wkent *entry) | 
|  | { | 
|  | int proc_cnt; | 
|  |  | 
|  | bcm_vk_sg_free(dev, entry->dma, VK_DMA_MAX_ADDRS, &proc_cnt); | 
|  | if (proc_cnt) | 
|  | atomic_dec(&entry->ctx->dma_cnt); | 
|  |  | 
|  | kfree(entry->to_h_msg); | 
|  | kfree(entry); | 
|  | } | 
|  |  | 
|  | static void bcm_vk_drain_all_pend(struct device *dev, | 
|  | struct bcm_vk_msg_chan *chan, | 
|  | struct bcm_vk_ctx *ctx) | 
|  | { | 
|  | u32 num; | 
|  | struct bcm_vk_wkent *entry, *tmp; | 
|  | struct bcm_vk *vk; | 
|  | struct list_head del_q; | 
|  |  | 
|  | if (ctx) | 
|  | vk = container_of(ctx->miscdev, struct bcm_vk, miscdev); | 
|  |  | 
|  | INIT_LIST_HEAD(&del_q); | 
|  | spin_lock(&chan->pendq_lock); | 
|  | for (num = 0; num < chan->q_nr; num++) { | 
|  | list_for_each_entry_safe(entry, tmp, &chan->pendq[num], node) { | 
|  | if ((!ctx) || (entry->ctx->idx == ctx->idx)) { | 
|  | list_move_tail(&entry->node, &del_q); | 
|  | } | 
|  | } | 
|  | } | 
|  | spin_unlock(&chan->pendq_lock); | 
|  |  | 
|  | /* batch clean up */ | 
|  | num = 0; | 
|  | list_for_each_entry_safe(entry, tmp, &del_q, node) { | 
|  | list_del(&entry->node); | 
|  | num++; | 
|  | if (ctx) { | 
|  | struct vk_msg_blk *msg; | 
|  | int bit_set; | 
|  | bool responded; | 
|  | u32 msg_id; | 
|  |  | 
|  | /* if it is specific ctx, log for any stuck */ | 
|  | msg = entry->to_v_msg; | 
|  | msg_id = get_msg_id(msg); | 
|  | bit_set = test_bit(msg_id, vk->bmap); | 
|  | responded = entry->to_h_msg ? true : false; | 
|  | if (num <= batch_log) | 
|  | dev_info(dev, | 
|  | "Drained: fid %u size %u msg 0x%x(seq-%x) ctx 0x%x[fd-%d] args:[0x%x 0x%x] resp %s, bmap %d\n", | 
|  | msg->function_id, msg->size, | 
|  | msg_id, entry->seq_num, | 
|  | msg->context_id, entry->ctx->idx, | 
|  | msg->cmd, msg->arg, | 
|  | responded ? "T" : "F", bit_set); | 
|  | if (responded) | 
|  | atomic_dec(&ctx->pend_cnt); | 
|  | else if (bit_set) | 
|  | bcm_vk_msgid_bitmap_clear(vk, msg_id, 1); | 
|  | } | 
|  | bcm_vk_free_wkent(dev, entry); | 
|  | } | 
|  | if (num && ctx) | 
|  | dev_info(dev, "Total drained items %d [fd-%d]\n", | 
|  | num, ctx->idx); | 
|  | } | 
|  |  | 
|  | void bcm_vk_drain_msg_on_reset(struct bcm_vk *vk) | 
|  | { | 
|  | bcm_vk_drain_all_pend(&vk->pdev->dev, &vk->to_v_msg_chan, NULL); | 
|  | bcm_vk_drain_all_pend(&vk->pdev->dev, &vk->to_h_msg_chan, NULL); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Function to sync up the messages queue info that is provided by BAR1 | 
|  | */ | 
|  | int bcm_vk_sync_msgq(struct bcm_vk *vk, bool force_sync) | 
|  | { | 
|  | struct bcm_vk_msgq __iomem *msgq; | 
|  | struct device *dev = &vk->pdev->dev; | 
|  | u32 msgq_off; | 
|  | u32 num_q; | 
|  | struct bcm_vk_msg_chan *chan_list[] = {&vk->to_v_msg_chan, | 
|  | &vk->to_h_msg_chan}; | 
|  | struct bcm_vk_msg_chan *chan; | 
|  | int i, j; | 
|  | int ret = 0; | 
|  |  | 
|  | /* | 
|  | * If the driver is loaded at startup where vk OS is not up yet, | 
|  | * the msgq-info may not be available until a later time.  In | 
|  | * this case, we skip and the sync function is supposed to be | 
|  | * called again. | 
|  | */ | 
|  | if (!bcm_vk_msgq_marker_valid(vk)) { | 
|  | dev_info(dev, "BAR1 msgq marker not initialized.\n"); | 
|  | return -EAGAIN; | 
|  | } | 
|  |  | 
|  | msgq_off = vkread32(vk, BAR_1, VK_BAR1_MSGQ_CTRL_OFF); | 
|  |  | 
|  | /* each side is always half the total  */ | 
|  | num_q = vkread32(vk, BAR_1, VK_BAR1_MSGQ_NR) / 2; | 
|  | if (!num_q || (num_q > VK_MSGQ_PER_CHAN_MAX)) { | 
|  | dev_err(dev, | 
|  | "Advertised msgq %d error - max %d allowed\n", | 
|  | num_q, VK_MSGQ_PER_CHAN_MAX); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | vk->to_v_msg_chan.q_nr = num_q; | 
|  | vk->to_h_msg_chan.q_nr = num_q; | 
|  |  | 
|  | /* first msgq location */ | 
|  | msgq = vk->bar[BAR_1] + msgq_off; | 
|  |  | 
|  | /* | 
|  | * if this function is called when it is already inited, | 
|  | * something is wrong | 
|  | */ | 
|  | if (bcm_vk_drv_access_ok(vk) && !force_sync) { | 
|  | dev_err(dev, "Msgq info already in sync\n"); | 
|  | return -EPERM; | 
|  | } | 
|  |  | 
|  | for (i = 0; i < ARRAY_SIZE(chan_list); i++) { | 
|  | chan = chan_list[i]; | 
|  | memset(chan->sync_qinfo, 0, sizeof(chan->sync_qinfo)); | 
|  |  | 
|  | for (j = 0; j < num_q; j++) { | 
|  | struct bcm_vk_sync_qinfo *qinfo; | 
|  | u32 msgq_start; | 
|  | u32 msgq_size; | 
|  | u32 msgq_nxt; | 
|  | u32 msgq_db_offset, q_db_offset; | 
|  |  | 
|  | chan->msgq[j] = msgq; | 
|  | msgq_start = readl_relaxed(&msgq->start); | 
|  | msgq_size = readl_relaxed(&msgq->size); | 
|  | msgq_nxt = readl_relaxed(&msgq->nxt); | 
|  | msgq_db_offset = readl_relaxed(&msgq->db_offset); | 
|  | q_db_offset = (msgq_db_offset & ((1 << DB_SHIFT) - 1)); | 
|  | if (q_db_offset  == (~msgq_db_offset >> DB_SHIFT)) | 
|  | msgq_db_offset = q_db_offset; | 
|  | else | 
|  | /* fall back to default */ | 
|  | msgq_db_offset = VK_BAR0_Q_DB_BASE(j); | 
|  |  | 
|  | dev_info(dev, | 
|  | "MsgQ[%d] type %d num %d, @ 0x%x, db_offset 0x%x rd_idx %d wr_idx %d, size %d, nxt 0x%x\n", | 
|  | j, | 
|  | readw_relaxed(&msgq->type), | 
|  | readw_relaxed(&msgq->num), | 
|  | msgq_start, | 
|  | msgq_db_offset, | 
|  | readl_relaxed(&msgq->rd_idx), | 
|  | readl_relaxed(&msgq->wr_idx), | 
|  | msgq_size, | 
|  | msgq_nxt); | 
|  |  | 
|  | qinfo = &chan->sync_qinfo[j]; | 
|  | /* formulate and record static info */ | 
|  | qinfo->q_start = vk->bar[BAR_1] + msgq_start; | 
|  | qinfo->q_size = msgq_size; | 
|  | /* set low threshold as 50% or 1/2 */ | 
|  | qinfo->q_low = qinfo->q_size >> 1; | 
|  | qinfo->q_mask = qinfo->q_size - 1; | 
|  | qinfo->q_db_offset = msgq_db_offset; | 
|  |  | 
|  | msgq++; | 
|  | } | 
|  | } | 
|  | atomic_set(&vk->msgq_inited, 1); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int bcm_vk_msg_chan_init(struct bcm_vk_msg_chan *chan) | 
|  | { | 
|  | u32 i; | 
|  |  | 
|  | mutex_init(&chan->msgq_mutex); | 
|  | spin_lock_init(&chan->pendq_lock); | 
|  | for (i = 0; i < VK_MSGQ_MAX_NR; i++) | 
|  | INIT_LIST_HEAD(&chan->pendq[i]); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static void bcm_vk_append_pendq(struct bcm_vk_msg_chan *chan, u16 q_num, | 
|  | struct bcm_vk_wkent *entry) | 
|  | { | 
|  | struct bcm_vk_ctx *ctx; | 
|  |  | 
|  | spin_lock(&chan->pendq_lock); | 
|  | list_add_tail(&entry->node, &chan->pendq[q_num]); | 
|  | if (entry->to_h_msg) { | 
|  | ctx = entry->ctx; | 
|  | atomic_inc(&ctx->pend_cnt); | 
|  | wake_up_interruptible(&ctx->rd_wq); | 
|  | } | 
|  | spin_unlock(&chan->pendq_lock); | 
|  | } | 
|  |  | 
|  | static u32 bcm_vk_append_ib_sgl(struct bcm_vk *vk, | 
|  | struct bcm_vk_wkent *entry, | 
|  | struct _vk_data *data, | 
|  | unsigned int num_planes) | 
|  | { | 
|  | unsigned int i; | 
|  | unsigned int item_cnt = 0; | 
|  | struct device *dev = &vk->pdev->dev; | 
|  | struct bcm_vk_msg_chan *chan = &vk->to_v_msg_chan; | 
|  | struct vk_msg_blk *msg = &entry->to_v_msg[0]; | 
|  | struct bcm_vk_msgq __iomem *msgq; | 
|  | struct bcm_vk_sync_qinfo *qinfo; | 
|  | u32 ib_sgl_size = 0; | 
|  | u8 *buf = (u8 *)&entry->to_v_msg[entry->to_v_blks]; | 
|  | u32 avail; | 
|  | u32 q_num; | 
|  |  | 
|  | /* check if high watermark is hit, and if so, skip */ | 
|  | q_num = get_q_num(msg); | 
|  | msgq = chan->msgq[q_num]; | 
|  | qinfo = &chan->sync_qinfo[q_num]; | 
|  | avail = msgq_avail_space(msgq, qinfo); | 
|  | if (avail < qinfo->q_low) { | 
|  | dev_dbg(dev, "Skip inserting inband SGL, [0x%x/0x%x]\n", | 
|  | avail, qinfo->q_size); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | for (i = 0; i < num_planes; i++) { | 
|  | if (data[i].address && | 
|  | (ib_sgl_size + data[i].size) <= vk->ib_sgl_size) { | 
|  | item_cnt++; | 
|  | memcpy(buf, entry->dma[i].sglist, data[i].size); | 
|  | ib_sgl_size += data[i].size; | 
|  | buf += data[i].size; | 
|  | } | 
|  | } | 
|  |  | 
|  | dev_dbg(dev, "Num %u sgl items appended, size 0x%x, room 0x%x\n", | 
|  | item_cnt, ib_sgl_size, vk->ib_sgl_size); | 
|  |  | 
|  | /* round up size */ | 
|  | ib_sgl_size = (ib_sgl_size + VK_MSGQ_BLK_SIZE - 1) | 
|  | >> VK_MSGQ_BLK_SZ_SHIFT; | 
|  |  | 
|  | return ib_sgl_size; | 
|  | } | 
|  |  | 
|  | void bcm_to_v_q_doorbell(struct bcm_vk *vk, u32 q_num, u32 db_val) | 
|  | { | 
|  | struct bcm_vk_msg_chan *chan = &vk->to_v_msg_chan; | 
|  | struct bcm_vk_sync_qinfo *qinfo = &chan->sync_qinfo[q_num]; | 
|  |  | 
|  | vkwrite32(vk, db_val, BAR_0, qinfo->q_db_offset); | 
|  | } | 
|  |  | 
|  | static int bcm_to_v_msg_enqueue(struct bcm_vk *vk, struct bcm_vk_wkent *entry) | 
|  | { | 
|  | static u32 seq_num; | 
|  | struct bcm_vk_msg_chan *chan = &vk->to_v_msg_chan; | 
|  | struct device *dev = &vk->pdev->dev; | 
|  | struct vk_msg_blk *src = &entry->to_v_msg[0]; | 
|  |  | 
|  | struct vk_msg_blk __iomem *dst; | 
|  | struct bcm_vk_msgq __iomem *msgq; | 
|  | struct bcm_vk_sync_qinfo *qinfo; | 
|  | u32 q_num = get_q_num(src); | 
|  | u32 wr_idx; /* local copy */ | 
|  | u32 i; | 
|  | u32 avail; | 
|  | u32 retry; | 
|  |  | 
|  | if (entry->to_v_blks != src->size + 1) { | 
|  | dev_err(dev, "number of blks %d not matching %d MsgId[0x%x]: func %d ctx 0x%x\n", | 
|  | entry->to_v_blks, | 
|  | src->size + 1, | 
|  | get_msg_id(src), | 
|  | src->function_id, | 
|  | src->context_id); | 
|  | return -EMSGSIZE; | 
|  | } | 
|  |  | 
|  | msgq = chan->msgq[q_num]; | 
|  | qinfo = &chan->sync_qinfo[q_num]; | 
|  |  | 
|  | mutex_lock(&chan->msgq_mutex); | 
|  |  | 
|  | avail = msgq_avail_space(msgq, qinfo); | 
|  |  | 
|  | /* if not enough space, return EAGAIN and let app handles it */ | 
|  | retry = 0; | 
|  | while ((avail < entry->to_v_blks) && | 
|  | (retry++ < BCM_VK_H2VK_ENQ_RETRY)) { | 
|  | mutex_unlock(&chan->msgq_mutex); | 
|  |  | 
|  | msleep(BCM_VK_H2VK_ENQ_RETRY_DELAY_MS); | 
|  | mutex_lock(&chan->msgq_mutex); | 
|  | avail = msgq_avail_space(msgq, qinfo); | 
|  | } | 
|  | if (retry > BCM_VK_H2VK_ENQ_RETRY) { | 
|  | mutex_unlock(&chan->msgq_mutex); | 
|  | return -EAGAIN; | 
|  | } | 
|  |  | 
|  | /* at this point, mutex is taken and there is enough space */ | 
|  | entry->seq_num = seq_num++; /* update debug seq number */ | 
|  | wr_idx = readl_relaxed(&msgq->wr_idx); | 
|  |  | 
|  | if (wr_idx >= qinfo->q_size) { | 
|  | dev_crit(dev, "Invalid wr_idx 0x%x => max 0x%x!", | 
|  | wr_idx, qinfo->q_size); | 
|  | bcm_vk_blk_drv_access(vk); | 
|  | bcm_vk_set_host_alert(vk, ERR_LOG_HOST_PCIE_DWN); | 
|  | goto idx_err; | 
|  | } | 
|  |  | 
|  | dst = msgq_blk_addr(qinfo, wr_idx); | 
|  | for (i = 0; i < entry->to_v_blks; i++) { | 
|  | memcpy_toio(dst, src, sizeof(*dst)); | 
|  |  | 
|  | src++; | 
|  | wr_idx = msgq_inc(qinfo, wr_idx, 1); | 
|  | dst = msgq_blk_addr(qinfo, wr_idx); | 
|  | } | 
|  |  | 
|  | /* flush the write pointer */ | 
|  | writel(wr_idx, &msgq->wr_idx); | 
|  |  | 
|  | /* log new info for debugging */ | 
|  | dev_dbg(dev, | 
|  | "MsgQ[%d] [Rd Wr] = [%d %d] blks inserted %d - Q = [u-%d a-%d]/%d\n", | 
|  | readl_relaxed(&msgq->num), | 
|  | readl_relaxed(&msgq->rd_idx), | 
|  | wr_idx, | 
|  | entry->to_v_blks, | 
|  | msgq_occupied(msgq, qinfo), | 
|  | msgq_avail_space(msgq, qinfo), | 
|  | readl_relaxed(&msgq->size)); | 
|  | /* | 
|  | * press door bell based on queue number. 1 is added to the wr_idx | 
|  | * to avoid the value of 0 appearing on the VK side to distinguish | 
|  | * from initial value. | 
|  | */ | 
|  | bcm_to_v_q_doorbell(vk, q_num, wr_idx + 1); | 
|  | idx_err: | 
|  | mutex_unlock(&chan->msgq_mutex); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | int bcm_vk_send_shutdown_msg(struct bcm_vk *vk, u32 shut_type, | 
|  | const pid_t pid, const u32 q_num) | 
|  | { | 
|  | int rc = 0; | 
|  | struct bcm_vk_wkent *entry; | 
|  | struct device *dev = &vk->pdev->dev; | 
|  |  | 
|  | /* | 
|  | * check if the marker is still good.  Sometimes, the PCIe interface may | 
|  | * have gone done, and if so and we ship down thing based on broken | 
|  | * values, kernel may panic. | 
|  | */ | 
|  | if (!bcm_vk_msgq_marker_valid(vk)) { | 
|  | dev_info(dev, "PCIe comm chan - invalid marker (0x%x)!\n", | 
|  | vkread32(vk, BAR_1, VK_BAR1_MSGQ_DEF_RDY)); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | entry = kzalloc(struct_size(entry, to_v_msg, 1), GFP_KERNEL); | 
|  | if (!entry) | 
|  | return -ENOMEM; | 
|  | entry->to_v_blks = 1;	/* always 1 block */ | 
|  |  | 
|  | /* fill up necessary data */ | 
|  | entry->to_v_msg[0].function_id = VK_FID_SHUTDOWN; | 
|  | set_q_num(&entry->to_v_msg[0], q_num); | 
|  | set_msg_id(&entry->to_v_msg[0], VK_SIMPLEX_MSG_ID); | 
|  |  | 
|  | entry->to_v_msg[0].cmd = shut_type; | 
|  | entry->to_v_msg[0].arg = pid; | 
|  |  | 
|  | rc = bcm_to_v_msg_enqueue(vk, entry); | 
|  | if (rc) | 
|  | dev_err(dev, | 
|  | "Sending shutdown message to q %d for pid %d fails.\n", | 
|  | get_q_num(&entry->to_v_msg[0]), pid); | 
|  |  | 
|  | kfree(entry); | 
|  |  | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | static int bcm_vk_handle_last_sess(struct bcm_vk *vk, const pid_t pid, | 
|  | const u32 q_num) | 
|  | { | 
|  | int rc = 0; | 
|  | struct device *dev = &vk->pdev->dev; | 
|  |  | 
|  | /* | 
|  | * don't send down or do anything if message queue is not initialized | 
|  | * and if it is the reset session, clear it. | 
|  | */ | 
|  | if (!bcm_vk_drv_access_ok(vk)) { | 
|  | if (vk->reset_pid == pid) | 
|  | vk->reset_pid = 0; | 
|  | return -EPERM; | 
|  | } | 
|  |  | 
|  | dev_dbg(dev, "No more sessions, shut down pid %d\n", pid); | 
|  |  | 
|  | /* only need to do it if it is not the reset process */ | 
|  | if (vk->reset_pid != pid) | 
|  | rc = bcm_vk_send_shutdown_msg(vk, VK_SHUTDOWN_PID, pid, q_num); | 
|  | else | 
|  | /* put reset_pid to 0 if it is exiting last session */ | 
|  | vk->reset_pid = 0; | 
|  |  | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | static struct bcm_vk_wkent *bcm_vk_dequeue_pending(struct bcm_vk *vk, | 
|  | struct bcm_vk_msg_chan *chan, | 
|  | u16 q_num, | 
|  | u16 msg_id) | 
|  | { | 
|  | struct bcm_vk_wkent *entry = NULL, *iter; | 
|  |  | 
|  | spin_lock(&chan->pendq_lock); | 
|  | list_for_each_entry(iter, &chan->pendq[q_num], node) { | 
|  | if (get_msg_id(&iter->to_v_msg[0]) == msg_id) { | 
|  | list_del(&iter->node); | 
|  | entry = iter; | 
|  | bcm_vk_msgid_bitmap_clear(vk, msg_id, 1); | 
|  | break; | 
|  | } | 
|  | } | 
|  | spin_unlock(&chan->pendq_lock); | 
|  | return entry; | 
|  | } | 
|  |  | 
|  | s32 bcm_to_h_msg_dequeue(struct bcm_vk *vk) | 
|  | { | 
|  | struct device *dev = &vk->pdev->dev; | 
|  | struct bcm_vk_msg_chan *chan = &vk->to_h_msg_chan; | 
|  | struct vk_msg_blk *data; | 
|  | struct vk_msg_blk __iomem *src; | 
|  | struct vk_msg_blk *dst; | 
|  | struct bcm_vk_msgq __iomem *msgq; | 
|  | struct bcm_vk_sync_qinfo *qinfo; | 
|  | struct bcm_vk_wkent *entry; | 
|  | u32 rd_idx, wr_idx; | 
|  | u32 q_num, msg_id, j; | 
|  | u32 num_blks; | 
|  | s32 total = 0; | 
|  | int cnt = 0; | 
|  | int msg_processed = 0; | 
|  | int max_msg_to_process; | 
|  | bool exit_loop; | 
|  |  | 
|  | /* | 
|  | * drain all the messages from the queues, and find its pending | 
|  | * entry in the to_v queue, based on msg_id & q_num, and move the | 
|  | * entry to the to_h pending queue, waiting for user space | 
|  | * program to extract | 
|  | */ | 
|  | mutex_lock(&chan->msgq_mutex); | 
|  |  | 
|  | for (q_num = 0; q_num < chan->q_nr; q_num++) { | 
|  | msgq = chan->msgq[q_num]; | 
|  | qinfo = &chan->sync_qinfo[q_num]; | 
|  | max_msg_to_process = BCM_VK_MSG_PROC_MAX_LOOP * qinfo->q_size; | 
|  |  | 
|  | rd_idx = readl_relaxed(&msgq->rd_idx); | 
|  | wr_idx = readl_relaxed(&msgq->wr_idx); | 
|  | msg_processed = 0; | 
|  | exit_loop = false; | 
|  | while ((rd_idx != wr_idx) && !exit_loop) { | 
|  | u8 src_size; | 
|  |  | 
|  | /* | 
|  | * Make a local copy and get pointer to src blk | 
|  | * The rd_idx is masked before getting the pointer to | 
|  | * avoid out of bound access in case the interface goes | 
|  | * down.  It will end up pointing to the last block in | 
|  | * the buffer, but subsequent src->size check would be | 
|  | * able to catch this. | 
|  | */ | 
|  | src = msgq_blk_addr(qinfo, rd_idx & qinfo->q_mask); | 
|  | src_size = readb(&src->size); | 
|  |  | 
|  | if ((rd_idx >= qinfo->q_size) || | 
|  | (src_size > (qinfo->q_size - 1))) { | 
|  | dev_crit(dev, | 
|  | "Invalid rd_idx 0x%x or size 0x%x => max 0x%x!", | 
|  | rd_idx, src_size, qinfo->q_size); | 
|  | bcm_vk_blk_drv_access(vk); | 
|  | bcm_vk_set_host_alert(vk, | 
|  | ERR_LOG_HOST_PCIE_DWN); | 
|  | goto idx_err; | 
|  | } | 
|  |  | 
|  | num_blks = src_size + 1; | 
|  | data = kzalloc(num_blks * VK_MSGQ_BLK_SIZE, GFP_KERNEL); | 
|  | if (data) { | 
|  | /* copy messages and linearize it */ | 
|  | dst = data; | 
|  | for (j = 0; j < num_blks; j++) { | 
|  | memcpy_fromio(dst, src, sizeof(*dst)); | 
|  |  | 
|  | dst++; | 
|  | rd_idx = msgq_inc(qinfo, rd_idx, 1); | 
|  | src = msgq_blk_addr(qinfo, rd_idx); | 
|  | } | 
|  | total++; | 
|  | } else { | 
|  | /* | 
|  | * if we could not allocate memory in kernel, | 
|  | * that is fatal. | 
|  | */ | 
|  | dev_crit(dev, "Kernel mem allocation failure.\n"); | 
|  | total = -ENOMEM; | 
|  | goto idx_err; | 
|  | } | 
|  |  | 
|  | /* flush rd pointer after a message is dequeued */ | 
|  | writel(rd_idx, &msgq->rd_idx); | 
|  |  | 
|  | /* log new info for debugging */ | 
|  | dev_dbg(dev, | 
|  | "MsgQ[%d] [Rd Wr] = [%d %d] blks extracted %d - Q = [u-%d a-%d]/%d\n", | 
|  | readl_relaxed(&msgq->num), | 
|  | rd_idx, | 
|  | wr_idx, | 
|  | num_blks, | 
|  | msgq_occupied(msgq, qinfo), | 
|  | msgq_avail_space(msgq, qinfo), | 
|  | readl_relaxed(&msgq->size)); | 
|  |  | 
|  | /* | 
|  | * No need to search if it is an autonomous one-way | 
|  | * message from driver, as these messages do not bear | 
|  | * a to_v pending item. Currently, only the shutdown | 
|  | * message falls into this category. | 
|  | */ | 
|  | if (data->function_id == VK_FID_SHUTDOWN) { | 
|  | kfree(data); | 
|  | continue; | 
|  | } | 
|  |  | 
|  | msg_id = get_msg_id(data); | 
|  | /* lookup original message in to_v direction */ | 
|  | entry = bcm_vk_dequeue_pending(vk, | 
|  | &vk->to_v_msg_chan, | 
|  | q_num, | 
|  | msg_id); | 
|  |  | 
|  | /* | 
|  | * if there is message to does not have prior send, | 
|  | * this is the location to add here | 
|  | */ | 
|  | if (entry) { | 
|  | entry->to_h_blks = num_blks; | 
|  | entry->to_h_msg = data; | 
|  | bcm_vk_append_pendq(&vk->to_h_msg_chan, | 
|  | q_num, entry); | 
|  |  | 
|  | } else { | 
|  | if (cnt++ < batch_log) | 
|  | dev_info(dev, | 
|  | "Could not find MsgId[0x%x] for resp func %d bmap %d\n", | 
|  | msg_id, data->function_id, | 
|  | test_bit(msg_id, vk->bmap)); | 
|  | kfree(data); | 
|  | } | 
|  | /* Fetch wr_idx to handle more back-to-back events */ | 
|  | wr_idx = readl(&msgq->wr_idx); | 
|  |  | 
|  | /* | 
|  | * cap the max so that even we try to handle more back-to-back events, | 
|  | * so that it won't hold CPU too long or in case rd/wr idexes are | 
|  | * corrupted which triggers infinite looping. | 
|  | */ | 
|  | if (++msg_processed >= max_msg_to_process) { | 
|  | dev_warn(dev, "Q[%d] Per loop processing exceeds %d\n", | 
|  | q_num, max_msg_to_process); | 
|  | exit_loop = true; | 
|  | } | 
|  | } | 
|  | } | 
|  | idx_err: | 
|  | mutex_unlock(&chan->msgq_mutex); | 
|  | dev_dbg(dev, "total %d drained from queues\n", total); | 
|  |  | 
|  | return total; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * init routine for all required data structures | 
|  | */ | 
|  | static int bcm_vk_data_init(struct bcm_vk *vk) | 
|  | { | 
|  | int i; | 
|  |  | 
|  | spin_lock_init(&vk->ctx_lock); | 
|  | for (i = 0; i < ARRAY_SIZE(vk->ctx); i++) { | 
|  | vk->ctx[i].in_use = false; | 
|  | vk->ctx[i].idx = i;	/* self identity */ | 
|  | vk->ctx[i].miscdev = NULL; | 
|  | } | 
|  | spin_lock_init(&vk->msg_id_lock); | 
|  | spin_lock_init(&vk->host_alert_lock); | 
|  | vk->msg_id = 0; | 
|  |  | 
|  | /* initialize hash table */ | 
|  | for (i = 0; i < VK_PID_HT_SZ; i++) | 
|  | INIT_LIST_HEAD(&vk->pid_ht[i].head); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | irqreturn_t bcm_vk_msgq_irqhandler(int irq, void *dev_id) | 
|  | { | 
|  | struct bcm_vk *vk = dev_id; | 
|  |  | 
|  | if (!bcm_vk_drv_access_ok(vk)) { | 
|  | dev_err(&vk->pdev->dev, | 
|  | "Interrupt %d received when msgq not inited\n", irq); | 
|  | goto skip_schedule_work; | 
|  | } | 
|  |  | 
|  | queue_work(vk->wq_thread, &vk->wq_work); | 
|  |  | 
|  | skip_schedule_work: | 
|  | return IRQ_HANDLED; | 
|  | } | 
|  |  | 
|  | int bcm_vk_open(struct inode *inode, struct file *p_file) | 
|  | { | 
|  | struct bcm_vk_ctx *ctx; | 
|  | struct miscdevice *miscdev = (struct miscdevice *)p_file->private_data; | 
|  | struct bcm_vk *vk = container_of(miscdev, struct bcm_vk, miscdev); | 
|  | struct device *dev = &vk->pdev->dev; | 
|  | int rc = 0; | 
|  |  | 
|  | /* get a context and set it up for file */ | 
|  | ctx = bcm_vk_get_ctx(vk, task_tgid_nr(current)); | 
|  | if (!ctx) { | 
|  | dev_err(dev, "Error allocating context\n"); | 
|  | rc = -ENOMEM; | 
|  | } else { | 
|  | /* | 
|  | * set up context and replace private data with context for | 
|  | * other methods to use.  Reason for the context is because | 
|  | * it is allowed for multiple sessions to open the sysfs, and | 
|  | * for each file open, when upper layer query the response, | 
|  | * only those that are tied to a specific open should be | 
|  | * returned.  The context->idx will be used for such binding | 
|  | */ | 
|  | ctx->miscdev = miscdev; | 
|  | p_file->private_data = ctx; | 
|  | dev_dbg(dev, "ctx_returned with idx %d, pid %d\n", | 
|  | ctx->idx, ctx->pid); | 
|  | } | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | ssize_t bcm_vk_read(struct file *p_file, | 
|  | char __user *buf, | 
|  | size_t count, | 
|  | loff_t *f_pos) | 
|  | { | 
|  | ssize_t rc = -ENOMSG; | 
|  | struct bcm_vk_ctx *ctx = p_file->private_data; | 
|  | struct bcm_vk *vk = container_of(ctx->miscdev, struct bcm_vk, | 
|  | miscdev); | 
|  | struct device *dev = &vk->pdev->dev; | 
|  | struct bcm_vk_msg_chan *chan = &vk->to_h_msg_chan; | 
|  | struct bcm_vk_wkent *entry = NULL, *iter; | 
|  | u32 q_num; | 
|  | u32 rsp_length; | 
|  |  | 
|  | if (!bcm_vk_drv_access_ok(vk)) | 
|  | return -EPERM; | 
|  |  | 
|  | dev_dbg(dev, "Buf count %zu\n", count); | 
|  |  | 
|  | /* | 
|  | * search through the pendq on the to_h chan, and return only those | 
|  | * that belongs to the same context.  Search is always from the high to | 
|  | * the low priority queues | 
|  | */ | 
|  | spin_lock(&chan->pendq_lock); | 
|  | for (q_num = 0; q_num < chan->q_nr; q_num++) { | 
|  | list_for_each_entry(iter, &chan->pendq[q_num], node) { | 
|  | if (iter->ctx->idx == ctx->idx) { | 
|  | if (count >= | 
|  | (iter->to_h_blks * VK_MSGQ_BLK_SIZE)) { | 
|  | list_del(&iter->node); | 
|  | atomic_dec(&ctx->pend_cnt); | 
|  | entry = iter; | 
|  | } else { | 
|  | /* buffer not big enough */ | 
|  | rc = -EMSGSIZE; | 
|  | } | 
|  | goto read_loop_exit; | 
|  | } | 
|  | } | 
|  | } | 
|  | read_loop_exit: | 
|  | spin_unlock(&chan->pendq_lock); | 
|  |  | 
|  | if (entry) { | 
|  | /* retrieve the passed down msg_id */ | 
|  | set_msg_id(&entry->to_h_msg[0], entry->usr_msg_id); | 
|  | rsp_length = entry->to_h_blks * VK_MSGQ_BLK_SIZE; | 
|  | if (copy_to_user(buf, entry->to_h_msg, rsp_length) == 0) | 
|  | rc = rsp_length; | 
|  |  | 
|  | bcm_vk_free_wkent(dev, entry); | 
|  | } else if (rc == -EMSGSIZE) { | 
|  | struct vk_msg_blk tmp_msg = entry->to_h_msg[0]; | 
|  |  | 
|  | /* | 
|  | * in this case, return just the first block, so | 
|  | * that app knows what size it is looking for. | 
|  | */ | 
|  | set_msg_id(&tmp_msg, entry->usr_msg_id); | 
|  | tmp_msg.size = entry->to_h_blks - 1; | 
|  | if (copy_to_user(buf, &tmp_msg, VK_MSGQ_BLK_SIZE) != 0) { | 
|  | dev_err(dev, "Error return 1st block in -EMSGSIZE\n"); | 
|  | rc = -EFAULT; | 
|  | } | 
|  | } | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | ssize_t bcm_vk_write(struct file *p_file, | 
|  | const char __user *buf, | 
|  | size_t count, | 
|  | loff_t *f_pos) | 
|  | { | 
|  | ssize_t rc; | 
|  | struct bcm_vk_ctx *ctx = p_file->private_data; | 
|  | struct bcm_vk *vk = container_of(ctx->miscdev, struct bcm_vk, | 
|  | miscdev); | 
|  | struct bcm_vk_msgq __iomem *msgq; | 
|  | struct device *dev = &vk->pdev->dev; | 
|  | struct bcm_vk_wkent *entry; | 
|  | u32 sgl_extra_blks; | 
|  | u32 q_num; | 
|  | u32 msg_size; | 
|  | u32 msgq_size; | 
|  |  | 
|  | if (!bcm_vk_drv_access_ok(vk)) | 
|  | return -EPERM; | 
|  |  | 
|  | dev_dbg(dev, "Msg count %zu\n", count); | 
|  |  | 
|  | /* first, do sanity check where count should be multiple of basic blk */ | 
|  | if (count & (VK_MSGQ_BLK_SIZE - 1)) { | 
|  | dev_err(dev, "Failure with size %zu not multiple of %zu\n", | 
|  | count, VK_MSGQ_BLK_SIZE); | 
|  | rc = -EINVAL; | 
|  | goto write_err; | 
|  | } | 
|  |  | 
|  | /* allocate the work entry + buffer for size count and inband sgl */ | 
|  | entry = kzalloc(sizeof(*entry) + count + vk->ib_sgl_size, | 
|  | GFP_KERNEL); | 
|  | if (!entry) { | 
|  | rc = -ENOMEM; | 
|  | goto write_err; | 
|  | } | 
|  |  | 
|  | /* now copy msg from user space, and then formulate the work entry */ | 
|  | if (copy_from_user(&entry->to_v_msg[0], buf, count)) { | 
|  | rc = -EFAULT; | 
|  | goto write_free_ent; | 
|  | } | 
|  |  | 
|  | entry->to_v_blks = count >> VK_MSGQ_BLK_SZ_SHIFT; | 
|  | entry->ctx = ctx; | 
|  |  | 
|  | /* do a check on the blk size which could not exceed queue space */ | 
|  | q_num = get_q_num(&entry->to_v_msg[0]); | 
|  | msgq = vk->to_v_msg_chan.msgq[q_num]; | 
|  | msgq_size = readl_relaxed(&msgq->size); | 
|  | if (entry->to_v_blks + (vk->ib_sgl_size >> VK_MSGQ_BLK_SZ_SHIFT) | 
|  | > (msgq_size - 1)) { | 
|  | dev_err(dev, "Blk size %d exceed max queue size allowed %d\n", | 
|  | entry->to_v_blks, msgq_size - 1); | 
|  | rc = -EINVAL; | 
|  | goto write_free_ent; | 
|  | } | 
|  |  | 
|  | /* Use internal message id */ | 
|  | entry->usr_msg_id = get_msg_id(&entry->to_v_msg[0]); | 
|  | rc = bcm_vk_get_msg_id(vk); | 
|  | if (rc == VK_MSG_ID_OVERFLOW) { | 
|  | dev_err(dev, "msg_id overflow\n"); | 
|  | rc = -EOVERFLOW; | 
|  | goto write_free_ent; | 
|  | } | 
|  | set_msg_id(&entry->to_v_msg[0], rc); | 
|  | ctx->q_num = q_num; | 
|  |  | 
|  | dev_dbg(dev, | 
|  | "[Q-%d]Message ctx id %d, usr_msg_id 0x%x sent msg_id 0x%x\n", | 
|  | ctx->q_num, ctx->idx, entry->usr_msg_id, | 
|  | get_msg_id(&entry->to_v_msg[0])); | 
|  |  | 
|  | if (entry->to_v_msg[0].function_id == VK_FID_TRANS_BUF) { | 
|  | /* Convert any pointers to sg list */ | 
|  | unsigned int num_planes; | 
|  | int dir; | 
|  | struct _vk_data *data; | 
|  |  | 
|  | /* | 
|  | * check if we are in reset, if so, no buffer transfer is | 
|  | * allowed and return error. | 
|  | */ | 
|  | if (vk->reset_pid) { | 
|  | dev_dbg(dev, "No Transfer allowed during reset, pid %d.\n", | 
|  | ctx->pid); | 
|  | rc = -EACCES; | 
|  | goto write_free_msgid; | 
|  | } | 
|  |  | 
|  | num_planes = entry->to_v_msg[0].cmd & VK_CMD_PLANES_MASK; | 
|  | if ((entry->to_v_msg[0].cmd & VK_CMD_MASK) == VK_CMD_DOWNLOAD) | 
|  | dir = DMA_FROM_DEVICE; | 
|  | else | 
|  | dir = DMA_TO_DEVICE; | 
|  |  | 
|  | /* Calculate vk_data location */ | 
|  | /* Go to end of the message */ | 
|  | msg_size = entry->to_v_msg[0].size; | 
|  | if (msg_size > entry->to_v_blks) { | 
|  | rc = -EMSGSIZE; | 
|  | goto write_free_msgid; | 
|  | } | 
|  |  | 
|  | data = (struct _vk_data *)&entry->to_v_msg[msg_size + 1]; | 
|  |  | 
|  | /* Now back up to the start of the pointers */ | 
|  | data -= num_planes; | 
|  |  | 
|  | /* Convert user addresses to DMA SG List */ | 
|  | rc = bcm_vk_sg_alloc(dev, entry->dma, dir, data, num_planes); | 
|  | if (rc) | 
|  | goto write_free_msgid; | 
|  |  | 
|  | atomic_inc(&ctx->dma_cnt); | 
|  | /* try to embed inband sgl */ | 
|  | sgl_extra_blks = bcm_vk_append_ib_sgl(vk, entry, data, | 
|  | num_planes); | 
|  | entry->to_v_blks += sgl_extra_blks; | 
|  | entry->to_v_msg[0].size += sgl_extra_blks; | 
|  | } else if (entry->to_v_msg[0].function_id == VK_FID_INIT && | 
|  | entry->to_v_msg[0].context_id == VK_NEW_CTX) { | 
|  | /* | 
|  | * Init happens in 2 stages, only the first stage contains the | 
|  | * pid that needs translating. | 
|  | */ | 
|  | pid_t org_pid, pid; | 
|  |  | 
|  | /* | 
|  | * translate the pid into the unique host space as user | 
|  | * may run sessions inside containers or process | 
|  | * namespaces. | 
|  | */ | 
|  | #define VK_MSG_PID_MASK 0xffffff00 | 
|  | #define VK_MSG_PID_SH   8 | 
|  | org_pid = (entry->to_v_msg[0].arg & VK_MSG_PID_MASK) | 
|  | >> VK_MSG_PID_SH; | 
|  |  | 
|  | pid = task_tgid_nr(current); | 
|  | entry->to_v_msg[0].arg = | 
|  | (entry->to_v_msg[0].arg & ~VK_MSG_PID_MASK) | | 
|  | (pid << VK_MSG_PID_SH); | 
|  | if (org_pid != pid) | 
|  | dev_dbg(dev, "In PID 0x%x(%d), converted PID 0x%x(%d)\n", | 
|  | org_pid, org_pid, pid, pid); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * store work entry to pending queue until a response is received. | 
|  | * This needs to be done before enqueuing the message | 
|  | */ | 
|  | bcm_vk_append_pendq(&vk->to_v_msg_chan, q_num, entry); | 
|  |  | 
|  | rc = bcm_to_v_msg_enqueue(vk, entry); | 
|  | if (rc) { | 
|  | dev_err(dev, "Fail to enqueue msg to to_v queue\n"); | 
|  |  | 
|  | /* remove message from pending list */ | 
|  | entry = bcm_vk_dequeue_pending | 
|  | (vk, | 
|  | &vk->to_v_msg_chan, | 
|  | q_num, | 
|  | get_msg_id(&entry->to_v_msg[0])); | 
|  | goto write_free_ent; | 
|  | } | 
|  |  | 
|  | return count; | 
|  |  | 
|  | write_free_msgid: | 
|  | bcm_vk_msgid_bitmap_clear(vk, get_msg_id(&entry->to_v_msg[0]), 1); | 
|  | write_free_ent: | 
|  | kfree(entry); | 
|  | write_err: | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | __poll_t bcm_vk_poll(struct file *p_file, struct poll_table_struct *wait) | 
|  | { | 
|  | __poll_t ret = 0; | 
|  | int cnt; | 
|  | struct bcm_vk_ctx *ctx = p_file->private_data; | 
|  | struct bcm_vk *vk = container_of(ctx->miscdev, struct bcm_vk, miscdev); | 
|  | struct device *dev = &vk->pdev->dev; | 
|  |  | 
|  | poll_wait(p_file, &ctx->rd_wq, wait); | 
|  |  | 
|  | cnt = atomic_read(&ctx->pend_cnt); | 
|  | if (cnt) { | 
|  | ret = (__force __poll_t)(POLLIN | POLLRDNORM); | 
|  | if (cnt < 0) { | 
|  | dev_err(dev, "Error cnt %d, setting back to 0", cnt); | 
|  | atomic_set(&ctx->pend_cnt, 0); | 
|  | } | 
|  | } | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | int bcm_vk_release(struct inode *inode, struct file *p_file) | 
|  | { | 
|  | int ret; | 
|  | struct bcm_vk_ctx *ctx = p_file->private_data; | 
|  | struct bcm_vk *vk = container_of(ctx->miscdev, struct bcm_vk, miscdev); | 
|  | struct device *dev = &vk->pdev->dev; | 
|  | pid_t pid = ctx->pid; | 
|  | int dma_cnt; | 
|  | unsigned long timeout, start_time; | 
|  |  | 
|  | /* | 
|  | * if there are outstanding DMA transactions, need to delay long enough | 
|  | * to ensure that the card side would have stopped touching the host buffer | 
|  | * and its SGL list.  A race condition could happen if the host app is killed | 
|  | * abruptly, eg kill -9, while some DMA transfer orders are still inflight. | 
|  | * Nothing could be done except for a delay as host side is running in a | 
|  | * completely async fashion. | 
|  | */ | 
|  | start_time = jiffies; | 
|  | timeout = start_time + msecs_to_jiffies(BCM_VK_DMA_DRAIN_MAX_MS); | 
|  | do { | 
|  | if (time_after(jiffies, timeout)) { | 
|  | dev_warn(dev, "%d dma still pending for [fd-%d] pid %d\n", | 
|  | dma_cnt, ctx->idx, pid); | 
|  | break; | 
|  | } | 
|  | dma_cnt = atomic_read(&ctx->dma_cnt); | 
|  | cpu_relax(); | 
|  | cond_resched(); | 
|  | } while (dma_cnt); | 
|  | dev_dbg(dev, "Draining for [fd-%d] pid %d - delay %d ms\n", | 
|  | ctx->idx, pid, jiffies_to_msecs(jiffies - start_time)); | 
|  |  | 
|  | bcm_vk_drain_all_pend(&vk->pdev->dev, &vk->to_v_msg_chan, ctx); | 
|  | bcm_vk_drain_all_pend(&vk->pdev->dev, &vk->to_h_msg_chan, ctx); | 
|  |  | 
|  | ret = bcm_vk_free_ctx(vk, ctx); | 
|  | if (ret == 0) | 
|  | ret = bcm_vk_handle_last_sess(vk, pid, ctx->q_num); | 
|  | else | 
|  | ret = 0; | 
|  |  | 
|  | kref_put(&vk->kref, bcm_vk_release_data); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | int bcm_vk_msg_init(struct bcm_vk *vk) | 
|  | { | 
|  | struct device *dev = &vk->pdev->dev; | 
|  | int ret; | 
|  |  | 
|  | if (bcm_vk_data_init(vk)) { | 
|  | dev_err(dev, "Error initializing internal data structures\n"); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | if (bcm_vk_msg_chan_init(&vk->to_v_msg_chan) || | 
|  | bcm_vk_msg_chan_init(&vk->to_h_msg_chan)) { | 
|  | dev_err(dev, "Error initializing communication channel\n"); | 
|  | return -EIO; | 
|  | } | 
|  |  | 
|  | /* read msgq info if ready */ | 
|  | ret = bcm_vk_sync_msgq(vk, false); | 
|  | if (ret && (ret != -EAGAIN)) { | 
|  | dev_err(dev, "Error reading comm msg Q info\n"); | 
|  | return -EIO; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | void bcm_vk_msg_remove(struct bcm_vk *vk) | 
|  | { | 
|  | bcm_vk_blk_drv_access(vk); | 
|  |  | 
|  | /* drain all pending items */ | 
|  | bcm_vk_drain_all_pend(&vk->pdev->dev, &vk->to_v_msg_chan, NULL); | 
|  | bcm_vk_drain_all_pend(&vk->pdev->dev, &vk->to_h_msg_chan, NULL); | 
|  | } | 
|  |  |