| $ | #!/bin/bash |
| $ | # ============================================================================= |
| $ | # Jetson Headless Virtual Display Setup (GPU-Accelerated) |
| $ | # ============================================================================= |
| $ | # |
| $ | # Creates a virtual 1920x1080@60Hz display on Jetson AGX Orin without a |
| $ | # physical monitor. EGL, CUDA, and NVIDIA Argus all work. A real monitor |
| $ | # can still be hot-plugged at any time. Works with NoMachine/VNC for remote |
| $ | # desktop access. |
| $ | # |
| $ | # Platform: Jetson AGX Orin |
| $ | # - JetPack 5.x (L4T R35.x, Ubuntu 20.04) — Xorg "nvidia" DDX driver |
| $ | # - JetPack 6.x (L4T R36.x, Ubuntu 22.04) — Xorg "nvidia" DDX driver |
| $ | # |
| $ | # HOW IT WORKS: |
| $ | # ------------- |
| $ | # Uses the nvidia DDX Xorg driver with ConnectedMonitor + CustomEDID options |
| $ | # to fake a connected 1920x1080 display on the DP port. This provides: |
| $ | # - Full NVIDIA EGL support (nvbufsurface, Argus camera can create EGLImages) |
| $ | # - CUDA-EGL interop (cuGraphicsEGLRegisterImage works) |
| $ | # - Hardware-accelerated X11/GLX rendering via NVIDIA GPU |
| $ | # - Proper DRI2 for remote desktop protocols (NoMachine, VNC, etc.) |
| $ | # |
| $ | # JP6-SPECIFIC NOTES: |
| $ | # ------------------- |
| $ | # nvidia-drm.modeset=1 is NOT compatible with nvidia DDX driver — it causes |
| $ | # "Failed to acquire modesetting permission". We remove it from boot params. |
| $ | # The nvidia DDX handles modesetting directly without nvidia-drm KMS. |
| $ | # |
| $ | # EGL/CUDA/Argus work fine WITHOUT nvidia-drm.modeset=1 when using nvidia DDX, |
| $ | # because applications access NVIDIA EGL/CUDA directly via the ICD, not through |
| $ | # the kernel DRM modesetting path. |
| $ | # |
| $ | # REAL MONITOR: |
| $ | # ------------- |
| $ | # If you plug in a real DP monitor, it appears automatically in xrandr |
| $ | # and is usable alongside (or instead of) the virtual framebuffer. |
| $ | # |
| $ | # USAGE: |
| $ | # chmod +x setup-virtual-display-fixed.sh |
| $ | # sudo ./setup-virtual-display-fixed.sh |
| $ | # sudo reboot |
| $ | # |
| $ | # VERIFY: |
| $ | # DISPLAY=:0 xrandr # 1920x1080 screen |
| $ | # DISPLAY=:0 eglinfo 2>&1 | head -15 # EGL vendor: NVIDIA |
| $ | # python3 -c "import ctypes; c=ctypes.CDLL('libcuda.so.1'); print(c.cuInit(0))" # 0 |
| $ | # # Connect via NoMachine — should show full GNOME desktop |
| $ | # |
| $ | # TO REVERT: |
| $ | # sudo cp /etc/X11/xorg.conf.bak.original /etc/X11/xorg.conf # or rm it |
| $ | # sudo rm -f /etc/X11/edid-1080p.bin |
| $ | # sudo rm -f /etc/udev/rules.d/99-jetson-drm-seat.rules |
| $ | # sudo systemctl enable seatd 2>/dev/null # if you need seatd back |
| $ | # # For JP6: add back nvidia-drm.modeset=1 to /boot/extlinux/extlinux.conf if needed |
| $ | # sudo reboot |
| $ | # |
| $ | # ============================================================================= |
| $ | |
| $ | set -euo pipefail |
| $ | |
| $ | if [[ $EUID -ne 0 ]]; then |
| $ | echo "ERROR: Must run as root (sudo)." |
| $ | exit 1 |
| $ | fi |
| $ | |
| $ | # --- Detect JetPack / L4T version --- |
| $ | detect_jetpack_version() { |
| > | local l4t_major="" |
| > | if [[ -f /etc/nv_tegra_release ]]; then |
| > | l4t_major=$(sed -n 's/^# R\([0-9]*\).*/\1/p' /etc/nv_tegra_release) |
| > | fi |
| > | if [[ -z "$l4t_major" ]] && command -v dpkg-query &>/dev/null; then |
| > | l4t_major=$(dpkg-query -W -f='${Version}' nvidia-l4t-core 2>/dev/null \ |
| > | | sed -n 's/^\([0-9]*\)\..*/\1/p') || true |
| > | fi |
| > | if [[ -n "$l4t_major" && "$l4t_major" -ge 36 ]]; then |
| > | echo 6 |
| > | elif [[ -n "$l4t_major" && "$l4t_major" -ge 35 ]]; then |
| > | echo 5 |
| > | else |
| > | echo 0 |
| > | fi |
| > | } |
| $ | |
| $ | # --- Detect first available DP connector --- |
| $ | detect_dp_connector_drm() { |
| > | # DRM naming (1-indexed): DP-1, DP-2, etc. |
| > | for card_dp in /sys/class/drm/card*-DP-*; do |
| > | if [[ -d "$card_dp" ]]; then |
| > | basename "$card_dp" | sed 's/card[0-9]*-//' |
| > | return |
| > | fi |
| > | done |
| > | echo "DP-1" |
| > | } |
| $ | |
| $ | detect_dp_connector_xorg() { |
| > | # nvidia DDX uses 0-indexed: DP-0, DP-1, etc. |
| > | local drm_idx |
| > | drm_idx=$(detect_dp_connector_drm | sed 's/.*DP-//') |
| > | echo "DP-$((drm_idx - 1))" |
| > | } |
| $ | |
| $ | JP_VERSION=$(detect_jetpack_version) |
| $ | DP_DRM=$(detect_dp_connector_drm) |
| $ | DP_XORG=$(detect_dp_connector_xorg) |
| $ | |
| $ | echo "=== Jetson Virtual Display Setup ===" |
| $ | |
| $ | if [[ "$JP_VERSION" -eq 6 ]]; then |
| $ | echo "Detected: JetPack 6 (L4T R36.x)" |
| $ | elif [[ "$JP_VERSION" -eq 5 ]]; then |
| $ | echo "Detected: JetPack 5 (L4T R35.x)" |
| $ | else |
| $ | echo "WARNING: Could not detect JetPack version, defaulting to JP5 path" |
| $ | JP_VERSION=5 |
| $ | fi |
| $ | |
| $ | # ========================================================================= |
| $ | # COMMON PATH (JP5 + JP6): nvidia DDX driver with ConnectedMonitor + CustomEDID |
| $ | # ========================================================================= |
| $ | |
| $ | echo "Using nvidia DDX driver with connector ${DP_XORG}" |
| $ | STEP=0 |
| $ | TOTAL_JP5=3 |
| $ | TOTAL_JP6=5 |
| $ | |
| $ | # ------------------------------------------------------------------ |
| $ | # Step 1: Backup xorg.conf |
| $ | # ------------------------------------------------------------------ |
| $ | STEP=$((STEP+1)) |
| $ | if [[ -f /etc/X11/xorg.conf && ! -f /etc/X11/xorg.conf.bak.original ]]; then |
| $ | cp /etc/X11/xorg.conf /etc/X11/xorg.conf.bak.original |
| $ | echo "[${STEP}] Backed up xorg.conf" |
| $ | else |
| $ | echo "[${STEP}] Backup: skipped (already exists or no file)" |
| $ | fi |
| $ | |
| $ | # ------------------------------------------------------------------ |
| $ | # Step 2: EDID binary |
| $ | # ------------------------------------------------------------------ |
| $ | STEP=$((STEP+1)) |
| $ | base64 -d > /etc/X11/edid-1080p.bin << 'EDID_EOF' |
| $ | AP///////wBZ5QEAAQAAAAEiAQOAAAB4Cu6Ro1RMmSYPUFQhCAABAQEBAQEBAQEBAQEBAQEBAjqA |
| $ | GHE4LUBYLEUADyghAAAeAAAA/ABWaXJ0dWFsIDEwODBwAAAA/QAySx5REQAKICAgICAgAAAA/wAw |
| $ | MDAwMDAwMDAwMDAxAJc= |
| $ | EDID_EOF |
| $ | chmod 644 /etc/X11/edid-1080p.bin |
| $ | echo "[${STEP}] Installed EDID binary" |
| $ | |
| $ | # ------------------------------------------------------------------ |
| $ | # Step 3: xorg.conf (nvidia DDX) |
| $ | # ------------------------------------------------------------------ |
| $ | STEP=$((STEP+1)) |
| $ | cat > /etc/X11/xorg.conf << XORG_EOF |
| $ | # Jetson JP${JP_VERSION} - nvidia DDX driver, virtual display |
| $ | Section "DRI" |
| $ | Mode 0666 |
| $ | EndSection |
| $ | |
| $ | Section "Module" |
| $ | Disable "dri" |
| $ | SubSection "extmod" |
| $ | Option "omit xfree86-dga" |
| $ | EndSubSection |
| $ | EndSection |
| $ | |
| $ | Section "Device" |
| $ | Identifier "Tegra0" |
| $ | Driver "nvidia" |
| $ | Option "AllowEmptyInitialConfiguration" "true" |
| $ | Option "ConnectedMonitor" "${DP_XORG}" |
| $ | Option "CustomEDID" "${DP_XORG}:/etc/X11/edid-1080p.bin" |
| $ | Option "HardDPMS" "false" |
| $ | EndSection |
| $ | |
| $ | Section "Monitor" |
| $ | Identifier "Virtual-1080p" |
| $ | HorizSync 30-81 |
| $ | VertRefresh 50-75 |
| $ | ModeLine "1920x1080_60" 148.50 1920 2008 2052 2200 1080 1084 1089 1125 +HSync +VSync |
| $ | Option "PreferredMode" "1920x1080_60" |
| $ | EndSection |
| $ | |
| $ | Section "Screen" |
| $ | Identifier "Default Screen" |
| $ | Device "Tegra0" |
| $ | Monitor "Virtual-1080p" |
| $ | DefaultDepth 24 |
| $ | SubSection "Display" |
| $ | Depth 24 |
| $ | Modes "1920x1080_60" |
| $ | EndSubSection |
| $ | EndSection |
| $ | XORG_EOF |
| $ | echo "[${STEP}] Wrote xorg.conf (nvidia DDX)" |
| $ | |
| $ | # ------------------------------------------------------------------ |
| $ | # JP6-SPECIFIC: Additional steps |
| $ | # ------------------------------------------------------------------ |
| $ | if [[ "$JP_VERSION" -eq 6 ]]; then |
| $ | |
| $ | # ------------------------------------------------------------------ |
| $ | # Step 4: Remove nvidia-drm.modeset=1 from boot params |
| $ | # ------------------------------------------------------------------ |
| $ | STEP=$((STEP+1)) |
| $ | EXTLINUX_CONF="/boot/extlinux/extlinux.conf" |
| $ | if [[ -f "$EXTLINUX_CONF" ]]; then |
| $ | if grep -q "nvidia-drm.modeset=1" "$EXTLINUX_CONF"; then |
| $ | cp "$EXTLINUX_CONF" "${EXTLINUX_CONF}.bak.$(date +%Y%m%d%H%M%S)" |
| $ | sed -i 's| nvidia-drm\.modeset=1||g' "$EXTLINUX_CONF" |
| $ | echo "[${STEP}] Removed nvidia-drm.modeset=1 from extlinux.conf" |
| $ | else |
| $ | echo "[${STEP}] nvidia-drm.modeset=1 not present (OK)" |
| $ | fi |
| $ | else |
| $ | echo "[${STEP}] WARNING: ${EXTLINUX_CONF} not found" |
| $ | fi |
| $ | |
| $ | # ------------------------------------------------------------------ |
| $ | # Step 5: Disable seatd if present (optional, helps avoid conflicts) |
| $ | # ------------------------------------------------------------------ |
| $ | STEP=$((STEP+1)) |
| $ | if systemctl is-enabled seatd &>/dev/null 2>&1; then |
| $ | systemctl stop seatd 2>/dev/null || true |
| $ | systemctl disable seatd 2>/dev/null || true |
| $ | echo "[${STEP}] Disabled seatd (avoids DRM master conflicts)" |
| $ | else |
| $ | echo "[${STEP}] seatd not present or already disabled" |
| $ | fi |
| $ | |
| $ | fi |
| $ | |
| $ | echo "" |
| $ | echo "=== Done! ===" |
| $ | echo "" |
| $ | echo "A reboot is REQUIRED:" |
| $ | echo " sudo reboot" |
| $ | echo "" |
| $ | echo "After reboot, verify:" |
| $ | echo " DISPLAY=:0 xrandr # 1920x1080 screen" |
| $ | echo " DISPLAY=:0 eglinfo 2>&1 | head -15 # EGL vendor: NVIDIA" |
| $ | echo " python3 -c 'import ctypes; c=ctypes.CDLL(\"libcuda.so.1\"); print(c.cuInit(0))' # 0" |
| $ | echo "" |
| $ | if [[ "$JP_VERSION" -eq 6 ]]; then |
| $ | echo "JP6 NOTES:" |
| $ | echo " - nvidia DDX driver active (NOT modesetting)" |
| $ | echo " - nvidia-drm.modeset=1 removed from boot (incompatible with nvidia DDX)" |
| $ | echo " - EGL/CUDA/Argus work via nvidia DDX's own rendering path" |
| $ | echo " - NoMachine/VNC will show full hardware-accelerated desktop" |
| $ | echo "" |
| $ | fi |
| $ | echo "Connect via NoMachine or VNC to access the desktop remotely." |