1 /*
2  * Copyright (c) 2009 Corey Tabaka
3  * Copyright (c) 2020 Travis Geiselbrecht
4  *
5  * Use of this source code is governed by a MIT-style
6  * license that can be found in the LICENSE file or at
7  * https://opensource.org/licenses/MIT
8  */
9 #include "bios32.h"
10 
11 #include <lk/debug.h>
12 #include <lk/err.h>
13 #include <stdlib.h>
14 #include <string.h>
15 #include <kernel/thread.h>
16 #include <kernel/spinlock.h>
17 #include <dev/bus/pci.h>
18 #include <lk/trace.h>
19 
20 #include "../pci_priv.h"
21 
22 #if ARCH_X86_32
23 // Only actually supported on x86-32
24 
25 #include <arch/x86/descriptor.h>
26 
27 #define LOCAL_TRACE 0
28 
29 /*
30  * PCI BIOS access return codes
31  */
32 // TODO: write routine to convert these to LK errors
33 #define _PCI_SUCCESSFUL             0x00
34 #define _PCI_FUNC_NOT_SUPPORTED     0x81
35 #define _PCI_BAD_VENDOR_ID          0x83
36 #define _PCI_DEVICE_NOT_FOUND       0x86
37 #define _PCI_BAD_REGISTER_NUMBER    0x87
38 #define _PCI_SET_FAILED             0x88
39 #define _PCI_BUFFER_TOO_SMALL       0x89
40 
41 
42 #define PCIBIOS_PRESENT                 0xB101
43 #define PCIBIOS_FIND_PCI_DEVICE         0xB102
44 #define PCIBIOS_FIND_PCI_CLASS_CODE     0xB103
45 #define PCIBIOS_GENERATE_SPECIAL_CYCLE  0xB106
46 #define PCIBIOS_READ_CONFIG_BYTE        0xB108
47 #define PCIBIOS_READ_CONFIG_WORD        0xB109
48 #define PCIBIOS_READ_CONFIG_DWORD       0xB10A
49 #define PCIBIOS_WRITE_CONFIG_BYTE       0xB10B
50 #define PCIBIOS_WRITE_CONFIG_WORD       0xB10C
51 #define PCIBIOS_WRITE_CONFIG_DWORD      0xB10D
52 #define PCIBIOS_GET_IRQ_ROUTING_OPTIONS 0xB10E
53 #define PCIBIOS_PCI_SET_IRQ_HW_INT      0xB10F
54 
55 #define PCIBIOS_SUCCESSFUL              0x00
56 #define PCIBIOS_FUNC_NOT_SUPPORTED      0x81
57 #define PCIBIOS_BAD_VENDOR_ID           0x83
58 #define PCIBIOS_DEVICE_NOT_FOUND        0x86
59 #define PCIBIOS_BAD_REGISTER_NUMBER     0x87
60 #define PCIBIOS_SET_FAILED              0x88
61 #define PCIBIOS_BUFFER_TOO_SMALL        0x89
62 
63 /*
64  * BIOS32 entry header
65  */
66 typedef struct {
67     uint8_t magic[4];   // "_32_"
68     void *entry;        // entry point
69     uint8_t revision;
70     uint8_t length;
71     uint8_t checksum;
72     uint8_t reserved[5];
73 } __PACKED pci_bios_info;
74 
75 /*
76  * scan for pci bios
77  */
78 static const char *pci_bios_magic = "_32_";
find_pci_bios_info(void)79 static pci_bios_info *find_pci_bios_info(void) {
80     uint32_t *head = (uint32_t *) (0x000e0000 + KERNEL_BASE);
81     int8_t sum, *b;
82     uint i;
83 
84     while (head < (uint32_t *) (0x000ffff0 + KERNEL_BASE)) {
85         if (*head == *(uint32_t *) pci_bios_magic) {
86             // perform the checksum
87             sum = 0;
88             b = (int8_t *) head;
89             for (i=0; i < sizeof(pci_bios_info); i++) {
90                 sum += b[i];
91             }
92 
93             if (sum == 0) {
94                 return (pci_bios_info *) head;
95             }
96         }
97 
98         head += 4;
99     }
100 
101     return NULL;
102 }
103 
104 /*
105  * local BIOS32 PCI routines
106  */
107 static const char *pci_signature = "PCI ";
108 
109 // new C++ version
detect()110 pci_bios32 *pci_bios32::detect() {
111     LTRACE_ENTRY;
112 
113     pci_bios_info *pci = find_pci_bios_info();
114     if (!pci) {
115         return nullptr;
116     }
117 
118     dprintf(INFO, "PCI: found BIOS32 structure at %p\n", pci);
119 
120     LTRACEF("BIOS32 header info:\n");
121     LTRACEF("magic '%c%c%c%c' entry %p len %d checksum %#hhx\n",
122             pci->magic[0], pci->magic[1], pci->magic[2], pci->magic[3],
123             pci->entry, pci->length * 16, pci->checksum);
124 
125     uint32_t adr, temp, len;
126     uint8_t err;
127 
128     bios32_entry b32_entry;
129     b32_entry.offset = (uint32_t)(uintptr_t)pci->entry + KERNEL_BASE;
130     b32_entry.selector = CODE_SELECTOR;
131 
132     __asm__(
133         "lcall *(%%edi)"
134         : "=a"(err),    /* AL out=status */
135         "=b"(adr),    /* EBX out=code segment base adr */
136         "=c"(len),    /* ECX out=code segment size */
137         "=d"(temp)    /* EDX out=entry pt offset in code */
138         : "0"(0x49435024),/* EAX in=service="$PCI" */
139         "1"(0),   /* EBX in=0=get service entry pt */
140         "D"(&b32_entry)
141         : "cc", "memory"
142     );
143 
144     if (err == 0x80) {
145         dprintf(INFO, "BIOS32 found, but no PCI BIOS\n");
146         return nullptr;
147     }
148 
149     if (err != 0) {
150         dprintf(INFO, "BIOS32 call to locate PCI BIOS returned %x\n", err);
151         return nullptr;
152     }
153 
154     LTRACEF("BIOS32 entry segment base %#x offset %#x\n", adr, temp);
155 
156     b32_entry.offset = adr + temp + KERNEL_BASE;
157 
158     // now call PCI_BIOS_PRESENT to get version, hw mechanism, and last bus
159     uint16_t present, version, busses;
160     uint32_t signature;
161     __asm__(
162         "lcall *(%%edi)		\n\t"
163         "jc 1f				\n\t"
164         "xor %%ah,%%ah		\n"
165         "1:"
166         : "=a"(present),
167         "=b"(version),
168         "=c"(busses),
169         "=d"(signature)
170         : "0"(PCIBIOS_PRESENT),
171         "D"(&b32_entry)
172         : "cc", "memory"
173     );
174     LTRACEF("PCI_BIOS_PRESENT returns present %#x, version %#x, busses %#x, signature %#x\n",
175             present, version, busses, signature);
176 
177     if (present & 0xff00) {
178         dprintf(INFO, "PCI_BIOS_PRESENT call returned ah=%#02x\n", present >> 8);
179         return nullptr;
180     }
181 
182     if (signature != *(uint32_t *)pci_signature) {
183         dprintf(INFO, "PCI_BIOS_PRESENT call returned edx=%#08x\n", signature);
184         return nullptr;
185     }
186 
187     int last_bus = busses & 0xff;
188 
189     auto b32 = new pci_bios32(b32_entry);
190     b32->set_last_bus(last_bus);
191 
192     return b32;
193 }
194 
read_config_byte(const pci_location_t state,uint32_t reg,uint8_t * value)195 int pci_bios32::read_config_byte(const pci_location_t state, uint32_t reg, uint8_t *value) {
196     uint32_t bx, ret;
197 
198     bx = state.bus;
199     bx <<= 8;
200     bx |= (state.dev << 3) | state.fn;
201     __asm__(
202         "lcall *(%%esi)			\n\t"
203         "jc 1f					\n\t"
204         "xor %%ah,%%ah			\n"
205         "1:"
206         : "=c"(*value),
207         "=a"(ret)
208         : "1"(PCIBIOS_READ_CONFIG_BYTE),
209         "b"(bx),
210         "D"(reg),
211         "S"(&bios32_entry_)
212         : "cc", "memory");
213     ret >>= 8;
214     return ret & 0xFF;
215 }
216 
read_config_half(const pci_location_t state,uint32_t reg,uint16_t * value)217 int pci_bios32::read_config_half(const pci_location_t state, uint32_t reg, uint16_t *value) {
218     uint32_t bx, ret;
219 
220     bx = state.bus;
221     bx <<= 8;
222     bx |= (state.dev << 3) | state.fn;
223     __asm__(
224         "lcall *(%%esi)			\n\t"
225         "jc 1f					\n\t"
226         "xor %%ah,%%ah			\n"
227         "1:"
228         : "=c"(*value),
229         "=a"(ret)
230         : "1"(PCIBIOS_READ_CONFIG_WORD),
231         "b"(bx),
232         "D"(reg),
233         "S"(&bios32_entry_)
234         : "cc", "memory");
235     ret >>= 8;
236     return ret & 0xFF;
237 }
238 
read_config_word(const pci_location_t state,uint32_t reg,uint32_t * value)239 int pci_bios32::read_config_word(const pci_location_t state, uint32_t reg, uint32_t *value) {
240     uint32_t bx, ret;
241 
242     bx = state.bus;
243     bx <<= 8;
244     bx |= (state.dev << 3) | state.fn;
245     __asm__(
246         "lcall *(%%esi)			\n\t"
247         "jc 1f					\n\t"
248         "xor %%ah,%%ah			\n"
249         "1:"
250         : "=c"(*value),
251         "=a"(ret)
252         : "1"(PCIBIOS_READ_CONFIG_DWORD),
253         "b"(bx),
254         "D"(reg),
255         "S"(&bios32_entry_)
256         : "cc", "memory");
257     ret >>= 8;
258     return ret & 0xFF;
259 }
260 
write_config_byte(const pci_location_t state,uint32_t reg,uint8_t value)261 int pci_bios32::write_config_byte(const pci_location_t state, uint32_t reg, uint8_t value) {
262     uint32_t bx, ret;
263 
264     bx = state.bus;
265     bx <<= 8;
266     bx |= (state.dev << 3) | state.fn;
267     __asm__(
268         "lcall *(%%esi)			\n\t"
269         "jc 1f					\n\t"
270         "xor %%ah,%%ah			\n"
271         "1:"
272         : "=a"(ret)
273         : "0"(PCIBIOS_WRITE_CONFIG_BYTE),
274         "c"(value),
275         "b"(bx),
276         "D"(reg),
277         "S"(&bios32_entry_)
278         : "cc", "memory");
279     ret >>= 8;
280     return ret & 0xFF;
281 }
282 
write_config_half(const pci_location_t state,uint32_t reg,uint16_t value)283 int pci_bios32::write_config_half(const pci_location_t state, uint32_t reg, uint16_t value) {
284     uint32_t bx, ret;
285 
286     bx = state.bus;
287     bx <<= 8;
288     bx |= (state.dev << 3) | state.fn;
289     __asm__(
290         "lcall *(%%esi)	\n\t"
291         "jc 1f					\n\t"
292         "xor %%ah,%%ah			\n"
293         "1:"
294         : "=a"(ret)
295         : "0"(PCIBIOS_WRITE_CONFIG_WORD),
296         "c"(value),
297         "b"(bx),
298         "D"(reg),
299         "S"(&bios32_entry_)
300         : "cc", "memory");
301     ret >>= 8;
302     return ret & 0xFF;
303 }
304 
write_config_word(const pci_location_t state,uint32_t reg,uint32_t value)305 int pci_bios32::write_config_word(const pci_location_t state, uint32_t reg, uint32_t value) {
306     uint32_t bx, ret;
307 
308     bx = state.bus;
309     bx <<= 8;
310     bx |= (state.dev << 3) | state.fn;
311     __asm__(
312         "lcall *(%%esi)			\n\t"
313         "jc 1f					\n\t"
314         "xor %%ah,%%ah			\n"
315         "1:"
316         : "=a"(ret)
317         : "0"(PCIBIOS_WRITE_CONFIG_DWORD),
318         "c"(value),
319         "b"(bx),
320         "D"(reg),
321         "S"(&bios32_entry_)
322         : "cc", "memory");
323     ret >>= 8;
324     return ret & 0xFF;
325 }
326 
327 #endif
328