xref: /aosp_15_r20/external/coreboot/src/drivers/intel/gma/opregion.c (revision b9411a12aaaa7e1e6a6fb7c5e057f44ee179a49c)
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