1929 lines · 52.9 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0/* * Microsemi Switchtec(tm) PCIe Management Driver * Copyright (c) 2017, Microsemi Corporation */ #include <linux/switchtec.h>#include <linux/switchtec_ioctl.h> #include <linux/interrupt.h>#include <linux/module.h>#include <linux/fs.h>#include <linux/uaccess.h>#include <linux/poll.h>#include <linux/wait.h>#include <linux/io-64-nonatomic-lo-hi.h>#include <linux/nospec.h> MODULE_DESCRIPTION("Microsemi Switchtec(tm) PCIe Management Driver");MODULE_VERSION("0.1");MODULE_LICENSE("GPL");MODULE_AUTHOR("Microsemi Corporation"); static int max_devices = 16;module_param(max_devices, int, 0644);MODULE_PARM_DESC(max_devices, "max number of switchtec device instances"); static bool use_dma_mrpc = true;module_param(use_dma_mrpc, bool, 0644);MODULE_PARM_DESC(use_dma_mrpc,		 "Enable the use of the DMA MRPC feature"); static int nirqs = 32;module_param(nirqs, int, 0644);MODULE_PARM_DESC(nirqs, "number of interrupts to allocate (more may be useful for NTB applications)"); static dev_t switchtec_devt;static DEFINE_IDA(switchtec_minor_ida); const struct class switchtec_class = {	.name = "switchtec",};EXPORT_SYMBOL_GPL(switchtec_class); enum mrpc_state {	MRPC_IDLE = 0,	MRPC_QUEUED,	MRPC_RUNNING,	MRPC_DONE,	MRPC_IO_ERROR,}; struct switchtec_user {	struct switchtec_dev *stdev; 	enum mrpc_state state; 	wait_queue_head_t cmd_comp;	struct kref kref;	struct list_head list; 	bool cmd_done;	u32 cmd;	u32 status;	u32 return_code;	size_t data_len;	size_t read_len;	unsigned char data[SWITCHTEC_MRPC_PAYLOAD_SIZE];	int event_cnt;}; /* * The MMIO reads to the device_id register should always return the device ID * of the device, otherwise the firmware is probably stuck or unreachable * due to a firmware reset which clears PCI state including the BARs and Memory * Space Enable bits. */static int is_firmware_running(struct switchtec_dev *stdev){	u32 device = ioread32(&stdev->mmio_sys_info->device_id); 	return stdev->pdev->device == device;} static struct switchtec_user *stuser_create(struct switchtec_dev *stdev){	struct switchtec_user *stuser; 	stuser = kzalloc_obj(*stuser);	if (!stuser)		return ERR_PTR(-ENOMEM); 	get_device(&stdev->dev);	stuser->stdev = stdev;	kref_init(&stuser->kref);	INIT_LIST_HEAD(&stuser->list);	init_waitqueue_head(&stuser->cmd_comp);	stuser->event_cnt = atomic_read(&stdev->event_cnt); 	dev_dbg(&stdev->dev, "%s: %p\n", __func__, stuser); 	return stuser;} static void stuser_free(struct kref *kref){	struct switchtec_user *stuser; 	stuser = container_of(kref, struct switchtec_user, kref); 	dev_dbg(&stuser->stdev->dev, "%s: %p\n", __func__, stuser); 	put_device(&stuser->stdev->dev);	kfree(stuser);} static void stuser_put(struct switchtec_user *stuser){	kref_put(&stuser->kref, stuser_free);} static void stuser_set_state(struct switchtec_user *stuser,			     enum mrpc_state state){	/* requires the mrpc_mutex to already be held when called */ 	static const char * const state_names[] = {		[MRPC_IDLE] = "IDLE",		[MRPC_QUEUED] = "QUEUED",		[MRPC_RUNNING] = "RUNNING",		[MRPC_DONE] = "DONE",		[MRPC_IO_ERROR] = "IO_ERROR",	}; 	stuser->state = state; 	dev_dbg(&stuser->stdev->dev, "stuser state %p -> %s",		stuser, state_names[state]);} static void mrpc_complete_cmd(struct switchtec_dev *stdev); static void flush_wc_buf(struct switchtec_dev *stdev){	struct ntb_dbmsg_regs __iomem *mmio_dbmsg; 	/*	 * odb (outbound doorbell) register is processed by low latency	 * hardware and w/o side effect	 */	mmio_dbmsg = (void __iomem *)stdev->mmio_ntb +		SWITCHTEC_NTB_REG_DBMSG_OFFSET;	ioread32(&mmio_dbmsg->odb);} static void mrpc_cmd_submit(struct switchtec_dev *stdev){	/* requires the mrpc_mutex to already be held when called */ 	struct switchtec_user *stuser; 	if (stdev->mrpc_busy)		return; 	if (list_empty(&stdev->mrpc_queue))		return; 	stuser = list_entry(stdev->mrpc_queue.next, struct switchtec_user,			    list); 	if (stdev->dma_mrpc) {		stdev->dma_mrpc->status = SWITCHTEC_MRPC_STATUS_INPROGRESS;		memset(stdev->dma_mrpc->data, 0xFF, SWITCHTEC_MRPC_PAYLOAD_SIZE);	} 	stuser_set_state(stuser, MRPC_RUNNING);	stdev->mrpc_busy = 1;	memcpy_toio(&stdev->mmio_mrpc->input_data,		    stuser->data, stuser->data_len);	flush_wc_buf(stdev);	iowrite32(stuser->cmd, &stdev->mmio_mrpc->cmd); 	schedule_delayed_work(&stdev->mrpc_timeout,			      msecs_to_jiffies(500));} static int mrpc_queue_cmd(struct switchtec_user *stuser){	/* requires the mrpc_mutex to already be held when called */ 	struct switchtec_dev *stdev = stuser->stdev; 	kref_get(&stuser->kref);	stuser->read_len = sizeof(stuser->data);	stuser_set_state(stuser, MRPC_QUEUED);	stuser->cmd_done = false;	list_add_tail(&stuser->list, &stdev->mrpc_queue); 	mrpc_cmd_submit(stdev); 	return 0;} static void mrpc_cleanup_cmd(struct switchtec_dev *stdev){	/* requires the mrpc_mutex to already be held when called */ 	struct switchtec_user *stuser = list_entry(stdev->mrpc_queue.next,						   struct switchtec_user, list); 	stuser->cmd_done = true;	wake_up_interruptible(&stuser->cmd_comp);	list_del_init(&stuser->list);	stuser_put(stuser);	stdev->mrpc_busy = 0; 	mrpc_cmd_submit(stdev);} static void mrpc_complete_cmd(struct switchtec_dev *stdev){	/* requires the mrpc_mutex to already be held when called */ 	struct switchtec_user *stuser; 	if (list_empty(&stdev->mrpc_queue))		return; 	stuser = list_entry(stdev->mrpc_queue.next, struct switchtec_user,			    list); 	if (stdev->dma_mrpc)		stuser->status = stdev->dma_mrpc->status;	else		stuser->status = ioread32(&stdev->mmio_mrpc->status); 	if (stuser->status == SWITCHTEC_MRPC_STATUS_INPROGRESS)		return; 	stuser_set_state(stuser, MRPC_DONE);	stuser->return_code = 0; 	if (stuser->status != SWITCHTEC_MRPC_STATUS_DONE &&	    stuser->status != SWITCHTEC_MRPC_STATUS_ERROR)		goto out; 	if (stdev->dma_mrpc)		stuser->return_code = stdev->dma_mrpc->rtn_code;	else		stuser->return_code = ioread32(&stdev->mmio_mrpc->ret_value);	if (stuser->return_code != 0)		goto out; 	if (stdev->dma_mrpc)		memcpy(stuser->data, &stdev->dma_mrpc->data,			      stuser->read_len);	else		memcpy_fromio(stuser->data, &stdev->mmio_mrpc->output_data,			      stuser->read_len);out:	mrpc_cleanup_cmd(stdev);} static void mrpc_event_work(struct work_struct *work){	struct switchtec_dev *stdev; 	stdev = container_of(work, struct switchtec_dev, mrpc_work); 	dev_dbg(&stdev->dev, "%s\n", __func__); 	guard(mutex)(&stdev->mrpc_mutex);	cancel_delayed_work(&stdev->mrpc_timeout);	mrpc_complete_cmd(stdev);} static void mrpc_error_complete_cmd(struct switchtec_dev *stdev){	/* requires the mrpc_mutex to already be held when called */ 	struct switchtec_user *stuser; 	if (list_empty(&stdev->mrpc_queue))		return; 	stuser = list_entry(stdev->mrpc_queue.next,			    struct switchtec_user, list); 	stuser_set_state(stuser, MRPC_IO_ERROR); 	mrpc_cleanup_cmd(stdev);} static void mrpc_timeout_work(struct work_struct *work){	struct switchtec_dev *stdev;	u32 status; 	stdev = container_of(work, struct switchtec_dev, mrpc_timeout.work); 	dev_dbg(&stdev->dev, "%s\n", __func__); 	mutex_lock(&stdev->mrpc_mutex); 	if (!is_firmware_running(stdev)) {		mrpc_error_complete_cmd(stdev);		goto out;	} 	if (stdev->dma_mrpc)		status = stdev->dma_mrpc->status;	else		status = ioread32(&stdev->mmio_mrpc->status);	if (status == SWITCHTEC_MRPC_STATUS_INPROGRESS) {		schedule_delayed_work(&stdev->mrpc_timeout,				      msecs_to_jiffies(500));		goto out;	} 	mrpc_complete_cmd(stdev);out:	mutex_unlock(&stdev->mrpc_mutex);} static ssize_t device_version_show(struct device *dev,	struct device_attribute *attr, char *buf){	struct switchtec_dev *stdev = to_stdev(dev);	u32 ver; 	ver = ioread32(&stdev->mmio_sys_info->device_version); 	return sysfs_emit(buf, "%x\n", ver);}static DEVICE_ATTR_RO(device_version); static ssize_t fw_version_show(struct device *dev,	struct device_attribute *attr, char *buf){	struct switchtec_dev *stdev = to_stdev(dev);	u32 ver; 	ver = ioread32(&stdev->mmio_sys_info->firmware_version); 	return sysfs_emit(buf, "%08x\n", ver);}static DEVICE_ATTR_RO(fw_version); static ssize_t io_string_show(char *buf, void __iomem *attr, size_t len){	int i; 	memcpy_fromio(buf, attr, len);	buf[len] = '\n';	buf[len + 1] = 0; 	for (i = len - 1; i > 0; i--) {		if (buf[i] != ' ')			break;		buf[i] = '\n';		buf[i + 1] = 0;	} 	return strlen(buf);} #define DEVICE_ATTR_SYS_INFO_STR(field) \static ssize_t field ## _show(struct device *dev, \	struct device_attribute *attr, char *buf) \{ \	struct switchtec_dev *stdev = to_stdev(dev); \	struct sys_info_regs __iomem *si = stdev->mmio_sys_info; \	if (stdev->gen == SWITCHTEC_GEN3) \		return io_string_show(buf, &si->gen3.field, \				      sizeof(si->gen3.field)); \	else if (stdev->gen >= SWITCHTEC_GEN4) \		return io_string_show(buf, &si->gen4.field, \				      sizeof(si->gen4.field)); \	else \		return -EOPNOTSUPP; \} \\static DEVICE_ATTR_RO(field) DEVICE_ATTR_SYS_INFO_STR(vendor_id);DEVICE_ATTR_SYS_INFO_STR(product_id);DEVICE_ATTR_SYS_INFO_STR(product_revision); static ssize_t component_vendor_show(struct device *dev,				     struct device_attribute *attr, char *buf){	struct switchtec_dev *stdev = to_stdev(dev);	struct sys_info_regs __iomem *si = stdev->mmio_sys_info; 	/* component_vendor field not supported after gen3 */	if (stdev->gen != SWITCHTEC_GEN3)		return sysfs_emit(buf, "none\n"); 	return io_string_show(buf, &si->gen3.component_vendor,			      sizeof(si->gen3.component_vendor));}static DEVICE_ATTR_RO(component_vendor); static ssize_t component_id_show(struct device *dev,	struct device_attribute *attr, char *buf){	struct switchtec_dev *stdev = to_stdev(dev);	int id = ioread16(&stdev->mmio_sys_info->gen3.component_id); 	/* component_id field not supported after gen3 */	if (stdev->gen != SWITCHTEC_GEN3)		return sysfs_emit(buf, "none\n"); 	return sysfs_emit(buf, "PM%04X\n", id);}static DEVICE_ATTR_RO(component_id); static ssize_t component_revision_show(struct device *dev,	struct device_attribute *attr, char *buf){	struct switchtec_dev *stdev = to_stdev(dev);	int rev = ioread8(&stdev->mmio_sys_info->gen3.component_revision); 	/* component_revision field not supported after gen3 */	if (stdev->gen != SWITCHTEC_GEN3)		return sysfs_emit(buf, "255\n"); 	return sysfs_emit(buf, "%d\n", rev);}static DEVICE_ATTR_RO(component_revision); static ssize_t partition_show(struct device *dev,	struct device_attribute *attr, char *buf){	struct switchtec_dev *stdev = to_stdev(dev); 	return sysfs_emit(buf, "%d\n", stdev->partition);}static DEVICE_ATTR_RO(partition); static ssize_t partition_count_show(struct device *dev,	struct device_attribute *attr, char *buf){	struct switchtec_dev *stdev = to_stdev(dev); 	return sysfs_emit(buf, "%d\n", stdev->partition_count);}static DEVICE_ATTR_RO(partition_count); static struct attribute *switchtec_device_attrs[] = {	&dev_attr_device_version.attr,	&dev_attr_fw_version.attr,	&dev_attr_vendor_id.attr,	&dev_attr_product_id.attr,	&dev_attr_product_revision.attr,	&dev_attr_component_vendor.attr,	&dev_attr_component_id.attr,	&dev_attr_component_revision.attr,	&dev_attr_partition.attr,	&dev_attr_partition_count.attr,	NULL,}; ATTRIBUTE_GROUPS(switchtec_device); static int switchtec_dev_open(struct inode *inode, struct file *filp){	struct switchtec_dev *stdev;	struct switchtec_user *stuser; 	stdev = container_of(inode->i_cdev, struct switchtec_dev, cdev); 	stuser = stuser_create(stdev);	if (IS_ERR(stuser))		return PTR_ERR(stuser); 	filp->private_data = stuser;	stream_open(inode, filp); 	dev_dbg(&stdev->dev, "%s: %p\n", __func__, stuser); 	return 0;} static int switchtec_dev_release(struct inode *inode, struct file *filp){	struct switchtec_user *stuser = filp->private_data; 	stuser_put(stuser); 	return 0;} static int lock_mutex_and_test_alive(struct switchtec_dev *stdev){	if (mutex_lock_interruptible(&stdev->mrpc_mutex))		return -EINTR; 	if (!stdev->alive) {		mutex_unlock(&stdev->mrpc_mutex);

Lines 1–500 of 1929.