Fixed MTP to work with TWRP

This commit is contained in:
awab228 2018-06-19 23:16:04 +02:00
commit f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions

54
drivers/misc/mic/Kconfig Normal file
View file

@ -0,0 +1,54 @@
comment "Intel MIC Bus Driver"
config INTEL_MIC_BUS
tristate "Intel MIC Bus Driver"
depends on 64BIT && PCI && X86 && X86_DEV_DMA_OPS
help
This option is selected by any driver which registers a
device or driver on the MIC Bus, such as CONFIG_INTEL_MIC_HOST,
CONFIG_INTEL_MIC_CARD, CONFIG_INTEL_MIC_X100_DMA etc.
If you are building a host/card kernel with an Intel MIC device
then say M (recommended) or Y, else say N. If unsure say N.
More information about the Intel MIC family as well as the Linux
OS and tools for MIC to use with this driver are available from
<http://software.intel.com/en-us/mic-developer>.
comment "Intel MIC Host Driver"
config INTEL_MIC_HOST
tristate "Intel MIC Host Driver"
depends on 64BIT && PCI && X86 && INTEL_MIC_BUS
select VHOST_RING
help
This enables Host Driver support for the Intel Many Integrated
Core (MIC) family of PCIe form factor coprocessor devices that
run a 64 bit Linux OS. The driver manages card OS state and
enables communication between host and card. Intel MIC X100
devices are currently supported.
If you are building a host kernel with an Intel MIC device then
say M (recommended) or Y, else say N. If unsure say N.
More information about the Intel MIC family as well as the Linux
OS and tools for MIC to use with this driver are available from
<http://software.intel.com/en-us/mic-developer>.
comment "Intel MIC Card Driver"
config INTEL_MIC_CARD
tristate "Intel MIC Card Driver"
depends on 64BIT && X86 && INTEL_MIC_BUS
select VIRTIO
help
This enables card driver support for the Intel Many Integrated
Core (MIC) device family. The card driver communicates shutdown/
crash events to the host and allows registration/configuration of
virtio devices. Intel MIC X100 devices are currently supported.
If you are building a card kernel for an Intel MIC device then
say M (recommended) or Y, else say N. If unsure say N.
For more information see
<http://software.intel.com/en-us/mic-developer>.

View file

@ -0,0 +1,7 @@
#
# Makefile - Intel MIC Linux driver.
# Copyright(c) 2013, Intel Corporation.
#
obj-$(CONFIG_INTEL_MIC_HOST) += host/
obj-$(CONFIG_INTEL_MIC_CARD) += card/
obj-$(CONFIG_INTEL_MIC_BUS) += bus/

View file

@ -0,0 +1,5 @@
#
# Makefile - Intel MIC Linux driver.
# Copyright(c) 2014, Intel Corporation.
#
obj-$(CONFIG_INTEL_MIC_BUS) += mic_bus.o

View file

@ -0,0 +1,218 @@
/*
* Intel MIC Platform Software Stack (MPSS)
*
* Copyright(c) 2014 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 Bus driver.
*
* This implementation is very similar to the the virtio bus driver
* implementation @ drivers/virtio/virtio.c
*/
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/idr.h>
#include <linux/mic_bus.h>
/* Unique numbering for mbus devices. */
static DEFINE_IDA(mbus_index_ida);
static ssize_t device_show(struct device *d,
struct device_attribute *attr, char *buf)
{
struct mbus_device *dev = dev_to_mbus(d);
return sprintf(buf, "0x%04x\n", dev->id.device);
}
static DEVICE_ATTR_RO(device);
static ssize_t vendor_show(struct device *d,
struct device_attribute *attr, char *buf)
{
struct mbus_device *dev = dev_to_mbus(d);
return sprintf(buf, "0x%04x\n", dev->id.vendor);
}
static DEVICE_ATTR_RO(vendor);
static ssize_t modalias_show(struct device *d,
struct device_attribute *attr, char *buf)
{
struct mbus_device *dev = dev_to_mbus(d);
return sprintf(buf, "mbus:d%08Xv%08X\n",
dev->id.device, dev->id.vendor);
}
static DEVICE_ATTR_RO(modalias);
static struct attribute *mbus_dev_attrs[] = {
&dev_attr_device.attr,
&dev_attr_vendor.attr,
&dev_attr_modalias.attr,
NULL,
};
ATTRIBUTE_GROUPS(mbus_dev);
static inline int mbus_id_match(const struct mbus_device *dev,
const struct mbus_device_id *id)
{
if (id->device != dev->id.device && id->device != MBUS_DEV_ANY_ID)
return 0;
return id->vendor == MBUS_DEV_ANY_ID || id->vendor == dev->id.vendor;
}
/*
* This looks through all the IDs a driver claims to support. If any of them
* match, we return 1 and the kernel will call mbus_dev_probe().
*/
static int mbus_dev_match(struct device *dv, struct device_driver *dr)
{
unsigned int i;
struct mbus_device *dev = dev_to_mbus(dv);
const struct mbus_device_id *ids;
ids = drv_to_mbus(dr)->id_table;
for (i = 0; ids[i].device; i++)
if (mbus_id_match(dev, &ids[i]))
return 1;
return 0;
}
static int mbus_uevent(struct device *dv, struct kobj_uevent_env *env)
{
struct mbus_device *dev = dev_to_mbus(dv);
return add_uevent_var(env, "MODALIAS=mbus:d%08Xv%08X",
dev->id.device, dev->id.vendor);
}
static int mbus_dev_probe(struct device *d)
{
int err;
struct mbus_device *dev = dev_to_mbus(d);
struct mbus_driver *drv = drv_to_mbus(dev->dev.driver);
err = drv->probe(dev);
if (!err)
if (drv->scan)
drv->scan(dev);
return err;
}
static int mbus_dev_remove(struct device *d)
{
struct mbus_device *dev = dev_to_mbus(d);
struct mbus_driver *drv = drv_to_mbus(dev->dev.driver);
drv->remove(dev);
return 0;
}
static struct bus_type mic_bus = {
.name = "mic_bus",
.match = mbus_dev_match,
.dev_groups = mbus_dev_groups,
.uevent = mbus_uevent,
.probe = mbus_dev_probe,
.remove = mbus_dev_remove,
};
int mbus_register_driver(struct mbus_driver *driver)
{
driver->driver.bus = &mic_bus;
return driver_register(&driver->driver);
}
EXPORT_SYMBOL_GPL(mbus_register_driver);
void mbus_unregister_driver(struct mbus_driver *driver)
{
driver_unregister(&driver->driver);
}
EXPORT_SYMBOL_GPL(mbus_unregister_driver);
static void mbus_release_dev(struct device *d)
{
struct mbus_device *mbdev = dev_to_mbus(d);
kfree(mbdev);
}
struct mbus_device *
mbus_register_device(struct device *pdev, int id, struct dma_map_ops *dma_ops,
struct mbus_hw_ops *hw_ops, void __iomem *mmio_va)
{
int ret;
struct mbus_device *mbdev;
mbdev = kzalloc(sizeof(*mbdev), GFP_KERNEL);
if (!mbdev)
return ERR_PTR(-ENOMEM);
mbdev->mmio_va = mmio_va;
mbdev->dev.parent = pdev;
mbdev->id.device = id;
mbdev->id.vendor = MBUS_DEV_ANY_ID;
mbdev->dev.archdata.dma_ops = dma_ops;
mbdev->dev.dma_mask = &mbdev->dev.coherent_dma_mask;
dma_set_mask(&mbdev->dev, DMA_BIT_MASK(64));
mbdev->dev.release = mbus_release_dev;
mbdev->hw_ops = hw_ops;
mbdev->dev.bus = &mic_bus;
/* Assign a unique device index and hence name. */
ret = ida_simple_get(&mbus_index_ida, 0, 0, GFP_KERNEL);
if (ret < 0)
goto free_mbdev;
mbdev->index = ret;
dev_set_name(&mbdev->dev, "mbus-dev%u", mbdev->index);
/*
* device_register() causes the bus infrastructure to look for a
* matching driver.
*/
ret = device_register(&mbdev->dev);
if (ret)
goto ida_remove;
return mbdev;
ida_remove:
ida_simple_remove(&mbus_index_ida, mbdev->index);
free_mbdev:
kfree(mbdev);
return ERR_PTR(ret);
}
EXPORT_SYMBOL_GPL(mbus_register_device);
void mbus_unregister_device(struct mbus_device *mbdev)
{
int index = mbdev->index; /* save for after device release */
device_unregister(&mbdev->dev);
ida_simple_remove(&mbus_index_ida, index);
}
EXPORT_SYMBOL_GPL(mbus_unregister_device);
static int __init mbus_init(void)
{
return bus_register(&mic_bus);
}
static void __exit mbus_exit(void)
{
bus_unregister(&mic_bus);
ida_destroy(&mbus_index_ida);
}
core_initcall(mbus_init);
module_exit(mbus_exit);
MODULE_AUTHOR("Intel Corporation");
MODULE_DESCRIPTION("Intel(R) MIC Bus driver");
MODULE_LICENSE("GPL v2");

View file

@ -0,0 +1,11 @@
#
# Makefile - Intel MIC Linux driver.
# Copyright(c) 2013, Intel Corporation.
#
ccflags-y += -DINTEL_MIC_CARD
obj-$(CONFIG_INTEL_MIC_CARD) += mic_card.o
mic_card-y += mic_x100.o
mic_card-y += mic_device.o
mic_card-y += mic_debugfs.o
mic_card-y += mic_virtio.o

View file

@ -0,0 +1,130 @@
/*
* 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".
*
* Disclaimer: The codes contained in these modules may be specific to
* the Intel Software Development Platform codenamed: Knights Ferry, and
* the Intel product codenamed: Knights Corner, and are not backward
* compatible with other Intel products. Additionally, Intel will NOT
* support the codes or instruction set in future products.
*
* Intel MIC Card driver.
*
*/
#include <linux/debugfs.h>
#include <linux/delay.h>
#include <linux/seq_file.h>
#include <linux/interrupt.h>
#include <linux/device.h>
#include "../common/mic_dev.h"
#include "mic_device.h"
/* Debugfs parent dir */
static struct dentry *mic_dbg;
/**
* mic_intr_test - Send interrupts to host.
*/
static int mic_intr_test(struct seq_file *s, void *unused)
{
struct mic_driver *mdrv = s->private;
struct mic_device *mdev = &mdrv->mdev;
mic_send_intr(mdev, 0);
msleep(1000);
mic_send_intr(mdev, 1);
msleep(1000);
mic_send_intr(mdev, 2);
msleep(1000);
mic_send_intr(mdev, 3);
msleep(1000);
return 0;
}
static int mic_intr_test_open(struct inode *inode, struct file *file)
{
return single_open(file, mic_intr_test, inode->i_private);
}
static int mic_intr_test_release(struct inode *inode, struct file *file)
{
return single_release(inode, file);
}
static const struct file_operations intr_test_ops = {
.owner = THIS_MODULE,
.open = mic_intr_test_open,
.read = seq_read,
.llseek = seq_lseek,
.release = mic_intr_test_release
};
/**
* mic_create_card_debug_dir - Initialize MIC debugfs entries.
*/
void __init mic_create_card_debug_dir(struct mic_driver *mdrv)
{
struct dentry *d;
if (!mic_dbg)
return;
mdrv->dbg_dir = debugfs_create_dir(mdrv->name, mic_dbg);
if (!mdrv->dbg_dir) {
dev_err(mdrv->dev, "Cant create dbg_dir %s\n", mdrv->name);
return;
}
d = debugfs_create_file("intr_test", 0444, mdrv->dbg_dir,
mdrv, &intr_test_ops);
if (!d) {
dev_err(mdrv->dev,
"Cant create dbg intr_test %s\n", mdrv->name);
return;
}
}
/**
* mic_delete_card_debug_dir - Uninitialize MIC debugfs entries.
*/
void mic_delete_card_debug_dir(struct mic_driver *mdrv)
{
if (!mdrv->dbg_dir)
return;
debugfs_remove_recursive(mdrv->dbg_dir);
}
/**
* mic_init_card_debugfs - Initialize global debugfs entry.
*/
void __init mic_init_card_debugfs(void)
{
mic_dbg = debugfs_create_dir(KBUILD_MODNAME, NULL);
if (!mic_dbg)
pr_err("can't create debugfs dir\n");
}
/**
* mic_exit_card_debugfs - Uninitialize global debugfs entry
*/
void mic_exit_card_debugfs(void)
{
debugfs_remove(mic_dbg);
}

View file

@ -0,0 +1,308 @@
/*
* 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".
*
* Disclaimer: The codes contained in these modules may be specific to
* the Intel Software Development Platform codenamed: Knights Ferry, and
* the Intel product codenamed: Knights Corner, and are not backward
* compatible with other Intel products. Additionally, Intel will NOT
* support the codes or instruction set in future products.
*
* Intel MIC Card driver.
*
*/
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/interrupt.h>
#include <linux/reboot.h>
#include <linux/mic_common.h>
#include "../common/mic_dev.h"
#include "mic_device.h"
#include "mic_virtio.h"
static struct mic_driver *g_drv;
static struct mic_irq *shutdown_cookie;
static void mic_notify_host(u8 state)
{
struct mic_driver *mdrv = g_drv;
struct mic_bootparam __iomem *bootparam = mdrv->dp;
iowrite8(state, &bootparam->shutdown_status);
dev_dbg(mdrv->dev, "%s %d system_state %d\n",
__func__, __LINE__, state);
mic_send_intr(&mdrv->mdev, ioread8(&bootparam->c2h_shutdown_db));
}
static int mic_panic_event(struct notifier_block *this, unsigned long event,
void *ptr)
{
struct mic_driver *mdrv = g_drv;
struct mic_bootparam __iomem *bootparam = mdrv->dp;
iowrite8(-1, &bootparam->h2c_config_db);
iowrite8(-1, &bootparam->h2c_shutdown_db);
mic_notify_host(MIC_CRASHED);
return NOTIFY_DONE;
}
static struct notifier_block mic_panic = {
.notifier_call = mic_panic_event,
};
static irqreturn_t mic_shutdown_isr(int irq, void *data)
{
struct mic_driver *mdrv = g_drv;
struct mic_bootparam __iomem *bootparam = mdrv->dp;
mic_ack_interrupt(&g_drv->mdev);
if (ioread8(&bootparam->shutdown_card))
orderly_poweroff(true);
return IRQ_HANDLED;
}
static int mic_shutdown_init(void)
{
int rc = 0;
struct mic_driver *mdrv = g_drv;
struct mic_bootparam __iomem *bootparam = mdrv->dp;
int shutdown_db;
shutdown_db = mic_next_card_db();
shutdown_cookie = mic_request_card_irq(mic_shutdown_isr, NULL,
"Shutdown", mdrv, shutdown_db);
if (IS_ERR(shutdown_cookie))
rc = PTR_ERR(shutdown_cookie);
else
iowrite8(shutdown_db, &bootparam->h2c_shutdown_db);
return rc;
}
static void mic_shutdown_uninit(void)
{
struct mic_driver *mdrv = g_drv;
struct mic_bootparam __iomem *bootparam = mdrv->dp;
iowrite8(-1, &bootparam->h2c_shutdown_db);
mic_free_card_irq(shutdown_cookie, mdrv);
}
static int __init mic_dp_init(void)
{
struct mic_driver *mdrv = g_drv;
struct mic_device *mdev = &mdrv->mdev;
struct mic_bootparam __iomem *bootparam;
u64 lo, hi, dp_dma_addr;
u32 magic;
lo = mic_read_spad(&mdrv->mdev, MIC_DPLO_SPAD);
hi = mic_read_spad(&mdrv->mdev, MIC_DPHI_SPAD);
dp_dma_addr = lo | (hi << 32);
mdrv->dp = mic_card_map(mdev, dp_dma_addr, MIC_DP_SIZE);
if (!mdrv->dp) {
dev_err(mdrv->dev, "Cannot remap Aperture BAR\n");
return -ENOMEM;
}
bootparam = mdrv->dp;
magic = ioread32(&bootparam->magic);
if (MIC_MAGIC != magic) {
dev_err(mdrv->dev, "bootparam magic mismatch 0x%x\n", magic);
return -EIO;
}
return 0;
}
/* Uninitialize the device page */
static void mic_dp_uninit(void)
{
mic_card_unmap(&g_drv->mdev, g_drv->dp);
}
/**
* mic_request_card_irq - request an irq.
*
* @handler: interrupt handler passed to request_threaded_irq.
* @thread_fn: thread fn. passed to 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.
* @index: The doorbell index of the requester.
*
* 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_card_irq(irq_handler_t handler,
irq_handler_t thread_fn, const char *name,
void *data, int index)
{
int rc = 0;
unsigned long cookie;
struct mic_driver *mdrv = g_drv;
rc = request_threaded_irq(mic_db_to_irq(mdrv, index), handler,
thread_fn, 0, name, data);
if (rc) {
dev_err(mdrv->dev, "request_threaded_irq failed rc = %d\n", rc);
goto err;
}
mdrv->irq_info.irq_usage_count[index]++;
cookie = index;
return (struct mic_irq *)cookie;
err:
return ERR_PTR(rc);
}
/**
* mic_free_card_irq - free irq.
*
* @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_card_irq(struct mic_irq *cookie, void *data)
{
int index;
struct mic_driver *mdrv = g_drv;
index = (unsigned long)cookie & 0xFFFFU;
free_irq(mic_db_to_irq(mdrv, index), data);
mdrv->irq_info.irq_usage_count[index]--;
}
/**
* mic_next_card_db - Get the doorbell with minimum usage count.
*
* Returns the irq index.
*/
int mic_next_card_db(void)
{
int i;
int index = 0;
struct mic_driver *mdrv = g_drv;
for (i = 0; i < mdrv->intr_info.num_intr; i++) {
if (mdrv->irq_info.irq_usage_count[i] <
mdrv->irq_info.irq_usage_count[index])
index = i;
}
return index;
}
/**
* mic_init_irq - Initialize irq information.
*
* Returns 0 in success. Appropriate error code on failure.
*/
static int mic_init_irq(void)
{
struct mic_driver *mdrv = g_drv;
mdrv->irq_info.irq_usage_count = kzalloc((sizeof(u32) *
mdrv->intr_info.num_intr),
GFP_KERNEL);
if (!mdrv->irq_info.irq_usage_count)
return -ENOMEM;
return 0;
}
/**
* mic_uninit_irq - Uninitialize irq information.
*
* None.
*/
static void mic_uninit_irq(void)
{
struct mic_driver *mdrv = g_drv;
kfree(mdrv->irq_info.irq_usage_count);
}
/*
* mic_driver_init - MIC driver initialization tasks.
*
* Returns 0 in success. Appropriate error code on failure.
*/
int __init mic_driver_init(struct mic_driver *mdrv)
{
int rc;
g_drv = mdrv;
/*
* Unloading the card module is not supported. The MIC card module
* handles fundamental operations like host/card initiated shutdowns
* and informing the host about card crashes and cannot be unloaded.
*/
if (!try_module_get(mdrv->dev->driver->owner)) {
rc = -ENODEV;
goto done;
}
rc = mic_dp_init();
if (rc)
goto put;
rc = mic_init_irq();
if (rc)
goto dp_uninit;
rc = mic_shutdown_init();
if (rc)
goto irq_uninit;
rc = mic_devices_init(mdrv);
if (rc)
goto shutdown_uninit;
mic_create_card_debug_dir(mdrv);
atomic_notifier_chain_register(&panic_notifier_list, &mic_panic);
done:
return rc;
shutdown_uninit:
mic_shutdown_uninit();
irq_uninit:
mic_uninit_irq();
dp_uninit:
mic_dp_uninit();
put:
module_put(mdrv->dev->driver->owner);
return rc;
}
/*
* mic_driver_uninit - MIC driver uninitialization tasks.
*
* Returns None
*/
void mic_driver_uninit(struct mic_driver *mdrv)
{
mic_delete_card_debug_dir(mdrv);
mic_devices_uninit(mdrv);
/*
* Inform the host about the shutdown status i.e. poweroff/restart etc.
* The module cannot be unloaded so the only code path to call
* mic_devices_uninit(..) is the shutdown callback.
*/
mic_notify_host(system_state);
mic_shutdown_uninit();
mic_uninit_irq();
mic_dp_uninit();
module_put(mdrv->dev->driver->owner);
}

View file

@ -0,0 +1,139 @@
/*
* 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".
*
* Disclaimer: The codes contained in these modules may be specific to
* the Intel Software Development Platform codenamed: Knights Ferry, and
* the Intel product codenamed: Knights Corner, and are not backward
* compatible with other Intel products. Additionally, Intel will NOT
* support the codes or instruction set in future products.
*
* Intel MIC Card driver.
*
*/
#ifndef _MIC_CARD_DEVICE_H_
#define _MIC_CARD_DEVICE_H_
#include <linux/workqueue.h>
#include <linux/io.h>
#include <linux/irqreturn.h>
#include <linux/interrupt.h>
#include <linux/mic_bus.h>
/**
* struct mic_intr_info - Contains h/w specific interrupt sources info
*
* @num_intr: The number of irqs available
*/
struct mic_intr_info {
u32 num_intr;
};
/**
* struct mic_irq_info - OS specific irq information
*
* @irq_usage_count: usage count array tracking the number of sources
* assigned for each irq.
*/
struct mic_irq_info {
int *irq_usage_count;
};
/**
* struct mic_device - MIC device information.
*
* @mmio: MMIO bar information.
*/
struct mic_device {
struct mic_mw mmio;
};
/**
* struct mic_driver - MIC card driver information.
*
* @name: Name for MIC driver.
* @dbg_dir: debugfs directory of this MIC device.
* @dev: The device backing this MIC.
* @dp: The pointer to the virtio device page.
* @mdev: MIC device information for the host.
* @hotplug_work: Hot plug work for adding/removing virtio devices.
* @irq_info: The OS specific irq information
* @intr_info: H/W specific interrupt information.
* @dma_mbdev: dma device on the MIC virtual bus.
*/
struct mic_driver {
char name[20];
struct dentry *dbg_dir;
struct device *dev;
void __iomem *dp;
struct mic_device mdev;
struct work_struct hotplug_work;
struct mic_irq_info irq_info;
struct mic_intr_info intr_info;
struct mbus_device *dma_mbdev;
};
/**
* struct mic_irq - opaque pointer used as cookie
*/
struct mic_irq;
/**
* 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);
}
int mic_driver_init(struct mic_driver *mdrv);
void mic_driver_uninit(struct mic_driver *mdrv);
int mic_next_card_db(void);
struct mic_irq *
mic_request_card_irq(irq_handler_t handler, irq_handler_t thread_fn,
const char *name, void *data, int intr_src);
void mic_free_card_irq(struct mic_irq *cookie, void *data);
u32 mic_read_spad(struct mic_device *mdev, unsigned int idx);
void mic_send_intr(struct mic_device *mdev, int doorbell);
int mic_db_to_irq(struct mic_driver *mdrv, int db);
u32 mic_ack_interrupt(struct mic_device *mdev);
void mic_hw_intr_init(struct mic_driver *mdrv);
void __iomem *
mic_card_map(struct mic_device *mdev, dma_addr_t addr, size_t size);
void mic_card_unmap(struct mic_device *mdev, void __iomem *addr);
void __init mic_create_card_debug_dir(struct mic_driver *mdrv);
void mic_delete_card_debug_dir(struct mic_driver *mdrv);
void __init mic_init_card_debugfs(void);
void mic_exit_card_debugfs(void);
#endif

View file

@ -0,0 +1,630 @@
/*
* 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".
*
* Disclaimer: The codes contained in these modules may be specific to
* the Intel Software Development Platform codenamed: Knights Ferry, and
* the Intel product codenamed: Knights Corner, and are not backward
* compatible with other Intel products. Additionally, Intel will NOT
* support the codes or instruction set in future products.
*
* Adapted from:
*
* virtio for kvm on s390
*
* Copyright IBM Corp. 2008
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License (version 2 only)
* as published by the Free Software Foundation.
*
* Author(s): Christian Borntraeger <borntraeger@de.ibm.com>
*
* Intel MIC Card driver.
*
*/
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/virtio_config.h>
#include "../common/mic_dev.h"
#include "mic_virtio.h"
#define VIRTIO_SUBCODE_64 0x0D00
#define MIC_MAX_VRINGS 4
struct mic_vdev {
struct virtio_device vdev;
struct mic_device_desc __iomem *desc;
struct mic_device_ctrl __iomem *dc;
struct mic_device *mdev;
void __iomem *vr[MIC_MAX_VRINGS];
int used_size[MIC_MAX_VRINGS];
struct completion reset_done;
struct mic_irq *virtio_cookie;
int c2h_vdev_db;
};
static struct mic_irq *virtio_config_cookie;
#define to_micvdev(vd) container_of(vd, struct mic_vdev, vdev)
/* Helper API to obtain the parent of the virtio device */
static inline struct device *mic_dev(struct mic_vdev *mvdev)
{
return mvdev->vdev.dev.parent;
}
/* This gets the device's feature bits. */
static u32 mic_get_features(struct virtio_device *vdev)
{
unsigned int i, bits;
u32 features = 0;
struct mic_device_desc __iomem *desc = to_micvdev(vdev)->desc;
u8 __iomem *in_features = mic_vq_features(desc);
int feature_len = ioread8(&desc->feature_len);
bits = min_t(unsigned, feature_len,
sizeof(vdev->features)) * 8;
for (i = 0; i < bits; i++)
if (ioread8(&in_features[i / 8]) & (BIT(i % 8)))
features |= BIT(i);
return features;
}
static void mic_finalize_features(struct virtio_device *vdev)
{
unsigned int i, bits;
struct mic_device_desc __iomem *desc = to_micvdev(vdev)->desc;
u8 feature_len = ioread8(&desc->feature_len);
/* Second half of bitmap is features we accept. */
u8 __iomem *out_features =
mic_vq_features(desc) + feature_len;
/* Give virtio_ring a chance to accept features. */
vring_transport_features(vdev);
memset_io(out_features, 0, feature_len);
bits = min_t(unsigned, feature_len,
sizeof(vdev->features)) * 8;
for (i = 0; i < bits; i++) {
if (test_bit(i, vdev->features))
iowrite8(ioread8(&out_features[i / 8]) | (1 << (i % 8)),
&out_features[i / 8]);
}
}
/*
* Reading and writing elements in config space
*/
static void mic_get(struct virtio_device *vdev, unsigned int offset,
void *buf, unsigned len)
{
struct mic_device_desc __iomem *desc = to_micvdev(vdev)->desc;
if (offset + len > ioread8(&desc->config_len))
return;
memcpy_fromio(buf, mic_vq_configspace(desc) + offset, len);
}
static void mic_set(struct virtio_device *vdev, unsigned int offset,
const void *buf, unsigned len)
{
struct mic_device_desc __iomem *desc = to_micvdev(vdev)->desc;
if (offset + len > ioread8(&desc->config_len))
return;
memcpy_toio(mic_vq_configspace(desc) + offset, buf, len);
}
/*
* The operations to get and set the status word just access the status
* field of the device descriptor. set_status also interrupts the host
* to tell about status changes.
*/
static u8 mic_get_status(struct virtio_device *vdev)
{
return ioread8(&to_micvdev(vdev)->desc->status);
}
static void mic_set_status(struct virtio_device *vdev, u8 status)
{
struct mic_vdev *mvdev = to_micvdev(vdev);
if (!status)
return;
iowrite8(status, &mvdev->desc->status);
mic_send_intr(mvdev->mdev, mvdev->c2h_vdev_db);
}
/* Inform host on a virtio device reset and wait for ack from host */
static void mic_reset_inform_host(struct virtio_device *vdev)
{
struct mic_vdev *mvdev = to_micvdev(vdev);
struct mic_device_ctrl __iomem *dc = mvdev->dc;
int retry;
iowrite8(0, &dc->host_ack);
iowrite8(1, &dc->vdev_reset);
mic_send_intr(mvdev->mdev, mvdev->c2h_vdev_db);
/* Wait till host completes all card accesses and acks the reset */
for (retry = 100; retry--;) {
if (ioread8(&dc->host_ack))
break;
msleep(100);
};
dev_dbg(mic_dev(mvdev), "%s: retry: %d\n", __func__, retry);
/* Reset status to 0 in case we timed out */
iowrite8(0, &mvdev->desc->status);
}
static void mic_reset(struct virtio_device *vdev)
{
struct mic_vdev *mvdev = to_micvdev(vdev);
dev_dbg(mic_dev(mvdev), "%s: virtio id %d\n",
__func__, vdev->id.device);
mic_reset_inform_host(vdev);
complete_all(&mvdev->reset_done);
}
/*
* The virtio_ring code calls this API when it wants to notify the Host.
*/
static bool mic_notify(struct virtqueue *vq)
{
struct mic_vdev *mvdev = vq->priv;
mic_send_intr(mvdev->mdev, mvdev->c2h_vdev_db);
return true;
}
static void mic_del_vq(struct virtqueue *vq, int n)
{
struct mic_vdev *mvdev = to_micvdev(vq->vdev);
struct vring *vr = (struct vring *)(vq + 1);
free_pages((unsigned long) vr->used, get_order(mvdev->used_size[n]));
vring_del_virtqueue(vq);
mic_card_unmap(mvdev->mdev, mvdev->vr[n]);
mvdev->vr[n] = NULL;
}
static void mic_del_vqs(struct virtio_device *vdev)
{
struct mic_vdev *mvdev = to_micvdev(vdev);
struct virtqueue *vq, *n;
int idx = 0;
dev_dbg(mic_dev(mvdev), "%s\n", __func__);
list_for_each_entry_safe(vq, n, &vdev->vqs, list)
mic_del_vq(vq, idx++);
}
/*
* This routine will assign vring's allocated in host/io memory. Code in
* virtio_ring.c however continues to access this io memory as if it were local
* memory without io accessors.
*/
static struct virtqueue *mic_find_vq(struct virtio_device *vdev,
unsigned index,
void (*callback)(struct virtqueue *vq),
const char *name)
{
struct mic_vdev *mvdev = to_micvdev(vdev);
struct mic_vqconfig __iomem *vqconfig;
struct mic_vqconfig config;
struct virtqueue *vq;
void __iomem *va;
struct _mic_vring_info __iomem *info;
void *used;
int vr_size, _vr_size, err, magic;
struct vring *vr;
u8 type = ioread8(&mvdev->desc->type);
if (index >= ioread8(&mvdev->desc->num_vq))
return ERR_PTR(-ENOENT);
if (!name)
return ERR_PTR(-ENOENT);
/* First assign the vring's allocated in host memory */
vqconfig = mic_vq_config(mvdev->desc) + index;
memcpy_fromio(&config, vqconfig, sizeof(config));
_vr_size = vring_size(le16_to_cpu(config.num), MIC_VIRTIO_RING_ALIGN);
vr_size = PAGE_ALIGN(_vr_size + sizeof(struct _mic_vring_info));
va = mic_card_map(mvdev->mdev, le64_to_cpu(config.address), vr_size);
if (!va)
return ERR_PTR(-ENOMEM);
mvdev->vr[index] = va;
memset_io(va, 0x0, _vr_size);
vq = vring_new_virtqueue(index, le16_to_cpu(config.num),
MIC_VIRTIO_RING_ALIGN, vdev, false,
(void __force *)va, mic_notify, callback,
name);
if (!vq) {
err = -ENOMEM;
goto unmap;
}
info = va + _vr_size;
magic = ioread32(&info->magic);
if (WARN(magic != MIC_MAGIC + type + index, "magic mismatch")) {
err = -EIO;
goto unmap;
}
/* Allocate and reassign used ring now */
mvdev->used_size[index] = PAGE_ALIGN(sizeof(__u16) * 3 +
sizeof(struct vring_used_elem) *
le16_to_cpu(config.num));
used = (void *)__get_free_pages(GFP_KERNEL | __GFP_ZERO,
get_order(mvdev->used_size[index]));
if (!used) {
err = -ENOMEM;
dev_err(mic_dev(mvdev), "%s %d err %d\n",
__func__, __LINE__, err);
goto del_vq;
}
iowrite64(virt_to_phys(used), &vqconfig->used_address);
/*
* To reassign the used ring here we are directly accessing
* struct vring_virtqueue which is a private data structure
* in virtio_ring.c. At the minimum, a BUILD_BUG_ON() in
* vring_new_virtqueue() would ensure that
* (&vq->vring == (struct vring *) (&vq->vq + 1));
*/
vr = (struct vring *)(vq + 1);
vr->used = used;
vq->priv = mvdev;
return vq;
del_vq:
vring_del_virtqueue(vq);
unmap:
mic_card_unmap(mvdev->mdev, mvdev->vr[index]);
return ERR_PTR(err);
}
static int mic_find_vqs(struct virtio_device *vdev, unsigned nvqs,
struct virtqueue *vqs[],
vq_callback_t *callbacks[],
const char *names[])
{
struct mic_vdev *mvdev = to_micvdev(vdev);
struct mic_device_ctrl __iomem *dc = mvdev->dc;
int i, err, retry;
/* We must have this many virtqueues. */
if (nvqs > ioread8(&mvdev->desc->num_vq))
return -ENOENT;
for (i = 0; i < nvqs; ++i) {
dev_dbg(mic_dev(mvdev), "%s: %d: %s\n",
__func__, i, names[i]);
vqs[i] = mic_find_vq(vdev, i, callbacks[i], names[i]);
if (IS_ERR(vqs[i])) {
err = PTR_ERR(vqs[i]);
goto error;
}
}
iowrite8(1, &dc->used_address_updated);
/*
* Send an interrupt to the host to inform it that used
* rings have been re-assigned.
*/
mic_send_intr(mvdev->mdev, mvdev->c2h_vdev_db);
for (retry = 100; retry--;) {
if (!ioread8(&dc->used_address_updated))
break;
msleep(100);
};
dev_dbg(mic_dev(mvdev), "%s: retry: %d\n", __func__, retry);
if (!retry) {
err = -ENODEV;
goto error;
}
return 0;
error:
mic_del_vqs(vdev);
return err;
}
/*
* The config ops structure as defined by virtio config
*/
static struct virtio_config_ops mic_vq_config_ops = {
.get_features = mic_get_features,
.finalize_features = mic_finalize_features,
.get = mic_get,
.set = mic_set,
.get_status = mic_get_status,
.set_status = mic_set_status,
.reset = mic_reset,
.find_vqs = mic_find_vqs,
.del_vqs = mic_del_vqs,
};
static irqreturn_t
mic_virtio_intr_handler(int irq, void *data)
{
struct mic_vdev *mvdev = data;
struct virtqueue *vq;
mic_ack_interrupt(mvdev->mdev);
list_for_each_entry(vq, &mvdev->vdev.vqs, list)
vring_interrupt(0, vq);
return IRQ_HANDLED;
}
static void mic_virtio_release_dev(struct device *_d)
{
/*
* No need for a release method similar to virtio PCI.
* Provide an empty one to avoid getting a warning from core.
*/
}
/*
* adds a new device and register it with virtio
* appropriate drivers are loaded by the device model
*/
static int mic_add_device(struct mic_device_desc __iomem *d,
unsigned int offset, struct mic_driver *mdrv)
{
struct mic_vdev *mvdev;
int ret;
int virtio_db;
u8 type = ioread8(&d->type);
mvdev = kzalloc(sizeof(*mvdev), GFP_KERNEL);
if (!mvdev) {
dev_err(mdrv->dev, "Cannot allocate mic dev %u type %u\n",
offset, type);
return -ENOMEM;
}
mvdev->mdev = &mdrv->mdev;
mvdev->vdev.dev.parent = mdrv->dev;
mvdev->vdev.dev.release = mic_virtio_release_dev;
mvdev->vdev.id.device = type;
mvdev->vdev.config = &mic_vq_config_ops;
mvdev->desc = d;
mvdev->dc = (void __iomem *)d + mic_aligned_desc_size(d);
init_completion(&mvdev->reset_done);
virtio_db = mic_next_card_db();
mvdev->virtio_cookie = mic_request_card_irq(mic_virtio_intr_handler,
NULL, "virtio intr", mvdev, virtio_db);
if (IS_ERR(mvdev->virtio_cookie)) {
ret = PTR_ERR(mvdev->virtio_cookie);
goto kfree;
}
iowrite8((u8)virtio_db, &mvdev->dc->h2c_vdev_db);
mvdev->c2h_vdev_db = ioread8(&mvdev->dc->c2h_vdev_db);
ret = register_virtio_device(&mvdev->vdev);
if (ret) {
dev_err(mic_dev(mvdev),
"Failed to register mic device %u type %u\n",
offset, type);
goto free_irq;
}
iowrite64((u64)mvdev, &mvdev->dc->vdev);
dev_dbg(mic_dev(mvdev), "%s: registered mic device %u type %u mvdev %p\n",
__func__, offset, type, mvdev);
return 0;
free_irq:
mic_free_card_irq(mvdev->virtio_cookie, mvdev);
kfree:
kfree(mvdev);
return ret;
}
/*
* match for a mic device with a specific desc pointer
*/
static int mic_match_desc(struct device *dev, void *data)
{
struct virtio_device *vdev = dev_to_virtio(dev);
struct mic_vdev *mvdev = to_micvdev(vdev);
return mvdev->desc == (void __iomem *)data;
}
static void mic_handle_config_change(struct mic_device_desc __iomem *d,
unsigned int offset, struct mic_driver *mdrv)
{
struct mic_device_ctrl __iomem *dc
= (void __iomem *)d + mic_aligned_desc_size(d);
struct mic_vdev *mvdev = (struct mic_vdev *)ioread64(&dc->vdev);
if (ioread8(&dc->config_change) != MIC_VIRTIO_PARAM_CONFIG_CHANGED)
return;
dev_dbg(mdrv->dev, "%s %d\n", __func__, __LINE__);
virtio_config_changed(&mvdev->vdev);
iowrite8(1, &dc->guest_ack);
}
/*
* removes a virtio device if a hot remove event has been
* requested by the host.
*/
static int mic_remove_device(struct mic_device_desc __iomem *d,
unsigned int offset, struct mic_driver *mdrv)
{
struct mic_device_ctrl __iomem *dc
= (void __iomem *)d + mic_aligned_desc_size(d);
struct mic_vdev *mvdev = (struct mic_vdev *)ioread64(&dc->vdev);
u8 status;
int ret = -1;
if (ioread8(&dc->config_change) == MIC_VIRTIO_PARAM_DEV_REMOVE) {
dev_dbg(mdrv->dev,
"%s %d config_change %d type %d mvdev %p\n",
__func__, __LINE__,
ioread8(&dc->config_change), ioread8(&d->type), mvdev);
status = ioread8(&d->status);
reinit_completion(&mvdev->reset_done);
unregister_virtio_device(&mvdev->vdev);
mic_free_card_irq(mvdev->virtio_cookie, mvdev);
if (status & VIRTIO_CONFIG_S_DRIVER_OK)
wait_for_completion(&mvdev->reset_done);
kfree(mvdev);
iowrite8(1, &dc->guest_ack);
dev_dbg(mdrv->dev, "%s %d guest_ack %d\n",
__func__, __LINE__, ioread8(&dc->guest_ack));
ret = 0;
}
return ret;
}
#define REMOVE_DEVICES true
static void mic_scan_devices(struct mic_driver *mdrv, bool remove)
{
s8 type;
unsigned int i;
struct mic_device_desc __iomem *d;
struct mic_device_ctrl __iomem *dc;
struct device *dev;
int ret;
for (i = sizeof(struct mic_bootparam); i < MIC_DP_SIZE;
i += mic_total_desc_size(d)) {
d = mdrv->dp + i;
dc = (void __iomem *)d + mic_aligned_desc_size(d);
/*
* This read barrier is paired with the corresponding write
* barrier on the host which is inserted before adding or
* removing a virtio device descriptor, by updating the type.
*/
rmb();
type = ioread8(&d->type);
/* end of list */
if (type == 0)
break;
if (type == -1)
continue;
/* device already exists */
dev = device_find_child(mdrv->dev, (void __force *)d,
mic_match_desc);
if (dev) {
if (remove)
iowrite8(MIC_VIRTIO_PARAM_DEV_REMOVE,
&dc->config_change);
put_device(dev);
mic_handle_config_change(d, i, mdrv);
ret = mic_remove_device(d, i, mdrv);
if (!ret && !remove)
iowrite8(-1, &d->type);
if (remove) {
iowrite8(0, &dc->config_change);
iowrite8(0, &dc->guest_ack);
}
continue;
}
/* new device */
dev_dbg(mdrv->dev, "%s %d Adding new virtio device %p\n",
__func__, __LINE__, d);
if (!remove)
mic_add_device(d, i, mdrv);
}
}
/*
* mic_hotplug_device tries to find changes in the device page.
*/
static void mic_hotplug_devices(struct work_struct *work)
{
struct mic_driver *mdrv = container_of(work,
struct mic_driver, hotplug_work);
mic_scan_devices(mdrv, !REMOVE_DEVICES);
}
/*
* Interrupt handler for hot plug/config changes etc.
*/
static irqreturn_t
mic_extint_handler(int irq, void *data)
{
struct mic_driver *mdrv = (struct mic_driver *)data;
dev_dbg(mdrv->dev, "%s %d hotplug work\n",
__func__, __LINE__);
mic_ack_interrupt(&mdrv->mdev);
schedule_work(&mdrv->hotplug_work);
return IRQ_HANDLED;
}
/*
* Init function for virtio
*/
int mic_devices_init(struct mic_driver *mdrv)
{
int rc;
struct mic_bootparam __iomem *bootparam;
int config_db;
INIT_WORK(&mdrv->hotplug_work, mic_hotplug_devices);
mic_scan_devices(mdrv, !REMOVE_DEVICES);
config_db = mic_next_card_db();
virtio_config_cookie = mic_request_card_irq(mic_extint_handler, NULL,
"virtio_config_intr", mdrv,
config_db);
if (IS_ERR(virtio_config_cookie)) {
rc = PTR_ERR(virtio_config_cookie);
goto exit;
}
bootparam = mdrv->dp;
iowrite8(config_db, &bootparam->h2c_config_db);
return 0;
exit:
return rc;
}
/*
* Uninit function for virtio
*/
void mic_devices_uninit(struct mic_driver *mdrv)
{
struct mic_bootparam __iomem *bootparam = mdrv->dp;
iowrite8(-1, &bootparam->h2c_config_db);
mic_free_card_irq(virtio_config_cookie, mdrv);
flush_work(&mdrv->hotplug_work);
mic_scan_devices(mdrv, REMOVE_DEVICES);
}

View file

@ -0,0 +1,76 @@
/*
* 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".
*
* Disclaimer: The codes contained in these modules may be specific to
* the Intel Software Development Platform codenamed: Knights Ferry, and
* the Intel product codenamed: Knights Corner, and are not backward
* compatible with other Intel products. Additionally, Intel will NOT
* support the codes or instruction set in future products.
*
* Intel MIC Card driver.
*
*/
#ifndef __MIC_CARD_VIRTIO_H
#define __MIC_CARD_VIRTIO_H
#include <linux/mic_common.h>
#include "mic_device.h"
/*
* 64 bit I/O access
*/
#ifndef ioread64
#define ioread64 readq
#endif
#ifndef iowrite64
#define iowrite64 writeq
#endif
static inline unsigned mic_desc_size(struct mic_device_desc __iomem *desc)
{
return sizeof(*desc)
+ ioread8(&desc->num_vq) * sizeof(struct mic_vqconfig)
+ ioread8(&desc->feature_len) * 2
+ ioread8(&desc->config_len);
}
static inline struct mic_vqconfig __iomem *
mic_vq_config(struct mic_device_desc __iomem *desc)
{
return (struct mic_vqconfig __iomem *)(desc + 1);
}
static inline __u8 __iomem *
mic_vq_features(struct mic_device_desc __iomem *desc)
{
return (__u8 __iomem *)(mic_vq_config(desc) + ioread8(&desc->num_vq));
}
static inline __u8 __iomem *
mic_vq_configspace(struct mic_device_desc __iomem *desc)
{
return mic_vq_features(desc) + ioread8(&desc->feature_len) * 2;
}
static inline unsigned mic_total_desc_size(struct mic_device_desc __iomem *desc)
{
return mic_aligned_desc_size(desc) + sizeof(struct mic_device_ctrl);
}
int mic_devices_init(struct mic_driver *mdrv);
void mic_devices_uninit(struct mic_driver *mdrv);
#endif

View file

@ -0,0 +1,306 @@
/*
* 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".
*
* Disclaimer: The codes contained in these modules may be specific to
* the Intel Software Development Platform codenamed: Knights Ferry, and
* the Intel product codenamed: Knights Corner, and are not backward
* compatible with other Intel products. Additionally, Intel will NOT
* support the codes or instruction set in future products.
*
* Intel MIC Card driver.
*
*/
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/platform_device.h>
#include "../common/mic_dev.h"
#include "mic_device.h"
#include "mic_x100.h"
static const char mic_driver_name[] = "mic";
static struct mic_driver g_drv;
/**
* mic_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.
*/
u32 mic_read_spad(struct mic_device *mdev, unsigned int idx)
{
return mic_mmio_read(&mdev->mmio,
MIC_X100_SBOX_BASE_ADDRESS +
MIC_X100_SBOX_SPAD0 + idx * 4);
}
/**
* __mic_send_intr - Send interrupt to Host.
* @mdev: pointer to mic_device instance
* @doorbell: Doorbell number.
*/
void mic_send_intr(struct mic_device *mdev, int doorbell)
{
struct mic_mw *mw = &mdev->mmio;
if (doorbell > MIC_X100_MAX_DOORBELL_IDX)
return;
/* Ensure that the interrupt is ordered w.r.t previous stores. */
wmb();
mic_mmio_write(mw, MIC_X100_SBOX_SDBIC0_DBREQ_BIT,
MIC_X100_SBOX_BASE_ADDRESS +
(MIC_X100_SBOX_SDBIC0 + (4 * doorbell)));
}
/**
* mic_ack_interrupt - Device specific interrupt handling.
* @mdev: pointer to mic_device instance
*
* Returns: bitmask of doorbell events triggered.
*/
u32 mic_ack_interrupt(struct mic_device *mdev)
{
return 0;
}
static inline int mic_get_sbox_irq(int db)
{
return MIC_X100_IRQ_BASE + db;
}
static inline int mic_get_rdmasr_irq(int index)
{
return MIC_X100_RDMASR_IRQ_BASE + index;
}
/**
* mic_hw_intr_init - Initialize h/w specific interrupt
* information.
* @mdrv: pointer to mic_driver
*/
void mic_hw_intr_init(struct mic_driver *mdrv)
{
mdrv->intr_info.num_intr = MIC_X100_NUM_SBOX_IRQ +
MIC_X100_NUM_RDMASR_IRQ;
}
/**
* mic_db_to_irq - Retrieve irq number corresponding to a doorbell.
* @mdrv: pointer to mic_driver
* @db: The doorbell obtained for which the irq is needed. Doorbell
* may correspond to an sbox doorbell or an rdmasr index.
*
* Returns the irq corresponding to the doorbell.
*/
int mic_db_to_irq(struct mic_driver *mdrv, int db)
{
int rdmasr_index;
if (db < MIC_X100_NUM_SBOX_IRQ) {
return mic_get_sbox_irq(db);
} else {
rdmasr_index = db - MIC_X100_NUM_SBOX_IRQ +
MIC_X100_RDMASR_IRQ_BASE;
return mic_get_rdmasr_irq(rdmasr_index);
}
}
/*
* mic_card_map - Allocate virtual address for a remote memory region.
* @mdev: pointer to mic_device instance.
* @addr: Remote DMA address.
* @size: Size of the region.
*
* Returns: Virtual address backing the remote memory region.
*/
void __iomem *
mic_card_map(struct mic_device *mdev, dma_addr_t addr, size_t size)
{
return ioremap(addr, size);
}
/*
* mic_card_unmap - Unmap the virtual address for a remote memory region.
* @mdev: pointer to mic_device instance.
* @addr: Virtual address for remote memory region.
*
* Returns: None.
*/
void mic_card_unmap(struct mic_device *mdev, void __iomem *addr)
{
iounmap(addr);
}
static inline struct mic_driver *mbdev_to_mdrv(struct mbus_device *mbdev)
{
return dev_get_drvdata(mbdev->dev.parent);
}
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)
{
int rc = 0;
unsigned int irq = intr_src;
unsigned long cookie = irq;
rc = request_threaded_irq(irq, handler, thread_fn, 0, name, data);
if (rc) {
dev_err(mbdev_to_mdrv(mbdev)->dev,
"request_threaded_irq failed rc = %d\n", rc);
return ERR_PTR(rc);
}
return (struct mic_irq *)cookie;
}
static void _mic_free_irq(struct mbus_device *mbdev,
struct mic_irq *cookie, void *data)
{
unsigned long irq = (unsigned long)cookie;
free_irq(irq, data);
}
static void _mic_ack_interrupt(struct mbus_device *mbdev, int num)
{
mic_ack_interrupt(&mbdev_to_mdrv(mbdev)->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,
};
static int __init mic_probe(struct platform_device *pdev)
{
struct mic_driver *mdrv = &g_drv;
struct mic_device *mdev = &mdrv->mdev;
int rc = 0;
mdrv->dev = &pdev->dev;
snprintf(mdrv->name, sizeof(mic_driver_name), mic_driver_name);
mdev->mmio.pa = MIC_X100_MMIO_BASE;
mdev->mmio.len = MIC_X100_MMIO_LEN;
mdev->mmio.va = devm_ioremap(&pdev->dev, MIC_X100_MMIO_BASE,
MIC_X100_MMIO_LEN);
if (!mdev->mmio.va) {
dev_err(&pdev->dev, "Cannot remap MMIO BAR\n");
rc = -EIO;
goto done;
}
mic_hw_intr_init(mdrv);
platform_set_drvdata(pdev, mdrv);
mdrv->dma_mbdev = mbus_register_device(mdrv->dev, MBUS_DEV_DMA_MIC,
NULL, &mbus_hw_ops,
mdrv->mdev.mmio.va);
if (IS_ERR(mdrv->dma_mbdev)) {
rc = PTR_ERR(mdrv->dma_mbdev);
dev_err(&pdev->dev, "mbus_add_device failed rc %d\n", rc);
goto done;
}
rc = mic_driver_init(mdrv);
if (rc) {
dev_err(&pdev->dev, "mic_driver_init failed rc %d\n", rc);
goto remove_dma;
}
done:
return rc;
remove_dma:
mbus_unregister_device(mdrv->dma_mbdev);
return rc;
}
static int mic_remove(struct platform_device *pdev)
{
struct mic_driver *mdrv = &g_drv;
mic_driver_uninit(mdrv);
mbus_unregister_device(mdrv->dma_mbdev);
return 0;
}
static void mic_platform_shutdown(struct platform_device *pdev)
{
mic_remove(pdev);
}
static struct platform_device mic_platform_dev = {
.name = mic_driver_name,
.id = 0,
.num_resources = 0,
};
static struct platform_driver __refdata mic_platform_driver = {
.probe = mic_probe,
.remove = mic_remove,
.shutdown = mic_platform_shutdown,
.driver = {
.name = mic_driver_name,
.owner = THIS_MODULE,
},
};
static int __init mic_init(void)
{
int ret;
struct cpuinfo_x86 *c = &cpu_data(0);
if (!(c->x86 == 11 && c->x86_model == 1)) {
ret = -ENODEV;
pr_err("%s not running on X100 ret %d\n", __func__, ret);
goto done;
}
mic_init_card_debugfs();
ret = platform_device_register(&mic_platform_dev);
if (ret) {
pr_err("platform_device_register ret %d\n", ret);
goto cleanup_debugfs;
}
ret = platform_driver_register(&mic_platform_driver);
if (ret) {
pr_err("platform_driver_register ret %d\n", ret);
goto device_unregister;
}
return ret;
device_unregister:
platform_device_unregister(&mic_platform_dev);
cleanup_debugfs:
mic_exit_card_debugfs();
done:
return ret;
}
static void __exit mic_exit(void)
{
platform_driver_unregister(&mic_platform_driver);
platform_device_unregister(&mic_platform_dev);
mic_exit_card_debugfs();
}
module_init(mic_init);
module_exit(mic_exit);
MODULE_AUTHOR("Intel Corporation");
MODULE_DESCRIPTION("Intel(R) MIC X100 Card driver");
MODULE_LICENSE("GPL v2");

View file

@ -0,0 +1,48 @@
/*
* 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".
*
* Disclaimer: The codes contained in these modules may be specific to
* the Intel Software Development Platform codenamed: Knights Ferry, and
* the Intel product codenamed: Knights Corner, and are not backward
* compatible with other Intel products. Additionally, Intel will NOT
* support the codes or instruction set in future products.
*
* Intel MIC Card driver.
*
*/
#ifndef _MIC_X100_CARD_H_
#define _MIC_X100_CARD_H_
#define MIC_X100_MMIO_BASE 0x08007C0000ULL
#define MIC_X100_MMIO_LEN 0x00020000ULL
#define MIC_X100_SBOX_BASE_ADDRESS 0x00010000ULL
#define MIC_X100_SBOX_SPAD0 0x0000AB20
#define MIC_X100_SBOX_SDBIC0 0x0000CC90
#define MIC_X100_SBOX_SDBIC0_DBREQ_BIT 0x80000000
#define MIC_X100_SBOX_RDMASR0 0x0000B180
#define MIC_X100_MAX_DOORBELL_IDX 8
#define MIC_X100_NUM_SBOX_IRQ 8
#define MIC_X100_NUM_RDMASR_IRQ 8
#define MIC_X100_SBOX_IRQ_BASE 0
#define MIC_X100_RDMASR_IRQ_BASE 17
#define MIC_X100_IRQ_BASE 26
#endif

View file

@ -0,0 +1,51 @@
/*
* 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 driver.
*
*/
#ifndef __MIC_DEV_H__
#define __MIC_DEV_H__
/**
* struct mic_mw - MIC memory window
*
* @pa: Base physical address.
* @va: Base ioremap'd virtual address.
* @len: Size of the memory window.
*/
struct mic_mw {
phys_addr_t pa;
void __iomem *va;
resource_size_t len;
};
/*
* Scratch pad register offsets used by the host to communicate
* device page DMA address to the card.
*/
#define MIC_DPLO_SPAD 14
#define MIC_DPHI_SPAD 15
/*
* These values are supposed to be in the config_change field of the
* device page when the host sends a config change interrupt to the card.
*/
#define MIC_VIRTIO_PARAM_DEV_REMOVE 0x1
#define MIC_VIRTIO_PARAM_CONFIG_CHANGED 0x2
#endif

View 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

View 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);
}

View 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);
}

View 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

View 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(&copy, 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, &copy);
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,
&copy.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;
}

View 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

View 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);
}
}
}

View 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

View 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");

View 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);
}
}

View 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

View 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;
}

View 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);
}

View 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

View 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, &reg);
else
__clear_bit(offset, &reg);
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,
};

View 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