Tom Rini | 10e4779 | 2018-05-06 17:58:06 -0400 | [diff] [blame] | 1 | // SPDX-License-Identifier: GPL-2.0 |
Simon Glass | b2978d3 | 2014-11-14 20:56:32 -0700 | [diff] [blame] | 2 | /* |
| 3 | * From Coreboot file device/oprom/realmode/x86.c |
| 4 | * |
| 5 | * Copyright (C) 2007 Advanced Micro Devices, Inc. |
| 6 | * Copyright (C) 2009-2010 coresystems GmbH |
Simon Glass | b2978d3 | 2014-11-14 20:56:32 -0700 | [diff] [blame] | 7 | */ |
Simon Glass | 3ba929a | 2020-10-30 21:38:53 -0600 | [diff] [blame] | 8 | #include <compiler.h> |
Simon Glass | b2978d3 | 2014-11-14 20:56:32 -0700 | [diff] [blame] | 9 | #include <bios_emul.h> |
Simon Glass | 9b61c7c | 2019-11-14 12:57:41 -0700 | [diff] [blame] | 10 | #include <irq_func.h> |
Simon Glass | 0f2af88 | 2020-05-10 11:40:05 -0600 | [diff] [blame] | 11 | #include <log.h> |
Simon Glass | ec86bc6 | 2022-07-30 15:52:04 -0600 | [diff] [blame] | 12 | #include <vesa.h> |
Masahiro Yamada | d0506f7 | 2014-12-03 17:36:57 +0900 | [diff] [blame] | 13 | #include <linux/linkage.h> |
Simon Glass | b2978d3 | 2014-11-14 20:56:32 -0700 | [diff] [blame] | 14 | #include <asm/cache.h> |
| 15 | #include <asm/processor.h> |
| 16 | #include <asm/i8259.h> |
| 17 | #include <asm/io.h> |
| 18 | #include <asm/post.h> |
| 19 | #include "bios.h" |
| 20 | |
| 21 | /* Interrupt handlers for each interrupt the ROM can call */ |
| 22 | static int (*int_handler[256])(void); |
| 23 | |
| 24 | /* to have a common register file for interrupt handlers */ |
Simon Glass | ee95ec1 | 2023-07-15 21:38:58 -0600 | [diff] [blame] | 25 | #if !CONFIG_IS_ENABLED(BIOSEMU) |
Simon Glass | b2978d3 | 2014-11-14 20:56:32 -0700 | [diff] [blame] | 26 | X86EMU_sysEnv _X86EMU_env; |
Bin Meng | 9885721 | 2021-07-07 15:36:26 +0800 | [diff] [blame] | 27 | #endif |
Simon Glass | b2978d3 | 2014-11-14 20:56:32 -0700 | [diff] [blame] | 28 | |
| 29 | asmlinkage void (*realmode_call)(u32 addr, u32 eax, u32 ebx, u32 ecx, u32 edx, |
| 30 | u32 esi, u32 edi); |
| 31 | |
| 32 | asmlinkage void (*realmode_interrupt)(u32 intno, u32 eax, u32 ebx, u32 ecx, |
| 33 | u32 edx, u32 esi, u32 edi); |
| 34 | |
| 35 | static void setup_realmode_code(void) |
| 36 | { |
| 37 | memcpy((void *)REALMODE_BASE, &asm_realmode_code, |
| 38 | asm_realmode_code_size); |
| 39 | |
| 40 | /* Ensure the global pointers are relocated properly. */ |
| 41 | realmode_call = PTR_TO_REAL_MODE(asm_realmode_call); |
| 42 | realmode_interrupt = PTR_TO_REAL_MODE(__realmode_interrupt); |
| 43 | |
| 44 | debug("Real mode stub @%x: %d bytes\n", REALMODE_BASE, |
| 45 | asm_realmode_code_size); |
| 46 | } |
| 47 | |
| 48 | static void setup_rombios(void) |
| 49 | { |
| 50 | const char date[] = "06/11/99"; |
| 51 | memcpy((void *)0xffff5, &date, 8); |
| 52 | |
| 53 | const char ident[] = "PCI_ISA"; |
| 54 | memcpy((void *)0xfffd9, &ident, 7); |
| 55 | |
| 56 | /* system model: IBM-AT */ |
| 57 | writeb(0xfc, 0xffffe); |
| 58 | } |
| 59 | |
| 60 | static int int_exception_handler(void) |
| 61 | { |
| 62 | /* compatibility shim */ |
| 63 | struct eregs reg_info = { |
| 64 | .eax = M.x86.R_EAX, |
| 65 | .ecx = M.x86.R_ECX, |
| 66 | .edx = M.x86.R_EDX, |
| 67 | .ebx = M.x86.R_EBX, |
| 68 | .esp = M.x86.R_ESP, |
| 69 | .ebp = M.x86.R_EBP, |
| 70 | .esi = M.x86.R_ESI, |
| 71 | .edi = M.x86.R_EDI, |
| 72 | .vector = M.x86.intno, |
| 73 | .error_code = 0, |
| 74 | .eip = M.x86.R_EIP, |
| 75 | .cs = M.x86.R_CS, |
| 76 | .eflags = M.x86.R_EFLG |
| 77 | }; |
| 78 | struct eregs *regs = ®_info; |
| 79 | |
Simon Glass | 13e3e3e | 2023-07-15 21:38:44 -0600 | [diff] [blame] | 80 | log_err("Exception %d while executing option rom\n", regs->vector); |
Simon Glass | b2978d3 | 2014-11-14 20:56:32 -0700 | [diff] [blame] | 81 | cpu_hlt(); |
| 82 | |
| 83 | return 0; |
| 84 | } |
| 85 | |
| 86 | static int int_unknown_handler(void) |
| 87 | { |
| 88 | debug("Unsupported software interrupt #0x%x eax 0x%x\n", |
| 89 | M.x86.intno, M.x86.R_EAX); |
| 90 | |
| 91 | return -1; |
| 92 | } |
| 93 | |
| 94 | /* setup interrupt handlers for mainboard */ |
| 95 | void bios_set_interrupt_handler(int intnum, int (*int_func)(void)) |
| 96 | { |
| 97 | int_handler[intnum] = int_func; |
| 98 | } |
| 99 | |
| 100 | static void setup_interrupt_handlers(void) |
| 101 | { |
| 102 | int i; |
| 103 | |
| 104 | /* |
| 105 | * The first 16 int_handler functions are not BIOS services, |
| 106 | * but the CPU-generated exceptions ("hardware interrupts") |
| 107 | */ |
| 108 | for (i = 0; i < 0x10; i++) |
| 109 | int_handler[i] = &int_exception_handler; |
| 110 | |
| 111 | /* Mark all other int_handler calls as unknown first */ |
| 112 | for (i = 0x10; i < 0x100; i++) { |
| 113 | /* Skip if bios_set_interrupt_handler() isn't called first */ |
| 114 | if (int_handler[i]) |
| 115 | continue; |
| 116 | |
| 117 | /* |
| 118 | * Now set the default functions that are actually needed |
| 119 | * to initialize the option roms. The board may override |
| 120 | * these with bios_set_interrupt_handler() |
| 121 | */ |
| 122 | switch (i) { |
| 123 | case 0x10: |
| 124 | int_handler[0x10] = &int10_handler; |
| 125 | break; |
| 126 | case 0x12: |
| 127 | int_handler[0x12] = &int12_handler; |
| 128 | break; |
| 129 | case 0x16: |
| 130 | int_handler[0x16] = &int16_handler; |
| 131 | break; |
| 132 | case 0x1a: |
| 133 | int_handler[0x1a] = &int1a_handler; |
| 134 | break; |
| 135 | default: |
| 136 | int_handler[i] = &int_unknown_handler; |
| 137 | break; |
| 138 | } |
| 139 | } |
| 140 | } |
| 141 | |
| 142 | static void write_idt_stub(void *target, u8 intnum) |
| 143 | { |
| 144 | unsigned char *codeptr; |
| 145 | |
| 146 | codeptr = (unsigned char *)target; |
| 147 | memcpy(codeptr, &__idt_handler, __idt_handler_size); |
| 148 | codeptr[3] = intnum; /* modify int# in the code stub. */ |
| 149 | } |
| 150 | |
| 151 | static void setup_realmode_idt(void) |
| 152 | { |
| 153 | struct realmode_idt *idts = NULL; |
| 154 | int i; |
| 155 | |
| 156 | /* |
| 157 | * Copy IDT stub code for each interrupt. This might seem wasteful |
| 158 | * but it is really simple |
| 159 | */ |
| 160 | for (i = 0; i < 256; i++) { |
| 161 | idts[i].cs = 0; |
| 162 | idts[i].offset = 0x1000 + (i * __idt_handler_size); |
Simon Glass | aa0ed9d | 2017-01-16 07:03:42 -0700 | [diff] [blame] | 163 | write_idt_stub((void *)((ulong)idts[i].offset), i); |
Simon Glass | b2978d3 | 2014-11-14 20:56:32 -0700 | [diff] [blame] | 164 | } |
| 165 | |
| 166 | /* |
| 167 | * Many option ROMs use the hard coded interrupt entry points in the |
| 168 | * system bios. So install them at the known locations. |
| 169 | */ |
| 170 | |
| 171 | /* int42 is the relocated int10 */ |
| 172 | write_idt_stub((void *)0xff065, 0x42); |
| 173 | /* BIOS Int 11 Handler F000:F84D */ |
| 174 | write_idt_stub((void *)0xff84d, 0x11); |
| 175 | /* BIOS Int 12 Handler F000:F841 */ |
| 176 | write_idt_stub((void *)0xff841, 0x12); |
| 177 | /* BIOS Int 13 Handler F000:EC59 */ |
| 178 | write_idt_stub((void *)0xfec59, 0x13); |
| 179 | /* BIOS Int 14 Handler F000:E739 */ |
| 180 | write_idt_stub((void *)0xfe739, 0x14); |
| 181 | /* BIOS Int 15 Handler F000:F859 */ |
| 182 | write_idt_stub((void *)0xff859, 0x15); |
| 183 | /* BIOS Int 16 Handler F000:E82E */ |
| 184 | write_idt_stub((void *)0xfe82e, 0x16); |
| 185 | /* BIOS Int 17 Handler F000:EFD2 */ |
| 186 | write_idt_stub((void *)0xfefd2, 0x17); |
| 187 | /* ROM BIOS Int 1A Handler F000:FE6E */ |
| 188 | write_idt_stub((void *)0xffe6e, 0x1a); |
| 189 | } |
| 190 | |
Bin Meng | c9dba41 | 2018-04-11 22:02:15 -0700 | [diff] [blame] | 191 | #ifdef CONFIG_FRAMEBUFFER_SET_VESA_MODE |
Simon Glass | 5b92520 | 2022-07-30 15:52:05 -0600 | [diff] [blame] | 192 | static u8 vbe_get_mode_info(struct vesa_state *mi) |
Simon Glass | b2978d3 | 2014-11-14 20:56:32 -0700 | [diff] [blame] | 193 | { |
| 194 | u16 buffer_seg; |
| 195 | u16 buffer_adr; |
| 196 | char *buffer; |
| 197 | |
| 198 | debug("VBE: Getting information about VESA mode %04x\n", |
| 199 | mi->video_mode); |
| 200 | buffer = PTR_TO_REAL_MODE(asm_realmode_buffer); |
| 201 | buffer_seg = (((unsigned long)buffer) >> 4) & 0xff00; |
| 202 | buffer_adr = ((unsigned long)buffer) & 0xffff; |
| 203 | |
| 204 | realmode_interrupt(0x10, VESA_GET_MODE_INFO, 0x0000, mi->video_mode, |
| 205 | 0x0000, buffer_seg, buffer_adr); |
Simon Glass | 58766fd | 2023-07-30 11:16:04 -0600 | [diff] [blame] | 206 | memcpy(mi->mode_info_block, buffer, sizeof(struct vesa_mode_info)); |
Simon Glass | b2978d3 | 2014-11-14 20:56:32 -0700 | [diff] [blame] | 207 | mi->valid = true; |
| 208 | |
| 209 | return 0; |
| 210 | } |
| 211 | |
Simon Glass | 5b92520 | 2022-07-30 15:52:05 -0600 | [diff] [blame] | 212 | static u8 vbe_set_mode(struct vesa_state *mi) |
Simon Glass | b2978d3 | 2014-11-14 20:56:32 -0700 | [diff] [blame] | 213 | { |
Simon Glass | 91ea034 | 2015-01-01 16:18:04 -0700 | [diff] [blame] | 214 | int video_mode = mi->video_mode; |
| 215 | |
| 216 | debug("VBE: Setting VESA mode %#04x\n", video_mode); |
Simon Glass | b2978d3 | 2014-11-14 20:56:32 -0700 | [diff] [blame] | 217 | /* request linear framebuffer mode */ |
Simon Glass | 91ea034 | 2015-01-01 16:18:04 -0700 | [diff] [blame] | 218 | video_mode |= (1 << 14); |
Simon Glass | 6548ce9 | 2015-01-01 16:18:03 -0700 | [diff] [blame] | 219 | /* don't clear the framebuffer, we do that later */ |
Simon Glass | 91ea034 | 2015-01-01 16:18:04 -0700 | [diff] [blame] | 220 | video_mode |= (1 << 15); |
| 221 | realmode_interrupt(0x10, VESA_SET_MODE, video_mode, |
Simon Glass | b2978d3 | 2014-11-14 20:56:32 -0700 | [diff] [blame] | 222 | 0x0000, 0x0000, 0x0000, 0x0000); |
| 223 | |
| 224 | return 0; |
| 225 | } |
| 226 | |
Simon Glass | 5b92520 | 2022-07-30 15:52:05 -0600 | [diff] [blame] | 227 | static void vbe_set_graphics(int vesa_mode, struct vesa_state *mode_info) |
Simon Glass | b2978d3 | 2014-11-14 20:56:32 -0700 | [diff] [blame] | 228 | { |
| 229 | unsigned char *framebuffer; |
| 230 | |
| 231 | mode_info->video_mode = (1 << 14) | vesa_mode; |
| 232 | vbe_get_mode_info(mode_info); |
| 233 | |
Simon Glass | aa0ed9d | 2017-01-16 07:03:42 -0700 | [diff] [blame] | 234 | framebuffer = (unsigned char *)(ulong)mode_info->vesa.phys_base_ptr; |
Simon Glass | b2978d3 | 2014-11-14 20:56:32 -0700 | [diff] [blame] | 235 | debug("VBE: resolution: %dx%d@%d\n", |
| 236 | le16_to_cpu(mode_info->vesa.x_resolution), |
| 237 | le16_to_cpu(mode_info->vesa.y_resolution), |
| 238 | mode_info->vesa.bits_per_pixel); |
| 239 | debug("VBE: framebuffer: %p\n", framebuffer); |
| 240 | if (!framebuffer) { |
| 241 | debug("VBE: Mode does not support linear framebuffer\n"); |
| 242 | return; |
| 243 | } |
| 244 | |
Simon Glass | 91ea034 | 2015-01-01 16:18:04 -0700 | [diff] [blame] | 245 | mode_info->video_mode &= 0x3ff; |
Simon Glass | b2978d3 | 2014-11-14 20:56:32 -0700 | [diff] [blame] | 246 | vbe_set_mode(mode_info); |
| 247 | } |
Bin Meng | c9dba41 | 2018-04-11 22:02:15 -0700 | [diff] [blame] | 248 | #endif /* CONFIG_FRAMEBUFFER_SET_VESA_MODE */ |
Simon Glass | b2978d3 | 2014-11-14 20:56:32 -0700 | [diff] [blame] | 249 | |
Simon Glass | a063086 | 2015-11-29 13:17:58 -0700 | [diff] [blame] | 250 | void bios_run_on_x86(struct udevice *dev, unsigned long addr, int vesa_mode, |
Simon Glass | 5b92520 | 2022-07-30 15:52:05 -0600 | [diff] [blame] | 251 | struct vesa_state *mode_info) |
Simon Glass | b2978d3 | 2014-11-14 20:56:32 -0700 | [diff] [blame] | 252 | { |
Simon Glass | a063086 | 2015-11-29 13:17:58 -0700 | [diff] [blame] | 253 | pci_dev_t pcidev = dm_pci_get_bdf(dev); |
Simon Glass | b2978d3 | 2014-11-14 20:56:32 -0700 | [diff] [blame] | 254 | u32 num_dev; |
| 255 | |
| 256 | num_dev = PCI_BUS(pcidev) << 8 | PCI_DEV(pcidev) << 3 | |
| 257 | PCI_FUNC(pcidev); |
| 258 | |
| 259 | /* Needed to avoid exceptions in some ROMs */ |
| 260 | interrupt_init(); |
| 261 | |
| 262 | /* Set up some legacy information in the F segment */ |
| 263 | setup_rombios(); |
| 264 | |
| 265 | /* Set up C interrupt handlers */ |
| 266 | setup_interrupt_handlers(); |
| 267 | |
| 268 | /* Set up real-mode IDT */ |
| 269 | setup_realmode_idt(); |
| 270 | |
| 271 | /* Make sure the code is placed. */ |
| 272 | setup_realmode_code(); |
| 273 | |
Simon Glass | b2978d3 | 2014-11-14 20:56:32 -0700 | [diff] [blame] | 274 | debug("Calling Option ROM at %lx, pci device %#x...", addr, num_dev); |
| 275 | |
| 276 | /* Option ROM entry point is at OPROM start + 3 */ |
| 277 | realmode_call(addr + 0x0003, num_dev, 0xffff, 0x0000, 0xffff, 0x0, |
| 278 | 0x0); |
| 279 | debug("done\n"); |
| 280 | |
Bin Meng | c9dba41 | 2018-04-11 22:02:15 -0700 | [diff] [blame] | 281 | #ifdef CONFIG_FRAMEBUFFER_SET_VESA_MODE |
Simon Glass | b2978d3 | 2014-11-14 20:56:32 -0700 | [diff] [blame] | 282 | if (vesa_mode != -1) |
| 283 | vbe_set_graphics(vesa_mode, mode_info); |
Bin Meng | c9dba41 | 2018-04-11 22:02:15 -0700 | [diff] [blame] | 284 | #endif |
Simon Glass | b2978d3 | 2014-11-14 20:56:32 -0700 | [diff] [blame] | 285 | } |
| 286 | |
| 287 | asmlinkage int interrupt_handler(u32 intnumber, u32 gsfs, u32 dses, |
| 288 | u32 edi, u32 esi, u32 ebp, u32 esp, |
| 289 | u32 ebx, u32 edx, u32 ecx, u32 eax, |
| 290 | u32 cs_ip, u16 stackflags) |
| 291 | { |
| 292 | u32 ip; |
| 293 | u32 cs; |
| 294 | u32 flags; |
| 295 | int ret = 0; |
| 296 | |
| 297 | ip = cs_ip & 0xffff; |
| 298 | cs = cs_ip >> 16; |
| 299 | flags = stackflags; |
| 300 | |
| 301 | #ifdef CONFIG_REALMODE_DEBUG |
| 302 | debug("oprom: INT# 0x%x\n", intnumber); |
| 303 | debug("oprom: eax: %08x ebx: %08x ecx: %08x edx: %08x\n", |
| 304 | eax, ebx, ecx, edx); |
| 305 | debug("oprom: ebp: %08x esp: %08x edi: %08x esi: %08x\n", |
| 306 | ebp, esp, edi, esi); |
| 307 | debug("oprom: ip: %04x cs: %04x flags: %08x\n", |
| 308 | ip, cs, flags); |
| 309 | debug("oprom: stackflags = %04x\n", stackflags); |
| 310 | #endif |
| 311 | |
| 312 | /* |
| 313 | * Fetch arguments from the stack and put them to a place |
| 314 | * suitable for the interrupt handlers |
| 315 | */ |
| 316 | M.x86.R_EAX = eax; |
| 317 | M.x86.R_ECX = ecx; |
| 318 | M.x86.R_EDX = edx; |
| 319 | M.x86.R_EBX = ebx; |
| 320 | M.x86.R_ESP = esp; |
| 321 | M.x86.R_EBP = ebp; |
| 322 | M.x86.R_ESI = esi; |
| 323 | M.x86.R_EDI = edi; |
| 324 | M.x86.intno = intnumber; |
| 325 | M.x86.R_EIP = ip; |
| 326 | M.x86.R_CS = cs; |
| 327 | M.x86.R_EFLG = flags; |
| 328 | |
| 329 | /* Call the interrupt handler for this interrupt number */ |
| 330 | ret = int_handler[intnumber](); |
| 331 | |
| 332 | /* |
| 333 | * This code is quite strange... |
| 334 | * |
| 335 | * Put registers back on the stack. The assembler code will pop them |
| 336 | * later. We force (volatile!) changing the values of the parameters |
| 337 | * of this function. We know that they stay alive on the stack after |
| 338 | * we leave this function. |
| 339 | */ |
| 340 | *(volatile u32 *)&eax = M.x86.R_EAX; |
| 341 | *(volatile u32 *)&ecx = M.x86.R_ECX; |
| 342 | *(volatile u32 *)&edx = M.x86.R_EDX; |
| 343 | *(volatile u32 *)&ebx = M.x86.R_EBX; |
| 344 | *(volatile u32 *)&esi = M.x86.R_ESI; |
| 345 | *(volatile u32 *)&edi = M.x86.R_EDI; |
| 346 | flags = M.x86.R_EFLG; |
| 347 | |
| 348 | /* Pass success or error back to our caller via the CARRY flag */ |
| 349 | if (ret) { |
| 350 | flags &= ~1; /* no error: clear carry */ |
| 351 | } else { |
| 352 | debug("int%02x call returned error\n", intnumber); |
| 353 | flags |= 1; /* error: set carry */ |
| 354 | } |
| 355 | *(volatile u16 *)&stackflags = flags; |
| 356 | |
| 357 | return ret; |
| 358 | } |