mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2024-12-29 09:13:38 +00:00
3c69d52e3e
Prepare for the coming implementation by GCC and Clang of the __counted_by attribute. Flexible array members annotated with __counted_by can have their accesses bounds-checked at run-time checking via CONFIG_UBSAN_BOUNDS (for array indexing) and CONFIG_FORTIFY_SOURCE (for strcpy/memcpy-family functions). As found with Coccinelle[1], add __counted_by for struct bcm_vk_wkent. Additionally, since the element count member must be set before accessing the annotated flexible array member, move its initialization earlier. [1] https://github.com/kees/kernel-tools/blob/trunk/coccinelle/examples/counted_by.cocci Cc: Scott Branden <scott.branden@broadcom.com> Cc: Broadcom internal kernel review list <bcm-kernel-feedback-list@broadcom.com> Cc: Arnd Bergmann <arnd@arndb.de> Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org> Signed-off-by: Kees Cook <keescook@chromium.org> Reviewed-by: "Gustavo A. R. Silva" <gustavoars@kernel.org> Link: https://lore.kernel.org/r/20230922175057.work.558-kees@kernel.org Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
1353 lines
34 KiB
C
1353 lines
34 KiB
C
// 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);
|
|
}
|
|
|