Reading HDMI power states on NVIDIA Jetson

July 28, 2020

We use Jetson Nano devices in production, they are connected over HDMI to a "digital signage" display, which is essentially just a fancy monitor and the Nano is crunching through camera stream, detecting people, faces, age, gender etc.

These devices are mostly left alone during the operation, but are manually powered off out of working hours. Would be good if we could remotely tell if the display is actually powered on or not! The state can be monitored from the web and/or notifications can be sent to the user if the display has malfunctioned or simply someone forgot to turn it on.

Turns out on Tegra platforms, we can read HDMI state from Serial Output Resource (SOR) located in kernel debugfs. SOR is part of display driver on Tegra SoCs, responsible for HDMI/DisplayPort/LVDS management.

Reading through SOR sources included in android kernel for tegra I found a register called NV_SOR_PWR which reports the power state, that should do.

In order to access debugfs as non-root, we have to mount it with a different mode, I added this to fstab to do this on boot.

echo "debugfs /sys/kernel/debug debugfs defaults,mode=755 0 0" | tee -a /etc/fstab

Now we can read the debugfs, let's look through the SOR registers and grep for the one related to power. This shows the register, it's offset and the current value.

grep NV_SOR_PWR /sys/kernel/debug/tegradc.0/sor/regs
NV_SOR_PWR                        015  10000000

Through simple experiments I discovered that most significant bit indicates that there is no power at HDMI, so 10000000 means the display is in OFF or UNPLUGGED state, while the least significant bit indicates POWERED state - 00000001

Based on this we can write a function to read from this file line by line, until we hit NV_SOR_PWR, read it's value and determine the HDMI state. I opted for C++, but you can of course, use any language.

#pragma once

#include <string>
#include <fstream>

/*!
 * HDMI state
 */
enum class hdmi_state { unknown = 0, powered, unpowered };

/*!
 * Default debufs mount path.
 */
static const std::string debugfs_mount = "/sys/kernel/debug/";

/*!
 * Nvidia SOR power registers.
 */
static const std::string tegradc_sor_regs = "tegradc.0/sor/regs";
static const std::string NV_SOR_PWR = "NV_SOR_PWR";

/*!
 * Get current HDMI state.
 */
hdmi_state get_hdmi_state() {
  std::string path = hwio::debugfs_mount + hwio::tegradc_sor_regs;
  int ret = access(path.c_str(), R_OK);
  if (ret == ENOENT || ret == EACCES || ret == -1) {
    std::cerr << "Path not found, or wrong permissions: " << path << std::endl;
  } else {
    std::ifstream input(path, std::ios::in);

    // find the power register
    for (std::string line; std::getline(input, line);) {
      if (line.find(NV_SOR_PWR) == 0) {
        std::string power = line.substr(line.size() - 8);
        // todo: should probably convert to a bitset<8> and compare bits
        if (power.back() == '1') {
          return hdmi_state::powered;
        } else if (power.front() == '1') {
          return hdmi_state::unpowered;
        }
        break;
      }
    }
  }
  return hdmi_state::unknown;
}

This could probably be improved by reading the bits from the register value into std::bitset<8> instead of using substr(), but this will also do.

If user does not have permissions to access debugfs or register doesn't have any of the bits set, this will default to hdmi_state::unknown.

Now we can use this value inside the application for any additional logic, user notifications, flashing an LED connected to the GPIO of the board or whatever might be appropriate.

That's about it!

PS. There is also a /sys/kernel/debug/tegradc.0/hotplug, which I expected to provide exactly the same info, but it appears that no matter the state of the display, it always reads 0.


Profile picture

Written by Alex March