1287 lines · 33.8 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0/* * Volume Management Device driver * Copyright (c) 2015, Intel Corporation. */ #include <linux/device.h>#include <linux/interrupt.h>#include <linux/irq.h>#include <linux/irqchip/irq-msi-lib.h>#include <linux/kernel.h>#include <linux/module.h>#include <linux/msi.h>#include <linux/pci.h>#include <linux/pci-acpi.h>#include <linux/pci-ecam.h>#include <linux/srcu.h>#include <linux/rculist.h>#include <linux/rcupdate.h> #include <xen/xen.h> #include <asm/irqdomain.h> #define VMD_CFGBAR	0#define VMD_MEMBAR1	2#define VMD_MEMBAR2	4 #define PCI_REG_VMCAP		0x40#define BUS_RESTRICT_CAP(vmcap)	(vmcap & 0x1)#define PCI_REG_VMCONFIG	0x44#define BUS_RESTRICT_CFG(vmcfg)	((vmcfg >> 8) & 0x3)#define VMCONFIG_MSI_REMAP	0x2#define PCI_REG_VMLOCK		0x70#define MB2_SHADOW_EN(vmlock)	(vmlock & 0x2) #define MB2_SHADOW_OFFSET	0x2000#define MB2_SHADOW_SIZE		16 /* DMR BAR4 register offsets */#define SHADOW_MEMBAR1_28C1		0x2818 /* MEMBAR1 physical address */#define SHADOW_MEMBAR2_28C1		0x2820 /* MEMBAR2 physical address */#define BASE_ID_REG_28C1		0x2840#define MEMBAR2_OFFSET_28C1		0x30d0 enum vmd_features {	/*	 * Device may contain registers which hint the physical location of the	 * membars, in order to allow proper address translation during	 * resource assignment to enable guest virtualization	 */	VMD_FEAT_HAS_MEMBAR_SHADOW		= (1 << 0), 	/*	 * Device may provide root port configuration information which limits	 * bus numbering	 */	VMD_FEAT_HAS_BUS_RESTRICTIONS		= (1 << 1), 	/*	 * Device contains physical location shadow registers in	 * vendor-specific capability space	 */	VMD_FEAT_HAS_MEMBAR_SHADOW_VSCAP	= (1 << 2), 	/*	 * Device may use MSI-X vector 0 for software triggering and will not	 * be used for MSI remapping	 */	VMD_FEAT_OFFSET_FIRST_VECTOR		= (1 << 3), 	/*	 * Device can bypass remapping MSI-X transactions into its MSI-X table,	 * avoiding the requirement of a VMD MSI domain for child device	 * interrupt handling.	 */	VMD_FEAT_CAN_BYPASS_MSI_REMAP		= (1 << 4), 	/*	 * Enable ASPM on the PCIE root ports and set the default LTR of the	 * storage devices on platforms where these values are not configured by	 * BIOS. This is needed for laptops, which require these settings for	 * proper power management of the SoC.	 */	VMD_FEAT_BIOS_PM_QUIRK		= (1 << 5), 	/*	 * Newer VMD with device ID 0x28C1 has unique settings compared to its	 * predecessor where BIOS enumerates the entire VMD device tree and	 * stores respective configurations including bus start range and	 * shadow registers in VMD MMIO space in VMD BAR4/BAR5, otherwise	 * referred to as MEMBAR2 or MSI-X BAR.	 */	VMD_FEAT_USE_BIOS_INFO		= (1 << 6),}; #define VMD_BIOS_PM_QUIRK_LTR	0x1003	/* 3145728 ns */ #define VMD_FEATS_CLIENT	(VMD_FEAT_HAS_MEMBAR_SHADOW_VSCAP |	\				 VMD_FEAT_HAS_BUS_RESTRICTIONS |	\				 VMD_FEAT_OFFSET_FIRST_VECTOR |		\				 VMD_FEAT_BIOS_PM_QUIRK) static DEFINE_IDA(vmd_instance_ida); /* * Lock for manipulating VMD IRQ lists. */static DEFINE_RAW_SPINLOCK(list_lock); /** * struct vmd_irq - private data to map driver IRQ to the VMD shared vector * @node:	list item for parent traversal. * @irq:	back pointer to parent. * @enabled:	true if driver enabled IRQ * @virq:	the virtual IRQ value provided to the requesting driver. * * Every MSI/MSI-X IRQ requested for a device in a VMD domain will be mapped to * a VMD IRQ using this structure. */struct vmd_irq {	struct list_head	node;	struct vmd_irq_list	*irq;	bool			enabled;	unsigned int		virq;}; /** * struct vmd_irq_list - list of driver requested IRQs mapping to a VMD vector * @irq_list:	the list of irq's the VMD one demuxes to. * @srcu:	SRCU struct for local synchronization. * @count:	number of child IRQs assigned to this vector; used to track *		sharing. * @virq:	The underlying VMD Linux interrupt number */struct vmd_irq_list {	struct list_head	irq_list;	struct srcu_struct	srcu;	unsigned int		count;	unsigned int		virq;}; struct vmd_dev {	struct pci_dev		*dev; 	raw_spinlock_t		cfg_lock;	void __iomem		*cfgbar; 	int msix_count;	struct vmd_irq_list	*irqs; 	struct pci_sysdata	sysdata;	struct resource		resources[3];	struct irq_domain	*irq_domain;	struct pci_bus		*bus;	u8			busn_start;	u8			first_vec;	char			*name;	int			instance;	unsigned long		features;}; static inline struct vmd_dev *vmd_from_bus(struct pci_bus *bus){	return container_of(bus->sysdata, struct vmd_dev, sysdata);} static inline unsigned int index_from_irqs(struct vmd_dev *vmd,					   struct vmd_irq_list *irqs){	return irqs - vmd->irqs;} /* * Drivers managing a device in a VMD domain allocate their own IRQs as before, * but the MSI entry for the hardware it's driving will be programmed with a * destination ID for the VMD MSI-X table.  The VMD muxes interrupts in its * domain into one of its own, and the VMD driver de-muxes these for the * handlers sharing that VMD IRQ.  The vmd irq_domain provides the operations * and irq_chip to set this up. */static void vmd_compose_msi_msg(struct irq_data *data, struct msi_msg *msg){	struct vmd_irq *vmdirq = data->chip_data;	struct vmd_irq_list *irq = vmdirq->irq;	struct vmd_dev *vmd = irq_data_get_irq_handler_data(data); 	memset(msg, 0, sizeof(*msg));	msg->address_hi = X86_MSI_BASE_ADDRESS_HIGH;	msg->arch_addr_lo.base_address = X86_MSI_BASE_ADDRESS_LOW;	msg->arch_addr_lo.destid_0_7 = index_from_irqs(vmd, irq);} static void vmd_irq_enable(struct irq_data *data){	struct vmd_irq *vmdirq = data->chip_data; 	scoped_guard(raw_spinlock_irqsave, &list_lock) {		WARN_ON(vmdirq->enabled);		list_add_tail_rcu(&vmdirq->node, &vmdirq->irq->irq_list);		vmdirq->enabled = true;	}} static void vmd_pci_msi_enable(struct irq_data *data){	vmd_irq_enable(data->parent_data);	data->chip->irq_unmask(data);} static unsigned int vmd_pci_msi_startup(struct irq_data *data){	vmd_pci_msi_enable(data);	return 0;} static void vmd_irq_disable(struct irq_data *data){	struct vmd_irq *vmdirq = data->chip_data; 	scoped_guard(raw_spinlock_irqsave, &list_lock) {		if (vmdirq->enabled) {			list_del_rcu(&vmdirq->node);			vmdirq->enabled = false;		}	}} static void vmd_pci_msi_disable(struct irq_data *data){	data->chip->irq_mask(data);	vmd_irq_disable(data->parent_data);} static void vmd_pci_msi_shutdown(struct irq_data *data){	vmd_pci_msi_disable(data);} static struct irq_chip vmd_msi_controller = {	.name			= "VMD-MSI",	.irq_compose_msi_msg	= vmd_compose_msi_msg,}; /* * XXX: We can be even smarter selecting the best IRQ once we solve the * affinity problem. */static struct vmd_irq_list *vmd_next_irq(struct vmd_dev *vmd, struct msi_desc *desc){	int i, best; 	if (vmd->msix_count == 1 + vmd->first_vec)		return &vmd->irqs[vmd->first_vec]; 	/*	 * White list for fast-interrupt handlers. All others will share the	 * "slow" interrupt vector.	 */	switch (msi_desc_to_pci_dev(desc)->class) {	case PCI_CLASS_STORAGE_EXPRESS:		break;	default:		return &vmd->irqs[vmd->first_vec];	} 	scoped_guard(raw_spinlock_irq, &list_lock) {		best = vmd->first_vec + 1;		for (i = best; i < vmd->msix_count; i++)			if (vmd->irqs[i].count < vmd->irqs[best].count)				best = i;		vmd->irqs[best].count++;	} 	return &vmd->irqs[best];} static void vmd_msi_free(struct irq_domain *domain, unsigned int virq,			 unsigned int nr_irqs); static int vmd_msi_alloc(struct irq_domain *domain, unsigned int virq,			 unsigned int nr_irqs, void *arg){	struct msi_desc *desc = ((msi_alloc_info_t *)arg)->desc;	struct vmd_dev *vmd = domain->host_data;	struct vmd_irq *vmdirq; 	for (int i = 0; i < nr_irqs; ++i) {		vmdirq = kzalloc_obj(*vmdirq);		if (!vmdirq) {			vmd_msi_free(domain, virq, i);			return -ENOMEM;		} 		INIT_LIST_HEAD(&vmdirq->node);		vmdirq->irq = vmd_next_irq(vmd, desc);		vmdirq->virq = virq + i; 		irq_domain_set_info(domain, virq + i, vmdirq->irq->virq,				    &vmd_msi_controller, vmdirq,				    handle_untracked_irq, vmd, NULL);	} 	return 0;} static void vmd_msi_free(struct irq_domain *domain, unsigned int virq,			 unsigned int nr_irqs){	struct irq_data *irq_data;	struct vmd_irq *vmdirq; 	for (int i = 0; i < nr_irqs; ++i) {		irq_data = irq_domain_get_irq_data(domain, virq + i);		vmdirq = irq_data->chip_data; 		synchronize_srcu(&vmdirq->irq->srcu); 		/* XXX: Potential optimization to rebalance */		scoped_guard(raw_spinlock_irq, &list_lock)			vmdirq->irq->count--; 		kfree(vmdirq);	}} static const struct irq_domain_ops vmd_msi_domain_ops = {	.alloc		= vmd_msi_alloc,	.free		= vmd_msi_free,}; static bool vmd_init_dev_msi_info(struct device *dev, struct irq_domain *domain,				  struct irq_domain *real_parent,				  struct msi_domain_info *info){	if (!msi_lib_init_dev_msi_info(dev, domain, real_parent, info))		return false; 	info->chip->irq_startup		= vmd_pci_msi_startup;	info->chip->irq_shutdown	= vmd_pci_msi_shutdown;	info->chip->irq_enable		= vmd_pci_msi_enable;	info->chip->irq_disable		= vmd_pci_msi_disable;	return true;} #define VMD_MSI_FLAGS_SUPPORTED	(MSI_GENERIC_FLAGS_MASK | MSI_FLAG_PCI_MSIX)#define VMD_MSI_FLAGS_REQUIRED	(MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_NO_AFFINITY) static const struct msi_parent_ops vmd_msi_parent_ops = {	.supported_flags	= VMD_MSI_FLAGS_SUPPORTED,	.required_flags		= VMD_MSI_FLAGS_REQUIRED,	.bus_select_token	= DOMAIN_BUS_VMD_MSI,	.bus_select_mask	= MATCH_PCI_MSI,	.prefix			= "VMD-",	.init_dev_msi_info	= vmd_init_dev_msi_info,}; static int vmd_create_irq_domain(struct vmd_dev *vmd){	struct irq_domain_info info = {		.size		= vmd->msix_count,		.ops		= &vmd_msi_domain_ops,		.host_data	= vmd,	}; 	info.fwnode = irq_domain_alloc_named_id_fwnode("VMD-MSI",						       vmd->sysdata.domain);	if (!info.fwnode)		return -ENODEV; 	vmd->irq_domain = msi_create_parent_irq_domain(&info,						       &vmd_msi_parent_ops);	if (!vmd->irq_domain) {		irq_domain_free_fwnode(info.fwnode);		return -ENODEV;	} 	return 0;} static void vmd_set_msi_remapping(struct vmd_dev *vmd, bool enable){	u16 reg; 	if (!!(vmd->features & VMD_FEAT_USE_BIOS_INFO))		return; 	pci_read_config_word(vmd->dev, PCI_REG_VMCONFIG, &reg);	reg = enable ? (reg & ~VMCONFIG_MSI_REMAP) :		       (reg | VMCONFIG_MSI_REMAP);	pci_write_config_word(vmd->dev, PCI_REG_VMCONFIG, reg);} static void vmd_remove_irq_domain(struct vmd_dev *vmd){	/*	 * Some production BIOS won't enable remapping between soft reboots.	 * Ensure remapping is restored before unloading the driver.	 */	if (!vmd->msix_count)		vmd_set_msi_remapping(vmd, true); 	if (vmd->irq_domain) {		struct fwnode_handle *fn = vmd->irq_domain->fwnode; 		irq_domain_remove(vmd->irq_domain);		irq_domain_free_fwnode(fn);	}} static unsigned int vmd_bus_to_ecam(struct vmd_dev *vmd, unsigned int busnr){	if (!!(vmd->features & VMD_FEAT_USE_BIOS_INFO))		return busnr; 	return busnr - vmd->busn_start;} static void __iomem *vmd_cfg_addr(struct vmd_dev *vmd, struct pci_bus *bus,				  unsigned int devfn, int reg, int len){	unsigned int busnr_ecam;	u32 offset; 	busnr_ecam = vmd_bus_to_ecam(vmd, bus->number);	offset = PCIE_ECAM_OFFSET(busnr_ecam, devfn, reg); 	if (offset + len >= resource_size(&vmd->dev->resource[VMD_CFGBAR]))		return NULL; 	return vmd->cfgbar + offset;} /* * CPU may deadlock if config space is not serialized on some versions of this * hardware, so all config space access is done under a spinlock. */static int vmd_pci_read(struct pci_bus *bus, unsigned int devfn, int reg,			int len, u32 *value){	struct vmd_dev *vmd = vmd_from_bus(bus);	void __iomem *addr = vmd_cfg_addr(vmd, bus, devfn, reg, len); 	if (!addr)		return -EFAULT; 	guard(raw_spinlock_irqsave)(&vmd->cfg_lock);	switch (len) {	case 1:		*value = readb(addr);		return 0;	case 2:		*value = readw(addr);		return 0;	case 4:		*value = readl(addr);		return 0;	default:		return -EINVAL;	}} /* * VMD h/w converts non-posted config writes to posted memory writes. The * read-back in this function forces the completion so it returns only after * the config space was written, as expected. */static int vmd_pci_write(struct pci_bus *bus, unsigned int devfn, int reg,			 int len, u32 value){	struct vmd_dev *vmd = vmd_from_bus(bus);	void __iomem *addr = vmd_cfg_addr(vmd, bus, devfn, reg, len); 	if (!addr)		return -EFAULT; 	guard(raw_spinlock_irqsave)(&vmd->cfg_lock);	switch (len) {	case 1:		writeb(value, addr);		readb(addr);		return 0;	case 2:		writew(value, addr);		readw(addr);		return 0;	case 4:		writel(value, addr);		readl(addr);		return 0;	default:		return -EINVAL;	}} static struct pci_ops vmd_ops = {	.read		= vmd_pci_read,	.write		= vmd_pci_write,}; 

Lines 1–500 of 1287.