From: Michael Ellerman <michael@ellerman.id.au>
To: Greg Kroah-Hartman <greg@kroah.com>
Cc: linuxppc-dev@ozlabs.org, Paul Mackerras <paulus@samba.org>,
Olof Johannsson <olof@lixom.net>,
linux-pci@atrey.karlin.mff.cuni.cz
Subject: [PATCH 2/7] Powerpc MSI implementation
Date: Thu, 11 Jan 2007 22:25:19 +1100 [thread overview]
Message-ID: <20070111112503.0CC1BDDF13@ozlabs.org> (raw)
In-Reply-To: <1168514716.63474.857278133999.qpush@cradle>
Powerpc MSI implementation, based on a collection of "ops" callbacks.
We have to take the ops approach to accomodate RTAS, where firmware
handles almost all details of MSI setup/teardown. Bare-metal MSI
can be accomodated also.
See the comments in include/asm-powerpc/msi.h for more info.
Signed-off-by: Michael Ellerman <michael@ellerman.id.au>
---
arch/powerpc/kernel/msi.c | 320 ++++++++++++++++++++++++++++++++++++++++++
include/asm-powerpc/machdep.h | 6
include/asm-powerpc/msi.h | 173 ++++++++++++++++++++++
include/linux/pci.h | 7
4 files changed, 506 insertions(+)
Index: msi/arch/powerpc/kernel/msi.c
===================================================================
--- /dev/null
+++ msi/arch/powerpc/kernel/msi.c
@@ -0,0 +1,320 @@
+/*
+ * Copyright 2006-2007, Michael Ellerman, IBM Corporation.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <asm/msi.h>
+#include <asm/machdep.h>
+
+static struct ppc_msi_ops *get_msi_ops(struct pci_dev *pdev)
+{
+ if (ppc_md.get_msi_ops)
+ return ppc_md.get_msi_ops(pdev);
+
+ return NULL;
+}
+
+/* Activated by pci=nomsi on the command line. */
+static int no_msi;
+
+void pci_no_msi(void)
+{
+ printk(KERN_DEBUG "PCI MSI disabled on command line.\n");
+ no_msi = 1;
+}
+
+
+/* msi_info helpers */
+
+static int alloc_msi_info(struct pci_dev *pdev, int num,
+ struct msix_entry *entries, int type)
+{
+ struct msi_info *info;
+ unsigned int entries_size;
+
+ entries_size = sizeof(struct msix_entry) * num;
+
+ info = kzalloc(sizeof(struct msi_info) + entries_size, GFP_KERNEL);
+ if (!info) {
+ msi_debug("kzalloc failed for %s\n", pci_name(pdev));
+ return -ENOMEM;
+ }
+
+ info->type = type;
+ info->num = num;
+ info->entries = (struct msix_entry *)(info + 1);
+ memcpy(info->entries, entries, entries_size);
+
+ BUG_ON(pdev->msi_info); /* don't leak info structs */
+ pdev->msi_info = info;
+
+ return 0;
+}
+
+static struct msi_info *get_msi_info(struct pci_dev *pdev)
+{
+ return pdev->msi_info;
+}
+
+static void free_msi_info(struct pci_dev *pdev)
+{
+ kfree(pdev->msi_info);
+ pdev->msi_info = NULL;
+}
+
+
+/* Generic helpers */
+
+static int generic_msi_enable(struct pci_dev *pdev, int nvec,
+ struct msix_entry *entries, int type)
+{
+ struct ppc_msi_ops *ops;
+ int i, rc;
+
+ if (no_msi || !pdev || !entries || !nvec || get_msi_info(pdev)) {
+ msi_debug("precondition failed for %p\n", pdev);
+ return -EINVAL;
+ }
+
+ ops = get_msi_ops(pdev);
+ if (!ops) {
+ msi_debug("no ops for %s\n", pci_name(pdev));
+ return -EINVAL;
+ }
+
+ for (i = 0; i < nvec; i++)
+ entries[i].vector = NO_IRQ;
+
+ rc = ops->check(pdev, nvec, entries, type);
+ if (rc) {
+ msi_debug("check failed (%d) for %s\n", rc, pci_name(pdev));
+ return rc;
+ }
+
+ rc = alloc_msi_info(pdev, nvec, entries, type);
+ if (rc)
+ return rc;
+
+ rc = ops->alloc(pdev, nvec, entries, type);
+ if (rc) {
+ msi_debug("alloc failed (%d) for %s\n", rc, pci_name(pdev));
+ goto out_free_info;
+ }
+
+ if (ops->enable) {
+ rc = ops->enable(pdev, nvec, entries, type);
+ if (rc) {
+ msi_debug("enable failed (%d) for %s\n", rc,
+ pci_name(pdev));
+ goto out_ops_free;
+ }
+ }
+
+ pci_intx(pdev, 0);
+
+ return 0;
+
+ out_ops_free:
+ ops->free(pdev, nvec, entries, type);
+ out_free_info:
+ free_msi_info(pdev);
+
+ return rc;
+}
+
+static int generic_msi_disable(struct pci_dev *pdev, int type)
+{
+ struct ppc_msi_ops *ops;
+ struct msi_info *info;
+
+ if (no_msi || !pdev) {
+ msi_debug("precondition failed for %p\n", pdev);
+ return -1;
+ }
+
+ info = get_msi_info(pdev);
+ if (!info) {
+ msi_debug("No info for %s\n", pci_name(pdev));
+ return -1;
+ }
+
+ ops = get_msi_ops(pdev);
+ if (!ops) {
+ msi_debug("no ops for %s\n", pci_name(pdev));
+ return -1;
+ }
+
+ if (ops->disable)
+ ops->disable(pdev, info->num, info->entries, type);
+
+ ops->free(pdev, info->num, info->entries, type);
+
+ pci_intx(pdev, 1);
+
+ return 0;
+}
+
+
+/* MSI */
+
+int pci_enable_msi(struct pci_dev *pdev)
+{
+ struct msix_entry entry;
+ int rc;
+
+ entry.entry = 0;
+
+ rc = generic_msi_enable(pdev, 1, &entry, PCI_CAP_ID_MSI);
+ if (rc)
+ return rc;
+
+ get_msi_info(pdev)->saved_irq = pdev->irq;
+ pdev->irq = entry.vector;
+ pdev->msi_enabled = 1;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(pci_enable_msi);
+
+void pci_disable_msi(struct pci_dev *pdev)
+{
+ if (generic_msi_disable(pdev, PCI_CAP_ID_MSI) != 0)
+ return;
+
+ pdev->irq = get_msi_info(pdev)->saved_irq;
+ free_msi_info(pdev);
+ pdev->msi_enabled = 0;
+}
+EXPORT_SYMBOL_GPL(pci_disable_msi);
+
+
+/* MSI-X */
+
+int pci_enable_msix(struct pci_dev *pdev, struct msix_entry *entries, int nvec)
+{
+ int rc;
+
+ rc = generic_msi_enable(pdev, nvec, entries, PCI_CAP_ID_MSIX);
+ if (rc)
+ return rc;
+
+ pdev->msix_enabled = 1;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(pci_enable_msix);
+
+void pci_disable_msix(struct pci_dev *pdev)
+{
+ if (generic_msi_disable(pdev, PCI_CAP_ID_MSIX) != 0)
+ return;
+
+ free_msi_info(pdev);
+ pdev->msix_enabled = 0;
+}
+EXPORT_SYMBOL_GPL(pci_disable_msix);
+
+
+/* Stubs for now */
+
+void disable_msi_mode(struct pci_dev *dev, int pos, int type)
+{
+ return;
+}
+
+void pci_scan_msi_device(struct pci_dev *dev)
+{
+ return;
+}
+
+void msi_remove_pci_irq_vectors(struct pci_dev* dev)
+{
+ return;
+}
+
+
+/* Bare metal enable/disable */
+
+int msi_raw_enable(struct pci_dev *pdev, int num,
+ struct msix_entry *entries, int type)
+{
+ struct ppc_msi_ops *ops;
+ struct msi_msg msg;
+ int pos;
+ u16 control;
+
+ pos = pci_find_capability(pdev, type);
+ if (!pos) {
+ msi_debug("cap (%d) not found for %s\n", type, pci_name(pdev));
+ return -1;
+ }
+
+ ops = get_msi_ops(pdev);
+ BUG_ON(!ops);
+
+ pci_read_config_word(pdev, pos + PCI_MSI_FLAGS, &control);
+
+ switch (type) {
+ case PCI_CAP_ID_MSI:
+ BUG_ON(!ops->setup_msi_msg);
+
+ ops->setup_msi_msg(pdev, &entries[0], &msg, type);
+
+ pci_write_config_dword(pdev, pos + PCI_MSI_ADDRESS_LO,
+ msg.address_lo);
+
+ if (control & PCI_MSI_FLAGS_64BIT) {
+ pci_write_config_dword(pdev, pos + PCI_MSI_ADDRESS_HI,
+ msg.address_hi);
+ pci_write_config_dword(pdev, pos + PCI_MSI_DATA_64,
+ msg.data);
+ } else {
+ pci_write_config_dword(pdev, pos + PCI_MSI_DATA_32,
+ msg.data);
+ }
+
+ control |= PCI_MSI_FLAGS_ENABLE;
+ break;
+ case PCI_CAP_ID_MSIX:
+ WARN_ON(1); /* XXX implement me */
+ return -1;
+ default:
+ BUG();
+ }
+
+ pci_write_config_word(pdev, pos + PCI_MSI_FLAGS, control);
+
+ return 0;
+}
+
+void msi_raw_disable(struct pci_dev *pdev, int num,
+ struct msix_entry *entries, int type)
+{
+ int pos;
+ u16 control;
+
+ pos = pci_find_capability(pdev, type);
+ BUG_ON(!pos);
+
+ pci_read_config_word(pdev, pos + PCI_MSI_FLAGS, &control);
+
+ switch (type) {
+ case PCI_CAP_ID_MSI:
+ control &= ~PCI_MSI_FLAGS_ENABLE;
+ break;
+ case PCI_CAP_ID_MSIX:
+ control &= ~PCI_MSIX_FLAGS_ENABLE;
+ break;
+ default:
+ BUG();
+ }
+
+ pci_write_config_word(pdev, pos + PCI_MSI_FLAGS, control);
+
+ return;
+}
Index: msi/include/asm-powerpc/machdep.h
===================================================================
--- msi.orig/include/asm-powerpc/machdep.h
+++ msi/include/asm-powerpc/machdep.h
@@ -30,6 +30,9 @@ struct pci_controller;
#ifdef CONFIG_KEXEC
struct kimage;
#endif
+#ifdef CONFIG_PCI_MSI
+struct ppc_msi_ops;
+#endif
#ifdef CONFIG_SMP
struct smp_ops_t {
@@ -111,6 +114,9 @@ struct machdep_calls {
void (*pcibios_fixup)(void);
int (*pci_probe_mode)(struct pci_bus *);
void (*pci_irq_fixup)(struct pci_dev *dev);
+#ifdef CONFIG_PCI_MSI
+ struct ppc_msi_ops* (*get_msi_ops)(struct pci_dev *pdev);
+#endif
/* To setup PHBs when using automatic OF platform driver for PCI */
int (*pci_setup_phb)(struct pci_controller *host);
Index: msi/include/asm-powerpc/msi.h
===================================================================
--- /dev/null
+++ msi/include/asm-powerpc/msi.h
@@ -0,0 +1,173 @@
+/*
+ * Copyright 2006-2007, Michael Ellerman, IBM Corporation.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#ifndef _ASM_POWERPC_MSI_H
+#define _ASM_POWERPC_MSI_H
+
+#ifdef __KERNEL__
+#ifndef __ASSEMBLY__
+
+#include <linux/pci.h>
+#include <linux/msi.h>
+
+/*
+ * MSI and MSI-X although different in some details, are also similar in
+ * many respects, and ultimately achieve the same end. Given that, this code
+ * tries as far as possible to implement both MSI and MSI-X with a minimum
+ * of code duplication. We will use "MSI" to refer to both MSI and MSI-X,
+ * except where it is important to differentiate between the two.
+ *
+ * Enabling MSI for a device can be broken down into:
+ * 1) Checking the device can support the type/number of MSIs requested.
+ * 2) Allocating irqs for the MSIs and setting up the irq_descs.
+ * 3) Writing the appropriate configuration to the device and enabling MSIs.
+ *
+ * To implement that we have the following callbacks:
+ * 1) check(pdev, num, msix_entries, type)
+ * 2) alloc(pdev, num, msix_entries, type)
+ * 3) enable(pdev, num, msix_entries, type)
+ * a) setup_msi_msg(pdev, msix_entry, msi_msg, type)
+ *
+ * We give platforms full control over the enable step. However many
+ * platforms will simply want to program the device using standard PCI
+ * accessors. These platforms can use a generic enable callback and define
+ * a setup_msi_msg() callback which simply fills in the "magic" address and
+ * data values. Other platforms may leave setup_msi_msg() empty.
+ *
+ * Disabling MSI requires:
+ * 1) Disabling MSI on the device.
+ * 2) Freeing the irqs and any associated accounting information.
+ *
+ * Which maps directly to the two callbacks:
+ * 1) disable(pdev, num, msix_entries, type)
+ * 2) free(pdev, num, msix_entries, type)
+ */
+
+struct ppc_msi_ops
+{
+ /* check - Check that the requested MSI allocation is OK.
+ *
+ * @pdev: PCI device structure.
+ * @num: The number of MSIs being requested.
+ * @entries: An array of @num msix_entry structures.
+ * @type: The type, MSI or MSI-X.
+ *
+ * This routine is responsible for checking that the given PCI device
+ * can be allocated the requested type and number of MSIs.
+ *
+ * It is up to this routine to determine if the requested number of
+ * MSIs is valid for the device in question. If the number of MSIs,
+ * or the particular MSI entries, can not be supported for any
+ * reason this routine must return non-zero.
+ *
+ * If the check is succesful this routine must return 0.
+ */
+ int (*check) (struct pci_dev *pdev, int num,
+ struct msix_entry *entries, int type);
+
+ /* alloc - Allocate MSIs for the given device.
+ *
+ * @pdev: PCI device structure.
+ * @num: The number of MSIs being requested.
+ * @entries: An array of @num msix_entry structures.
+ * @type: The type, MSI or MSI-X.
+ *
+ * This routine is responsible for allocating the number of
+ * MSIs to the given PCI device.
+ *
+ * Upon completion there must be @num MSIs assigned to this device,
+ * the "vector" member of each struct msix_entry must be filled in
+ * with the Linux irq number allocated to it. The corresponding
+ * irq_descs must also be setup with an appropriate handler if
+ * required.
+ *
+ * If the allocation completes succesfully this routine must return 0.
+ */
+ int (*alloc) (struct pci_dev *pdev, int num,
+ struct msix_entry *entries, int type);
+
+ /* enable - Enable the MSIs on the given device.
+ *
+ * @pdev: PCI device structure.
+ * @num: The number of MSIs being requested.
+ * @entries: An array of @num msix_entry structures.
+ * @type: The type, MSI or MSI-X.
+ *
+ * This routine enables the MSIs on the given PCI device.
+ *
+ * If the enable completes succesfully this routine must return 0.
+ *
+ * This callback is optional.
+ */
+ int (*enable) (struct pci_dev *pdev, int num,
+ struct msix_entry *entries, int type);
+
+ /* setup_msi_msg - Setup an MSI message for the given device.
+ *
+ * @pdev: PCI device structure.
+ * @entry: The MSI entry to create a msi_msg for.
+ * @msg: Written with the magic address and data.
+ * @type: The type, MSI or MSI-X.
+ *
+ * Returns the "magic address and data" used to trigger the msi.
+ * If the setup is succesful this routine must return 0.
+ *
+ * This callback is optional.
+ */
+ int (*setup_msi_msg) (struct pci_dev *pdev, struct msix_entry *entry,
+ struct msi_msg *msg, int type);
+
+ /* disable - disable the MSI for the given device.
+ *
+ * @pdev: PCI device structure.
+ * @num: The number of MSIs to disable.
+ * @entries: An array of @num msix_entry structures.
+ * @type: The type, MSI or MSI-X.
+ *
+ * This routine should perform the inverse of enable.
+ */
+ void (*disable) (struct pci_dev *pdev, int num,
+ struct msix_entry *entries, int type);
+
+ /* free - free the MSIs assigned to the device.
+ *
+ * @pdev: PCI device structure.
+ * @num: The number of MSIs.
+ * @entries: An array of @num msix_entry structures.
+ * @type: The type, MSI or MSI-X.
+ *
+ * Free all MSIs and associated resources for the device. If any
+ * MSIs have been enabled they will have been disabled already by
+ * the generic code.
+ */
+ void (*free) (struct pci_dev *pdev, int num,
+ struct msix_entry *entries, int type);
+};
+
+
+/* Used by the MSI code to track MSI info for a pci_dev */
+struct msi_info {
+ int type;
+ unsigned int saved_irq;
+ unsigned int num;
+ struct msix_entry *entries;
+ void __iomem *msix_base;
+};
+
+extern int msi_raw_enable(struct pci_dev *pdev, int num,
+ struct msix_entry *entries, int type);
+extern void msi_raw_disable(struct pci_dev *pdev, int num,
+ struct msix_entry *entries, int type);
+
+#define msi_debug(fmt, args...) \
+ pr_debug("MSI:%s:%d: " fmt, __FUNCTION__, __LINE__, ## args)
+
+#endif /* __ASSEMBLY__ */
+#endif /* __KERNEL__ */
+#endif /* _ASM_POWERPC_MSI_H */
Index: msi/include/linux/pci.h
===================================================================
--- msi.orig/include/linux/pci.h
+++ msi/include/linux/pci.h
@@ -107,6 +107,10 @@ struct pci_cap_saved_state {
u32 data[0];
};
+#if defined(CONFIG_PCI_MSI) && defined(CONFIG_PPC_MERGE)
+struct msi_info;
+#endif
+
/*
* The pci_dev structure is used to describe PCI devices.
*/
@@ -174,6 +178,9 @@ struct pci_dev {
struct bin_attribute *rom_attr; /* attribute descriptor for sysfs ROM entry */
int rom_attr_enabled; /* has display of the rom attribute been enabled? */
struct bin_attribute *res_attr[DEVICE_COUNT_RESOURCE]; /* sysfs file for resources */
+#if defined(CONFIG_PCI_MSI) && defined(CONFIG_PPC_MERGE)
+ struct msi_info *msi_info;
+#endif
};
#define pci_dev_g(n) list_entry(n, struct pci_dev, global_list)
next prev parent reply other threads:[~2007-01-11 11:25 UTC|newest]
Thread overview: 28+ messages / expand[flat|nested] mbox.gz Atom feed top
2007-01-11 11:25 [PATCH 0/7] Powerpc MSI Implementation Michael Ellerman
2007-01-11 11:25 ` [PATCH 1/7] Rip out the existing powerpc msi stubs Michael Ellerman
2007-01-11 21:31 ` Christoph Hellwig
2007-01-11 11:25 ` Michael Ellerman [this message]
2007-01-11 19:44 ` [PATCH 2/7] Powerpc MSI implementation Greg KH
2007-01-11 21:20 ` Benjamin Herrenschmidt
2007-01-11 21:54 ` Benjamin Herrenschmidt
2007-01-12 23:13 ` Greg KH
2007-01-13 5:57 ` Benjamin Herrenschmidt
2007-01-13 19:27 ` Greg KH
2007-01-13 20:40 ` Benjamin Herrenschmidt
2007-01-12 23:11 ` Greg KH
2007-01-11 21:36 ` Christoph Hellwig
2007-01-12 7:27 ` Benjamin Herrenschmidt
2007-01-11 11:25 ` [PATCH 3/7] Enable MSI on Powerpc Michael Ellerman
2007-01-11 21:37 ` Christoph Hellwig
2007-01-12 7:24 ` Benjamin Herrenschmidt
2007-01-11 11:25 ` [PATCH 5/7] MPIC MSI backend Michael Ellerman
2007-01-11 11:25 ` [PATCH 4/7] MPIC MSI allocator Michael Ellerman
2007-01-11 15:14 ` Segher Boessenkool
2007-01-11 17:19 ` Will Schmidt
2007-01-11 17:27 ` Segher Boessenkool
2007-01-12 0:27 ` Olof Johansson
2007-01-12 0:33 ` Michael Ellerman
2007-01-11 11:25 ` [PATCH 6/7] Enable MSI mappings for MPIC Michael Ellerman
2007-01-11 15:22 ` Segher Boessenkool
2007-01-11 11:25 ` [PATCH 7/7] Activate MSI for the MPIC backend on U3 Michael Ellerman
2007-01-11 15:23 ` [PATCH 0/7] Powerpc MSI Implementation Segher Boessenkool
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20070111112503.0CC1BDDF13@ozlabs.org \
--to=michael@ellerman.id.au \
--cc=greg@kroah.com \
--cc=linux-pci@atrey.karlin.mff.cuni.cz \
--cc=linuxppc-dev@ozlabs.org \
--cc=olof@lixom.net \
--cc=paulus@samba.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.