diff options
Diffstat (limited to 'drivers/misc/mic')
27 files changed, 6766 insertions, 0 deletions
diff --git a/drivers/misc/mic/Kconfig b/drivers/misc/mic/Kconfig new file mode 100644 index 00000000000..462a5b1d865 --- /dev/null +++ b/drivers/misc/mic/Kconfig @@ -0,0 +1,37 @@ +comment "Intel MIC Host Driver" + +config INTEL_MIC_HOST +	tristate "Intel MIC Host Driver" +	depends on 64BIT && PCI && X86 +	select VHOST_RING +	help +	  This enables Host Driver support for the Intel Many Integrated +	  Core (MIC) family of PCIe form factor coprocessor devices that +	  run a 64 bit Linux OS. The driver manages card OS state and +	  enables communication between host and card. Intel MIC X100 +	  devices are currently supported. + +	  If you are building a host kernel with an Intel MIC device then +	  say M (recommended) or Y, else say N. If unsure say N. + +	  More information about the Intel MIC family as well as the Linux +	  OS and tools for MIC to use with this driver are available from +	  <http://software.intel.com/en-us/mic-developer>. + +comment "Intel MIC Card Driver" + +config INTEL_MIC_CARD +	tristate "Intel MIC Card Driver" +	depends on 64BIT && X86 +	select VIRTIO +	help +	  This enables card driver support for the Intel Many Integrated +	  Core (MIC) device family. The card driver communicates shutdown/ +	  crash events to the host and allows registration/configuration of +	  virtio devices. Intel MIC X100 devices are currently supported. + +	  If you are building a card kernel for an Intel MIC device then +	  say M (recommended) or Y, else say N. If unsure say N. + +	  For more information see +	  <http://software.intel.com/en-us/mic-developer>. diff --git a/drivers/misc/mic/Makefile b/drivers/misc/mic/Makefile new file mode 100644 index 00000000000..05b34d683a5 --- /dev/null +++ b/drivers/misc/mic/Makefile @@ -0,0 +1,6 @@ +# +# Makefile - Intel MIC Linux driver. +# Copyright(c) 2013, Intel Corporation. +# +obj-$(CONFIG_INTEL_MIC_HOST) += host/ +obj-$(CONFIG_INTEL_MIC_CARD) += card/ diff --git a/drivers/misc/mic/card/Makefile b/drivers/misc/mic/card/Makefile new file mode 100644 index 00000000000..69d58bef92c --- /dev/null +++ b/drivers/misc/mic/card/Makefile @@ -0,0 +1,11 @@ +# +# Makefile - Intel MIC Linux driver. +# Copyright(c) 2013, Intel Corporation. +# +ccflags-y += -DINTEL_MIC_CARD + +obj-$(CONFIG_INTEL_MIC_CARD) += mic_card.o +mic_card-y += mic_x100.o +mic_card-y += mic_device.o +mic_card-y += mic_debugfs.o +mic_card-y += mic_virtio.o diff --git a/drivers/misc/mic/card/mic_debugfs.c b/drivers/misc/mic/card/mic_debugfs.c new file mode 100644 index 00000000000..421b3d7911d --- /dev/null +++ b/drivers/misc/mic/card/mic_debugfs.c @@ -0,0 +1,130 @@ +/* + * Intel MIC Platform Software Stack (MPSS) + * + * Copyright(c) 2013 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2, as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * The full GNU General Public License is included in this distribution in + * the file called "COPYING". + * + * Disclaimer: The codes contained in these modules may be specific to + * the Intel Software Development Platform codenamed: Knights Ferry, and + * the Intel product codenamed: Knights Corner, and are not backward + * compatible with other Intel products. Additionally, Intel will NOT + * support the codes or instruction set in future products. + * + * Intel MIC Card driver. + * + */ +#include <linux/debugfs.h> +#include <linux/delay.h> +#include <linux/seq_file.h> +#include <linux/interrupt.h> +#include <linux/device.h> + +#include "../common/mic_dev.h" +#include "mic_device.h" + +/* Debugfs parent dir */ +static struct dentry *mic_dbg; + +/** + * mic_intr_test - Send interrupts to host. + */ +static int mic_intr_test(struct seq_file *s, void *unused) +{ +	struct mic_driver *mdrv = s->private; +	struct mic_device *mdev = &mdrv->mdev; + +	mic_send_intr(mdev, 0); +	msleep(1000); +	mic_send_intr(mdev, 1); +	msleep(1000); +	mic_send_intr(mdev, 2); +	msleep(1000); +	mic_send_intr(mdev, 3); +	msleep(1000); + +	return 0; +} + +static int mic_intr_test_open(struct inode *inode, struct file *file) +{ +	return single_open(file, mic_intr_test, inode->i_private); +} + +static int mic_intr_test_release(struct inode *inode, struct file *file) +{ +	return single_release(inode, file); +} + +static const struct file_operations intr_test_ops = { +	.owner   = THIS_MODULE, +	.open    = mic_intr_test_open, +	.read    = seq_read, +	.llseek  = seq_lseek, +	.release = mic_intr_test_release +}; + +/** + * mic_create_card_debug_dir - Initialize MIC debugfs entries. + */ +void __init mic_create_card_debug_dir(struct mic_driver *mdrv) +{ +	struct dentry *d; + +	if (!mic_dbg) +		return; + +	mdrv->dbg_dir = debugfs_create_dir(mdrv->name, mic_dbg); +	if (!mdrv->dbg_dir) { +		dev_err(mdrv->dev, "Cant create dbg_dir %s\n", mdrv->name); +		return; +	} + +	d = debugfs_create_file("intr_test", 0444, mdrv->dbg_dir, +		mdrv, &intr_test_ops); + +	if (!d) { +		dev_err(mdrv->dev, +			"Cant create dbg intr_test %s\n", mdrv->name); +		return; +	} +} + +/** + * mic_delete_card_debug_dir - Uninitialize MIC debugfs entries. + */ +void mic_delete_card_debug_dir(struct mic_driver *mdrv) +{ +	if (!mdrv->dbg_dir) +		return; + +	debugfs_remove_recursive(mdrv->dbg_dir); +} + +/** + * mic_init_card_debugfs - Initialize global debugfs entry. + */ +void __init mic_init_card_debugfs(void) +{ +	mic_dbg = debugfs_create_dir(KBUILD_MODNAME, NULL); +	if (!mic_dbg) +		pr_err("can't create debugfs dir\n"); +} + +/** + * mic_exit_card_debugfs - Uninitialize global debugfs entry + */ +void mic_exit_card_debugfs(void) +{ +	debugfs_remove(mic_dbg); +} diff --git a/drivers/misc/mic/card/mic_device.c b/drivers/misc/mic/card/mic_device.c new file mode 100644 index 00000000000..d0980ff9683 --- /dev/null +++ b/drivers/misc/mic/card/mic_device.c @@ -0,0 +1,305 @@ +/* + * Intel MIC Platform Software Stack (MPSS) + * + * Copyright(c) 2013 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2, as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * The full GNU General Public License is included in this distribution in + * the file called "COPYING". + * + * Disclaimer: The codes contained in these modules may be specific to + * the Intel Software Development Platform codenamed: Knights Ferry, and + * the Intel product codenamed: Knights Corner, and are not backward + * compatible with other Intel products. Additionally, Intel will NOT + * support the codes or instruction set in future products. + * + * Intel MIC Card driver. + * + */ +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/interrupt.h> +#include <linux/reboot.h> + +#include <linux/mic_common.h> +#include "../common/mic_dev.h" +#include "mic_device.h" +#include "mic_virtio.h" + +static struct mic_driver *g_drv; +static struct mic_irq *shutdown_cookie; + +static void mic_notify_host(u8 state) +{ +	struct mic_driver *mdrv = g_drv; +	struct mic_bootparam __iomem *bootparam = mdrv->dp; + +	iowrite8(state, &bootparam->shutdown_status); +	dev_dbg(mdrv->dev, "%s %d system_state %d\n", +		__func__, __LINE__, state); +	mic_send_intr(&mdrv->mdev, ioread8(&bootparam->c2h_shutdown_db)); +} + +static int mic_panic_event(struct notifier_block *this, unsigned long event, +		void *ptr) +{ +	struct mic_driver *mdrv = g_drv; +	struct mic_bootparam __iomem *bootparam = mdrv->dp; + +	iowrite8(-1, &bootparam->h2c_config_db); +	iowrite8(-1, &bootparam->h2c_shutdown_db); +	mic_notify_host(MIC_CRASHED); +	return NOTIFY_DONE; +} + +static struct notifier_block mic_panic = { +	.notifier_call  = mic_panic_event, +}; + +static irqreturn_t mic_shutdown_isr(int irq, void *data) +{ +	struct mic_driver *mdrv = g_drv; +	struct mic_bootparam __iomem *bootparam = mdrv->dp; + +	mic_ack_interrupt(&g_drv->mdev); +	if (ioread8(&bootparam->shutdown_card)) +		orderly_poweroff(true); +	return IRQ_HANDLED; +} + +static int mic_shutdown_init(void) +{ +	int rc = 0; +	struct mic_driver *mdrv = g_drv; +	struct mic_bootparam __iomem *bootparam = mdrv->dp; +	int shutdown_db; + +	shutdown_db = mic_next_card_db(); +	shutdown_cookie = mic_request_card_irq(mic_shutdown_isr, +			"Shutdown", mdrv, shutdown_db); +	if (IS_ERR(shutdown_cookie)) +		rc = PTR_ERR(shutdown_cookie); +	else +		iowrite8(shutdown_db, &bootparam->h2c_shutdown_db); +	return rc; +} + +static void mic_shutdown_uninit(void) +{ +	struct mic_driver *mdrv = g_drv; +	struct mic_bootparam __iomem *bootparam = mdrv->dp; + +	iowrite8(-1, &bootparam->h2c_shutdown_db); +	mic_free_card_irq(shutdown_cookie, mdrv); +} + +static int __init mic_dp_init(void) +{ +	struct mic_driver *mdrv = g_drv; +	struct mic_device *mdev = &mdrv->mdev; +	struct mic_bootparam __iomem *bootparam; +	u64 lo, hi, dp_dma_addr; +	u32 magic; + +	lo = mic_read_spad(&mdrv->mdev, MIC_DPLO_SPAD); +	hi = mic_read_spad(&mdrv->mdev, MIC_DPHI_SPAD); + +	dp_dma_addr = lo | (hi << 32); +	mdrv->dp = mic_card_map(mdev, dp_dma_addr, MIC_DP_SIZE); +	if (!mdrv->dp) { +		dev_err(mdrv->dev, "Cannot remap Aperture BAR\n"); +		return -ENOMEM; +	} +	bootparam = mdrv->dp; +	magic = ioread32(&bootparam->magic); +	if (MIC_MAGIC != magic) { +		dev_err(mdrv->dev, "bootparam magic mismatch 0x%x\n", magic); +		return -EIO; +	} +	return 0; +} + +/* Uninitialize the device page */ +static void mic_dp_uninit(void) +{ +	mic_card_unmap(&g_drv->mdev, g_drv->dp); +} + +/** + * mic_request_card_irq - request an irq. + * + * @func: The callback function that handles the interrupt. + * @name: The ASCII name of the callee requesting the irq. + * @data: private data that is returned back when calling the + * function handler. + * @index: The doorbell index of the requester. + * + * returns: The cookie that is transparent to the caller. Passed + * back when calling mic_free_irq. An appropriate error code + * is returned on failure. Caller needs to use IS_ERR(return_val) + * to check for failure and PTR_ERR(return_val) to obtained the + * error code. + * + */ +struct mic_irq *mic_request_card_irq(irqreturn_t (*func)(int irq, void *data), +	const char *name, void *data, int index) +{ +	int rc = 0; +	unsigned long cookie; +	struct mic_driver *mdrv = g_drv; + +	rc  = request_irq(mic_db_to_irq(mdrv, index), func, +		0, name, data); +	if (rc) { +		dev_err(mdrv->dev, "request_irq failed rc = %d\n", rc); +		goto err; +	} +	mdrv->irq_info.irq_usage_count[index]++; +	cookie = index; +	return (struct mic_irq *)cookie; +err: +	return ERR_PTR(rc); +} + +/** + * mic_free_card_irq - free irq. + * + * @cookie: cookie obtained during a successful call to mic_request_irq + * @data: private data specified by the calling function during the + * mic_request_irq + * + * returns: none. + */ +void mic_free_card_irq(struct mic_irq *cookie, void *data) +{ +	int index; +	struct mic_driver *mdrv = g_drv; + +	index = (unsigned long)cookie & 0xFFFFU; +	free_irq(mic_db_to_irq(mdrv, index), data); +	mdrv->irq_info.irq_usage_count[index]--; +} + +/** + * mic_next_card_db - Get the doorbell with minimum usage count. + * + * Returns the irq index. + */ +int mic_next_card_db(void) +{ +	int i; +	int index = 0; +	struct mic_driver *mdrv = g_drv; + +	for (i = 0; i < mdrv->intr_info.num_intr; i++) { +		if (mdrv->irq_info.irq_usage_count[i] < +			mdrv->irq_info.irq_usage_count[index]) +			index = i; +	} + +	return index; +} + +/** + * mic_init_irq - Initialize irq information. + * + * Returns 0 in success. Appropriate error code on failure. + */ +static int mic_init_irq(void) +{ +	struct mic_driver *mdrv = g_drv; + +	mdrv->irq_info.irq_usage_count = kzalloc((sizeof(u32) * +			mdrv->intr_info.num_intr), +			GFP_KERNEL); +	if (!mdrv->irq_info.irq_usage_count) +		return -ENOMEM; +	return 0; +} + +/** + * mic_uninit_irq - Uninitialize irq information. + * + * None. + */ +static void mic_uninit_irq(void) +{ +	struct mic_driver *mdrv = g_drv; + +	kfree(mdrv->irq_info.irq_usage_count); +} + +/* + * mic_driver_init - MIC driver initialization tasks. + * + * Returns 0 in success. Appropriate error code on failure. + */ +int __init mic_driver_init(struct mic_driver *mdrv) +{ +	int rc; + +	g_drv = mdrv; +	/* +	 * Unloading the card module is not supported. The MIC card module +	 * handles fundamental operations like host/card initiated shutdowns +	 * and informing the host about card crashes and cannot be unloaded. +	 */ +	if (!try_module_get(mdrv->dev->driver->owner)) { +		rc = -ENODEV; +		goto done; +	} +	rc = mic_dp_init(); +	if (rc) +		goto put; +	rc = mic_init_irq(); +	if (rc) +		goto dp_uninit; +	rc = mic_shutdown_init(); +	if (rc) +		goto irq_uninit; +	rc = mic_devices_init(mdrv); +	if (rc) +		goto shutdown_uninit; +	mic_create_card_debug_dir(mdrv); +	atomic_notifier_chain_register(&panic_notifier_list, &mic_panic); +done: +	return rc; +shutdown_uninit: +	mic_shutdown_uninit(); +irq_uninit: +	mic_uninit_irq(); +dp_uninit: +	mic_dp_uninit(); +put: +	module_put(mdrv->dev->driver->owner); +	return rc; +} + +/* + * mic_driver_uninit - MIC driver uninitialization tasks. + * + * Returns None + */ +void mic_driver_uninit(struct mic_driver *mdrv) +{ +	mic_delete_card_debug_dir(mdrv); +	mic_devices_uninit(mdrv); +	/* +	 * Inform the host about the shutdown status i.e. poweroff/restart etc. +	 * The module cannot be unloaded so the only code path to call +	 * mic_devices_uninit(..) is the shutdown callback. +	 */ +	mic_notify_host(system_state); +	mic_shutdown_uninit(); +	mic_uninit_irq(); +	mic_dp_uninit(); +	module_put(mdrv->dev->driver->owner); +} diff --git a/drivers/misc/mic/card/mic_device.h b/drivers/misc/mic/card/mic_device.h new file mode 100644 index 00000000000..306f502be95 --- /dev/null +++ b/drivers/misc/mic/card/mic_device.h @@ -0,0 +1,134 @@ +/* + * Intel MIC Platform Software Stack (MPSS) + * + * Copyright(c) 2013 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2, as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * The full GNU General Public License is included in this distribution in + * the file called "COPYING". + * + * Disclaimer: The codes contained in these modules may be specific to + * the Intel Software Development Platform codenamed: Knights Ferry, and + * the Intel product codenamed: Knights Corner, and are not backward + * compatible with other Intel products. Additionally, Intel will NOT + * support the codes or instruction set in future products. + * + * Intel MIC Card driver. + * + */ +#ifndef _MIC_CARD_DEVICE_H_ +#define _MIC_CARD_DEVICE_H_ + +#include <linux/workqueue.h> +#include <linux/io.h> +#include <linux/irqreturn.h> + +/** + * struct mic_intr_info - Contains h/w specific interrupt sources info + * + * @num_intr: The number of irqs available + */ +struct mic_intr_info { +	u32 num_intr; +}; + +/** + * struct mic_irq_info - OS specific irq information + * + * @irq_usage_count: usage count array tracking the number of sources + * assigned for each irq. + */ +struct mic_irq_info { +	int *irq_usage_count; +}; + +/** + * struct mic_device -  MIC device information. + * + * @mmio: MMIO bar information. + */ +struct mic_device { +	struct mic_mw mmio; +}; + +/** + * struct mic_driver - MIC card driver information. + * + * @name: Name for MIC driver. + * @dbg_dir: debugfs directory of this MIC device. + * @dev: The device backing this MIC. + * @dp: The pointer to the virtio device page. + * @mdev: MIC device information for the host. + * @hotplug_work: Hot plug work for adding/removing virtio devices. + * @irq_info: The OS specific irq information + * @intr_info: H/W specific interrupt information. + */ +struct mic_driver { +	char name[20]; +	struct dentry *dbg_dir; +	struct device *dev; +	void __iomem *dp; +	struct mic_device mdev; +	struct work_struct hotplug_work; +	struct mic_irq_info irq_info; +	struct mic_intr_info intr_info; +}; + +/** + * struct mic_irq - opaque pointer used as cookie + */ +struct mic_irq; + +/** + * mic_mmio_read - read from an MMIO register. + * @mw: MMIO register base virtual address. + * @offset: register offset. + * + * RETURNS: register value. + */ +static inline u32 mic_mmio_read(struct mic_mw *mw, u32 offset) +{ +	return ioread32(mw->va + offset); +} + +/** + * mic_mmio_write - write to an MMIO register. + * @mw: MMIO register base virtual address. + * @val: the data value to put into the register + * @offset: register offset. + * + * RETURNS: none. + */ +static inline void +mic_mmio_write(struct mic_mw *mw, u32 val, u32 offset) +{ +	iowrite32(val, mw->va + offset); +} + +int mic_driver_init(struct mic_driver *mdrv); +void mic_driver_uninit(struct mic_driver *mdrv); +int mic_next_card_db(void); +struct mic_irq *mic_request_card_irq(irqreturn_t (*func)(int irq, void *data), +	const char *name, void *data, int intr_src); +void mic_free_card_irq(struct mic_irq *cookie, void *data); +u32 mic_read_spad(struct mic_device *mdev, unsigned int idx); +void mic_send_intr(struct mic_device *mdev, int doorbell); +int mic_db_to_irq(struct mic_driver *mdrv, int db); +u32 mic_ack_interrupt(struct mic_device *mdev); +void mic_hw_intr_init(struct mic_driver *mdrv); +void __iomem * +mic_card_map(struct mic_device *mdev, dma_addr_t addr, size_t size); +void mic_card_unmap(struct mic_device *mdev, void __iomem *addr); +void __init mic_create_card_debug_dir(struct mic_driver *mdrv); +void mic_delete_card_debug_dir(struct mic_driver *mdrv); +void __init mic_init_card_debugfs(void); +void mic_exit_card_debugfs(void); +#endif diff --git a/drivers/misc/mic/card/mic_virtio.c b/drivers/misc/mic/card/mic_virtio.c new file mode 100644 index 00000000000..653799b96bf --- /dev/null +++ b/drivers/misc/mic/card/mic_virtio.c @@ -0,0 +1,633 @@ +/* + * Intel MIC Platform Software Stack (MPSS) + * + * Copyright(c) 2013 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2, as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * The full GNU General Public License is included in this distribution in + * the file called "COPYING". + * + * Disclaimer: The codes contained in these modules may be specific to + * the Intel Software Development Platform codenamed: Knights Ferry, and + * the Intel product codenamed: Knights Corner, and are not backward + * compatible with other Intel products. Additionally, Intel will NOT + * support the codes or instruction set in future products. + * + * Adapted from: + * + * virtio for kvm on s390 + * + * Copyright IBM Corp. 2008 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License (version 2 only) + * as published by the Free Software Foundation. + * + *    Author(s): Christian Borntraeger <borntraeger@de.ibm.com> + * + * Intel MIC Card driver. + * + */ +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/virtio_config.h> + +#include "../common/mic_dev.h" +#include "mic_virtio.h" + +#define VIRTIO_SUBCODE_64 0x0D00 + +#define MIC_MAX_VRINGS                4 +struct mic_vdev { +	struct virtio_device vdev; +	struct mic_device_desc __iomem *desc; +	struct mic_device_ctrl __iomem *dc; +	struct mic_device *mdev; +	void __iomem *vr[MIC_MAX_VRINGS]; +	int used_size[MIC_MAX_VRINGS]; +	struct completion reset_done; +	struct mic_irq *virtio_cookie; +	int c2h_vdev_db; +}; + +static struct mic_irq *virtio_config_cookie; +#define to_micvdev(vd) container_of(vd, struct mic_vdev, vdev) + +/* Helper API to obtain the parent of the virtio device */ +static inline struct device *mic_dev(struct mic_vdev *mvdev) +{ +	return mvdev->vdev.dev.parent; +} + +/* This gets the device's feature bits. */ +static u32 mic_get_features(struct virtio_device *vdev) +{ +	unsigned int i, bits; +	u32 features = 0; +	struct mic_device_desc __iomem *desc = to_micvdev(vdev)->desc; +	u8 __iomem *in_features = mic_vq_features(desc); +	int feature_len = ioread8(&desc->feature_len); + +	bits = min_t(unsigned, feature_len, +		sizeof(vdev->features)) * 8; +	for (i = 0; i < bits; i++) +		if (ioread8(&in_features[i / 8]) & (BIT(i % 8))) +			features |= BIT(i); + +	return features; +} + +static void mic_finalize_features(struct virtio_device *vdev) +{ +	unsigned int i, bits; +	struct mic_device_desc __iomem *desc = to_micvdev(vdev)->desc; +	u8 feature_len = ioread8(&desc->feature_len); +	/* Second half of bitmap is features we accept. */ +	u8 __iomem *out_features = +		mic_vq_features(desc) + feature_len; + +	/* Give virtio_ring a chance to accept features. */ +	vring_transport_features(vdev); + +	memset_io(out_features, 0, feature_len); +	bits = min_t(unsigned, feature_len, +		sizeof(vdev->features)) * 8; +	for (i = 0; i < bits; i++) { +		if (test_bit(i, vdev->features)) +			iowrite8(ioread8(&out_features[i / 8]) | (1 << (i % 8)), +				 &out_features[i / 8]); +	} +} + +/* + * Reading and writing elements in config space + */ +static void mic_get(struct virtio_device *vdev, unsigned int offset, +		   void *buf, unsigned len) +{ +	struct mic_device_desc __iomem *desc = to_micvdev(vdev)->desc; + +	if (offset + len > ioread8(&desc->config_len)) +		return; +	memcpy_fromio(buf, mic_vq_configspace(desc) + offset, len); +} + +static void mic_set(struct virtio_device *vdev, unsigned int offset, +		   const void *buf, unsigned len) +{ +	struct mic_device_desc __iomem *desc = to_micvdev(vdev)->desc; + +	if (offset + len > ioread8(&desc->config_len)) +		return; +	memcpy_toio(mic_vq_configspace(desc) + offset, buf, len); +} + +/* + * The operations to get and set the status word just access the status + * field of the device descriptor. set_status also interrupts the host + * to tell about status changes. + */ +static u8 mic_get_status(struct virtio_device *vdev) +{ +	return ioread8(&to_micvdev(vdev)->desc->status); +} + +static void mic_set_status(struct virtio_device *vdev, u8 status) +{ +	struct mic_vdev *mvdev = to_micvdev(vdev); +	if (!status) +		return; +	iowrite8(status, &mvdev->desc->status); +	mic_send_intr(mvdev->mdev, mvdev->c2h_vdev_db); +} + +/* Inform host on a virtio device reset and wait for ack from host */ +static void mic_reset_inform_host(struct virtio_device *vdev) +{ +	struct mic_vdev *mvdev = to_micvdev(vdev); +	struct mic_device_ctrl __iomem *dc = mvdev->dc; +	int retry; + +	iowrite8(0, &dc->host_ack); +	iowrite8(1, &dc->vdev_reset); +	mic_send_intr(mvdev->mdev, mvdev->c2h_vdev_db); + +	/* Wait till host completes all card accesses and acks the reset */ +	for (retry = 100; retry--;) { +		if (ioread8(&dc->host_ack)) +			break; +		msleep(100); +	}; + +	dev_dbg(mic_dev(mvdev), "%s: retry: %d\n", __func__, retry); + +	/* Reset status to 0 in case we timed out */ +	iowrite8(0, &mvdev->desc->status); +} + +static void mic_reset(struct virtio_device *vdev) +{ +	struct mic_vdev *mvdev = to_micvdev(vdev); + +	dev_dbg(mic_dev(mvdev), "%s: virtio id %d\n", +		__func__, vdev->id.device); + +	mic_reset_inform_host(vdev); +	complete_all(&mvdev->reset_done); +} + +/* + * The virtio_ring code calls this API when it wants to notify the Host. + */ +static bool mic_notify(struct virtqueue *vq) +{ +	struct mic_vdev *mvdev = vq->priv; + +	mic_send_intr(mvdev->mdev, mvdev->c2h_vdev_db); +	return true; +} + +static void mic_del_vq(struct virtqueue *vq, int n) +{ +	struct mic_vdev *mvdev = to_micvdev(vq->vdev); +	struct vring *vr = (struct vring *)(vq + 1); + +	free_pages((unsigned long) vr->used, get_order(mvdev->used_size[n])); +	vring_del_virtqueue(vq); +	mic_card_unmap(mvdev->mdev, mvdev->vr[n]); +	mvdev->vr[n] = NULL; +} + +static void mic_del_vqs(struct virtio_device *vdev) +{ +	struct mic_vdev *mvdev = to_micvdev(vdev); +	struct virtqueue *vq, *n; +	int idx = 0; + +	dev_dbg(mic_dev(mvdev), "%s\n", __func__); + +	list_for_each_entry_safe(vq, n, &vdev->vqs, list) +		mic_del_vq(vq, idx++); +} + +/* + * This routine will assign vring's allocated in host/io memory. Code in + * virtio_ring.c however continues to access this io memory as if it were local + * memory without io accessors. + */ +static struct virtqueue *mic_find_vq(struct virtio_device *vdev, +				     unsigned index, +				     void (*callback)(struct virtqueue *vq), +				     const char *name) +{ +	struct mic_vdev *mvdev = to_micvdev(vdev); +	struct mic_vqconfig __iomem *vqconfig; +	struct mic_vqconfig config; +	struct virtqueue *vq; +	void __iomem *va; +	struct _mic_vring_info __iomem *info; +	void *used; +	int vr_size, _vr_size, err, magic; +	struct vring *vr; +	u8 type = ioread8(&mvdev->desc->type); + +	if (index >= ioread8(&mvdev->desc->num_vq)) +		return ERR_PTR(-ENOENT); + +	if (!name) +		return ERR_PTR(-ENOENT); + +	/* First assign the vring's allocated in host memory */ +	vqconfig = mic_vq_config(mvdev->desc) + index; +	memcpy_fromio(&config, vqconfig, sizeof(config)); +	_vr_size = vring_size(le16_to_cpu(config.num), MIC_VIRTIO_RING_ALIGN); +	vr_size = PAGE_ALIGN(_vr_size + sizeof(struct _mic_vring_info)); +	va = mic_card_map(mvdev->mdev, le64_to_cpu(config.address), vr_size); +	if (!va) +		return ERR_PTR(-ENOMEM); +	mvdev->vr[index] = va; +	memset_io(va, 0x0, _vr_size); +	vq = vring_new_virtqueue(index, le16_to_cpu(config.num), +				 MIC_VIRTIO_RING_ALIGN, vdev, false, +				 (void __force *)va, mic_notify, callback, +				 name); +	if (!vq) { +		err = -ENOMEM; +		goto unmap; +	} +	info = va + _vr_size; +	magic = ioread32(&info->magic); + +	if (WARN(magic != MIC_MAGIC + type + index, "magic mismatch")) { +		err = -EIO; +		goto unmap; +	} + +	/* Allocate and reassign used ring now */ +	mvdev->used_size[index] = PAGE_ALIGN(sizeof(__u16) * 3 + +					     sizeof(struct vring_used_elem) * +					     le16_to_cpu(config.num)); +	used = (void *)__get_free_pages(GFP_KERNEL | __GFP_ZERO, +					get_order(mvdev->used_size[index])); +	if (!used) { +		err = -ENOMEM; +		dev_err(mic_dev(mvdev), "%s %d err %d\n", +			__func__, __LINE__, err); +		goto del_vq; +	} +	iowrite64(virt_to_phys(used), &vqconfig->used_address); + +	/* +	 * To reassign the used ring here we are directly accessing +	 * struct vring_virtqueue which is a private data structure +	 * in virtio_ring.c. At the minimum, a BUILD_BUG_ON() in +	 * vring_new_virtqueue() would ensure that +	 *  (&vq->vring == (struct vring *) (&vq->vq + 1)); +	 */ +	vr = (struct vring *)(vq + 1); +	vr->used = used; + +	vq->priv = mvdev; +	return vq; +del_vq: +	vring_del_virtqueue(vq); +unmap: +	mic_card_unmap(mvdev->mdev, mvdev->vr[index]); +	return ERR_PTR(err); +} + +static int mic_find_vqs(struct virtio_device *vdev, unsigned nvqs, +			struct virtqueue *vqs[], +			vq_callback_t *callbacks[], +			const char *names[]) +{ +	struct mic_vdev *mvdev = to_micvdev(vdev); +	struct mic_device_ctrl __iomem *dc = mvdev->dc; +	int i, err, retry; + +	/* We must have this many virtqueues. */ +	if (nvqs > ioread8(&mvdev->desc->num_vq)) +		return -ENOENT; + +	for (i = 0; i < nvqs; ++i) { +		dev_dbg(mic_dev(mvdev), "%s: %d: %s\n", +			__func__, i, names[i]); +		vqs[i] = mic_find_vq(vdev, i, callbacks[i], names[i]); +		if (IS_ERR(vqs[i])) { +			err = PTR_ERR(vqs[i]); +			goto error; +		} +	} + +	iowrite8(1, &dc->used_address_updated); +	/* +	 * Send an interrupt to the host to inform it that used +	 * rings have been re-assigned. +	 */ +	mic_send_intr(mvdev->mdev, mvdev->c2h_vdev_db); +	for (retry = 100; retry--;) { +		if (!ioread8(&dc->used_address_updated)) +			break; +		msleep(100); +	}; + +	dev_dbg(mic_dev(mvdev), "%s: retry: %d\n", __func__, retry); +	if (!retry) { +		err = -ENODEV; +		goto error; +	} + +	return 0; +error: +	mic_del_vqs(vdev); +	return err; +} + +/* + * The config ops structure as defined by virtio config + */ +static struct virtio_config_ops mic_vq_config_ops = { +	.get_features = mic_get_features, +	.finalize_features = mic_finalize_features, +	.get = mic_get, +	.set = mic_set, +	.get_status = mic_get_status, +	.set_status = mic_set_status, +	.reset = mic_reset, +	.find_vqs = mic_find_vqs, +	.del_vqs = mic_del_vqs, +}; + +static irqreturn_t +mic_virtio_intr_handler(int irq, void *data) +{ +	struct mic_vdev *mvdev = data; +	struct virtqueue *vq; + +	mic_ack_interrupt(mvdev->mdev); +	list_for_each_entry(vq, &mvdev->vdev.vqs, list) +		vring_interrupt(0, vq); + +	return IRQ_HANDLED; +} + +static void mic_virtio_release_dev(struct device *_d) +{ +	/* +	 * No need for a release method similar to virtio PCI. +	 * Provide an empty one to avoid getting a warning from core. +	 */ +} + +/* + * adds a new device and register it with virtio + * appropriate drivers are loaded by the device model + */ +static int mic_add_device(struct mic_device_desc __iomem *d, +	unsigned int offset, struct mic_driver *mdrv) +{ +	struct mic_vdev *mvdev; +	int ret; +	int virtio_db; +	u8 type = ioread8(&d->type); + +	mvdev = kzalloc(sizeof(*mvdev), GFP_KERNEL); +	if (!mvdev) { +		dev_err(mdrv->dev, "Cannot allocate mic dev %u type %u\n", +			offset, type); +		return -ENOMEM; +	} + +	mvdev->mdev = &mdrv->mdev; +	mvdev->vdev.dev.parent = mdrv->dev; +	mvdev->vdev.dev.release = mic_virtio_release_dev; +	mvdev->vdev.id.device = type; +	mvdev->vdev.config = &mic_vq_config_ops; +	mvdev->desc = d; +	mvdev->dc = (void __iomem *)d + mic_aligned_desc_size(d); +	init_completion(&mvdev->reset_done); + +	virtio_db = mic_next_card_db(); +	mvdev->virtio_cookie = mic_request_card_irq(mic_virtio_intr_handler, +			"virtio intr", mvdev, virtio_db); +	if (IS_ERR(mvdev->virtio_cookie)) { +		ret = PTR_ERR(mvdev->virtio_cookie); +		goto kfree; +	} +	iowrite8((u8)virtio_db, &mvdev->dc->h2c_vdev_db); +	mvdev->c2h_vdev_db = ioread8(&mvdev->dc->c2h_vdev_db); + +	ret = register_virtio_device(&mvdev->vdev); +	if (ret) { +		dev_err(mic_dev(mvdev), +			"Failed to register mic device %u type %u\n", +			offset, type); +		goto free_irq; +	} +	iowrite64((u64)mvdev, &mvdev->dc->vdev); +	dev_dbg(mic_dev(mvdev), "%s: registered mic device %u type %u mvdev %p\n", +		__func__, offset, type, mvdev); + +	return 0; + +free_irq: +	mic_free_card_irq(mvdev->virtio_cookie, mvdev); +kfree: +	kfree(mvdev); +	return ret; +} + +/* + * match for a mic device with a specific desc pointer + */ +static int mic_match_desc(struct device *dev, void *data) +{ +	struct virtio_device *vdev = dev_to_virtio(dev); +	struct mic_vdev *mvdev = to_micvdev(vdev); + +	return mvdev->desc == (void __iomem *)data; +} + +static void mic_handle_config_change(struct mic_device_desc __iomem *d, +	unsigned int offset, struct mic_driver *mdrv) +{ +	struct mic_device_ctrl __iomem *dc +		= (void __iomem *)d + mic_aligned_desc_size(d); +	struct mic_vdev *mvdev = (struct mic_vdev *)ioread64(&dc->vdev); +	struct virtio_driver *drv; + +	if (ioread8(&dc->config_change) != MIC_VIRTIO_PARAM_CONFIG_CHANGED) +		return; + +	dev_dbg(mdrv->dev, "%s %d\n", __func__, __LINE__); +	drv = container_of(mvdev->vdev.dev.driver, +				struct virtio_driver, driver); +	if (drv->config_changed) +		drv->config_changed(&mvdev->vdev); +	iowrite8(1, &dc->guest_ack); +} + +/* + * removes a virtio device if a hot remove event has been + * requested by the host. + */ +static int mic_remove_device(struct mic_device_desc __iomem *d, +	unsigned int offset, struct mic_driver *mdrv) +{ +	struct mic_device_ctrl __iomem *dc +		= (void __iomem *)d + mic_aligned_desc_size(d); +	struct mic_vdev *mvdev = (struct mic_vdev *)ioread64(&dc->vdev); +	u8 status; +	int ret = -1; + +	if (ioread8(&dc->config_change) == MIC_VIRTIO_PARAM_DEV_REMOVE) { +		dev_dbg(mdrv->dev, +			"%s %d config_change %d type %d mvdev %p\n", +			__func__, __LINE__, +			ioread8(&dc->config_change), ioread8(&d->type), mvdev); + +		status = ioread8(&d->status); +		reinit_completion(&mvdev->reset_done); +		unregister_virtio_device(&mvdev->vdev); +		mic_free_card_irq(mvdev->virtio_cookie, mvdev); +		if (status & VIRTIO_CONFIG_S_DRIVER_OK) +			wait_for_completion(&mvdev->reset_done); +		kfree(mvdev); +		iowrite8(1, &dc->guest_ack); +		dev_dbg(mdrv->dev, "%s %d guest_ack %d\n", +			__func__, __LINE__, ioread8(&dc->guest_ack)); +		ret = 0; +	} + +	return ret; +} + +#define REMOVE_DEVICES true + +static void mic_scan_devices(struct mic_driver *mdrv, bool remove) +{ +	s8 type; +	unsigned int i; +	struct mic_device_desc __iomem *d; +	struct mic_device_ctrl __iomem *dc; +	struct device *dev; +	int ret; + +	for (i = sizeof(struct mic_bootparam); i < MIC_DP_SIZE; +		i += mic_total_desc_size(d)) { +		d = mdrv->dp + i; +		dc = (void __iomem *)d + mic_aligned_desc_size(d); +		/* +		 * This read barrier is paired with the corresponding write +		 * barrier on the host which is inserted before adding or +		 * removing a virtio device descriptor, by updating the type. +		 */ +		rmb(); +		type = ioread8(&d->type); + +		/* end of list */ +		if (type == 0) +			break; + +		if (type == -1) +			continue; + +		/* device already exists */ +		dev = device_find_child(mdrv->dev, (void __force *)d, +					mic_match_desc); +		if (dev) { +			if (remove) +				iowrite8(MIC_VIRTIO_PARAM_DEV_REMOVE, +					 &dc->config_change); +			put_device(dev); +			mic_handle_config_change(d, i, mdrv); +			ret = mic_remove_device(d, i, mdrv); +			if (!ret && !remove) +				iowrite8(-1, &d->type); +			if (remove) { +				iowrite8(0, &dc->config_change); +				iowrite8(0, &dc->guest_ack); +			} +			continue; +		} + +		/* new device */ +		dev_dbg(mdrv->dev, "%s %d Adding new virtio device %p\n", +			__func__, __LINE__, d); +		if (!remove) +			mic_add_device(d, i, mdrv); +	} +} + +/* + * mic_hotplug_device tries to find changes in the device page. + */ +static void mic_hotplug_devices(struct work_struct *work) +{ +	struct mic_driver *mdrv = container_of(work, +		struct mic_driver, hotplug_work); + +	mic_scan_devices(mdrv, !REMOVE_DEVICES); +} + +/* + * Interrupt handler for hot plug/config changes etc. + */ +static irqreturn_t +mic_extint_handler(int irq, void *data) +{ +	struct mic_driver *mdrv = (struct mic_driver *)data; + +	dev_dbg(mdrv->dev, "%s %d hotplug work\n", +		__func__, __LINE__); +	mic_ack_interrupt(&mdrv->mdev); +	schedule_work(&mdrv->hotplug_work); +	return IRQ_HANDLED; +} + +/* + * Init function for virtio + */ +int mic_devices_init(struct mic_driver *mdrv) +{ +	int rc; +	struct mic_bootparam __iomem *bootparam; +	int config_db; + +	INIT_WORK(&mdrv->hotplug_work, mic_hotplug_devices); +	mic_scan_devices(mdrv, !REMOVE_DEVICES); + +	config_db = mic_next_card_db(); +	virtio_config_cookie = mic_request_card_irq(mic_extint_handler, +			"virtio_config_intr", mdrv, config_db); +	if (IS_ERR(virtio_config_cookie)) { +		rc = PTR_ERR(virtio_config_cookie); +		goto exit; +	} + +	bootparam = mdrv->dp; +	iowrite8(config_db, &bootparam->h2c_config_db); +	return 0; +exit: +	return rc; +} + +/* + * Uninit function for virtio + */ +void mic_devices_uninit(struct mic_driver *mdrv) +{ +	struct mic_bootparam __iomem *bootparam = mdrv->dp; +	iowrite8(-1, &bootparam->h2c_config_db); +	mic_free_card_irq(virtio_config_cookie, mdrv); +	flush_work(&mdrv->hotplug_work); +	mic_scan_devices(mdrv, REMOVE_DEVICES); +} diff --git a/drivers/misc/mic/card/mic_virtio.h b/drivers/misc/mic/card/mic_virtio.h new file mode 100644 index 00000000000..d0407ba53bb --- /dev/null +++ b/drivers/misc/mic/card/mic_virtio.h @@ -0,0 +1,76 @@ +/* + * Intel MIC Platform Software Stack (MPSS) + * + * Copyright(c) 2013 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2, as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * The full GNU General Public License is included in this distribution in + * the file called "COPYING". + * + * Disclaimer: The codes contained in these modules may be specific to + * the Intel Software Development Platform codenamed: Knights Ferry, and + * the Intel product codenamed: Knights Corner, and are not backward + * compatible with other Intel products. Additionally, Intel will NOT + * support the codes or instruction set in future products. + * + * Intel MIC Card driver. + * + */ +#ifndef __MIC_CARD_VIRTIO_H +#define __MIC_CARD_VIRTIO_H + +#include <linux/mic_common.h> +#include "mic_device.h" + +/* + * 64 bit I/O access + */ +#ifndef ioread64 +#define ioread64 readq +#endif +#ifndef iowrite64 +#define iowrite64 writeq +#endif + +static inline unsigned mic_desc_size(struct mic_device_desc __iomem *desc) +{ +	return sizeof(*desc) +		+ ioread8(&desc->num_vq) * sizeof(struct mic_vqconfig) +		+ ioread8(&desc->feature_len) * 2 +		+ ioread8(&desc->config_len); +} + +static inline struct mic_vqconfig __iomem * +mic_vq_config(struct mic_device_desc __iomem *desc) +{ +	return (struct mic_vqconfig __iomem *)(desc + 1); +} + +static inline __u8 __iomem * +mic_vq_features(struct mic_device_desc __iomem *desc) +{ +	return (__u8 __iomem *)(mic_vq_config(desc) + ioread8(&desc->num_vq)); +} + +static inline __u8 __iomem * +mic_vq_configspace(struct mic_device_desc __iomem *desc) +{ +	return mic_vq_features(desc) + ioread8(&desc->feature_len) * 2; +} +static inline unsigned mic_total_desc_size(struct mic_device_desc __iomem *desc) +{ +	return mic_aligned_desc_size(desc) + sizeof(struct mic_device_ctrl); +} + +int mic_devices_init(struct mic_driver *mdrv); +void mic_devices_uninit(struct mic_driver *mdrv); + +#endif diff --git a/drivers/misc/mic/card/mic_x100.c b/drivers/misc/mic/card/mic_x100.c new file mode 100644 index 00000000000..2868945c9a4 --- /dev/null +++ b/drivers/misc/mic/card/mic_x100.c @@ -0,0 +1,256 @@ +/* + * Intel MIC Platform Software Stack (MPSS) + * + * Copyright(c) 2013 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2, as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * The full GNU General Public License is included in this distribution in + * the file called "COPYING". + * + * Disclaimer: The codes contained in these modules may be specific to + * the Intel Software Development Platform codenamed: Knights Ferry, and + * the Intel product codenamed: Knights Corner, and are not backward + * compatible with other Intel products. Additionally, Intel will NOT + * support the codes or instruction set in future products. + * + * Intel MIC Card driver. + * + */ +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/platform_device.h> + +#include "../common/mic_dev.h" +#include "mic_device.h" +#include "mic_x100.h" + +static const char mic_driver_name[] = "mic"; + +static struct mic_driver g_drv; + +/** + * mic_read_spad - read from the scratchpad register + * @mdev: pointer to mic_device instance + * @idx: index to scratchpad register, 0 based + * + * This function allows reading of the 32bit scratchpad register. + * + * RETURNS: An appropriate -ERRNO error value on error, or zero for success. + */ +u32 mic_read_spad(struct mic_device *mdev, unsigned int idx) +{ +	return mic_mmio_read(&mdev->mmio, +		MIC_X100_SBOX_BASE_ADDRESS + +		MIC_X100_SBOX_SPAD0 + idx * 4); +} + +/** + * __mic_send_intr - Send interrupt to Host. + * @mdev: pointer to mic_device instance + * @doorbell: Doorbell number. + */ +void mic_send_intr(struct mic_device *mdev, int doorbell) +{ +	struct mic_mw *mw = &mdev->mmio; + +	if (doorbell > MIC_X100_MAX_DOORBELL_IDX) +		return; +	/* Ensure that the interrupt is ordered w.r.t previous stores. */ +	wmb(); +	mic_mmio_write(mw, MIC_X100_SBOX_SDBIC0_DBREQ_BIT, +		       MIC_X100_SBOX_BASE_ADDRESS + +		       (MIC_X100_SBOX_SDBIC0 + (4 * doorbell))); +} + +/** + * mic_ack_interrupt - Device specific interrupt handling. + * @mdev: pointer to mic_device instance + * + * Returns: bitmask of doorbell events triggered. + */ +u32 mic_ack_interrupt(struct mic_device *mdev) +{ +	return 0; +} + +static inline int mic_get_sbox_irq(int db) +{ +	return MIC_X100_IRQ_BASE + db; +} + +static inline int mic_get_rdmasr_irq(int index) +{ +	return  MIC_X100_RDMASR_IRQ_BASE + index; +} + +/** + * mic_hw_intr_init - Initialize h/w specific interrupt + * information. + * @mdrv: pointer to mic_driver + */ +void mic_hw_intr_init(struct mic_driver *mdrv) +{ +	mdrv->intr_info.num_intr = MIC_X100_NUM_SBOX_IRQ + +				MIC_X100_NUM_RDMASR_IRQ; +} + +/** + * mic_db_to_irq - Retrieve irq number corresponding to a doorbell. + * @mdrv: pointer to mic_driver + * @db: The doorbell obtained for which the irq is needed. Doorbell + * may correspond to an sbox doorbell or an rdmasr index. + * + * Returns the irq corresponding to the doorbell. + */ +int mic_db_to_irq(struct mic_driver *mdrv, int db) +{ +	int rdmasr_index; +	if (db < MIC_X100_NUM_SBOX_IRQ) { +		return mic_get_sbox_irq(db); +	} else { +		rdmasr_index = db - MIC_X100_NUM_SBOX_IRQ + +			MIC_X100_RDMASR_IRQ_BASE; +		return mic_get_rdmasr_irq(rdmasr_index); +	} +} + +/* + * mic_card_map - Allocate virtual address for a remote memory region. + * @mdev: pointer to mic_device instance. + * @addr: Remote DMA address. + * @size: Size of the region. + * + * Returns: Virtual address backing the remote memory region. + */ +void __iomem * +mic_card_map(struct mic_device *mdev, dma_addr_t addr, size_t size) +{ +	return ioremap(addr, size); +} + +/* + * mic_card_unmap - Unmap the virtual address for a remote memory region. + * @mdev: pointer to mic_device instance. + * @addr: Virtual address for remote memory region. + * + * Returns: None. + */ +void mic_card_unmap(struct mic_device *mdev, void __iomem *addr) +{ +	iounmap(addr); +} + +static int __init mic_probe(struct platform_device *pdev) +{ +	struct mic_driver *mdrv = &g_drv; +	struct mic_device *mdev = &mdrv->mdev; +	int rc = 0; + +	mdrv->dev = &pdev->dev; +	snprintf(mdrv->name, sizeof(mic_driver_name), mic_driver_name); + +	mdev->mmio.pa = MIC_X100_MMIO_BASE; +	mdev->mmio.len = MIC_X100_MMIO_LEN; +	mdev->mmio.va = ioremap(MIC_X100_MMIO_BASE, MIC_X100_MMIO_LEN); +	if (!mdev->mmio.va) { +		dev_err(&pdev->dev, "Cannot remap MMIO BAR\n"); +		rc = -EIO; +		goto done; +	} +	mic_hw_intr_init(mdrv); +	rc = mic_driver_init(mdrv); +	if (rc) { +		dev_err(&pdev->dev, "mic_driver_init failed rc %d\n", rc); +		goto iounmap; +	} +done: +	return rc; +iounmap: +	iounmap(mdev->mmio.va); +	return rc; +} + +static int mic_remove(struct platform_device *pdev) +{ +	struct mic_driver *mdrv = &g_drv; +	struct mic_device *mdev = &mdrv->mdev; + +	mic_driver_uninit(mdrv); +	iounmap(mdev->mmio.va); +	return 0; +} + +static void mic_platform_shutdown(struct platform_device *pdev) +{ +	mic_remove(pdev); +} + +static struct platform_device mic_platform_dev = { +	.name = mic_driver_name, +	.id   = 0, +	.num_resources = 0, +}; + +static struct platform_driver __refdata mic_platform_driver = { +	.probe = mic_probe, +	.remove = mic_remove, +	.shutdown = mic_platform_shutdown, +	.driver         = { +		.name   = mic_driver_name, +		.owner	= THIS_MODULE, +	}, +}; + +static int __init mic_init(void) +{ +	int ret; +	struct cpuinfo_x86 *c = &cpu_data(0); + +	if (!(c->x86 == 11 && c->x86_model == 1)) { +		ret = -ENODEV; +		pr_err("%s not running on X100 ret %d\n", __func__, ret); +		goto done; +	} + +	mic_init_card_debugfs(); +	ret = platform_device_register(&mic_platform_dev); +	if (ret) { +		pr_err("platform_device_register ret %d\n", ret); +		goto cleanup_debugfs; +	} +	ret = platform_driver_register(&mic_platform_driver); +	if (ret) { +		pr_err("platform_driver_register ret %d\n", ret); +		goto device_unregister; +	} +	return ret; + +device_unregister: +	platform_device_unregister(&mic_platform_dev); +cleanup_debugfs: +	mic_exit_card_debugfs(); +done: +	return ret; +} + +static void __exit mic_exit(void) +{ +	platform_driver_unregister(&mic_platform_driver); +	platform_device_unregister(&mic_platform_dev); +	mic_exit_card_debugfs(); +} + +module_init(mic_init); +module_exit(mic_exit); + +MODULE_AUTHOR("Intel Corporation"); +MODULE_DESCRIPTION("Intel(R) MIC X100 Card driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/misc/mic/card/mic_x100.h b/drivers/misc/mic/card/mic_x100.h new file mode 100644 index 00000000000..d66ea55639c --- /dev/null +++ b/drivers/misc/mic/card/mic_x100.h @@ -0,0 +1,48 @@ +/* + * Intel MIC Platform Software Stack (MPSS) + * + * Copyright(c) 2013 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2, as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * The full GNU General Public License is included in this distribution in + * the file called "COPYING". + * + * Disclaimer: The codes contained in these modules may be specific to + * the Intel Software Development Platform codenamed: Knights Ferry, and + * the Intel product codenamed: Knights Corner, and are not backward + * compatible with other Intel products. Additionally, Intel will NOT + * support the codes or instruction set in future products. + * + * Intel MIC Card driver. + * + */ +#ifndef _MIC_X100_CARD_H_ +#define _MIC_X100_CARD_H_ + +#define MIC_X100_MMIO_BASE 0x08007C0000ULL +#define MIC_X100_MMIO_LEN 0x00020000ULL +#define MIC_X100_SBOX_BASE_ADDRESS 0x00010000ULL + +#define MIC_X100_SBOX_SPAD0 0x0000AB20 +#define MIC_X100_SBOX_SDBIC0 0x0000CC90 +#define MIC_X100_SBOX_SDBIC0_DBREQ_BIT 0x80000000 +#define MIC_X100_SBOX_RDMASR0	0x0000B180 + +#define MIC_X100_MAX_DOORBELL_IDX 8 + +#define MIC_X100_NUM_SBOX_IRQ 8 +#define MIC_X100_NUM_RDMASR_IRQ 8 +#define MIC_X100_SBOX_IRQ_BASE 0 +#define MIC_X100_RDMASR_IRQ_BASE 17 + +#define MIC_X100_IRQ_BASE 26 + +#endif diff --git a/drivers/misc/mic/common/mic_dev.h b/drivers/misc/mic/common/mic_dev.h new file mode 100644 index 00000000000..92999c2bbf8 --- /dev/null +++ b/drivers/misc/mic/common/mic_dev.h @@ -0,0 +1,51 @@ +/* + * Intel MIC Platform Software Stack (MPSS) + * + * Copyright(c) 2013 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2, as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * The full GNU General Public License is included in this distribution in + * the file called "COPYING". + * + * Intel MIC driver. + * + */ +#ifndef __MIC_DEV_H__ +#define __MIC_DEV_H__ + +/** + * struct mic_mw - MIC memory window + * + * @pa: Base physical address. + * @va: Base ioremap'd virtual address. + * @len: Size of the memory window. + */ +struct mic_mw { +	phys_addr_t pa; +	void __iomem *va; +	resource_size_t len; +}; + +/* + * Scratch pad register offsets used by the host to communicate + * device page DMA address to the card. + */ +#define MIC_DPLO_SPAD 14 +#define MIC_DPHI_SPAD 15 + +/* + * These values are supposed to be in the config_change field of the + * device page when the host sends a config change interrupt to the card. + */ +#define MIC_VIRTIO_PARAM_DEV_REMOVE 0x1 +#define MIC_VIRTIO_PARAM_CONFIG_CHANGED 0x2 + +#endif diff --git a/drivers/misc/mic/host/Makefile b/drivers/misc/mic/host/Makefile new file mode 100644 index 00000000000..c2197f99939 --- /dev/null +++ b/drivers/misc/mic/host/Makefile @@ -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 diff --git a/drivers/misc/mic/host/mic_boot.c b/drivers/misc/mic/host/mic_boot.c new file mode 100644 index 00000000000..b75c6b5cc20 --- /dev/null +++ b/drivers/misc/mic/host/mic_boot.c @@ -0,0 +1,300 @@ +/* + * 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 "../common/mic_dev.h" +#include "mic_device.h" +#include "mic_smpt.h" +#include "mic_virtio.h" + +/** + * 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; +	} +	rc = mdev->ops->load_mic_fw(mdev, buf); +	if (rc) +		goto unlock_ret; +	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); +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); +		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); +} diff --git a/drivers/misc/mic/host/mic_debugfs.c b/drivers/misc/mic/host/mic_debugfs.c new file mode 100644 index 00000000000..028ba5d6fd1 --- /dev/null +++ b/drivers/misc/mic/host/mic_debugfs.c @@ -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); +} diff --git a/drivers/misc/mic/host/mic_device.h b/drivers/misc/mic/host/mic_device.h new file mode 100644 index 00000000000..0398c696d25 --- /dev/null +++ b/drivers/misc/mic/host/mic_device.h @@ -0,0 +1,207 @@ +/* + * 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 "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. + */ +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 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. + */ +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); +}; + +/** + * 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); +} + +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 diff --git a/drivers/misc/mic/host/mic_fops.c b/drivers/misc/mic/host/mic_fops.c new file mode 100644 index 00000000000..85776d7327f --- /dev/null +++ b/drivers/misc/mic/host/mic_fops.c @@ -0,0 +1,222 @@ +/* + * Intel MIC Platform Software Stack (MPSS) + * + * Copyright(c) 2013 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2, as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * The full GNU General Public License is included in this distribution in + * the file called "COPYING". + * + * Intel MIC Host driver. + * + */ +#include <linux/poll.h> +#include <linux/pci.h> + +#include <linux/mic_common.h> +#include "../common/mic_dev.h" +#include "mic_device.h" +#include "mic_fops.h" +#include "mic_virtio.h" + +int mic_open(struct inode *inode, struct file *f) +{ +	struct mic_vdev *mvdev; +	struct mic_device *mdev = container_of(inode->i_cdev, +		struct mic_device, cdev); + +	mvdev = kzalloc(sizeof(*mvdev), GFP_KERNEL); +	if (!mvdev) +		return -ENOMEM; + +	init_waitqueue_head(&mvdev->waitq); +	INIT_LIST_HEAD(&mvdev->list); +	mvdev->mdev = mdev; +	mvdev->virtio_id = -1; + +	f->private_data = mvdev; +	return 0; +} + +int mic_release(struct inode *inode, struct file *f) +{ +	struct mic_vdev *mvdev = (struct mic_vdev *)f->private_data; + +	if (-1 != mvdev->virtio_id) +		mic_virtio_del_device(mvdev); +	f->private_data = NULL; +	kfree(mvdev); +	return 0; +} + +long mic_ioctl(struct file *f, unsigned int cmd, unsigned long arg) +{ +	struct mic_vdev *mvdev = (struct mic_vdev *)f->private_data; +	void __user *argp = (void __user *)arg; +	int ret; + +	switch (cmd) { +	case MIC_VIRTIO_ADD_DEVICE: +	{ +		ret = mic_virtio_add_device(mvdev, argp); +		if (ret < 0) { +			dev_err(mic_dev(mvdev), +				"%s %d errno ret %d\n", +				__func__, __LINE__, ret); +			return ret; +		} +		break; +	} +	case MIC_VIRTIO_COPY_DESC: +	{ +		struct mic_copy_desc copy; + +		ret = mic_vdev_inited(mvdev); +		if (ret) +			return ret; + +		if (copy_from_user(©, argp, sizeof(copy))) +			return -EFAULT; + +		dev_dbg(mic_dev(mvdev), +			"%s %d === iovcnt 0x%x vr_idx 0x%x update_used %d\n", +			__func__, __LINE__, copy.iovcnt, copy.vr_idx, +			copy.update_used); + +		ret = mic_virtio_copy_desc(mvdev, ©); +		if (ret < 0) { +			dev_err(mic_dev(mvdev), +				"%s %d errno ret %d\n", +				__func__, __LINE__, ret); +			return ret; +		} +		if (copy_to_user( +			&((struct mic_copy_desc __user *)argp)->out_len, +			©.out_len, sizeof(copy.out_len))) { +			dev_err(mic_dev(mvdev), "%s %d errno ret %d\n", +				__func__, __LINE__, -EFAULT); +			return -EFAULT; +		} +		break; +	} +	case MIC_VIRTIO_CONFIG_CHANGE: +	{ +		ret = mic_vdev_inited(mvdev); +		if (ret) +			return ret; + +		ret = mic_virtio_config_change(mvdev, argp); +		if (ret < 0) { +			dev_err(mic_dev(mvdev), +				"%s %d errno ret %d\n", +				__func__, __LINE__, ret); +			return ret; +		} +		break; +	} +	default: +		return -ENOIOCTLCMD; +	}; +	return 0; +} + +/* + * We return POLLIN | POLLOUT from poll when new buffers are enqueued, and + * not when previously enqueued buffers may be available. This means that + * in the card->host (TX) path, when userspace is unblocked by poll it + * must drain all available descriptors or it can stall. + */ +unsigned int mic_poll(struct file *f, poll_table *wait) +{ +	struct mic_vdev *mvdev = (struct mic_vdev *)f->private_data; +	int mask = 0; + +	poll_wait(f, &mvdev->waitq, wait); + +	if (mic_vdev_inited(mvdev)) { +		mask = POLLERR; +	} else if (mvdev->poll_wake) { +		mvdev->poll_wake = 0; +		mask = POLLIN | POLLOUT; +	} + +	return mask; +} + +static inline int +mic_query_offset(struct mic_vdev *mvdev, unsigned long offset, +		 unsigned long *size, unsigned long *pa) +{ +	struct mic_device *mdev = mvdev->mdev; +	unsigned long start = MIC_DP_SIZE; +	int i; + +	/* +	 * MMAP interface is as follows: +	 * offset				region +	 * 0x0					virtio device_page +	 * 0x1000				first vring +	 * 0x1000 + size of 1st vring		second vring +	 * .... +	 */ +	if (!offset) { +		*pa = virt_to_phys(mdev->dp); +		*size = MIC_DP_SIZE; +		return 0; +	} + +	for (i = 0; i < mvdev->dd->num_vq; i++) { +		struct mic_vringh *mvr = &mvdev->mvr[i]; +		if (offset == start) { +			*pa = virt_to_phys(mvr->vring.va); +			*size = mvr->vring.len; +			return 0; +		} +		start += mvr->vring.len; +	} +	return -1; +} + +/* + * Maps the device page and virtio rings to user space for readonly access. + */ +int +mic_mmap(struct file *f, struct vm_area_struct *vma) +{ +	struct mic_vdev *mvdev = (struct mic_vdev *)f->private_data; +	unsigned long offset = vma->vm_pgoff << PAGE_SHIFT; +	unsigned long pa, size = vma->vm_end - vma->vm_start, size_rem = size; +	int i, err; + +	err = mic_vdev_inited(mvdev); +	if (err) +		return err; + +	if (vma->vm_flags & VM_WRITE) +		return -EACCES; + +	while (size_rem) { +		i = mic_query_offset(mvdev, offset, &size, &pa); +		if (i < 0) +			return -EINVAL; +		err = remap_pfn_range(vma, vma->vm_start + offset, +			pa >> PAGE_SHIFT, size, vma->vm_page_prot); +		if (err) +			return err; +		dev_dbg(mic_dev(mvdev), +			"%s %d type %d size 0x%lx off 0x%lx pa 0x%lx vma 0x%lx\n", +			__func__, __LINE__, mvdev->virtio_id, size, offset, +			pa, vma->vm_start + offset); +		size_rem -= size; +		offset += size; +	} +	return 0; +} diff --git a/drivers/misc/mic/host/mic_fops.h b/drivers/misc/mic/host/mic_fops.h new file mode 100644 index 00000000000..dc3893dff66 --- /dev/null +++ b/drivers/misc/mic/host/mic_fops.h @@ -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 diff --git a/drivers/misc/mic/host/mic_intr.c b/drivers/misc/mic/host/mic_intr.c new file mode 100644 index 00000000000..dbc5afde139 --- /dev/null +++ b/drivers/misc/mic/host/mic_intr.c @@ -0,0 +1,630 @@ +/* + * Intel MIC Platform Software Stack (MPSS) + * + * Copyright(c) 2013 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2, as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * The full GNU General Public License is included in this distribution in + * the file called "COPYING". + * + * Intel MIC Host driver. + * + */ +#include <linux/pci.h> +#include <linux/interrupt.h> + +#include "../common/mic_dev.h" +#include "mic_device.h" + +/* + * mic_invoke_callback - Invoke callback functions registered for + * the corresponding source id. + * + * @mdev: pointer to the mic_device instance + * @idx: The interrupt source id. + * + * Returns none. + */ +static inline void mic_invoke_callback(struct mic_device *mdev, int idx) +{ +	struct mic_intr_cb *intr_cb; +	struct pci_dev *pdev = container_of(mdev->sdev->parent, +		struct pci_dev, dev); + +	spin_lock(&mdev->irq_info.mic_intr_lock); +	list_for_each_entry(intr_cb, &mdev->irq_info.cb_list[idx], list) +		if (intr_cb->func) +			intr_cb->func(pdev->irq, intr_cb->data); +	spin_unlock(&mdev->irq_info.mic_intr_lock); +} + +/** + * 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 *info = mdev->intr_info; +	u32 mask; +	int i; + +	mask = mdev->ops->ack_interrupt(mdev); +	if (!mask) +		return IRQ_NONE; + +	for (i = info->intr_start_idx[MIC_INTR_DB]; +			i < info->intr_len[MIC_INTR_DB]; i++) +		if (mask & BIT(i)) +			mic_invoke_callback(mdev, i); + +	return IRQ_HANDLED; +} + +/* 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. + * @func: The function to be called when the source id receives + * the interrupt. + * @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, irqreturn_t (*func) (int irq, void *dev), +			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->func = func; +	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_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); + +	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; + +	for (i = 0;  i < MIC_NUM_OFFSETS; i++) { +		spin_lock_irqsave(&mdev->irq_info.mic_intr_lock, flags); +		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); +				return i; +			} +		} +		spin_unlock_irqrestore(&mdev->irq_info.mic_intr_lock, flags); +	} +	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); +	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; + +	for (i = 0; i < MIC_NUM_OFFSETS; i++) { +		spin_lock_irqsave(&mdev->irq_info.mic_intr_lock, flags); + +		if (list_empty(&mdev->irq_info.cb_list[i])) { +			spin_unlock_irqrestore(&mdev->irq_info.mic_intr_lock, +					       flags); +			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); +	} +	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_irq(pdev->irq, mic_interrupt, 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_irq(pdev->irq, mic_interrupt, +		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_irq - request an irq. mic_mutex needs + * to be held before calling this function. + * + * @mdev: pointer to mic_device instance + * @func: The callback function that handles the interrupt. + * The function needs to call ack_interrupts + * (mdev->ops->ack_interrupt(mdev)) when handling the interrupts. + * @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_irq(struct mic_device *mdev, +	irqreturn_t (*func)(int irq, void *dev), +	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_irq(msix->vector, func, 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, func, 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_irq + * @data: private data specified by the calling function during the + * mic_request_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); +		} +	} +} diff --git a/drivers/misc/mic/host/mic_intr.h b/drivers/misc/mic/host/mic_intr.h new file mode 100644 index 00000000000..6091aa97e11 --- /dev/null +++ b/drivers/misc/mic/host/mic_intr.h @@ -0,0 +1,137 @@ +/* + * 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_ + +/* + * The minimum number of msix vectors required for normal operation. + * 3 for virtio network, console and block devices. + * 1 for card shutdown notifications. + */ +#define MIC_MIN_MSIX 4 +#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. + * @cb_list: Array of callback lists one for each source. + */ +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; +	struct list_head *cb_list; +}; + +/** + * struct mic_intr_cb - Interrupt callback structure. + * + * @func: The callback function + * @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 { +	irqreturn_t (*func) (int irq, void *data); +	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_irq(struct mic_device *mdev, +	irqreturn_t (*func)(int irq, void *data), +	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 diff --git a/drivers/misc/mic/host/mic_main.c b/drivers/misc/mic/host/mic_main.c new file mode 100644 index 00000000000..c04a021e20c --- /dev/null +++ b/drivers/misc/mic/host/mic_main.c @@ -0,0 +1,536 @@ +/* + * 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 DEFINE_PCI_DEVICE_TABLE(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_irq(mdev, mic_shutdown_db, +		"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"); diff --git a/drivers/misc/mic/host/mic_smpt.c b/drivers/misc/mic/host/mic_smpt.c new file mode 100644 index 00000000000..fae474c4899 --- /dev/null +++ b/drivers/misc/mic/host/mic_smpt.c @@ -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); +	} +} diff --git a/drivers/misc/mic/host/mic_smpt.h b/drivers/misc/mic/host/mic_smpt.h new file mode 100644 index 00000000000..51970abfe7d --- /dev/null +++ b/drivers/misc/mic/host/mic_smpt.h @@ -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 diff --git a/drivers/misc/mic/host/mic_sysfs.c b/drivers/misc/mic/host/mic_sysfs.c new file mode 100644 index 00000000000..6dd864e4a61 --- /dev/null +++ b/drivers/misc/mic/host/mic_sysfs.c @@ -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; +} diff --git a/drivers/misc/mic/host/mic_virtio.c b/drivers/misc/mic/host/mic_virtio.c new file mode 100644 index 00000000000..7e1ef0ebbb8 --- /dev/null +++ b/drivers/misc/mic/host/mic_virtio.c @@ -0,0 +1,701 @@ +/* + * 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/mic_common.h> +#include "../common/mic_dev.h" +#include "mic_device.h" +#include "mic_smpt.h" +#include "mic_virtio.h" + +/* + * Initiates the copies across the PCIe bus from card memory to + * a user space buffer. + */ +static int mic_virtio_copy_to_user(struct mic_vdev *mvdev, +		void __user *ubuf, size_t len, u64 addr) +{ +	int err; +	void __iomem *dbuf = mvdev->mdev->aper.va + addr; +	/* +	 * We are copying from IO below an should ideally use something +	 * like copy_to_user_fromio(..) if it existed. +	 */ +	if (copy_to_user(ubuf, (void __force *)dbuf, len)) { +		err = -EFAULT; +		dev_err(mic_dev(mvdev), "%s %d err %d\n", +			__func__, __LINE__, err); +		goto err; +	} +	mvdev->in_bytes += len; +	err = 0; +err: +	return err; +} + +/* + * Initiates copies across the PCIe bus from a user space + * buffer to card memory. + */ +static int mic_virtio_copy_from_user(struct mic_vdev *mvdev, +		void __user *ubuf, size_t len, u64 addr) +{ +	int err; +	void __iomem *dbuf = mvdev->mdev->aper.va + addr; +	/* +	 * 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; +		dev_err(mic_dev(mvdev), "%s %d err %d\n", +			__func__, __LINE__, err); +		goto err; +	} +	mvdev->out_bytes += len; +	err = 0; +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, 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); +		else +			ret = mic_virtio_copy_from_user(mvdev, +				ubuf, partlen, +				(u64)iov->iov[iov->i].iov_base); +		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, &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, &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); +	} + +	snprintf(irqname, sizeof(irqname), "mic%dvirtio%d", mdev->id, +		 mvdev->virtio_id); +	mvdev->virtio_db = mic_next_db(mdev); +	mvdev->virtio_cookie = mic_request_irq(mdev, mic_virtio_intr_handler, +			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]; +		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); +} diff --git a/drivers/misc/mic/host/mic_virtio.h b/drivers/misc/mic/host/mic_virtio.h new file mode 100644 index 00000000000..184f3c84805 --- /dev/null +++ b/drivers/misc/mic/host/mic_virtio.h @@ -0,0 +1,138 @@ +/* + * 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. + * @head: The VRINGH head index address passed to vringh_getdesc_kern(..). + * @vr_mutex: Mutex for synchronizing access to the VRING. + * @mvdev: Back pointer to MIC virtio device for vringh_notify(..). + */ +struct mic_vringh { +	struct mic_vring vring; +	struct vringh vrh; +	struct vringh_kiov riov; +	struct vringh_kiov wiov; +	u16 head; +	struct mutex vr_mutex; +	struct mic_vdev *mvdev; +}; + +/** + * 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. + * @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; +	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 diff --git a/drivers/misc/mic/host/mic_x100.c b/drivers/misc/mic/host/mic_x100.c new file mode 100644 index 00000000000..5562fdd3ef4 --- /dev/null +++ b/drivers/misc/mic/host/mic_x100.c @@ -0,0 +1,574 @@ +/* + * Intel MIC Platform Software Stack (MPSS) + * + * Copyright(c) 2013 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2, as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * The full GNU General Public License is included in this distribution in + * the file called "COPYING". + * + * Intel MIC Host driver. + * + */ +#include <linux/fs.h> +#include <linux/pci.h> +#include <linux/sched.h> +#include <linux/firmware.h> +#include <linux/delay.h> + +#include "../common/mic_dev.h" +#include "mic_device.h" +#include "mic_x100.h" +#include "mic_smpt.h" + +/** + * mic_x100_write_spad - write to the scratchpad register + * @mdev: pointer to mic_device instance + * @idx: index to the scratchpad register, 0 based + * @val: the data value to put into the register + * + * This function allows writing of a 32bit value to the indexed scratchpad + * register. + * + * RETURNS: none. + */ +static void +mic_x100_write_spad(struct mic_device *mdev, unsigned int idx, u32 val) +{ +	dev_dbg(mdev->sdev->parent, "Writing 0x%x to scratch pad index %d\n", +		val, idx); +	mic_mmio_write(&mdev->mmio, val, +		       MIC_X100_SBOX_BASE_ADDRESS + +		       MIC_X100_SBOX_SPAD0 + idx * 4); +} + +/** + * mic_x100_read_spad - read from the scratchpad register + * @mdev: pointer to mic_device instance + * @idx: index to scratchpad register, 0 based + * + * This function allows reading of the 32bit scratchpad register. + * + * RETURNS: An appropriate -ERRNO error value on error, or zero for success. + */ +static u32 +mic_x100_read_spad(struct mic_device *mdev, unsigned int idx) +{ +	u32 val = mic_mmio_read(&mdev->mmio, +		MIC_X100_SBOX_BASE_ADDRESS + +		MIC_X100_SBOX_SPAD0 + idx * 4); + +	dev_dbg(mdev->sdev->parent, +		"Reading 0x%x from scratch pad index %d\n", val, idx); +	return val; +} + +/** + * mic_x100_enable_interrupts - Enable interrupts. + * @mdev: pointer to mic_device instance + */ +static void mic_x100_enable_interrupts(struct mic_device *mdev) +{ +	u32 reg; +	struct mic_mw *mw = &mdev->mmio; +	u32 sice0 = MIC_X100_SBOX_BASE_ADDRESS + MIC_X100_SBOX_SICE0; +	u32 siac0 = MIC_X100_SBOX_BASE_ADDRESS + MIC_X100_SBOX_SIAC0; + +	reg = mic_mmio_read(mw, sice0); +	reg |= MIC_X100_SBOX_DBR_BITS(0xf) | MIC_X100_SBOX_DMA_BITS(0xff); +	mic_mmio_write(mw, reg, sice0); + +	/* +	 * Enable auto-clear when enabling interrupts. Applicable only for +	 * MSI-x. Legacy and MSI mode cannot have auto-clear enabled. +	 */ +	if (mdev->irq_info.num_vectors > 1) { +		reg = mic_mmio_read(mw, siac0); +		reg |= MIC_X100_SBOX_DBR_BITS(0xf) | +			MIC_X100_SBOX_DMA_BITS(0xff); +		mic_mmio_write(mw, reg, siac0); +	} +} + +/** + * mic_x100_disable_interrupts - Disable interrupts. + * @mdev: pointer to mic_device instance + */ +static void mic_x100_disable_interrupts(struct mic_device *mdev) +{ +	u32 reg; +	struct mic_mw *mw = &mdev->mmio; +	u32 sice0 = MIC_X100_SBOX_BASE_ADDRESS + MIC_X100_SBOX_SICE0; +	u32 siac0 = MIC_X100_SBOX_BASE_ADDRESS + MIC_X100_SBOX_SIAC0; +	u32 sicc0 = MIC_X100_SBOX_BASE_ADDRESS + MIC_X100_SBOX_SICC0; + +	reg = mic_mmio_read(mw, sice0); +	mic_mmio_write(mw, reg, sicc0); + +	if (mdev->irq_info.num_vectors > 1) { +		reg = mic_mmio_read(mw, siac0); +		reg &= ~(MIC_X100_SBOX_DBR_BITS(0xf) | +			MIC_X100_SBOX_DMA_BITS(0xff)); +		mic_mmio_write(mw, reg, siac0); +	} +} + +/** + * mic_x100_send_sbox_intr - Send an MIC_X100_SBOX interrupt to MIC. + * @mdev: pointer to mic_device instance + */ +static void mic_x100_send_sbox_intr(struct mic_device *mdev, +			int doorbell) +{ +	struct mic_mw *mw = &mdev->mmio; +	u64 apic_icr_offset = MIC_X100_SBOX_APICICR0 + doorbell * 8; +	u32 apicicr_low = mic_mmio_read(mw, MIC_X100_SBOX_BASE_ADDRESS + +					apic_icr_offset); + +	/* for MIC we need to make sure we "hit" the send_icr bit (13) */ +	apicicr_low = (apicicr_low | (1 << 13)); + +	/* Ensure that the interrupt is ordered w.r.t. previous stores. */ +	wmb(); +	mic_mmio_write(mw, apicicr_low, +		       MIC_X100_SBOX_BASE_ADDRESS + apic_icr_offset); +} + +/** + * mic_x100_send_rdmasr_intr - Send an RDMASR interrupt to MIC. + * @mdev: pointer to mic_device instance + */ +static void mic_x100_send_rdmasr_intr(struct mic_device *mdev, +			int doorbell) +{ +	int rdmasr_offset = MIC_X100_SBOX_RDMASR0 + (doorbell << 2); +	/* Ensure that the interrupt is ordered w.r.t. previous stores. */ +	wmb(); +	mic_mmio_write(&mdev->mmio, 0, +		       MIC_X100_SBOX_BASE_ADDRESS + rdmasr_offset); +} + +/** + * __mic_x100_send_intr - Send interrupt to MIC. + * @mdev: pointer to mic_device instance + * @doorbell: doorbell number. + */ +static void mic_x100_send_intr(struct mic_device *mdev, int doorbell) +{ +	int rdmasr_db; +	if (doorbell < MIC_X100_NUM_SBOX_IRQ) { +		mic_x100_send_sbox_intr(mdev, doorbell); +	} else { +		rdmasr_db = doorbell - MIC_X100_NUM_SBOX_IRQ + +			MIC_X100_RDMASR_IRQ_BASE; +		mic_x100_send_rdmasr_intr(mdev, rdmasr_db); +	} +} + +/** + * mic_x100_ack_interrupt - Read the interrupt sources register and + * clear it. This function will be called in the MSI/INTx case. + * @mdev: Pointer to mic_device instance. + * + * Returns: bitmask of interrupt sources triggered. + */ +static u32 mic_x100_ack_interrupt(struct mic_device *mdev) +{ +	u32 sicr0 = MIC_X100_SBOX_BASE_ADDRESS + MIC_X100_SBOX_SICR0; +	u32 reg = mic_mmio_read(&mdev->mmio, sicr0); +	mic_mmio_write(&mdev->mmio, reg, sicr0); +	return reg; +} + +/** + * mic_x100_intr_workarounds - These hardware specific workarounds are + * to be invoked everytime an interrupt is handled. + * @mdev: Pointer to mic_device instance. + * + * Returns: none + */ +static void mic_x100_intr_workarounds(struct mic_device *mdev) +{ +	struct mic_mw *mw = &mdev->mmio; + +	/* Clear pending bit array. */ +	if (MIC_A0_STEP == mdev->stepping) +		mic_mmio_write(mw, 1, MIC_X100_SBOX_BASE_ADDRESS + +			MIC_X100_SBOX_MSIXPBACR); + +	if (mdev->stepping >= MIC_B0_STEP) +		mdev->intr_ops->enable_interrupts(mdev); +} + +/** + * mic_x100_hw_intr_init - Initialize h/w specific interrupt + * information. + * @mdev: pointer to mic_device instance + */ +static void mic_x100_hw_intr_init(struct mic_device *mdev) +{ +	mdev->intr_info = (struct mic_intr_info *)mic_x100_intr_init; +} + +/** + * mic_x100_read_msi_to_src_map - read from the MSI mapping registers + * @mdev: pointer to mic_device instance + * @idx: index to the mapping register, 0 based + * + * This function allows reading of the 32bit MSI mapping register. + * + * RETURNS: The value in the register. + */ +static u32 +mic_x100_read_msi_to_src_map(struct mic_device *mdev, int idx) +{ +	return mic_mmio_read(&mdev->mmio, +		MIC_X100_SBOX_BASE_ADDRESS + +		MIC_X100_SBOX_MXAR0 + idx * 4); +} + +/** + * mic_x100_program_msi_to_src_map - program the MSI mapping registers + * @mdev: pointer to mic_device instance + * @idx: index to the mapping register, 0 based + * @offset: The bit offset in the register that needs to be updated. + * @set: boolean specifying if the bit in the specified offset needs + * to be set or cleared. + * + * RETURNS: None. + */ +static void +mic_x100_program_msi_to_src_map(struct mic_device *mdev, +				int idx, int offset, bool set) +{ +	unsigned long reg; +	struct mic_mw *mw = &mdev->mmio; +	u32 mxar = MIC_X100_SBOX_BASE_ADDRESS + +		MIC_X100_SBOX_MXAR0 + idx * 4; + +	reg = mic_mmio_read(mw, mxar); +	if (set) +		__set_bit(offset, ®); +	else +		__clear_bit(offset, ®); +	mic_mmio_write(mw, reg, mxar); +} + +/* + * mic_x100_reset_fw_ready - Reset Firmware ready status field. + * @mdev: pointer to mic_device instance + */ +static void mic_x100_reset_fw_ready(struct mic_device *mdev) +{ +	mdev->ops->write_spad(mdev, MIC_X100_DOWNLOAD_INFO, 0); +} + +/* + * mic_x100_is_fw_ready - Check if firmware is ready. + * @mdev: pointer to mic_device instance + */ +static bool mic_x100_is_fw_ready(struct mic_device *mdev) +{ +	u32 scratch2 = mdev->ops->read_spad(mdev, MIC_X100_DOWNLOAD_INFO); +	return MIC_X100_SPAD2_DOWNLOAD_STATUS(scratch2) ? true : false; +} + +/** + * mic_x100_get_apic_id - Get bootstrap APIC ID. + * @mdev: pointer to mic_device instance + */ +static u32 mic_x100_get_apic_id(struct mic_device *mdev) +{ +	u32 scratch2 = 0; + +	scratch2 = mdev->ops->read_spad(mdev, MIC_X100_DOWNLOAD_INFO); +	return MIC_X100_SPAD2_APIC_ID(scratch2); +} + +/** + * mic_x100_send_firmware_intr - Send an interrupt to the firmware on MIC. + * @mdev: pointer to mic_device instance + */ +static void mic_x100_send_firmware_intr(struct mic_device *mdev) +{ +	u32 apicicr_low; +	u64 apic_icr_offset = MIC_X100_SBOX_APICICR7; +	int vector = MIC_X100_BSP_INTERRUPT_VECTOR; +	struct mic_mw *mw = &mdev->mmio; + +	/* +	 * For MIC we need to make sure we "hit" +	 * the send_icr bit (13). +	 */ +	apicicr_low = (vector | (1 << 13)); + +	mic_mmio_write(mw, mic_x100_get_apic_id(mdev), +		       MIC_X100_SBOX_BASE_ADDRESS + apic_icr_offset + 4); + +	/* Ensure that the interrupt is ordered w.r.t. previous stores. */ +	wmb(); +	mic_mmio_write(mw, apicicr_low, +		       MIC_X100_SBOX_BASE_ADDRESS + apic_icr_offset); +} + +/** + * mic_x100_hw_reset - Reset the MIC device. + * @mdev: pointer to mic_device instance + */ +static void mic_x100_hw_reset(struct mic_device *mdev) +{ +	u32 reset_reg; +	u32 rgcr = MIC_X100_SBOX_BASE_ADDRESS + MIC_X100_SBOX_RGCR; +	struct mic_mw *mw = &mdev->mmio; + +	/* Ensure that the reset is ordered w.r.t. previous loads and stores */ +	mb(); +	/* Trigger reset */ +	reset_reg = mic_mmio_read(mw, rgcr); +	reset_reg |= 0x1; +	mic_mmio_write(mw, reset_reg, rgcr); +	/* +	 * It seems we really want to delay at least 1 second +	 * after touching reset to prevent a lot of problems. +	 */ +	msleep(1000); +} + +/** + * mic_x100_load_command_line - Load command line to MIC. + * @mdev: pointer to mic_device instance + * @fw: the firmware image + * + * RETURNS: An appropriate -ERRNO error value on error, or zero for success. + */ +static int +mic_x100_load_command_line(struct mic_device *mdev, const struct firmware *fw) +{ +	u32 len = 0; +	u32 boot_mem; +	char *buf; +	void __iomem *cmd_line_va = mdev->aper.va + mdev->bootaddr + fw->size; +#define CMDLINE_SIZE 2048 + +	boot_mem = mdev->aper.len >> 20; +	buf = kzalloc(CMDLINE_SIZE, GFP_KERNEL); +	if (!buf) { +		dev_err(mdev->sdev->parent, +			"%s %d allocation failed\n", __func__, __LINE__); +		return -ENOMEM; +	} +	len += snprintf(buf, CMDLINE_SIZE - len, +		" mem=%dM", boot_mem); +	if (mdev->cmdline) +		snprintf(buf + len, CMDLINE_SIZE - len, " %s", mdev->cmdline); +	memcpy_toio(cmd_line_va, buf, strlen(buf) + 1); +	kfree(buf); +	return 0; +} + +/** + * mic_x100_load_ramdisk - Load ramdisk to MIC. + * @mdev: pointer to mic_device instance + * + * RETURNS: An appropriate -ERRNO error value on error, or zero for success. + */ +static int +mic_x100_load_ramdisk(struct mic_device *mdev) +{ +	const struct firmware *fw; +	int rc; +	struct boot_params __iomem *bp = mdev->aper.va + mdev->bootaddr; + +	rc = request_firmware(&fw, +			mdev->ramdisk, mdev->sdev->parent); +	if (rc < 0) { +		dev_err(mdev->sdev->parent, +			"ramdisk request_firmware failed: %d %s\n", +			rc, mdev->ramdisk); +		goto error; +	} +	/* +	 * Typically the bootaddr for card OS is 64M +	 * so copy over the ramdisk @ 128M. +	 */ +	memcpy_toio(mdev->aper.va + (mdev->bootaddr << 1), fw->data, fw->size); +	iowrite32(mdev->bootaddr << 1, &bp->hdr.ramdisk_image); +	iowrite32(fw->size, &bp->hdr.ramdisk_size); +	release_firmware(fw); +error: +	return rc; +} + +/** + * mic_x100_get_boot_addr - Get MIC boot address. + * @mdev: pointer to mic_device instance + * + * This function is called during firmware load to determine + * the address at which the OS should be downloaded in card + * memory i.e. GDDR. + * RETURNS: An appropriate -ERRNO error value on error, or zero for success. + */ +static int +mic_x100_get_boot_addr(struct mic_device *mdev) +{ +	u32 scratch2, boot_addr; +	int rc = 0; + +	scratch2 = mdev->ops->read_spad(mdev, MIC_X100_DOWNLOAD_INFO); +	boot_addr = MIC_X100_SPAD2_DOWNLOAD_ADDR(scratch2); +	dev_dbg(mdev->sdev->parent, "%s %d boot_addr 0x%x\n", +		__func__, __LINE__, boot_addr); +	if (boot_addr > (1 << 31)) { +		dev_err(mdev->sdev->parent, +			"incorrect bootaddr 0x%x\n", +			boot_addr); +		rc = -EINVAL; +		goto error; +	} +	mdev->bootaddr = boot_addr; +error: +	return rc; +} + +/** + * mic_x100_load_firmware - Load firmware to MIC. + * @mdev: pointer to mic_device instance + * @buf: buffer containing boot string including firmware/ramdisk path. + * + * RETURNS: An appropriate -ERRNO error value on error, or zero for success. + */ +static int +mic_x100_load_firmware(struct mic_device *mdev, const char *buf) +{ +	int rc; +	const struct firmware *fw; + +	rc = mic_x100_get_boot_addr(mdev); +	if (rc) +		goto error; +	/* load OS */ +	rc = request_firmware(&fw, mdev->firmware, mdev->sdev->parent); +	if (rc < 0) { +		dev_err(mdev->sdev->parent, +			"ramdisk request_firmware failed: %d %s\n", +			rc, mdev->firmware); +		goto error; +	} +	if (mdev->bootaddr > mdev->aper.len - fw->size) { +		rc = -EINVAL; +		dev_err(mdev->sdev->parent, "%s %d rc %d bootaddr 0x%x\n", +			__func__, __LINE__, rc, mdev->bootaddr); +		release_firmware(fw); +		goto error; +	} +	memcpy_toio(mdev->aper.va + mdev->bootaddr, fw->data, fw->size); +	mdev->ops->write_spad(mdev, MIC_X100_FW_SIZE, fw->size); +	if (!strcmp(mdev->bootmode, "elf")) +		goto done; +	/* load command line */ +	rc = mic_x100_load_command_line(mdev, fw); +	if (rc) { +		dev_err(mdev->sdev->parent, "%s %d rc %d\n", +			__func__, __LINE__, rc); +		goto error; +	} +	release_firmware(fw); +	/* load ramdisk */ +	if (mdev->ramdisk) +		rc = mic_x100_load_ramdisk(mdev); +error: +	dev_dbg(mdev->sdev->parent, "%s %d rc %d\n", __func__, __LINE__, rc); +done: +	return rc; +} + +/** + * mic_x100_get_postcode - Get postcode status from firmware. + * @mdev: pointer to mic_device instance + * + * RETURNS: postcode. + */ +static u32 mic_x100_get_postcode(struct mic_device *mdev) +{ +	return mic_mmio_read(&mdev->mmio, MIC_X100_POSTCODE); +} + +/** + * mic_x100_smpt_set - Update an SMPT entry with a DMA address. + * @mdev: pointer to mic_device instance + * + * RETURNS: none. + */ +static void +mic_x100_smpt_set(struct mic_device *mdev, dma_addr_t dma_addr, u8 index) +{ +#define SNOOP_ON	(0 << 0) +#define SNOOP_OFF	(1 << 0) +/* + * Sbox Smpt Reg Bits: + * Bits	31:2	Host address + * Bits	1	RSVD + * Bits	0	No snoop + */ +#define BUILD_SMPT(NO_SNOOP, HOST_ADDR)  \ +	(u32)(((HOST_ADDR) << 2) | ((NO_SNOOP) & 0x01)) + +	uint32_t smpt_reg_val = BUILD_SMPT(SNOOP_ON, +			dma_addr >> mdev->smpt->info.page_shift); +	mic_mmio_write(&mdev->mmio, smpt_reg_val, +		       MIC_X100_SBOX_BASE_ADDRESS + +		       MIC_X100_SBOX_SMPT00 + (4 * index)); +} + +/** + * mic_x100_smpt_hw_init - Initialize SMPT X100 specific fields. + * @mdev: pointer to mic_device instance + * + * RETURNS: none. + */ +static void mic_x100_smpt_hw_init(struct mic_device *mdev) +{ +	struct mic_smpt_hw_info *info = &mdev->smpt->info; + +	info->num_reg = 32; +	info->page_shift = 34; +	info->page_size = (1ULL << info->page_shift); +	info->base = 0x8000000000ULL; +} + +struct mic_smpt_ops mic_x100_smpt_ops = { +	.init = mic_x100_smpt_hw_init, +	.set = mic_x100_smpt_set, +}; + +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, +}; + +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, +}; diff --git a/drivers/misc/mic/host/mic_x100.h b/drivers/misc/mic/host/mic_x100.h new file mode 100644 index 00000000000..8b7daa182e5 --- /dev/null +++ b/drivers/misc/mic/host/mic_x100.h @@ -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  | 
