Michael S. Tsirkin | 02eb84d | 2009-06-21 19:49:54 +0300 | [diff] [blame] | 1 | /* |
| 2 | * MSI-X device support |
| 3 | * |
| 4 | * This module includes support for MSI-X in pci devices. |
| 5 | * |
| 6 | * Author: Michael S. Tsirkin <mst@redhat.com> |
| 7 | * |
| 8 | * Copyright (c) 2009, Red Hat Inc, Michael S. Tsirkin (mst@redhat.com) |
| 9 | * |
| 10 | * This work is licensed under the terms of the GNU GPL, version 2. See |
| 11 | * the COPYING file in the top-level directory. |
Paolo Bonzini | 6b620ca | 2012-01-13 17:44:23 +0100 | [diff] [blame] | 12 | * |
| 13 | * Contributions after 2012-01-13 are licensed under the terms of the |
| 14 | * GNU GPL, version 2 or (at your option) any later version. |
Michael S. Tsirkin | 02eb84d | 2009-06-21 19:49:54 +0300 | [diff] [blame] | 15 | */ |
| 16 | |
| 17 | #include "hw.h" |
Jan Kiszka | 60ba3cc | 2011-10-15 14:33:17 +0200 | [diff] [blame] | 18 | #include "msi.h" |
Michael S. Tsirkin | 02eb84d | 2009-06-21 19:49:54 +0300 | [diff] [blame] | 19 | #include "msix.h" |
| 20 | #include "pci.h" |
Blue Swirl | bf1b007 | 2010-09-18 05:53:14 +0000 | [diff] [blame] | 21 | #include "range.h" |
Michael S. Tsirkin | 02eb84d | 2009-06-21 19:49:54 +0300 | [diff] [blame] | 22 | |
Michael S. Tsirkin | 02eb84d | 2009-06-21 19:49:54 +0300 | [diff] [blame] | 23 | #define MSIX_CAP_LENGTH 12 |
| 24 | |
Michael S. Tsirkin | 2760952 | 2009-11-25 12:18:00 +0200 | [diff] [blame] | 25 | /* MSI enable bit and maskall bit are in byte 1 in FLAGS register */ |
| 26 | #define MSIX_CONTROL_OFFSET (PCI_MSIX_FLAGS + 1) |
Michael S. Tsirkin | 02eb84d | 2009-06-21 19:49:54 +0300 | [diff] [blame] | 27 | #define MSIX_ENABLE_MASK (PCI_MSIX_FLAGS_ENABLE >> 8) |
Michael S. Tsirkin | 5b5cb08 | 2009-11-25 12:19:32 +0200 | [diff] [blame] | 28 | #define MSIX_MASKALL_MASK (PCI_MSIX_FLAGS_MASKALL >> 8) |
Michael S. Tsirkin | 02eb84d | 2009-06-21 19:49:54 +0300 | [diff] [blame] | 29 | |
Michael S. Tsirkin | 5a1fc5e | 2009-09-29 18:53:26 +0200 | [diff] [blame] | 30 | /* How much space does an MSIX table need. */ |
| 31 | /* The spec requires giving the table structure |
| 32 | * a 4K aligned region all by itself. */ |
| 33 | #define MSIX_PAGE_SIZE 0x1000 |
| 34 | /* Reserve second half of the page for pending bits */ |
| 35 | #define MSIX_PAGE_PENDING (MSIX_PAGE_SIZE / 2) |
Michael S. Tsirkin | 02eb84d | 2009-06-21 19:49:54 +0300 | [diff] [blame] | 36 | #define MSIX_MAX_ENTRIES 32 |
| 37 | |
| 38 | |
Michael S. Tsirkin | 02eb84d | 2009-06-21 19:49:54 +0300 | [diff] [blame] | 39 | /* Add MSI-X capability to the config space for the device. */ |
| 40 | /* Given a bar and its size, add MSI-X table on top of it |
| 41 | * and fill MSI-X capability in the config space. |
| 42 | * Original bar size must be a power of 2 or 0. |
| 43 | * New bar size is returned. */ |
| 44 | static int msix_add_config(struct PCIDevice *pdev, unsigned short nentries, |
| 45 | unsigned bar_nr, unsigned bar_size) |
| 46 | { |
| 47 | int config_offset; |
| 48 | uint8_t *config; |
| 49 | uint32_t new_size; |
| 50 | |
| 51 | if (nentries < 1 || nentries > PCI_MSIX_FLAGS_QSIZE + 1) |
| 52 | return -EINVAL; |
| 53 | if (bar_size > 0x80000000) |
| 54 | return -ENOSPC; |
| 55 | |
| 56 | /* Add space for MSI-X structures */ |
Blue Swirl | 5e520a7 | 2009-09-20 15:35:55 +0000 | [diff] [blame] | 57 | if (!bar_size) { |
Michael S. Tsirkin | 5a1fc5e | 2009-09-29 18:53:26 +0200 | [diff] [blame] | 58 | new_size = MSIX_PAGE_SIZE; |
| 59 | } else if (bar_size < MSIX_PAGE_SIZE) { |
| 60 | bar_size = MSIX_PAGE_SIZE; |
| 61 | new_size = MSIX_PAGE_SIZE * 2; |
| 62 | } else { |
Michael S. Tsirkin | 02eb84d | 2009-06-21 19:49:54 +0300 | [diff] [blame] | 63 | new_size = bar_size * 2; |
Michael S. Tsirkin | 5a1fc5e | 2009-09-29 18:53:26 +0200 | [diff] [blame] | 64 | } |
Michael S. Tsirkin | 02eb84d | 2009-06-21 19:49:54 +0300 | [diff] [blame] | 65 | |
| 66 | pdev->msix_bar_size = new_size; |
Isaku Yamahata | ca77089 | 2010-09-06 16:46:16 +0900 | [diff] [blame] | 67 | config_offset = pci_add_capability(pdev, PCI_CAP_ID_MSIX, |
| 68 | 0, MSIX_CAP_LENGTH); |
Michael S. Tsirkin | 02eb84d | 2009-06-21 19:49:54 +0300 | [diff] [blame] | 69 | if (config_offset < 0) |
| 70 | return config_offset; |
| 71 | config = pdev->config + config_offset; |
| 72 | |
| 73 | pci_set_word(config + PCI_MSIX_FLAGS, nentries - 1); |
| 74 | /* Table on top of BAR */ |
Jan Kiszka | 01731cf | 2011-06-09 09:39:56 +0200 | [diff] [blame] | 75 | pci_set_long(config + PCI_MSIX_TABLE, bar_size | bar_nr); |
Michael S. Tsirkin | 02eb84d | 2009-06-21 19:49:54 +0300 | [diff] [blame] | 76 | /* Pending bits on top of that */ |
Jan Kiszka | 01731cf | 2011-06-09 09:39:56 +0200 | [diff] [blame] | 77 | pci_set_long(config + PCI_MSIX_PBA, (bar_size + MSIX_PAGE_PENDING) | |
Michael S. Tsirkin | 5a1fc5e | 2009-09-29 18:53:26 +0200 | [diff] [blame] | 78 | bar_nr); |
Michael S. Tsirkin | 02eb84d | 2009-06-21 19:49:54 +0300 | [diff] [blame] | 79 | pdev->msix_cap = config_offset; |
Stefan Weil | ebabb67 | 2011-04-26 10:29:36 +0200 | [diff] [blame] | 80 | /* Make flags bit writable. */ |
Michael S. Tsirkin | 5b5cb08 | 2009-11-25 12:19:32 +0200 | [diff] [blame] | 81 | pdev->wmask[config_offset + MSIX_CONTROL_OFFSET] |= MSIX_ENABLE_MASK | |
| 82 | MSIX_MASKALL_MASK; |
Michael S. Tsirkin | 5032224 | 2011-11-21 18:57:21 +0200 | [diff] [blame] | 83 | pdev->msix_function_masked = true; |
Michael S. Tsirkin | 02eb84d | 2009-06-21 19:49:54 +0300 | [diff] [blame] | 84 | return 0; |
| 85 | } |
| 86 | |
Avi Kivity | 95524ae | 2011-08-08 16:09:26 +0300 | [diff] [blame] | 87 | static uint64_t msix_mmio_read(void *opaque, target_phys_addr_t addr, |
| 88 | unsigned size) |
Michael S. Tsirkin | 02eb84d | 2009-06-21 19:49:54 +0300 | [diff] [blame] | 89 | { |
| 90 | PCIDevice *dev = opaque; |
Michael S. Tsirkin | 76f5159 | 2009-10-26 16:22:44 +0200 | [diff] [blame] | 91 | unsigned int offset = addr & (MSIX_PAGE_SIZE - 1) & ~0x3; |
Michael S. Tsirkin | 02eb84d | 2009-06-21 19:49:54 +0300 | [diff] [blame] | 92 | void *page = dev->msix_table_page; |
Michael S. Tsirkin | 02eb84d | 2009-06-21 19:49:54 +0300 | [diff] [blame] | 93 | |
Michael S. Tsirkin | 76f5159 | 2009-10-26 16:22:44 +0200 | [diff] [blame] | 94 | return pci_get_long(page + offset); |
Michael S. Tsirkin | 02eb84d | 2009-06-21 19:49:54 +0300 | [diff] [blame] | 95 | } |
| 96 | |
Michael S. Tsirkin | 02eb84d | 2009-06-21 19:49:54 +0300 | [diff] [blame] | 97 | static uint8_t msix_pending_mask(int vector) |
| 98 | { |
| 99 | return 1 << (vector % 8); |
| 100 | } |
| 101 | |
| 102 | static uint8_t *msix_pending_byte(PCIDevice *dev, int vector) |
| 103 | { |
Michael S. Tsirkin | 5a1fc5e | 2009-09-29 18:53:26 +0200 | [diff] [blame] | 104 | return dev->msix_table_page + MSIX_PAGE_PENDING + vector / 8; |
Michael S. Tsirkin | 02eb84d | 2009-06-21 19:49:54 +0300 | [diff] [blame] | 105 | } |
| 106 | |
| 107 | static int msix_is_pending(PCIDevice *dev, int vector) |
| 108 | { |
| 109 | return *msix_pending_byte(dev, vector) & msix_pending_mask(vector); |
| 110 | } |
| 111 | |
| 112 | static void msix_set_pending(PCIDevice *dev, int vector) |
| 113 | { |
| 114 | *msix_pending_byte(dev, vector) |= msix_pending_mask(vector); |
| 115 | } |
| 116 | |
| 117 | static void msix_clr_pending(PCIDevice *dev, int vector) |
| 118 | { |
| 119 | *msix_pending_byte(dev, vector) &= ~msix_pending_mask(vector); |
| 120 | } |
| 121 | |
Michael S. Tsirkin | ae392c4 | 2011-11-21 18:57:50 +0200 | [diff] [blame] | 122 | static bool msix_vector_masked(PCIDevice *dev, int vector, bool fmask) |
Michael S. Tsirkin | 02eb84d | 2009-06-21 19:49:54 +0300 | [diff] [blame] | 123 | { |
Michael S. Tsirkin | ae392c4 | 2011-11-21 18:57:50 +0200 | [diff] [blame] | 124 | unsigned offset = vector * PCI_MSIX_ENTRY_SIZE + PCI_MSIX_ENTRY_VECTOR_CTRL; |
| 125 | return fmask || dev->msix_table_page[offset] & PCI_MSIX_ENTRY_CTRL_MASKBIT; |
Michael S. Tsirkin | 5b5cb08 | 2009-11-25 12:19:32 +0200 | [diff] [blame] | 126 | } |
| 127 | |
Michael S. Tsirkin | ae392c4 | 2011-11-21 18:57:50 +0200 | [diff] [blame] | 128 | static bool msix_is_masked(PCIDevice *dev, int vector) |
Michael S. Tsirkin | 5b5cb08 | 2009-11-25 12:19:32 +0200 | [diff] [blame] | 129 | { |
Michael S. Tsirkin | ae392c4 | 2011-11-21 18:57:50 +0200 | [diff] [blame] | 130 | return msix_vector_masked(dev, vector, dev->msix_function_masked); |
| 131 | } |
| 132 | |
| 133 | static void msix_handle_mask_update(PCIDevice *dev, int vector, bool was_masked) |
| 134 | { |
| 135 | bool is_masked = msix_is_masked(dev, vector); |
| 136 | if (is_masked == was_masked) { |
| 137 | return; |
| 138 | } |
| 139 | |
| 140 | if (!is_masked && msix_is_pending(dev, vector)) { |
Michael S. Tsirkin | 5b5cb08 | 2009-11-25 12:19:32 +0200 | [diff] [blame] | 141 | msix_clr_pending(dev, vector); |
| 142 | msix_notify(dev, vector); |
| 143 | } |
| 144 | } |
| 145 | |
Michael S. Tsirkin | 5032224 | 2011-11-21 18:57:21 +0200 | [diff] [blame] | 146 | static void msix_update_function_masked(PCIDevice *dev) |
| 147 | { |
| 148 | dev->msix_function_masked = !msix_enabled(dev) || |
| 149 | (dev->config[dev->msix_cap + MSIX_CONTROL_OFFSET] & MSIX_MASKALL_MASK); |
| 150 | } |
| 151 | |
Michael S. Tsirkin | 5b5cb08 | 2009-11-25 12:19:32 +0200 | [diff] [blame] | 152 | /* Handle MSI-X capability config write. */ |
| 153 | void msix_write_config(PCIDevice *dev, uint32_t addr, |
| 154 | uint32_t val, int len) |
| 155 | { |
| 156 | unsigned enable_pos = dev->msix_cap + MSIX_CONTROL_OFFSET; |
| 157 | int vector; |
Michael S. Tsirkin | 5032224 | 2011-11-21 18:57:21 +0200 | [diff] [blame] | 158 | bool was_masked; |
Michael S. Tsirkin | 5b5cb08 | 2009-11-25 12:19:32 +0200 | [diff] [blame] | 159 | |
Isaku Yamahata | 98a3cb0 | 2009-12-15 20:26:04 +0900 | [diff] [blame] | 160 | if (!range_covers_byte(addr, len, enable_pos)) { |
Michael S. Tsirkin | 5b5cb08 | 2009-11-25 12:19:32 +0200 | [diff] [blame] | 161 | return; |
| 162 | } |
| 163 | |
Michael S. Tsirkin | 5032224 | 2011-11-21 18:57:21 +0200 | [diff] [blame] | 164 | was_masked = dev->msix_function_masked; |
| 165 | msix_update_function_masked(dev); |
| 166 | |
Michael S. Tsirkin | 5b5cb08 | 2009-11-25 12:19:32 +0200 | [diff] [blame] | 167 | if (!msix_enabled(dev)) { |
| 168 | return; |
| 169 | } |
| 170 | |
Isaku Yamahata | e407bf1 | 2011-01-20 16:21:40 +0900 | [diff] [blame] | 171 | pci_device_deassert_intx(dev); |
Michael S. Tsirkin | 5b5cb08 | 2009-11-25 12:19:32 +0200 | [diff] [blame] | 172 | |
Michael S. Tsirkin | 5032224 | 2011-11-21 18:57:21 +0200 | [diff] [blame] | 173 | if (dev->msix_function_masked == was_masked) { |
Michael S. Tsirkin | 5b5cb08 | 2009-11-25 12:19:32 +0200 | [diff] [blame] | 174 | return; |
| 175 | } |
| 176 | |
| 177 | for (vector = 0; vector < dev->msix_entries_nr; ++vector) { |
Michael S. Tsirkin | ae392c4 | 2011-11-21 18:57:50 +0200 | [diff] [blame] | 178 | msix_handle_mask_update(dev, vector, |
| 179 | msix_vector_masked(dev, vector, was_masked)); |
Michael S. Tsirkin | 5b5cb08 | 2009-11-25 12:19:32 +0200 | [diff] [blame] | 180 | } |
Michael S. Tsirkin | 02eb84d | 2009-06-21 19:49:54 +0300 | [diff] [blame] | 181 | } |
| 182 | |
Avi Kivity | 95524ae | 2011-08-08 16:09:26 +0300 | [diff] [blame] | 183 | static void msix_mmio_write(void *opaque, target_phys_addr_t addr, |
| 184 | uint64_t val, unsigned size) |
Michael S. Tsirkin | 02eb84d | 2009-06-21 19:49:54 +0300 | [diff] [blame] | 185 | { |
| 186 | PCIDevice *dev = opaque; |
Michael S. Tsirkin | 76f5159 | 2009-10-26 16:22:44 +0200 | [diff] [blame] | 187 | unsigned int offset = addr & (MSIX_PAGE_SIZE - 1) & ~0x3; |
Jan Kiszka | 01731cf | 2011-06-09 09:39:56 +0200 | [diff] [blame] | 188 | int vector = offset / PCI_MSIX_ENTRY_SIZE; |
Michael S. Tsirkin | ae392c4 | 2011-11-21 18:57:50 +0200 | [diff] [blame] | 189 | bool was_masked; |
Michael S. Tsirkin | 9a93b61 | 2011-11-21 18:57:31 +0200 | [diff] [blame] | 190 | |
| 191 | /* MSI-X page includes a read-only PBA and a writeable Vector Control. */ |
| 192 | if (vector >= dev->msix_entries_nr) { |
| 193 | return; |
| 194 | } |
| 195 | |
Michael S. Tsirkin | ae392c4 | 2011-11-21 18:57:50 +0200 | [diff] [blame] | 196 | was_masked = msix_is_masked(dev, vector); |
Michael S. Tsirkin | 76f5159 | 2009-10-26 16:22:44 +0200 | [diff] [blame] | 197 | pci_set_long(dev->msix_table_page + offset, val); |
Michael S. Tsirkin | ae392c4 | 2011-11-21 18:57:50 +0200 | [diff] [blame] | 198 | msix_handle_mask_update(dev, vector, was_masked); |
Michael S. Tsirkin | 02eb84d | 2009-06-21 19:49:54 +0300 | [diff] [blame] | 199 | } |
| 200 | |
Avi Kivity | 95524ae | 2011-08-08 16:09:26 +0300 | [diff] [blame] | 201 | static const MemoryRegionOps msix_mmio_ops = { |
| 202 | .read = msix_mmio_read, |
| 203 | .write = msix_mmio_write, |
| 204 | .endianness = DEVICE_NATIVE_ENDIAN, |
| 205 | .valid = { |
| 206 | .min_access_size = 4, |
| 207 | .max_access_size = 4, |
| 208 | }, |
Michael S. Tsirkin | 02eb84d | 2009-06-21 19:49:54 +0300 | [diff] [blame] | 209 | }; |
| 210 | |
Avi Kivity | 95524ae | 2011-08-08 16:09:26 +0300 | [diff] [blame] | 211 | static void msix_mmio_setup(PCIDevice *d, MemoryRegion *bar) |
Michael S. Tsirkin | 02eb84d | 2009-06-21 19:49:54 +0300 | [diff] [blame] | 212 | { |
| 213 | uint8_t *config = d->config + d->msix_cap; |
Jan Kiszka | 01731cf | 2011-06-09 09:39:56 +0200 | [diff] [blame] | 214 | uint32_t table = pci_get_long(config + PCI_MSIX_TABLE); |
Michael S. Tsirkin | 5a1fc5e | 2009-09-29 18:53:26 +0200 | [diff] [blame] | 215 | uint32_t offset = table & ~(MSIX_PAGE_SIZE - 1); |
Michael S. Tsirkin | 02eb84d | 2009-06-21 19:49:54 +0300 | [diff] [blame] | 216 | /* TODO: for assigned devices, we'll want to make it possible to map |
| 217 | * pending bits separately in case they are in a separate bar. */ |
Michael S. Tsirkin | 02eb84d | 2009-06-21 19:49:54 +0300 | [diff] [blame] | 218 | |
Avi Kivity | 95524ae | 2011-08-08 16:09:26 +0300 | [diff] [blame] | 219 | memory_region_add_subregion(bar, offset, &d->msix_mmio); |
Michael S. Tsirkin | 02eb84d | 2009-06-21 19:49:54 +0300 | [diff] [blame] | 220 | } |
| 221 | |
Michael S. Tsirkin | ae1be0b | 2009-11-25 11:41:48 +0200 | [diff] [blame] | 222 | static void msix_mask_all(struct PCIDevice *dev, unsigned nentries) |
| 223 | { |
| 224 | int vector; |
| 225 | for (vector = 0; vector < nentries; ++vector) { |
Jan Kiszka | 01731cf | 2011-06-09 09:39:56 +0200 | [diff] [blame] | 226 | unsigned offset = |
| 227 | vector * PCI_MSIX_ENTRY_SIZE + PCI_MSIX_ENTRY_VECTOR_CTRL; |
| 228 | dev->msix_table_page[offset] |= PCI_MSIX_ENTRY_CTRL_MASKBIT; |
Michael S. Tsirkin | ae1be0b | 2009-11-25 11:41:48 +0200 | [diff] [blame] | 229 | } |
| 230 | } |
| 231 | |
Michael S. Tsirkin | 02eb84d | 2009-06-21 19:49:54 +0300 | [diff] [blame] | 232 | /* Initialize the MSI-X structures. Note: if MSI-X is supported, BAR size is |
| 233 | * modified, it should be retrieved with msix_bar_size. */ |
| 234 | int msix_init(struct PCIDevice *dev, unsigned short nentries, |
Avi Kivity | 95524ae | 2011-08-08 16:09:26 +0300 | [diff] [blame] | 235 | MemoryRegion *bar, |
Michael S. Tsirkin | 5a1fc5e | 2009-09-29 18:53:26 +0200 | [diff] [blame] | 236 | unsigned bar_nr, unsigned bar_size) |
Michael S. Tsirkin | 02eb84d | 2009-06-21 19:49:54 +0300 | [diff] [blame] | 237 | { |
| 238 | int ret; |
Michael S. Tsirkin | 02eb84d | 2009-06-21 19:49:54 +0300 | [diff] [blame] | 239 | |
Jan Kiszka | 60ba3cc | 2011-10-15 14:33:17 +0200 | [diff] [blame] | 240 | /* Nothing to do if MSI is not supported by interrupt controller */ |
| 241 | if (!msi_supported) { |
| 242 | return -ENOTSUP; |
| 243 | } |
Michael S. Tsirkin | 02eb84d | 2009-06-21 19:49:54 +0300 | [diff] [blame] | 244 | if (nentries > MSIX_MAX_ENTRIES) |
| 245 | return -EINVAL; |
| 246 | |
Anthony Liguori | 7267c09 | 2011-08-20 22:09:37 -0500 | [diff] [blame] | 247 | dev->msix_entry_used = g_malloc0(MSIX_MAX_ENTRIES * |
Michael S. Tsirkin | 02eb84d | 2009-06-21 19:49:54 +0300 | [diff] [blame] | 248 | sizeof *dev->msix_entry_used); |
| 249 | |
Anthony Liguori | 7267c09 | 2011-08-20 22:09:37 -0500 | [diff] [blame] | 250 | dev->msix_table_page = g_malloc0(MSIX_PAGE_SIZE); |
Michael S. Tsirkin | ae1be0b | 2009-11-25 11:41:48 +0200 | [diff] [blame] | 251 | msix_mask_all(dev, nentries); |
Michael S. Tsirkin | 02eb84d | 2009-06-21 19:49:54 +0300 | [diff] [blame] | 252 | |
Avi Kivity | 95524ae | 2011-08-08 16:09:26 +0300 | [diff] [blame] | 253 | memory_region_init_io(&dev->msix_mmio, &msix_mmio_ops, dev, |
| 254 | "msix", MSIX_PAGE_SIZE); |
Michael S. Tsirkin | 02eb84d | 2009-06-21 19:49:54 +0300 | [diff] [blame] | 255 | |
| 256 | dev->msix_entries_nr = nentries; |
| 257 | ret = msix_add_config(dev, nentries, bar_nr, bar_size); |
| 258 | if (ret) |
| 259 | goto err_config; |
| 260 | |
| 261 | dev->cap_present |= QEMU_PCI_CAP_MSIX; |
Avi Kivity | 95524ae | 2011-08-08 16:09:26 +0300 | [diff] [blame] | 262 | msix_mmio_setup(dev, bar); |
Michael S. Tsirkin | 02eb84d | 2009-06-21 19:49:54 +0300 | [diff] [blame] | 263 | return 0; |
| 264 | |
| 265 | err_config: |
Michael S. Tsirkin | 3174ecd | 2009-07-22 18:51:14 +0300 | [diff] [blame] | 266 | dev->msix_entries_nr = 0; |
Avi Kivity | 95524ae | 2011-08-08 16:09:26 +0300 | [diff] [blame] | 267 | memory_region_destroy(&dev->msix_mmio); |
Anthony Liguori | 7267c09 | 2011-08-20 22:09:37 -0500 | [diff] [blame] | 268 | g_free(dev->msix_table_page); |
Michael S. Tsirkin | 02eb84d | 2009-06-21 19:49:54 +0300 | [diff] [blame] | 269 | dev->msix_table_page = NULL; |
Anthony Liguori | 7267c09 | 2011-08-20 22:09:37 -0500 | [diff] [blame] | 270 | g_free(dev->msix_entry_used); |
Michael S. Tsirkin | 02eb84d | 2009-06-21 19:49:54 +0300 | [diff] [blame] | 271 | dev->msix_entry_used = NULL; |
| 272 | return ret; |
| 273 | } |
| 274 | |
Michael S. Tsirkin | 98304c8 | 2009-11-25 12:24:14 +0200 | [diff] [blame] | 275 | static void msix_free_irq_entries(PCIDevice *dev) |
| 276 | { |
| 277 | int vector; |
| 278 | |
| 279 | for (vector = 0; vector < dev->msix_entries_nr; ++vector) { |
| 280 | dev->msix_entry_used[vector] = 0; |
| 281 | msix_clr_pending(dev, vector); |
| 282 | } |
| 283 | } |
| 284 | |
Michael S. Tsirkin | 02eb84d | 2009-06-21 19:49:54 +0300 | [diff] [blame] | 285 | /* Clean up resources for the device. */ |
Avi Kivity | 95524ae | 2011-08-08 16:09:26 +0300 | [diff] [blame] | 286 | int msix_uninit(PCIDevice *dev, MemoryRegion *bar) |
Michael S. Tsirkin | 02eb84d | 2009-06-21 19:49:54 +0300 | [diff] [blame] | 287 | { |
| 288 | if (!(dev->cap_present & QEMU_PCI_CAP_MSIX)) |
| 289 | return 0; |
| 290 | pci_del_capability(dev, PCI_CAP_ID_MSIX, MSIX_CAP_LENGTH); |
| 291 | dev->msix_cap = 0; |
| 292 | msix_free_irq_entries(dev); |
| 293 | dev->msix_entries_nr = 0; |
Avi Kivity | 95524ae | 2011-08-08 16:09:26 +0300 | [diff] [blame] | 294 | memory_region_del_subregion(bar, &dev->msix_mmio); |
| 295 | memory_region_destroy(&dev->msix_mmio); |
Anthony Liguori | 7267c09 | 2011-08-20 22:09:37 -0500 | [diff] [blame] | 296 | g_free(dev->msix_table_page); |
Michael S. Tsirkin | 02eb84d | 2009-06-21 19:49:54 +0300 | [diff] [blame] | 297 | dev->msix_table_page = NULL; |
Anthony Liguori | 7267c09 | 2011-08-20 22:09:37 -0500 | [diff] [blame] | 298 | g_free(dev->msix_entry_used); |
Michael S. Tsirkin | 02eb84d | 2009-06-21 19:49:54 +0300 | [diff] [blame] | 299 | dev->msix_entry_used = NULL; |
| 300 | dev->cap_present &= ~QEMU_PCI_CAP_MSIX; |
| 301 | return 0; |
| 302 | } |
| 303 | |
| 304 | void msix_save(PCIDevice *dev, QEMUFile *f) |
| 305 | { |
Michael S. Tsirkin | 9a3e12c | 2009-07-01 16:28:00 +0300 | [diff] [blame] | 306 | unsigned n = dev->msix_entries_nr; |
| 307 | |
Michael S. Tsirkin | 72755a7 | 2009-07-05 15:58:52 +0300 | [diff] [blame] | 308 | if (!(dev->cap_present & QEMU_PCI_CAP_MSIX)) { |
Michael S. Tsirkin | 9a3e12c | 2009-07-01 16:28:00 +0300 | [diff] [blame] | 309 | return; |
Michael S. Tsirkin | 72755a7 | 2009-07-05 15:58:52 +0300 | [diff] [blame] | 310 | } |
Michael S. Tsirkin | 9a3e12c | 2009-07-01 16:28:00 +0300 | [diff] [blame] | 311 | |
Jan Kiszka | 01731cf | 2011-06-09 09:39:56 +0200 | [diff] [blame] | 312 | qemu_put_buffer(f, dev->msix_table_page, n * PCI_MSIX_ENTRY_SIZE); |
Michael S. Tsirkin | 5a1fc5e | 2009-09-29 18:53:26 +0200 | [diff] [blame] | 313 | qemu_put_buffer(f, dev->msix_table_page + MSIX_PAGE_PENDING, (n + 7) / 8); |
Michael S. Tsirkin | 02eb84d | 2009-06-21 19:49:54 +0300 | [diff] [blame] | 314 | } |
| 315 | |
| 316 | /* Should be called after restoring the config space. */ |
| 317 | void msix_load(PCIDevice *dev, QEMUFile *f) |
| 318 | { |
| 319 | unsigned n = dev->msix_entries_nr; |
| 320 | |
Blue Swirl | 98846d7 | 2009-07-05 08:11:39 +0000 | [diff] [blame] | 321 | if (!(dev->cap_present & QEMU_PCI_CAP_MSIX)) { |
Michael S. Tsirkin | 02eb84d | 2009-06-21 19:49:54 +0300 | [diff] [blame] | 322 | return; |
Blue Swirl | 98846d7 | 2009-07-05 08:11:39 +0000 | [diff] [blame] | 323 | } |
Michael S. Tsirkin | 02eb84d | 2009-06-21 19:49:54 +0300 | [diff] [blame] | 324 | |
Michael S. Tsirkin | 4bfd171 | 2009-07-05 15:58:44 +0300 | [diff] [blame] | 325 | msix_free_irq_entries(dev); |
Jan Kiszka | 01731cf | 2011-06-09 09:39:56 +0200 | [diff] [blame] | 326 | qemu_get_buffer(f, dev->msix_table_page, n * PCI_MSIX_ENTRY_SIZE); |
Michael S. Tsirkin | 5a1fc5e | 2009-09-29 18:53:26 +0200 | [diff] [blame] | 327 | qemu_get_buffer(f, dev->msix_table_page + MSIX_PAGE_PENDING, (n + 7) / 8); |
Michael S. Tsirkin | 5032224 | 2011-11-21 18:57:21 +0200 | [diff] [blame] | 328 | msix_update_function_masked(dev); |
Michael S. Tsirkin | 02eb84d | 2009-06-21 19:49:54 +0300 | [diff] [blame] | 329 | } |
| 330 | |
| 331 | /* Does device support MSI-X? */ |
| 332 | int msix_present(PCIDevice *dev) |
| 333 | { |
| 334 | return dev->cap_present & QEMU_PCI_CAP_MSIX; |
| 335 | } |
| 336 | |
| 337 | /* Is MSI-X enabled? */ |
| 338 | int msix_enabled(PCIDevice *dev) |
| 339 | { |
| 340 | return (dev->cap_present & QEMU_PCI_CAP_MSIX) && |
Michael S. Tsirkin | 2760952 | 2009-11-25 12:18:00 +0200 | [diff] [blame] | 341 | (dev->config[dev->msix_cap + MSIX_CONTROL_OFFSET] & |
Michael S. Tsirkin | 02eb84d | 2009-06-21 19:49:54 +0300 | [diff] [blame] | 342 | MSIX_ENABLE_MASK); |
| 343 | } |
| 344 | |
| 345 | /* Size of bar where MSI-X table resides, or 0 if MSI-X not supported. */ |
| 346 | uint32_t msix_bar_size(PCIDevice *dev) |
| 347 | { |
| 348 | return (dev->cap_present & QEMU_PCI_CAP_MSIX) ? |
| 349 | dev->msix_bar_size : 0; |
| 350 | } |
| 351 | |
| 352 | /* Send an MSI-X message */ |
| 353 | void msix_notify(PCIDevice *dev, unsigned vector) |
| 354 | { |
Jan Kiszka | 01731cf | 2011-06-09 09:39:56 +0200 | [diff] [blame] | 355 | uint8_t *table_entry = dev->msix_table_page + vector * PCI_MSIX_ENTRY_SIZE; |
Michael S. Tsirkin | 02eb84d | 2009-06-21 19:49:54 +0300 | [diff] [blame] | 356 | uint64_t address; |
| 357 | uint32_t data; |
| 358 | |
| 359 | if (vector >= dev->msix_entries_nr || !dev->msix_entry_used[vector]) |
| 360 | return; |
| 361 | if (msix_is_masked(dev, vector)) { |
| 362 | msix_set_pending(dev, vector); |
| 363 | return; |
| 364 | } |
| 365 | |
Jan Kiszka | 01731cf | 2011-06-09 09:39:56 +0200 | [diff] [blame] | 366 | address = pci_get_quad(table_entry + PCI_MSIX_ENTRY_LOWER_ADDR); |
| 367 | data = pci_get_long(table_entry + PCI_MSIX_ENTRY_DATA); |
Alexander Graf | ae5d3eb | 2011-07-05 18:28:06 +0200 | [diff] [blame] | 368 | stl_le_phys(address, data); |
Michael S. Tsirkin | 02eb84d | 2009-06-21 19:49:54 +0300 | [diff] [blame] | 369 | } |
| 370 | |
| 371 | void msix_reset(PCIDevice *dev) |
| 372 | { |
| 373 | if (!(dev->cap_present & QEMU_PCI_CAP_MSIX)) |
| 374 | return; |
| 375 | msix_free_irq_entries(dev); |
Michael S. Tsirkin | 2760952 | 2009-11-25 12:18:00 +0200 | [diff] [blame] | 376 | dev->config[dev->msix_cap + MSIX_CONTROL_OFFSET] &= |
| 377 | ~dev->wmask[dev->msix_cap + MSIX_CONTROL_OFFSET]; |
Michael S. Tsirkin | 5a1fc5e | 2009-09-29 18:53:26 +0200 | [diff] [blame] | 378 | memset(dev->msix_table_page, 0, MSIX_PAGE_SIZE); |
Michael S. Tsirkin | ae1be0b | 2009-11-25 11:41:48 +0200 | [diff] [blame] | 379 | msix_mask_all(dev, dev->msix_entries_nr); |
Michael S. Tsirkin | 02eb84d | 2009-06-21 19:49:54 +0300 | [diff] [blame] | 380 | } |
| 381 | |
| 382 | /* PCI spec suggests that devices make it possible for software to configure |
| 383 | * less vectors than supported by the device, but does not specify a standard |
| 384 | * mechanism for devices to do so. |
| 385 | * |
| 386 | * We support this by asking devices to declare vectors software is going to |
| 387 | * actually use, and checking this on the notification path. Devices that |
| 388 | * don't want to follow the spec suggestion can declare all vectors as used. */ |
| 389 | |
| 390 | /* Mark vector as used. */ |
| 391 | int msix_vector_use(PCIDevice *dev, unsigned vector) |
| 392 | { |
| 393 | if (vector >= dev->msix_entries_nr) |
| 394 | return -EINVAL; |
| 395 | dev->msix_entry_used[vector]++; |
| 396 | return 0; |
| 397 | } |
| 398 | |
| 399 | /* Mark vector as unused. */ |
| 400 | void msix_vector_unuse(PCIDevice *dev, unsigned vector) |
| 401 | { |
Michael S. Tsirkin | 98304c8 | 2009-11-25 12:24:14 +0200 | [diff] [blame] | 402 | if (vector >= dev->msix_entries_nr || !dev->msix_entry_used[vector]) { |
| 403 | return; |
| 404 | } |
| 405 | if (--dev->msix_entry_used[vector]) { |
| 406 | return; |
| 407 | } |
| 408 | msix_clr_pending(dev, vector); |
Michael S. Tsirkin | 02eb84d | 2009-06-21 19:49:54 +0300 | [diff] [blame] | 409 | } |
Michael S. Tsirkin | b5f28bc | 2009-11-24 16:44:15 +0200 | [diff] [blame] | 410 | |
| 411 | void msix_unuse_all_vectors(PCIDevice *dev) |
| 412 | { |
| 413 | if (!(dev->cap_present & QEMU_PCI_CAP_MSIX)) |
| 414 | return; |
| 415 | msix_free_irq_entries(dev); |
| 416 | } |