mirror of https://gitee.com/openkylin/linux.git
223 lines
5.1 KiB
C
223 lines
5.1 KiB
C
/*
|
|
* 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/poll.h>
|
|
#include <linux/pci.h>
|
|
|
|
#include <linux/mic_common.h>
|
|
#include "../common/mic_dev.h"
|
|
#include "mic_device.h"
|
|
#include "mic_fops.h"
|
|
#include "mic_virtio.h"
|
|
|
|
int mic_open(struct inode *inode, struct file *f)
|
|
{
|
|
struct mic_vdev *mvdev;
|
|
struct mic_device *mdev = container_of(inode->i_cdev,
|
|
struct mic_device, cdev);
|
|
|
|
mvdev = kzalloc(sizeof(*mvdev), GFP_KERNEL);
|
|
if (!mvdev)
|
|
return -ENOMEM;
|
|
|
|
init_waitqueue_head(&mvdev->waitq);
|
|
INIT_LIST_HEAD(&mvdev->list);
|
|
mvdev->mdev = mdev;
|
|
mvdev->virtio_id = -1;
|
|
|
|
f->private_data = mvdev;
|
|
return 0;
|
|
}
|
|
|
|
int mic_release(struct inode *inode, struct file *f)
|
|
{
|
|
struct mic_vdev *mvdev = (struct mic_vdev *)f->private_data;
|
|
|
|
if (-1 != mvdev->virtio_id)
|
|
mic_virtio_del_device(mvdev);
|
|
f->private_data = NULL;
|
|
kfree(mvdev);
|
|
return 0;
|
|
}
|
|
|
|
long mic_ioctl(struct file *f, unsigned int cmd, unsigned long arg)
|
|
{
|
|
struct mic_vdev *mvdev = (struct mic_vdev *)f->private_data;
|
|
void __user *argp = (void __user *)arg;
|
|
int ret;
|
|
|
|
switch (cmd) {
|
|
case MIC_VIRTIO_ADD_DEVICE:
|
|
{
|
|
ret = mic_virtio_add_device(mvdev, argp);
|
|
if (ret < 0) {
|
|
dev_err(mic_dev(mvdev),
|
|
"%s %d errno ret %d\n",
|
|
__func__, __LINE__, ret);
|
|
return ret;
|
|
}
|
|
break;
|
|
}
|
|
case MIC_VIRTIO_COPY_DESC:
|
|
{
|
|
struct mic_copy_desc copy;
|
|
|
|
ret = mic_vdev_inited(mvdev);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (copy_from_user(©, argp, sizeof(copy)))
|
|
return -EFAULT;
|
|
|
|
dev_dbg(mic_dev(mvdev),
|
|
"%s %d === iovcnt 0x%x vr_idx 0x%x update_used %d\n",
|
|
__func__, __LINE__, copy.iovcnt, copy.vr_idx,
|
|
copy.update_used);
|
|
|
|
ret = mic_virtio_copy_desc(mvdev, ©);
|
|
if (ret < 0) {
|
|
dev_err(mic_dev(mvdev),
|
|
"%s %d errno ret %d\n",
|
|
__func__, __LINE__, ret);
|
|
return ret;
|
|
}
|
|
if (copy_to_user(
|
|
&((struct mic_copy_desc __user *)argp)->out_len,
|
|
©.out_len, sizeof(copy.out_len))) {
|
|
dev_err(mic_dev(mvdev), "%s %d errno ret %d\n",
|
|
__func__, __LINE__, -EFAULT);
|
|
return -EFAULT;
|
|
}
|
|
break;
|
|
}
|
|
case MIC_VIRTIO_CONFIG_CHANGE:
|
|
{
|
|
ret = mic_vdev_inited(mvdev);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = mic_virtio_config_change(mvdev, argp);
|
|
if (ret < 0) {
|
|
dev_err(mic_dev(mvdev),
|
|
"%s %d errno ret %d\n",
|
|
__func__, __LINE__, ret);
|
|
return ret;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
return -ENOIOCTLCMD;
|
|
};
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* We return POLLIN | POLLOUT from poll when new buffers are enqueued, and
|
|
* not when previously enqueued buffers may be available. This means that
|
|
* in the card->host (TX) path, when userspace is unblocked by poll it
|
|
* must drain all available descriptors or it can stall.
|
|
*/
|
|
unsigned int mic_poll(struct file *f, poll_table *wait)
|
|
{
|
|
struct mic_vdev *mvdev = (struct mic_vdev *)f->private_data;
|
|
int mask = 0;
|
|
|
|
poll_wait(f, &mvdev->waitq, wait);
|
|
|
|
if (mic_vdev_inited(mvdev)) {
|
|
mask = POLLERR;
|
|
} else if (mvdev->poll_wake) {
|
|
mvdev->poll_wake = 0;
|
|
mask = POLLIN | POLLOUT;
|
|
}
|
|
|
|
return mask;
|
|
}
|
|
|
|
static inline int
|
|
mic_query_offset(struct mic_vdev *mvdev, unsigned long offset,
|
|
unsigned long *size, unsigned long *pa)
|
|
{
|
|
struct mic_device *mdev = mvdev->mdev;
|
|
unsigned long start = MIC_DP_SIZE;
|
|
int i;
|
|
|
|
/*
|
|
* MMAP interface is as follows:
|
|
* offset region
|
|
* 0x0 virtio device_page
|
|
* 0x1000 first vring
|
|
* 0x1000 + size of 1st vring second vring
|
|
* ....
|
|
*/
|
|
if (!offset) {
|
|
*pa = virt_to_phys(mdev->dp);
|
|
*size = MIC_DP_SIZE;
|
|
return 0;
|
|
}
|
|
|
|
for (i = 0; i < mvdev->dd->num_vq; i++) {
|
|
struct mic_vringh *mvr = &mvdev->mvr[i];
|
|
if (offset == start) {
|
|
*pa = virt_to_phys(mvr->vring.va);
|
|
*size = mvr->vring.len;
|
|
return 0;
|
|
}
|
|
start += mvr->vring.len;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* Maps the device page and virtio rings to user space for readonly access.
|
|
*/
|
|
int
|
|
mic_mmap(struct file *f, struct vm_area_struct *vma)
|
|
{
|
|
struct mic_vdev *mvdev = (struct mic_vdev *)f->private_data;
|
|
unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
|
|
unsigned long pa, size = vma->vm_end - vma->vm_start, size_rem = size;
|
|
int i, err;
|
|
|
|
err = mic_vdev_inited(mvdev);
|
|
if (err)
|
|
return err;
|
|
|
|
if (vma->vm_flags & VM_WRITE)
|
|
return -EACCES;
|
|
|
|
while (size_rem) {
|
|
i = mic_query_offset(mvdev, offset, &size, &pa);
|
|
if (i < 0)
|
|
return -EINVAL;
|
|
err = remap_pfn_range(vma, vma->vm_start + offset,
|
|
pa >> PAGE_SHIFT, size, vma->vm_page_prot);
|
|
if (err)
|
|
return err;
|
|
dev_dbg(mic_dev(mvdev),
|
|
"%s %d type %d size 0x%lx off 0x%lx pa 0x%lx vma 0x%lx\n",
|
|
__func__, __LINE__, mvdev->virtio_id, size, offset,
|
|
pa, vma->vm_start + offset);
|
|
size_rem -= size;
|
|
offset += size;
|
|
}
|
|
return 0;
|
|
}
|