Andrew Jones | 456c55b | 2016-01-18 19:01:02 +0100 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2013, Red Hat Inc, Michael S. Tsirkin <mst@redhat.com> |
| 3 | * |
| 4 | * This work is licensed under the terms of the GNU LGPL, version 2. |
| 5 | */ |
Michael S. Tsirkin | 4932b58 | 2013-04-03 11:52:33 +0300 | [diff] [blame] | 6 | #include <linux/pci_regs.h> |
| 7 | #include "pci.h" |
Andrew Jones | 456c55b | 2016-01-18 19:01:02 +0100 | [diff] [blame] | 8 | #include "asm/pci.h" |
Michael S. Tsirkin | 4932b58 | 2013-04-03 11:52:33 +0300 | [diff] [blame] | 9 | |
Peter Xu | 903b051 | 2016-12-12 11:08:19 +0800 | [diff] [blame] | 10 | typedef void (*pci_cap_handler)(struct pci_dev *dev, int cap_offset); |
| 11 | |
| 12 | static void pci_cap_msi_handler(struct pci_dev *dev, int cap_offset) |
| 13 | { |
| 14 | printf("Detected MSI for device 0x%x offset 0x%x\n", |
| 15 | dev->bdf, cap_offset); |
| 16 | dev->msi_offset = cap_offset; |
| 17 | } |
| 18 | |
| 19 | static pci_cap_handler cap_handlers[PCI_CAP_ID_MAX + 1] = { |
| 20 | [PCI_CAP_ID_MSI] = pci_cap_msi_handler, |
| 21 | }; |
| 22 | |
| 23 | void pci_cap_walk(struct pci_dev *dev) |
| 24 | { |
| 25 | uint8_t cap_offset; |
| 26 | uint8_t cap_id; |
| 27 | int count = 0; |
| 28 | |
| 29 | cap_offset = pci_config_readb(dev->bdf, PCI_CAPABILITY_LIST); |
| 30 | while (cap_offset) { |
| 31 | cap_id = pci_config_readb(dev->bdf, cap_offset); |
| 32 | printf("PCI detected cap 0x%x\n", cap_id); |
| 33 | assert(cap_id < PCI_CAP_ID_MAX + 1); |
| 34 | if (cap_handlers[cap_id]) |
| 35 | cap_handlers[cap_id](dev, cap_offset); |
| 36 | cap_offset = pci_config_readb(dev->bdf, cap_offset + 1); |
| 37 | /* Avoid dead loop during cap walk */ |
| 38 | assert(++count <= 255); |
| 39 | } |
| 40 | } |
| 41 | |
| 42 | bool pci_setup_msi(struct pci_dev *dev, uint64_t msi_addr, uint32_t msi_data) |
| 43 | { |
| 44 | uint16_t msi_control; |
| 45 | uint16_t offset; |
| 46 | pcidevaddr_t addr; |
| 47 | |
| 48 | assert(dev); |
| 49 | |
| 50 | if (!dev->msi_offset) { |
| 51 | printf("MSI: dev 0x%x does not support MSI.\n", dev->bdf); |
| 52 | return false; |
| 53 | } |
| 54 | |
| 55 | addr = dev->bdf; |
| 56 | offset = dev->msi_offset; |
| 57 | msi_control = pci_config_readw(addr, offset + PCI_MSI_FLAGS); |
| 58 | pci_config_writel(addr, offset + PCI_MSI_ADDRESS_LO, |
| 59 | msi_addr & 0xffffffff); |
| 60 | |
| 61 | if (msi_control & PCI_MSI_FLAGS_64BIT) { |
| 62 | pci_config_writel(addr, offset + PCI_MSI_ADDRESS_HI, |
| 63 | (uint32_t)(msi_addr >> 32)); |
| 64 | pci_config_writel(addr, offset + PCI_MSI_DATA_64, msi_data); |
| 65 | printf("MSI: dev 0x%x init 64bit address: ", addr); |
| 66 | } else { |
| 67 | pci_config_writel(addr, offset + PCI_MSI_DATA_32, msi_data); |
| 68 | printf("MSI: dev 0x%x init 32bit address: ", addr); |
| 69 | } |
| 70 | printf("addr=0x%lx, data=0x%x\n", msi_addr, msi_data); |
| 71 | |
| 72 | msi_control |= PCI_MSI_FLAGS_ENABLE; |
| 73 | pci_config_writew(addr, offset + PCI_MSI_FLAGS, msi_control); |
| 74 | |
| 75 | return true; |
| 76 | } |
| 77 | |
Peter Xu | 66082ed | 2016-12-12 11:08:16 +0800 | [diff] [blame] | 78 | void pci_cmd_set_clr(struct pci_dev *dev, uint16_t set, uint16_t clr) |
| 79 | { |
| 80 | uint16_t val = pci_config_readw(dev->bdf, PCI_COMMAND); |
| 81 | |
| 82 | /* No overlap is allowed */ |
| 83 | assert((set & clr) == 0); |
| 84 | val |= set; |
| 85 | val &= ~clr; |
| 86 | |
| 87 | pci_config_writew(dev->bdf, PCI_COMMAND, val); |
| 88 | } |
| 89 | |
Alexander Gordeev | e1cad5c | 2016-11-07 11:14:40 +0100 | [diff] [blame] | 90 | bool pci_dev_exists(pcidevaddr_t dev) |
| 91 | { |
| 92 | return (pci_config_readw(dev, PCI_VENDOR_ID) != 0xffff && |
| 93 | pci_config_readw(dev, PCI_DEVICE_ID) != 0xffff); |
| 94 | } |
| 95 | |
Peter Xu | 4d6cefa | 2016-12-12 11:08:14 +0800 | [diff] [blame] | 96 | void pci_dev_init(struct pci_dev *dev, pcidevaddr_t bdf) |
| 97 | { |
| 98 | memset(dev, 0, sizeof(*dev)); |
| 99 | dev->bdf = bdf; |
| 100 | } |
| 101 | |
Michael S. Tsirkin | 4932b58 | 2013-04-03 11:52:33 +0300 | [diff] [blame] | 102 | /* Scan bus look for a specific device. Only bus 0 scanned for now. */ |
| 103 | pcidevaddr_t pci_find_dev(uint16_t vendor_id, uint16_t device_id) |
| 104 | { |
Alexander Gordeev | ebb58e7 | 2016-11-07 11:14:33 +0100 | [diff] [blame] | 105 | pcidevaddr_t dev; |
| 106 | |
Peter Xu | 4d6cefa | 2016-12-12 11:08:14 +0800 | [diff] [blame] | 107 | for (dev = 0; dev < PCI_DEVFN_MAX; ++dev) { |
Alexander Gordeev | d8369c7 | 2016-11-07 11:14:36 +0100 | [diff] [blame] | 108 | if (pci_config_readw(dev, PCI_VENDOR_ID) == vendor_id && |
| 109 | pci_config_readw(dev, PCI_DEVICE_ID) == device_id) |
Alexander Gordeev | ebb58e7 | 2016-11-07 11:14:33 +0100 | [diff] [blame] | 110 | return dev; |
| 111 | } |
| 112 | |
| 113 | return PCIDEVADDR_INVALID; |
Michael S. Tsirkin | 4932b58 | 2013-04-03 11:52:33 +0300 | [diff] [blame] | 114 | } |
| 115 | |
Alexander Gordeev | 33d78b0 | 2016-11-07 11:14:42 +0100 | [diff] [blame] | 116 | uint32_t pci_bar_mask(uint32_t bar) |
Alexander Gordeev | 2455ef2 | 2016-11-07 11:14:38 +0100 | [diff] [blame] | 117 | { |
| 118 | return (bar & PCI_BASE_ADDRESS_SPACE_IO) ? |
| 119 | PCI_BASE_ADDRESS_IO_MASK : PCI_BASE_ADDRESS_MEM_MASK; |
| 120 | } |
| 121 | |
Peter Xu | 4d6cefa | 2016-12-12 11:08:14 +0800 | [diff] [blame] | 122 | uint32_t pci_bar_get(struct pci_dev *dev, int bar_num) |
Alexander Gordeev | 7aa8330 | 2016-11-07 11:14:37 +0100 | [diff] [blame] | 123 | { |
Peter Xu | 4d6cefa | 2016-12-12 11:08:14 +0800 | [diff] [blame] | 124 | return pci_config_readl(dev->bdf, PCI_BASE_ADDRESS_0 + |
| 125 | bar_num * 4); |
Alexander Gordeev | 7aa8330 | 2016-11-07 11:14:37 +0100 | [diff] [blame] | 126 | } |
| 127 | |
Peter Xu | 4d6cefa | 2016-12-12 11:08:14 +0800 | [diff] [blame] | 128 | phys_addr_t pci_bar_get_addr(struct pci_dev *dev, int bar_num) |
Michael S. Tsirkin | 4932b58 | 2013-04-03 11:52:33 +0300 | [diff] [blame] | 129 | { |
Alexander Gordeev | 7aa8330 | 2016-11-07 11:14:37 +0100 | [diff] [blame] | 130 | uint32_t bar = pci_bar_get(dev, bar_num); |
Alexander Gordeev | 2455ef2 | 2016-11-07 11:14:38 +0100 | [diff] [blame] | 131 | uint32_t mask = pci_bar_mask(bar); |
| 132 | uint64_t addr = bar & mask; |
Alexander Gordeev | 647d2ab | 2016-11-29 15:48:51 +0100 | [diff] [blame] | 133 | phys_addr_t phys_addr; |
Alexander Gordeev | ebb58e7 | 2016-11-07 11:14:33 +0100 | [diff] [blame] | 134 | |
Alexander Gordeev | 2455ef2 | 2016-11-07 11:14:38 +0100 | [diff] [blame] | 135 | if (pci_bar_is64(dev, bar_num)) |
| 136 | addr |= (uint64_t)pci_bar_get(dev, bar_num + 1) << 32; |
| 137 | |
Peter Xu | 4d6cefa | 2016-12-12 11:08:14 +0800 | [diff] [blame] | 138 | phys_addr = pci_translate_addr(dev->bdf, addr); |
Alexander Gordeev | 647d2ab | 2016-11-29 15:48:51 +0100 | [diff] [blame] | 139 | assert(phys_addr != INVALID_PHYS_ADDR); |
| 140 | |
| 141 | return phys_addr; |
Alexander Gordeev | 2455ef2 | 2016-11-07 11:14:38 +0100 | [diff] [blame] | 142 | } |
| 143 | |
Peter Xu | 4d6cefa | 2016-12-12 11:08:14 +0800 | [diff] [blame] | 144 | void pci_bar_set_addr(struct pci_dev *dev, int bar_num, phys_addr_t addr) |
Alexander Gordeev | 647f92c | 2016-11-07 11:14:39 +0100 | [diff] [blame] | 145 | { |
| 146 | int off = PCI_BASE_ADDRESS_0 + bar_num * 4; |
| 147 | |
Peter Xu | 4d6cefa | 2016-12-12 11:08:14 +0800 | [diff] [blame] | 148 | pci_config_writel(dev->bdf, off, (uint32_t)addr); |
Alexander Gordeev | 647f92c | 2016-11-07 11:14:39 +0100 | [diff] [blame] | 149 | |
| 150 | if (pci_bar_is64(dev, bar_num)) |
Peter Xu | 4d6cefa | 2016-12-12 11:08:14 +0800 | [diff] [blame] | 151 | pci_config_writel(dev->bdf, off + 4, |
| 152 | (uint32_t)(addr >> 32)); |
Alexander Gordeev | 647f92c | 2016-11-07 11:14:39 +0100 | [diff] [blame] | 153 | } |
| 154 | |
Alexander Gordeev | 2455ef2 | 2016-11-07 11:14:38 +0100 | [diff] [blame] | 155 | /* |
| 156 | * To determine the amount of address space needed by a PCI device, |
| 157 | * one must save the original value of the BAR, write a value of |
| 158 | * all 1's to the register, and then read it back. The amount of |
| 159 | * memory can be then determined by masking the information bits, |
| 160 | * performing a bitwise NOT, and incrementing the value by 1. |
| 161 | * |
| 162 | * The following pci_bar_size_helper() and pci_bar_size() functions |
| 163 | * implement the algorithm. |
| 164 | */ |
Peter Xu | 4d6cefa | 2016-12-12 11:08:14 +0800 | [diff] [blame] | 165 | static uint32_t pci_bar_size_helper(struct pci_dev *dev, int bar_num) |
Alexander Gordeev | 2455ef2 | 2016-11-07 11:14:38 +0100 | [diff] [blame] | 166 | { |
| 167 | int off = PCI_BASE_ADDRESS_0 + bar_num * 4; |
Peter Xu | 4d6cefa | 2016-12-12 11:08:14 +0800 | [diff] [blame] | 168 | uint16_t bdf = dev->bdf; |
Alexander Gordeev | 2455ef2 | 2016-11-07 11:14:38 +0100 | [diff] [blame] | 169 | uint32_t bar, val; |
| 170 | |
Peter Xu | 4d6cefa | 2016-12-12 11:08:14 +0800 | [diff] [blame] | 171 | bar = pci_config_readl(bdf, off); |
| 172 | pci_config_writel(bdf, off, ~0u); |
| 173 | val = pci_config_readl(bdf, off); |
| 174 | pci_config_writel(bdf, off, bar); |
Alexander Gordeev | 2455ef2 | 2016-11-07 11:14:38 +0100 | [diff] [blame] | 175 | |
| 176 | return val; |
| 177 | } |
| 178 | |
Peter Xu | 4d6cefa | 2016-12-12 11:08:14 +0800 | [diff] [blame] | 179 | phys_addr_t pci_bar_size(struct pci_dev *dev, int bar_num) |
Alexander Gordeev | 2455ef2 | 2016-11-07 11:14:38 +0100 | [diff] [blame] | 180 | { |
| 181 | uint32_t bar, size; |
| 182 | |
| 183 | size = pci_bar_size_helper(dev, bar_num); |
| 184 | if (!size) |
| 185 | return 0; |
| 186 | |
| 187 | bar = pci_bar_get(dev, bar_num); |
| 188 | size &= pci_bar_mask(bar); |
| 189 | |
| 190 | if (pci_bar_is64(dev, bar_num)) { |
| 191 | phys_addr_t size64 = pci_bar_size_helper(dev, bar_num + 1); |
| 192 | size64 = (size64 << 32) | size; |
| 193 | |
| 194 | return ~size64 + 1; |
| 195 | } else { |
| 196 | return ~size + 1; |
| 197 | } |
Michael S. Tsirkin | 4932b58 | 2013-04-03 11:52:33 +0300 | [diff] [blame] | 198 | } |
| 199 | |
Peter Xu | 4d6cefa | 2016-12-12 11:08:14 +0800 | [diff] [blame] | 200 | bool pci_bar_is_memory(struct pci_dev *dev, int bar_num) |
Michael S. Tsirkin | 4932b58 | 2013-04-03 11:52:33 +0300 | [diff] [blame] | 201 | { |
Alexander Gordeev | 7aa8330 | 2016-11-07 11:14:37 +0100 | [diff] [blame] | 202 | uint32_t bar = pci_bar_get(dev, bar_num); |
Alexander Gordeev | ebb58e7 | 2016-11-07 11:14:33 +0100 | [diff] [blame] | 203 | |
| 204 | return !(bar & PCI_BASE_ADDRESS_SPACE_IO); |
Michael S. Tsirkin | 4932b58 | 2013-04-03 11:52:33 +0300 | [diff] [blame] | 205 | } |
| 206 | |
Peter Xu | 4d6cefa | 2016-12-12 11:08:14 +0800 | [diff] [blame] | 207 | bool pci_bar_is_valid(struct pci_dev *dev, int bar_num) |
Michael S. Tsirkin | 4932b58 | 2013-04-03 11:52:33 +0300 | [diff] [blame] | 208 | { |
Alexander Gordeev | 7aa8330 | 2016-11-07 11:14:37 +0100 | [diff] [blame] | 209 | return pci_bar_get(dev, bar_num); |
Michael S. Tsirkin | 4932b58 | 2013-04-03 11:52:33 +0300 | [diff] [blame] | 210 | } |
Alexander Gordeev | 2455ef2 | 2016-11-07 11:14:38 +0100 | [diff] [blame] | 211 | |
Peter Xu | 4d6cefa | 2016-12-12 11:08:14 +0800 | [diff] [blame] | 212 | bool pci_bar_is64(struct pci_dev *dev, int bar_num) |
Alexander Gordeev | 2455ef2 | 2016-11-07 11:14:38 +0100 | [diff] [blame] | 213 | { |
| 214 | uint32_t bar = pci_bar_get(dev, bar_num); |
| 215 | |
| 216 | if (bar & PCI_BASE_ADDRESS_SPACE_IO) |
| 217 | return false; |
| 218 | |
| 219 | return (bar & PCI_BASE_ADDRESS_MEM_TYPE_MASK) == |
| 220 | PCI_BASE_ADDRESS_MEM_TYPE_64; |
| 221 | } |
Alexander Gordeev | e461152 | 2016-11-07 11:14:41 +0100 | [diff] [blame] | 222 | |
Peter Xu | 4d6cefa | 2016-12-12 11:08:14 +0800 | [diff] [blame] | 223 | void pci_bar_print(struct pci_dev *dev, int bar_num) |
Alexander Gordeev | e461152 | 2016-11-07 11:14:41 +0100 | [diff] [blame] | 224 | { |
| 225 | phys_addr_t size, start, end; |
| 226 | uint32_t bar; |
| 227 | |
| 228 | size = pci_bar_size(dev, bar_num); |
| 229 | if (!size) |
| 230 | return; |
| 231 | |
| 232 | bar = pci_bar_get(dev, bar_num); |
| 233 | start = pci_bar_get_addr(dev, bar_num); |
| 234 | end = start + size - 1; |
| 235 | |
| 236 | if (pci_bar_is64(dev, bar_num)) { |
| 237 | printf("BAR#%d,%d [%" PRIx64 "-%" PRIx64 " ", |
| 238 | bar_num, bar_num + 1, start, end); |
| 239 | } else { |
| 240 | printf("BAR#%d [%02x-%02x ", |
| 241 | bar_num, (uint32_t)start, (uint32_t)end); |
| 242 | } |
| 243 | |
| 244 | if (bar & PCI_BASE_ADDRESS_SPACE_IO) { |
| 245 | printf("PIO"); |
| 246 | } else { |
| 247 | printf("MEM"); |
| 248 | switch (bar & PCI_BASE_ADDRESS_MEM_TYPE_MASK) { |
| 249 | case PCI_BASE_ADDRESS_MEM_TYPE_32: |
| 250 | printf("32"); |
| 251 | break; |
| 252 | case PCI_BASE_ADDRESS_MEM_TYPE_1M: |
| 253 | printf("1M"); |
| 254 | break; |
| 255 | case PCI_BASE_ADDRESS_MEM_TYPE_64: |
| 256 | printf("64"); |
| 257 | break; |
| 258 | default: |
| 259 | assert(0); |
| 260 | } |
| 261 | } |
| 262 | |
| 263 | if (bar & PCI_BASE_ADDRESS_MEM_PREFETCH) |
| 264 | printf("/p"); |
| 265 | |
| 266 | printf("]"); |
| 267 | } |
| 268 | |
Alexander Gordeev | 33d78b0 | 2016-11-07 11:14:42 +0100 | [diff] [blame] | 269 | void pci_dev_print_id(pcidevaddr_t dev) |
Alexander Gordeev | e461152 | 2016-11-07 11:14:41 +0100 | [diff] [blame] | 270 | { |
| 271 | printf("00.%02x.%1x %04x:%04x", dev / 8, dev % 8, |
| 272 | pci_config_readw(dev, PCI_VENDOR_ID), |
| 273 | pci_config_readw(dev, PCI_DEVICE_ID)); |
| 274 | } |
| 275 | |
| 276 | static void pci_dev_print(pcidevaddr_t dev) |
| 277 | { |
| 278 | uint8_t header = pci_config_readb(dev, PCI_HEADER_TYPE); |
| 279 | uint8_t progif = pci_config_readb(dev, PCI_CLASS_PROG); |
| 280 | uint8_t subclass = pci_config_readb(dev, PCI_CLASS_DEVICE); |
| 281 | uint8_t class = pci_config_readb(dev, PCI_CLASS_DEVICE + 1); |
Peter Xu | 4d6cefa | 2016-12-12 11:08:14 +0800 | [diff] [blame] | 282 | struct pci_dev pci_dev; |
Alexander Gordeev | e461152 | 2016-11-07 11:14:41 +0100 | [diff] [blame] | 283 | int i; |
| 284 | |
Peter Xu | 4d6cefa | 2016-12-12 11:08:14 +0800 | [diff] [blame] | 285 | pci_dev_init(&pci_dev, dev); |
| 286 | |
Alexander Gordeev | e461152 | 2016-11-07 11:14:41 +0100 | [diff] [blame] | 287 | pci_dev_print_id(dev); |
| 288 | printf(" type %02x progif %02x class %02x subclass %02x\n", |
| 289 | header, progif, class, subclass); |
| 290 | |
| 291 | if ((header & PCI_HEADER_TYPE_MASK) != PCI_HEADER_TYPE_NORMAL) |
| 292 | return; |
| 293 | |
Peter Xu | e954ce2 | 2016-12-12 11:08:15 +0800 | [diff] [blame] | 294 | for (i = 0; i < PCI_BAR_NUM; i++) { |
Peter Xu | 4d6cefa | 2016-12-12 11:08:14 +0800 | [diff] [blame] | 295 | if (pci_bar_size(&pci_dev, i)) { |
Alexander Gordeev | e461152 | 2016-11-07 11:14:41 +0100 | [diff] [blame] | 296 | printf("\t"); |
Peter Xu | 4d6cefa | 2016-12-12 11:08:14 +0800 | [diff] [blame] | 297 | pci_bar_print(&pci_dev, i); |
Alexander Gordeev | e461152 | 2016-11-07 11:14:41 +0100 | [diff] [blame] | 298 | printf("\n"); |
| 299 | } |
Peter Xu | 4d6cefa | 2016-12-12 11:08:14 +0800 | [diff] [blame] | 300 | if (pci_bar_is64(&pci_dev, i)) |
Alexander Gordeev | e461152 | 2016-11-07 11:14:41 +0100 | [diff] [blame] | 301 | i++; |
| 302 | } |
| 303 | } |
| 304 | |
| 305 | void pci_print(void) |
| 306 | { |
| 307 | pcidevaddr_t dev; |
| 308 | |
Peter Xu | 4d6cefa | 2016-12-12 11:08:14 +0800 | [diff] [blame] | 309 | for (dev = 0; dev < PCI_DEVFN_MAX; ++dev) { |
Alexander Gordeev | e461152 | 2016-11-07 11:14:41 +0100 | [diff] [blame] | 310 | if (pci_dev_exists(dev)) |
| 311 | pci_dev_print(dev); |
| 312 | } |
| 313 | } |
Peter Xu | e954ce2 | 2016-12-12 11:08:15 +0800 | [diff] [blame] | 314 | |
| 315 | void pci_scan_bars(struct pci_dev *dev) |
| 316 | { |
| 317 | int i; |
| 318 | |
| 319 | for (i = 0; i < PCI_BAR_NUM; i++) { |
| 320 | if (!pci_bar_is_valid(dev, i)) |
| 321 | continue; |
| 322 | dev->resource[i] = pci_bar_get_addr(dev, i); |
| 323 | if (pci_bar_is64(dev, i)) { |
| 324 | i++; |
| 325 | dev->resource[i] = (phys_addr_t)0; |
| 326 | } |
| 327 | } |
| 328 | } |
Peter Xu | 66082ed | 2016-12-12 11:08:16 +0800 | [diff] [blame] | 329 | |
Peter Xu | 352096c | 2016-12-30 16:55:53 +0800 | [diff] [blame^] | 330 | uint8_t pci_intx_line(struct pci_dev *dev) |
| 331 | { |
| 332 | return pci_config_readb(dev->bdf, PCI_INTERRUPT_LINE); |
| 333 | } |
| 334 | |
Peter Xu | 66082ed | 2016-12-12 11:08:16 +0800 | [diff] [blame] | 335 | void pci_enable_defaults(struct pci_dev *dev) |
| 336 | { |
| 337 | pci_scan_bars(dev); |
| 338 | /* Enable device DMA operations */ |
| 339 | pci_cmd_set_clr(dev, PCI_COMMAND_MASTER, 0); |
Peter Xu | 903b051 | 2016-12-12 11:08:19 +0800 | [diff] [blame] | 340 | pci_cap_walk(dev); |
Peter Xu | 66082ed | 2016-12-12 11:08:16 +0800 | [diff] [blame] | 341 | } |