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

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