diff options
author | Miod Vallat <miod@cvs.openbsd.org> | 2024-11-06 09:34:11 +0000 |
---|---|---|
committer | Miod Vallat <miod@cvs.openbsd.org> | 2024-11-06 09:34:11 +0000 |
commit | f622ce285e0a3e2436eb1b5c7de1fa4ff08b41be (patch) | |
tree | a40517f94c95fd12151023c12d620d9fb6bffbab | |
parent | 03ef666bfb604a0cc01a953adf738638d1d6be0d (diff) |
Sync with NetBSD: remove duplicate mode entries when parsing edid records,
be more robust against bogus edid data.
-rw-r--r-- | sys/dev/videomode/edid.c | 224 | ||||
-rw-r--r-- | sys/dev/videomode/edidreg.h | 15 | ||||
-rw-r--r-- | sys/dev/videomode/edidvar.h | 8 | ||||
-rw-r--r-- | sys/dev/videomode/modelines2c.awk | 9 |
4 files changed, 163 insertions, 93 deletions
diff --git a/sys/dev/videomode/edid.c b/sys/dev/videomode/edid.c index d647b2f0bac..9756c83eb64 100644 --- a/sys/dev/videomode/edid.c +++ b/sys/dev/videomode/edid.c @@ -1,5 +1,5 @@ -/* $OpenBSD: edid.c,v 1.7 2024/11/06 07:09:45 miod Exp $ */ -/* $NetBSD: edid.c,v 1.5 2007/03/07 19:56:40 macallan Exp $ */ +/* $OpenBSD: edid.c,v 1.8 2024/11/06 09:34:10 miod Exp $ */ +/* $NetBSD: edid.c,v 1.15 2020/01/25 15:59:11 maxv Exp $ */ /*- * Copyright (c) 2006 Itronix Inc. @@ -30,13 +30,12 @@ * 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. - */ + */ #include <sys/param.h> #include <sys/systm.h> #include <sys/device.h> #include <sys/kernel.h> -#include <sys/malloc.h> #include <dev/videomode/videomode.h> #include <dev/videomode/ediddevs.h> #include <dev/videomode/edidreg.h> @@ -47,21 +46,25 @@ const char *edid_findvendor(const char *); const char *edid_findproduct(const char *, uint16_t); void edid_strchomp(char *); const struct videomode *edid_mode_lookup_list(const char *); +struct videomode *edid_search_mode(struct edid_info *, + const struct videomode *); int edid_std_timing(uint8_t *, struct videomode *); int edid_det_timing(uint8_t *, struct videomode *); +void bump_preferred_mode(struct edid_info *, struct videomode *); void edid_block(struct edid_info *, uint8_t *); /* #define EDID_DEBUG */ #define EDIDVERBOSE 1 #define DIVIDE(x,y) (((x) + ((y) / 2)) / (y)) +/* These are reversed established timing order */ static const char *_edid_modes[] = { "1280x1024x75", "1024x768x75", "1024x768x70", "1024x768x60", "1024x768x87i", - "832x768x74", /* rounding error, 74.55 Hz aka "832x624x75" */ + "832x624x74", /* rounding error, 74.55 Hz aka "832x624x75" */ "800x600x75", "800x600x72", "800x600x60", @@ -70,8 +73,8 @@ static const char *_edid_modes[] = { "640x480x72", "640x480x67", "640x480x60", - "720x400x85", /* should this really be "720x400x88" ? */ - "720x400x70", /* hmm... videomode.c doesn't have this one */ + "720x400x87", /* rounding error, 87.85 Hz aka "720x400x88" */ + "720x400x70", }; #ifdef EDIDVERBOSE @@ -97,7 +100,7 @@ edid_findvendor(const char *vendor) for (n = 0; n < edid_nvendors; n++) if (memcmp(edid_vendors[n].vendor, vendor, 3) == 0) - return (edid_vendors[n].name); + return edid_vendors[n].name; #endif return NULL; } @@ -109,9 +112,9 @@ edid_findproduct(const char *vendor, uint16_t product) int n; for (n = 0; n < edid_nproducts; n++) - if ((edid_products[n].product == product) && - (memcmp(edid_products[n].vendor, vendor, 3) == 0)) - return (edid_products[n].name); + if (edid_products[n].product == product && + memcmp(edid_products[n].vendor, vendor, 3) == 0) + return edid_products[n].name; #endif /* EDIDVERBOSE */ return NULL; @@ -122,11 +125,11 @@ edid_strchomp(char *ptr) { for (;;) { switch (*ptr) { - case 0: + case '\0': return; case '\r': case '\n': - *ptr = 0; + *ptr = '\0'; return; } ptr++; @@ -138,15 +141,15 @@ edid_is_valid(uint8_t *d) { int sum = 0, i; uint8_t sig[8] = EDID_SIGNATURE; - + if (memcmp(d, sig, 8) != 0) return EINVAL; - + for (i = 0; i < 128; i++) sum += d[i]; if ((sum & 0xff) != 0) return EINVAL; - + return 0; } @@ -267,20 +270,35 @@ edid_print(struct edid_info *edid) } printf("Video modes:\n"); for (i = 0; i < edid->edid_nmodes; i++) { - printf("\t%dx%d @ %dHz\n", + printf("\t%dx%d @ %dHz", edid->edid_modes[i].hdisplay, edid->edid_modes[i].vdisplay, DIVIDE(DIVIDE(edid->edid_modes[i].dot_clock * 1000, - edid->edid_modes[i].htotal), + edid->edid_modes[i].htotal), edid->edid_modes[i].vtotal)); + printf(" (%d %d %d %d %d %d %d", + edid->edid_modes[i].dot_clock, + edid->edid_modes[i].hsync_start, + edid->edid_modes[i].hsync_end, + edid->edid_modes[i].htotal, + edid->edid_modes[i].vsync_start, + edid->edid_modes[i].vsync_end, + edid->edid_modes[i].vtotal); + printf(" %s%sH %s%sV)\n", + edid->edid_modes[i].flags & VID_PHSYNC ? "+" : "", + edid->edid_modes[i].flags & VID_NHSYNC ? "-" : "", + edid->edid_modes[i].flags & VID_PVSYNC ? "+" : "", + edid->edid_modes[i].flags & VID_NVSYNC ? "-" : ""); } if (edid->edid_preferred_mode) printf("Preferred mode: %dx%d @ %dHz\n", edid->edid_preferred_mode->hdisplay, edid->edid_preferred_mode->vdisplay, DIVIDE(DIVIDE(edid->edid_preferred_mode->dot_clock * 1000, - edid->edid_preferred_mode->htotal), + edid->edid_preferred_mode->htotal), edid->edid_preferred_mode->vtotal)); + + printf("Number of extension blocks: %d\n", edid->edid_ext_block_count); } #endif @@ -295,6 +313,26 @@ edid_mode_lookup_list(const char *name) return NULL; } +struct videomode * +edid_search_mode(struct edid_info *edid, const struct videomode *mode) +{ + int refresh, i; + + refresh = DIVIDE(DIVIDE(mode->dot_clock * 1000, + mode->htotal), mode->vtotal); + for (i = 0; i < edid->edid_nmodes; i++) { + if (mode->hdisplay == edid->edid_modes[i].hdisplay && + mode->vdisplay == edid->edid_modes[i].vdisplay && + refresh == DIVIDE(DIVIDE( + edid->edid_modes[i].dot_clock * 1000, + edid->edid_modes[i].htotal), + edid->edid_modes[i].vtotal)) { + return &edid->edid_modes[i]; + } + } + return NULL; +} + int edid_std_timing(uint8_t *data, struct videomode *vmp) { @@ -326,13 +364,11 @@ edid_std_timing(uint8_t *data, struct videomode *vmp) f = EDID_STD_TIMING_VFREQ(data); /* first try to lookup the mode as a DMT timing */ - snprintf(name, sizeof (name), "%dx%dx%d", x, y, f); + snprintf(name, sizeof(name), "%dx%dx%d", x, y, f); if ((lookup = edid_mode_lookup_list(name)) != NULL) { *vmp = *lookup; - } - - /* failing that, calculate it using gtf */ - else { + } else { + /* failing that, calculate it using gtf */ /* * Hmm. I'm not using alternate GTF timings, which * could, in theory, be present. @@ -353,7 +389,7 @@ edid_det_timing(uint8_t *data, struct videomode *vmp) /* we don't support stereo modes (for now) */ if (flags & (EDID_DET_TIMING_FLAG_STEREO | - EDID_DET_TIMING_FLAG_STEREO1)) + EDID_DET_TIMING_FLAG_STEREO_MODE)) return 0; vmp->dot_clock = EDID_DET_TIMING_DOT_CLOCK(data) / 1000; @@ -367,8 +403,8 @@ edid_det_timing(uint8_t *data, struct videomode *vmp) vblank = EDID_DET_TIMING_VBLANK(data); vsyncwid = EDID_DET_TIMING_VSYNC_WIDTH(data); vsyncoff = EDID_DET_TIMING_VSYNC_OFFSET(data); - - /* XXX: I'm not doing anything with the borders, should I? */ + + /* Borders are contained within the blank areas. */ vmp->hdisplay = hactive; vmp->htotal = hactive + hblank; @@ -397,64 +433,74 @@ edid_det_timing(uint8_t *data, struct videomode *vmp) return 1; } +void bump_preferred_mode(struct edid_info *edid, struct videomode *m) +{ + /* + * XXX + * Iiyama 4800 series monitors may have their native resolution in the + * 2nd detailed timing descriptor instead of the 1st. Try to detect + * that here and pick the native mode anyway. + */ + if (edid->edid_preferred_mode == NULL) { + edid->edid_preferred_mode = m; + } else if ((strncmp(edid->edid_vendor, "IVM", 3) == 0) && + (edid->edid_product == 0x4800) && + (edid->edid_preferred_mode->dot_clock < m->dot_clock)) + edid->edid_preferred_mode = m; +} + void edid_block(struct edid_info *edid, uint8_t *data) { int i; - struct videomode mode; + struct videomode mode, *exist_mode; if (EDID_BLOCK_IS_DET_TIMING(data)) { - if (edid_det_timing(data, &mode)) { + if (!edid_det_timing(data, &mode)) + return; + /* Does this mode already exist? */ + exist_mode = edid_search_mode(edid, &mode); + if (exist_mode != NULL) { + *exist_mode = mode; + bump_preferred_mode(edid, exist_mode); + } else { edid->edid_modes[edid->edid_nmodes] = mode; - if (edid->edid_preferred_mode == NULL) { - edid->edid_preferred_mode = - &edid->edid_modes[edid->edid_nmodes]; - } - edid->edid_nmodes++; + bump_preferred_mode(edid, + &edid->edid_modes[edid->edid_nmodes]); + edid->edid_nmodes++; } return; } switch (EDID_BLOCK_TYPE(data)) { case EDID_DESC_BLOCK_TYPE_SERIAL: - memcpy(edid->edid_serial, - data + EDID_DESC_ASCII_DATA_OFFSET, + memcpy(edid->edid_serial, data + EDID_DESC_ASCII_DATA_OFFSET, EDID_DESC_ASCII_DATA_LEN); - edid->edid_serial[sizeof (edid->edid_serial) - 1] = 0; + edid->edid_serial[EDID_DESC_ASCII_DATA_LEN] = 0; break; case EDID_DESC_BLOCK_TYPE_ASCII: - memcpy(edid->edid_comment, - data + EDID_DESC_ASCII_DATA_OFFSET, + memcpy(edid->edid_comment, data + EDID_DESC_ASCII_DATA_OFFSET, EDID_DESC_ASCII_DATA_LEN); - edid->edid_comment[sizeof (edid->edid_comment) - 1] = 0; + edid->edid_comment[EDID_DESC_ASCII_DATA_LEN] = 0; break; case EDID_DESC_BLOCK_TYPE_RANGE: edid->edid_have_range = 1; - edid->edid_range.er_min_vfreq = - EDID_DESC_RANGE_MIN_VFREQ(data); - edid->edid_range.er_max_vfreq = - EDID_DESC_RANGE_MAX_VFREQ(data); - edid->edid_range.er_min_hfreq = - EDID_DESC_RANGE_MIN_HFREQ(data); - edid->edid_range.er_max_hfreq = - EDID_DESC_RANGE_MAX_HFREQ(data); - edid->edid_range.er_max_clock = - EDID_DESC_RANGE_MAX_CLOCK(data); - if (EDID_DESC_RANGE_HAVE_GTF2(data)) { - edid->edid_range.er_have_gtf2 = 1; - edid->edid_range.er_gtf2_hfreq = - EDID_DESC_RANGE_GTF2_HFREQ(data); - edid->edid_range.er_gtf2_c = - EDID_DESC_RANGE_GTF2_C(data); - edid->edid_range.er_gtf2_m = - EDID_DESC_RANGE_GTF2_M(data); - edid->edid_range.er_gtf2_j = - EDID_DESC_RANGE_GTF2_J(data); - edid->edid_range.er_gtf2_k = - EDID_DESC_RANGE_GTF2_K(data); - } + edid->edid_range.er_min_vfreq = EDID_DESC_RANGE_MIN_VFREQ(data); + edid->edid_range.er_max_vfreq = EDID_DESC_RANGE_MAX_VFREQ(data); + edid->edid_range.er_min_hfreq = EDID_DESC_RANGE_MIN_HFREQ(data); + edid->edid_range.er_max_hfreq = EDID_DESC_RANGE_MAX_HFREQ(data); + edid->edid_range.er_max_clock = EDID_DESC_RANGE_MAX_CLOCK(data); + if (!EDID_DESC_RANGE_HAVE_GTF2(data)) + break; + edid->edid_range.er_have_gtf2 = 1; + edid->edid_range.er_gtf2_hfreq = + EDID_DESC_RANGE_GTF2_HFREQ(data); + edid->edid_range.er_gtf2_c = EDID_DESC_RANGE_GTF2_C(data); + edid->edid_range.er_gtf2_m = EDID_DESC_RANGE_GTF2_M(data); + edid->edid_range.er_gtf2_j = EDID_DESC_RANGE_GTF2_J(data); + edid->edid_range.er_gtf2_k = EDID_DESC_RANGE_GTF2_K(data); break; case EDID_DESC_BLOCK_TYPE_NAME: @@ -462,14 +508,20 @@ edid_block(struct edid_info *edid, uint8_t *data) memcpy(edid->edid_productname, data + EDID_DESC_ASCII_DATA_OFFSET, EDID_DESC_ASCII_DATA_LEN); + edid->edid_productname[EDID_DESC_ASCII_DATA_LEN] = '\0'; break; case EDID_DESC_BLOCK_TYPE_STD_TIMING: data += EDID_DESC_STD_TIMING_START; for (i = 0; i < EDID_DESC_STD_TIMING_COUNT; i++) { if (edid_std_timing(data, &mode)) { - edid->edid_modes[edid->edid_nmodes] = mode; - edid->edid_nmodes++; + /* Does this mode already exist? */ + exist_mode = edid_search_mode(edid, &mode); + if (exist_mode == NULL) { + edid->edid_modes[edid->edid_nmodes] = + mode; + edid->edid_nmodes++; + } } data += 2; } @@ -482,7 +534,7 @@ edid_block(struct edid_info *edid, uint8_t *data) } /* - * Gets EDID version in BCD, e.g. EDID v1.3 returned as 0x0103 + * Gets EDID version in BCD, e.g. EDID v1.3 returned as 0x0103 */ int edid_parse(const char *devname, uint8_t *data, struct edid_info *edid) @@ -504,24 +556,26 @@ edid_parse(const char *devname, uint8_t *data, struct edid_info *edid) edid->edid_vendor[2] = EDID_MANFID_2(manfid); edid->edid_vendor[3] = 0; /* null terminate for convenience */ - edid->edid_product = data[EDID_OFFSET_PRODUCT_ID] + + edid->edid_product = data[EDID_OFFSET_PRODUCT_ID] + (data[EDID_OFFSET_PRODUCT_ID + 1] << 8); name = edid_findvendor(edid->edid_vendor); if (name != NULL) { - snprintf(edid->edid_vendorname, - sizeof (edid->edid_vendorname), "%s", name); - } - edid->edid_vendorname[sizeof (edid->edid_vendorname) - 1] = 0; + strlcpy(edid->edid_vendorname, name, + sizeof(edid->edid_vendorname)); + } else + edid->edid_vendorname[0] = '\0'; name = edid_findproduct(edid->edid_vendor, edid->edid_product); if (name != NULL) { - snprintf(edid->edid_productname, - sizeof (edid->edid_productname), "%s", name); - } - edid->edid_productname[sizeof (edid->edid_productname) - 1] = 0; + strlcpy(edid->edid_productname, name, + sizeof(edid->edid_productname)); + } else + edid->edid_productname[0] = '\0'; + + edid->edid_comment[0] = '\0'; - snprintf(edid->edid_serial, sizeof (edid->edid_serial), "%08x", + snprintf(edid->edid_serial, sizeof(edid->edid_serial), "%08x", EDID_SERIAL_NUMBER(data)); edid->edid_week = EDID_WEEK(data); @@ -547,11 +601,14 @@ edid_parse(const char *devname, uint8_t *data, struct edid_info *edid) edid->edid_chroma.ec_whitex = EDID_CHROMA_WHITEX(data); edid->edid_chroma.ec_whitey = EDID_CHROMA_WHITEY(data); + edid->edid_ext_block_count = EDID_EXT_BLOCK_COUNT(data); + /* lookup established modes */ edid->edid_nmodes = 0; edid->edid_preferred_mode = NULL; estmodes = EDID_EST_TIMING(data); - for (i = 0; i < 16; i++) { + /* Iterate in established timing order */ + for (i = 15; i >= 0; i--) { if (estmodes & (1 << i)) { vmp = edid_mode_lookup_list(_edid_modes[i]); if (vmp != NULL) { @@ -568,11 +625,15 @@ edid_parse(const char *devname, uint8_t *data, struct edid_info *edid) /* do standard timing section */ for (i = 0; i < EDID_STD_TIMING_COUNT; i++) { - struct videomode mode; + struct videomode mode, *exist_mode; if (edid_std_timing(data + EDID_OFFSET_STD_TIMING + i * 2, &mode)) { - edid->edid_modes[edid->edid_nmodes] = mode; - edid->edid_nmodes++; + /* Does this mode already exist? */ + exist_mode = edid_search_mode(edid, &mode); + if (exist_mode == NULL) { + edid->edid_modes[edid->edid_nmodes] = mode; + edid->edid_nmodes++; + } } } /* do detailed timings and descriptors */ @@ -598,13 +659,14 @@ edid_parse(const char *devname, uint8_t *data, struct edid_info *edid) if (edid->edid_modes[i].dot_clock > max_dotclock) max_dotclock = edid->edid_modes[i].dot_clock; +#ifdef DIAGNOSTIC printf("%s: max_dotclock according to supported modes: %d\n", devname, max_dotclock); +#endif mhz = (max_dotclock + 999) / 1000; if (edid->edid_have_range) { - if (mhz > edid->edid_range.er_max_clock) edid->edid_range.er_max_clock = mhz; } else diff --git a/sys/dev/videomode/edidreg.h b/sys/dev/videomode/edidreg.h index 473c728e812..bcac6797b14 100644 --- a/sys/dev/videomode/edidreg.h +++ b/sys/dev/videomode/edidreg.h @@ -1,4 +1,5 @@ -/* $NetBSD: edidreg.h,v 1.2 2006/05/13 00:39:19 gdamore Exp $ */ +/* $OpenBSD: edidreg.h,v 1.2 2024/11/06 09:34:10 miod Exp $ */ +/* $NetBSD: edidreg.h,v 1.4 2014/11/17 00:46:04 jmcneill Exp $ */ /*- * Copyright (c) 2006 Itronix Inc. @@ -29,7 +30,7 @@ * 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. - */ + */ #ifndef _DEV_VIDEOMODE_EDIDREG_H #define _DEV_VIDEOMODE_EDIDREG_H @@ -203,11 +204,11 @@ #define EDID_DET_TIMING_VBORDER(ptr) ((ptr)[16]) #define EDID_DET_TIMING_FLAGS(ptr) ((ptr)[17]) #define EDID_DET_TIMING_FLAG_INTERLACE 0x80 -#define EDID_DET_TIMING_FLAG_STEREO 0x60 /* wtf? */ +#define EDID_DET_TIMING_FLAG_STEREO 0x60 /* stereo or not */ #define EDID_DET_TIMING_FLAG_SYNC_SEPARATE 0x18 -#define EDID_DET_TIMING_FLAG_HSYNC_POSITIVE 0x04 -#define EDID_DET_TIMING_FLAG_VSYNC_POSITIVE 0x02 -#define EDID_DET_TIMING_FLAG_STEREO1 0x01 /* wtf? */ +#define EDID_DET_TIMING_FLAG_VSYNC_POSITIVE 0x04 +#define EDID_DET_TIMING_FLAG_HSYNC_POSITIVE 0x02 +#define EDID_DET_TIMING_FLAG_STEREO_MODE 0x01 /* stereo mode */ /* N.B.: these tests assume that we already checked for detailed timing! */ @@ -250,4 +251,6 @@ #define EDID_DESC_STD_TIMING_START 5 #define EDID_DESC_STD_TIMING_COUNT 6 +#define EDID_EXT_BLOCK_COUNT(ptr) ((ptr)[126]) + #endif /* _DEV_VIDEOMODE_EDIDREG_H */ diff --git a/sys/dev/videomode/edidvar.h b/sys/dev/videomode/edidvar.h index e1b006f1fb1..ebf4465c7b4 100644 --- a/sys/dev/videomode/edidvar.h +++ b/sys/dev/videomode/edidvar.h @@ -1,4 +1,5 @@ -/* $NetBSD: edidvar.h,v 1.2 2006/05/11 19:05:41 gdamore Exp $ */ +/* $OpenBSD: edidvar.h,v 1.3 2024/11/06 09:34:10 miod Exp $ */ +/* $NetBSD: edidvar.h,v 1.5 2021/12/19 11:01:01 riastradh Exp $ */ /*- * Copyright (c) 2006 Itronix Inc. @@ -29,11 +30,13 @@ * 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. - */ + */ #ifndef _DEV_VIDEOMODE_EDIDVAR_H #define _DEV_VIDEOMODE_EDIDVAR_H +#include <dev/videomode/videomode.h> + struct edid_chroma { uint16_t ec_redx; uint16_t ec_redy; @@ -75,6 +78,7 @@ struct edid_info { uint8_t edid_max_vsize; /* in cm */ uint8_t edid_gamma; uint8_t edid_features; + uint8_t edid_ext_block_count; int edid_have_range; struct edid_range edid_range; diff --git a/sys/dev/videomode/modelines2c.awk b/sys/dev/videomode/modelines2c.awk index 424610ceaf6..97341a570ca 100644 --- a/sys/dev/videomode/modelines2c.awk +++ b/sys/dev/videomode/modelines2c.awk @@ -1,6 +1,6 @@ #! /usr/bin/awk -f -# $NetBSD: modelines2c.awk,v 1.4 2006/10/26 23:19:50 bjh21 Exp $ -# $OpenBSD: modelines2c.awk,v 1.2 2012/12/05 23:20:22 deraadt Exp $ +# $NetBSD: modelines2c.awk,v 1.5 2011/03/21 19:32:26 jdc Exp $ +# $OpenBSD: modelines2c.awk,v 1.3 2024/11/06 09:34:10 miod Exp $ # # Copyright (c) 2006 Itronix Inc. # All rights reserved. @@ -104,8 +104,9 @@ NR == 1 { ifactor = 2.0; } - # why the additional .1 to vrefresh? well it seems that awk likes to - # make some "rounding errors", and this will compensate + # We truncate the vrefresh figure, but some mode descriptions rely + # on rounding, so we can't win here. Adding an additional .1 + # compensates to some extent. hrefresh= (dotclock * 1000000) / htotal; vrefresh= int(((hrefresh * ifactor) / vtotal) + .1); |