package drm import ( "fmt" "os" "path" "regexp" "strconv" "strings" "edge-infra.dev/pkg/sds/lib/edid" ) const DRMPath = "/sys/class/drm" const ( SubSystem = "drm" cardPrefix = "card" edidFileName = "edid" connectorPattern = "^card[0-9](-[A-Za-z0-9]+)+$" ) // Connector represents a connector in /sys/class/drm, // e.g. "card0-HDMI-A-1" or "card1-DP-2". type Connector struct { card int port string edid *edid.EDID } // The full connector name, e.g. "card0-HDMI-A-1". func (connector Connector) String() string { return fmt.Sprintf("%s%d-%s", cardPrefix, connector.card, connector.port) } // The connector's card, e.g. "card0". func (connector Connector) Card() string { return fmt.Sprintf("%s%d", cardPrefix, connector.card) } // The connector's card number. func (connector Connector) CardNum() int { return connector.card } // The connector's port, e.g. "HDMI-A-1". func (connector Connector) Port() string { return connector.port } // The path to the connector, e.g. "/sys/class/drm/card0-HDMI-A-1". func (connector Connector) Path() string { return path.Join(DRMPath, connector.String()) } // The connector's EDID. func (connector Connector) EDID() *edid.EDID { return connector.edid } // Override the connector's EDID. func (connector *Connector) WithEDID(edid *edid.EDID) { connector.edid = edid } func NewConnector(name string) (*Connector, error) { cardName, port, err := SplitConnectorName(name) if err != nil { return nil, err } // we can ignore the error check as we already validated name card, err := strconv.Atoi(strings.TrimPrefix(cardName, cardPrefix)) if err != nil { return nil, err } connector := &Connector{ card: card, port: port, } // add EDID if it is present edid, err := edid.NewFromFile(path.Join(connector.Path(), edidFileName)) if err == nil { connector.edid = edid } return connector, nil } // Splits connector into card and port, // e.g. "card0-HDMI-A-1" becomes "card0" and "HDMI-A-1". func SplitConnectorName(name string) (card, port string, err error) { if err := ValidateConnectorName(name); err != nil { return "", "", err } card, port, _ = strings.Cut(name, "-") return card, port, nil } // Checks connector name is of format "card<%d>-", // i.e. matches the pattern ^card[0-9](-[A-Za-z0-9]+)+$ func ValidateConnectorName(name string) error { re, err := regexp.Compile(connectorPattern) if err != nil { return err } if match := re.MatchString(name); !match { return fmt.Errorf("%s is not a valid connector name", name) } return nil } // Returns a list of all connectors in /sys/class/drm. func Connectors() ([]Connector, error) { files, err := os.ReadDir(DRMPath) if err != nil { return nil, fmt.Errorf("failed to read directory %s: %w", DRMPath, err) } connectors := []Connector{} for _, file := range files { connector, err := NewConnector(file.Name()) if err == nil { connectors = append(connectors, *connector) } } return connectors, nil }