1 /* SPDX-License-Identifier: GPL-2.0-only */
2
3 #include <acpi/acpi.h>
4 #include <types.h>
5 #include <string.h>
6 #include <cbfs.h>
7 #include <device/device.h>
8 #include <device/pci.h>
9 #include <device/pci_ids.h>
10 #include <device/pci_ops.h>
11 #include <console/console.h>
12 #include <cbmem.h>
13 #include "intel_bios.h"
14 #include "opregion.h"
15
16 __weak
mainboard_vbt_filename(void)17 const char *mainboard_vbt_filename(void)
18 {
19 return "vbt.bin";
20 }
21
locate_vbt(size_t * vbt_size)22 void *locate_vbt(size_t *vbt_size)
23 {
24 static void *data;
25 static size_t size;
26
27 if (data)
28 goto out;
29
30 data = cbfs_map(mainboard_vbt_filename(), &size);
31 if (!data || size == 0) {
32 printk(BIOS_ERR, "Could not find or load %s CBFS file\n",
33 mainboard_vbt_filename());
34 goto err;
35 }
36
37 if (*(uint32_t *)data == VBT_SIGNATURE) {
38 printk(BIOS_INFO, "Found a VBT of %zu bytes\n", size);
39 goto out;
40 }
41
42 printk(BIOS_ERR, "Missing/invalid signature in VBT data file!\n");
43
44 err:
45 if (data) {
46 cbfs_unmap(data);
47 data = NULL;
48 }
49 size = 0;
50
51 out:
52 if (vbt_size && size)
53 *vbt_size = size;
54 return data;
55 }
56
57 /* Write ASLS PCI register and prepare SWSCI register. */
intel_gma_opregion_register(uintptr_t opregion)58 static void intel_gma_opregion_register(uintptr_t opregion)
59 {
60 struct device *igd;
61 u16 reg16;
62 u16 sci_reg;
63
64 igd = pcidev_on_root(0x2, 0);
65 if (!igd || !igd->enabled)
66 return;
67
68 /*
69 * Intel BIOS Specification
70 * Chapter 5.3.7 "Initialize Hardware State"
71 */
72 pci_write_config32(igd, ASLS, opregion);
73
74 /*
75 * Atom-based platforms use a combined SMI/SCI register,
76 * whereas non-Atom platforms use a separate SCI register.
77 */
78 if (CONFIG(INTEL_GMA_SWSMISCI))
79 sci_reg = SWSMISCI;
80 else
81 sci_reg = SWSCI;
82
83 /*
84 * Intel's Windows driver relies on this:
85 * Intel BIOS Specification
86 * Chapter 5.4 "ASL Software SCI Handler"
87 */
88 reg16 = pci_read_config16(igd, sci_reg);
89 reg16 &= ~GSSCIE;
90 reg16 |= SMISCISEL;
91 pci_write_config16(igd, sci_reg, reg16);
92 }
93
94 /* Restore ASLS register on S3 resume and prepare SWSCI. */
intel_gma_restore_opregion(void)95 static enum cb_err intel_gma_restore_opregion(void)
96 {
97 const igd_opregion_t *const opregion = cbmem_find(CBMEM_ID_IGD_OPREGION);
98 if (!opregion) {
99 printk(BIOS_ERR, "GMA: Failed to find IGD OpRegion.\n");
100 return CB_ERR;
101 }
102 /* Write ASLS PCI register and prepare SWSCI register. */
103 intel_gma_opregion_register((uintptr_t)opregion);
104 return CB_SUCCESS;
105 }
106
vbt_validate(struct region_device * rdev)107 static enum cb_err vbt_validate(struct region_device *rdev)
108 {
109 uint32_t sig;
110
111 if (rdev_readat(rdev, &sig, 0, sizeof(sig)) != sizeof(sig))
112 return CB_ERR;
113
114 if (sig != VBT_SIGNATURE)
115 return CB_ERR;
116
117 return CB_SUCCESS;
118 }
119
locate_vbt_vbios(const u8 * vbios,struct region_device * rdev)120 static enum cb_err locate_vbt_vbios(const u8 *vbios, struct region_device *rdev)
121 {
122 const optionrom_header_t *oprom;
123 const optionrom_pcir_t *pcir;
124 struct region_device rd;
125 enum cb_err ret;
126 u8 opromsize;
127 size_t offset;
128
129 // FIXME: caller should supply a region_device instead of vbios pointer
130 if (rdev_chain_mem(&rd, vbios, sizeof(*oprom)))
131 return CB_ERR;
132
133 if (rdev_readat(&rd, &opromsize, offsetof(optionrom_header_t, size),
134 sizeof(opromsize)) != sizeof(opromsize) || !opromsize)
135 return CB_ERR;
136
137 if (rdev_chain_mem(&rd, vbios, opromsize * 512))
138 return CB_ERR;
139
140 oprom = rdev_mmap(&rd, 0, sizeof(*oprom));
141 if (!oprom)
142 return CB_ERR;
143
144 if (!oprom->pcir_offset || !oprom->vbt_offset) {
145 rdev_munmap(&rd, (void *)oprom);
146 return CB_ERR;
147 }
148
149 pcir = rdev_mmap(&rd, oprom->pcir_offset, sizeof(*pcir));
150 if (pcir == NULL) {
151 rdev_munmap(&rd, (void *)oprom);
152 return CB_ERR;
153 }
154
155 printk(BIOS_DEBUG, "GMA: %s: %x %x %x %x %x\n", __func__,
156 oprom->signature, pcir->vendor, pcir->classcode[0],
157 pcir->classcode[1], pcir->classcode[2]);
158
159 /* Make sure we got an Intel VGA option rom */
160 if ((oprom->signature != OPROM_SIGNATURE) ||
161 (pcir->vendor != PCI_VID_INTEL) ||
162 (pcir->signature != 0x52494350) ||
163 (pcir->classcode[0] != 0x00) ||
164 (pcir->classcode[1] != 0x00) ||
165 (pcir->classcode[2] != 0x03)) {
166 rdev_munmap(&rd, (void *)oprom);
167 rdev_munmap(&rd, (void *)pcir);
168 return CB_ERR;
169 }
170
171 rdev_munmap(&rd, (void *)pcir);
172
173 /* Search for $VBT as some VBIOS are broken... */
174 offset = oprom->vbt_offset;
175 do {
176 ret = rdev_chain(rdev, &rd, offset,
177 (opromsize * 512) - offset);
178 offset++;
179 } while (ret == CB_SUCCESS && vbt_validate(rdev) != CB_SUCCESS);
180
181 offset--;
182
183 if (ret == CB_SUCCESS && offset != oprom->vbt_offset)
184 printk(BIOS_WARNING, "GMA: Buggy VBIOS found\n");
185 else if (ret != CB_SUCCESS)
186 printk(BIOS_ERR, "GMA: Broken VBIOS found\n");
187
188 rdev_munmap(&rd, (void *)oprom);
189 return ret;
190 }
191
locate_vbt_cbfs(struct region_device * rdev)192 static enum cb_err locate_vbt_cbfs(struct region_device *rdev)
193 {
194 size_t vbt_data_size;
195 void *vbt = locate_vbt(&vbt_data_size);
196
197 if (vbt == NULL)
198 return CB_ERR;
199
200 if (rdev_chain_mem(rdev, vbt, vbt_data_size))
201 return CB_ERR;
202
203 printk(BIOS_INFO, "GMA: Found VBT in CBFS\n");
204
205 return CB_SUCCESS;
206 }
207
locate_vbt_vbios_cbfs(struct region_device * rdev)208 static enum cb_err locate_vbt_vbios_cbfs(struct region_device *rdev)
209 {
210 const u8 *oprom =
211 (const u8 *)pci_rom_probe(pcidev_on_root(0x2, 0));
212 if (oprom == NULL)
213 return CB_ERR;
214
215 printk(BIOS_INFO, "GMA: Found VBIOS in CBFS\n");
216
217 return locate_vbt_vbios(oprom, rdev);
218 }
219
220 /*
221 * Try to locate VBT in possible locations and return if found.
222 * VBT can be possibly in one of 3 regions:
223 * 1. Stitched directly into CBFS region as VBT
224 * 2. Part of pci8086 option ROM within CBFS
225 * 3. part of VBIOS at location 0xC0000.
226 */
find_vbt_location(struct region_device * rdev)227 static enum cb_err find_vbt_location(struct region_device *rdev)
228 {
229 /* Search for vbt.bin in CBFS. */
230 if (locate_vbt_cbfs(rdev) == CB_SUCCESS &&
231 vbt_validate(rdev) == CB_SUCCESS) {
232 printk(BIOS_INFO, "GMA: Found valid VBT in CBFS\n");
233 return CB_SUCCESS;
234 }
235 /* Search for pci8086,XXXX.rom in CBFS. */
236 else if (locate_vbt_vbios_cbfs(rdev) == CB_SUCCESS &&
237 vbt_validate(rdev) == CB_SUCCESS) {
238 printk(BIOS_INFO, "GMA: Found valid VBT in VBIOS\n");
239 return CB_SUCCESS;
240 }
241 /*
242 * Try to locate Intel VBIOS at 0xc0000. It might have been placed by
243 * Native Graphics Init as fake Option ROM or when coreboot did run the
244 * VBIOS on legacy platforms.
245 * TODO: Place generated fake VBT in CBMEM and get rid of this.
246 */
247 else if (locate_vbt_vbios((u8 *)0xc0000, rdev) == CB_SUCCESS &&
248 vbt_validate(rdev) == CB_SUCCESS) {
249 printk(BIOS_INFO, "GMA: Found valid VBT in legacy area\n");
250 return CB_SUCCESS;
251 }
252
253 printk(BIOS_ERR, "GMA: VBT couldn't be found\n");
254 return CB_ERR;
255 }
256
257 /* Function to get the IGD Opregion version */
opregion_get_version(void)258 static struct opregion_version opregion_get_version(void)
259 {
260 if (CONFIG(INTEL_GMA_OPREGION_2_1))
261 return (struct opregion_version) { .major = 2, .minor = 1 };
262
263 return (struct opregion_version) { .major = 2, .minor = 0 };
264 }
265
266 /*
267 * Function to determine if we need to use extended VBT region to pass
268 * VBT pointer. If VBT size > 6 KiB then we need to use extended VBT
269 * region.
270 */
is_ext_vbt_required(igd_opregion_t * opregion,optionrom_vbt_t * vbt)271 static inline bool is_ext_vbt_required(igd_opregion_t *opregion, optionrom_vbt_t *vbt)
272 {
273 return (vbt->hdr_vbt_size > sizeof(opregion->vbt.gvd1));
274 }
275
276 /* Function to determine if the VBT uses a relative address */
uses_relative_vbt_addr(opregion_header_t * header)277 static inline bool uses_relative_vbt_addr(opregion_header_t *header)
278 {
279 if (header->opver.major > 2)
280 return true;
281
282 return header->opver.major >= 2 && header->opver.minor >= 1;
283 }
284
285 /*
286 * Copy extended VBT at the end of opregion and fill rvda and rvds
287 * values correctly for the opregion.
288 */
opregion_add_ext_vbt(igd_opregion_t * opregion,uint8_t * ext_vbt,optionrom_vbt_t * vbt)289 static void opregion_add_ext_vbt(igd_opregion_t *opregion, uint8_t *ext_vbt,
290 optionrom_vbt_t *vbt)
291 {
292 opregion_header_t *header = &opregion->header;
293 /* Copy VBT into extended VBT region (at offset 8 KiB) */
294 memcpy(ext_vbt, vbt, vbt->hdr_vbt_size);
295
296 /* Fill RVDA value with relative address of the opregion buffer in case of
297 IGD Opregion version 2.1+ and physical address otherwise */
298
299 if (uses_relative_vbt_addr(header))
300 opregion->mailbox3.rvda = sizeof(*opregion);
301 else
302 opregion->mailbox3.rvda = (uintptr_t)ext_vbt;
303
304 opregion->mailbox3.rvds = vbt->hdr_vbt_size;
305 }
306
307 /* Initialize IGD OpRegion, called from ACPI code and OS drivers */
intel_gma_init_igd_opregion(void)308 enum cb_err intel_gma_init_igd_opregion(void)
309 {
310 igd_opregion_t *opregion;
311 struct region_device rdev;
312 optionrom_vbt_t *vbt = NULL;
313 size_t opregion_size = sizeof(igd_opregion_t);
314
315 if (acpi_is_wakeup_s3())
316 return intel_gma_restore_opregion();
317
318 if (find_vbt_location(&rdev) != CB_SUCCESS)
319 return CB_ERR;
320
321 vbt = rdev_mmap_full(&rdev);
322 if (!vbt) {
323 printk(BIOS_ERR, "GMA: Error mapping VBT\n");
324 return CB_ERR;
325 }
326
327 if (vbt->hdr_vbt_size > region_device_sz(&rdev)) {
328 printk(BIOS_ERR, "GMA: Error mapped only a partial VBT\n");
329 rdev_munmap(&rdev, vbt);
330 return CB_ERR;
331 }
332
333 /* Add the space for the extended VBT header even if it's not used */
334 opregion_size += vbt->hdr_vbt_size;
335
336 opregion = cbmem_add(CBMEM_ID_IGD_OPREGION, opregion_size);
337 if (!opregion) {
338 printk(BIOS_ERR, "GMA: Failed to add IGD OpRegion to CBMEM.\n");
339 return CB_ERR;
340 }
341
342 memset(opregion, 0, opregion_size);
343
344 memcpy(&opregion->header.signature, IGD_OPREGION_SIGNATURE,
345 sizeof(opregion->header.signature));
346 memcpy(opregion->header.vbios_version, vbt->coreblock_biosbuild,
347 ARRAY_SIZE(vbt->coreblock_biosbuild));
348
349 /* Get the opregion version information */
350 opregion->header.opver = opregion_get_version();
351
352 /* Extended VBT support */
353 if (is_ext_vbt_required(opregion, vbt)) {
354 /* Place extended VBT just after opregion */
355 uint8_t *ext_vbt = (uint8_t *)opregion + sizeof(*opregion);
356 opregion_add_ext_vbt(opregion, ext_vbt, vbt);
357 } else {
358 /* Raw VBT size which can fit in gvd1 */
359 memcpy(opregion->vbt.gvd1, vbt, vbt->hdr_vbt_size);
360 }
361
362 rdev_munmap(&rdev, vbt);
363
364 /* 8kb */
365 opregion->header.size = sizeof(igd_opregion_t) / 1024;
366
367 // FIXME We just assume we're mobile for now
368 opregion->header.mailboxes = MAILBOXES_MOBILE;
369
370 // TODO Initialize Mailbox 1
371 opregion->mailbox1.clid = 1;
372
373 // TODO Initialize Mailbox 3
374 opregion->mailbox3.bclp = IGD_BACKLIGHT_BRIGHTNESS;
375 opregion->mailbox3.pfit = IGD_FIELD_VALID | IGD_PFIT_STRETCH;
376 opregion->mailbox3.pcft = 0; // should be (IMON << 1) & 0x3e
377 opregion->mailbox3.cblv = IGD_FIELD_VALID | IGD_INITIAL_BRIGHTNESS;
378 opregion->mailbox3.bclm[0] = IGD_WORD_FIELD_VALID + 0x0000;
379 opregion->mailbox3.bclm[1] = IGD_WORD_FIELD_VALID + 0x0a19;
380 opregion->mailbox3.bclm[2] = IGD_WORD_FIELD_VALID + 0x1433;
381 opregion->mailbox3.bclm[3] = IGD_WORD_FIELD_VALID + 0x1e4c;
382 opregion->mailbox3.bclm[4] = IGD_WORD_FIELD_VALID + 0x2866;
383 opregion->mailbox3.bclm[5] = IGD_WORD_FIELD_VALID + 0x327f;
384 opregion->mailbox3.bclm[6] = IGD_WORD_FIELD_VALID + 0x3c99;
385 opregion->mailbox3.bclm[7] = IGD_WORD_FIELD_VALID + 0x46b2;
386 opregion->mailbox3.bclm[8] = IGD_WORD_FIELD_VALID + 0x50cc;
387 opregion->mailbox3.bclm[9] = IGD_WORD_FIELD_VALID + 0x5ae5;
388 opregion->mailbox3.bclm[10] = IGD_WORD_FIELD_VALID + 0x64ff;
389
390 /* Write ASLS PCI register and prepare SWSCI register. */
391 intel_gma_opregion_register((uintptr_t)opregion);
392
393 return CB_SUCCESS;
394 }
395