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