Linux Audio

Check our new training course

Embedded Linux Audio

Check our new training course
with Creative Commons CC-BY-SA
lecture materials

Bootlin logo

Elixir Cross Referencer

Loading...
/*
 * 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 const unsigned int msi_hash_bits = 8;
#define MSI_HASH_BUCKETS (1U << msi_hash_bits)
#define msi_hashfn(nr)	hash_long(nr, msi_hash_bits)

static DEFINE_SPINLOCK(msi_map_lock);

struct msi_desc *__irq_get_msi_desc(unsigned int irq)
{
	struct msi_map *map;

	hlist_for_each_entry_rcu(map,
			&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;
	INIT_HLIST_NODE(&map->msi_chain);

	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(MSI_HASH_BUCKETS * sizeof(*msi_hash), GFP_KERNEL);
	if (!msi_hash)
		return -ENOMEM;

	for (i = 0; i < MSI_HASH_BUCKETS; i++)
		INIT_HLIST_HEAD(&msi_hash[i]);
	return 0;
}

void __init zpci_msihash_exit(void)
{
	kfree(msi_hash);
}