mirror of
https://github.com/AetherDroid/android_kernel_samsung_on5xelte.git
synced 2025-09-09 01:28:05 -04:00
Fixed MTP to work with TWRP
This commit is contained in:
commit
f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions
14
drivers/misc/mic/host/Makefile
Normal file
14
drivers/misc/mic/host/Makefile
Normal file
|
@ -0,0 +1,14 @@
|
|||
#
|
||||
# Makefile - Intel MIC Linux driver.
|
||||
# Copyright(c) 2013, Intel Corporation.
|
||||
#
|
||||
obj-$(CONFIG_INTEL_MIC_HOST) += mic_host.o
|
||||
mic_host-objs := mic_main.o
|
||||
mic_host-objs += mic_x100.o
|
||||
mic_host-objs += mic_sysfs.o
|
||||
mic_host-objs += mic_smpt.o
|
||||
mic_host-objs += mic_intr.o
|
||||
mic_host-objs += mic_boot.o
|
||||
mic_host-objs += mic_debugfs.o
|
||||
mic_host-objs += mic_fops.o
|
||||
mic_host-objs += mic_virtio.o
|
381
drivers/misc/mic/host/mic_boot.c
Normal file
381
drivers/misc/mic/host/mic_boot.c
Normal file
|
@ -0,0 +1,381 @@
|
|||
/*
|
||||
* 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/delay.h>
|
||||
#include <linux/firmware.h>
|
||||
#include <linux/pci.h>
|
||||
|
||||
#include <linux/mic_common.h>
|
||||
#include <linux/mic_bus.h>
|
||||
#include "../common/mic_dev.h"
|
||||
#include "mic_device.h"
|
||||
#include "mic_smpt.h"
|
||||
#include "mic_virtio.h"
|
||||
|
||||
static inline struct mic_device *mbdev_to_mdev(struct mbus_device *mbdev)
|
||||
{
|
||||
return dev_get_drvdata(mbdev->dev.parent);
|
||||
}
|
||||
|
||||
static dma_addr_t
|
||||
mic_dma_map_page(struct device *dev, struct page *page,
|
||||
unsigned long offset, size_t size, enum dma_data_direction dir,
|
||||
struct dma_attrs *attrs)
|
||||
{
|
||||
void *va = phys_to_virt(page_to_phys(page)) + offset;
|
||||
struct mic_device *mdev = dev_get_drvdata(dev->parent);
|
||||
|
||||
return mic_map_single(mdev, va, size);
|
||||
}
|
||||
|
||||
static void
|
||||
mic_dma_unmap_page(struct device *dev, dma_addr_t dma_addr,
|
||||
size_t size, enum dma_data_direction dir,
|
||||
struct dma_attrs *attrs)
|
||||
{
|
||||
struct mic_device *mdev = dev_get_drvdata(dev->parent);
|
||||
mic_unmap_single(mdev, dma_addr, size);
|
||||
}
|
||||
|
||||
static struct dma_map_ops mic_dma_ops = {
|
||||
.map_page = mic_dma_map_page,
|
||||
.unmap_page = mic_dma_unmap_page,
|
||||
};
|
||||
|
||||
static struct mic_irq *
|
||||
_mic_request_threaded_irq(struct mbus_device *mbdev,
|
||||
irq_handler_t handler, irq_handler_t thread_fn,
|
||||
const char *name, void *data, int intr_src)
|
||||
{
|
||||
return mic_request_threaded_irq(mbdev_to_mdev(mbdev), handler,
|
||||
thread_fn, name, data,
|
||||
intr_src, MIC_INTR_DMA);
|
||||
}
|
||||
|
||||
static void _mic_free_irq(struct mbus_device *mbdev,
|
||||
struct mic_irq *cookie, void *data)
|
||||
{
|
||||
return mic_free_irq(mbdev_to_mdev(mbdev), cookie, data);
|
||||
}
|
||||
|
||||
static void _mic_ack_interrupt(struct mbus_device *mbdev, int num)
|
||||
{
|
||||
struct mic_device *mdev = mbdev_to_mdev(mbdev);
|
||||
mdev->ops->intr_workarounds(mdev);
|
||||
}
|
||||
|
||||
static struct mbus_hw_ops mbus_hw_ops = {
|
||||
.request_threaded_irq = _mic_request_threaded_irq,
|
||||
.free_irq = _mic_free_irq,
|
||||
.ack_interrupt = _mic_ack_interrupt,
|
||||
};
|
||||
|
||||
/**
|
||||
* mic_reset - Reset the MIC device.
|
||||
* @mdev: pointer to mic_device instance
|
||||
*/
|
||||
static void mic_reset(struct mic_device *mdev)
|
||||
{
|
||||
int i;
|
||||
|
||||
#define MIC_RESET_TO (45)
|
||||
|
||||
reinit_completion(&mdev->reset_wait);
|
||||
mdev->ops->reset_fw_ready(mdev);
|
||||
mdev->ops->reset(mdev);
|
||||
|
||||
for (i = 0; i < MIC_RESET_TO; i++) {
|
||||
if (mdev->ops->is_fw_ready(mdev))
|
||||
goto done;
|
||||
/*
|
||||
* Resets typically take 10s of seconds to complete.
|
||||
* Since an MMIO read is required to check if the
|
||||
* firmware is ready or not, a 1 second delay works nicely.
|
||||
*/
|
||||
msleep(1000);
|
||||
}
|
||||
mic_set_state(mdev, MIC_RESET_FAILED);
|
||||
done:
|
||||
complete_all(&mdev->reset_wait);
|
||||
}
|
||||
|
||||
/* Initialize the MIC bootparams */
|
||||
void mic_bootparam_init(struct mic_device *mdev)
|
||||
{
|
||||
struct mic_bootparam *bootparam = mdev->dp;
|
||||
|
||||
bootparam->magic = cpu_to_le32(MIC_MAGIC);
|
||||
bootparam->c2h_shutdown_db = mdev->shutdown_db;
|
||||
bootparam->h2c_shutdown_db = -1;
|
||||
bootparam->h2c_config_db = -1;
|
||||
bootparam->shutdown_status = 0;
|
||||
bootparam->shutdown_card = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* mic_start - Start the MIC.
|
||||
* @mdev: pointer to mic_device instance
|
||||
* @buf: buffer containing boot string including firmware/ramdisk path.
|
||||
*
|
||||
* This function prepares an MIC for boot and initiates boot.
|
||||
* RETURNS: An appropriate -ERRNO error value on error, or zero for success.
|
||||
*/
|
||||
int mic_start(struct mic_device *mdev, const char *buf)
|
||||
{
|
||||
int rc;
|
||||
mutex_lock(&mdev->mic_mutex);
|
||||
retry:
|
||||
if (MIC_OFFLINE != mdev->state) {
|
||||
rc = -EINVAL;
|
||||
goto unlock_ret;
|
||||
}
|
||||
if (!mdev->ops->is_fw_ready(mdev)) {
|
||||
mic_reset(mdev);
|
||||
/*
|
||||
* The state will either be MIC_OFFLINE if the reset succeeded
|
||||
* or MIC_RESET_FAILED if the firmware reset failed.
|
||||
*/
|
||||
goto retry;
|
||||
}
|
||||
mdev->dma_mbdev = mbus_register_device(mdev->sdev->parent,
|
||||
MBUS_DEV_DMA_HOST, &mic_dma_ops,
|
||||
&mbus_hw_ops, mdev->mmio.va);
|
||||
if (IS_ERR(mdev->dma_mbdev)) {
|
||||
rc = PTR_ERR(mdev->dma_mbdev);
|
||||
goto unlock_ret;
|
||||
}
|
||||
mdev->dma_ch = mic_request_dma_chan(mdev);
|
||||
if (!mdev->dma_ch) {
|
||||
rc = -ENXIO;
|
||||
goto dma_remove;
|
||||
}
|
||||
rc = mdev->ops->load_mic_fw(mdev, buf);
|
||||
if (rc)
|
||||
goto dma_release;
|
||||
mic_smpt_restore(mdev);
|
||||
mic_intr_restore(mdev);
|
||||
mdev->intr_ops->enable_interrupts(mdev);
|
||||
mdev->ops->write_spad(mdev, MIC_DPLO_SPAD, mdev->dp_dma_addr);
|
||||
mdev->ops->write_spad(mdev, MIC_DPHI_SPAD, mdev->dp_dma_addr >> 32);
|
||||
mdev->ops->send_firmware_intr(mdev);
|
||||
mic_set_state(mdev, MIC_ONLINE);
|
||||
goto unlock_ret;
|
||||
dma_release:
|
||||
dma_release_channel(mdev->dma_ch);
|
||||
dma_remove:
|
||||
mbus_unregister_device(mdev->dma_mbdev);
|
||||
unlock_ret:
|
||||
mutex_unlock(&mdev->mic_mutex);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* mic_stop - Prepare the MIC for reset and trigger reset.
|
||||
* @mdev: pointer to mic_device instance
|
||||
* @force: force a MIC to reset even if it is already offline.
|
||||
*
|
||||
* RETURNS: None.
|
||||
*/
|
||||
void mic_stop(struct mic_device *mdev, bool force)
|
||||
{
|
||||
mutex_lock(&mdev->mic_mutex);
|
||||
if (MIC_OFFLINE != mdev->state || force) {
|
||||
mic_virtio_reset_devices(mdev);
|
||||
if (mdev->dma_ch) {
|
||||
dma_release_channel(mdev->dma_ch);
|
||||
mdev->dma_ch = NULL;
|
||||
}
|
||||
mbus_unregister_device(mdev->dma_mbdev);
|
||||
mic_bootparam_init(mdev);
|
||||
mic_reset(mdev);
|
||||
if (MIC_RESET_FAILED == mdev->state)
|
||||
goto unlock;
|
||||
mic_set_shutdown_status(mdev, MIC_NOP);
|
||||
if (MIC_SUSPENDED != mdev->state)
|
||||
mic_set_state(mdev, MIC_OFFLINE);
|
||||
}
|
||||
unlock:
|
||||
mutex_unlock(&mdev->mic_mutex);
|
||||
}
|
||||
|
||||
/**
|
||||
* mic_shutdown - Initiate MIC shutdown.
|
||||
* @mdev: pointer to mic_device instance
|
||||
*
|
||||
* RETURNS: None.
|
||||
*/
|
||||
void mic_shutdown(struct mic_device *mdev)
|
||||
{
|
||||
struct mic_bootparam *bootparam = mdev->dp;
|
||||
s8 db = bootparam->h2c_shutdown_db;
|
||||
|
||||
mutex_lock(&mdev->mic_mutex);
|
||||
if (MIC_ONLINE == mdev->state && db != -1) {
|
||||
bootparam->shutdown_card = 1;
|
||||
mdev->ops->send_intr(mdev, db);
|
||||
mic_set_state(mdev, MIC_SHUTTING_DOWN);
|
||||
}
|
||||
mutex_unlock(&mdev->mic_mutex);
|
||||
}
|
||||
|
||||
/**
|
||||
* mic_shutdown_work - Handle shutdown interrupt from MIC.
|
||||
* @work: The work structure.
|
||||
*
|
||||
* This work is scheduled whenever the host has received a shutdown
|
||||
* interrupt from the MIC.
|
||||
*/
|
||||
void mic_shutdown_work(struct work_struct *work)
|
||||
{
|
||||
struct mic_device *mdev = container_of(work, struct mic_device,
|
||||
shutdown_work);
|
||||
struct mic_bootparam *bootparam = mdev->dp;
|
||||
|
||||
mutex_lock(&mdev->mic_mutex);
|
||||
mic_set_shutdown_status(mdev, bootparam->shutdown_status);
|
||||
bootparam->shutdown_status = 0;
|
||||
|
||||
/*
|
||||
* if state is MIC_SUSPENDED, OSPM suspend is in progress. We do not
|
||||
* change the state here so as to prevent users from booting the card
|
||||
* during and after the suspend operation.
|
||||
*/
|
||||
if (MIC_SHUTTING_DOWN != mdev->state &&
|
||||
MIC_SUSPENDED != mdev->state)
|
||||
mic_set_state(mdev, MIC_SHUTTING_DOWN);
|
||||
mutex_unlock(&mdev->mic_mutex);
|
||||
}
|
||||
|
||||
/**
|
||||
* mic_reset_trigger_work - Trigger MIC reset.
|
||||
* @work: The work structure.
|
||||
*
|
||||
* This work is scheduled whenever the host wants to reset the MIC.
|
||||
*/
|
||||
void mic_reset_trigger_work(struct work_struct *work)
|
||||
{
|
||||
struct mic_device *mdev = container_of(work, struct mic_device,
|
||||
reset_trigger_work);
|
||||
|
||||
mic_stop(mdev, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* mic_complete_resume - Complete MIC Resume after an OSPM suspend/hibernate
|
||||
* event.
|
||||
* @mdev: pointer to mic_device instance
|
||||
*
|
||||
* RETURNS: None.
|
||||
*/
|
||||
void mic_complete_resume(struct mic_device *mdev)
|
||||
{
|
||||
if (mdev->state != MIC_SUSPENDED) {
|
||||
dev_warn(mdev->sdev->parent, "state %d should be %d\n",
|
||||
mdev->state, MIC_SUSPENDED);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Make sure firmware is ready */
|
||||
if (!mdev->ops->is_fw_ready(mdev))
|
||||
mic_stop(mdev, true);
|
||||
|
||||
mutex_lock(&mdev->mic_mutex);
|
||||
mic_set_state(mdev, MIC_OFFLINE);
|
||||
mutex_unlock(&mdev->mic_mutex);
|
||||
}
|
||||
|
||||
/**
|
||||
* mic_prepare_suspend - Handle suspend notification for the MIC device.
|
||||
* @mdev: pointer to mic_device instance
|
||||
*
|
||||
* RETURNS: None.
|
||||
*/
|
||||
void mic_prepare_suspend(struct mic_device *mdev)
|
||||
{
|
||||
int rc;
|
||||
|
||||
#define MIC_SUSPEND_TIMEOUT (60 * HZ)
|
||||
|
||||
mutex_lock(&mdev->mic_mutex);
|
||||
switch (mdev->state) {
|
||||
case MIC_OFFLINE:
|
||||
/*
|
||||
* Card is already offline. Set state to MIC_SUSPENDED
|
||||
* to prevent users from booting the card.
|
||||
*/
|
||||
mic_set_state(mdev, MIC_SUSPENDED);
|
||||
mutex_unlock(&mdev->mic_mutex);
|
||||
break;
|
||||
case MIC_ONLINE:
|
||||
/*
|
||||
* Card is online. Set state to MIC_SUSPENDING and notify
|
||||
* MIC user space daemon which will issue card
|
||||
* shutdown and reset.
|
||||
*/
|
||||
mic_set_state(mdev, MIC_SUSPENDING);
|
||||
mutex_unlock(&mdev->mic_mutex);
|
||||
rc = wait_for_completion_timeout(&mdev->reset_wait,
|
||||
MIC_SUSPEND_TIMEOUT);
|
||||
/* Force reset the card if the shutdown completion timed out */
|
||||
if (!rc) {
|
||||
mutex_lock(&mdev->mic_mutex);
|
||||
mic_set_state(mdev, MIC_SUSPENDED);
|
||||
mutex_unlock(&mdev->mic_mutex);
|
||||
mic_stop(mdev, true);
|
||||
}
|
||||
break;
|
||||
case MIC_SHUTTING_DOWN:
|
||||
/*
|
||||
* Card is shutting down. Set state to MIC_SUSPENDED
|
||||
* to prevent further boot of the card.
|
||||
*/
|
||||
mic_set_state(mdev, MIC_SUSPENDED);
|
||||
mutex_unlock(&mdev->mic_mutex);
|
||||
rc = wait_for_completion_timeout(&mdev->reset_wait,
|
||||
MIC_SUSPEND_TIMEOUT);
|
||||
/* Force reset the card if the shutdown completion timed out */
|
||||
if (!rc)
|
||||
mic_stop(mdev, true);
|
||||
break;
|
||||
default:
|
||||
mutex_unlock(&mdev->mic_mutex);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* mic_suspend - Initiate MIC suspend. Suspend merely issues card shutdown.
|
||||
* @mdev: pointer to mic_device instance
|
||||
*
|
||||
* RETURNS: None.
|
||||
*/
|
||||
void mic_suspend(struct mic_device *mdev)
|
||||
{
|
||||
struct mic_bootparam *bootparam = mdev->dp;
|
||||
s8 db = bootparam->h2c_shutdown_db;
|
||||
|
||||
mutex_lock(&mdev->mic_mutex);
|
||||
if (MIC_SUSPENDING == mdev->state && db != -1) {
|
||||
bootparam->shutdown_card = 1;
|
||||
mdev->ops->send_intr(mdev, db);
|
||||
mic_set_state(mdev, MIC_SUSPENDED);
|
||||
}
|
||||
mutex_unlock(&mdev->mic_mutex);
|
||||
}
|
491
drivers/misc/mic/host/mic_debugfs.c
Normal file
491
drivers/misc/mic/host/mic_debugfs.c
Normal file
|
@ -0,0 +1,491 @@
|
|||
/*
|
||||
* 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/debugfs.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/seq_file.h>
|
||||
|
||||
#include <linux/mic_common.h>
|
||||
#include "../common/mic_dev.h"
|
||||
#include "mic_device.h"
|
||||
#include "mic_smpt.h"
|
||||
#include "mic_virtio.h"
|
||||
|
||||
/* Debugfs parent dir */
|
||||
static struct dentry *mic_dbg;
|
||||
|
||||
/**
|
||||
* mic_log_buf_show - Display MIC kernel log buffer.
|
||||
*
|
||||
* log_buf addr/len is read from System.map by user space
|
||||
* and populated in sysfs entries.
|
||||
*/
|
||||
static int mic_log_buf_show(struct seq_file *s, void *unused)
|
||||
{
|
||||
void __iomem *log_buf_va;
|
||||
int __iomem *log_buf_len_va;
|
||||
struct mic_device *mdev = s->private;
|
||||
void *kva;
|
||||
int size;
|
||||
unsigned long aper_offset;
|
||||
|
||||
if (!mdev || !mdev->log_buf_addr || !mdev->log_buf_len)
|
||||
goto done;
|
||||
/*
|
||||
* Card kernel will never be relocated and any kernel text/data mapping
|
||||
* can be translated to phys address by subtracting __START_KERNEL_map.
|
||||
*/
|
||||
aper_offset = (unsigned long)mdev->log_buf_len - __START_KERNEL_map;
|
||||
log_buf_len_va = mdev->aper.va + aper_offset;
|
||||
aper_offset = (unsigned long)mdev->log_buf_addr - __START_KERNEL_map;
|
||||
log_buf_va = mdev->aper.va + aper_offset;
|
||||
size = ioread32(log_buf_len_va);
|
||||
|
||||
kva = kmalloc(size, GFP_KERNEL);
|
||||
if (!kva)
|
||||
goto done;
|
||||
mutex_lock(&mdev->mic_mutex);
|
||||
memcpy_fromio(kva, log_buf_va, size);
|
||||
switch (mdev->state) {
|
||||
case MIC_ONLINE:
|
||||
/* Fall through */
|
||||
case MIC_SHUTTING_DOWN:
|
||||
seq_write(s, kva, size);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
mutex_unlock(&mdev->mic_mutex);
|
||||
kfree(kva);
|
||||
done:
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mic_log_buf_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
return single_open(file, mic_log_buf_show, inode->i_private);
|
||||
}
|
||||
|
||||
static int mic_log_buf_release(struct inode *inode, struct file *file)
|
||||
{
|
||||
return single_release(inode, file);
|
||||
}
|
||||
|
||||
static const struct file_operations log_buf_ops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = mic_log_buf_open,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
.release = mic_log_buf_release
|
||||
};
|
||||
|
||||
static int mic_smpt_show(struct seq_file *s, void *pos)
|
||||
{
|
||||
int i;
|
||||
struct mic_device *mdev = s->private;
|
||||
unsigned long flags;
|
||||
|
||||
seq_printf(s, "MIC %-2d |%-10s| %-14s %-10s\n",
|
||||
mdev->id, "SMPT entry", "SW DMA addr", "RefCount");
|
||||
seq_puts(s, "====================================================\n");
|
||||
|
||||
if (mdev->smpt) {
|
||||
struct mic_smpt_info *smpt_info = mdev->smpt;
|
||||
spin_lock_irqsave(&smpt_info->smpt_lock, flags);
|
||||
for (i = 0; i < smpt_info->info.num_reg; i++) {
|
||||
seq_printf(s, "%9s|%-10d| %-#14llx %-10lld\n",
|
||||
" ", i, smpt_info->entry[i].dma_addr,
|
||||
smpt_info->entry[i].ref_count);
|
||||
}
|
||||
spin_unlock_irqrestore(&smpt_info->smpt_lock, flags);
|
||||
}
|
||||
seq_puts(s, "====================================================\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mic_smpt_debug_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
return single_open(file, mic_smpt_show, inode->i_private);
|
||||
}
|
||||
|
||||
static int mic_smpt_debug_release(struct inode *inode, struct file *file)
|
||||
{
|
||||
return single_release(inode, file);
|
||||
}
|
||||
|
||||
static const struct file_operations smpt_file_ops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = mic_smpt_debug_open,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
.release = mic_smpt_debug_release
|
||||
};
|
||||
|
||||
static int mic_soft_reset_show(struct seq_file *s, void *pos)
|
||||
{
|
||||
struct mic_device *mdev = s->private;
|
||||
|
||||
mic_stop(mdev, true);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mic_soft_reset_debug_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
return single_open(file, mic_soft_reset_show, inode->i_private);
|
||||
}
|
||||
|
||||
static int mic_soft_reset_debug_release(struct inode *inode, struct file *file)
|
||||
{
|
||||
return single_release(inode, file);
|
||||
}
|
||||
|
||||
static const struct file_operations soft_reset_ops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = mic_soft_reset_debug_open,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
.release = mic_soft_reset_debug_release
|
||||
};
|
||||
|
||||
static int mic_post_code_show(struct seq_file *s, void *pos)
|
||||
{
|
||||
struct mic_device *mdev = s->private;
|
||||
u32 reg = mdev->ops->get_postcode(mdev);
|
||||
|
||||
seq_printf(s, "%c%c", reg & 0xff, (reg >> 8) & 0xff);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mic_post_code_debug_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
return single_open(file, mic_post_code_show, inode->i_private);
|
||||
}
|
||||
|
||||
static int mic_post_code_debug_release(struct inode *inode, struct file *file)
|
||||
{
|
||||
return single_release(inode, file);
|
||||
}
|
||||
|
||||
static const struct file_operations post_code_ops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = mic_post_code_debug_open,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
.release = mic_post_code_debug_release
|
||||
};
|
||||
|
||||
static int mic_dp_show(struct seq_file *s, void *pos)
|
||||
{
|
||||
struct mic_device *mdev = s->private;
|
||||
struct mic_device_desc *d;
|
||||
struct mic_device_ctrl *dc;
|
||||
struct mic_vqconfig *vqconfig;
|
||||
__u32 *features;
|
||||
__u8 *config;
|
||||
struct mic_bootparam *bootparam = mdev->dp;
|
||||
int i, j;
|
||||
|
||||
seq_printf(s, "Bootparam: magic 0x%x\n",
|
||||
bootparam->magic);
|
||||
seq_printf(s, "Bootparam: h2c_shutdown_db %d\n",
|
||||
bootparam->h2c_shutdown_db);
|
||||
seq_printf(s, "Bootparam: h2c_config_db %d\n",
|
||||
bootparam->h2c_config_db);
|
||||
seq_printf(s, "Bootparam: c2h_shutdown_db %d\n",
|
||||
bootparam->c2h_shutdown_db);
|
||||
seq_printf(s, "Bootparam: shutdown_status %d\n",
|
||||
bootparam->shutdown_status);
|
||||
seq_printf(s, "Bootparam: shutdown_card %d\n",
|
||||
bootparam->shutdown_card);
|
||||
|
||||
for (i = sizeof(*bootparam); i < MIC_DP_SIZE;
|
||||
i += mic_total_desc_size(d)) {
|
||||
d = mdev->dp + i;
|
||||
dc = (void *)d + mic_aligned_desc_size(d);
|
||||
|
||||
/* end of list */
|
||||
if (d->type == 0)
|
||||
break;
|
||||
|
||||
if (d->type == -1)
|
||||
continue;
|
||||
|
||||
seq_printf(s, "Type %d ", d->type);
|
||||
seq_printf(s, "Num VQ %d ", d->num_vq);
|
||||
seq_printf(s, "Feature Len %d\n", d->feature_len);
|
||||
seq_printf(s, "Config Len %d ", d->config_len);
|
||||
seq_printf(s, "Shutdown Status %d\n", d->status);
|
||||
|
||||
for (j = 0; j < d->num_vq; j++) {
|
||||
vqconfig = mic_vq_config(d) + j;
|
||||
seq_printf(s, "vqconfig[%d]: ", j);
|
||||
seq_printf(s, "address 0x%llx ", vqconfig->address);
|
||||
seq_printf(s, "num %d ", vqconfig->num);
|
||||
seq_printf(s, "used address 0x%llx\n",
|
||||
vqconfig->used_address);
|
||||
}
|
||||
|
||||
features = (__u32 *)mic_vq_features(d);
|
||||
seq_printf(s, "Features: Host 0x%x ", features[0]);
|
||||
seq_printf(s, "Guest 0x%x\n", features[1]);
|
||||
|
||||
config = mic_vq_configspace(d);
|
||||
for (j = 0; j < d->config_len; j++)
|
||||
seq_printf(s, "config[%d]=%d\n", j, config[j]);
|
||||
|
||||
seq_puts(s, "Device control:\n");
|
||||
seq_printf(s, "Config Change %d ", dc->config_change);
|
||||
seq_printf(s, "Vdev reset %d\n", dc->vdev_reset);
|
||||
seq_printf(s, "Guest Ack %d ", dc->guest_ack);
|
||||
seq_printf(s, "Host ack %d\n", dc->host_ack);
|
||||
seq_printf(s, "Used address updated %d ",
|
||||
dc->used_address_updated);
|
||||
seq_printf(s, "Vdev 0x%llx\n", dc->vdev);
|
||||
seq_printf(s, "c2h doorbell %d ", dc->c2h_vdev_db);
|
||||
seq_printf(s, "h2c doorbell %d\n", dc->h2c_vdev_db);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mic_dp_debug_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
return single_open(file, mic_dp_show, inode->i_private);
|
||||
}
|
||||
|
||||
static int mic_dp_debug_release(struct inode *inode, struct file *file)
|
||||
{
|
||||
return single_release(inode, file);
|
||||
}
|
||||
|
||||
static const struct file_operations dp_ops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = mic_dp_debug_open,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
.release = mic_dp_debug_release
|
||||
};
|
||||
|
||||
static int mic_vdev_info_show(struct seq_file *s, void *unused)
|
||||
{
|
||||
struct mic_device *mdev = s->private;
|
||||
struct list_head *pos, *tmp;
|
||||
struct mic_vdev *mvdev;
|
||||
int i, j;
|
||||
|
||||
mutex_lock(&mdev->mic_mutex);
|
||||
list_for_each_safe(pos, tmp, &mdev->vdev_list) {
|
||||
mvdev = list_entry(pos, struct mic_vdev, list);
|
||||
seq_printf(s, "VDEV type %d state %s in %ld out %ld\n",
|
||||
mvdev->virtio_id,
|
||||
mic_vdevup(mvdev) ? "UP" : "DOWN",
|
||||
mvdev->in_bytes,
|
||||
mvdev->out_bytes);
|
||||
for (i = 0; i < MIC_MAX_VRINGS; i++) {
|
||||
struct vring_desc *desc;
|
||||
struct vring_avail *avail;
|
||||
struct vring_used *used;
|
||||
struct mic_vringh *mvr = &mvdev->mvr[i];
|
||||
struct vringh *vrh = &mvr->vrh;
|
||||
int num = vrh->vring.num;
|
||||
if (!num)
|
||||
continue;
|
||||
desc = vrh->vring.desc;
|
||||
seq_printf(s, "vring i %d avail_idx %d",
|
||||
i, mvr->vring.info->avail_idx & (num - 1));
|
||||
seq_printf(s, " vring i %d avail_idx %d\n",
|
||||
i, mvr->vring.info->avail_idx);
|
||||
seq_printf(s, "vrh i %d weak_barriers %d",
|
||||
i, vrh->weak_barriers);
|
||||
seq_printf(s, " last_avail_idx %d last_used_idx %d",
|
||||
vrh->last_avail_idx, vrh->last_used_idx);
|
||||
seq_printf(s, " completed %d\n", vrh->completed);
|
||||
for (j = 0; j < num; j++) {
|
||||
seq_printf(s, "desc[%d] addr 0x%llx len %d",
|
||||
j, desc->addr, desc->len);
|
||||
seq_printf(s, " flags 0x%x next %d\n",
|
||||
desc->flags, desc->next);
|
||||
desc++;
|
||||
}
|
||||
avail = vrh->vring.avail;
|
||||
seq_printf(s, "avail flags 0x%x idx %d\n",
|
||||
avail->flags, avail->idx & (num - 1));
|
||||
seq_printf(s, "avail flags 0x%x idx %d\n",
|
||||
avail->flags, avail->idx);
|
||||
for (j = 0; j < num; j++)
|
||||
seq_printf(s, "avail ring[%d] %d\n",
|
||||
j, avail->ring[j]);
|
||||
used = vrh->vring.used;
|
||||
seq_printf(s, "used flags 0x%x idx %d\n",
|
||||
used->flags, used->idx & (num - 1));
|
||||
seq_printf(s, "used flags 0x%x idx %d\n",
|
||||
used->flags, used->idx);
|
||||
for (j = 0; j < num; j++)
|
||||
seq_printf(s, "used ring[%d] id %d len %d\n",
|
||||
j, used->ring[j].id,
|
||||
used->ring[j].len);
|
||||
}
|
||||
}
|
||||
mutex_unlock(&mdev->mic_mutex);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mic_vdev_info_debug_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
return single_open(file, mic_vdev_info_show, inode->i_private);
|
||||
}
|
||||
|
||||
static int mic_vdev_info_debug_release(struct inode *inode, struct file *file)
|
||||
{
|
||||
return single_release(inode, file);
|
||||
}
|
||||
|
||||
static const struct file_operations vdev_info_ops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = mic_vdev_info_debug_open,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
.release = mic_vdev_info_debug_release
|
||||
};
|
||||
|
||||
static int mic_msi_irq_info_show(struct seq_file *s, void *pos)
|
||||
{
|
||||
struct mic_device *mdev = s->private;
|
||||
int reg;
|
||||
int i, j;
|
||||
u16 entry;
|
||||
u16 vector;
|
||||
struct pci_dev *pdev = container_of(mdev->sdev->parent,
|
||||
struct pci_dev, dev);
|
||||
|
||||
if (pci_dev_msi_enabled(pdev)) {
|
||||
for (i = 0; i < mdev->irq_info.num_vectors; i++) {
|
||||
if (pdev->msix_enabled) {
|
||||
entry = mdev->irq_info.msix_entries[i].entry;
|
||||
vector = mdev->irq_info.msix_entries[i].vector;
|
||||
} else {
|
||||
entry = 0;
|
||||
vector = pdev->irq;
|
||||
}
|
||||
|
||||
reg = mdev->intr_ops->read_msi_to_src_map(mdev, entry);
|
||||
|
||||
seq_printf(s, "%s %-10d %s %-10d MXAR[%d]: %08X\n",
|
||||
"IRQ:", vector, "Entry:", entry, i, reg);
|
||||
|
||||
seq_printf(s, "%-10s", "offset:");
|
||||
for (j = (MIC_NUM_OFFSETS - 1); j >= 0; j--)
|
||||
seq_printf(s, "%4d ", j);
|
||||
seq_puts(s, "\n");
|
||||
|
||||
|
||||
seq_printf(s, "%-10s", "count:");
|
||||
for (j = (MIC_NUM_OFFSETS - 1); j >= 0; j--)
|
||||
seq_printf(s, "%4d ",
|
||||
(mdev->irq_info.mic_msi_map[i] &
|
||||
BIT(j)) ? 1 : 0);
|
||||
seq_puts(s, "\n\n");
|
||||
}
|
||||
} else {
|
||||
seq_puts(s, "MSI/MSIx interrupts not enabled\n");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mic_msi_irq_info_debug_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
return single_open(file, mic_msi_irq_info_show, inode->i_private);
|
||||
}
|
||||
|
||||
static int
|
||||
mic_msi_irq_info_debug_release(struct inode *inode, struct file *file)
|
||||
{
|
||||
return single_release(inode, file);
|
||||
}
|
||||
|
||||
static const struct file_operations msi_irq_info_ops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = mic_msi_irq_info_debug_open,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
.release = mic_msi_irq_info_debug_release
|
||||
};
|
||||
|
||||
/**
|
||||
* mic_create_debug_dir - Initialize MIC debugfs entries.
|
||||
*/
|
||||
void mic_create_debug_dir(struct mic_device *mdev)
|
||||
{
|
||||
if (!mic_dbg)
|
||||
return;
|
||||
|
||||
mdev->dbg_dir = debugfs_create_dir(dev_name(mdev->sdev), mic_dbg);
|
||||
if (!mdev->dbg_dir)
|
||||
return;
|
||||
|
||||
debugfs_create_file("log_buf", 0444, mdev->dbg_dir, mdev, &log_buf_ops);
|
||||
|
||||
debugfs_create_file("smpt", 0444, mdev->dbg_dir, mdev, &smpt_file_ops);
|
||||
|
||||
debugfs_create_file("soft_reset", 0444, mdev->dbg_dir, mdev,
|
||||
&soft_reset_ops);
|
||||
|
||||
debugfs_create_file("post_code", 0444, mdev->dbg_dir, mdev,
|
||||
&post_code_ops);
|
||||
|
||||
debugfs_create_file("dp", 0444, mdev->dbg_dir, mdev, &dp_ops);
|
||||
|
||||
debugfs_create_file("vdev_info", 0444, mdev->dbg_dir, mdev,
|
||||
&vdev_info_ops);
|
||||
|
||||
debugfs_create_file("msi_irq_info", 0444, mdev->dbg_dir, mdev,
|
||||
&msi_irq_info_ops);
|
||||
}
|
||||
|
||||
/**
|
||||
* mic_delete_debug_dir - Uninitialize MIC debugfs entries.
|
||||
*/
|
||||
void mic_delete_debug_dir(struct mic_device *mdev)
|
||||
{
|
||||
if (!mdev->dbg_dir)
|
||||
return;
|
||||
|
||||
debugfs_remove_recursive(mdev->dbg_dir);
|
||||
}
|
||||
|
||||
/**
|
||||
* mic_init_debugfs - Initialize global debugfs entry.
|
||||
*/
|
||||
void __init mic_init_debugfs(void)
|
||||
{
|
||||
mic_dbg = debugfs_create_dir(KBUILD_MODNAME, NULL);
|
||||
if (!mic_dbg)
|
||||
pr_err("can't create debugfs dir\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* mic_exit_debugfs - Uninitialize global debugfs entry
|
||||
*/
|
||||
void mic_exit_debugfs(void)
|
||||
{
|
||||
debugfs_remove(mic_dbg);
|
||||
}
|
231
drivers/misc/mic/host/mic_device.h
Normal file
231
drivers/misc/mic/host/mic_device.h
Normal file
|
@ -0,0 +1,231 @@
|
|||
/*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
#ifndef _MIC_DEVICE_H_
|
||||
#define _MIC_DEVICE_H_
|
||||
|
||||
#include <linux/cdev.h>
|
||||
#include <linux/idr.h>
|
||||
#include <linux/notifier.h>
|
||||
#include <linux/irqreturn.h>
|
||||
#include <linux/dmaengine.h>
|
||||
#include <linux/mic_bus.h>
|
||||
|
||||
#include "mic_intr.h"
|
||||
|
||||
/* The maximum number of MIC devices supported in a single host system. */
|
||||
#define MIC_MAX_NUM_DEVS 256
|
||||
|
||||
/**
|
||||
* enum mic_hw_family - The hardware family to which a device belongs.
|
||||
*/
|
||||
enum mic_hw_family {
|
||||
MIC_FAMILY_X100 = 0,
|
||||
MIC_FAMILY_UNKNOWN
|
||||
};
|
||||
|
||||
/**
|
||||
* enum mic_stepping - MIC stepping ids.
|
||||
*/
|
||||
enum mic_stepping {
|
||||
MIC_A0_STEP = 0x0,
|
||||
MIC_B0_STEP = 0x10,
|
||||
MIC_B1_STEP = 0x11,
|
||||
MIC_C0_STEP = 0x20,
|
||||
};
|
||||
|
||||
/**
|
||||
* struct mic_device - MIC device information for each card.
|
||||
*
|
||||
* @mmio: MMIO bar information.
|
||||
* @aper: Aperture bar information.
|
||||
* @family: The MIC family to which this device belongs.
|
||||
* @ops: MIC HW specific operations.
|
||||
* @id: The unique device id for this MIC device.
|
||||
* @stepping: Stepping ID.
|
||||
* @attr_group: Pointer to list of sysfs attribute groups.
|
||||
* @sdev: Device for sysfs entries.
|
||||
* @mic_mutex: Mutex for synchronizing access to mic_device.
|
||||
* @intr_ops: HW specific interrupt operations.
|
||||
* @smpt_ops: Hardware specific SMPT operations.
|
||||
* @smpt: MIC SMPT information.
|
||||
* @intr_info: H/W specific interrupt information.
|
||||
* @irq_info: The OS specific irq information
|
||||
* @dbg_dir: debugfs directory of this MIC device.
|
||||
* @cmdline: Kernel command line.
|
||||
* @firmware: Firmware file name.
|
||||
* @ramdisk: Ramdisk file name.
|
||||
* @bootmode: Boot mode i.e. "linux" or "elf" for flash updates.
|
||||
* @bootaddr: MIC boot address.
|
||||
* @reset_trigger_work: Work for triggering reset requests.
|
||||
* @shutdown_work: Work for handling shutdown interrupts.
|
||||
* @state: MIC state.
|
||||
* @shutdown_status: MIC status reported by card for shutdown/crashes.
|
||||
* @state_sysfs: Sysfs dirent for notifying ring 3 about MIC state changes.
|
||||
* @reset_wait: Waitqueue for sleeping while reset completes.
|
||||
* @log_buf_addr: Log buffer address for MIC.
|
||||
* @log_buf_len: Log buffer length address for MIC.
|
||||
* @dp: virtio device page
|
||||
* @dp_dma_addr: virtio device page DMA address.
|
||||
* @shutdown_db: shutdown doorbell.
|
||||
* @shutdown_cookie: shutdown cookie.
|
||||
* @cdev: Character device for MIC.
|
||||
* @vdev_list: list of virtio devices.
|
||||
* @pm_notifier: Handles PM notifications from the OS.
|
||||
* @dma_mbdev: MIC BUS DMA device.
|
||||
* @dma_ch: DMA channel reserved by this driver for use by virtio devices.
|
||||
*/
|
||||
struct mic_device {
|
||||
struct mic_mw mmio;
|
||||
struct mic_mw aper;
|
||||
enum mic_hw_family family;
|
||||
struct mic_hw_ops *ops;
|
||||
int id;
|
||||
enum mic_stepping stepping;
|
||||
const struct attribute_group **attr_group;
|
||||
struct device *sdev;
|
||||
struct mutex mic_mutex;
|
||||
struct mic_hw_intr_ops *intr_ops;
|
||||
struct mic_smpt_ops *smpt_ops;
|
||||
struct mic_smpt_info *smpt;
|
||||
struct mic_intr_info *intr_info;
|
||||
struct mic_irq_info irq_info;
|
||||
struct dentry *dbg_dir;
|
||||
char *cmdline;
|
||||
char *firmware;
|
||||
char *ramdisk;
|
||||
char *bootmode;
|
||||
u32 bootaddr;
|
||||
struct work_struct reset_trigger_work;
|
||||
struct work_struct shutdown_work;
|
||||
u8 state;
|
||||
u8 shutdown_status;
|
||||
struct kernfs_node *state_sysfs;
|
||||
struct completion reset_wait;
|
||||
void *log_buf_addr;
|
||||
int *log_buf_len;
|
||||
void *dp;
|
||||
dma_addr_t dp_dma_addr;
|
||||
int shutdown_db;
|
||||
struct mic_irq *shutdown_cookie;
|
||||
struct cdev cdev;
|
||||
struct list_head vdev_list;
|
||||
struct notifier_block pm_notifier;
|
||||
struct mbus_device *dma_mbdev;
|
||||
struct dma_chan *dma_ch;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct mic_hw_ops - MIC HW specific operations.
|
||||
* @aper_bar: Aperture bar resource number.
|
||||
* @mmio_bar: MMIO bar resource number.
|
||||
* @read_spad: Read from scratch pad register.
|
||||
* @write_spad: Write to scratch pad register.
|
||||
* @send_intr: Send an interrupt for a particular doorbell on the card.
|
||||
* @ack_interrupt: Hardware specific operations to ack the h/w on
|
||||
* receipt of an interrupt.
|
||||
* @intr_workarounds: Hardware specific workarounds needed after
|
||||
* handling an interrupt.
|
||||
* @reset: Reset the remote processor.
|
||||
* @reset_fw_ready: Reset firmware ready field.
|
||||
* @is_fw_ready: Check if firmware is ready for OS download.
|
||||
* @send_firmware_intr: Send an interrupt to the card firmware.
|
||||
* @load_mic_fw: Load firmware segments required to boot the card
|
||||
* into card memory. This includes the kernel, command line, ramdisk etc.
|
||||
* @get_postcode: Get post code status from firmware.
|
||||
* @dma_filter: DMA filter function to be used.
|
||||
*/
|
||||
struct mic_hw_ops {
|
||||
u8 aper_bar;
|
||||
u8 mmio_bar;
|
||||
u32 (*read_spad)(struct mic_device *mdev, unsigned int idx);
|
||||
void (*write_spad)(struct mic_device *mdev, unsigned int idx, u32 val);
|
||||
void (*send_intr)(struct mic_device *mdev, int doorbell);
|
||||
u32 (*ack_interrupt)(struct mic_device *mdev);
|
||||
void (*intr_workarounds)(struct mic_device *mdev);
|
||||
void (*reset)(struct mic_device *mdev);
|
||||
void (*reset_fw_ready)(struct mic_device *mdev);
|
||||
bool (*is_fw_ready)(struct mic_device *mdev);
|
||||
void (*send_firmware_intr)(struct mic_device *mdev);
|
||||
int (*load_mic_fw)(struct mic_device *mdev, const char *buf);
|
||||
u32 (*get_postcode)(struct mic_device *mdev);
|
||||
bool (*dma_filter)(struct dma_chan *chan, void *param);
|
||||
};
|
||||
|
||||
/**
|
||||
* mic_mmio_read - read from an MMIO register.
|
||||
* @mw: MMIO register base virtual address.
|
||||
* @offset: register offset.
|
||||
*
|
||||
* RETURNS: register value.
|
||||
*/
|
||||
static inline u32 mic_mmio_read(struct mic_mw *mw, u32 offset)
|
||||
{
|
||||
return ioread32(mw->va + offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* mic_mmio_write - write to an MMIO register.
|
||||
* @mw: MMIO register base virtual address.
|
||||
* @val: the data value to put into the register
|
||||
* @offset: register offset.
|
||||
*
|
||||
* RETURNS: none.
|
||||
*/
|
||||
static inline void
|
||||
mic_mmio_write(struct mic_mw *mw, u32 val, u32 offset)
|
||||
{
|
||||
iowrite32(val, mw->va + offset);
|
||||
}
|
||||
|
||||
static inline struct dma_chan *mic_request_dma_chan(struct mic_device *mdev)
|
||||
{
|
||||
dma_cap_mask_t mask;
|
||||
struct dma_chan *chan;
|
||||
|
||||
dma_cap_zero(mask);
|
||||
dma_cap_set(DMA_MEMCPY, mask);
|
||||
chan = dma_request_channel(mask, mdev->ops->dma_filter,
|
||||
mdev->sdev->parent);
|
||||
if (chan)
|
||||
return chan;
|
||||
dev_err(mdev->sdev->parent, "%s %d unable to acquire channel\n",
|
||||
__func__, __LINE__);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void mic_sysfs_init(struct mic_device *mdev);
|
||||
int mic_start(struct mic_device *mdev, const char *buf);
|
||||
void mic_stop(struct mic_device *mdev, bool force);
|
||||
void mic_shutdown(struct mic_device *mdev);
|
||||
void mic_reset_delayed_work(struct work_struct *work);
|
||||
void mic_reset_trigger_work(struct work_struct *work);
|
||||
void mic_shutdown_work(struct work_struct *work);
|
||||
void mic_bootparam_init(struct mic_device *mdev);
|
||||
void mic_set_state(struct mic_device *mdev, u8 state);
|
||||
void mic_set_shutdown_status(struct mic_device *mdev, u8 status);
|
||||
void mic_create_debug_dir(struct mic_device *dev);
|
||||
void mic_delete_debug_dir(struct mic_device *dev);
|
||||
void __init mic_init_debugfs(void);
|
||||
void mic_exit_debugfs(void);
|
||||
void mic_prepare_suspend(struct mic_device *mdev);
|
||||
void mic_complete_resume(struct mic_device *mdev);
|
||||
void mic_suspend(struct mic_device *mdev);
|
||||
#endif
|
222
drivers/misc/mic/host/mic_fops.c
Normal file
222
drivers/misc/mic/host/mic_fops.c
Normal file
|
@ -0,0 +1,222 @@
|
|||
/*
|
||||
* 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;
|
||||
}
|
32
drivers/misc/mic/host/mic_fops.h
Normal file
32
drivers/misc/mic/host/mic_fops.h
Normal file
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
#ifndef _MIC_FOPS_H_
|
||||
#define _MIC_FOPS_H_
|
||||
|
||||
int mic_open(struct inode *inode, struct file *filp);
|
||||
int mic_release(struct inode *inode, struct file *filp);
|
||||
ssize_t mic_read(struct file *filp, char __user *buf,
|
||||
size_t count, loff_t *pos);
|
||||
long mic_ioctl(struct file *filp, unsigned int cmd, unsigned long arg);
|
||||
int mic_mmap(struct file *f, struct vm_area_struct *vma);
|
||||
unsigned int mic_poll(struct file *f, poll_table *wait);
|
||||
|
||||
#endif
|
653
drivers/misc/mic/host/mic_intr.c
Normal file
653
drivers/misc/mic/host/mic_intr.c
Normal file
|
@ -0,0 +1,653 @@
|
|||
/*
|
||||
* 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/interrupt.h>
|
||||
|
||||
#include "../common/mic_dev.h"
|
||||
#include "mic_device.h"
|
||||
|
||||
static irqreturn_t mic_thread_fn(int irq, void *dev)
|
||||
{
|
||||
struct mic_device *mdev = dev;
|
||||
struct mic_intr_info *intr_info = mdev->intr_info;
|
||||
struct mic_irq_info *irq_info = &mdev->irq_info;
|
||||
struct mic_intr_cb *intr_cb;
|
||||
struct pci_dev *pdev = container_of(mdev->sdev->parent,
|
||||
struct pci_dev, dev);
|
||||
int i;
|
||||
|
||||
spin_lock(&irq_info->mic_thread_lock);
|
||||
for (i = intr_info->intr_start_idx[MIC_INTR_DB];
|
||||
i < intr_info->intr_len[MIC_INTR_DB]; i++)
|
||||
if (test_and_clear_bit(i, &irq_info->mask)) {
|
||||
list_for_each_entry(intr_cb, &irq_info->cb_list[i],
|
||||
list)
|
||||
if (intr_cb->thread_fn)
|
||||
intr_cb->thread_fn(pdev->irq,
|
||||
intr_cb->data);
|
||||
}
|
||||
spin_unlock(&irq_info->mic_thread_lock);
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
/**
|
||||
* mic_interrupt - Generic interrupt handler for
|
||||
* MSI and INTx based interrupts.
|
||||
*/
|
||||
static irqreturn_t mic_interrupt(int irq, void *dev)
|
||||
{
|
||||
struct mic_device *mdev = dev;
|
||||
struct mic_intr_info *intr_info = mdev->intr_info;
|
||||
struct mic_irq_info *irq_info = &mdev->irq_info;
|
||||
struct mic_intr_cb *intr_cb;
|
||||
struct pci_dev *pdev = container_of(mdev->sdev->parent,
|
||||
struct pci_dev, dev);
|
||||
u32 mask;
|
||||
int i;
|
||||
|
||||
mask = mdev->ops->ack_interrupt(mdev);
|
||||
if (!mask)
|
||||
return IRQ_NONE;
|
||||
|
||||
spin_lock(&irq_info->mic_intr_lock);
|
||||
for (i = intr_info->intr_start_idx[MIC_INTR_DB];
|
||||
i < intr_info->intr_len[MIC_INTR_DB]; i++)
|
||||
if (mask & BIT(i)) {
|
||||
list_for_each_entry(intr_cb, &irq_info->cb_list[i],
|
||||
list)
|
||||
if (intr_cb->handler)
|
||||
intr_cb->handler(pdev->irq,
|
||||
intr_cb->data);
|
||||
set_bit(i, &irq_info->mask);
|
||||
}
|
||||
spin_unlock(&irq_info->mic_intr_lock);
|
||||
return IRQ_WAKE_THREAD;
|
||||
}
|
||||
|
||||
/* Return the interrupt offset from the index. Index is 0 based. */
|
||||
static u16 mic_map_src_to_offset(struct mic_device *mdev,
|
||||
int intr_src, enum mic_intr_type type)
|
||||
{
|
||||
if (type >= MIC_NUM_INTR_TYPES)
|
||||
return MIC_NUM_OFFSETS;
|
||||
if (intr_src >= mdev->intr_info->intr_len[type])
|
||||
return MIC_NUM_OFFSETS;
|
||||
|
||||
return mdev->intr_info->intr_start_idx[type] + intr_src;
|
||||
}
|
||||
|
||||
/* Return next available msix_entry. */
|
||||
static struct msix_entry *mic_get_available_vector(struct mic_device *mdev)
|
||||
{
|
||||
int i;
|
||||
struct mic_irq_info *info = &mdev->irq_info;
|
||||
|
||||
for (i = 0; i < info->num_vectors; i++)
|
||||
if (!info->mic_msi_map[i])
|
||||
return &info->msix_entries[i];
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* mic_register_intr_callback - Register a callback handler for the
|
||||
* given source id.
|
||||
*
|
||||
* @mdev: pointer to the mic_device instance
|
||||
* @idx: The source id to be registered.
|
||||
* @handler: The function to be called when the source id receives
|
||||
* the interrupt.
|
||||
* @thread_fn: thread fn. corresponding to the handler
|
||||
* @data: Private data of the requester.
|
||||
* Return the callback structure that was registered or an
|
||||
* appropriate error on failure.
|
||||
*/
|
||||
static struct mic_intr_cb *mic_register_intr_callback(struct mic_device *mdev,
|
||||
u8 idx, irq_handler_t handler, irq_handler_t thread_fn,
|
||||
void *data)
|
||||
{
|
||||
struct mic_intr_cb *intr_cb;
|
||||
unsigned long flags;
|
||||
int rc;
|
||||
intr_cb = kmalloc(sizeof(*intr_cb), GFP_KERNEL);
|
||||
|
||||
if (!intr_cb)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
intr_cb->handler = handler;
|
||||
intr_cb->thread_fn = thread_fn;
|
||||
intr_cb->data = data;
|
||||
intr_cb->cb_id = ida_simple_get(&mdev->irq_info.cb_ida,
|
||||
0, 0, GFP_KERNEL);
|
||||
if (intr_cb->cb_id < 0) {
|
||||
rc = intr_cb->cb_id;
|
||||
goto ida_fail;
|
||||
}
|
||||
|
||||
spin_lock(&mdev->irq_info.mic_thread_lock);
|
||||
spin_lock_irqsave(&mdev->irq_info.mic_intr_lock, flags);
|
||||
list_add_tail(&intr_cb->list, &mdev->irq_info.cb_list[idx]);
|
||||
spin_unlock_irqrestore(&mdev->irq_info.mic_intr_lock, flags);
|
||||
spin_unlock(&mdev->irq_info.mic_thread_lock);
|
||||
|
||||
return intr_cb;
|
||||
ida_fail:
|
||||
kfree(intr_cb);
|
||||
return ERR_PTR(rc);
|
||||
}
|
||||
|
||||
/**
|
||||
* mic_unregister_intr_callback - Unregister the callback handler
|
||||
* identified by its callback id.
|
||||
*
|
||||
* @mdev: pointer to the mic_device instance
|
||||
* @idx: The callback structure id to be unregistered.
|
||||
* Return the source id that was unregistered or MIC_NUM_OFFSETS if no
|
||||
* such callback handler was found.
|
||||
*/
|
||||
static u8 mic_unregister_intr_callback(struct mic_device *mdev, u32 idx)
|
||||
{
|
||||
struct list_head *pos, *tmp;
|
||||
struct mic_intr_cb *intr_cb;
|
||||
unsigned long flags;
|
||||
int i;
|
||||
|
||||
spin_lock(&mdev->irq_info.mic_thread_lock);
|
||||
spin_lock_irqsave(&mdev->irq_info.mic_intr_lock, flags);
|
||||
for (i = 0; i < MIC_NUM_OFFSETS; i++) {
|
||||
list_for_each_safe(pos, tmp, &mdev->irq_info.cb_list[i]) {
|
||||
intr_cb = list_entry(pos, struct mic_intr_cb, list);
|
||||
if (intr_cb->cb_id == idx) {
|
||||
list_del(pos);
|
||||
ida_simple_remove(&mdev->irq_info.cb_ida,
|
||||
intr_cb->cb_id);
|
||||
kfree(intr_cb);
|
||||
spin_unlock_irqrestore(
|
||||
&mdev->irq_info.mic_intr_lock, flags);
|
||||
spin_unlock(&mdev->irq_info.mic_thread_lock);
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
spin_unlock_irqrestore(&mdev->irq_info.mic_intr_lock, flags);
|
||||
spin_unlock(&mdev->irq_info.mic_thread_lock);
|
||||
return MIC_NUM_OFFSETS;
|
||||
}
|
||||
|
||||
/**
|
||||
* mic_setup_msix - Initializes MSIx interrupts.
|
||||
*
|
||||
* @mdev: pointer to mic_device instance
|
||||
*
|
||||
*
|
||||
* RETURNS: An appropriate -ERRNO error value on error, or zero for success.
|
||||
*/
|
||||
static int mic_setup_msix(struct mic_device *mdev, struct pci_dev *pdev)
|
||||
{
|
||||
int rc, i;
|
||||
int entry_size = sizeof(*mdev->irq_info.msix_entries);
|
||||
|
||||
mdev->irq_info.msix_entries = kmalloc_array(MIC_MIN_MSIX,
|
||||
entry_size, GFP_KERNEL);
|
||||
if (!mdev->irq_info.msix_entries) {
|
||||
rc = -ENOMEM;
|
||||
goto err_nomem1;
|
||||
}
|
||||
|
||||
for (i = 0; i < MIC_MIN_MSIX; i++)
|
||||
mdev->irq_info.msix_entries[i].entry = i;
|
||||
|
||||
rc = pci_enable_msix_exact(pdev, mdev->irq_info.msix_entries,
|
||||
MIC_MIN_MSIX);
|
||||
if (rc) {
|
||||
dev_dbg(&pdev->dev, "Error enabling MSIx. rc = %d\n", rc);
|
||||
goto err_enable_msix;
|
||||
}
|
||||
|
||||
mdev->irq_info.num_vectors = MIC_MIN_MSIX;
|
||||
mdev->irq_info.mic_msi_map = kzalloc((sizeof(u32) *
|
||||
mdev->irq_info.num_vectors), GFP_KERNEL);
|
||||
|
||||
if (!mdev->irq_info.mic_msi_map) {
|
||||
rc = -ENOMEM;
|
||||
goto err_nomem2;
|
||||
}
|
||||
|
||||
dev_dbg(mdev->sdev->parent,
|
||||
"%d MSIx irqs setup\n", mdev->irq_info.num_vectors);
|
||||
return 0;
|
||||
err_nomem2:
|
||||
pci_disable_msix(pdev);
|
||||
err_enable_msix:
|
||||
kfree(mdev->irq_info.msix_entries);
|
||||
err_nomem1:
|
||||
mdev->irq_info.num_vectors = 0;
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* mic_setup_callbacks - Initialize data structures needed
|
||||
* to handle callbacks.
|
||||
*
|
||||
* @mdev: pointer to mic_device instance
|
||||
*/
|
||||
static int mic_setup_callbacks(struct mic_device *mdev)
|
||||
{
|
||||
int i;
|
||||
|
||||
mdev->irq_info.cb_list = kmalloc_array(MIC_NUM_OFFSETS,
|
||||
sizeof(*mdev->irq_info.cb_list),
|
||||
GFP_KERNEL);
|
||||
if (!mdev->irq_info.cb_list)
|
||||
return -ENOMEM;
|
||||
|
||||
for (i = 0; i < MIC_NUM_OFFSETS; i++)
|
||||
INIT_LIST_HEAD(&mdev->irq_info.cb_list[i]);
|
||||
ida_init(&mdev->irq_info.cb_ida);
|
||||
spin_lock_init(&mdev->irq_info.mic_intr_lock);
|
||||
spin_lock_init(&mdev->irq_info.mic_thread_lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* mic_release_callbacks - Uninitialize data structures needed
|
||||
* to handle callbacks.
|
||||
*
|
||||
* @mdev: pointer to mic_device instance
|
||||
*/
|
||||
static void mic_release_callbacks(struct mic_device *mdev)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct list_head *pos, *tmp;
|
||||
struct mic_intr_cb *intr_cb;
|
||||
int i;
|
||||
|
||||
spin_lock(&mdev->irq_info.mic_thread_lock);
|
||||
spin_lock_irqsave(&mdev->irq_info.mic_intr_lock, flags);
|
||||
for (i = 0; i < MIC_NUM_OFFSETS; i++) {
|
||||
|
||||
if (list_empty(&mdev->irq_info.cb_list[i]))
|
||||
break;
|
||||
|
||||
list_for_each_safe(pos, tmp, &mdev->irq_info.cb_list[i]) {
|
||||
intr_cb = list_entry(pos, struct mic_intr_cb, list);
|
||||
list_del(pos);
|
||||
ida_simple_remove(&mdev->irq_info.cb_ida,
|
||||
intr_cb->cb_id);
|
||||
kfree(intr_cb);
|
||||
}
|
||||
}
|
||||
spin_unlock_irqrestore(&mdev->irq_info.mic_intr_lock, flags);
|
||||
spin_unlock(&mdev->irq_info.mic_thread_lock);
|
||||
ida_destroy(&mdev->irq_info.cb_ida);
|
||||
kfree(mdev->irq_info.cb_list);
|
||||
}
|
||||
|
||||
/**
|
||||
* mic_setup_msi - Initializes MSI interrupts.
|
||||
*
|
||||
* @mdev: pointer to mic_device instance
|
||||
* @pdev: PCI device structure
|
||||
*
|
||||
* RETURNS: An appropriate -ERRNO error value on error, or zero for success.
|
||||
*/
|
||||
static int mic_setup_msi(struct mic_device *mdev, struct pci_dev *pdev)
|
||||
{
|
||||
int rc;
|
||||
|
||||
rc = pci_enable_msi(pdev);
|
||||
if (rc) {
|
||||
dev_dbg(&pdev->dev, "Error enabling MSI. rc = %d\n", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
mdev->irq_info.num_vectors = 1;
|
||||
mdev->irq_info.mic_msi_map = kzalloc((sizeof(u32) *
|
||||
mdev->irq_info.num_vectors), GFP_KERNEL);
|
||||
|
||||
if (!mdev->irq_info.mic_msi_map) {
|
||||
rc = -ENOMEM;
|
||||
goto err_nomem1;
|
||||
}
|
||||
|
||||
rc = mic_setup_callbacks(mdev);
|
||||
if (rc) {
|
||||
dev_err(&pdev->dev, "Error setting up callbacks\n");
|
||||
goto err_nomem2;
|
||||
}
|
||||
|
||||
rc = request_threaded_irq(pdev->irq, mic_interrupt, mic_thread_fn,
|
||||
0, "mic-msi", mdev);
|
||||
if (rc) {
|
||||
dev_err(&pdev->dev, "Error allocating MSI interrupt\n");
|
||||
goto err_irq_req_fail;
|
||||
}
|
||||
|
||||
dev_dbg(&pdev->dev, "%d MSI irqs setup\n", mdev->irq_info.num_vectors);
|
||||
return 0;
|
||||
err_irq_req_fail:
|
||||
mic_release_callbacks(mdev);
|
||||
err_nomem2:
|
||||
kfree(mdev->irq_info.mic_msi_map);
|
||||
err_nomem1:
|
||||
pci_disable_msi(pdev);
|
||||
mdev->irq_info.num_vectors = 0;
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* mic_setup_intx - Initializes legacy interrupts.
|
||||
*
|
||||
* @mdev: pointer to mic_device instance
|
||||
* @pdev: PCI device structure
|
||||
*
|
||||
* RETURNS: An appropriate -ERRNO error value on error, or zero for success.
|
||||
*/
|
||||
static int mic_setup_intx(struct mic_device *mdev, struct pci_dev *pdev)
|
||||
{
|
||||
int rc;
|
||||
|
||||
pci_msi_off(pdev);
|
||||
|
||||
/* Enable intx */
|
||||
pci_intx(pdev, 1);
|
||||
rc = mic_setup_callbacks(mdev);
|
||||
if (rc) {
|
||||
dev_err(&pdev->dev, "Error setting up callbacks\n");
|
||||
goto err_nomem;
|
||||
}
|
||||
|
||||
rc = request_threaded_irq(pdev->irq, mic_interrupt, mic_thread_fn,
|
||||
IRQF_SHARED, "mic-intx", mdev);
|
||||
if (rc)
|
||||
goto err;
|
||||
|
||||
dev_dbg(&pdev->dev, "intx irq setup\n");
|
||||
return 0;
|
||||
err:
|
||||
mic_release_callbacks(mdev);
|
||||
err_nomem:
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* mic_next_db - Retrieve the next doorbell interrupt source id.
|
||||
* The id is picked sequentially from the available pool of
|
||||
* doorlbell ids.
|
||||
*
|
||||
* @mdev: pointer to the mic_device instance.
|
||||
*
|
||||
* Returns the next doorbell interrupt source.
|
||||
*/
|
||||
int mic_next_db(struct mic_device *mdev)
|
||||
{
|
||||
int next_db;
|
||||
|
||||
next_db = mdev->irq_info.next_avail_src %
|
||||
mdev->intr_info->intr_len[MIC_INTR_DB];
|
||||
mdev->irq_info.next_avail_src++;
|
||||
return next_db;
|
||||
}
|
||||
|
||||
#define COOKIE_ID_SHIFT 16
|
||||
#define GET_ENTRY(cookie) ((cookie) & 0xFFFF)
|
||||
#define GET_OFFSET(cookie) ((cookie) >> COOKIE_ID_SHIFT)
|
||||
#define MK_COOKIE(x, y) ((x) | (y) << COOKIE_ID_SHIFT)
|
||||
|
||||
/**
|
||||
* mic_request_threaded_irq - request an irq. mic_mutex needs
|
||||
* to be held before calling this function.
|
||||
*
|
||||
* @mdev: pointer to mic_device instance
|
||||
* @handler: The callback function that handles the interrupt.
|
||||
* The function needs to call ack_interrupts
|
||||
* (mdev->ops->ack_interrupt(mdev)) when handling the interrupts.
|
||||
* @thread_fn: thread fn required by request_threaded_irq.
|
||||
* @name: The ASCII name of the callee requesting the irq.
|
||||
* @data: private data that is returned back when calling the
|
||||
* function handler.
|
||||
* @intr_src: The source id of the requester. Its the doorbell id
|
||||
* for Doorbell interrupts and DMA channel id for DMA interrupts.
|
||||
* @type: The type of interrupt. Values defined in mic_intr_type
|
||||
*
|
||||
* returns: The cookie that is transparent to the caller. Passed
|
||||
* back when calling mic_free_irq. An appropriate error code
|
||||
* is returned on failure. Caller needs to use IS_ERR(return_val)
|
||||
* to check for failure and PTR_ERR(return_val) to obtained the
|
||||
* error code.
|
||||
*
|
||||
*/
|
||||
struct mic_irq *
|
||||
mic_request_threaded_irq(struct mic_device *mdev,
|
||||
irq_handler_t handler, irq_handler_t thread_fn,
|
||||
const char *name, void *data, int intr_src,
|
||||
enum mic_intr_type type)
|
||||
{
|
||||
u16 offset;
|
||||
int rc = 0;
|
||||
struct msix_entry *msix = NULL;
|
||||
unsigned long cookie = 0;
|
||||
u16 entry;
|
||||
struct mic_intr_cb *intr_cb;
|
||||
struct pci_dev *pdev = container_of(mdev->sdev->parent,
|
||||
struct pci_dev, dev);
|
||||
|
||||
offset = mic_map_src_to_offset(mdev, intr_src, type);
|
||||
if (offset >= MIC_NUM_OFFSETS) {
|
||||
dev_err(mdev->sdev->parent,
|
||||
"Error mapping index %d to a valid source id.\n",
|
||||
intr_src);
|
||||
rc = -EINVAL;
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (mdev->irq_info.num_vectors > 1) {
|
||||
msix = mic_get_available_vector(mdev);
|
||||
if (!msix) {
|
||||
dev_err(mdev->sdev->parent,
|
||||
"No MSIx vectors available for use.\n");
|
||||
rc = -ENOSPC;
|
||||
goto err;
|
||||
}
|
||||
|
||||
rc = request_threaded_irq(msix->vector, handler, thread_fn,
|
||||
0, name, data);
|
||||
if (rc) {
|
||||
dev_dbg(mdev->sdev->parent,
|
||||
"request irq failed rc = %d\n", rc);
|
||||
goto err;
|
||||
}
|
||||
entry = msix->entry;
|
||||
mdev->irq_info.mic_msi_map[entry] |= BIT(offset);
|
||||
mdev->intr_ops->program_msi_to_src_map(mdev,
|
||||
entry, offset, true);
|
||||
cookie = MK_COOKIE(entry, offset);
|
||||
dev_dbg(mdev->sdev->parent, "irq: %d assigned for src: %d\n",
|
||||
msix->vector, intr_src);
|
||||
} else {
|
||||
intr_cb = mic_register_intr_callback(mdev, offset, handler,
|
||||
thread_fn, data);
|
||||
if (IS_ERR(intr_cb)) {
|
||||
dev_err(mdev->sdev->parent,
|
||||
"No available callback entries for use\n");
|
||||
rc = PTR_ERR(intr_cb);
|
||||
goto err;
|
||||
}
|
||||
|
||||
entry = 0;
|
||||
if (pci_dev_msi_enabled(pdev)) {
|
||||
mdev->irq_info.mic_msi_map[entry] |= (1 << offset);
|
||||
mdev->intr_ops->program_msi_to_src_map(mdev,
|
||||
entry, offset, true);
|
||||
}
|
||||
cookie = MK_COOKIE(entry, intr_cb->cb_id);
|
||||
dev_dbg(mdev->sdev->parent, "callback %d registered for src: %d\n",
|
||||
intr_cb->cb_id, intr_src);
|
||||
}
|
||||
return (struct mic_irq *)cookie;
|
||||
err:
|
||||
return ERR_PTR(rc);
|
||||
}
|
||||
|
||||
/**
|
||||
* mic_free_irq - free irq. mic_mutex
|
||||
* needs to be held before calling this function.
|
||||
*
|
||||
* @mdev: pointer to mic_device instance
|
||||
* @cookie: cookie obtained during a successful call to mic_request_threaded_irq
|
||||
* @data: private data specified by the calling function during the
|
||||
* mic_request_threaded_irq
|
||||
*
|
||||
* returns: none.
|
||||
*/
|
||||
void mic_free_irq(struct mic_device *mdev,
|
||||
struct mic_irq *cookie, void *data)
|
||||
{
|
||||
u32 offset;
|
||||
u32 entry;
|
||||
u8 src_id;
|
||||
unsigned int irq;
|
||||
struct pci_dev *pdev = container_of(mdev->sdev->parent,
|
||||
struct pci_dev, dev);
|
||||
|
||||
entry = GET_ENTRY((unsigned long)cookie);
|
||||
offset = GET_OFFSET((unsigned long)cookie);
|
||||
if (mdev->irq_info.num_vectors > 1) {
|
||||
if (entry >= mdev->irq_info.num_vectors) {
|
||||
dev_warn(mdev->sdev->parent,
|
||||
"entry %d should be < num_irq %d\n",
|
||||
entry, mdev->irq_info.num_vectors);
|
||||
return;
|
||||
}
|
||||
irq = mdev->irq_info.msix_entries[entry].vector;
|
||||
free_irq(irq, data);
|
||||
mdev->irq_info.mic_msi_map[entry] &= ~(BIT(offset));
|
||||
mdev->intr_ops->program_msi_to_src_map(mdev,
|
||||
entry, offset, false);
|
||||
|
||||
dev_dbg(mdev->sdev->parent, "irq: %d freed\n", irq);
|
||||
} else {
|
||||
irq = pdev->irq;
|
||||
src_id = mic_unregister_intr_callback(mdev, offset);
|
||||
if (src_id >= MIC_NUM_OFFSETS) {
|
||||
dev_warn(mdev->sdev->parent, "Error unregistering callback\n");
|
||||
return;
|
||||
}
|
||||
if (pci_dev_msi_enabled(pdev)) {
|
||||
mdev->irq_info.mic_msi_map[entry] &= ~(BIT(src_id));
|
||||
mdev->intr_ops->program_msi_to_src_map(mdev,
|
||||
entry, src_id, false);
|
||||
}
|
||||
dev_dbg(mdev->sdev->parent, "callback %d unregistered for src: %d\n",
|
||||
offset, src_id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* mic_setup_interrupts - Initializes interrupts.
|
||||
*
|
||||
* @mdev: pointer to mic_device instance
|
||||
* @pdev: PCI device structure
|
||||
*
|
||||
* RETURNS: An appropriate -ERRNO error value on error, or zero for success.
|
||||
*/
|
||||
int mic_setup_interrupts(struct mic_device *mdev, struct pci_dev *pdev)
|
||||
{
|
||||
int rc;
|
||||
|
||||
rc = mic_setup_msix(mdev, pdev);
|
||||
if (!rc)
|
||||
goto done;
|
||||
|
||||
rc = mic_setup_msi(mdev, pdev);
|
||||
if (!rc)
|
||||
goto done;
|
||||
|
||||
rc = mic_setup_intx(mdev, pdev);
|
||||
if (rc) {
|
||||
dev_err(mdev->sdev->parent, "no usable interrupts\n");
|
||||
return rc;
|
||||
}
|
||||
done:
|
||||
mdev->intr_ops->enable_interrupts(mdev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* mic_free_interrupts - Frees interrupts setup by mic_setup_interrupts
|
||||
*
|
||||
* @mdev: pointer to mic_device instance
|
||||
* @pdev: PCI device structure
|
||||
*
|
||||
* returns none.
|
||||
*/
|
||||
void mic_free_interrupts(struct mic_device *mdev, struct pci_dev *pdev)
|
||||
{
|
||||
int i;
|
||||
|
||||
mdev->intr_ops->disable_interrupts(mdev);
|
||||
if (mdev->irq_info.num_vectors > 1) {
|
||||
for (i = 0; i < mdev->irq_info.num_vectors; i++) {
|
||||
if (mdev->irq_info.mic_msi_map[i])
|
||||
dev_warn(&pdev->dev, "irq %d may still be in use.\n",
|
||||
mdev->irq_info.msix_entries[i].vector);
|
||||
}
|
||||
kfree(mdev->irq_info.mic_msi_map);
|
||||
kfree(mdev->irq_info.msix_entries);
|
||||
pci_disable_msix(pdev);
|
||||
} else {
|
||||
if (pci_dev_msi_enabled(pdev)) {
|
||||
free_irq(pdev->irq, mdev);
|
||||
kfree(mdev->irq_info.mic_msi_map);
|
||||
pci_disable_msi(pdev);
|
||||
} else {
|
||||
free_irq(pdev->irq, mdev);
|
||||
}
|
||||
mic_release_callbacks(mdev);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* mic_intr_restore - Restore MIC interrupt registers.
|
||||
*
|
||||
* @mdev: pointer to mic_device instance.
|
||||
*
|
||||
* Restore the interrupt registers to values previously
|
||||
* stored in the SW data structures. mic_mutex needs to
|
||||
* be held before calling this function.
|
||||
*
|
||||
* returns None.
|
||||
*/
|
||||
void mic_intr_restore(struct mic_device *mdev)
|
||||
{
|
||||
int entry, offset;
|
||||
struct pci_dev *pdev = container_of(mdev->sdev->parent,
|
||||
struct pci_dev, dev);
|
||||
|
||||
if (!pci_dev_msi_enabled(pdev))
|
||||
return;
|
||||
|
||||
for (entry = 0; entry < mdev->irq_info.num_vectors; entry++) {
|
||||
for (offset = 0; offset < MIC_NUM_OFFSETS; offset++) {
|
||||
if (mdev->irq_info.mic_msi_map[entry] & BIT(offset))
|
||||
mdev->intr_ops->program_msi_to_src_map(mdev,
|
||||
entry, offset, true);
|
||||
}
|
||||
}
|
||||
}
|
148
drivers/misc/mic/host/mic_intr.h
Normal file
148
drivers/misc/mic/host/mic_intr.h
Normal file
|
@ -0,0 +1,148 @@
|
|||
/*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
#ifndef _MIC_INTR_H_
|
||||
#define _MIC_INTR_H_
|
||||
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/interrupt.h>
|
||||
/*
|
||||
* The minimum number of msix vectors required for normal operation.
|
||||
* 3 for virtio network, console and block devices.
|
||||
* 1 for card shutdown notifications.
|
||||
* 4 for host owned DMA channels.
|
||||
*/
|
||||
#define MIC_MIN_MSIX 8
|
||||
#define MIC_NUM_OFFSETS 32
|
||||
|
||||
/**
|
||||
* mic_intr_source - The type of source that will generate
|
||||
* the interrupt.The number of types needs to be in sync with
|
||||
* MIC_NUM_INTR_TYPES
|
||||
*
|
||||
* MIC_INTR_DB: The source is a doorbell
|
||||
* MIC_INTR_DMA: The source is a DMA channel
|
||||
* MIC_INTR_ERR: The source is an error interrupt e.g. SBOX ERR
|
||||
* MIC_NUM_INTR_TYPES: Total number of interrupt sources.
|
||||
*/
|
||||
enum mic_intr_type {
|
||||
MIC_INTR_DB = 0,
|
||||
MIC_INTR_DMA,
|
||||
MIC_INTR_ERR,
|
||||
MIC_NUM_INTR_TYPES
|
||||
};
|
||||
|
||||
/**
|
||||
* struct mic_intr_info - Contains h/w specific interrupt sources
|
||||
* information.
|
||||
*
|
||||
* @intr_start_idx: Contains the starting indexes of the
|
||||
* interrupt types.
|
||||
* @intr_len: Contains the length of the interrupt types.
|
||||
*/
|
||||
struct mic_intr_info {
|
||||
u16 intr_start_idx[MIC_NUM_INTR_TYPES];
|
||||
u16 intr_len[MIC_NUM_INTR_TYPES];
|
||||
};
|
||||
|
||||
/**
|
||||
* struct mic_irq_info - OS specific irq information
|
||||
*
|
||||
* @next_avail_src: next available doorbell that can be assigned.
|
||||
* @msix_entries: msix entries allocated while setting up MSI-x
|
||||
* @mic_msi_map: The MSI/MSI-x mapping information.
|
||||
* @num_vectors: The number of MSI/MSI-x vectors that have been allocated.
|
||||
* @cb_ida: callback ID allocator to track the callbacks registered.
|
||||
* @mic_intr_lock: spinlock to protect the interrupt callback list.
|
||||
* @mic_thread_lock: spinlock to protect the thread callback list.
|
||||
* This lock is used to protect against thread_fn while
|
||||
* mic_intr_lock is used to protect against interrupt handler.
|
||||
* @cb_list: Array of callback lists one for each source.
|
||||
* @mask: Mask used by the main thread fn to call the underlying thread fns.
|
||||
*/
|
||||
struct mic_irq_info {
|
||||
int next_avail_src;
|
||||
struct msix_entry *msix_entries;
|
||||
u32 *mic_msi_map;
|
||||
u16 num_vectors;
|
||||
struct ida cb_ida;
|
||||
spinlock_t mic_intr_lock;
|
||||
spinlock_t mic_thread_lock;
|
||||
struct list_head *cb_list;
|
||||
unsigned long mask;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct mic_intr_cb - Interrupt callback structure.
|
||||
*
|
||||
* @handler: The callback function
|
||||
* @thread_fn: The thread_fn.
|
||||
* @data: Private data of the requester.
|
||||
* @cb_id: The callback id. Identifies this callback.
|
||||
* @list: list head pointing to the next callback structure.
|
||||
*/
|
||||
struct mic_intr_cb {
|
||||
irq_handler_t handler;
|
||||
irq_handler_t thread_fn;
|
||||
void *data;
|
||||
int cb_id;
|
||||
struct list_head list;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct mic_irq - opaque pointer used as cookie
|
||||
*/
|
||||
struct mic_irq;
|
||||
|
||||
/* Forward declaration */
|
||||
struct mic_device;
|
||||
|
||||
/**
|
||||
* struct mic_hw_intr_ops: MIC HW specific interrupt operations
|
||||
* @intr_init: Initialize H/W specific interrupt information.
|
||||
* @enable_interrupts: Enable interrupts from the hardware.
|
||||
* @disable_interrupts: Disable interrupts from the hardware.
|
||||
* @program_msi_to_src_map: Update MSI mapping registers with
|
||||
* irq information.
|
||||
* @read_msi_to_src_map: Read MSI mapping registers containing
|
||||
* irq information.
|
||||
*/
|
||||
struct mic_hw_intr_ops {
|
||||
void (*intr_init)(struct mic_device *mdev);
|
||||
void (*enable_interrupts)(struct mic_device *mdev);
|
||||
void (*disable_interrupts)(struct mic_device *mdev);
|
||||
void (*program_msi_to_src_map) (struct mic_device *mdev,
|
||||
int idx, int intr_src, bool set);
|
||||
u32 (*read_msi_to_src_map) (struct mic_device *mdev,
|
||||
int idx);
|
||||
};
|
||||
|
||||
int mic_next_db(struct mic_device *mdev);
|
||||
struct mic_irq *
|
||||
mic_request_threaded_irq(struct mic_device *mdev,
|
||||
irq_handler_t handler, irq_handler_t thread_fn,
|
||||
const char *name, void *data, int intr_src,
|
||||
enum mic_intr_type type);
|
||||
void mic_free_irq(struct mic_device *mdev,
|
||||
struct mic_irq *cookie, void *data);
|
||||
int mic_setup_interrupts(struct mic_device *mdev, struct pci_dev *pdev);
|
||||
void mic_free_interrupts(struct mic_device *mdev, struct pci_dev *pdev);
|
||||
void mic_intr_restore(struct mic_device *mdev);
|
||||
#endif
|
537
drivers/misc/mic/host/mic_main.c
Normal file
537
drivers/misc/mic/host/mic_main.c
Normal file
|
@ -0,0 +1,537 @@
|
|||
/*
|
||||
* 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.
|
||||
*
|
||||
* Global TODO's across the driver to be added after initial base
|
||||
* patches are accepted upstream:
|
||||
* 1) Enable DMA support.
|
||||
* 2) Enable per vring interrupt support.
|
||||
*/
|
||||
#include <linux/fs.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/poll.h>
|
||||
#include <linux/suspend.h>
|
||||
|
||||
#include <linux/mic_common.h>
|
||||
#include "../common/mic_dev.h"
|
||||
#include "mic_device.h"
|
||||
#include "mic_x100.h"
|
||||
#include "mic_smpt.h"
|
||||
#include "mic_fops.h"
|
||||
#include "mic_virtio.h"
|
||||
|
||||
static const char mic_driver_name[] = "mic";
|
||||
|
||||
static const struct pci_device_id mic_pci_tbl[] = {
|
||||
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, MIC_X100_PCI_DEVICE_2250)},
|
||||
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, MIC_X100_PCI_DEVICE_2251)},
|
||||
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, MIC_X100_PCI_DEVICE_2252)},
|
||||
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, MIC_X100_PCI_DEVICE_2253)},
|
||||
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, MIC_X100_PCI_DEVICE_2254)},
|
||||
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, MIC_X100_PCI_DEVICE_2255)},
|
||||
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, MIC_X100_PCI_DEVICE_2256)},
|
||||
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, MIC_X100_PCI_DEVICE_2257)},
|
||||
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, MIC_X100_PCI_DEVICE_2258)},
|
||||
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, MIC_X100_PCI_DEVICE_2259)},
|
||||
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, MIC_X100_PCI_DEVICE_225a)},
|
||||
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, MIC_X100_PCI_DEVICE_225b)},
|
||||
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, MIC_X100_PCI_DEVICE_225c)},
|
||||
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, MIC_X100_PCI_DEVICE_225d)},
|
||||
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, MIC_X100_PCI_DEVICE_225e)},
|
||||
|
||||
/* required last entry */
|
||||
{ 0, }
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(pci, mic_pci_tbl);
|
||||
|
||||
/* ID allocator for MIC devices */
|
||||
static struct ida g_mic_ida;
|
||||
/* Class of MIC devices for sysfs accessibility. */
|
||||
static struct class *g_mic_class;
|
||||
/* Base device node number for MIC devices */
|
||||
static dev_t g_mic_devno;
|
||||
|
||||
static const struct file_operations mic_fops = {
|
||||
.open = mic_open,
|
||||
.release = mic_release,
|
||||
.unlocked_ioctl = mic_ioctl,
|
||||
.poll = mic_poll,
|
||||
.mmap = mic_mmap,
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
/* Initialize the device page */
|
||||
static int mic_dp_init(struct mic_device *mdev)
|
||||
{
|
||||
mdev->dp = kzalloc(MIC_DP_SIZE, GFP_KERNEL);
|
||||
if (!mdev->dp) {
|
||||
dev_err(mdev->sdev->parent, "%s %d err %d\n",
|
||||
__func__, __LINE__, -ENOMEM);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
mdev->dp_dma_addr = mic_map_single(mdev,
|
||||
mdev->dp, MIC_DP_SIZE);
|
||||
if (mic_map_error(mdev->dp_dma_addr)) {
|
||||
kfree(mdev->dp);
|
||||
dev_err(mdev->sdev->parent, "%s %d err %d\n",
|
||||
__func__, __LINE__, -ENOMEM);
|
||||
return -ENOMEM;
|
||||
}
|
||||
mdev->ops->write_spad(mdev, MIC_DPLO_SPAD, mdev->dp_dma_addr);
|
||||
mdev->ops->write_spad(mdev, MIC_DPHI_SPAD, mdev->dp_dma_addr >> 32);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Uninitialize the device page */
|
||||
static void mic_dp_uninit(struct mic_device *mdev)
|
||||
{
|
||||
mic_unmap_single(mdev, mdev->dp_dma_addr, MIC_DP_SIZE);
|
||||
kfree(mdev->dp);
|
||||
}
|
||||
|
||||
/**
|
||||
* mic_shutdown_db - Shutdown doorbell interrupt handler.
|
||||
*/
|
||||
static irqreturn_t mic_shutdown_db(int irq, void *data)
|
||||
{
|
||||
struct mic_device *mdev = data;
|
||||
struct mic_bootparam *bootparam = mdev->dp;
|
||||
|
||||
mdev->ops->intr_workarounds(mdev);
|
||||
|
||||
switch (bootparam->shutdown_status) {
|
||||
case MIC_HALTED:
|
||||
case MIC_POWER_OFF:
|
||||
case MIC_RESTART:
|
||||
/* Fall through */
|
||||
case MIC_CRASHED:
|
||||
schedule_work(&mdev->shutdown_work);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
};
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
/**
|
||||
* mic_ops_init: Initialize HW specific operation tables.
|
||||
*
|
||||
* @mdev: pointer to mic_device instance
|
||||
*
|
||||
* returns none.
|
||||
*/
|
||||
static void mic_ops_init(struct mic_device *mdev)
|
||||
{
|
||||
switch (mdev->family) {
|
||||
case MIC_FAMILY_X100:
|
||||
mdev->ops = &mic_x100_ops;
|
||||
mdev->intr_ops = &mic_x100_intr_ops;
|
||||
mdev->smpt_ops = &mic_x100_smpt_ops;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* mic_get_family - Determine hardware family to which this MIC belongs.
|
||||
*
|
||||
* @pdev: The pci device structure
|
||||
*
|
||||
* returns family.
|
||||
*/
|
||||
static enum mic_hw_family mic_get_family(struct pci_dev *pdev)
|
||||
{
|
||||
enum mic_hw_family family;
|
||||
|
||||
switch (pdev->device) {
|
||||
case MIC_X100_PCI_DEVICE_2250:
|
||||
case MIC_X100_PCI_DEVICE_2251:
|
||||
case MIC_X100_PCI_DEVICE_2252:
|
||||
case MIC_X100_PCI_DEVICE_2253:
|
||||
case MIC_X100_PCI_DEVICE_2254:
|
||||
case MIC_X100_PCI_DEVICE_2255:
|
||||
case MIC_X100_PCI_DEVICE_2256:
|
||||
case MIC_X100_PCI_DEVICE_2257:
|
||||
case MIC_X100_PCI_DEVICE_2258:
|
||||
case MIC_X100_PCI_DEVICE_2259:
|
||||
case MIC_X100_PCI_DEVICE_225a:
|
||||
case MIC_X100_PCI_DEVICE_225b:
|
||||
case MIC_X100_PCI_DEVICE_225c:
|
||||
case MIC_X100_PCI_DEVICE_225d:
|
||||
case MIC_X100_PCI_DEVICE_225e:
|
||||
family = MIC_FAMILY_X100;
|
||||
break;
|
||||
default:
|
||||
family = MIC_FAMILY_UNKNOWN;
|
||||
break;
|
||||
}
|
||||
return family;
|
||||
}
|
||||
|
||||
/**
|
||||
* mic_pm_notifier: Notifier callback function that handles
|
||||
* PM notifications.
|
||||
*
|
||||
* @notifier_block: The notifier structure.
|
||||
* @pm_event: The event for which the driver was notified.
|
||||
* @unused: Meaningless. Always NULL.
|
||||
*
|
||||
* returns NOTIFY_DONE
|
||||
*/
|
||||
static int mic_pm_notifier(struct notifier_block *notifier,
|
||||
unsigned long pm_event, void *unused)
|
||||
{
|
||||
struct mic_device *mdev = container_of(notifier,
|
||||
struct mic_device, pm_notifier);
|
||||
|
||||
switch (pm_event) {
|
||||
case PM_HIBERNATION_PREPARE:
|
||||
/* Fall through */
|
||||
case PM_SUSPEND_PREPARE:
|
||||
mic_prepare_suspend(mdev);
|
||||
break;
|
||||
case PM_POST_HIBERNATION:
|
||||
/* Fall through */
|
||||
case PM_POST_SUSPEND:
|
||||
/* Fall through */
|
||||
case PM_POST_RESTORE:
|
||||
mic_complete_resume(mdev);
|
||||
break;
|
||||
case PM_RESTORE_PREPARE:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return NOTIFY_DONE;
|
||||
}
|
||||
|
||||
/**
|
||||
* mic_device_init - Allocates and initializes the MIC device structure
|
||||
*
|
||||
* @mdev: pointer to mic_device instance
|
||||
* @pdev: The pci device structure
|
||||
*
|
||||
* returns none.
|
||||
*/
|
||||
static int
|
||||
mic_device_init(struct mic_device *mdev, struct pci_dev *pdev)
|
||||
{
|
||||
int rc;
|
||||
|
||||
mdev->family = mic_get_family(pdev);
|
||||
mdev->stepping = pdev->revision;
|
||||
mic_ops_init(mdev);
|
||||
mic_sysfs_init(mdev);
|
||||
mutex_init(&mdev->mic_mutex);
|
||||
mdev->irq_info.next_avail_src = 0;
|
||||
INIT_WORK(&mdev->reset_trigger_work, mic_reset_trigger_work);
|
||||
INIT_WORK(&mdev->shutdown_work, mic_shutdown_work);
|
||||
init_completion(&mdev->reset_wait);
|
||||
INIT_LIST_HEAD(&mdev->vdev_list);
|
||||
mdev->pm_notifier.notifier_call = mic_pm_notifier;
|
||||
rc = register_pm_notifier(&mdev->pm_notifier);
|
||||
if (rc) {
|
||||
dev_err(&pdev->dev, "register_pm_notifier failed rc %d\n",
|
||||
rc);
|
||||
goto register_pm_notifier_fail;
|
||||
}
|
||||
return 0;
|
||||
register_pm_notifier_fail:
|
||||
flush_work(&mdev->shutdown_work);
|
||||
flush_work(&mdev->reset_trigger_work);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* mic_device_uninit - Frees resources allocated during mic_device_init(..)
|
||||
*
|
||||
* @mdev: pointer to mic_device instance
|
||||
*
|
||||
* returns none
|
||||
*/
|
||||
static void mic_device_uninit(struct mic_device *mdev)
|
||||
{
|
||||
/* The cmdline sysfs entry might have allocated cmdline */
|
||||
kfree(mdev->cmdline);
|
||||
kfree(mdev->firmware);
|
||||
kfree(mdev->ramdisk);
|
||||
kfree(mdev->bootmode);
|
||||
flush_work(&mdev->reset_trigger_work);
|
||||
flush_work(&mdev->shutdown_work);
|
||||
unregister_pm_notifier(&mdev->pm_notifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* mic_probe - Device Initialization Routine
|
||||
*
|
||||
* @pdev: PCI device structure
|
||||
* @ent: entry in mic_pci_tbl
|
||||
*
|
||||
* returns 0 on success, < 0 on failure.
|
||||
*/
|
||||
static int mic_probe(struct pci_dev *pdev,
|
||||
const struct pci_device_id *ent)
|
||||
{
|
||||
int rc;
|
||||
struct mic_device *mdev;
|
||||
|
||||
mdev = kzalloc(sizeof(*mdev), GFP_KERNEL);
|
||||
if (!mdev) {
|
||||
rc = -ENOMEM;
|
||||
dev_err(&pdev->dev, "mdev kmalloc failed rc %d\n", rc);
|
||||
goto mdev_alloc_fail;
|
||||
}
|
||||
mdev->id = ida_simple_get(&g_mic_ida, 0, MIC_MAX_NUM_DEVS, GFP_KERNEL);
|
||||
if (mdev->id < 0) {
|
||||
rc = mdev->id;
|
||||
dev_err(&pdev->dev, "ida_simple_get failed rc %d\n", rc);
|
||||
goto ida_fail;
|
||||
}
|
||||
|
||||
rc = mic_device_init(mdev, pdev);
|
||||
if (rc) {
|
||||
dev_err(&pdev->dev, "mic_device_init failed rc %d\n", rc);
|
||||
goto device_init_fail;
|
||||
}
|
||||
|
||||
rc = pci_enable_device(pdev);
|
||||
if (rc) {
|
||||
dev_err(&pdev->dev, "failed to enable pci device.\n");
|
||||
goto uninit_device;
|
||||
}
|
||||
|
||||
pci_set_master(pdev);
|
||||
|
||||
rc = pci_request_regions(pdev, mic_driver_name);
|
||||
if (rc) {
|
||||
dev_err(&pdev->dev, "failed to get pci regions.\n");
|
||||
goto disable_device;
|
||||
}
|
||||
|
||||
rc = pci_set_dma_mask(pdev, DMA_BIT_MASK(64));
|
||||
if (rc) {
|
||||
dev_err(&pdev->dev, "Cannot set DMA mask\n");
|
||||
goto release_regions;
|
||||
}
|
||||
|
||||
mdev->mmio.pa = pci_resource_start(pdev, mdev->ops->mmio_bar);
|
||||
mdev->mmio.len = pci_resource_len(pdev, mdev->ops->mmio_bar);
|
||||
mdev->mmio.va = pci_ioremap_bar(pdev, mdev->ops->mmio_bar);
|
||||
if (!mdev->mmio.va) {
|
||||
dev_err(&pdev->dev, "Cannot remap MMIO BAR\n");
|
||||
rc = -EIO;
|
||||
goto release_regions;
|
||||
}
|
||||
|
||||
mdev->aper.pa = pci_resource_start(pdev, mdev->ops->aper_bar);
|
||||
mdev->aper.len = pci_resource_len(pdev, mdev->ops->aper_bar);
|
||||
mdev->aper.va = ioremap_wc(mdev->aper.pa, mdev->aper.len);
|
||||
if (!mdev->aper.va) {
|
||||
dev_err(&pdev->dev, "Cannot remap Aperture BAR\n");
|
||||
rc = -EIO;
|
||||
goto unmap_mmio;
|
||||
}
|
||||
|
||||
mdev->intr_ops->intr_init(mdev);
|
||||
rc = mic_setup_interrupts(mdev, pdev);
|
||||
if (rc) {
|
||||
dev_err(&pdev->dev, "mic_setup_interrupts failed %d\n", rc);
|
||||
goto unmap_aper;
|
||||
}
|
||||
rc = mic_smpt_init(mdev);
|
||||
if (rc) {
|
||||
dev_err(&pdev->dev, "smpt_init failed %d\n", rc);
|
||||
goto free_interrupts;
|
||||
}
|
||||
|
||||
pci_set_drvdata(pdev, mdev);
|
||||
|
||||
mdev->sdev = device_create_with_groups(g_mic_class, &pdev->dev,
|
||||
MKDEV(MAJOR(g_mic_devno), mdev->id), NULL,
|
||||
mdev->attr_group, "mic%d", mdev->id);
|
||||
if (IS_ERR(mdev->sdev)) {
|
||||
rc = PTR_ERR(mdev->sdev);
|
||||
dev_err(&pdev->dev,
|
||||
"device_create_with_groups failed rc %d\n", rc);
|
||||
goto smpt_uninit;
|
||||
}
|
||||
mdev->state_sysfs = sysfs_get_dirent(mdev->sdev->kobj.sd, "state");
|
||||
if (!mdev->state_sysfs) {
|
||||
rc = -ENODEV;
|
||||
dev_err(&pdev->dev, "sysfs_get_dirent failed rc %d\n", rc);
|
||||
goto destroy_device;
|
||||
}
|
||||
|
||||
rc = mic_dp_init(mdev);
|
||||
if (rc) {
|
||||
dev_err(&pdev->dev, "mic_dp_init failed rc %d\n", rc);
|
||||
goto sysfs_put;
|
||||
}
|
||||
mutex_lock(&mdev->mic_mutex);
|
||||
|
||||
mdev->shutdown_db = mic_next_db(mdev);
|
||||
mdev->shutdown_cookie = mic_request_threaded_irq(mdev, mic_shutdown_db,
|
||||
NULL, "shutdown-interrupt", mdev,
|
||||
mdev->shutdown_db, MIC_INTR_DB);
|
||||
if (IS_ERR(mdev->shutdown_cookie)) {
|
||||
rc = PTR_ERR(mdev->shutdown_cookie);
|
||||
mutex_unlock(&mdev->mic_mutex);
|
||||
goto dp_uninit;
|
||||
}
|
||||
mutex_unlock(&mdev->mic_mutex);
|
||||
mic_bootparam_init(mdev);
|
||||
|
||||
mic_create_debug_dir(mdev);
|
||||
cdev_init(&mdev->cdev, &mic_fops);
|
||||
mdev->cdev.owner = THIS_MODULE;
|
||||
rc = cdev_add(&mdev->cdev, MKDEV(MAJOR(g_mic_devno), mdev->id), 1);
|
||||
if (rc) {
|
||||
dev_err(&pdev->dev, "cdev_add err id %d rc %d\n", mdev->id, rc);
|
||||
goto cleanup_debug_dir;
|
||||
}
|
||||
return 0;
|
||||
cleanup_debug_dir:
|
||||
mic_delete_debug_dir(mdev);
|
||||
mutex_lock(&mdev->mic_mutex);
|
||||
mic_free_irq(mdev, mdev->shutdown_cookie, mdev);
|
||||
mutex_unlock(&mdev->mic_mutex);
|
||||
dp_uninit:
|
||||
mic_dp_uninit(mdev);
|
||||
sysfs_put:
|
||||
sysfs_put(mdev->state_sysfs);
|
||||
destroy_device:
|
||||
device_destroy(g_mic_class, MKDEV(MAJOR(g_mic_devno), mdev->id));
|
||||
smpt_uninit:
|
||||
mic_smpt_uninit(mdev);
|
||||
free_interrupts:
|
||||
mic_free_interrupts(mdev, pdev);
|
||||
unmap_aper:
|
||||
iounmap(mdev->aper.va);
|
||||
unmap_mmio:
|
||||
iounmap(mdev->mmio.va);
|
||||
release_regions:
|
||||
pci_release_regions(pdev);
|
||||
disable_device:
|
||||
pci_disable_device(pdev);
|
||||
uninit_device:
|
||||
mic_device_uninit(mdev);
|
||||
device_init_fail:
|
||||
ida_simple_remove(&g_mic_ida, mdev->id);
|
||||
ida_fail:
|
||||
kfree(mdev);
|
||||
mdev_alloc_fail:
|
||||
dev_err(&pdev->dev, "Probe failed rc %d\n", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* mic_remove - Device Removal Routine
|
||||
* mic_remove is called by the PCI subsystem to alert the driver
|
||||
* that it should release a PCI device.
|
||||
*
|
||||
* @pdev: PCI device structure
|
||||
*/
|
||||
static void mic_remove(struct pci_dev *pdev)
|
||||
{
|
||||
struct mic_device *mdev;
|
||||
|
||||
mdev = pci_get_drvdata(pdev);
|
||||
if (!mdev)
|
||||
return;
|
||||
|
||||
mic_stop(mdev, false);
|
||||
cdev_del(&mdev->cdev);
|
||||
mic_delete_debug_dir(mdev);
|
||||
mutex_lock(&mdev->mic_mutex);
|
||||
mic_free_irq(mdev, mdev->shutdown_cookie, mdev);
|
||||
mutex_unlock(&mdev->mic_mutex);
|
||||
flush_work(&mdev->shutdown_work);
|
||||
mic_dp_uninit(mdev);
|
||||
sysfs_put(mdev->state_sysfs);
|
||||
device_destroy(g_mic_class, MKDEV(MAJOR(g_mic_devno), mdev->id));
|
||||
mic_smpt_uninit(mdev);
|
||||
mic_free_interrupts(mdev, pdev);
|
||||
iounmap(mdev->mmio.va);
|
||||
iounmap(mdev->aper.va);
|
||||
mic_device_uninit(mdev);
|
||||
pci_release_regions(pdev);
|
||||
pci_disable_device(pdev);
|
||||
ida_simple_remove(&g_mic_ida, mdev->id);
|
||||
kfree(mdev);
|
||||
}
|
||||
static struct pci_driver mic_driver = {
|
||||
.name = mic_driver_name,
|
||||
.id_table = mic_pci_tbl,
|
||||
.probe = mic_probe,
|
||||
.remove = mic_remove
|
||||
};
|
||||
|
||||
static int __init mic_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = alloc_chrdev_region(&g_mic_devno, 0,
|
||||
MIC_MAX_NUM_DEVS, mic_driver_name);
|
||||
if (ret) {
|
||||
pr_err("alloc_chrdev_region failed ret %d\n", ret);
|
||||
goto error;
|
||||
}
|
||||
|
||||
g_mic_class = class_create(THIS_MODULE, mic_driver_name);
|
||||
if (IS_ERR(g_mic_class)) {
|
||||
ret = PTR_ERR(g_mic_class);
|
||||
pr_err("class_create failed ret %d\n", ret);
|
||||
goto cleanup_chrdev;
|
||||
}
|
||||
|
||||
mic_init_debugfs();
|
||||
ida_init(&g_mic_ida);
|
||||
ret = pci_register_driver(&mic_driver);
|
||||
if (ret) {
|
||||
pr_err("pci_register_driver failed ret %d\n", ret);
|
||||
goto cleanup_debugfs;
|
||||
}
|
||||
return ret;
|
||||
cleanup_debugfs:
|
||||
mic_exit_debugfs();
|
||||
class_destroy(g_mic_class);
|
||||
cleanup_chrdev:
|
||||
unregister_chrdev_region(g_mic_devno, MIC_MAX_NUM_DEVS);
|
||||
error:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void __exit mic_exit(void)
|
||||
{
|
||||
pci_unregister_driver(&mic_driver);
|
||||
ida_destroy(&g_mic_ida);
|
||||
mic_exit_debugfs();
|
||||
class_destroy(g_mic_class);
|
||||
unregister_chrdev_region(g_mic_devno, MIC_MAX_NUM_DEVS);
|
||||
}
|
||||
|
||||
module_init(mic_init);
|
||||
module_exit(mic_exit);
|
||||
|
||||
MODULE_AUTHOR("Intel Corporation");
|
||||
MODULE_DESCRIPTION("Intel(R) MIC X100 Host driver");
|
||||
MODULE_LICENSE("GPL v2");
|
442
drivers/misc/mic/host/mic_smpt.c
Normal file
442
drivers/misc/mic/host/mic_smpt.c
Normal file
|
@ -0,0 +1,442 @@
|
|||
/*
|
||||
* 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 "../common/mic_dev.h"
|
||||
#include "mic_device.h"
|
||||
#include "mic_smpt.h"
|
||||
|
||||
static inline u64 mic_system_page_mask(struct mic_device *mdev)
|
||||
{
|
||||
return (1ULL << mdev->smpt->info.page_shift) - 1ULL;
|
||||
}
|
||||
|
||||
static inline u8 mic_sys_addr_to_smpt(struct mic_device *mdev, dma_addr_t pa)
|
||||
{
|
||||
return (pa - mdev->smpt->info.base) >> mdev->smpt->info.page_shift;
|
||||
}
|
||||
|
||||
static inline u64 mic_smpt_to_pa(struct mic_device *mdev, u8 index)
|
||||
{
|
||||
return mdev->smpt->info.base + (index * mdev->smpt->info.page_size);
|
||||
}
|
||||
|
||||
static inline u64 mic_smpt_offset(struct mic_device *mdev, dma_addr_t pa)
|
||||
{
|
||||
return pa & mic_system_page_mask(mdev);
|
||||
}
|
||||
|
||||
static inline u64 mic_smpt_align_low(struct mic_device *mdev, dma_addr_t pa)
|
||||
{
|
||||
return ALIGN(pa - mic_system_page_mask(mdev),
|
||||
mdev->smpt->info.page_size);
|
||||
}
|
||||
|
||||
static inline u64 mic_smpt_align_high(struct mic_device *mdev, dma_addr_t pa)
|
||||
{
|
||||
return ALIGN(pa, mdev->smpt->info.page_size);
|
||||
}
|
||||
|
||||
/* Total Cumulative system memory accessible by MIC across all SMPT entries */
|
||||
static inline u64 mic_max_system_memory(struct mic_device *mdev)
|
||||
{
|
||||
return mdev->smpt->info.num_reg * mdev->smpt->info.page_size;
|
||||
}
|
||||
|
||||
/* Maximum system memory address accessible by MIC */
|
||||
static inline u64 mic_max_system_addr(struct mic_device *mdev)
|
||||
{
|
||||
return mdev->smpt->info.base + mic_max_system_memory(mdev) - 1ULL;
|
||||
}
|
||||
|
||||
/* Check if the DMA address is a MIC system memory address */
|
||||
static inline bool
|
||||
mic_is_system_addr(struct mic_device *mdev, dma_addr_t pa)
|
||||
{
|
||||
return pa >= mdev->smpt->info.base && pa <= mic_max_system_addr(mdev);
|
||||
}
|
||||
|
||||
/* Populate an SMPT entry and update the reference counts. */
|
||||
static void mic_add_smpt_entry(int spt, s64 *ref, u64 addr,
|
||||
int entries, struct mic_device *mdev)
|
||||
{
|
||||
struct mic_smpt_info *smpt_info = mdev->smpt;
|
||||
int i;
|
||||
|
||||
for (i = spt; i < spt + entries; i++,
|
||||
addr += smpt_info->info.page_size) {
|
||||
if (!smpt_info->entry[i].ref_count &&
|
||||
(smpt_info->entry[i].dma_addr != addr)) {
|
||||
mdev->smpt_ops->set(mdev, addr, i);
|
||||
smpt_info->entry[i].dma_addr = addr;
|
||||
}
|
||||
smpt_info->entry[i].ref_count += ref[i - spt];
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Find an available MIC address in MIC SMPT address space
|
||||
* for a given DMA address and size.
|
||||
*/
|
||||
static dma_addr_t mic_smpt_op(struct mic_device *mdev, u64 dma_addr,
|
||||
int entries, s64 *ref, size_t size)
|
||||
{
|
||||
int spt;
|
||||
int ae = 0;
|
||||
int i;
|
||||
unsigned long flags;
|
||||
dma_addr_t mic_addr = 0;
|
||||
dma_addr_t addr = dma_addr;
|
||||
struct mic_smpt_info *smpt_info = mdev->smpt;
|
||||
|
||||
spin_lock_irqsave(&smpt_info->smpt_lock, flags);
|
||||
|
||||
/* find existing entries */
|
||||
for (i = 0; i < smpt_info->info.num_reg; i++) {
|
||||
if (smpt_info->entry[i].dma_addr == addr) {
|
||||
ae++;
|
||||
addr += smpt_info->info.page_size;
|
||||
} else if (ae) /* cannot find contiguous entries */
|
||||
goto not_found;
|
||||
|
||||
if (ae == entries)
|
||||
goto found;
|
||||
}
|
||||
|
||||
/* find free entry */
|
||||
for (ae = 0, i = 0; i < smpt_info->info.num_reg; i++) {
|
||||
ae = (smpt_info->entry[i].ref_count == 0) ? ae + 1 : 0;
|
||||
if (ae == entries)
|
||||
goto found;
|
||||
}
|
||||
|
||||
not_found:
|
||||
spin_unlock_irqrestore(&smpt_info->smpt_lock, flags);
|
||||
return mic_addr;
|
||||
|
||||
found:
|
||||
spt = i - entries + 1;
|
||||
mic_addr = mic_smpt_to_pa(mdev, spt);
|
||||
mic_add_smpt_entry(spt, ref, dma_addr, entries, mdev);
|
||||
smpt_info->map_count++;
|
||||
smpt_info->ref_count += (s64)size;
|
||||
spin_unlock_irqrestore(&smpt_info->smpt_lock, flags);
|
||||
return mic_addr;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns number of smpt entries needed for dma_addr to dma_addr + size
|
||||
* also returns the reference count array for each of those entries
|
||||
* and the starting smpt address
|
||||
*/
|
||||
static int mic_get_smpt_ref_count(struct mic_device *mdev, dma_addr_t dma_addr,
|
||||
size_t size, s64 *ref, u64 *smpt_start)
|
||||
{
|
||||
u64 start = dma_addr;
|
||||
u64 end = dma_addr + size;
|
||||
int i = 0;
|
||||
|
||||
while (start < end) {
|
||||
ref[i++] = min(mic_smpt_align_high(mdev, start + 1),
|
||||
end) - start;
|
||||
start = mic_smpt_align_high(mdev, start + 1);
|
||||
}
|
||||
|
||||
if (smpt_start)
|
||||
*smpt_start = mic_smpt_align_low(mdev, dma_addr);
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
/*
|
||||
* mic_to_dma_addr - Converts a MIC address to a DMA address.
|
||||
*
|
||||
* @mdev: pointer to mic_device instance.
|
||||
* @mic_addr: MIC address.
|
||||
*
|
||||
* returns a DMA address.
|
||||
*/
|
||||
static dma_addr_t
|
||||
mic_to_dma_addr(struct mic_device *mdev, dma_addr_t mic_addr)
|
||||
{
|
||||
struct mic_smpt_info *smpt_info = mdev->smpt;
|
||||
int spt;
|
||||
dma_addr_t dma_addr;
|
||||
|
||||
if (!mic_is_system_addr(mdev, mic_addr)) {
|
||||
dev_err(mdev->sdev->parent,
|
||||
"mic_addr is invalid. mic_addr = 0x%llx\n", mic_addr);
|
||||
return -EINVAL;
|
||||
}
|
||||
spt = mic_sys_addr_to_smpt(mdev, mic_addr);
|
||||
dma_addr = smpt_info->entry[spt].dma_addr +
|
||||
mic_smpt_offset(mdev, mic_addr);
|
||||
return dma_addr;
|
||||
}
|
||||
|
||||
/**
|
||||
* mic_map - Maps a DMA address to a MIC physical address.
|
||||
*
|
||||
* @mdev: pointer to mic_device instance.
|
||||
* @dma_addr: DMA address.
|
||||
* @size: Size of the region to be mapped.
|
||||
*
|
||||
* This API converts the DMA address provided to a DMA address understood
|
||||
* by MIC. Caller should check for errors by calling mic_map_error(..).
|
||||
*
|
||||
* returns DMA address as required by MIC.
|
||||
*/
|
||||
dma_addr_t mic_map(struct mic_device *mdev, dma_addr_t dma_addr, size_t size)
|
||||
{
|
||||
dma_addr_t mic_addr = 0;
|
||||
int num_entries;
|
||||
s64 *ref;
|
||||
u64 smpt_start;
|
||||
|
||||
if (!size || size > mic_max_system_memory(mdev))
|
||||
return mic_addr;
|
||||
|
||||
ref = kmalloc(mdev->smpt->info.num_reg * sizeof(s64), GFP_KERNEL);
|
||||
if (!ref)
|
||||
return mic_addr;
|
||||
|
||||
num_entries = mic_get_smpt_ref_count(mdev, dma_addr, size,
|
||||
ref, &smpt_start);
|
||||
|
||||
/* Set the smpt table appropriately and get 16G aligned mic address */
|
||||
mic_addr = mic_smpt_op(mdev, smpt_start, num_entries, ref, size);
|
||||
|
||||
kfree(ref);
|
||||
|
||||
/*
|
||||
* If mic_addr is zero then its an error case
|
||||
* since mic_addr can never be zero.
|
||||
* else generate mic_addr by adding the 16G offset in dma_addr
|
||||
*/
|
||||
if (!mic_addr && MIC_FAMILY_X100 == mdev->family) {
|
||||
dev_err(mdev->sdev->parent,
|
||||
"mic_map failed dma_addr 0x%llx size 0x%lx\n",
|
||||
dma_addr, size);
|
||||
return mic_addr;
|
||||
} else {
|
||||
return mic_addr + mic_smpt_offset(mdev, dma_addr);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* mic_unmap - Unmaps a MIC physical address.
|
||||
*
|
||||
* @mdev: pointer to mic_device instance.
|
||||
* @mic_addr: MIC physical address.
|
||||
* @size: Size of the region to be unmapped.
|
||||
*
|
||||
* This API unmaps the mappings created by mic_map(..).
|
||||
*
|
||||
* returns None.
|
||||
*/
|
||||
void mic_unmap(struct mic_device *mdev, dma_addr_t mic_addr, size_t size)
|
||||
{
|
||||
struct mic_smpt_info *smpt_info = mdev->smpt;
|
||||
s64 *ref;
|
||||
int num_smpt;
|
||||
int spt;
|
||||
int i;
|
||||
unsigned long flags;
|
||||
|
||||
if (!size)
|
||||
return;
|
||||
|
||||
if (!mic_is_system_addr(mdev, mic_addr)) {
|
||||
dev_err(mdev->sdev->parent,
|
||||
"invalid address: 0x%llx\n", mic_addr);
|
||||
return;
|
||||
}
|
||||
|
||||
spt = mic_sys_addr_to_smpt(mdev, mic_addr);
|
||||
ref = kmalloc(mdev->smpt->info.num_reg * sizeof(s64), GFP_KERNEL);
|
||||
if (!ref)
|
||||
return;
|
||||
|
||||
/* Get number of smpt entries to be mapped, ref count array */
|
||||
num_smpt = mic_get_smpt_ref_count(mdev, mic_addr, size, ref, NULL);
|
||||
|
||||
spin_lock_irqsave(&smpt_info->smpt_lock, flags);
|
||||
smpt_info->unmap_count++;
|
||||
smpt_info->ref_count -= (s64)size;
|
||||
|
||||
for (i = spt; i < spt + num_smpt; i++) {
|
||||
smpt_info->entry[i].ref_count -= ref[i - spt];
|
||||
if (smpt_info->entry[i].ref_count < 0)
|
||||
dev_warn(mdev->sdev->parent,
|
||||
"ref count for entry %d is negative\n", i);
|
||||
}
|
||||
spin_unlock_irqrestore(&smpt_info->smpt_lock, flags);
|
||||
kfree(ref);
|
||||
}
|
||||
|
||||
/**
|
||||
* mic_map_single - Maps a virtual address to a MIC physical address.
|
||||
*
|
||||
* @mdev: pointer to mic_device instance.
|
||||
* @va: Kernel direct mapped virtual address.
|
||||
* @size: Size of the region to be mapped.
|
||||
*
|
||||
* This API calls pci_map_single(..) for the direct mapped virtual address
|
||||
* and then converts the DMA address provided to a DMA address understood
|
||||
* by MIC. Caller should check for errors by calling mic_map_error(..).
|
||||
*
|
||||
* returns DMA address as required by MIC.
|
||||
*/
|
||||
dma_addr_t mic_map_single(struct mic_device *mdev, void *va, size_t size)
|
||||
{
|
||||
dma_addr_t mic_addr = 0;
|
||||
struct pci_dev *pdev = container_of(mdev->sdev->parent,
|
||||
struct pci_dev, dev);
|
||||
dma_addr_t dma_addr =
|
||||
pci_map_single(pdev, va, size, PCI_DMA_BIDIRECTIONAL);
|
||||
|
||||
if (!pci_dma_mapping_error(pdev, dma_addr)) {
|
||||
mic_addr = mic_map(mdev, dma_addr, size);
|
||||
if (!mic_addr) {
|
||||
dev_err(mdev->sdev->parent,
|
||||
"mic_map failed dma_addr 0x%llx size 0x%lx\n",
|
||||
dma_addr, size);
|
||||
pci_unmap_single(pdev, dma_addr,
|
||||
size, PCI_DMA_BIDIRECTIONAL);
|
||||
}
|
||||
}
|
||||
return mic_addr;
|
||||
}
|
||||
|
||||
/**
|
||||
* mic_unmap_single - Unmaps a MIC physical address.
|
||||
*
|
||||
* @mdev: pointer to mic_device instance.
|
||||
* @mic_addr: MIC physical address.
|
||||
* @size: Size of the region to be unmapped.
|
||||
*
|
||||
* This API unmaps the mappings created by mic_map_single(..).
|
||||
*
|
||||
* returns None.
|
||||
*/
|
||||
void
|
||||
mic_unmap_single(struct mic_device *mdev, dma_addr_t mic_addr, size_t size)
|
||||
{
|
||||
struct pci_dev *pdev = container_of(mdev->sdev->parent,
|
||||
struct pci_dev, dev);
|
||||
dma_addr_t dma_addr = mic_to_dma_addr(mdev, mic_addr);
|
||||
mic_unmap(mdev, mic_addr, size);
|
||||
pci_unmap_single(pdev, dma_addr, size, PCI_DMA_BIDIRECTIONAL);
|
||||
}
|
||||
|
||||
/**
|
||||
* mic_smpt_init - Initialize MIC System Memory Page Tables.
|
||||
*
|
||||
* @mdev: pointer to mic_device instance.
|
||||
*
|
||||
* returns 0 for success and -errno for error.
|
||||
*/
|
||||
int mic_smpt_init(struct mic_device *mdev)
|
||||
{
|
||||
int i, err = 0;
|
||||
dma_addr_t dma_addr;
|
||||
struct mic_smpt_info *smpt_info;
|
||||
|
||||
mdev->smpt = kmalloc(sizeof(*mdev->smpt), GFP_KERNEL);
|
||||
if (!mdev->smpt)
|
||||
return -ENOMEM;
|
||||
|
||||
smpt_info = mdev->smpt;
|
||||
mdev->smpt_ops->init(mdev);
|
||||
smpt_info->entry = kmalloc_array(smpt_info->info.num_reg,
|
||||
sizeof(*smpt_info->entry), GFP_KERNEL);
|
||||
if (!smpt_info->entry) {
|
||||
err = -ENOMEM;
|
||||
goto free_smpt;
|
||||
}
|
||||
spin_lock_init(&smpt_info->smpt_lock);
|
||||
for (i = 0; i < smpt_info->info.num_reg; i++) {
|
||||
dma_addr = i * smpt_info->info.page_size;
|
||||
smpt_info->entry[i].dma_addr = dma_addr;
|
||||
smpt_info->entry[i].ref_count = 0;
|
||||
mdev->smpt_ops->set(mdev, dma_addr, i);
|
||||
}
|
||||
smpt_info->ref_count = 0;
|
||||
smpt_info->map_count = 0;
|
||||
smpt_info->unmap_count = 0;
|
||||
return 0;
|
||||
free_smpt:
|
||||
kfree(smpt_info);
|
||||
return err;
|
||||
}
|
||||
|
||||
/**
|
||||
* mic_smpt_uninit - UnInitialize MIC System Memory Page Tables.
|
||||
*
|
||||
* @mdev: pointer to mic_device instance.
|
||||
*
|
||||
* returns None.
|
||||
*/
|
||||
void mic_smpt_uninit(struct mic_device *mdev)
|
||||
{
|
||||
struct mic_smpt_info *smpt_info = mdev->smpt;
|
||||
int i;
|
||||
|
||||
dev_dbg(mdev->sdev->parent,
|
||||
"nodeid %d SMPT ref count %lld map %lld unmap %lld\n",
|
||||
mdev->id, smpt_info->ref_count,
|
||||
smpt_info->map_count, smpt_info->unmap_count);
|
||||
|
||||
for (i = 0; i < smpt_info->info.num_reg; i++) {
|
||||
dev_dbg(mdev->sdev->parent,
|
||||
"SMPT entry[%d] dma_addr = 0x%llx ref_count = %lld\n",
|
||||
i, smpt_info->entry[i].dma_addr,
|
||||
smpt_info->entry[i].ref_count);
|
||||
if (smpt_info->entry[i].ref_count)
|
||||
dev_warn(mdev->sdev->parent,
|
||||
"ref count for entry %d is not zero\n", i);
|
||||
}
|
||||
kfree(smpt_info->entry);
|
||||
kfree(smpt_info);
|
||||
}
|
||||
|
||||
/**
|
||||
* mic_smpt_restore - Restore MIC System Memory Page Tables.
|
||||
*
|
||||
* @mdev: pointer to mic_device instance.
|
||||
*
|
||||
* Restore the SMPT registers to values previously stored in the
|
||||
* SW data structures. Some MIC steppings lose register state
|
||||
* across resets and this API should be called for performing
|
||||
* a restore operation if required.
|
||||
*
|
||||
* returns None.
|
||||
*/
|
||||
void mic_smpt_restore(struct mic_device *mdev)
|
||||
{
|
||||
int i;
|
||||
dma_addr_t dma_addr;
|
||||
|
||||
for (i = 0; i < mdev->smpt->info.num_reg; i++) {
|
||||
dma_addr = mdev->smpt->entry[i].dma_addr;
|
||||
mdev->smpt_ops->set(mdev, dma_addr, i);
|
||||
}
|
||||
}
|
98
drivers/misc/mic/host/mic_smpt.h
Normal file
98
drivers/misc/mic/host/mic_smpt.h
Normal file
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
#ifndef MIC_SMPT_H
|
||||
#define MIC_SMPT_H
|
||||
/**
|
||||
* struct mic_smpt_ops - MIC HW specific SMPT operations.
|
||||
* @init: Initialize hardware specific SMPT information in mic_smpt_hw_info.
|
||||
* @set: Set the value for a particular SMPT entry.
|
||||
*/
|
||||
struct mic_smpt_ops {
|
||||
void (*init)(struct mic_device *mdev);
|
||||
void (*set)(struct mic_device *mdev, dma_addr_t dma_addr, u8 index);
|
||||
};
|
||||
|
||||
/**
|
||||
* struct mic_smpt - MIC SMPT entry information.
|
||||
* @dma_addr: Base DMA address for this SMPT entry.
|
||||
* @ref_count: Number of active mappings for this SMPT entry in bytes.
|
||||
*/
|
||||
struct mic_smpt {
|
||||
dma_addr_t dma_addr;
|
||||
s64 ref_count;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct mic_smpt_hw_info - MIC SMPT hardware specific information.
|
||||
* @num_reg: Number of SMPT registers.
|
||||
* @page_shift: System memory page shift.
|
||||
* @page_size: System memory page size.
|
||||
* @base: System address base.
|
||||
*/
|
||||
struct mic_smpt_hw_info {
|
||||
u8 num_reg;
|
||||
u8 page_shift;
|
||||
u64 page_size;
|
||||
u64 base;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct mic_smpt_info - MIC SMPT information.
|
||||
* @entry: Array of SMPT entries.
|
||||
* @smpt_lock: Spin lock protecting access to SMPT data structures.
|
||||
* @info: Hardware specific SMPT information.
|
||||
* @ref_count: Number of active SMPT mappings (for debug).
|
||||
* @map_count: Number of SMPT mappings created (for debug).
|
||||
* @unmap_count: Number of SMPT mappings destroyed (for debug).
|
||||
*/
|
||||
struct mic_smpt_info {
|
||||
struct mic_smpt *entry;
|
||||
spinlock_t smpt_lock;
|
||||
struct mic_smpt_hw_info info;
|
||||
s64 ref_count;
|
||||
s64 map_count;
|
||||
s64 unmap_count;
|
||||
};
|
||||
|
||||
dma_addr_t mic_map_single(struct mic_device *mdev, void *va, size_t size);
|
||||
void mic_unmap_single(struct mic_device *mdev,
|
||||
dma_addr_t mic_addr, size_t size);
|
||||
dma_addr_t mic_map(struct mic_device *mdev,
|
||||
dma_addr_t dma_addr, size_t size);
|
||||
void mic_unmap(struct mic_device *mdev, dma_addr_t mic_addr, size_t size);
|
||||
|
||||
/**
|
||||
* mic_map_error - Check a MIC address for errors.
|
||||
*
|
||||
* @mdev: pointer to mic_device instance.
|
||||
*
|
||||
* returns Whether there was an error during mic_map..(..) APIs.
|
||||
*/
|
||||
static inline bool mic_map_error(dma_addr_t mic_addr)
|
||||
{
|
||||
return !mic_addr;
|
||||
}
|
||||
|
||||
int mic_smpt_init(struct mic_device *mdev);
|
||||
void mic_smpt_uninit(struct mic_device *mdev);
|
||||
void mic_smpt_restore(struct mic_device *mdev);
|
||||
|
||||
#endif
|
459
drivers/misc/mic/host/mic_sysfs.c
Normal file
459
drivers/misc/mic/host/mic_sysfs.c
Normal file
|
@ -0,0 +1,459 @@
|
|||
/*
|
||||
* 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/mic_common.h>
|
||||
#include "../common/mic_dev.h"
|
||||
#include "mic_device.h"
|
||||
|
||||
/*
|
||||
* A state-to-string lookup table, for exposing a human readable state
|
||||
* via sysfs. Always keep in sync with enum mic_states
|
||||
*/
|
||||
static const char * const mic_state_string[] = {
|
||||
[MIC_OFFLINE] = "offline",
|
||||
[MIC_ONLINE] = "online",
|
||||
[MIC_SHUTTING_DOWN] = "shutting_down",
|
||||
[MIC_RESET_FAILED] = "reset_failed",
|
||||
[MIC_SUSPENDING] = "suspending",
|
||||
[MIC_SUSPENDED] = "suspended",
|
||||
};
|
||||
|
||||
/*
|
||||
* A shutdown-status-to-string lookup table, for exposing a human
|
||||
* readable state via sysfs. Always keep in sync with enum mic_shutdown_status
|
||||
*/
|
||||
static const char * const mic_shutdown_status_string[] = {
|
||||
[MIC_NOP] = "nop",
|
||||
[MIC_CRASHED] = "crashed",
|
||||
[MIC_HALTED] = "halted",
|
||||
[MIC_POWER_OFF] = "poweroff",
|
||||
[MIC_RESTART] = "restart",
|
||||
};
|
||||
|
||||
void mic_set_shutdown_status(struct mic_device *mdev, u8 shutdown_status)
|
||||
{
|
||||
dev_dbg(mdev->sdev->parent, "Shutdown Status %s -> %s\n",
|
||||
mic_shutdown_status_string[mdev->shutdown_status],
|
||||
mic_shutdown_status_string[shutdown_status]);
|
||||
mdev->shutdown_status = shutdown_status;
|
||||
}
|
||||
|
||||
void mic_set_state(struct mic_device *mdev, u8 state)
|
||||
{
|
||||
dev_dbg(mdev->sdev->parent, "State %s -> %s\n",
|
||||
mic_state_string[mdev->state],
|
||||
mic_state_string[state]);
|
||||
mdev->state = state;
|
||||
sysfs_notify_dirent(mdev->state_sysfs);
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
family_show(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
static const char x100[] = "x100";
|
||||
static const char unknown[] = "Unknown";
|
||||
const char *card = NULL;
|
||||
struct mic_device *mdev = dev_get_drvdata(dev->parent);
|
||||
|
||||
if (!mdev)
|
||||
return -EINVAL;
|
||||
|
||||
switch (mdev->family) {
|
||||
case MIC_FAMILY_X100:
|
||||
card = x100;
|
||||
break;
|
||||
default:
|
||||
card = unknown;
|
||||
break;
|
||||
}
|
||||
return scnprintf(buf, PAGE_SIZE, "%s\n", card);
|
||||
}
|
||||
static DEVICE_ATTR_RO(family);
|
||||
|
||||
static ssize_t
|
||||
stepping_show(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct mic_device *mdev = dev_get_drvdata(dev->parent);
|
||||
char *string = "??";
|
||||
|
||||
if (!mdev)
|
||||
return -EINVAL;
|
||||
|
||||
switch (mdev->stepping) {
|
||||
case MIC_A0_STEP:
|
||||
string = "A0";
|
||||
break;
|
||||
case MIC_B0_STEP:
|
||||
string = "B0";
|
||||
break;
|
||||
case MIC_B1_STEP:
|
||||
string = "B1";
|
||||
break;
|
||||
case MIC_C0_STEP:
|
||||
string = "C0";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return scnprintf(buf, PAGE_SIZE, "%s\n", string);
|
||||
}
|
||||
static DEVICE_ATTR_RO(stepping);
|
||||
|
||||
static ssize_t
|
||||
state_show(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct mic_device *mdev = dev_get_drvdata(dev->parent);
|
||||
|
||||
if (!mdev || mdev->state >= MIC_LAST)
|
||||
return -EINVAL;
|
||||
|
||||
return scnprintf(buf, PAGE_SIZE, "%s\n",
|
||||
mic_state_string[mdev->state]);
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
state_store(struct device *dev, struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
int rc = 0;
|
||||
struct mic_device *mdev = dev_get_drvdata(dev->parent);
|
||||
if (!mdev)
|
||||
return -EINVAL;
|
||||
if (sysfs_streq(buf, "boot")) {
|
||||
rc = mic_start(mdev, buf);
|
||||
if (rc) {
|
||||
dev_err(mdev->sdev->parent,
|
||||
"mic_boot failed rc %d\n", rc);
|
||||
count = rc;
|
||||
}
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (sysfs_streq(buf, "reset")) {
|
||||
schedule_work(&mdev->reset_trigger_work);
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (sysfs_streq(buf, "shutdown")) {
|
||||
mic_shutdown(mdev);
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (sysfs_streq(buf, "suspend")) {
|
||||
mic_suspend(mdev);
|
||||
goto done;
|
||||
}
|
||||
|
||||
count = -EINVAL;
|
||||
done:
|
||||
return count;
|
||||
}
|
||||
static DEVICE_ATTR_RW(state);
|
||||
|
||||
static ssize_t shutdown_status_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct mic_device *mdev = dev_get_drvdata(dev->parent);
|
||||
|
||||
if (!mdev || mdev->shutdown_status >= MIC_STATUS_LAST)
|
||||
return -EINVAL;
|
||||
|
||||
return scnprintf(buf, PAGE_SIZE, "%s\n",
|
||||
mic_shutdown_status_string[mdev->shutdown_status]);
|
||||
}
|
||||
static DEVICE_ATTR_RO(shutdown_status);
|
||||
|
||||
static ssize_t
|
||||
cmdline_show(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct mic_device *mdev = dev_get_drvdata(dev->parent);
|
||||
char *cmdline;
|
||||
|
||||
if (!mdev)
|
||||
return -EINVAL;
|
||||
|
||||
cmdline = mdev->cmdline;
|
||||
|
||||
if (cmdline)
|
||||
return scnprintf(buf, PAGE_SIZE, "%s\n", cmdline);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
cmdline_store(struct device *dev, struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct mic_device *mdev = dev_get_drvdata(dev->parent);
|
||||
|
||||
if (!mdev)
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&mdev->mic_mutex);
|
||||
kfree(mdev->cmdline);
|
||||
|
||||
mdev->cmdline = kmalloc(count + 1, GFP_KERNEL);
|
||||
if (!mdev->cmdline) {
|
||||
count = -ENOMEM;
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
strncpy(mdev->cmdline, buf, count);
|
||||
|
||||
if (mdev->cmdline[count - 1] == '\n')
|
||||
mdev->cmdline[count - 1] = '\0';
|
||||
else
|
||||
mdev->cmdline[count] = '\0';
|
||||
unlock:
|
||||
mutex_unlock(&mdev->mic_mutex);
|
||||
return count;
|
||||
}
|
||||
static DEVICE_ATTR_RW(cmdline);
|
||||
|
||||
static ssize_t
|
||||
firmware_show(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct mic_device *mdev = dev_get_drvdata(dev->parent);
|
||||
char *firmware;
|
||||
|
||||
if (!mdev)
|
||||
return -EINVAL;
|
||||
|
||||
firmware = mdev->firmware;
|
||||
|
||||
if (firmware)
|
||||
return scnprintf(buf, PAGE_SIZE, "%s\n", firmware);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
firmware_store(struct device *dev, struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct mic_device *mdev = dev_get_drvdata(dev->parent);
|
||||
|
||||
if (!mdev)
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&mdev->mic_mutex);
|
||||
kfree(mdev->firmware);
|
||||
|
||||
mdev->firmware = kmalloc(count + 1, GFP_KERNEL);
|
||||
if (!mdev->firmware) {
|
||||
count = -ENOMEM;
|
||||
goto unlock;
|
||||
}
|
||||
strncpy(mdev->firmware, buf, count);
|
||||
|
||||
if (mdev->firmware[count - 1] == '\n')
|
||||
mdev->firmware[count - 1] = '\0';
|
||||
else
|
||||
mdev->firmware[count] = '\0';
|
||||
unlock:
|
||||
mutex_unlock(&mdev->mic_mutex);
|
||||
return count;
|
||||
}
|
||||
static DEVICE_ATTR_RW(firmware);
|
||||
|
||||
static ssize_t
|
||||
ramdisk_show(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct mic_device *mdev = dev_get_drvdata(dev->parent);
|
||||
char *ramdisk;
|
||||
|
||||
if (!mdev)
|
||||
return -EINVAL;
|
||||
|
||||
ramdisk = mdev->ramdisk;
|
||||
|
||||
if (ramdisk)
|
||||
return scnprintf(buf, PAGE_SIZE, "%s\n", ramdisk);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
ramdisk_store(struct device *dev, struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct mic_device *mdev = dev_get_drvdata(dev->parent);
|
||||
|
||||
if (!mdev)
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&mdev->mic_mutex);
|
||||
kfree(mdev->ramdisk);
|
||||
|
||||
mdev->ramdisk = kmalloc(count + 1, GFP_KERNEL);
|
||||
if (!mdev->ramdisk) {
|
||||
count = -ENOMEM;
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
strncpy(mdev->ramdisk, buf, count);
|
||||
|
||||
if (mdev->ramdisk[count - 1] == '\n')
|
||||
mdev->ramdisk[count - 1] = '\0';
|
||||
else
|
||||
mdev->ramdisk[count] = '\0';
|
||||
unlock:
|
||||
mutex_unlock(&mdev->mic_mutex);
|
||||
return count;
|
||||
}
|
||||
static DEVICE_ATTR_RW(ramdisk);
|
||||
|
||||
static ssize_t
|
||||
bootmode_show(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct mic_device *mdev = dev_get_drvdata(dev->parent);
|
||||
char *bootmode;
|
||||
|
||||
if (!mdev)
|
||||
return -EINVAL;
|
||||
|
||||
bootmode = mdev->bootmode;
|
||||
|
||||
if (bootmode)
|
||||
return scnprintf(buf, PAGE_SIZE, "%s\n", bootmode);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
bootmode_store(struct device *dev, struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct mic_device *mdev = dev_get_drvdata(dev->parent);
|
||||
|
||||
if (!mdev)
|
||||
return -EINVAL;
|
||||
|
||||
if (!sysfs_streq(buf, "linux") && !sysfs_streq(buf, "elf"))
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&mdev->mic_mutex);
|
||||
kfree(mdev->bootmode);
|
||||
|
||||
mdev->bootmode = kmalloc(count + 1, GFP_KERNEL);
|
||||
if (!mdev->bootmode) {
|
||||
count = -ENOMEM;
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
strncpy(mdev->bootmode, buf, count);
|
||||
|
||||
if (mdev->bootmode[count - 1] == '\n')
|
||||
mdev->bootmode[count - 1] = '\0';
|
||||
else
|
||||
mdev->bootmode[count] = '\0';
|
||||
unlock:
|
||||
mutex_unlock(&mdev->mic_mutex);
|
||||
return count;
|
||||
}
|
||||
static DEVICE_ATTR_RW(bootmode);
|
||||
|
||||
static ssize_t
|
||||
log_buf_addr_show(struct device *dev, struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct mic_device *mdev = dev_get_drvdata(dev->parent);
|
||||
|
||||
if (!mdev)
|
||||
return -EINVAL;
|
||||
|
||||
return scnprintf(buf, PAGE_SIZE, "%p\n", mdev->log_buf_addr);
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
log_buf_addr_store(struct device *dev, struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct mic_device *mdev = dev_get_drvdata(dev->parent);
|
||||
int ret;
|
||||
unsigned long addr;
|
||||
|
||||
if (!mdev)
|
||||
return -EINVAL;
|
||||
|
||||
ret = kstrtoul(buf, 16, &addr);
|
||||
if (ret)
|
||||
goto exit;
|
||||
|
||||
mdev->log_buf_addr = (void *)addr;
|
||||
ret = count;
|
||||
exit:
|
||||
return ret;
|
||||
}
|
||||
static DEVICE_ATTR_RW(log_buf_addr);
|
||||
|
||||
static ssize_t
|
||||
log_buf_len_show(struct device *dev, struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct mic_device *mdev = dev_get_drvdata(dev->parent);
|
||||
|
||||
if (!mdev)
|
||||
return -EINVAL;
|
||||
|
||||
return scnprintf(buf, PAGE_SIZE, "%p\n", mdev->log_buf_len);
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
log_buf_len_store(struct device *dev, struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct mic_device *mdev = dev_get_drvdata(dev->parent);
|
||||
int ret;
|
||||
unsigned long addr;
|
||||
|
||||
if (!mdev)
|
||||
return -EINVAL;
|
||||
|
||||
ret = kstrtoul(buf, 16, &addr);
|
||||
if (ret)
|
||||
goto exit;
|
||||
|
||||
mdev->log_buf_len = (int *)addr;
|
||||
ret = count;
|
||||
exit:
|
||||
return ret;
|
||||
}
|
||||
static DEVICE_ATTR_RW(log_buf_len);
|
||||
|
||||
static struct attribute *mic_default_attrs[] = {
|
||||
&dev_attr_family.attr,
|
||||
&dev_attr_stepping.attr,
|
||||
&dev_attr_state.attr,
|
||||
&dev_attr_shutdown_status.attr,
|
||||
&dev_attr_cmdline.attr,
|
||||
&dev_attr_firmware.attr,
|
||||
&dev_attr_ramdisk.attr,
|
||||
&dev_attr_bootmode.attr,
|
||||
&dev_attr_log_buf_addr.attr,
|
||||
&dev_attr_log_buf_len.attr,
|
||||
|
||||
NULL
|
||||
};
|
||||
|
||||
ATTRIBUTE_GROUPS(mic_default);
|
||||
|
||||
void mic_sysfs_init(struct mic_device *mdev)
|
||||
{
|
||||
mdev->attr_group = mic_default_groups;
|
||||
}
|
812
drivers/misc/mic/host/mic_virtio.c
Normal file
812
drivers/misc/mic/host/mic_virtio.c
Normal file
|
@ -0,0 +1,812 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
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->sdev->parent, "%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->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->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->sdev->parent, "%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->sdev->parent,
|
||||
"%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->sdev->parent, "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->sdev->parent, "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->sdev->parent,
|
||||
"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->sdev->parent,
|
||||
"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->sdev->parent,
|
||||
"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);
|
||||
}
|
155
drivers/misc/mic/host/mic_virtio.h
Normal file
155
drivers/misc/mic/host/mic_virtio.h
Normal file
|
@ -0,0 +1,155 @@
|
|||
/*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
#ifndef MIC_VIRTIO_H
|
||||
#define MIC_VIRTIO_H
|
||||
|
||||
#include <linux/virtio_config.h>
|
||||
#include <linux/mic_ioctl.h>
|
||||
|
||||
/*
|
||||
* Note on endianness.
|
||||
* 1. Host can be both BE or LE
|
||||
* 2. Guest/card is LE. Host uses le_to_cpu to access desc/avail
|
||||
* rings and ioreadXX/iowriteXX to access used ring.
|
||||
* 3. Device page exposed by host to guest contains LE values. Guest
|
||||
* accesses these using ioreadXX/iowriteXX etc. This way in general we
|
||||
* obey the virtio spec according to which guest works with native
|
||||
* endianness and host is aware of guest endianness and does all
|
||||
* required endianness conversion.
|
||||
* 4. Data provided from user space to guest (in ADD_DEVICE and
|
||||
* CONFIG_CHANGE ioctl's) is not interpreted by the driver and should be
|
||||
* in guest endianness.
|
||||
*/
|
||||
|
||||
/**
|
||||
* struct mic_vringh - Virtio ring host information.
|
||||
*
|
||||
* @vring: The MIC vring used for setting up user space mappings.
|
||||
* @vrh: The host VRINGH used for accessing the card vrings.
|
||||
* @riov: The VRINGH read kernel IOV.
|
||||
* @wiov: The VRINGH write kernel IOV.
|
||||
* @vr_mutex: Mutex for synchronizing access to the VRING.
|
||||
* @buf: Temporary kernel buffer used to copy in/out data
|
||||
* from/to the card via DMA.
|
||||
* @buf_da: dma address of buf.
|
||||
* @mvdev: Back pointer to MIC virtio device for vringh_notify(..).
|
||||
* @head: The VRINGH head index address passed to vringh_getdesc_kern(..).
|
||||
*/
|
||||
struct mic_vringh {
|
||||
struct mic_vring vring;
|
||||
struct vringh vrh;
|
||||
struct vringh_kiov riov;
|
||||
struct vringh_kiov wiov;
|
||||
struct mutex vr_mutex;
|
||||
void *buf;
|
||||
dma_addr_t buf_da;
|
||||
struct mic_vdev *mvdev;
|
||||
u16 head;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct mic_vdev - Host information for a card Virtio device.
|
||||
*
|
||||
* @virtio_id - Virtio device id.
|
||||
* @waitq - Waitqueue to allow ring3 apps to poll.
|
||||
* @mdev - Back pointer to host MIC device.
|
||||
* @poll_wake - Used for waking up threads blocked in poll.
|
||||
* @out_bytes - Debug stats for number of bytes copied from host to card.
|
||||
* @in_bytes - Debug stats for number of bytes copied from card to host.
|
||||
* @out_bytes_dma - Debug stats for number of bytes copied from host to card
|
||||
* using DMA.
|
||||
* @in_bytes_dma - Debug stats for number of bytes copied from card to host
|
||||
* using DMA.
|
||||
* @tx_len_unaligned - Debug stats for number of bytes copied to the card where
|
||||
* the transfer length did not have the required DMA alignment.
|
||||
* @tx_dst_unaligned - Debug stats for number of bytes copied where the
|
||||
* destination address on the card did not have the required DMA alignment.
|
||||
* @mvr - Store per VRING data structures.
|
||||
* @virtio_bh_work - Work struct used to schedule virtio bottom half handling.
|
||||
* @dd - Virtio device descriptor.
|
||||
* @dc - Virtio device control fields.
|
||||
* @list - List of Virtio devices.
|
||||
* @virtio_db - The doorbell used by the card to interrupt the host.
|
||||
* @virtio_cookie - The cookie returned while requesting interrupts.
|
||||
*/
|
||||
struct mic_vdev {
|
||||
int virtio_id;
|
||||
wait_queue_head_t waitq;
|
||||
struct mic_device *mdev;
|
||||
int poll_wake;
|
||||
unsigned long out_bytes;
|
||||
unsigned long in_bytes;
|
||||
unsigned long out_bytes_dma;
|
||||
unsigned long in_bytes_dma;
|
||||
unsigned long tx_len_unaligned;
|
||||
unsigned long tx_dst_unaligned;
|
||||
struct mic_vringh mvr[MIC_MAX_VRINGS];
|
||||
struct work_struct virtio_bh_work;
|
||||
struct mic_device_desc *dd;
|
||||
struct mic_device_ctrl *dc;
|
||||
struct list_head list;
|
||||
int virtio_db;
|
||||
struct mic_irq *virtio_cookie;
|
||||
};
|
||||
|
||||
void mic_virtio_uninit(struct mic_device *mdev);
|
||||
int mic_virtio_add_device(struct mic_vdev *mvdev,
|
||||
void __user *argp);
|
||||
void mic_virtio_del_device(struct mic_vdev *mvdev);
|
||||
int mic_virtio_config_change(struct mic_vdev *mvdev,
|
||||
void __user *argp);
|
||||
int mic_virtio_copy_desc(struct mic_vdev *mvdev,
|
||||
struct mic_copy_desc *request);
|
||||
void mic_virtio_reset_devices(struct mic_device *mdev);
|
||||
void mic_bh_handler(struct work_struct *work);
|
||||
|
||||
/* Helper API to obtain the MIC PCIe device */
|
||||
static inline struct device *mic_dev(struct mic_vdev *mvdev)
|
||||
{
|
||||
return mvdev->mdev->sdev->parent;
|
||||
}
|
||||
|
||||
/* Helper API to check if a virtio device is initialized */
|
||||
static inline int mic_vdev_inited(struct mic_vdev *mvdev)
|
||||
{
|
||||
/* Device has not been created yet */
|
||||
if (!mvdev->dd || !mvdev->dd->type) {
|
||||
dev_err(mic_dev(mvdev), "%s %d err %d\n",
|
||||
__func__, __LINE__, -EINVAL);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Device has been removed/deleted */
|
||||
if (mvdev->dd->type == -1) {
|
||||
dev_err(mic_dev(mvdev), "%s %d err %d\n",
|
||||
__func__, __LINE__, -ENODEV);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Helper API to check if a virtio device is running */
|
||||
static inline bool mic_vdevup(struct mic_vdev *mvdev)
|
||||
{
|
||||
return !!mvdev->dd->status;
|
||||
}
|
||||
#endif
|
582
drivers/misc/mic/host/mic_x100.c
Normal file
582
drivers/misc/mic/host/mic_x100.c
Normal file
|
@ -0,0 +1,582 @@
|
|||
/*
|
||||
* 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/fs.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/firmware.h>
|
||||
#include <linux/delay.h>
|
||||
|
||||
#include "../common/mic_dev.h"
|
||||
#include "mic_device.h"
|
||||
#include "mic_x100.h"
|
||||
#include "mic_smpt.h"
|
||||
|
||||
/**
|
||||
* mic_x100_write_spad - write to the scratchpad register
|
||||
* @mdev: pointer to mic_device instance
|
||||
* @idx: index to the scratchpad register, 0 based
|
||||
* @val: the data value to put into the register
|
||||
*
|
||||
* This function allows writing of a 32bit value to the indexed scratchpad
|
||||
* register.
|
||||
*
|
||||
* RETURNS: none.
|
||||
*/
|
||||
static void
|
||||
mic_x100_write_spad(struct mic_device *mdev, unsigned int idx, u32 val)
|
||||
{
|
||||
dev_dbg(mdev->sdev->parent, "Writing 0x%x to scratch pad index %d\n",
|
||||
val, idx);
|
||||
mic_mmio_write(&mdev->mmio, val,
|
||||
MIC_X100_SBOX_BASE_ADDRESS +
|
||||
MIC_X100_SBOX_SPAD0 + idx * 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* mic_x100_read_spad - read from the scratchpad register
|
||||
* @mdev: pointer to mic_device instance
|
||||
* @idx: index to scratchpad register, 0 based
|
||||
*
|
||||
* This function allows reading of the 32bit scratchpad register.
|
||||
*
|
||||
* RETURNS: An appropriate -ERRNO error value on error, or zero for success.
|
||||
*/
|
||||
static u32
|
||||
mic_x100_read_spad(struct mic_device *mdev, unsigned int idx)
|
||||
{
|
||||
u32 val = mic_mmio_read(&mdev->mmio,
|
||||
MIC_X100_SBOX_BASE_ADDRESS +
|
||||
MIC_X100_SBOX_SPAD0 + idx * 4);
|
||||
|
||||
dev_dbg(mdev->sdev->parent,
|
||||
"Reading 0x%x from scratch pad index %d\n", val, idx);
|
||||
return val;
|
||||
}
|
||||
|
||||
/**
|
||||
* mic_x100_enable_interrupts - Enable interrupts.
|
||||
* @mdev: pointer to mic_device instance
|
||||
*/
|
||||
static void mic_x100_enable_interrupts(struct mic_device *mdev)
|
||||
{
|
||||
u32 reg;
|
||||
struct mic_mw *mw = &mdev->mmio;
|
||||
u32 sice0 = MIC_X100_SBOX_BASE_ADDRESS + MIC_X100_SBOX_SICE0;
|
||||
u32 siac0 = MIC_X100_SBOX_BASE_ADDRESS + MIC_X100_SBOX_SIAC0;
|
||||
|
||||
reg = mic_mmio_read(mw, sice0);
|
||||
reg |= MIC_X100_SBOX_DBR_BITS(0xf) | MIC_X100_SBOX_DMA_BITS(0xff);
|
||||
mic_mmio_write(mw, reg, sice0);
|
||||
|
||||
/*
|
||||
* Enable auto-clear when enabling interrupts. Applicable only for
|
||||
* MSI-x. Legacy and MSI mode cannot have auto-clear enabled.
|
||||
*/
|
||||
if (mdev->irq_info.num_vectors > 1) {
|
||||
reg = mic_mmio_read(mw, siac0);
|
||||
reg |= MIC_X100_SBOX_DBR_BITS(0xf) |
|
||||
MIC_X100_SBOX_DMA_BITS(0xff);
|
||||
mic_mmio_write(mw, reg, siac0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* mic_x100_disable_interrupts - Disable interrupts.
|
||||
* @mdev: pointer to mic_device instance
|
||||
*/
|
||||
static void mic_x100_disable_interrupts(struct mic_device *mdev)
|
||||
{
|
||||
u32 reg;
|
||||
struct mic_mw *mw = &mdev->mmio;
|
||||
u32 sice0 = MIC_X100_SBOX_BASE_ADDRESS + MIC_X100_SBOX_SICE0;
|
||||
u32 siac0 = MIC_X100_SBOX_BASE_ADDRESS + MIC_X100_SBOX_SIAC0;
|
||||
u32 sicc0 = MIC_X100_SBOX_BASE_ADDRESS + MIC_X100_SBOX_SICC0;
|
||||
|
||||
reg = mic_mmio_read(mw, sice0);
|
||||
mic_mmio_write(mw, reg, sicc0);
|
||||
|
||||
if (mdev->irq_info.num_vectors > 1) {
|
||||
reg = mic_mmio_read(mw, siac0);
|
||||
reg &= ~(MIC_X100_SBOX_DBR_BITS(0xf) |
|
||||
MIC_X100_SBOX_DMA_BITS(0xff));
|
||||
mic_mmio_write(mw, reg, siac0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* mic_x100_send_sbox_intr - Send an MIC_X100_SBOX interrupt to MIC.
|
||||
* @mdev: pointer to mic_device instance
|
||||
*/
|
||||
static void mic_x100_send_sbox_intr(struct mic_device *mdev,
|
||||
int doorbell)
|
||||
{
|
||||
struct mic_mw *mw = &mdev->mmio;
|
||||
u64 apic_icr_offset = MIC_X100_SBOX_APICICR0 + doorbell * 8;
|
||||
u32 apicicr_low = mic_mmio_read(mw, MIC_X100_SBOX_BASE_ADDRESS +
|
||||
apic_icr_offset);
|
||||
|
||||
/* for MIC we need to make sure we "hit" the send_icr bit (13) */
|
||||
apicicr_low = (apicicr_low | (1 << 13));
|
||||
|
||||
/* Ensure that the interrupt is ordered w.r.t. previous stores. */
|
||||
wmb();
|
||||
mic_mmio_write(mw, apicicr_low,
|
||||
MIC_X100_SBOX_BASE_ADDRESS + apic_icr_offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* mic_x100_send_rdmasr_intr - Send an RDMASR interrupt to MIC.
|
||||
* @mdev: pointer to mic_device instance
|
||||
*/
|
||||
static void mic_x100_send_rdmasr_intr(struct mic_device *mdev,
|
||||
int doorbell)
|
||||
{
|
||||
int rdmasr_offset = MIC_X100_SBOX_RDMASR0 + (doorbell << 2);
|
||||
/* Ensure that the interrupt is ordered w.r.t. previous stores. */
|
||||
wmb();
|
||||
mic_mmio_write(&mdev->mmio, 0,
|
||||
MIC_X100_SBOX_BASE_ADDRESS + rdmasr_offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* __mic_x100_send_intr - Send interrupt to MIC.
|
||||
* @mdev: pointer to mic_device instance
|
||||
* @doorbell: doorbell number.
|
||||
*/
|
||||
static void mic_x100_send_intr(struct mic_device *mdev, int doorbell)
|
||||
{
|
||||
int rdmasr_db;
|
||||
if (doorbell < MIC_X100_NUM_SBOX_IRQ) {
|
||||
mic_x100_send_sbox_intr(mdev, doorbell);
|
||||
} else {
|
||||
rdmasr_db = doorbell - MIC_X100_NUM_SBOX_IRQ +
|
||||
MIC_X100_RDMASR_IRQ_BASE;
|
||||
mic_x100_send_rdmasr_intr(mdev, rdmasr_db);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* mic_x100_ack_interrupt - Read the interrupt sources register and
|
||||
* clear it. This function will be called in the MSI/INTx case.
|
||||
* @mdev: Pointer to mic_device instance.
|
||||
*
|
||||
* Returns: bitmask of interrupt sources triggered.
|
||||
*/
|
||||
static u32 mic_x100_ack_interrupt(struct mic_device *mdev)
|
||||
{
|
||||
u32 sicr0 = MIC_X100_SBOX_BASE_ADDRESS + MIC_X100_SBOX_SICR0;
|
||||
u32 reg = mic_mmio_read(&mdev->mmio, sicr0);
|
||||
mic_mmio_write(&mdev->mmio, reg, sicr0);
|
||||
return reg;
|
||||
}
|
||||
|
||||
/**
|
||||
* mic_x100_intr_workarounds - These hardware specific workarounds are
|
||||
* to be invoked everytime an interrupt is handled.
|
||||
* @mdev: Pointer to mic_device instance.
|
||||
*
|
||||
* Returns: none
|
||||
*/
|
||||
static void mic_x100_intr_workarounds(struct mic_device *mdev)
|
||||
{
|
||||
struct mic_mw *mw = &mdev->mmio;
|
||||
|
||||
/* Clear pending bit array. */
|
||||
if (MIC_A0_STEP == mdev->stepping)
|
||||
mic_mmio_write(mw, 1, MIC_X100_SBOX_BASE_ADDRESS +
|
||||
MIC_X100_SBOX_MSIXPBACR);
|
||||
|
||||
if (mdev->stepping >= MIC_B0_STEP)
|
||||
mdev->intr_ops->enable_interrupts(mdev);
|
||||
}
|
||||
|
||||
/**
|
||||
* mic_x100_hw_intr_init - Initialize h/w specific interrupt
|
||||
* information.
|
||||
* @mdev: pointer to mic_device instance
|
||||
*/
|
||||
static void mic_x100_hw_intr_init(struct mic_device *mdev)
|
||||
{
|
||||
mdev->intr_info = (struct mic_intr_info *)mic_x100_intr_init;
|
||||
}
|
||||
|
||||
/**
|
||||
* mic_x100_read_msi_to_src_map - read from the MSI mapping registers
|
||||
* @mdev: pointer to mic_device instance
|
||||
* @idx: index to the mapping register, 0 based
|
||||
*
|
||||
* This function allows reading of the 32bit MSI mapping register.
|
||||
*
|
||||
* RETURNS: The value in the register.
|
||||
*/
|
||||
static u32
|
||||
mic_x100_read_msi_to_src_map(struct mic_device *mdev, int idx)
|
||||
{
|
||||
return mic_mmio_read(&mdev->mmio,
|
||||
MIC_X100_SBOX_BASE_ADDRESS +
|
||||
MIC_X100_SBOX_MXAR0 + idx * 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* mic_x100_program_msi_to_src_map - program the MSI mapping registers
|
||||
* @mdev: pointer to mic_device instance
|
||||
* @idx: index to the mapping register, 0 based
|
||||
* @offset: The bit offset in the register that needs to be updated.
|
||||
* @set: boolean specifying if the bit in the specified offset needs
|
||||
* to be set or cleared.
|
||||
*
|
||||
* RETURNS: None.
|
||||
*/
|
||||
static void
|
||||
mic_x100_program_msi_to_src_map(struct mic_device *mdev,
|
||||
int idx, int offset, bool set)
|
||||
{
|
||||
unsigned long reg;
|
||||
struct mic_mw *mw = &mdev->mmio;
|
||||
u32 mxar = MIC_X100_SBOX_BASE_ADDRESS +
|
||||
MIC_X100_SBOX_MXAR0 + idx * 4;
|
||||
|
||||
reg = mic_mmio_read(mw, mxar);
|
||||
if (set)
|
||||
__set_bit(offset, ®);
|
||||
else
|
||||
__clear_bit(offset, ®);
|
||||
mic_mmio_write(mw, reg, mxar);
|
||||
}
|
||||
|
||||
/*
|
||||
* mic_x100_reset_fw_ready - Reset Firmware ready status field.
|
||||
* @mdev: pointer to mic_device instance
|
||||
*/
|
||||
static void mic_x100_reset_fw_ready(struct mic_device *mdev)
|
||||
{
|
||||
mdev->ops->write_spad(mdev, MIC_X100_DOWNLOAD_INFO, 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* mic_x100_is_fw_ready - Check if firmware is ready.
|
||||
* @mdev: pointer to mic_device instance
|
||||
*/
|
||||
static bool mic_x100_is_fw_ready(struct mic_device *mdev)
|
||||
{
|
||||
u32 scratch2 = mdev->ops->read_spad(mdev, MIC_X100_DOWNLOAD_INFO);
|
||||
return MIC_X100_SPAD2_DOWNLOAD_STATUS(scratch2) ? true : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* mic_x100_get_apic_id - Get bootstrap APIC ID.
|
||||
* @mdev: pointer to mic_device instance
|
||||
*/
|
||||
static u32 mic_x100_get_apic_id(struct mic_device *mdev)
|
||||
{
|
||||
u32 scratch2 = 0;
|
||||
|
||||
scratch2 = mdev->ops->read_spad(mdev, MIC_X100_DOWNLOAD_INFO);
|
||||
return MIC_X100_SPAD2_APIC_ID(scratch2);
|
||||
}
|
||||
|
||||
/**
|
||||
* mic_x100_send_firmware_intr - Send an interrupt to the firmware on MIC.
|
||||
* @mdev: pointer to mic_device instance
|
||||
*/
|
||||
static void mic_x100_send_firmware_intr(struct mic_device *mdev)
|
||||
{
|
||||
u32 apicicr_low;
|
||||
u64 apic_icr_offset = MIC_X100_SBOX_APICICR7;
|
||||
int vector = MIC_X100_BSP_INTERRUPT_VECTOR;
|
||||
struct mic_mw *mw = &mdev->mmio;
|
||||
|
||||
/*
|
||||
* For MIC we need to make sure we "hit"
|
||||
* the send_icr bit (13).
|
||||
*/
|
||||
apicicr_low = (vector | (1 << 13));
|
||||
|
||||
mic_mmio_write(mw, mic_x100_get_apic_id(mdev),
|
||||
MIC_X100_SBOX_BASE_ADDRESS + apic_icr_offset + 4);
|
||||
|
||||
/* Ensure that the interrupt is ordered w.r.t. previous stores. */
|
||||
wmb();
|
||||
mic_mmio_write(mw, apicicr_low,
|
||||
MIC_X100_SBOX_BASE_ADDRESS + apic_icr_offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* mic_x100_hw_reset - Reset the MIC device.
|
||||
* @mdev: pointer to mic_device instance
|
||||
*/
|
||||
static void mic_x100_hw_reset(struct mic_device *mdev)
|
||||
{
|
||||
u32 reset_reg;
|
||||
u32 rgcr = MIC_X100_SBOX_BASE_ADDRESS + MIC_X100_SBOX_RGCR;
|
||||
struct mic_mw *mw = &mdev->mmio;
|
||||
|
||||
/* Ensure that the reset is ordered w.r.t. previous loads and stores */
|
||||
mb();
|
||||
/* Trigger reset */
|
||||
reset_reg = mic_mmio_read(mw, rgcr);
|
||||
reset_reg |= 0x1;
|
||||
mic_mmio_write(mw, reset_reg, rgcr);
|
||||
/*
|
||||
* It seems we really want to delay at least 1 second
|
||||
* after touching reset to prevent a lot of problems.
|
||||
*/
|
||||
msleep(1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* mic_x100_load_command_line - Load command line to MIC.
|
||||
* @mdev: pointer to mic_device instance
|
||||
* @fw: the firmware image
|
||||
*
|
||||
* RETURNS: An appropriate -ERRNO error value on error, or zero for success.
|
||||
*/
|
||||
static int
|
||||
mic_x100_load_command_line(struct mic_device *mdev, const struct firmware *fw)
|
||||
{
|
||||
u32 len = 0;
|
||||
u32 boot_mem;
|
||||
char *buf;
|
||||
void __iomem *cmd_line_va = mdev->aper.va + mdev->bootaddr + fw->size;
|
||||
#define CMDLINE_SIZE 2048
|
||||
|
||||
boot_mem = mdev->aper.len >> 20;
|
||||
buf = kzalloc(CMDLINE_SIZE, GFP_KERNEL);
|
||||
if (!buf) {
|
||||
dev_err(mdev->sdev->parent,
|
||||
"%s %d allocation failed\n", __func__, __LINE__);
|
||||
return -ENOMEM;
|
||||
}
|
||||
len += snprintf(buf, CMDLINE_SIZE - len,
|
||||
" mem=%dM", boot_mem);
|
||||
if (mdev->cmdline)
|
||||
snprintf(buf + len, CMDLINE_SIZE - len, " %s", mdev->cmdline);
|
||||
memcpy_toio(cmd_line_va, buf, strlen(buf) + 1);
|
||||
kfree(buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* mic_x100_load_ramdisk - Load ramdisk to MIC.
|
||||
* @mdev: pointer to mic_device instance
|
||||
*
|
||||
* RETURNS: An appropriate -ERRNO error value on error, or zero for success.
|
||||
*/
|
||||
static int
|
||||
mic_x100_load_ramdisk(struct mic_device *mdev)
|
||||
{
|
||||
const struct firmware *fw;
|
||||
int rc;
|
||||
struct boot_params __iomem *bp = mdev->aper.va + mdev->bootaddr;
|
||||
|
||||
rc = request_firmware(&fw,
|
||||
mdev->ramdisk, mdev->sdev->parent);
|
||||
if (rc < 0) {
|
||||
dev_err(mdev->sdev->parent,
|
||||
"ramdisk request_firmware failed: %d %s\n",
|
||||
rc, mdev->ramdisk);
|
||||
goto error;
|
||||
}
|
||||
/*
|
||||
* Typically the bootaddr for card OS is 64M
|
||||
* so copy over the ramdisk @ 128M.
|
||||
*/
|
||||
memcpy_toio(mdev->aper.va + (mdev->bootaddr << 1), fw->data, fw->size);
|
||||
iowrite32(mdev->bootaddr << 1, &bp->hdr.ramdisk_image);
|
||||
iowrite32(fw->size, &bp->hdr.ramdisk_size);
|
||||
release_firmware(fw);
|
||||
error:
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* mic_x100_get_boot_addr - Get MIC boot address.
|
||||
* @mdev: pointer to mic_device instance
|
||||
*
|
||||
* This function is called during firmware load to determine
|
||||
* the address at which the OS should be downloaded in card
|
||||
* memory i.e. GDDR.
|
||||
* RETURNS: An appropriate -ERRNO error value on error, or zero for success.
|
||||
*/
|
||||
static int
|
||||
mic_x100_get_boot_addr(struct mic_device *mdev)
|
||||
{
|
||||
u32 scratch2, boot_addr;
|
||||
int rc = 0;
|
||||
|
||||
scratch2 = mdev->ops->read_spad(mdev, MIC_X100_DOWNLOAD_INFO);
|
||||
boot_addr = MIC_X100_SPAD2_DOWNLOAD_ADDR(scratch2);
|
||||
dev_dbg(mdev->sdev->parent, "%s %d boot_addr 0x%x\n",
|
||||
__func__, __LINE__, boot_addr);
|
||||
if (boot_addr > (1 << 31)) {
|
||||
dev_err(mdev->sdev->parent,
|
||||
"incorrect bootaddr 0x%x\n",
|
||||
boot_addr);
|
||||
rc = -EINVAL;
|
||||
goto error;
|
||||
}
|
||||
mdev->bootaddr = boot_addr;
|
||||
error:
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* mic_x100_load_firmware - Load firmware to MIC.
|
||||
* @mdev: pointer to mic_device instance
|
||||
* @buf: buffer containing boot string including firmware/ramdisk path.
|
||||
*
|
||||
* RETURNS: An appropriate -ERRNO error value on error, or zero for success.
|
||||
*/
|
||||
static int
|
||||
mic_x100_load_firmware(struct mic_device *mdev, const char *buf)
|
||||
{
|
||||
int rc;
|
||||
const struct firmware *fw;
|
||||
|
||||
rc = mic_x100_get_boot_addr(mdev);
|
||||
if (rc)
|
||||
goto error;
|
||||
/* load OS */
|
||||
rc = request_firmware(&fw, mdev->firmware, mdev->sdev->parent);
|
||||
if (rc < 0) {
|
||||
dev_err(mdev->sdev->parent,
|
||||
"ramdisk request_firmware failed: %d %s\n",
|
||||
rc, mdev->firmware);
|
||||
goto error;
|
||||
}
|
||||
if (mdev->bootaddr > mdev->aper.len - fw->size) {
|
||||
rc = -EINVAL;
|
||||
dev_err(mdev->sdev->parent, "%s %d rc %d bootaddr 0x%x\n",
|
||||
__func__, __LINE__, rc, mdev->bootaddr);
|
||||
release_firmware(fw);
|
||||
goto error;
|
||||
}
|
||||
memcpy_toio(mdev->aper.va + mdev->bootaddr, fw->data, fw->size);
|
||||
mdev->ops->write_spad(mdev, MIC_X100_FW_SIZE, fw->size);
|
||||
if (!strcmp(mdev->bootmode, "elf"))
|
||||
goto done;
|
||||
/* load command line */
|
||||
rc = mic_x100_load_command_line(mdev, fw);
|
||||
if (rc) {
|
||||
dev_err(mdev->sdev->parent, "%s %d rc %d\n",
|
||||
__func__, __LINE__, rc);
|
||||
goto error;
|
||||
}
|
||||
release_firmware(fw);
|
||||
/* load ramdisk */
|
||||
if (mdev->ramdisk)
|
||||
rc = mic_x100_load_ramdisk(mdev);
|
||||
error:
|
||||
dev_dbg(mdev->sdev->parent, "%s %d rc %d\n", __func__, __LINE__, rc);
|
||||
done:
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* mic_x100_get_postcode - Get postcode status from firmware.
|
||||
* @mdev: pointer to mic_device instance
|
||||
*
|
||||
* RETURNS: postcode.
|
||||
*/
|
||||
static u32 mic_x100_get_postcode(struct mic_device *mdev)
|
||||
{
|
||||
return mic_mmio_read(&mdev->mmio, MIC_X100_POSTCODE);
|
||||
}
|
||||
|
||||
/**
|
||||
* mic_x100_smpt_set - Update an SMPT entry with a DMA address.
|
||||
* @mdev: pointer to mic_device instance
|
||||
*
|
||||
* RETURNS: none.
|
||||
*/
|
||||
static void
|
||||
mic_x100_smpt_set(struct mic_device *mdev, dma_addr_t dma_addr, u8 index)
|
||||
{
|
||||
#define SNOOP_ON (0 << 0)
|
||||
#define SNOOP_OFF (1 << 0)
|
||||
/*
|
||||
* Sbox Smpt Reg Bits:
|
||||
* Bits 31:2 Host address
|
||||
* Bits 1 RSVD
|
||||
* Bits 0 No snoop
|
||||
*/
|
||||
#define BUILD_SMPT(NO_SNOOP, HOST_ADDR) \
|
||||
(u32)(((HOST_ADDR) << 2) | ((NO_SNOOP) & 0x01))
|
||||
|
||||
uint32_t smpt_reg_val = BUILD_SMPT(SNOOP_ON,
|
||||
dma_addr >> mdev->smpt->info.page_shift);
|
||||
mic_mmio_write(&mdev->mmio, smpt_reg_val,
|
||||
MIC_X100_SBOX_BASE_ADDRESS +
|
||||
MIC_X100_SBOX_SMPT00 + (4 * index));
|
||||
}
|
||||
|
||||
/**
|
||||
* mic_x100_smpt_hw_init - Initialize SMPT X100 specific fields.
|
||||
* @mdev: pointer to mic_device instance
|
||||
*
|
||||
* RETURNS: none.
|
||||
*/
|
||||
static void mic_x100_smpt_hw_init(struct mic_device *mdev)
|
||||
{
|
||||
struct mic_smpt_hw_info *info = &mdev->smpt->info;
|
||||
|
||||
info->num_reg = 32;
|
||||
info->page_shift = 34;
|
||||
info->page_size = (1ULL << info->page_shift);
|
||||
info->base = 0x8000000000ULL;
|
||||
}
|
||||
|
||||
struct mic_smpt_ops mic_x100_smpt_ops = {
|
||||
.init = mic_x100_smpt_hw_init,
|
||||
.set = mic_x100_smpt_set,
|
||||
};
|
||||
|
||||
static bool mic_x100_dma_filter(struct dma_chan *chan, void *param)
|
||||
{
|
||||
if (chan->device->dev->parent == (struct device *)param)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
struct mic_hw_ops mic_x100_ops = {
|
||||
.aper_bar = MIC_X100_APER_BAR,
|
||||
.mmio_bar = MIC_X100_MMIO_BAR,
|
||||
.read_spad = mic_x100_read_spad,
|
||||
.write_spad = mic_x100_write_spad,
|
||||
.send_intr = mic_x100_send_intr,
|
||||
.ack_interrupt = mic_x100_ack_interrupt,
|
||||
.intr_workarounds = mic_x100_intr_workarounds,
|
||||
.reset = mic_x100_hw_reset,
|
||||
.reset_fw_ready = mic_x100_reset_fw_ready,
|
||||
.is_fw_ready = mic_x100_is_fw_ready,
|
||||
.send_firmware_intr = mic_x100_send_firmware_intr,
|
||||
.load_mic_fw = mic_x100_load_firmware,
|
||||
.get_postcode = mic_x100_get_postcode,
|
||||
.dma_filter = mic_x100_dma_filter,
|
||||
};
|
||||
|
||||
struct mic_hw_intr_ops mic_x100_intr_ops = {
|
||||
.intr_init = mic_x100_hw_intr_init,
|
||||
.enable_interrupts = mic_x100_enable_interrupts,
|
||||
.disable_interrupts = mic_x100_disable_interrupts,
|
||||
.program_msi_to_src_map = mic_x100_program_msi_to_src_map,
|
||||
.read_msi_to_src_map = mic_x100_read_msi_to_src_map,
|
||||
};
|
98
drivers/misc/mic/host/mic_x100.h
Normal file
98
drivers/misc/mic/host/mic_x100.h
Normal file
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
#ifndef _MIC_X100_HW_H_
|
||||
#define _MIC_X100_HW_H_
|
||||
|
||||
#define MIC_X100_PCI_DEVICE_2250 0x2250
|
||||
#define MIC_X100_PCI_DEVICE_2251 0x2251
|
||||
#define MIC_X100_PCI_DEVICE_2252 0x2252
|
||||
#define MIC_X100_PCI_DEVICE_2253 0x2253
|
||||
#define MIC_X100_PCI_DEVICE_2254 0x2254
|
||||
#define MIC_X100_PCI_DEVICE_2255 0x2255
|
||||
#define MIC_X100_PCI_DEVICE_2256 0x2256
|
||||
#define MIC_X100_PCI_DEVICE_2257 0x2257
|
||||
#define MIC_X100_PCI_DEVICE_2258 0x2258
|
||||
#define MIC_X100_PCI_DEVICE_2259 0x2259
|
||||
#define MIC_X100_PCI_DEVICE_225a 0x225a
|
||||
#define MIC_X100_PCI_DEVICE_225b 0x225b
|
||||
#define MIC_X100_PCI_DEVICE_225c 0x225c
|
||||
#define MIC_X100_PCI_DEVICE_225d 0x225d
|
||||
#define MIC_X100_PCI_DEVICE_225e 0x225e
|
||||
|
||||
#define MIC_X100_APER_BAR 0
|
||||
#define MIC_X100_MMIO_BAR 4
|
||||
|
||||
#define MIC_X100_SBOX_BASE_ADDRESS 0x00010000
|
||||
#define MIC_X100_SBOX_SPAD0 0x0000AB20
|
||||
#define MIC_X100_SBOX_SICR0_DBR(x) ((x) & 0xf)
|
||||
#define MIC_X100_SBOX_SICR0_DMA(x) (((x) >> 8) & 0xff)
|
||||
#define MIC_X100_SBOX_SICE0_DBR(x) ((x) & 0xf)
|
||||
#define MIC_X100_SBOX_DBR_BITS(x) ((x) & 0xf)
|
||||
#define MIC_X100_SBOX_SICE0_DMA(x) (((x) >> 8) & 0xff)
|
||||
#define MIC_X100_SBOX_DMA_BITS(x) (((x) & 0xff) << 8)
|
||||
|
||||
#define MIC_X100_SBOX_APICICR0 0x0000A9D0
|
||||
#define MIC_X100_SBOX_SICR0 0x00009004
|
||||
#define MIC_X100_SBOX_SICE0 0x0000900C
|
||||
#define MIC_X100_SBOX_SICC0 0x00009010
|
||||
#define MIC_X100_SBOX_SIAC0 0x00009014
|
||||
#define MIC_X100_SBOX_MSIXPBACR 0x00009084
|
||||
#define MIC_X100_SBOX_MXAR0 0x00009044
|
||||
#define MIC_X100_SBOX_SMPT00 0x00003100
|
||||
#define MIC_X100_SBOX_RDMASR0 0x0000B180
|
||||
|
||||
#define MIC_X100_DOORBELL_IDX_START 0
|
||||
#define MIC_X100_NUM_DOORBELL 4
|
||||
#define MIC_X100_DMA_IDX_START 8
|
||||
#define MIC_X100_NUM_DMA 8
|
||||
#define MIC_X100_ERR_IDX_START 30
|
||||
#define MIC_X100_NUM_ERR 1
|
||||
|
||||
#define MIC_X100_NUM_SBOX_IRQ 8
|
||||
#define MIC_X100_NUM_RDMASR_IRQ 8
|
||||
#define MIC_X100_RDMASR_IRQ_BASE 17
|
||||
#define MIC_X100_SPAD2_DOWNLOAD_STATUS(x) ((x) & 0x1)
|
||||
#define MIC_X100_SPAD2_APIC_ID(x) (((x) >> 1) & 0x1ff)
|
||||
#define MIC_X100_SPAD2_DOWNLOAD_ADDR(x) ((x) & 0xfffff000)
|
||||
#define MIC_X100_SBOX_APICICR7 0x0000AA08
|
||||
#define MIC_X100_SBOX_RGCR 0x00004010
|
||||
#define MIC_X100_SBOX_SDBIC0 0x0000CC90
|
||||
#define MIC_X100_DOWNLOAD_INFO 2
|
||||
#define MIC_X100_FW_SIZE 5
|
||||
#define MIC_X100_POSTCODE 0x242c
|
||||
|
||||
static const u16 mic_x100_intr_init[] = {
|
||||
MIC_X100_DOORBELL_IDX_START,
|
||||
MIC_X100_DMA_IDX_START,
|
||||
MIC_X100_ERR_IDX_START,
|
||||
MIC_X100_NUM_DOORBELL,
|
||||
MIC_X100_NUM_DMA,
|
||||
MIC_X100_NUM_ERR,
|
||||
};
|
||||
|
||||
/* Host->Card(bootstrap) Interrupt Vector */
|
||||
#define MIC_X100_BSP_INTERRUPT_VECTOR 229
|
||||
|
||||
extern struct mic_hw_ops mic_x100_ops;
|
||||
extern struct mic_smpt_ops mic_x100_smpt_ops;
|
||||
extern struct mic_hw_intr_ops mic_x100_intr_ops;
|
||||
|
||||
#endif
|
Loading…
Add table
Add a link
Reference in a new issue