blob: 781c1cce52b30913ef7646cda00c7743385f473d [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'Connorfa9c66a2013-09-14 19:10:40 -040013#include "string.h" // memset
Kevin O'Connor2d2fa312013-09-14 21:55:26 -040014#include "util.h" // udelay
Kevin O'Connor71036f82014-04-07 20:36:02 -040015#include "x86.h" // outl
Kevin O'Connora0dc2962008-03-16 14:29:32 -040016
Kevin O'Connorbe19cdc2008-11-09 15:33:47 -050017void pci_config_writel(u16 bdf, u32 addr, u32 val)
Kevin O'Connora0dc2962008-03-16 14:29:32 -040018{
Kevin O'Connorbe19cdc2008-11-09 15:33:47 -050019 outl(0x80000000 | (bdf << 8) | (addr & 0xfc), PORT_PCI_CMD);
Kevin O'Connor0f803e42008-05-24 23:07:16 -040020 outl(val, PORT_PCI_DATA);
Kevin O'Connora0dc2962008-03-16 14:29:32 -040021}
22
Kevin O'Connorbe19cdc2008-11-09 15:33:47 -050023void pci_config_writew(u16 bdf, u32 addr, u16 val)
Kevin O'Connora0dc2962008-03-16 14:29:32 -040024{
Kevin O'Connorbe19cdc2008-11-09 15:33:47 -050025 outl(0x80000000 | (bdf << 8) | (addr & 0xfc), PORT_PCI_CMD);
Kevin O'Connor0f803e42008-05-24 23:07:16 -040026 outw(val, PORT_PCI_DATA + (addr & 2));
Kevin O'Connora0dc2962008-03-16 14:29:32 -040027}
28
Kevin O'Connorbe19cdc2008-11-09 15:33:47 -050029void pci_config_writeb(u16 bdf, u32 addr, u8 val)
Kevin O'Connora0dc2962008-03-16 14:29:32 -040030{
Kevin O'Connorbe19cdc2008-11-09 15:33:47 -050031 outl(0x80000000 | (bdf << 8) | (addr & 0xfc), PORT_PCI_CMD);
Kevin O'Connor0f803e42008-05-24 23:07:16 -040032 outb(val, PORT_PCI_DATA + (addr & 3));
Kevin O'Connora0dc2962008-03-16 14:29:32 -040033}
34
Kevin O'Connorbe19cdc2008-11-09 15:33:47 -050035u32 pci_config_readl(u16 bdf, u32 addr)
Kevin O'Connora0dc2962008-03-16 14:29:32 -040036{
Kevin O'Connorbe19cdc2008-11-09 15:33:47 -050037 outl(0x80000000 | (bdf << 8) | (addr & 0xfc), PORT_PCI_CMD);
Kevin O'Connor0f803e42008-05-24 23:07:16 -040038 return inl(PORT_PCI_DATA);
Kevin O'Connora0dc2962008-03-16 14:29:32 -040039}
40
Kevin O'Connorbe19cdc2008-11-09 15:33:47 -050041u16 pci_config_readw(u16 bdf, u32 addr)
Kevin O'Connora0dc2962008-03-16 14:29:32 -040042{
Kevin O'Connorbe19cdc2008-11-09 15:33:47 -050043 outl(0x80000000 | (bdf << 8) | (addr & 0xfc), PORT_PCI_CMD);
Kevin O'Connor0f803e42008-05-24 23:07:16 -040044 return inw(PORT_PCI_DATA + (addr & 2));
Kevin O'Connora0dc2962008-03-16 14:29:32 -040045}
46
Kevin O'Connorbe19cdc2008-11-09 15:33:47 -050047u8 pci_config_readb(u16 bdf, u32 addr)
Kevin O'Connora0dc2962008-03-16 14:29:32 -040048{
Kevin O'Connorbe19cdc2008-11-09 15:33:47 -050049 outl(0x80000000 | (bdf << 8) | (addr & 0xfc), PORT_PCI_CMD);
Kevin O'Connor0f803e42008-05-24 23:07:16 -040050 return inb(PORT_PCI_DATA + (addr & 3));
51}
52
Kevin O'Connor59f02832009-10-12 10:09:15 -040053void
54pci_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'Connorbaac6b62011-06-19 10:46:28 -040061// Helper function for foreachbdf() macro - return next device
Kevin O'Connore6338322008-11-29 20:31:49 -050062int
Kevin O'Connor2b333e42011-07-02 14:49:41 -040063pci_next(int bdf, int bus)
Kevin O'Connor0f803e42008-05-24 23:07:16 -040064{
Kevin O'Connor2b333e42011-07-02 14:49:41 -040065 if (pci_bdf_to_fn(bdf) == 0
66 && (pci_config_readb(bdf, PCI_HEADER_TYPE) & 0x80) == 0)
Kevin O'Connore6338322008-11-29 20:31:49 -050067 // Last found device wasn't a multi-function device - skip to
68 // the next device.
Kevin O'Connor2b333e42011-07-02 14:49:41 -040069 bdf += 8;
70 else
71 bdf += 1;
Kevin O'Connorbe19cdc2008-11-09 15:33:47 -050072
Kevin O'Connore6338322008-11-29 20:31:49 -050073 for (;;) {
Kevin O'Connor2b333e42011-07-02 14:49:41 -040074 if (pci_bdf_to_bus(bdf) != bus)
75 return -1;
Kevin O'Connore6338322008-11-29 20:31:49 -050076
77 u16 v = pci_config_readw(bdf, PCI_VENDOR_ID);
78 if (v != 0x0000 && v != 0xffff)
79 // Device is present.
Kevin O'Connor2b333e42011-07-02 14:49:41 -040080 return bdf;
Kevin O'Connore6338322008-11-29 20:31:49 -050081
82 if (pci_bdf_to_fn(bdf) == 0)
83 bdf += 8;
84 else
85 bdf += 1;
86 }
Kevin O'Connorbe19cdc2008-11-09 15:33:47 -050087}
88
Kevin O'Connorb98a4b12013-06-08 21:53:36 -040089struct hlist_head PCIDevices VARVERIFY32INIT;
Kevin O'Connor89a2f962013-02-18 23:36:03 -050090int MaxPCIBus VARFSEG;
Kevin O'Connor096a9b12011-06-19 14:09:20 -040091
Jan Kiszka58e6b3f2011-09-21 08:16:21 +020092// Check if PCI is available at all
93int
94pci_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'Connor0f654a92011-07-02 14:32:11 -0400104// Find all PCI devices and populate PCIDevices linked list.
Kevin O'Connor096a9b12011-06-19 14:09:20 -0400105void
Jan Kiszka58e6b3f2011-09-21 08:16:21 +0200106pci_probe_devices(void)
Kevin O'Connor096a9b12011-06-19 14:09:20 -0400107{
Kevin O'Connor0f654a92011-07-02 14:32:11 -0400108 dprintf(3, "PCI probe\n");
Kevin O'Connor096a9b12011-06-19 14:09:20 -0400109 struct pci_device *busdevs[256];
110 memset(busdevs, 0, sizeof(busdevs));
Kevin O'Connorb98a4b12013-06-08 21:53:36 -0400111 struct hlist_node **pprev = &PCIDevices.first;
Kevin O'Connorb044e772011-07-05 20:40:11 -0400112 int extraroots = romfile_loadint("etc/extra-pci-roots", 0);
Kevin O'Connor0f654a92011-07-02 14:32:11 -0400113 int bus = -1, lastbus = 0, rootbuses = 0, count=0;
Kevin O'Connorb044e772011-07-05 20:40:11 -0400114 while (bus < 0xff && (bus < MaxPCIBus || rootbuses < extraroots)) {
Kevin O'Connor0f654a92011-07-02 14:32:11 -0400115 bus++;
Kevin O'Connor2b333e42011-07-02 14:49:41 -0400116 int bdf;
117 foreachbdf(bdf, bus) {
Kevin O'Connor0f654a92011-07-02 14:32:11 -0400118 // 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'Connorb98a4b12013-06-08 21:53:36 -0400125 hlist_add(&dev->node, pprev);
Kevin O'Connor2a9aeab2013-07-14 13:55:52 -0400126 pprev = &dev->node.next;
Kevin O'Connor0f654a92011-07-02 14:32:11 -0400127 count++;
Kevin O'Connor096a9b12011-06-19 14:09:20 -0400128
Kevin O'Connor0f654a92011-07-02 14:32:11 -0400129 // 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'Connor3076cfb2011-07-02 18:39:03 -0400137 if (bus > MaxPCIBus)
138 MaxPCIBus = bus;
Kevin O'Connor0f654a92011-07-02 14:32:11 -0400139 } else {
140 rootbus = parent->rootbus;
141 }
Kevin O'Connor096a9b12011-06-19 14:09:20 -0400142
Kevin O'Connor0f654a92011-07-02 14:32:11 -0400143 // 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'Connor096a9b12011-06-19 14:09:20 -0400168 }
169 }
Kevin O'Connor0f654a92011-07-02 14:32:11 -0400170 dprintf(1, "Found %d PCI devices (max PCI bus is %02x)\n", count, MaxPCIBus);
Kevin O'Connor096a9b12011-06-19 14:09:20 -0400171}
172
Kevin O'Connorbe19cdc2008-11-09 15:33:47 -0500173// Search for a device with the specified vendor and device ids.
Kevin O'Connor0cd70052011-07-02 14:04:19 -0400174struct pci_device *
Kevin O'Connor4132e022008-12-04 19:39:10 -0500175pci_find_device(u16 vendid, u16 devid)
Kevin O'Connorbe19cdc2008-11-09 15:33:47 -0500176{
Kevin O'Connor0cd70052011-07-02 14:04:19 -0400177 struct pci_device *pci;
178 foreachpci(pci) {
179 if (pci->vendor == vendid && pci->device == devid)
180 return pci;
Kevin O'Connor0f803e42008-05-24 23:07:16 -0400181 }
Kevin O'Connor0cd70052011-07-02 14:04:19 -0400182 return NULL;
Kevin O'Connor0f803e42008-05-24 23:07:16 -0400183}
184
Kevin O'Connor5fdaa032008-08-31 11:06:27 -0400185// Search for a device with the specified class id.
Kevin O'Connor0cd70052011-07-02 14:04:19 -0400186struct pci_device *
Kevin O'Connor4132e022008-12-04 19:39:10 -0500187pci_find_class(u16 classid)
Kevin O'Connor5fdaa032008-08-31 11:06:27 -0400188{
Kevin O'Connor0cd70052011-07-02 14:04:19 -0400189 struct pci_device *pci;
190 foreachpci(pci) {
191 if (pci->class == classid)
192 return pci;
Kevin O'Connor0f803e42008-05-24 23:07:16 -0400193 }
Kevin O'Connor0cd70052011-07-02 14:04:19 -0400194 return NULL;
Kevin O'Connora0dc2962008-03-16 14:29:32 -0400195}
Isaku Yamahata968d3a82010-07-07 12:14:01 +0900196
Kevin O'Connor278b19f2011-06-21 22:41:15 -0400197int pci_init_device(const struct pci_device_id *ids
198 , struct pci_device *pci, void *arg)
Isaku Yamahata968d3a82010-07-07 12:14:01 +0900199{
Isaku Yamahata968d3a82010-07-07 12:14:01 +0900200 while (ids->vendid || ids->class_mask) {
Kevin O'Connor278b19f2011-06-21 22:41:15 -0400201 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 Yamahata968d3a82010-07-07 12:14:01 +0900206 return 0;
207 }
208 ids++;
209 }
210 return -1;
211}
Isaku Yamahata23173ac2010-07-20 16:37:15 +0900212
Kevin O'Connor278b19f2011-06-21 22:41:15 -0400213struct pci_device *
214pci_find_init_device(const struct pci_device_id *ids, void *arg)
Isaku Yamahata23173ac2010-07-20 16:37:15 +0900215{
Kevin O'Connor278b19f2011-06-21 22:41:15 -0400216 struct pci_device *pci;
217 foreachpci(pci) {
218 if (pci_init_device(ids, pci, arg) == 0)
219 return pci;
Isaku Yamahata23173ac2010-07-20 16:37:15 +0900220 }
Kevin O'Connor278b19f2011-06-21 22:41:15 -0400221 return NULL;
Isaku Yamahata23173ac2010-07-20 16:37:15 +0900222}
Kevin O'Connoradaf3732010-09-13 20:22:07 -0400223
Marcel Apfelbaumc6e298e2014-04-10 21:55:21 +0300224u8 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'Connoradaf3732010-09-13 20:22:07 -0400243void
244pci_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}