PCI_MSI(9)

PCI_MSI(9) | Kernel Developer's Manual | PCI_MSI(9)

# NAME

`pci_msi`, `pci_msix`, `pci_msi_count`, `pci_msi_alloc`, `pci_msi_alloc_exact`, `pci_msix_count`, `pci_msix_alloc`, `pci_msix_alloc_exact`, `pci_msix_alloc_map`, `pci_intx_alloc`, `pci_intr_alloc`, `pci_intr_release`, `pci_intr_type` —
PCI MSI{,-X} manipulation functions

# SYNOPSIS

int
`pci_msi_count`(pci_chipset_tag_t pc, pcitag_t tag);

int
`pci_msi_alloc`(const struct pci_attach_args *pa, pci_intr_handle_t **ihps, int *count);

int
`pci_msi_alloc_exact`(const struct pci_attach_args *pa, pci_intr_handle_t **ihps, int count);

int
`pci_msix_count`(pci_chipset_tag_t pc, pcitag_t tag);

int
`pci_msix_alloc`(const struct pci_attach_args *pa, pci_intr_handle_t **ihps, int *count);

int
`pci_msix_alloc_exact`(const struct pci_attach_args *pa, pci_intr_handle_t **ihps, int count);

int
`pci_msix_alloc_map`(const struct pci_attach_args *pa, pci_intr_handle_t **ihps, u_int *table_indexes, int count);

int
`pci_intx_alloc`(const struct pci_attach_args *pa, pci_intr_handle_t **ihp);

int
`pci_intr_alloc`(const struct pci_attach_args *pa, pci_intr_handle_t **ihp, int *counts, pci_intr_type_t max_type);

void
`pci_intr_release`(pci_chipset_tag_t pc, pci_intr_handle_t *pih, int count);

pci_intr_type_t
`pci_intr_type`(pci_chipset_tag_t pc, pci_intr_handle_t ih);

# DESCRIPTION

The `pci_msi` functions exist to allow device drivers to use MSI/MSI-X. When the system uses MSI/MSI-X, it must define the `__HAVE_PCI_MSI_MSIX` build option.

Each driver has an `attach`() function which has a bus-specific attach_args structure. Each driver for a PCI device is passed a pointer to an object of type struct pci_attach_args which contains, among other things, information about the location of the device in the PCI bus topology sufficient to allow interrupts from the device to be handled.

`pci_msi_count`() returns the max number of the device's MSI. If the device can not use MSI, `pci_msi_count`() returns zero. `pci_msix_count`() works in the same manner for MSI-X.

If a driver wishes to establish an MSI handler for the device, it should pass the struct pci_attach_args * and count `pci_msi_alloc`() or `pci_msi_alloc_exact`() functions, which return zero on success, and nonzero on failure. When the functions are successful, they return the pointer to the allocated handle array in pihs whose size is count or less. The difference between `pci_msi_alloc`() and `pci_msi_alloc_exact`() is whether count can be decremented or not. `pci_msi_alloc`() can decrement count, and which is similar to FreeBSD's `pci_alloc_msi`(). In contrast, `pci_msi_alloc_exact`() can not decrement count.

If the driver wishes to refer to the MSI source in an attach or error message, it should use the value returned by `pci_intr_string`() the same as INTx. The buffer passed to `pci_intr_string`() should be at least `PCI_INTRSTR_LEN` bytes long.

Subsequently, when the driver is prepared to receive MSIs, it should call `pci_intr_establish`() the same as INTx to actually establish the handler; when the device interrupts, intrhand will be called with a single argument intrarg, and will run at the interrupt priority level ipl.

The return value of `pci_intr_establish`() may be saved and passed to `pci_intr_disestablish`() to disable the interrupt handler the same as INTx when the driver is no longer interested in MSIs from the device. After that, the driver should also call `pci_intr_release`() to free resources about MSI as well as INTx and MSI-X. If pih is NULL, `pci_intr_release`() does nothing.

If a driver wishes to establish an MSI-X handler for the device, it is almost the same as MSI. The only differences is `pci_msix_alloc_map`(). This function can assign separate handlers for each MSI-X table entry. I.e., if the driver wants to assign the handlers in the following way:

    	msix_handler0 => MSI-X table index: 4
    	msix_handler1 => MSI-X table index: 5
    	msix_handler2 => MSI-X table index: 0

the driver should set table_indexes this way:

    	table_indexes[0] = 4;
    	table_indexes[1] = 5;
    	table_indexes[2] = 0;

If the driver wants to fall back to INTx, the driver should use `pci_intx_alloc`() and `pci_intr_release`() instead of `pci_intr_map`() to resolve contradiction of the interrupt handler ownership. I.e., `pci_intr_map`() does not have the ownership (the function just calculates value), in contrast, `pci_msi_alloc`() and `pci_msix_alloc`() have (the functions allocate memory for interrupt handlers).

`pci_intr_alloc`() is wrapper function which select and automatically fallback allocation functions according to the argument counts. The elements of counts array means each required interrupt count for INTx, MSI, and MSI-X. The index count of counts must be `PCI_INTR_TYPE_SIZE`. max_type must be `PCI_INTR_TYPE_MSIX`, `PCI_INTR_TYPE_MSI`, or `PCI_INTR_TYPE_INTX`. The parameter does not mean array index counts of counts. The parameter means the interrupt type which `pci_intr_alloc`() tries to allocate first. I.e., if the driver wants to allocate interrupts in the following way:

    	5 MSI-X
    	1 MSI (if MSI-X allocation failed)
    	INTx (if MSI allocation failed either)

the driver should call `pci_intr_alloc`() in the following way:

    	int counts[PCI_INTR_TYPE_SIZE];
    	counts[PCI_INTR_TYPE_MSIX] = 5;
    	counts[PCI_INTR_TYPE_MSI] = 1;
    	counts[PCI_INTR_TYPE_INTX] = 1;
    	error = pci_intr_alloc(pa, ihps, counts,
    			       PCI_INTR_TYPE_MSIX);

If the driver wants to allocate interrupts in the following way:

    	hardware max number MSI-X
    	1 MSI (if MSI-X allocation failed)

that is, the driver does not use INTx, the driver should call `pci_intr_alloc`() in the following way:

    	int counts[PCI_INTR_TYPE_SIZE];
    	counts[PCI_INTR_TYPE_MSIX] = -1; /* -1 means max */
    	counts[PCI_INTR_TYPE_MSI] = 1;
    	counts[PCI_INTR_TYPE_INTX] = 0; /* 0 means not use */
    	error = pci_intr_alloc(pa, ihps, counts,
    			       PCI_INTR_TYPE_MSIX);

If the driver wants to allocate interrupts in the following way:

    	3 MSI
    	INTx (if MSI allocation failed)

that is, the driver does not use MSI-X, the driver should call `pci_intr_alloc`() in the following way:

    	int counts[PCI_INTR_TYPE_SIZE];
    	counts[PCI_INTR_TYPE_MSIX] = 0; /* 0 means not use */
    	counts[PCI_INTR_TYPE_MSI] = 3;
    	counts[PCI_INTR_TYPE_INTX] = 1;
    	error = pci_intr_alloc(pa, ihps, counts,
    			       PCI_INTR_TYPE_MSI);

If the driver wants to allocate interrupts in the following way:

    	1 MSI-X
    	1 MSI
    	INTx (if MSI/MSI-X allocation failed)

that is, general usage, the driver should call simply `pci_intr_alloc`() in the following way:

    	error = pci_intr_alloc(pa, ihps, NULL, 0);

max_type is ignored in this case. `pci_intr_alloc`() returns zero on any allocation function success, and non-zero on all allocation function failures. On success, counts is overwritten by a really allocated count. I.e., if 5 MSI-X is allocated, counts is

    	counts[PCI_INTR_TYPE_MSIX] == 5
    	counts[PCI_INTR_TYPE_MSI] == 0
    	counts[PCI_INTR_TYPE_INTX] == 0

on return.

`pci_intr_type`() returns the interrupt type of ih. The return value is `PCI_INTR_TYPE_MSIX` for MSI-X, `PCI_INTR_TYPE_MSI` for MSI, and `PCI_INTR_TYPE_INTX` for others.

# SEE ALSO

pci_intr(9)

# HISTORY

`pci_msi` support first appeared in NetBSD 8.0. Support is present on i386, amd64 and aarch64 architectures.

# AUTHORS

The `pci_msi` interfaces were designed and implemented by Kengo Nakahara <knakahara@NetBSD.org>.

January 12, 2021 | NetBSD 9.3