Kevin O'Connor | 061d137 | 2008-06-11 22:39:46 -0400 | [diff] [blame] | 1 | // PCI config space access functions. |
| 2 | // |
| 3 | // Copyright (C) 2008 Kevin O'Connor <kevin@koconnor.net> |
| 4 | // Copyright (C) 2002 MandrakeSoft S.A. |
| 5 | // |
Kevin O'Connor | b1b7c2a | 2009-01-15 20:52:58 -0500 | [diff] [blame] | 6 | // This file may be distributed under the terms of the GNU LGPLv3 license. |
Kevin O'Connor | 061d137 | 2008-06-11 22:39:46 -0400 | [diff] [blame] | 7 | |
Kevin O'Connor | 9dea590 | 2013-09-14 20:23:54 -0400 | [diff] [blame] | 8 | #include "malloc.h" // malloc_tmp |
Kevin O'Connor | 2d2fa31 | 2013-09-14 21:55:26 -0400 | [diff] [blame] | 9 | #include "output.h" // dprintf |
| 10 | #include "pci.h" // pci_config_writel |
Kevin O'Connor | 2d2fa31 | 2013-09-14 21:55:26 -0400 | [diff] [blame] | 11 | #include "pci_regs.h" // PCI_VENDOR_ID |
Kevin O'Connor | 41639f8 | 2013-09-14 19:37:36 -0400 | [diff] [blame] | 12 | #include "romfile.h" // romfile_loadint |
Kevin O'Connor | fa9c66a | 2013-09-14 19:10:40 -0400 | [diff] [blame] | 13 | #include "string.h" // memset |
Kevin O'Connor | 2d2fa31 | 2013-09-14 21:55:26 -0400 | [diff] [blame] | 14 | #include "util.h" // udelay |
Kevin O'Connor | 71036f8 | 2014-04-07 20:36:02 -0400 | [diff] [blame] | 15 | #include "x86.h" // outl |
Kevin O'Connor | a0dc296 | 2008-03-16 14:29:32 -0400 | [diff] [blame] | 16 | |
Kevin O'Connor | be19cdc | 2008-11-09 15:33:47 -0500 | [diff] [blame] | 17 | void pci_config_writel(u16 bdf, u32 addr, u32 val) |
Kevin O'Connor | a0dc296 | 2008-03-16 14:29:32 -0400 | [diff] [blame] | 18 | { |
Kevin O'Connor | be19cdc | 2008-11-09 15:33:47 -0500 | [diff] [blame] | 19 | outl(0x80000000 | (bdf << 8) | (addr & 0xfc), PORT_PCI_CMD); |
Kevin O'Connor | 0f803e4 | 2008-05-24 23:07:16 -0400 | [diff] [blame] | 20 | outl(val, PORT_PCI_DATA); |
Kevin O'Connor | a0dc296 | 2008-03-16 14:29:32 -0400 | [diff] [blame] | 21 | } |
| 22 | |
Kevin O'Connor | be19cdc | 2008-11-09 15:33:47 -0500 | [diff] [blame] | 23 | void pci_config_writew(u16 bdf, u32 addr, u16 val) |
Kevin O'Connor | a0dc296 | 2008-03-16 14:29:32 -0400 | [diff] [blame] | 24 | { |
Kevin O'Connor | be19cdc | 2008-11-09 15:33:47 -0500 | [diff] [blame] | 25 | outl(0x80000000 | (bdf << 8) | (addr & 0xfc), PORT_PCI_CMD); |
Kevin O'Connor | 0f803e4 | 2008-05-24 23:07:16 -0400 | [diff] [blame] | 26 | outw(val, PORT_PCI_DATA + (addr & 2)); |
Kevin O'Connor | a0dc296 | 2008-03-16 14:29:32 -0400 | [diff] [blame] | 27 | } |
| 28 | |
Kevin O'Connor | be19cdc | 2008-11-09 15:33:47 -0500 | [diff] [blame] | 29 | void pci_config_writeb(u16 bdf, u32 addr, u8 val) |
Kevin O'Connor | a0dc296 | 2008-03-16 14:29:32 -0400 | [diff] [blame] | 30 | { |
Kevin O'Connor | be19cdc | 2008-11-09 15:33:47 -0500 | [diff] [blame] | 31 | outl(0x80000000 | (bdf << 8) | (addr & 0xfc), PORT_PCI_CMD); |
Kevin O'Connor | 0f803e4 | 2008-05-24 23:07:16 -0400 | [diff] [blame] | 32 | outb(val, PORT_PCI_DATA + (addr & 3)); |
Kevin O'Connor | a0dc296 | 2008-03-16 14:29:32 -0400 | [diff] [blame] | 33 | } |
| 34 | |
Kevin O'Connor | be19cdc | 2008-11-09 15:33:47 -0500 | [diff] [blame] | 35 | u32 pci_config_readl(u16 bdf, u32 addr) |
Kevin O'Connor | a0dc296 | 2008-03-16 14:29:32 -0400 | [diff] [blame] | 36 | { |
Kevin O'Connor | be19cdc | 2008-11-09 15:33:47 -0500 | [diff] [blame] | 37 | outl(0x80000000 | (bdf << 8) | (addr & 0xfc), PORT_PCI_CMD); |
Kevin O'Connor | 0f803e4 | 2008-05-24 23:07:16 -0400 | [diff] [blame] | 38 | return inl(PORT_PCI_DATA); |
Kevin O'Connor | a0dc296 | 2008-03-16 14:29:32 -0400 | [diff] [blame] | 39 | } |
| 40 | |
Kevin O'Connor | be19cdc | 2008-11-09 15:33:47 -0500 | [diff] [blame] | 41 | u16 pci_config_readw(u16 bdf, u32 addr) |
Kevin O'Connor | a0dc296 | 2008-03-16 14:29:32 -0400 | [diff] [blame] | 42 | { |
Kevin O'Connor | be19cdc | 2008-11-09 15:33:47 -0500 | [diff] [blame] | 43 | outl(0x80000000 | (bdf << 8) | (addr & 0xfc), PORT_PCI_CMD); |
Kevin O'Connor | 0f803e4 | 2008-05-24 23:07:16 -0400 | [diff] [blame] | 44 | return inw(PORT_PCI_DATA + (addr & 2)); |
Kevin O'Connor | a0dc296 | 2008-03-16 14:29:32 -0400 | [diff] [blame] | 45 | } |
| 46 | |
Kevin O'Connor | be19cdc | 2008-11-09 15:33:47 -0500 | [diff] [blame] | 47 | u8 pci_config_readb(u16 bdf, u32 addr) |
Kevin O'Connor | a0dc296 | 2008-03-16 14:29:32 -0400 | [diff] [blame] | 48 | { |
Kevin O'Connor | be19cdc | 2008-11-09 15:33:47 -0500 | [diff] [blame] | 49 | outl(0x80000000 | (bdf << 8) | (addr & 0xfc), PORT_PCI_CMD); |
Kevin O'Connor | 0f803e4 | 2008-05-24 23:07:16 -0400 | [diff] [blame] | 50 | return inb(PORT_PCI_DATA + (addr & 3)); |
| 51 | } |
| 52 | |
Kevin O'Connor | 59f0283 | 2009-10-12 10:09:15 -0400 | [diff] [blame] | 53 | void |
| 54 | pci_config_maskw(u16 bdf, u32 addr, u16 off, u16 on) |
| 55 | { |
| 56 | u16 val = pci_config_readw(bdf, addr); |
| 57 | val = (val & ~off) | on; |
| 58 | pci_config_writew(bdf, addr, val); |
| 59 | } |
| 60 | |
Kevin O'Connor | baac6b6 | 2011-06-19 10:46:28 -0400 | [diff] [blame] | 61 | // Helper function for foreachbdf() macro - return next device |
Kevin O'Connor | e633832 | 2008-11-29 20:31:49 -0500 | [diff] [blame] | 62 | int |
Kevin O'Connor | 2b333e4 | 2011-07-02 14:49:41 -0400 | [diff] [blame] | 63 | pci_next(int bdf, int bus) |
Kevin O'Connor | 0f803e4 | 2008-05-24 23:07:16 -0400 | [diff] [blame] | 64 | { |
Kevin O'Connor | 2b333e4 | 2011-07-02 14:49:41 -0400 | [diff] [blame] | 65 | if (pci_bdf_to_fn(bdf) == 0 |
| 66 | && (pci_config_readb(bdf, PCI_HEADER_TYPE) & 0x80) == 0) |
Kevin O'Connor | e633832 | 2008-11-29 20:31:49 -0500 | [diff] [blame] | 67 | // Last found device wasn't a multi-function device - skip to |
| 68 | // the next device. |
Kevin O'Connor | 2b333e4 | 2011-07-02 14:49:41 -0400 | [diff] [blame] | 69 | bdf += 8; |
| 70 | else |
| 71 | bdf += 1; |
Kevin O'Connor | be19cdc | 2008-11-09 15:33:47 -0500 | [diff] [blame] | 72 | |
Kevin O'Connor | e633832 | 2008-11-29 20:31:49 -0500 | [diff] [blame] | 73 | for (;;) { |
Kevin O'Connor | 2b333e4 | 2011-07-02 14:49:41 -0400 | [diff] [blame] | 74 | if (pci_bdf_to_bus(bdf) != bus) |
| 75 | return -1; |
Kevin O'Connor | e633832 | 2008-11-29 20:31:49 -0500 | [diff] [blame] | 76 | |
| 77 | u16 v = pci_config_readw(bdf, PCI_VENDOR_ID); |
| 78 | if (v != 0x0000 && v != 0xffff) |
| 79 | // Device is present. |
Kevin O'Connor | 2b333e4 | 2011-07-02 14:49:41 -0400 | [diff] [blame] | 80 | return bdf; |
Kevin O'Connor | e633832 | 2008-11-29 20:31:49 -0500 | [diff] [blame] | 81 | |
| 82 | if (pci_bdf_to_fn(bdf) == 0) |
| 83 | bdf += 8; |
| 84 | else |
| 85 | bdf += 1; |
| 86 | } |
Kevin O'Connor | be19cdc | 2008-11-09 15:33:47 -0500 | [diff] [blame] | 87 | } |
| 88 | |
Kevin O'Connor | b98a4b1 | 2013-06-08 21:53:36 -0400 | [diff] [blame] | 89 | struct hlist_head PCIDevices VARVERIFY32INIT; |
Kevin O'Connor | 89a2f96 | 2013-02-18 23:36:03 -0500 | [diff] [blame] | 90 | int MaxPCIBus VARFSEG; |
Kevin O'Connor | 096a9b1 | 2011-06-19 14:09:20 -0400 | [diff] [blame] | 91 | |
Jan Kiszka | 58e6b3f | 2011-09-21 08:16:21 +0200 | [diff] [blame] | 92 | // Check if PCI is available at all |
| 93 | int |
| 94 | pci_probe_host(void) |
| 95 | { |
| 96 | outl(0x80000000, PORT_PCI_CMD); |
| 97 | if (inl(PORT_PCI_CMD) != 0x80000000) { |
| 98 | dprintf(1, "Detected non-PCI system\n"); |
| 99 | return -1; |
| 100 | } |
| 101 | return 0; |
| 102 | } |
| 103 | |
Kevin O'Connor | 0f654a9 | 2011-07-02 14:32:11 -0400 | [diff] [blame] | 104 | // Find all PCI devices and populate PCIDevices linked list. |
Kevin O'Connor | 096a9b1 | 2011-06-19 14:09:20 -0400 | [diff] [blame] | 105 | void |
Jan Kiszka | 58e6b3f | 2011-09-21 08:16:21 +0200 | [diff] [blame] | 106 | pci_probe_devices(void) |
Kevin O'Connor | 096a9b1 | 2011-06-19 14:09:20 -0400 | [diff] [blame] | 107 | { |
Kevin O'Connor | 0f654a9 | 2011-07-02 14:32:11 -0400 | [diff] [blame] | 108 | dprintf(3, "PCI probe\n"); |
Kevin O'Connor | 096a9b1 | 2011-06-19 14:09:20 -0400 | [diff] [blame] | 109 | struct pci_device *busdevs[256]; |
| 110 | memset(busdevs, 0, sizeof(busdevs)); |
Kevin O'Connor | b98a4b1 | 2013-06-08 21:53:36 -0400 | [diff] [blame] | 111 | struct hlist_node **pprev = &PCIDevices.first; |
Kevin O'Connor | b044e77 | 2011-07-05 20:40:11 -0400 | [diff] [blame] | 112 | int extraroots = romfile_loadint("etc/extra-pci-roots", 0); |
Kevin O'Connor | 0f654a9 | 2011-07-02 14:32:11 -0400 | [diff] [blame] | 113 | int bus = -1, lastbus = 0, rootbuses = 0, count=0; |
Kevin O'Connor | b044e77 | 2011-07-05 20:40:11 -0400 | [diff] [blame] | 114 | while (bus < 0xff && (bus < MaxPCIBus || rootbuses < extraroots)) { |
Kevin O'Connor | 0f654a9 | 2011-07-02 14:32:11 -0400 | [diff] [blame] | 115 | bus++; |
Kevin O'Connor | 2b333e4 | 2011-07-02 14:49:41 -0400 | [diff] [blame] | 116 | int bdf; |
| 117 | foreachbdf(bdf, bus) { |
Kevin O'Connor | 0f654a9 | 2011-07-02 14:32:11 -0400 | [diff] [blame] | 118 | // Create new pci_device struct and add to list. |
| 119 | struct pci_device *dev = malloc_tmp(sizeof(*dev)); |
| 120 | if (!dev) { |
| 121 | warn_noalloc(); |
| 122 | return; |
| 123 | } |
| 124 | memset(dev, 0, sizeof(*dev)); |
Kevin O'Connor | b98a4b1 | 2013-06-08 21:53:36 -0400 | [diff] [blame] | 125 | hlist_add(&dev->node, pprev); |
Kevin O'Connor | 2a9aeab | 2013-07-14 13:55:52 -0400 | [diff] [blame] | 126 | pprev = &dev->node.next; |
Kevin O'Connor | 0f654a9 | 2011-07-02 14:32:11 -0400 | [diff] [blame] | 127 | count++; |
Kevin O'Connor | 096a9b1 | 2011-06-19 14:09:20 -0400 | [diff] [blame] | 128 | |
Kevin O'Connor | 0f654a9 | 2011-07-02 14:32:11 -0400 | [diff] [blame] | 129 | // Find parent device. |
| 130 | int rootbus; |
| 131 | struct pci_device *parent = busdevs[bus]; |
| 132 | if (!parent) { |
| 133 | if (bus != lastbus) |
| 134 | rootbuses++; |
| 135 | lastbus = bus; |
| 136 | rootbus = rootbuses; |
Kevin O'Connor | 3076cfb | 2011-07-02 18:39:03 -0400 | [diff] [blame] | 137 | if (bus > MaxPCIBus) |
| 138 | MaxPCIBus = bus; |
Kevin O'Connor | 0f654a9 | 2011-07-02 14:32:11 -0400 | [diff] [blame] | 139 | } else { |
| 140 | rootbus = parent->rootbus; |
| 141 | } |
Kevin O'Connor | 096a9b1 | 2011-06-19 14:09:20 -0400 | [diff] [blame] | 142 | |
Kevin O'Connor | 0f654a9 | 2011-07-02 14:32:11 -0400 | [diff] [blame] | 143 | // Populate pci_device info. |
| 144 | dev->bdf = bdf; |
| 145 | dev->parent = parent; |
| 146 | dev->rootbus = rootbus; |
| 147 | u32 vendev = pci_config_readl(bdf, PCI_VENDOR_ID); |
| 148 | dev->vendor = vendev & 0xffff; |
| 149 | dev->device = vendev >> 16; |
| 150 | u32 classrev = pci_config_readl(bdf, PCI_CLASS_REVISION); |
| 151 | dev->class = classrev >> 16; |
| 152 | dev->prog_if = classrev >> 8; |
| 153 | dev->revision = classrev & 0xff; |
| 154 | dev->header_type = pci_config_readb(bdf, PCI_HEADER_TYPE); |
| 155 | u8 v = dev->header_type & 0x7f; |
| 156 | if (v == PCI_HEADER_TYPE_BRIDGE || v == PCI_HEADER_TYPE_CARDBUS) { |
| 157 | u8 secbus = pci_config_readb(bdf, PCI_SECONDARY_BUS); |
| 158 | dev->secondary_bus = secbus; |
| 159 | if (secbus > bus && !busdevs[secbus]) |
| 160 | busdevs[secbus] = dev; |
| 161 | if (secbus > MaxPCIBus) |
| 162 | MaxPCIBus = secbus; |
| 163 | } |
| 164 | dprintf(4, "PCI device %02x:%02x.%x (vd=%04x:%04x c=%04x)\n" |
| 165 | , pci_bdf_to_bus(bdf), pci_bdf_to_dev(bdf) |
| 166 | , pci_bdf_to_fn(bdf) |
| 167 | , dev->vendor, dev->device, dev->class); |
Kevin O'Connor | 096a9b1 | 2011-06-19 14:09:20 -0400 | [diff] [blame] | 168 | } |
| 169 | } |
Kevin O'Connor | 0f654a9 | 2011-07-02 14:32:11 -0400 | [diff] [blame] | 170 | dprintf(1, "Found %d PCI devices (max PCI bus is %02x)\n", count, MaxPCIBus); |
Kevin O'Connor | 096a9b1 | 2011-06-19 14:09:20 -0400 | [diff] [blame] | 171 | } |
| 172 | |
Kevin O'Connor | be19cdc | 2008-11-09 15:33:47 -0500 | [diff] [blame] | 173 | // Search for a device with the specified vendor and device ids. |
Kevin O'Connor | 0cd7005 | 2011-07-02 14:04:19 -0400 | [diff] [blame] | 174 | struct pci_device * |
Kevin O'Connor | 4132e02 | 2008-12-04 19:39:10 -0500 | [diff] [blame] | 175 | pci_find_device(u16 vendid, u16 devid) |
Kevin O'Connor | be19cdc | 2008-11-09 15:33:47 -0500 | [diff] [blame] | 176 | { |
Kevin O'Connor | 0cd7005 | 2011-07-02 14:04:19 -0400 | [diff] [blame] | 177 | struct pci_device *pci; |
| 178 | foreachpci(pci) { |
| 179 | if (pci->vendor == vendid && pci->device == devid) |
| 180 | return pci; |
Kevin O'Connor | 0f803e4 | 2008-05-24 23:07:16 -0400 | [diff] [blame] | 181 | } |
Kevin O'Connor | 0cd7005 | 2011-07-02 14:04:19 -0400 | [diff] [blame] | 182 | return NULL; |
Kevin O'Connor | 0f803e4 | 2008-05-24 23:07:16 -0400 | [diff] [blame] | 183 | } |
| 184 | |
Kevin O'Connor | 5fdaa03 | 2008-08-31 11:06:27 -0400 | [diff] [blame] | 185 | // Search for a device with the specified class id. |
Kevin O'Connor | 0cd7005 | 2011-07-02 14:04:19 -0400 | [diff] [blame] | 186 | struct pci_device * |
Kevin O'Connor | 4132e02 | 2008-12-04 19:39:10 -0500 | [diff] [blame] | 187 | pci_find_class(u16 classid) |
Kevin O'Connor | 5fdaa03 | 2008-08-31 11:06:27 -0400 | [diff] [blame] | 188 | { |
Kevin O'Connor | 0cd7005 | 2011-07-02 14:04:19 -0400 | [diff] [blame] | 189 | struct pci_device *pci; |
| 190 | foreachpci(pci) { |
| 191 | if (pci->class == classid) |
| 192 | return pci; |
Kevin O'Connor | 0f803e4 | 2008-05-24 23:07:16 -0400 | [diff] [blame] | 193 | } |
Kevin O'Connor | 0cd7005 | 2011-07-02 14:04:19 -0400 | [diff] [blame] | 194 | return NULL; |
Kevin O'Connor | a0dc296 | 2008-03-16 14:29:32 -0400 | [diff] [blame] | 195 | } |
Isaku Yamahata | 968d3a8 | 2010-07-07 12:14:01 +0900 | [diff] [blame] | 196 | |
Kevin O'Connor | 278b19f | 2011-06-21 22:41:15 -0400 | [diff] [blame] | 197 | int pci_init_device(const struct pci_device_id *ids |
| 198 | , struct pci_device *pci, void *arg) |
Isaku Yamahata | 968d3a8 | 2010-07-07 12:14:01 +0900 | [diff] [blame] | 199 | { |
Isaku Yamahata | 968d3a8 | 2010-07-07 12:14:01 +0900 | [diff] [blame] | 200 | while (ids->vendid || ids->class_mask) { |
Kevin O'Connor | 278b19f | 2011-06-21 22:41:15 -0400 | [diff] [blame] | 201 | if ((ids->vendid == PCI_ANY_ID || ids->vendid == pci->vendor) && |
| 202 | (ids->devid == PCI_ANY_ID || ids->devid == pci->device) && |
| 203 | !((ids->class ^ pci->class) & ids->class_mask)) { |
| 204 | if (ids->func) |
| 205 | ids->func(pci, arg); |
Isaku Yamahata | 968d3a8 | 2010-07-07 12:14:01 +0900 | [diff] [blame] | 206 | return 0; |
| 207 | } |
| 208 | ids++; |
| 209 | } |
| 210 | return -1; |
| 211 | } |
Isaku Yamahata | 23173ac | 2010-07-20 16:37:15 +0900 | [diff] [blame] | 212 | |
Kevin O'Connor | 278b19f | 2011-06-21 22:41:15 -0400 | [diff] [blame] | 213 | struct pci_device * |
| 214 | pci_find_init_device(const struct pci_device_id *ids, void *arg) |
Isaku Yamahata | 23173ac | 2010-07-20 16:37:15 +0900 | [diff] [blame] | 215 | { |
Kevin O'Connor | 278b19f | 2011-06-21 22:41:15 -0400 | [diff] [blame] | 216 | struct pci_device *pci; |
| 217 | foreachpci(pci) { |
| 218 | if (pci_init_device(ids, pci, arg) == 0) |
| 219 | return pci; |
Isaku Yamahata | 23173ac | 2010-07-20 16:37:15 +0900 | [diff] [blame] | 220 | } |
Kevin O'Connor | 278b19f | 2011-06-21 22:41:15 -0400 | [diff] [blame] | 221 | return NULL; |
Isaku Yamahata | 23173ac | 2010-07-20 16:37:15 +0900 | [diff] [blame] | 222 | } |
Kevin O'Connor | adaf373 | 2010-09-13 20:22:07 -0400 | [diff] [blame] | 223 | |
Marcel Apfelbaum | c6e298e | 2014-04-10 21:55:21 +0300 | [diff] [blame^] | 224 | u8 pci_find_capability(struct pci_device *pci, u8 cap_id) |
| 225 | { |
| 226 | int i; |
| 227 | u8 cap; |
| 228 | u16 status = pci_config_readw(pci->bdf, PCI_STATUS); |
| 229 | |
| 230 | if (!(status & PCI_STATUS_CAP_LIST)) |
| 231 | return 0; |
| 232 | |
| 233 | cap = pci_config_readb(pci->bdf, PCI_CAPABILITY_LIST); |
| 234 | for (i = 0; cap && i <= 0xff; i++) { |
| 235 | if (pci_config_readb(pci->bdf, cap + PCI_CAP_LIST_ID) == cap_id) |
| 236 | return cap; |
| 237 | cap = pci_config_readb(pci->bdf, cap + PCI_CAP_LIST_NEXT); |
| 238 | } |
| 239 | |
| 240 | return 0; |
| 241 | } |
| 242 | |
Kevin O'Connor | adaf373 | 2010-09-13 20:22:07 -0400 | [diff] [blame] | 243 | void |
| 244 | pci_reboot(void) |
| 245 | { |
| 246 | u8 v = inb(PORT_PCI_REBOOT) & ~6; |
| 247 | outb(v|2, PORT_PCI_REBOOT); /* Request hard reset */ |
| 248 | udelay(50); |
| 249 | outb(v|6, PORT_PCI_REBOOT); /* Actually do the reset */ |
| 250 | udelay(50); |
| 251 | } |