diff options
Diffstat (limited to 'arch/s390/pci')
| -rw-r--r-- | arch/s390/pci/Makefile | 6 | ||||
| -rw-r--r-- | arch/s390/pci/pci.c | 935 | ||||
| -rw-r--r-- | arch/s390/pci/pci_clp.c | 392 | ||||
| -rw-r--r-- | arch/s390/pci/pci_debug.c | 167 | ||||
| -rw-r--r-- | arch/s390/pci/pci_dma.c | 506 | ||||
| -rw-r--r-- | arch/s390/pci/pci_event.c | 136 | ||||
| -rw-r--r-- | arch/s390/pci/pci_insn.c | 202 | ||||
| -rw-r--r-- | arch/s390/pci/pci_sysfs.c | 110 | 
8 files changed, 2454 insertions, 0 deletions
diff --git a/arch/s390/pci/Makefile b/arch/s390/pci/Makefile new file mode 100644 index 00000000000..a9e1dc4ae44 --- /dev/null +++ b/arch/s390/pci/Makefile @@ -0,0 +1,6 @@ +# +# Makefile for the s390 PCI subsystem. +# + +obj-$(CONFIG_PCI)	+= pci.o pci_dma.o pci_clp.o pci_sysfs.o \ +			   pci_event.o pci_debug.o pci_insn.o diff --git a/arch/s390/pci/pci.c b/arch/s390/pci/pci.c new file mode 100644 index 00000000000..30de42730b2 --- /dev/null +++ b/arch/s390/pci/pci.c @@ -0,0 +1,935 @@ +/* + * Copyright IBM Corp. 2012 + * + * Author(s): + *   Jan Glauber <jang@linux.vnet.ibm.com> + * + * The System z PCI code is a rewrite from a prototype by + * the following people (Kudoz!): + *   Alexander Schmidt + *   Christoph Raisch + *   Hannes Hering + *   Hoang-Nam Nguyen + *   Jan-Bernd Themann + *   Stefan Roscher + *   Thomas Klein + */ + +#define COMPONENT "zPCI" +#define pr_fmt(fmt) COMPONENT ": " fmt + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/err.h> +#include <linux/export.h> +#include <linux/delay.h> +#include <linux/irq.h> +#include <linux/kernel_stat.h> +#include <linux/seq_file.h> +#include <linux/pci.h> +#include <linux/msi.h> + +#include <asm/isc.h> +#include <asm/airq.h> +#include <asm/facility.h> +#include <asm/pci_insn.h> +#include <asm/pci_clp.h> +#include <asm/pci_dma.h> + +#define DEBUG				/* enable pr_debug */ + +#define	SIC_IRQ_MODE_ALL		0 +#define	SIC_IRQ_MODE_SINGLE		1 + +#define ZPCI_NR_DMA_SPACES		1 +#define ZPCI_NR_DEVICES			CONFIG_PCI_NR_FUNCTIONS + +/* list of all detected zpci devices */ +static LIST_HEAD(zpci_list); +static DEFINE_SPINLOCK(zpci_list_lock); + +static struct irq_chip zpci_irq_chip = { +	.name = "zPCI", +	.irq_unmask = unmask_msi_irq, +	.irq_mask = mask_msi_irq, +}; + +static DECLARE_BITMAP(zpci_domain, ZPCI_NR_DEVICES); +static DEFINE_SPINLOCK(zpci_domain_lock); + +static struct airq_iv *zpci_aisb_iv; +static struct airq_iv *zpci_aibv[ZPCI_NR_DEVICES]; + +/* Adapter interrupt definitions */ +static void zpci_irq_handler(struct airq_struct *airq); + +static struct airq_struct zpci_airq = { +	.handler = zpci_irq_handler, +	.isc = PCI_ISC, +}; + +/* I/O Map */ +static DEFINE_SPINLOCK(zpci_iomap_lock); +static DECLARE_BITMAP(zpci_iomap, ZPCI_IOMAP_MAX_ENTRIES); +struct zpci_iomap_entry *zpci_iomap_start; +EXPORT_SYMBOL_GPL(zpci_iomap_start); + +static struct kmem_cache *zdev_fmb_cache; + +struct zpci_dev *get_zdev(struct pci_dev *pdev) +{ +	return (struct zpci_dev *) pdev->sysdata; +} + +struct zpci_dev *get_zdev_by_fid(u32 fid) +{ +	struct zpci_dev *tmp, *zdev = NULL; + +	spin_lock(&zpci_list_lock); +	list_for_each_entry(tmp, &zpci_list, entry) { +		if (tmp->fid == fid) { +			zdev = tmp; +			break; +		} +	} +	spin_unlock(&zpci_list_lock); +	return zdev; +} + +static struct zpci_dev *get_zdev_by_bus(struct pci_bus *bus) +{ +	return (bus && bus->sysdata) ? (struct zpci_dev *) bus->sysdata : NULL; +} + +int pci_domain_nr(struct pci_bus *bus) +{ +	return ((struct zpci_dev *) bus->sysdata)->domain; +} +EXPORT_SYMBOL_GPL(pci_domain_nr); + +int pci_proc_domain(struct pci_bus *bus) +{ +	return pci_domain_nr(bus); +} +EXPORT_SYMBOL_GPL(pci_proc_domain); + +/* Modify PCI: Register adapter interruptions */ +static int zpci_set_airq(struct zpci_dev *zdev) +{ +	u64 req = ZPCI_CREATE_REQ(zdev->fh, 0, ZPCI_MOD_FC_REG_INT); +	struct zpci_fib fib = {0}; + +	fib.isc = PCI_ISC; +	fib.sum = 1;		/* enable summary notifications */ +	fib.noi = airq_iv_end(zdev->aibv); +	fib.aibv = (unsigned long) zdev->aibv->vector; +	fib.aibvo = 0;		/* each zdev has its own interrupt vector */ +	fib.aisb = (unsigned long) zpci_aisb_iv->vector + (zdev->aisb/64)*8; +	fib.aisbo = zdev->aisb & 63; + +	return zpci_mod_fc(req, &fib); +} + +struct mod_pci_args { +	u64 base; +	u64 limit; +	u64 iota; +	u64 fmb_addr; +}; + +static int mod_pci(struct zpci_dev *zdev, int fn, u8 dmaas, struct mod_pci_args *args) +{ +	u64 req = ZPCI_CREATE_REQ(zdev->fh, dmaas, fn); +	struct zpci_fib fib = {0}; + +	fib.pba = args->base; +	fib.pal = args->limit; +	fib.iota = args->iota; +	fib.fmb_addr = args->fmb_addr; + +	return zpci_mod_fc(req, &fib); +} + +/* Modify PCI: Register I/O address translation parameters */ +int zpci_register_ioat(struct zpci_dev *zdev, u8 dmaas, +		       u64 base, u64 limit, u64 iota) +{ +	struct mod_pci_args args = { base, limit, iota, 0 }; + +	WARN_ON_ONCE(iota & 0x3fff); +	args.iota |= ZPCI_IOTA_RTTO_FLAG; +	return mod_pci(zdev, ZPCI_MOD_FC_REG_IOAT, dmaas, &args); +} + +/* Modify PCI: Unregister I/O address translation parameters */ +int zpci_unregister_ioat(struct zpci_dev *zdev, u8 dmaas) +{ +	struct mod_pci_args args = { 0, 0, 0, 0 }; + +	return mod_pci(zdev, ZPCI_MOD_FC_DEREG_IOAT, dmaas, &args); +} + +/* Modify PCI: Unregister adapter interruptions */ +static int zpci_clear_airq(struct zpci_dev *zdev) +{ +	struct mod_pci_args args = { 0, 0, 0, 0 }; + +	return mod_pci(zdev, ZPCI_MOD_FC_DEREG_INT, 0, &args); +} + +/* Modify PCI: Set PCI function measurement parameters */ +int zpci_fmb_enable_device(struct zpci_dev *zdev) +{ +	struct mod_pci_args args = { 0, 0, 0, 0 }; + +	if (zdev->fmb) +		return -EINVAL; + +	zdev->fmb = kmem_cache_zalloc(zdev_fmb_cache, GFP_KERNEL); +	if (!zdev->fmb) +		return -ENOMEM; +	WARN_ON((u64) zdev->fmb & 0xf); + +	args.fmb_addr = virt_to_phys(zdev->fmb); +	return mod_pci(zdev, ZPCI_MOD_FC_SET_MEASURE, 0, &args); +} + +/* Modify PCI: Disable PCI function measurement */ +int zpci_fmb_disable_device(struct zpci_dev *zdev) +{ +	struct mod_pci_args args = { 0, 0, 0, 0 }; +	int rc; + +	if (!zdev->fmb) +		return -EINVAL; + +	/* Function measurement is disabled if fmb address is zero */ +	rc = mod_pci(zdev, ZPCI_MOD_FC_SET_MEASURE, 0, &args); + +	kmem_cache_free(zdev_fmb_cache, zdev->fmb); +	zdev->fmb = NULL; +	return rc; +} + +#define ZPCI_PCIAS_CFGSPC	15 + +static int zpci_cfg_load(struct zpci_dev *zdev, int offset, u32 *val, u8 len) +{ +	u64 req = ZPCI_CREATE_REQ(zdev->fh, ZPCI_PCIAS_CFGSPC, len); +	u64 data; +	int rc; + +	rc = zpci_load(&data, req, offset); +	if (!rc) { +		data = data << ((8 - len) * 8); +		data = le64_to_cpu(data); +		*val = (u32) data; +	} else +		*val = 0xffffffff; +	return rc; +} + +static int zpci_cfg_store(struct zpci_dev *zdev, int offset, u32 val, u8 len) +{ +	u64 req = ZPCI_CREATE_REQ(zdev->fh, ZPCI_PCIAS_CFGSPC, len); +	u64 data = val; +	int rc; + +	data = cpu_to_le64(data); +	data = data >> ((8 - len) * 8); +	rc = zpci_store(data, req, offset); +	return rc; +} + +void pcibios_fixup_bus(struct pci_bus *bus) +{ +} + +resource_size_t pcibios_align_resource(void *data, const struct resource *res, +				       resource_size_t size, +				       resource_size_t align) +{ +	return 0; +} + +/* combine single writes by using store-block insn */ +void __iowrite64_copy(void __iomem *to, const void *from, size_t count) +{ +       zpci_memcpy_toio(to, from, count); +} + +/* Create a virtual mapping cookie for a PCI BAR */ +void __iomem *pci_iomap(struct pci_dev *pdev, int bar, unsigned long max) +{ +	struct zpci_dev *zdev =	get_zdev(pdev); +	u64 addr; +	int idx; + +	if ((bar & 7) != bar) +		return NULL; + +	idx = zdev->bars[bar].map_idx; +	spin_lock(&zpci_iomap_lock); +	zpci_iomap_start[idx].fh = zdev->fh; +	zpci_iomap_start[idx].bar = bar; +	spin_unlock(&zpci_iomap_lock); + +	addr = ZPCI_IOMAP_ADDR_BASE | ((u64) idx << 48); +	return (void __iomem *) addr; +} +EXPORT_SYMBOL_GPL(pci_iomap); + +void pci_iounmap(struct pci_dev *pdev, void __iomem *addr) +{ +	unsigned int idx; + +	idx = (((__force u64) addr) & ~ZPCI_IOMAP_ADDR_BASE) >> 48; +	spin_lock(&zpci_iomap_lock); +	zpci_iomap_start[idx].fh = 0; +	zpci_iomap_start[idx].bar = 0; +	spin_unlock(&zpci_iomap_lock); +} +EXPORT_SYMBOL_GPL(pci_iounmap); + +static int pci_read(struct pci_bus *bus, unsigned int devfn, int where, +		    int size, u32 *val) +{ +	struct zpci_dev *zdev = get_zdev_by_bus(bus); +	int ret; + +	if (!zdev || devfn != ZPCI_DEVFN) +		ret = -ENODEV; +	else +		ret = zpci_cfg_load(zdev, where, val, size); + +	return ret; +} + +static int pci_write(struct pci_bus *bus, unsigned int devfn, int where, +		     int size, u32 val) +{ +	struct zpci_dev *zdev = get_zdev_by_bus(bus); +	int ret; + +	if (!zdev || devfn != ZPCI_DEVFN) +		ret = -ENODEV; +	else +		ret = zpci_cfg_store(zdev, where, val, size); + +	return ret; +} + +static struct pci_ops pci_root_ops = { +	.read = pci_read, +	.write = pci_write, +}; + +static void zpci_irq_handler(struct airq_struct *airq) +{ +	unsigned long si, ai; +	struct airq_iv *aibv; +	int irqs_on = 0; + +	inc_irq_stat(IRQIO_PCI); +	for (si = 0;;) { +		/* Scan adapter summary indicator bit vector */ +		si = airq_iv_scan(zpci_aisb_iv, si, airq_iv_end(zpci_aisb_iv)); +		if (si == -1UL) { +			if (irqs_on++) +				/* End of second scan with interrupts on. */ +				break; +			/* First scan complete, reenable interrupts. */ +			zpci_set_irq_ctrl(SIC_IRQ_MODE_SINGLE, NULL, PCI_ISC); +			si = 0; +			continue; +		} + +		/* Scan the adapter interrupt vector for this device. */ +		aibv = zpci_aibv[si]; +		for (ai = 0;;) { +			ai = airq_iv_scan(aibv, ai, airq_iv_end(aibv)); +			if (ai == -1UL) +				break; +			inc_irq_stat(IRQIO_MSI); +			airq_iv_lock(aibv, ai); +			generic_handle_irq(airq_iv_get_data(aibv, ai)); +			airq_iv_unlock(aibv, ai); +		} +	} +} + +int arch_setup_msi_irqs(struct pci_dev *pdev, int nvec, int type) +{ +	struct zpci_dev *zdev = get_zdev(pdev); +	unsigned int hwirq, msi_vecs; +	unsigned long aisb; +	struct msi_desc *msi; +	struct msi_msg msg; +	int rc, irq; + +	if (type == PCI_CAP_ID_MSI && nvec > 1) +		return 1; +	msi_vecs = min(nvec, ZPCI_MSI_VEC_MAX); +	msi_vecs = min_t(unsigned int, msi_vecs, CONFIG_PCI_NR_MSI); + +	/* Allocate adapter summary indicator bit */ +	rc = -EIO; +	aisb = airq_iv_alloc_bit(zpci_aisb_iv); +	if (aisb == -1UL) +		goto out; +	zdev->aisb = aisb; + +	/* Create adapter interrupt vector */ +	rc = -ENOMEM; +	zdev->aibv = airq_iv_create(msi_vecs, AIRQ_IV_DATA | AIRQ_IV_BITLOCK); +	if (!zdev->aibv) +		goto out_si; + +	/* Wire up shortcut pointer */ +	zpci_aibv[aisb] = zdev->aibv; + +	/* Request MSI interrupts */ +	hwirq = 0; +	list_for_each_entry(msi, &pdev->msi_list, list) { +		rc = -EIO; +		irq = irq_alloc_desc(0);	/* Alloc irq on node 0 */ +		if (irq < 0) +			goto out_msi; +		rc = irq_set_msi_desc(irq, msi); +		if (rc) +			goto out_msi; +		irq_set_chip_and_handler(irq, &zpci_irq_chip, +					 handle_simple_irq); +		msg.data = hwirq; +		msg.address_lo = zdev->msi_addr & 0xffffffff; +		msg.address_hi = zdev->msi_addr >> 32; +		write_msi_msg(irq, &msg); +		airq_iv_set_data(zdev->aibv, hwirq, irq); +		hwirq++; +	} + +	/* Enable adapter interrupts */ +	rc = zpci_set_airq(zdev); +	if (rc) +		goto out_msi; + +	return (msi_vecs == nvec) ? 0 : msi_vecs; + +out_msi: +	list_for_each_entry(msi, &pdev->msi_list, list) { +		if (hwirq-- == 0) +			break; +		irq_set_msi_desc(msi->irq, NULL); +		irq_free_desc(msi->irq); +		msi->msg.address_lo = 0; +		msi->msg.address_hi = 0; +		msi->msg.data = 0; +		msi->irq = 0; +	} +	zpci_aibv[aisb] = NULL; +	airq_iv_release(zdev->aibv); +out_si: +	airq_iv_free_bit(zpci_aisb_iv, aisb); +out: +	return rc; +} + +void arch_teardown_msi_irqs(struct pci_dev *pdev) +{ +	struct zpci_dev *zdev = get_zdev(pdev); +	struct msi_desc *msi; +	int rc; + +	/* Disable adapter interrupts */ +	rc = zpci_clear_airq(zdev); +	if (rc) +		return; + +	/* Release MSI interrupts */ +	list_for_each_entry(msi, &pdev->msi_list, list) { +		if (msi->msi_attrib.is_msix) +			default_msix_mask_irq(msi, 1); +		else +			default_msi_mask_irq(msi, 1, 1); +		irq_set_msi_desc(msi->irq, NULL); +		irq_free_desc(msi->irq); +		msi->msg.address_lo = 0; +		msi->msg.address_hi = 0; +		msi->msg.data = 0; +		msi->irq = 0; +	} + +	zpci_aibv[zdev->aisb] = NULL; +	airq_iv_release(zdev->aibv); +	airq_iv_free_bit(zpci_aisb_iv, zdev->aisb); +} + +static void zpci_map_resources(struct zpci_dev *zdev) +{ +	struct pci_dev *pdev = zdev->pdev; +	resource_size_t len; +	int i; + +	for (i = 0; i < PCI_BAR_COUNT; i++) { +		len = pci_resource_len(pdev, i); +		if (!len) +			continue; +		pdev->resource[i].start = (resource_size_t) pci_iomap(pdev, i, 0); +		pdev->resource[i].end = pdev->resource[i].start + len - 1; +	} +} + +static void zpci_unmap_resources(struct zpci_dev *zdev) +{ +	struct pci_dev *pdev = zdev->pdev; +	resource_size_t len; +	int i; + +	for (i = 0; i < PCI_BAR_COUNT; i++) { +		len = pci_resource_len(pdev, i); +		if (!len) +			continue; +		pci_iounmap(pdev, (void *) pdev->resource[i].start); +	} +} + +static int __init zpci_irq_init(void) +{ +	int rc; + +	rc = register_adapter_interrupt(&zpci_airq); +	if (rc) +		goto out; +	/* Set summary to 1 to be called every time for the ISC. */ +	*zpci_airq.lsi_ptr = 1; + +	rc = -ENOMEM; +	zpci_aisb_iv = airq_iv_create(ZPCI_NR_DEVICES, AIRQ_IV_ALLOC); +	if (!zpci_aisb_iv) +		goto out_airq; + +	zpci_set_irq_ctrl(SIC_IRQ_MODE_SINGLE, NULL, PCI_ISC); +	return 0; + +out_airq: +	unregister_adapter_interrupt(&zpci_airq); +out: +	return rc; +} + +static void zpci_irq_exit(void) +{ +	airq_iv_release(zpci_aisb_iv); +	unregister_adapter_interrupt(&zpci_airq); +} + +static int zpci_alloc_iomap(struct zpci_dev *zdev) +{ +	int entry; + +	spin_lock(&zpci_iomap_lock); +	entry = find_first_zero_bit(zpci_iomap, ZPCI_IOMAP_MAX_ENTRIES); +	if (entry == ZPCI_IOMAP_MAX_ENTRIES) { +		spin_unlock(&zpci_iomap_lock); +		return -ENOSPC; +	} +	set_bit(entry, zpci_iomap); +	spin_unlock(&zpci_iomap_lock); +	return entry; +} + +static void zpci_free_iomap(struct zpci_dev *zdev, int entry) +{ +	spin_lock(&zpci_iomap_lock); +	memset(&zpci_iomap_start[entry], 0, sizeof(struct zpci_iomap_entry)); +	clear_bit(entry, zpci_iomap); +	spin_unlock(&zpci_iomap_lock); +} + +static struct resource *__alloc_res(struct zpci_dev *zdev, unsigned long start, +				    unsigned long size, unsigned long flags) +{ +	struct resource *r; + +	r = kzalloc(sizeof(*r), GFP_KERNEL); +	if (!r) +		return NULL; + +	r->start = start; +	r->end = r->start + size - 1; +	r->flags = flags; +	r->name = zdev->res_name; + +	if (request_resource(&iomem_resource, r)) { +		kfree(r); +		return NULL; +	} +	return r; +} + +static int zpci_setup_bus_resources(struct zpci_dev *zdev, +				    struct list_head *resources) +{ +	unsigned long addr, size, flags; +	struct resource *res; +	int i, entry; + +	snprintf(zdev->res_name, sizeof(zdev->res_name), +		 "PCI Bus %04x:%02x", zdev->domain, ZPCI_BUS_NR); + +	for (i = 0; i < PCI_BAR_COUNT; i++) { +		if (!zdev->bars[i].size) +			continue; +		entry = zpci_alloc_iomap(zdev); +		if (entry < 0) +			return entry; +		zdev->bars[i].map_idx = entry; + +		/* only MMIO is supported */ +		flags = IORESOURCE_MEM; +		if (zdev->bars[i].val & 8) +			flags |= IORESOURCE_PREFETCH; +		if (zdev->bars[i].val & 4) +			flags |= IORESOURCE_MEM_64; + +		addr = ZPCI_IOMAP_ADDR_BASE + ((u64) entry << 48); + +		size = 1UL << zdev->bars[i].size; + +		res = __alloc_res(zdev, addr, size, flags); +		if (!res) { +			zpci_free_iomap(zdev, entry); +			return -ENOMEM; +		} +		zdev->bars[i].res = res; +		pci_add_resource(resources, res); +	} + +	return 0; +} + +static void zpci_cleanup_bus_resources(struct zpci_dev *zdev) +{ +	int i; + +	for (i = 0; i < PCI_BAR_COUNT; i++) { +		if (!zdev->bars[i].size) +			continue; + +		zpci_free_iomap(zdev, zdev->bars[i].map_idx); +		release_resource(zdev->bars[i].res); +		kfree(zdev->bars[i].res); +	} +} + +int pcibios_add_device(struct pci_dev *pdev) +{ +	struct zpci_dev *zdev = get_zdev(pdev); +	struct resource *res; +	int i; + +	zdev->pdev = pdev; +	pdev->dev.groups = zpci_attr_groups; +	zpci_map_resources(zdev); + +	for (i = 0; i < PCI_BAR_COUNT; i++) { +		res = &pdev->resource[i]; +		if (res->parent || !res->flags) +			continue; +		pci_claim_resource(pdev, i); +	} + +	return 0; +} + +int pcibios_enable_device(struct pci_dev *pdev, int mask) +{ +	struct zpci_dev *zdev = get_zdev(pdev); + +	zdev->pdev = pdev; +	zpci_debug_init_device(zdev); +	zpci_fmb_enable_device(zdev); +	zpci_map_resources(zdev); + +	return pci_enable_resources(pdev, mask); +} + +void pcibios_disable_device(struct pci_dev *pdev) +{ +	struct zpci_dev *zdev = get_zdev(pdev); + +	zpci_unmap_resources(zdev); +	zpci_fmb_disable_device(zdev); +	zpci_debug_exit_device(zdev); +	zdev->pdev = NULL; +} + +#ifdef CONFIG_HIBERNATE_CALLBACKS +static int zpci_restore(struct device *dev) +{ +	struct zpci_dev *zdev = get_zdev(to_pci_dev(dev)); +	int ret = 0; + +	if (zdev->state != ZPCI_FN_STATE_ONLINE) +		goto out; + +	ret = clp_enable_fh(zdev, ZPCI_NR_DMA_SPACES); +	if (ret) +		goto out; + +	zpci_map_resources(zdev); +	zpci_register_ioat(zdev, 0, zdev->start_dma + PAGE_OFFSET, +			   zdev->start_dma + zdev->iommu_size - 1, +			   (u64) zdev->dma_table); + +out: +	return ret; +} + +static int zpci_freeze(struct device *dev) +{ +	struct zpci_dev *zdev = get_zdev(to_pci_dev(dev)); + +	if (zdev->state != ZPCI_FN_STATE_ONLINE) +		return 0; + +	zpci_unregister_ioat(zdev, 0); +	return clp_disable_fh(zdev); +} + +struct dev_pm_ops pcibios_pm_ops = { +	.thaw_noirq = zpci_restore, +	.freeze_noirq = zpci_freeze, +	.restore_noirq = zpci_restore, +	.poweroff_noirq = zpci_freeze, +}; +#endif /* CONFIG_HIBERNATE_CALLBACKS */ + +static int zpci_alloc_domain(struct zpci_dev *zdev) +{ +	spin_lock(&zpci_domain_lock); +	zdev->domain = find_first_zero_bit(zpci_domain, ZPCI_NR_DEVICES); +	if (zdev->domain == ZPCI_NR_DEVICES) { +		spin_unlock(&zpci_domain_lock); +		return -ENOSPC; +	} +	set_bit(zdev->domain, zpci_domain); +	spin_unlock(&zpci_domain_lock); +	return 0; +} + +static void zpci_free_domain(struct zpci_dev *zdev) +{ +	spin_lock(&zpci_domain_lock); +	clear_bit(zdev->domain, zpci_domain); +	spin_unlock(&zpci_domain_lock); +} + +void pcibios_remove_bus(struct pci_bus *bus) +{ +	struct zpci_dev *zdev = get_zdev_by_bus(bus); + +	zpci_exit_slot(zdev); +	zpci_cleanup_bus_resources(zdev); +	zpci_free_domain(zdev); + +	spin_lock(&zpci_list_lock); +	list_del(&zdev->entry); +	spin_unlock(&zpci_list_lock); + +	kfree(zdev); +} + +static int zpci_scan_bus(struct zpci_dev *zdev) +{ +	LIST_HEAD(resources); +	int ret; + +	ret = zpci_setup_bus_resources(zdev, &resources); +	if (ret) +		return ret; + +	zdev->bus = pci_scan_root_bus(NULL, ZPCI_BUS_NR, &pci_root_ops, +				      zdev, &resources); +	if (!zdev->bus) { +		zpci_cleanup_bus_resources(zdev); +		return -EIO; +	} + +	zdev->bus->max_bus_speed = zdev->max_bus_speed; +	return 0; +} + +int zpci_enable_device(struct zpci_dev *zdev) +{ +	int rc; + +	rc = clp_enable_fh(zdev, ZPCI_NR_DMA_SPACES); +	if (rc) +		goto out; + +	rc = zpci_dma_init_device(zdev); +	if (rc) +		goto out_dma; + +	zdev->state = ZPCI_FN_STATE_ONLINE; +	return 0; + +out_dma: +	clp_disable_fh(zdev); +out: +	return rc; +} +EXPORT_SYMBOL_GPL(zpci_enable_device); + +int zpci_disable_device(struct zpci_dev *zdev) +{ +	zpci_dma_exit_device(zdev); +	return clp_disable_fh(zdev); +} +EXPORT_SYMBOL_GPL(zpci_disable_device); + +int zpci_create_device(struct zpci_dev *zdev) +{ +	int rc; + +	rc = zpci_alloc_domain(zdev); +	if (rc) +		goto out; + +	if (zdev->state == ZPCI_FN_STATE_CONFIGURED) { +		rc = zpci_enable_device(zdev); +		if (rc) +			goto out_free; +	} +	rc = zpci_scan_bus(zdev); +	if (rc) +		goto out_disable; + +	spin_lock(&zpci_list_lock); +	list_add_tail(&zdev->entry, &zpci_list); +	spin_unlock(&zpci_list_lock); + +	zpci_init_slot(zdev); + +	return 0; + +out_disable: +	if (zdev->state == ZPCI_FN_STATE_ONLINE) +		zpci_disable_device(zdev); +out_free: +	zpci_free_domain(zdev); +out: +	return rc; +} + +void zpci_stop_device(struct zpci_dev *zdev) +{ +	zpci_dma_exit_device(zdev); +	/* +	 * Note: SCLP disables fh via set-pci-fn so don't +	 * do that here. +	 */ +} +EXPORT_SYMBOL_GPL(zpci_stop_device); + +static inline int barsize(u8 size) +{ +	return (size) ? (1 << size) >> 10 : 0; +} + +static int zpci_mem_init(void) +{ +	zdev_fmb_cache = kmem_cache_create("PCI_FMB_cache", sizeof(struct zpci_fmb), +				16, 0, NULL); +	if (!zdev_fmb_cache) +		goto error_zdev; + +	/* TODO: use realloc */ +	zpci_iomap_start = kzalloc(ZPCI_IOMAP_MAX_ENTRIES * sizeof(*zpci_iomap_start), +				   GFP_KERNEL); +	if (!zpci_iomap_start) +		goto error_iomap; +	return 0; + +error_iomap: +	kmem_cache_destroy(zdev_fmb_cache); +error_zdev: +	return -ENOMEM; +} + +static void zpci_mem_exit(void) +{ +	kfree(zpci_iomap_start); +	kmem_cache_destroy(zdev_fmb_cache); +} + +static unsigned int s390_pci_probe = 1; +static unsigned int s390_pci_initialized; + +char * __init pcibios_setup(char *str) +{ +	if (!strcmp(str, "off")) { +		s390_pci_probe = 0; +		return NULL; +	} +	return str; +} + +bool zpci_is_enabled(void) +{ +	return s390_pci_initialized; +} + +static int __init pci_base_init(void) +{ +	int rc; + +	if (!s390_pci_probe) +		return 0; + +	if (!test_facility(2) || !test_facility(69) +	    || !test_facility(71) || !test_facility(72)) +		return 0; + +	rc = zpci_debug_init(); +	if (rc) +		goto out; + +	rc = zpci_mem_init(); +	if (rc) +		goto out_mem; + +	rc = zpci_irq_init(); +	if (rc) +		goto out_irq; + +	rc = zpci_dma_init(); +	if (rc) +		goto out_dma; + +	rc = clp_scan_pci_devices(); +	if (rc) +		goto out_find; + +	s390_pci_initialized = 1; +	return 0; + +out_find: +	zpci_dma_exit(); +out_dma: +	zpci_irq_exit(); +out_irq: +	zpci_mem_exit(); +out_mem: +	zpci_debug_exit(); +out: +	return rc; +} +subsys_initcall_sync(pci_base_init); + +void zpci_rescan(void) +{ +	if (zpci_is_enabled()) +		clp_rescan_pci_devices_simple(); +} diff --git a/arch/s390/pci/pci_clp.c b/arch/s390/pci/pci_clp.c new file mode 100644 index 00000000000..96545d7659f --- /dev/null +++ b/arch/s390/pci/pci_clp.c @@ -0,0 +1,392 @@ +/* + * Copyright IBM Corp. 2012 + * + * Author(s): + *   Jan Glauber <jang@linux.vnet.ibm.com> + */ + +#define COMPONENT "zPCI" +#define pr_fmt(fmt) COMPONENT ": " fmt + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/err.h> +#include <linux/delay.h> +#include <linux/pci.h> +#include <asm/pci_debug.h> +#include <asm/pci_clp.h> + +static inline void zpci_err_clp(unsigned int rsp, int rc) +{ +	struct { +		unsigned int rsp; +		int rc; +	} __packed data = {rsp, rc}; + +	zpci_err_hex(&data, sizeof(data)); +} + +/* + * Call Logical Processor + * Retry logic is handled by the caller. + */ +static inline u8 clp_instr(void *data) +{ +	struct { u8 _[CLP_BLK_SIZE]; } *req = data; +	u64 ignored; +	u8 cc; + +	asm volatile ( +		"	.insn	rrf,0xb9a00000,%[ign],%[req],0x0,0x2\n" +		"	ipm	%[cc]\n" +		"	srl	%[cc],28\n" +		: [cc] "=d" (cc), [ign] "=d" (ignored), "+m" (*req) +		: [req] "a" (req) +		: "cc"); +	return cc; +} + +static void *clp_alloc_block(gfp_t gfp_mask) +{ +	return (void *) __get_free_pages(gfp_mask, get_order(CLP_BLK_SIZE)); +} + +static void clp_free_block(void *ptr) +{ +	free_pages((unsigned long) ptr, get_order(CLP_BLK_SIZE)); +} + +static void clp_store_query_pci_fngrp(struct zpci_dev *zdev, +				      struct clp_rsp_query_pci_grp *response) +{ +	zdev->tlb_refresh = response->refresh; +	zdev->dma_mask = response->dasm; +	zdev->msi_addr = response->msia; +	zdev->fmb_update = response->mui; + +	switch (response->version) { +	case 1: +		zdev->max_bus_speed = PCIE_SPEED_5_0GT; +		break; +	default: +		zdev->max_bus_speed = PCI_SPEED_UNKNOWN; +		break; +	} +} + +static int clp_query_pci_fngrp(struct zpci_dev *zdev, u8 pfgid) +{ +	struct clp_req_rsp_query_pci_grp *rrb; +	int rc; + +	rrb = clp_alloc_block(GFP_KERNEL); +	if (!rrb) +		return -ENOMEM; + +	memset(rrb, 0, sizeof(*rrb)); +	rrb->request.hdr.len = sizeof(rrb->request); +	rrb->request.hdr.cmd = CLP_QUERY_PCI_FNGRP; +	rrb->response.hdr.len = sizeof(rrb->response); +	rrb->request.pfgid = pfgid; + +	rc = clp_instr(rrb); +	if (!rc && rrb->response.hdr.rsp == CLP_RC_OK) +		clp_store_query_pci_fngrp(zdev, &rrb->response); +	else { +		zpci_err("Q PCI FGRP:\n"); +		zpci_err_clp(rrb->response.hdr.rsp, rc); +		rc = -EIO; +	} +	clp_free_block(rrb); +	return rc; +} + +static int clp_store_query_pci_fn(struct zpci_dev *zdev, +				  struct clp_rsp_query_pci *response) +{ +	int i; + +	for (i = 0; i < PCI_BAR_COUNT; i++) { +		zdev->bars[i].val = le32_to_cpu(response->bar[i]); +		zdev->bars[i].size = response->bar_size[i]; +	} +	zdev->start_dma = response->sdma; +	zdev->end_dma = response->edma; +	zdev->pchid = response->pchid; +	zdev->pfgid = response->pfgid; +	zdev->pft = response->pft; +	zdev->vfn = response->vfn; +	zdev->uid = response->uid; + +	memcpy(zdev->pfip, response->pfip, sizeof(zdev->pfip)); +	if (response->util_str_avail) { +		memcpy(zdev->util_str, response->util_str, +		       sizeof(zdev->util_str)); +	} + +	return 0; +} + +static int clp_query_pci_fn(struct zpci_dev *zdev, u32 fh) +{ +	struct clp_req_rsp_query_pci *rrb; +	int rc; + +	rrb = clp_alloc_block(GFP_KERNEL); +	if (!rrb) +		return -ENOMEM; + +	memset(rrb, 0, sizeof(*rrb)); +	rrb->request.hdr.len = sizeof(rrb->request); +	rrb->request.hdr.cmd = CLP_QUERY_PCI_FN; +	rrb->response.hdr.len = sizeof(rrb->response); +	rrb->request.fh = fh; + +	rc = clp_instr(rrb); +	if (!rc && rrb->response.hdr.rsp == CLP_RC_OK) { +		rc = clp_store_query_pci_fn(zdev, &rrb->response); +		if (rc) +			goto out; +		if (rrb->response.pfgid) +			rc = clp_query_pci_fngrp(zdev, rrb->response.pfgid); +	} else { +		zpci_err("Q PCI FN:\n"); +		zpci_err_clp(rrb->response.hdr.rsp, rc); +		rc = -EIO; +	} +out: +	clp_free_block(rrb); +	return rc; +} + +int clp_add_pci_device(u32 fid, u32 fh, int configured) +{ +	struct zpci_dev *zdev; +	int rc; + +	zpci_dbg(3, "add fid:%x, fh:%x, c:%d\n", fid, fh, configured); +	zdev = kzalloc(sizeof(*zdev), GFP_KERNEL); +	if (!zdev) +		return -ENOMEM; + +	zdev->fh = fh; +	zdev->fid = fid; + +	/* Query function properties and update zdev */ +	rc = clp_query_pci_fn(zdev, fh); +	if (rc) +		goto error; + +	if (configured) +		zdev->state = ZPCI_FN_STATE_CONFIGURED; +	else +		zdev->state = ZPCI_FN_STATE_STANDBY; + +	rc = zpci_create_device(zdev); +	if (rc) +		goto error; +	return 0; + +error: +	kfree(zdev); +	return rc; +} + +/* + * Enable/Disable a given PCI function defined by its function handle. + */ +static int clp_set_pci_fn(u32 *fh, u8 nr_dma_as, u8 command) +{ +	struct clp_req_rsp_set_pci *rrb; +	int rc, retries = 100; + +	rrb = clp_alloc_block(GFP_KERNEL); +	if (!rrb) +		return -ENOMEM; + +	do { +		memset(rrb, 0, sizeof(*rrb)); +		rrb->request.hdr.len = sizeof(rrb->request); +		rrb->request.hdr.cmd = CLP_SET_PCI_FN; +		rrb->response.hdr.len = sizeof(rrb->response); +		rrb->request.fh = *fh; +		rrb->request.oc = command; +		rrb->request.ndas = nr_dma_as; + +		rc = clp_instr(rrb); +		if (rrb->response.hdr.rsp == CLP_RC_SETPCIFN_BUSY) { +			retries--; +			if (retries < 0) +				break; +			msleep(20); +		} +	} while (rrb->response.hdr.rsp == CLP_RC_SETPCIFN_BUSY); + +	if (!rc && rrb->response.hdr.rsp == CLP_RC_OK) +		*fh = rrb->response.fh; +	else { +		zpci_err("Set PCI FN:\n"); +		zpci_err_clp(rrb->response.hdr.rsp, rc); +		rc = -EIO; +	} +	clp_free_block(rrb); +	return rc; +} + +int clp_enable_fh(struct zpci_dev *zdev, u8 nr_dma_as) +{ +	u32 fh = zdev->fh; +	int rc; + +	rc = clp_set_pci_fn(&fh, nr_dma_as, CLP_SET_ENABLE_PCI_FN); +	if (!rc) +		/* Success -> store enabled handle in zdev */ +		zdev->fh = fh; + +	zpci_dbg(3, "ena fid:%x, fh:%x, rc:%d\n", zdev->fid, zdev->fh, rc); +	return rc; +} + +int clp_disable_fh(struct zpci_dev *zdev) +{ +	u32 fh = zdev->fh; +	int rc; + +	if (!zdev_enabled(zdev)) +		return 0; + +	rc = clp_set_pci_fn(&fh, 0, CLP_SET_DISABLE_PCI_FN); +	if (!rc) +		/* Success -> store disabled handle in zdev */ +		zdev->fh = fh; + +	zpci_dbg(3, "dis fid:%x, fh:%x, rc:%d\n", zdev->fid, zdev->fh, rc); +	return rc; +} + +static int clp_list_pci(struct clp_req_rsp_list_pci *rrb, +			void (*cb)(struct clp_fh_list_entry *entry)) +{ +	u64 resume_token = 0; +	int entries, i, rc; + +	do { +		memset(rrb, 0, sizeof(*rrb)); +		rrb->request.hdr.len = sizeof(rrb->request); +		rrb->request.hdr.cmd = CLP_LIST_PCI; +		/* store as many entries as possible */ +		rrb->response.hdr.len = CLP_BLK_SIZE - LIST_PCI_HDR_LEN; +		rrb->request.resume_token = resume_token; + +		/* Get PCI function handle list */ +		rc = clp_instr(rrb); +		if (rc || rrb->response.hdr.rsp != CLP_RC_OK) { +			zpci_err("List PCI FN:\n"); +			zpci_err_clp(rrb->response.hdr.rsp, rc); +			rc = -EIO; +			goto out; +		} + +		WARN_ON_ONCE(rrb->response.entry_size != +			sizeof(struct clp_fh_list_entry)); + +		entries = (rrb->response.hdr.len - LIST_PCI_HDR_LEN) / +			rrb->response.entry_size; + +		resume_token = rrb->response.resume_token; +		for (i = 0; i < entries; i++) +			cb(&rrb->response.fh_list[i]); +	} while (resume_token); +out: +	return rc; +} + +static void __clp_add(struct clp_fh_list_entry *entry) +{ +	if (!entry->vendor_id) +		return; + +	clp_add_pci_device(entry->fid, entry->fh, entry->config_state); +} + +static void __clp_rescan(struct clp_fh_list_entry *entry) +{ +	struct zpci_dev *zdev; + +	if (!entry->vendor_id) +		return; + +	zdev = get_zdev_by_fid(entry->fid); +	if (!zdev) { +		clp_add_pci_device(entry->fid, entry->fh, entry->config_state); +		return; +	} + +	if (!entry->config_state) { +		/* +		 * The handle is already disabled, that means no iota/irq freeing via +		 * the firmware interfaces anymore. Need to free resources manually +		 * (DMA memory, debug, sysfs)... +		 */ +		zpci_stop_device(zdev); +	} +} + +static void __clp_update(struct clp_fh_list_entry *entry) +{ +	struct zpci_dev *zdev; + +	if (!entry->vendor_id) +		return; + +	zdev = get_zdev_by_fid(entry->fid); +	if (!zdev) +		return; + +	zdev->fh = entry->fh; +} + +int clp_scan_pci_devices(void) +{ +	struct clp_req_rsp_list_pci *rrb; +	int rc; + +	rrb = clp_alloc_block(GFP_KERNEL); +	if (!rrb) +		return -ENOMEM; + +	rc = clp_list_pci(rrb, __clp_add); + +	clp_free_block(rrb); +	return rc; +} + +int clp_rescan_pci_devices(void) +{ +	struct clp_req_rsp_list_pci *rrb; +	int rc; + +	rrb = clp_alloc_block(GFP_KERNEL); +	if (!rrb) +		return -ENOMEM; + +	rc = clp_list_pci(rrb, __clp_rescan); + +	clp_free_block(rrb); +	return rc; +} + +int clp_rescan_pci_devices_simple(void) +{ +	struct clp_req_rsp_list_pci *rrb; +	int rc; + +	rrb = clp_alloc_block(GFP_NOWAIT); +	if (!rrb) +		return -ENOMEM; + +	rc = clp_list_pci(rrb, __clp_update); + +	clp_free_block(rrb); +	return rc; +} diff --git a/arch/s390/pci/pci_debug.c b/arch/s390/pci/pci_debug.c new file mode 100644 index 00000000000..c5c66840ac0 --- /dev/null +++ b/arch/s390/pci/pci_debug.c @@ -0,0 +1,167 @@ +/* + *  Copyright IBM Corp. 2012 + * + *  Author(s): + *    Jan Glauber <jang@linux.vnet.ibm.com> + */ + +#define COMPONENT "zPCI" +#define pr_fmt(fmt) COMPONENT ": " fmt + +#include <linux/kernel.h> +#include <linux/seq_file.h> +#include <linux/debugfs.h> +#include <linux/export.h> +#include <linux/pci.h> +#include <asm/debug.h> + +#include <asm/pci_dma.h> + +static struct dentry *debugfs_root; +debug_info_t *pci_debug_msg_id; +EXPORT_SYMBOL_GPL(pci_debug_msg_id); +debug_info_t *pci_debug_err_id; +EXPORT_SYMBOL_GPL(pci_debug_err_id); + +static char *pci_perf_names[] = { +	/* hardware counters */ +	"Load operations", +	"Store operations", +	"Store block operations", +	"Refresh operations", +	"DMA read bytes", +	"DMA write bytes", +	/* software counters */ +	"Allocated pages", +	"Mapped pages", +	"Unmapped pages", +}; + +static int pci_perf_show(struct seq_file *m, void *v) +{ +	struct zpci_dev *zdev = m->private; +	u64 *stat; +	int i; + +	if (!zdev) +		return 0; +	if (!zdev->fmb) +		return seq_printf(m, "FMB statistics disabled\n"); + +	/* header */ +	seq_printf(m, "FMB @ %p\n", zdev->fmb); +	seq_printf(m, "Update interval: %u ms\n", zdev->fmb_update); +	seq_printf(m, "Samples: %u\n", zdev->fmb->samples); +	seq_printf(m, "Last update TOD: %Lx\n", zdev->fmb->last_update); + +	/* hardware counters */ +	stat = (u64 *) &zdev->fmb->ld_ops; +	for (i = 0; i < 4; i++) +		seq_printf(m, "%26s:\t%llu\n", +			   pci_perf_names[i], *(stat + i)); +	if (zdev->fmb->dma_valid) +		for (i = 4; i < 6; i++) +			seq_printf(m, "%26s:\t%llu\n", +				   pci_perf_names[i], *(stat + i)); +	/* software counters */ +	for (i = 6; i < ARRAY_SIZE(pci_perf_names); i++) +		seq_printf(m, "%26s:\t%llu\n", +			   pci_perf_names[i], +			   atomic64_read((atomic64_t *) (stat + i))); + +	return 0; +} + +static ssize_t pci_perf_seq_write(struct file *file, const char __user *ubuf, +				  size_t count, loff_t *off) +{ +	struct zpci_dev *zdev = ((struct seq_file *) file->private_data)->private; +	unsigned long val; +	int rc; + +	if (!zdev) +		return 0; + +	rc = kstrtoul_from_user(ubuf, count, 10, &val); +	if (rc) +		return rc; + +	switch (val) { +	case 0: +		rc = zpci_fmb_disable_device(zdev); +		if (rc) +			return rc; +		break; +	case 1: +		rc = zpci_fmb_enable_device(zdev); +		if (rc) +			return rc; +		break; +	} +	return count; +} + +static int pci_perf_seq_open(struct inode *inode, struct file *filp) +{ +	return single_open(filp, pci_perf_show, +			   file_inode(filp)->i_private); +} + +static const struct file_operations debugfs_pci_perf_fops = { +	.open	 = pci_perf_seq_open, +	.read	 = seq_read, +	.write	 = pci_perf_seq_write, +	.llseek  = seq_lseek, +	.release = single_release, +}; + +void zpci_debug_init_device(struct zpci_dev *zdev) +{ +	zdev->debugfs_dev = debugfs_create_dir(dev_name(&zdev->pdev->dev), +					       debugfs_root); +	if (IS_ERR(zdev->debugfs_dev)) +		zdev->debugfs_dev = NULL; + +	zdev->debugfs_perf = debugfs_create_file("statistics", +				S_IFREG | S_IRUGO | S_IWUSR, +				zdev->debugfs_dev, zdev, +				&debugfs_pci_perf_fops); +	if (IS_ERR(zdev->debugfs_perf)) +		zdev->debugfs_perf = NULL; +} + +void zpci_debug_exit_device(struct zpci_dev *zdev) +{ +	debugfs_remove(zdev->debugfs_perf); +	debugfs_remove(zdev->debugfs_dev); +} + +int __init zpci_debug_init(void) +{ +	/* event trace buffer */ +	pci_debug_msg_id = debug_register("pci_msg", 8, 1, 8 * sizeof(long)); +	if (!pci_debug_msg_id) +		return -EINVAL; +	debug_register_view(pci_debug_msg_id, &debug_sprintf_view); +	debug_set_level(pci_debug_msg_id, 3); + +	/* error log */ +	pci_debug_err_id = debug_register("pci_error", 2, 1, 16); +	if (!pci_debug_err_id) +		return -EINVAL; +	debug_register_view(pci_debug_err_id, &debug_hex_ascii_view); +	debug_set_level(pci_debug_err_id, 6); + +	debugfs_root = debugfs_create_dir("pci", NULL); +	return 0; +} + +void zpci_debug_exit(void) +{ +	if (pci_debug_msg_id) +		debug_unregister(pci_debug_msg_id); +	if (pci_debug_err_id) +		debug_unregister(pci_debug_err_id); + +	debugfs_remove(debugfs_root); +} diff --git a/arch/s390/pci/pci_dma.c b/arch/s390/pci/pci_dma.c new file mode 100644 index 00000000000..f91c0311980 --- /dev/null +++ b/arch/s390/pci/pci_dma.c @@ -0,0 +1,506 @@ +/* + * Copyright IBM Corp. 2012 + * + * Author(s): + *   Jan Glauber <jang@linux.vnet.ibm.com> + */ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/export.h> +#include <linux/iommu-helper.h> +#include <linux/dma-mapping.h> +#include <linux/vmalloc.h> +#include <linux/pci.h> +#include <asm/pci_dma.h> + +static struct kmem_cache *dma_region_table_cache; +static struct kmem_cache *dma_page_table_cache; + +static unsigned long *dma_alloc_cpu_table(void) +{ +	unsigned long *table, *entry; + +	table = kmem_cache_alloc(dma_region_table_cache, GFP_ATOMIC); +	if (!table) +		return NULL; + +	for (entry = table; entry < table + ZPCI_TABLE_ENTRIES; entry++) +		*entry = ZPCI_TABLE_INVALID | ZPCI_TABLE_PROTECTED; +	return table; +} + +static void dma_free_cpu_table(void *table) +{ +	kmem_cache_free(dma_region_table_cache, table); +} + +static unsigned long *dma_alloc_page_table(void) +{ +	unsigned long *table, *entry; + +	table = kmem_cache_alloc(dma_page_table_cache, GFP_ATOMIC); +	if (!table) +		return NULL; + +	for (entry = table; entry < table + ZPCI_PT_ENTRIES; entry++) +		*entry = ZPCI_PTE_INVALID | ZPCI_TABLE_PROTECTED; +	return table; +} + +static void dma_free_page_table(void *table) +{ +	kmem_cache_free(dma_page_table_cache, table); +} + +static unsigned long *dma_get_seg_table_origin(unsigned long *entry) +{ +	unsigned long *sto; + +	if (reg_entry_isvalid(*entry)) +		sto = get_rt_sto(*entry); +	else { +		sto = dma_alloc_cpu_table(); +		if (!sto) +			return NULL; + +		set_rt_sto(entry, sto); +		validate_rt_entry(entry); +		entry_clr_protected(entry); +	} +	return sto; +} + +static unsigned long *dma_get_page_table_origin(unsigned long *entry) +{ +	unsigned long *pto; + +	if (reg_entry_isvalid(*entry)) +		pto = get_st_pto(*entry); +	else { +		pto = dma_alloc_page_table(); +		if (!pto) +			return NULL; +		set_st_pto(entry, pto); +		validate_st_entry(entry); +		entry_clr_protected(entry); +	} +	return pto; +} + +static unsigned long *dma_walk_cpu_trans(unsigned long *rto, dma_addr_t dma_addr) +{ +	unsigned long *sto, *pto; +	unsigned int rtx, sx, px; + +	rtx = calc_rtx(dma_addr); +	sto = dma_get_seg_table_origin(&rto[rtx]); +	if (!sto) +		return NULL; + +	sx = calc_sx(dma_addr); +	pto = dma_get_page_table_origin(&sto[sx]); +	if (!pto) +		return NULL; + +	px = calc_px(dma_addr); +	return &pto[px]; +} + +static void dma_update_cpu_trans(struct zpci_dev *zdev, void *page_addr, +				 dma_addr_t dma_addr, int flags) +{ +	unsigned long *entry; + +	entry = dma_walk_cpu_trans(zdev->dma_table, dma_addr); +	if (!entry) { +		WARN_ON_ONCE(1); +		return; +	} + +	if (flags & ZPCI_PTE_INVALID) { +		invalidate_pt_entry(entry); +		return; +	} else { +		set_pt_pfaa(entry, page_addr); +		validate_pt_entry(entry); +	} + +	if (flags & ZPCI_TABLE_PROTECTED) +		entry_set_protected(entry); +	else +		entry_clr_protected(entry); +} + +static int dma_update_trans(struct zpci_dev *zdev, unsigned long pa, +			    dma_addr_t dma_addr, size_t size, int flags) +{ +	unsigned int nr_pages = PAGE_ALIGN(size) >> PAGE_SHIFT; +	u8 *page_addr = (u8 *) (pa & PAGE_MASK); +	dma_addr_t start_dma_addr = dma_addr; +	unsigned long irq_flags; +	int i, rc = 0; + +	if (!nr_pages) +		return -EINVAL; + +	spin_lock_irqsave(&zdev->dma_table_lock, irq_flags); +	if (!zdev->dma_table) +		goto no_refresh; + +	for (i = 0; i < nr_pages; i++) { +		dma_update_cpu_trans(zdev, page_addr, dma_addr, flags); +		page_addr += PAGE_SIZE; +		dma_addr += PAGE_SIZE; +	} + +	/* +	 * rpcit is not required to establish new translations when previously +	 * invalid translation-table entries are validated, however it is +	 * required when altering previously valid entries. +	 */ +	if (!zdev->tlb_refresh && +	    ((flags & ZPCI_PTE_VALID_MASK) == ZPCI_PTE_VALID)) +		/* +		 * TODO: also need to check that the old entry is indeed INVALID +		 * and not only for one page but for the whole range... +		 * -> now we WARN_ON in that case but with lazy unmap that +		 * needs to be redone! +		 */ +		goto no_refresh; + +	rc = zpci_refresh_trans((u64) zdev->fh << 32, start_dma_addr, +				nr_pages * PAGE_SIZE); + +no_refresh: +	spin_unlock_irqrestore(&zdev->dma_table_lock, irq_flags); +	return rc; +} + +static void dma_free_seg_table(unsigned long entry) +{ +	unsigned long *sto = get_rt_sto(entry); +	int sx; + +	for (sx = 0; sx < ZPCI_TABLE_ENTRIES; sx++) +		if (reg_entry_isvalid(sto[sx])) +			dma_free_page_table(get_st_pto(sto[sx])); + +	dma_free_cpu_table(sto); +} + +static void dma_cleanup_tables(struct zpci_dev *zdev) +{ +	unsigned long *table; +	int rtx; + +	if (!zdev || !zdev->dma_table) +		return; + +	table = zdev->dma_table; +	for (rtx = 0; rtx < ZPCI_TABLE_ENTRIES; rtx++) +		if (reg_entry_isvalid(table[rtx])) +			dma_free_seg_table(table[rtx]); + +	dma_free_cpu_table(table); +	zdev->dma_table = NULL; +} + +static unsigned long __dma_alloc_iommu(struct zpci_dev *zdev, +				       unsigned long start, int size) +{ +	unsigned long boundary_size; + +	boundary_size = ALIGN(dma_get_seg_boundary(&zdev->pdev->dev) + 1, +			      PAGE_SIZE) >> PAGE_SHIFT; +	return iommu_area_alloc(zdev->iommu_bitmap, zdev->iommu_pages, +				start, size, 0, boundary_size, 0); +} + +static unsigned long dma_alloc_iommu(struct zpci_dev *zdev, int size) +{ +	unsigned long offset, flags; + +	spin_lock_irqsave(&zdev->iommu_bitmap_lock, flags); +	offset = __dma_alloc_iommu(zdev, zdev->next_bit, size); +	if (offset == -1) +		offset = __dma_alloc_iommu(zdev, 0, size); + +	if (offset != -1) { +		zdev->next_bit = offset + size; +		if (zdev->next_bit >= zdev->iommu_pages) +			zdev->next_bit = 0; +	} +	spin_unlock_irqrestore(&zdev->iommu_bitmap_lock, flags); +	return offset; +} + +static void dma_free_iommu(struct zpci_dev *zdev, unsigned long offset, int size) +{ +	unsigned long flags; + +	spin_lock_irqsave(&zdev->iommu_bitmap_lock, flags); +	if (!zdev->iommu_bitmap) +		goto out; +	bitmap_clear(zdev->iommu_bitmap, offset, size); +	if (offset >= zdev->next_bit) +		zdev->next_bit = offset + size; +out: +	spin_unlock_irqrestore(&zdev->iommu_bitmap_lock, flags); +} + +int dma_set_mask(struct device *dev, u64 mask) +{ +	if (!dev->dma_mask || !dma_supported(dev, mask)) +		return -EIO; + +	*dev->dma_mask = mask; +	return 0; +} +EXPORT_SYMBOL_GPL(dma_set_mask); + +static dma_addr_t s390_dma_map_pages(struct device *dev, struct page *page, +				     unsigned long offset, size_t size, +				     enum dma_data_direction direction, +				     struct dma_attrs *attrs) +{ +	struct zpci_dev *zdev = get_zdev(to_pci_dev(dev)); +	unsigned long nr_pages, iommu_page_index; +	unsigned long pa = page_to_phys(page) + offset; +	int flags = ZPCI_PTE_VALID; +	dma_addr_t dma_addr; + +	/* This rounds up number of pages based on size and offset */ +	nr_pages = iommu_num_pages(pa, size, PAGE_SIZE); +	iommu_page_index = dma_alloc_iommu(zdev, nr_pages); +	if (iommu_page_index == -1) +		goto out_err; + +	/* Use rounded up size */ +	size = nr_pages * PAGE_SIZE; + +	dma_addr = zdev->start_dma + iommu_page_index * PAGE_SIZE; +	if (dma_addr + size > zdev->end_dma) +		goto out_free; + +	if (direction == DMA_NONE || direction == DMA_TO_DEVICE) +		flags |= ZPCI_TABLE_PROTECTED; + +	if (!dma_update_trans(zdev, pa, dma_addr, size, flags)) { +		atomic64_add(nr_pages, &zdev->fmb->mapped_pages); +		return dma_addr + (offset & ~PAGE_MASK); +	} + +out_free: +	dma_free_iommu(zdev, iommu_page_index, nr_pages); +out_err: +	zpci_err("map error:\n"); +	zpci_err_hex(&pa, sizeof(pa)); +	return DMA_ERROR_CODE; +} + +static void s390_dma_unmap_pages(struct device *dev, dma_addr_t dma_addr, +				 size_t size, enum dma_data_direction direction, +				 struct dma_attrs *attrs) +{ +	struct zpci_dev *zdev = get_zdev(to_pci_dev(dev)); +	unsigned long iommu_page_index; +	int npages; + +	npages = iommu_num_pages(dma_addr, size, PAGE_SIZE); +	dma_addr = dma_addr & PAGE_MASK; +	if (dma_update_trans(zdev, 0, dma_addr, npages * PAGE_SIZE, +			     ZPCI_TABLE_PROTECTED | ZPCI_PTE_INVALID)) { +		zpci_err("unmap error:\n"); +		zpci_err_hex(&dma_addr, sizeof(dma_addr)); +	} + +	atomic64_add(npages, &zdev->fmb->unmapped_pages); +	iommu_page_index = (dma_addr - zdev->start_dma) >> PAGE_SHIFT; +	dma_free_iommu(zdev, iommu_page_index, npages); +} + +static void *s390_dma_alloc(struct device *dev, size_t size, +			    dma_addr_t *dma_handle, gfp_t flag, +			    struct dma_attrs *attrs) +{ +	struct zpci_dev *zdev = get_zdev(to_pci_dev(dev)); +	struct page *page; +	unsigned long pa; +	dma_addr_t map; + +	size = PAGE_ALIGN(size); +	page = alloc_pages(flag, get_order(size)); +	if (!page) +		return NULL; + +	pa = page_to_phys(page); +	memset((void *) pa, 0, size); + +	map = s390_dma_map_pages(dev, page, pa % PAGE_SIZE, +				 size, DMA_BIDIRECTIONAL, NULL); +	if (dma_mapping_error(dev, map)) { +		free_pages(pa, get_order(size)); +		return NULL; +	} + +	atomic64_add(size / PAGE_SIZE, &zdev->fmb->allocated_pages); +	if (dma_handle) +		*dma_handle = map; +	return (void *) pa; +} + +static void s390_dma_free(struct device *dev, size_t size, +			  void *pa, dma_addr_t dma_handle, +			  struct dma_attrs *attrs) +{ +	struct zpci_dev *zdev = get_zdev(to_pci_dev(dev)); + +	size = PAGE_ALIGN(size); +	atomic64_sub(size / PAGE_SIZE, &zdev->fmb->allocated_pages); +	s390_dma_unmap_pages(dev, dma_handle, size, DMA_BIDIRECTIONAL, NULL); +	free_pages((unsigned long) pa, get_order(size)); +} + +static int s390_dma_map_sg(struct device *dev, struct scatterlist *sg, +			   int nr_elements, enum dma_data_direction dir, +			   struct dma_attrs *attrs) +{ +	int mapped_elements = 0; +	struct scatterlist *s; +	int i; + +	for_each_sg(sg, s, nr_elements, i) { +		struct page *page = sg_page(s); +		s->dma_address = s390_dma_map_pages(dev, page, s->offset, +						    s->length, dir, NULL); +		if (!dma_mapping_error(dev, s->dma_address)) { +			s->dma_length = s->length; +			mapped_elements++; +		} else +			goto unmap; +	} +out: +	return mapped_elements; + +unmap: +	for_each_sg(sg, s, mapped_elements, i) { +		if (s->dma_address) +			s390_dma_unmap_pages(dev, s->dma_address, s->dma_length, +					     dir, NULL); +		s->dma_address = 0; +		s->dma_length = 0; +	} +	mapped_elements = 0; +	goto out; +} + +static void s390_dma_unmap_sg(struct device *dev, struct scatterlist *sg, +			      int nr_elements, enum dma_data_direction dir, +			      struct dma_attrs *attrs) +{ +	struct scatterlist *s; +	int i; + +	for_each_sg(sg, s, nr_elements, i) { +		s390_dma_unmap_pages(dev, s->dma_address, s->dma_length, dir, NULL); +		s->dma_address = 0; +		s->dma_length = 0; +	} +} + +int zpci_dma_init_device(struct zpci_dev *zdev) +{ +	int rc; + +	spin_lock_init(&zdev->iommu_bitmap_lock); +	spin_lock_init(&zdev->dma_table_lock); + +	zdev->dma_table = dma_alloc_cpu_table(); +	if (!zdev->dma_table) { +		rc = -ENOMEM; +		goto out_clean; +	} + +	zdev->iommu_size = (unsigned long) high_memory - PAGE_OFFSET; +	zdev->iommu_pages = zdev->iommu_size >> PAGE_SHIFT; +	zdev->iommu_bitmap = vzalloc(zdev->iommu_pages / 8); +	if (!zdev->iommu_bitmap) { +		rc = -ENOMEM; +		goto out_reg; +	} + +	rc = zpci_register_ioat(zdev, +				0, +				zdev->start_dma + PAGE_OFFSET, +				zdev->start_dma + zdev->iommu_size - 1, +				(u64) zdev->dma_table); +	if (rc) +		goto out_reg; +	return 0; + +out_reg: +	dma_free_cpu_table(zdev->dma_table); +out_clean: +	return rc; +} + +void zpci_dma_exit_device(struct zpci_dev *zdev) +{ +	zpci_unregister_ioat(zdev, 0); +	dma_cleanup_tables(zdev); +	vfree(zdev->iommu_bitmap); +	zdev->iommu_bitmap = NULL; +	zdev->next_bit = 0; +} + +static int __init dma_alloc_cpu_table_caches(void) +{ +	dma_region_table_cache = kmem_cache_create("PCI_DMA_region_tables", +					ZPCI_TABLE_SIZE, ZPCI_TABLE_ALIGN, +					0, NULL); +	if (!dma_region_table_cache) +		return -ENOMEM; + +	dma_page_table_cache = kmem_cache_create("PCI_DMA_page_tables", +					ZPCI_PT_SIZE, ZPCI_PT_ALIGN, +					0, NULL); +	if (!dma_page_table_cache) { +		kmem_cache_destroy(dma_region_table_cache); +		return -ENOMEM; +	} +	return 0; +} + +int __init zpci_dma_init(void) +{ +	return dma_alloc_cpu_table_caches(); +} + +void zpci_dma_exit(void) +{ +	kmem_cache_destroy(dma_page_table_cache); +	kmem_cache_destroy(dma_region_table_cache); +} + +#define PREALLOC_DMA_DEBUG_ENTRIES	(1 << 16) + +static int __init dma_debug_do_init(void) +{ +	dma_debug_init(PREALLOC_DMA_DEBUG_ENTRIES); +	return 0; +} +fs_initcall(dma_debug_do_init); + +struct dma_map_ops s390_dma_ops = { +	.alloc		= s390_dma_alloc, +	.free		= s390_dma_free, +	.map_sg		= s390_dma_map_sg, +	.unmap_sg	= s390_dma_unmap_sg, +	.map_page	= s390_dma_map_pages, +	.unmap_page	= s390_dma_unmap_pages, +	/* if we support direct DMA this must be conditional */ +	.is_phys	= 0, +	/* dma_supported is unconditionally true without a callback */ +}; +EXPORT_SYMBOL_GPL(s390_dma_ops); diff --git a/arch/s390/pci/pci_event.c b/arch/s390/pci/pci_event.c new file mode 100644 index 00000000000..6d7f5a3016c --- /dev/null +++ b/arch/s390/pci/pci_event.c @@ -0,0 +1,136 @@ +/* + *  Copyright IBM Corp. 2012 + * + *  Author(s): + *    Jan Glauber <jang@linux.vnet.ibm.com> + */ + +#define COMPONENT "zPCI" +#define pr_fmt(fmt) COMPONENT ": " fmt + +#include <linux/kernel.h> +#include <linux/pci.h> +#include <asm/pci_debug.h> +#include <asm/sclp.h> + +/* Content Code Description for PCI Function Error */ +struct zpci_ccdf_err { +	u32 reserved1; +	u32 fh;				/* function handle */ +	u32 fid;			/* function id */ +	u32 ett		:  4;		/* expected table type */ +	u32 mvn		: 12;		/* MSI vector number */ +	u32 dmaas	:  8;		/* DMA address space */ +	u32		:  6; +	u32 q		:  1;		/* event qualifier */ +	u32 rw		:  1;		/* read/write */ +	u64 faddr;			/* failing address */ +	u32 reserved3; +	u16 reserved4; +	u16 pec;			/* PCI event code */ +} __packed; + +/* Content Code Description for PCI Function Availability */ +struct zpci_ccdf_avail { +	u32 reserved1; +	u32 fh;				/* function handle */ +	u32 fid;			/* function id */ +	u32 reserved2; +	u32 reserved3; +	u32 reserved4; +	u32 reserved5; +	u16 reserved6; +	u16 pec;			/* PCI event code */ +} __packed; + +static void __zpci_event_error(struct zpci_ccdf_err *ccdf) +{ +	struct zpci_dev *zdev = get_zdev_by_fid(ccdf->fid); + +	zpci_err("error CCDF:\n"); +	zpci_err_hex(ccdf, sizeof(*ccdf)); + +	if (!zdev) +		return; + +	pr_err("%s: Event 0x%x reports an error for PCI function 0x%x\n", +	       pci_name(zdev->pdev), ccdf->pec, ccdf->fid); +} + +void zpci_event_error(void *data) +{ +	if (zpci_is_enabled()) +		__zpci_event_error(data); +} + +static void __zpci_event_availability(struct zpci_ccdf_avail *ccdf) +{ +	struct zpci_dev *zdev = get_zdev_by_fid(ccdf->fid); +	struct pci_dev *pdev = zdev ? zdev->pdev : NULL; +	int ret; + +	pr_info("%s: Event 0x%x reconfigured PCI function 0x%x\n", +		pdev ? pci_name(pdev) : "n/a", ccdf->pec, ccdf->fid); +	zpci_err("avail CCDF:\n"); +	zpci_err_hex(ccdf, sizeof(*ccdf)); + +	switch (ccdf->pec) { +	case 0x0301: /* Standby -> Configured */ +		if (!zdev || zdev->state != ZPCI_FN_STATE_STANDBY) +			break; +		zdev->state = ZPCI_FN_STATE_CONFIGURED; +		zdev->fh = ccdf->fh; +		ret = zpci_enable_device(zdev); +		if (ret) +			break; +		pci_rescan_bus(zdev->bus); +		break; +	case 0x0302: /* Reserved -> Standby */ +		if (!zdev) +			clp_add_pci_device(ccdf->fid, ccdf->fh, 0); +		break; +	case 0x0303: /* Deconfiguration requested */ +		if (pdev) +			pci_stop_and_remove_bus_device(pdev); + +		ret = zpci_disable_device(zdev); +		if (ret) +			break; + +		ret = sclp_pci_deconfigure(zdev->fid); +		zpci_dbg(3, "deconf fid:%x, rc:%d\n", zdev->fid, ret); +		if (!ret) +			zdev->state = ZPCI_FN_STATE_STANDBY; + +		break; +	case 0x0304: /* Configured -> Standby */ +		if (pdev) { +			/* Give the driver a hint that the function is +			 * already unusable. */ +			pdev->error_state = pci_channel_io_perm_failure; +			pci_stop_and_remove_bus_device(pdev); +		} + +		zdev->fh = ccdf->fh; +		zpci_disable_device(zdev); +		zdev->state = ZPCI_FN_STATE_STANDBY; +		break; +	case 0x0306: /* 0x308 or 0x302 for multiple devices */ +		clp_rescan_pci_devices(); +		break; +	case 0x0308: /* Standby -> Reserved */ +		if (!zdev) +			break; +		pci_stop_root_bus(zdev->bus); +		pci_remove_root_bus(zdev->bus); +		break; +	default: +		break; +	} +} + +void zpci_event_availability(void *data) +{ +	if (zpci_is_enabled()) +		__zpci_event_availability(data); +} diff --git a/arch/s390/pci/pci_insn.c b/arch/s390/pci/pci_insn.c new file mode 100644 index 00000000000..85267c058af --- /dev/null +++ b/arch/s390/pci/pci_insn.c @@ -0,0 +1,202 @@ +/* + * s390 specific pci instructions + * + * Copyright IBM Corp. 2013 + */ + +#include <linux/export.h> +#include <linux/errno.h> +#include <linux/delay.h> +#include <asm/pci_insn.h> +#include <asm/processor.h> + +#define ZPCI_INSN_BUSY_DELAY	1	/* 1 microsecond */ + +/* Modify PCI Function Controls */ +static inline u8 __mpcifc(u64 req, struct zpci_fib *fib, u8 *status) +{ +	u8 cc; + +	asm volatile ( +		"	.insn	rxy,0xe300000000d0,%[req],%[fib]\n" +		"	ipm	%[cc]\n" +		"	srl	%[cc],28\n" +		: [cc] "=d" (cc), [req] "+d" (req), [fib] "+Q" (*fib) +		: : "cc"); +	*status = req >> 24 & 0xff; +	return cc; +} + +int zpci_mod_fc(u64 req, struct zpci_fib *fib) +{ +	u8 cc, status; + +	do { +		cc = __mpcifc(req, fib, &status); +		if (cc == 2) +			msleep(ZPCI_INSN_BUSY_DELAY); +	} while (cc == 2); + +	if (cc) +		printk_once(KERN_ERR "%s: error cc: %d  status: %d\n", +			     __func__, cc, status); +	return (cc) ? -EIO : 0; +} + +/* Refresh PCI Translations */ +static inline u8 __rpcit(u64 fn, u64 addr, u64 range, u8 *status) +{ +	register u64 __addr asm("2") = addr; +	register u64 __range asm("3") = range; +	u8 cc; + +	asm volatile ( +		"	.insn	rre,0xb9d30000,%[fn],%[addr]\n" +		"	ipm	%[cc]\n" +		"	srl	%[cc],28\n" +		: [cc] "=d" (cc), [fn] "+d" (fn) +		: [addr] "d" (__addr), "d" (__range) +		: "cc"); +	*status = fn >> 24 & 0xff; +	return cc; +} + +int zpci_refresh_trans(u64 fn, u64 addr, u64 range) +{ +	u8 cc, status; + +	do { +		cc = __rpcit(fn, addr, range, &status); +		if (cc == 2) +			udelay(ZPCI_INSN_BUSY_DELAY); +	} while (cc == 2); + +	if (cc) +		printk_once(KERN_ERR "%s: error cc: %d  status: %d  dma_addr: %Lx  size: %Lx\n", +			    __func__, cc, status, addr, range); +	return (cc) ? -EIO : 0; +} + +/* Set Interruption Controls */ +void zpci_set_irq_ctrl(u16 ctl, char *unused, u8 isc) +{ +	asm volatile ( +		"	.insn	rsy,0xeb00000000d1,%[ctl],%[isc],%[u]\n" +		: : [ctl] "d" (ctl), [isc] "d" (isc << 27), [u] "Q" (*unused)); +} + +/* PCI Load */ +static inline int __pcilg(u64 *data, u64 req, u64 offset, u8 *status) +{ +	register u64 __req asm("2") = req; +	register u64 __offset asm("3") = offset; +	int cc = -ENXIO; +	u64 __data; + +	asm volatile ( +		"	.insn	rre,0xb9d20000,%[data],%[req]\n" +		"0:	ipm	%[cc]\n" +		"	srl	%[cc],28\n" +		"1:\n" +		EX_TABLE(0b, 1b) +		: [cc] "+d" (cc), [data] "=d" (__data), [req] "+d" (__req) +		:  "d" (__offset) +		: "cc"); +	*status = __req >> 24 & 0xff; +	if (!cc) +		*data = __data; + +	return cc; +} + +int zpci_load(u64 *data, u64 req, u64 offset) +{ +	u8 status; +	int cc; + +	do { +		cc = __pcilg(data, req, offset, &status); +		if (cc == 2) +			udelay(ZPCI_INSN_BUSY_DELAY); +	} while (cc == 2); + +	if (cc) +		printk_once(KERN_ERR "%s: error cc: %d  status: %d  req: %Lx  offset: %Lx\n", +			    __func__, cc, status, req, offset); +	return (cc > 0) ? -EIO : cc; +} +EXPORT_SYMBOL_GPL(zpci_load); + +/* PCI Store */ +static inline int __pcistg(u64 data, u64 req, u64 offset, u8 *status) +{ +	register u64 __req asm("2") = req; +	register u64 __offset asm("3") = offset; +	int cc = -ENXIO; + +	asm volatile ( +		"	.insn	rre,0xb9d00000,%[data],%[req]\n" +		"0:	ipm	%[cc]\n" +		"	srl	%[cc],28\n" +		"1:\n" +		EX_TABLE(0b, 1b) +		: [cc] "+d" (cc), [req] "+d" (__req) +		: "d" (__offset), [data] "d" (data) +		: "cc"); +	*status = __req >> 24 & 0xff; +	return cc; +} + +int zpci_store(u64 data, u64 req, u64 offset) +{ +	u8 status; +	int cc; + +	do { +		cc = __pcistg(data, req, offset, &status); +		if (cc == 2) +			udelay(ZPCI_INSN_BUSY_DELAY); +	} while (cc == 2); + +	if (cc) +		printk_once(KERN_ERR "%s: error cc: %d  status: %d  req: %Lx  offset: %Lx\n", +			__func__, cc, status, req, offset); +	return (cc > 0) ? -EIO : cc; +} +EXPORT_SYMBOL_GPL(zpci_store); + +/* PCI Store Block */ +static inline int __pcistb(const u64 *data, u64 req, u64 offset, u8 *status) +{ +	int cc = -ENXIO; + +	asm volatile ( +		"	.insn	rsy,0xeb00000000d0,%[req],%[offset],%[data]\n" +		"0:	ipm	%[cc]\n" +		"	srl	%[cc],28\n" +		"1:\n" +		EX_TABLE(0b, 1b) +		: [cc] "+d" (cc), [req] "+d" (req) +		: [offset] "d" (offset), [data] "Q" (*data) +		: "cc"); +	*status = req >> 24 & 0xff; +	return cc; +} + +int zpci_store_block(const u64 *data, u64 req, u64 offset) +{ +	u8 status; +	int cc; + +	do { +		cc = __pcistb(data, req, offset, &status); +		if (cc == 2) +			udelay(ZPCI_INSN_BUSY_DELAY); +	} while (cc == 2); + +	if (cc) +		printk_once(KERN_ERR "%s: error cc: %d  status: %d  req: %Lx  offset: %Lx\n", +			    __func__, cc, status, req, offset); +	return (cc > 0) ? -EIO : cc; +} +EXPORT_SYMBOL_GPL(zpci_store_block); diff --git a/arch/s390/pci/pci_sysfs.c b/arch/s390/pci/pci_sysfs.c new file mode 100644 index 00000000000..9190214b870 --- /dev/null +++ b/arch/s390/pci/pci_sysfs.c @@ -0,0 +1,110 @@ +/* + * Copyright IBM Corp. 2012 + * + * Author(s): + *   Jan Glauber <jang@linux.vnet.ibm.com> + */ + +#define COMPONENT "zPCI" +#define pr_fmt(fmt) COMPONENT ": " fmt + +#include <linux/kernel.h> +#include <linux/stat.h> +#include <linux/pci.h> + +#define zpci_attr(name, fmt, member)					\ +static ssize_t name##_show(struct device *dev,				\ +			   struct device_attribute *attr, char *buf)	\ +{									\ +	struct zpci_dev *zdev = get_zdev(to_pci_dev(dev));		\ +									\ +	return sprintf(buf, fmt, zdev->member);				\ +}									\ +static DEVICE_ATTR_RO(name) + +zpci_attr(function_id, "0x%08x\n", fid); +zpci_attr(function_handle, "0x%08x\n", fh); +zpci_attr(pchid, "0x%04x\n", pchid); +zpci_attr(pfgid, "0x%02x\n", pfgid); +zpci_attr(vfn, "0x%04x\n", vfn); +zpci_attr(pft, "0x%02x\n", pft); +zpci_attr(uid, "0x%x\n", uid); +zpci_attr(segment0, "0x%02x\n", pfip[0]); +zpci_attr(segment1, "0x%02x\n", pfip[1]); +zpci_attr(segment2, "0x%02x\n", pfip[2]); +zpci_attr(segment3, "0x%02x\n", pfip[3]); + +static ssize_t recover_store(struct device *dev, struct device_attribute *attr, +			     const char *buf, size_t count) +{ +	struct pci_dev *pdev = to_pci_dev(dev); +	struct zpci_dev *zdev = get_zdev(pdev); +	int ret; + +	if (!device_remove_file_self(dev, attr)) +		return count; + +	pci_stop_and_remove_bus_device(pdev); +	ret = zpci_disable_device(zdev); +	if (ret) +		return ret; + +	ret = zpci_enable_device(zdev); +	if (ret) +		return ret; + +	pci_rescan_bus(zdev->bus); +	return count; +} +static DEVICE_ATTR_WO(recover); + +static ssize_t util_string_read(struct file *filp, struct kobject *kobj, +				struct bin_attribute *attr, char *buf, +				loff_t off, size_t count) +{ +	struct device *dev = kobj_to_dev(kobj); +	struct pci_dev *pdev = to_pci_dev(dev); +	struct zpci_dev *zdev = get_zdev(pdev); + +	return memory_read_from_buffer(buf, count, &off, zdev->util_str, +				       sizeof(zdev->util_str)); +} +static BIN_ATTR_RO(util_string, CLP_UTIL_STR_LEN); +static struct bin_attribute *zpci_bin_attrs[] = { +	&bin_attr_util_string, +	NULL, +}; + +static struct attribute *zpci_dev_attrs[] = { +	&dev_attr_function_id.attr, +	&dev_attr_function_handle.attr, +	&dev_attr_pchid.attr, +	&dev_attr_pfgid.attr, +	&dev_attr_pft.attr, +	&dev_attr_vfn.attr, +	&dev_attr_uid.attr, +	&dev_attr_recover.attr, +	NULL, +}; +static struct attribute_group zpci_attr_group = { +	.attrs = zpci_dev_attrs, +	.bin_attrs = zpci_bin_attrs, +}; + +static struct attribute *pfip_attrs[] = { +	&dev_attr_segment0.attr, +	&dev_attr_segment1.attr, +	&dev_attr_segment2.attr, +	&dev_attr_segment3.attr, +	NULL, +}; +static struct attribute_group pfip_attr_group = { +	.name = "pfip", +	.attrs = pfip_attrs, +}; + +const struct attribute_group *zpci_attr_groups[] = { +	&zpci_attr_group, +	&pfip_attr_group, +	NULL, +};  | 
