// Package drmtree exposes DRM data as a tree structure.
package drmtree
import (
"encoding/json"
"fmt"
"math"
"sort"
"strings"
"git.sr.ht/~emersion/drmdb/treefmt"
"git.sr.ht/~emersion/go-drm"
)
type DriverVersion struct {
Major int32 `json:"major"`
Minor int32 `json:"minor"`
Patch int32 `json:"patch"`
Date string `json:"date"`
}
func (ver *DriverVersion) String() string {
return fmt.Sprintf("%v.%v.%v (%v)", ver.Major, ver.Minor, ver.Patch, ver.Date)
}
func (ver *DriverVersion) Less(other *DriverVersion) bool {
if ver.Major < other.Major {
return true
} else if ver.Major == other.Major {
if ver.Minor < other.Minor {
return true
} else if ver.Minor == other.Minor {
if ver.Patch < other.Patch {
return true
} else if ver.Patch == other.Patch {
return ver.Date < other.Date
}
}
}
return false
}
type Kernel struct {
SysName string `json:"sysname"`
Release string `json:"release"`
Version string `json:"version"`
}
func (k *Kernel) String() string {
if k.SysName == "" {
return "unknown kernel"
}
s := k.SysName
if k.Release != "" {
s += " " + k.Release
}
return s
}
type Driver struct {
Name string `json:"name"`
Desc string `json:"desc"`
Version DriverVersion `json:"version"`
Kernel Kernel `json:"kernel"`
Caps map[string]*uint64 `json:"caps"`
ClientCaps map[string]bool `json:"client_caps"`
}
func (drv *Driver) FormatTree(tf treefmt.Formatter) {
tf.Printf("Driver: %v (%v) version %v on %v", drv.Name, drv.Desc, &drv.Version, &drv.Kernel)
tfc := tf.NewChild()
capNames := make([]string, 0, len(drv.Caps))
for c := range drv.Caps {
capNames = append(capNames, c)
}
sort.Strings(capNames)
clientCapNames := make([]string, 0, len(drv.ClientCaps))
for c := range drv.ClientCaps {
clientCapNames = append(clientCapNames, c)
}
sort.Strings(clientCapNames)
for _, c := range capNames {
v := drv.Caps[c]
if v != nil {
if c == "PRIME" {
tfc.Printf("DRM_CAP_PRIME supported")
tfcc := tfc.NewChild()
tfcc.Printf("DRM_CAP_PRIME_IMPORT = %v", *v&drm.CapPrimeImport != 0)
tfcc.Printf("DRM_CAP_PRIME_EXPORT = %v", *v&drm.CapPrimeExport != 0)
} else {
tfc.Printf("DRM_CAP_%v = %v", c, *v)
}
} else {
tfc.Printf("DRM_CAP_%v unsupported", c)
}
}
for _, c := range clientCapNames {
if drv.ClientCaps[c] {
tfc.Printf("DRM_CLIENT_CAP_%v supported", c)
} else {
tfc.Printf("DRM_CLIENT_CAP_%v unsupported", c)
}
}
}
type DevicePCI struct {
Vendor uint32 `json:"vendor"`
Device uint32 `json:"device"`
SubVendor uint32 `json:"subsystem_vendor"`
SubDevice uint32 `json:"subsystem_device"`
}
type DevicePlatform struct {
Compatible []string `json:"compatible"`
}
type DeviceUSB struct {
Vendor uint16 `json:"vendor"`
Product uint16 `json:"product"`
}
type Device struct {
BusType drm.BusType `json:"bus_type"`
DeviceData interface{} `json:"device_data,omitempty"`
}
func (dev *Device) UnmarshalJSON(b []byte) error {
type device Device
var deviceData json.RawMessage
rawDev := device{DeviceData: &deviceData}
if err := json.Unmarshal(b, &rawDev); err != nil {
return err
}
switch rawDev.BusType {
case drm.BusPCI:
var devPCI DevicePCI
if err := json.Unmarshal(deviceData, &devPCI); err != nil {
return err
}
rawDev.DeviceData = &devPCI
case drm.BusPlatform:
var devPlatform DevicePlatform
if err := json.Unmarshal(deviceData, &devPlatform); err != nil {
return err
}
rawDev.DeviceData = &devPlatform
case drm.BusUSB:
var devUSB DeviceUSB
if err := json.Unmarshal(deviceData, &devUSB); err != nil {
return err
}
rawDev.DeviceData = &devUSB
default:
rawDev.DeviceData = nil
}
*dev = Device(rawDev)
return nil
}
func (dev *Device) BusID() string {
switch dev := dev.DeviceData.(type) {
case *DevicePCI:
return fmt.Sprintf("%04X:%04X", dev.Vendor, dev.Device)
case *DevicePlatform:
return strings.Join(dev.Compatible, "+")
case *DeviceUSB:
return fmt.Sprintf("%04X:%04X", dev.Vendor, dev.Product)
default:
return ""
}
}
func (dev *Device) FormatTree(tf treefmt.Formatter) {
tf.Printf("Device: %v %v", dev.BusType, dev.BusID())
}
type Mode struct {
Clock uint32 `json:"clock"`
HDisplay uint16 `json:"hdisplay"`
HSyncStart uint16 `json:"hsync_start"`
HSyncEnd uint16 `json:"hsync_end"`
HTotal uint16 `json:"htotal"`
HSkew uint16 `json:"hskew"`
VDisplay uint16 `json:"vdisplay"`
VSyncStart uint16 `json:"vsync_total"`
VSyncEnd uint16 `json:"vsync_end"`
VTotal uint16 `json:"vtotal"`
VScan uint16 `json:"vscan"`
VRefresh uint32 `json:"vrefresh"`
Flags uint32 `json:"flags"`
Type uint32 `json:"type"`
Name string `json:"name"`
}
func (mode *Mode) String() string {
// TODO: refresh and flags
return mode.Name
}
type prettyUint64 uint64
func (u prettyUint64) String() string {
switch uint64(u) {
case math.MaxUint64:
return "UINT64_MAX"
case math.MaxUint32:
return "UINT32_MAX"
case math.MaxInt64:
return "INT64_MAX"
case math.MaxInt32:
return "INT32_MAX"
default:
return fmt.Sprintf("%v", uint64(u))
}
}
type prettyInt64 int64
func (i prettyInt64) String() string {
if i > 0 {
return prettyUint64(i).String()
}
switch int64(i) {
case math.MinInt64:
return "INT64_MIN"
case math.MinInt32:
return "INT32_MIN"
default:
return fmt.Sprintf("%v", int64(i))
}
}
type PlaneInFormatsModifier struct {
Modifier drm.Modifier `json:"modifier"`
Formats []drm.Format `json:"formats"`
}
type PropertySpecEnumEntry struct {
Name string `json:"name"`
Value uint64 `json:"value"`
}
func (spec *PropertySpecEnumEntry) String() string {
return fmt.Sprintf("%q (0x%X)", spec.Name, spec.Value)
}
type PropertySpecEnum []PropertySpecEnumEntry
func (spec PropertySpecEnum) String() string {
l := make([]string, len(spec))
for i, e := range spec {
l[i] = fmt.Sprintf("%q", e.Name)
}
return "{" + strings.Join(l, ", ") + "}"
}
type PropertySpecRange struct {
Min uint64 `json:"min"`
Max uint64 `json:"max`
}
func (spec *PropertySpecRange) String() string {
return fmt.Sprintf("[%v, %v]", prettyUint64(spec.Min), prettyUint64(spec.Max))
}
type PropertySpecSignedRange struct {
Min int64 `json:"min"`
Max int64 `json:"max`
}
func (spec *PropertySpecSignedRange) String() string {
return fmt.Sprintf("[%v, %v]", prettyInt64(spec.Min), prettyInt64(spec.Max))
}
type Property struct {
ID drm.PropertyID `json:"id"`
Type drm.PropertyType `json:"type"`
Immutable bool `json:"immutable"`
Atomic bool `json:"atomic"`
Spec interface{} `json:"spec"`
RawValue uint64 `json:"raw_value"`
// Value interpreted with the property type
Value interface{} `json:"value"`
// Value interpreted with the property name, optional
Data interface{} `json:"data"`
// For Data unmarshaling, populated by PropertyMap.UnmarshalJSON
name string
}
func (prop *Property) UnmarshalJSON(b []byte) error {
type property Property
var rawSpec, rawValue, rawData json.RawMessage
rawProp := property{
Spec: &rawSpec,
Value: &rawValue,
Data: &rawData,
}
if err := json.Unmarshal(b, &rawProp); err != nil {
return err
}
rawProp.Spec = nil
rawProp.Value = nil
rawProp.Data = nil
if rawSpec != nil {
var err error
switch rawProp.Type {
case drm.PropertyRange:
var spec PropertySpecRange
err = json.Unmarshal(rawSpec, &spec)
rawProp.Spec = &spec
case drm.PropertyEnum, drm.PropertyBitmask:
var spec PropertySpecEnum
err = json.Unmarshal(rawSpec, &spec)
rawProp.Spec = spec
case drm.PropertyObject:
spec := drm.ObjectType(0)
err = json.Unmarshal(rawSpec, &spec)
rawProp.Spec = spec
case drm.PropertySignedRange:
spec := PropertySpecSignedRange{}
err = json.Unmarshal(rawSpec, &spec)
rawProp.Spec = &spec
}
if err != nil {
return err
}
}
if rawValue != nil {
var err error
switch rawProp.Type {
case drm.PropertyRange, drm.PropertyEnum, drm.PropertyBitmask:
var value uint64
err = json.Unmarshal(rawValue, &value)
rawProp.Value = value
case drm.PropertyBlob:
var value []byte
err = json.Unmarshal(rawValue, &value)
rawProp.Value = value
case drm.PropertyObject:
var value drm.ObjectID
err = json.Unmarshal(rawValue, &value)
rawProp.Value = value
case drm.PropertySignedRange:
var value int64
err = json.Unmarshal(rawValue, &value)
rawProp.Value = value
}
if err != nil {
return err
}
}
if rawData != nil {
var err error
switch prop.name {
case "SRC_X", "SRC_Y", "SRC_W", "SRC_H":
var data uint64
err = json.Unmarshal(rawData, &data)
rawProp.Data = data
case "IN_FORMATS":
var data []PlaneInFormatsModifier
err = json.Unmarshal(rawData, &data)
rawProp.Data = data
case "MODE_ID":
var data Mode
err = json.Unmarshal(rawData, &data)
rawProp.Data = &data
case "WRITEBACK_PIXEL_FORMATS":
var data []drm.Format
err = json.Unmarshal(rawData, &data)
rawProp.Data = data
case "PATH":
var data string
err = json.Unmarshal(rawData, &data)
rawProp.Data = data
}
if err != nil {
return err
}
}
*prop = Property(rawProp)
return nil
}
type PropertyMap map[string]Property
func (m *PropertyMap) UnmarshalJSON(b []byte) error {
var rawMap map[string]json.RawMessage
if err := json.Unmarshal(b, &rawMap); err != nil {
return err
}
*m = make(PropertyMap)
for name, rawProp := range rawMap {
prop := Property{name: name}
if err := json.Unmarshal(rawProp, &prop); err != nil {
return err
}
(*m)[name] = prop
}
return nil
}
func (m *PropertyMap) FormatTree(tf treefmt.Formatter) {
names := make([]string, 0, len(*m))
for name := range *m {
names = append(names, name)
}
sort.Strings(names)
for _, name := range names {
prop := (*m)[name]
s := fmt.Sprintf("%q", name)
var attrs []string
if prop.Atomic {
attrs = append(attrs, "atomic")
}
if prop.Immutable {
attrs = append(attrs, "immutable")
}
if len(attrs) > 0 {
s += " (" + strings.Join(attrs, ", ") + ")"
}
s += ": " + prop.Type.String()
var specStr string
if s, ok := prop.Spec.(fmt.Stringer); ok {
specStr = s.String()
}
if specStr != "" {
s += " " + specStr
}
val := prop.Data
if val == nil {
val = prop.Value
}
switch val := val.(type) {
case []PlaneInFormatsModifier:
tf.Printf("%v", s)
tfc := tf.NewChild()
for _, mod := range val {
tfc.Printf("%v (0x%X)", mod.Modifier, mod.Modifier)
tfcc := tfc.NewChild()
for _, fmt := range mod.Formats {
tfcc.Printf("%v (0x%X)", fmt, fmt)
}
}
case []drm.Format:
tf.Printf("%v", s)
tfc := tf.NewChild()
for _, fmt := range val {
tfc.Printf("%v", fmt)
}
default:
switch prop.Type {
case drm.PropertyEnum:
u := val.(uint64)
entries := prop.Spec.(PropertySpecEnum)
val = "<invalid>"
for _, e := range entries {
if e.Value == u {
val = e.Name
break
}
}
case drm.PropertyBitmask:
u := val.(uint64)
entries := prop.Spec.(PropertySpecEnum)
var names []string
for _, e := range entries {
bit := uint64(1 << e.Value)
if u&bit != 0 {
names = append(names, e.Name)
}
}
val = "(" + strings.Join(names, " | ") + ")"
}
tf.Printf("%v = %v", s, val)
}
}
}
type Connector struct {
ID drm.ConnectorID `json:"id"`
Type drm.ConnectorType `json:"type"`
Status drm.ConnectorStatus `json:"status"`
PhyWidth uint32 `json:"phy_width"`
PhyHeight uint32 `json:"phy_height"`
Subpixel drm.Subpixel `json:"subpixel"`
Encoders []drm.EncoderID `json:"encoders"`
Modes []Mode `json:"modes"`
Properties PropertyMap `json:"properties"`
}
func encoderIDsString(encs []drm.EncoderID) string {
s := "{"
for i, id := range encs {
if i != 0 {
s += ", "
}
s += fmt.Sprintf("%v", id)
}
s += "}"
return s
}
func (conn *Connector) FormatTree(tf treefmt.Formatter) {
tf.Printf("Object ID: %v", conn.ID)
tf.Printf("Type: %v", conn.Type)
tf.Printf("Status: %v", conn.Status)
if conn.Status == drm.ConnectorStatusConnected {
tf.Printf("Physical size: %vx%v mm", conn.PhyWidth, conn.PhyHeight)
tf.Printf("Subpixel: %v", conn.Subpixel)
}
tf.Printf("Encoders: %v", encoderIDsString(conn.Encoders))
if len(conn.Modes) > 0 {
tf.Printf("Modes")
tfc := tf.NewChild()
for _, m := range conn.Modes {
tfc.Printf("%v", &m)
}
}
if len(conn.Properties) > 0 {
tf.Printf("Properties")
conn.Properties.FormatTree(tf.NewChild())
}
}
type Encoder struct {
ID drm.EncoderID `json:"id"`
Type drm.EncoderType `json:"type"`
CRTC drm.CRTCID `json:"crtc"`
PossibleCRTCs uint32 `json:"possible_crtcs"`
PossibleClones uint32 `json:"possible_clones"`
}
func bitfieldString(v uint32) string {
s := "{"
first := true
for i := 0; i < 32; i++ {
if v&(1<<uint(i)) != 0 {
if !first {
s += ", "
}
s += fmt.Sprintf("%v", i)
first = false
}
}
s += "}"
return s
}
func (enc *Encoder) FormatTree(tf treefmt.Formatter) {
tf.Printf("Object ID: %v", enc.ID)
tf.Printf("Type: %v", enc.Type)
tf.Printf("CRTCs: %v", bitfieldString(enc.PossibleCRTCs))
tf.Printf("Clones: %v", bitfieldString(enc.PossibleClones))
}
type CRTC struct {
ID drm.CRTCID `json:"id"`
FB drm.FBID `json:"fb"`
X uint32 `json:"x"`
Y uint32 `json:"y"`
GammaSize uint32 `json:"gamma_size"`
Mode *Mode `json:"mode"`
Properties PropertyMap `json:"properties"`
}
func (crtc *CRTC) FormatTree(tf treefmt.Formatter) {
tf.Printf("Object ID: %v", crtc.ID)
tf.Printf("FB: %v", crtc.FB)
tf.Printf("Position: %v, %v", crtc.X, crtc.Y)
tf.Printf("Gamma size: %v", crtc.GammaSize)
if crtc.Mode != nil {
tf.Printf("Mode: %v", crtc.Mode)
}
if len(crtc.Properties) > 0 {
tf.Printf("Properties")
crtc.Properties.FormatTree(tf.NewChild())
}
}
type Plane struct {
ID drm.PlaneID `json:"id"`
CRTC drm.CRTCID `json:"crtc"`
FB drm.FBID `json:"fb"`
PossibleCRTCs uint32 `json:"possible_crtcs"`
GammaSize uint32 `json:"gamma_size"`
Formats []drm.Format `json:"formats"`
Properties PropertyMap `json:"properties"`
}
func (plane *Plane) Type() drm.PlaneType {
if t, ok := plane.Properties["type"]; ok {
return drm.PlaneType(t.RawValue)
}
return 0
}
func (plane *Plane) InFormats() []PlaneInFormatsModifier {
if inFormats, ok := plane.Properties["IN_FORMATS"]; ok {
// Some drivers expose an empty IN_FORMATS property
fmts := inFormats.Data.([]PlaneInFormatsModifier)
if len(fmts) > 0 {
return fmts
}
}
return []PlaneInFormatsModifier{
{
Modifier: drm.ModifierLinear,
Formats: plane.Formats,
},
}
}
func (plane *Plane) FormatTree(tf treefmt.Formatter) {
tf.Printf("Object ID: %v", plane.ID)
tf.Printf("CRTC: %v", plane.CRTC)
tf.Printf("FB: %v", plane.FB)
tf.Printf("CRTCs: %v", bitfieldString(plane.PossibleCRTCs))
tf.Printf("Gamma size: %v", plane.GammaSize)
if len(plane.Formats) > 0 {
tf.Printf("Formats")
tfc := tf.NewChild()
for _, fmt := range plane.Formats {
tfc.Printf("%v", fmt)
}
}
if len(plane.Properties) > 0 {
tf.Printf("Properties")
plane.Properties.FormatTree(tf.NewChild())
}
}
type Node struct {
Driver *Driver `json:"driver"`
Device *Device `json:"device"`
Connectors []Connector `json:"connectors"`
Encoders []Encoder `json:"encoders"`
CRTCs []CRTC `json:"crtcs"`
Planes []Plane `json:"planes"`
}
func (n *Node) FormatTree(tf treefmt.Formatter) {
n.Device.FormatTree(tf)
n.Driver.FormatTree(tf)
tf.Printf("Connectors")
for i, conn := range n.Connectors {
tfc := tf.NewChild()
tfc.Printf("Connector %v", i)
conn.FormatTree(tfc.NewChild())
}
tf.Printf("Encoders")
for i, enc := range n.Encoders {
tfc := tf.NewChild()
tfc.Printf("Encoder %v", i)
enc.FormatTree(tfc.NewChild())
}
tf.Printf("CRTCs")
for i, crtc := range n.CRTCs {
tfc := tf.NewChild()
tfc.Printf("CRTC %v", i)
crtc.FormatTree(tfc.NewChild())
}
tf.Printf("Planes")
for i, plane := range n.Planes {
tfc := tf.NewChild()
tfc.Printf("Plane %v", i)
plane.FormatTree(tfc.NewChild())
}
}
type NodeMap map[string]*Node
func (m NodeMap) FormatTree(tf treefmt.Formatter) {
for path, n := range m {
tf.Printf("Node: %s", path)
n.FormatTree(tf.NewChild())
}
}