blob: caf9265fcdbe622debf39591249a3818302427b7 [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'Connor59d6ca52012-05-31 00:20:55 -04008#include "config.h" // CONFIG_*
Kevin O'Connorb044e772011-07-05 20:40:11 -04009#include "farptr.h" // MAKE_FLATPTR
Kevin O'Connor9dea5902013-09-14 20:23:54 -040010#include "malloc.h" // malloc_tmp
Kevin O'Connor2d2fa312013-09-14 21:55:26 -040011#include "output.h" // dprintf
12#include "pci.h" // pci_config_writel
Kevin O'Connor04eece22009-07-19 19:05:30 -040013#include "pci_ids.h" // PCI_CLASS_DISPLAY_VGA
Kevin O'Connor2d2fa312013-09-14 21:55:26 -040014#include "pci_regs.h" // PCI_VENDOR_ID
Kevin O'Connor41639f82013-09-14 19:37:36 -040015#include "romfile.h" // romfile_loadint
Kevin O'Connor3df600b2013-09-14 19:28:55 -040016#include "stacks.h" // call32
Kevin O'Connorfa9c66a2013-09-14 19:10:40 -040017#include "string.h" // memset
Kevin O'Connor2d2fa312013-09-14 21:55:26 -040018#include "util.h" // udelay
Kevin O'Connorb9c6a962013-09-14 13:01:30 -040019#include "x86.h" // readl
Kevin O'Connora0dc2962008-03-16 14:29:32 -040020
Kevin O'Connorbe19cdc2008-11-09 15:33:47 -050021void pci_config_writel(u16 bdf, u32 addr, u32 val)
Kevin O'Connora0dc2962008-03-16 14:29:32 -040022{
Kevin O'Connorbe19cdc2008-11-09 15:33:47 -050023 outl(0x80000000 | (bdf << 8) | (addr & 0xfc), PORT_PCI_CMD);
Kevin O'Connor0f803e42008-05-24 23:07:16 -040024 outl(val, PORT_PCI_DATA);
Kevin O'Connora0dc2962008-03-16 14:29:32 -040025}
26
Kevin O'Connorbe19cdc2008-11-09 15:33:47 -050027void pci_config_writew(u16 bdf, u32 addr, u16 val)
Kevin O'Connora0dc2962008-03-16 14:29:32 -040028{
Kevin O'Connorbe19cdc2008-11-09 15:33:47 -050029 outl(0x80000000 | (bdf << 8) | (addr & 0xfc), PORT_PCI_CMD);
Kevin O'Connor0f803e42008-05-24 23:07:16 -040030 outw(val, PORT_PCI_DATA + (addr & 2));
Kevin O'Connora0dc2962008-03-16 14:29:32 -040031}
32
Kevin O'Connorbe19cdc2008-11-09 15:33:47 -050033void pci_config_writeb(u16 bdf, u32 addr, u8 val)
Kevin O'Connora0dc2962008-03-16 14:29:32 -040034{
Kevin O'Connorbe19cdc2008-11-09 15:33:47 -050035 outl(0x80000000 | (bdf << 8) | (addr & 0xfc), PORT_PCI_CMD);
Kevin O'Connor0f803e42008-05-24 23:07:16 -040036 outb(val, PORT_PCI_DATA + (addr & 3));
Kevin O'Connora0dc2962008-03-16 14:29:32 -040037}
38
Kevin O'Connorbe19cdc2008-11-09 15:33:47 -050039u32 pci_config_readl(u16 bdf, u32 addr)
Kevin O'Connora0dc2962008-03-16 14:29:32 -040040{
Kevin O'Connorbe19cdc2008-11-09 15:33:47 -050041 outl(0x80000000 | (bdf << 8) | (addr & 0xfc), PORT_PCI_CMD);
Kevin O'Connor0f803e42008-05-24 23:07:16 -040042 return inl(PORT_PCI_DATA);
Kevin O'Connora0dc2962008-03-16 14:29:32 -040043}
44
Kevin O'Connorbe19cdc2008-11-09 15:33:47 -050045u16 pci_config_readw(u16 bdf, u32 addr)
Kevin O'Connora0dc2962008-03-16 14:29:32 -040046{
Kevin O'Connorbe19cdc2008-11-09 15:33:47 -050047 outl(0x80000000 | (bdf << 8) | (addr & 0xfc), PORT_PCI_CMD);
Kevin O'Connor0f803e42008-05-24 23:07:16 -040048 return inw(PORT_PCI_DATA + (addr & 2));
Kevin O'Connora0dc2962008-03-16 14:29:32 -040049}
50
Kevin O'Connorbe19cdc2008-11-09 15:33:47 -050051u8 pci_config_readb(u16 bdf, u32 addr)
Kevin O'Connora0dc2962008-03-16 14:29:32 -040052{
Kevin O'Connorbe19cdc2008-11-09 15:33:47 -050053 outl(0x80000000 | (bdf << 8) | (addr & 0xfc), PORT_PCI_CMD);
Kevin O'Connor0f803e42008-05-24 23:07:16 -040054 return inb(PORT_PCI_DATA + (addr & 3));
55}
56
Kevin O'Connor59f02832009-10-12 10:09:15 -040057void
58pci_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'Connorbaac6b62011-06-19 10:46:28 -040065// Helper function for foreachbdf() macro - return next device
Kevin O'Connore6338322008-11-29 20:31:49 -050066int
Kevin O'Connor2b333e42011-07-02 14:49:41 -040067pci_next(int bdf, int bus)
Kevin O'Connor0f803e42008-05-24 23:07:16 -040068{
Kevin O'Connor2b333e42011-07-02 14:49:41 -040069 if (pci_bdf_to_fn(bdf) == 0
70 && (pci_config_readb(bdf, PCI_HEADER_TYPE) & 0x80) == 0)
Kevin O'Connore6338322008-11-29 20:31:49 -050071 // Last found device wasn't a multi-function device - skip to
72 // the next device.
Kevin O'Connor2b333e42011-07-02 14:49:41 -040073 bdf += 8;
74 else
75 bdf += 1;
Kevin O'Connorbe19cdc2008-11-09 15:33:47 -050076
Kevin O'Connore6338322008-11-29 20:31:49 -050077 for (;;) {
Kevin O'Connor2b333e42011-07-02 14:49:41 -040078 if (pci_bdf_to_bus(bdf) != bus)
79 return -1;
Kevin O'Connore6338322008-11-29 20:31:49 -050080
81 u16 v = pci_config_readw(bdf, PCI_VENDOR_ID);
82 if (v != 0x0000 && v != 0xffff)
83 // Device is present.
Kevin O'Connor2b333e42011-07-02 14:49:41 -040084 return bdf;
Kevin O'Connore6338322008-11-29 20:31:49 -050085
86 if (pci_bdf_to_fn(bdf) == 0)
87 bdf += 8;
88 else
89 bdf += 1;
90 }
Kevin O'Connorbe19cdc2008-11-09 15:33:47 -050091}
92
Kevin O'Connorb98a4b12013-06-08 21:53:36 -040093struct hlist_head PCIDevices VARVERIFY32INIT;
Kevin O'Connor89a2f962013-02-18 23:36:03 -050094int MaxPCIBus VARFSEG;
Kevin O'Connor096a9b12011-06-19 14:09:20 -040095
Jan Kiszka58e6b3f2011-09-21 08:16:21 +020096// Check if PCI is available at all
97int
98pci_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'Connor0f654a92011-07-02 14:32:11 -0400108// Find all PCI devices and populate PCIDevices linked list.
Kevin O'Connor096a9b12011-06-19 14:09:20 -0400109void
Jan Kiszka58e6b3f2011-09-21 08:16:21 +0200110pci_probe_devices(void)
Kevin O'Connor096a9b12011-06-19 14:09:20 -0400111{
Kevin O'Connor0f654a92011-07-02 14:32:11 -0400112 dprintf(3, "PCI probe\n");
Kevin O'Connor096a9b12011-06-19 14:09:20 -0400113 struct pci_device *busdevs[256];
114 memset(busdevs, 0, sizeof(busdevs));
Kevin O'Connorb98a4b12013-06-08 21:53:36 -0400115 struct hlist_node **pprev = &PCIDevices.first;
Kevin O'Connorb044e772011-07-05 20:40:11 -0400116 int extraroots = romfile_loadint("etc/extra-pci-roots", 0);
Kevin O'Connor0f654a92011-07-02 14:32:11 -0400117 int bus = -1, lastbus = 0, rootbuses = 0, count=0;
Kevin O'Connorb044e772011-07-05 20:40:11 -0400118 while (bus < 0xff && (bus < MaxPCIBus || rootbuses < extraroots)) {
Kevin O'Connor0f654a92011-07-02 14:32:11 -0400119 bus++;
Kevin O'Connor2b333e42011-07-02 14:49:41 -0400120 int bdf;
121 foreachbdf(bdf, bus) {
Kevin O'Connor0f654a92011-07-02 14:32:11 -0400122 // 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'Connorb98a4b12013-06-08 21:53:36 -0400129 hlist_add(&dev->node, pprev);
Kevin O'Connor2a9aeab2013-07-14 13:55:52 -0400130 pprev = &dev->node.next;
Kevin O'Connor0f654a92011-07-02 14:32:11 -0400131 count++;
Kevin O'Connor096a9b12011-06-19 14:09:20 -0400132
Kevin O'Connor0f654a92011-07-02 14:32:11 -0400133 // 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'Connor3076cfb2011-07-02 18:39:03 -0400141 if (bus > MaxPCIBus)
142 MaxPCIBus = bus;
Kevin O'Connor0f654a92011-07-02 14:32:11 -0400143 } else {
144 rootbus = parent->rootbus;
145 }
Kevin O'Connor096a9b12011-06-19 14:09:20 -0400146
Kevin O'Connor0f654a92011-07-02 14:32:11 -0400147 // 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'Connor096a9b12011-06-19 14:09:20 -0400172 }
173 }
Kevin O'Connor0f654a92011-07-02 14:32:11 -0400174 dprintf(1, "Found %d PCI devices (max PCI bus is %02x)\n", count, MaxPCIBus);
Kevin O'Connor096a9b12011-06-19 14:09:20 -0400175}
176
Kevin O'Connorbe19cdc2008-11-09 15:33:47 -0500177// Search for a device with the specified vendor and device ids.
Kevin O'Connor0cd70052011-07-02 14:04:19 -0400178struct pci_device *
Kevin O'Connor4132e022008-12-04 19:39:10 -0500179pci_find_device(u16 vendid, u16 devid)
Kevin O'Connorbe19cdc2008-11-09 15:33:47 -0500180{
Kevin O'Connor0cd70052011-07-02 14:04:19 -0400181 struct pci_device *pci;
182 foreachpci(pci) {
183 if (pci->vendor == vendid && pci->device == devid)
184 return pci;
Kevin O'Connor0f803e42008-05-24 23:07:16 -0400185 }
Kevin O'Connor0cd70052011-07-02 14:04:19 -0400186 return NULL;
Kevin O'Connor0f803e42008-05-24 23:07:16 -0400187}
188
Kevin O'Connor5fdaa032008-08-31 11:06:27 -0400189// Search for a device with the specified class id.
Kevin O'Connor0cd70052011-07-02 14:04:19 -0400190struct pci_device *
Kevin O'Connor4132e022008-12-04 19:39:10 -0500191pci_find_class(u16 classid)
Kevin O'Connor5fdaa032008-08-31 11:06:27 -0400192{
Kevin O'Connor0cd70052011-07-02 14:04:19 -0400193 struct pci_device *pci;
194 foreachpci(pci) {
195 if (pci->class == classid)
196 return pci;
Kevin O'Connor0f803e42008-05-24 23:07:16 -0400197 }
Kevin O'Connor0cd70052011-07-02 14:04:19 -0400198 return NULL;
Kevin O'Connora0dc2962008-03-16 14:29:32 -0400199}
Isaku Yamahata968d3a82010-07-07 12:14:01 +0900200
Kevin O'Connor278b19f2011-06-21 22:41:15 -0400201int pci_init_device(const struct pci_device_id *ids
202 , struct pci_device *pci, void *arg)
Isaku Yamahata968d3a82010-07-07 12:14:01 +0900203{
Isaku Yamahata968d3a82010-07-07 12:14:01 +0900204 while (ids->vendid || ids->class_mask) {
Kevin O'Connor278b19f2011-06-21 22:41:15 -0400205 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 Yamahata968d3a82010-07-07 12:14:01 +0900210 return 0;
211 }
212 ids++;
213 }
214 return -1;
215}
Isaku Yamahata23173ac2010-07-20 16:37:15 +0900216
Kevin O'Connor278b19f2011-06-21 22:41:15 -0400217struct pci_device *
218pci_find_init_device(const struct pci_device_id *ids, void *arg)
Isaku Yamahata23173ac2010-07-20 16:37:15 +0900219{
Kevin O'Connor278b19f2011-06-21 22:41:15 -0400220 struct pci_device *pci;
221 foreachpci(pci) {
222 if (pci_init_device(ids, pci, arg) == 0)
223 return pci;
Isaku Yamahata23173ac2010-07-20 16:37:15 +0900224 }
Kevin O'Connor278b19f2011-06-21 22:41:15 -0400225 return NULL;
Isaku Yamahata23173ac2010-07-20 16:37:15 +0900226}
Kevin O'Connoradaf3732010-09-13 20:22:07 -0400227
228void
229pci_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}