|  | /* | 
|  | * QEMU EDID generator. | 
|  | * | 
|  | * This work is licensed under the terms of the GNU GPL, version 2 or later. | 
|  | * See the COPYING file in the top-level directory. | 
|  | */ | 
|  | #include "qemu/osdep.h" | 
|  | #include "qemu/bswap.h" | 
|  | #include "hw/display/edid.h" | 
|  |  | 
|  | static const struct edid_mode { | 
|  | uint32_t xres; | 
|  | uint32_t yres; | 
|  | uint32_t byte; | 
|  | uint32_t xtra3; | 
|  | uint32_t bit; | 
|  | uint32_t dta; | 
|  | } modes[] = { | 
|  | /* dea/dta extension timings (all @ 50 Hz) */ | 
|  | { .xres = 5120,   .yres = 2160,   .dta = 125 }, | 
|  | { .xres = 4096,   .yres = 2160,   .dta = 101 }, | 
|  | { .xres = 3840,   .yres = 2160,   .dta =  96 }, | 
|  | { .xres = 2560,   .yres = 1080,   .dta =  89 }, | 
|  | { .xres = 2048,   .yres = 1152 }, | 
|  | { .xres = 1920,   .yres = 1080,   .dta =  31 }, | 
|  |  | 
|  | /* dea/dta extension timings (all @ 60 Hz) */ | 
|  | { .xres = 3840,   .yres = 2160,   .dta =  97 }, | 
|  |  | 
|  | /* additional standard timings 3 (all @ 60Hz) */ | 
|  | { .xres = 1920,   .yres = 1200,   .xtra3 = 10,   .bit = 0 }, | 
|  | { .xres = 1600,   .yres = 1200,   .xtra3 =  9,   .bit = 2 }, | 
|  | { .xres = 1680,   .yres = 1050,   .xtra3 =  9,   .bit = 5 }, | 
|  | { .xres = 1440,   .yres =  900,   .xtra3 =  8,   .bit = 5 }, | 
|  | { .xres = 1280,   .yres = 1024,   .xtra3 =  7,   .bit = 1 }, | 
|  | { .xres = 1280,   .yres =  960,   .xtra3 =  7,   .bit = 3 }, | 
|  | { .xres = 1280,   .yres =  768,   .xtra3 =  7,   .bit = 6 }, | 
|  |  | 
|  | { .xres = 1920,   .yres = 1440,   .xtra3 = 11,   .bit = 5 }, | 
|  | { .xres = 1856,   .yres = 1392,   .xtra3 = 10,   .bit = 3 }, | 
|  | { .xres = 1792,   .yres = 1344,   .xtra3 = 10,   .bit = 5 }, | 
|  | { .xres = 1440,   .yres = 1050,   .xtra3 =  8,   .bit = 1 }, | 
|  | { .xres = 1360,   .yres =  768,   .xtra3 =  8,   .bit = 7 }, | 
|  |  | 
|  | /* established timings (all @ 60Hz) */ | 
|  | { .xres = 1024,   .yres =  768,   .byte  = 36,   .bit = 3 }, | 
|  | { .xres =  800,   .yres =  600,   .byte  = 35,   .bit = 0 }, | 
|  | { .xres =  640,   .yres =  480,   .byte  = 35,   .bit = 5 }, | 
|  | }; | 
|  |  | 
|  | typedef struct Timings { | 
|  | uint32_t xfront; | 
|  | uint32_t xsync; | 
|  | uint32_t xblank; | 
|  |  | 
|  | uint32_t yfront; | 
|  | uint32_t ysync; | 
|  | uint32_t yblank; | 
|  |  | 
|  | uint64_t clock; | 
|  | } Timings; | 
|  |  | 
|  | static void generate_timings(Timings *timings, uint32_t refresh_rate, | 
|  | uint32_t xres, uint32_t yres) | 
|  | { | 
|  | /* pull some realistic looking timings out of thin air */ | 
|  | timings->xfront = xres * 25 / 100; | 
|  | timings->xsync  = xres *  3 / 100; | 
|  | timings->xblank = xres * 35 / 100; | 
|  |  | 
|  | timings->yfront = yres *  5 / 1000; | 
|  | timings->ysync  = yres *  5 / 1000; | 
|  | timings->yblank = yres * 35 / 1000; | 
|  |  | 
|  | timings->clock  = ((uint64_t)refresh_rate * | 
|  | (xres + timings->xblank) * | 
|  | (yres + timings->yblank)) / 10000000; | 
|  | } | 
|  |  | 
|  | static void edid_ext_dta(uint8_t *dta) | 
|  | { | 
|  | dta[0] = 0x02; | 
|  | dta[1] = 0x03; | 
|  | dta[2] = 0x05; | 
|  | dta[3] = 0x00; | 
|  |  | 
|  | /* video data block */ | 
|  | dta[4] = 0x40; | 
|  | } | 
|  |  | 
|  | static void edid_ext_dta_mode(uint8_t *dta, uint8_t nr) | 
|  | { | 
|  | dta[dta[2]] = nr; | 
|  | dta[2]++; | 
|  | dta[4]++; | 
|  | } | 
|  |  | 
|  | static int edid_std_mode(uint8_t *mode, uint32_t xres, uint32_t yres) | 
|  | { | 
|  | uint32_t aspect; | 
|  |  | 
|  | if (xres == 0 || yres == 0) { | 
|  | mode[0] = 0x01; | 
|  | mode[1] = 0x01; | 
|  | return 0; | 
|  |  | 
|  | } else if (xres * 10 == yres * 16) { | 
|  | aspect = 0; | 
|  | } else if (xres * 3 == yres * 4) { | 
|  | aspect = 1; | 
|  | } else if (xres * 4 == yres * 5) { | 
|  | aspect = 2; | 
|  | } else if (xres * 9 == yres * 16) { | 
|  | aspect = 3; | 
|  | } else { | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | if ((xres / 8) - 31 > 255) { | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | mode[0] = (xres / 8) - 31; | 
|  | mode[1] = ((aspect << 6) | (60 - 60)); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static void edid_fill_modes(uint8_t *edid, uint8_t *xtra3, uint8_t *dta, | 
|  | uint32_t maxx, uint32_t maxy) | 
|  | { | 
|  | const struct edid_mode *mode; | 
|  | int std = 38; | 
|  | int rc, i; | 
|  |  | 
|  | for (i = 0; i < ARRAY_SIZE(modes); i++) { | 
|  | mode = modes + i; | 
|  |  | 
|  | if ((maxx && mode->xres > maxx) || | 
|  | (maxy && mode->yres > maxy)) { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | if (mode->byte) { | 
|  | edid[mode->byte] |= (1 << mode->bit); | 
|  | } else if (std < 54) { | 
|  | rc = edid_std_mode(edid + std, mode->xres, mode->yres); | 
|  | if (rc == 0) { | 
|  | std += 2; | 
|  | } | 
|  | } else if (mode->xtra3 && xtra3) { | 
|  | xtra3[mode->xtra3] |= (1 << mode->bit); | 
|  | } | 
|  |  | 
|  | if (dta && mode->dta) { | 
|  | edid_ext_dta_mode(dta, mode->dta); | 
|  | } | 
|  | } | 
|  |  | 
|  | while (std < 54) { | 
|  | edid_std_mode(edid + std, 0, 0); | 
|  | std += 2; | 
|  | } | 
|  | } | 
|  |  | 
|  | static void edid_checksum(uint8_t *edid, size_t len) | 
|  | { | 
|  | uint32_t sum = 0; | 
|  | int i; | 
|  |  | 
|  | for (i = 0; i < len; i++) { | 
|  | sum += edid[i]; | 
|  | } | 
|  | sum &= 0xff; | 
|  | if (sum) { | 
|  | edid[len] = 0x100 - sum; | 
|  | } | 
|  | } | 
|  |  | 
|  | static uint8_t *edid_desc_next(uint8_t *edid, uint8_t *dta, uint8_t *desc) | 
|  | { | 
|  | if (desc == NULL) { | 
|  | return NULL; | 
|  | } | 
|  | if (desc + 18 + 18 < edid + 127) { | 
|  | return desc + 18; | 
|  | } | 
|  | if (dta) { | 
|  | if (desc < edid + 127) { | 
|  | return dta + dta[2]; | 
|  | } | 
|  | if (desc + 18 + 18 < dta + 127) { | 
|  | return desc + 18; | 
|  | } | 
|  | } | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | static void edid_desc_type(uint8_t *desc, uint8_t type) | 
|  | { | 
|  | desc[0] = 0; | 
|  | desc[1] = 0; | 
|  | desc[2] = 0; | 
|  | desc[3] = type; | 
|  | desc[4] = 0; | 
|  | } | 
|  |  | 
|  | static void edid_desc_text(uint8_t *desc, uint8_t type, | 
|  | const char *text) | 
|  | { | 
|  | size_t len; | 
|  |  | 
|  | edid_desc_type(desc, type); | 
|  | memset(desc + 5, ' ', 13); | 
|  |  | 
|  | len = strlen(text); | 
|  | if (len > 12) { | 
|  | len = 12; | 
|  | } | 
|  | memcpy(desc + 5, text, len); | 
|  | desc[5 + len] = '\n'; | 
|  | } | 
|  |  | 
|  | static void edid_desc_ranges(uint8_t *desc) | 
|  | { | 
|  | edid_desc_type(desc, 0xfd); | 
|  |  | 
|  | /* vertical (50 -> 125 Hz) */ | 
|  | desc[5] =  50; | 
|  | desc[6] = 125; | 
|  |  | 
|  | /* horizontal (30 -> 160 kHz) */ | 
|  | desc[7] =  30; | 
|  | desc[8] = 160; | 
|  |  | 
|  | /* max dot clock (2550 MHz) */ | 
|  | desc[9] = 2550 / 10; | 
|  |  | 
|  | /* no extended timing information */ | 
|  | desc[10] = 0x01; | 
|  |  | 
|  | /* padding */ | 
|  | desc[11] = '\n'; | 
|  | memset(desc + 12, ' ', 6); | 
|  | } | 
|  |  | 
|  | /* additional standard timings 3 */ | 
|  | static void edid_desc_xtra3_std(uint8_t *desc) | 
|  | { | 
|  | edid_desc_type(desc, 0xf7); | 
|  | desc[5] = 10; | 
|  | } | 
|  |  | 
|  | static void edid_desc_dummy(uint8_t *desc) | 
|  | { | 
|  | edid_desc_type(desc, 0x10); | 
|  | } | 
|  |  | 
|  | static void edid_desc_timing(uint8_t *desc, const Timings *timings, | 
|  | uint32_t xres, uint32_t yres, | 
|  | uint32_t xmm, uint32_t ymm) | 
|  | { | 
|  | stw_le_p(desc, timings->clock); | 
|  |  | 
|  | desc[2] = xres   & 0xff; | 
|  | desc[3] = timings->xblank & 0xff; | 
|  | desc[4] = (((xres   & 0xf00) >> 4) | | 
|  | ((timings->xblank & 0xf00) >> 8)); | 
|  |  | 
|  | desc[5] = yres   & 0xff; | 
|  | desc[6] = timings->yblank & 0xff; | 
|  | desc[7] = (((yres   & 0xf00) >> 4) | | 
|  | ((timings->yblank & 0xf00) >> 8)); | 
|  |  | 
|  | desc[8] = timings->xfront & 0xff; | 
|  | desc[9] = timings->xsync  & 0xff; | 
|  |  | 
|  | desc[10] = (((timings->yfront & 0x00f) << 4) | | 
|  | ((timings->ysync  & 0x00f) << 0)); | 
|  | desc[11] = (((timings->xfront & 0x300) >> 2) | | 
|  | ((timings->xsync  & 0x300) >> 4) | | 
|  | ((timings->yfront & 0x030) >> 2) | | 
|  | ((timings->ysync  & 0x030) >> 4)); | 
|  |  | 
|  | desc[12] = xmm & 0xff; | 
|  | desc[13] = ymm & 0xff; | 
|  | desc[14] = (((xmm & 0xf00) >> 4) | | 
|  | ((ymm & 0xf00) >> 8)); | 
|  |  | 
|  | desc[17] = 0x18; | 
|  | } | 
|  |  | 
|  | static uint32_t edid_to_10bit(float value) | 
|  | { | 
|  | return (uint32_t)(value * 1024 + 0.5); | 
|  | } | 
|  |  | 
|  | static void edid_colorspace(uint8_t *edid, | 
|  | float rx, float ry, | 
|  | float gx, float gy, | 
|  | float bx, float by, | 
|  | float wx, float wy) | 
|  | { | 
|  | uint32_t red_x   = edid_to_10bit(rx); | 
|  | uint32_t red_y   = edid_to_10bit(ry); | 
|  | uint32_t green_x = edid_to_10bit(gx); | 
|  | uint32_t green_y = edid_to_10bit(gy); | 
|  | uint32_t blue_x  = edid_to_10bit(bx); | 
|  | uint32_t blue_y  = edid_to_10bit(by); | 
|  | uint32_t white_x = edid_to_10bit(wx); | 
|  | uint32_t white_y = edid_to_10bit(wy); | 
|  |  | 
|  | edid[25] = (((red_x   & 0x03) << 6) | | 
|  | ((red_y   & 0x03) << 4) | | 
|  | ((green_x & 0x03) << 2) | | 
|  | ((green_y & 0x03) << 0)); | 
|  | edid[26] = (((blue_x  & 0x03) << 6) | | 
|  | ((blue_y  & 0x03) << 4) | | 
|  | ((white_x & 0x03) << 2) | | 
|  | ((white_y & 0x03) << 0)); | 
|  | edid[27] = red_x   >> 2; | 
|  | edid[28] = red_y   >> 2; | 
|  | edid[29] = green_x >> 2; | 
|  | edid[30] = green_y >> 2; | 
|  | edid[31] = blue_x  >> 2; | 
|  | edid[32] = blue_y  >> 2; | 
|  | edid[33] = white_x >> 2; | 
|  | edid[34] = white_y >> 2; | 
|  | } | 
|  |  | 
|  | static uint32_t qemu_edid_dpi_from_mm(uint32_t mm, uint32_t res) | 
|  | { | 
|  | return res * 254 / 10 / mm; | 
|  | } | 
|  |  | 
|  | uint32_t qemu_edid_dpi_to_mm(uint32_t dpi, uint32_t res) | 
|  | { | 
|  | return res * 254 / 10 / dpi; | 
|  | } | 
|  |  | 
|  | static void init_displayid(uint8_t *did) | 
|  | { | 
|  | did[0] = 0x70; /* display id extension */ | 
|  | did[1] = 0x13; /* version 1.3 */ | 
|  | did[2] = 4;    /* length */ | 
|  | did[3] = 0x03; /* product type (0x03 == standalone display device) */ | 
|  | edid_checksum(did + 1, did[2] + 4); | 
|  | } | 
|  |  | 
|  | static void qemu_displayid_generate(uint8_t *did, const Timings *timings, | 
|  | uint32_t xres, uint32_t yres, | 
|  | uint32_t xmm, uint32_t ymm) | 
|  | { | 
|  | did[0] = 0x70; /* display id extension */ | 
|  | did[1] = 0x13; /* version 1.3 */ | 
|  | did[2] = 23;   /* length */ | 
|  | did[3] = 0x03; /* product type (0x03 == standalone display device) */ | 
|  |  | 
|  | did[5] = 0x03; /* Detailed Timings Data Block */ | 
|  | did[6] = 0x00; /* revision */ | 
|  | did[7] = 0x14; /* block length */ | 
|  |  | 
|  | did[8]  = timings->clock  & 0xff; | 
|  | did[9]  = (timings->clock & 0xff00) >> 8; | 
|  | did[10] = (timings->clock & 0xff0000) >> 16; | 
|  |  | 
|  | did[11] = 0x88; /* leave aspect ratio undefined */ | 
|  |  | 
|  | stw_le_p(did + 12, 0xffff & (xres - 1)); | 
|  | stw_le_p(did + 14, 0xffff & (timings->xblank - 1)); | 
|  | stw_le_p(did + 16, 0xffff & (timings->xfront - 1)); | 
|  | stw_le_p(did + 18, 0xffff & (timings->xsync - 1)); | 
|  |  | 
|  | stw_le_p(did + 20, 0xffff & (yres - 1)); | 
|  | stw_le_p(did + 22, 0xffff & (timings->yblank - 1)); | 
|  | stw_le_p(did + 24, 0xffff & (timings->yfront - 1)); | 
|  | stw_le_p(did + 26, 0xffff & (timings->ysync - 1)); | 
|  |  | 
|  | edid_checksum(did + 1, did[2] + 4); | 
|  | } | 
|  |  | 
|  | void qemu_edid_generate(uint8_t *edid, size_t size, | 
|  | qemu_edid_info *info) | 
|  | { | 
|  | Timings timings; | 
|  | uint8_t *desc = edid + 54; | 
|  | uint8_t *xtra3 = NULL; | 
|  | uint8_t *dta = NULL; | 
|  | uint8_t *did = NULL; | 
|  | uint32_t width_mm, height_mm; | 
|  | uint32_t refresh_rate = info->refresh_rate ? info->refresh_rate : 75000; | 
|  | uint32_t dpi = 100; /* if no width_mm/height_mm */ | 
|  | uint32_t large_screen = 0; | 
|  |  | 
|  | /* =============== set defaults  =============== */ | 
|  |  | 
|  | if (!info->vendor || strlen(info->vendor) != 3) { | 
|  | info->vendor = "RHT"; | 
|  | } | 
|  | if (!info->name) { | 
|  | info->name = "QEMU Monitor"; | 
|  | } | 
|  | if (!info->prefx) { | 
|  | info->prefx = 1280; | 
|  | } | 
|  | if (!info->prefy) { | 
|  | info->prefy = 800; | 
|  | } | 
|  | if (info->width_mm && info->height_mm) { | 
|  | width_mm = info->width_mm; | 
|  | height_mm = info->height_mm; | 
|  | dpi = qemu_edid_dpi_from_mm(width_mm, info->prefx); | 
|  | } else { | 
|  | width_mm = qemu_edid_dpi_to_mm(dpi, info->prefx); | 
|  | height_mm = qemu_edid_dpi_to_mm(dpi, info->prefy); | 
|  | } | 
|  |  | 
|  | generate_timings(&timings, refresh_rate, info->prefx, info->prefy); | 
|  | if (info->prefx >= 4096 || info->prefy >= 4096 || timings.clock >= 65536) { | 
|  | large_screen = 1; | 
|  | } | 
|  |  | 
|  | /* =============== extensions  =============== */ | 
|  |  | 
|  | if (size >= 256) { | 
|  | dta = edid + 128; | 
|  | edid[126]++; | 
|  | edid_ext_dta(dta); | 
|  | } | 
|  |  | 
|  | if (size >= 384 && large_screen) { | 
|  | did = edid + 256; | 
|  | edid[126]++; | 
|  | init_displayid(did); | 
|  | } | 
|  |  | 
|  | /* =============== header information =============== */ | 
|  |  | 
|  | /* fixed */ | 
|  | edid[0] = 0x00; | 
|  | edid[1] = 0xff; | 
|  | edid[2] = 0xff; | 
|  | edid[3] = 0xff; | 
|  | edid[4] = 0xff; | 
|  | edid[5] = 0xff; | 
|  | edid[6] = 0xff; | 
|  | edid[7] = 0x00; | 
|  |  | 
|  | /* manufacturer id, product code, serial number */ | 
|  | uint16_t vendor_id = ((((info->vendor[0] - '@') & 0x1f) << 10) | | 
|  | (((info->vendor[1] - '@') & 0x1f) <<  5) | | 
|  | (((info->vendor[2] - '@') & 0x1f) <<  0)); | 
|  | uint16_t model_nr = 0x1234; | 
|  | uint32_t serial_nr = info->serial ? atoi(info->serial) : 0; | 
|  | stw_be_p(edid +  8, vendor_id); | 
|  | stw_le_p(edid + 10, model_nr); | 
|  | stl_le_p(edid + 12, serial_nr); | 
|  |  | 
|  | /* manufacture week and year */ | 
|  | edid[16] = 42; | 
|  | edid[17] = 2014 - 1990; | 
|  |  | 
|  | /* edid version */ | 
|  | edid[18] = 1; | 
|  | edid[19] = 4; | 
|  |  | 
|  |  | 
|  | /* =============== basic display parameters =============== */ | 
|  |  | 
|  | /* video input: digital, 8bpc, displayport */ | 
|  | edid[20] = 0xa5; | 
|  |  | 
|  | /* screen size: undefined */ | 
|  | edid[21] = width_mm / 10; | 
|  | edid[22] = height_mm / 10; | 
|  |  | 
|  | /* display gamma: 2.2 */ | 
|  | edid[23] = 220 - 100; | 
|  |  | 
|  | /* supported features bitmap: std sRGB, preferred timing */ | 
|  | edid[24] = 0x06; | 
|  |  | 
|  |  | 
|  | /* =============== chromaticity coordinates =============== */ | 
|  |  | 
|  | /* standard sRGB colorspace */ | 
|  | edid_colorspace(edid, | 
|  | 0.6400, 0.3300,   /* red   */ | 
|  | 0.3000, 0.6000,   /* green */ | 
|  | 0.1500, 0.0600,   /* blue  */ | 
|  | 0.3127, 0.3290);  /* white point  */ | 
|  |  | 
|  | /* =============== established timing bitmap =============== */ | 
|  | /* =============== standard timing information =============== */ | 
|  |  | 
|  | /* both filled by edid_fill_modes() */ | 
|  |  | 
|  |  | 
|  | /* =============== descriptor blocks =============== */ | 
|  |  | 
|  | if (!large_screen) { | 
|  | /* The DTD section has only 12 bits to store the resolution */ | 
|  | edid_desc_timing(desc, &timings, info->prefx, info->prefy, | 
|  | width_mm, height_mm); | 
|  | desc = edid_desc_next(edid, dta, desc); | 
|  | } | 
|  |  | 
|  | xtra3 = desc; | 
|  | edid_desc_xtra3_std(xtra3); | 
|  | desc = edid_desc_next(edid, dta, desc); | 
|  | edid_fill_modes(edid, xtra3, dta, info->maxx, info->maxy); | 
|  | /* | 
|  | * dta video data block is finished at thus point, | 
|  | * so dta descriptor offsets don't move any more. | 
|  | */ | 
|  |  | 
|  | edid_desc_ranges(desc); | 
|  | desc = edid_desc_next(edid, dta, desc); | 
|  |  | 
|  | if (desc && info->name) { | 
|  | edid_desc_text(desc, 0xfc, info->name); | 
|  | desc = edid_desc_next(edid, dta, desc); | 
|  | } | 
|  |  | 
|  | if (desc && info->serial) { | 
|  | edid_desc_text(desc, 0xff, info->serial); | 
|  | desc = edid_desc_next(edid, dta, desc); | 
|  | } | 
|  |  | 
|  | while (desc) { | 
|  | edid_desc_dummy(desc); | 
|  | desc = edid_desc_next(edid, dta, desc); | 
|  | } | 
|  |  | 
|  | /* =============== display id extensions =============== */ | 
|  |  | 
|  | if (did && large_screen) { | 
|  | qemu_displayid_generate(did, &timings, info->prefx, info->prefy, | 
|  | width_mm, height_mm); | 
|  | } | 
|  |  | 
|  | /* =============== finish up =============== */ | 
|  |  | 
|  | edid_checksum(edid, 127); | 
|  | if (dta) { | 
|  | edid_checksum(dta, 127); | 
|  | } | 
|  | if (did) { | 
|  | edid_checksum(did, 127); | 
|  | } | 
|  | } | 
|  |  | 
|  | size_t qemu_edid_size(uint8_t *edid) | 
|  | { | 
|  | uint32_t exts; | 
|  |  | 
|  | if (edid[0] != 0x00 || | 
|  | edid[1] != 0xff) { | 
|  | /* doesn't look like a valid edid block */ | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | exts = edid[126]; | 
|  | return 128 * (exts + 1); | 
|  | } |