diff options
Diffstat (limited to 'arch/s390/pci/pci_msi.c')
-rw-r--r-- | arch/s390/pci/pci_msi.c | 141 |
1 files changed, 141 insertions, 0 deletions
diff --git a/arch/s390/pci/pci_msi.c b/arch/s390/pci/pci_msi.c new file mode 100644 index 000000000000..90fd3482b9e2 --- /dev/null +++ b/arch/s390/pci/pci_msi.c @@ -0,0 +1,141 @@ +/* + * 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/err.h> +#include <linux/rculist.h> +#include <linux/hash.h> +#include <linux/pci.h> +#include <linux/msi.h> +#include <asm/hw_irq.h> + +/* mapping of irq numbers to msi_desc */ +static struct hlist_head *msi_hash; +static unsigned int msihash_shift = 6; +#define msi_hashfn(nr) hash_long(nr, msihash_shift) + +static DEFINE_SPINLOCK(msi_map_lock); + +struct msi_desc *__irq_get_msi_desc(unsigned int irq) +{ + struct hlist_node *entry; + struct msi_map *map; + + hlist_for_each_entry_rcu(map, entry, + &msi_hash[msi_hashfn(irq)], msi_chain) + if (map->irq == irq) + return map->msi; + return NULL; +} + +int zpci_msi_set_mask_bits(struct msi_desc *msi, u32 mask, u32 flag) +{ + if (msi->msi_attrib.is_msix) { + int offset = msi->msi_attrib.entry_nr * PCI_MSIX_ENTRY_SIZE + + PCI_MSIX_ENTRY_VECTOR_CTRL; + msi->masked = readl(msi->mask_base + offset); + writel(flag, msi->mask_base + offset); + } else { + if (msi->msi_attrib.maskbit) { + int pos; + u32 mask_bits; + + pos = (long) msi->mask_base; + pci_read_config_dword(msi->dev, pos, &mask_bits); + mask_bits &= ~(mask); + mask_bits |= flag & mask; + pci_write_config_dword(msi->dev, pos, mask_bits); + } else { + return 0; + } + } + + msi->msi_attrib.maskbit = !!flag; + return 1; +} + +int zpci_setup_msi_irq(struct zpci_dev *zdev, struct msi_desc *msi, + unsigned int nr, int offset) +{ + struct msi_map *map; + struct msi_msg msg; + int rc; + + map = kmalloc(sizeof(*map), GFP_KERNEL); + if (map == NULL) + return -ENOMEM; + + map->irq = nr; + map->msi = msi; + zdev->msi_map[nr & ZPCI_MSI_MASK] = map; + + pr_debug("%s hashing irq: %u to bucket nr: %llu\n", + __func__, nr, msi_hashfn(nr)); + hlist_add_head_rcu(&map->msi_chain, &msi_hash[msi_hashfn(nr)]); + + spin_lock(&msi_map_lock); + rc = irq_set_msi_desc(nr, msi); + if (rc) { + spin_unlock(&msi_map_lock); + hlist_del_rcu(&map->msi_chain); + kfree(map); + zdev->msi_map[nr & ZPCI_MSI_MASK] = NULL; + return rc; + } + spin_unlock(&msi_map_lock); + + msg.data = nr - offset; + msg.address_lo = zdev->msi_addr & 0xffffffff; + msg.address_hi = zdev->msi_addr >> 32; + write_msi_msg(nr, &msg); + return 0; +} + +void zpci_teardown_msi_irq(struct zpci_dev *zdev, struct msi_desc *msi) +{ + int irq = msi->irq & ZPCI_MSI_MASK; + struct msi_map *map; + + msi->msg.address_lo = 0; + msi->msg.address_hi = 0; + msi->msg.data = 0; + msi->irq = 0; + zpci_msi_set_mask_bits(msi, 1, 1); + + spin_lock(&msi_map_lock); + map = zdev->msi_map[irq]; + hlist_del_rcu(&map->msi_chain); + kfree(map); + zdev->msi_map[irq] = NULL; + spin_unlock(&msi_map_lock); +} + +/* + * The msi hash table has 256 entries which is good for 4..20 + * devices (a typical device allocates 10 + CPUs MSI's). Maybe make + * the hash table size adjustable later. + */ +int __init zpci_msihash_init(void) +{ + unsigned int i; + + msi_hash = kmalloc(256 * sizeof(*msi_hash), GFP_KERNEL); + if (!msi_hash) + return -ENOMEM; + + for (i = 0; i < (1U << msihash_shift); i++) + INIT_HLIST_HEAD(&msi_hash[i]); + return 0; +} + +void __init zpci_msihash_exit(void) +{ + kfree(msi_hash); +} |