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