49698ac58b8d0a7481007d9e42021d9c4336155b
[seabios.git] / src / pci.c
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 //
6 // This file may be distributed under the terms of the GNU LGPLv3 license.
7
8 #include "pci.h" // pci_config_writel
9 #include "ioport.h" // outl
10 #include "util.h" // dprintf
11 #include "paravirt.h" // romfile_loadint
12 #include "farptr.h" // MAKE_FLATPTR
13 #include "pci_regs.h" // PCI_VENDOR_ID
14 #include "pci_ids.h" // PCI_CLASS_DISPLAY_VGA
15
16 void pci_config_writel(u16 bdf, u32 addr, u32 val)
17 {
18     outl(0x80000000 | (bdf << 8) | (addr & 0xfc), PORT_PCI_CMD);
19     outl(val, PORT_PCI_DATA);
20 }
21
22 void pci_config_writew(u16 bdf, u32 addr, u16 val)
23 {
24     outl(0x80000000 | (bdf << 8) | (addr & 0xfc), PORT_PCI_CMD);
25     outw(val, PORT_PCI_DATA + (addr & 2));
26 }
27
28 void pci_config_writeb(u16 bdf, u32 addr, u8 val)
29 {
30     outl(0x80000000 | (bdf << 8) | (addr & 0xfc), PORT_PCI_CMD);
31     outb(val, PORT_PCI_DATA + (addr & 3));
32 }
33
34 u32 pci_config_readl(u16 bdf, u32 addr)
35 {
36     outl(0x80000000 | (bdf << 8) | (addr & 0xfc), PORT_PCI_CMD);
37     return inl(PORT_PCI_DATA);
38 }
39
40 u16 pci_config_readw(u16 bdf, u32 addr)
41 {
42     outl(0x80000000 | (bdf << 8) | (addr & 0xfc), PORT_PCI_CMD);
43     return inw(PORT_PCI_DATA + (addr & 2));
44 }
45
46 u8 pci_config_readb(u16 bdf, u32 addr)
47 {
48     outl(0x80000000 | (bdf << 8) | (addr & 0xfc), PORT_PCI_CMD);
49     return inb(PORT_PCI_DATA + (addr & 3));
50 }
51
52 void
53 pci_config_maskw(u16 bdf, u32 addr, u16 off, u16 on)
54 {
55     u16 val = pci_config_readw(bdf, addr);
56     val = (val & ~off) | on;
57     pci_config_writew(bdf, addr, val);
58 }
59
60 // Helper function for foreachbdf() macro - return next device
61 int
62 pci_next(int bdf, int bus)
63 {
64     if (pci_bdf_to_fn(bdf) == 0
65         && (pci_config_readb(bdf, PCI_HEADER_TYPE) & 0x80) == 0)
66         // Last found device wasn't a multi-function device - skip to
67         // the next device.
68         bdf += 8;
69     else
70         bdf += 1;
71
72     for (;;) {
73         if (pci_bdf_to_bus(bdf) != bus)
74             return -1;
75
76         u16 v = pci_config_readw(bdf, PCI_VENDOR_ID);
77         if (v != 0x0000 && v != 0xffff)
78             // Device is present.
79             return bdf;
80
81         if (pci_bdf_to_fn(bdf) == 0)
82             bdf += 8;
83         else
84             bdf += 1;
85     }
86 }
87
88 struct pci_device *PCIDevices;
89 int MaxPCIBus VAR16VISIBLE;
90
91 // Find all PCI devices and populate PCIDevices linked list.
92 void
93 pci_probe(void)
94 {
95     dprintf(3, "PCI probe\n");
96     struct pci_device *busdevs[256];
97     memset(busdevs, 0, sizeof(busdevs));
98     struct pci_device **pprev = &PCIDevices;
99     int extraroots = romfile_loadint("etc/extra-pci-roots", 0);
100     int bus = -1, lastbus = 0, rootbuses = 0, count=0;
101     while (bus < 0xff && (bus < MaxPCIBus || rootbuses < extraroots)) {
102         bus++;
103         int bdf;
104         foreachbdf(bdf, bus) {
105             // Create new pci_device struct and add to list.
106             struct pci_device *dev = malloc_tmp(sizeof(*dev));
107             if (!dev) {
108                 warn_noalloc();
109                 return;
110             }
111             memset(dev, 0, sizeof(*dev));
112             *pprev = dev;
113             pprev = &dev->next;
114             count++;
115
116             // Find parent device.
117             int rootbus;
118             struct pci_device *parent = busdevs[bus];
119             if (!parent) {
120                 if (bus != lastbus)
121                     rootbuses++;
122                 lastbus = bus;
123                 rootbus = rootbuses;
124                 if (bus > MaxPCIBus)
125                     MaxPCIBus = bus;
126             } else {
127                 rootbus = parent->rootbus;
128             }
129
130             // Populate pci_device info.
131             dev->bdf = bdf;
132             dev->parent = parent;
133             dev->rootbus = rootbus;
134             u32 vendev = pci_config_readl(bdf, PCI_VENDOR_ID);
135             dev->vendor = vendev & 0xffff;
136             dev->device = vendev >> 16;
137             u32 classrev = pci_config_readl(bdf, PCI_CLASS_REVISION);
138             dev->class = classrev >> 16;
139             dev->prog_if = classrev >> 8;
140             dev->revision = classrev & 0xff;
141             dev->header_type = pci_config_readb(bdf, PCI_HEADER_TYPE);
142             u8 v = dev->header_type & 0x7f;
143             if (v == PCI_HEADER_TYPE_BRIDGE || v == PCI_HEADER_TYPE_CARDBUS) {
144                 u8 secbus = pci_config_readb(bdf, PCI_SECONDARY_BUS);
145                 dev->secondary_bus = secbus;
146                 if (secbus > bus && !busdevs[secbus])
147                     busdevs[secbus] = dev;
148                 if (secbus > MaxPCIBus)
149                     MaxPCIBus = secbus;
150             }
151             dprintf(4, "PCI device %02x:%02x.%x (vd=%04x:%04x c=%04x)\n"
152                     , pci_bdf_to_bus(bdf), pci_bdf_to_dev(bdf)
153                     , pci_bdf_to_fn(bdf)
154                     , dev->vendor, dev->device, dev->class);
155         }
156     }
157     dprintf(1, "Found %d PCI devices (max PCI bus is %02x)\n", count, MaxPCIBus);
158 }
159
160 // Search for a device with the specified vendor and device ids.
161 struct pci_device *
162 pci_find_device(u16 vendid, u16 devid)
163 {
164     struct pci_device *pci;
165     foreachpci(pci) {
166         if (pci->vendor == vendid && pci->device == devid)
167             return pci;
168     }
169     return NULL;
170 }
171
172 // Search for a device with the specified class id.
173 struct pci_device *
174 pci_find_class(u16 classid)
175 {
176     struct pci_device *pci;
177     foreachpci(pci) {
178         if (pci->class == classid)
179             return pci;
180     }
181     return NULL;
182 }
183
184 int pci_init_device(const struct pci_device_id *ids
185                     , struct pci_device *pci, void *arg)
186 {
187     while (ids->vendid || ids->class_mask) {
188         if ((ids->vendid == PCI_ANY_ID || ids->vendid == pci->vendor) &&
189             (ids->devid == PCI_ANY_ID || ids->devid == pci->device) &&
190             !((ids->class ^ pci->class) & ids->class_mask)) {
191             if (ids->func)
192                 ids->func(pci, arg);
193             return 0;
194         }
195         ids++;
196     }
197     return -1;
198 }
199
200 struct pci_device *
201 pci_find_init_device(const struct pci_device_id *ids, void *arg)
202 {
203     struct pci_device *pci;
204     foreachpci(pci) {
205         if (pci_init_device(ids, pci, arg) == 0)
206             return pci;
207     }
208     return NULL;
209 }
210
211 void
212 pci_reboot(void)
213 {
214     u8 v = inb(PORT_PCI_REBOOT) & ~6;
215     outb(v|2, PORT_PCI_REBOOT); /* Request hard reset */
216     udelay(50);
217     outb(v|6, PORT_PCI_REBOOT); /* Actually do the reset */
218     udelay(50);
219 }
220
221 // helper functions to access pci mmio bars from real mode
222
223 u32 VISIBLE32FLAT
224 pci_readl_32(u32 addr)
225 {
226     dprintf(3, "32: pci read : %x\n", addr);
227     return readl((void*)addr);
228 }
229
230 u32 pci_readl(u32 addr)
231 {
232     if (MODESEGMENT) {
233         dprintf(3, "16: pci read : %x\n", addr);
234         extern void _cfunc32flat_pci_readl_32(u32 addr);
235         return call32(_cfunc32flat_pci_readl_32, addr, -1);
236     } else {
237         return pci_readl_32(addr);
238     }
239 }
240
241 struct reg32 {
242     u32 addr;
243     u32 data;
244 };
245
246 void VISIBLE32FLAT
247 pci_writel_32(struct reg32 *reg32)
248 {
249     dprintf(3, "32: pci write: %x, %x (%p)\n", reg32->addr, reg32->data, reg32);
250     writel((void*)(reg32->addr), reg32->data);
251 }
252
253 void pci_writel(u32 addr, u32 val)
254 {
255     struct reg32 reg32 = { .addr = addr, .data = val };
256     if (MODESEGMENT) {
257         dprintf(3, "16: pci write: %x, %x (%x:%p)\n",
258                 reg32.addr, reg32.data, GET_SEG(SS), &reg32);
259         void *flatptr = MAKE_FLATPTR(GET_SEG(SS), &reg32);
260         extern void _cfunc32flat_pci_writel_32(struct reg32 *reg32);
261         call32(_cfunc32flat_pci_writel_32, (u32)flatptr, -1);
262     } else {
263         pci_writel_32(&reg32);
264     }
265 }