|
@@ -1,811 +0,0 @@
|
|
-/*
|
|
|
|
- * Intel MIC Platform Software Stack (MPSS)
|
|
|
|
- *
|
|
|
|
- * Copyright(c) 2013 Intel Corporation.
|
|
|
|
- *
|
|
|
|
- * This program is free software; you can redistribute it and/or modify
|
|
|
|
- * it under the terms of the GNU General Public License, version 2, as
|
|
|
|
- * published by the Free Software Foundation.
|
|
|
|
- *
|
|
|
|
- * This program is distributed in the hope that it will be useful, but
|
|
|
|
- * WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
- * General Public License for more details.
|
|
|
|
- *
|
|
|
|
- * The full GNU General Public License is included in this distribution in
|
|
|
|
- * the file called "COPYING".
|
|
|
|
- *
|
|
|
|
- * Intel MIC Host driver.
|
|
|
|
- *
|
|
|
|
- */
|
|
|
|
-#include <linux/pci.h>
|
|
|
|
-#include <linux/sched.h>
|
|
|
|
-#include <linux/uaccess.h>
|
|
|
|
-#include <linux/dmaengine.h>
|
|
|
|
-#include <linux/mic_common.h>
|
|
|
|
-#include "../common/mic_dev.h"
|
|
|
|
-#include "mic_device.h"
|
|
|
|
-#include "mic_smpt.h"
|
|
|
|
-#include "mic_virtio.h"
|
|
|
|
-
|
|
|
|
-/*
|
|
|
|
- * Size of the internal buffer used during DMA's as an intermediate buffer
|
|
|
|
- * for copy to/from user.
|
|
|
|
- */
|
|
|
|
-#define MIC_INT_DMA_BUF_SIZE PAGE_ALIGN(64 * 1024ULL)
|
|
|
|
-
|
|
|
|
-static int mic_sync_dma(struct mic_device *mdev, dma_addr_t dst,
|
|
|
|
- dma_addr_t src, size_t len)
|
|
|
|
-{
|
|
|
|
- int err = 0;
|
|
|
|
- struct dma_async_tx_descriptor *tx;
|
|
|
|
- struct dma_chan *mic_ch = mdev->dma_ch[0];
|
|
|
|
-
|
|
|
|
- if (!mic_ch) {
|
|
|
|
- err = -EBUSY;
|
|
|
|
- goto error;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- tx = mic_ch->device->device_prep_dma_memcpy(mic_ch, dst, src, len,
|
|
|
|
- DMA_PREP_FENCE);
|
|
|
|
- if (!tx) {
|
|
|
|
- err = -ENOMEM;
|
|
|
|
- goto error;
|
|
|
|
- } else {
|
|
|
|
- dma_cookie_t cookie = tx->tx_submit(tx);
|
|
|
|
-
|
|
|
|
- err = dma_submit_error(cookie);
|
|
|
|
- if (err)
|
|
|
|
- goto error;
|
|
|
|
- err = dma_sync_wait(mic_ch, cookie);
|
|
|
|
- }
|
|
|
|
-error:
|
|
|
|
- if (err)
|
|
|
|
- dev_err(&mdev->pdev->dev, "%s %d err %d\n",
|
|
|
|
- __func__, __LINE__, err);
|
|
|
|
- return err;
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-/*
|
|
|
|
- * Initiates the copies across the PCIe bus from card memory to a user
|
|
|
|
- * space buffer. When transfers are done using DMA, source/destination
|
|
|
|
- * addresses and transfer length must follow the alignment requirements of
|
|
|
|
- * the MIC DMA engine.
|
|
|
|
- */
|
|
|
|
-static int mic_virtio_copy_to_user(struct mic_vdev *mvdev, void __user *ubuf,
|
|
|
|
- size_t len, u64 daddr, size_t dlen,
|
|
|
|
- int vr_idx)
|
|
|
|
-{
|
|
|
|
- struct mic_device *mdev = mvdev->mdev;
|
|
|
|
- void __iomem *dbuf = mdev->aper.va + daddr;
|
|
|
|
- struct mic_vringh *mvr = &mvdev->mvr[vr_idx];
|
|
|
|
- size_t dma_alignment = 1 << mdev->dma_ch[0]->device->copy_align;
|
|
|
|
- size_t dma_offset;
|
|
|
|
- size_t partlen;
|
|
|
|
- int err;
|
|
|
|
-
|
|
|
|
- dma_offset = daddr - round_down(daddr, dma_alignment);
|
|
|
|
- daddr -= dma_offset;
|
|
|
|
- len += dma_offset;
|
|
|
|
-
|
|
|
|
- while (len) {
|
|
|
|
- partlen = min_t(size_t, len, MIC_INT_DMA_BUF_SIZE);
|
|
|
|
-
|
|
|
|
- err = mic_sync_dma(mdev, mvr->buf_da, daddr,
|
|
|
|
- ALIGN(partlen, dma_alignment));
|
|
|
|
- if (err)
|
|
|
|
- goto err;
|
|
|
|
-
|
|
|
|
- if (copy_to_user(ubuf, mvr->buf + dma_offset,
|
|
|
|
- partlen - dma_offset)) {
|
|
|
|
- err = -EFAULT;
|
|
|
|
- goto err;
|
|
|
|
- }
|
|
|
|
- daddr += partlen;
|
|
|
|
- ubuf += partlen;
|
|
|
|
- dbuf += partlen;
|
|
|
|
- mvdev->in_bytes_dma += partlen;
|
|
|
|
- mvdev->in_bytes += partlen;
|
|
|
|
- len -= partlen;
|
|
|
|
- dma_offset = 0;
|
|
|
|
- }
|
|
|
|
- return 0;
|
|
|
|
-err:
|
|
|
|
- dev_err(mic_dev(mvdev), "%s %d err %d\n", __func__, __LINE__, err);
|
|
|
|
- return err;
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-/*
|
|
|
|
- * Initiates copies across the PCIe bus from a user space buffer to card
|
|
|
|
- * memory. When transfers are done using DMA, source/destination addresses
|
|
|
|
- * and transfer length must follow the alignment requirements of the MIC
|
|
|
|
- * DMA engine.
|
|
|
|
- */
|
|
|
|
-static int mic_virtio_copy_from_user(struct mic_vdev *mvdev, void __user *ubuf,
|
|
|
|
- size_t len, u64 daddr, size_t dlen,
|
|
|
|
- int vr_idx)
|
|
|
|
-{
|
|
|
|
- struct mic_device *mdev = mvdev->mdev;
|
|
|
|
- void __iomem *dbuf = mdev->aper.va + daddr;
|
|
|
|
- struct mic_vringh *mvr = &mvdev->mvr[vr_idx];
|
|
|
|
- size_t dma_alignment = 1 << mdev->dma_ch[0]->device->copy_align;
|
|
|
|
- size_t partlen;
|
|
|
|
- int err;
|
|
|
|
-
|
|
|
|
- if (daddr & (dma_alignment - 1)) {
|
|
|
|
- mvdev->tx_dst_unaligned += len;
|
|
|
|
- goto memcpy;
|
|
|
|
- } else if (ALIGN(len, dma_alignment) > dlen) {
|
|
|
|
- mvdev->tx_len_unaligned += len;
|
|
|
|
- goto memcpy;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- while (len) {
|
|
|
|
- partlen = min_t(size_t, len, MIC_INT_DMA_BUF_SIZE);
|
|
|
|
-
|
|
|
|
- if (copy_from_user(mvr->buf, ubuf, partlen)) {
|
|
|
|
- err = -EFAULT;
|
|
|
|
- goto err;
|
|
|
|
- }
|
|
|
|
- err = mic_sync_dma(mdev, daddr, mvr->buf_da,
|
|
|
|
- ALIGN(partlen, dma_alignment));
|
|
|
|
- if (err)
|
|
|
|
- goto err;
|
|
|
|
- daddr += partlen;
|
|
|
|
- ubuf += partlen;
|
|
|
|
- dbuf += partlen;
|
|
|
|
- mvdev->out_bytes_dma += partlen;
|
|
|
|
- mvdev->out_bytes += partlen;
|
|
|
|
- len -= partlen;
|
|
|
|
- }
|
|
|
|
-memcpy:
|
|
|
|
- /*
|
|
|
|
- * We are copying to IO below and should ideally use something
|
|
|
|
- * like copy_from_user_toio(..) if it existed.
|
|
|
|
- */
|
|
|
|
- if (copy_from_user((void __force *)dbuf, ubuf, len)) {
|
|
|
|
- err = -EFAULT;
|
|
|
|
- goto err;
|
|
|
|
- }
|
|
|
|
- mvdev->out_bytes += len;
|
|
|
|
- return 0;
|
|
|
|
-err:
|
|
|
|
- dev_err(mic_dev(mvdev), "%s %d err %d\n", __func__, __LINE__, err);
|
|
|
|
- return err;
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-#define MIC_VRINGH_READ true
|
|
|
|
-
|
|
|
|
-/* The function to call to notify the card about added buffers */
|
|
|
|
-static void mic_notify(struct vringh *vrh)
|
|
|
|
-{
|
|
|
|
- struct mic_vringh *mvrh = container_of(vrh, struct mic_vringh, vrh);
|
|
|
|
- struct mic_vdev *mvdev = mvrh->mvdev;
|
|
|
|
- s8 db = mvdev->dc->h2c_vdev_db;
|
|
|
|
-
|
|
|
|
- if (db != -1)
|
|
|
|
- mvdev->mdev->ops->send_intr(mvdev->mdev, db);
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-/* Determine the total number of bytes consumed in a VRINGH KIOV */
|
|
|
|
-static inline u32 mic_vringh_iov_consumed(struct vringh_kiov *iov)
|
|
|
|
-{
|
|
|
|
- int i;
|
|
|
|
- u32 total = iov->consumed;
|
|
|
|
-
|
|
|
|
- for (i = 0; i < iov->i; i++)
|
|
|
|
- total += iov->iov[i].iov_len;
|
|
|
|
- return total;
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-/*
|
|
|
|
- * Traverse the VRINGH KIOV and issue the APIs to trigger the copies.
|
|
|
|
- * This API is heavily based on the vringh_iov_xfer(..) implementation
|
|
|
|
- * in vringh.c. The reason we cannot reuse vringh_iov_pull_kern(..)
|
|
|
|
- * and vringh_iov_push_kern(..) directly is because there is no
|
|
|
|
- * way to override the VRINGH xfer(..) routines as of v3.10.
|
|
|
|
- */
|
|
|
|
-static int mic_vringh_copy(struct mic_vdev *mvdev, struct vringh_kiov *iov,
|
|
|
|
- void __user *ubuf, size_t len, bool read, int vr_idx,
|
|
|
|
- size_t *out_len)
|
|
|
|
-{
|
|
|
|
- int ret = 0;
|
|
|
|
- size_t partlen, tot_len = 0;
|
|
|
|
-
|
|
|
|
- while (len && iov->i < iov->used) {
|
|
|
|
- partlen = min(iov->iov[iov->i].iov_len, len);
|
|
|
|
- if (read)
|
|
|
|
- ret = mic_virtio_copy_to_user(mvdev, ubuf, partlen,
|
|
|
|
- (u64)iov->iov[iov->i].iov_base,
|
|
|
|
- iov->iov[iov->i].iov_len,
|
|
|
|
- vr_idx);
|
|
|
|
- else
|
|
|
|
- ret = mic_virtio_copy_from_user(mvdev, ubuf, partlen,
|
|
|
|
- (u64)iov->iov[iov->i].iov_base,
|
|
|
|
- iov->iov[iov->i].iov_len,
|
|
|
|
- vr_idx);
|
|
|
|
- if (ret) {
|
|
|
|
- dev_err(mic_dev(mvdev), "%s %d err %d\n",
|
|
|
|
- __func__, __LINE__, ret);
|
|
|
|
- break;
|
|
|
|
- }
|
|
|
|
- len -= partlen;
|
|
|
|
- ubuf += partlen;
|
|
|
|
- tot_len += partlen;
|
|
|
|
- iov->consumed += partlen;
|
|
|
|
- iov->iov[iov->i].iov_len -= partlen;
|
|
|
|
- iov->iov[iov->i].iov_base += partlen;
|
|
|
|
- if (!iov->iov[iov->i].iov_len) {
|
|
|
|
- /* Fix up old iov element then increment. */
|
|
|
|
- iov->iov[iov->i].iov_len = iov->consumed;
|
|
|
|
- iov->iov[iov->i].iov_base -= iov->consumed;
|
|
|
|
-
|
|
|
|
- iov->consumed = 0;
|
|
|
|
- iov->i++;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- *out_len = tot_len;
|
|
|
|
- return ret;
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-/*
|
|
|
|
- * Use the standard VRINGH infrastructure in the kernel to fetch new
|
|
|
|
- * descriptors, initiate the copies and update the used ring.
|
|
|
|
- */
|
|
|
|
-static int _mic_virtio_copy(struct mic_vdev *mvdev,
|
|
|
|
- struct mic_copy_desc *copy)
|
|
|
|
-{
|
|
|
|
- int ret = 0;
|
|
|
|
- u32 iovcnt = copy->iovcnt;
|
|
|
|
- struct iovec iov;
|
|
|
|
- struct iovec __user *u_iov = copy->iov;
|
|
|
|
- void __user *ubuf = NULL;
|
|
|
|
- struct mic_vringh *mvr = &mvdev->mvr[copy->vr_idx];
|
|
|
|
- struct vringh_kiov *riov = &mvr->riov;
|
|
|
|
- struct vringh_kiov *wiov = &mvr->wiov;
|
|
|
|
- struct vringh *vrh = &mvr->vrh;
|
|
|
|
- u16 *head = &mvr->head;
|
|
|
|
- struct mic_vring *vr = &mvr->vring;
|
|
|
|
- size_t len = 0, out_len;
|
|
|
|
-
|
|
|
|
- copy->out_len = 0;
|
|
|
|
- /* Fetch a new IOVEC if all previous elements have been processed */
|
|
|
|
- if (riov->i == riov->used && wiov->i == wiov->used) {
|
|
|
|
- ret = vringh_getdesc_kern(vrh, riov, wiov,
|
|
|
|
- head, GFP_KERNEL);
|
|
|
|
- /* Check if there are available descriptors */
|
|
|
|
- if (ret <= 0)
|
|
|
|
- return ret;
|
|
|
|
- }
|
|
|
|
- while (iovcnt) {
|
|
|
|
- if (!len) {
|
|
|
|
- /* Copy over a new iovec from user space. */
|
|
|
|
- ret = copy_from_user(&iov, u_iov, sizeof(*u_iov));
|
|
|
|
- if (ret) {
|
|
|
|
- ret = -EINVAL;
|
|
|
|
- dev_err(mic_dev(mvdev), "%s %d err %d\n",
|
|
|
|
- __func__, __LINE__, ret);
|
|
|
|
- break;
|
|
|
|
- }
|
|
|
|
- len = iov.iov_len;
|
|
|
|
- ubuf = iov.iov_base;
|
|
|
|
- }
|
|
|
|
- /* Issue all the read descriptors first */
|
|
|
|
- ret = mic_vringh_copy(mvdev, riov, ubuf, len, MIC_VRINGH_READ,
|
|
|
|
- copy->vr_idx, &out_len);
|
|
|
|
- if (ret) {
|
|
|
|
- dev_err(mic_dev(mvdev), "%s %d err %d\n",
|
|
|
|
- __func__, __LINE__, ret);
|
|
|
|
- break;
|
|
|
|
- }
|
|
|
|
- len -= out_len;
|
|
|
|
- ubuf += out_len;
|
|
|
|
- copy->out_len += out_len;
|
|
|
|
- /* Issue the write descriptors next */
|
|
|
|
- ret = mic_vringh_copy(mvdev, wiov, ubuf, len, !MIC_VRINGH_READ,
|
|
|
|
- copy->vr_idx, &out_len);
|
|
|
|
- if (ret) {
|
|
|
|
- dev_err(mic_dev(mvdev), "%s %d err %d\n",
|
|
|
|
- __func__, __LINE__, ret);
|
|
|
|
- break;
|
|
|
|
- }
|
|
|
|
- len -= out_len;
|
|
|
|
- ubuf += out_len;
|
|
|
|
- copy->out_len += out_len;
|
|
|
|
- if (!len) {
|
|
|
|
- /* One user space iovec is now completed */
|
|
|
|
- iovcnt--;
|
|
|
|
- u_iov++;
|
|
|
|
- }
|
|
|
|
- /* Exit loop if all elements in KIOVs have been processed. */
|
|
|
|
- if (riov->i == riov->used && wiov->i == wiov->used)
|
|
|
|
- break;
|
|
|
|
- }
|
|
|
|
- /*
|
|
|
|
- * Update the used ring if a descriptor was available and some data was
|
|
|
|
- * copied in/out and the user asked for a used ring update.
|
|
|
|
- */
|
|
|
|
- if (*head != USHRT_MAX && copy->out_len && copy->update_used) {
|
|
|
|
- u32 total = 0;
|
|
|
|
-
|
|
|
|
- /* Determine the total data consumed */
|
|
|
|
- total += mic_vringh_iov_consumed(riov);
|
|
|
|
- total += mic_vringh_iov_consumed(wiov);
|
|
|
|
- vringh_complete_kern(vrh, *head, total);
|
|
|
|
- *head = USHRT_MAX;
|
|
|
|
- if (vringh_need_notify_kern(vrh) > 0)
|
|
|
|
- vringh_notify(vrh);
|
|
|
|
- vringh_kiov_cleanup(riov);
|
|
|
|
- vringh_kiov_cleanup(wiov);
|
|
|
|
- /* Update avail idx for user space */
|
|
|
|
- vr->info->avail_idx = vrh->last_avail_idx;
|
|
|
|
- }
|
|
|
|
- return ret;
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-static inline int mic_verify_copy_args(struct mic_vdev *mvdev,
|
|
|
|
- struct mic_copy_desc *copy)
|
|
|
|
-{
|
|
|
|
- if (copy->vr_idx >= mvdev->dd->num_vq) {
|
|
|
|
- dev_err(mic_dev(mvdev), "%s %d err %d\n",
|
|
|
|
- __func__, __LINE__, -EINVAL);
|
|
|
|
- return -EINVAL;
|
|
|
|
- }
|
|
|
|
- return 0;
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-/* Copy a specified number of virtio descriptors in a chain */
|
|
|
|
-int mic_virtio_copy_desc(struct mic_vdev *mvdev,
|
|
|
|
- struct mic_copy_desc *copy)
|
|
|
|
-{
|
|
|
|
- int err;
|
|
|
|
- struct mic_vringh *mvr = &mvdev->mvr[copy->vr_idx];
|
|
|
|
-
|
|
|
|
- err = mic_verify_copy_args(mvdev, copy);
|
|
|
|
- if (err)
|
|
|
|
- return err;
|
|
|
|
-
|
|
|
|
- mutex_lock(&mvr->vr_mutex);
|
|
|
|
- if (!mic_vdevup(mvdev)) {
|
|
|
|
- err = -ENODEV;
|
|
|
|
- dev_err(mic_dev(mvdev), "%s %d err %d\n",
|
|
|
|
- __func__, __LINE__, err);
|
|
|
|
- goto err;
|
|
|
|
- }
|
|
|
|
- err = _mic_virtio_copy(mvdev, copy);
|
|
|
|
- if (err) {
|
|
|
|
- dev_err(mic_dev(mvdev), "%s %d err %d\n",
|
|
|
|
- __func__, __LINE__, err);
|
|
|
|
- }
|
|
|
|
-err:
|
|
|
|
- mutex_unlock(&mvr->vr_mutex);
|
|
|
|
- return err;
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-static void mic_virtio_init_post(struct mic_vdev *mvdev)
|
|
|
|
-{
|
|
|
|
- struct mic_vqconfig *vqconfig = mic_vq_config(mvdev->dd);
|
|
|
|
- int i;
|
|
|
|
-
|
|
|
|
- for (i = 0; i < mvdev->dd->num_vq; i++) {
|
|
|
|
- if (!le64_to_cpu(vqconfig[i].used_address)) {
|
|
|
|
- dev_warn(mic_dev(mvdev), "used_address zero??\n");
|
|
|
|
- continue;
|
|
|
|
- }
|
|
|
|
- mvdev->mvr[i].vrh.vring.used =
|
|
|
|
- (void __force *)mvdev->mdev->aper.va +
|
|
|
|
- le64_to_cpu(vqconfig[i].used_address);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- mvdev->dc->used_address_updated = 0;
|
|
|
|
-
|
|
|
|
- dev_dbg(mic_dev(mvdev), "%s: device type %d LINKUP\n",
|
|
|
|
- __func__, mvdev->virtio_id);
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-static inline void mic_virtio_device_reset(struct mic_vdev *mvdev)
|
|
|
|
-{
|
|
|
|
- int i;
|
|
|
|
-
|
|
|
|
- dev_dbg(mic_dev(mvdev), "%s: status %d device type %d RESET\n",
|
|
|
|
- __func__, mvdev->dd->status, mvdev->virtio_id);
|
|
|
|
-
|
|
|
|
- for (i = 0; i < mvdev->dd->num_vq; i++)
|
|
|
|
- /*
|
|
|
|
- * Avoid lockdep false positive. The + 1 is for the mic
|
|
|
|
- * mutex which is held in the reset devices code path.
|
|
|
|
- */
|
|
|
|
- mutex_lock_nested(&mvdev->mvr[i].vr_mutex, i + 1);
|
|
|
|
-
|
|
|
|
- /* 0 status means "reset" */
|
|
|
|
- mvdev->dd->status = 0;
|
|
|
|
- mvdev->dc->vdev_reset = 0;
|
|
|
|
- mvdev->dc->host_ack = 1;
|
|
|
|
-
|
|
|
|
- for (i = 0; i < mvdev->dd->num_vq; i++) {
|
|
|
|
- struct vringh *vrh = &mvdev->mvr[i].vrh;
|
|
|
|
- mvdev->mvr[i].vring.info->avail_idx = 0;
|
|
|
|
- vrh->completed = 0;
|
|
|
|
- vrh->last_avail_idx = 0;
|
|
|
|
- vrh->last_used_idx = 0;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- for (i = 0; i < mvdev->dd->num_vq; i++)
|
|
|
|
- mutex_unlock(&mvdev->mvr[i].vr_mutex);
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-void mic_virtio_reset_devices(struct mic_device *mdev)
|
|
|
|
-{
|
|
|
|
- struct list_head *pos, *tmp;
|
|
|
|
- struct mic_vdev *mvdev;
|
|
|
|
-
|
|
|
|
- dev_dbg(&mdev->pdev->dev, "%s\n", __func__);
|
|
|
|
-
|
|
|
|
- list_for_each_safe(pos, tmp, &mdev->vdev_list) {
|
|
|
|
- mvdev = list_entry(pos, struct mic_vdev, list);
|
|
|
|
- mic_virtio_device_reset(mvdev);
|
|
|
|
- mvdev->poll_wake = 1;
|
|
|
|
- wake_up(&mvdev->waitq);
|
|
|
|
- }
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-void mic_bh_handler(struct work_struct *work)
|
|
|
|
-{
|
|
|
|
- struct mic_vdev *mvdev = container_of(work, struct mic_vdev,
|
|
|
|
- virtio_bh_work);
|
|
|
|
-
|
|
|
|
- if (mvdev->dc->used_address_updated)
|
|
|
|
- mic_virtio_init_post(mvdev);
|
|
|
|
-
|
|
|
|
- if (mvdev->dc->vdev_reset)
|
|
|
|
- mic_virtio_device_reset(mvdev);
|
|
|
|
-
|
|
|
|
- mvdev->poll_wake = 1;
|
|
|
|
- wake_up(&mvdev->waitq);
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-static irqreturn_t mic_virtio_intr_handler(int irq, void *data)
|
|
|
|
-{
|
|
|
|
- struct mic_vdev *mvdev = data;
|
|
|
|
- struct mic_device *mdev = mvdev->mdev;
|
|
|
|
-
|
|
|
|
- mdev->ops->intr_workarounds(mdev);
|
|
|
|
- schedule_work(&mvdev->virtio_bh_work);
|
|
|
|
- return IRQ_HANDLED;
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-int mic_virtio_config_change(struct mic_vdev *mvdev,
|
|
|
|
- void __user *argp)
|
|
|
|
-{
|
|
|
|
- DECLARE_WAIT_QUEUE_HEAD_ONSTACK(wake);
|
|
|
|
- int ret = 0, retry, i;
|
|
|
|
- struct mic_bootparam *bootparam = mvdev->mdev->dp;
|
|
|
|
- s8 db = bootparam->h2c_config_db;
|
|
|
|
-
|
|
|
|
- mutex_lock(&mvdev->mdev->mic_mutex);
|
|
|
|
- for (i = 0; i < mvdev->dd->num_vq; i++)
|
|
|
|
- mutex_lock_nested(&mvdev->mvr[i].vr_mutex, i + 1);
|
|
|
|
-
|
|
|
|
- if (db == -1 || mvdev->dd->type == -1) {
|
|
|
|
- ret = -EIO;
|
|
|
|
- goto exit;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if (copy_from_user(mic_vq_configspace(mvdev->dd),
|
|
|
|
- argp, mvdev->dd->config_len)) {
|
|
|
|
- dev_err(mic_dev(mvdev), "%s %d err %d\n",
|
|
|
|
- __func__, __LINE__, -EFAULT);
|
|
|
|
- ret = -EFAULT;
|
|
|
|
- goto exit;
|
|
|
|
- }
|
|
|
|
- mvdev->dc->config_change = MIC_VIRTIO_PARAM_CONFIG_CHANGED;
|
|
|
|
- mvdev->mdev->ops->send_intr(mvdev->mdev, db);
|
|
|
|
-
|
|
|
|
- for (retry = 100; retry--;) {
|
|
|
|
- ret = wait_event_timeout(wake,
|
|
|
|
- mvdev->dc->guest_ack, msecs_to_jiffies(100));
|
|
|
|
- if (ret)
|
|
|
|
- break;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- dev_dbg(mic_dev(mvdev),
|
|
|
|
- "%s %d retry: %d\n", __func__, __LINE__, retry);
|
|
|
|
- mvdev->dc->config_change = 0;
|
|
|
|
- mvdev->dc->guest_ack = 0;
|
|
|
|
-exit:
|
|
|
|
- for (i = 0; i < mvdev->dd->num_vq; i++)
|
|
|
|
- mutex_unlock(&mvdev->mvr[i].vr_mutex);
|
|
|
|
- mutex_unlock(&mvdev->mdev->mic_mutex);
|
|
|
|
- return ret;
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-static int mic_copy_dp_entry(struct mic_vdev *mvdev,
|
|
|
|
- void __user *argp,
|
|
|
|
- __u8 *type,
|
|
|
|
- struct mic_device_desc **devpage)
|
|
|
|
-{
|
|
|
|
- struct mic_device *mdev = mvdev->mdev;
|
|
|
|
- struct mic_device_desc dd, *dd_config, *devp;
|
|
|
|
- struct mic_vqconfig *vqconfig;
|
|
|
|
- int ret = 0, i;
|
|
|
|
- bool slot_found = false;
|
|
|
|
-
|
|
|
|
- if (copy_from_user(&dd, argp, sizeof(dd))) {
|
|
|
|
- dev_err(mic_dev(mvdev), "%s %d err %d\n",
|
|
|
|
- __func__, __LINE__, -EFAULT);
|
|
|
|
- return -EFAULT;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if (mic_aligned_desc_size(&dd) > MIC_MAX_DESC_BLK_SIZE ||
|
|
|
|
- dd.num_vq > MIC_MAX_VRINGS) {
|
|
|
|
- dev_err(mic_dev(mvdev), "%s %d err %d\n",
|
|
|
|
- __func__, __LINE__, -EINVAL);
|
|
|
|
- return -EINVAL;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- dd_config = kmalloc(mic_desc_size(&dd), GFP_KERNEL);
|
|
|
|
- if (dd_config == NULL) {
|
|
|
|
- dev_err(mic_dev(mvdev), "%s %d err %d\n",
|
|
|
|
- __func__, __LINE__, -ENOMEM);
|
|
|
|
- return -ENOMEM;
|
|
|
|
- }
|
|
|
|
- if (copy_from_user(dd_config, argp, mic_desc_size(&dd))) {
|
|
|
|
- ret = -EFAULT;
|
|
|
|
- dev_err(mic_dev(mvdev), "%s %d err %d\n",
|
|
|
|
- __func__, __LINE__, ret);
|
|
|
|
- goto exit;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- vqconfig = mic_vq_config(dd_config);
|
|
|
|
- for (i = 0; i < dd.num_vq; i++) {
|
|
|
|
- if (le16_to_cpu(vqconfig[i].num) > MIC_MAX_VRING_ENTRIES) {
|
|
|
|
- ret = -EINVAL;
|
|
|
|
- dev_err(mic_dev(mvdev), "%s %d err %d\n",
|
|
|
|
- __func__, __LINE__, ret);
|
|
|
|
- goto exit;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /* Find the first free device page entry */
|
|
|
|
- for (i = sizeof(struct mic_bootparam);
|
|
|
|
- i < MIC_DP_SIZE - mic_total_desc_size(dd_config);
|
|
|
|
- i += mic_total_desc_size(devp)) {
|
|
|
|
- devp = mdev->dp + i;
|
|
|
|
- if (devp->type == 0 || devp->type == -1) {
|
|
|
|
- slot_found = true;
|
|
|
|
- break;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- if (!slot_found) {
|
|
|
|
- ret = -EINVAL;
|
|
|
|
- dev_err(mic_dev(mvdev), "%s %d err %d\n",
|
|
|
|
- __func__, __LINE__, ret);
|
|
|
|
- goto exit;
|
|
|
|
- }
|
|
|
|
- /*
|
|
|
|
- * Save off the type before doing the memcpy. Type will be set in the
|
|
|
|
- * end after completing all initialization for the new device.
|
|
|
|
- */
|
|
|
|
- *type = dd_config->type;
|
|
|
|
- dd_config->type = 0;
|
|
|
|
- memcpy(devp, dd_config, mic_desc_size(dd_config));
|
|
|
|
-
|
|
|
|
- *devpage = devp;
|
|
|
|
-exit:
|
|
|
|
- kfree(dd_config);
|
|
|
|
- return ret;
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-static void mic_init_device_ctrl(struct mic_vdev *mvdev,
|
|
|
|
- struct mic_device_desc *devpage)
|
|
|
|
-{
|
|
|
|
- struct mic_device_ctrl *dc;
|
|
|
|
-
|
|
|
|
- dc = (void *)devpage + mic_aligned_desc_size(devpage);
|
|
|
|
-
|
|
|
|
- dc->config_change = 0;
|
|
|
|
- dc->guest_ack = 0;
|
|
|
|
- dc->vdev_reset = 0;
|
|
|
|
- dc->host_ack = 0;
|
|
|
|
- dc->used_address_updated = 0;
|
|
|
|
- dc->c2h_vdev_db = -1;
|
|
|
|
- dc->h2c_vdev_db = -1;
|
|
|
|
- mvdev->dc = dc;
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-int mic_virtio_add_device(struct mic_vdev *mvdev,
|
|
|
|
- void __user *argp)
|
|
|
|
-{
|
|
|
|
- struct mic_device *mdev = mvdev->mdev;
|
|
|
|
- struct mic_device_desc *dd = NULL;
|
|
|
|
- struct mic_vqconfig *vqconfig;
|
|
|
|
- int vr_size, i, j, ret;
|
|
|
|
- u8 type = 0;
|
|
|
|
- s8 db;
|
|
|
|
- char irqname[10];
|
|
|
|
- struct mic_bootparam *bootparam = mdev->dp;
|
|
|
|
- u16 num;
|
|
|
|
- dma_addr_t vr_addr;
|
|
|
|
-
|
|
|
|
- mutex_lock(&mdev->mic_mutex);
|
|
|
|
-
|
|
|
|
- ret = mic_copy_dp_entry(mvdev, argp, &type, &dd);
|
|
|
|
- if (ret) {
|
|
|
|
- mutex_unlock(&mdev->mic_mutex);
|
|
|
|
- return ret;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- mic_init_device_ctrl(mvdev, dd);
|
|
|
|
-
|
|
|
|
- mvdev->dd = dd;
|
|
|
|
- mvdev->virtio_id = type;
|
|
|
|
- vqconfig = mic_vq_config(dd);
|
|
|
|
- INIT_WORK(&mvdev->virtio_bh_work, mic_bh_handler);
|
|
|
|
-
|
|
|
|
- for (i = 0; i < dd->num_vq; i++) {
|
|
|
|
- struct mic_vringh *mvr = &mvdev->mvr[i];
|
|
|
|
- struct mic_vring *vr = &mvdev->mvr[i].vring;
|
|
|
|
- num = le16_to_cpu(vqconfig[i].num);
|
|
|
|
- mutex_init(&mvr->vr_mutex);
|
|
|
|
- vr_size = PAGE_ALIGN(vring_size(num, MIC_VIRTIO_RING_ALIGN) +
|
|
|
|
- sizeof(struct _mic_vring_info));
|
|
|
|
- vr->va = (void *)
|
|
|
|
- __get_free_pages(GFP_KERNEL | __GFP_ZERO,
|
|
|
|
- get_order(vr_size));
|
|
|
|
- if (!vr->va) {
|
|
|
|
- ret = -ENOMEM;
|
|
|
|
- dev_err(mic_dev(mvdev), "%s %d err %d\n",
|
|
|
|
- __func__, __LINE__, ret);
|
|
|
|
- goto err;
|
|
|
|
- }
|
|
|
|
- vr->len = vr_size;
|
|
|
|
- vr->info = vr->va + vring_size(num, MIC_VIRTIO_RING_ALIGN);
|
|
|
|
- vr->info->magic = cpu_to_le32(MIC_MAGIC + mvdev->virtio_id + i);
|
|
|
|
- vr_addr = mic_map_single(mdev, vr->va, vr_size);
|
|
|
|
- if (mic_map_error(vr_addr)) {
|
|
|
|
- free_pages((unsigned long)vr->va, get_order(vr_size));
|
|
|
|
- ret = -ENOMEM;
|
|
|
|
- dev_err(mic_dev(mvdev), "%s %d err %d\n",
|
|
|
|
- __func__, __LINE__, ret);
|
|
|
|
- goto err;
|
|
|
|
- }
|
|
|
|
- vqconfig[i].address = cpu_to_le64(vr_addr);
|
|
|
|
-
|
|
|
|
- vring_init(&vr->vr, num, vr->va, MIC_VIRTIO_RING_ALIGN);
|
|
|
|
- ret = vringh_init_kern(&mvr->vrh,
|
|
|
|
- *(u32 *)mic_vq_features(mvdev->dd), num, false,
|
|
|
|
- vr->vr.desc, vr->vr.avail, vr->vr.used);
|
|
|
|
- if (ret) {
|
|
|
|
- dev_err(mic_dev(mvdev), "%s %d err %d\n",
|
|
|
|
- __func__, __LINE__, ret);
|
|
|
|
- goto err;
|
|
|
|
- }
|
|
|
|
- vringh_kiov_init(&mvr->riov, NULL, 0);
|
|
|
|
- vringh_kiov_init(&mvr->wiov, NULL, 0);
|
|
|
|
- mvr->head = USHRT_MAX;
|
|
|
|
- mvr->mvdev = mvdev;
|
|
|
|
- mvr->vrh.notify = mic_notify;
|
|
|
|
- dev_dbg(&mdev->pdev->dev,
|
|
|
|
- "%s %d index %d va %p info %p vr_size 0x%x\n",
|
|
|
|
- __func__, __LINE__, i, vr->va, vr->info, vr_size);
|
|
|
|
- mvr->buf = (void *)__get_free_pages(GFP_KERNEL,
|
|
|
|
- get_order(MIC_INT_DMA_BUF_SIZE));
|
|
|
|
- mvr->buf_da = mic_map_single(mvdev->mdev, mvr->buf,
|
|
|
|
- MIC_INT_DMA_BUF_SIZE);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- snprintf(irqname, sizeof(irqname), "mic%dvirtio%d", mdev->id,
|
|
|
|
- mvdev->virtio_id);
|
|
|
|
- mvdev->virtio_db = mic_next_db(mdev);
|
|
|
|
- mvdev->virtio_cookie = mic_request_threaded_irq(mdev,
|
|
|
|
- mic_virtio_intr_handler,
|
|
|
|
- NULL, irqname, mvdev,
|
|
|
|
- mvdev->virtio_db, MIC_INTR_DB);
|
|
|
|
- if (IS_ERR(mvdev->virtio_cookie)) {
|
|
|
|
- ret = PTR_ERR(mvdev->virtio_cookie);
|
|
|
|
- dev_dbg(&mdev->pdev->dev, "request irq failed\n");
|
|
|
|
- goto err;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- mvdev->dc->c2h_vdev_db = mvdev->virtio_db;
|
|
|
|
-
|
|
|
|
- list_add_tail(&mvdev->list, &mdev->vdev_list);
|
|
|
|
- /*
|
|
|
|
- * Order the type update with previous stores. This write barrier
|
|
|
|
- * is paired with the corresponding read barrier before the uncached
|
|
|
|
- * system memory read of the type, on the card while scanning the
|
|
|
|
- * device page.
|
|
|
|
- */
|
|
|
|
- smp_wmb();
|
|
|
|
- dd->type = type;
|
|
|
|
-
|
|
|
|
- dev_dbg(&mdev->pdev->dev, "Added virtio device id %d\n", dd->type);
|
|
|
|
-
|
|
|
|
- db = bootparam->h2c_config_db;
|
|
|
|
- if (db != -1)
|
|
|
|
- mdev->ops->send_intr(mdev, db);
|
|
|
|
- mutex_unlock(&mdev->mic_mutex);
|
|
|
|
- return 0;
|
|
|
|
-err:
|
|
|
|
- vqconfig = mic_vq_config(dd);
|
|
|
|
- for (j = 0; j < i; j++) {
|
|
|
|
- struct mic_vringh *mvr = &mvdev->mvr[j];
|
|
|
|
- mic_unmap_single(mdev, le64_to_cpu(vqconfig[j].address),
|
|
|
|
- mvr->vring.len);
|
|
|
|
- free_pages((unsigned long)mvr->vring.va,
|
|
|
|
- get_order(mvr->vring.len));
|
|
|
|
- }
|
|
|
|
- mutex_unlock(&mdev->mic_mutex);
|
|
|
|
- return ret;
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-void mic_virtio_del_device(struct mic_vdev *mvdev)
|
|
|
|
-{
|
|
|
|
- struct list_head *pos, *tmp;
|
|
|
|
- struct mic_vdev *tmp_mvdev;
|
|
|
|
- struct mic_device *mdev = mvdev->mdev;
|
|
|
|
- DECLARE_WAIT_QUEUE_HEAD_ONSTACK(wake);
|
|
|
|
- int i, ret, retry;
|
|
|
|
- struct mic_vqconfig *vqconfig;
|
|
|
|
- struct mic_bootparam *bootparam = mdev->dp;
|
|
|
|
- s8 db;
|
|
|
|
-
|
|
|
|
- mutex_lock(&mdev->mic_mutex);
|
|
|
|
- db = bootparam->h2c_config_db;
|
|
|
|
- if (db == -1)
|
|
|
|
- goto skip_hot_remove;
|
|
|
|
- dev_dbg(&mdev->pdev->dev,
|
|
|
|
- "Requesting hot remove id %d\n", mvdev->virtio_id);
|
|
|
|
- mvdev->dc->config_change = MIC_VIRTIO_PARAM_DEV_REMOVE;
|
|
|
|
- mdev->ops->send_intr(mdev, db);
|
|
|
|
- for (retry = 100; retry--;) {
|
|
|
|
- ret = wait_event_timeout(wake,
|
|
|
|
- mvdev->dc->guest_ack, msecs_to_jiffies(100));
|
|
|
|
- if (ret)
|
|
|
|
- break;
|
|
|
|
- }
|
|
|
|
- dev_dbg(&mdev->pdev->dev,
|
|
|
|
- "Device id %d config_change %d guest_ack %d retry %d\n",
|
|
|
|
- mvdev->virtio_id, mvdev->dc->config_change,
|
|
|
|
- mvdev->dc->guest_ack, retry);
|
|
|
|
- mvdev->dc->config_change = 0;
|
|
|
|
- mvdev->dc->guest_ack = 0;
|
|
|
|
-skip_hot_remove:
|
|
|
|
- mic_free_irq(mdev, mvdev->virtio_cookie, mvdev);
|
|
|
|
- flush_work(&mvdev->virtio_bh_work);
|
|
|
|
- vqconfig = mic_vq_config(mvdev->dd);
|
|
|
|
- for (i = 0; i < mvdev->dd->num_vq; i++) {
|
|
|
|
- struct mic_vringh *mvr = &mvdev->mvr[i];
|
|
|
|
-
|
|
|
|
- mic_unmap_single(mvdev->mdev, mvr->buf_da,
|
|
|
|
- MIC_INT_DMA_BUF_SIZE);
|
|
|
|
- free_pages((unsigned long)mvr->buf,
|
|
|
|
- get_order(MIC_INT_DMA_BUF_SIZE));
|
|
|
|
- vringh_kiov_cleanup(&mvr->riov);
|
|
|
|
- vringh_kiov_cleanup(&mvr->wiov);
|
|
|
|
- mic_unmap_single(mdev, le64_to_cpu(vqconfig[i].address),
|
|
|
|
- mvr->vring.len);
|
|
|
|
- free_pages((unsigned long)mvr->vring.va,
|
|
|
|
- get_order(mvr->vring.len));
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- list_for_each_safe(pos, tmp, &mdev->vdev_list) {
|
|
|
|
- tmp_mvdev = list_entry(pos, struct mic_vdev, list);
|
|
|
|
- if (tmp_mvdev == mvdev) {
|
|
|
|
- list_del(pos);
|
|
|
|
- dev_dbg(&mdev->pdev->dev,
|
|
|
|
- "Removing virtio device id %d\n",
|
|
|
|
- mvdev->virtio_id);
|
|
|
|
- break;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- /*
|
|
|
|
- * Order the type update with previous stores. This write barrier
|
|
|
|
- * is paired with the corresponding read barrier before the uncached
|
|
|
|
- * system memory read of the type, on the card while scanning the
|
|
|
|
- * device page.
|
|
|
|
- */
|
|
|
|
- smp_wmb();
|
|
|
|
- mvdev->dd->type = -1;
|
|
|
|
- mutex_unlock(&mdev->mic_mutex);
|
|
|
|
-}
|
|
|