PCI/MSI: Add pci_enable_msi_range() and pci_enable_msix_range()

This adds pci_enable_msi_range(), which supersedes the pci_enable_msi()
and pci_enable_msi_block() MSI interfaces.

It also adds pci_enable_msix_range(), which supersedes the
pci_enable_msix() MSI-X interface.

The old interfaces have three categories of return values:

    negative: failure; caller should not retry
    positive: failure; value indicates number of interrupts that *could*
	have been allocated, and caller may retry with a smaller request
    zero: success; at least as many interrupts allocated as requested

It is error-prone to handle these three cases correctly in drivers.

The new functions return either a negative error code or a number of
successfully allocated MSI/MSI-X interrupts, which is expected to lead to
clearer device driver code.

pci_enable_msi(), pci_enable_msi_block() and pci_enable_msix() still exist
unchanged, but are deprecated and may be removed after callers are updated.

[bhelgaas: tweak changelog]
Suggested-by: Ben Hutchings <bhutchings@solarflare.com>
Signed-off-by: Alexander Gordeev <agordeev@redhat.com>
Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
Reviewed-by: Tejun Heo <tj@kernel.org>
This commit is contained in:
Alexander Gordeev 2013-12-30 08:28:16 +01:00 committed by Bjorn Helgaas
parent ff1aa430a2
commit 302a2523c2
3 changed files with 273 additions and 74 deletions

View File

@ -82,67 +82,97 @@ Most of the hard work is done for the driver in the PCI layer. It simply
has to request that the PCI layer set up the MSI capability for this has to request that the PCI layer set up the MSI capability for this
device. device.
4.2.1 pci_enable_msi 4.2.1 pci_enable_msi_range
int pci_enable_msi(struct pci_dev *dev) int pci_enable_msi_range(struct pci_dev *dev, int minvec, int maxvec)
A successful call allocates ONE interrupt to the device, regardless This function allows a device driver to request any number of MSI
of how many MSIs the device supports. The device is switched from interrupts within specified range from 'minvec' to 'maxvec'.
pin-based interrupt mode to MSI mode. The dev->irq number is changed
to a new number which represents the message signaled interrupt;
consequently, this function should be called before the driver calls
request_irq(), because an MSI is delivered via a vector that is
different from the vector of a pin-based interrupt.
4.2.2 pci_enable_msi_block If this function returns a positive number it indicates the number of
MSI interrupts that have been successfully allocated. In this case
int pci_enable_msi_block(struct pci_dev *dev, int count) the device is switched from pin-based interrupt mode to MSI mode and
updates dev->irq to be the lowest of the new interrupts assigned to it.
This variation on the above call allows a device driver to request multiple The other interrupts assigned to the device are in the range dev->irq
MSIs. The MSI specification only allows interrupts to be allocated in to dev->irq + returned value - 1. Device driver can use the returned
powers of two, up to a maximum of 2^5 (32). number of successfully allocated MSI interrupts to further allocate
and initialize device resources.
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 If this function returns a negative number, it indicates an error and
the driver should not attempt to request any more MSI interrupts for the driver should not attempt to request any more MSI interrupts for
this device. If this function returns a positive number, it is this device.
less than 'count' and indicates the number of interrupts that could have
been allocated. In neither case is the irq value updated or the device
switched into MSI mode.
The device driver must decide what action to take if This function should be called before the driver calls request_irq(),
pci_enable_msi_block() returns a value less than the number requested. because MSI interrupts are delivered via vectors that are different
For instance, the driver could still make use of fewer interrupts; from the vector of a pin-based interrupt.
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()
returns as soon as it finds any constraint that doesn't allow the
call to succeed.
4.2.3 pci_disable_msi It is ideal if drivers can cope with a variable number of MSI interrupts;
there are many reasons why the platform may not be able to provide the
exact number that a driver asks for.
There could be devices that can not operate with just any number of MSI
interrupts within a range. See chapter 4.3.1.3 to get the idea how to
handle such devices for MSI-X - the same logic applies to MSI.
4.2.1.1 Maximum possible number of MSI interrupts
The typical usage of MSI interrupts is to allocate as many vectors as
possible, likely up to the limit returned by pci_msi_vec_count() function:
static int foo_driver_enable_msi(struct pci_dev *pdev, int nvec)
{
return pci_enable_msi_range(pdev, 1, nvec);
}
Note the value of 'minvec' parameter is 1. As 'minvec' is inclusive,
the value of 0 would be meaningless and could result in error.
Some devices have a minimal limit on number of MSI interrupts.
In this case the function could look like this:
static int foo_driver_enable_msi(struct pci_dev *pdev, int nvec)
{
return pci_enable_msi_range(pdev, FOO_DRIVER_MINIMUM_NVEC, nvec);
}
4.2.1.2 Exact number of MSI interrupts
If a driver is unable or unwilling to deal with a variable number of MSI
interrupts it could request a particular number of interrupts by passing
that number to pci_enable_msi_range() function as both 'minvec' and 'maxvec'
parameters:
static int foo_driver_enable_msi(struct pci_dev *pdev, int nvec)
{
return pci_enable_msi_range(pdev, nvec, nvec);
}
4.2.1.3 Single MSI mode
The most notorious example of the request type described above is
enabling the single MSI mode for a device. It could be done by passing
two 1s as 'minvec' and 'maxvec':
static int foo_driver_enable_single_msi(struct pci_dev *pdev)
{
return pci_enable_msi_range(pdev, 1, 1);
}
4.2.2 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() or This function should be used to undo the effect of pci_enable_msi_range().
pci_enable_msi_block(). Calling it restores dev->irq to the pin-based Calling it restores dev->irq to the pin-based interrupt number and frees
interrupt number and frees the previously allocated message signaled the previously allocated MSIs. The interrupts may subsequently be assigned
interrupt(s). The interrupt may subsequently be assigned to another to another device, so drivers should not cache the value of dev->irq.
device, so drivers should not cache the value of dev->irq.
Before calling this function, a device driver must always call free_irq() Before calling this function, a device driver must always call free_irq()
on any interrupt for which it previously called request_irq(). on any interrupt for which it previously called request_irq().
Failure to do so results in a BUG_ON(), leaving the device with Failure to do so results in a BUG_ON(), leaving the device with
MSI enabled and thus leaking its vector. MSI enabled and thus leaking its vector.
4.2.4 pci_msi_vec_count 4.2.3 pci_msi_vec_count
int pci_msi_vec_count(struct pci_dev *dev) int pci_msi_vec_count(struct pci_dev *dev)
@ -176,26 +206,31 @@ in each element of the array to indicate for which entries the kernel
should assign interrupts; it is invalid to fill in two entries with the should assign interrupts; it is invalid to fill in two entries with the
same number. same number.
4.3.1 pci_enable_msix 4.3.1 pci_enable_msix_range
int pci_enable_msix(struct pci_dev *dev, struct msix_entry *entries, int nvec) int pci_enable_msix_range(struct pci_dev *dev, struct msix_entry *entries,
int minvec, int maxvec)
Calling this function asks the PCI subsystem to allocate 'nvec' MSIs. Calling this function asks the PCI subsystem to allocate any number of
MSI-X interrupts within specified range from 'minvec' to 'maxvec'.
The 'entries' argument is a pointer to an array of msix_entry structs The 'entries' argument is a pointer to an array of msix_entry structs
which should be at least 'nvec' entries in size. On success, the which should be at least 'maxvec' entries in size.
device is switched into MSI-X mode and the function returns 0.
The 'vector' member in each entry is populated with the interrupt number; On success, the device is switched into MSI-X mode and the function
returns the number of MSI-X interrupts that have been successfully
allocated. In this case the 'vector' member in entries numbered from
0 to the returned value - 1 is populated with the interrupt number;
the driver should then call request_irq() for each 'vector' that it the driver should then call request_irq() for each 'vector' that it
decides to use. The device driver is responsible for keeping track of the decides to use. The device driver is responsible for keeping track of the
interrupts assigned to the MSI-X vectors so it can free them again later. interrupts assigned to the MSI-X vectors so it can free them again later.
Device driver can use the returned number of successfully allocated MSI-X
interrupts to further allocate and initialize device resources.
If this function returns a negative number, it indicates an error and If this function returns a negative number, it indicates an error and
the driver should not attempt to allocate any more MSI-X interrupts for the driver should not attempt to allocate any more MSI-X interrupts for
this device. If it returns a positive number, it indicates the maximum this device.
number of interrupt vectors that could have been allocated. See example
below.
This function, in contrast with pci_enable_msi(), does not adjust This function, in contrast with pci_enable_msi_range(), does not adjust
dev->irq. The device will not generate interrupts for this interrupt dev->irq. The device will not generate interrupts for this interrupt
number once MSI-X is enabled. number once MSI-X is enabled.
@ -206,28 +241,103 @@ It is ideal if drivers can cope with a variable number of MSI-X interrupts;
there are many reasons why the platform may not be able to provide the there are many reasons why the platform may not be able to provide the
exact number that a driver asks for. exact number that a driver asks for.
A request loop to achieve that might look like: There could be devices that can not operate with just any number of MSI-X
interrupts within a range. E.g., an network adapter might need let's say
four vectors per each queue it provides. Therefore, a number of MSI-X
interrupts allocated should be a multiple of four. In this case interface
pci_enable_msix_range() can not be used alone to request MSI-X interrupts
(since it can allocate any number within the range, without any notion of
the multiple of four) and the device driver should master a custom logic
to request the required number of MSI-X interrupts.
4.3.1.1 Maximum possible number of MSI-X interrupts
The typical usage of MSI-X interrupts is to allocate as many vectors as
possible, likely up to the limit returned by pci_msix_vec_count() function:
static int foo_driver_enable_msix(struct foo_adapter *adapter, int nvec) static int foo_driver_enable_msix(struct foo_adapter *adapter, int nvec)
{ {
while (nvec >= FOO_DRIVER_MINIMUM_NVEC) { return pci_enable_msi_range(adapter->pdev, adapter->msix_entries,
rc = pci_enable_msix(adapter->pdev, 1, nvec);
adapter->msix_entries, nvec); }
if (rc > 0)
nvec = rc; Note the value of 'minvec' parameter is 1. As 'minvec' is inclusive,
else the value of 0 would be meaningless and could result in error.
return rc;
Some devices have a minimal limit on number of MSI-X interrupts.
In this case the function could look like this:
static int foo_driver_enable_msix(struct foo_adapter *adapter, int nvec)
{
return pci_enable_msi_range(adapter->pdev, adapter->msix_entries,
FOO_DRIVER_MINIMUM_NVEC, nvec);
}
4.3.1.2 Exact number of MSI-X interrupts
If a driver is unable or unwilling to deal with a variable number of MSI-X
interrupts it could request a particular number of interrupts by passing
that number to pci_enable_msix_range() function as both 'minvec' and 'maxvec'
parameters:
static int foo_driver_enable_msix(struct foo_adapter *adapter, int nvec)
{
return pci_enable_msi_range(adapter->pdev, adapter->msix_entries,
nvec, nvec);
}
4.3.1.3 Specific requirements to the number of MSI-X interrupts
As noted above, there could be devices that can not operate with just any
number of MSI-X interrupts within a range. E.g., let's assume a device that
is only capable sending the number of MSI-X interrupts which is a power of
two. A routine that enables MSI-X mode for such device might look like this:
/*
* Assume 'minvec' and 'maxvec' are non-zero
*/
static int foo_driver_enable_msix(struct foo_adapter *adapter,
int minvec, int maxvec)
{
int rc;
minvec = roundup_pow_of_two(minvec);
maxvec = rounddown_pow_of_two(maxvec);
if (minvec > maxvec)
return -ERANGE;
retry:
rc = pci_enable_msix_range(adapter->pdev, adapter->msix_entries,
maxvec, maxvec);
/*
* -ENOSPC is the only error code allowed to be analized
*/
if (rc == -ENOSPC) {
if (maxvec == 1)
return -ENOSPC;
maxvec /= 2;
if (minvec > maxvec)
return -ENOSPC;
goto retry;
} }
return -ENOSPC; return rc;
} }
Note how pci_enable_msix_range() return value is analized for a fallback -
any error code other than -ENOSPC indicates a fatal error and should not
be retried.
4.3.2 pci_disable_msix 4.3.2 pci_disable_msix
void pci_disable_msix(struct pci_dev *dev) void pci_disable_msix(struct pci_dev *dev)
This function should be used to undo the effect of pci_enable_msix(). It frees This function should be used to undo the effect of pci_enable_msix_range().
the previously allocated message signaled interrupts. The interrupts may It frees the previously allocated MSI-X interrupts. The interrupts may
subsequently be assigned to another device, so drivers should not cache subsequently be assigned to another device, so drivers should not cache
the value of the 'vector' elements over a call to pci_disable_msix(). the value of the 'vector' elements over a call to pci_disable_msix().
@ -261,13 +371,14 @@ number of MSI-X interrupt vectors that could be allocated.
If a device implements both MSI and MSI-X capabilities, it can If a device implements both MSI and MSI-X capabilities, it can
run in either MSI mode or MSI-X mode, but not both simultaneously. run in either MSI mode or MSI-X mode, but not both simultaneously.
This is a requirement of the PCI spec, and it is enforced by the This is a requirement of the PCI spec, and it is enforced by the
PCI layer. Calling pci_enable_msi() when MSI-X is already enabled or PCI layer. Calling pci_enable_msi_range() when MSI-X is already
pci_enable_msix() when MSI is already enabled results in an error. enabled or pci_enable_msix_range() when MSI is already enabled
If a device driver wishes to switch between MSI and MSI-X at runtime, results in an error. If a device driver wishes to switch between MSI
it must first quiesce the device, then switch it back to pin-interrupt and MSI-X at runtime, it must first quiesce the device, then switch
mode, before calling pci_enable_msi() or pci_enable_msix() and resuming it back to pin-interrupt mode, before calling pci_enable_msi_range()
operation. This is not expected to be a common operation but may be or pci_enable_msix_range() and resuming operation. This is not expected
useful for debugging or testing during development. to be a common operation but may be useful for debugging or testing
during development.
4.5 Considerations when using MSIs 4.5 Considerations when using MSIs
@ -382,5 +493,5 @@ or disabled (0). If 0 is found in any of the msi_bus files belonging
to bridges between the PCI root and the device, MSIs are disabled. to bridges between the PCI root and the device, MSIs are disabled.
It is also worth checking the device driver to see whether it supports MSIs. It is also worth checking the device driver to see whether it supports MSIs.
For example, it may contain calls to pci_enable_msi(), pci_enable_msix() or For example, it may contain calls to pci_enable_msi_range() or
pci_enable_msi_block(). pci_enable_msix_range().

View File

@ -1102,3 +1102,77 @@ void pci_msi_init_pci_dev(struct pci_dev *dev)
if (dev->msix_cap) if (dev->msix_cap)
msix_set_enable(dev, 0); msix_set_enable(dev, 0);
} }
/**
* pci_enable_msi_range - configure device's MSI capability structure
* @dev: device to configure
* @minvec: minimal number of interrupts to configure
* @maxvec: maximum number of interrupts to configure
*
* This function tries to allocate a maximum possible number of interrupts in a
* range between @minvec and @maxvec. It returns a negative errno if an error
* occurs. If it succeeds, it returns the actual number of interrupts allocated
* and updates the @dev's irq member to the lowest new interrupt number;
* the other interrupt numbers allocated to this device are consecutive.
**/
int pci_enable_msi_range(struct pci_dev *dev, int minvec, int maxvec)
{
int nvec = maxvec;
int rc;
if (maxvec < minvec)
return -ERANGE;
do {
rc = pci_enable_msi_block(dev, nvec);
if (rc < 0) {
return rc;
} else if (rc > 0) {
if (rc < minvec)
return -ENOSPC;
nvec = rc;
}
} while (rc);
return nvec;
}
EXPORT_SYMBOL(pci_enable_msi_range);
/**
* pci_enable_msix_range - configure device's MSI-X capability structure
* @dev: pointer to the pci_dev data structure of MSI-X device function
* @entries: pointer to an array of MSI-X entries
* @minvec: minimum number of MSI-X irqs requested
* @maxvec: maximum number of MSI-X irqs requested
*
* Setup the MSI-X capability structure of device function with a maximum
* possible number of interrupts in the range between @minvec and @maxvec
* upon its software driver call to request for MSI-X mode enabled on its
* hardware device function. It returns a negative errno if an error occurs.
* If it succeeds, it returns the actual number of interrupts allocated and
* indicates the successful configuration of MSI-X capability structure
* with new allocated MSI-X interrupts.
**/
int pci_enable_msix_range(struct pci_dev *dev, struct msix_entry *entries,
int minvec, int maxvec)
{
int nvec = maxvec;
int rc;
if (maxvec < minvec)
return -ERANGE;
do {
rc = pci_enable_msix(dev, entries, nvec);
if (rc < 0) {
return rc;
} else if (rc > 0) {
if (rc < minvec)
return -ENOSPC;
nvec = rc;
}
} while (rc);
return nvec;
}
EXPORT_SYMBOL(pci_enable_msix_range);

View File

@ -1193,6 +1193,17 @@ static inline int pci_msi_enabled(void)
{ {
return 0; return 0;
} }
static inline int pci_enable_msi_range(struct pci_dev *dev, int minvec,
int maxvec)
{
return -ENOSYS;
}
static inline int pci_enable_msix_range(struct pci_dev *dev,
struct msix_entry *entries, int minvec, int maxvec)
{
return -ENOSYS;
}
#else #else
int pci_msi_vec_count(struct pci_dev *dev); int pci_msi_vec_count(struct pci_dev *dev);
int pci_enable_msi_block(struct pci_dev *dev, int nvec); int pci_enable_msi_block(struct pci_dev *dev, int nvec);
@ -1205,6 +1216,9 @@ void pci_disable_msix(struct pci_dev *dev);
void msi_remove_pci_irq_vectors(struct pci_dev *dev); void msi_remove_pci_irq_vectors(struct pci_dev *dev);
void pci_restore_msi_state(struct pci_dev *dev); void pci_restore_msi_state(struct pci_dev *dev);
int pci_msi_enabled(void); int pci_msi_enabled(void);
int pci_enable_msi_range(struct pci_dev *dev, int minvec, int maxvec);
int pci_enable_msix_range(struct pci_dev *dev, struct msix_entry *entries,
int minvec, int maxvec);
#endif #endif
#ifdef CONFIG_PCIEPORTBUS #ifdef CONFIG_PCIEPORTBUS