diff --git a/.gitignore b/.gitignore index f1a5b7c..1962d80 100644 --- a/.gitignore +++ b/.gitignore @@ -35,4 +35,6 @@ *.c *.bin *.ihex +*.o.pre +*.o.post diff --git a/1024x768.S b/1024x768.S index 6f3e4b7..af96b86 100644 --- a/1024x768.S +++ b/1024x768.S @@ -39,6 +39,5 @@ #define ESTABLISHED_TIMING2_BITS 0x08 /* Bit 3 -> 1024x768 @60 Hz */ #define HSYNC_POL 0 #define VSYNC_POL 0 -#define CRC 0x55 #include "edid.S" diff --git a/1280x1024.S b/1280x1024.S index bd9bef2..dbf5402 100644 --- a/1280x1024.S +++ b/1280x1024.S @@ -39,6 +39,5 @@ /* No ESTABLISHED_TIMINGx_BITS */ #define HSYNC_POL 1 #define VSYNC_POL 1 -#define CRC 0xa0 #include "edid.S" diff --git a/1600x1200.S b/1600x1200.S index a45101c..a232111 100644 --- a/1600x1200.S +++ b/1600x1200.S @@ -39,6 +39,5 @@ /* No ESTABLISHED_TIMINGx_BITS */ #define HSYNC_POL 1 #define VSYNC_POL 1 -#define CRC 0x9d #include "edid.S" diff --git a/1680x1050.S b/1680x1050.S index b0d7c69..bf8b9e6 100644 --- a/1680x1050.S +++ b/1680x1050.S @@ -39,6 +39,5 @@ /* No ESTABLISHED_TIMINGx_BITS */ #define HSYNC_POL 1 #define VSYNC_POL 1 -#define CRC 0x26 #include "edid.S" diff --git a/1920x1080.S b/1920x1080.S index 3084355..da1abe2 100644 --- a/1920x1080.S +++ b/1920x1080.S @@ -39,6 +39,5 @@ /* No ESTABLISHED_TIMINGx_BITS */ #define HSYNC_POL 1 #define VSYNC_POL 1 -#define CRC 0x05 #include "edid.S" diff --git a/3840x2160.S b/3840x2160.S index 8be4306..2b24abb 100644 --- a/3840x2160.S +++ b/3840x2160.S @@ -1,6 +1,5 @@ -/* 3840x2160: 3840x2160 594.000 3840 4016 4104 4400 2160 2168 2178 2250 +hsync +vsync */ +/* 3840x2160: Modeline "3840x2160" 594.000 3840 4016 4104 4400 2160 2168 2178 2250 +hsync +vsync */ #define HSYNC_POL 1 -#define CRC 0x9b #define VSYNC_POL 1 #define YPULSE (63+10) #define XPULSE 88 diff --git a/800x600.S b/800x600.S index 6644e26..23f46c2 100644 --- a/800x600.S +++ b/800x600.S @@ -36,6 +36,5 @@ #define ESTABLISHED_TIMING1_BITS 0x01 /* Bit 0: 800x600 @ 60Hz */ #define HSYNC_POL 1 #define VSYNC_POL 1 -#define CRC 0xc2 #include "edid.S" diff --git a/HOWTO.txt b/HOWTO.txt deleted file mode 100644 index 835db33..0000000 --- a/HOWTO.txt +++ /dev/null @@ -1,58 +0,0 @@ -In the good old days when graphics parameters were configured explicitly -in a file called xorg.conf, even broken hardware could be managed. - -Today, with the advent of Kernel Mode Setting, a graphics board is -either correctly working because all components follow the standards - -or the computer is unusable, because the screen remains dark after -booting or it displays the wrong area. Cases when this happens are: -- The graphics board does not recognize the monitor. -- The graphics board is unable to detect any EDID data. -- The graphics board incorrectly forwards EDID data to the driver. -- The monitor sends no or bogus EDID data. -- A KVM sends its own EDID data instead of querying the connected monitor. -Adding the kernel parameter "nomodeset" helps in most cases, but causes -restrictions later on. - -As a remedy for such situations, the kernel configuration item -CONFIG_DRM_LOAD_EDID_FIRMWARE was introduced. It allows to provide an -individually prepared or corrected EDID data set in the /lib/firmware -directory from where it is loaded via the firmware interface. The code -(see drivers/gpu/drm/drm_edid_load.c) contains built-in data sets for -commonly used screen resolutions (800x600, 1024x768, 1280x1024, 1600x1200, -1680x1050, 1920x1080) as binary blobs, but the kernel source tree does -not contain code to create these data. In order to elucidate the origin -of the built-in binary EDID blobs and to facilitate the creation of -individual data for a specific misbehaving monitor, commented sources -and a Makefile environment are given here. - -To create binary EDID and C source code files from the existing data -material, simply type "make". - -If you want to create your own EDID file, copy the file 1024x768.S, -replace the settings with your own data and add a new target to the -Makefile. Please note that the EDID data structure expects the timing -values in a different way as compared to the standard X11 format. - -X11: -HTimings: hdisp hsyncstart hsyncend htotal -VTimings: vdisp vsyncstart vsyncend vtotal - -EDID: -#define XPIX hdisp -#define XBLANK htotal-hdisp -#define XOFFSET hsyncstart-hdisp -#define XPULSE hsyncend-hsyncstart - -#define YPIX vdisp -#define YBLANK vtotal-vdisp -#define YOFFSET (63+(vsyncstart-vdisp)) -#define YPULSE (63+(vsyncend-vsyncstart)) - -The CRC value in the last line - #define CRC 0x55 -also is a bit tricky. After a first version of the binary data set is -created, it must be checked with the "edid-decode" utility which will -most probably complain about a wrong CRC. Fortunately, the utility also -displays the correct CRC which must then be inserted into the source -file. After the make procedure is repeated, the EDID data set is ready -to be used. diff --git a/Makefile b/Makefile index e8a10bb..da35328 100644 --- a/Makefile +++ b/Makefile @@ -10,22 +10,28 @@ CODE := $(patsubst %.S, %.c, $(SOURCES)) all: $(BIN) $(IHEX) $(CODE) clean: - rm -f *.o *.bin.ihex *.bin *.c + rm -f *.o *.crc *.bin *.bin.ihex *.c %.o: %.S - cc -c $^ + cc -c -DCRC="0x00" -o $@ $^ -%.bin: %.o +%.bin.nocrc: %.o objcopy -Obinary $^ $@ -%.bin.ihex: %.o +%.crc: %.bin.nocrc + cat $^ | edid-decode \ + | sed -ne 's/^Checksum: 0x\w\+ (should be \(0x\w\+\))$$/\1/p' >$@ + +%.p: %.crc %.S + cc -c -DCRC="$$(cat $*.crc)" -o $@ $*.S + +%.bin: %.p + objcopy -Obinary $^ $@ + +%.bin.ihex: %.p objcopy -Oihex $^ $@ dos2unix $@ 2>/dev/null %.c: %.bin @echo "{" >$@; hexdump -f hex $^ >>$@; echo "};" >>$@ -%.crc: %.bin - ./compute-crc $^ >$@ - echo $(MAKE) $^ - diff --git a/README.md b/README.md index 434ae81..1f2e549 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,65 @@ -# edid-generator +edid-generator +============== + Hackerswork to generate an EDID binary file from given Xorg Modelines + +An extension of the awesome work provided in the Linux kernel documentation (in `docs/EDID`). + +Simplifies the process greatly by allowing you to use a standard modeline as well as automatically calculating the CRC +and applying it to the resulting image. + +Requirements +------------ + +``` +sudo apt install zsh edid-decode automake +``` + +Usage +----- + +If you don't have a `.S` prepared yet, generate one using a file containing Xorg Modelines. Lines that do not +contain modelines are ignored, so you can just read right from `xorg.conf`. + +```s +./modeline2edid /etc/X11/xorg.conf +``` + +You can also just read from `stdin` the way you'd expect: + +```s +./modeline2edid +# or explicitly: +./modeline2edid - +``` + +After this creates your `.S` files for each modeline it encounters, simply `make`: + +```sh +make +``` + +The end result, providing all goes well, is a glorious EDID bin image for each mode you gave to it. A `.S` file +is templated, and then `make` is invoked to compile it into `.bin`. It's actually compiled twice; once with an +invalid CRC in order to generate said CRC to enter it into the template, after which we recompile, hence glorious bins. + +NOTE: If you use a ratio other than 16:9, you'll need to specify it at the end of the modeline.as `ratio=4:3`. +Ratios are hard defined in `edid.S`, so if you are trying to do something non-standard you'll need to add it. + +Why? +---- + +Many monitors and TVs (both high and low end) provide invalid EDID data. After dealing with this for years, I wanted to +automate this process. + +The final straw was when I bought a Sceptre 4K tv for ~$225 and ran into a long series of hurdles to get it to operate +as expected at `3840x2160@60`. After doing this enough times, I had to automate it or I was going to go crazy. + +I used this to quickly iterate while troubleshhooting, finally it's all working from KMS all the way down to X! + +(Via `drm_kms_helper.edid_firmware=DP-1:edid/blah.bin``if you're interested. I'm using radeon + intel, with nvidia you +have to specify it in `xorg.conf`/`xorg.conf.d` as they don't yet support KMS for the fb console yet; their beta +drivers, 367 at the time of writing, only support using KMS for the xorg server.) + +Sometimes I hate being such a perfectionist. Keep in mind this project was made in a couple hours, I certainly didn't +attempt to polish it in the least ;) diff --git a/compute-crc b/compute-crc deleted file mode 100755 index ab0dfd8..0000000 --- a/compute-crc +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh -# A tad gross, but hey, it works. -cat "$bin_fn" | edid-decode | sed -ne 's/^Checksum: 0x\w\+ (should be \(0x\w\+\))$/\1/p' diff --git a/modeline2edid b/modeline2edid index d034c87..d604157 100755 --- a/modeline2edid +++ b/modeline2edid @@ -1,148 +1,97 @@ #!/bin/zsh setopt errexit errreturn -setopt sourcetrace verbose -setopt xtrace +#setopt xtrace +template-S() { + [[ ${(L)1} = mode(|line) ]] || return 1 + echo "-- Found naughty unicorn: $@" + shift 1 -munge-x11-modeline() { - local args=() arg - for arg in "$@"; do - [[ "$arg" != \"*\" ]] || arg="${arg:1:-1}" - args+=("$arg") - done - set -- ${(@)args} + local name="${1//\"}" + [[ -z "$name" ]] && echo "Could not parse modeline: $@" >&2 && return 1 + local fn="${name}.S" - [[ "$1" != "Modeline" ]] || shift 1 - name="$1" pixel_clock_mhz="$2"; shift 2 - hdisp="$1" hsyncstart="$2" hsyncend="$3" htotal="$4"; shift 4 - vdisp="$1" vsyncstart="$2" vsyncend="$3" vtotal="$4"; shift 4 + local -F pixel_clock_mhz=$2 + local -i pixel_clock_khz=$((pixel_clock_mhz * 1000)) + shift 2 - fn="${name}.S" - bin_fn="${name}.bin" + local -i hdisp="$1" hsyncstart="$2" hsyncend="$3" htotal="$4"; shift 4 + local -i vdisp="$1" vsyncstart="$2" vsyncend="$3" vtotal="$4"; shift 4 - hsync_polarity=0 - vsync_polarity=0 - ratio="16:9" # todo calc - dpi="96" - edid_version="1.3" - vfreq_hz="60" - crc="0x00" + local -i hsync_polarity=0 vsync_polarity=0 dpi=96 vfreq_hz=60 + local edid_version="1.3" ratio="16:9" # TODO calc ratio local arg for arg in "$@"; do case "${(L)arg}" in - *hsync) [[ "${arg:1:1}" == "-" ]] || hsync_polarity=1 ;; - *vsync) [[ "${arg:1:1}" == "-" ]] || vsync_polarity=1 ;; + [-+]hsync) [[ "${arg:1:1}" == "-" ]] || hsync_polarity=1 ;; + [-+]vsync) [[ "${arg:1:1}" == "-" ]] || vsync_polarity=1 ;; ratio=*|xy_ratio=*) ratio="${arg#*=}" ;; dpi=*) dpi="${arg#*=}" ;; edid_version=*) edid_version="${arg#*=}" ;; vfreq=*|vfreq_hz=*) vfreq_hz="${arg#*=}" ;; - crc=*) crc="${arg#*=}" ;; - *) echo "Unknown modeline option passed: $arg" >&2 ;; + *) echo "Ignoring unknown modeline option passed: '$arg'" >&2 ;; esac done -} - -gen-S-defines() { - [[ $# -eq 0 ]] || munge-x11-modeline "$@" - local -i pixel_clock_khz=$((pixel_clock_mhz * 1000)) + local -A defines defines=( TIMING_NAME "${(qqq)name}" - CLOCK "$pixel_clock_khz" - XPIX "$hdisp" - XBLANK "$((htotal - hdisp))" - XOFFSET "$((hsyncstart - hdisp))" - XPULSE "$((hsyncend - hsyncstart))" + CLOCK "$pixel_clock_khz" + XPIX "$hdisp" + XBLANK "$((htotal - hdisp))" + XOFFSET "$((hsyncstart - hdisp))" + XPULSE "$((hsyncend - hsyncstart))" - YPIX "$vdisp" - YBLANK "$((vtotal - vdisp))" - YOFFSET "(63+$((vsyncstart - vdisp)))" - YPULSE "(63+$((vsyncend - vsyncstart)))" + YPIX "$vdisp" + YBLANK "$((vtotal - vdisp))" + YOFFSET "(63+$((vsyncstart - vdisp)))" + YPULSE "(63+$((vsyncend - vsyncstart)))" - VERSION "${edid_version%%.*}" - REVISION "${edid_version#*.}" + VERSION "${edid_version%%.*}" + REVISION "${edid_version#*.}" - XY_RATIO "XY_RATIO_${(U)ratio//:/_}" - DPI "$dpi" - VFREQ "$vfreq_hz" - HSYNC_POL "$hsync_polarity" - VSYNC_POL "$vsync_polarity" - CRC "$crc" + XY_RATIO "XY_RATIO_${(U)ratio//:/_}" + DPI "$dpi" + VFREQ "$vfreq_hz" + HSYNC_POL "$hsync_polarity" + VSYNC_POL "$vsync_polarity" ) -} -template-S() { - local -a lines=("/* $name: $* */") + local -a lines=("/* $name: $REPLY */") local k for k in ${(k)defines}; do lines+=("#define $k ${defines[$k]}") done lines+=('#include "edid.S"') - echo "${(j:\n:)lines[@]}" | tee "$fn" - echo "Wrote $fn" >&2 + echo "${(j:\n:)lines[@]}" > "$fn" + echo "Wrote $fn" } -compile() { - rm -fv $bin_fn - echo "Compiling $bin_fn" >&2 - local i=0 - while true; do - let i++ || : - make >/dev/null 2>&1 || : - ! test -f "$bin_fn" || break - [[ $i -lt 100 ]] || (echo "Failed to compute checksum for $bin_fn" >&2; exit 1) - done - echo "Compiled $bin_fn in $i iterations" >&2 -} +local f=${1:-'-'} +[[ $f != '-' ]] || f="/dev/stdin" -modeline2edid() { - local name - local -A defines - gen-S-defines "$@" - [[ -n "$name" ]] || (echo "Didn't get a name? The hell?" >&2 && exit 1) - - # Template and compile - template-S "$@" - compile - - # Fix CRC - local crc=$(${0:h}/compute-crc) - # we're done if we don't have a crc to fix - [[ -n "$crc" ]] || return - - # Re-template and compile with proper CRC - defines[CRC]=$crc template-S "$@" - compile -} - - -main() { - zmodload -i zsh/zutil - - # support single argument filename - [[ -z $1 || $1 = -* ]] || set -- -f "$@" - - local o_file=() - zparseopts -K -D -E -- f:o_file - local f=${o_file[-1]} - - case $f in - -|) f="/dev/stdin" ;; - esac - - if [ -z "$f" ]; then - echo "Usage:" >&2 - echo "$0 [-f] FILENAME Parse modelines out of a file (eg xorg.conf) or '-' for stdin." >&2 - exit 1 - fi - - echo "Searching for unicorns in $f" >&2 - while read; do - echo "-- Found a unicorn: $REPLY" >&2 - modeline2edid $=REPLY - done < $f -} +if [[ -z "$f" || "$f" == "-h" ]]; then + self=${0:t} + cat >&2 <<-EOF + Modeline2EDID, version forever 0.0.1 + Help: + $self -h + Parse modelines from stdin: + $self + $self - + Parse modelines from a file (eg xorg.conf) + $self FILENAME + EOF + exit 1 +fi +echo "Searching for runaway unicorns in '$f'" +while read; do + # trim + REPLY=($=REPLY) + [[ -n "$REPLY" ]] || continue + template-S ${(@)REPLY} || : +done < $f