123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442 |
- /*
- * msi.c
- *
- * Copyright (c) 2010 Isaku Yamahata <yamahata at valinux co jp>
- * VA Linux Systems Japan K.K.
- *
- * 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.
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, see <http://www.gnu.org/licenses/>.
- */
- #include "qemu/osdep.h"
- #include "hw/pci/msi.h"
- #include "hw/xen/xen.h"
- #include "qemu/range.h"
- #include "qapi/error.h"
- /* PCI_MSI_ADDRESS_LO */
- #define PCI_MSI_ADDRESS_LO_MASK (~0x3)
- /* If we get rid of cap allocator, we won't need those. */
- #define PCI_MSI_32_SIZEOF 0x0a
- #define PCI_MSI_64_SIZEOF 0x0e
- #define PCI_MSI_32M_SIZEOF 0x14
- #define PCI_MSI_64M_SIZEOF 0x18
- #define PCI_MSI_VECTORS_MAX 32
- /*
- * Flag for interrupt controllers to declare broken MSI/MSI-X support.
- * values: false - broken; true - non-broken.
- *
- * Setting this flag to false will remove MSI/MSI-X capability from all devices.
- *
- * It is preferable for controllers to set this to true (non-broken) even if
- * they do not actually support MSI/MSI-X: guests normally probe the controller
- * type and do not attempt to enable MSI/MSI-X with interrupt controllers not
- * supporting such, so removing the capability is not required, and
- * it seems cleaner to have a given device look the same for all boards.
- *
- * TODO: some existing controllers violate the above rule. Identify and fix them.
- */
- bool msi_nonbroken;
- /* If we get rid of cap allocator, we won't need this. */
- static inline uint8_t msi_cap_sizeof(uint16_t flags)
- {
- switch (flags & (PCI_MSI_FLAGS_MASKBIT | PCI_MSI_FLAGS_64BIT)) {
- case PCI_MSI_FLAGS_MASKBIT | PCI_MSI_FLAGS_64BIT:
- return PCI_MSI_64M_SIZEOF;
- case PCI_MSI_FLAGS_64BIT:
- return PCI_MSI_64_SIZEOF;
- case PCI_MSI_FLAGS_MASKBIT:
- return PCI_MSI_32M_SIZEOF;
- case 0:
- return PCI_MSI_32_SIZEOF;
- default:
- abort();
- break;
- }
- return 0;
- }
- //#define MSI_DEBUG
- #ifdef MSI_DEBUG
- # define MSI_DPRINTF(fmt, ...) \
- fprintf(stderr, "%s:%d " fmt, __func__, __LINE__, ## __VA_ARGS__)
- #else
- # define MSI_DPRINTF(fmt, ...) do { } while (0)
- #endif
- #define MSI_DEV_PRINTF(dev, fmt, ...) \
- MSI_DPRINTF("%s:%x " fmt, (dev)->name, (dev)->devfn, ## __VA_ARGS__)
- static inline unsigned int msi_nr_vectors(uint16_t flags)
- {
- return 1U <<
- ((flags & PCI_MSI_FLAGS_QSIZE) >> ctz32(PCI_MSI_FLAGS_QSIZE));
- }
- static inline uint8_t msi_flags_off(const PCIDevice* dev)
- {
- return dev->msi_cap + PCI_MSI_FLAGS;
- }
- static inline uint8_t msi_address_lo_off(const PCIDevice* dev)
- {
- return dev->msi_cap + PCI_MSI_ADDRESS_LO;
- }
- static inline uint8_t msi_address_hi_off(const PCIDevice* dev)
- {
- return dev->msi_cap + PCI_MSI_ADDRESS_HI;
- }
- static inline uint8_t msi_data_off(const PCIDevice* dev, bool msi64bit)
- {
- return dev->msi_cap + (msi64bit ? PCI_MSI_DATA_64 : PCI_MSI_DATA_32);
- }
- static inline uint8_t msi_mask_off(const PCIDevice* dev, bool msi64bit)
- {
- return dev->msi_cap + (msi64bit ? PCI_MSI_MASK_64 : PCI_MSI_MASK_32);
- }
- static inline uint8_t msi_pending_off(const PCIDevice* dev, bool msi64bit)
- {
- return dev->msi_cap + (msi64bit ? PCI_MSI_PENDING_64 : PCI_MSI_PENDING_32);
- }
- /*
- * Special API for POWER to configure the vectors through
- * a side channel. Should never be used by devices.
- */
- void msi_set_message(PCIDevice *dev, MSIMessage msg)
- {
- uint16_t flags = pci_get_word(dev->config + msi_flags_off(dev));
- bool msi64bit = flags & PCI_MSI_FLAGS_64BIT;
- if (msi64bit) {
- pci_set_quad(dev->config + msi_address_lo_off(dev), msg.address);
- } else {
- pci_set_long(dev->config + msi_address_lo_off(dev), msg.address);
- }
- pci_set_word(dev->config + msi_data_off(dev, msi64bit), msg.data);
- }
- MSIMessage msi_get_message(PCIDevice *dev, unsigned int vector)
- {
- uint16_t flags = pci_get_word(dev->config + msi_flags_off(dev));
- bool msi64bit = flags & PCI_MSI_FLAGS_64BIT;
- unsigned int nr_vectors = msi_nr_vectors(flags);
- MSIMessage msg;
- assert(vector < nr_vectors);
- if (msi64bit) {
- msg.address = pci_get_quad(dev->config + msi_address_lo_off(dev));
- } else {
- msg.address = pci_get_long(dev->config + msi_address_lo_off(dev));
- }
- /* upper bit 31:16 is zero */
- msg.data = pci_get_word(dev->config + msi_data_off(dev, msi64bit));
- if (nr_vectors > 1) {
- msg.data &= ~(nr_vectors - 1);
- msg.data |= vector;
- }
- return msg;
- }
- bool msi_enabled(const PCIDevice *dev)
- {
- return msi_present(dev) &&
- (pci_get_word(dev->config + msi_flags_off(dev)) &
- PCI_MSI_FLAGS_ENABLE);
- }
- /*
- * Make PCI device @dev MSI-capable.
- * Non-zero @offset puts capability MSI at that offset in PCI config
- * space.
- * @nr_vectors is the number of MSI vectors (1, 2, 4, 8, 16 or 32).
- * If @msi64bit, make the device capable of sending a 64-bit message
- * address.
- * If @msi_per_vector_mask, make the device support per-vector masking.
- * @errp is for returning errors.
- * Return 0 on success; set @errp and return -errno on error.
- *
- * -ENOTSUP means lacking msi support for a msi-capable platform.
- * -EINVAL means capability overlap, happens when @offset is non-zero,
- * also means a programming error, except device assignment, which can check
- * if a real HW is broken.
- */
- int msi_init(struct PCIDevice *dev, uint8_t offset,
- unsigned int nr_vectors, bool msi64bit,
- bool msi_per_vector_mask, Error **errp)
- {
- unsigned int vectors_order;
- uint16_t flags;
- uint8_t cap_size;
- int config_offset;
- if (!msi_nonbroken) {
- error_setg(errp, "MSI is not supported by interrupt controller");
- return -ENOTSUP;
- }
- MSI_DEV_PRINTF(dev,
- "init offset: 0x%"PRIx8" vector: %"PRId8
- " 64bit %d mask %d\n",
- offset, nr_vectors, msi64bit, msi_per_vector_mask);
- assert(!(nr_vectors & (nr_vectors - 1))); /* power of 2 */
- assert(nr_vectors > 0);
- assert(nr_vectors <= PCI_MSI_VECTORS_MAX);
- /* the nr of MSI vectors is up to 32 */
- vectors_order = ctz32(nr_vectors);
- flags = vectors_order << ctz32(PCI_MSI_FLAGS_QMASK);
- if (msi64bit) {
- flags |= PCI_MSI_FLAGS_64BIT;
- }
- if (msi_per_vector_mask) {
- flags |= PCI_MSI_FLAGS_MASKBIT;
- }
- cap_size = msi_cap_sizeof(flags);
- config_offset = pci_add_capability(dev, PCI_CAP_ID_MSI, offset,
- cap_size, errp);
- if (config_offset < 0) {
- return config_offset;
- }
- dev->msi_cap = config_offset;
- dev->cap_present |= QEMU_PCI_CAP_MSI;
- pci_set_word(dev->config + msi_flags_off(dev), flags);
- pci_set_word(dev->wmask + msi_flags_off(dev),
- PCI_MSI_FLAGS_QSIZE | PCI_MSI_FLAGS_ENABLE);
- pci_set_long(dev->wmask + msi_address_lo_off(dev),
- PCI_MSI_ADDRESS_LO_MASK);
- if (msi64bit) {
- pci_set_long(dev->wmask + msi_address_hi_off(dev), 0xffffffff);
- }
- pci_set_word(dev->wmask + msi_data_off(dev, msi64bit), 0xffff);
- if (msi_per_vector_mask) {
- /* Make mask bits 0 to nr_vectors - 1 writable. */
- pci_set_long(dev->wmask + msi_mask_off(dev, msi64bit),
- 0xffffffff >> (PCI_MSI_VECTORS_MAX - nr_vectors));
- }
- return 0;
- }
- void msi_uninit(struct PCIDevice *dev)
- {
- uint16_t flags;
- uint8_t cap_size;
- if (!msi_present(dev)) {
- return;
- }
- flags = pci_get_word(dev->config + msi_flags_off(dev));
- cap_size = msi_cap_sizeof(flags);
- pci_del_capability(dev, PCI_CAP_ID_MSI, cap_size);
- dev->cap_present &= ~QEMU_PCI_CAP_MSI;
- MSI_DEV_PRINTF(dev, "uninit\n");
- }
- void msi_reset(PCIDevice *dev)
- {
- uint16_t flags;
- bool msi64bit;
- if (!msi_present(dev)) {
- return;
- }
- flags = pci_get_word(dev->config + msi_flags_off(dev));
- flags &= ~(PCI_MSI_FLAGS_QSIZE | PCI_MSI_FLAGS_ENABLE);
- msi64bit = flags & PCI_MSI_FLAGS_64BIT;
- pci_set_word(dev->config + msi_flags_off(dev), flags);
- pci_set_long(dev->config + msi_address_lo_off(dev), 0);
- if (msi64bit) {
- pci_set_long(dev->config + msi_address_hi_off(dev), 0);
- }
- pci_set_word(dev->config + msi_data_off(dev, msi64bit), 0);
- if (flags & PCI_MSI_FLAGS_MASKBIT) {
- pci_set_long(dev->config + msi_mask_off(dev, msi64bit), 0);
- pci_set_long(dev->config + msi_pending_off(dev, msi64bit), 0);
- }
- MSI_DEV_PRINTF(dev, "reset\n");
- }
- bool msi_is_masked(const PCIDevice *dev, unsigned int vector)
- {
- uint16_t flags = pci_get_word(dev->config + msi_flags_off(dev));
- uint32_t mask, data;
- bool msi64bit = flags & PCI_MSI_FLAGS_64BIT;
- assert(vector < PCI_MSI_VECTORS_MAX);
- if (!(flags & PCI_MSI_FLAGS_MASKBIT)) {
- return false;
- }
- data = pci_get_word(dev->config + msi_data_off(dev, msi64bit));
- if (xen_is_pirq_msi(data)) {
- return false;
- }
- mask = pci_get_long(dev->config +
- msi_mask_off(dev, flags & PCI_MSI_FLAGS_64BIT));
- return mask & (1U << vector);
- }
- void msi_notify(PCIDevice *dev, unsigned int vector)
- {
- uint16_t flags = pci_get_word(dev->config + msi_flags_off(dev));
- bool msi64bit = flags & PCI_MSI_FLAGS_64BIT;
- unsigned int nr_vectors = msi_nr_vectors(flags);
- MSIMessage msg;
- assert(vector < nr_vectors);
- if (msi_is_masked(dev, vector)) {
- assert(flags & PCI_MSI_FLAGS_MASKBIT);
- pci_long_test_and_set_mask(
- dev->config + msi_pending_off(dev, msi64bit), 1U << vector);
- MSI_DEV_PRINTF(dev, "pending vector 0x%x\n", vector);
- return;
- }
- msg = msi_get_message(dev, vector);
- MSI_DEV_PRINTF(dev,
- "notify vector 0x%x"
- " address: 0x%"PRIx64" data: 0x%"PRIx32"\n",
- vector, msg.address, msg.data);
- msi_send_message(dev, msg);
- }
- void msi_send_message(PCIDevice *dev, MSIMessage msg)
- {
- MemTxAttrs attrs = {};
- attrs.requester_id = pci_requester_id(dev);
- address_space_stl_le(&dev->bus_master_as, msg.address, msg.data,
- attrs, NULL);
- }
- /* Normally called by pci_default_write_config(). */
- void msi_write_config(PCIDevice *dev, uint32_t addr, uint32_t val, int len)
- {
- uint16_t flags = pci_get_word(dev->config + msi_flags_off(dev));
- bool msi64bit = flags & PCI_MSI_FLAGS_64BIT;
- bool msi_per_vector_mask = flags & PCI_MSI_FLAGS_MASKBIT;
- unsigned int nr_vectors;
- uint8_t log_num_vecs;
- uint8_t log_max_vecs;
- unsigned int vector;
- uint32_t pending;
- if (!msi_present(dev) ||
- !ranges_overlap(addr, len, dev->msi_cap, msi_cap_sizeof(flags))) {
- return;
- }
- #ifdef MSI_DEBUG
- MSI_DEV_PRINTF(dev, "addr 0x%"PRIx32" val 0x%"PRIx32" len %d\n",
- addr, val, len);
- MSI_DEV_PRINTF(dev, "ctrl: 0x%"PRIx16" address: 0x%"PRIx32,
- flags,
- pci_get_long(dev->config + msi_address_lo_off(dev)));
- if (msi64bit) {
- fprintf(stderr, " address-hi: 0x%"PRIx32,
- pci_get_long(dev->config + msi_address_hi_off(dev)));
- }
- fprintf(stderr, " data: 0x%"PRIx16,
- pci_get_word(dev->config + msi_data_off(dev, msi64bit)));
- if (flags & PCI_MSI_FLAGS_MASKBIT) {
- fprintf(stderr, " mask 0x%"PRIx32" pending 0x%"PRIx32,
- pci_get_long(dev->config + msi_mask_off(dev, msi64bit)),
- pci_get_long(dev->config + msi_pending_off(dev, msi64bit)));
- }
- fprintf(stderr, "\n");
- #endif
- if (!(flags & PCI_MSI_FLAGS_ENABLE)) {
- return;
- }
- /*
- * Now MSI is enabled, clear INTx# interrupts.
- * the driver is prohibited from writing enable bit to mask
- * a service request. But the guest OS could do this.
- * So we just discard the interrupts as moderate fallback.
- *
- * 6.8.3.3. Enabling Operation
- * While enabled for MSI or MSI-X operation, a function is prohibited
- * from using its INTx# pin (if implemented) to request
- * service (MSI, MSI-X, and INTx# are mutually exclusive).
- */
- pci_device_deassert_intx(dev);
- /*
- * nr_vectors might be set bigger than capable. So clamp it.
- * This is not legal by spec, so we can do anything we like,
- * just don't crash the host
- */
- log_num_vecs =
- (flags & PCI_MSI_FLAGS_QSIZE) >> ctz32(PCI_MSI_FLAGS_QSIZE);
- log_max_vecs =
- (flags & PCI_MSI_FLAGS_QMASK) >> ctz32(PCI_MSI_FLAGS_QMASK);
- if (log_num_vecs > log_max_vecs) {
- flags &= ~PCI_MSI_FLAGS_QSIZE;
- flags |= log_max_vecs << ctz32(PCI_MSI_FLAGS_QSIZE);
- pci_set_word(dev->config + msi_flags_off(dev), flags);
- }
- if (!msi_per_vector_mask) {
- /* if per vector masking isn't supported,
- there is no pending interrupt. */
- return;
- }
- nr_vectors = msi_nr_vectors(flags);
- /* This will discard pending interrupts, if any. */
- pending = pci_get_long(dev->config + msi_pending_off(dev, msi64bit));
- pending &= 0xffffffff >> (PCI_MSI_VECTORS_MAX - nr_vectors);
- pci_set_long(dev->config + msi_pending_off(dev, msi64bit), pending);
- /* deliver pending interrupts which are unmasked */
- for (vector = 0; vector < nr_vectors; ++vector) {
- if (msi_is_masked(dev, vector) || !(pending & (1U << vector))) {
- continue;
- }
- pci_long_test_and_clear_mask(
- dev->config + msi_pending_off(dev, msi64bit), 1U << vector);
- msi_notify(dev, vector);
- }
- }
- unsigned int msi_nr_vectors_allocated(const PCIDevice *dev)
- {
- uint16_t flags = pci_get_word(dev->config + msi_flags_off(dev));
- return msi_nr_vectors(flags);
- }
|