root/sys/arch/i386/stand/lib/vbe.c
/* $NetBSD: vbe.c,v 1.10 2019/12/15 03:38:17 christos Exp $ */

/*-
 * Copyright (c) 2009 Jared D. McNeill <jmcneill@invisible.ca>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

/*
 * VESA BIOS Extensions routines
 */

#include <lib/libsa/stand.h>
#include <lib/libkern/libkern.h>
#include <machine/bootinfo.h>
#include "libi386.h"
#include "vbe.h"

extern const uint8_t rasops_cmap[];
static uint8_t *vbe_edid = NULL;
static int vbe_edid_valid = 0;

static struct _vbestate {
        int             available;
        int             modenum;
} vbestate;

/*
 * https://pdos.csail.mit.edu/6.828/2018/readings/hardware/vbe3.pdf
 * p32
 */
#define VBE_MODEATTR_MODE_HARDWARE_SUPPORTED            0x0001u
#define VBE_MODEATTR_RESERVED_1                         0x0002u
#define VBE_MODEATTR_TTY_OUTPUT_FUNCTIONS_SUPPORTED     0x0004u
#define VBE_MODEATTR_COLOR_MODE                         0x0008u
#define VBE_MODEATTR_GRAPHICS_MODE                      0x0010u
#define VBE_MODEATTR_VGA_COMPATIBLE_MODE                0x0020u
#define VBE_MODEATTR_VGA_COMPATIBLE_WINDOWD_MEMORY_MODE 0x0040u
#define VBE_MODEATTR_LINEAR_FRAME_BUFFER_MODE           0x0080u
#define VBE_MODEATTR_DOUBLE_SCAN_MODE                   0x0100u
#define VBE_MODEATTR_INTERLACED_MODE                    0x0200u
#define VBE_MODEATTR_HARDWARE_TRIPPLE_BUFFERING_SUPPORT 0x0400u
#define VBE_MODEATTR_HARDWARE_STEREOSCOPIC_SUPPORT      0x0800u
#define VBE_MODEATTR_DUAL_DISPLAY_START_ADDRESS_SUPPORT 0x1000u
#define VBE_MODEATTR_RESERVED_2                         0x2000u
#define VBE_MODEATTR_RESERVED_3                         0x4000u
#define VBE_MODEATTR_RESERVED_4                         0x8000u

/*
 * p36
 */
#define VBE_MEMMODEL_TEXT               0x00u
#define VBE_MEMMODEL_CGA                0x01u
#define VBE_MEMMODEL_HERCULES           0x02u
#define VBE_MEMMODEL_PLANAR             0x03u
#define VBE_MEMMODEL_PACKED_PIXEL       0x04u
#define VBE_MEMMODEL_NON_CHAIN_4_256    0x05u
#define VBE_MEMMODEL_DIRECT_COLOR       0x06u
#define VBE_MEMMODEL_YUV                0x07u
/* VESA Reserved                        0x08u-0x0fu */
/* OEM Reserved                         0x10u-0xffU */


static int
vbe_mode_is_supported(struct modeinfoblock *mi)
{
        if ((mi->ModeAttributes & VBE_MODEATTR_MODE_HARDWARE_SUPPORTED) == 0)
                return 0;       /* mode not supported by hardware */
        if ((mi->ModeAttributes & VBE_MODEATTR_COLOR_MODE) == 0)
                return 0;       /* only color modes are supported */
        if ((mi->ModeAttributes & VBE_MODEATTR_GRAPHICS_MODE) == 0)
                return 0;       /* text mode */
        if ((mi->ModeAttributes & VBE_MODEATTR_LINEAR_FRAME_BUFFER_MODE) == 0)
                return 0;       /* linear fb not available */
        if (mi->NumberOfPlanes != 1)
                return 0;       /* planar mode not supported */
        if (mi->MemoryModel != VBE_MEMMODEL_PACKED_PIXEL /* Packed pixel */ &&
            mi->MemoryModel != VBE_MEMMODEL_DIRECT_COLOR /* Direct Color */)
                return 0;       /* unsupported pixel format */
        return 1;
}

static bool
vbe_check(void)
{
        if (!vbestate.available) {
                printf("VBE not available\n");
                return false;
        }
        return true;
}

void
vbe_init(void)
{
        struct vbeinfoblock vbe;

        memset(&vbe, 0, sizeof(vbe));
        memcpy(vbe.VbeSignature, "VBE2", 4);
        if (biosvbe_info(&vbe) != 0x004f)
                return;
        if (memcmp(vbe.VbeSignature, "VESA", 4) != 0)
                return;

        vbestate.available = 1;
        vbestate.modenum = 0;
}

int
vbe_available(void)
{
        return vbestate.available;
}

int
vbe_set_palette(const uint8_t *cmap, int slot)
{
        struct paletteentry pe;
        int ret;

        if (!vbe_check())
                return 1;

        pe.Blue = cmap[2] >> 2;
        pe.Green = cmap[1] >> 2;
        pe.Red = cmap[0] >> 2;
        pe.Alignment = 0;

        ret = biosvbe_palette_data(0x0600, slot, &pe);

        return ret == 0x004f ? 0 : 1;
}

int
vbe_set_mode(int modenum)
{
        struct modeinfoblock mi;
        struct btinfo_framebuffer fb;
        int ret, i;

        if (!vbe_check())
                return 1;

        ret = biosvbe_get_mode_info(modenum, &mi);
        if (ret != 0x004f) {
                printf("mode 0x%x invalid\n", modenum);
                return 1;
        }

        if (!vbe_mode_is_supported(&mi)) {
                printf("mode 0x%x not supported\n", modenum);
                return 1;
        }

        ret = biosvbe_set_mode(modenum);
        if (ret != 0x004f) {
                printf("mode 0x%x could not be set\n", modenum);
                return 1;
        }

        /* Setup palette for packed pixel mode */
        if (mi.MemoryModel == 0x04)
                for (i = 0; i < 256; i++)
                        vbe_set_palette(&rasops_cmap[i * 3], i);

        memset(&fb, 0, sizeof(fb));
        fb.physaddr = (uint64_t)mi.PhysBasePtr & 0xffffffff;
        fb.width = mi.XResolution;
        fb.height = mi.YResolution;
        fb.stride = mi.BytesPerScanLine;
        fb.depth = mi.BitsPerPixel;
        fb.flags = 0;
        fb.rnum = mi.RedMaskSize;
        fb.rpos = mi.RedFieldPosition;
        fb.gnum = mi.GreenMaskSize;
        fb.gpos = mi.GreenFieldPosition;
        fb.bnum = mi.BlueMaskSize;
        fb.bpos = mi.BlueFieldPosition;
        fb.vbemode = modenum;

        framebuffer_configure(&fb);

        return 0;
}

int
vbe_commit(void)
{
        int ret = 1;

        if (vbestate.modenum > 0) {
                ret = vbe_set_mode(vbestate.modenum);
                if (ret) {
                        printf("WARNING: failed to set VBE mode 0x%x\n",
                            vbestate.modenum);
                        wait_sec(5);
                }
        }
        return ret;
}

static void *
vbe_farptr(uint32_t farptr)
{
        return VBEPHYPTR((((farptr & 0xffff0000) >> 12) + (farptr & 0xffff)));
}

static int
vbe_parse_mode_str(char *str, int *x, int *y, int *depth)
{
        char *p;

        p = str;
        *x = strtoul(p, NULL, 0);
        if (*x == 0)
                return 0;
        p = strchr(p, 'x');
        if (!p)
                return 0;
        ++p;
        *y = strtoul(p, NULL, 0);
        if (*y == 0)
                return 0;
        p = strchr(p, 'x');
        if (!p)
                *depth = 8;
        else {
                ++p;
                *depth = strtoul(p, NULL, 0);
                if (*depth == 0)
                        return 0;
        }

        return 1;
}

static int
vbe_find_mode_xyd(int x, int y, int depth)
{
        struct vbeinfoblock vbe;
        struct modeinfoblock mi;
        uint32_t farptr;
        uint16_t mode;
        int safety = 0;

        memset(&vbe, 0, sizeof(vbe));
        memcpy(vbe.VbeSignature, "VBE2", 4);
        if (biosvbe_info(&vbe) != 0x004f)
                return 0;
        if (memcmp(vbe.VbeSignature, "VESA", 4) != 0)
                return 0;
        farptr = vbe.VideoModePtr;
        if (farptr == 0)
                return 0;

        while ((mode = *(uint16_t *)vbe_farptr(farptr)) != 0xffff) {
                safety++;
                farptr += 2;
                if (safety == 100)
                        return 0;
                if (biosvbe_get_mode_info(mode, &mi) != 0x004f)
                        continue;
                /* we only care about linear modes here */
                if (vbe_mode_is_supported(&mi) == 0)
                        continue;
                safety = 0;
                if (mi.XResolution == x &&
                    mi.YResolution == y &&
                    mi.BitsPerPixel == depth)
                        return mode;
        }

        return 0;
}

static int
vbe_find_mode(char *str)
{
        int x, y, depth;

        if (!vbe_parse_mode_str(str, &x, &y, &depth))
                return 0;

        return vbe_find_mode_xyd(x, y, depth);
}

static void
vbe_dump_mode(int modenum, struct modeinfoblock *mi)
{
        printf("0x%x=%dx%dx%d", modenum,
            mi->XResolution, mi->YResolution, mi->BitsPerPixel);
}

static int
vbe_get_edid(int *pwidth, int *pheight)
{
        const uint8_t magic[] = EDID_MAGIC;
        int ddc_caps, ret;

        ddc_caps = biosvbe_ddc_caps();
        if (ddc_caps == 0) {
                return 1;
        }

        if (vbe_edid == NULL) {
                vbe_edid = alloc(128);
        }
        if (vbe_edid_valid == 0) {
                ret = biosvbe_ddc_read_edid(0, vbe_edid);
                if (ret != 0x004f)
                        return 1;
                if (memcmp(vbe_edid, magic, sizeof(magic)) != 0)
                        return 1;
                vbe_edid_valid = 1;
        }

        *pwidth = vbe_edid[EDID_DESC_BLOCK + 2] |
            (((int)vbe_edid[EDID_DESC_BLOCK + 4] & 0xf0) << 4);
        *pheight = vbe_edid[EDID_DESC_BLOCK + 5] |
            (((int)vbe_edid[EDID_DESC_BLOCK + 7] & 0xf0) << 4);

        return 0;
}

void
vbe_modelist(void)
{
        struct vbeinfoblock vbe;
        struct modeinfoblock mi;
        uint32_t farptr;
        uint16_t mode;
        int nmodes = 0, safety = 0;
        int ddc_caps, edid_width, edid_height;

        if (!vbe_check())
                return;

        ddc_caps = biosvbe_ddc_caps();
        if (ddc_caps & 3) {
                printf("DDC");
                if (ddc_caps & 1)
                        printf(" [DDC1]");
                if (ddc_caps & 2)
                        printf(" [DDC2]");

                if (vbe_get_edid(&edid_width, &edid_height) != 0)
                        printf(": no EDID information\n");
                else
                        printf(": EDID %dx%d\n", edid_width, edid_height);
        }

        printf("Modes: ");
        memset(&vbe, 0, sizeof(vbe));
        memcpy(vbe.VbeSignature, "VBE2", 4);
        if (biosvbe_info(&vbe) != 0x004f)
                goto done;
        if (memcmp(vbe.VbeSignature, "VESA", 4) != 0)
                goto done;
        farptr = vbe.VideoModePtr;
        if (farptr == 0)
                goto done;

        while ((mode = *(uint16_t *)vbe_farptr(farptr)) != 0xffff) {
                safety++;
                farptr += 2;
                if (safety == 100) {
                        printf("[?] ");
                        break;
                }
                if (biosvbe_get_mode_info(mode, &mi) != 0x004f)
                        continue;
                /* we only care about linear modes here */
                if (vbe_mode_is_supported(&mi) == 0)
                        continue;
                safety = 0;
                if (nmodes % 4 == 0)
                        printf("\n");
                else
                        printf("  ");
                vbe_dump_mode(mode, &mi);
                nmodes++;
        }

done:
        if (nmodes == 0)
                printf("none found");
        printf("\n");
}

void
command_vesa(char *cmd)
{
        char arg[20];
        int modenum, edid_width, edid_height;

        if (!vbe_check())
                return;

        strlcpy(arg, cmd, sizeof(arg));

        if (strcmp(arg, "list") == 0) {
                vbe_modelist();
                return;
        }

        if (strcmp(arg, "disabled") == 0 || strcmp(arg, "off") == 0) {
                vbestate.modenum = 0;
                return;
        }

        if (strcmp(arg, "enabled") == 0 || strcmp(arg, "on") == 0) {
                if (vbe_get_edid(&edid_width, &edid_height) != 0) {
                        modenum = VBE_DEFAULT_MODE;
                } else {
                        modenum = vbe_find_mode_xyd(edid_width, edid_height, 8);
                        if (modenum == 0)
                                modenum = VBE_DEFAULT_MODE;
                }
        } else if (strncmp(arg, "0x", 2) == 0) {
                modenum = strtoul(arg, NULL, 0);
        } else if (strchr(arg, 'x') != NULL) {
                modenum = vbe_find_mode(arg);
                if (modenum == 0) {
                        printf("mode %s not supported by firmware\n", arg);
                        return;
                }
        } else {
                modenum = 0;
        }

        if (modenum >= 0x100) {
                vbestate.modenum = modenum;
                return;
        }

        printf("invalid flag, must be 'on', 'off', 'list', "
            "a display mode, or a VBE mode number\n");
}