| #include <sys/ansi.h> |
| #include <sys/io.h> |
| #include <fs.h> |
| #include <bios.h> |
| #include <com32.h> |
| #include <graphics.h> |
| #include <syslinux/memscan.h> |
| #include <syslinux/firmware.h> |
| #include <syslinux/video.h> |
| |
| #include <sys/vesa/vesa.h> |
| #include <sys/vesa/video.h> |
| #include <sys/vesa/debug.h> |
| #include <minmax.h> |
| #include "core.h" |
| |
| __export struct firmware *firmware = NULL; |
| |
| extern struct ansi_ops bios_ansi_ops; |
| |
| #define BIOS_CURXY ((struct curxy *)0x450) /* Array for each page */ |
| #define BIOS_ROWS (*(uint8_t *)0x484) /* Minus one; if zero use 24 (= 25 lines) */ |
| #define BIOS_COLS (*(uint16_t *)0x44A) |
| #define BIOS_PAGE (*(uint8_t *)0x462) |
| |
| static void bios_text_mode(void) |
| { |
| syslinux_force_text_mode(); |
| } |
| |
| static void bios_get_mode(int *cols, int *rows) |
| { |
| *rows = BIOS_ROWS ? BIOS_ROWS + 1 : 25; |
| *cols = BIOS_COLS; |
| } |
| |
| static uint16_t cursor_type; /* Saved cursor pattern */ |
| |
| static void bios_get_cursor(uint8_t *x, uint8_t *y) |
| { |
| com32sys_t ireg, oreg; |
| |
| memset(&ireg, 0, sizeof(ireg)); |
| |
| ireg.eax.b[1] = 0x03; |
| ireg.ebx.b[1] = BIOS_PAGE; |
| __intcall(0x10, &ireg, &oreg); |
| cursor_type = oreg.ecx.w[0]; |
| *x = oreg.edx.b[0]; |
| *y = oreg.edx.b[1]; |
| } |
| |
| static void bios_erase(int x0, int y0, int x1, int y1, uint8_t attribute) |
| { |
| static com32sys_t ireg; |
| memset(&ireg, 0, sizeof(ireg)); |
| |
| ireg.eax.w[0] = 0x0600; /* Clear window */ |
| ireg.ebx.b[1] = attribute; |
| ireg.ecx.b[0] = x0; |
| ireg.ecx.b[1] = y0; |
| ireg.edx.b[0] = x1; |
| ireg.edx.b[1] = y1; |
| __intcall(0x10, &ireg, NULL); |
| } |
| |
| static void bios_showcursor(const struct term_state *st) |
| { |
| static com32sys_t ireg; |
| uint16_t cursor = st->cursor ? cursor_type : 0x2020; |
| |
| memset(&ireg, 0, sizeof(ireg)); |
| |
| ireg.eax.b[1] = 0x01; |
| ireg.ecx.w[0] = cursor; |
| __intcall(0x10, &ireg, NULL); |
| } |
| |
| static void bios_set_cursor(int x, int y, bool visible) |
| { |
| const int page = BIOS_PAGE; |
| struct curxy xy = BIOS_CURXY[page]; |
| static com32sys_t ireg; |
| |
| memset(&ireg, 0, sizeof(ireg)); |
| |
| (void)visible; |
| |
| if (xy.x != x || xy.y != y) { |
| ireg.eax.b[1] = 0x02; |
| ireg.ebx.b[1] = page; |
| ireg.edx.b[1] = y; |
| ireg.edx.b[0] = x; |
| __intcall(0x10, &ireg, NULL); |
| } |
| } |
| |
| static void bios_write_char(uint8_t ch, uint8_t attribute) |
| { |
| static com32sys_t ireg; |
| |
| memset(&ireg, 0, sizeof(ireg)); |
| |
| ireg.eax.b[1] = 0x09; |
| ireg.eax.b[0] = ch; |
| ireg.ebx.b[1] = BIOS_PAGE; |
| ireg.ebx.b[0] = attribute; |
| ireg.ecx.w[0] = 1; |
| __intcall(0x10, &ireg, NULL); |
| } |
| |
| static void bios_scroll_up(uint8_t cols, uint8_t rows, uint8_t attribute) |
| { |
| static com32sys_t ireg; |
| |
| memset(&ireg, 0, sizeof(ireg)); |
| |
| ireg.eax.w[0] = 0x0601; |
| ireg.ebx.b[1] = attribute; |
| ireg.ecx.w[0] = 0; |
| ireg.edx.b[1] = rows; |
| ireg.edx.b[0] = cols; |
| __intcall(0x10, &ireg, NULL); /* Scroll */ |
| } |
| |
| static void bios_beep(void) |
| { |
| static com32sys_t ireg; |
| |
| memset(&ireg, 0, sizeof(ireg)); |
| |
| ireg.eax.w[0] = 0x0e07; |
| ireg.ebx.b[1] = BIOS_PAGE; |
| __intcall(0x10, &ireg, NULL); |
| } |
| |
| struct output_ops bios_output_ops = { |
| .erase = bios_erase, |
| .write_char = bios_write_char, |
| .showcursor = bios_showcursor, |
| .set_cursor = bios_set_cursor, |
| .scroll_up = bios_scroll_up, |
| .beep = bios_beep, |
| .get_mode = bios_get_mode, |
| .text_mode = bios_text_mode, |
| .get_cursor = bios_get_cursor, |
| }; |
| |
| extern char bios_getchar(char *); |
| extern int bios_pollchar(void); |
| extern uint8_t bios_shiftflags(void); |
| |
| struct input_ops bios_input_ops = { |
| .getchar = bios_getchar, |
| .pollchar = bios_pollchar, |
| .shiftflags = bios_shiftflags, |
| }; |
| |
| static void bios_get_serial_console_info(uint16_t *iobase, uint16_t *divisor, |
| uint16_t *flowctl) |
| { |
| *iobase = SerialPort; |
| *divisor = BaudDivisor; |
| |
| *flowctl = FlowOutput | FlowInput | (FlowIgnore << 4); |
| |
| if (!DisplayCon) |
| *flowctl |= (0x80 << 8); |
| } |
| |
| void bios_adv_init(void) |
| { |
| static com32sys_t reg; |
| |
| memset(®, 0, sizeof(reg)); |
| call16(adv_init, ®, NULL); |
| } |
| |
| int bios_adv_write(void) |
| { |
| static com32sys_t reg; |
| |
| memset(®, 0, sizeof(reg)); |
| call16(adv_write, ®, ®); |
| return (reg.eflags.l & EFLAGS_CF) ? -1 : 0; |
| } |
| |
| struct adv_ops bios_adv_ops = { |
| .init = bios_adv_init, |
| .write = bios_adv_write, |
| }; |
| |
| |
| static int __constfunc is_power_of_2(unsigned int x) |
| { |
| return x && !(x & (x - 1)); |
| } |
| |
| static int vesacon_paged_mode_ok(const struct vesa_mode_info *mi) |
| { |
| int i; |
| |
| if (!is_power_of_2(mi->win_size) || |
| !is_power_of_2(mi->win_grain) || mi->win_grain > mi->win_size) |
| return 0; /* Impossible... */ |
| |
| for (i = 0; i < 2; i++) { |
| if ((mi->win_attr[i] & 0x05) == 0x05 && mi->win_seg[i]) |
| return 1; /* Usable window */ |
| } |
| |
| return 0; /* Nope... */ |
| } |
| |
| static int bios_vesacon_set_mode(struct vesa_info *vesa_info, int *px, int *py, |
| enum vesa_pixel_format *bestpxf) |
| { |
| com32sys_t rm; |
| uint16_t mode, bestmode, *mode_ptr; |
| struct vesa_info *vi; |
| struct vesa_general_info *gi; |
| struct vesa_mode_info *mi; |
| enum vesa_pixel_format pxf; |
| int x = *px, y = *py; |
| int err = 0; |
| |
| /* Allocate space in the bounce buffer for these structures */ |
| vi = lzalloc(sizeof *vi); |
| if (!vi) { |
| err = 10; /* Out of memory */ |
| goto exit; |
| } |
| gi = &vi->gi; |
| mi = &vi->mi; |
| |
| memset(&rm, 0, sizeof rm); |
| |
| gi->signature = VBE2_MAGIC; /* Get VBE2 extended data */ |
| rm.eax.w[0] = 0x4F00; /* Get SVGA general information */ |
| rm.edi.w[0] = OFFS(gi); |
| rm.es = SEG(gi); |
| __intcall(0x10, &rm, &rm); |
| |
| if (rm.eax.w[0] != 0x004F) { |
| err = 1; /* Function call failed */ |
| goto exit; |
| } |
| if (gi->signature != VESA_MAGIC) { |
| err = 2; /* No magic */ |
| goto exit; |
| } |
| if (gi->version < 0x0102) { |
| err = 3; /* VESA 1.2+ required */ |
| goto exit; |
| } |
| |
| /* Copy general info */ |
| memcpy(&vesa_info->gi, gi, sizeof *gi); |
| |
| /* Search for the proper mode with a suitable color and memory model... */ |
| |
| mode_ptr = GET_PTR(gi->video_mode_ptr); |
| bestmode = 0; |
| *bestpxf = PXF_NONE; |
| |
| while ((mode = *mode_ptr++) != 0xFFFF) { |
| mode &= 0x1FF; /* The rest are attributes of sorts */ |
| |
| debug("Found mode: 0x%04x\r\n", mode); |
| |
| memset(&rm, 0, sizeof rm); |
| memset(mi, 0, sizeof *mi); |
| rm.eax.w[0] = 0x4F01; /* Get SVGA mode information */ |
| rm.ecx.w[0] = mode; |
| rm.edi.w[0] = OFFS(mi); |
| rm.es = SEG(mi); |
| __intcall(0x10, &rm, &rm); |
| |
| /* Must be a supported mode */ |
| if (rm.eax.w[0] != 0x004f) |
| continue; |
| |
| debug |
| ("mode_attr 0x%04x, h_res = %4d, v_res = %4d, bpp = %2d, layout = %d (%d,%d,%d)\r\n", |
| mi->mode_attr, mi->h_res, mi->v_res, mi->bpp, mi->memory_layout, |
| mi->rpos, mi->gpos, mi->bpos); |
| |
| /* Must be an LFB color graphics mode supported by the hardware. |
| |
| The bits tested are: |
| 4 - graphics mode |
| 3 - color mode |
| 1 - mode information available (mandatory in VBE 1.2+) |
| 0 - mode supported by hardware |
| */ |
| if ((mi->mode_attr & 0x001b) != 0x001b) |
| continue; |
| |
| /* Must be the chosen size */ |
| if (mi->h_res != x || mi->v_res != y) |
| continue; |
| |
| /* We don't support multibank (interlaced memory) modes */ |
| /* |
| * Note: The Bochs VESA BIOS (vbe.c 1.58 2006/08/19) violates the |
| * specification which states that banks == 1 for unbanked modes; |
| * fortunately it does report bank_size == 0 for those. |
| */ |
| if (mi->banks > 1 && mi->bank_size) { |
| debug("bad: banks = %d, banksize = %d, pages = %d\r\n", |
| mi->banks, mi->bank_size, mi->image_pages); |
| continue; |
| } |
| |
| /* Must be either a flat-framebuffer mode, or be an acceptable |
| paged mode */ |
| if (!(mi->mode_attr & 0x0080) && !vesacon_paged_mode_ok(mi)) { |
| debug("bad: invalid paged mode\r\n"); |
| continue; |
| } |
| |
| /* Must either be a packed-pixel mode or a direct color mode |
| (depending on VESA version ); must be a supported pixel format */ |
| pxf = PXF_NONE; /* Not usable */ |
| |
| if (mi->bpp == 32 && |
| (mi->memory_layout == 4 || |
| (mi->memory_layout == 6 && mi->rpos == 16 && mi->gpos == 8 && |
| mi->bpos == 0))) |
| pxf = PXF_BGRA32; |
| else if (mi->bpp == 24 && |
| (mi->memory_layout == 4 || |
| (mi->memory_layout == 6 && mi->rpos == 16 && mi->gpos == 8 && |
| mi->bpos == 0))) |
| pxf = PXF_BGR24; |
| else if (mi->bpp == 16 && |
| (mi->memory_layout == 4 || |
| (mi->memory_layout == 6 && mi->rpos == 11 && mi->gpos == 5 && |
| mi->bpos == 0))) |
| pxf = PXF_LE_RGB16_565; |
| else if (mi->bpp == 15 && |
| (mi->memory_layout == 4 || |
| (mi->memory_layout == 6 && mi->rpos == 10 && mi->gpos == 5 && |
| mi->bpos == 0))) |
| pxf = PXF_LE_RGB15_555; |
| |
| if (pxf < *bestpxf) { |
| debug("Best mode so far, pxf = %d\r\n", pxf); |
| |
| /* Best mode so far... */ |
| bestmode = mode; |
| *bestpxf = pxf; |
| |
| /* Copy mode info */ |
| memcpy(&vesa_info->mi, mi, sizeof *mi); |
| } |
| } |
| |
| if (*bestpxf == PXF_NONE) { |
| err = 4; /* No mode found */ |
| goto exit; |
| } |
| |
| mi = &vesa_info->mi; |
| mode = bestmode; |
| |
| memset(&rm, 0, sizeof rm); |
| /* Now set video mode */ |
| rm.eax.w[0] = 0x4F02; /* Set SVGA video mode */ |
| if (mi->mode_attr & 0x0080) |
| mode |= 0x4000; /* Request linear framebuffer if supported */ |
| rm.ebx.w[0] = mode; |
| __intcall(0x10, &rm, &rm); |
| if (rm.eax.w[0] != 0x004F) { |
| err = 9; /* Failed to set mode */ |
| goto exit; |
| } |
| |
| exit: |
| if (vi) |
| lfree(vi); |
| |
| return err; |
| } |
| |
| static void set_window_pos(struct win_info *wi, size_t win_pos) |
| { |
| static com32sys_t ireg; |
| |
| wi->win_pos = win_pos; |
| |
| if (wi->win_num < 0) |
| return; /* This should never happen... */ |
| |
| memset(&ireg, 0, sizeof ireg); |
| ireg.eax.w[0] = 0x4F05; |
| ireg.ebx.b[0] = wi->win_num; |
| ireg.edx.w[0] = win_pos >> wi->win_gshift; |
| |
| __intcall(0x10, &ireg, NULL); |
| } |
| |
| static void bios_vesacon_screencpy(size_t dst, const uint32_t * src, |
| size_t bytes, struct win_info *wi) |
| { |
| size_t win_pos, win_off; |
| size_t win_size = wi->win_size; |
| size_t omask = win_size - 1; |
| char *win_base = wi->win_base; |
| const char *s = (const char *)src; |
| size_t l; |
| |
| while (bytes) { |
| win_off = dst & omask; |
| win_pos = dst & ~omask; |
| |
| if (__unlikely(win_pos != wi->win_pos)) |
| set_window_pos(wi, win_pos); |
| |
| l = min(bytes, win_size - win_off); |
| memcpy(win_base + win_off, s, l); |
| |
| bytes -= l; |
| s += l; |
| dst += l; |
| } |
| } |
| |
| static int bios_font_query(uint8_t **font) |
| { |
| com32sys_t rm; |
| |
| /* Get BIOS 8x16 font */ |
| |
| memset(&rm, 0, sizeof rm); |
| |
| rm.eax.w[0] = 0x1130; /* Get Font Information */ |
| rm.ebx.w[0] = 0x0600; /* Get 8x16 ROM font */ |
| __intcall(0x10, &rm, &rm); |
| *font = MK_PTR(rm.es, rm.ebp.w[0]); |
| |
| return 16; |
| |
| } |
| struct vesa_ops bios_vesa_ops = { |
| .set_mode = bios_vesacon_set_mode, |
| .screencpy = bios_vesacon_screencpy, |
| .font_query = bios_font_query, |
| }; |
| |
| static uint32_t min_lowmem_heap = 65536; |
| extern char __lowmem_heap[]; |
| uint8_t KbdFlags; /* Check for keyboard escapes */ |
| __export uint8_t KbdMap[256]; /* Keyboard map */ |
| |
| __export uint16_t PXERetry; |
| |
| static inline void check_escapes(void) |
| { |
| com32sys_t ireg, oreg; |
| |
| memset(&ireg, 0, sizeof ireg); |
| ireg.eax.b[1] = 0x02; /* Check keyboard flags */ |
| __intcall(0x16, &ireg, &oreg); |
| |
| KbdFlags = oreg.eax.b[0]; |
| |
| /* Ctrl->skip 386 check */ |
| if (!(oreg.eax.b[0] & 0x04)) { |
| /* |
| * Now check that there is sufficient low (DOS) memory |
| * |
| * NOTE: Linux doesn't use all of real_mode_seg, but we use |
| * the same segment for COMBOOT images, which can use all 64K. |
| */ |
| uint32_t mem; |
| |
| __intcall(0x12, &ireg, &oreg); |
| |
| mem = ((uint32_t)__lowmem_heap) + min_lowmem_heap + 1023; |
| mem = mem >> 10; |
| |
| if (oreg.eax.w[0] < mem) { |
| char buf[256]; |
| |
| snprintf(buf, sizeof(buf), |
| "It appears your computer has only " |
| "%dK of low (\"DOS\") RAM.\n" |
| "This version of Syslinux needs " |
| "%dK to boot. " |
| "If you get this\nmessage in error, " |
| "hold down the Ctrl key while booting, " |
| "and I\nwill take your word for it.\n", |
| oreg.eax.w[0], mem); |
| writestr(buf); |
| kaboom(); |
| } |
| } |
| } |
| |
| extern uint32_t BIOS_timer_next; |
| extern uint32_t timer_irq; |
| static inline void bios_timer_init(void) |
| { |
| unsigned long next; |
| uint32_t *hook = (uint32_t *)BIOS_timer_hook; |
| |
| next = *hook; |
| BIOS_timer_next = next; |
| *hook = (uint32_t)&timer_irq; |
| } |
| |
| extern uint16_t *bios_free_mem; |
| |
| struct e820_entry { |
| uint64_t start; |
| uint64_t len; |
| uint32_t type; |
| }; |
| |
| static int bios_scan_memory(scan_memory_callback_t callback, void *data) |
| { |
| static com32sys_t ireg; |
| com32sys_t oreg; |
| struct e820_entry *e820buf; |
| uint64_t start, len, maxlen; |
| int memfound = 0; |
| int rv; |
| addr_t dosmem; |
| const addr_t bios_data = 0x510; /* Amount to reserve for BIOS data */ |
| |
| /* Use INT 12h to get DOS memory */ |
| __intcall(0x12, &__com32_zero_regs, &oreg); |
| dosmem = oreg.eax.w[0] << 10; |
| if (dosmem < 32 * 1024 || dosmem > 640 * 1024) { |
| /* INT 12h reports nonsense... now what? */ |
| uint16_t ebda_seg = *(uint16_t *) 0x40e; |
| if (ebda_seg >= 0x8000 && ebda_seg < 0xa000) |
| dosmem = ebda_seg << 4; |
| else |
| dosmem = 640 * 1024; /* Hope for the best... */ |
| } |
| rv = callback(data, bios_data, dosmem - bios_data, SMT_FREE); |
| if (rv) |
| return rv; |
| |
| /* First try INT 15h AX=E820h */ |
| e820buf = lzalloc(sizeof *e820buf); |
| if (!e820buf) |
| return -1; |
| |
| memset(&ireg, 0, sizeof ireg); |
| ireg.eax.l = 0xe820; |
| ireg.edx.l = 0x534d4150; |
| ireg.ebx.l = 0; |
| ireg.ecx.l = sizeof(*e820buf); |
| ireg.es = SEG(e820buf); |
| ireg.edi.w[0] = OFFS(e820buf); |
| |
| do { |
| __intcall(0x15, &ireg, &oreg); |
| |
| if ((oreg.eflags.l & EFLAGS_CF) || |
| (oreg.eax.l != 0x534d4150) || (oreg.ecx.l < 20)) |
| break; |
| |
| start = e820buf->start; |
| len = e820buf->len; |
| |
| if (start < 0x100000000ULL) { |
| /* Don't rely on E820 being valid for low memory. Doing so |
| could mean stuff like overwriting the PXE stack even when |
| using "keeppxe", etc. */ |
| if (start < 0x100000ULL) { |
| if (len > 0x100000ULL - start) |
| len -= 0x100000ULL - start; |
| else |
| len = 0; |
| start = 0x100000ULL; |
| } |
| |
| maxlen = 0x100000000ULL - start; |
| if (len > maxlen) |
| len = maxlen; |
| |
| if (len) { |
| enum syslinux_memmap_types type; |
| |
| type = e820buf->type == 1 ? SMT_FREE : SMT_RESERVED; |
| rv = callback(data, (addr_t) start, (addr_t) len, type); |
| if (rv) |
| return rv; |
| memfound = 1; |
| } |
| } |
| |
| ireg.ebx.l = oreg.ebx.l; |
| } while (oreg.ebx.l); |
| |
| lfree(e820buf); |
| |
| if (memfound) |
| return 0; |
| |
| /* Next try INT 15h AX=E801h */ |
| memset(&ireg, 0, sizeof ireg); |
| ireg.eax.w[0] = 0xe801; |
| __intcall(0x15, &ireg, &oreg); |
| |
| if (!(oreg.eflags.l & EFLAGS_CF) && oreg.ecx.w[0]) { |
| rv = callback(data, (addr_t) 1 << 20, oreg.ecx.w[0] << 10, SMT_FREE); |
| if (rv) |
| return rv; |
| |
| if (oreg.edx.w[0]) { |
| rv = callback(data, (addr_t) 16 << 20, |
| oreg.edx.w[0] << 16, SMT_FREE); |
| if (rv) |
| return rv; |
| } |
| |
| return 0; |
| } |
| |
| /* Finally try INT 15h AH=88h */ |
| memset(&ireg, 0, sizeof ireg); |
| ireg.eax.w[0] = 0x8800; |
| __intcall(0x15, &ireg, &oreg); |
| if (!(oreg.eflags.l & EFLAGS_CF) && oreg.eax.w[0]) { |
| rv = callback(data, (addr_t) 1 << 20, oreg.ecx.w[0] << 10, SMT_FREE); |
| if (rv) |
| return rv; |
| } |
| |
| return 0; |
| } |
| |
| static struct syslinux_memscan bios_memscan = { |
| .func = bios_scan_memory, |
| }; |
| |
| void bios_init(void) |
| { |
| int i; |
| |
| /* Initialize timer */ |
| bios_timer_init(); |
| |
| for (i = 0; i < 256; i++) |
| KbdMap[i] = i; |
| |
| bios_adjust_screen(); |
| |
| /* Init the memory subsystem */ |
| bios_free_mem = (uint16_t *)0x413; |
| syslinux_memscan_add(&bios_memscan); |
| mem_init(); |
| |
| dprintf("%s%s", syslinux_banner, copyright_str); |
| |
| /* CPU-dependent initialization and related checks. */ |
| check_escapes(); |
| |
| /* |
| * Scan the DMI tables for interesting information. |
| */ |
| dmi_init(); |
| } |
| |
| extern void bios_timer_cleanup(void); |
| |
| extern uint32_t OrigFDCTabPtr; |
| |
| static void bios_cleanup_hardware(void) |
| { |
| /* Restore the original pointer to the floppy descriptor table */ |
| if (OrigFDCTabPtr) |
| *((uint32_t *)(4 * 0x1e)) = OrigFDCTabPtr; |
| |
| /* |
| * Linux wants the floppy motor shut off before starting the |
| * kernel, at least bootsect.S seems to imply so. If we don't |
| * load the floppy driver, this is *definitely* so! |
| */ |
| __intcall(0x13, &zero_regs, NULL); |
| |
| call16(bios_timer_cleanup, &zero_regs, NULL); |
| |
| /* If we enabled serial port interrupts, clean them up now */ |
| sirq_cleanup(); |
| } |
| |
| extern void *bios_malloc(size_t, enum heap, size_t); |
| extern void *bios_realloc(void *, size_t); |
| extern void bios_free(void *); |
| |
| struct mem_ops bios_mem_ops = { |
| .malloc = bios_malloc, |
| .realloc = bios_realloc, |
| .free = bios_free, |
| }; |
| |
| struct firmware bios_fw = { |
| .init = bios_init, |
| .adjust_screen = bios_adjust_screen, |
| .cleanup = bios_cleanup_hardware, |
| .disk_init = bios_disk_init, |
| .o_ops = &bios_output_ops, |
| .i_ops = &bios_input_ops, |
| .get_serial_console_info = bios_get_serial_console_info, |
| .adv_ops = &bios_adv_ops, |
| .vesa = &bios_vesa_ops, |
| .mem = &bios_mem_ops, |
| }; |
| |
| void syslinux_register_bios(void) |
| { |
| firmware = &bios_fw; |
| } |