PCI MSI: Add support for multiple MSI
Add the new API pci_enable_msi_block() to allow drivers to request multiple MSI and reimplement pci_enable_msi in terms of pci_enable_msi_block. Ensure that the architecture back ends don't have to know about multiple MSI. Signed-off-by: Matthew Wilcox <willy@linux.intel.com> Signed-off-by: Jesse Barnes <jbarnes@virtuousgeek.org>
This commit is contained in:
Родитель
f2440d9acb
Коммит
1c8d7b0a56
|
@ -94,15 +94,48 @@ This function should be called before the driver calls request_irq()
|
||||||
since enabling MSIs disables the pin-based IRQ and the driver will not
|
since enabling MSIs disables the pin-based IRQ and the driver will not
|
||||||
receive interrupts on the old interrupt.
|
receive interrupts on the old interrupt.
|
||||||
|
|
||||||
4.2.2 pci_disable_msi
|
4.2.2 pci_enable_msi_block
|
||||||
|
|
||||||
|
int pci_enable_msi_block(struct pci_dev *dev, int count)
|
||||||
|
|
||||||
|
This variation on the above call allows a device driver to request multiple
|
||||||
|
MSIs. The MSI specification only allows interrupts to be allocated in
|
||||||
|
powers of two, up to a maximum of 2^5 (32).
|
||||||
|
|
||||||
|
If this function returns 0, it has succeeded in allocating at least as many
|
||||||
|
interrupts as the driver requested (it may have allocated more in order
|
||||||
|
to satisfy the power-of-two requirement). In this case, the function
|
||||||
|
enables MSI on this device and updates dev->irq to be the lowest of
|
||||||
|
the new interrupts assigned to it. The other interrupts assigned to
|
||||||
|
the device are in the range dev->irq to dev->irq + count - 1.
|
||||||
|
|
||||||
|
If this function returns a negative number, it indicates an error and
|
||||||
|
the driver should not attempt to request any more MSI interrupts for
|
||||||
|
this device. If this function returns a positive number, it will be
|
||||||
|
less than 'count' and indicate the number of interrupts that could have
|
||||||
|
been allocated. In neither case will the irq value have been
|
||||||
|
updated, nor will the device have been switched into MSI mode.
|
||||||
|
|
||||||
|
The device driver must decide what action to take if
|
||||||
|
pci_enable_msi_block() returns a value less than the number asked for.
|
||||||
|
Some devices can make use of fewer interrupts than the maximum they
|
||||||
|
request; in this case the driver should call pci_enable_msi_block()
|
||||||
|
again. Note that it is not guaranteed to succeed, even when the
|
||||||
|
'count' has been reduced to the value returned from a previous call to
|
||||||
|
pci_enable_msi_block(). This is because there are multiple constraints
|
||||||
|
on the number of vectors that can be allocated; pci_enable_msi_block()
|
||||||
|
will return as soon as it finds any constraint that doesn't allow the
|
||||||
|
call to succeed.
|
||||||
|
|
||||||
|
4.2.3 pci_disable_msi
|
||||||
|
|
||||||
void pci_disable_msi(struct pci_dev *dev)
|
void pci_disable_msi(struct pci_dev *dev)
|
||||||
|
|
||||||
This function should be used to undo the effect of pci_enable_msi().
|
This function should be used to undo the effect of pci_enable_msi() or
|
||||||
Calling it restores dev->irq to the pin-based interrupt number and frees
|
pci_enable_msi_block(). Calling it restores dev->irq to the pin-based
|
||||||
the previously allocated message signaled interrupt(s). The interrupt
|
interrupt number and frees the previously allocated message signaled
|
||||||
may subsequently be assigned to another device, so drivers should not
|
interrupt(s). The interrupt may subsequently be assigned to another
|
||||||
cache the value of dev->irq.
|
device, so drivers should not cache the value of dev->irq.
|
||||||
|
|
||||||
A device driver must always call free_irq() on the interrupt(s)
|
A device driver must always call free_irq() on the interrupt(s)
|
||||||
for which it has called request_irq() before calling this function.
|
for which it has called request_irq() before calling this function.
|
||||||
|
|
|
@ -19,6 +19,10 @@ int arch_msi_check_device(struct pci_dev* dev, int nvec, int type)
|
||||||
return -ENOSYS;
|
return -ENOSYS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* PowerPC doesn't support multiple MSI yet */
|
||||||
|
if (type == PCI_CAP_ID_MSI && nvec > 1)
|
||||||
|
return 1;
|
||||||
|
|
||||||
if (ppc_md.msi_check_device) {
|
if (ppc_md.msi_check_device) {
|
||||||
pr_debug("msi: Using platform check routine.\n");
|
pr_debug("msi: Using platform check routine.\n");
|
||||||
return ppc_md.msi_check_device(dev, nvec, type);
|
return ppc_md.msi_check_device(dev, nvec, type);
|
||||||
|
|
|
@ -3510,6 +3510,10 @@ int arch_setup_msi_irqs(struct pci_dev *dev, int nvec, int type)
|
||||||
int index = 0;
|
int index = 0;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/* x86 doesn't support multiple MSI yet */
|
||||||
|
if (type == PCI_CAP_ID_MSI && nvec > 1)
|
||||||
|
return 1;
|
||||||
|
|
||||||
irq_want = nr_irqs_gsi;
|
irq_want = nr_irqs_gsi;
|
||||||
sub_handle = 0;
|
sub_handle = 0;
|
||||||
list_for_each_entry(msidesc, &dev->msi_list, list) {
|
list_for_each_entry(msidesc, &dev->msi_list, list) {
|
||||||
|
|
|
@ -40,6 +40,13 @@ int arch_setup_msi_irqs(struct pci_dev *dev, int nvec, int type)
|
||||||
struct msi_desc *entry;
|
struct msi_desc *entry;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If an architecture wants to support multiple MSI, it needs to
|
||||||
|
* override arch_setup_msi_irqs()
|
||||||
|
*/
|
||||||
|
if (type == PCI_CAP_ID_MSI && nvec > 1)
|
||||||
|
return 1;
|
||||||
|
|
||||||
list_for_each_entry(entry, &dev->msi_list, list) {
|
list_for_each_entry(entry, &dev->msi_list, list) {
|
||||||
ret = arch_setup_msi_irq(dev, entry);
|
ret = arch_setup_msi_irq(dev, entry);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
|
@ -58,8 +65,12 @@ void arch_teardown_msi_irqs(struct pci_dev *dev)
|
||||||
struct msi_desc *entry;
|
struct msi_desc *entry;
|
||||||
|
|
||||||
list_for_each_entry(entry, &dev->msi_list, list) {
|
list_for_each_entry(entry, &dev->msi_list, list) {
|
||||||
if (entry->irq != 0)
|
int i, nvec;
|
||||||
arch_teardown_msi_irq(entry->irq);
|
if (entry->irq == 0)
|
||||||
|
continue;
|
||||||
|
nvec = 1 << entry->msi_attrib.multiple;
|
||||||
|
for (i = 0; i < nvec; i++)
|
||||||
|
arch_teardown_msi_irq(entry->irq + i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@ -163,7 +174,8 @@ static void msi_set_mask_bit(unsigned irq, u32 flag)
|
||||||
msix_mask_irq(desc, flag);
|
msix_mask_irq(desc, flag);
|
||||||
readl(desc->mask_base); /* Flush write to device */
|
readl(desc->mask_base); /* Flush write to device */
|
||||||
} else {
|
} else {
|
||||||
msi_mask_irq(desc, 1, flag);
|
unsigned offset = irq - desc->dev->irq;
|
||||||
|
msi_mask_irq(desc, 1 << offset, flag << offset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -229,6 +241,12 @@ void write_msi_msg_desc(struct irq_desc *desc, struct msi_msg *msg)
|
||||||
} else {
|
} else {
|
||||||
struct pci_dev *dev = entry->dev;
|
struct pci_dev *dev = entry->dev;
|
||||||
int pos = entry->msi_attrib.pos;
|
int pos = entry->msi_attrib.pos;
|
||||||
|
u16 msgctl;
|
||||||
|
|
||||||
|
pci_read_config_word(dev, msi_control_reg(pos), &msgctl);
|
||||||
|
msgctl &= ~PCI_MSI_FLAGS_QSIZE;
|
||||||
|
msgctl |= entry->msi_attrib.multiple << 4;
|
||||||
|
pci_write_config_word(dev, msi_control_reg(pos), msgctl);
|
||||||
|
|
||||||
pci_write_config_dword(dev, msi_lower_address_reg(pos),
|
pci_write_config_dword(dev, msi_lower_address_reg(pos),
|
||||||
msg->address_lo);
|
msg->address_lo);
|
||||||
|
@ -291,7 +309,7 @@ static void __pci_restore_msi_state(struct pci_dev *dev)
|
||||||
pci_read_config_word(dev, pos + PCI_MSI_FLAGS, &control);
|
pci_read_config_word(dev, pos + PCI_MSI_FLAGS, &control);
|
||||||
msi_mask_irq(entry, msi_capable_mask(control), entry->masked);
|
msi_mask_irq(entry, msi_capable_mask(control), entry->masked);
|
||||||
control &= ~PCI_MSI_FLAGS_QSIZE;
|
control &= ~PCI_MSI_FLAGS_QSIZE;
|
||||||
control |= PCI_MSI_FLAGS_ENABLE;
|
control |= (entry->msi_attrib.multiple << 4) | PCI_MSI_FLAGS_ENABLE;
|
||||||
pci_write_config_word(dev, pos + PCI_MSI_FLAGS, control);
|
pci_write_config_word(dev, pos + PCI_MSI_FLAGS, control);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -332,13 +350,15 @@ EXPORT_SYMBOL_GPL(pci_restore_msi_state);
|
||||||
/**
|
/**
|
||||||
* msi_capability_init - configure device's MSI capability structure
|
* msi_capability_init - configure device's MSI capability structure
|
||||||
* @dev: pointer to the pci_dev data structure of MSI device function
|
* @dev: pointer to the pci_dev data structure of MSI device function
|
||||||
|
* @nvec: number of interrupts to allocate
|
||||||
*
|
*
|
||||||
* Setup the MSI capability structure of device function with a single
|
* Setup the MSI capability structure of the device with the requested
|
||||||
* MSI irq, regardless of device function is capable of handling
|
* number of interrupts. A return value of zero indicates the successful
|
||||||
* multiple messages. A return of zero indicates the successful setup
|
* setup of an entry with the new MSI irq. A negative return value indicates
|
||||||
* of an entry zero with the new MSI irq or non-zero for otherwise.
|
* an error, and a positive return value indicates the number of interrupts
|
||||||
**/
|
* which could have been allocated.
|
||||||
static int msi_capability_init(struct pci_dev *dev)
|
*/
|
||||||
|
static int msi_capability_init(struct pci_dev *dev, int nvec)
|
||||||
{
|
{
|
||||||
struct msi_desc *entry;
|
struct msi_desc *entry;
|
||||||
int pos, ret;
|
int pos, ret;
|
||||||
|
@ -371,7 +391,7 @@ static int msi_capability_init(struct pci_dev *dev)
|
||||||
list_add_tail(&entry->list, &dev->msi_list);
|
list_add_tail(&entry->list, &dev->msi_list);
|
||||||
|
|
||||||
/* Configure MSI capability structure */
|
/* Configure MSI capability structure */
|
||||||
ret = arch_setup_msi_irqs(dev, 1, PCI_CAP_ID_MSI);
|
ret = arch_setup_msi_irqs(dev, nvec, PCI_CAP_ID_MSI);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
msi_free_irqs(dev);
|
msi_free_irqs(dev);
|
||||||
return ret;
|
return ret;
|
||||||
|
@ -524,35 +544,48 @@ static int pci_msi_check_device(struct pci_dev* dev, int nvec, int type)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* pci_enable_msi - configure device's MSI capability structure
|
* pci_enable_msi_block - configure device's MSI capability structure
|
||||||
* @dev: pointer to the pci_dev data structure of MSI device function
|
* @dev: device to configure
|
||||||
|
* @nvec: number of interrupts to configure
|
||||||
*
|
*
|
||||||
* Setup the MSI capability structure of device function with
|
* Allocate IRQs for a device with the MSI capability.
|
||||||
* a single MSI irq upon its software driver call to request for
|
* This function returns a negative errno if an error occurs. If it
|
||||||
* MSI mode enabled on its hardware device function. A return of zero
|
* is unable to allocate the number of interrupts requested, it returns
|
||||||
* indicates the successful setup of an entry zero with the new MSI
|
* the number of interrupts it might be able to allocate. If it successfully
|
||||||
* irq or non-zero for otherwise.
|
* allocates at least the number of interrupts requested, it returns 0 and
|
||||||
**/
|
* updates the @dev's irq member to the lowest new interrupt number; the
|
||||||
int pci_enable_msi(struct pci_dev* dev)
|
* other interrupt numbers allocated to this device are consecutive.
|
||||||
|
*/
|
||||||
|
int pci_enable_msi_block(struct pci_dev *dev, unsigned int nvec)
|
||||||
{
|
{
|
||||||
int status;
|
int status, pos, maxvec;
|
||||||
|
u16 msgctl;
|
||||||
|
|
||||||
status = pci_msi_check_device(dev, 1, PCI_CAP_ID_MSI);
|
pos = pci_find_capability(dev, PCI_CAP_ID_MSI);
|
||||||
|
if (!pos)
|
||||||
|
return -EINVAL;
|
||||||
|
pci_read_config_word(dev, pos + PCI_MSI_FLAGS, &msgctl);
|
||||||
|
maxvec = 1 << ((msgctl & PCI_MSI_FLAGS_QMASK) >> 1);
|
||||||
|
if (nvec > maxvec)
|
||||||
|
return maxvec;
|
||||||
|
|
||||||
|
status = pci_msi_check_device(dev, nvec, PCI_CAP_ID_MSI);
|
||||||
if (status)
|
if (status)
|
||||||
return status;
|
return status;
|
||||||
|
|
||||||
WARN_ON(!!dev->msi_enabled);
|
WARN_ON(!!dev->msi_enabled);
|
||||||
|
|
||||||
/* Check whether driver already requested for MSI-X irqs */
|
/* Check whether driver already requested MSI-X irqs */
|
||||||
if (dev->msix_enabled) {
|
if (dev->msix_enabled) {
|
||||||
dev_info(&dev->dev, "can't enable MSI "
|
dev_info(&dev->dev, "can't enable MSI "
|
||||||
"(MSI-X already enabled)\n");
|
"(MSI-X already enabled)\n");
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
status = msi_capability_init(dev);
|
|
||||||
|
status = msi_capability_init(dev, nvec);
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL(pci_enable_msi);
|
EXPORT_SYMBOL(pci_enable_msi_block);
|
||||||
|
|
||||||
void pci_msi_shutdown(struct pci_dev *dev)
|
void pci_msi_shutdown(struct pci_dev *dev)
|
||||||
{
|
{
|
||||||
|
@ -599,8 +632,12 @@ static int msi_free_irqs(struct pci_dev* dev)
|
||||||
struct msi_desc *entry, *tmp;
|
struct msi_desc *entry, *tmp;
|
||||||
|
|
||||||
list_for_each_entry(entry, &dev->msi_list, list) {
|
list_for_each_entry(entry, &dev->msi_list, list) {
|
||||||
if (entry->irq)
|
int i, nvec;
|
||||||
BUG_ON(irq_has_action(entry->irq));
|
if (!entry->irq)
|
||||||
|
continue;
|
||||||
|
nvec = 1 << entry->msi_attrib.multiple;
|
||||||
|
for (i = 0; i < nvec; i++)
|
||||||
|
BUG_ON(irq_has_action(entry->irq + i));
|
||||||
}
|
}
|
||||||
|
|
||||||
arch_teardown_msi_irqs(dev);
|
arch_teardown_msi_irqs(dev);
|
||||||
|
|
|
@ -20,14 +20,8 @@
|
||||||
#define msi_mask_bits_reg(base, is64bit) \
|
#define msi_mask_bits_reg(base, is64bit) \
|
||||||
( (is64bit == 1) ? base+PCI_MSI_MASK_BIT : base+PCI_MSI_MASK_BIT-4)
|
( (is64bit == 1) ? base+PCI_MSI_MASK_BIT : base+PCI_MSI_MASK_BIT-4)
|
||||||
#define msi_disable(control) control &= ~PCI_MSI_FLAGS_ENABLE
|
#define msi_disable(control) control &= ~PCI_MSI_FLAGS_ENABLE
|
||||||
#define multi_msi_capable(control) \
|
|
||||||
(1 << ((control & PCI_MSI_FLAGS_QMASK) >> 1))
|
|
||||||
#define multi_msi_enable(control, num) \
|
|
||||||
control |= (((num >> 1) << 4) & PCI_MSI_FLAGS_QSIZE);
|
|
||||||
#define is_64bit_address(control) (!!(control & PCI_MSI_FLAGS_64BIT))
|
#define is_64bit_address(control) (!!(control & PCI_MSI_FLAGS_64BIT))
|
||||||
#define is_mask_bit_support(control) (!!(control & PCI_MSI_FLAGS_MASKBIT))
|
#define is_mask_bit_support(control) (!!(control & PCI_MSI_FLAGS_MASKBIT))
|
||||||
#define msi_enable(control, num) multi_msi_enable(control, num); \
|
|
||||||
control |= PCI_MSI_FLAGS_ENABLE
|
|
||||||
|
|
||||||
#define msix_table_offset_reg(base) (base + 0x04)
|
#define msix_table_offset_reg(base) (base + 0x04)
|
||||||
#define msix_pba_offset_reg(base) (base + 0x08)
|
#define msix_pba_offset_reg(base) (base + 0x08)
|
||||||
|
|
|
@ -21,6 +21,7 @@ extern void write_msi_msg(unsigned int irq, struct msi_msg *msg);
|
||||||
struct msi_desc {
|
struct msi_desc {
|
||||||
struct {
|
struct {
|
||||||
__u8 is_msix : 1;
|
__u8 is_msix : 1;
|
||||||
|
__u8 multiple: 3; /* log2 number of messages */
|
||||||
__u8 maskbit : 1; /* mask-pending bit supported ? */
|
__u8 maskbit : 1; /* mask-pending bit supported ? */
|
||||||
__u8 is_64 : 1; /* Address size: 0=32bit 1=64bit */
|
__u8 is_64 : 1; /* Address size: 0=32bit 1=64bit */
|
||||||
__u8 pos; /* Location of the msi capability */
|
__u8 pos; /* Location of the msi capability */
|
||||||
|
|
|
@ -789,7 +789,7 @@ struct msix_entry {
|
||||||
|
|
||||||
|
|
||||||
#ifndef CONFIG_PCI_MSI
|
#ifndef CONFIG_PCI_MSI
|
||||||
static inline int pci_enable_msi(struct pci_dev *dev)
|
static inline int pci_enable_msi_block(struct pci_dev *dev, unsigned int nvec)
|
||||||
{
|
{
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
@ -824,7 +824,7 @@ static inline int pci_msi_enabled(void)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
extern int pci_enable_msi(struct pci_dev *dev);
|
extern int pci_enable_msi_block(struct pci_dev *dev, unsigned int nvec);
|
||||||
extern void pci_msi_shutdown(struct pci_dev *dev);
|
extern void pci_msi_shutdown(struct pci_dev *dev);
|
||||||
extern void pci_disable_msi(struct pci_dev *dev);
|
extern void pci_disable_msi(struct pci_dev *dev);
|
||||||
extern int pci_msix_table_size(struct pci_dev *dev);
|
extern int pci_msix_table_size(struct pci_dev *dev);
|
||||||
|
@ -846,6 +846,8 @@ static inline int pcie_aspm_enabled(void)
|
||||||
extern int pcie_aspm_enabled(void);
|
extern int pcie_aspm_enabled(void);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#define pci_enable_msi(pdev) pci_enable_msi_block(pdev, 1)
|
||||||
|
|
||||||
#ifdef CONFIG_HT_IRQ
|
#ifdef CONFIG_HT_IRQ
|
||||||
/* The functions a driver should call */
|
/* The functions a driver should call */
|
||||||
int ht_create_irq(struct pci_dev *dev, int idx);
|
int ht_create_irq(struct pci_dev *dev, int idx);
|
||||||
|
|
Загрузка…
Ссылка в новой задаче