blob: 76c293c88d1660fdc7df07ca5d3d38e37bc7138f [file] [log] [blame]
Kevin O'Connor061d1372008-06-11 22:39:46 -04001// 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'Connorb1b7c2a2009-01-15 20:52:58 -05006// This file may be distributed under the terms of the GNU LGPLv3 license.
Kevin O'Connor061d1372008-06-11 22:39:46 -04007
Kevin O'Connor9dea5902013-09-14 20:23:54 -04008#include "malloc.h" // malloc_tmp
Kevin O'Connor2d2fa312013-09-14 21:55:26 -04009#include "output.h" // dprintf
10#include "pci.h" // pci_config_writel
Kevin O'Connor2d2fa312013-09-14 21:55:26 -040011#include "pci_regs.h" // PCI_VENDOR_ID
Kevin O'Connor41639f82013-09-14 19:37:36 -040012#include "romfile.h" // romfile_loadint
Kevin O'Connorf1b1d762016-02-02 22:09:57 -050013#include "stacks.h" // wait_preempt
Kevin O'Connorfa9c66a2013-09-14 19:10:40 -040014#include "string.h" // memset
Kevin O'Connor2d2fa312013-09-14 21:55:26 -040015#include "util.h" // udelay
Kevin O'Connor71036f82014-04-07 20:36:02 -040016#include "x86.h" // outl
Kevin O'Connora0dc2962008-03-16 14:29:32 -040017
Kevin O'Connorbe19cdc2008-11-09 15:33:47 -050018void pci_config_writel(u16 bdf, u32 addr, u32 val)
Kevin O'Connora0dc2962008-03-16 14:29:32 -040019{
Kevin O'Connorbe19cdc2008-11-09 15:33:47 -050020 outl(0x80000000 | (bdf << 8) | (addr & 0xfc), PORT_PCI_CMD);
Kevin O'Connor0f803e42008-05-24 23:07:16 -040021 outl(val, PORT_PCI_DATA);
Kevin O'Connora0dc2962008-03-16 14:29:32 -040022}
23
Kevin O'Connorbe19cdc2008-11-09 15:33:47 -050024void pci_config_writew(u16 bdf, u32 addr, u16 val)
Kevin O'Connora0dc2962008-03-16 14:29:32 -040025{
Kevin O'Connorbe19cdc2008-11-09 15:33:47 -050026 outl(0x80000000 | (bdf << 8) | (addr & 0xfc), PORT_PCI_CMD);
Kevin O'Connor0f803e42008-05-24 23:07:16 -040027 outw(val, PORT_PCI_DATA + (addr & 2));
Kevin O'Connora0dc2962008-03-16 14:29:32 -040028}
29
Kevin O'Connorbe19cdc2008-11-09 15:33:47 -050030void pci_config_writeb(u16 bdf, u32 addr, u8 val)
Kevin O'Connora0dc2962008-03-16 14:29:32 -040031{
Kevin O'Connorbe19cdc2008-11-09 15:33:47 -050032 outl(0x80000000 | (bdf << 8) | (addr & 0xfc), PORT_PCI_CMD);
Kevin O'Connor0f803e42008-05-24 23:07:16 -040033 outb(val, PORT_PCI_DATA + (addr & 3));
Kevin O'Connora0dc2962008-03-16 14:29:32 -040034}
35
Kevin O'Connorbe19cdc2008-11-09 15:33:47 -050036u32 pci_config_readl(u16 bdf, u32 addr)
Kevin O'Connora0dc2962008-03-16 14:29:32 -040037{
Kevin O'Connorbe19cdc2008-11-09 15:33:47 -050038 outl(0x80000000 | (bdf << 8) | (addr & 0xfc), PORT_PCI_CMD);
Kevin O'Connor0f803e42008-05-24 23:07:16 -040039 return inl(PORT_PCI_DATA);
Kevin O'Connora0dc2962008-03-16 14:29:32 -040040}
41
Kevin O'Connorbe19cdc2008-11-09 15:33:47 -050042u16 pci_config_readw(u16 bdf, u32 addr)
Kevin O'Connora0dc2962008-03-16 14:29:32 -040043{
Kevin O'Connorbe19cdc2008-11-09 15:33:47 -050044 outl(0x80000000 | (bdf << 8) | (addr & 0xfc), PORT_PCI_CMD);
Kevin O'Connor0f803e42008-05-24 23:07:16 -040045 return inw(PORT_PCI_DATA + (addr & 2));
Kevin O'Connora0dc2962008-03-16 14:29:32 -040046}
47
Kevin O'Connorbe19cdc2008-11-09 15:33:47 -050048u8 pci_config_readb(u16 bdf, u32 addr)
Kevin O'Connora0dc2962008-03-16 14:29:32 -040049{
Kevin O'Connorbe19cdc2008-11-09 15:33:47 -050050 outl(0x80000000 | (bdf << 8) | (addr & 0xfc), PORT_PCI_CMD);
Kevin O'Connor0f803e42008-05-24 23:07:16 -040051 return inb(PORT_PCI_DATA + (addr & 3));
52}
53
Kevin O'Connor59f02832009-10-12 10:09:15 -040054void
55pci_config_maskw(u16 bdf, u32 addr, u16 off, u16 on)
56{
57 u16 val = pci_config_readw(bdf, addr);
58 val = (val & ~off) | on;
59 pci_config_writew(bdf, addr, val);
60}
61
Kevin O'Connorbaac6b62011-06-19 10:46:28 -040062// Helper function for foreachbdf() macro - return next device
Kevin O'Connore6338322008-11-29 20:31:49 -050063int
Kevin O'Connor2b333e42011-07-02 14:49:41 -040064pci_next(int bdf, int bus)
Kevin O'Connor0f803e42008-05-24 23:07:16 -040065{
Kevin O'Connor2b333e42011-07-02 14:49:41 -040066 if (pci_bdf_to_fn(bdf) == 0
67 && (pci_config_readb(bdf, PCI_HEADER_TYPE) & 0x80) == 0)
Kevin O'Connore6338322008-11-29 20:31:49 -050068 // Last found device wasn't a multi-function device - skip to
69 // the next device.
Kevin O'Connor2b333e42011-07-02 14:49:41 -040070 bdf += 8;
71 else
72 bdf += 1;
Kevin O'Connorbe19cdc2008-11-09 15:33:47 -050073
Kevin O'Connore6338322008-11-29 20:31:49 -050074 for (;;) {
Kevin O'Connor2b333e42011-07-02 14:49:41 -040075 if (pci_bdf_to_bus(bdf) != bus)
76 return -1;
Kevin O'Connore6338322008-11-29 20:31:49 -050077
78 u16 v = pci_config_readw(bdf, PCI_VENDOR_ID);
79 if (v != 0x0000 && v != 0xffff)
80 // Device is present.
Kevin O'Connor2b333e42011-07-02 14:49:41 -040081 return bdf;
Kevin O'Connore6338322008-11-29 20:31:49 -050082
83 if (pci_bdf_to_fn(bdf) == 0)
84 bdf += 8;
85 else
86 bdf += 1;
87 }
Kevin O'Connorbe19cdc2008-11-09 15:33:47 -050088}
89
Kevin O'Connorb98a4b12013-06-08 21:53:36 -040090struct hlist_head PCIDevices VARVERIFY32INIT;
Kevin O'Connor89a2f962013-02-18 23:36:03 -050091int MaxPCIBus VARFSEG;
Kevin O'Connor096a9b12011-06-19 14:09:20 -040092
Jan Kiszka58e6b3f2011-09-21 08:16:21 +020093// Check if PCI is available at all
94int
95pci_probe_host(void)
96{
97 outl(0x80000000, PORT_PCI_CMD);
98 if (inl(PORT_PCI_CMD) != 0x80000000) {
99 dprintf(1, "Detected non-PCI system\n");
100 return -1;
101 }
102 return 0;
103}
104
Kevin O'Connor0f654a92011-07-02 14:32:11 -0400105// Find all PCI devices and populate PCIDevices linked list.
Kevin O'Connor096a9b12011-06-19 14:09:20 -0400106void
Jan Kiszka58e6b3f2011-09-21 08:16:21 +0200107pci_probe_devices(void)
Kevin O'Connor096a9b12011-06-19 14:09:20 -0400108{
Kevin O'Connor0f654a92011-07-02 14:32:11 -0400109 dprintf(3, "PCI probe\n");
Kevin O'Connor096a9b12011-06-19 14:09:20 -0400110 struct pci_device *busdevs[256];
111 memset(busdevs, 0, sizeof(busdevs));
Kevin O'Connorb98a4b12013-06-08 21:53:36 -0400112 struct hlist_node **pprev = &PCIDevices.first;
Kevin O'Connorb044e772011-07-05 20:40:11 -0400113 int extraroots = romfile_loadint("etc/extra-pci-roots", 0);
Kevin O'Connor0f654a92011-07-02 14:32:11 -0400114 int bus = -1, lastbus = 0, rootbuses = 0, count=0;
Kevin O'Connorb044e772011-07-05 20:40:11 -0400115 while (bus < 0xff && (bus < MaxPCIBus || rootbuses < extraroots)) {
Kevin O'Connor0f654a92011-07-02 14:32:11 -0400116 bus++;
Kevin O'Connor2b333e42011-07-02 14:49:41 -0400117 int bdf;
118 foreachbdf(bdf, bus) {
Kevin O'Connor0f654a92011-07-02 14:32:11 -0400119 // Create new pci_device struct and add to list.
120 struct pci_device *dev = malloc_tmp(sizeof(*dev));
121 if (!dev) {
122 warn_noalloc();
123 return;
124 }
125 memset(dev, 0, sizeof(*dev));
Kevin O'Connorb98a4b12013-06-08 21:53:36 -0400126 hlist_add(&dev->node, pprev);
Kevin O'Connor2a9aeab2013-07-14 13:55:52 -0400127 pprev = &dev->node.next;
Kevin O'Connor0f654a92011-07-02 14:32:11 -0400128 count++;
Kevin O'Connor096a9b12011-06-19 14:09:20 -0400129
Kevin O'Connor0f654a92011-07-02 14:32:11 -0400130 // Find parent device.
131 int rootbus;
132 struct pci_device *parent = busdevs[bus];
133 if (!parent) {
134 if (bus != lastbus)
135 rootbuses++;
136 lastbus = bus;
137 rootbus = rootbuses;
Kevin O'Connor3076cfb2011-07-02 18:39:03 -0400138 if (bus > MaxPCIBus)
139 MaxPCIBus = bus;
Kevin O'Connor0f654a92011-07-02 14:32:11 -0400140 } else {
141 rootbus = parent->rootbus;
142 }
Kevin O'Connor096a9b12011-06-19 14:09:20 -0400143
Kevin O'Connor0f654a92011-07-02 14:32:11 -0400144 // Populate pci_device info.
145 dev->bdf = bdf;
146 dev->parent = parent;
147 dev->rootbus = rootbus;
148 u32 vendev = pci_config_readl(bdf, PCI_VENDOR_ID);
149 dev->vendor = vendev & 0xffff;
150 dev->device = vendev >> 16;
151 u32 classrev = pci_config_readl(bdf, PCI_CLASS_REVISION);
152 dev->class = classrev >> 16;
153 dev->prog_if = classrev >> 8;
154 dev->revision = classrev & 0xff;
155 dev->header_type = pci_config_readb(bdf, PCI_HEADER_TYPE);
156 u8 v = dev->header_type & 0x7f;
157 if (v == PCI_HEADER_TYPE_BRIDGE || v == PCI_HEADER_TYPE_CARDBUS) {
158 u8 secbus = pci_config_readb(bdf, PCI_SECONDARY_BUS);
159 dev->secondary_bus = secbus;
160 if (secbus > bus && !busdevs[secbus])
161 busdevs[secbus] = dev;
162 if (secbus > MaxPCIBus)
163 MaxPCIBus = secbus;
164 }
165 dprintf(4, "PCI device %02x:%02x.%x (vd=%04x:%04x c=%04x)\n"
166 , pci_bdf_to_bus(bdf), pci_bdf_to_dev(bdf)
167 , pci_bdf_to_fn(bdf)
168 , dev->vendor, dev->device, dev->class);
Kevin O'Connor096a9b12011-06-19 14:09:20 -0400169 }
170 }
Kevin O'Connor0f654a92011-07-02 14:32:11 -0400171 dprintf(1, "Found %d PCI devices (max PCI bus is %02x)\n", count, MaxPCIBus);
Kevin O'Connor096a9b12011-06-19 14:09:20 -0400172}
173
Kevin O'Connorbe19cdc2008-11-09 15:33:47 -0500174// Search for a device with the specified vendor and device ids.
Kevin O'Connor0cd70052011-07-02 14:04:19 -0400175struct pci_device *
Kevin O'Connor4132e022008-12-04 19:39:10 -0500176pci_find_device(u16 vendid, u16 devid)
Kevin O'Connorbe19cdc2008-11-09 15:33:47 -0500177{
Kevin O'Connor0cd70052011-07-02 14:04:19 -0400178 struct pci_device *pci;
179 foreachpci(pci) {
180 if (pci->vendor == vendid && pci->device == devid)
181 return pci;
Kevin O'Connor0f803e42008-05-24 23:07:16 -0400182 }
Kevin O'Connor0cd70052011-07-02 14:04:19 -0400183 return NULL;
Kevin O'Connor0f803e42008-05-24 23:07:16 -0400184}
185
Kevin O'Connor5fdaa032008-08-31 11:06:27 -0400186// Search for a device with the specified class id.
Kevin O'Connor0cd70052011-07-02 14:04:19 -0400187struct pci_device *
Kevin O'Connor4132e022008-12-04 19:39:10 -0500188pci_find_class(u16 classid)
Kevin O'Connor5fdaa032008-08-31 11:06:27 -0400189{
Kevin O'Connor0cd70052011-07-02 14:04:19 -0400190 struct pci_device *pci;
191 foreachpci(pci) {
192 if (pci->class == classid)
193 return pci;
Kevin O'Connor0f803e42008-05-24 23:07:16 -0400194 }
Kevin O'Connor0cd70052011-07-02 14:04:19 -0400195 return NULL;
Kevin O'Connora0dc2962008-03-16 14:29:32 -0400196}
Isaku Yamahata968d3a82010-07-07 12:14:01 +0900197
Kevin O'Connor278b19f2011-06-21 22:41:15 -0400198int pci_init_device(const struct pci_device_id *ids
199 , struct pci_device *pci, void *arg)
Isaku Yamahata968d3a82010-07-07 12:14:01 +0900200{
Isaku Yamahata968d3a82010-07-07 12:14:01 +0900201 while (ids->vendid || ids->class_mask) {
Kevin O'Connor278b19f2011-06-21 22:41:15 -0400202 if ((ids->vendid == PCI_ANY_ID || ids->vendid == pci->vendor) &&
203 (ids->devid == PCI_ANY_ID || ids->devid == pci->device) &&
204 !((ids->class ^ pci->class) & ids->class_mask)) {
205 if (ids->func)
206 ids->func(pci, arg);
Isaku Yamahata968d3a82010-07-07 12:14:01 +0900207 return 0;
208 }
209 ids++;
210 }
211 return -1;
212}
Isaku Yamahata23173ac2010-07-20 16:37:15 +0900213
Kevin O'Connor278b19f2011-06-21 22:41:15 -0400214struct pci_device *
215pci_find_init_device(const struct pci_device_id *ids, void *arg)
Isaku Yamahata23173ac2010-07-20 16:37:15 +0900216{
Kevin O'Connor278b19f2011-06-21 22:41:15 -0400217 struct pci_device *pci;
218 foreachpci(pci) {
219 if (pci_init_device(ids, pci, arg) == 0)
220 return pci;
Isaku Yamahata23173ac2010-07-20 16:37:15 +0900221 }
Kevin O'Connor278b19f2011-06-21 22:41:15 -0400222 return NULL;
Isaku Yamahata23173ac2010-07-20 16:37:15 +0900223}
Kevin O'Connoradaf3732010-09-13 20:22:07 -0400224
Gerd Hoffmanne38be2d2015-06-25 10:49:10 +0200225u8 pci_find_capability(struct pci_device *pci, u8 cap_id, u8 cap)
Marcel Apfelbaumc6e298e2014-04-10 21:55:21 +0300226{
227 int i;
Marcel Apfelbaumc6e298e2014-04-10 21:55:21 +0300228 u16 status = pci_config_readw(pci->bdf, PCI_STATUS);
229
230 if (!(status & PCI_STATUS_CAP_LIST))
231 return 0;
232
Gerd Hoffmanne38be2d2015-06-25 10:49:10 +0200233 if (cap == 0) {
234 /* find first */
235 cap = pci_config_readb(pci->bdf, PCI_CAPABILITY_LIST);
236 } else {
237 /* find next */
238 cap = pci_config_readb(pci->bdf, cap + PCI_CAP_LIST_NEXT);
239 }
Marcel Apfelbaumc6e298e2014-04-10 21:55:21 +0300240 for (i = 0; cap && i <= 0xff; i++) {
241 if (pci_config_readb(pci->bdf, cap + PCI_CAP_LIST_ID) == cap_id)
242 return cap;
243 cap = pci_config_readb(pci->bdf, cap + PCI_CAP_LIST_NEXT);
244 }
245
246 return 0;
247}
248
Marcel Apfelbaum0784d042014-04-10 21:55:22 +0300249/* Test whether bridge support forwarding of transactions
250 * of a specific type.
251 * Note: disables bridge's window registers as a side effect.
252 */
253int pci_bridge_has_region(struct pci_device *pci,
254 enum pci_region_type region_type)
255{
256 u8 base;
257
258 switch (region_type) {
259 case PCI_REGION_TYPE_IO:
260 base = PCI_IO_BASE;
261 break;
262 case PCI_REGION_TYPE_PREFMEM:
263 base = PCI_PREF_MEMORY_BASE;
264 break;
265 default:
266 /* Regular memory support is mandatory */
267 return 1;
268 }
269
270 pci_config_writeb(pci->bdf, base, 0xFF);
271
272 return pci_config_readb(pci->bdf, base) != 0;
273}
274
Kevin O'Connorf1b1d762016-02-02 22:09:57 -0500275// Enable PCI bus-mastering (ie, DMA) support on a pci device
276void
277pci_enable_busmaster(struct pci_device *pci)
278{
279 ASSERT32FLAT();
280 wait_preempt();
281 pci_config_maskw(pci->bdf, PCI_COMMAND, 0, PCI_COMMAND_MASTER);
Kevin O'Connor01a30dc2016-02-02 22:50:33 -0500282 pci->have_driver = 1;
Kevin O'Connorf1b1d762016-02-02 22:09:57 -0500283}
284
285// Verify an IO bar and return it to the caller
286u16
287pci_enable_iobar(struct pci_device *pci, u32 addr)
288{
289 ASSERT32FLAT();
290 wait_preempt();
291 u32 bar = pci_config_readl(pci->bdf, addr);
292 if (!(bar & PCI_BASE_ADDRESS_SPACE_IO)) {
293 warn_internalerror();
294 return 0;
295 }
296 bar &= PCI_BASE_ADDRESS_IO_MASK;
297 if (bar == 0 || bar > 0xffff) {
298 warn_internalerror();
299 return 0;
300 }
301 pci_config_maskw(pci->bdf, PCI_COMMAND, 0, PCI_COMMAND_IO);
Kevin O'Connor01a30dc2016-02-02 22:50:33 -0500302 pci->have_driver = 1;
Kevin O'Connorf1b1d762016-02-02 22:09:57 -0500303 return bar;
304}
305
306// Verify a memory bar and return it to the caller
307void *
308pci_enable_membar(struct pci_device *pci, u32 addr)
309{
310 ASSERT32FLAT();
311 wait_preempt();
312 u32 bar = pci_config_readl(pci->bdf, addr);
313 if (bar & PCI_BASE_ADDRESS_SPACE_IO) {
314 warn_internalerror();
315 return NULL;
316 }
317 if (bar & PCI_BASE_ADDRESS_MEM_TYPE_64) {
318 u32 high = pci_config_readl(pci->bdf, addr+4);
319 if (high) {
320 dprintf(1, "Can not map memory bar over 4Gig\n");
321 return NULL;
322 }
323 }
324 bar &= PCI_BASE_ADDRESS_MEM_MASK;
325 if (bar + 4*1024*1024 < 20*1024*1024) {
326 // Bar doesn't look valid (it is in last 4M or first 16M)
327 warn_internalerror();
328 return NULL;
329 }
330 pci_config_maskw(pci->bdf, PCI_COMMAND, 0, PCI_COMMAND_MEMORY);
Kevin O'Connor01a30dc2016-02-02 22:50:33 -0500331 pci->have_driver = 1;
Kevin O'Connorf1b1d762016-02-02 22:09:57 -0500332 return (void*)bar;
333}
334
Kevin O'Connoradaf3732010-09-13 20:22:07 -0400335void
336pci_reboot(void)
337{
338 u8 v = inb(PORT_PCI_REBOOT) & ~6;
339 outb(v|2, PORT_PCI_REBOOT); /* Request hard reset */
340 udelay(50);
341 outb(v|6, PORT_PCI_REBOOT); /* Actually do the reset */
342 udelay(50);
343}