split into separate files + work on device API.

This commit is contained in:
Jackson Netherwood-Imig
2026-02-27 21:53:01 -08:00
parent 25c7cc7395
commit d46ec8aec2
6 changed files with 690 additions and 694 deletions
+4
View File
@@ -25,4 +25,8 @@ pub fn build(b: *std.Build) void {
const run_tests = b.addRunArtifact(test_exe); const run_tests = b.addRunArtifact(test_exe);
const test_step = b.step("test", "Run tests."); const test_step = b.step("test", "Run tests.");
test_step.dependOn(&run_tests.step); test_step.dependOn(&run_tests.step);
const check_obj = b.addObject(.{ .name = "check", .root_module = drm });
const check_step = b.step("check", "ZLS check");
check_step.dependOn(&check_obj.step);
} }
+2 -2
View File
@@ -7,7 +7,7 @@ pub fn main(init: std.process.Init) !void {
const card = try drm.Card.openAuto(io); const card = try drm.Card.openAuto(io);
defer card.close(io); defer card.close(io);
const res = try card.getModesettingResourcesAlloc(gpa); const res = try card.getModesettingResources(gpa);
defer res.deinit(gpa); defer res.deinit(gpa);
const connector = try chooseConnector(card, gpa, res.connectors); const connector = try chooseConnector(card, gpa, res.connectors);
@@ -30,7 +30,7 @@ fn chooseConnector(
connectors: []const u32, connectors: []const u32,
) !drm.Connector { ) !drm.Connector {
for (connectors) |id| { for (connectors) |id| {
const connector = try card.getConnectorAlloc(gpa, id); const connector = try card.getConnector(gpa, id);
if (connector.connection == .connected) return connector; if (connector.connection == .connected) return connector;
connector.deinit(gpa); connector.deinit(gpa);
} }
+422
View File
@@ -0,0 +1,422 @@
const std = @import("std");
const drm = @import("drm.zig");
const fmt = @import("format.zig");
const sys = @import("sys.zig");
const log = std.log.scoped(.drm);
const Card = @This();
handle: std.Io.File,
pub const OpenError = std.Io.File.OpenError || std.Io.Dir.OpenError;
pub fn open(io: std.Io, path: []const u8) OpenError!Card {
if (std.fs.path.isAbsolute(path))
return Card{ .handle = try std.Io.Dir.openFileAbsolute(io, path, .{ .mode = .read_write }) };
const dir = try std.Io.Dir.openDirAbsolute(io, "/dev/dri", .{});
defer dir.close(io);
return Card{ .handle = try dir.openFile(io, path, .{ .mode = .read_write }) };
}
pub const OpenAutoError = OpenError || std.Io.Dir.Iterator.Error || error{NoDevicesFound};
pub fn openAuto(io: std.Io) OpenAutoError!Card {
const dir = try std.Io.Dir.openDirAbsolute(io, "/dev/dri", .{ .iterate = true });
defer dir.close(io);
var it = dir.iterateAssumeFirstIteration();
while (try it.next(io)) |entry| if (entry.kind == .character_device)
return Card{ .handle = try dir.openFile(io, entry.name, .{ .mode = .read_write }) };
return error.NoDevicesFound;
}
pub fn close(self: Card, io: std.Io) void {
self.handle.close(io);
}
pub fn handleEvents(
self: Card,
io: std.Io,
callback: fn (Card, drm.Event, anytype) anyerror!void,
args: anytype,
) anyerror!void {
var buf: [4096]u8 = undefined;
const read = try self.handle.readStreaming(io, &.{&buf});
if (read == 0) return error.EndOfStream;
var offset: usize = 0;
while (offset < read) {
if (read - offset < @sizeOf(sys.Event)) return error.IncompleteEvent;
const ev: *align(1) const sys.Event = std.mem.bytesAsValue(
sys.Event,
buf[offset..][0..@sizeOf(sys.Event)],
);
offset += ev.length;
const event = drm.Event.parse(ev);
try callback(self, event, args);
}
}
pub fn setClientCapability(self: Card, capability: sys.ClientCapability, value: u64) sys.IoctlError!void {
var set_cap = sys.SetClientCap{
.capability = capability,
.value = value,
};
try sys.ioctl(self.handle.handle, .set_client_cap, &set_cap);
}
pub fn getCapability(self: Card, capability: sys.Capability) sys.IoctlError!u64 {
var get_cap = sys.GetCap{
.capability = capability,
.value = 0,
};
try sys.ioctl(self.handle.handle, .get_cap, &get_cap);
return get_cap.value;
}
pub fn createModesettingPropertyBlob(self: Card, data: []const u8) sys.IoctlError!u32 {
var create = std.mem.zeroInit(sys.mode.CreateBlob, .{
.length = @as(u32, @intCast(data.len)),
.data = @intFromPtr(data.ptr),
});
try sys.ioctl(self.handle.handle, .mode_createpropblob, &create);
return create.blob_id;
}
// BEGIN MODESETTING API
pub const GetResourcesError = sys.IoctlError || error{OutOfMemory};
pub fn getModesettingResources(
self: Card,
gpa: std.mem.Allocator,
) GetResourcesError!drm.ModesettingResources {
while (true) {
var res = std.mem.zeroes(sys.mode.CardRes);
try sys.ioctl(self.handle.handle, .mode_getresources, &res);
var ret = drm.ModesettingResources{
.min_width = res.min_width,
.max_width = res.max_width,
.min_height = res.min_height,
.max_height = res.max_height,
.fbs = &.{},
.crtcs = &.{},
.connectors = &.{},
.encoders = &.{},
};
ret.fbs = try gpa.alloc(u32, res.count_fbs);
errdefer gpa.free(ret.fbs);
res.fb_id_ptr = @intFromPtr(ret.fbs.ptr);
ret.crtcs = try gpa.alloc(u32, res.count_crtcs);
errdefer gpa.free(ret.crtcs);
res.crtc_id_ptr = @intFromPtr(ret.crtcs.ptr);
ret.connectors = try gpa.alloc(u32, res.count_connectors);
errdefer gpa.free(ret.connectors);
res.connector_id_ptr = @intFromPtr(ret.connectors.ptr);
ret.encoders = try gpa.alloc(u32, res.count_encoders);
errdefer gpa.free(ret.encoders);
res.encoder_id_ptr = @intFromPtr(ret.encoders.ptr);
const cached = res;
try sys.ioctl(self.handle.handle, .mode_getresources, &res);
if (cached.count_fbs < res.count_fbs or
cached.count_crtcs < res.count_crtcs or
cached.count_connectors < res.count_connectors or
cached.count_encoders < res.count_encoders)
{
gpa.free(ret.encoders);
gpa.free(ret.connectors);
gpa.free(ret.crtcs);
gpa.free(ret.fbs);
continue;
}
return ret;
}
}
pub const GetConnectorError = sys.IoctlError || error{OutOfMemory};
pub fn getConnector(self: Card, gpa: std.mem.Allocator, id: u32) GetConnectorError!drm.Connector {
while (true) {
var get_conn = std.mem.zeroInit(sys.mode.GetConnector, .{ .connector_id = id });
try sys.ioctl(self.handle.handle, .mode_getconnector, &get_conn);
const cached = get_conn;
const encoders = try gpa.alloc(u32, get_conn.count_encoders);
errdefer gpa.free(encoders);
const modes = try gpa.alloc(sys.mode.ModeInfo, get_conn.count_modes);
errdefer gpa.free(modes);
const props = try gpa.alloc(u32, get_conn.count_props);
errdefer gpa.free(props);
const prop_values = try gpa.alloc(u64, get_conn.count_props);
errdefer gpa.free(prop_values);
get_conn.encoders_ptr = @intFromPtr(encoders.ptr);
get_conn.modes_ptr = @intFromPtr(modes.ptr);
get_conn.props_ptr = @intFromPtr(props.ptr);
get_conn.prop_values_ptr = @intFromPtr(prop_values.ptr);
try sys.ioctl(self.handle.handle, .mode_getconnector, &get_conn);
if (cached.count_encoders < get_conn.count_encoders or
cached.count_modes < get_conn.count_modes or
cached.count_props < get_conn.count_props)
{
gpa.free(prop_values);
gpa.free(props);
gpa.free(modes);
gpa.free(encoders);
continue;
}
return drm.Connector{
.id = id,
.encoder_id = get_conn.encoder_id,
.connection = @enumFromInt(get_conn.connection),
// Unsure why libdrm does this conversion,
// but we'll follow suite for compatability.
.subpixel = @enumFromInt(get_conn.subpixel + 1),
.encoders = encoders[0..get_conn.count_encoders],
.modes = modes[0..get_conn.count_modes],
.props = props[0..get_conn.count_props],
.prop_values = prop_values[0..get_conn.count_props],
.mm_width = get_conn.mm_width,
.mm_height = get_conn.mm_height,
.type = get_conn.connector_type,
.type_id = get_conn.connector_type_id,
};
}
}
pub fn getEncoder(self: Card, id: u32) sys.IoctlError!drm.Encoder {
var get_encoder = std.mem.zeroInit(sys.mode.GetEncoder, .{ .encoder_id = id });
try sys.ioctl(self.handle.handle, .mode_getencoder, &get_encoder);
return drm.Encoder{
.id = id,
.type = get_encoder.encoder_type,
.crtc_id = get_encoder.crtc_id,
.possible_crtcs = get_encoder.possible_crtcs,
.possible_clones = get_encoder.possible_clones,
};
}
pub fn getCrtc(self: Card, id: u32) sys.IoctlError!drm.Crtc {
var crtc = std.mem.zeroInit(sys.mode.Crtc, .{ .crtc_id = id });
try sys.ioctl(self.handle.handle, .mode_getcrtc, &crtc);
return drm.Crtc{
.id = id,
.fb_id = crtc.fb_id,
.x = crtc.x,
.y = crtc.y,
.gamma_size = crtc.gamma_size,
.mode = if (crtc.mode_valid != 0) crtc.mode else null,
};
}
pub fn setCrtc(
self: Card,
id: u32,
fb_id: u32,
x: u32,
y: u32,
connectors: []u32,
mode: ?sys.mode.ModeInfo,
) sys.IoctlError!void {
var crtc = std.mem.zeroInit(sys.mode.Crtc, .{
.crtc_id = id,
.fb_id = fb_id,
.x = x,
.y = y,
.count_connectors = @as(u32, @intCast(connectors.len)),
.set_connectors_ptr = @intFromPtr(connectors.ptr),
});
if (mode) |m| {
crtc.mode_valid = 1;
crtc.mode = m;
}
try sys.ioctl(self.handle.handle, .mode_setcrtc, &crtc);
}
pub fn createDumbBuffer(self: Card, width: u32, height: u32, bpp: u32) sys.IoctlError!drm.DumbBuffer {
var create = std.mem.zeroInit(sys.mode.CreateDumb, .{
.width = width,
.height = height,
.bpp = bpp,
});
try sys.ioctl(self.handle.handle, .mode_create_dumb, &create);
return drm.DumbBuffer{
.handle = create.handle,
.width = create.width,
.height = create.height,
.stride = create.pitch,
.size = @intCast(create.size),
};
}
pub fn destroyDumbBuffer(self: Card, handle: u32) sys.IoctlError!void {
var destroy = sys.mode.DestroyDumb{ .handle = handle };
try sys.ioctl(self.handle.handle, .mode_destroy_dumb, &destroy);
}
pub fn mapDumbBuffer(self: Card, dumb: drm.DumbBuffer, offset: usize) sys.IoctlError!usize {
var map = sys.mode.MapDumb{ .handle = dumb.handle, .pad = 0, .offset = offset };
try sys.ioctl(self.handle.handle, .mode_map_dumb, &map);
return @intCast(map.offset);
}
pub fn addFb2(
self: Card,
width: u32,
height: u32,
format: fmt.Format,
handles: [4]u32,
pitches: [4]u32,
offsets: [4]u32,
flags: u32, // FIXME: add flags type
) sys.IoctlError!u32 {
var cmd = std.mem.zeroInit(sys.mode.FbCmd2, .{
.width = width,
.height = height,
.pixel_format = @intFromEnum(format),
.handles = handles,
.pitches = pitches,
.offsets = offsets,
.flags = flags,
});
try sys.ioctl(self.handle.handle, .mode_addfb2, &cmd);
return cmd.fb_id;
}
pub fn removeFb(self: Card, fb: u32) sys.IoctlError!void {
try sys.ioctl(self.handle.handle, .mode_rmfb, &fb);
}
pub fn pageFlip(
self: Card,
crtc_id: u32,
fb_id: u32,
flags: sys.mode.PageFlipFlags,
data: ?*anyopaque,
) sys.IoctlError!void {
var flip = sys.mode.CrtcPageFlip{
.crtc_id = crtc_id,
.fb_id = fb_id,
.flags = flags,
.reserved = 0,
.user_data = @intFromPtr(data),
};
try sys.ioctl(self.handle.handle, .mode_page_flip, &flip);
}
pub const GetPlaneResourcesError = sys.IoctlError || error{OutOfMemory};
pub fn getPlaneResources(self: Card, gpa: std.mem.Allocator) GetPlaneResourcesError!drm.PlaneResources {
while (true) {
var res = std.mem.zeroes(sys.mode.GetPlaneRes);
try sys.ioctl(self.handle.handle, .mode_getplaneresources, &res);
const cached = res;
const planes = try gpa.alloc(u32, res.count_planes);
errdefer gpa.free(planes);
res.plane_id_ptr = @intFromPtr(planes.ptr);
try sys.ioctl(self.handle.handle, .mode_getplaneresources, &res);
if (res.count_planes > cached.count_planes) {
gpa.free(planes);
continue;
}
return drm.PlaneResources{ .planes = planes };
}
}
pub const GetPlaneError = sys.IoctlError || error{OutOfMemory};
pub fn getPlane(self: Card, gpa: std.mem.Allocator, id: u32) GetPlaneError!drm.Plane {
while (true) {
var get = std.mem.zeroInit(sys.mode.GetPlane, .{ .plane_id = id });
try sys.ioctl(self.handle.handle, .mode_getplane, &get);
const cached = get;
const formats = try gpa.alloc(u32, get.count_format_types);
errdefer gpa.free(formats);
get.format_type_ptr = @intFromPtr(formats.ptr);
try sys.ioctl(self.handle.handle, .mode_getplane, &get);
if (get.count_format_types > cached.count_format_types) {
gpa.free(formats);
continue;
}
return drm.Plane{
.id = id,
.formats = formats,
.crtc_id = get.crtc_id,
.fb_id = get.fb_id,
.crtc_x = 0,
.crtc_y = 0,
.x = 0,
.y = 0,
.possible_crtcs = get.possible_crtcs,
.gamma_size = get.gamma_size,
};
}
}
pub const GetObjectPropertiesError = sys.IoctlError || error{OutOfMemory};
pub fn getObjectProperties(
self: Card,
gpa: std.mem.Allocator,
object_id: u32,
object_type: sys.mode.ObjType,
) GetObjectPropertiesError!drm.ObjectProperties {
while (true) {
var get = std.mem.zeroInit(sys.mode.ObjGetProperties, .{
.obj_id = object_id,
.obj_type = object_type,
});
try sys.ioctl(self.handle.handle, .mode_obj_getproperties, &get);
const cached = get;
const keys = try gpa.alloc(u32, get.count_props);
errdefer gpa.free(keys);
get.props_ptr = keys.ptr;
const values = try gpa.alloc(u64, get.count_props);
errdefer gpa.free(values);
get.prop_values_ptr = values.ptr;
try sys.ioctl(self.handle.handle, .mode_obj_getproperties, &get);
if (get.count_props > cached.count_props) {
gpa.free(keys);
gpa.free(values);
continue;
}
return drm.ObjectProperties{
.keys = keys,
.values = values,
};
}
}
+200
View File
@@ -0,0 +1,200 @@
const std = @import("std");
const drm = @import("drm.zig");
const sys = @import("sys.zig");
const log = std.log.scoped(.drm);
const node_max = 3;
const Device = @This();
nodes: [node_max][]const u8,
available_nodes: packed struct(u3) {
primary: bool = false,
/// Deprecated
control: bool = false,
render: bool = false,
},
bus_info: union(BusType) {
pci: PciBusInfo,
usb: UsbBusInfo,
platform: PlatformBusInfo,
host1x: Host1xBusInfo,
faux: FauxBusInfo,
virtio: void,
},
device_info: union(BusType) {
pci: PciDeviceInfo,
usb: UsbDeviceInfo,
platform: PlatformDeviceInfo,
host1x: Host1xDeviceInfo,
faux: void,
virtio: void,
},
pub const Flags = packed struct {
get_pci_revision: bool = false,
};
pub fn getFromDevId(io: std.Io, devid: std.posix.dev_t, flags: Flags) !Device {
var local_devices: [drm.max_nodes]Device = undefined;
const major = drm.devMajor(devid);
const minor = drm.devMinor(devid);
if (!drm.nodeIsDrm(io, major, minor))
return error.NotDrmDevice;
const subsystem_type = try drm.parseSubsystemType(io, major, minor);
const sysdir = try std.Io.Dir.openDirAbsolute(io, drm.dir_name, .{ .iterate = true });
defer sysdir.close(io);
var it = sysdir.iterate();
const i: usize = 0;
while (try it.next(io)) |entry| {
const dev = processDevice(entry.name, subsystem_type, true, flags) catch continue;
if (i >= drm.max_nodes) {
log.err(
"More than {d} drm nodes detected. This is a bug. Extra nodes will be skipped.",
.{drm.max_nodes},
);
break;
}
local_devices[i] = dev;
i += 1;
}
return for (local_devices[0..i]) |dev| {
if (hasRdev(dev, devid)) break dev;
} else error.NoDeviceFound;
}
pub const BusType = enum(c_int) {
pci = 0,
usb = 1,
platform = 2,
host1x = 3,
faux = 4,
/// According to libdrm, "Little white lie to avoid major rework of the existing code"
virtio = 0x10,
};
pub const PciBusInfo = struct {
domain: u16,
bus: u8,
dev: u8,
func: u8,
};
pub const PciDeviceInfo = struct {
vendor_id: u16,
device_id: u16,
subvendor_id: u16,
subdevice_id: u16,
revision_id: u8,
};
pub const UsbBusInfo = struct {
bus: u8,
dev: u8,
};
pub const UsbDeviceInfo = struct {
vendor: u16,
product: u16,
};
pub const platform_device_name_len = 512;
pub const PlatformBusInfo = struct {
fullname: [platform_device_name_len]u8,
};
pub const PlatformDeviceInfo = struct {
compatible: [][]const u8,
};
pub const host1x_device_name_len = 512;
pub const Host1xBusInfo = struct {
fullname: [host1x_device_name_len]u8,
};
pub const Host1xDeviceInfo = struct {
compatible: [][]const u8,
};
pub const faux_device_name_len = 512;
pub const FauxBusInfo = struct {
name: [faux_device_name_len]u8,
};
fn processDevice(
io: std.Io,
name: []const u8,
required_subsystem_type: sys.BusType,
fetch_devinfo: bool,
flags: Flags,
) !Device {
const max_node_length = std.mem.alignForward(usize, drm.max_node_name, @sizeOf(usize));
const node_type = try getNodeType(name);
var path_buf: [std.os.linux.PATH_MAX]u8 = undefined;
const node = std.fmt.bufPrint(&path_buf, "{s}/{s}", .{ drm.dir_name, name }) catch unreachable;
if (node.len + 1 > max_node_length)
return error.NodePathTooLong;
const stat = try statx(node);
if (!drm.nodeIsDrm(io, stat.rdev_major, stat.rdev_minor) || !std.os.linux.S.ISCHR(stat.mode))
return error.InvalidDevice;
const subsystem_type = try drm.parseSubsystemType(io, stat.rdev_major, stat.rdev_minor);
if (subsystem_type != required_subsystem_type)
return error.IncorrectSubsystemType;
_ = fetch_devinfo;
_ = flags;
_ = node_type;
return switch (subsystem_type) {
.pci, .virtio => error.PciUnimplemented,
.usb => error.UsbInimplemented,
.platform => error.PlatformUnimplemented,
.host1x => error.Host1xUnimplemented,
.faux => error.FauxUnimplemented,
};
}
fn hasRdev(dev: Device, rdev: std.posix.dev_t) bool {
const node_fields = @typeInfo(@FieldType(Device, "available_nodes")).@"struct".fields;
inline for (node_fields, dev.nodes) |field, node| {
if (@field(dev.available_nodes, field.name)) {
const stat = statx(node) catch continue;
if (drm.makeDev(stat.rdev_major, stat.rdev_minor) == rdev) return true;
}
}
return false;
}
fn getNodeType(name: []const u8) !drm.NodeType {
if (std.mem.eql(u8, name, drm.primary_minor_name)) return .primary;
if (std.mem.eql(u8, name, drm.control_minor_name)) return .control;
if (std.mem.eql(u8, name, drm.render_minor_name)) return .render;
return error.InvalidName;
}
fn statx(path: []const u8) !std.os.linux.Statx {
const posix_path = std.posix.toPosixPath(path);
var stat_buf: std.os.linux.Statx = undefined;
const rc = std.os.linux.statx(std.os.linux.AT.FDCWD, &posix_path, 0, .{}, &stat_buf);
return switch (std.posix.errno(rc)) {
.SUCCESS => stat_buf,
else => |err| std.posix.unexpectedErrno(err),
};
}
+62 -681
View File
@@ -1,30 +1,69 @@
const std = @import("std"); const std = @import("std");
const fmt = @import("format.zig"); const dev_t = std.posix.dev_t;
const log = std.log.scoped(.drm);
pub const sys = @import("sys.zig"); pub const Card = @import("Card.zig");
pub const Device = @import("Device.zig");
const fmt = @import("format.zig");
pub const Format = fmt.Format; pub const Format = fmt.Format;
pub const FormatModifiers = fmt.FormatModifiers; pub const FormatModifiers = fmt.FormatModifiers;
pub const sys = @import("sys.zig");
pub const ModeInfo = sys.mode.ModeInfo; pub const ModeInfo = sys.mode.ModeInfo;
const log = std.log.scoped(.drm);
pub const dir_name = "/dev/dri";
pub const primary_minor_name = "card";
pub const control_minor_name = "controlD";
pub const render_minor_name = "renderD";
pub const max_nodes = 256;
pub const max_node_name = dir_name.len + @max(
primary_minor_name.len,
control_minor_name.len,
render_minor_name.len,
) + 3;
pub fn devMajor(dev: dev_t) u32 {
return @intCast(((dev & @as(dev_t, 0x00000000000fff00)) >> 8) |
((dev & @as(dev_t, 0xfffff00000000000)) >> 32));
}
pub fn devMinor(dev: dev_t) u32 {
return @intCast(((dev & @as(dev_t, 0x00000000000000ff)) >> 0) |
((dev & @as(dev_t, 0x00000ffffff00000)) >> 12));
}
pub fn makeDev(major: u32, minor: u32) dev_t {
return (@as(dev_t, major & 0x00000fff) << 8) |
(@as(dev_t, major & 0xfffff000) << 32) |
(@as(dev_t, minor & 0x000000ff) << 0) |
(@as(dev_t, minor & 0xffffff00) << 12);
}
pub fn nodeIsDrm(io: std.Io, major: u32, minor: u32) bool { pub fn nodeIsDrm(io: std.Io, major: u32, minor: u32) bool {
const drm_node_path_format = "/sys/dev/char/{d}:{d}/device/drm"; const drm_node_path_format = "/sys/dev/char/{d}:{d}/device/drm";
var path_buf: [64]u8 = undefined; var path_buf: [64]u8 = undefined;
const path = std.fmt.bufPrint(&path_buf, drm_node_path_format, .{ major, minor }) catch unreachable; const path = std.fmt.bufPrint(&path_buf, drm_node_path_format, .{ major, minor }) catch unreachable;
_ = std.Io.Dir.cwd().statFile(io, path, .{}) catch return false; return if (std.Io.Dir.cwd().statFile(io, path, .{})) |_| true else |_| false;
return true;
} }
pub fn parseSubsystemType(io: std.Io, major: u32, minor: u32) !sys.BusType { pub fn parseSubsystemType(io: std.Io, major: u32, minor: u32) !Device.BusType {
var path_buf: [std.os.linux.PATH_MAX]u8 = undefined; var path_buf: [std.os.linux.PATH_MAX]u8 = undefined;
var path = std.ArrayList(u8).initBuffer(&path_buf); var path = std.ArrayList(u8).initBuffer(&path_buf);
path.printAssumeCapacity("/sys/dev/char/{d}:{d}/device", .{major, minor}); path.printAssumeCapacity("/sys/dev/char/{d}:{d}/device", .{ major, minor });
path.appendSliceAssumeCapacity("/subsystem");
const bus_types = [_]struct{name: []const u8, bus_type: sys.BusType}{ var subsystem_type = try getSubsystemType(io, path.items);
if (subsystem_type == .virtio) {
path.appendSliceAssumeCapacity("/..");
subsystem_type = getSubsystemType(io, path.items) catch .virtio;
}
return subsystem_type;
}
fn getSubsystemType(io: std.Io, device_path: []const u8) !Device.BusType {
const bus_types = [_]struct { name: []const u8, bus_type: Device.BusType }{
.{ .name = "/pci", .bus_type = .pci }, .{ .name = "/pci", .bus_type = .pci },
.{ .name = "/usb", .bus_type = .usb }, .{ .name = "/usb", .bus_type = .usb },
.{ .name = "/platform", .bus_type = .platform }, .{ .name = "/platform", .bus_type = .platform },
@@ -34,686 +73,28 @@ pub fn parseSubsystemType(io: std.Io, major: u32, minor: u32) !sys.BusType {
.{ .name = "/faux", .bus_type = .faux }, .{ .name = "/faux", .bus_type = .faux },
}; };
var path_buf: [std.os.linux.PATH_MAX]u8 = undefined;
const path = std.fmt.bufPrint(&path_buf, "{s}/subsystem", .{device_path}) catch unreachable;
var link_buf: [std.os.linux.PATH_MAX]u8 = undefined; var link_buf: [std.os.linux.PATH_MAX]u8 = undefined;
const link_len = try std.Io.Dir.cwd().readLink(io, path.items, &link_buf); const link_len = try std.Io.Dir.cwd().readLink(io, path, &link_buf);
const link = link_buf[0..link_len]; const link = link_buf[0..link_len];
const last = std.mem.findScalarLast(u8, link, '/') orelse unreachable; const last = std.mem.findScalarLast(u8, link, '/') orelse unreachable;
const name = link[last..]; const name = link[last..];
if (name.len == 0) return error.InvalidDevice; if (name.len == 0) return error.InvalidDevice;
const bus_type = for (bus_types) |bus_type| { return for (bus_types) |bus_type| {
if (std.mem.eql(u8, bus_type.name, name)) break bus_type; if (std.mem.eql(u8, bus_type.name, name)) break bus_type.bus_type;
} else return error.InvalidDevice; } else error.InvalidDevice;
/* Try to get the parent (underlying) device type */
if (subsystem_type == DRM_BUS_VIRTIO) {
/* Assume virtio-pci on error */
if (!realpath(path, real_path))
return DRM_BUS_VIRTIO;
strncat(path, "/..", PATH_MAX);
subsystem_type = get_subsystem_type(path);
if (subsystem_type < 0)
return DRM_BUS_VIRTIO;
}
return subsystem_type;
} }
pub const Device = struct { pub const NodeType = enum {
nodes: [3][]const u8, primary,
available_nodes: packed struct(u3) { /// Deprecated
primary: bool = false, control,
/// Deprecated render,
control: bool = false, _max,
render: bool = false,
},
bus_info: union(sys.BusType) {
pci: sys.PciBusInfo,
usb: sys.UsbBusInfo,
platform: sys.PlatformBusInfo,
host1x: sys.Host1xBusInfo,
faux: sys.FauxBusInfo,
},
device_info: union(sys.BusType) {
pci: sys.PciDeviceInfo,
usb: sys.UsbDeviceInfo,
platform: sys.PlatformDeviceInfo,
host1x: sys.Host1xDeviceInfo,
faux: sys.FauxDeviceInfo,
},
pub const GetFromDevIdFlags = packed struct {
get_pci_revision: bool = false,
};
const dev_t = std.posix.dev_t;
pub fn devMajor(dev: dev_t) u32 {
return ((dev & @as(dev_t, 0x00000000000fff00)) >> 8) |
((dev & @as(dev_t, 0xfffff00000000000)) >> 32);
}
pub fn devMinor(dev: dev_t) u32 {
return ((dev & @as(dev_t, 0x00000000000000ff)) >> 0) |
((dev & @as(dev_t, 0x00000ffffff00000)) >> 12);
}
pub fn makeDev(major: u32, minor: u32) dev_t {
return (@as(dev_t, major & 0x00000fff) << 8) |
(@as(dev_t, major & 0xfffff000) << 32) |
(@as(dev_t, minor & 0x000000ff) << 0) |
(@as(dev_t, minor & 0xffffff00) << 12);
}
pub fn getFromDevId(io: std.Io, devid: std.posix.dev_t, flags: GetFromDevIdFlags) !Device {
var local_devices: [256]Device = undefined;
const major = devMajor(devid);
const minor = devMinor(devid);
if (!nodeIsDrm(io, major, minor))
return error.NotDrmDevice;
// subsystem_type = drmParseSubsystemType(maj, min);
// if (subsystem_type < 0)
// return subsystem_type;
// sysdir = opendir(DRM_DIR_NAME);
// if (!sysdir)
// return -errno;
// i = 0;
// while ((dent = readdir(sysdir))) {
// ret = process_device(&d, dent->d_name, subsystem_type, true, flags);
// if (ret)
// continue;
// if (i >= MAX_DRM_NODES) {
// fprintf(stderr, "More than %d drm nodes detected. "
// "Please report a bug - that should not happen.\n"
// "Skipping extra nodes\n", MAX_DRM_NODES);
// break;
// }
// local_devices[i] = d;
// i++;
// }
// node_count = i;
// drmFoldDuplicatedDevices(local_devices, node_count);
// *device = NULL;
// for (i = 0; i < node_count; i++) {
// if (!local_devices[i])
// continue;
// if (drm_device_has_rdev(local_devices[i], find_rdev))
// *device = local_devices[i];
// else
// drmFreeDevice(&local_devices[i]);
// }
// closedir(sysdir);
// if (*device == NULL)
// return -ENODEV;
// return 0;
}
};
pub const Card = struct {
handle: std.Io.File,
pub const OpenError = std.Io.File.OpenError || std.Io.Dir.OpenError;
pub fn open(io: std.Io, path: []const u8) OpenError!Card {
if (std.fs.path.isAbsolute(path))
return Card{ .handle = try std.Io.Dir.openFileAbsolute(io, path, .{ .mode = .read_write }) };
const dir = try std.Io.Dir.openDirAbsolute(io, "/dev/dri", .{});
defer dir.close(io);
return Card{ .handle = try dir.openFile(io, path, .{ .mode = .read_write }) };
}
pub const OpenAutoError = OpenError || std.Io.Dir.Iterator.Error || error{NoDevicesFound};
pub fn openAuto(io: std.Io) OpenAutoError!Card {
const dir = try std.Io.Dir.openDirAbsolute(io, "/dev/dri", .{ .iterate = true });
defer dir.close(io);
var it = dir.iterateAssumeFirstIteration();
while (try it.next(io)) |entry| if (entry.kind == .character_device)
return Card{ .handle = try dir.openFile(io, entry.name, .{ .mode = .read_write }) };
return error.NoDevicesFound;
}
pub fn close(self: Card, io: std.Io) void {
self.handle.close(io);
}
pub fn handleEvents(
self: Card,
io: std.Io,
callback: fn (Card, Event, anytype) anyerror!void,
args: anytype,
) anyerror!void {
var buf: [4096]u8 = undefined;
const read = try self.handle.readStreaming(io, &.{&buf});
if (read == 0) return error.EndOfStream;
var offset: usize = 0;
while (offset < read) {
if (read - offset < @sizeOf(sys.Event)) return error.IncompleteEvent;
const ev: *align(1) const sys.Event = std.mem.bytesAsValue(
sys.Event,
buf[offset..][0..@sizeOf(sys.Event)],
);
offset += ev.length;
const event = Event.parse(ev);
try callback(self, event, args);
}
}
pub fn setClientCapability(self: Card, capability: sys.ClientCapability, value: u64) sys.IoctlError!void {
var set_cap = sys.SetClientCap{
.capability = capability,
.value = value,
};
try sys.ioctl(self.handle.handle, .set_client_cap, &set_cap);
}
pub fn getCapability(self: Card, capability: sys.Capability) sys.IoctlError!u64 {
var get_cap = sys.GetCap{
.capability = capability,
.value = 0,
};
try sys.ioctl(self.handle.handle, .get_cap, &get_cap);
return get_cap.value;
}
pub fn createModesettingPropertyBlob(self: Card, data: []const u8) sys.IoctlError!u32 {
var create = std.mem.zeroInit(sys.mode.CreateBlob, .{
.length = @as(u32, @intCast(data.len)),
.data = @intFromPtr(data.ptr),
});
try sys.ioctl(self.handle.handle, .mode_createpropblob, &create);
return create.blob_id;
}
// BEGIN MODESETTING API
pub const GetResourcesError = sys.IoctlError || error{OutOfMemory};
pub fn getModesettingResourcesAlloc(
self: Card,
gpa: std.mem.Allocator,
) GetResourcesError!ModesettingResources {
while (true) {
var res = std.mem.zeroes(sys.mode.CardRes);
try sys.ioctl(self.handle.handle, .mode_getresources, &res);
var ret = ModesettingResources{
.min_width = res.min_width,
.max_width = res.max_width,
.min_height = res.min_height,
.max_height = res.max_height,
.fbs = &.{},
.crtcs = &.{},
.connectors = &.{},
.encoders = &.{},
};
ret.fbs = try gpa.alloc(u32, res.count_fbs);
errdefer gpa.free(ret.fbs);
res.fb_id_ptr = @intFromPtr(ret.fbs.ptr);
ret.crtcs = try gpa.alloc(u32, res.count_crtcs);
errdefer gpa.free(ret.crtcs);
res.crtc_id_ptr = @intFromPtr(ret.crtcs.ptr);
ret.connectors = try gpa.alloc(u32, res.count_connectors);
errdefer gpa.free(ret.connectors);
res.connector_id_ptr = @intFromPtr(ret.connectors.ptr);
ret.encoders = try gpa.alloc(u32, res.count_encoders);
errdefer gpa.free(ret.encoders);
res.encoder_id_ptr = @intFromPtr(ret.encoders.ptr);
const cached = res;
try sys.ioctl(self.handle.handle, .mode_getresources, &res);
if (cached.count_fbs < res.count_fbs or
cached.count_crtcs < res.count_crtcs or
cached.count_connectors < res.count_connectors or
cached.count_encoders < res.count_encoders)
{
gpa.free(ret.encoders);
gpa.free(ret.connectors);
gpa.free(ret.crtcs);
gpa.free(ret.fbs);
continue;
}
return ret;
}
}
pub fn getModesettingResourcesBuffered(
self: Card,
fbs: []u32,
crtcs: []u32,
connectors: []u32,
encoders: []u32,
) GetResourcesError!ModesettingResources {
var res = sys.mode.CardRes{
.min_width = 0,
.max_width = 0,
.min_height = 0,
.max_height = 0,
.count_fbs = @intCast(fbs.len),
.fb_id_ptr = @intFromPtr(fbs.ptr),
.count_crtcs = @intCast(crtcs.len),
.crtc_id_ptr = @intFromPtr(crtcs.ptr),
.count_connectors = @intCast(connectors.len),
.connector_id_ptr = @intFromPtr(connectors.ptr),
.count_encoders = @intCast(encoders.len),
.encoder_id_ptr = @intFromPtr(encoders.ptr),
};
try sys.ioctl(self.handle.handle, .mode_getresources, &res);
if (res.count_fbs > fbs.len or
res.count_crtcs > crtcs.len or
res.count_connectors > connectors.len or
res.count_encoders > encoders.len)
return error.OutOfMemory;
return ModesettingResources{
.min_width = res.min_width,
.max_width = res.max_width,
.min_height = res.min_height,
.max_height = res.max_height,
.fbs = fbs[0..res.count_fbs],
.crtcs = crtcs[0..res.count_crtcs],
.connectors = connectors[0..res.count_connectors],
.encoders = encoders[0..res.count_encoders],
};
}
pub const GetConnectorError = sys.IoctlError || error{OutOfMemory};
pub fn getConnectorAlloc(self: Card, gpa: std.mem.Allocator, id: u32) GetConnectorError!Connector {
while (true) {
var get_conn = std.mem.zeroInit(sys.mode.GetConnector, .{ .connector_id = id });
try sys.ioctl(self.handle.handle, .mode_getconnector, &get_conn);
const cached = get_conn;
const encoders = try gpa.alloc(u32, get_conn.count_encoders);
errdefer gpa.free(encoders);
const modes = try gpa.alloc(sys.mode.ModeInfo, get_conn.count_modes);
errdefer gpa.free(modes);
const props = try gpa.alloc(u32, get_conn.count_props);
errdefer gpa.free(props);
const prop_values = try gpa.alloc(u64, get_conn.count_props);
errdefer gpa.free(prop_values);
get_conn.encoders_ptr = @intFromPtr(encoders.ptr);
get_conn.modes_ptr = @intFromPtr(modes.ptr);
get_conn.props_ptr = @intFromPtr(props.ptr);
get_conn.prop_values_ptr = @intFromPtr(prop_values.ptr);
try sys.ioctl(self.handle.handle, .mode_getconnector, &get_conn);
if (cached.count_encoders < get_conn.count_encoders or
cached.count_modes < get_conn.count_modes or
cached.count_props < get_conn.count_props)
{
gpa.free(prop_values);
gpa.free(props);
gpa.free(modes);
gpa.free(encoders);
continue;
}
return Connector{
.id = id,
.encoder_id = get_conn.encoder_id,
.connection = @enumFromInt(get_conn.connection),
// Unsure why libdrm does this conversion,
// but we'll follow suite for compatability.
.subpixel = @enumFromInt(get_conn.subpixel + 1),
.encoders = encoders[0..get_conn.count_encoders],
.modes = modes[0..get_conn.count_modes],
.props = props[0..get_conn.count_props],
.prop_values = prop_values[0..get_conn.count_props],
.mm_width = get_conn.mm_width,
.mm_height = get_conn.mm_height,
.type = get_conn.connector_type,
.type_id = get_conn.connector_type_id,
};
}
}
pub fn getConnectorBuffered(
self: Card,
encoders: []u32,
modes: []sys.mode.ModeInfo,
props: []u32,
prop_values: []u64,
id: u32,
) GetConnectorError!Connector {
std.debug.assert(props.len == prop_values.len);
var get_conn = std.mem.zeroInit(sys.mode.GetConnector, .{
.connector_id = id,
.count_encoders = @as(u32, @intCast(encoders.len)),
.encoders_ptr = @intFromPtr(encoders.ptr),
.count_modes = @as(u32, @intCast(modes.len)),
.modes_ptr = @intFromPtr(modes.ptr),
.count_props = @as(u32, @intCast(props.len)),
.props_ptr = @intFromPtr(props.ptr),
.prop_values_ptr = @intFromPtr(prop_values.ptr),
});
try sys.ioctl(self.handle.handle, .mode_getconnector, &get_conn);
if (get_conn.count_encoders > encoders.len or
get_conn.count_modes > modes.len or
get_conn.count_props > props.len)
return error.OutOfMemory;
return Connector{
.id = id,
.encoder_id = get_conn.encoder_id,
.connection = @enumFromInt(get_conn.connection),
.subpixel = @enumFromInt(get_conn.subpixel + 1),
.encoders = encoders[0..get_conn.count_encoders],
.modes = modes[0..get_conn.count_modes],
.props = props[0..get_conn.count_props],
.prop_values = prop_values[0..get_conn.count_props],
.mm_width = get_conn.mm_width,
.mm_height = get_conn.mm_height,
.type = get_conn.connector_type,
.type_id = get_conn.connector_type_id,
};
}
pub fn getEncoder(self: Card, id: u32) sys.IoctlError!Encoder {
var get_encoder = std.mem.zeroInit(sys.mode.GetEncoder, .{ .encoder_id = id });
try sys.ioctl(self.handle.handle, .mode_getencoder, &get_encoder);
return Encoder{
.id = id,
.type = get_encoder.encoder_type,
.crtc_id = get_encoder.crtc_id,
.possible_crtcs = get_encoder.possible_crtcs,
.possible_clones = get_encoder.possible_clones,
};
}
pub const GetCrtcError = sys.IoctlError;
pub fn getCrtc(self: Card, id: u32) GetCrtcError!Crtc {
var crtc = std.mem.zeroInit(sys.mode.Crtc, .{ .crtc_id = id });
try sys.ioctl(self.handle.handle, .mode_getcrtc, &crtc);
return Crtc{
.id = id,
.fb_id = crtc.fb_id,
.x = crtc.x,
.y = crtc.y,
.gamma_size = crtc.gamma_size,
.mode = if (crtc.mode_valid != 0) crtc.mode else null,
};
}
pub fn setCrtc(
self: Card,
id: u32,
fb_id: u32,
x: u32,
y: u32,
connectors: []u32,
mode: ?sys.mode.ModeInfo,
) sys.IoctlError!void {
var crtc = std.mem.zeroInit(sys.mode.Crtc, .{
.crtc_id = id,
.fb_id = fb_id,
.x = x,
.y = y,
.count_connectors = @as(u32, @intCast(connectors.len)),
.set_connectors_ptr = @intFromPtr(connectors.ptr),
});
if (mode) |m| {
crtc.mode_valid = 1;
crtc.mode = m;
}
try sys.ioctl(self.handle.handle, .mode_setcrtc, &crtc);
}
pub fn createDumbBuffer(self: Card, width: u32, height: u32, bpp: u32) sys.IoctlError!DumbBuffer {
var create = std.mem.zeroInit(sys.mode.CreateDumb, .{
.width = width,
.height = height,
.bpp = bpp,
});
try sys.ioctl(self.handle.handle, .mode_create_dumb, &create);
return DumbBuffer{
.handle = create.handle,
.width = create.width,
.height = create.height,
.stride = create.pitch,
.size = @intCast(create.size),
};
}
pub fn destroyDumbBuffer(self: Card, handle: u32) sys.IoctlError!void {
var destroy = sys.mode.DestroyDumb{ .handle = handle };
try sys.ioctl(self.handle.handle, .mode_destroy_dumb, &destroy);
}
pub fn mapDumbBuffer(self: Card, dumb: DumbBuffer, offset: usize) sys.IoctlError!usize {
var map = sys.mode.MapDumb{ .handle = dumb.handle, .pad = 0, .offset = offset };
try sys.ioctl(self.handle.handle, .mode_map_dumb, &map);
return @intCast(map.offset);
}
pub fn addFb2(
self: Card,
width: u32,
height: u32,
format: fmt.Format,
handles: [4]u32,
pitches: [4]u32,
offsets: [4]u32,
flags: u32, // FIXME: add flags type
) sys.IoctlError!u32 {
var cmd = std.mem.zeroInit(sys.mode.FbCmd2, .{
.width = width,
.height = height,
.pixel_format = @intFromEnum(format),
.handles = handles,
.pitches = pitches,
.offsets = offsets,
.flags = flags,
});
try sys.ioctl(self.handle.handle, .mode_addfb2, &cmd);
return cmd.fb_id;
}
pub fn removeFb(self: Card, fb: u32) sys.IoctlError!void {
try sys.ioctl(self.handle.handle, .mode_rmfb, &fb);
}
pub fn pageFlip(
self: Card,
crtc_id: u32,
fb_id: u32,
flags: sys.mode.PageFlipFlags,
data: ?*anyopaque,
) sys.IoctlError!void {
var flip = sys.mode.CrtcPageFlip{
.crtc_id = crtc_id,
.fb_id = fb_id,
.flags = flags,
.reserved = 0,
.user_data = @intFromPtr(data),
};
try sys.ioctl(self.handle.handle, .mode_page_flip, &flip);
}
pub const GetPlaneResourcesError = sys.IoctlError || error{OutOfMemory};
pub fn getPlaneResourcesAlloc(self: Card, gpa: std.mem.Allocator) GetPlaneResourcesError!PlaneResources {
while (true) {
var res = std.mem.zeroes(sys.mode.GetPlaneRes);
try sys.ioctl(self.handle.handle, .mode_getplaneresources, &res);
const cached = res;
const planes = try gpa.alloc(u32, res.count_planes);
errdefer gpa.free(planes);
res.plane_id_ptr = @intFromPtr(planes.ptr);
try sys.ioctl(self.handle.handle, .mode_getplaneresources, &res);
if (res.count_planes > cached.count_planes) {
gpa.free(planes);
continue;
}
return PlaneResources{ .planes = planes };
}
}
pub fn getPlaneResourcesBuffered(self: Card, plane_buf: []u32) GetPlaneResourcesError!PlaneResources {
var res = sys.mode.GetPlaneRes{
.count_planes = plane_buf.len,
.plane_id_ptr = @intFromPtr(plane_buf.ptr),
};
try sys.ioctl(self.handle.handle, .mode_getplaneresources, &res);
if (res.count_planes > plane_buf.len) return error.OutOfMemory;
return PlaneResources{ .planes = plane_buf[0..res.count_planes] };
}
pub const GetPlaneError = sys.IoctlError || error{OutOfMemory};
pub fn getPlaneAlloc(self: Card, gpa: std.mem.Allocator, id: u32) GetPlaneError!Plane {
while (true) {
var get = std.mem.zeroInit(sys.mode.GetPlane, .{ .plane_id = id });
try sys.ioctl(self.handle.handle, .mode_getplane, &get);
const cached = get;
const formats = try gpa.alloc(u32, get.count_format_types);
errdefer gpa.free(formats);
get.format_type_ptr = @intFromPtr(formats.ptr);
try sys.ioctl(self.handle.handle, .mode_getplane, &get);
if (get.count_format_types > cached.count_format_types) {
gpa.free(formats);
continue;
}
return Plane{
.id = id,
.formats = formats,
.crtc_id = get.crtc_id,
.fb_id = get.fb_id,
.crtc_x = 0,
.crtc_y = 0,
.x = 0,
.y = 0,
.possible_crtcs = get.possible_crtcs,
.gamma_size = get.gamma_size,
};
}
}
pub fn getPlaneBuffered(self: Card, format_buf: []u32, id: u32) GetPlaneError!Plane {
var get = std.mem.zeroInit(sys.mode.GetPlane, .{
.plane_id = id,
.count_format_types = format_buf.len,
.format_types_ptr = @intFromPtr(format_buf.ptr),
});
try sys.ioctl(self.handle.handle, .mode_getplane, &get);
if (get.count_format_types > format_buf.len) return error.OutOfMemory;
return Plane{
.id = id,
.formats = format_buf[0..get.count_format_types],
.crtc_id = get.crtc_id,
.fb_id = get.fb_id,
.crtc_x = 0,
.crtc_y = 0,
.x = 0,
.y = 0,
.possible_crtcs = get.possible_crtcs,
.gamma_size = get.gamma_size,
};
}
pub const GetObjectPropertiesError = sys.IoctlError || error{OutOfMemory};
pub fn getObjectPropertiesAlloc(
self: Card,
gpa: std.mem.Allocator,
object_id: u32,
object_type: sys.mode.ObjType,
) GetObjectPropertiesError!ObjectProperties {
while (true) {
var get = std.mem.zeroInit(sys.mode.ObjGetProperties, .{
.obj_id = object_id,
.obj_type = object_type,
});
try sys.ioctl(self.handle.handle, .mode_obj_getproperties, &get);
const cached = get;
const keys = try gpa.alloc(u32, get.count_props);
errdefer gpa.free(keys);
get.props_ptr = keys.ptr;
const values = try gpa.alloc(u64, get.count_props);
errdefer gpa.free(values);
get.prop_values_ptr = values.ptr;
try sys.ioctl(self.handle.handle, .mode_obj_getproperties, &get);
if (get.count_props > cached.count_props) {
gpa.free(keys);
gpa.free(values);
continue;
}
return ObjectProperties{
.keys = keys,
.values = values,
};
}
}
pub fn getObjectPropertiesBuffered(
self: Card,
key_buf: []u32,
value_buf: []u64,
object_id: u32,
object_type: sys.mode.ObjType,
) GetObjectPropertiesError!ObjectProperties {
var get = sys.mode.ObjGetProperties{
.obj_id = object_id,
.obj_type = object_type,
.count_props = @intCast(@min(key_buf.len, value_buf.len)),
.props_ptr = @intFromEnum(key_buf.ptr),
.prop_values_ptr = @intFromEnum(value_buf.ptr),
};
try sys.ioctl(self.handle.handle, .mode_obj_getproperties, &get);
if (get.count_props > @min(key_buf.len, value_buf.len)) return error.OutOfMemory;
return ObjectProperties{
.keys = key_buf[0..get.count_props],
.values = value_buf[0..get.count_props],
};
}
}; };
pub const Event = union(enum) { pub const Event = union(enum) {
-11
View File
@@ -334,17 +334,6 @@ pub const EventCrtcSequence = extern struct {
sequence: u64, sequence: u64,
}; };
pub const BusType = enum(u32) {
pci = 0,
usb = 1,
platform = 2,
host1x = 3,
faux = 4,
virtio = 0x10,
};
pub const PciBusInfo = extern struct {};
pub const mode = struct { pub const mode = struct {
pub const display_mode_len = 32; pub const display_mode_len = 32;
pub const prop_name_len = 32; pub const prop_name_len = 32;